bunderstack 0.18.0 → 0.19.1

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 (110) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/access.js.map +1 -1
  3. package/dist/api/builder.js.map +1 -1
  4. package/dist/api/context.js.map +1 -1
  5. package/dist/api/crud-router.d.ts +121 -0
  6. package/dist/api/crud-router.d.ts.map +1 -1
  7. package/dist/api/crud-router.js +110 -4
  8. package/dist/api/crud-router.js.map +1 -1
  9. package/dist/api/list-input-schema.d.ts +15 -0
  10. package/dist/api/list-input-schema.d.ts.map +1 -1
  11. package/dist/api/list-input-schema.js +16 -0
  12. package/dist/api/list-input-schema.js.map +1 -1
  13. package/dist/api/list-spec.js.map +1 -1
  14. package/dist/api/live-view.d.ts +14 -0
  15. package/dist/api/live-view.d.ts.map +1 -0
  16. package/dist/api/live-view.js +42 -0
  17. package/dist/api/live-view.js.map +1 -0
  18. package/dist/api/live-window.d.ts +40 -0
  19. package/dist/api/live-window.d.ts.map +1 -0
  20. package/dist/api/live-window.js +83 -0
  21. package/dist/api/live-window.js.map +1 -0
  22. package/dist/api/openapi.js.map +1 -1
  23. package/dist/api/realtime-router.js.map +1 -1
  24. package/dist/api/registry.js.map +1 -1
  25. package/dist/api/router.js.map +1 -1
  26. package/dist/api/storage-router.js.map +1 -1
  27. package/dist/api/types.d.ts +1 -1
  28. package/dist/api/types.d.ts.map +1 -1
  29. package/dist/api/types.js.map +1 -1
  30. package/dist/auth.js.map +1 -1
  31. package/dist/blueprint-generator.js.map +1 -1
  32. package/dist/blueprint.js.map +1 -1
  33. package/dist/cli-skills.js.map +1 -1
  34. package/dist/cli.js.map +1 -1
  35. package/dist/config.js.map +1 -1
  36. package/dist/cron.js.map +1 -1
  37. package/dist/crud-operations.js.map +1 -1
  38. package/dist/database/adapter.js.map +1 -1
  39. package/dist/database/bun-sql.js.map +1 -1
  40. package/dist/database/libsql.js.map +1 -1
  41. package/dist/database/pglite.js.map +1 -1
  42. package/dist/database/postgres-js.js.map +1 -1
  43. package/dist/db.js.map +1 -1
  44. package/dist/dialect.js.map +1 -1
  45. package/dist/email/smtp.js.map +1 -1
  46. package/dist/email.js.map +1 -1
  47. package/dist/env.js.map +1 -1
  48. package/dist/errors.js.map +1 -1
  49. package/dist/handler.js.map +1 -1
  50. package/dist/idempotency.js.map +1 -1
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +1 -0
  53. package/dist/index.js.map +1 -1
  54. package/dist/internal-tables-pg.js.map +1 -1
  55. package/dist/internal-tables.js.map +1 -1
  56. package/dist/jobs/cron.js.map +1 -1
  57. package/dist/jobs/define.js.map +1 -1
  58. package/dist/jobs/index.js.map +1 -1
  59. package/dist/jobs/queue.js.map +1 -1
  60. package/dist/jobs/runtime.js.map +1 -1
  61. package/dist/jobs/slots.js.map +1 -1
  62. package/dist/jobs/worker.js.map +1 -1
  63. package/dist/lifecycle.js.map +1 -1
  64. package/dist/list-query.js.map +1 -1
  65. package/dist/live/apply.d.ts +13 -0
  66. package/dist/live/apply.d.ts.map +1 -0
  67. package/dist/live/apply.js +32 -0
  68. package/dist/live/apply.js.map +1 -0
  69. package/dist/live/index.d.ts +48 -0
  70. package/dist/live/index.d.ts.map +1 -0
  71. package/dist/live/index.js +108 -0
  72. package/dist/live/index.js.map +1 -0
  73. package/dist/live/protocol.d.ts +44 -0
  74. package/dist/live/protocol.d.ts.map +1 -0
  75. package/dist/live/protocol.js +12 -0
  76. package/dist/live/protocol.js.map +1 -0
  77. package/dist/live/sse.d.ts +22 -0
  78. package/dist/live/sse.d.ts.map +1 -0
  79. package/dist/live/sse.js +41 -0
  80. package/dist/live/sse.js.map +1 -0
  81. package/dist/manifest.js.map +1 -1
  82. package/dist/provision-internals.js.map +1 -1
  83. package/dist/provision.js.map +1 -1
  84. package/dist/rate-limit.js.map +1 -1
  85. package/dist/realtime/facade.js.map +1 -1
  86. package/dist/realtime/filter.d.ts +34 -5
  87. package/dist/realtime/filter.d.ts.map +1 -1
  88. package/dist/realtime/filter.js +62 -25
  89. package/dist/realtime/filter.js.map +1 -1
  90. package/dist/realtime/heartbeat.js.map +1 -1
  91. package/dist/realtime/publisher.js.map +1 -1
  92. package/dist/schema-export-pg.js.map +1 -1
  93. package/dist/schema-export.js.map +1 -1
  94. package/dist/scope.js.map +1 -1
  95. package/dist/standard-schema.js.map +1 -1
  96. package/dist/storage/buckets.js.map +1 -1
  97. package/dist/storage/delete.js.map +1 -1
  98. package/dist/storage/file-meta.js.map +1 -1
  99. package/dist/storage/index.js.map +1 -1
  100. package/dist/storage/local.js.map +1 -1
  101. package/dist/storage/operations.js.map +1 -1
  102. package/dist/storage/registry.js.map +1 -1
  103. package/dist/storage/s3.js.map +1 -1
  104. package/dist/storage/sweep.js.map +1 -1
  105. package/dist/storage/thumbnails.js.map +1 -1
  106. package/dist/testing.js.map +1 -1
  107. package/dist/typeid-pg.js.map +1 -1
  108. package/dist/typeid.js.map +1 -1
  109. package/llms.txt +3 -3
  110. package/package.json +6 -2
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply.js","sourceRoot":"","sources":["../../src/live/apply.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAC5B,IAAqB,EACrB,KAAsB;IAEtB,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU;QAAE,OAAO,KAAK,CAAC,KAAK,CAAA;IACjD,IAAI,KAAK,CAAC,IAAI,KAAK,WAAW;QAAE,OAAO,IAAI,CAAA;IAE3C,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC5B,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,KAAK,CAAC,EAAE,CAAC,CAAA;QACtD,OAAO,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAA;IAClD,CAAC;IAED,0DAA0D;IAC1D,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;IAC7D,IAAI,KAAa,CAAA;IACjB,IAAI,KAAK,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;QAC3B,KAAK,GAAG,CAAC,CAAA;IACX,CAAC;SAAM,CAAC;QACN,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,KAAK,CAAC,OAAO,CAAC,CAAA;QAChE,qEAAqE;QACrE,KAAK,GAAG,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAA;IAClD,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;IACnC,OAAO,IAAI,CAAA;AACb,CAAC","sourcesContent":["import type { LiveFrame } from './protocol'\n\n/**\n * One frame folded into a view's rows.\n *\n * Pure and immutable: a changed view is a new array, an unchanged view is the\n * same array, and a row the frame did not touch keeps its identity. That is\n * what `useSyncExternalStore` and a keyed list need to skip the rows a frame\n * did not change.\n */\nexport function applyLiveFrame<TRow extends { id: string }>(\n rows: readonly TRow[],\n frame: LiveFrame<TRow>,\n): readonly TRow[] {\n if (frame.type === 'snapshot') return frame.items\n if (frame.type === 'heartbeat') return rows\n\n if (frame.type === 'remove') {\n const next = rows.filter((row) => row.id !== frame.id)\n return next.length === rows.length ? rows : next\n }\n\n // An upsert can move a row, so the stale copy goes first.\n const next = rows.filter((row) => row.id !== frame.record.id)\n let place: number\n if (frame.afterId === null) {\n place = 0\n } else {\n const anchor = next.findIndex((row) => row.id === frame.afterId)\n // An anchor the view does not hold means the row belongs at the end.\n place = anchor === -1 ? next.length : anchor + 1\n }\n next.splice(place, 0, frame.record)\n return next\n}\n"]}
@@ -0,0 +1,48 @@
1
+ import type { LiveInput } from './protocol.js';
2
+ export type { LiveDeltaFrame, LiveFrame, LiveHeartbeatFrame, LiveInput, LiveRemoveFrame, LiveSnapshotFrame, LiveUpsertFrame, } from './protocol.js';
3
+ export { applyLiveFrame } from './apply.js';
4
+ export { parseSseFrames, type ByteStream } from './sse.js';
5
+ export type LiveStatus = 'connecting' | 'live' | 'reconnecting' | 'failed';
6
+ export type LiveView<TRow extends {
7
+ id: string;
8
+ }> = {
9
+ /** The current rows. A new array appears only when the view changes. */
10
+ getRows: () => readonly TRow[];
11
+ getStatus: () => LiveStatus;
12
+ getError: () => unknown;
13
+ /** Register a listener; the return value removes it. */
14
+ subscribe: (listener: () => void) => () => void;
15
+ /**
16
+ * Write to the view before the server confirms. Replace a row rather than
17
+ * mutating it, so consumers that compare by identity see the change:
18
+ * `patch((rows) => { rows[0] = { ...rows[0], done: true } })`.
19
+ * The server echo replaces the write, and `resync()` discards it.
20
+ */
21
+ patch: (recipe: (rows: TRow[]) => void) => void;
22
+ /** Reconnect. The new snapshot is the resynchronisation. */
23
+ resync: () => void;
24
+ /** Stop the loop and abort the request. */
25
+ close: () => void;
26
+ };
27
+ export type CreateLiveViewOptions = {
28
+ input?: LiveInput;
29
+ /** For tests, and for a fetch that carries credentials or a base URL. */
30
+ fetch?: (input: string, init?: RequestInit) => Promise<Response>;
31
+ /** Delay in milliseconds before retry number `attempt`, counted from zero. */
32
+ backoff?: (attempt: number) => number;
33
+ };
34
+ /**
35
+ * One live view: a reconnecting loop over `GET /api/live/{table}` that folds
36
+ * frames into an immutable array of rows.
37
+ *
38
+ * The view holds no cache and no second copy of the data. Every connection
39
+ * opens with a snapshot, so a reconnect is the resynchronisation, and the
40
+ * server places each row, so this module never sorts.
41
+ *
42
+ * `subscribe` plus `getRows` is the pair `useSyncExternalStore` expects. A
43
+ * Solid or Vue binding writes `getRows()` into a store from the same listener.
44
+ */
45
+ export declare function createLiveView<TRow extends {
46
+ id: string;
47
+ }>(url: string, options?: CreateLiveViewOptions): LiveView<TRow>;
48
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/live/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAa,SAAS,EAAE,MAAM,YAAY,CAAA;AAKtD,YAAY,EACV,cAAc,EACd,SAAS,EACT,kBAAkB,EAClB,SAAS,EACT,eAAe,EACf,iBAAiB,EACjB,eAAe,GAChB,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AACxC,OAAO,EAAE,cAAc,EAAE,KAAK,UAAU,EAAE,MAAM,OAAO,CAAA;AAEvD,MAAM,MAAM,UAAU,GAAG,YAAY,GAAG,MAAM,GAAG,cAAc,GAAG,QAAQ,CAAA;AAE1E,MAAM,MAAM,QAAQ,CAAC,IAAI,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,IAAI;IAClD,wEAAwE;IACxE,OAAO,EAAE,MAAM,SAAS,IAAI,EAAE,CAAA;IAC9B,SAAS,EAAE,MAAM,UAAU,CAAA;IAC3B,QAAQ,EAAE,MAAM,OAAO,CAAA;IACvB,wDAAwD;IACxD,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAA;IAC/C;;;;;OAKG;IACH,KAAK,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,IAAI,KAAK,IAAI,CAAA;IAC/C,4DAA4D;IAC5D,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,2CAA2C;IAC3C,KAAK,EAAE,MAAM,IAAI,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG;IAClC,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,yEAAyE;IACzE,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;IAChE,8EAA8E;IAC9E,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAA;CACtC,CAAA;AAoBD;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,IAAI,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EACxD,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,qBAA0B,GAClC,QAAQ,CAAC,IAAI,CAAC,CA4EhB"}
@@ -0,0 +1,108 @@
1
+ import { applyLiveFrame } from './apply.js';
2
+ import { parseSseFrames } from './sse.js';
3
+ export { applyLiveFrame } from './apply.js';
4
+ export { parseSseFrames } from './sse.js';
5
+ /** Full jitter, the same shape the realtime client uses. */
6
+ const defaultBackoff = (attempt) => Math.floor(Math.random() * Math.min(30_000, 1_000 * 2 ** attempt));
7
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
8
+ function buildUrl(url, input) {
9
+ if (!input)
10
+ return url;
11
+ const params = new URLSearchParams();
12
+ if (input.limit !== undefined)
13
+ params.set('limit', String(input.limit));
14
+ if (input.sort)
15
+ params.set('sort', input.sort);
16
+ if (input.order)
17
+ params.set('order', input.order);
18
+ if (input.filters && Object.keys(input.filters).length > 0) {
19
+ params.set('filters', JSON.stringify(input.filters));
20
+ }
21
+ return params.size > 0 ? `${url}?${params}` : url;
22
+ }
23
+ /**
24
+ * One live view: a reconnecting loop over `GET /api/live/{table}` that folds
25
+ * frames into an immutable array of rows.
26
+ *
27
+ * The view holds no cache and no second copy of the data. Every connection
28
+ * opens with a snapshot, so a reconnect is the resynchronisation, and the
29
+ * server places each row, so this module never sorts.
30
+ *
31
+ * `subscribe` plus `getRows` is the pair `useSyncExternalStore` expects. A
32
+ * Solid or Vue binding writes `getRows()` into a store from the same listener.
33
+ */
34
+ export function createLiveView(url, options = {}) {
35
+ const fetchImpl = options.fetch ?? globalThis.fetch;
36
+ const backoff = options.backoff ?? defaultBackoff;
37
+ const listeners = new Set();
38
+ let rows = [];
39
+ let status = 'connecting';
40
+ let error;
41
+ let closed = false;
42
+ let controller;
43
+ const notify = () => {
44
+ for (const listener of listeners)
45
+ listener();
46
+ };
47
+ void (async () => {
48
+ let attempt = 0;
49
+ while (!closed) {
50
+ controller = new AbortController();
51
+ try {
52
+ const response = await fetchImpl(buildUrl(url, options.input), {
53
+ signal: controller.signal,
54
+ headers: { accept: 'text/event-stream' },
55
+ });
56
+ if (!response.ok || !response.body) {
57
+ throw new Error(`live request failed (${response.status})`);
58
+ }
59
+ for await (const frame of parseSseFrames(response.body)) {
60
+ attempt = 0;
61
+ const next = applyLiveFrame(rows, frame);
62
+ const changed = next !== rows || status !== 'live' || error !== undefined;
63
+ rows = next;
64
+ status = 'live';
65
+ error = undefined;
66
+ if (changed)
67
+ notify();
68
+ }
69
+ // A live view never ends on its own, so reaching here is a drop.
70
+ throw new Error('live stream closed');
71
+ }
72
+ catch (cause) {
73
+ if (closed)
74
+ return;
75
+ error = cause;
76
+ // Rows on screen mean the view still works while it reconnects.
77
+ status = rows.length > 0 ? 'reconnecting' : 'failed';
78
+ notify();
79
+ await sleep(backoff(attempt++));
80
+ }
81
+ }
82
+ })();
83
+ return {
84
+ getRows: () => rows,
85
+ getStatus: () => status,
86
+ getError: () => error,
87
+ subscribe: (listener) => {
88
+ listeners.add(listener);
89
+ return () => {
90
+ listeners.delete(listener);
91
+ };
92
+ },
93
+ patch: (recipe) => {
94
+ const draft = [...rows];
95
+ recipe(draft);
96
+ rows = draft;
97
+ notify();
98
+ },
99
+ resync: () => {
100
+ controller?.abort();
101
+ },
102
+ close: () => {
103
+ closed = true;
104
+ controller?.abort();
105
+ },
106
+ };
107
+ }
108
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/live/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { LiveFrame, LiveInput } from './protocol'\n\nimport { applyLiveFrame } from './apply'\nimport { parseSseFrames } from './sse'\n\nexport type {\n LiveDeltaFrame,\n LiveFrame,\n LiveHeartbeatFrame,\n LiveInput,\n LiveRemoveFrame,\n LiveSnapshotFrame,\n LiveUpsertFrame,\n} from './protocol'\nexport { applyLiveFrame } from './apply'\nexport { parseSseFrames, type ByteStream } from './sse'\n\nexport type LiveStatus = 'connecting' | 'live' | 'reconnecting' | 'failed'\n\nexport type LiveView<TRow extends { id: string }> = {\n /** The current rows. A new array appears only when the view changes. */\n getRows: () => readonly TRow[]\n getStatus: () => LiveStatus\n getError: () => unknown\n /** Register a listener; the return value removes it. */\n subscribe: (listener: () => void) => () => void\n /**\n * Write to the view before the server confirms. Replace a row rather than\n * mutating it, so consumers that compare by identity see the change:\n * `patch((rows) => { rows[0] = { ...rows[0], done: true } })`.\n * The server echo replaces the write, and `resync()` discards it.\n */\n patch: (recipe: (rows: TRow[]) => void) => void\n /** Reconnect. The new snapshot is the resynchronisation. */\n resync: () => void\n /** Stop the loop and abort the request. */\n close: () => void\n}\n\nexport type CreateLiveViewOptions = {\n input?: LiveInput\n /** For tests, and for a fetch that carries credentials or a base URL. */\n fetch?: (input: string, init?: RequestInit) => Promise<Response>\n /** Delay in milliseconds before retry number `attempt`, counted from zero. */\n backoff?: (attempt: number) => number\n}\n\n/** Full jitter, the same shape the realtime client uses. */\nconst defaultBackoff = (attempt: number): number =>\n Math.floor(Math.random() * Math.min(30_000, 1_000 * 2 ** attempt))\n\nconst sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))\n\nfunction buildUrl(url: string, input: LiveInput | undefined): string {\n if (!input) return url\n const params = new URLSearchParams()\n if (input.limit !== undefined) params.set('limit', String(input.limit))\n if (input.sort) params.set('sort', input.sort)\n if (input.order) params.set('order', input.order)\n if (input.filters && Object.keys(input.filters).length > 0) {\n params.set('filters', JSON.stringify(input.filters))\n }\n return params.size > 0 ? `${url}?${params}` : url\n}\n\n/**\n * One live view: a reconnecting loop over `GET /api/live/{table}` that folds\n * frames into an immutable array of rows.\n *\n * The view holds no cache and no second copy of the data. Every connection\n * opens with a snapshot, so a reconnect is the resynchronisation, and the\n * server places each row, so this module never sorts.\n *\n * `subscribe` plus `getRows` is the pair `useSyncExternalStore` expects. A\n * Solid or Vue binding writes `getRows()` into a store from the same listener.\n */\nexport function createLiveView<TRow extends { id: string }>(\n url: string,\n options: CreateLiveViewOptions = {},\n): LiveView<TRow> {\n const fetchImpl = options.fetch ?? globalThis.fetch\n const backoff = options.backoff ?? defaultBackoff\n const listeners = new Set<() => void>()\n\n let rows: readonly TRow[] = []\n let status: LiveStatus = 'connecting'\n let error: unknown\n let closed = false\n let controller: AbortController | undefined\n\n const notify = () => {\n for (const listener of listeners) listener()\n }\n\n void (async () => {\n let attempt = 0\n while (!closed) {\n controller = new AbortController()\n try {\n const response = await fetchImpl(buildUrl(url, options.input), {\n signal: controller.signal,\n headers: { accept: 'text/event-stream' },\n })\n if (!response.ok || !response.body) {\n throw new Error(`live request failed (${response.status})`)\n }\n for await (const frame of parseSseFrames<LiveFrame<TRow>>(\n response.body,\n )) {\n attempt = 0\n const next = applyLiveFrame(rows, frame)\n const changed =\n next !== rows || status !== 'live' || error !== undefined\n rows = next\n status = 'live'\n error = undefined\n if (changed) notify()\n }\n // A live view never ends on its own, so reaching here is a drop.\n throw new Error('live stream closed')\n } catch (cause) {\n if (closed) return\n error = cause\n // Rows on screen mean the view still works while it reconnects.\n status = rows.length > 0 ? 'reconnecting' : 'failed'\n notify()\n await sleep(backoff(attempt++))\n }\n }\n })()\n\n return {\n getRows: () => rows,\n getStatus: () => status,\n getError: () => error,\n subscribe: (listener) => {\n listeners.add(listener)\n return () => {\n listeners.delete(listener)\n }\n },\n patch: (recipe) => {\n const draft = [...rows]\n recipe(draft)\n rows = draft\n notify()\n },\n resync: () => {\n controller?.abort()\n },\n close: () => {\n closed = true\n controller?.abort()\n },\n }\n}\n"]}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * The wire frames of a live view (`GET /api/{table}:live`).
3
+ *
4
+ * A live view is one SSE stream that opens with a snapshot of a list query and
5
+ * then delivers only what that view cares about. The server decides membership
6
+ * and placement, so a client applies frames without knowing the filter or sort
7
+ * rules, and a reconnect replays a fresh snapshot instead of a refetch.
8
+ *
9
+ * Types only: both the browser client and the server import this module.
10
+ */
11
+ export type LiveSnapshotFrame<TRow = Record<string, unknown>> = {
12
+ type: 'snapshot';
13
+ items: TRow[];
14
+ /** The values the server resolved, not the values the caller sent. */
15
+ sort: string;
16
+ order: 'asc' | 'desc';
17
+ limit: number;
18
+ /** True when rows exist below the window. */
19
+ hasMore: boolean;
20
+ };
21
+ export type LiveUpsertFrame<TRow = Record<string, unknown>> = {
22
+ type: 'upsert';
23
+ record: TRow;
24
+ /** The id this row follows in the view; `null` means the head. */
25
+ afterId: string | null;
26
+ };
27
+ export type LiveRemoveFrame = {
28
+ type: 'remove';
29
+ id: string;
30
+ };
31
+ export type LiveHeartbeatFrame = {
32
+ type: 'heartbeat';
33
+ intervalMs: number;
34
+ };
35
+ export type LiveDeltaFrame<TRow = Record<string, unknown>> = LiveUpsertFrame<TRow> | LiveRemoveFrame;
36
+ export type LiveFrame<TRow = Record<string, unknown>> = LiveSnapshotFrame<TRow> | LiveDeltaFrame<TRow> | LiveHeartbeatFrame;
37
+ /** The query a live view accepts: the list contract a stream can honor. */
38
+ export type LiveInput = {
39
+ limit?: number;
40
+ sort?: string;
41
+ order?: 'asc' | 'desc';
42
+ filters?: Record<string, unknown>;
43
+ };
44
+ //# sourceMappingURL=protocol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../src/live/protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,MAAM,iBAAiB,CAAC,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI;IAC9D,IAAI,EAAE,UAAU,CAAA;IAChB,KAAK,EAAE,IAAI,EAAE,CAAA;IACb,sEAAsE;IACtE,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,KAAK,GAAG,MAAM,CAAA;IACrB,KAAK,EAAE,MAAM,CAAA;IACb,6CAA6C;IAC7C,OAAO,EAAE,OAAO,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,eAAe,CAAC,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI;IAC5D,IAAI,EAAE,QAAQ,CAAA;IACd,MAAM,EAAE,IAAI,CAAA;IACZ,kEAAkE;IAClE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CACvB,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAA;AAE5D,MAAM,MAAM,kBAAkB,GAAG;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAA;AAE1E,MAAM,MAAM,cAAc,CAAC,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IACrD,eAAe,CAAC,IAAI,CAAC,GACrB,eAAe,CAAA;AAEnB,MAAM,MAAM,SAAS,CAAC,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAChD,iBAAiB,CAAC,IAAI,CAAC,GACvB,cAAc,CAAC,IAAI,CAAC,GACpB,kBAAkB,CAAA;AAEtB,2EAA2E;AAC3E,MAAM,MAAM,SAAS,GAAG;IACtB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM,CAAA;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAClC,CAAA"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * The wire frames of a live view (`GET /api/{table}:live`).
3
+ *
4
+ * A live view is one SSE stream that opens with a snapshot of a list query and
5
+ * then delivers only what that view cares about. The server decides membership
6
+ * and placement, so a client applies frames without knowing the filter or sort
7
+ * rules, and a reconnect replays a fresh snapshot instead of a refetch.
8
+ *
9
+ * Types only: both the browser client and the server import this module.
10
+ */
11
+ export {};
12
+ //# sourceMappingURL=protocol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../../src/live/protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG","sourcesContent":["/**\n * The wire frames of a live view (`GET /api/{table}:live`).\n *\n * A live view is one SSE stream that opens with a snapshot of a list query and\n * then delivers only what that view cares about. The server decides membership\n * and placement, so a client applies frames without knowing the filter or sort\n * rules, and a reconnect replays a fresh snapshot instead of a refetch.\n *\n * Types only: both the browser client and the server import this module.\n */\n\nexport type LiveSnapshotFrame<TRow = Record<string, unknown>> = {\n type: 'snapshot'\n items: TRow[]\n /** The values the server resolved, not the values the caller sent. */\n sort: string\n order: 'asc' | 'desc'\n limit: number\n /** True when rows exist below the window. */\n hasMore: boolean\n}\n\nexport type LiveUpsertFrame<TRow = Record<string, unknown>> = {\n type: 'upsert'\n record: TRow\n /** The id this row follows in the view; `null` means the head. */\n afterId: string | null\n}\n\nexport type LiveRemoveFrame = { type: 'remove'; id: string }\n\nexport type LiveHeartbeatFrame = { type: 'heartbeat'; intervalMs: number }\n\nexport type LiveDeltaFrame<TRow = Record<string, unknown>> =\n | LiveUpsertFrame<TRow>\n | LiveRemoveFrame\n\nexport type LiveFrame<TRow = Record<string, unknown>> =\n | LiveSnapshotFrame<TRow>\n | LiveDeltaFrame<TRow>\n | LiveHeartbeatFrame\n\n/** The query a live view accepts: the list contract a stream can honor. */\nexport type LiveInput = {\n limit?: number\n sort?: string\n order?: 'asc' | 'desc'\n filters?: Record<string, unknown>\n}\n"]}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The SSE frames of one response body, decoded as JSON.
3
+ *
4
+ * A live view carries JSON in `data:` lines; keepalive comments and event
5
+ * names never surface. Written here rather than taken from a library, so the
6
+ * browser entry point keeps zero dependencies.
7
+ */
8
+ /**
9
+ * The part of a byte stream this reader uses. Stated structurally, so the DOM
10
+ * and Bun definitions of `ReadableStream` both satisfy it without a cast.
11
+ */
12
+ export type ByteStream = {
13
+ getReader: () => {
14
+ read: () => Promise<{
15
+ done: boolean;
16
+ value?: Uint8Array;
17
+ }>;
18
+ cancel: () => Promise<unknown>;
19
+ };
20
+ };
21
+ export declare function parseSseFrames<TFrame>(body: ByteStream): AsyncGenerator<TFrame, void, void>;
22
+ //# sourceMappingURL=sse.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse.d.ts","sourceRoot":"","sources":["../../src/live/sse.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG;IACvB,SAAS,EAAE,MAAM;QACf,IAAI,EAAE,MAAM,OAAO,CAAC;YAAE,IAAI,EAAE,OAAO,CAAC;YAAC,KAAK,CAAC,EAAE,UAAU,CAAA;SAAE,CAAC,CAAA;QAC1D,MAAM,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;KAC/B,CAAA;CACF,CAAA;AAED,wBAAuB,cAAc,CAAC,MAAM,EAC1C,IAAI,EAAE,UAAU,GACf,cAAc,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,CAmCpC"}
@@ -0,0 +1,41 @@
1
+ export async function* parseSseFrames(body) {
2
+ const reader = body.getReader();
3
+ const decoder = new TextDecoder();
4
+ let buffer = '';
5
+ let data = [];
6
+ const flush = () => {
7
+ if (data.length === 0)
8
+ return undefined;
9
+ const frame = JSON.parse(data.join('\n'));
10
+ data = [];
11
+ return frame;
12
+ };
13
+ try {
14
+ while (true) {
15
+ const { done, value } = await reader.read();
16
+ if (done)
17
+ break;
18
+ buffer += decoder.decode(value, { stream: true });
19
+ let newline;
20
+ while ((newline = buffer.indexOf('\n')) !== -1) {
21
+ const line = buffer.slice(0, newline);
22
+ buffer = buffer.slice(newline + 1);
23
+ if (line === '') {
24
+ const frame = flush();
25
+ if (frame !== undefined)
26
+ yield frame;
27
+ }
28
+ else if (line.startsWith('data:')) {
29
+ data.push(line.slice(line.startsWith('data: ') ? 6 : 5));
30
+ }
31
+ }
32
+ }
33
+ const frame = flush();
34
+ if (frame !== undefined)
35
+ yield frame;
36
+ }
37
+ finally {
38
+ await reader.cancel().catch(() => { });
39
+ }
40
+ }
41
+ //# sourceMappingURL=sse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse.js","sourceRoot":"","sources":["../../src/live/sse.ts"],"names":[],"mappings":"AAkBA,MAAM,CAAC,KAAK,SAAS,CAAC,CAAC,cAAc,CACnC,IAAgB;IAEhB,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,CAAA;IAC/B,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAA;IACjC,IAAI,MAAM,GAAG,EAAE,CAAA;IACf,IAAI,IAAI,GAAa,EAAE,CAAA;IAEvB,MAAM,KAAK,GAAG,GAAuB,EAAE;QACrC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,SAAS,CAAA;QACvC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAW,CAAA;QACnD,IAAI,GAAG,EAAE,CAAA;QACT,OAAO,KAAK,CAAA;IACd,CAAC,CAAA;IAED,IAAI,CAAC;QACH,OAAO,IAAI,EAAE,CAAC;YACZ,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAA;YAC3C,IAAI,IAAI;gBAAE,MAAK;YACf,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAA;YACjD,IAAI,OAAe,CAAA;YACnB,OAAO,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;gBAC/C,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,CAAA;gBACrC,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,CAAC,CAAA;gBAClC,IAAI,IAAI,KAAK,EAAE,EAAE,CAAC;oBAChB,MAAM,KAAK,GAAG,KAAK,EAAE,CAAA;oBACrB,IAAI,KAAK,KAAK,SAAS;wBAAE,MAAM,KAAK,CAAA;gBACtC,CAAC;qBAAM,IAAI,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;oBACpC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;gBAC1D,CAAC;YACH,CAAC;QACH,CAAC;QACD,MAAM,KAAK,GAAG,KAAK,EAAE,CAAA;QACrB,IAAI,KAAK,KAAK,SAAS;YAAE,MAAM,KAAK,CAAA;IACtC,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;IACvC,CAAC;AACH,CAAC","sourcesContent":["/**\n * The SSE frames of one response body, decoded as JSON.\n *\n * A live view carries JSON in `data:` lines; keepalive comments and event\n * names never surface. Written here rather than taken from a library, so the\n * browser entry point keeps zero dependencies.\n */\n/**\n * The part of a byte stream this reader uses. Stated structurally, so the DOM\n * and Bun definitions of `ReadableStream` both satisfy it without a cast.\n */\nexport type ByteStream = {\n getReader: () => {\n read: () => Promise<{ done: boolean; value?: Uint8Array }>\n cancel: () => Promise<unknown>\n }\n}\n\nexport async function* parseSseFrames<TFrame>(\n body: ByteStream,\n): AsyncGenerator<TFrame, void, void> {\n const reader = body.getReader()\n const decoder = new TextDecoder()\n let buffer = ''\n let data: string[] = []\n\n const flush = (): TFrame | undefined => {\n if (data.length === 0) return undefined\n const frame = JSON.parse(data.join('\\n')) as TFrame\n data = []\n return frame\n }\n\n try {\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n buffer += decoder.decode(value, { stream: true })\n let newline: number\n while ((newline = buffer.indexOf('\\n')) !== -1) {\n const line = buffer.slice(0, newline)\n buffer = buffer.slice(newline + 1)\n if (line === '') {\n const frame = flush()\n if (frame !== undefined) yield frame\n } else if (line.startsWith('data:')) {\n data.push(line.slice(line.startsWith('data: ') ? 6 : 5))\n }\n }\n }\n const frame = flush()\n if (frame !== undefined) yield frame\n } finally {\n await reader.cancel().catch(() => {})\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"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","sourcesContent":["import type { StandardSchemaV1 } from '@standard-schema/spec'\n\nimport { getTableName, isTable } from 'drizzle-orm'\nimport * as v from 'valibot'\n\nimport type { Dialect } from './dialect'\nimport type { EnvConfigInput } from './env'\nimport type { JobsDefs } from './jobs/define'\nimport type { ResolvedBucket, ResolvedStorageBuckets } from './storage/buckets'\n\nimport {\n bunderstackEmailEvents,\n bunderstackEmails,\n bunderstackFiles,\n bunderstackIdempotency,\n bunderstackJobs,\n} from './internal-tables'\nimport { parseCron } from './jobs/cron'\nimport {\n StandardSchemaValidationError,\n validateStandardSchema,\n} from './standard-schema'\n\nexport type ManifestEnvVar = {\n key: string\n required: boolean\n scope: 'server' | 'client'\n}\n\nexport type BunderstackManifest = {\n version: 3\n database: {\n dialect: Dialect\n migrationsDirectory: string\n tables: { exportName: string; physicalName: string; system: boolean }[]\n }\n storage: {\n defaultBucket: string\n buckets: { name: string; visibility: ResolvedBucket['visibility'] }[]\n }\n realtime: { required: boolean }\n environment: ManifestEnvVar[]\n background: {\n jobs: { name: string }[]\n cron: { name: string; schedule: string; timezone: 'UTC' }[]\n maintenance: {\n name: 'storage-sweep'\n schedule: string\n timezone: 'UTC'\n }[]\n }\n}\n\nconst nonEmpty = v.pipe(v.string(), v.minLength(1))\nconst migrationDirectory = v.pipe(\n nonEmpty,\n v.check(\n (value) =>\n value.startsWith('/') ||\n (!value.includes('\\\\') &&\n value.split('/').every((part) => part !== '..' && part !== '')),\n 'migrationsDirectory must be an absolute path or a relative path without traversal',\n ),\n)\nconst cronSchedule = v.pipe(\n nonEmpty,\n v.check((value) => {\n try {\n parseCron(value)\n return true\n } catch {\n return false\n }\n }, 'invalid cron schedule'),\n)\n\nconst manifestSchema = v.strictObject({\n version: v.literal(3),\n database: v.strictObject({\n dialect: v.picklist(['sqlite', 'pg']),\n migrationsDirectory: migrationDirectory,\n tables: v.array(\n v.strictObject({\n exportName: nonEmpty,\n physicalName: nonEmpty,\n system: v.boolean(),\n }),\n ),\n }),\n storage: v.strictObject({\n defaultBucket: nonEmpty,\n buckets: v.array(\n v.strictObject({\n name: nonEmpty,\n visibility: v.picklist(['public', 'private']),\n }),\n ),\n }),\n realtime: v.strictObject({ required: v.boolean() }),\n environment: v.array(\n v.strictObject({\n key: nonEmpty,\n required: v.boolean(),\n scope: v.picklist(['server', 'client']),\n }),\n ),\n background: v.strictObject({\n jobs: v.array(v.strictObject({ name: nonEmpty })),\n cron: v.array(\n v.strictObject({\n name: nonEmpty,\n schedule: cronSchedule,\n timezone: v.literal('UTC'),\n }),\n ),\n maintenance: v.array(\n v.strictObject({\n name: v.literal('storage-sweep'),\n schedule: cronSchedule,\n timezone: v.literal('UTC'),\n }),\n ),\n }),\n})\n\nfunction sortBy<T>(entries: readonly T[], key: (entry: T) => string): T[] {\n return [...entries].sort((left, right) => key(left).localeCompare(key(right)))\n}\n\nfunction rejectDuplicates(collection: string, values: readonly string[]): void {\n const seen = new Set<string>()\n for (const value of values) {\n if (seen.has(value))\n throw new Error(`[bunderstack] duplicate ${collection} \"${value}\"`)\n seen.add(value)\n }\n}\n\nfunction describeTables(schema: Record<string, unknown>) {\n const systemNames = new Set<string>(\n systemTables().map((table) => table.physicalName),\n )\n return sortBy(\n Object.entries(schema).flatMap(([exportName, value]) =>\n isTable(value) && !systemNames.has(getTableName(value))\n ? [{ exportName, physicalName: getTableName(value), system: false }]\n : [],\n ),\n (entry) => entry.physicalName,\n )\n}\n\nfunction describeSection(\n section: Record<string, StandardSchemaV1> | undefined,\n scope: ManifestEnvVar['scope'],\n): ManifestEnvVar[] {\n return Object.entries(section ?? {}).map(([key, schema]) => {\n let required = false\n try {\n validateStandardSchema(schema, undefined, 'env')\n } catch (error) {\n if (!(error instanceof StandardSchemaValidationError)) throw error\n required = true\n }\n return { key, required, scope }\n })\n}\n\nfunction systemTables() {\n return [\n {\n exportName: '_system.emailEvents',\n physicalName: getTableName(bunderstackEmailEvents),\n system: true,\n },\n {\n exportName: '_system.emails',\n physicalName: getTableName(bunderstackEmails),\n system: true,\n },\n {\n exportName: '_system.files',\n physicalName: getTableName(bunderstackFiles),\n system: true,\n },\n {\n exportName: '_system.idempotency',\n physicalName: getTableName(bunderstackIdempotency),\n system: true,\n },\n {\n exportName: '_system.jobs',\n physicalName: getTableName(bunderstackJobs),\n system: true,\n },\n ]\n}\n\nexport function parseManifest(value: unknown): BunderstackManifest {\n const manifest = validateStandardSchema(\n manifestSchema,\n value,\n 'manifest',\n ) as BunderstackManifest\n rejectDuplicates(\n 'database physical table',\n manifest.database.tables.map((entry) => entry.physicalName),\n )\n rejectDuplicates(\n 'database export table',\n manifest.database.tables.map((entry) => entry.exportName),\n )\n rejectDuplicates(\n 'storage bucket',\n manifest.storage.buckets.map((entry) => entry.name),\n )\n rejectDuplicates(\n 'environment key',\n manifest.environment.map((entry) => entry.key),\n )\n rejectDuplicates(\n 'background job',\n manifest.background.jobs.map((entry) => entry.name),\n )\n rejectDuplicates(\n 'background cron',\n manifest.background.cron.map((entry) => entry.name),\n )\n rejectDuplicates(\n 'background maintenance',\n manifest.background.maintenance.map((entry) => entry.name),\n )\n return manifest\n}\n\nexport function buildManifest(args: {\n schema: Record<string, unknown>\n dialect: Dialect\n migrationsDirectory: string\n storage: ResolvedStorageBuckets\n envConfig: EnvConfigInput | undefined\n emailProvider: string | undefined\n realtime: boolean\n jobs: JobsDefs | undefined\n}): BunderstackManifest {\n const environment = [\n ...describeSection(args.envConfig?.server, 'server'),\n ...describeSection(args.envConfig?.client, 'client'),\n ...(args.emailProvider === 'resend'\n ? [{ key: 'RESEND_API_KEY', required: true, scope: 'server' as const }]\n : []),\n ...(args.emailProvider === 'smtp'\n ? [{ key: 'SMTP_URL', required: true, scope: 'server' as const }]\n : []),\n ]\n rejectDuplicates(\n 'environment key',\n environment.map((entry) => entry.key),\n )\n\n return parseManifest({\n version: 3,\n database: {\n dialect: args.dialect,\n migrationsDirectory: args.migrationsDirectory,\n tables: sortBy(\n [...systemTables(), ...describeTables(args.schema)],\n (entry) => entry.physicalName,\n ),\n },\n storage: {\n defaultBucket: args.storage.defaultBucket,\n buckets: sortBy(\n [...args.storage.buckets.values()].map((bucket) => ({\n name: bucket.name,\n visibility: bucket.visibility,\n })),\n (bucket) => bucket.name,\n ),\n },\n realtime: { required: args.realtime },\n environment: sortBy(environment, (entry) => entry.key),\n background: {\n jobs: sortBy(\n Object.entries(args.jobs ?? {})\n .filter(([, def]) => def.kind === 'job')\n .map(([name]) => ({ name })),\n (entry) => entry.name,\n ),\n cron: sortBy(\n Object.entries(args.jobs ?? {})\n .filter(([, def]) => def.kind === 'cron')\n .map(([name, def]) => ({\n name,\n schedule: def.kind === 'cron' ? def.schedule : '',\n timezone: 'UTC' as const,\n })),\n (entry) => entry.name,\n ),\n maintenance: [\n {\n name: 'storage-sweep',\n schedule: '0 4 * * *',\n timezone: 'UTC' as const,\n },\n ],\n },\n })\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"provision-internals.js","sourceRoot":"","sources":["../src/provision-internals.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAkB,MAAM,CAAC,GAAG,CAC1D,iCAAiC,CAClC,CAAA"}
1
+ {"version":3,"file":"provision-internals.js","sourceRoot":"","sources":["../src/provision-internals.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAkB,MAAM,CAAC,GAAG,CAC1D,iCAAiC,CAClC,CAAA","sourcesContent":["import type { Driver } from './db'\n// src/provision-internals.ts\nimport type { AnyDb, Dialect } from './dialect'\n\n/**\n * Hidden handle connecting `createBunderstack()` to the optional\n * `bunderstack/provision` entry. Lives in its own module so the main entry\n * never imports provision code (and its drizzle-kit reference).\n */\nexport const PROVISION_INTERNALS: unique symbol = Symbol.for(\n 'bunderstack.provision-internals',\n)\n\nexport interface ProvisionInternals {\n /** Runtime db typed over the MERGED schema (user + internal tables). */\n db: AnyDb\n /** Merged schema used for push. */\n schema: Record<string, unknown>\n databaseUrl: string\n /** Resolved migrations folder (config `database.migrations`). */\n migrationsFolder: string\n dialect: Dialect\n driver: Driver\n adapter: import('./database/adapter').DatabaseAdapter\n}\n\nexport interface WithProvisionInternals {\n [PROVISION_INTERNALS]?: ProvisionInternals\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"provision.js","sourceRoot":"","sources":["../src/provision.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"provision.js","sourceRoot":"","sources":["../src/provision.ts"],"names":[],"mappings":"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","sourcesContent":["// src/provision.ts\nimport { access, mkdir } from 'node:fs/promises'\nimport { dirname, join } from 'node:path'\n\nimport type { AnyDb, Dialect } from './dialect'\n\nimport { detectDialect } from './dialect'\nimport {\n PROVISION_INTERNALS,\n type WithProvisionInternals,\n} from './provision-internals'\n\n/** Create the local backing directory for file-based urls, per dialect. */\nasync function ensureLocalDataDir(\n url: string,\n dialect: Dialect,\n): Promise<void> {\n if (dialect === 'pg') {\n // PGlite data dir: `file:<dir>` or a bare path; server/memory urls need nothing.\n if (/^postgres(ql)?:\\/\\//.test(url)) return\n const raw = url.startsWith('file:') ? url.slice('file:'.length) : url\n if (raw === ':memory:' || raw.startsWith('memory://')) return\n await mkdir(raw, { recursive: true })\n return\n }\n const match = /^file:(.+)$/.exec(url)\n if (!match) return\n const filePath = match[1]!\n if (filePath === ':memory:') return\n await mkdir(dirname(filePath), { recursive: true })\n}\n\nasync function exists(path: string): Promise<boolean> {\n try {\n await access(path)\n return true\n } catch {\n return false\n }\n}\n\nconst DRIZZLE_KIT_HINT =\n '[bunderstack] Schema push requires drizzle-kit, which is not installed.\\n' +\n ' Development: run `bun add -d drizzle-kit` — provision() will push schema changes to the database on startup.\\n' +\n ' Production: generate migrations locally with `bunx drizzle-kit generate` and commit the folder — provision() applies them without drizzle-kit.'\n\n/** Push the merged schema to the database via drizzle-kit/api. */\nexport async function provisionSchema<TSchema extends Record<string, unknown>>(\n db: AnyDb,\n schema: TSchema,\n options?: { force?: boolean; databaseUrl?: string },\n): Promise<void> {\n const dialect = detectDialect(schema)\n if (options?.databaseUrl) {\n await ensureLocalDataDir(options.databaseUrl, dialect)\n }\n\n let kit: typeof import('drizzle-kit/api')\n try {\n kit = await import('drizzle-kit/api')\n } catch (cause) {\n throw new Error(DRIZZLE_KIT_HINT, { cause })\n }\n\n const result =\n dialect === 'pg'\n ? await kit.pushSchema(schema, db as never)\n : await kit.pushSQLiteSchema(schema, db as never)\n\n if (result.hasDataLoss && !options?.force) {\n throw new Error(\n '[bunderstack] Schema push would cause data loss. Run `bunx drizzle-kit push` or call provision(app, { force: true }).',\n )\n }\n\n if (result.warnings.length > 0) {\n for (const warning of result.warnings) {\n console.warn(`[bunderstack] ${warning}`)\n }\n }\n\n if (result.statementsToExecute.length === 0) return\n\n await result.apply()\n\n console.log(\n `[bunderstack] provisioned ${result.statementsToExecute.length} schema change(s)`,\n )\n}\n\n/**\n * Provision the database for a Bunderstack app.\n *\n * The migrations folder is the mode switch:\n * - `<migrations>/meta/_journal.json` exists → apply committed migrations via\n * drizzle-orm's migrator. Pure runtime; drizzle-kit is never imported, so a\n * fresh clone deploys with `bun install --production`.\n * - No migrations → development: push the schema straight to the database via\n * drizzle-kit (install it with `bun add -d drizzle-kit`).\n *\n * Once you run `bunx drizzle-kit generate` for the first time, provision stops\n * pushing and every schema change goes through an explicit `generate`.\n */\nexport async function provision(\n app: object,\n options?: { force?: boolean },\n): Promise<void> {\n if (process.env.BUNDERSTACK_INTROSPECT === '1') return\n\n const internals = (app as WithProvisionInternals)[PROVISION_INTERNALS]\n if (!internals) {\n throw new Error(\n '[bunderstack] provision() expects the app returned by createBunderstack().',\n )\n }\n\n const { db, schema, databaseUrl, migrationsFolder, dialect, adapter } =\n internals\n const journal = join(migrationsFolder, 'meta', '_journal.json')\n\n if (await exists(journal)) {\n await ensureLocalDataDir(databaseUrl, dialect)\n await adapter.migrate(db as never, migrationsFolder)\n return\n }\n\n await provisionSchema(db, schema, { force: options?.force, databaseUrl })\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../src/rate-limit.ts"],"names":[],"mappings":"AAWA,MAAM,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAA;AAEzC,SAAS,aAAa,CACpB,MAA6C;IAE7C,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAA;IACxB,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,EAAE,CAAA;IAC9B,OAAO,MAAM,CAAA;AACf,CAAC;AAED,SAAS,SAAS,CAAC,GAAY;IAC7B,MAAM,SAAS,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAA;IACpD,IAAI,SAAS;QAAE,OAAO,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC,IAAI,EAAE,CAAA;IACrD,OAAO,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,OAAO,CAAA;AAChD,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,MAA6C;IAE7C,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAM,CAAC,CAAA;IACtC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,KAAK,EAAE,IAAa,EAAE,EAAE,CAAC,IAAI,CAAA;IACtC,CAAC;IAED,MAAM,QAAQ,GAAG,QAAQ,CAAC,QAAQ,IAAI,MAAM,CAAA;IAC5C,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,IAAI,GAAG,CAAA;IAE/B,OAAO,KAAK,EAAE,GAAY,EAA4B,EAAE;QACtD,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC;YAAE,OAAO,IAAI,CAAA;QAErC,MAAM,GAAG,GAAG,GAAG,SAAS,CAAC,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAA;QAC5D,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QACtB,IAAI,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAE7B,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,OAAO,IAAI,GAAG,EAAE,CAAC;YACrC,MAAM,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,GAAG,GAAG,QAAQ,EAAE,CAAA;YAC9C,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;QAC1B,CAAC;QAED,MAAM,CAAC,KAAK,IAAI,CAAC,CAAA;QACjB,IAAI,MAAM,CAAC,KAAK,GAAG,GAAG,EAAE,CAAC;YACvB,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,CAAA;YAC3D,OAAO,IAAI,QAAQ,CACjB,IAAI,CAAC,SAAS,CAAC;gBACb,KAAK,EAAE,mBAAmB;gBAC1B,IAAI,EAAE,mBAAmB;aAC1B,CAAC,EACF;gBACE,MAAM,EAAE,GAAG;gBACX,OAAO,EAAE;oBACP,cAAc,EAAE,kBAAkB;oBAClC,aAAa,EAAE,MAAM,CAAC,UAAU,CAAC;iBAClC;aACF,CACF,CAAA;QACH,CAAC;QAED,OAAO,IAAI,CAAA;IACb,CAAC,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../src/rate-limit.ts"],"names":[],"mappings":"AAWA,MAAM,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAA;AAEzC,SAAS,aAAa,CACpB,MAA6C;IAE7C,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAA;IACxB,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,EAAE,CAAA;IAC9B,OAAO,MAAM,CAAA;AACf,CAAC;AAED,SAAS,SAAS,CAAC,GAAY;IAC7B,MAAM,SAAS,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAA;IACpD,IAAI,SAAS;QAAE,OAAO,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC,IAAI,EAAE,CAAA;IACrD,OAAO,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,OAAO,CAAA;AAChD,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,MAA6C;IAE7C,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAM,CAAC,CAAA;IACtC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,KAAK,EAAE,IAAa,EAAE,EAAE,CAAC,IAAI,CAAA;IACtC,CAAC;IAED,MAAM,QAAQ,GAAG,QAAQ,CAAC,QAAQ,IAAI,MAAM,CAAA;IAC5C,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,IAAI,GAAG,CAAA;IAE/B,OAAO,KAAK,EAAE,GAAY,EAA4B,EAAE;QACtD,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC;YAAE,OAAO,IAAI,CAAA;QAErC,MAAM,GAAG,GAAG,GAAG,SAAS,CAAC,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAA;QAC5D,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QACtB,IAAI,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAE7B,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,OAAO,IAAI,GAAG,EAAE,CAAC;YACrC,MAAM,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,GAAG,GAAG,QAAQ,EAAE,CAAA;YAC9C,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;QAC1B,CAAC;QAED,MAAM,CAAC,KAAK,IAAI,CAAC,CAAA;QACjB,IAAI,MAAM,CAAC,KAAK,GAAG,GAAG,EAAE,CAAC;YACvB,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,CAAA;YAC3D,OAAO,IAAI,QAAQ,CACjB,IAAI,CAAC,SAAS,CAAC;gBACb,KAAK,EAAE,mBAAmB;gBAC1B,IAAI,EAAE,mBAAmB;aAC1B,CAAC,EACF;gBACE,MAAM,EAAE,GAAG;gBACX,OAAO,EAAE;oBACP,cAAc,EAAE,kBAAkB;oBAClC,aAAa,EAAE,MAAM,CAAC,UAAU,CAAC;iBAClC;aACF,CACF,CAAA;QACH,CAAC;QAED,OAAO,IAAI,CAAA;IACb,CAAC,CAAA;AACH,CAAC","sourcesContent":["export type RateLimitConfig = {\n windowMs?: number\n max?: number\n skip?: (req: Request) => boolean\n}\n\ntype Bucket = {\n count: number\n resetAt: number\n}\n\nconst buckets = new Map<string, Bucket>()\n\nfunction resolveConfig(\n config: boolean | RateLimitConfig | undefined,\n): RateLimitConfig | null {\n if (!config) return null\n if (config === true) return {}\n return config\n}\n\nfunction clientKey(req: Request): string {\n const forwarded = req.headers.get('x-forwarded-for')\n if (forwarded) return forwarded.split(',')[0]!.trim()\n return req.headers.get('x-real-ip') ?? 'local'\n}\n\nexport function createRateLimiter(\n config: boolean | RateLimitConfig | undefined,\n): (req: Request) => Promise<Response | null> {\n const resolved = resolveConfig(config)\n if (!resolved) {\n return async (_req: Request) => null\n }\n\n const windowMs = resolved.windowMs ?? 60_000\n const max = resolved.max ?? 100\n\n return async (req: Request): Promise<Response | null> => {\n if (resolved.skip?.(req)) return null\n\n const key = `${clientKey(req)}:${new URL(req.url).pathname}`\n const now = Date.now()\n let bucket = buckets.get(key)\n\n if (!bucket || bucket.resetAt <= now) {\n bucket = { count: 0, resetAt: now + windowMs }\n buckets.set(key, bucket)\n }\n\n bucket.count += 1\n if (bucket.count > max) {\n const retryAfter = Math.ceil((bucket.resetAt - now) / 1000)\n return new Response(\n JSON.stringify({\n error: 'Too many requests',\n code: 'TOO_MANY_REQUESTS',\n }),\n {\n status: 429,\n headers: {\n 'Content-Type': 'application/json',\n 'Retry-After': String(retryAfter),\n },\n },\n )\n }\n\n return null\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"facade.js","sourceRoot":"","sources":["../../src/realtime/facade.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,OAAO,GAGR,MAAM,aAAa,CAAA;AAwBpB;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAClC,SAA6B,EAC7B,YAA+B,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,EAChE,MAAgB;IAEhB,IAAI,CAAC,SAAS,IAAI,SAAS,KAAK,UAAU,EAAE,CAAC;QAC3C,MAAM,IAAI,KAAK,CACb,kEAAkE,CACnE,CAAA;IACH,CAAC;IACD,IAAI,SAAS,IAAI,SAAS,KAAK,UAAU,EAAE,CAAC;QAC1C,MAAM,IAAI,KAAK,CACb,sEAAsE,CACvE,CAAA;IACH,CAAC;IAED,MAAM,cAAc,GAAG,IAAI,GAAG,EAAkB,CAAA;IAChD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE,CAAC;QACxD,IAAI,OAAO,CAAC,KAAK,CAAC;YAAE,cAAc,CAAC,GAAG,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAA;IAClE,CAAC;IAED,OAAO;QACL,OAAO,EAAE,SAAS,KAAK,SAAS;QAChC,SAAS;QACT,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM;YACjC,IAAI,CAAC,SAAS;gBAAE,OAAM;YACtB,MAAM,SAAS,GAAG,YAAY,CAAC,KAAK,CAAC,CAAA;YACrC,MAAM,SAAS,CAAC,OAAO,CAAC,QAAQ,EAAE;gBAChC,KAAK,EAAE,cAAc,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,SAAS;gBACjD,MAAM;gBACN,MAAM,EAAE,MAA4C;aACrD,CAAC,CAAA;QACJ,CAAC;KACF,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"facade.js","sourceRoot":"","sources":["../../src/realtime/facade.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,OAAO,GAGR,MAAM,aAAa,CAAA;AAwBpB;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAClC,SAA6B,EAC7B,YAA+B,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,EAChE,MAAgB;IAEhB,IAAI,CAAC,SAAS,IAAI,SAAS,KAAK,UAAU,EAAE,CAAC;QAC3C,MAAM,IAAI,KAAK,CACb,kEAAkE,CACnE,CAAA;IACH,CAAC;IACD,IAAI,SAAS,IAAI,SAAS,KAAK,UAAU,EAAE,CAAC;QAC1C,MAAM,IAAI,KAAK,CACb,sEAAsE,CACvE,CAAA;IACH,CAAC;IAED,MAAM,cAAc,GAAG,IAAI,GAAG,EAAkB,CAAA;IAChD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE,CAAC;QACxD,IAAI,OAAO,CAAC,KAAK,CAAC;YAAE,cAAc,CAAC,GAAG,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAA;IAClE,CAAC;IAED,OAAO;QACL,OAAO,EAAE,SAAS,KAAK,SAAS;QAChC,SAAS;QACT,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM;YACjC,IAAI,CAAC,SAAS;gBAAE,OAAM;YACtB,MAAM,SAAS,GAAG,YAAY,CAAC,KAAK,CAAC,CAAA;YACrC,MAAM,SAAS,CAAC,OAAO,CAAC,QAAQ,EAAE;gBAChC,KAAK,EAAE,cAAc,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,SAAS;gBACjD,MAAM;gBACN,MAAM,EAAE,MAA4C;aACrD,CAAC,CAAA;QACJ,CAAC;KACF,CAAA;AACH,CAAC","sourcesContent":["import {\n getTableName,\n isTable,\n type InferSelectModel,\n type Table,\n} from 'drizzle-orm'\n\nimport type { RealtimeAction, RealtimePublisher } from './publisher'\n\nexport type RealtimeTransport = 'disabled' | 'memory' | 'redis'\n\nexport type SchemaTable<TSchema extends Record<string, unknown>> = Extract<\n TSchema[keyof TSchema],\n Table\n>\n\nexport interface RealtimeFacade<\n TSchema extends Record<string, unknown> = Record<string, unknown>,\n> {\n readonly enabled: boolean\n readonly transport: RealtimeTransport\n\n publish<TTable extends SchemaTable<TSchema>>(\n table: TTable,\n action: RealtimeAction,\n record: InferSelectModel<TTable>,\n ): Promise<void>\n}\n\n/**\n * One name for a table everywhere: events, subscriptions, and the CRUD router\n * all use the schema key. Pass the schema so a key like `creditBalances` is not\n * published as its SQL name `credit_balances` — clients subscribe with the key\n * they call procedures with. Without a schema, the SQL name is the only name\n * available and is used as-is.\n */\nexport function createRealtimeFacade<TSchema extends Record<string, unknown>>(\n publisher?: RealtimePublisher,\n transport: RealtimeTransport = publisher ? 'memory' : 'disabled',\n schema?: TSchema,\n): RealtimeFacade<TSchema> {\n if (!publisher && transport !== 'disabled') {\n throw new Error(\n '[bunderstack] an enabled realtime transport requires a publisher',\n )\n }\n if (publisher && transport === 'disabled') {\n throw new Error(\n '[bunderstack] a realtime publisher cannot use the disabled transport',\n )\n }\n\n const keyByTableName = new Map<string, string>()\n for (const [key, value] of Object.entries(schema ?? {})) {\n if (isTable(value)) keyByTableName.set(getTableName(value), key)\n }\n\n return {\n enabled: publisher !== undefined,\n transport,\n async publish(table, action, record) {\n if (!publisher) return\n const tableName = getTableName(table)\n await publisher.publish('change', {\n table: keyByTableName.get(tableName) ?? tableName,\n action,\n record: record as unknown as Record<string, unknown>,\n })\n },\n }\n}\n"]}
@@ -1,13 +1,42 @@
1
- import type { AccessUser, ResolvedAccess } from '../access.js';
1
+ import type { AccessUser, OperationRule, ResolvedAccess, ResolvedTableAccess } from '../access.js';
2
2
  import type { RealtimeChange } from './publisher.js';
3
+ type GetSession = () => Promise<{
4
+ user: AccessUser | null;
5
+ activeOrganizationId: string | null;
6
+ }>;
3
7
  export interface FilterRealtimeChangesOptions {
4
8
  subscriptions: readonly string[];
5
9
  access: ResolvedAccess;
6
10
  request: Request;
7
- getSession: () => Promise<{
8
- user: AccessUser | null;
9
- activeOrganizationId: string | null;
10
- }>;
11
+ getSession: GetSession;
11
12
  }
13
+ export interface FilterTableChangesOptions {
14
+ /** Events arrive under this name — the schema key they were published with. */
15
+ tableName: string;
16
+ entry: ResolvedTableAccess;
17
+ /**
18
+ * The operation right this stream reads under: `get` for a subscription to
19
+ * rows, `list` for a live view of a query.
20
+ */
21
+ rule: OperationRule;
22
+ request: Request;
23
+ getSession: GetSession;
24
+ }
25
+ /**
26
+ * Whether one change may reach this subscriber: the given right plus the read
27
+ * scope of the row, evaluated against the caller's session. The session
28
+ * resolves at most once per stream.
29
+ */
30
+ export declare function createChangeGuard(entry: ResolvedTableAccess, options: {
31
+ rule: OperationRule;
32
+ request: Request;
33
+ getSession: GetSession;
34
+ }): (change: RealtimeChange) => Promise<boolean>;
12
35
  export declare function filterRealtimeChanges(source: AsyncIterable<RealtimeChange>, options: FilterRealtimeChangesOptions): AsyncGenerator<RealtimeChange, void, void>;
36
+ /**
37
+ * The same stream narrowed to one table with its access entry known up front —
38
+ * what a live view (`GET /{table}:live`) consumes.
39
+ */
40
+ export declare function filterTableChanges(source: AsyncIterable<RealtimeChange>, options: FilterTableChangesOptions): AsyncGenerator<RealtimeChange, void, void>;
41
+ export {};
13
42
  //# sourceMappingURL=filter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"filter.d.ts","sourceRoot":"","sources":["../../src/realtime/filter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAIjD,MAAM,WAAW,4BAA4B;IAC3C,aAAa,EAAE,SAAS,MAAM,EAAE,CAAA;IAChC,MAAM,EAAE,cAAc,CAAA;IACtB,OAAO,EAAE,OAAO,CAAA;IAChB,UAAU,EAAE,MAAM,OAAO,CAAC;QACxB,IAAI,EAAE,UAAU,GAAG,IAAI,CAAA;QACvB,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAA;KACpC,CAAC,CAAA;CACH;AAED,wBAAuB,qBAAqB,CAC1C,MAAM,EAAE,aAAa,CAAC,cAAc,CAAC,EACrC,OAAO,EAAE,4BAA4B,GACpC,cAAc,CAAC,cAAc,EAAE,IAAI,EAAE,IAAI,CAAC,CAqD5C"}
1
+ {"version":3,"file":"filter.d.ts","sourceRoot":"","sources":["../../src/realtime/filter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,UAAU,EACV,aAAa,EACb,cAAc,EACd,mBAAmB,EACpB,MAAM,WAAW,CAAA;AAClB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAIjD,KAAK,UAAU,GAAG,MAAM,OAAO,CAAC;IAC9B,IAAI,EAAE,UAAU,GAAG,IAAI,CAAA;IACvB,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAA;CACpC,CAAC,CAAA;AAEF,MAAM,WAAW,4BAA4B;IAC3C,aAAa,EAAE,SAAS,MAAM,EAAE,CAAA;IAChC,MAAM,EAAE,cAAc,CAAA;IACtB,OAAO,EAAE,OAAO,CAAA;IAChB,UAAU,EAAE,UAAU,CAAA;CACvB;AAED,MAAM,WAAW,yBAAyB;IACxC,+EAA+E;IAC/E,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,mBAAmB,CAAA;IAC1B;;;OAGG;IACH,IAAI,EAAE,aAAa,CAAA;IACnB,OAAO,EAAE,OAAO,CAAA;IAChB,UAAU,EAAE,UAAU,CAAA;CACvB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,mBAAmB,EAC1B,OAAO,EAAE;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,UAAU,CAAA;CAAE,GACzE,CAAC,MAAM,EAAE,cAAc,KAAK,OAAO,CAAC,OAAO,CAAC,CA6B9C;AAaD,wBAAuB,qBAAqB,CAC1C,MAAM,EAAE,aAAa,CAAC,cAAc,CAAC,EACrC,OAAO,EAAE,4BAA4B,GACpC,cAAc,CAAC,cAAc,EAAE,IAAI,EAAE,IAAI,CAAC,CAiC5C;AAED;;;GAGG;AACH,wBAAuB,kBAAkB,CACvC,MAAM,EAAE,aAAa,CAAC,cAAc,CAAC,EACrC,OAAO,EAAE,yBAAyB,GACjC,cAAc,CAAC,cAAc,EAAE,IAAI,EAAE,IAAI,CAAC,CAO5C"}
@@ -1,9 +1,50 @@
1
1
  import { getEventMeta, withEventMeta } from '@orpc/server';
2
2
  import { checkAccess, rowMatchesScope, tableEntryForName } from '../access.js';
3
- export async function* filterRealtimeChanges(source, options) {
4
- const subscriptions = new Set(options.subscriptions);
3
+ /**
4
+ * Whether one change may reach this subscriber: the given right plus the read
5
+ * scope of the row, evaluated against the caller's session. The session
6
+ * resolves at most once per stream.
7
+ */
8
+ export function createChangeGuard(entry, options) {
5
9
  let sessionPromise;
6
10
  const getSession = () => (sessionPromise ??= options.getSession());
11
+ return async (change) => {
12
+ if (!entry.enabled || options.rule === 'deny')
13
+ return false;
14
+ const needsSession = options.rule !== 'public' || entry.readScope !== undefined;
15
+ const session = needsSession
16
+ ? await getSession()
17
+ : { user: null, activeOrganizationId: null };
18
+ const context = {
19
+ request: options.request,
20
+ user: session.user,
21
+ row: change.record,
22
+ session: { activeOrganizationId: session.activeOrganizationId },
23
+ };
24
+ if (!(await checkAccess(options.rule, context, entry.ownerColumn)).allowed) {
25
+ return false;
26
+ }
27
+ if (entry.readScope &&
28
+ !rowMatchesScope(change.record, entry.readScope(context))) {
29
+ return false;
30
+ }
31
+ return true;
32
+ };
33
+ }
34
+ /** Preserve publisher event metadata (ids) across the projection. */
35
+ function project(change) {
36
+ const projected = {
37
+ table: change.table,
38
+ action: change.action,
39
+ record: change.record,
40
+ };
41
+ const meta = getEventMeta(change);
42
+ return meta ? withEventMeta(projected, meta) : projected;
43
+ }
44
+ export async function* filterRealtimeChanges(source, options) {
45
+ const subscriptions = new Set(options.subscriptions);
46
+ // One guard — and therefore one cached session — per table entry.
47
+ const guards = new Map();
7
48
  for await (const change of source) {
8
49
  // Events name tables by schema key; the SQL-name lookup stays as a fallback
9
50
  // for publishers outside the CRUD path that only know the physical name.
@@ -17,32 +58,28 @@ export async function* filterRealtimeChanges(source, options) {
17
58
  !subscriptions.has(`${change.table}/${String(recordId)}`))) {
18
59
  continue;
19
60
  }
20
- if (entry.get === 'deny')
61
+ let guard = guards.get(entry);
62
+ if (!guard) {
63
+ guard = createChangeGuard(entry, { ...options, rule: entry.get });
64
+ guards.set(entry, guard);
65
+ }
66
+ if (!(await guard(change)))
21
67
  continue;
22
- const needsSession = entry.get !== 'public' || entry.readScope !== undefined;
23
- const session = needsSession
24
- ? await getSession()
25
- : { user: null, activeOrganizationId: null };
26
- const context = {
27
- request: options.request,
28
- user: session.user,
29
- row: change.record,
30
- session: { activeOrganizationId: session.activeOrganizationId },
31
- };
32
- if (!(await checkAccess(entry.get, context, entry.ownerColumn)).allowed) {
68
+ yield project(change);
69
+ }
70
+ }
71
+ /**
72
+ * The same stream narrowed to one table with its access entry known up front —
73
+ * what a live view (`GET /{table}:live`) consumes.
74
+ */
75
+ export async function* filterTableChanges(source, options) {
76
+ const guard = createChangeGuard(options.entry, options);
77
+ for await (const change of source) {
78
+ if (change.table !== options.tableName)
33
79
  continue;
34
- }
35
- if (entry.readScope &&
36
- !rowMatchesScope(change.record, entry.readScope(context))) {
80
+ if (!(await guard(change)))
37
81
  continue;
38
- }
39
- const projected = {
40
- table: change.table,
41
- action: change.action,
42
- record: change.record,
43
- };
44
- const meta = getEventMeta(change);
45
- yield meta ? withEventMeta(projected, meta) : projected;
82
+ yield project(change);
46
83
  }
47
84
  }
48
85
  //# sourceMappingURL=filter.js.map