@volter/world-platform 2.0.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.
Files changed (75) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +29 -0
  3. package/client/platform.css +228 -0
  4. package/client/platform.tsx +1049 -0
  5. package/client/reserved.ts +3 -0
  6. package/dist/client/platform.bundle.js +237 -0
  7. package/dist/client/platform.css +228 -0
  8. package/dist/client/platform.tsx +1049 -0
  9. package/dist/client/reserved.d.ts +1 -0
  10. package/dist/client/reserved.js +3 -0
  11. package/dist/client/reserved.ts +3 -0
  12. package/dist/src/audit.d.ts +24 -0
  13. package/dist/src/audit.js +23 -0
  14. package/dist/src/backup.d.ts +37 -0
  15. package/dist/src/backup.js +94 -0
  16. package/dist/src/biller.d.ts +59 -0
  17. package/dist/src/biller.js +1 -0
  18. package/dist/src/cli.d.ts +2 -0
  19. package/dist/src/cli.js +112 -0
  20. package/dist/src/db/migrations/0000_init.sql +143 -0
  21. package/dist/src/db/migrations/meta/0000_snapshot.json +877 -0
  22. package/dist/src/db/migrations/meta/_journal.json +13 -0
  23. package/dist/src/db/migrations.d.ts +4 -0
  24. package/dist/src/db/migrations.js +33 -0
  25. package/dist/src/db/open.d.ts +11 -0
  26. package/dist/src/db/open.js +114 -0
  27. package/dist/src/db/pack-migrations.d.ts +1 -0
  28. package/dist/src/db/pack-migrations.js +33 -0
  29. package/dist/src/db/schema.d.ts +1803 -0
  30. package/dist/src/db/schema.js +123 -0
  31. package/dist/src/directory.d.ts +75 -0
  32. package/dist/src/directory.js +199 -0
  33. package/dist/src/doors.d.ts +16 -0
  34. package/dist/src/doors.js +123 -0
  35. package/dist/src/identity.d.ts +88 -0
  36. package/dist/src/identity.js +314 -0
  37. package/dist/src/labs.d.ts +14 -0
  38. package/dist/src/labs.js +7 -0
  39. package/dist/src/mail.d.ts +16 -0
  40. package/dist/src/mail.js +27 -0
  41. package/dist/src/pages.d.ts +15 -0
  42. package/dist/src/pages.js +73 -0
  43. package/dist/src/platform.d.ts +77 -0
  44. package/dist/src/platform.js +1845 -0
  45. package/dist/src/sample.d.ts +11 -0
  46. package/dist/src/sample.js +93 -0
  47. package/dist/src/store.d.ts +110 -0
  48. package/dist/src/store.js +142 -0
  49. package/dist/src/tokens.d.ts +69 -0
  50. package/dist/src/tokens.js +96 -0
  51. package/dist/src/webhooks.d.ts +60 -0
  52. package/dist/src/webhooks.js +92 -0
  53. package/package.json +78 -0
  54. package/src/audit.ts +26 -0
  55. package/src/backup.ts +73 -0
  56. package/src/biller.ts +45 -0
  57. package/src/cli.ts +99 -0
  58. package/src/db/migrations/0000_init.sql +143 -0
  59. package/src/db/migrations/meta/0000_snapshot.json +877 -0
  60. package/src/db/migrations/meta/_journal.json +13 -0
  61. package/src/db/migrations.ts +33 -0
  62. package/src/db/open.ts +92 -0
  63. package/src/db/pack-migrations.ts +19 -0
  64. package/src/db/schema.ts +137 -0
  65. package/src/directory.ts +216 -0
  66. package/src/doors.ts +138 -0
  67. package/src/identity.ts +279 -0
  68. package/src/labs.ts +8 -0
  69. package/src/mail.ts +27 -0
  70. package/src/pages.ts +69 -0
  71. package/src/platform.ts +1183 -0
  72. package/src/sample.ts +84 -0
  73. package/src/store.ts +154 -0
  74. package/src/tokens.ts +94 -0
  75. package/src/webhooks.ts +85 -0
