bunderstack 0.19.2 → 0.21.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 (149) hide show
  1. package/CHANGELOG.md +39 -1
  2. package/dist/api/crud-router.d.ts +23 -28
  3. package/dist/api/crud-router.d.ts.map +1 -1
  4. package/dist/api/crud-router.js +39 -18
  5. package/dist/api/crud-router.js.map +1 -1
  6. package/dist/api/live-window.d.ts.map +1 -1
  7. package/dist/api/live-window.js +13 -3
  8. package/dist/api/live-window.js.map +1 -1
  9. package/dist/api/realtime-router.d.ts +3 -1
  10. package/dist/api/realtime-router.d.ts.map +1 -1
  11. package/dist/api/realtime-router.js +1 -0
  12. package/dist/api/realtime-router.js.map +1 -1
  13. package/dist/api/types.d.ts +2 -2
  14. package/dist/api/types.d.ts.map +1 -1
  15. package/dist/api/types.js.map +1 -1
  16. package/dist/client/client.d.ts +53 -0
  17. package/dist/client/client.d.ts.map +1 -0
  18. package/dist/client/client.js +111 -0
  19. package/dist/client/client.js.map +1 -0
  20. package/dist/client/index.d.ts +4 -0
  21. package/dist/client/index.d.ts.map +1 -0
  22. package/dist/client/index.js +4 -0
  23. package/dist/client/index.js.map +1 -0
  24. package/dist/client/live-view.d.ts +49 -0
  25. package/dist/client/live-view.d.ts.map +1 -0
  26. package/dist/client/live-view.js +184 -0
  27. package/dist/client/live-view.js.map +1 -0
  28. package/dist/client/react.d.ts +4 -0
  29. package/dist/client/react.d.ts.map +1 -0
  30. package/dist/client/react.js +6 -0
  31. package/dist/client/react.js.map +1 -0
  32. package/dist/client/realtime.d.ts +54 -0
  33. package/dist/client/realtime.d.ts.map +1 -0
  34. package/dist/client/realtime.js +105 -0
  35. package/dist/client/realtime.js.map +1 -0
  36. package/dist/client/rest.d.ts +2 -0
  37. package/dist/client/rest.d.ts.map +1 -0
  38. package/dist/client/rest.js +2 -0
  39. package/dist/client/rest.js.map +1 -0
  40. package/dist/client/rpc-client.d.ts +38 -0
  41. package/dist/client/rpc-client.d.ts.map +1 -0
  42. package/dist/client/rpc-client.js +59 -0
  43. package/dist/client/rpc-client.js.map +1 -0
  44. package/dist/client/solid.d.ts +7 -0
  45. package/dist/client/solid.d.ts.map +1 -0
  46. package/dist/client/solid.js +21 -0
  47. package/dist/client/solid.js.map +1 -0
  48. package/dist/client/svelte.d.ts +6 -0
  49. package/dist/client/svelte.d.ts.map +1 -0
  50. package/dist/client/svelte.js +9 -0
  51. package/dist/client/svelte.js.map +1 -0
  52. package/dist/client/vue.d.ts +5 -0
  53. package/dist/client/vue.d.ts.map +1 -0
  54. package/dist/client/vue.js +11 -0
  55. package/dist/client/vue.js.map +1 -0
  56. package/dist/codegen.d.ts +36 -0
  57. package/dist/codegen.d.ts.map +1 -0
  58. package/dist/codegen.js +235 -0
  59. package/dist/codegen.js.map +1 -0
  60. package/dist/crud-operations.d.ts.map +1 -1
  61. package/dist/crud-operations.js +7 -3
  62. package/dist/crud-operations.js.map +1 -1
  63. package/dist/index.d.ts +9 -4
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js.map +1 -1
  66. package/dist/live/protocol.d.ts +3 -0
  67. package/dist/live/protocol.d.ts.map +1 -1
  68. package/dist/live/protocol.js.map +1 -1
  69. package/dist/query/api.d.ts +10 -0
  70. package/dist/query/api.d.ts.map +1 -0
  71. package/dist/query/api.js +7 -0
  72. package/dist/query/api.js.map +1 -0
  73. package/dist/query/client.d.ts +33 -0
  74. package/dist/query/client.d.ts.map +1 -0
  75. package/dist/query/client.js +98 -0
  76. package/dist/query/client.js.map +1 -0
  77. package/dist/query/fetch.d.ts +3 -0
  78. package/dist/query/fetch.d.ts.map +1 -0
  79. package/dist/query/fetch.js +4 -0
  80. package/dist/query/fetch.js.map +1 -0
  81. package/dist/query/index.d.ts +8 -0
  82. package/dist/query/index.d.ts.map +1 -0
  83. package/dist/query/index.js +4 -0
  84. package/dist/query/index.js.map +1 -0
  85. package/dist/query/infer.d.ts +25 -0
  86. package/dist/query/infer.d.ts.map +1 -0
  87. package/dist/query/infer.js +2 -0
  88. package/dist/query/infer.js.map +1 -0
  89. package/dist/query/react.d.ts +3 -0
  90. package/dist/query/react.d.ts.map +1 -0
  91. package/dist/query/react.js +2 -0
  92. package/dist/query/react.js.map +1 -0
  93. package/dist/query/realtime-flush.d.ts +26 -0
  94. package/dist/query/realtime-flush.d.ts.map +1 -0
  95. package/dist/query/realtime-flush.js +45 -0
  96. package/dist/query/realtime-flush.js.map +1 -0
  97. package/dist/query/realtime-stream.d.ts +3 -0
  98. package/dist/query/realtime-stream.d.ts.map +1 -0
  99. package/dist/query/realtime-stream.js +3 -0
  100. package/dist/query/realtime-stream.js.map +1 -0
  101. package/dist/query/realtime.d.ts +88 -0
  102. package/dist/query/realtime.d.ts.map +1 -0
  103. package/dist/query/realtime.js +255 -0
  104. package/dist/query/realtime.js.map +1 -0
  105. package/dist/realtime/facade.d.ts +3 -1
  106. package/dist/realtime/facade.d.ts.map +1 -1
  107. package/dist/realtime/facade.js +2 -1
  108. package/dist/realtime/facade.js.map +1 -1
  109. package/dist/realtime/filter.d.ts.map +1 -1
  110. package/dist/realtime/filter.js +1 -0
  111. package/dist/realtime/filter.js.map +1 -1
  112. package/dist/realtime/publisher.d.ts +2 -0
  113. package/dist/realtime/publisher.d.ts.map +1 -1
  114. package/dist/realtime/publisher.js.map +1 -1
  115. package/dist/start/auth-client.d.ts +11 -0
  116. package/dist/start/auth-client.d.ts.map +1 -0
  117. package/dist/start/auth-client.js +15 -0
  118. package/dist/start/auth-client.js.map +1 -0
  119. package/dist/start/index.d.ts +66 -0
  120. package/dist/start/index.d.ts.map +1 -0
  121. package/dist/start/index.js +49 -0
  122. package/dist/start/index.js.map +1 -0
  123. package/dist/start/isomorphic-fetch.d.ts +13 -0
  124. package/dist/start/isomorphic-fetch.d.ts.map +1 -0
  125. package/dist/start/isomorphic-fetch.js +33 -0
  126. package/dist/start/isomorphic-fetch.js.map +1 -0
  127. package/dist/sync/collection.d.ts +106 -0
  128. package/dist/sync/collection.d.ts.map +1 -0
  129. package/dist/sync/collection.js +258 -0
  130. package/dist/sync/collection.js.map +1 -0
  131. package/dist/sync/index.d.ts +8 -0
  132. package/dist/sync/index.d.ts.map +1 -0
  133. package/dist/sync/index.js +4 -0
  134. package/dist/sync/index.js.map +1 -0
  135. package/dist/sync/realtime-sync.d.ts +31 -0
  136. package/dist/sync/realtime-sync.d.ts.map +1 -0
  137. package/dist/sync/realtime-sync.js +46 -0
  138. package/dist/sync/realtime-sync.js.map +1 -0
  139. package/dist/sync/sync-client.d.ts +28 -0
  140. package/dist/sync/sync-client.d.ts.map +1 -0
  141. package/dist/sync/sync-client.js +52 -0
  142. package/dist/sync/sync-client.js.map +1 -0
  143. package/dist/sync/update-queue.d.ts +27 -0
  144. package/dist/sync/update-queue.d.ts.map +1 -0
  145. package/dist/sync/update-queue.js +66 -0
  146. package/dist/sync/update-queue.js.map +1 -0
  147. package/package.json +107 -3
  148. package/skills/creating-bunderstack-apps/references/runtime-integrations.md +1 -1
  149. package/skills/migrating-to-bunderstack/references/runtime-replacements.md +2 -2
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-client.d.ts","sourceRoot":"","sources":["../../src/sync/sync-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAExD,OAAO,EAEL,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,WAAW,EACjB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAyB,KAAK,eAAe,EAAE,MAAM,cAAc,CAAA;AAM1E,MAAM,MAAM,MAAM,CAChB,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACvC,CAAC,SAAS,MAAM,OAAO,IACrB,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,GACzC;IAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,GACvB,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,GACrD,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GACvB;IAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,CAAA;AAE7B,MAAM,MAAM,SAAS,CACnB,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACvC,CAAC,SAAS,MAAM,OAAO,IACrB,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,GACzC,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,GAC3B,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAA;AAE3B,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,GAAG,GAAG,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC3E,WAAW,EAAE,WAAW,CAAA;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,qBAAqB,CAAC,IAAI,SAAS,iBAAiB,IAAI;KACjE,CAAC,IAAI,WAAW,CAAC,IAAI,CAAC,GAAG,eAAe,CACvC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAC5B,SAAS,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAC/B,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CACtC;CACF,GAAG;IACF,KAAK,EAAE,iBAAiB,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAA;IACvC,QAAQ,EACJ;QAAE,KAAK,IAAI,IAAI,CAAC;QAAC,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;KAAE,GAC7D,SAAS,CAAA;CACd,CAAA;AAED,wBAAgB,gBAAgB,CAAC,IAAI,SAAS,iBAAiB,EAC7D,OAAO,EAAE,iBAAiB,GACzB,qBAAqB,CAAC,IAAI,CAAC,CA+C7B"}
@@ -0,0 +1,52 @@
1
+ import { createClient, } from '../query/index.js';
2
+ import { createTableCollection } from './collection.js';
3
+ import { createSyncRealtimeClient, } from './realtime-sync.js';
4
+ export function createSyncClient(options) {
5
+ const api = createClient(options);
6
+ const materialized = new Map();
7
+ const realtimeHandles = new Map();
8
+ const tables = new Map();
9
+ const realtimeEnabled = options.realtime ?? typeof window !== 'undefined';
10
+ const result = new Proxy({}, {
11
+ get(_target, property) {
12
+ if (typeof property !== 'string')
13
+ return undefined;
14
+ if (property === 'files')
15
+ return api.files;
16
+ if (property === 'realtime') {
17
+ return realtimeEnabled
18
+ ? {
19
+ close: () => realtimeHandles.forEach((handle) => handle.close()),
20
+ subscribe: async (names) => {
21
+ for (const name of names)
22
+ void result[name];
23
+ },
24
+ }
25
+ : undefined;
26
+ }
27
+ if (['then', 'toJSON', 'constructor', '$$typeof'].includes(property))
28
+ return undefined;
29
+ const cached = tables.get(property);
30
+ if (cached)
31
+ return cached;
32
+ const collection = createTableCollection({
33
+ tableName: property,
34
+ procedures: api[property],
35
+ queryClient: options.queryClient,
36
+ });
37
+ tables.set(property, collection);
38
+ materialized.set(property, collection);
39
+ if (realtimeEnabled) {
40
+ realtimeHandles.set(property, createSyncRealtimeClient({
41
+ api: api,
42
+ tables: [property],
43
+ resolve: (table) => materialized.get(table),
44
+ resolveAll: () => materialized.values(),
45
+ }));
46
+ }
47
+ return collection;
48
+ },
49
+ });
50
+ return result;
51
+ }
52
+ //# sourceMappingURL=sync-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-client.js","sourceRoot":"","sources":["../../src/sync/sync-client.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,YAAY,GAOb,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,qBAAqB,EAAwB,MAAM,cAAc,CAAA;AAC1E,OAAO,EACL,wBAAwB,GAEzB,MAAM,iBAAiB,CAAA;AAsCxB,MAAM,UAAU,gBAAgB,CAC9B,OAA0B;IAE1B,MAAM,GAAG,GAAG,YAAY,CAAO,OAAO,CAAC,CAAA;IACvC,MAAM,YAAY,GAAG,IAAI,GAAG,EAA8B,CAAA;IAC1D,MAAM,eAAe,GAAG,IAAI,GAAG,EAA6B,CAAA;IAC5D,MAAM,MAAM,GAAG,IAAI,GAAG,EAAmB,CAAA;IACzC,MAAM,eAAe,GAAG,OAAO,CAAC,QAAQ,IAAI,OAAO,MAAM,KAAK,WAAW,CAAA;IAEzE,MAAM,MAAM,GAAG,IAAI,KAAK,CAAC,EAAiC,EAAE;QAC1D,GAAG,CAAC,OAAO,EAAE,QAAQ;YACnB,IAAI,OAAO,QAAQ,KAAK,QAAQ;gBAAE,OAAO,SAAS,CAAA;YAClD,IAAI,QAAQ,KAAK,OAAO;gBAAE,OAAO,GAAG,CAAC,KAAK,CAAA;YAC1C,IAAI,QAAQ,KAAK,UAAU,EAAE,CAAC;gBAC5B,OAAO,eAAe;oBACpB,CAAC,CAAC;wBACE,KAAK,EAAE,GAAG,EAAE,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;wBAChE,SAAS,EAAE,KAAK,EAAE,KAAe,EAAE,EAAE;4BACnC,KAAK,MAAM,IAAI,IAAI,KAAK;gCAAE,KAAM,MAAc,CAAC,IAAI,CAAC,CAAA;wBACtD,CAAC;qBACF;oBACH,CAAC,CAAC,SAAS,CAAA;YACf,CAAC;YACD,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,aAAa,EAAE,UAAU,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC;gBAClE,OAAO,SAAS,CAAA;YAClB,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;YACnC,IAAI,MAAM;gBAAE,OAAO,MAAM,CAAA;YACzB,MAAM,UAAU,GAAG,qBAAqB,CAAC;gBACvC,SAAS,EAAE,QAAQ;gBACnB,UAAU,EAAG,GAAW,CAAC,QAAQ,CAAC;gBAClC,WAAW,EAAE,OAAO,CAAC,WAAW;aACjC,CAAC,CAAA;YACF,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAA;YAChC,YAAY,CAAC,GAAG,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAA;YACtC,IAAI,eAAe,EAAE,CAAC;gBACpB,eAAe,CAAC,GAAG,CACjB,QAAQ,EACR,wBAAwB,CAAC;oBACvB,GAAG,EAAE,GAAU;oBACf,MAAM,EAAE,CAAC,QAAQ,CAAC;oBAClB,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC;oBAC3C,UAAU,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE;iBACxC,CAAC,CACH,CAAA;YACH,CAAC;YACD,OAAO,UAAU,CAAA;QACnB,CAAC;KACF,CAAC,CAAA;IACF,OAAO,MAAM,CAAA;AACf,CAAC","sourcesContent":["import type { QueryClient } from '@tanstack/react-query'\n\nimport {\n createClient,\n type AnyBunderstackApp,\n type BunderstackClient,\n type InferInsert,\n type InferSchema,\n type InferSelect,\n type InferTables,\n} from '../query/index'\nimport { createTableCollection, type TableCollection } from './collection'\nimport {\n createSyncRealtimeClient,\n type SyncRealtimeTarget,\n} from './realtime-sync'\n\nexport type RowFor<\n TSchema extends Record<string, unknown>,\n K extends keyof TSchema,\n> = [InferSelect<TSchema[K]>] extends [never]\n ? { id: string | number }\n : InferSelect<TSchema[K]> extends { id: string | number }\n ? InferSelect<TSchema[K]>\n : { id: string | number }\n\nexport type CreateFor<\n TSchema extends Record<string, unknown>,\n K extends keyof TSchema,\n> = [InferInsert<TSchema[K]>] extends [never]\n ? Partial<RowFor<TSchema, K>>\n : InferInsert<TSchema[K]>\n\nexport type SyncClientOptions = {\n baseUrl?: string\n fetch?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>\n queryClient: QueryClient\n realtime?: boolean\n}\n\nexport type BunderstackSyncClient<TApp extends AnyBunderstackApp> = {\n [K in InferTables<TApp>]: TableCollection<\n RowFor<InferSchema<TApp>, K>,\n CreateFor<InferSchema<TApp>, K>,\n Partial<RowFor<InferSchema<TApp>, K>>\n >\n} & {\n files: BunderstackClient<TApp>['files']\n realtime:\n | { close(): void; subscribe(tables: string[]): Promise<void> }\n | undefined\n}\n\nexport function createSyncClient<TApp extends AnyBunderstackApp>(\n options: SyncClientOptions,\n): BunderstackSyncClient<TApp> {\n const api = createClient<TApp>(options)\n const materialized = new Map<string, SyncRealtimeTarget>()\n const realtimeHandles = new Map<string, { close(): void }>()\n const tables = new Map<string, unknown>()\n const realtimeEnabled = options.realtime ?? typeof window !== 'undefined'\n\n const result = new Proxy({} as BunderstackSyncClient<TApp>, {\n get(_target, property) {\n if (typeof property !== 'string') return undefined\n if (property === 'files') return api.files\n if (property === 'realtime') {\n return realtimeEnabled\n ? {\n close: () => realtimeHandles.forEach((handle) => handle.close()),\n subscribe: async (names: string[]) => {\n for (const name of names) void (result as any)[name]\n },\n }\n : undefined\n }\n if (['then', 'toJSON', 'constructor', '$$typeof'].includes(property))\n return undefined\n const cached = tables.get(property)\n if (cached) return cached\n const collection = createTableCollection({\n tableName: property,\n procedures: (api as any)[property],\n queryClient: options.queryClient,\n })\n tables.set(property, collection)\n materialized.set(property, collection)\n if (realtimeEnabled) {\n realtimeHandles.set(\n property,\n createSyncRealtimeClient({\n api: api as any,\n tables: [property],\n resolve: (table) => materialized.get(table),\n resolveAll: () => materialized.values(),\n }),\n )\n }\n return collection\n },\n })\n return result\n}\n"]}
@@ -0,0 +1,27 @@
1
+ /** Per-key coalescing queue for high-frequency row updates.
2
+ *
3
+ * Cursor-shaped workloads call `collection.update()` many times a second. Sent
4
+ * one-for-one, each call is a request, a database write and a broadcast. This
5
+ * queue holds a single merged slot per row key: while a request is in flight,
6
+ * further updates for that key merge into the slot and go out as one follow-up
7
+ * once it lands. Request frequency settles at `min(update rate, 1/RTT)` without
8
+ * a tuned throttle constant, and the optimistic UI is untouched — only the
9
+ * network is coalesced.
10
+ */
11
+ export type UpdateQueueConfig<TKey, TRow> = {
12
+ /** Sends one merged batch of changes and resolves with the canonical row. */
13
+ send: (key: TKey, changes: Record<string, unknown>) => Promise<TRow>;
14
+ /** Called with the server's row before the batch's waiters resolve. Awaited,
15
+ * so a handler that needs to recover (refetch) finishes first. */
16
+ onResult: (row: TRow) => void | Promise<void>;
17
+ };
18
+ export type UpdateQueue<TKey> = {
19
+ /** Queues changes for `key`. Resolves once a request carrying them lands. */
20
+ enqueue: (key: TKey, changes: Record<string, unknown>) => Promise<void>;
21
+ /** Drops changes still queued for `key` (resolving their waiters) and waits
22
+ * for any in-flight request to land. For operations that supersede pending
23
+ * updates and must not race them — a delete of the same row. */
24
+ settle: (key: TKey) => Promise<void>;
25
+ };
26
+ export declare function createUpdateQueue<TKey, TRow>(config: UpdateQueueConfig<TKey, TRow>): UpdateQueue<TKey>;
27
+ //# sourceMappingURL=update-queue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update-queue.d.ts","sourceRoot":"","sources":["../../src/sync/update-queue.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAcH,MAAM,MAAM,iBAAiB,CAAC,IAAI,EAAE,IAAI,IAAI;IAC1C,6EAA6E;IAC7E,IAAI,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACpE;sEACkE;IAClE,QAAQ,EAAE,CAAC,GAAG,EAAE,IAAI,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC9C,CAAA;AAED,MAAM,MAAM,WAAW,CAAC,IAAI,IAAI;IAC9B,6EAA6E;IAC7E,OAAO,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACvE;;oEAEgE;IAChE,MAAM,EAAE,CAAC,GAAG,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CACrC,CAAA;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAC1C,MAAM,EAAE,iBAAiB,CAAC,IAAI,EAAE,IAAI,CAAC,GACpC,WAAW,CAAC,IAAI,CAAC,CAkDnB"}
@@ -0,0 +1,66 @@
1
+ /** Per-key coalescing queue for high-frequency row updates.
2
+ *
3
+ * Cursor-shaped workloads call `collection.update()` many times a second. Sent
4
+ * one-for-one, each call is a request, a database write and a broadcast. This
5
+ * queue holds a single merged slot per row key: while a request is in flight,
6
+ * further updates for that key merge into the slot and go out as one follow-up
7
+ * once it lands. Request frequency settles at `min(update rate, 1/RTT)` without
8
+ * a tuned throttle constant, and the optimistic UI is untouched — only the
9
+ * network is coalesced.
10
+ */
11
+ export function createUpdateQueue(config) {
12
+ const slots = new Map();
13
+ async function drain(key, slot) {
14
+ while (Object.keys(slot.changes).length > 0) {
15
+ // Taking changes and waiters as one snapshot is what guarantees a waiter
16
+ // resolves only after a request that carried its own changes.
17
+ const changes = slot.changes;
18
+ const waiters = slot.waiters;
19
+ slot.changes = {};
20
+ slot.waiters = [];
21
+ try {
22
+ await config.onResult(await config.send(key, changes));
23
+ for (const waiter of waiters)
24
+ waiter.resolve();
25
+ }
26
+ catch (error) {
27
+ // Replaying queued changes on top of a failed base would diverge from
28
+ // the server silently, so the whole key is rolled back instead.
29
+ const queued = slot.waiters;
30
+ slot.changes = {};
31
+ slot.waiters = [];
32
+ for (const waiter of [...waiters, ...queued])
33
+ waiter.reject(error);
34
+ }
35
+ }
36
+ slots.delete(key);
37
+ }
38
+ return {
39
+ enqueue(key, changes) {
40
+ let slot = slots.get(key);
41
+ if (!slot) {
42
+ slot = { changes: {}, waiters: [], done: null };
43
+ slots.set(key, slot);
44
+ }
45
+ Object.assign(slot.changes, changes);
46
+ const promise = new Promise((resolve, reject) => {
47
+ slot.waiters.push({ resolve, reject });
48
+ });
49
+ if (!slot.done)
50
+ slot.done = drain(key, slot);
51
+ return promise;
52
+ },
53
+ settle(key) {
54
+ const slot = slots.get(key);
55
+ if (!slot)
56
+ return Promise.resolve();
57
+ const waiters = slot.waiters;
58
+ slot.changes = {};
59
+ slot.waiters = [];
60
+ for (const waiter of waiters)
61
+ waiter.resolve();
62
+ return slot.done ?? Promise.resolve();
63
+ },
64
+ };
65
+ }
66
+ //# sourceMappingURL=update-queue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update-queue.js","sourceRoot":"","sources":["../../src/sync/update-queue.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AA+BH,MAAM,UAAU,iBAAiB,CAC/B,MAAqC;IAErC,MAAM,KAAK,GAAG,IAAI,GAAG,EAAc,CAAA;IAEnC,KAAK,UAAU,KAAK,CAAC,GAAS,EAAE,IAAU;QACxC,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC5C,yEAAyE;YACzE,8DAA8D;YAC9D,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAA;YAC5B,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAA;YAC5B,IAAI,CAAC,OAAO,GAAG,EAAE,CAAA;YACjB,IAAI,CAAC,OAAO,GAAG,EAAE,CAAA;YACjB,IAAI,CAAC;gBACH,MAAM,MAAM,CAAC,QAAQ,CAAC,MAAM,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAA;gBACtD,KAAK,MAAM,MAAM,IAAI,OAAO;oBAAE,MAAM,CAAC,OAAO,EAAE,CAAA;YAChD,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,sEAAsE;gBACtE,gEAAgE;gBAChE,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAA;gBAC3B,IAAI,CAAC,OAAO,GAAG,EAAE,CAAA;gBACjB,IAAI,CAAC,OAAO,GAAG,EAAE,CAAA;gBACjB,KAAK,MAAM,MAAM,IAAI,CAAC,GAAG,OAAO,EAAE,GAAG,MAAM,CAAC;oBAAE,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YACpE,CAAC;QACH,CAAC;QACD,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IACnB,CAAC;IAED,OAAO;QACL,OAAO,CAAC,GAAG,EAAE,OAAO;YAClB,IAAI,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;YACzB,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,IAAI,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;gBAC/C,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;YACtB,CAAC;YACD,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;YACpC,MAAM,OAAO,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;gBACpD,IAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAA;YACzC,CAAC,CAAC,CAAA;YACF,IAAI,CAAC,IAAI,CAAC,IAAI;gBAAE,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;YAC5C,OAAO,OAAO,CAAA;QAChB,CAAC;QACD,MAAM,CAAC,GAAG;YACR,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;YAC3B,IAAI,CAAC,IAAI;gBAAE,OAAO,OAAO,CAAC,OAAO,EAAE,CAAA;YACnC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAA;YAC5B,IAAI,CAAC,OAAO,GAAG,EAAE,CAAA;YACjB,IAAI,CAAC,OAAO,GAAG,EAAE,CAAA;YACjB,KAAK,MAAM,MAAM,IAAI,OAAO;gBAAE,MAAM,CAAC,OAAO,EAAE,CAAA;YAC9C,OAAO,IAAI,CAAC,IAAI,IAAI,OAAO,CAAC,OAAO,EAAE,CAAA;QACvC,CAAC;KACF,CAAA;AACH,CAAC","sourcesContent":["/** Per-key coalescing queue for high-frequency row updates.\n *\n * Cursor-shaped workloads call `collection.update()` many times a second. Sent\n * one-for-one, each call is a request, a database write and a broadcast. This\n * queue holds a single merged slot per row key: while a request is in flight,\n * further updates for that key merge into the slot and go out as one follow-up\n * once it lands. Request frequency settles at `min(update rate, 1/RTT)` without\n * a tuned throttle constant, and the optimistic UI is untouched — only the\n * network is coalesced.\n */\n\ntype Waiter = {\n resolve: () => void\n reject: (error: unknown) => void\n}\n\ntype Slot = {\n changes: Record<string, unknown>\n waiters: Waiter[]\n /** Promise of the running drain loop; never rejects. */\n done: Promise<void> | null\n}\n\nexport type UpdateQueueConfig<TKey, TRow> = {\n /** Sends one merged batch of changes and resolves with the canonical row. */\n send: (key: TKey, changes: Record<string, unknown>) => Promise<TRow>\n /** Called with the server's row before the batch's waiters resolve. Awaited,\n * so a handler that needs to recover (refetch) finishes first. */\n onResult: (row: TRow) => void | Promise<void>\n}\n\nexport type UpdateQueue<TKey> = {\n /** Queues changes for `key`. Resolves once a request carrying them lands. */\n enqueue: (key: TKey, changes: Record<string, unknown>) => Promise<void>\n /** Drops changes still queued for `key` (resolving their waiters) and waits\n * for any in-flight request to land. For operations that supersede pending\n * updates and must not race them — a delete of the same row. */\n settle: (key: TKey) => Promise<void>\n}\n\nexport function createUpdateQueue<TKey, TRow>(\n config: UpdateQueueConfig<TKey, TRow>,\n): UpdateQueue<TKey> {\n const slots = new Map<TKey, Slot>()\n\n async function drain(key: TKey, slot: Slot) {\n while (Object.keys(slot.changes).length > 0) {\n // Taking changes and waiters as one snapshot is what guarantees a waiter\n // resolves only after a request that carried its own changes.\n const changes = slot.changes\n const waiters = slot.waiters\n slot.changes = {}\n slot.waiters = []\n try {\n await config.onResult(await config.send(key, changes))\n for (const waiter of waiters) waiter.resolve()\n } catch (error) {\n // Replaying queued changes on top of a failed base would diverge from\n // the server silently, so the whole key is rolled back instead.\n const queued = slot.waiters\n slot.changes = {}\n slot.waiters = []\n for (const waiter of [...waiters, ...queued]) waiter.reject(error)\n }\n }\n slots.delete(key)\n }\n\n return {\n enqueue(key, changes) {\n let slot = slots.get(key)\n if (!slot) {\n slot = { changes: {}, waiters: [], done: null }\n slots.set(key, slot)\n }\n Object.assign(slot.changes, changes)\n const promise = new Promise<void>((resolve, reject) => {\n slot!.waiters.push({ resolve, reject })\n })\n if (!slot.done) slot.done = drain(key, slot)\n return promise\n },\n settle(key) {\n const slot = slots.get(key)\n if (!slot) return Promise.resolve()\n const waiters = slot.waiters\n slot.changes = {}\n slot.waiters = []\n for (const waiter of waiters) waiter.resolve()\n return slot.done ?? Promise.resolve()\n },\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bunderstack",
3
- "version": "0.19.2",
3
+ "version": "0.21.0",
4
4
  "description": "Batteries-included backend framework for Bun: type-safe oRPC APIs, auth, storage, realtime, jobs, email, and validated env from one config.",
