@voidbase-cloud/voidbase 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +9 -0
- package/CHANGELOG.md +19 -0
- package/COMPAT.md +43 -0
- package/LICENSE +21 -0
- package/NOTICE +8 -0
- package/README.md +124 -0
- package/bin/voidbase.ts +158 -0
- package/crons/every-minute.ts +13 -0
- package/db/migrations/20260905175935_large_swarm.sql +87 -0
- package/db/migrations/20260905185720_wild_sunspot.sql +16 -0
- package/db/migrations/20260905190723_solid_toro.sql +1 -0
- package/db/migrations/20260905213340_remarkable_union_jack.sql +11 -0
- package/db/migrations/meta/20260905175935_snapshot.json +599 -0
- package/db/migrations/meta/20260905185720_snapshot.json +703 -0
- package/db/migrations/meta/20260905190723_snapshot.json +710 -0
- package/db/migrations/meta/20260905213340_snapshot.json +781 -0
- package/db/migrations/meta/_journal.json +34 -0
- package/db/schema.ts +130 -0
- package/docs/deploy.md +153 -0
- package/docs/differences.md +88 -0
- package/docs/hooks.md +84 -0
- package/docs/migrating.md +29 -0
- package/docs/perf.md +53 -0
- package/docs/platform.md +208 -0
- package/docs/releasing.md +38 -0
- package/env.ts +23 -0
- package/hooks-plugin.ts +237 -0
- package/package.json +134 -0
- package/queues/jobs.ts +13 -0
- package/routes/api/[...path].ts +19 -0
- package/scripts/bench-realtime.ts +46 -0
- package/scripts/bench.ts +39 -0
- package/scripts/ci-suites.sh +27 -0
- package/scripts/dev.sh +29 -0
- package/scripts/export.ts +70 -0
- package/scripts/seed-app-user.sh +14 -0
- package/scripts/seed-d1.ts +17 -0
- package/scripts/seed-reference.sh +29 -0
- package/scripts/starter.sh +22 -0
- package/scripts/sync-app.ts +22 -0
- package/scripts/sync-panel.ts +66 -0
- package/src/cloud/rest.ts +297 -0
- package/src/node/assets.ts +22 -0
- package/src/node/bundle.ts +88 -0
- package/src/node/cloud-init.ts +51 -0
- package/src/node/d1.ts +44 -0
- package/src/node/deploy-cf.ts +179 -0
- package/src/node/index.ts +5 -0
- package/src/node/panel.ts +21 -0
- package/src/node/serve.ts +125 -0
- package/src/node/storage.ts +51 -0
- package/src/platform/node/env.ts +4 -0
- package/src/platform/node/hooks.ts +19 -0
- package/src/platform/node/log.ts +7 -0
- package/src/platform/node/migrations.ts +5 -0
- package/src/platform/node/photon.ts +1 -0
- package/src/platform/node/sockets.ts +22 -0
- package/src/platform/node/sse.ts +23 -0
- package/src/platform/workers/env.ts +3 -0
- package/src/platform/workers/hooks.ts +2 -0
- package/src/platform/workers/log.ts +1 -0
- package/src/platform/workers/migrations.ts +1 -0
- package/src/platform/workers/photon.ts +1 -0
- package/src/platform/workers/sockets.ts +3 -0
- package/src/platform/workers/sse.ts +1 -0
- package/src/server/api.ts +27 -0
- package/src/server/app.ts +582 -0
- package/src/server/auth-extra.ts +113 -0
- package/src/server/auth-flows.ts +186 -0
- package/src/server/auth-response.ts +111 -0
- package/src/server/auth.ts +187 -0
- package/src/server/backups.ts +234 -0
- package/src/server/batch.ts +123 -0
- package/src/server/bootstrap.ts +71 -0
- package/src/server/collections/auth-option-shape.json +71 -0
- package/src/server/collections/ddl.ts +127 -0
- package/src/server/collections/fields.ts +120 -0
- package/src/server/collections/model.ts +185 -0
- package/src/server/collections/oauth2-providers.json +1 -0
- package/src/server/collections/scaffolds.json +210 -0
- package/src/server/collections/service.ts +392 -0
- package/src/server/collections/system.json +605 -0
- package/src/server/collections/system.ts +19 -0
- package/src/server/collections/validate.ts +239 -0
- package/src/server/crc32.ts +13 -0
- package/src/server/crons.ts +100 -0
- package/src/server/crypto.ts +26 -0
- package/src/server/db.ts +37 -0
- package/src/server/errors.ts +53 -0
- package/src/server/files-api.ts +52 -0
- package/src/server/filter/compile.ts +420 -0
- package/src/server/filter/lexer.ts +107 -0
- package/src/server/filter/parser.ts +49 -0
- package/src/server/hardening.ts +136 -0
- package/src/server/hooks/index.ts +147 -0
- package/src/server/hooks/migrations.ts +58 -0
- package/src/server/hooks/node-async-hooks.d.ts +7 -0
- package/src/server/hooks/record.ts +152 -0
- package/src/server/hooks/runtime.ts +344 -0
- package/src/server/hooks/virtual-migrations.d.ts +4 -0
- package/src/server/hooks/virtual.d.ts +7 -0
- package/src/server/hub.ts +91 -0
- package/src/server/ids.ts +22 -0
- package/src/server/jobs.ts +84 -0
- package/src/server/jwt.ts +61 -0
- package/src/server/logs.ts +144 -0
- package/src/server/mail/index.ts +99 -0
- package/src/server/mail/message.ts +43 -0
- package/src/server/mail/smtp.ts +82 -0
- package/src/server/mail/templates.ts +168 -0
- package/src/server/oauth2/index.ts +198 -0
- package/src/server/oauth2/providers.ts +153 -0
- package/src/server/password.ts +17 -0
- package/src/server/realtime/hub-client.ts +50 -0
- package/src/server/realtime/index.ts +239 -0
- package/src/server/records/expand.ts +129 -0
- package/src/server/records/files.ts +69 -0
- package/src/server/records/json.ts +23 -0
- package/src/server/records/picker.ts +80 -0
- package/src/server/records/service.ts +598 -0
- package/src/server/records/thumbs.ts +148 -0
- package/src/server/records/values.ts +295 -0
- package/src/server/settings-api.ts +104 -0
- package/src/server/settings.ts +215 -0
- package/src/server/sql.ts +61 -0
- package/src/server/static.ts +17 -0
- package/src/server/storage/s3.ts +118 -0
- package/src/server/types.ts +25 -0
- package/src/server/webauthn.ts +168 -0
- package/tsconfig.json +36 -0
- package/tsconfig.node.json +27 -0
- package/types/pb_data.d.ts +24438 -0
- package/vite.config.ts +10 -0
- package/void.json +12 -0
package/.env.example
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Copy to .env for local dev. Both must be set for the bootstrap superuser upsert.
|
|
2
|
+
VOIDBASE_SUPERUSER_EMAIL=admin@example.com
|
|
3
|
+
VOIDBASE_SUPERUSER_PASSWORD=changeme123
|
|
4
|
+
|
|
5
|
+
# PocketBase-style JS hooks directory bundled at build time (default: ./pb_hooks)
|
|
6
|
+
VOIDBASE_HOOKS_DIR=./pb_hooks
|
|
7
|
+
# Build-time: directories bundled into the Worker (PocketBase pb_hooks and pb_migrations layouts)
|
|
8
|
+
VOIDBASE_HOOKS_DIR=pb_hooks
|
|
9
|
+
VOIDBASE_MIGRATIONS_DIR=pb_migrations
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Releases are tagged `vX.Y.Z`; the section for the tagged version becomes the GitHub release notes.
|
|
4
|
+
|
|
5
|
+
## Unreleased
|
|
6
|
+
|
|
7
|
+
## 0.1.0
|
|
8
|
+
|
|
9
|
+
First release candidate: PocketBase 0.40 wire compatibility on Cloudflare Workers (D1, R2, cron) via Void.
|
|
10
|
+
|
|
11
|
+
- Collections engine with runtime DDL, all 14 field types, views with inferred fields, import/export, API rule validation.
|
|
12
|
+
- Records API: filter/sort/expand/fields, files with thumbnails and ranges, batch, cascade delete.
|
|
13
|
+
- Auth: password, OAuth2 (32 providers), OTP, MFA, passkeys, verification / password reset / email change flows, impersonation, auth alerts.
|
|
14
|
+
- Realtime over SSE with a D1 change feed; JS hooks and migrations (`pb_hooks`, `pb_migrations`) bundled at build time.
|
|
15
|
+
- Settings, SMTP over Cloudflare sockets, S3 file and backup storage, logs, crons, backups, SQL console, rate limits, trusted proxy, encryption at rest.
|
|
16
|
+
- Unmodified PocketBase admin panel served at `/_/`; unmodified `pocketbase` JS SDK 0.28 supported.
|
|
17
|
+
- Published as `@voidbase-cloud/voidbase` from GitHub Actions (npm + GitHub Packages).
|
|
18
|
+
- Cloudflare cost shape: assets and deep links served by the asset layer without invoking the Worker (`404.html` shells, deep links carry status 404), request logs written only from warnings up by default (`VOIDBASE_LOG_MIN_LEVEL`), change-feed rows only while a client is subscribed, cron triggers derived from the hooks' `cronAdd` expressions plus lazy maintenance, Smart Placement, lazy Photon. Background jobs (system mail, automatic backups) through a Cloudflare Queue with retries, a rate-limit binding as a per-location ceiling, an opt-in Analytics Engine request log; `voidbase deploy` creates the queue and declares the bindings. Realtime pushes through a per-instance Durable Object hub (hibernating sockets, tens of milliseconds instead of a one-second poll); the D1 poll remains the fallback without the binding.
|
|
19
|
+
- Differential conformance suites against a reference PocketBase, SDK coverage matrix, security suite, generated filter corpus, browser suites for the panel and the SvelteKit starter, CI workflow.
|
package/COMPAT.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Compatibility matrix
|
|
2
|
+
|
|
3
|
+
Verified against these upstream versions. "Verified" means a differential test drives the same requests (or the
|
|
4
|
+
same UI) against a reference PocketBase and voidbase and compares the results; see `surface/surface.json` for the
|
|
5
|
+
item-level map (`bun run surface`).
|
|
6
|
+
|
|
7
|
+
| Component | Version | How it is verified |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| PocketBase admin panel (`ui/dist`, served unmodified at `/_/`) | 0.40.2 | `test/panel-*.ts` (Playwright + system Chrome): login, collections, records, admin screens, superuser password reset, API preview |
|
|
10
|
+
| Reference PocketBase server | 0.39.11 binary (0.40.2 source for behavior) | `test/conformance/*.ts` differential suites. 0.40-only features (`Cross-Origin-Opener-Policy`, Instagram provider, `DELETE /api/logs`) are tolerated when the 0.39 binary lacks them |
|
|
11
|
+
| `pocketbase` JS SDK | 0.28 | through `pocketbase-sveltekit-starter` (`test/starter-*.ts`) and the SDK-shaped requests in the conformance suites |
|
|
12
|
+
| Void | 0.10.13 | dev, preview and build |
|
|
13
|
+
| Cloudflare Workers compatibility date | 2026-09-05, `nodejs_compat` | `void.json` |
|
|
14
|
+
|
|
15
|
+
## API endpoints
|
|
16
|
+
|
|
17
|
+
| Area | Endpoints | Status |
|
|
18
|
+
| --- | --- | --- |
|
|
19
|
+
| Health | `GET /api/health` | verified |
|
|
20
|
+
| Collections | `GET/POST /api/collections`, `GET/PATCH/DELETE /api/collections/:c`, `DELETE .../truncate`, `PUT /api/collections/import`, `GET /api/collections/meta/scaffolds`, `GET /api/collections/meta/oauth2-providers` | verified (incl. view inference, rule validation) |
|
|
21
|
+
| Records | `GET/POST .../records`, `GET/PATCH/DELETE .../records/:id` with filter, sort, expand, fields, skipTotal, `@request`/`@collection` rules, multipart files, modifiers | verified (88 recorded + live cases) |
|
|
22
|
+
| Auth | `auth-methods`, `auth-with-password`, `auth-refresh`, `auth-with-oauth2`, `auth-with-otp`, `request-otp`, MFA handshake, `impersonate`, `request/confirm-verification`, `request/confirm-password-reset`, `request/confirm-email-change`, auth alerts, `GET/POST /api/oauth2-redirect` | verified |
|
|
23
|
+
| Files | `GET /api/files/:c/:r/:f` (thumbs, ranges, 304, download), `POST /api/files/token`, protected files | verified |
|
|
24
|
+
| Realtime | `GET /api/realtime` (SSE), `POST /api/realtime` | verified (polling fanout, see differences) |
|
|
25
|
+
| Batch | `POST /api/batch` | verified (transaction emulated with undo statements) |
|
|
26
|
+
| Settings | `GET/PATCH /api/settings`, `test/email`, `test/s3` (validation only), `apple/generate-client-secret` | verified; S3 backend not implemented |
|
|
27
|
+
| Logs | `GET /api/logs`, `/api/logs/:id`, `/api/logs/stats`, `DELETE /api/logs` | verified |
|
|
28
|
+
| Crons | `GET /api/crons`, `POST /api/crons/:id` | verified (built-ins + `cronAdd`) |
|
|
29
|
+
| Backups | `GET/POST /api/backups`, `upload`, `GET/DELETE /api/backups/:key`, `POST .../restore` | verified; voidbase archive format |
|
|
30
|
+
| SQL console | `POST /api/sql` | verified |
|
|
31
|
+
| Passkeys (starter) | `/api/webauthn/*` | verified with a virtual authenticator |
|
|
32
|
+
|
|
33
|
+
## Hooks
|
|
34
|
+
|
|
35
|
+
All `on*` event families, `routerAdd`/`routerUse`, `cronAdd`/`cronRemove`, `migrate` and the `$app`, `$apis`,
|
|
36
|
+
`$http`, `$filesystem`, `$security`, `$os`, `$dbx` globals listed in [docs/hooks.md](docs/hooks.md).
|
|
37
|
+
|
|
38
|
+
## Tracking upstream
|
|
39
|
+
|
|
40
|
+
- Panel: rerun `bun run panel:sync` from a newer `pocketbase/ui/dist`, then `bun test/panel-*.ts`.
|
|
41
|
+
- Server behavior: point the conformance suites at a newer reference binary
|
|
42
|
+
(`bun test/conformance/compare.ts http://127.0.0.1:8090 http://127.0.0.1:5180`) and fix the diffs.
|
|
43
|
+
- SDK: bump `pocketbase` in the starter and rerun `test/starter-*.ts`.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 voidbase contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/NOTICE
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
voidbase reimplements the HTTP API, validation rules, email templates and JS hooks surface of PocketBase
|
|
2
|
+
(https://pocketbase.io, MIT License, Copyright (c) 2022 - present, Gani Georgiev) on Cloudflare Workers.
|
|
3
|
+
The admin panel served at /_/ is PocketBase's own unmodified UI build (ui/dist), copied in by
|
|
4
|
+
`bun scripts/sync-panel.ts`; it stays under PocketBase's MIT license. voidbase is not affiliated with or endorsed
|
|
5
|
+
by the PocketBase project.
|
|
6
|
+
|
|
7
|
+
Runtime dependencies: hono (MIT), fflate (MIT), @cf-wasm/photon (Apache-2.0, Photon by Silvia O'Dwyer),
|
|
8
|
+
@simplewebauthn/server (MIT), bcryptjs (MIT). Built and deployed with Void (https://void.cloud).
|
package/README.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# voidbase
|
|
2
|
+
|
|
3
|
+
A PocketBase-wire-compatible backend on Cloudflare Workers, built with [Void](https://void.cloud).
|
|
4
|
+
The unmodified PocketBase admin panel (0.40.2) and the unmodified `pocketbase` JS SDK (0.28) are the two oracles that define done.
|
|
5
|
+
|
|
6
|
+
Codename: `kanz-zjy`. Progress map: `surface/surface.json` rendered by `bun run surface`.
|
|
7
|
+
|
|
8
|
+
## Use it like PocketBase as a framework
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
// main.ts
|
|
12
|
+
import { voidbase, parseServeArgs, type VoidbaseApp } from "@voidbase-cloud/voidbase";
|
|
13
|
+
export function register(app: VoidbaseApp) {
|
|
14
|
+
app.hooks.onRecordAfterCreateSuccess(async (e) => { /* ... */ }, "posts"); // the same on* functions pb_hooks get
|
|
15
|
+
app.router.get("/api/hello", (c) => c.json({ hello: "world" })); // Hono-style routes
|
|
16
|
+
app.hooks.cronAdd("digest", "0 8 * * *", () => { /* ... */ });
|
|
17
|
+
}
|
|
18
|
+
if (import.meta.main) { const app = await voidbase(parseServeArgs()); register(app); await app.start(); }
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`bun main.ts --http 127.0.0.1:8090` runs it; `voidbase deploy` composes `register` into the Worker as well.
|
|
22
|
+
`voidbase-sveltekit-starter/vb` is the worked example (audit log, `hooks` collection actions, passkeys).
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
bun add @voidbase-cloud/voidbase # the package: library, CLI (`voidbase`) and the Cloudflare project generator
|
|
28
|
+
bunx @voidbase-cloud/voidbase serve # or run the CLI without installing
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Releases are tagged `vX.Y.Z` and published from GitHub Actions to npm (with provenance once the repository is
|
|
32
|
+
public) and to GitHub Packages; see [docs/releasing.md](docs/releasing.md).
|
|
33
|
+
|
|
34
|
+
## Run it like PocketBase
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
bun install
|
|
38
|
+
bunx voidbase serve --http 127.0.0.1:8090 --dir pb_data --hooksDir pb_hooks --migrationsDir pb_migrations --publicDir ./public
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
One Bun process, SQLite in `pb_data/data.db`, files in `pb_data/storage/`, the admin panel at `/_/`, the same
|
|
42
|
+
`pb_hooks` and `pb_migrations` you would give PocketBase (`--dev` restarts on hook changes, `voidbase superuser
|
|
43
|
+
upsert email pass` works offline on `pb_data`). The Cloudflare deployment runs the same code on D1 and R2 with
|
|
44
|
+
`voidbase deploy` from the same directory (see docs/deploy.md). The PocketBase-shaped
|
|
45
|
+
consumer is `voidbase-sveltekit-starter/vb`.
|
|
46
|
+
|
|
47
|
+
## Run locally (this checkout, Workers dev server)
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
bun install
|
|
51
|
+
cp .env.example .env # first superuser, upserted at bootstrap
|
|
52
|
+
bun run panel:sync # copies ../pocketbase/ui/dist to public/_ (set POCKETBASE_UI_DIST to override)
|
|
53
|
+
./node_modules/.bin/void db migrate
|
|
54
|
+
./scripts/dev.sh start 5180 # background dev server with a pidfile; stop / status / log
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Panel: http://127.0.0.1:5180/_/ · API: http://127.0.0.1:5180/api/health
|
|
58
|
+
|
|
59
|
+
## Verify against PocketBase
|
|
60
|
+
|
|
61
|
+
With a reference PocketBase on 127.0.0.1:8090 (the starter's `pb/` works, superuser admin@example.com / changeme123):
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
bun test/conformance/compare.ts # same requests at both servers, JSON diffed with volatile fields masked
|
|
65
|
+
bun test/panel-smoke.ts # headless login through the unmodified panel, screenshot to /tmp/panel.png
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## The starter fork
|
|
69
|
+
|
|
70
|
+
`voidbase-sveltekit-starter` is the reference consumer: `pocketbase-sveltekit-starter` with `pb/` replaced by
|
|
71
|
+
`vb/` (a PocketBase-shaped directory: `pb_hooks`, `pb_migrations`, `pb_data`, `main.ts`, `entrypoint.sh`) and
|
|
72
|
+
nothing else changed. Its `vb/package.json` depends on this package; `bun run backend` in `sk` runs `voidbase serve`,
|
|
73
|
+
`bun run dev:backend` runs `main.ts`, and `bun run deploy` in `vb` goes live on Cloudflare. The original
|
|
74
|
+
`pocketbase-sveltekit-starter` checkout stays on upstream master as the PocketBase reference for the differential
|
|
75
|
+
suites (`scripts/seed-reference.sh` runs its `pb/` against the reference binary).
|
|
76
|
+
|
|
77
|
+
## CLI
|
|
78
|
+
|
|
79
|
+
`bun bin/voidbase.ts --help` (or `voidbase` when installed): `init`, `dev`, `build`, `preview`, `deploy [--cloudflare]`,
|
|
80
|
+
`superuser upsert|list`, `import <collections.json>`, `export <outDir>`, `panel sync [--brand dir]`, `app sync`,
|
|
81
|
+
`seed-user`. Remote commands take `--url` and `--admin email:password`.
|
|
82
|
+
|
|
83
|
+
## Tests
|
|
84
|
+
|
|
85
|
+
Conformance suites in `test/conformance/` run the same requests against a reference PocketBase (8090) and voidbase
|
|
86
|
+
(5180) and compare; browser suites `test/panel-*.ts` and `test/starter-*.ts` drive the unmodified panel and the
|
|
87
|
+
unmodified `pocketbase-sveltekit-starter`. Helpers that must be running for some suites: `bun test/smtp-sink.ts`
|
|
88
|
+
(SMTP 2525 / HTTP 2526), `bun test/mock-oidc.ts` (5190) and `bun test/s3-mock.ts` (5195, S3 with SigV4 verification). `bun test/fresh-db.ts` builds the production Worker
|
|
89
|
+
with the fixture hooks and migrations and boots it on an empty D1; `bun test/mail-http.ts` does the same with the HTTP mail
|
|
90
|
+
provider variables.
|
|
91
|
+
|
|
92
|
+
## Go live
|
|
93
|
+
|
|
94
|
+
`voidbase token` prints a Cloudflare dashboard link that creates `VOIDBASE_DEPLOY_CF_API_KEY` with the right
|
|
95
|
+
permissions pre-selected; with that variable set, `voidbase deploy` provisions D1 and R2, generates the Void project
|
|
96
|
+
inside the package (`node_modules/voidbase/.cloud/<name>`), stores the superuser as secrets and uploads the Worker.
|
|
97
|
+
Your directory stays `pb_hooks` + `pb_migrations` + `pb_data`, like a PocketBase folder. See [docs/deploy.md](docs/deploy.md).
|
|
98
|
+
|
|
99
|
+
## Continuous integration
|
|
100
|
+
|
|
101
|
+
`.github/workflows/ci.yml` checks out the two oracles (the starter and PocketBase's panel build), starts voidbase and
|
|
102
|
+
a seeded reference PocketBase (`scripts/seed-reference.sh`), and runs every suite through `scripts/ci-suites.sh`,
|
|
103
|
+
then `test/fresh-db.ts` and the starter smoke. The same scripts run locally against any pair of servers.
|
|
104
|
+
|
|
105
|
+
## Docs
|
|
106
|
+
|
|
107
|
+
- [docs/deploy.md](docs/deploy.md): Void platform or your own Cloudflare account.
|
|
108
|
+
- [docs/differences.md](docs/differences.md): what the platform changes (D1 batches, per-isolate limits, polling realtime, backups format).
|
|
109
|
+
- [docs/hooks.md](docs/hooks.md): `pb_hooks` and `pb_migrations` on Workers, supported events and globals.
|
|
110
|
+
- [docs/migrating.md](docs/migrating.md): moving an existing PocketBase app.
|
|
111
|
+
- [docs/platform.md](docs/platform.md): how to run cheap and fast on Cloudflare (assets off the Worker, log writes, change feed, crons, placement, queues, rate limits, the realtime hub) and the per-app Durable Object design for going beyond the account limits.
|
|
112
|
+
- [COMPAT.md](COMPAT.md): verified upstream versions and endpoint matrix.
|
|
113
|
+
|
|
114
|
+
## Layout
|
|
115
|
+
|
|
116
|
+
- `routes/api/[...path].ts` hands every `/api/*` request to the Hono app in `src/server/app.ts`.
|
|
117
|
+
- `src/server/` is the server: collections model, auth, settings, records, bootstrap.
|
|
118
|
+
- `db/schema.ts` defines only the system tables. User collections are rows in `_collections` and tables created at runtime, as in PocketBase.
|
|
119
|
+
- `public/_` is the panel build, synced, never edited (`bun run panel:sync --brand <dir>` for an optional logo/title/docs-link swap).
|
|
120
|
+
- Everything outside `/api` is served by Cloudflare's asset layer without invoking the Worker; deep links get the SPA shell through `404.html` copies of `index.html` (written at build time by `hooks-plugin.ts` and by the sync scripts).
|
|
121
|
+
- `crons/every-minute.ts` runs PocketBase's maintenance jobs and `cronAdd` jobs.
|
|
122
|
+
- `queues/jobs.ts` consumes the jobs queue (system mail, automatic backups) with retries; without it every job runs inline.
|
|
123
|
+
- `src/server/hub.ts` is the realtime hub, a Durable Object exported from this Worker (`hooks-plugin.ts` appends it to Void's entry; `wrangler.jsonc` binds it); without the binding realtime polls the D1 change feed.
|
|
124
|
+
- `hooks-plugin.ts` bundles `pb_hooks` and `pb_migrations` into the Worker at build time.
|
package/bin/voidbase.ts
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// voidbase CLI: the PocketBase-shaped chores (superuser upsert, collections import/export, panel sync) plus thin
|
|
3
|
+
// wrappers over the Void toolchain (dev, build, preview, deploy). Runs with Bun from the project root.
|
|
4
|
+
// voidbase <command> [options] voidbase --help
|
|
5
|
+
import { existsSync, mkdirSync, writeFileSync, cpSync } from "node:fs";
|
|
6
|
+
import { resolve } from "node:path";
|
|
7
|
+
import { exportAll } from "../scripts/export";
|
|
8
|
+
|
|
9
|
+
const ROOT = resolve(`${import.meta.dir}/..`);
|
|
10
|
+
const argv = process.argv.slice(2);
|
|
11
|
+
const flags: Record<string, string> = {}; const positional: string[] = [];
|
|
12
|
+
for (let i = 0; i < argv.length; i++) { const a = argv[i]!; if (a.startsWith("--")) { const [k, v] = a.slice(2).split("="); flags[k!] = v ?? (argv[i + 1] && !argv[i + 1]!.startsWith("--") ? argv[++i]! : "1"); } else positional.push(a); }
|
|
13
|
+
const [cmd, sub, ...rest] = positional;
|
|
14
|
+
const url = (flags.url ?? process.env.VOIDBASE_URL ?? "http://127.0.0.1:8090").replace(/\/$/, "");
|
|
15
|
+
const serveOpts = () => ({ http: flags.http, dir: flags.dir, hooksDir: flags.hooksDir, migrationsDir: flags.migrationsDir, publicDir: flags.publicDir });
|
|
16
|
+
const admin = () => { const [email, password] = (flags.admin ?? `${process.env.VOIDBASE_SUPERUSER_EMAIL ?? "admin@example.com"}:${process.env.VOIDBASE_SUPERUSER_PASSWORD ?? ""}`).split(":") as [string, string]; return { email, password }; };
|
|
17
|
+
const HELP = `voidbase - PocketBase-compatible backend: a single Bun process locally, Cloudflare Workers via Void in production
|
|
18
|
+
|
|
19
|
+
serve [--http 127.0.0.1:8090] [--dir pb_data] [--hooksDir pb_hooks] [--migrationsDir pb_migrations] [--publicDir ../sk/build] [--dev] [--entry main.ts]
|
|
20
|
+
run the server like "pocketbase serve" (--dev restarts when hooks or migrations change;
|
|
21
|
+
--entry runs your own main.ts, the counterpart of a custom PocketBase build)
|
|
22
|
+
superuser upsert <email> <password> create or update a superuser: on the local data directory (--dir) or on a running
|
|
23
|
+
instance (--url, --admin email:pass)
|
|
24
|
+
|
|
25
|
+
init [dir] scaffold .env, pb_hooks/, pb_migrations/ in a fresh checkout and sync the panel
|
|
26
|
+
dev [--port 5180] start the Void dev server (vp dev)
|
|
27
|
+
build | preview [--port 5181] production build / run the built Worker locally (vp build / vp preview)
|
|
28
|
+
deploy [--name worker] [--account id] [--domain api.example.com] [--public-dir ../sk/build] [--dry-run] [--no-queue] [--no-hub] [--no-cron]
|
|
29
|
+
[--analytics] [--rate-limit 300/10]
|
|
30
|
+
go live on your Cloudflare account with VOIDBASE_DEPLOY_CF_API_KEY: creates the D1
|
|
31
|
+
database and R2 bucket, writes cloud/ (voidbase cloud init) with wrangler.jsonc,
|
|
32
|
+
stores the superuser as worker secrets and runs void deploy --backend cloudflare
|
|
33
|
+
deploy --void deploy to the Void platform instead (void auth login first)
|
|
34
|
+
token print the Cloudflare dashboard link that creates VOIDBASE_DEPLOY_CF_API_KEY
|
|
35
|
+
bundle [--out dir] [--version v] build the generic Worker + panel as a release directory (default .cloud/releases/<v>)
|
|
36
|
+
[--push http://vb --token t] and optionally push it into a voidbase control plane (POST /api/vbcloud/releases)
|
|
37
|
+
release push <dir> --url http://vb --token <superuser token> push a built release ( --no-activate keeps the current one)
|
|
38
|
+
superuser list list superusers (--url, --admin)
|
|
39
|
+
import <collections.json> [--delete-missing] PUT /api/collections/import on a running instance (--url, --admin)
|
|
40
|
+
export <outDir> SQLite + collections.json + storage/ from a running instance (--url, --admin)
|
|
41
|
+
cloud init [dir=cloud] write a Void project (routes, middleware, crons, db, env, vite/void config) that
|
|
42
|
+
imports voidbase and uses ../pb_hooks and ../pb_migrations, for "void deploy"
|
|
43
|
+
panel sync [--brand <dir>] copy PocketBase's ui/dist into public/_ (POCKETBASE_UI_DIST), optional branding
|
|
44
|
+
app sync copy a static app build into public/ (VOIDBASE_APP_DIR)
|
|
45
|
+
seed-user [email] [password] create the app user (default user@example.com) on a running instance
|
|
46
|
+
|
|
47
|
+
Options: --url http://host (default $VOIDBASE_URL or http://127.0.0.1:5180); --admin email:password (default $VOIDBASE_SUPERUSER_EMAIL / _PASSWORD)`;
|
|
48
|
+
const run = async (bin: string, args: string[], env: Record<string, string> = {}) => { const p = Bun.spawn([bin, ...args], { cwd: ROOT, stdio: ["inherit", "inherit", "inherit"], env: { ...process.env, ...env } }); const code = await p.exited; if (code !== 0) process.exit(code); };
|
|
49
|
+
async function api(method: string, path: string, body?: unknown, token?: string) {
|
|
50
|
+
const r = await fetch(url + path, { method, headers: { ...(body !== undefined ? { "content-type": "application/json" } : {}), ...(token ? { authorization: token } : {}) }, body: body !== undefined ? JSON.stringify(body) : undefined });
|
|
51
|
+
const text = await r.text(); let json: Record<string, unknown> = {}; try { json = JSON.parse(text); } catch { json = { raw: text }; }
|
|
52
|
+
return { status: r.status, json };
|
|
53
|
+
}
|
|
54
|
+
async function login(): Promise<string> {
|
|
55
|
+
const { email, password } = admin();
|
|
56
|
+
if (!password) { console.error("superuser credentials needed: --admin email:password or VOIDBASE_SUPERUSER_EMAIL/_PASSWORD"); process.exit(1); }
|
|
57
|
+
const r = await api("POST", "/api/collections/_superusers/auth-with-password", { identity: email, password });
|
|
58
|
+
if (r.status !== 200) { console.error(`login as ${email} at ${url} failed: ${r.status} ${JSON.stringify(r.json)}`); process.exit(1); }
|
|
59
|
+
return String(r.json.token);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
switch (cmd) {
|
|
63
|
+
case undefined: case "help": case "--help": console.log(HELP); break;
|
|
64
|
+
case "token": { const { tokenHelp } = await import("../src/node/deploy-cf"); console.log(tokenHelp()); break; }
|
|
65
|
+
case "bundle": {
|
|
66
|
+
const { buildRelease, pushRelease } = await import("../src/node/bundle");
|
|
67
|
+
const r = await buildRelease({ out: flags.out as string | undefined, version: flags.version as string | undefined, hub: flags["no-hub"] ? false : undefined, queue: flags["no-queue"] ? false : undefined, keepProject: !!flags["keep-project"] });
|
|
68
|
+
if (flags.push) await pushRelease({ dir: r.dir, url: String(flags.push), token: String(flags.token ?? process.env.VOIDBASE_RELEASE_TOKEN ?? ""), activate: !flags["no-activate"] });
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
case "release": {
|
|
72
|
+
if (sub !== "push") { console.error(`unknown release command "${sub}"\n\n${HELP}`); process.exit(1); }
|
|
73
|
+
const { pushRelease } = await import("../src/node/bundle");
|
|
74
|
+
await pushRelease({ dir: resolve(String(rest[0] ?? flags.dir ?? ".")), url: String(flags.url ?? process.env.VOIDBASE_URL ?? "http://127.0.0.1:8090"), token: String(flags.token ?? process.env.VOIDBASE_RELEASE_TOKEN ?? ""), activate: !flags["no-activate"] });
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
case "serve": {
|
|
78
|
+
// --entry main.ts: the project's own composition (pb's "custom" build), otherwise the stock server
|
|
79
|
+
if (!flags.dev) { if (flags.entry) { await run("bun", [resolve(flags.entry), ...process.argv.slice(3).filter((a, i, arr) => a !== "--entry" && arr[i - 1] !== "--entry")]); break; } const { serve } = await import("../src/node/serve"); await serve(serveOpts()); break; }
|
|
80
|
+
// --dev: run the server as a child and restart it when pb_hooks / pb_migrations change (like modd for PocketBase)
|
|
81
|
+
const { watch } = await import("node:fs");
|
|
82
|
+
const childArgs = process.argv.slice(2).filter((a) => a !== "--dev");
|
|
83
|
+
const entry = flags.entry ? resolve(flags.entry) : null;
|
|
84
|
+
let child: ReturnType<typeof Bun.spawn> | null = null; let timer: ReturnType<typeof setTimeout> | null = null;
|
|
85
|
+
const entryArgs = childArgs.slice(1).filter((a, i, arr) => a !== "--entry" && arr[i - 1] !== "--entry");
|
|
86
|
+
const start = () => { child = Bun.spawn(entry ? ["bun", entry, ...entryArgs] : ["bun", import.meta.path, ...childArgs], { stdio: ["inherit", "inherit", "inherit"], env: process.env }); };
|
|
87
|
+
const restart = () => { if (timer) clearTimeout(timer); timer = setTimeout(() => { console.log("voidbase: hooks changed, restarting"); child?.kill(); start(); }, 300); };
|
|
88
|
+
for (const d of [flags.hooksDir ?? "pb_hooks", flags.migrationsDir ?? "pb_migrations", ...(entry ? [entry] : [])]) { try { watch(resolve(d), { recursive: true }, restart); } catch { /* directory may not exist yet */ } }
|
|
89
|
+
start();
|
|
90
|
+
process.on("SIGINT", () => { child?.kill(); process.exit(0); }); process.on("SIGTERM", () => { child?.kill(); process.exit(0); });
|
|
91
|
+
await new Promise(() => undefined);
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
case "init": {
|
|
95
|
+
const dir = resolve(sub ?? ".");
|
|
96
|
+
mkdirSync(`${dir}/pb_hooks`, { recursive: true }); mkdirSync(`${dir}/pb_migrations`, { recursive: true });
|
|
97
|
+
if (!existsSync(`${dir}/.env`)) { cpSync(`${ROOT}/.env.example`, `${dir}/.env`); console.log("wrote .env from .env.example (set VOIDBASE_SUPERUSER_EMAIL/PASSWORD)"); }
|
|
98
|
+
if (!existsSync(`${dir}/pb_hooks/main.pb.js`)) writeFileSync(`${dir}/pb_hooks/main.pb.js`, `/// <reference path="../pb_data/types.d.ts" />\nrouterAdd("GET", "/api/hello", (e) => e.json(200, { hello: "voidbase" }));\n`);
|
|
99
|
+
console.log("pb_hooks/ and pb_migrations/ ready");
|
|
100
|
+
await run("bun", ["scripts/sync-panel.ts"]).catch(() => undefined);
|
|
101
|
+
console.log("\nnext: bun install && ./node_modules/.bin/void db migrate && voidbase dev");
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
case "dev": await run("./node_modules/.bin/vp", ["dev", "--port", flags.port ?? "5180", "--host", flags.host ?? "127.0.0.1"]); break;
|
|
105
|
+
case "build": await run("./node_modules/.bin/vp", ["build"]); break;
|
|
106
|
+
case "preview": await run("./node_modules/.bin/vp", ["preview", "--port", flags.port ?? "5181", "--host", flags.host ?? "127.0.0.1"]); break;
|
|
107
|
+
case "deploy": {
|
|
108
|
+
if (flags.void) { await run("./node_modules/.bin/void", ["deploy"]); break; } // the Void platform (void auth login first)
|
|
109
|
+
const { deployToCloudflare } = await import("../src/node/deploy-cf");
|
|
110
|
+
await deployToCloudflare({ name: flags.name, account: flags.account, dir: flags.dir, publicDir: flags["public-dir"] ?? flags.publicDir, dryRun: !!flags["dry-run"], regenerate: !!flags.regenerate, queue: flags["no-queue"] ? false : undefined, cron: flags["no-cron"] ? false : undefined, domain: flags.domain as string | undefined, analytics: flags.analytics ? true : undefined, rateLimit: flags["rate-limit"], hub: flags["no-hub"] ? false : undefined });
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
case "superuser": {
|
|
114
|
+
if (!flags.url && sub === "upsert" && rest.length >= 2) {
|
|
115
|
+
// offline, straight on the data directory (pocketbase superuser upsert)
|
|
116
|
+
const { openLocal } = await import("../src/node/serve"); const { upsertSuperuser } = await import("../src/server/bootstrap");
|
|
117
|
+
const { env, sqlite } = await openLocal({ ...serveOpts(), dir: flags.dir ?? "pb_data" });
|
|
118
|
+
const { ensureBootstrapped } = await import("../src/server/bootstrap"); await ensureBootstrapped(env.DB);
|
|
119
|
+
const [email, password] = rest as [string, string];
|
|
120
|
+
console.log(`${await upsertSuperuser(env.DB, email, password)} superuser ${email} in ${flags.dir ?? "pb_data"}`); sqlite.close(); break;
|
|
121
|
+
}
|
|
122
|
+
const token = await login();
|
|
123
|
+
if (sub === "list") { const r = await api("GET", "/api/collections/_superusers/records?perPage=200&sort=email", undefined, token); for (const s of (r.json.items as { id: string; email: string; created: string }[]) ?? []) console.log(`${s.id} ${s.email} ${s.created}`); break; }
|
|
124
|
+
if (sub !== "upsert" || rest.length < 2) { console.error("usage: voidbase superuser upsert <email> <password>"); process.exit(1); }
|
|
125
|
+
const [email, password] = rest as [string, string];
|
|
126
|
+
const existing = await api("GET", `/api/collections/_superusers/records?filter=${encodeURIComponent(`email = '${email.replace(/'/g, "\\'")}'`)}`, undefined, token);
|
|
127
|
+
const found = ((existing.json.items as { id: string }[]) ?? [])[0];
|
|
128
|
+
const r = found ? await api("PATCH", `/api/collections/_superusers/records/${found.id}`, { password, passwordConfirm: password }, token) : await api("POST", "/api/collections/_superusers/records", { email, password, passwordConfirm: password }, token);
|
|
129
|
+
if (r.status !== 200) { console.error(`upsert failed: ${r.status} ${JSON.stringify(r.json)}`); process.exit(1); }
|
|
130
|
+
console.log(`${found ? "updated" : "created"} superuser ${email} (${r.json.id})`); break;
|
|
131
|
+
}
|
|
132
|
+
case "import": {
|
|
133
|
+
if (!sub) { console.error("usage: voidbase import <collections.json> [--delete-missing]"); process.exit(1); }
|
|
134
|
+
const token = await login();
|
|
135
|
+
const collections = JSON.parse(await Bun.file(sub).text()) as unknown[];
|
|
136
|
+
const r = await api("PUT", "/api/collections/import", { collections, deleteMissing: !!flags["delete-missing"] }, token);
|
|
137
|
+
if (r.status !== 204) { console.error(`import failed: ${r.status} ${JSON.stringify(r.json)}`); process.exit(1); }
|
|
138
|
+
console.log(`imported ${collections.length} collections into ${url}`); break;
|
|
139
|
+
}
|
|
140
|
+
case "export": {
|
|
141
|
+
if (!sub) { console.error("usage: voidbase export <outDir>"); process.exit(1); }
|
|
142
|
+
const { email, password } = admin();
|
|
143
|
+
const r = await exportAll(url, sub, email, password);
|
|
144
|
+
console.log(`exported ${r.collections} collections, ${r.rows} rows, ${r.files} files to ${sub}`); break;
|
|
145
|
+
}
|
|
146
|
+
case "cloud": {
|
|
147
|
+
if (sub !== "init") { console.error("usage: voidbase cloud init [dir]"); process.exit(1); }
|
|
148
|
+
const { writeCloudProject } = await import("../src/node/cloud-init");
|
|
149
|
+
const r = writeCloudProject(resolve(rest[0] ?? "cloud"));
|
|
150
|
+
console.log(`wrote ${r.files} files + db/migrations to ${rest[0] ?? "cloud"}\nnext: voidbase deploy (or: cd ${rest[0] ?? "cloud"} && bun install && bun run panel:sync && void deploy)`);
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
// destinations resolve against the caller's directory (run() executes in the package root)
|
|
154
|
+
case "panel": await run("bun", ["scripts/sync-panel.ts", "--dest", resolve(flags.dest ?? "public/_"), ...(flags.brand ? ["--brand", resolve(flags.brand)] : [])]); break;
|
|
155
|
+
case "app": await run("bun", ["scripts/sync-app.ts", "--dest", resolve(flags.dest ?? "public")], { VOIDBASE_APP_DIR: resolve(flags.src ?? process.env.VOIDBASE_APP_DIR ?? "../sk/build") }); break;
|
|
156
|
+
case "seed-user": await run("bash", ["scripts/seed-app-user.sh", url], { REFERENCE_USER_EMAIL: sub ?? "user@example.com", REFERENCE_USER_PASSWORD: rest[0] ?? "changeme123" }); break;
|
|
157
|
+
default: console.error(`unknown command "${cmd}"\n\n${HELP}`); process.exit(1);
|
|
158
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Cloudflare cron trigger. Hourly here (PocketBase's maintenance and settings.backups.cron are caught up to an hour late);
|
|
2
|
+
// `voidbase deploy` generates a project whose triggers are the hooks' own cronAdd expressions plus this hourly tick.
|
|
3
|
+
// `runDue` runs every job that became due since the previous tick.
|
|
4
|
+
import { defineScheduled } from "void";
|
|
5
|
+
import "../src/server/app"; // loads the pb_hooks so their cronAdd registrations exist
|
|
6
|
+
import { runDue } from "../src/server/crons";
|
|
7
|
+
|
|
8
|
+
export const cron = "0 * * * *";
|
|
9
|
+
|
|
10
|
+
export default defineScheduled(async (controller, env) => {
|
|
11
|
+
const ran = await runDue(env as never, new Date(controller.scheduledTime));
|
|
12
|
+
if (ran.length) console.log(`voidbase: cron ran ${ran.join(", ")}`);
|
|
13
|
+
});
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
CREATE TABLE `_authOrigins` (
|
|
2
|
+
`id` text PRIMARY KEY DEFAULT ('r'||lower(hex(randomblob(7)))) NOT NULL,
|
|
3
|
+
`collectionRef` text DEFAULT '' NOT NULL,
|
|
4
|
+
`recordRef` text DEFAULT '' NOT NULL,
|
|
5
|
+
`created` text DEFAULT '' NOT NULL,
|
|
6
|
+
`updated` text DEFAULT '' NOT NULL,
|
|
7
|
+
`fingerprint` text DEFAULT '' NOT NULL
|
|
8
|
+
);
|
|
9
|
+
--> statement-breakpoint
|
|
10
|
+
CREATE UNIQUE INDEX `idx_authOrigins_unique_pairs` ON `_authOrigins` (`collectionRef`,`recordRef`,`fingerprint`);--> statement-breakpoint
|
|
11
|
+
CREATE TABLE `_collections` (
|
|
12
|
+
`id` text PRIMARY KEY DEFAULT ('r'||lower(hex(randomblob(7)))) NOT NULL,
|
|
13
|
+
`system` integer DEFAULT false NOT NULL,
|
|
14
|
+
`type` text DEFAULT 'base' NOT NULL,
|
|
15
|
+
`name` text NOT NULL,
|
|
16
|
+
`fields` text DEFAULT '[]' NOT NULL,
|
|
17
|
+
`indexes` text DEFAULT '[]' NOT NULL,
|
|
18
|
+
`listRule` text,
|
|
19
|
+
`viewRule` text,
|
|
20
|
+
`createRule` text,
|
|
21
|
+
`updateRule` text,
|
|
22
|
+
`deleteRule` text,
|
|
23
|
+
`options` text DEFAULT '{}' NOT NULL,
|
|
24
|
+
`created` text DEFAULT (strftime('%Y-%m-%d %H:%M:%fZ')) NOT NULL,
|
|
25
|
+
`updated` text DEFAULT (strftime('%Y-%m-%d %H:%M:%fZ')) NOT NULL
|
|
26
|
+
);
|
|
27
|
+
--> statement-breakpoint
|
|
28
|
+
CREATE UNIQUE INDEX `_collections_name_unique` ON `_collections` (`name`);--> statement-breakpoint
|
|
29
|
+
CREATE INDEX `idx__collections_type` ON `_collections` (`type`);--> statement-breakpoint
|
|
30
|
+
CREATE TABLE `_externalAuths` (
|
|
31
|
+
`id` text PRIMARY KEY DEFAULT ('r'||lower(hex(randomblob(7)))) NOT NULL,
|
|
32
|
+
`collectionRef` text DEFAULT '' NOT NULL,
|
|
33
|
+
`recordRef` text DEFAULT '' NOT NULL,
|
|
34
|
+
`created` text DEFAULT '' NOT NULL,
|
|
35
|
+
`updated` text DEFAULT '' NOT NULL,
|
|
36
|
+
`provider` text DEFAULT '' NOT NULL,
|
|
37
|
+
`providerId` text DEFAULT '' NOT NULL
|
|
38
|
+
);
|
|
39
|
+
--> statement-breakpoint
|
|
40
|
+
CREATE UNIQUE INDEX `idx_externalAuths_record_provider` ON `_externalAuths` (`collectionRef`,`recordRef`,`provider`);--> statement-breakpoint
|
|
41
|
+
CREATE UNIQUE INDEX `idx_externalAuths_collection_provider` ON `_externalAuths` (`collectionRef`,`provider`,`providerId`);--> statement-breakpoint
|
|
42
|
+
CREATE TABLE `_mfas` (
|
|
43
|
+
`id` text PRIMARY KEY DEFAULT ('r'||lower(hex(randomblob(7)))) NOT NULL,
|
|
44
|
+
`collectionRef` text DEFAULT '' NOT NULL,
|
|
45
|
+
`recordRef` text DEFAULT '' NOT NULL,
|
|
46
|
+
`created` text DEFAULT '' NOT NULL,
|
|
47
|
+
`updated` text DEFAULT '' NOT NULL,
|
|
48
|
+
`method` text DEFAULT '' NOT NULL
|
|
49
|
+
);
|
|
50
|
+
--> statement-breakpoint
|
|
51
|
+
CREATE INDEX `idx_mfas_collectionRef_recordRef` ON `_mfas` (`collectionRef`,`recordRef`);--> statement-breakpoint
|
|
52
|
+
CREATE TABLE `_pbMigrations` (
|
|
53
|
+
`file` text PRIMARY KEY NOT NULL,
|
|
54
|
+
`applied` integer NOT NULL
|
|
55
|
+
);
|
|
56
|
+
--> statement-breakpoint
|
|
57
|
+
CREATE TABLE `_otps` (
|
|
58
|
+
`id` text PRIMARY KEY DEFAULT ('r'||lower(hex(randomblob(7)))) NOT NULL,
|
|
59
|
+
`collectionRef` text DEFAULT '' NOT NULL,
|
|
60
|
+
`recordRef` text DEFAULT '' NOT NULL,
|
|
61
|
+
`created` text DEFAULT '' NOT NULL,
|
|
62
|
+
`updated` text DEFAULT '' NOT NULL,
|
|
63
|
+
`password` text DEFAULT '' NOT NULL,
|
|
64
|
+
`sentTo` text DEFAULT '' NOT NULL
|
|
65
|
+
);
|
|
66
|
+
--> statement-breakpoint
|
|
67
|
+
CREATE INDEX `idx_otps_collectionRef_recordRef` ON `_otps` (`collectionRef`,`recordRef`);--> statement-breakpoint
|
|
68
|
+
CREATE TABLE `_params` (
|
|
69
|
+
`id` text PRIMARY KEY DEFAULT ('r'||lower(hex(randomblob(7)))) NOT NULL,
|
|
70
|
+
`value` text,
|
|
71
|
+
`created` text DEFAULT (strftime('%Y-%m-%d %H:%M:%fZ')) NOT NULL,
|
|
72
|
+
`updated` text DEFAULT (strftime('%Y-%m-%d %H:%M:%fZ')) NOT NULL
|
|
73
|
+
);
|
|
74
|
+
--> statement-breakpoint
|
|
75
|
+
CREATE TABLE `_superusers` (
|
|
76
|
+
`id` text PRIMARY KEY DEFAULT ('r'||lower(hex(randomblob(7)))) NOT NULL,
|
|
77
|
+
`password` text DEFAULT '' NOT NULL,
|
|
78
|
+
`tokenKey` text DEFAULT '' NOT NULL,
|
|
79
|
+
`email` text DEFAULT '' NOT NULL,
|
|
80
|
+
`emailVisibility` integer DEFAULT false NOT NULL,
|
|
81
|
+
`verified` integer DEFAULT false NOT NULL,
|
|
82
|
+
`created` text DEFAULT '' NOT NULL,
|
|
83
|
+
`updated` text DEFAULT '' NOT NULL
|
|
84
|
+
);
|
|
85
|
+
--> statement-breakpoint
|
|
86
|
+
CREATE UNIQUE INDEX `idx_tokenKey_pbc_3142635823` ON `_superusers` (`tokenKey`);--> statement-breakpoint
|
|
87
|
+
CREATE UNIQUE INDEX `idx_email_pbc_3142635823` ON `_superusers` (`email`) WHERE "_superusers"."email" != '';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
CREATE TABLE `_changes` (
|
|
2
|
+
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
|
3
|
+
`collection` text NOT NULL,
|
|
4
|
+
`recordId` text NOT NULL,
|
|
5
|
+
`action` text NOT NULL,
|
|
6
|
+
`created` text DEFAULT (strftime('%Y-%m-%d %H:%M:%fZ')) NOT NULL
|
|
7
|
+
);
|
|
8
|
+
--> statement-breakpoint
|
|
9
|
+
CREATE INDEX `idx__changes_collection` ON `_changes` (`collection`,`id`);--> statement-breakpoint
|
|
10
|
+
CREATE TABLE `_realtime_clients` (
|
|
11
|
+
`id` text PRIMARY KEY NOT NULL,
|
|
12
|
+
`subscriptions` text DEFAULT '[]' NOT NULL,
|
|
13
|
+
`token` text DEFAULT '' NOT NULL,
|
|
14
|
+
`created` text DEFAULT (strftime('%Y-%m-%d %H:%M:%fZ')) NOT NULL,
|
|
15
|
+
`updated` text DEFAULT (strftime('%Y-%m-%d %H:%M:%fZ')) NOT NULL
|
|
16
|
+
);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ALTER TABLE `_changes` ADD `data` text;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
CREATE TABLE `_logs` (
|
|
2
|
+
`id` text PRIMARY KEY NOT NULL,
|
|
3
|
+
`created` text DEFAULT '' NOT NULL,
|
|
4
|
+
`data` text DEFAULT '{}' NOT NULL,
|
|
5
|
+
`message` text DEFAULT '' NOT NULL,
|
|
6
|
+
`level` integer DEFAULT 0 NOT NULL
|
|
7
|
+
);
|
|
8
|
+
--> statement-breakpoint
|
|
9
|
+
CREATE INDEX `idx_logs_created` ON `_logs` (`created`);--> statement-breakpoint
|
|
10
|
+
CREATE INDEX `idx_logs_level` ON `_logs` (`level`);--> statement-breakpoint
|
|
11
|
+
CREATE INDEX `idx_logs_message` ON `_logs` (`message`);
|