@@ -0,0 +1,13 @@
1
+ {
2
+ "version": "7",
3
+ "dialect": "sqlite",
4
+ "entries": [
5
+ {
6
+ "idx": 0,
7
+ "version": "6",
8
+ "when": 1790550512216,
9
+ "tag": "0000_init",
10
+ "breakpoints": true
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,4 @@
1
+ export declare const MIGRATIONS: ReadonlyArray<{
2
+ tag: string;
3
+ statements: readonly string[];
4
+ }>;
@@ -0,0 +1,33 @@
1
+ // GENERATED by `bun src/db/pack-migrations.ts --write` from ./migrations/*.sql (drizzle-kit's): do not edit.
2
+ export const MIGRATIONS = [
3
+ {
4
+ "tag": "0000_init",
5
+ "statements": [
6
+ "CREATE TABLE `audit` (\n\t`seq` integer PRIMARY KEY AUTOINCREMENT NOT NULL,\n\t`org` text NOT NULL,\n\t`at` text NOT NULL,\n\t`event` text NOT NULL,\n\t`actor` text NOT NULL,\n\t`target` text,\n\t`data` text\n);",
7
+ "CREATE INDEX `audit_org` ON `audit` (`org`,`seq`);",
8
+ "CREATE TABLE `biller_records` (\n\t`key` text PRIMARY KEY NOT NULL,\n\t`value` text NOT NULL\n);",
9
+ "CREATE TABLE `deliveries` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`org_id` text NOT NULL,\n\t`endpoint_id` text NOT NULL,\n\t`event_id` text NOT NULL,\n\t`event` text NOT NULL,\n\t`body` text NOT NULL,\n\t`attempt` integer NOT NULL,\n\t`next_at` text NOT NULL,\n\t`attempts` text NOT NULL,\n\t`state` text NOT NULL,\n\t`seq` integer NOT NULL\n);",
10
+ "CREATE INDEX `deliveries_state` ON `deliveries` (`state`,`next_at`);",
11
+ "CREATE INDEX `deliveries_endpoint` ON `deliveries` (`endpoint_id`);",
12
+ "CREATE TABLE `dir_members` (\n\t`org_id` text NOT NULL,\n\t`user_id` text NOT NULL,\n\t`role` text NOT NULL,\n\tPRIMARY KEY(`org_id`, `user_id`)\n);",
13
+ "CREATE INDEX `dir_members_user` ON `dir_members` (`user_id`);",
14
+ "CREATE TABLE `dir_orgs` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`slug` text NOT NULL,\n\t`name` text NOT NULL,\n\t`created_at` text NOT NULL\n);",
15
+ "CREATE UNIQUE INDEX `dir_orgs_slug_unique` ON `dir_orgs` (`slug`);",
16
+ "CREATE TABLE `invitations` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`org_id` text NOT NULL,\n\t`email` text NOT NULL,\n\t`role` text NOT NULL,\n\t`inviter_id` text NOT NULL,\n\t`created_at` text NOT NULL,\n\t`expires_at` text NOT NULL\n);",
17
+ "CREATE INDEX `invitations_org` ON `invitations` (`org_id`);",
18
+ "CREATE INDEX `invitations_email` ON `invitations` (`email`);",
19
+ "CREATE TABLE `meta` (\n\t`key` text PRIMARY KEY NOT NULL,\n\t`value` text\n);",
20
+ "CREATE TABLE `org_settings` (\n\t`org_id` text PRIMARY KEY NOT NULL,\n\t`security` text,\n\t`flags` text,\n\t`early_adopter` integer,\n\t`support_access` text\n);",
21
+ "CREATE TABLE `org_worlds` (\n\t`org_id` text NOT NULL,\n\t`name` text NOT NULL,\n\t`host` text NOT NULL,\n\t`base` text NOT NULL,\n\t`provisioned_at` text NOT NULL,\n\tPRIMARY KEY(`org_id`, `name`)\n);",
22
+ "CREATE TABLE `people` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`email` text,\n\t`email_verified` integer NOT NULL,\n\t`name` text,\n\t`first_seen_at` text NOT NULL,\n\t`last_seen_at` text NOT NULL\n);",
23
+ "CREATE INDEX `people_email` ON `people` (`email`);",
24
+ "CREATE TABLE `sessions` (\n\t`id_hash` text PRIMARY KEY NOT NULL,\n\t`subject` text NOT NULL,\n\t`email` text,\n\t`name` text,\n\t`expires_at` text NOT NULL\n);",
25
+ "CREATE TABLE `support_requests` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`at` text NOT NULL,\n\t`person` text NOT NULL,\n\t`org` text,\n\t`world` text,\n\t`category` text NOT NULL,\n\t`severity` text NOT NULL,\n\t`subject` text NOT NULL,\n\t`message` text NOT NULL,\n\t`status` text NOT NULL\n);",
26
+ "CREATE TABLE `tokens` (\n\t`hash` text PRIMARY KEY NOT NULL,\n\t`user_id` text NOT NULL,\n\t`name` text NOT NULL,\n\t`created_at` text NOT NULL,\n\t`last4` text NOT NULL,\n\t`last_used_at` text,\n\t`expires_at` text,\n\t`scopes` text,\n\t`org_id` text,\n\t`support` text,\n\t`world_keys` text\n);",
27
+ "CREATE INDEX `tokens_user` ON `tokens` (`user_id`);",
28
+ "CREATE INDEX `tokens_org` ON `tokens` (`org_id`);",
29
+ "CREATE TABLE `webhooks` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`org_id` text NOT NULL,\n\t`url` text NOT NULL,\n\t`secret` text NOT NULL,\n\t`events` text NOT NULL,\n\t`description` text NOT NULL,\n\t`created_at` text NOT NULL,\n\t`consecutive_failures` integer NOT NULL,\n\t`last_failed_at` text,\n\t`disabled_at` text\n);",
30
+ "CREATE INDEX `webhooks_org` ON `webhooks` (`org_id`);"
31
+ ]
32
+ }
33
+ ];
@@ -0,0 +1,11 @@
1
+ import { BaseSQLiteDatabase } from 'drizzle-orm/sqlite-core';
2
+ export type PlatformDb = BaseSQLiteDatabase<'sync', any>;
3
+ /** The database's file in the state directory, and a restored one staged beside it (backup.ts). */
4
+ export declare const DB_FILE = "platform.db";
5
+ export declare const RESTORE_FILE = "platform.db.restore";
6
+ /** The platform's database at `<stateDir>/platform.db` (`:memory:` with no state directory), opened once per process.
7
+ * A restore staged since the last start (`platform.db.restore`) is put in place first. */
8
+ export declare function openDatabase(stateDir?: string): PlatformDb;
9
+ /** Close the state directory's database in this process (its file can then be moved or removed); the next
10
+ * openDatabase opens it again. */
11
+ export declare function closeDatabase(stateDir: string): void;
@@ -0,0 +1,114 @@
1
+ // OPENING THE PLATFORM'S DATABASE (docs/contributing/architecture.md, "The platform's state is SQLite"): one Drizzle
2
+ // database, synchronous on every driver the platform runs on — Bun's `bun:sqlite`, Node's built-in `node:sqlite` (behind
3
+ // Drizzle's better-sqlite3 session, which it answers like), and a Durable Object's SQLite (`openDurableDatabase`). The
4
+ // migrations (./migrations.ts) are applied once, in order, and recorded.
5
+ import { createRequire } from 'node:module';
6
+ import { chmodSync, existsSync, mkdirSync, renameSync, rmSync, writeFileSync } from 'node:fs';
7
+ import { dirname, join, resolve } from 'node:path';
8
+ import { BaseSQLiteDatabase, SQLiteSyncDialect } from 'drizzle-orm/sqlite-core';
9
+ import { BetterSQLiteSession } from 'drizzle-orm/better-sqlite3/session';
10
+ import { MIGRATIONS } from "./migrations.js";
11
+ /** Node's DatabaseSync, answering as better-sqlite3 does where Drizzle's session reads it. */
12
+ function nodeClient(path) {
13
+ const { DatabaseSync } = createRequire(import.meta.url)('node:sqlite');
14
+ const raw = new DatabaseSync(path);
15
+ const prepare = (sql) => {
16
+ const st = raw.prepare(sql);
17
+ const arrays = (fn) => { st.setReturnArrays(true); try {
18
+ return fn();
19
+ }
20
+ finally {
21
+ st.setReturnArrays(false);
22
+ } };
23
+ return {
24
+ run: (...p) => { const r = st.run(...p); return { changes: Number(r.changes), lastInsertRowid: r.lastInsertRowid }; },
25
+ all: (...p) => st.all(...p),
26
+ get: (...p) => st.get(...p),
27
+ raw: () => ({ all: (...p) => arrays(() => st.all(...p)), get: (...p) => arrays(() => st.get(...p)) }),
28
+ };
29
+ };
30
+ // better-sqlite3's transaction(fn): a function that runs fn between BEGIN and COMMIT, rolling back on a throw
31
+ const transaction = (fn) => {
32
+ const run = (mode) => (...a) => { raw.exec(`BEGIN ${mode}`); try {
33
+ const r = fn(...a);
34
+ raw.exec('COMMIT');
35
+ return r;
36
+ }
37
+ catch (e) {
38
+ raw.exec('ROLLBACK');
39
+ throw e;
40
+ } };
41
+ return Object.assign(run('DEFERRED'), { deferred: run('DEFERRED'), immediate: run('IMMEDIATE'), exclusive: run('EXCLUSIVE') });
42
+ };
43
+ return { prepare, transaction, exec: (sql) => raw.exec(sql), close: () => raw.close() };
44
+ }
45
+ /** Apply what has not been applied, each migration in one transaction, and record it. */
46
+ function migrate(exec, applied, inTransaction) {
47
+ exec('CREATE TABLE IF NOT EXISTS _migrations (tag TEXT PRIMARY KEY, applied_at TEXT NOT NULL)');
48
+ const done = new Set(applied());
49
+ for (const m of MIGRATIONS) {
50
+ if (done.has(m.tag))
51
+ continue;
52
+ inTransaction(() => { for (const s of m.statements)
53
+ exec(s); exec(`INSERT INTO _migrations (tag, applied_at) VALUES ('${m.tag.replace(/'/g, "''")}', '${new Date().toISOString()}')`); });
54
+ }
55
+ }
56
+ /** The database's file in the state directory, and a restored one staged beside it (backup.ts). */
57
+ export const DB_FILE = 'platform.db';
58
+ export const RESTORE_FILE = 'platform.db.restore';
59
+ const opened = new Map();
60
+ const closers = new Map();
61
+ /** The platform's database at `<stateDir>/platform.db` (`:memory:` with no state directory), opened once per process.
62
+ * A restore staged since the last start (`platform.db.restore`) is put in place first. */
63
+ export function openDatabase(stateDir) {
64
+ const path = stateDir ? join(resolve(stateDir), DB_FILE) : ':memory:';
65
+ const known = opened.get(path);
66
+ if (known)
67
+ return known;
68
+ if (stateDir) {
69
+ mkdirSync(dirname(path), { recursive: true });
70
+ const staged = join(resolve(stateDir), RESTORE_FILE);
71
+ if (existsSync(staged)) {
72
+ for (const f of [`${path}-wal`, `${path}-shm`])
73
+ rmSync(f, { force: true });
74
+ renameSync(staged, path);
75
+ }
76
+ // owner-only, like the platform's other state; SQLite gives its write-ahead log and index the database's mode
77
+ if (!existsSync(path))
78
+ writeFileSync(path, '', { mode: 0o600 });
79
+ chmodSync(path, 0o600);
80
+ }
81
+ const req = createRequire(import.meta.url);
82
+ let db;
83
+ if (globalThis.Bun) {
84
+ const { Database } = req('bun:sqlite');
85
+ const { drizzle } = req('drizzle-orm/bun-sqlite');
86
+ const raw = new Database(path, { create: true });
87
+ raw.run('PRAGMA journal_mode = WAL');
88
+ raw.run('PRAGMA busy_timeout = 5000');
89
+ raw.run('PRAGMA foreign_keys = ON');
90
+ migrate((s) => raw.run(s), () => raw.query('SELECT tag FROM _migrations').all().map((r) => r.tag), (fn) => raw.transaction(fn)());
91
+ db = drizzle(raw);
92
+ closers.set(path, () => raw.close());
93
+ }
94
+ else {
95
+ const client = nodeClient(path);
96
+ client.exec('PRAGMA journal_mode = WAL');
97
+ client.exec('PRAGMA busy_timeout = 5000');
98
+ client.exec('PRAGMA foreign_keys = ON');
99
+ migrate((s) => client.exec(s), () => client.prepare('SELECT tag FROM _migrations').all().map((r) => r.tag), (fn) => client.transaction(fn)());
100
+ const dialect = new SQLiteSyncDialect();
101
+ db = new BaseSQLiteDatabase('sync', dialect, new BetterSQLiteSession(client, dialect, undefined, {}), undefined);
102
+ closers.set(path, client.close);
103
+ }
104
+ opened.set(path, db);
105
+ return db;
106
+ }
107
+ /** Close the state directory's database in this process (its file can then be moved or removed); the next
108
+ * openDatabase opens it again. */
109
+ export function closeDatabase(stateDir) {
110
+ const path = join(resolve(stateDir), DB_FILE);
111
+ closers.get(path)?.();
112
+ closers.delete(path);
113
+ opened.delete(path);
114
+ }
@@ -0,0 +1 @@
1
+ export declare function packed(): string;
@@ -0,0 +1,33 @@
1
+ // The migrations as a module (./migrations.ts): what `drizzle-kit generate` wrote under ./migrations, split into
2
+ // statements, so every driver (a Durable Object reads no files) applies the same SQL.
3
+ // bun src/db/pack-migrations.ts --write after `drizzle-kit generate --dialect sqlite --schema ./src/db/schema.ts --out ./src/db/migrations`
4
+ // bun src/db/pack-migrations.ts --check exits 1 when migrations.ts is behind the .sql files
5
+ import { readdirSync, readFileSync, writeFileSync } from 'node:fs';
6
+ import { join } from 'node:path';
7
+ const dir = join(import.meta.dir, 'migrations');
8
+ const target = join(import.meta.dir, 'migrations.ts');
9
+ export function packed() {
10
+ const files = readdirSync(dir).filter((f) => /^\d{4}_.+\.sql$/.test(f)).sort();
11
+ const list = files.map((f) => ({ tag: f.replace(/\.sql$/, ''), statements: readFileSync(join(dir, f), 'utf8').split('--> statement-breakpoint').map((s) => s.trim()).filter(Boolean) }));
12
+ return `// GENERATED by \`bun src/db/pack-migrations.ts --write\` from ./migrations/*.sql (drizzle-kit's): do not edit.\nexport const MIGRATIONS: ReadonlyArray<{ tag: string; statements: readonly string[] }> = ${JSON.stringify(list, null, 2)};\n`;
13
+ }
14
+ if (import.meta.main) {
15
+ if (process.argv.includes('--write')) {
16
+ writeFileSync(target, packed());
17
+ console.log(`wrote ${target}`);
18
+ }
19
+ else if (process.argv.includes('--check')) {
20
+ let current = '';
21
+ try {
22
+ current = readFileSync(target, 'utf8');
23
+ }
24
+ catch { /* none */ }
25
+ if (current.replace(/\r\n/g, '\n') !== packed()) {
26
+ console.error('src/db/migrations.ts is behind src/db/migrations/*.sql — run: bun src/db/pack-migrations.ts --write');
27
+ process.exit(1);
28
+ }
29
+ console.log('migrations.ts is current');
30
+ }
31
+ else
32
+ process.stdout.write(packed());
33
+ }