5
5
  "keywords": [
6
6
  "backend",
@@ -44,6 +44,10 @@
44
44
  "types": "./dist/access.d.ts",
45
45
  "default": "./dist/access.js"
46
46
  },
47
+ "./codegen": {
48
+ "types": "./dist/codegen.d.ts",
49
+ "default": "./dist/codegen.js"
50
+ },
47
51
  "./database/bun-sql": {
48
52
  "types": "./dist/database/bun-sql.d.ts",
49
53
  "default": "./dist/database/bun-sql.js"
@@ -103,6 +107,50 @@
103
107
  "./api": {
104
108
  "types": "./dist/api/types.d.ts",
105
109
  "default": "./dist/api/types.js"
110
+ },
111
+ "./client": {
112
+ "types": "./dist/client/index.d.ts",
113
+ "default": "./dist/client/index.js"
114
+ },
115
+ "./client/rest": {
116
+ "types": "./dist/client/rest.d.ts",
117
+ "default": "./dist/client/rest.js"
118
+ },
119
+ "./client/react": {
120
+ "types": "./dist/client/react.d.ts",
121
+ "default": "./dist/client/react.js"
122
+ },
123
+ "./client/solid": {
124
+ "types": "./dist/client/solid.d.ts",
125
+ "default": "./dist/client/solid.js"
126
+ },
127
+ "./client/svelte": {
128
+ "types": "./dist/client/svelte.d.ts",
129
+ "default": "./dist/client/svelte.js"
130
+ },
131
+ "./client/vue": {
132
+ "types": "./dist/client/vue.d.ts",
133
+ "default": "./dist/client/vue.js"
134
+ },
135
+ "./query": {
136
+ "types": "./dist/query/index.d.ts",
137
+ "default": "./dist/query/index.js"
138
+ },
139
+ "./query/react": {
140
+ "types": "./dist/query/react.d.ts",
141
+ "default": "./dist/query/react.js"
142
+ },
143
+ "./sync": {
144
+ "types": "./dist/sync/index.d.ts",
145
+ "default": "./dist/sync/index.js"
146
+ },
147
+ "./start": {
148
+ "types": "./dist/start/index.d.ts",
149
+ "default": "./dist/start/index.js"
150
+ },
151
+ "./start/auth": {
152
+ "types": "./dist/start/auth-client.d.ts",
153
+ "default": "./dist/start/auth-client.js"
106
154
  }
107
155
  },
