├── .changeset
├── README.md
└── config.json
├── .eslintrc.cjs
├── .github
└── workflows
│ └── release.yml
├── .gitignore
├── .npmrc
├── .prettierrc
├── .turbo
├── cookies
│ └── 1.cookie
└── daemon
│ └── 03e8599f0c2f549a-turbo.log.2024-10-25
├── .vscode
└── settings.json
├── CHANGELOG.md
├── README.md
├── bin.js
├── examples
└── zero-survey
│ ├── .cursorrules
│ ├── .eslintignore
│ ├── .eslintrc.cjs
│ ├── .gitignore
│ ├── .gitignore.txt
│ ├── .node-version
│ ├── .npmrc
│ ├── .prettierignore
│ ├── .prettierrc
│ ├── drizzle.config.ts
│ ├── drizzle
│ ├── 0000_grey_moonstone.sql
│ ├── 0001_petite_roland_deschain.sql
│ ├── 0002_happy_boomer.sql
│ ├── 0003_big_flatman.sql
│ ├── 0004_narrow_korvac.sql
│ ├── 0005_sour_warhawk.sql
│ ├── 0006_remarkable_quentin_quire.sql
│ ├── 0007_typical_mandarin.sql
│ ├── 0008_lethal_swordsman.sql
│ └── meta
│ │ ├── 0000_snapshot.json
│ │ ├── 0001_snapshot.json
│ │ ├── 0002_snapshot.json
│ │ ├── 0003_snapshot.json
│ │ ├── 0004_snapshot.json
│ │ ├── 0005_snapshot.json
│ │ ├── 0006_snapshot.json
│ │ ├── 0007_snapshot.json
│ │ ├── 0008_snapshot.json
│ │ └── _journal.json
│ ├── drop-in.config.js
│ ├── example.env
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ ├── app.d.ts
│ ├── app.html
│ ├── hooks.server.ts
│ ├── lib
│ │ ├── auth.svelte.ts
│ │ ├── auth
│ │ │ ├── ForgotPassword.svelte
│ │ │ ├── Login.svelte
│ │ │ ├── NewEmail.svelte
│ │ │ ├── Signup.svelte
│ │ │ ├── Verify.svelte
│ │ │ └── auth_form.svelte.ts
│ │ ├── components
│ │ │ └── UserMenu.svelte
│ │ ├── data
│ │ │ ├── db.ts
│ │ │ └── db_schema.ts
│ │ ├── email.ts
│ │ ├── new-query.svelte.ts
│ │ ├── shared
│ │ │ ├── abort-error.ts
│ │ │ ├── arrays.test.ts
│ │ │ ├── arrays.ts
│ │ │ ├── asserts.ts
│ │ │ ├── base62.test.ts
│ │ │ ├── base62.ts
│ │ │ ├── browser-env.ts
│ │ │ ├── buffer-sizer.test.ts
│ │ │ ├── buffer-sizer.ts
│ │ │ ├── build.d.ts
│ │ │ ├── build.js
│ │ │ ├── config.ts
│ │ │ ├── custom-key-map.ts
│ │ │ ├── custom-key-set.ts
│ │ │ ├── deep-clone.test.ts
│ │ │ ├── deep-clone.ts
│ │ │ ├── document-visible.test.ts
│ │ │ ├── document-visible.ts
│ │ │ ├── events
│ │ │ │ └── connection-seconds.ts
│ │ │ ├── expand.ts
│ │ │ ├── fetch-mocker.ts
│ │ │ ├── float-to-ordered-string.test.ts
│ │ │ ├── float-to-ordered-string.ts
│ │ │ ├── h64-with-reverse.ts
│ │ │ ├── has-own.ts
│ │ │ ├── headers.test.ts
│ │ │ ├── headers.ts
│ │ │ ├── immutable.ts
│ │ │ ├── iterables.test.ts
│ │ │ ├── iterables.ts
│ │ │ ├── json-schema.test.ts
│ │ │ ├── json-schema.ts
│ │ │ ├── json.test.ts
│ │ │ ├── json.ts
│ │ │ ├── logging-test-utils.ts
│ │ │ ├── mirror
│ │ │ │ ├── is-supported-semver-range.test.ts
│ │ │ │ └── is-supported-semver-range.ts
│ │ │ ├── mod.ts
│ │ │ ├── must.ts
│ │ │ ├── navigator.ts
│ │ │ ├── options.test.ts
│ │ │ ├── options.ts
│ │ │ ├── parse-big-int.test.ts
│ │ │ ├── parse-big-int.ts
│ │ │ ├── queue.test.ts
│ │ │ ├── queue.ts
│ │ │ ├── rand.ts
│ │ │ ├── random-uint64.ts
│ │ │ ├── random-values.ts
│ │ │ ├── read-json-file.js
│ │ │ ├── resolved-promises.ts
│ │ │ ├── reverse-string.ts
│ │ │ ├── set-util.test.ts
│ │ │ ├── set-utils.ts
│ │ │ ├── sleep.test.ts
│ │ │ ├── sleep.ts
│ │ │ ├── sorted-entries.ts
│ │ │ ├── string-compare.ts
│ │ │ ├── timed.ts
│ │ │ ├── tool
│ │ │ │ ├── get-external-from-package-json.js
│ │ │ │ ├── inject-require.js
│ │ │ │ ├── internal-packages.js
│ │ │ │ └── vitest-config.ts
│ │ │ ├── types.ts
│ │ │ ├── valita.test.ts
│ │ │ ├── valita.ts
│ │ │ ├── writable.ts
│ │ │ └── xxhash.ts
│ │ ├── svelte-view.svelte.ts
│ │ ├── types.ts
│ │ └── z.svelte.ts
│ ├── routes
│ │ ├── (app)
│ │ │ ├── +layout.svelte
│ │ │ ├── +layout.ts
│ │ │ ├── Footer.svelte
│ │ │ ├── Header.svelte
│ │ │ ├── README.md
│ │ │ ├── dashboard
│ │ │ │ └── +page.svelte
│ │ │ ├── profile
│ │ │ │ └── +page.svelte
│ │ │ └── surveys
│ │ │ │ └── [id]
│ │ │ │ ├── +layout.svelte
│ │ │ │ ├── +page.svelte
│ │ │ │ └── edit
│ │ │ │ ├── +page.svelte
│ │ │ │ └── EditRow.svelte
│ │ ├── (site)
│ │ │ ├── +layout.svelte
│ │ │ ├── +page.svelte
│ │ │ ├── README.md
│ │ │ ├── auth
│ │ │ │ ├── +layout.svelte
│ │ │ │ ├── confirm-email-change
│ │ │ │ │ └── +page.svelte
│ │ │ │ ├── confirm-password-reset
│ │ │ │ │ └── [token]
│ │ │ │ │ │ └── +page.svelte
│ │ │ │ ├── confirm-verification
│ │ │ │ │ └── +page.svelte
│ │ │ │ ├── forgot-password
│ │ │ │ │ └── +page.svelte
│ │ │ │ ├── login
│ │ │ │ │ └── +page.svelte
│ │ │ │ └── signup
│ │ │ │ │ └── +page.svelte
│ │ │ └── survey
│ │ │ │ └── [id]
│ │ │ │ ├── +page.svelte
│ │ │ │ └── [response_id]
│ │ │ │ ├── +page.svelte
│ │ │ │ ├── Answer.svelte
│ │ │ │ ├── LongText.svelte
│ │ │ │ ├── Rating.svelte
│ │ │ │ └── Text.svelte
│ │ ├── +layout.server.ts
│ │ ├── +layout.svelte
│ │ ├── UserWrapper.svelte
│ │ └── global.css
│ ├── schema.ts
│ ├── state
│ │ ├── README.md
│ │ └── app.svelte.ts
│ └── utils
│ │ ├── app_guard.ts
│ │ └── auth_guard.ts
│ ├── svelte.config.js
│ ├── tsconfig.json
│ ├── vite.config.ts
│ └── zero-schema.json
├── jsconfig.json
├── package.json
├── packages
├── beeper
│ ├── package.json
│ ├── src
│ │ ├── app.d.ts
│ │ ├── beeper.ts
│ │ └── index.ts
│ └── tsconfig.json
├── decks
│ ├── .gitignore
│ ├── .npmrc
│ ├── .prettierignore
│ ├── .prettierrc
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── eslint.config.js
│ ├── package.json
│ ├── src
│ │ ├── app.d.ts
│ │ ├── app.html
│ │ ├── lib
│ │ │ ├── Accordion.svelte
│ │ │ ├── AreYouSure.svelte
│ │ │ ├── Dialog.svelte
│ │ │ ├── Drawer.svelte
│ │ │ ├── Menu.svelte
│ │ │ ├── Pill.svelte
│ │ │ ├── Pills.svelte
│ │ │ ├── Share.svelte
│ │ │ ├── animated-details.ts
│ │ │ ├── drawer.ts
│ │ │ ├── index.ts
│ │ │ ├── local
│ │ │ │ └── Docs.svelte
│ │ │ ├── pannable.ts
│ │ │ └── toast
│ │ │ │ ├── Toast.svelte
│ │ │ │ ├── ToastSlice.svelte
│ │ │ │ ├── index.ts
│ │ │ │ └── toaster.svelte.ts
│ │ └── routes
│ │ │ └── +page.svelte
│ ├── static
│ │ └── favicon.png
│ ├── svelte.config.js
│ ├── tsconfig.json
│ └── vite.config.ts
├── docs
│ ├── .gitignore
│ ├── .vscode
│ │ ├── extensions.json
│ │ └── launch.json
│ ├── README.md
│ ├── astro.config.mjs
│ ├── package.json
│ ├── public
│ │ └── favicon.svg
│ ├── src
│ │ ├── assets
│ │ │ ├── drop-in-light.svg
│ │ │ └── drop-in.svg
│ │ ├── content.config.ts
│ │ ├── content
│ │ │ └── docs
│ │ │ │ ├── beeper
│ │ │ │ └── reference.md
│ │ │ │ ├── guides
│ │ │ │ └── get-started.mdx
│ │ │ │ ├── index.mdx
│ │ │ │ ├── pass
│ │ │ │ └── reference.md
│ │ │ │ ├── plugin
│ │ │ │ └── reference.md
│ │ │ │ └── ramps
│ │ │ │ └── reference.md
│ │ └── styles
│ │ │ └── custom.css
│ └── tsconfig.json
├── graffiti
│ ├── README.md
│ ├── bin.js
│ ├── build.js
│ ├── drop-in.css
│ ├── index.html
│ ├── package-lock.json
│ ├── package.json
│ └── raw.js
├── pass
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── package.json
│ ├── src
│ │ ├── app.d.ts
│ │ ├── authenticate.ts
│ │ ├── client
│ │ │ ├── api_calls.ts
│ │ │ └── index.ts
│ │ ├── client_jwt.ts
│ │ ├── cookies.ts
│ │ ├── db.ts
│ │ ├── email.ts
│ │ ├── find_user.ts
│ │ ├── index.ts
│ │ ├── jwt.ts
│ │ ├── login.ts
│ │ ├── logout.ts
│ │ ├── password.ts
│ │ ├── routes.ts
│ │ ├── schema.ts
│ │ ├── sign_up.ts
│ │ ├── token.ts
│ │ └── utils.ts
│ └── tsconfig.json
├── plugin
│ ├── global.d.ts
│ ├── package.json
│ ├── src
│ │ ├── hook.ts
│ │ └── index.ts
│ └── tsconfig.json
└── ramps
│ ├── .gitignore
│ ├── .npmrc
│ ├── .prettierignore
│ ├── .prettierrc
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── eslint.config.js
│ ├── package.json
│ ├── src
│ ├── app.d.ts
│ ├── app.html
│ ├── lib
│ │ ├── auth
│ │ │ ├── Login.svelte
│ │ │ ├── Signup.svelte
│ │ │ ├── Verify.svelte
│ │ │ └── auth_form.svelte.ts
│ │ └── index.ts
│ └── routes
│ │ └── +page.svelte
│ ├── static
│ └── favicon.png
│ ├── svelte.config.js
│ ├── tsconfig.json
│ └── vite.config.ts
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── templates
└── z
│ ├── .cursorrules
│ ├── .eslintignore
│ ├── .eslintrc.cjs
│ ├── .gitignore
│ ├── .gitignore.txt
│ ├── .meta.json
│ ├── .node-version
│ ├── .npmrc
│ ├── .prettierignore
│ ├── .prettierrc
│ ├── CHANGELOG.md
│ ├── docker
│ └── docker-compose.yml
│ ├── drizzle.config.ts
│ ├── drizzle
│ ├── 0000_grey_moonstone.sql
│ ├── 0001_petite_roland_deschain.sql
│ ├── 0002_happy_boomer.sql
│ ├── 0003_big_flatman.sql
│ └── meta
│ │ ├── 0000_snapshot.json
│ │ ├── 0001_snapshot.json
│ │ ├── 0002_snapshot.json
│ │ ├── 0003_snapshot.json
│ │ └── _journal.json
│ ├── drop-in.config.js
│ ├── example.env
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ ├── app.d.ts
│ ├── app.html
│ ├── db_schema.ts
│ ├── hooks.server.ts
│ ├── lib
│ │ ├── auth
│ │ │ ├── ForgotPassword.svelte
│ │ │ ├── NewEmail.svelte
│ │ │ └── auth_form.svelte.ts
│ │ ├── components
│ │ │ └── UserMenu.svelte
│ │ ├── data
│ │ │ └── db.ts
│ │ ├── email.ts
│ │ ├── queries.ts
│ │ └── z.svelte.ts
│ ├── routes
│ │ ├── (app)
│ │ │ ├── +layout.svelte
│ │ │ ├── +layout.ts
│ │ │ ├── Footer.svelte
│ │ │ ├── Header.svelte
│ │ │ ├── README.md
│ │ │ ├── dashboard
│ │ │ │ └── +page.svelte
│ │ │ └── profile
│ │ │ │ └── +page.svelte
│ │ ├── (site)
│ │ │ ├── +layout.server.ts
│ │ │ ├── +layout.svelte
│ │ │ ├── +page.svelte
│ │ │ ├── README.md
│ │ │ └── auth
│ │ │ │ ├── confirm-email-change
│ │ │ │ └── +page.svelte
│ │ │ │ ├── confirm-password-reset
│ │ │ │ └── [token]
│ │ │ │ │ └── +page.svelte
│ │ │ │ ├── forgot-password
│ │ │ │ └── +page.svelte
│ │ │ │ ├── login
│ │ │ │ └── +page.svelte
│ │ │ │ ├── signup
│ │ │ │ └── +page.svelte
│ │ │ │ └── verify-email
│ │ │ │ └── +page.svelte
│ │ ├── +layout.server.ts
│ │ └── +layout.svelte
│ ├── schema.ts
│ ├── state
│ │ ├── README.md
│ │ └── app.svelte.ts
│ └── utils
│ │ ├── app_guard.ts
│ │ └── auth_guard.ts
│ ├── svelte.config.js
│ ├── tsconfig.json
│ └── vite.config.ts
└── turbo.json
/.changeset/README.md:
--------------------------------------------------------------------------------
1 | # Changesets
2 |
3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
4 | with multi-package repos, or single-package repos to help you version and publish your code. You can
5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets)
6 |
7 | We have a quick list of common questions to get you started engaging with this project in
8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
9 |
--------------------------------------------------------------------------------
/.changeset/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json",
3 | "changelog": "@changesets/cli/changelog",
4 | "commit": false,
5 | "fixed": [],
6 | "linked": [],
7 | "access": "restricted",
8 | "baseBranch": "main",
9 | "updateInternalDependencies": "patch",
10 | "ignore": []
11 | }
12 |
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | /** @type { import("eslint").Linter.Config } */
2 | module.exports = {
3 | root: true,
4 | extends: [
5 | 'eslint:recommended',
6 | 'plugin:@typescript-eslint/recommended',
7 | 'plugin:svelte/recommended',
8 | 'prettier',
9 | ],
10 | parser: '@typescript-eslint/parser',
11 | plugins: ['@typescript-eslint'],
12 | parserOptions: {
13 | sourceType: 'module',
14 | ecmaVersion: 2020,
15 | extraFileExtensions: ['.svelte'],
16 | },
17 | env: {
18 | browser: true,
19 | es2017: true,
20 | node: true,
21 | },
22 | overrides: [
23 | {
24 | files: ['*.svelte'],
25 | parser: 'svelte-eslint-parser',
26 | parserOptions: {
27 | parser: '@typescript-eslint/parser',
28 | },
29 | },
30 | ],
31 | };
32 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | push:
5 | branches:
6 | - main # Or your primary branch
7 |
8 | concurrency: ${{ github.workflow }}-${{ github.ref }}
9 |
10 | jobs:
11 | release:
12 | name: Release
13 | runs-on: ubuntu-latest
14 | steps:
15 | - name: Checkout Repo
16 | uses: actions/checkout@v4
17 | # Fetch all history so Changesets can determine changed packages
18 | with:
19 | fetch-depth: 0
20 |
21 | - name: Setup PNPM
22 | uses: pnpm/action-setup@v4
23 |
24 | - name: Setup Node.js
25 | uses: actions/setup-node@v4
26 | with:
27 | node-version: 20 # Use your project's node version
28 | cache: 'pnpm'
29 |
30 | - name: Install Dependencies
31 | run: pnpm install --frozen-lockfile
32 |
33 | # Optional: Add build step if needed before versioning/publishing
34 | # - name: Build Packages
35 | # run: pnpm turbo build --filter='!./apps/*' # Example: Build all packages except those in apps/
36 |
37 | - name: Create Release Pull Request or Publish to npm
38 | id: changesets
39 | uses: changesets/action@v1
40 | with:
41 | # This command will create a PR if changesets are present and run publish if on main/master
42 | # It automatically bumps versions, commits, tags, and publishes
43 | publish: pnpm publish -r --no-git-checks
44 | env:
45 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
46 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} # Needs to be configured in repo secrets
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | /build
3 | .svelte-kit
4 | **/.svelte-kit
5 | /package
6 | /dist
7 | .env
8 | .env.*
9 | !.env.example
10 |
11 | .DS_STORE
12 | **/dist/
13 | **/build/
14 | *.tgz
15 |
16 | .turbo
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | ignore-workspace-root-check=true
2 | engine-strict=true
3 | link-workspace-packages=true
4 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 100,
3 | "singleQuote": true,
4 | "useTabs": true
5 | }
6 |
--------------------------------------------------------------------------------
/.turbo/cookies/1.cookie:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stolinski/drop-in/bbeff69a9b5568ed49517b3936cba67a8ae0f1cc/.turbo/cookies/1.cookie
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "cSpell.words": ["pocketbase"]
3 | }
4 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # @drop-in/new
2 |
3 | ## 0.1.0
4 |
5 | ### Minor Changes
6 |
7 | - 730a653: Removes workspace from copied template
8 |
--------------------------------------------------------------------------------
/examples/zero-survey/.eslintignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /.svelte-kit
5 | /package
6 | .env
7 | .env.*
8 | !.env.example
9 |
10 | # Ignore files for PNPM, NPM and YARN
11 | pnpm-lock.yaml
12 | package-lock.json
13 | yarn.lock
14 |
--------------------------------------------------------------------------------
/examples/zero-survey/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | /** @type { import("eslint").Linter.Config } */
2 | module.exports = {
3 | root: true,
4 | extends: [
5 | 'eslint:recommended',
6 | 'plugin:@typescript-eslint/recommended',
7 | 'plugin:svelte/recommended',
8 | 'prettier',
9 | ],
10 | parser: '@typescript-eslint/parser',
11 | plugins: ['@typescript-eslint'],
12 | parserOptions: {
13 | sourceType: 'module',
14 | ecmaVersion: 2020,
15 | extraFileExtensions: ['.svelte'],
16 | },
17 | env: {
18 | browser: true,
19 | es2017: true,
20 | node: true,
21 | },
22 | overrides: [
23 | {
24 | files: ['*.svelte'],
25 | parser: 'svelte-eslint-parser',
26 | parserOptions: {
27 | parser: '@typescript-eslint/parser',
28 | },
29 | },
30 | ],
31 | };
32 |
--------------------------------------------------------------------------------
/examples/zero-survey/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | /build
3 | /.svelte-kit
4 | /package
5 | .env
6 | .env.*
7 | !.env.example
8 |
9 | .DS_Store
--------------------------------------------------------------------------------
/examples/zero-survey/.gitignore.txt:
--------------------------------------------------------------------------------
1 | node_modules
2 | /build
3 | /.svelte-kit
4 | /package
5 | .env
6 | .env.*
7 | !.env.example
8 |
--------------------------------------------------------------------------------
/examples/zero-survey/.node-version:
--------------------------------------------------------------------------------
1 | 17.0.1
--------------------------------------------------------------------------------
/examples/zero-survey/.npmrc:
--------------------------------------------------------------------------------
1 | engine-strict=true
2 | link-workspace-packages=true
3 |
--------------------------------------------------------------------------------
/examples/zero-survey/.prettierignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /.svelte-kit
5 | /package
6 | .env
7 | .env.*
8 | !.env.example
9 |
10 | # Ignore files for PNPM, NPM and YARN
11 | pnpm-lock.yaml
12 | package-lock.json
13 | yarn.lock
14 |
--------------------------------------------------------------------------------
/examples/zero-survey/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "ignoreFiles": ["package.json"],
3 | "overrides": [
4 | {
5 | "files": "*.svelte",
6 | "options": {
7 | "parser": "svelte"
8 | }
9 | }
10 | ],
11 | "plugins": ["prettier-plugin-svelte"],
12 | "printWidth": 100,
13 | "singleQuote": true,
14 | "trailingComma": "none",
15 | "useTabs": true
16 | }
17 |
--------------------------------------------------------------------------------
/examples/zero-survey/drizzle.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'drizzle-kit';
2 |
3 | export default defineConfig({
4 | schema: './src/lib/data/db_schema.ts',
5 | dialect: 'postgresql',
6 | dbCredentials: {
7 | url: process.env.DATABASE_URL,
8 | },
9 | });
10 |
--------------------------------------------------------------------------------
/examples/zero-survey/drizzle/0000_grey_moonstone.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE IF NOT EXISTS "profile" (
2 | "id" varchar PRIMARY KEY NOT NULL,
3 | "user_id" varchar NOT NULL
4 | );
5 | --> statement-breakpoint
6 | CREATE TABLE IF NOT EXISTS "refresh_token" (
7 | "id" varchar PRIMARY KEY NOT NULL,
8 | "user_id" varchar NOT NULL,
9 | "token" varchar(255) NOT NULL,
10 | "created_at" timestamp DEFAULT now() NOT NULL,
11 | "expires_at" timestamp NOT NULL
12 | );
13 | --> statement-breakpoint
14 | CREATE TABLE IF NOT EXISTS "user" (
15 | "id" varchar PRIMARY KEY NOT NULL,
16 | "email" varchar(255) NOT NULL,
17 | "password_hash" varchar(255) NOT NULL,
18 | "created_at" timestamp DEFAULT now() NOT NULL,
19 | "updated_at" timestamp DEFAULT now() NOT NULL,
20 | "verified" boolean DEFAULT false NOT NULL,
21 | "verification_token" varchar(255),
22 | CONSTRAINT "user_email_unique" UNIQUE("email")
23 | );
24 | --> statement-breakpoint
25 | DO $$ BEGIN
26 | ALTER TABLE "profile" ADD CONSTRAINT "profile_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
27 | EXCEPTION
28 | WHEN duplicate_object THEN null;
29 | END $$;
30 | --> statement-breakpoint
31 | DO $$ BEGIN
32 | ALTER TABLE "refresh_token" ADD CONSTRAINT "refresh_token_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
33 | EXCEPTION
34 | WHEN duplicate_object THEN null;
35 | END $$;
36 |
--------------------------------------------------------------------------------
/examples/zero-survey/drizzle/0001_petite_roland_deschain.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE "profile" ADD COLUMN "username" varchar NOT NULL;
--------------------------------------------------------------------------------
/examples/zero-survey/drizzle/0002_happy_boomer.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE "profile" RENAME COLUMN "username" TO "name";
--------------------------------------------------------------------------------
/examples/zero-survey/drizzle/0003_big_flatman.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE "profile" ADD COLUMN "avatar" varchar;
--------------------------------------------------------------------------------
/examples/zero-survey/drizzle/0005_sour_warhawk.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE "questions" ADD COLUMN "description" text;
--------------------------------------------------------------------------------
/examples/zero-survey/drizzle/0006_remarkable_quentin_quire.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE "questions" RENAME COLUMN "order_num" TO "order";--> statement-breakpoint
2 | ALTER TABLE "questions" ADD COLUMN "config" jsonb DEFAULT '{"required":false}'::jsonb NOT NULL;
--------------------------------------------------------------------------------
/examples/zero-survey/drizzle/0007_typical_mandarin.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE "user" DROP COLUMN "created_at";--> statement-breakpoint
2 | ALTER TABLE "user" DROP COLUMN "updated_at";
--------------------------------------------------------------------------------
/examples/zero-survey/drizzle/0008_lethal_swordsman.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE "user" ADD COLUMN "created_at" timestamp DEFAULT now() NOT NULL;--> statement-breakpoint
2 | ALTER TABLE "user" ADD COLUMN "updated_at" timestamp DEFAULT now() NOT NULL;
--------------------------------------------------------------------------------
/examples/zero-survey/drizzle/meta/_journal.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "7",
3 | "dialect": "postgresql",
4 | "entries": [
5 | {
6 | "idx": 0,
7 | "version": "7",
8 | "when": 1729802614860,
9 | "tag": "0000_grey_moonstone",
10 | "breakpoints": true
11 | },
12 | {
13 | "idx": 1,
14 | "version": "7",
15 | "when": 1729875764211,
16 | "tag": "0001_petite_roland_deschain",
17 | "breakpoints": true
18 | },
19 | {
20 | "idx": 2,
21 | "version": "7",
22 | "when": 1729881836443,
23 | "tag": "0002_happy_boomer",
24 | "breakpoints": true
25 | },
26 | {
27 | "idx": 3,
28 | "version": "7",
29 | "when": 1731085971419,
30 | "tag": "0003_big_flatman",
31 | "breakpoints": true
32 | },
33 | {
34 | "idx": 4,
35 | "version": "7",
36 | "when": 1731699278991,
37 | "tag": "0004_narrow_korvac",
38 | "breakpoints": true
39 | },
40 | {
41 | "idx": 5,
42 | "version": "7",
43 | "when": 1732899862162,
44 | "tag": "0005_sour_warhawk",
45 | "breakpoints": true
46 | },
47 | {
48 | "idx": 6,
49 | "version": "7",
50 | "when": 1733522441302,
51 | "tag": "0006_remarkable_quentin_quire",
52 | "breakpoints": true
53 | },
54 | {
55 | "idx": 7,
56 | "version": "7",
57 | "when": 1733832289748,
58 | "tag": "0007_typical_mandarin",
59 | "breakpoints": true
60 | },
61 | {
62 | "idx": 8,
63 | "version": "7",
64 | "when": 1733848339041,
65 | "tag": "0008_lethal_swordsman",
66 | "breakpoints": true
67 | }
68 | ]
69 | }
--------------------------------------------------------------------------------
/examples/zero-survey/drop-in.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | email: {
3 | from: 'fake@changeme.com'
4 | },
5 | db: {
6 | url: process.env.DATABASE_URL
7 | },
8 | app: {
9 | public: {
10 | url: 'http://localhost:5173',
11 | name: 'Drop In',
12 | route: '/dashboard'
13 | }
14 | }
15 | };
16 |
--------------------------------------------------------------------------------
/examples/zero-survey/example.env:
--------------------------------------------------------------------------------
1 | # Required
2 | PUBLIC_PB_URL=
3 |
4 | # Optional
5 | # For `types` script
6 | PB_TYPEGEN_URL=
7 | PB_TYPEGEN_EMAIL=
8 | PB_TYPEGEN_PASSWORD=
9 |
--------------------------------------------------------------------------------
/examples/zero-survey/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "@drop-in/beeper": "workspace:^",
4 | "@drop-in/decks": "workspace:^",
5 | "@drop-in/graffiti": "^0.3.4",
6 | "@drop-in/pass": "workspace:^",
7 | "@drop-in/plugin": "workspace:^",
8 | "@rocicorp/zero": "^0.18.2025042300",
9 | "drizzle-orm": "^0.43.1",
10 | "nanoid": "^5.1.5",
11 | "pg": "^8.15.6",
12 | "zero-svelte": "^0.3.3"
13 | },
14 | "devDependencies": {
15 | "@sveltejs/adapter-auto": "^6.0.0",
16 | "@sveltejs/kit": "^2.20.7",
17 | "@sveltejs/vite-plugin-svelte": "^5.0.3",
18 | "@types/pg": "^8.11.14",
19 | "@typescript-eslint/eslint-plugin": "^8.31.0",
20 | "@typescript-eslint/parser": "^8.31.0",
21 | "drizzle-kit": "^0.31.0",
22 | "prettier": "^3.5.3",
23 | "prettier-plugin-svelte": "^3.3.3",
24 | "svelte": "^5.28.2",
25 | "svelte-check": "^4.1.6",
26 | "svelte-preprocess": "^6.0.2",
27 | "tslib": "^2.8.1",
28 | "typescript": "^5.8.3",
29 | "vite": "^6.3.3"
30 | },
31 | "engines": {
32 | "node": ">20.11.1"
33 | },
34 | "name": "dropin-survey",
35 | "scripts": {
36 | "build": "vite build",
37 | "check": "svelte-check --tsconfig ./tsconfig.json",
38 | "check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
39 | "dev": "vite dev",
40 | "format": "prettier --write --plugin-search-dir=. .",
41 | "lint": "prettier --check --plugin-search-dir=. . && eslint .",
42 | "make-package": "node ./src/packages/@drop-in/tools/package.js",
43 | "package": "svelte-kit package",
44 | "prepare": "svelte-kit sync",
45 | "preview": "svelte-kit preview",
46 | "zero:server": "npx zero-cache -p src/schema.ts",
47 | "db:change": "drizzle-kit generate; drizzle-kit migrate; pnpm exec zero-build-schema -p 'src/schema.ts'; rm /tmp/zerotest-sync-replica.db*;"
48 | },
49 | "type": "module",
50 | "version": "0.0.4"
51 | }
52 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/app.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | // See https://kit.svelte.dev/docs/types#app
4 | // for information about these interfaces
5 | // and what to do when importing types
6 | declare global {
7 | namespace App {
8 | // interface Locals {}
9 | // interface Platform {}
10 | // interface Session {}
11 | // interface Stuff {}
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
10 |
14 | %sveltekit.head%
15 |
16 |
17 | %sveltekit.body%
18 |
19 |
20 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/hooks.server.ts:
--------------------------------------------------------------------------------
1 | import { sequence } from '@sveltejs/kit/hooks';
2 | import { beeper } from '@drop-in/beeper';
3 | import { pass_routes } from '@drop-in/pass';
4 |
5 | beeper.send({
6 | to: 'test@test.com',
7 | subject: 'Test',
8 | html: 'Test '
9 | });
10 |
11 | export const handle = sequence(pass_routes);
12 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/auth.svelte.ts:
--------------------------------------------------------------------------------
1 | import { get_login } from '@drop-in/pass/client';
2 | import type { User } from '@drop-in/pass/schema';
3 | import type { Profile } from './data/db_schema';
4 |
5 | // This is a way to keep track of the user globally
6 | class Auth {
7 | user: Partial }> = $state({});
8 | auth: { sub: string | undefined; jwt: string | undefined } | null = $state(null);
9 | constructor() {
10 | this.auth = get_login();
11 | }
12 |
13 | set_user(user: User | {}) {
14 | console.log('user', user);
15 | this.user = user;
16 | }
17 | }
18 | export const auth = new Auth();
19 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/auth/ForgotPassword.svelte:
--------------------------------------------------------------------------------
1 |
30 |
31 | Forgot Password
32 | {#if auth.status === 'SUCCESS'}
33 | Please check your email for password reset instructions
34 | {:else}
35 |
46 |
47 | {#if auth.error_message}
48 |
{auth.error_message}
49 | {/if}
50 |
51 | {/if}
52 |
53 |
54 |
55 | Need an account?
56 | Sign Up
57 |
58 |
59 | Know your account?
60 | Login
61 |
62 |
63 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/auth/NewEmail.svelte:
--------------------------------------------------------------------------------
1 |
3 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/auth/Signup.svelte:
--------------------------------------------------------------------------------
1 |
44 |
45 | Sign up
46 |
61 |
62 | {#if auth.error_message}
63 |
{auth.error_message}
64 | {/if}
65 |
66 |
67 |
Already have an account?
68 |
Sign in
69 |
70 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/auth/Verify.svelte:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 | {#if !verified_sent}
15 |
16 | Your email is not verified.
17 | Send Verification
18 |
19 | {:else}
20 |
Verification email sent to {user.email}. Please check your email.
21 | {/if}
22 |
23 |
24 |
36 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/auth/auth_form.svelte.ts:
--------------------------------------------------------------------------------
1 | import { goto } from '$app/navigation';
2 | import { toaster } from '@drop-in/decks';
3 |
4 | export class AuthForm {
5 | status: 'LOADING' | 'SUCCESS' | 'ERROR' | 'INITIAL' = $state('INITIAL');
6 | error_message: string | undefined = $state();
7 |
8 | loading() {
9 | this.status = 'LOADING';
10 | }
11 |
12 | error(e_message: string) {
13 | toaster.error(e_message);
14 | this.status = 'ERROR';
15 | this.error_message = e_message;
16 | }
17 |
18 | success(route: string | boolean = DROP_IN.app.route, message: string = 'Success') {
19 | // Reset the Zero instance
20 | toaster.success(message);
21 | this.error_message = undefined;
22 | this.status = 'SUCCESS';
23 | // Redirect to wherever post login
24 | if (route && typeof route === 'string') goto(route, { invalidateAll: true });
25 | }
26 | }
27 |
28 | // TODO: Magic Link
29 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/components/UserMenu.svelte:
--------------------------------------------------------------------------------
1 |
17 |
18 | {#if z.current.userID !== 'anon'}
19 |
20 | {#snippet button()}
21 | {#if user?.current?.profile?.avatar}
22 |
23 | {:else}
24 | {user?.current?.email?.[0]}
25 | {/if}
26 | {/snippet}
27 |
28 |
Profile
29 |
30 |
{
32 | await pass.logout().catch((e) => {
33 | console.log('logout error', e);
34 | });
35 |
36 | clear_jwt();
37 | z.close();
38 | z.build(get_z_options());
39 | goto('/');
40 | }}>Logout
42 |
43 |
44 | {/if}
45 |
46 |
61 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/data/db.ts:
--------------------------------------------------------------------------------
1 | import { drizzle } from 'drizzle-orm/node-postgres';
2 | import { schema } from './db_schema';
3 |
4 | // The db connection
5 | // We use drizzle to connect to the database
6 | // We use the global drop_in_config to get the db url
7 | // This is the same db url that is in the .env file
8 |
9 | // The question here is really how much this should be possibly created in teh app itself so that there aren't multiple connections
10 | // But tbh not sure how much of a problem that is. LMK what you think. The goal is to make the user do a little bit of work as possible
11 | // To get up and running.
12 | if (!process.env.DATABASE_URL) throw new Error('DATABASE_URL required');
13 |
14 | export const db = drizzle({
15 | connection: process.env.DATABASE_URL,
16 | schema
17 | });
18 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/email.ts:
--------------------------------------------------------------------------------
1 | import { Beeper } from '@drop-in/beeper';
2 |
3 | export const beeper = new Beeper();
4 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/new-query.svelte.ts:
--------------------------------------------------------------------------------
1 | import type { Query as QueryParam, QueryType, Smash } from '@rocicorp/zero';
2 | import { type TableSchema, type AdvancedQuery } from '@rocicorp/zero/advanced';
3 | import { svelteViewFactory } from './svelte-view.svelte.js';
4 |
5 | export class Query {
6 | data = $state() as unknown as Smash;
7 | q: QueryParam;
8 |
9 | constructor(q: QueryParam) {
10 | this.q = q;
11 | this.data = {} as unknown as Smash;
12 |
13 | // We probably don't need an effect here? maybe you would want one just to destroy on cleanup?
14 | // NOt sure the best way to handle this.
15 |
16 | const view = this.q.materialize(svelteViewFactory);
17 | this.data = view;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/abort-error.ts:
--------------------------------------------------------------------------------
1 | export class AbortError extends Error {
2 | name = 'AbortError';
3 | }
4 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/arrays.test.ts:
--------------------------------------------------------------------------------
1 | import {describe, expect, test} from 'vitest';
2 | import {defined} from './arrays.js';
3 |
4 | describe('shared/arrays', () => {
5 | type Case = {
6 | input: (number | undefined)[];
7 | output: number[];
8 | };
9 |
10 | const cases: Case[] = [
11 | {
12 | input: [],
13 | output: [],
14 | },
15 | {
16 | input: [undefined],
17 | output: [],
18 | },
19 | {
20 | input: [undefined, undefined],
21 | output: [],
22 | },
23 | {
24 | input: [0, undefined],
25 | output: [0],
26 | },
27 | {
28 | input: [undefined, 0],
29 | output: [0],
30 | },
31 | {
32 | input: [undefined, 0, undefined],
33 | output: [0],
34 | },
35 | {
36 | input: [undefined, 0, 1],
37 | output: [0, 1],
38 | },
39 | {
40 | input: [0, undefined, 1],
41 | output: [0, 1],
42 | },
43 | {
44 | input: [0, undefined, 0, 1],
45 | output: [0, 0, 1],
46 | },
47 | {
48 | input: [0, undefined, 0, 1, undefined],
49 | output: [0, 0, 1],
50 | },
51 | {
52 | input: [0, undefined, 0, undefined, 1, undefined],
53 | output: [0, 0, 1],
54 | },
55 | {
56 | input: [2, 1, 0, undefined, 0, undefined, 1, undefined, 2],
57 | output: [2, 1, 0, 0, 1, 2],
58 | },
59 | ];
60 |
61 | for (const c of cases) {
62 | test(`defined(${JSON.stringify(c.input)})`, () => {
63 | const output = defined(c.input);
64 | expect(output).toEqual(c.output);
65 | if (output.length === c.input.length) {
66 | expect(output).toBe(c.input); // No copy
67 | }
68 | });
69 | }
70 | });
71 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/arrays.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Returns `arr` as is if none of the elements are `undefined`.
3 | * Otherwise returns a new array with only defined elements in `arr`.
4 | */
5 | export function defined(arr: (T | undefined)[]): T[] {
6 | // avoid an array copy if possible
7 | let i = arr.findIndex(x => x === undefined);
8 | if (i < 0) {
9 | return arr as T[];
10 | }
11 | const defined: T[] = arr.slice(0, i) as T[];
12 | for (i++; i < arr.length; i++) {
13 | const x = arr[i];
14 | if (x !== undefined) {
15 | defined.push(x);
16 | }
17 | }
18 | return defined;
19 | }
20 |
21 | export function areEqual(arr1: readonly T[], arr2: readonly T[]): boolean {
22 | return arr1.length === arr2.length && arr1.every((e, i) => e === arr2[i]);
23 | }
24 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/base62.test.ts:
--------------------------------------------------------------------------------
1 | import {expect, test} from 'vitest';
2 | import {encode} from './base62.js';
3 |
4 | test('it should encode base62', () => {
5 | expect(encode(0n)).toBe('0');
6 | expect(encode(1n)).toBe('1');
7 | expect(encode(9n)).toBe('9');
8 | expect(encode(10n)).toBe('A');
9 | expect(encode(35n)).toBe('Z');
10 | expect(encode(36n)).toBe('a');
11 | expect(encode(61n)).toBe('z');
12 | expect(encode(62n)).toBe('10');
13 | expect(encode(2n ** 31n - 1n)).toBe('2LKcb1');
14 | expect(encode(0x7fff_ffffn)).toBe('2LKcb1');
15 | expect(encode(2n ** 64n - 1n)).toBe('LygHa16AHYF');
16 | expect(encode(0xffff_ffff_ffff_ffffn)).toBe('LygHa16AHYF');
17 | });
18 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/base62.ts:
--------------------------------------------------------------------------------
1 | const alphabet =
2 | '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
3 |
4 | export function encode(n: bigint): string {
5 | if (n === 0n) {
6 | return '0';
7 | }
8 | let result = '';
9 | const base = BigInt(alphabet.length);
10 | while (n > 0n) {
11 | result = alphabet[Number(n % base)] + result;
12 | n = n / base;
13 | }
14 | return result;
15 | }
16 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/browser-env.ts:
--------------------------------------------------------------------------------
1 | // Helpers for some objects from the browser environment. These are wrapped in
2 | // functions because Replicache runs in environments that do not have these
3 | // objects (such as Web Workers, Deno etc).
4 |
5 | type GlobalThis = typeof globalThis;
6 |
7 | export function getBrowserGlobal(
8 | name: T,
9 | ): GlobalThis[T] | undefined {
10 | return globalThis[name];
11 | }
12 |
13 | /**
14 | * Returns the global method with the given name, bound to the global object.
15 | * This is important because some methods (e.g. `requestAnimationFrame`) are not
16 | * bound to the global object by default.
17 | *
18 | * If you end up using {@linkcode getBrowserGlobal} instead in a case like this:
19 | *
20 | * ```js
21 | * this.#raf = getBrowserGlobal('requestAnimationFrame') ?? rafFallback;
22 | * ...
23 | * this.#raf(() => ...);
24 | * ```
25 | *
26 | * You will end up with `Uncaught TypeError: Illegal invocation` because `this`
27 | * is not bound to the global object
28 | */
29 | export function getBrowserGlobalMethod(
30 | name: T,
31 | ): GlobalThis[T] | undefined {
32 | return globalThis[name]?.bind(globalThis);
33 | }
34 |
35 | export function mustGetBrowserGlobal(
36 | name: T,
37 | ): GlobalThis[T] {
38 | const r = getBrowserGlobal(name);
39 | if (r === undefined) {
40 | throw new Error(
41 | `Unsupported JavaScript environment: Could not find ${name}.`,
42 | );
43 | }
44 | return r;
45 | }
46 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/build.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @param {boolean=} minify
3 | * @param {boolean=} metafile
4 | */
5 | export function sharedOptions(
6 | minify?: boolean | undefined,
7 | metafile?: boolean | undefined,
8 | ): {
9 | readonly bundle: true;
10 | readonly target: 'es2022';
11 | readonly format: 'esm';
12 | readonly external: string[];
13 | readonly minify: boolean;
14 | readonly sourcemap: true;
15 | readonly metafile: boolean;
16 | };
17 | /**
18 | * @param {'debug'|'release'|'unknown'} mode
19 | * @return {Record}
20 | */
21 | export function makeDefine(
22 | mode?: 'debug' | 'release' | 'unknown',
23 | ): Record;
24 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/build.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | /* eslint-env node, es2022 */
3 |
4 | import {readFileSync} from 'node:fs';
5 |
6 | const external = [
7 | 'node:*',
8 | '@badrap/valita',
9 | '@rocicorp/datadog-util',
10 | '@rocicorp/lock',
11 | '@rocicorp/logger',
12 | '@rocicorp/resolver',
13 | 'replicache',
14 | ];
15 |
16 | /**
17 | * @param {boolean=} minify
18 | * @param {boolean=} metafile
19 | */
20 | export function sharedOptions(minify = true, metafile = false) {
21 | const opts = /** @type {const} */ ({
22 | bundle: true,
23 | target: 'es2022',
24 | format: 'esm',
25 | external,
26 | minify,
27 | sourcemap: true,
28 | metafile,
29 | });
30 | if (minify) {
31 | return /** @type {const} */ ({
32 | ...opts,
33 | mangleProps: /^_./,
34 | reserveProps: /^__.*__$/,
35 | });
36 | }
37 | return opts;
38 | }
39 |
40 | /**
41 | * @param {string} name
42 | * @return {string}
43 | */
44 |
45 | function getVersion(name) {
46 | const url = new URL(`../../${name}/package.json`, import.meta.url);
47 | const s = readFileSync(url, 'utf-8');
48 | return JSON.parse(s).version;
49 | }
50 |
51 | /**
52 | * @param {'debug'|'release'|'unknown'} mode
53 | * @return {Record}
54 | */
55 | export function makeDefine(mode = 'unknown') {
56 | /** @type {Record} */
57 | const define = {
58 | ['process.env.REPLICACHE_VERSION']: JSON.stringify(
59 | getVersion('replicache'),
60 | ),
61 | ['process.env.ZERO_VERSION']: JSON.stringify(getVersion('zero')),
62 | ['TESTING']: 'false',
63 | };
64 | if (mode === 'unknown') {
65 | return define;
66 | }
67 | return {
68 | ...define,
69 | 'process.env.NODE_ENV': mode === 'debug' ? '"development"' : '"production"',
70 | };
71 | }
72 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/config.ts:
--------------------------------------------------------------------------------
1 | declare const process: {
2 | env: {
3 | // eslint-disable-next-line @typescript-eslint/naming-convention
4 | NODE_ENV?: string;
5 | };
6 | };
7 |
8 | export const isProd = process.env.NODE_ENV === 'production';
9 |
10 | export {isProd as skipAssertJSONValue};
11 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/custom-key-set.ts:
--------------------------------------------------------------------------------
1 | type Primitive = undefined | null | boolean | string | number | symbol | bigint;
2 |
3 | /**
4 | * A {@link Set} that uses a custom value transformation function to convert values
5 | * to a primitive type that can be used as a {@link Set} value.
6 | *
7 | * This allows for using objects as values in a {@link Set} without worrying about
8 | * reference equality.
9 | */
10 |
11 | export class CustomKeySet implements Set {
12 | readonly [Symbol.toStringTag] = 'CustomKeySet';
13 | readonly #toKey: (value: V) => Primitive;
14 | readonly #map = new Map();
15 |
16 | constructor(toKey: (value: V) => Primitive, iterable?: Iterable | null) {
17 | this.#toKey = toKey;
18 | if (iterable) {
19 | for (const value of iterable ?? []) {
20 | this.#map.set(toKey(value), value);
21 | }
22 | }
23 | }
24 |
25 | add(value: V): this {
26 | this.#map.set(this.#toKey(value), value);
27 | return this;
28 | }
29 |
30 | clear(): void {
31 | this.#map.clear();
32 | }
33 |
34 | delete(value: V): boolean {
35 | return this.#map.delete(this.#toKey(value));
36 | }
37 |
38 | forEach(
39 | callbackfn: (value: V, value2: V, set: Set) => void,
40 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
41 | thisArg?: any,
42 | ): void {
43 | this.#map.forEach(value => {
44 | callbackfn.call(thisArg, value, value, this);
45 | });
46 | }
47 |
48 | has(value: V): boolean {
49 | return this.#map.has(this.#toKey(value));
50 | }
51 |
52 | get size(): number {
53 | return this.#map.size;
54 | }
55 |
56 | *entries(): IterableIterator<[V, V]> {
57 | for (const value of this.#map.values()) {
58 | yield [value, value];
59 | }
60 | }
61 |
62 | keys(): IterableIterator {
63 | return this.#map.values();
64 | }
65 |
66 | values(): IterableIterator {
67 | return this.#map.values();
68 | }
69 |
70 | [Symbol.iterator](): IterableIterator {
71 | return this.#map.values();
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/deep-clone.test.ts:
--------------------------------------------------------------------------------
1 | import {expect, test} from 'vitest';
2 | import {deepClone} from './deep-clone.js';
3 | import type {JSONValue, ReadonlyJSONValue} from './json.js';
4 |
5 | test('deepClone', () => {
6 | const t = (v: ReadonlyJSONValue) => {
7 | expect(deepClone(v)).toEqual(v);
8 | };
9 |
10 | t(null);
11 | t(1);
12 | t(1.2);
13 | t(0);
14 | t(-3412);
15 | t(1e20);
16 | t('');
17 | t('hi');
18 | t(true);
19 | t(false);
20 | t([]);
21 | t({});
22 |
23 | t({a: 42});
24 | t({a: 42, b: null});
25 | t({a: 42, b: 0});
26 | t({a: 42, b: true, c: false});
27 | t({a: 42, b: [1, 2, 3]});
28 | t([1, {}, 2]);
29 |
30 | const cyclicObject: JSONValue = {a: 42, cycle: null};
31 | cyclicObject.cycle = cyclicObject;
32 | expect(() => deepClone(cyclicObject)).toThrow('Cyclic object');
33 |
34 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
35 | const cyclicArray: any = {a: 42, cycle: [null]};
36 | cyclicArray.cycle[0] = cyclicArray;
37 | expect(() => deepClone(cyclicArray)).toThrow('Cyclic object');
38 |
39 | const sym = Symbol();
40 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
41 | expect(() => deepClone(sym as any)).toThrow('Invalid type: symbol');
42 | });
43 |
44 | test('deepClone - reuse references', () => {
45 | const t = (v: ReadonlyJSONValue) => expect(deepClone(v)).toEqual(v);
46 | const arr: number[] = [0, 1];
47 |
48 | t({a: arr, b: arr});
49 | t(['a', [arr, arr]]);
50 | t(['a', arr, {a: arr}]);
51 | t(['a', arr, {a: [arr]}]);
52 | });
53 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/deep-clone.ts:
--------------------------------------------------------------------------------
1 | import {hasOwn} from './has-own.js';
2 | import type {JSONValue, ReadonlyJSONValue} from './json.js';
3 |
4 | export function deepClone(value: ReadonlyJSONValue): JSONValue {
5 | const seen: Array = [];
6 | return internalDeepClone(value, seen);
7 | }
8 |
9 | export function internalDeepClone(
10 | value: ReadonlyJSONValue,
11 | seen: Array,
12 | ): JSONValue {
13 | switch (typeof value) {
14 | case 'boolean':
15 | case 'number':
16 | case 'string':
17 | case 'undefined':
18 | return value;
19 | case 'object': {
20 | if (value === null) {
21 | return null;
22 | }
23 | if (seen.includes(value)) {
24 | throw new Error('Cyclic object');
25 | }
26 | seen.push(value);
27 | if (Array.isArray(value)) {
28 | const rv = value.map(v => internalDeepClone(v, seen));
29 | seen.pop();
30 | return rv;
31 | }
32 |
33 | const obj: JSONValue = {};
34 |
35 | for (const k in value) {
36 | if (hasOwn(value, k)) {
37 | const v = (value as Record)[k];
38 | if (v !== undefined) {
39 | obj[k] = internalDeepClone(v, seen);
40 | }
41 | }
42 | }
43 | seen.pop();
44 | return obj;
45 | }
46 |
47 | default:
48 | throw new Error(`Invalid type: ${typeof value}`);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/events/connection-seconds.ts:
--------------------------------------------------------------------------------
1 | import * as v from '../valita.js';
2 |
3 | // Increment when making non-backwards compatible changes to the schema.
4 | const SCHEMA_VERSION = 2;
5 |
6 | export const connectionSecondsReportSchema = v.object({
7 | /** Reporting period, in seconds. */
8 | period: v.number(),
9 |
10 | /**
11 | * Connection-seconds elapsed during the interval.
12 | * It follows that `elapsed / interval` is equal to the
13 | * average number of connections during the interval.
14 | */
15 | elapsed: v.number(),
16 |
17 | /** Room ID of the connection. */
18 | roomID: v.string(),
19 | });
20 |
21 | export type ConnectionSecondsReport = v.Infer<
22 | typeof connectionSecondsReportSchema
23 | >;
24 |
25 | export const CONNECTION_SECONDS_CHANNEL_NAME = `connection-seconds@v${SCHEMA_VERSION}`;
26 |
27 | // Historic schemas for processing old Workers.
28 | export const CONNECTION_SECONDS_V1_CHANNEL_NAME = `connection-seconds@v1`;
29 | export const connectionSecondsReportV1Schema = v.object({
30 | /** Reporting interval, in seconds. */
31 | interval: v.number(),
32 |
33 | /**
34 | * Connection-seconds elapsed during the interval.
35 | * It follows that `elapsed / interval` is equal to the
36 | * average number of connections during the interval.
37 | */
38 | elapsed: v.number(),
39 | });
40 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/expand.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Expand/simplifies a type for display in Intellisense.
3 | */
4 | export type Expand = T extends infer O ? {[K in keyof O]: O[K]} : never;
5 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/float-to-ordered-string.test.ts:
--------------------------------------------------------------------------------
1 | import fc from 'fast-check';
2 | import {expect, test} from 'vitest';
3 | import {
4 | decodeFloat64AsString,
5 | encodeFloat64AsString,
6 | } from './float-to-ordered-string.js';
7 |
8 | const cases = [
9 | [-0, '1y2p0ij32e8e7'],
10 | [0, '1y2p0ij32e8e8'],
11 | [1, '2x2t6dniqybcw'],
12 | [2, '2x41irsmllclc'],
13 | [3, '2x4noyv6iwv7k'],
14 | [4, '2x59v5xqg8dts'],
15 | [-1, '0z2kunendu5fj'],
16 | [-2, '0z1ci99jj7473'],
17 | [-3, '0z0qc26zlvlkv'],
18 | [-4, '0z045v4fok2yn'],
19 | [3.141592653589793, '2x4qtzjh93rx4'],
20 | [NaN, '3w4rutzm7gy68'],
21 | [NaN, '3w4rutzm7gy68'],
22 | [Infinity, '3w45omx2a5fk0'],
23 | [-Infinity, '0018ce53un18f'],
24 | [Number.MAX_SAFE_INTEGER, '2yw3f766uv4sf'],
25 | [Number.MIN_SAFE_INTEGER, '0x9altvz9xc00'],
26 | [Number.MIN_VALUE, '1y2p0ij32e8e9'],
27 | [Number.MAX_VALUE, '3w45omx2a5fjz'],
28 | ] as const;
29 |
30 | const reversedCases = cases.map(([a, b]) => [b, a] as const);
31 |
32 | test.each(cases)('encode %f -> %s', (n, expected) => {
33 | expect(encodeFloat64AsString(n)).toBe(expected);
34 | });
35 |
36 | test.each(reversedCases)('decode %s -> %f', (s, expected) => {
37 | expect(decodeFloat64AsString(s)).toBe(expected);
38 | });
39 |
40 | test('random with fast-check', () => {
41 | fc.assert(
42 | fc.property(fc.float(), fc.float(), (a, b) => {
43 | const as = encodeFloat64AsString(a);
44 | const bs = encodeFloat64AsString(b);
45 |
46 | const a2 = decodeFloat64AsString(as);
47 | const b2 = decodeFloat64AsString(bs);
48 |
49 | expect(a2).toBe(a);
50 | expect(b2).toBe(b);
51 |
52 | if (Object.is(a, b)) {
53 | expect(as).toBe(bs);
54 | } else {
55 | expect(as).not.toBe(bs);
56 | if (!Number.isNaN(a) && !Number.isNaN(b)) {
57 | expect(as < bs).toBe(a < b);
58 | }
59 | }
60 | }),
61 | );
62 | });
63 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/float-to-ordered-string.ts:
--------------------------------------------------------------------------------
1 | import {assert} from './asserts.js';
2 | import {parseBigInt} from './parse-big-int.js';
3 |
4 | const view = new DataView(new ArrayBuffer(8));
5 |
6 | export function encodeFloat64AsString(n: number) {
7 | view.setFloat64(0, n);
8 |
9 | const high = view.getUint32(0);
10 | const low = view.getUint32(4);
11 |
12 | // The sign bit is 1 for negative numbers
13 | // We flip the sign bit so that positive numbers are ordered before negative numbers
14 |
15 | // If negative we flip all the bits so that larger absolute numbers are treated smaller
16 | if (n < 0 || Object.is(n, -0)) {
17 | view.setUint32(0, high ^ 0xffffffff);
18 | view.setUint32(4, low ^ 0xffffffff);
19 | } else {
20 | // we only flip the sign
21 | view.setUint32(0, high ^ (1 << 31));
22 | }
23 |
24 | const bigint = view.getBigUint64(0);
25 | return bigint.toString(36).padStart(13, '0');
26 | }
27 |
28 | export function decodeFloat64AsString(s: string): number {
29 | assert(s.length === 13, `Invalid encoded float64: ${s}`);
30 | const bigint = parseBigInt(s, 36);
31 | view.setBigUint64(0, bigint);
32 |
33 | const high = view.getUint32(0);
34 | const low = view.getUint32(4);
35 | const sign = high >> 31;
36 |
37 | // Positive
38 | if (sign) {
39 | // we only flip the sign
40 | view.setUint32(0, high ^ (1 << 31));
41 | } else {
42 | // If negative we flipped all the bits so that larger absolute numbers are treated smaller
43 | view.setUint32(0, high ^ 0xffffffff);
44 | view.setUint32(4, low ^ 0xffffffff);
45 | }
46 |
47 | return view.getFloat64(0);
48 | }
49 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/h64-with-reverse.ts:
--------------------------------------------------------------------------------
1 | import {reverseString} from './reverse-string.js';
2 | import {h64} from './xxhash.js';
3 |
4 | /**
5 | * xxhash only computes 64-bit values. Run it on the forward and reverse string
6 | * to get better collision resistance.
7 | */
8 | export function h64WithReverse(str: string): string {
9 | const forward = h64(str);
10 | const backward = h64(reverseString(str));
11 | const full = (forward << 64n) + backward;
12 | return full.toString(36);
13 | }
14 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/has-own.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-explicit-any */
2 | const objectPrototypeHasOwnProperty = Object.prototype.hasOwnProperty;
3 |
4 | /**
5 | * Object.hasOwn polyfill
6 | */
7 | export const hasOwn: (object: any, key: PropertyKey) => boolean =
8 | (Object as any).hasOwn ||
9 | ((object, key) => objectPrototypeHasOwnProperty.call(object, key));
10 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/headers.test.ts:
--------------------------------------------------------------------------------
1 | import {test, expect} from 'vitest';
2 | import {decodeHeaderValue, encodeHeaderValue} from './headers.js';
3 |
4 | function testEncodeDecodeHeaderValue(value: string, expected: string): void {
5 | const encoded = encodeHeaderValue(value);
6 | expect(encoded).toEqual(expected);
7 | expect(decodeHeaderValue(encoded)).toEqual(value);
8 | }
9 |
10 | test('encodeHeaderValue/decodeHeaderValue', () => {
11 | testEncodeDecodeHeaderValue('basic test', 'basic test');
12 | // All the chars normally escaped by encodeURIComponent, but which don't
13 | // need to be escaped for header values
14 | testEncodeDecodeHeaderValue(
15 | ':;,/"?{}[]@<>=+#$&`|^ ',
16 | ':;,/"?{}[]@<>=+#$&`|^ ',
17 | );
18 | testEncodeDecodeHeaderValue(
19 | '% char should be escaped',
20 | '%25 char should be escaped',
21 | );
22 | testEncodeDecodeHeaderValue(
23 | '{ userId: "1245678910" }',
24 | '{ userId: "1245678910" }',
25 | );
26 | testEncodeDecodeHeaderValue(
27 | 'هذا اختبار',
28 | '%D9%87%D8%B0%D8%A7 %D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1',
29 | );
30 | });
31 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/headers.ts:
--------------------------------------------------------------------------------
1 | export function encodeHeaderValue(value: string): string {
2 | // encodeURIComponent escapes the following chars which are allowed
3 | // in header values.
4 | // : ; , / " ? { } [ ] @ < > = + # $ & ` | ^ space and %
5 | // Unescape all of them expect %, to make the encoded value smaller and more
6 | // readable. Do not unescape % as that would break decoding of the
7 | // percent decoding done by encodeURIComponent.
8 | return encodeURIComponent(value).replace(
9 | /%(3A|3B|2C|2F|22|3F|7B|7D|5B|5D|40|3C|3E|3D|2B|23|24|26|60|7C|5E|20)/g,
10 | (_, hex) => String.fromCharCode(parseInt(hex, 16)),
11 | );
12 | }
13 |
14 | export function decodeHeaderValue(encoded: string): string {
15 | return decodeURIComponent(encoded);
16 | }
17 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/immutable.ts:
--------------------------------------------------------------------------------
1 | type Primitive = undefined | null | boolean | string | number | symbol | bigint;
2 |
3 | /**
4 | * Create a deeply immutable type from a type that may contain mutable types.
5 | */
6 | export type Immutable = T extends Primitive
7 | ? T
8 | : T extends Array
9 | ? ImmutableArray
10 | : ImmutableObject;
11 | // This does not deal with Maps or Sets (or Date or RegExp or ...).
12 |
13 | export type ImmutableArray = ReadonlyArray>;
14 |
15 | export type ImmutableObject = {readonly [K in keyof T]: Immutable};
16 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/json-schema.ts:
--------------------------------------------------------------------------------
1 | import * as valita from '@badrap/valita';
2 | import {skipAssertJSONValue} from './config.js';
3 | import type {ReadonlyJSONObject, ReadonlyJSONValue} from './json.js';
4 | import {isJSONObject, isJSONValue} from './json.js';
5 | import * as v from './valita.js';
6 |
7 | const path: (string | number)[] = [];
8 |
9 | export const jsonSchema: valita.Type = v
10 | .unknown()
11 | .chain(v => {
12 | if (skipAssertJSONValue) {
13 | return valita.ok(v as ReadonlyJSONValue);
14 | }
15 | const rv = isJSONValue(v, path)
16 | ? valita.ok(v)
17 | : valita.err({
18 | message: `Not a JSON value`,
19 | path: path.slice(),
20 | });
21 | path.length = 0;
22 | return rv;
23 | });
24 |
25 | export const jsonObjectSchema: valita.Type = v
26 | .unknown()
27 | .chain(v => {
28 | if (skipAssertJSONValue) {
29 | return valita.ok(v as ReadonlyJSONObject);
30 | }
31 | const rv = isJSONObject(v, path)
32 | ? valita.ok(v)
33 | : valita.err({
34 | message: `Not a JSON object`,
35 | path: path.slice(),
36 | });
37 | path.length = 0;
38 | return rv;
39 | });
40 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/logging-test-utils.ts:
--------------------------------------------------------------------------------
1 | import {
2 | type Context,
3 | LogContext,
4 | type LogLevel,
5 | type LogSink,
6 | } from '@rocicorp/logger';
7 |
8 | export class TestLogSink implements LogSink {
9 | messages: [LogLevel, Context | undefined, unknown[]][] = [];
10 | flushCallCount = 0;
11 |
12 | log(level: LogLevel, context: Context | undefined, ...args: unknown[]): void {
13 | this.messages.push([level, context, args]);
14 | }
15 |
16 | flush() {
17 | this.flushCallCount++;
18 | return Promise.resolve();
19 | }
20 | }
21 |
22 | export class SilentLogSink implements LogSink {
23 | log(_l: LogLevel, _c: Context | undefined, ..._args: unknown[]): void {
24 | return;
25 | }
26 | }
27 |
28 | export function createSilentLogContext() {
29 | return new LogContext('error', undefined, new SilentLogSink());
30 | }
31 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/mirror/is-supported-semver-range.ts:
--------------------------------------------------------------------------------
1 | import type {Comparator, Range} from 'semver';
2 |
3 | export function isSupportedSemverRange(range: Range): boolean {
4 | if (range.set.length === 1) {
5 | const comparators = range.set[0];
6 | if (comparators.length === 1) {
7 | const comparator = comparators[0];
8 | const {operator} = comparator;
9 | if (operator === '<' || operator === '<=') {
10 | const {semver} = comparator;
11 | return semver.patch === 0 && (semver.major === 0 || semver.minor === 0);
12 | }
13 | return operator === '>=' || operator === '>';
14 | }
15 |
16 | if (comparators.length === 2) {
17 | return isComparatorOK(comparators[0]) && isComparatorOK(comparators[1]);
18 | }
19 | }
20 |
21 | return false;
22 |
23 | function isComparatorOK(comparator: Comparator): boolean {
24 | const {operator} = comparator;
25 | if (operator === '<' || operator === '<=') {
26 | if (comparator.semver.major === 0) {
27 | return true;
28 | }
29 | return comparator.semver.minor === 0 && comparator.semver.patch === 0;
30 | }
31 | return operator === '>' || operator === '>=';
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/mod.ts:
--------------------------------------------------------------------------------
1 | export {};
2 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/must.ts:
--------------------------------------------------------------------------------
1 | export function must(v: T | undefined | null, msg?: string): T {
2 | // eslint-disable-next-line eqeqeq
3 | if (v == null) {
4 | throw new Error(msg ?? `Unexpected ${v} value`);
5 | }
6 | return v;
7 | }
8 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/navigator.ts:
--------------------------------------------------------------------------------
1 | type Navigator = {
2 | onLine: boolean;
3 | userAgent: string;
4 | // add more as needed
5 | };
6 |
7 | const localNavigator: Navigator | undefined =
8 | typeof navigator !== 'undefined' ? navigator : undefined;
9 |
10 | export {localNavigator as navigator};
11 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/parse-big-int.test.ts:
--------------------------------------------------------------------------------
1 | import fc from 'fast-check';
2 | import {expect, test} from 'vitest';
3 | import {parseBigInt} from './parse-big-int.js';
4 |
5 | const cases = [
6 | ['0', 10, 0n],
7 | ['1', 10, 1n],
8 | ['10', 10, 10n],
9 | ['10', 16, 16n],
10 | ['100', 10, 100n],
11 | ['100', 16, 256n],
12 | ['10', 36, 36n],
13 | ['100', 36, 1296n],
14 | ] as const;
15 |
16 | test.each(cases)('parseBigInt(%s, %s, %d)', (s, radix, expected) => {
17 | const actual = parseBigInt(s, radix);
18 | expect(actual).toBe(expected);
19 | });
20 |
21 | test('random using fast check', () => {
22 | fc.assert(
23 | fc.property(
24 | fc.bigInt({min: 0n}),
25 | fc.integer({min: 2, max: 36}),
26 | (n, radix) => {
27 | const s = n.toString(radix);
28 | const actual = parseBigInt(s, radix);
29 | expect(actual).toBe(n);
30 | },
31 | ),
32 | );
33 | });
34 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/parse-big-int.ts:
--------------------------------------------------------------------------------
1 | // Until there's BigInt.fromString(val, radix) ... https://github.com/tc39/proposal-number-fromstring
2 | export function parseBigInt(val: string, radix: number): bigint {
3 | const base = BigInt(radix);
4 | let result = 0n;
5 | for (let i = 0; i < val.length; i++) {
6 | result *= base;
7 | result += BigInt(parseInt(val[i], radix));
8 | }
9 | return result;
10 | }
11 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/rand.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @param min Inclusive minimum of the result.
3 | * @param max Inclusive maximum of the result.
4 | * @returns A random integer in the (inclusive) range of [`min`, `max`].
5 | */
6 | export function randInt(min: number, max: number) {
7 | min = Math.ceil(min);
8 | max = Math.floor(max);
9 | return Math.floor(Math.random() * (max - min + 1) + min);
10 | }
11 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/random-uint64.ts:
--------------------------------------------------------------------------------
1 | export function randomUint64(): bigint {
2 | // Generate two random 32-bit unsigned integers using Math.random()
3 | const high = Math.floor(Math.random() * 0xffffffff); // High 32 bits
4 | const low = Math.floor(Math.random() * 0xffffffff); // Low 32 bits
5 |
6 | // Combine the high and low parts to form a 64-bit unsigned integer
7 | return (BigInt(high) << 32n) | BigInt(low);
8 | }
9 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/random-values.ts:
--------------------------------------------------------------------------------
1 | export function getNonCryptoRandomValues(array: Uint8Array) {
2 | if (array === null) {
3 | throw new TypeError('array cannot be null');
4 | }
5 |
6 | // Fill the array with random values
7 | for (let i = 0; i < array.length; i++) {
8 | array[i] = Math.floor(Math.random() * 256); // Random byte (0-255)
9 | }
10 |
11 | return array;
12 | }
13 |
14 | export function randomCharacters(length: number) {
15 | let result = '';
16 | const characters =
17 | 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
18 | const charactersLength = characters.length;
19 | let counter = 0;
20 | while (counter < length) {
21 | result += characters.charAt(Math.floor(Math.random() * charactersLength));
22 | counter += 1;
23 | }
24 | return result;
25 | }
26 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/read-json-file.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 |
3 | import {readFile} from 'node:fs/promises';
4 |
5 | /**
6 | * @typedef {{
7 | * [key: string]: any;
8 | * name: string;
9 | * version: string;
10 | * }} PackageJSON
11 | */
12 |
13 | /**
14 | * @param {string} pathLike
15 | * @returns {Promise}
16 | */
17 | export async function readJSONFile(pathLike) {
18 | const s = await readFile(pathLike, 'utf-8');
19 | return JSON.parse(s);
20 | }
21 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/resolved-promises.ts:
--------------------------------------------------------------------------------
1 | export const promiseTrue = Promise.resolve(true);
2 | export const promiseFalse = Promise.resolve(false);
3 | export const promiseUndefined = Promise.resolve(undefined);
4 | export const promiseVoid = Promise.resolve();
5 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/reverse-string.ts:
--------------------------------------------------------------------------------
1 | export function reverseString(str: string): string {
2 | let reversed = '';
3 | for (let i = str.length - 1; i >= 0; i--) {
4 | reversed += str[i];
5 | }
6 | return reversed;
7 | }
8 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/sleep.ts:
--------------------------------------------------------------------------------
1 | import {resolver} from '@rocicorp/resolver';
2 | import {AbortError} from './abort-error.js';
3 |
4 | const promiseVoid = Promise.resolve();
5 | const promiseNever = new Promise(() => undefined);
6 |
7 | /**
8 | * Creates a promise that resolves after `ms` milliseconds. Note that if you
9 | * pass in `0` no `setTimeout` is used and the promise resolves immediately. In
10 | * other words no macro task is used in that case.
11 | *
12 | * Pass in an AbortSignal to clear the timeout.
13 | */
14 | export function sleep(ms: number, signal?: AbortSignal): Promise {
15 | const newAbortError = () => new AbortError('Aborted');
16 |
17 | if (signal?.aborted) {
18 | return Promise.reject(newAbortError());
19 | }
20 |
21 | if (ms === 0) {
22 | return promiseVoid;
23 | }
24 |
25 | return new Promise((resolve, reject) => {
26 | let handleAbort: () => void;
27 | if (signal) {
28 | handleAbort = () => {
29 | clearTimeout(id);
30 | reject(newAbortError());
31 | };
32 | signal.addEventListener('abort', handleAbort, {once: true});
33 | }
34 |
35 | const id = setTimeout(() => {
36 | resolve();
37 | signal?.removeEventListener('abort', handleAbort);
38 | }, ms);
39 | });
40 | }
41 |
42 | /**
43 | * Returns a pair of promises. The first promise resolves after `ms` milliseconds
44 | * unless the AbortSignal is aborted. The second promise resolves when the AbortSignal
45 | * is aborted.
46 | */
47 | export function sleepWithAbort(
48 | ms: number,
49 | signal: AbortSignal,
50 | ): [ok: Promise, aborted: Promise] {
51 | if (ms === 0) {
52 | return [promiseVoid, promiseNever];
53 | }
54 |
55 | const {promise: abortedPromise, resolve: abortedResolve} = resolver();
56 |
57 | const sleepPromise = new Promise(resolve => {
58 | const handleAbort = () => {
59 | clearTimeout(id);
60 | abortedResolve();
61 | };
62 |
63 | const id = setTimeout(() => {
64 | resolve();
65 | signal.removeEventListener('abort', handleAbort);
66 | }, ms);
67 |
68 | signal.addEventListener('abort', handleAbort, {once: true});
69 | });
70 |
71 | return [sleepPromise, abortedPromise];
72 | }
73 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/sorted-entries.ts:
--------------------------------------------------------------------------------
1 | import {stringCompare} from './string-compare.js';
2 |
3 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
4 | export function sortedEntries>(
5 | object: T,
6 | ): [keyof T & string, T[keyof T]][] {
7 | return Object.entries(object).sort((a, b) => stringCompare(a[0], b[0]));
8 | }
9 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/string-compare.ts:
--------------------------------------------------------------------------------
1 | export function stringCompare(a: string, b: string): number {
2 | if (a === b) {
3 | return 0;
4 | }
5 | if (a < b) {
6 | return -1;
7 | }
8 | return 1;
9 | }
10 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/timed.ts:
--------------------------------------------------------------------------------
1 | type LogFunction = ((...args: unknown[]) => void) | undefined;
2 |
3 | declare const performance: {
4 | now(): number;
5 | };
6 |
7 | /**
8 | * Times some async function and writes the result to a provided log function.
9 | * The log function can be undefined to simplify use with OptionalLogger.
10 | * @param log Log function to write to (ie LogContext.log)
11 | * @param label Label to write at start and end of function
12 | * @param fn Function to time
13 | * @returns The result of fn
14 | */
15 | export async function timed(
16 | log: LogFunction | undefined,
17 | label: string,
18 | fn: () => Promise,
19 | ): Promise {
20 | log?.(`Starting ${label}`);
21 | const clock = typeof performance !== 'undefined' ? performance : Date;
22 | const t0 = clock.now();
23 | try {
24 | return await fn();
25 | } finally {
26 | const t1 = clock.now();
27 | log?.(`Finished ${label} in ${t1 - t0}ms`);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/tool/get-external-from-package-json.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 |
3 | /* eslint-env es2022 */
4 |
5 | import {readFile} from 'node:fs/promises';
6 | import {pkgUp} from 'pkg-up';
7 | import {isInternalPackage} from './internal-packages.js';
8 |
9 | /**
10 | * @param {string} basePath
11 | * @param {boolean} includePeerDeps
12 | * @returns {Promise}
13 | */
14 | export async function getExternalFromPackageJSON(basePath, includePeerDeps) {
15 | const path = await pkgUp({cwd: basePath});
16 | if (!path) {
17 | throw new Error('Could not find package.json');
18 | }
19 | const x = await readFile(path, 'utf-8');
20 | const pkg = JSON.parse(x);
21 |
22 | const deps = new Set();
23 | for (const dep of Object.keys({
24 | ...pkg.dependencies,
25 | ...(includePeerDeps ? pkg.peerDependencies : {}),
26 | })) {
27 | if (isInternalPackage(dep)) {
28 | for (const depDep of await getRecursiveExternals(dep, includePeerDeps)) {
29 | deps.add(depDep);
30 | }
31 | } else {
32 | deps.add(dep);
33 | }
34 | }
35 | return [...deps];
36 | }
37 |
38 | /**
39 | * @param {string} name
40 | * @param {boolean} includePeerDeps
41 | */
42 | function getRecursiveExternals(name, includePeerDeps) {
43 | if (name === 'shared') {
44 | return getExternalFromPackageJSON(
45 | new URL(import.meta.url).pathname,
46 | includePeerDeps,
47 | );
48 | }
49 | const depPath = new URL(`../../../${name}/package.json`, import.meta.url)
50 | .pathname;
51 | return getExternalFromPackageJSON(depPath, includePeerDeps);
52 | }
53 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/tool/inject-require.js:
--------------------------------------------------------------------------------
1 | function createRandomIdentifier(name) {
2 | return `${name}_${Math.random() * 10000}`.replace('.', '');
3 | }
4 |
5 | /**
6 | * Injects a global `require` function into the bundle. This is sometimes needed
7 | * if the dependencies are incorrectly using require.
8 | *
9 | * @returns {esbuild.BuildOptions}
10 | */
11 | export function injectRequire() {
12 | const createRequireAlias = createRandomIdentifier('createRequire');
13 | return `import {createRequire as ${createRequireAlias}} from 'node:module';
14 | var require = ${createRequireAlias}(import.meta.url);
15 | `;
16 | }
17 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/tool/internal-packages.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 |
3 | /* eslint-env node, es2022 */
4 |
5 | import * as fs from 'node:fs';
6 | import * as path from 'node:path';
7 | import {fileURLToPath} from 'node:url';
8 |
9 | /**
10 | * Map from name to packages/name
11 | * @type {Map}
12 | */
13 | export const internalPackagesMap = new Map();
14 |
15 | const monoRootPath = fileURLToPath(new URL('../../../../', import.meta.url));
16 |
17 | for (const p of ['packages']) {
18 | for (const f of fs.readdirSync(path.join(monoRootPath, p))) {
19 | const stat = fs.statSync(path.join(monoRootPath, p, f));
20 | if (stat.isDirectory()) {
21 | // Also ensure that there is a package.json in that directory
22 | const packageJSONPath = path.join(monoRootPath, p, f, 'package.json');
23 |
24 | if (fs.existsSync(packageJSONPath)) {
25 | const packageJSON = JSON.parse(
26 | fs.readFileSync(packageJSONPath, 'utf-8'),
27 | );
28 | if (packageJSON.private) {
29 | internalPackagesMap.set(f, `${p}/${f}`);
30 | }
31 | }
32 | }
33 | }
34 | }
35 |
36 | export const internalPackages = [...internalPackagesMap.keys()];
37 |
38 | /**
39 | * @param {string} name
40 | */
41 | export function isInternalPackage(name) {
42 | return internalPackagesMap.has(name);
43 | }
44 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/tool/vitest-config.ts:
--------------------------------------------------------------------------------
1 | import {argv} from 'node:process';
2 | import {makeDefine} from '../build.js';
3 |
4 | const define = {
5 | ...makeDefine(),
6 | ['TESTING']: 'true',
7 | };
8 |
9 | /**
10 | * Find name from process.argv.
11 | *
12 | * The argv has --browser.name=chromium which
13 | * overrides the test.browser.name in the config but there is no way to read it
14 | * at this level so we have to do it manually.
15 | */
16 | function findBrowserName() {
17 | for (const arg of argv) {
18 | const m = arg.match(/--browser\.name=(.+)/);
19 | if (m) {
20 | return m[1];
21 | }
22 | }
23 | return undefined;
24 | }
25 |
26 | const logSilenceMessages = [
27 | 'Skipping license check for TEST_LICENSE_KEY.',
28 | 'REPLICACHE LICENSE NOT VALID',
29 | 'enableAnalytics false',
30 | 'no such entity',
31 | 'Zero starting up with no server URL',
32 | 'PokeHandler clearing due to unexpected poke error',
33 | 'Not indexing value',
34 | 'Zero starting up with no server URL',
35 | ];
36 | export default {
37 | // https://github.com/vitest-dev/vitest/issues/5332#issuecomment-1977785593
38 | optimizeDeps: {
39 | include: ['vitest > @vitest/expect > chai'],
40 | exclude: ['wa-sqlite'],
41 | },
42 | define,
43 | esbuild: {
44 | define,
45 | },
46 |
47 | test: {
48 | name: findBrowserName(),
49 | onConsoleLog(log: string) {
50 | for (const message of logSilenceMessages) {
51 | if (log.includes(message)) {
52 | return false;
53 | }
54 | }
55 | return undefined;
56 | },
57 | include: ['src/**/*.{test,spec}.?(c|m)[jt]s?(x)'],
58 | browser: {
59 | enabled: true,
60 | provider: 'playwright',
61 | headless: true,
62 | name: 'chromium',
63 | screenshotFailures: false,
64 | },
65 | typecheck: {
66 | enabled: false,
67 | },
68 | testTimeout: 10_000,
69 | },
70 | };
71 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/types.ts:
--------------------------------------------------------------------------------
1 | export type MaybePromise = T | Promise;
2 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/writable.ts:
--------------------------------------------------------------------------------
1 | export type Writable = {
2 | -readonly [P in keyof T]: T[P];
3 | };
4 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/shared/xxhash.ts:
--------------------------------------------------------------------------------
1 | import xxhash from 'xxhash-wasm';
2 |
3 | const {create64, h32, h64} = await xxhash();
4 |
5 | export {create64, h32, h64};
6 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/svelte-view.svelte.ts:
--------------------------------------------------------------------------------
1 | import type { QueryType, Smash } from '@rocicorp/zero';
2 | import {
3 | applyChange,
4 | type Change,
5 | type Format,
6 | type Input,
7 | type Output,
8 | type View,
9 | type Entry,
10 | type TableSchema
11 | } from '@rocicorp/zero/advanced';
12 | import type { Query } from 'pg';
13 |
14 | export class SvelteView implements Output {
15 | readonly #input: Input;
16 | readonly #format: Format;
17 | readonly #onDestroy: () => void;
18 |
19 | // Synthetic "root" entry that has a single "" relationship, so that we can
20 | // treat all changes, including the root change, generically.
21 | readonly #root: Entry = $state({ '': undefined });
22 |
23 | constructor(
24 | input: Input,
25 | format: Format = { singular: false, relationships: {} },
26 | onDestroy: () => void = () => {}
27 | ) {
28 | this.#input = input;
29 | this.#format = format;
30 | this.#onDestroy = onDestroy;
31 | this.#root = {
32 | '': format.singular ? undefined : []
33 | };
34 | input.setOutput(this);
35 | for (const node of input.fetch({})) {
36 | applyChange(this.#root, { type: 'add', node }, input.getSchema(), '', this.#format);
37 | }
38 | }
39 |
40 | get data() {
41 | return this.#root[''] as V;
42 | }
43 |
44 | destroy() {
45 | this.#onDestroy();
46 | }
47 |
48 | push(change: Change): void {
49 | applyChange(this.#root, change, this.#input.getSchema(), '', this.#format);
50 | }
51 | }
52 |
53 | export function svelteViewFactory(
54 | _query: Query,
55 | input: Input,
56 | format: Format,
57 | onDestroy: () => void
58 | ): SvelteView> {
59 | const v = new SvelteView>(input, format, onDestroy);
60 |
61 | return v;
62 | }
63 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/types.ts:
--------------------------------------------------------------------------------
1 | export type BaseQuestionConfig = {
2 | required: boolean;
3 | };
4 |
5 | export type TextConfig = BaseQuestionConfig & {
6 | minLength?: number;
7 | maxLength?: number;
8 | validationPattern?: string;
9 | };
10 |
11 | export type RatingConfig = BaseQuestionConfig & {
12 | max: number;
13 | kind: 'start' | 'number';
14 | labels?: {
15 | min: string;
16 | max: string;
17 | };
18 | };
19 |
20 | export type ChoiceConfig = BaseQuestionConfig & {
21 | options: Array<{
22 | id: string;
23 | label: string;
24 | value: string;
25 | }>;
26 | allowMultiple?: boolean;
27 | minSelections?: number;
28 | maxSelections?: number;
29 | allowOther?: boolean;
30 | };
31 |
32 | export type QuestionConfig = TextConfig | RatingConfig | ChoiceConfig;
33 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/lib/z.svelte.ts:
--------------------------------------------------------------------------------
1 | import { PUBLIC_SERVER } from '$env/static/public';
2 | import { schema } from '../schema';
3 | import { get_login } from '@drop-in/pass/client';
4 |
5 | export function get_z_options() {
6 | const a = get_login();
7 |
8 | return {
9 | userID: a.sub ?? 'anon',
10 | server: PUBLIC_SERVER,
11 | schema,
12 | auth: a.jwt
13 | } as const;
14 | }
15 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/routes/(app)/+layout.svelte:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
17 |
18 |
21 |
22 |
23 |
24 | {#if z.current.userID}
25 |
26 |
27 | {@render children()}
28 |
29 |
30 | {/if}
31 |
32 |
33 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/routes/(app)/+layout.ts:
--------------------------------------------------------------------------------
1 | export const ssr = false;
2 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/routes/(app)/Footer.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 |
19 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/routes/(app)/Header.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
15 |
16 |
33 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/routes/(app)/README.md:
--------------------------------------------------------------------------------
1 | This is the (app) route group, routes in here will be CSR'd only and is great user specific
2 | content, ie your app, user profile, ect.
3 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/routes/(app)/dashboard/+page.svelte:
--------------------------------------------------------------------------------
1 |
22 |
23 | Your Dashboard
24 |
25 | Profile
26 |
27 | Create Survey
28 |
29 |
30 | {#each surveys?.current as survey}
31 | {survey.title}
32 | {/each}
33 |
34 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/routes/(app)/profile/+page.svelte:
--------------------------------------------------------------------------------
1 |
43 |
44 | Profile
45 |
46 | {#if user?.current?.id}
47 |
48 | Email: {user.current.email}
49 |
50 |
51 |
57 |
58 |
59 |
60 | Logout
61 |
62 | Logout
63 | {/if}
64 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/routes/(app)/surveys/[id]/+layout.svelte:
--------------------------------------------------------------------------------
1 |
17 |
18 | {survey?.current?.title}
19 |
20 | {@render children()}
21 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/routes/(app)/surveys/[id]/+page.svelte:
--------------------------------------------------------------------------------
1 |
13 |
14 | Take Survey
15 |
16 | Responses
17 | See Responses
18 |
19 | Questions
20 |
21 | Edit
22 | {survey?.current?.questions?.length} Questions
23 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/routes/(app)/surveys/[id]/edit/+page.svelte:
--------------------------------------------------------------------------------
1 |
33 |
34 | Edit Survey
35 |
36 | {#each survey?.current?.questions as question, index}
37 |
38 | {/each}
39 |
40 |
41 | + Add Question
42 |
43 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/routes/(app)/surveys/[id]/edit/EditRow.svelte:
--------------------------------------------------------------------------------
1 |
21 |
22 |
50 |
51 | {#snippet type(index: number)}
52 | Type
53 |
59 | Text
60 | Long Text
61 | Rating
62 |
63 | {/snippet}
64 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/routes/(site)/+layout.svelte:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 | {#if !PUBLIC_SERVER}
11 |
12 | !IMPORTANT: You need to set the PUBLIC_SERVER environment variable in "/.env". This is the URL
13 | to your data server
14 |
15 | {/if}
16 |
17 |
18 |
19 |
20 |
21 | {@render children()}
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/routes/(site)/+page.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 | {DROP_IN.app.name}
7 |
8 | Sign Up
9 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/routes/(site)/README.md:
--------------------------------------------------------------------------------
1 | This is the (site) route group, routes in here will be SSR'd and is great for landing/marketing/info pages, blog ect.
2 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/routes/(site)/auth/+layout.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 | {@render children()}
9 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/routes/(site)/auth/confirm-email-change/+page.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/routes/(site)/auth/confirm-password-reset/[token]/+page.svelte:
--------------------------------------------------------------------------------
1 |
28 |
29 | Reset Password
30 |
31 |
32 | New Password
33 |
34 |
35 |
36 | New Password Confirm
37 |
38 |
39 |
40 | {#if loading}Resetting...{:else}
41 | Reest Password
42 | {/if}
43 |
44 |
45 |
46 | {#if auth.error_message}
47 | {auth.error_message}
48 | {/if}
49 |
50 |
51 | Need an account?
52 | Sign Up
53 |
54 |
55 |
56 | Forgot your password?
57 |
58 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/routes/(site)/auth/confirm-verification/+page.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/routes/(site)/auth/forgot-password/+page.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/routes/(site)/auth/login/+page.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/routes/(site)/auth/signup/+page.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/routes/(site)/survey/[id]/+page.svelte:
--------------------------------------------------------------------------------
1 |
21 |
22 | {survey.current?.title}
23 |
24 | Let's Get Started
25 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/routes/(site)/survey/[id]/[response_id]/+page.svelte:
--------------------------------------------------------------------------------
1 |
16 |
17 | {survey.current?.title}
18 |
19 |
20 | {#each survey.current?.questions || [] as question, index}
21 |
22 | {/each}
23 |
24 |
25 | Submit
26 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/routes/(site)/survey/[id]/[response_id]/Answer.svelte:
--------------------------------------------------------------------------------
1 |
30 |
31 |
32 |
33 |
{question.question_text}
34 | {#if question.question_type === 'text'}
35 |
36 | {:else if question.question_type === 'long'}
37 |
38 | {:else if question?.question_type === 'rating'}
39 |
40 | {/if}
41 |
42 |
43 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/routes/(site)/survey/[id]/[response_id]/LongText.svelte:
--------------------------------------------------------------------------------
1 |
10 |
11 | {label}
12 | {
15 | e.stopPropagation();
16 | oninput(e.target?.value, label);
17 | }}
18 | name="answer"
19 | >
20 |
21 |
26 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/routes/(site)/survey/[id]/[response_id]/Rating.svelte:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/routes/(site)/survey/[id]/[response_id]/Text.svelte:
--------------------------------------------------------------------------------
1 |
10 |
11 | {label}
12 | {
16 | e.stopPropagation();
17 | oninput(e.target?.value, label);
18 | }}
19 | name="answer"
20 | />
21 |
22 |
27 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/routes/+layout.server.ts:
--------------------------------------------------------------------------------
1 | export const ssr = false;
2 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/routes/+layout.svelte:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
16 | {#key z.current.userID}
17 | {@render children()}
18 | {/key}
19 |
20 |
28 |
29 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/routes/UserWrapper.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 | {@render children()}
7 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/routes/global.css:
--------------------------------------------------------------------------------
1 | .button {
2 | text-decoration: none;
3 | }
4 |
5 | textarea {
6 | width: 100%;
7 | }
8 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/state/README.md:
--------------------------------------------------------------------------------
1 | # $state
2 |
3 | Store global state here and you can access it via `import { create_store } from $state/store.svelte'`
4 |
5 | See implementation in ./app.svelte.ts for more information.
6 |
7 | This is useful anytime you have state that isn't tightly scoped to one specific route or component.
8 |
9 | Do not use for SSR because your state may be insecure.
10 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/state/app.svelte.ts:
--------------------------------------------------------------------------------
1 | class AppState {}
2 | // You can now import { app } from '$state/app.svelte'
3 | export const app = new AppState();
4 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/utils/app_guard.ts:
--------------------------------------------------------------------------------
1 | import settings from '';
2 | import { goto } from '$app/navigation';
3 | import { getZ } from 'zero-svelte';
4 | // Sends users to the app if they try to access login or landing pages after logging in.
5 | export function app_guard() {
6 | const z = getZ();
7 | if (z.current.userID && z.current.userID !== 'anon') {
8 | goto(settings.app.route);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/examples/zero-survey/src/utils/auth_guard.ts:
--------------------------------------------------------------------------------
1 | import { goto } from '$app/navigation';
2 | import { getZ } from 'zero-svelte';
3 | export function auth_guard() {
4 | const z = getZ();
5 | if (!z.current.userID || z.current.userID === 'anon') {
6 | goto('/auth/login');
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/examples/zero-survey/svelte.config.js:
--------------------------------------------------------------------------------
1 | import adapter from '@sveltejs/adapter-auto';
2 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
3 |
4 | /** @type {import('@sveltejs/kit').Config} */
5 | const config = {
6 | // Consult https://github.com/sveltejs/svelte-preprocess
7 | // for more information about preprocessors
8 | preprocess: vitePreprocess(),
9 |
10 | kit: {
11 | adapter: adapter(),
12 | alias: {
13 | $routes: './src/routes',
14 | $state: './src/state',
15 | $types: './src/types',
16 | $utils: './src/utils',
17 | },
18 | },
19 | };
20 |
21 | export default config;
22 |
--------------------------------------------------------------------------------
/examples/zero-survey/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./.svelte-kit/tsconfig.json",
3 | "compilerOptions": {
4 | "target": "ES2020",
5 | "allowJs": true,
6 | "checkJs": true,
7 | "esModuleInterop": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "useDefineForClassFields": true,
10 | "module": "ESNext",
11 | "moduleDetection": "force",
12 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
13 | "resolveJsonModule": true,
14 | "skipLibCheck": true,
15 | "sourceMap": true,
16 | "strict": true,
17 | "exactOptionalPropertyTypes": true,
18 | "noUnusedLocals": true,
19 | "noUnusedParameters": true,
20 | "noFallthroughCasesInSwitch": true
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/examples/zero-survey/vite.config.ts:
--------------------------------------------------------------------------------
1 | import dropin from '@drop-in/plugin';
2 | import { sveltekit } from '@sveltejs/kit/vite';
3 | import { defineConfig, loadEnv, type ConfigEnv } from 'vite';
4 |
5 | export default ({ mode }: ConfigEnv) => {
6 | Object.assign(process.env, loadEnv(mode, process.cwd(), ''));
7 |
8 | return defineConfig({
9 | server: {
10 | port: 1234
11 | },
12 |
13 | plugins: [dropin(), sveltekit()],
14 | });
15 | };
16 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "checkJs": true,
5 | "noEmit": true,
6 | "strict": true,
7 | "module": "ES2022",
8 | "target": "ES2022",
9 | "moduleResolution": "node"
10 | },
11 | "exclude": ["node_modules", "**/node_modules/*"],
12 | "include": ["**/*.js"]
13 | }
14 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@drop-in/new",
3 | "version": "0.1.0",
4 | "description": "A local data SvelteKit + Zero Starter+",
5 | "bin": "bin.js",
6 | "scripts": {
7 | "dev": "turbo dev",
8 | "package": "turbo package --filter=\"./packages/**\"",
9 | "all:update": "pnpm -r update -L -i",
10 | "test": "echo \"Error: no test specified\" && exit 1",
11 | "clean": "find ./ -name .svelte-kit -type d -exec rm -rf {} +; find ./ -name node_modules -type d -exec rm -rf {} +; find ./ -name pnpm-lock.yaml -type f -exec rm {} +;",
12 | "docs:dev": "pnpm -F @drop-in/docs run dev",
13 | "docs:build": "pnpm -F @drop-in/decks package && pnpm -F @drop-in/docs run build",
14 | "decks:dev": "pnpm -F @drop-in/decks run dev",
15 | "decks:package": "pnpm -F @drop-in/decks package",
16 | "template:z:dev": "pnpm -F z run dev",
17 | "template:base:dev": "pnpm -F base run dev"
18 | },
19 | "type": "module",
20 | "keywords": [
21 | "Svelte",
22 | "Svelte Kit",
23 | "Starter Kit"
24 | ],
25 | "packageManager": "pnpm@9.0.0",
26 | "author": "Scott Tolinski",
27 | "license": "ISC",
28 | "dependencies": {
29 | "@clack/prompts": "^0.10.1",
30 | "@drop-in/plugin": "workspace:^",
31 | "kleur": "^4.1.5",
32 | "sst": "^3.13.19"
33 | },
34 | "devDependencies": {
35 | "@changesets/cli": "^2.29.2",
36 | "eslint": "^9.25.1",
37 | "eslint-config-prettier": "^10.1.2",
38 | "eslint-plugin-svelte": "^3.5.1",
39 | "turbo": "^2.5.2",
40 | "typescript": "^5.8.3",
41 | "vite": "^6.3.3"
42 | },
43 | "pnpm": {
44 | "onlyBuiltDependencies": [
45 | "esbuild"
46 | ]
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/packages/beeper/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@drop-in/beeper",
3 | "version": "0.0.13",
4 | "description": "Your beeper",
5 | "main": "./dist/index.js",
6 | "types": "./dist/index.d.ts",
7 | "type": "module",
8 | "exports": {
9 | ".": {
10 | "types": "./dist/index.d.ts",
11 | "import": "./dist/index.js"
12 | }
13 | },
14 | "files": [
15 | "dist"
16 | ],
17 | "scripts": {
18 | "dev": "tsc --watch",
19 | "build": "tsc",
20 | "package": "tsc",
21 | "test": "echo \"Error: no test specified\" && exit 1",
22 | "ebuild": "esbuild src/index.ts --bundle --platform=node --format=esm --outdir=dist --external:pg --external:crypto --external:bcryptjs --external:fs --external:path --external:os --external:util --external:events"
23 | },
24 | "author": "",
25 | "license": "ISC",
26 | "dependencies": {
27 | "@types/nodemailer": "^6.4.17",
28 | "nodemailer": "^6.10.1"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/packages/beeper/src/app.d.ts:
--------------------------------------------------------------------------------
1 | declare global {
2 | const DROP_IN: {
3 | email: {
4 | host?: string;
5 | port?: number;
6 | secure?: boolean;
7 | from?: string;
8 | };
9 | app: {
10 | url: string;
11 | name: string;
12 | route: string;
13 | };
14 | };
15 | }
16 |
17 | export {};
18 |
--------------------------------------------------------------------------------
/packages/beeper/src/beeper.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | // This is beeper.
4 | // It's a wrapper around nodemailer that makes it easier to send emails.
5 | // Why a wrapper? Well, we want to use the same config for nodemailer as we do for the rest of the app.
6 | // Also if you don't have the config, it just outputs to the console. Which is nice for getting up and running. Meteor did this and I liked it.
7 |
8 | // Change your import to use a namespace import:
9 | import * as nodemailer from 'nodemailer';
10 |
11 | // The beeper class could be used if you want, but you are probably better off just using nodemailer directly at that point. Use beeper instance to send emails configured with config.email
12 | export class Beeper {
13 | transporter: nodemailer.Transporter | undefined;
14 | from: string | undefined;
15 | mode: 'PRINT' | 'SEND' = 'PRINT';
16 | constructor({
17 | host,
18 | port,
19 | secure,
20 | from,
21 | }:
22 | | {
23 | host?: string;
24 | port?: number;
25 | secure?: boolean;
26 | from?: string;
27 | }
28 | | undefined = {}) {
29 | const auth = { user: process.env.EMAIL_USER, pass: process.env.EMAIL_PASSWORD };
30 |
31 | if (host) {
32 | this.mode = 'SEND';
33 | this.transporter = nodemailer.createTransport({
34 | host,
35 | port,
36 | secure,
37 | auth,
38 | });
39 | }
40 | this.from = from;
41 | }
42 | async send({
43 | to,
44 | subject,
45 | html,
46 | from = this.from,
47 | }: {
48 | to?: string;
49 | subject?: string;
50 | html?: string;
51 | from?: string;
52 | } = {}) {
53 | if (this.mode === 'SEND' && this.transporter) {
54 | try {
55 | await this.transporter.sendMail({
56 | from,
57 | to,
58 | subject,
59 | html,
60 | });
61 | } catch (error) {
62 | console.error('Error sending email', error);
63 | }
64 | } else {
65 | console.log(
66 | 'Fake Email Send: If you indended this to be an actual email, you need to set email config in drop-in.config.json and the EMAIL_PASSWORD, EMAIL_USER env variables.',
67 | to,
68 | subject,
69 | html,
70 | from,
71 | );
72 | }
73 | }
74 | }
75 |
76 | export const beeper = new Beeper(DROP_IN.email);
77 |
--------------------------------------------------------------------------------
/packages/beeper/src/index.ts:
--------------------------------------------------------------------------------
1 | // Welcome to @drop-in/beeper
2 | // This is the email package for drop-in.
3 | // I grew up in the 90s and never had a beeper, but now I can.
4 |
5 | // This is the main entry point for the package.
6 | // Here we will export all the methods for the package.
7 |
8 | // Beeper is largely a wrapper around nodemailer. It's designed to use your config as it's defaults. You can always just use nodemailer directly, but if you want to use @drop-in/pass, this works in conjunction with it.
9 | // Right now Pass doesn't have hooks for sending emails, but it will. If you'd like to write that, you would probably have them be callbacks or something in the login functions. Well... anyways this package isn't about auth, so I'll stop talking about it.
10 | export * from './beeper.js';
11 |
--------------------------------------------------------------------------------
/packages/beeper/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "checkJs": true,
5 | "forceConsistentCasingInFileNames": true,
6 | "target": "esnext",
7 | "module": "NodeNext",
8 | "moduleResolution": "nodenext", // change from "Bundler" to "node16"
9 | "outDir": "./dist",
10 | "strict": true,
11 | "skipLibCheck": true,
12 | "declaration": true,
13 | "declarationMap": true,
14 | "sourceMap": true,
15 | "allowSyntheticDefaultImports": true
16 | },
17 | "include": ["src/**/*"]
18 | }
19 |
--------------------------------------------------------------------------------
/packages/decks/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
3 | # Output
4 | .output
5 | .vercel
6 | /.svelte-kit
7 | /build
8 | /dist
9 |
10 | # OS
11 | .DS_Store
12 | Thumbs.db
13 |
14 | # Env
15 | .env
16 | .env.*
17 | !.env.example
18 | !.env.test
19 |
20 | # Vite
21 | vite.config.js.timestamp-*
22 | vite.config.ts.timestamp-*
23 |
--------------------------------------------------------------------------------
/packages/decks/.npmrc:
--------------------------------------------------------------------------------
1 | engine-strict=true
2 | link-workspace-packages=true
--------------------------------------------------------------------------------
/packages/decks/.prettierignore:
--------------------------------------------------------------------------------
1 | # Package Managers
2 | package.json
3 | package-lock.json
4 | pnpm-lock.yaml
5 | yarn.lock
6 |
--------------------------------------------------------------------------------
/packages/decks/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "ignoreFiles": ["package.json"],
3 | "overrides": [
4 | {
5 | "files": "*.svelte",
6 | "options": {
7 | "parser": "svelte"
8 | }
9 | }
10 | ],
11 | "plugins": ["prettier-plugin-svelte"],
12 | "printWidth": 100,
13 | "singleQuote": true,
14 | "trailingComma": "none",
15 | "useTabs": true
16 | }
17 |
--------------------------------------------------------------------------------
/packages/decks/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # @drop-in/decks
2 |
3 | ## 0.0.15
4 |
5 | ### Patch Changes
6 |
7 | - 9ea23b5: Fixes early logout issues
8 |
--------------------------------------------------------------------------------
/packages/decks/README.md:
--------------------------------------------------------------------------------
1 | # Deprecated in favor of eventually moving to web awesome with a web awesome theme
2 |
3 | # @drop-in/decks
4 |
5 | Decks is a ui component library that doesn't depend on dependencies or css frameworks to function. They are also deeply committed to using browser standards and APIs before custom JavaScript. Add your own theme, or use `@drop-in/graffiti` to style your decks.
6 |
7 | Disclaimer: These are under active development. I encourage you to try them and submit pr's to improve them. I'd like to keep them as opinionated, drop-in style elements, rather than a deep customizable framework.
8 |
9 | `npm install @drop-in/decks`
10 |
11 | `import { Share } from '@drop-in/decks'`
12 |
13 | If you would like css, you can install and import `@drop-in/graffiti`. See https://github.com/stolinski/drop-in/tree/main/packages/graffiti
14 |
15 | ## Current Elements
16 |
17 | - Menu (Popover based)
18 | - Pills
19 | - Share
20 | - Are You Sure - Confirm Button
21 | - Accordion (Details based)
22 |
--------------------------------------------------------------------------------
/packages/decks/eslint.config.js:
--------------------------------------------------------------------------------
1 | import js from '@eslint/js';
2 | import ts from 'typescript-eslint';
3 | import svelte from 'eslint-plugin-svelte';
4 | import prettier from 'eslint-config-prettier';
5 | import globals from 'globals';
6 |
7 | /** @type {import('eslint').Linter.Config[]} */
8 | export default [
9 | js.configs.recommended,
10 | ...ts.configs.recommended,
11 | ...svelte.configs['flat/recommended'],
12 | prettier,
13 | ...svelte.configs['flat/prettier'],
14 | {
15 | languageOptions: {
16 | globals: {
17 | ...globals.browser,
18 | ...globals.node
19 | }
20 | }
21 | },
22 | {
23 | files: ['**/*.svelte'],
24 | languageOptions: {
25 | parserOptions: {
26 | parser: ts.parser
27 | }
28 | }
29 | },
30 | {
31 | ignores: ['build/', '.svelte-kit/', 'dist/']
32 | }
33 | ];
34 |
--------------------------------------------------------------------------------
/packages/decks/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "@oddbird/popover-polyfill": "^0.5.2"
4 | },
5 | "devDependencies": {
6 | "@drop-in/graffiti": "workspace:^",
7 | "@sveltejs/adapter-auto": "^6.0.0",
8 | "@sveltejs/kit": "^2.20.7",
9 | "@sveltejs/package": "^2.3.11",
10 | "@sveltejs/vite-plugin-svelte": "5.0.3",
11 | "@types/eslint": "^9.6.1",
12 | "eslint": "^9.25.1",
13 | "eslint-config-prettier": "^10.1.2",
14 | "eslint-plugin-svelte": "^3.5.1",
15 | "globals": "^16.0.0",
16 | "prettier": "^3.5.3",
17 | "prettier-plugin-svelte": "^3.3.3",
18 | "publint": "^0.3.12",
19 | "svelte": "5.28.2",
20 | "svelte-check": "^4.1.6",
21 | "typescript": "^5.8.3",
22 | "typescript-eslint": "^8.31.0",
23 | "vite": "^6.3.3"
24 | },
25 | "engines": {
26 | "node": ">20.11.1"
27 | },
28 | "exports": {
29 | ".": {
30 | "types": "./dist/index.d.ts",
31 | "svelte": "./dist/index.js",
32 | "default": "./dist/index.js"
33 | },
34 | "./docs": {
35 | "types": "./dist/local/Docs.svelte.d.ts",
36 | "svelte": "./dist/local/Docs.svelte"
37 | }
38 | },
39 | "files": [
40 | "dist",
41 | "!dist/**/*.test.*",
42 | "!dist/**/*.spec.*"
43 | ],
44 | "name": "@drop-in/decks",
45 | "peerDependencies": {
46 | "svelte": "^5.0.0"
47 | },
48 | "scripts": {
49 | "build": "vite build && npm run package",
50 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
51 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
52 | "dev": "vite dev",
53 | "format": "prettier --write .",
54 | "lint": "prettier --check . && eslint .",
55 | "package": "svelte-kit sync && svelte-package && publint",
56 | "prepublishOnly": "npm run package",
57 | "preview": "vite preview"
58 | },
59 | "main": "./dist/index.js",
60 | "svelte": "./dist/index.js",
61 | "type": "module",
62 | "types": "./dist/index.d.ts",
63 | "version": "0.0.15"
64 | }
65 |
--------------------------------------------------------------------------------
/packages/decks/src/app.d.ts:
--------------------------------------------------------------------------------
1 | // See https://kit.svelte.dev/docs/types#app
2 | // for information about these interfaces
3 | declare global {
4 | namespace App {
5 | // interface Error {}
6 | // interface Locals {}
7 | // interface PageData {}
8 | // interface PageState {}
9 | // interface Platform {}
10 | }
11 | }
12 |
13 | export {};
14 |
--------------------------------------------------------------------------------
/packages/decks/src/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | %sveltekit.head%
8 |
9 |
10 | %sveltekit.body%
11 |
12 |
13 |
--------------------------------------------------------------------------------
/packages/decks/src/lib/Accordion.svelte:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 | {summary}
15 |
16 |
17 | {@render children()}
18 |
19 |
20 |
21 |
26 |
--------------------------------------------------------------------------------
/packages/decks/src/lib/AreYouSure.svelte:
--------------------------------------------------------------------------------
1 |
34 |
35 |
36 | {text}
37 | {#if attempt_count !== 0}
38 |
39 | Press {remaining} more time{one_more_left ? '' : 's'}.
40 |
41 | {/if}
42 |
43 |
44 |
55 |
--------------------------------------------------------------------------------
/packages/decks/src/lib/Dialog.svelte:
--------------------------------------------------------------------------------
1 |
56 |
57 | {#if show_button}
58 | dialog.showModal()}>{button_text}
59 | {/if}
60 |
61 |
62 | {#if title}{title} {/if}
63 | ×
64 |
65 | {@render children()}
66 | {#if buttons}
67 |
68 | {cancel_text}
69 | {confirm_text}
70 |
71 | {/if}
72 |
73 |
74 |
--------------------------------------------------------------------------------
/packages/decks/src/lib/Pill.svelte:
--------------------------------------------------------------------------------
1 |
14 |
15 | {#if link}
16 | {@render children()}
17 | {:else if onclick}
18 | {@render children()}
19 | {:else}
20 | {@render children()}
21 | {/if}
22 |
23 |
41 |
--------------------------------------------------------------------------------
/packages/decks/src/lib/Pills.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 | {#each content as pill}
8 | {#if typeof pill === 'string'}
9 | {pill}
10 | {:else}
11 | {pill.text}
12 | {/if}
13 | {/each}
14 |
--------------------------------------------------------------------------------
/packages/decks/src/lib/Share.svelte:
--------------------------------------------------------------------------------
1 |
50 |
51 |
52 |
74 |
75 |
--------------------------------------------------------------------------------
/packages/decks/src/lib/drawer.ts:
--------------------------------------------------------------------------------
1 | export function drawer_animated(node: HTMLElement) {
2 | let x: number;
3 | let y: number;
4 |
5 | function handleMousedown(event: MouseEvent) {
6 | x = event.clientX;
7 | y = event.clientY;
8 |
9 | node.dispatchEvent(
10 | new CustomEvent('panstart', {
11 | detail: { x, y }
12 | })
13 | );
14 |
15 | window.addEventListener('mousemove', handleMousemove);
16 | window.addEventListener('mouseup', handleMouseup);
17 | }
18 |
19 | function handleMousemove(event: MouseEvent) {
20 | const dx = event.clientX - x;
21 | const dy = event.clientY - y;
22 | x = event.clientX;
23 | y = event.clientY;
24 |
25 | node.dispatchEvent(
26 | new CustomEvent('panmove', {
27 | detail: { x, y, dx, dy }
28 | })
29 | );
30 | }
31 |
32 | function handleMouseup(event: MouseEvent) {
33 | x = event.clientX;
34 | y = event.clientY;
35 | const dx = event.clientX - x;
36 | const dy = event.clientY - y;
37 |
38 | node.dispatchEvent(
39 | new CustomEvent('panend', {
40 | detail: { x, y, dx, dy }
41 | })
42 | );
43 |
44 | window.removeEventListener('mousemove', handleMousemove);
45 | window.removeEventListener('mouseup', handleMouseup);
46 | }
47 |
48 | node.addEventListener('mousedown', handleMousedown);
49 |
50 | return {
51 | destroy() {
52 | node.removeEventListener('mousedown', handleMousedown);
53 | }
54 | };
55 | }
56 |
--------------------------------------------------------------------------------
/packages/decks/src/lib/index.ts:
--------------------------------------------------------------------------------
1 | // Reexport your entry components here
2 | export { default as Dialog } from './Dialog.svelte';
3 | export { default as Drawer } from './Drawer.svelte';
4 | export { default as Menu } from './Menu.svelte';
5 | export { default as Pill } from './Pill.svelte';
6 | export { default as Pills } from './Pills.svelte';
7 | export { default as Share } from './Share.svelte';
8 | export { default as Accordion } from './Accordion.svelte';
9 | export * from './toast/index.js';
10 |
--------------------------------------------------------------------------------
/packages/decks/src/lib/toast/Toast.svelte:
--------------------------------------------------------------------------------
1 |
30 |
31 |
38 | {#each toaster.toasts as message (message.id)}
39 |
toaster.remove(message.id)}
41 | in:fly={{ opacity: 0, x: 100 }}
42 | out:fade
43 | animate:flip
44 | style="margin-top: var(--vs-s);"
45 | >
46 |
47 |
48 | {/each}
49 |
50 |
51 |
66 |
--------------------------------------------------------------------------------
/packages/decks/src/lib/toast/ToastSlice.svelte:
--------------------------------------------------------------------------------
1 |
25 |
26 |
27 |
{icon} {message.message}
28 |
29 |
30 |
31 |
65 |
--------------------------------------------------------------------------------
/packages/decks/src/lib/toast/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Toast } from './Toast.svelte';
2 | export { toaster } from './toaster.svelte';
3 |
--------------------------------------------------------------------------------
/packages/decks/src/lib/toast/toaster.svelte.ts:
--------------------------------------------------------------------------------
1 | type ToastType = 'SUCCESS' | 'INFO' | 'ERROR' | 'WARNING';
2 |
3 | interface ToastOptions {
4 | type?: ToastType;
5 | duration?: number;
6 | id?: string;
7 | }
8 |
9 | type Toast = {
10 | type: ToastType;
11 | message: string;
12 | duration: number;
13 | id: string;
14 | };
15 |
16 | function cook_toast() {
17 | let toasts: Toast[] = $state([]);
18 | const status: 'ON' | 'OFF' = $derived(toasts.length > 0 ? 'ON' : 'OFF');
19 |
20 | function send(
21 | message: string,
22 | { type = 'INFO', duration = 3000, id = 'toast-' + Math.random() }: ToastOptions = {}
23 | ) {
24 | toasts.push({ message, type, duration, id });
25 | }
26 |
27 | function clear() {
28 | toasts = [];
29 | }
30 | function info(
31 | message: string,
32 | { duration = 3000, id = 'toast-' + Math.random() }: ToastOptions = {}
33 | ) {
34 | send(message, { duration, id });
35 | }
36 |
37 | function success(
38 | message: string,
39 | { duration = 3000, id = 'toast-' + Math.random() }: ToastOptions = {}
40 | ) {
41 | send(message, { duration, id, type: 'SUCCESS' });
42 | }
43 |
44 | function error(
45 | message: string,
46 | { duration = 3000, id = 'toast-' + Math.random() }: ToastOptions = {}
47 | ) {
48 | send(message, { duration, id, type: 'ERROR' });
49 | }
50 |
51 | function warning(
52 | message: string,
53 | { duration = 3000, id = 'toast-' + Math.random() }: ToastOptions = {}
54 | ) {
55 | send(message, { duration, id, type: 'WARNING' });
56 | }
57 |
58 | function remove(id: string) {
59 | toasts = toasts.filter((toast) => toast.id !== id);
60 | }
61 |
62 | return {
63 | send,
64 | clear,
65 | remove,
66 | info,
67 | success,
68 | error,
69 | warning,
70 | get toasts() {
71 | return toasts;
72 | },
73 | get status() {
74 | return status;
75 | }
76 | };
77 | }
78 |
79 | export const toaster = cook_toast();
80 |
--------------------------------------------------------------------------------
/packages/decks/src/routes/+page.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/packages/decks/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stolinski/drop-in/bbeff69a9b5568ed49517b3936cba67a8ae0f1cc/packages/decks/static/favicon.png
--------------------------------------------------------------------------------
/packages/decks/svelte.config.js:
--------------------------------------------------------------------------------
1 | import adapter from '@sveltejs/adapter-auto';
2 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
3 |
4 | /** @type {import('@sveltejs/kit').Config} */
5 | const config = {
6 | // Consult https://kit.svelte.dev/docs/integrations#preprocessors
7 | // for more information about preprocessors
8 | preprocess: vitePreprocess(),
9 |
10 | kit: {
11 | // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
12 | // If your environment is not supported, or you settled on a specific environment, switch out the adapter.
13 | // See https://kit.svelte.dev/docs/adapters for more information about adapters.
14 | adapter: adapter()
15 | }
16 | };
17 |
18 | export default config;
19 |
--------------------------------------------------------------------------------
/packages/decks/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./.svelte-kit/tsconfig.json",
3 | "compilerOptions": {
4 | "allowJs": true,
5 | "checkJs": true,
6 | "esModuleInterop": true,
7 | "forceConsistentCasingInFileNames": true,
8 | "resolveJsonModule": true,
9 | "skipLibCheck": true,
10 | "sourceMap": true,
11 | "strict": true,
12 | "module": "NodeNext",
13 | "moduleResolution": "NodeNext"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/packages/decks/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { sveltekit } from '@sveltejs/kit/vite';
2 | import { defineConfig } from 'vite';
3 |
4 | export default defineConfig({
5 | plugins: [sveltekit()]
6 | });
7 |
--------------------------------------------------------------------------------
/packages/docs/.gitignore:
--------------------------------------------------------------------------------
1 | # build output
2 | dist/
3 | # generated types
4 | .astro/
5 |
6 | # dependencies
7 | node_modules/
8 |
9 | # logs
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 | pnpm-debug.log*
14 |
15 |
16 | # environment variables
17 | .env
18 | .env.production
19 |
20 | # macOS-specific files
21 | .DS_Store
22 |
--------------------------------------------------------------------------------
/packages/docs/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["astro-build.astro-vscode"],
3 | "unwantedRecommendations": []
4 | }
5 |
--------------------------------------------------------------------------------
/packages/docs/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "command": "./node_modules/.bin/astro dev",
6 | "name": "Development server",
7 | "request": "launch",
8 | "type": "node-terminal"
9 | }
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/packages/docs/astro.config.mjs:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | import { defineConfig } from 'astro/config';
3 | import starlight from '@astrojs/starlight';
4 |
5 | // https://astro.build/config
6 | export default defineConfig({
7 | integrations: [
8 | starlight({
9 | title: 'Drop In',
10 | logo: {
11 | dark: './src/assets/drop-in.svg',
12 | light: './src/assets/drop-in-light.svg',
13 | replacesTitle: true,
14 | },
15 | customCss: ['./src/styles/custom.css'],
16 | editLink: {
17 | baseUrl: 'https://github.com/stolinski/drop-in/edit/docs/',
18 | },
19 | social: [{ icon: 'github', label: 'GitHub', href: 'https://github.com/stolinski/drop-in' }],
20 | sidebar: [
21 | {
22 | label: 'Guides',
23 | items: [
24 | // Each item here is one entry in the navigation menu.
25 | { label: 'Get Started', slug: 'guides/get-started' },
26 | ],
27 | },
28 | {
29 | label: '@drop-in/pass',
30 | autogenerate: { directory: 'pass' },
31 | },
32 | {
33 | label: '@drop-in/beeper',
34 | autogenerate: { directory: 'beeper' },
35 | },
36 | {
37 | label: '@drop-in/ramps',
38 | autogenerate: { directory: 'ramps' },
39 | },
40 | {
41 | label: '@drop-in/plugin',
42 | autogenerate: { directory: 'plugin' },
43 | },
44 | ],
45 | }),
46 | ],
47 | });
48 |
--------------------------------------------------------------------------------
/packages/docs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@drop-in/docs",
3 | "type": "module",
4 | "version": "0.0.1",
5 | "private": true,
6 | "scripts": {
7 | "dev": "astro dev",
8 | "start": "astro dev",
9 | "build": "astro build",
10 | "preview": "astro preview",
11 | "astro": "astro"
12 | },
13 | "dependencies": {
14 | "@astrojs/starlight": "^0.34.2",
15 | "astro": "^5.6.1",
16 | "sharp": "^0.32.5"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/packages/docs/public/favicon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/docs/src/content.config.ts:
--------------------------------------------------------------------------------
1 | import { defineCollection } from 'astro:content';
2 | import { docsLoader } from '@astrojs/starlight/loaders';
3 | import { docsSchema } from '@astrojs/starlight/schema';
4 |
5 | export const collections = {
6 | docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }),
7 | };
8 |
--------------------------------------------------------------------------------
/packages/docs/src/content/docs/beeper/reference.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Beeper Reference
3 | description: A reference page for Beeper
4 | ---
5 |
6 | ## What is it?
7 |
8 | Beeper is a lightweight wrapper around NodeMailer that connects to `@drop-in/pass` so that user verification and messages work. It also allows for easy setup via `drop-in.config.js`.
9 |
10 | You can use any other email client at any point if you'd like for generally sending emails or you can import and use Beeper on the serverside.
11 |
12 | ## Methods
13 |
14 | TODO
15 |
--------------------------------------------------------------------------------
/packages/docs/src/content/docs/index.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Local data done fast
3 | description: Get started building your local data app quickly with Drop In.
4 | template: splash
5 | hero:
6 | tagline: Drop in to local first and grind on some apps with no friction.
7 | actions:
8 | - text: Get Started
9 | link: /guides/get-started/
10 | icon: right-arrow
11 | - text: Read the docs
12 | link: /reference
13 | variant: minimal
14 | ---
15 |
16 | import { Card, CardGrid } from '@astrojs/starlight/components';
17 |
18 | ## Next steps
19 |
20 |
21 |
22 | Learn more about Zero [the Zero Docs](https://zero.rocicorp.dev/).
23 |
24 |
25 | Learn more about Zero Svelte [the Zero Svelte Repo](https://github.com/stolinski/zero-svelte).
26 |
27 |
28 |
--------------------------------------------------------------------------------
/packages/docs/src/content/docs/plugin/reference.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Reference
3 | description: A reference page for Pass, the Drop In auth.
4 | ---
5 |
6 | Reference pages are ideal for outlining how things work in terse and clear terms.
7 | Less concerned with telling a story or addressing a specific use case, they should give a comprehensive outline of what you're documenting.
8 |
--------------------------------------------------------------------------------
/packages/docs/src/content/docs/ramps/reference.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Ramps Reference
3 | description: A reference page for Ramps, the Drop In Tempates.
4 | ---
5 |
6 | Reference pages are ideal for outlining how things work in terse and clear terms.
7 | Less concerned with telling a story or addressing a specific use case, they should give a comprehensive outline of what you're documenting.
8 |
--------------------------------------------------------------------------------
/packages/docs/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "astro/tsconfigs/strict",
3 | "include": [".astro/types.d.ts", "**/*"],
4 | "exclude": ["dist"]
5 | }
6 |
--------------------------------------------------------------------------------
/packages/graffiti/README.md:
--------------------------------------------------------------------------------
1 | # @drop-in/graffiti
2 |
3 | This is just base css that you can use as a starter for css in any kind of project. Install as a pacakge for vite based apps, or use the cli to add the .css file to your /src folder.
4 |
5 | ## Copy Into Your Project
6 |
7 | `npx @drop-in/graffiti`
8 |
9 | ## OR, Install and Import in Vite apps
10 |
11 | `npm install @drop-in/graffiti`
12 |
13 | Then in your root layout,
14 |
15 | `import @drop-in/graffiti`
16 |
--------------------------------------------------------------------------------
/packages/graffiti/bin.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import { promises as fs } from 'fs';
4 | import path from 'path';
5 | import { fileURLToPath } from 'url';
6 |
7 | const __filename = fileURLToPath(import.meta.url);
8 | const __dirname = path.dirname(__filename);
9 |
10 | // Function to copy the file
11 | async function copyFile(source, destination) {
12 | try {
13 | // Ensure the src directory exists
14 | const srcDir = path.dirname(destination);
15 | await fs.mkdir(srcDir, { recursive: true });
16 |
17 | // Copy the file
18 | await fs.copyFile(source, destination);
19 | console.log(`Successfully copied drop-in.css to ${destination}`);
20 | } catch (err) {
21 | console.error('Error occurred while copying the file:', err);
22 | process.exit(1);
23 | }
24 | }
25 |
26 | // Path to the source file in the package
27 | const sourcePath = path.join(__dirname, 'drop-in.css');
28 |
29 | // Path to the destination in the user's project
30 | const destinationPath = path.join(process.cwd(), 'src', 'drop-in.css');
31 |
32 | // Perform the copy operation
33 | copyFile(sourcePath, destinationPath);
34 |
--------------------------------------------------------------------------------
/packages/graffiti/build.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs/promises';
2 | import path from 'path';
3 | import { fileURLToPath } from 'url';
4 |
5 | const __filename = fileURLToPath(import.meta.url);
6 | const __dirname = path.dirname(__filename);
7 |
8 | async function buildCssModule() {
9 | try {
10 | const css = await fs.readFile(path.join(__dirname, 'drop-in.css'), 'utf8');
11 | const js = `const css = ${JSON.stringify(css)};\nexport default css;`;
12 | await fs.writeFile(path.join(__dirname, 'raw.js'), js);
13 | console.log('Successfully built raw.js');
14 | } catch (error) {
15 | console.error('Error building raw.js:', error);
16 | }
17 | }
18 |
19 | buildCssModule();
20 |
--------------------------------------------------------------------------------
/packages/graffiti/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Graffiti Debug
9 |
10 |
11 |
12 |
13 | Extra Large
14 | Large Heading
15 | Med Heading
16 | Small Heading
17 | Xtra Small Heading
18 | Base Heading
19 | Small Text Heading
20 |
21 |
22 | Learning to drop in on a skateboard is an exciting and challenging milestone for any
23 | skateboarder. Before attempting to drop in, ensure that you have a solid foundation of basic
24 | skateboarding skills, such as balancing, pushing, and stopping. It's also crucial to wear
25 | proper protective gear, including a helmet, wrist guards, elbow pads, and knee pads.
26 |
27 |
28 |
29 | .readable - Readable Text - To begin, find a suitable ramp or quarter pipe that matches your
30 | skill level. Start by standing on the deck of the ramp, with your skateboard positioned
31 | perpendicular to the coping (the metal edge at the top of the ramp). Place your front foot on
32 | the bolts of your skateboard, near the front truck, and your back foot on the tail of the
33 | board.
34 |
35 |
36 |
40 |
41 | Learning to drop in on a skateboard is an exciting and challenging milestone for any
42 | skateboarder. Before attempting to drop in, ensure that you have a solid foundation of basic
43 | skateboarding skills, such as balancing, pushing, and stopping. It's also crucial to wear
44 | proper protective gear, including a helmet, wrist guards, elbow pads, and knee pads.
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/packages/graffiti/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@drop-in/graffiti",
3 | "version": "0.4.0",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "@drop-in/graffiti",
9 | "version": "0.2.0",
10 | "license": "ISC",
11 | "dependencies": {
12 | "@drop-in/decks": "^0.0.10"
13 | },
14 | "bin": {
15 | "graffiti": "bin.js"
16 | }
17 | },
18 | "version": "0.0.10",
19 | "dependencies": {
20 | "@drop-in/graffiti": "workspace:^",
21 | "@oddbird/popover-polyfill": "^0.4.4"
22 | },
23 | "devDependencies": {
24 | "@sveltejs/adapter-auto": "^3.2.4",
25 | "@sveltejs/kit": "^2.5.27",
26 | "@sveltejs/package": "^2.3.5",
27 | "@sveltejs/vite-plugin-svelte": "4.0.0-next.7",
28 | "@types/eslint": "^9.6.1",
29 | "eslint": "^9.10.0",
30 | "eslint-config-prettier": "^9.1.0",
31 | "eslint-plugin-svelte": "^2.44.0",
32 | "globals": "^15.0.0",
33 | "prettier": "^3.1.1",
34 | "prettier-plugin-svelte": "^3.1.2",
35 | "publint": "^0.2.10",
36 | "svelte": "5.0.0-next.244",
37 | "svelte-check": "^4.0.2",
38 | "typescript": "^5.6.2",
39 | "typescript-eslint": "^8.5.0",
40 | "vite": "^5.4.5"
41 | },
42 | "engines": {
43 | "node": ">20.11.1"
44 | },
45 | "peerDependencies": {
46 | "svelte": "^5.0.0"
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/packages/graffiti/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Scott Tolinski",
3 | "bin": {
4 | "graffiti": "bin.js"
5 | },
6 | "description": "A dope package to theme @drop-in",
7 | "exports": {
8 | ".": "./drop-in.css",
9 | "./drop-in.css": "./drop-in.css",
10 | "./raw": "./raw.js"
11 | },
12 | "files": [
13 | "drop-in.css",
14 | "raw.js",
15 | "build.js"
16 | ],
17 | "license": "ISC",
18 | "main": "drop-in.css",
19 | "name": "@drop-in/graffiti",
20 | "scripts": {
21 | "test": "echo \"Error: no test specified\" && exit 1",
22 | "build": "node build.js",
23 | "package": "node build.js"
24 | },
25 | "type": "module",
26 | "version": "0.3.4",
27 | "publishConfig": {
28 | "access": "public",
29 | "directory": "."
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/packages/pass/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | /build
3 | /.svelte-kit
4 | /package
5 | .env
6 | .env.*
7 | !.env.example
8 |
9 | /dist
10 | .DS_Store
--------------------------------------------------------------------------------
/packages/pass/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # @drop-in/pass
2 |
3 | ## 0.0.36
4 |
5 | ### Patch Changes
6 |
7 | - 9ea23b5: Fixes early logout issues
8 |
--------------------------------------------------------------------------------
/packages/pass/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@drop-in/pass",
3 | "version": "0.0.36",
4 | "description": "Your drop-in season pass. aka Auth",
5 | "main": "dist/index.js",
6 | "type": "module",
7 | "exports": {
8 | ".": {
9 | "types": "./dist/index.d.ts",
10 | "default": "./dist/index.js"
11 | },
12 | "./client": {
13 | "types": "./dist/client/index.d.ts",
14 | "default": "./dist/client/index.js"
15 | },
16 | "./schema": {
17 | "types": "./dist/schema.d.ts",
18 | "default": "./dist/schema.js"
19 | },
20 | "./routes": {
21 | "types": "./dist/routes.d.ts",
22 | "default": "./dist/routes.js"
23 | }
24 | },
25 | "files": [
26 | "dist/"
27 | ],
28 | "scripts": {
29 | "dev": "tsc --watch",
30 | "test": "echo \"Error: no test specified\" && exit 1",
31 | "prepublish": "tsc",
32 | "build": "tsc",
33 | "package": "tsc",
34 | "ebuild": "esbuild src/index.ts --bundle --platform=node --format=esm --outdir=dist --external:pg --external:crypto --external:bcryptjs --external:fs --external:path --external:os --external:util --external:events"
35 | },
36 | "author": "Scott Tolinski",
37 | "license": "ISC",
38 | "dependencies": {
39 | "@drop-in/beeper": "workspace:^",
40 | "bcryptjs": "^3.0.2",
41 | "drizzle-orm": "^0.43.1",
42 | "jose": "^6.0.10",
43 | "js-cookie": "^3.0.5",
44 | "jsonwebtoken": "^9.0.2",
45 | "nanoid": "^5.1.5",
46 | "parse-nested-form-data": "^1.0.0",
47 | "pg": "^8.15.6",
48 | "postgres": "^3.4.5",
49 | "typescript": "^5.8.3"
50 | },
51 | "devDependencies": {
52 | "@sveltejs/kit": "^2.20.7",
53 | "@types/bcrypt": "^5.0.2",
54 | "@types/bcryptjs": "^3.0.0",
55 | "@types/js-cookie": "^3.0.6",
56 | "@types/jsonwebtoken": "^9.0.9",
57 | "@types/pg": "^8.11.14"
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/packages/pass/src/app.d.ts:
--------------------------------------------------------------------------------
1 | declare global {
2 | const DROP_IN: {
3 | email: {
4 | host?: string;
5 | port?: number;
6 | secure?: boolean;
7 | from?: string;
8 | };
9 | app: {
10 | url: string;
11 | name: string;
12 | route: string;
13 | };
14 | };
15 | }
16 |
17 | export {};
18 |
--------------------------------------------------------------------------------
/packages/pass/src/authenticate.ts:
--------------------------------------------------------------------------------
1 | // Authenticates the user with first the jwt access token, then the refresh token if necessary.
2 | // Returns the user_id if authenticated, null otherwise.
3 |
4 | import type { Cookies } from '@sveltejs/kit';
5 | import { get_raw_jwt } from './client_jwt.js';
6 | import { create_jwt, verify_access_token } from './jwt.js';
7 | import { jwt_cookie_options, cookie_options } from './cookies.js';
8 | import { refresh_refresh_token } from './token.js';
9 | import { verify_refresh_token } from './token.js';
10 |
11 | export async function authenticate_user(cookies: Cookies) {
12 | // Get cookies
13 | const jwt = get_raw_jwt();
14 | const refresh_token = cookies.get('refresh_token');
15 |
16 | if (jwt) {
17 | // Check if jwt access token is valid
18 | const payload = await verify_access_token(jwt);
19 |
20 | const current_time = Math.floor(Date.now() / 1000);
21 |
22 | // If the token has an expiration (exp) claim, verify it hasn't expired
23 | if (!(payload.exp && current_time > payload.exp)) {
24 | // Return the payload with your custom fields
25 | const { sub } = payload;
26 | return { user_id: sub };
27 | }
28 | } else if (refresh_token) {
29 | // Check if refresh token is valid
30 | // If it's valid, create a new jwt, update the refresh token expiration
31 | const refresh_payload = await verify_refresh_token(refresh_token);
32 | if (refresh_payload) {
33 | const jwt = await create_jwt(refresh_payload.user_id);
34 | const refresh_token = await refresh_refresh_token(refresh_payload.user_id);
35 | cookies.set('jwt', jwt, jwt_cookie_options);
36 | cookies.set('refresh_token', refresh_token, cookie_options);
37 | return { user_id: refresh_payload.user_id };
38 | }
39 | }
40 |
41 | return null;
42 | }
43 |
--------------------------------------------------------------------------------
/packages/pass/src/client/index.ts:
--------------------------------------------------------------------------------
1 | export * from './api_calls.js';
2 | export * from '../client_jwt.js';
3 |
--------------------------------------------------------------------------------
/packages/pass/src/client_jwt.ts:
--------------------------------------------------------------------------------
1 | // Client JWT utilities
2 | // These are jwt utilities that the client or the server can use, but they don't leak anything from the server.
3 |
4 | import { decodeJwt } from 'jose';
5 | import Cookies from 'js-cookie';
6 | import { JWTPayload } from './jwt.js';
7 |
8 | export function get_jwt(): JWTPayload | undefined {
9 | const token = get_raw_jwt();
10 | if (!token) {
11 | return undefined;
12 | }
13 | const payload = decodeJwt(token);
14 | const currentTime = Math.floor(Date.now() / 1000);
15 | if (payload.exp && payload.exp < currentTime) {
16 | return undefined;
17 | }
18 |
19 | return payload as JWTPayload;
20 | }
21 |
22 | export function get_raw_jwt() {
23 | return Cookies.get('jwt');
24 | }
25 |
26 | export function clear_jwt() {
27 | delete_cookie('jwt');
28 | }
29 |
30 | function delete_cookie(name: string) {
31 | Cookies.remove(name);
32 | }
33 |
34 | // Just a better named version of get_jwt for the client facing code
35 | export function get_login() {
36 | const jwt = get_raw_jwt();
37 | const payload = get_jwt();
38 | return {
39 | sub: payload?.sub,
40 | jwt,
41 | };
42 | }
43 |
--------------------------------------------------------------------------------
/packages/pass/src/cookies.ts:
--------------------------------------------------------------------------------
1 | // Cookie options
2 | // These are the options for the cookies that are set by the server
3 | // We should make them configurable with the global config.
4 | // The jwt cookie is usually very short, but since we're primarily targeting offline and local apps
5 | // I'm wondering if we should have it longer, like a week. LMK what you think.
6 |
7 | // Used for the refresh token
8 | // Longer maxAge, currently at 60 days. I dunno, when I think about mobile apps,
9 | // If I go to use them after 60 days, they don't usually make me sign in again.
10 | // But maybe they do, LMK what you think.
11 | export const cookie_options = {
12 | httpOnly: true,
13 | secure: true,
14 | path: '/',
15 | sameSite: 'strict',
16 | maxAge: 60 * 60 * 24 * 60,
17 | } as const;
18 |
19 | // The jwt cookie
20 | // This is the jwt access token, it's very short lived, like 1 week.
21 | // Could be shorter. See above note.
22 | export const jwt_cookie_options = {
23 | path: '/',
24 | maxAge: 60 * 60 * 24 * 7,
25 | httpOnly: false,
26 | sameSite: 'strict',
27 | secure: true,
28 | } as const;
29 |
--------------------------------------------------------------------------------
/packages/pass/src/db.ts:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 | import { drizzle } from 'drizzle-orm/node-postgres';
3 | import * as schema from './schema.js';
4 | import pg from 'pg';
5 | import { Resource } from 'sst';
6 |
7 | // The db connection
8 | // We use drizzle to connect to the database
9 | // We use the global drop_in_config to get the db url
10 | // This is the same db url that is in the .env file
11 |
12 | // The question here is really how much this should be possibly created in the app itself so that there aren't multiple connections
13 | // But tbh not sure how much of a problem that is. LMK what you think. The goal is to make the user do a little bit of work as possible
14 | // To get up and running.
15 | if (!process.env.DATABASE_URL) throw new Error('DATABASE_URL required');
16 | console.log('DEBUG: db-connection - ', process.env.DATABASE_URL);
17 |
18 | const pool = new pg.Pool({
19 | connectionString: process.env.DATABASE_URL,
20 | });
21 |
22 | export const db = drizzle(pool, {
23 | schema,
24 | });
25 |
--------------------------------------------------------------------------------
/packages/pass/src/index.ts:
--------------------------------------------------------------------------------
1 | // Welcome to @drop-in/pass
2 | // The season pass to your app
3 | // That's just cute language for an auth package but hey, we like to have fun around here.
4 |
5 | // This is the main entry point for the package.
6 | // Here we will export all the methods for the package.
7 | // Eventually, I'd like to expose every bit of this so that people can use it to build their on systems, but for v1 we're just trying to get it all working turnkey OOTB.
8 | // We are using JWT based auth in a cookie with refresh tokens in a httpOnly cookie
9 | // I'm open to improvements here, but want to keep it JWT for compatibility with Zero.
10 |
11 | // Routes
12 | // This might be the most interesting part, because we'll import all of the login routes and use them as a hook in the client's hooks.server.ts
13 | // See pass_routes() for the central station of auth routes.
14 | export * from './routes.js';
15 |
16 | // We also export all of the functions that are consumed by routes if you'd like to make your own routes.
17 |
18 | // Functions for creating an account
19 | export * from './sign_up.js';
20 |
21 | // Functions for logging in
22 | export * from './login.js';
23 | // Functions for logging out
24 | export * from './logout.js';
25 |
26 | // Methods for resetting password
27 | // export * from './reset_password';
28 |
29 | // Methods for deleting account
30 | // Methods for updating account
31 | // Methods for getting account
32 |
--------------------------------------------------------------------------------
/packages/pass/src/jwt.ts:
--------------------------------------------------------------------------------
1 | import { jwtVerify, SignJWT } from 'jose';
2 |
3 | export type JWTPayload = {
4 | sub: string;
5 | iat: number;
6 | exp?: number;
7 | };
8 |
9 | export async function create_jwt(user_id: string) {
10 | const jwtPayload: JWTPayload = {
11 | sub: user_id,
12 | iat: Math.floor(Date.now() / 1000),
13 | };
14 |
15 | return new SignJWT(jwtPayload)
16 | .setProtectedHeader({ alg: 'HS256' })
17 | .setExpirationTime('30days')
18 | .sign(new TextEncoder().encode(process.env.JWT_SECRET));
19 | }
20 |
21 | export async function verify_access_token(jwt: string): Promise {
22 | const { payload } = await jwtVerify(jwt, new TextEncoder().encode(process.env.JWT_SECRET));
23 | return payload as JWTPayload;
24 | }
25 |
--------------------------------------------------------------------------------
/packages/pass/src/login.ts:
--------------------------------------------------------------------------------
1 | import { get_full_user_by_email } from './find_user.js';
2 | import { verify_password } from './password.js';
3 | import { create_jwt } from './jwt.js';
4 | import { User } from './schema.js';
5 | import { create_refresh_token } from './token.js';
6 | /**
7 | * Logs in a user with the given email and password.
8 | *
9 | * @param email - The email of the user
10 | * @param password - The password of the user
11 | * @returns The user object, JWT, and refresh token if successful, null otherwise
12 | */
13 | export async function login(
14 | email: string,
15 | password: string,
16 | ): Promise<{
17 | user: Partial;
18 | jwt: string;
19 | refresh_token: string;
20 | } | null> {
21 | try {
22 | // Check if a user exists with that email
23 | const user = await get_full_user_by_email(email);
24 |
25 | if (!user) {
26 | console.log('User not found');
27 | return null;
28 | }
29 |
30 | // Is the password correct?
31 | const is_verified = await verify_password(password, user.password_hash, user.id);
32 | // If the password is not correct, return null
33 | if (!is_verified) {
34 | console.log('Password verification failed.');
35 | return null;
36 | }
37 |
38 | // Create a JWT and refresh_token
39 | const jwt = await create_jwt(user.id);
40 | const refresh_token: string = await create_refresh_token(user.id);
41 |
42 | return {
43 | user,
44 | refresh_token,
45 | jwt,
46 | };
47 | } catch (e) {
48 | console.error('Error during login:', e);
49 | if (e instanceof Error) {
50 | throw new Error(e.message);
51 | } else {
52 | throw new Error('An unknown error occurred');
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/packages/pass/src/logout.ts:
--------------------------------------------------------------------------------
1 | import { and, eq } from 'drizzle-orm';
2 | import { refresh_tokens } from './schema.js';
3 | import { db } from './db.js';
4 | import { verify_access_token } from './jwt.js';
5 |
6 | export async function logout(refresh_token: string, jwt: string): Promise {
7 | const token_id = refresh_token.split(':')[0];
8 | const payload = await verify_access_token(jwt);
9 | const user_id = payload.sub;
10 |
11 | db.delete(refresh_tokens).where(
12 | and(eq(refresh_tokens.id, token_id), eq(refresh_tokens.user_id, user_id)),
13 | );
14 | return;
15 | }
16 |
--------------------------------------------------------------------------------
/packages/pass/src/schema.ts:
--------------------------------------------------------------------------------
1 | import { InferSelectModel } from 'drizzle-orm';
2 | import { pgTable, varchar, timestamp, boolean } from 'drizzle-orm/pg-core';
3 |
4 | export const user_details = {
5 | id: varchar().primaryKey(), // 21 is a common length for nanoid
6 | email: varchar('email', { length: 255 }).notNull().unique(),
7 | password_hash: varchar('password_hash', { length: 255 }).notNull(),
8 | created_at: timestamp().defaultNow().notNull(),
9 | updated_at: timestamp().defaultNow().notNull(),
10 | verified: boolean().notNull().default(false),
11 | verification_token: varchar('verification_token', { length: 255 }),
12 | };
13 |
14 | // Define the 'user' table with a fixed schema
15 | export const user = pgTable('user', user_details);
16 |
17 | export type User = InferSelectModel;
18 |
19 | export const refresh_tokens_details = {
20 | id: varchar().primaryKey(),
21 | user_id: varchar()
22 | .notNull()
23 | .references(() => user.id, { onDelete: 'cascade' }),
24 | token: varchar('token', { length: 255 }).notNull(),
25 | created_at: timestamp().defaultNow().notNull(),
26 | expires_at: timestamp().notNull(),
27 | };
28 |
29 | export const refresh_tokens = pgTable('refresh_token', refresh_tokens_details);
30 |
31 | export type RefreshToken = InferSelectModel;
32 |
--------------------------------------------------------------------------------
/packages/pass/src/utils.ts:
--------------------------------------------------------------------------------
1 | import crypto from 'crypto';
2 |
3 | export function normalize_email(email: string): string {
4 | return decodeURIComponent(email).toLowerCase().trim();
5 | }
6 |
7 | export function is_valid_email(maybeEmail: unknown): maybeEmail is string {
8 | if (typeof maybeEmail !== 'string') return false;
9 | if (maybeEmail.length > 255) return false;
10 | const emailRegexp = /^.+@.+$/; // [one or more character]@[one or more character]
11 | return emailRegexp.test(maybeEmail);
12 | }
13 |
14 | export function check_is_password_valid(password: string): boolean {
15 | return typeof password === 'string' && password.length >= 6 && password.length <= 255;
16 | }
17 |
18 | export function create_expiring_auth_digest(email: string, expirationTimestamp: number) {
19 | const authString = `${process.env.JWT_SECRET}:${email}:${expirationTimestamp}`;
20 | return crypto.createHash('sha256').update(authString).digest('hex');
21 | }
22 |
23 | export function generate_token(length: number = 32) {
24 | return crypto.randomBytes(length).toString('hex');
25 | }
26 |
27 | /**
28 | * Hashes a password using SHA-256 and returns the hexadecimal string.
29 | *
30 | * @param password - The plaintext password to hash.
31 | * @returns The SHA-256 hash of the password as a hex string.
32 | */
33 | export function sha256(password: string): string {
34 | return crypto.createHash('sha256').update(password).digest('hex');
35 | }
36 |
--------------------------------------------------------------------------------
/packages/pass/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "checkJs": true,
5 | "forceConsistentCasingInFileNames": true,
6 | "target": "esnext",
7 | "module": "NodeNext",
8 | "moduleResolution": "nodenext", // change from "Bundler" to "node16"
9 | "outDir": "./dist",
10 | "strict": true,
11 | "skipLibCheck": true,
12 | "declaration": true,
13 | "declarationMap": true,
14 | "sourceMap": true,
15 | "allowSyntheticDefaultImports": true
16 | },
17 | "include": ["src/**/*"]
18 | }
19 |
--------------------------------------------------------------------------------
/packages/plugin/global.d.ts:
--------------------------------------------------------------------------------
1 | declare global {
2 | var DROP_IN: {
3 | email: {
4 | host?: string;
5 | port?: number;
6 | secure?: boolean;
7 | from?: string;
8 | };
9 | app: {
10 | url: string;
11 | name: string;
12 | route: string;
13 | };
14 | };
15 | }
16 |
17 | export {};
18 |
--------------------------------------------------------------------------------
/packages/plugin/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@drop-in/plugin",
3 | "version": "0.0.10",
4 | "description": "",
5 | "types": "global.d.ts",
6 | "type": "module",
7 | "exports": {
8 | ".": {
9 | "types": "./dist/index.js",
10 | "default": "./dist/index.js"
11 | },
12 | "./hook": {
13 | "types": "./dist/hook.js",
14 | "default": "./dist/hook.js"
15 | }
16 | },
17 | "scripts": {
18 | "dev": "tsc --watch",
19 | "test": "echo \"Error: no test specified\" && exit 1",
20 | "build": "tsc",
21 | "package": "tsc"
22 | },
23 | "keywords": [],
24 | "author": "Scott Tolinski",
25 | "license": "ISC",
26 | "devDependencies": {
27 | "typescript": "^5.8.3",
28 | "vite": "^6.3.3"
29 | },
30 | "dependencies": {
31 | "@sveltejs/kit": "^2.20.7"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/packages/plugin/src/hook.ts:
--------------------------------------------------------------------------------
1 | import type { Handle } from '@sveltejs/kit';
2 |
3 | export const drop_hook: Handle = async ({ event, resolve }) => {
4 | return await resolve(event, {
5 | transformPageChunk: async ({ html }) => {
6 | const head = ``;
7 | return html.replace('', head + '');
8 | },
9 | });
10 | };
11 |
--------------------------------------------------------------------------------
/packages/plugin/src/index.ts:
--------------------------------------------------------------------------------
1 | ///
2 | import type { Plugin, ResolvedConfig } from 'vite';
3 | import { resolve } from 'path';
4 | import { pathToFileURL } from 'node:url';
5 | import { normalize } from 'node:path';
6 |
7 | export default function dropin(): Plugin {
8 | let config: ResolvedConfig;
9 | let configModule: any;
10 |
11 | return {
12 | name: 'vite-plugin-global-config',
13 |
14 | configResolved(resolvedConfig: ResolvedConfig) {
15 | config = resolvedConfig;
16 | },
17 |
18 | async configureServer() {
19 | const configFilePath = normalize(resolve(config.root, 'drop-in.config.js'));
20 |
21 | // Explicitly convert to file URL string
22 | const fileUrl = pathToFileURL(configFilePath).toString();
23 |
24 | configModule = await import(fileUrl);
25 | globalThis.DROP_IN = configModule.default;
26 | },
27 | };
28 | }
29 |
--------------------------------------------------------------------------------
/packages/plugin/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "checkJs": true,
5 | "forceConsistentCasingInFileNames": true,
6 | "target": "esnext",
7 | "module": "esnext",
8 | "moduleResolution": "Bundler",
9 | "outDir": "./dist",
10 | "strict": true,
11 | "skipLibCheck": true,
12 | "declaration": true,
13 | "declarationMap": true,
14 | "sourceMap": true,
15 | "types": ["./global.d.ts"]
16 | },
17 | "include": ["src/**/*", "global.d.ts"],
18 | "exclude": ["node_modules", "dist"]
19 | }
20 |
--------------------------------------------------------------------------------
/packages/ramps/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
3 | # Output
4 | .output
5 | .vercel
6 | .netlify
7 | .wrangler
8 | /.svelte-kit
9 | /build
10 | /dist
11 |
12 | # OS
13 | .DS_Store
14 | Thumbs.db
15 |
16 | # Env
17 | .env
18 | .env.*
19 | !.env.example
20 | !.env.test
21 |
22 | # Vite
23 | vite.config.js.timestamp-*
24 | vite.config.ts.timestamp-*
25 |
--------------------------------------------------------------------------------
/packages/ramps/.npmrc:
--------------------------------------------------------------------------------
1 | engine-strict=true
2 |
--------------------------------------------------------------------------------
/packages/ramps/.prettierignore:
--------------------------------------------------------------------------------
1 | # Package Managers
2 | package-lock.json
3 | pnpm-lock.yaml
4 | yarn.lock
5 |
--------------------------------------------------------------------------------
/packages/ramps/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "useTabs": true,
3 | "singleQuote": true,
4 | "trailingComma": "none",
5 | "printWidth": 100,
6 | "plugins": ["prettier-plugin-svelte"],
7 | "overrides": [
8 | {
9 | "files": "*.svelte",
10 | "options": {
11 | "parser": "svelte"
12 | }
13 | }
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/packages/ramps/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # @drop-in/ramps
2 |
3 | ## 0.0.2
4 |
5 | ### Patch Changes
6 |
7 | - Updated dependencies [9ea23b5]
8 | - @drop-in/decks@0.0.15
9 | - @drop-in/pass@0.0.36
10 |
--------------------------------------------------------------------------------
/packages/ramps/README.md:
--------------------------------------------------------------------------------
1 | # create-svelte
2 |
3 | Everything you need to build a Svelte library, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/main/packages/create-svelte).
4 |
5 | Read more about creating a library [in the docs](https://svelte.dev/docs/kit/packaging).
6 |
7 | ## Creating a project
8 |
9 | If you're seeing this, you've probably already done this step. Congrats!
10 |
11 | ```bash
12 | # create a new project in the current directory
13 | npx sv create
14 |
15 | # create a new project in my-app
16 | npx sv create my-app
17 | ```
18 |
19 | ## Developing
20 |
21 | Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
22 |
23 | ```bash
24 | npm run dev
25 |
26 | # or start the server and open the app in a new browser tab
27 | npm run dev -- --open
28 | ```
29 |
30 | Everything inside `src/lib` is part of your library, everything inside `src/routes` can be used as a showcase or preview app.
31 |
32 | ## Building
33 |
34 | To build your library:
35 |
36 | ```bash
37 | npm run package
38 | ```
39 |
40 | To create a production version of your showcase app:
41 |
42 | ```bash
43 | npm run build
44 | ```
45 |
46 | You can preview the production build with `npm run preview`.
47 |
48 | > To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.
49 |
50 | ## Publishing
51 |
52 | Go into the `package.json` and give your package the desired name through the `"name"` option. Also consider adding a `"license"` field and point it to a `LICENSE` file which you can create from a template (one popular option is the [MIT license](https://opensource.org/license/mit/)).
53 |
54 | To publish your library to [npm](https://www.npmjs.com):
55 |
56 | ```bash
57 | npm publish
58 | ```
59 |
--------------------------------------------------------------------------------
/packages/ramps/eslint.config.js:
--------------------------------------------------------------------------------
1 | import prettier from 'eslint-config-prettier';
2 | import js from '@eslint/js';
3 | import { includeIgnoreFile } from '@eslint/compat';
4 | import svelte from 'eslint-plugin-svelte';
5 | import globals from 'globals';
6 | import { fileURLToPath } from 'node:url';
7 | import ts from 'typescript-eslint';
8 | const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url));
9 |
10 | export default ts.config(
11 | includeIgnoreFile(gitignorePath),
12 | js.configs.recommended,
13 | ...ts.configs.recommended,
14 | ...svelte.configs['flat/recommended'],
15 | prettier,
16 | ...svelte.configs['flat/prettier'],
17 | {
18 | languageOptions: {
19 | globals: {
20 | ...globals.browser,
21 | ...globals.node
22 | }
23 | }
24 | },
25 | {
26 | files: ['**/*.svelte'],
27 |
28 | languageOptions: {
29 | parserOptions: {
30 | parser: ts.parser
31 | }
32 | }
33 | }
34 | );
35 |
--------------------------------------------------------------------------------
/packages/ramps/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@drop-in/ramps",
3 | "version": "0.0.2",
4 | "publishConfig": {
5 | "access": "public"
6 | },
7 | "scripts": {
8 | "dev": "vite dev",
9 | "build": "vite build && npm run prepack",
10 | "preview": "vite preview",
11 | "prepare": "svelte-kit sync || echo ''",
12 | "prepack": "svelte-kit sync && svelte-package && publint",
13 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
14 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
15 | "package": "svelte-kit sync && svelte-package --watch",
16 | "format": "prettier --write .",
17 | "lint": "prettier --check . && eslint ."
18 | },
19 | "files": [
20 | "dist",
21 | "!dist/**/*.test.*",
22 | "!dist/**/*.spec.*"
23 | ],
24 | "sideEffects": [
25 | "**/*.css"
26 | ],
27 | "svelte": "./dist/index.js",
28 | "types": "./dist/index.d.ts",
29 | "type": "module",
30 | "exports": {
31 | ".": {
32 | "types": "./dist/index.d.ts",
33 | "svelte": "./dist/index.js"
34 | }
35 | },
36 | "peerDependencies": {
37 | "svelte": "^5.0.0"
38 | },
39 | "dependencies": {
40 | "@drop-in/decks": "workspace:^",
41 | "@drop-in/pass": "workspace:^"
42 | },
43 | "devDependencies": {
44 | "@eslint/compat": "^1.2.8",
45 | "@eslint/js": "^9.25.1",
46 | "@sveltejs/adapter-auto": "^6.0.0",
47 | "@sveltejs/kit": "^2.20.7",
48 | "@sveltejs/package": "^2.3.11",
49 | "@sveltejs/vite-plugin-svelte": "^5.0.0",
50 | "eslint": "^9.25.1",
51 | "eslint-config-prettier": "^10.1.2",
52 | "eslint-plugin-svelte": "^3.5.1",
53 | "globals": "^16.0.0",
54 | "prettier": "^3.5.3",
55 | "prettier-plugin-svelte": "^3.3.3",
56 | "publint": "^0.3.12",
57 | "svelte": "^5.28.2",
58 | "svelte-check": "^4.1.6",
59 | "typescript": "^5.8.3",
60 | "typescript-eslint": "^8.31.0",
61 | "vite": "^6.3.3"
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/packages/ramps/src/app.d.ts:
--------------------------------------------------------------------------------
1 | // See https://svelte.dev/docs/kit/types#app.d.ts
2 | // for information about these interfaces
3 | declare global {
4 | namespace App {
5 | // interface Error {}
6 | // interface Locals {}
7 | // interface PageData {}
8 | // interface PageState {}
9 | // interface Platform {}
10 | }
11 |
12 | const DROP_IN: {
13 | email: {
14 | host?: string;
15 | port?: number;
16 | secure?: boolean;
17 | from?: string;
18 | };
19 | app: {
20 | url: string;
21 | name: string;
22 | route: string;
23 | };
24 | };
25 | }
26 |
27 | export {};
28 |
--------------------------------------------------------------------------------
/packages/ramps/src/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | %sveltekit.head%
8 |
9 |
10 | %sveltekit.body%
11 |
12 |
13 |
--------------------------------------------------------------------------------
/packages/ramps/src/lib/auth/Login.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 | Login
7 |
8 |
9 | Email
10 |
11 |
12 |
13 | Password
14 |
15 |
16 |
17 | {#if loading}Logging in...{:else}
18 | Log Me In Please
19 | {/if}
20 |
21 |
22 |
23 |
24 | {#if auth_form.error_message}
25 |
{auth_form.error_message}
26 | {/if}
27 |
28 |
29 |
30 |
31 | Need an account?
32 | Sign Up
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/packages/ramps/src/lib/auth/Signup.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 | Sign up
7 |
8 |
9 | Email
10 |
11 |
12 |
13 | Password
14 |
15 |
16 | {#if loading}Signing up...{:else}
18 | Sign Me Up
19 | {/if}
21 |
22 |
23 | {#if auth_form.error_message}
24 |
{auth_form.error_message}
25 | {/if}
26 |
27 |
28 |
Already have an account?
29 |
Sign in
30 |
31 |
--------------------------------------------------------------------------------
/packages/ramps/src/lib/auth/Verify.svelte:
--------------------------------------------------------------------------------
1 |
18 |
19 |
20 | {#if !verified_sent}
21 |
22 | Your email is not verified.
23 | Send Verification
24 |
25 | {:else}
26 |
Verification email sent to {user.email}. Please check your email.
27 | {/if}
28 |
29 |
30 |
42 |
--------------------------------------------------------------------------------
/packages/ramps/src/lib/auth/auth_form.svelte.ts:
--------------------------------------------------------------------------------
1 | import { goto } from '$app/navigation';
2 | import { toaster } from '@drop-in/decks';
3 |
4 | export class AuthForm {
5 | status: 'LOADING' | 'SUCCESS' | 'ERROR' | 'INITIAL' = $state('INITIAL');
6 | error_message: string | undefined = $state();
7 |
8 | loading() {
9 | this.status = 'LOADING';
10 | }
11 |
12 | error(e_message: string) {
13 | toaster.error(e_message);
14 | this.status = 'ERROR';
15 | this.error_message = e_message;
16 | }
17 |
18 | success(route: string | boolean = DROP_IN.app.route, message: string = 'Success') {
19 | // Reset the Zero instance
20 | toaster.success(message);
21 | this.error_message = undefined;
22 | this.status = 'SUCCESS';
23 | // Redirect to wherever post login
24 | if (route && typeof route === 'string') goto(route, { invalidateAll: true });
25 | }
26 | }
27 |
28 | // TODO: Magic Link
29 |
--------------------------------------------------------------------------------
/packages/ramps/src/lib/index.ts:
--------------------------------------------------------------------------------
1 | // Reexport your entry components here
2 | import { AuthForm } from './auth/auth_form.svelte.js';
3 | import Login from './auth/Login.svelte';
4 | import Signup from './auth/Signup.svelte';
5 | import Verify from './auth/Verify.svelte';
6 |
7 | export { Login, AuthForm, Signup, Verify };
8 |
--------------------------------------------------------------------------------
/packages/ramps/src/routes/+page.svelte:
--------------------------------------------------------------------------------
1 | Welcome to your library project
2 | Create your package using @sveltejs/package and preview/showcase your work with SvelteKit
3 | Visit svelte.dev/docs/kit to read the documentation
4 |
--------------------------------------------------------------------------------
/packages/ramps/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stolinski/drop-in/bbeff69a9b5568ed49517b3936cba67a8ae0f1cc/packages/ramps/static/favicon.png
--------------------------------------------------------------------------------
/packages/ramps/svelte.config.js:
--------------------------------------------------------------------------------
1 | import adapter from '@sveltejs/adapter-auto';
2 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
3 |
4 | /** @type {import('@sveltejs/kit').Config} */
5 | const config = {
6 | // Consult https://svelte.dev/docs/kit/integrations
7 | // for more information about preprocessors
8 | preprocess: vitePreprocess(),
9 |
10 | kit: {
11 | // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
12 | // If your environment is not supported, or you settled on a specific environment, switch out the adapter.
13 | // See https://svelte.dev/docs/kit/adapters for more information about adapters.
14 | adapter: adapter()
15 | }
16 | };
17 |
18 | export default config;
19 |
--------------------------------------------------------------------------------
/packages/ramps/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./.svelte-kit/tsconfig.json",
3 | "compilerOptions": {
4 | "allowJs": true,
5 | "checkJs": true,
6 | "esModuleInterop": true,
7 | "forceConsistentCasingInFileNames": true,
8 | "resolveJsonModule": true,
9 | "skipLibCheck": true,
10 | "sourceMap": true,
11 | "strict": true,
12 | "module": "NodeNext",
13 | "moduleResolution": "NodeNext"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/packages/ramps/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { sveltekit } from '@sveltejs/kit/vite';
2 | import { defineConfig } from 'vite';
3 |
4 | export default defineConfig({
5 | plugins: [sveltekit()]
6 | });
7 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - '.'
3 | - 'packages/**'
4 | - 'templates/**'
5 | - 'demos/**'
6 |
--------------------------------------------------------------------------------
/templates/z/.cursorrules:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stolinski/drop-in/bbeff69a9b5568ed49517b3936cba67a8ae0f1cc/templates/z/.cursorrules
--------------------------------------------------------------------------------
/templates/z/.eslintignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /.svelte-kit
5 | /package
6 | .env
7 | .env.*
8 | !.env.example
9 |
10 | # Ignore files for PNPM, NPM and YARN
11 | pnpm-lock.yaml
12 | package-lock.json
13 | yarn.lock
14 |
--------------------------------------------------------------------------------
/templates/z/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | /** @type { import("eslint").Linter.Config } */
2 | module.exports = {
3 | root: true,
4 | extends: [
5 | 'eslint:recommended',
6 | 'plugin:@typescript-eslint/recommended',
7 | 'plugin:svelte/recommended',
8 | 'prettier',
9 | ],
10 | parser: '@typescript-eslint/parser',
11 | plugins: ['@typescript-eslint'],
12 | parserOptions: {
13 | sourceType: 'module',
14 | ecmaVersion: 2020,
15 | extraFileExtensions: ['.svelte'],
16 | },
17 | env: {
18 | browser: true,
19 | es2017: true,
20 | node: true,
21 | },
22 | overrides: [
23 | {
24 | files: ['*.svelte'],
25 | parser: 'svelte-eslint-parser',
26 | parserOptions: {
27 | parser: '@typescript-eslint/parser',
28 | },
29 | },
30 | ],
31 | };
32 |
--------------------------------------------------------------------------------
/templates/z/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | /build
3 | /.svelte-kit
4 | /package
5 | .env
6 | .env.*
7 | !.env.example
8 |
9 | .DS_Store
--------------------------------------------------------------------------------
/templates/z/.gitignore.txt:
--------------------------------------------------------------------------------
1 | node_modules
2 | /build
3 | /.svelte-kit
4 | /package
5 | .env
6 | .env.*
7 | !.env.example
8 |
--------------------------------------------------------------------------------
/templates/z/.meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "An app with built in auth, email sending, drizzle for ORM and local first sync via Zero",
3 | "title": "Local First with Zero"
4 | }
5 |
--------------------------------------------------------------------------------
/templates/z/.node-version:
--------------------------------------------------------------------------------
1 | 17.0.1
--------------------------------------------------------------------------------
/templates/z/.npmrc:
--------------------------------------------------------------------------------
1 | engine-strict=true
2 | link-workspace-packages=true
3 |
--------------------------------------------------------------------------------
/templates/z/.prettierignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /.svelte-kit
5 | /package
6 | .env
7 | .env.*
8 | !.env.example
9 |
10 | # Ignore files for PNPM, NPM and YARN
11 | pnpm-lock.yaml
12 | package-lock.json
13 | yarn.lock
14 |
--------------------------------------------------------------------------------
/templates/z/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "ignoreFiles": ["package.json"],
3 | "overrides": [
4 | {
5 | "files": "*.svelte",
6 | "options": {
7 | "parser": "svelte"
8 | }
9 | }
10 | ],
11 | "plugins": ["prettier-plugin-svelte"],
12 | "printWidth": 100,
13 | "singleQuote": true,
14 | "trailingComma": "none",
15 | "useTabs": true
16 | }
17 |
--------------------------------------------------------------------------------
/templates/z/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # z
2 |
3 | ## 0.0.5
4 |
5 | ### Patch Changes
6 |
7 | - Updated dependencies [9ea23b5]
8 | - @drop-in/decks@0.0.15
9 | - @drop-in/pass@0.0.36
10 | - @drop-in/ramps@0.0.2
11 |
--------------------------------------------------------------------------------
/templates/z/docker/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.9'
2 |
3 | services:
4 | # Existing Postgres service
5 | zstart_postgres:
6 | image: postgres:16.2-alpine
7 | shm_size: 1g
8 | user: postgres
9 | restart: always
10 | healthcheck:
11 | test: 'pg_isready -U user --dbname=postgres'
12 | interval: 10s
13 | timeout: 5s
14 | retries: 5
15 | ports:
16 | - 5430:5432
17 | environment:
18 | POSTGRES_USER: user
19 | POSTGRES_DB: postgres
20 | POSTGRES_PASSWORD: password
21 | command: |
22 | postgres
23 | -c wal_level=logical
24 | -c max_wal_senders=10
25 | -c max_replication_slots=5
26 | -c hot_standby=on
27 | -c hot_standby_feedback=on
28 | volumes:
29 | - zstart_pgdata:/var/lib/postgresql/data
30 | - ./:/docker-entrypoint-initdb.d
31 |
32 | # New Zero service
33 | zstart_zero:
34 | image: rocicorp/zero:canary
35 | restart: always
36 | # Add ports/env/volumes if needed
37 |
38 | volumes:
39 | zstart_pgdata:
40 | driver: local
41 |
--------------------------------------------------------------------------------
/templates/z/drizzle.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'drizzle-kit';
2 |
3 | export default defineConfig({
4 | schema: './src/lib/data/db_schema.ts',
5 | dialect: 'postgresql',
6 | dbCredentials: {
7 | url: process.env.DATABASE_URL,
8 | },
9 | });
10 |
--------------------------------------------------------------------------------
/templates/z/drizzle/0000_grey_moonstone.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE IF NOT EXISTS "profile" (
2 | "id" varchar PRIMARY KEY NOT NULL,
3 | "user_id" varchar NOT NULL
4 | );
5 | --> statement-breakpoint
6 | CREATE TABLE IF NOT EXISTS "refresh_token" (
7 | "id" varchar PRIMARY KEY NOT NULL,
8 | "user_id" varchar NOT NULL,
9 | "token" varchar(255) NOT NULL,
10 | "created_at" timestamp DEFAULT now() NOT NULL,
11 | "expires_at" timestamp NOT NULL
12 | );
13 | --> statement-breakpoint
14 | CREATE TABLE IF NOT EXISTS "user" (
15 | "id" varchar PRIMARY KEY NOT NULL,
16 | "email" varchar(255) NOT NULL,
17 | "password_hash" varchar(255) NOT NULL,
18 | "created_at" timestamp DEFAULT now() NOT NULL,
19 | "updated_at" timestamp DEFAULT now() NOT NULL,
20 | "verified" boolean DEFAULT false NOT NULL,
21 | "verification_token" varchar(255),
22 | CONSTRAINT "user_email_unique" UNIQUE("email")
23 | );
24 | --> statement-breakpoint
25 | DO $$ BEGIN
26 | ALTER TABLE "profile" ADD CONSTRAINT "profile_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
27 | EXCEPTION
28 | WHEN duplicate_object THEN null;
29 | END $$;
30 | --> statement-breakpoint
31 | DO $$ BEGIN
32 | ALTER TABLE "refresh_token" ADD CONSTRAINT "refresh_token_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
33 | EXCEPTION
34 | WHEN duplicate_object THEN null;
35 | END $$;
36 |
--------------------------------------------------------------------------------
/templates/z/drizzle/0001_petite_roland_deschain.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE "profile" ADD COLUMN "username" varchar NOT NULL;
--------------------------------------------------------------------------------
/templates/z/drizzle/0002_happy_boomer.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE "profile" RENAME COLUMN "username" TO "name";
--------------------------------------------------------------------------------
/templates/z/drizzle/0003_big_flatman.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE "profile" ADD COLUMN "avatar" varchar;
--------------------------------------------------------------------------------
/templates/z/drizzle/meta/_journal.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "7",
3 | "dialect": "postgresql",
4 | "entries": [
5 | {
6 | "idx": 0,
7 | "version": "7",
8 | "when": 1729802614860,
9 | "tag": "0000_grey_moonstone",
10 | "breakpoints": true
11 | },
12 | {
13 | "idx": 1,
14 | "version": "7",
15 | "when": 1729875764211,
16 | "tag": "0001_petite_roland_deschain",
17 | "breakpoints": true
18 | },
19 | {
20 | "idx": 2,
21 | "version": "7",
22 | "when": 1729881836443,
23 | "tag": "0002_happy_boomer",
24 | "breakpoints": true
25 | },
26 | {
27 | "idx": 3,
28 | "version": "7",
29 | "when": 1731085971419,
30 | "tag": "0003_big_flatman",
31 | "breakpoints": true
32 | }
33 | ]
34 | }
--------------------------------------------------------------------------------
/templates/z/drop-in.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | email: {
3 | from: 'fake@changeme.com'
4 | },
5 | app: {
6 | url: 'http://localhost:5173',
7 | name: 'Drop In',
8 | route: '/dashboard'
9 | }
10 | };
11 |
--------------------------------------------------------------------------------
/templates/z/example.env:
--------------------------------------------------------------------------------
1 | # This is the server of the local zero cache server, usually will be http://127.0.0.1:4848 unless you have changed it.
2 | PUBLIC_SERVER=http://127.0.0.1:4848
3 |
4 | # Database URL, should be your postgres database
5 | DATABASE_URL=""
6 |
7 | # * These must be the same so that zero can verify the JWT
8 | # * Generate a random string.
9 | JWT_SECRET=""
10 | ZERO_JWT_SECRET=""
11 |
12 | # * These are needed for Zero Cache
13 | # In the future we will support other types of upstreams besides PG
14 | ZERO_UPSTREAM_DB = ""
15 |
16 | # A separate Postgres database we use to store CVRs. CVRs (client view records)
17 | # keep track of which clients have which data. This is how we know what diff to
18 | # send on reconnect. It can be same database as above, but in production it
19 | # can make sense to have it be separate to scale them seperately.
20 | ZERO_CVR_DB = ""
21 |
22 | # Yet another Postgres database which we used to store a replication log.
23 | ZERO_CHANGE_DB = ""
24 |
25 | # Uniquely identifies a single instance of the zero-cache service.
26 | REPLICA_ID = "r1"
27 |
28 | # Place to store the SQLite data zero-cache maintains. This can be lost, but if
29 | # it is, zero-cache will have to re-replicate next time it starts up.
30 | ZERO_REPLICA_FILE = "/tmp/dropin-sync-replica.db"
31 |
32 | # Logging level for zero-cache service.
33 | ZERO_LOG_LEVEL = "debug"
--------------------------------------------------------------------------------
/templates/z/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "dependencies": {
4 | "@drop-in/beeper": "workspace:^",
5 | "@drop-in/decks": "workspace:^",
6 | "@drop-in/graffiti": "workspace:^",
7 | "@drop-in/pass": "workspace:^",
8 | "@drop-in/plugin": "workspace:^",
9 | "@drop-in/ramps": "workspace:^",
10 | "@rocicorp/zero": "^0.18.2025042300",
11 | "drizzle-orm": "^0.43.1",
12 | "drizzle-zero": "^0.9.1",
13 | "nanoid": "^5.1.5",
14 | "pg": "^8.15.6",
15 | "zero-svelte": "^0.3.3"
16 | },
17 | "devDependencies": {
18 | "@sveltejs/adapter-auto": "^6.0.0",
19 | "@sveltejs/kit": "^2.20.7",
20 | "@sveltejs/vite-plugin-svelte": "^5.0.3",
21 | "@types/pg": "^8.11.14",
22 | "@typescript-eslint/eslint-plugin": "^8.31.0",
23 | "@typescript-eslint/parser": "^8.31.0",
24 | "drizzle-kit": "^0.31.0",
25 | "prettier": "^3.5.3",
26 | "prettier-plugin-svelte": "^3.3.3",
27 | "svelte": "^5.28.2",
28 | "svelte-check": "^4.1.6",
29 | "svelte-preprocess": "^6.0.2",
30 | "tslib": "^2.8.1",
31 | "typescript": "^5.8.3",
32 | "vite": "^6.3.3"
33 | },
34 | "engines": {
35 | "node": ">20.11.1"
36 | },
37 | "name": "z",
38 | "scripts": {
39 | "start": "vite dev & pnpm dev:zero-cache & pnpm dev:db-up",
40 | "build": "vite build",
41 | "check": "svelte-check --tsconfig ./tsconfig.json",
42 | "check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
43 | "format": "prettier --write --plugin-search-dir=. .",
44 | "lint": "prettier --check --plugin-search-dir=. . && eslint .",
45 | "prepare": "svelte-kit sync",
46 | "preview": "svelte-kit preview",
47 | "dev": "vite dev",
48 | "dev:zero-cache": "zero-cache-dev -p src/schema.ts",
49 | "dev:db-up": "docker compose --env-file .env -f ./docker/docker-compose.yml up",
50 | "dev:db-down": "docker compose --env-file .env -f ./docker/docker-compose.yml down",
51 | "schema:generate": "drizzle-zero generate --format"
52 | },
53 | "type": "module",
54 | "version": "0.0.5"
55 | }
56 |
--------------------------------------------------------------------------------
/templates/z/src/app.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | // See https://kit.svelte.dev/docs/types#app
4 | // for information about these interfaces
5 | // and what to do when importing types
6 | declare global {
7 | namespace App {
8 | // interface Locals {}
9 | // interface Platform {}
10 | // interface Session {}
11 | // interface Stuff {}
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/templates/z/src/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | %sveltekit.head%
7 |
8 |
9 | %sveltekit.body%
10 |
11 |
12 |
--------------------------------------------------------------------------------
/templates/z/src/db_schema.ts:
--------------------------------------------------------------------------------
1 | import { user_details, refresh_tokens_details } from '@drop-in/pass/schema';
2 | import { pgTable, varchar } from 'drizzle-orm/pg-core';
3 |
4 | export const user = pgTable('user', user_details);
5 |
6 | export const refresh_tokens = pgTable('refresh_token', refresh_tokens_details);
7 | // Put any user information you want attached to the user here by extending the profile_base.
8 | export const profile = pgTable('profile', {
9 | id: varchar().primaryKey(),
10 | user_id: varchar()
11 | .notNull()
12 | .references(() => user.id, { onDelete: 'cascade' }),
13 | name: varchar().notNull(),
14 | avatar: varchar()
15 | // Additional fields can be added here by consumers
16 | });
17 |
--------------------------------------------------------------------------------
/templates/z/src/hooks.server.ts:
--------------------------------------------------------------------------------
1 | import { sequence } from '@sveltejs/kit/hooks';
2 | import { pass_routes } from '@drop-in/pass';
3 | import { drop_hook } from '@drop-in/plugin/hook';
4 |
5 | // ? What's up with the two hooks?
6 | // Pass Routes is the hook for auth authentication, it includes the routes where auth is handeled.
7 | // Drop hook adds a global so our drop-in.config.js is read and applied to globals
8 |
9 | export const handle = sequence(pass_routes, drop_hook);
10 |
--------------------------------------------------------------------------------
/templates/z/src/lib/auth/ForgotPassword.svelte:
--------------------------------------------------------------------------------
1 |
30 |
31 | Forgot Password
32 | {#if auth.status === 'SUCCESS'}
33 | Please check your email for password reset instructions
34 | {:else}
35 |
36 |
37 | Email
38 |
39 |
40 |
41 | {#if loading}Sending...{:else}
42 | Request Password Reset
43 | {/if}
44 |
45 |
46 |
47 | {#if auth.error_message}
48 |
{auth.error_message}
49 | {/if}
50 |
51 | {/if}
52 |
53 |
54 |
55 | Need an account?
56 | Sign Up
57 |
58 |
59 | Know your account?
60 | Login
61 |
62 |
63 |
--------------------------------------------------------------------------------
/templates/z/src/lib/auth/NewEmail.svelte:
--------------------------------------------------------------------------------
1 |
3 |
--------------------------------------------------------------------------------
/templates/z/src/lib/auth/auth_form.svelte.ts:
--------------------------------------------------------------------------------
1 | import { goto } from '$app/navigation';
2 | import { toaster } from '@drop-in/decks';
3 |
4 | export class AuthForm {
5 | status: 'LOADING' | 'SUCCESS' | 'ERROR' | 'INITIAL' = $state('INITIAL');
6 | error_message: string | undefined = $state();
7 |
8 | loading() {
9 | this.status = 'LOADING';
10 | }
11 |
12 | error(e_message: string) {
13 | toaster.error(e_message);
14 | this.status = 'ERROR';
15 | this.error_message = e_message;
16 | }
17 |
18 | success(route: string | boolean = DROP_IN.app.route, message: string = 'Success') {
19 | // Reset the Zero instance
20 | toaster.success(message);
21 | this.error_message = undefined;
22 | this.status = 'SUCCESS';
23 | // Redirect to wherever post login
24 | if (route && typeof route === 'string') goto(route, { invalidateAll: true });
25 | }
26 | }
27 |
28 | // TODO: Magic Link
29 |
--------------------------------------------------------------------------------
/templates/z/src/lib/components/UserMenu.svelte:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 | {#snippet button()}
14 | {#if user?.current.profile?.avatar}
15 |
16 | {:else}
17 | {user?.current.email?.[0]}
18 | {/if}
19 | {/snippet}
20 |
21 |
Profile
22 |
23 |
pass.logout()}>Logout
24 |
25 |
26 |
27 |
42 |
--------------------------------------------------------------------------------
/templates/z/src/lib/data/db.ts:
--------------------------------------------------------------------------------
1 | import { drizzle } from 'drizzle-orm/node-postgres';
2 | import * as schema from '../../db_schema';
3 |
4 | // The db connection
5 | // We use drizzle to connect to the database
6 | // We use the global drop_in_config to get the db url
7 | // This is the same db url that is in the .env file
8 |
9 | // The question here is really how much this should be possibly created in teh app itself so that there aren't multiple connections
10 | // But tbh not sure how much of a problem that is. LMK what you think. The goal is to make the user do a little bit of work as possible
11 | // To get up and running.
12 | if (!process.env.DATABASE_URL) throw new Error('DATABASE_URL required');
13 |
14 | export const db = drizzle({
15 | connection: process.env.DATABASE_URL,
16 | schema
17 | });
18 |
--------------------------------------------------------------------------------
/templates/z/src/lib/email.ts:
--------------------------------------------------------------------------------
1 | import { Beeper } from '@drop-in/beeper';
2 |
3 | export const beeper = new Beeper();
4 |
--------------------------------------------------------------------------------
/templates/z/src/lib/queries.ts:
--------------------------------------------------------------------------------
1 | import { z } from './z.svelte';
2 |
3 | export const current_user = z.current.query.user.where('id', '=', z.current.userID).one();
4 |
--------------------------------------------------------------------------------
/templates/z/src/lib/z.svelte.ts:
--------------------------------------------------------------------------------
1 | import { PUBLIC_SERVER } from '$env/static/public';
2 | import { Z } from 'zero-svelte';
3 | import { get_login } from '@drop-in/pass/client';
4 | import { schema, type Schema } from '../schema';
5 |
6 | export function get_z_options() {
7 | const a = get_login();
8 |
9 | return {
10 | userID: a.sub ?? 'anon',
11 | server: PUBLIC_SERVER,
12 | schema,
13 | auth: a.jwt
14 | } as const;
15 | }
16 |
17 | export const z = new Z(get_z_options());
18 |
--------------------------------------------------------------------------------
/templates/z/src/routes/(app)/+layout.svelte:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
16 |
19 |
20 |
21 |
22 |
23 |
24 | {@render children()}
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/templates/z/src/routes/(app)/+layout.ts:
--------------------------------------------------------------------------------
1 | export const ssr = false;
2 |
--------------------------------------------------------------------------------
/templates/z/src/routes/(app)/Footer.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 |
19 |
--------------------------------------------------------------------------------
/templates/z/src/routes/(app)/Header.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 |
18 |
19 |
36 |
--------------------------------------------------------------------------------
/templates/z/src/routes/(app)/README.md:
--------------------------------------------------------------------------------
1 | This is the (app) route group, routes in here will be CSR'd only and is great user specific
2 | content, ie your app, user profile, ect.
3 |
--------------------------------------------------------------------------------
/templates/z/src/routes/(app)/dashboard/+page.svelte:
--------------------------------------------------------------------------------
1 | Your Dashboard
2 |
3 | Profile
4 |
--------------------------------------------------------------------------------
/templates/z/src/routes/(app)/profile/+page.svelte:
--------------------------------------------------------------------------------
1 |
35 |
36 | Profile
37 |
38 |
39 | Email: {user.email}
40 |
41 |
42 |
43 |
44 | Name
45 |
46 |
47 |
48 |
49 |
50 |
51 | Logout
52 |
53 | Logout
54 |
--------------------------------------------------------------------------------
/templates/z/src/routes/(site)/+layout.server.ts:
--------------------------------------------------------------------------------
1 | export const ssr = false;
2 |
--------------------------------------------------------------------------------
/templates/z/src/routes/(site)/+layout.svelte:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 | {@render children()}
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/templates/z/src/routes/(site)/+page.svelte:
--------------------------------------------------------------------------------
1 | {globalThis.DROP_IN.app.name}
2 |
3 | Sign Up
4 |
--------------------------------------------------------------------------------
/templates/z/src/routes/(site)/README.md:
--------------------------------------------------------------------------------
1 | This is the (site) route group, routes in here will be SSR'd and is great for landing/marketing/info pages, blog ect.
2 |
--------------------------------------------------------------------------------
/templates/z/src/routes/(site)/auth/confirm-email-change/+page.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/templates/z/src/routes/(site)/auth/confirm-password-reset/[token]/+page.svelte:
--------------------------------------------------------------------------------
1 |
28 |
29 | Reset Password
30 |
31 |
32 | New Password
33 |
34 |
35 |
36 | New Password Confirm
37 |
38 |
39 |
40 | {#if loading}Resetting...{:else}
41 | Reest Password
42 | {/if}
43 |
44 |
45 |
46 | {#if auth.error_message}
47 | {auth.error_message}
48 | {/if}
49 |
50 |
51 | Need an account?
52 | Sign Up
53 |
54 |
55 |
56 | Forgot your password?
57 |
58 |
--------------------------------------------------------------------------------
/templates/z/src/routes/(site)/auth/forgot-password/+page.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/templates/z/src/routes/(site)/auth/login/+page.svelte:
--------------------------------------------------------------------------------
1 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/templates/z/src/routes/(site)/auth/signup/+page.svelte:
--------------------------------------------------------------------------------
1 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/templates/z/src/routes/(site)/auth/verify-email/+page.svelte:
--------------------------------------------------------------------------------
1 |
25 |
26 | Verifying email...
27 |
--------------------------------------------------------------------------------
/templates/z/src/routes/+layout.server.ts:
--------------------------------------------------------------------------------
1 | export const ssr = false;
2 |
--------------------------------------------------------------------------------
/templates/z/src/routes/+layout.svelte:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 | {@render children()}
15 |
16 |
17 |
25 |
26 |
--------------------------------------------------------------------------------
/templates/z/src/state/README.md:
--------------------------------------------------------------------------------
1 | # $state
2 |
3 | Store global state here and you can access it via `import { create_store } from $state/store.svelte'`
4 |
5 | See implementation in ./app.svelte.ts for more information.
6 |
7 | This is useful anytime you have state that isn't tightly scoped to one specific route or component.
8 |
9 | Do not use for SSR because your state may be insecure.
10 |
--------------------------------------------------------------------------------
/templates/z/src/state/app.svelte.ts:
--------------------------------------------------------------------------------
1 | class AppState {}
2 | // You can now import { app } from '$state/app.svelte'
3 | export const app = new AppState();
4 |
--------------------------------------------------------------------------------
/templates/z/src/utils/app_guard.ts:
--------------------------------------------------------------------------------
1 | import { goto } from '$app/navigation';
2 | import { z } from '$lib/z.svelte';
3 |
4 | // Sends users to the app if they try to access login or landing pages after logging in.
5 | export function app_guard() {
6 | if (z.current.userID && z.current.userID !== 'anon') {
7 | goto('/dashboard');
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/templates/z/src/utils/auth_guard.ts:
--------------------------------------------------------------------------------
1 | import { goto } from '$app/navigation';
2 | import { z } from '$lib/z.svelte';
3 |
4 | export function auth_guard() {
5 | if (!z.current.userID || z.current.userID === 'anon') {
6 | goto('/auth/login');
7 | } else {
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/templates/z/svelte.config.js:
--------------------------------------------------------------------------------
1 | import adapter from '@sveltejs/adapter-auto';
2 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
3 |
4 | /** @type {import('@sveltejs/kit').Config} */
5 | const config = {
6 | // Consult https://github.com/sveltejs/svelte-preprocess
7 | // for more information about preprocessors
8 | preprocess: vitePreprocess(),
9 |
10 | kit: {
11 | adapter: adapter(),
12 | alias: {
13 | $routes: './src/routes',
14 | $state: './src/state',
15 | $types: './src/types',
16 | $utils: './src/utils'
17 | }
18 | }
19 | };
20 |
21 | export default config;
22 |
--------------------------------------------------------------------------------
/templates/z/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./.svelte-kit/tsconfig.json",
3 | "compilerOptions": {
4 | "allowJs": true,
5 | "checkJs": true,
6 | "esModuleInterop": true,
7 | "forceConsistentCasingInFileNames": true,
8 | "resolveJsonModule": true,
9 | "skipLibCheck": true,
10 | "sourceMap": true,
11 | "strict": true
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/templates/z/vite.config.ts:
--------------------------------------------------------------------------------
1 | import dropin from '@drop-in/plugin';
2 | import { sveltekit } from '@sveltejs/kit/vite';
3 | import { defineConfig, loadEnv, type ConfigEnv } from 'vite';
4 |
5 | export default ({ mode }: ConfigEnv) => {
6 | Object.assign(process.env, loadEnv(mode, process.cwd(), ''));
7 | return defineConfig({
8 | server: {
9 | port: 4444
10 | },
11 | plugins: [dropin(), sveltekit()]
12 | });
13 | };
14 |
--------------------------------------------------------------------------------
/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "tasks": {
4 | "package": {
5 | "dependsOn": ["^package"],
6 | "outputs": ["dist/**", ".svelte-kit/**", "build/**", ".vercel/**"]
7 | },
8 | "build": {
9 | "dependsOn": ["^build"],
10 | "outputs": ["dist/**", ".svelte-kit/**", "build/**", ".vercel/**"]
11 | },
12 | "dev": {
13 | "cache": false,
14 | "persistent": true
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------