108
156
  "scripts": {
@@ -115,7 +163,10 @@
115
163
  "typecheck": "tsc --noEmit"
116
164
  },
117
165
  "dependencies": {
166
+ "@orpc/client": "2.0.0-beta.26",
167
+ "@orpc/server": "2.0.0-beta.26",
118
168
  "@standard-schema/spec": "1.1.0",
169
+ "@standardserver/core": "0.7.1",
119
170
  "valibot": "1.4.2",
120
171
  "yaml": "^2.9.0"
121
172
  },
@@ -123,33 +174,56 @@
123
174
  "@electric-sql/pglite": ">=0.3.0",
124
175
  "@libsql/client": ">=0.14.0",
125
176
  "@orpc/bun": "2.0.0-beta.26",
177
+ "@orpc/client": "2.0.0-beta.26",
126
178
  "@orpc/json-schema": "2.0.0-beta.26",
127
179
  "@orpc/openapi": "2.0.0-beta.26",
128
180
  "@orpc/publisher": "2.0.0-beta.26",
129
181
  "@orpc/server": "2.0.0-beta.26",
182
+ "@orpc/tanstack-query": "2.0.0-beta.26",
130
183
  "@orpc/valibot": "2.0.0-beta.26",
184
+ "@standardserver/core": "0.7.1",
185
+ "@tanstack/db": "0.6.16",
186
+ "@tanstack/query-core": "^5.101.1",
187
+ "@tanstack/query-db-collection": "1.1.0",
188
+ "@tanstack/react-query": "^5.101.1",
189
+ "@tanstack/react-start": "^1.168.26",
131
190
  "@types/nodemailer": "^6",
191
+ "@types/react": "^19.0.0",
132
192
  "better-auth": "^1.0.0",
133
193
  "drizzle-kit": "^0.30.0",
134
194
  "drizzle-orm": "^0.45.0",
135
- "drizzle-valibot": "0.4.2"
195
+ "drizzle-valibot": "0.4.2",
196
+ "react": "^19.0.0",
197
+ "solid-js": "2.0.0-rc.0",
198
+ "typescript": "^5.8.0",
199
+ "vue": "^3.5.0"
136
200
  },
137
201
  "peerDependencies": {
138
202
  "@electric-sql/pglite": ">=0.3.0",
139
203
  "@libsql/client": ">=0.14.0",
140
204
  "@orpc/bun": "2.0.0-beta.26",
205
+ "@orpc/client": "2.0.0-beta.26",
141
206
  "@orpc/json-schema": "2.0.0-beta.26",
142
207
  "@orpc/openapi": "2.0.0-beta.26",
143
208
  "@orpc/publisher": "2.0.0-beta.26",
144
209
  "@orpc/server": "2.0.0-beta.26",
210
+ "@orpc/tanstack-query": "2.0.0-beta.26",
145
211
  "@orpc/valibot": "2.0.0-beta.26",
212
+ "@tanstack/db": "0.6.16",
213
+ "@tanstack/query-core": "^5.101.0",
214
+ "@tanstack/query-db-collection": "1.1.0",
215
+ "@tanstack/react-query": "^5.101.0",
216
+ "@tanstack/react-start": ">=1.0.0",
146
217
  "better-auth": "^1.0.0",
147
218
  "drizzle-kit": "^0.30.0",
148
219
  "drizzle-orm": "^0.45.0",
149
220
  "drizzle-valibot": "0.4.2",
150
221
  "nodemailer": ">=6 <10",
151
222
  "postgres": ">=3.4.0",
152
- "typescript": ">=5"
223
+ "react": ">=18",
224
+ "solid-js": ">=2.0.0-rc.0",
225
+ "typescript": ">=5",
226
+ "vue": ">=3.5"
153
227
  },
154
228
  "peerDependenciesMeta": {
155
229
  "@electric-sql/pglite": {
@@ -158,6 +232,27 @@
158
232
  "@libsql/client": {
159
233
  "optional": true
160
234
  },
235
+ "@orpc/tanstack-query": {
236
+ "optional": true
237
+ },
238
+ "@tanstack/db": {
239
+ "optional": true
240
+ },
241
+ "@tanstack/query-core": {
242
+ "optional": true
243
+ },
244
+ "@tanstack/query-db-collection": {
245
+ "optional": true
246
+ },
247
+ "@tanstack/react-query": {
248
+ "optional": true
249
+ },
250
+ "@tanstack/react-start": {
251
+ "optional": true
252
+ },
253
+ "better-auth": {
254
+ "optional": true
255
+ },
161
256
  "drizzle-kit": {
162
257
  "optional": true
163
258
  },
@@ -167,8 +262,17 @@
167
262
  "postgres": {
168
263
  "optional": true
169
264
  },
265
+ "react": {
266
+ "optional": true
267
+ },
268
+ "solid-js": {
269
+ "optional": true
270
+ },
170
271
  "typescript": {
171
272
  "optional": true
273
+ },
274
+ "vue": {
275
+ "optional": true
172
276
  }
173
277
  }
174
278
  }
@@ -61,7 +61,7 @@ client filters it before cache callbacks and does not advance the Publisher
61
61
  resume ID. Do not add an application polling loop or publish heartbeat events
62
62
  through oRPC Publisher.
63
63
 
64
- `bunderstack-sync` reconciles successful mutations from the canonical row
64
+ `bunderstack/sync` reconciles successful mutations from the canonical row
65
65
  returned by generated CRUD, without a follow-up list refetch. Realtime echoes
66
66
  are idempotent, and reconnect performs the full refetch used to repair drift.
67
67
  Keep custom replacement procedures compatible by returning the complete row,
@@ -94,7 +94,7 @@ circular evaluation loop at boot.
94
94
  One catch-all route, built by the adapter rather than by hand:
95
95
 
96
96
  ```ts
97
- import { createApiHandlers } from 'bunderstack-start'
97
+ import { createApiHandlers } from 'bunderstack/start'
98
98
  import { createFileRoute } from '@tanstack/react-router'
99
99
  import { app } from '../../bunderstack'
100
100
 
@@ -188,7 +188,7 @@ query client consumes it automatically without updating cache state or the
188
188
  Publisher resume ID. Delete custom polling, keepalive, SSE registration, and
189
189
  client reconnect loops instead of wrapping them around the oRPC stream.
190
190
 
191
- For TanStack DB applications, use `bunderstack-sync`. Successful mutations are
191
+ For TanStack DB applications, use `bunderstack/sync`. Successful mutations are
192
192
  reconciled from their complete server response without a follow-up `list`
193
193
  refetch, and same-row updates are coalesced while a request is in flight.
194
194