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,105 @@
1
+ import { getEventMeta } from '@standardserver/core';
2
+ export const DEFAULT_KEEPALIVE_MS = 5_000;
3
+ export const DEFAULT_LIVENESS_FACTOR = 2.5;
4
+ const systemClock = {
5
+ setTimeout: (fn, ms) => setTimeout(fn, ms),
6
+ clearTimeout: (handle) => clearTimeout(handle),
7
+ };
8
+ export function isRealtimeHeartbeat(event) {
9
+ return 'type' in event && event.type === 'heartbeat';
10
+ }
11
+ const LIVENESS_ABORT = 'bunderstack: realtime stream went silent';
12
+ /** Shared reconnecting transport used by LiveView, Query, and DB adapters. */
13
+ export function openRealtimeStream(options) {
14
+ const controller = new AbortController();
15
+ const signal = AbortSignal.any([controller.signal, options.signal]);
16
+ const clock = options.clock ?? systemClock;
17
+ const random = options.random ?? Math.random;
18
+ const retryMs = Math.max(0, options.retryMs ?? 1_000);
19
+ const maxRetryMs = Math.max(retryMs, options.maxRetryMs ?? 30_000);
20
+ const livenessFactor = options.livenessFactor ?? DEFAULT_LIVENESS_FACTOR;
21
+ let keepaliveMs = options.defaultKeepaliveMs ?? DEFAULT_KEEPALIVE_MS;
22
+ const wait = (ms) => new Promise((resolve) => {
23
+ if (signal.aborted)
24
+ return resolve();
25
+ const timer = clock.setTimeout(done, ms);
26
+ function done() {
27
+ clock.clearTimeout(timer);
28
+ signal.removeEventListener('abort', done);
29
+ resolve();
30
+ }
31
+ signal.addEventListener('abort', done, { once: true });
32
+ });
33
+ const done = (async () => {
34
+ let connected = false;
35
+ let lastEventId;
36
+ let retryAttempt = 0;
37
+ while (!signal.aborted) {
38
+ const attempt = new AbortController();
39
+ const attemptSignal = AbortSignal.any([signal, attempt.signal]);
40
+ let timer;
41
+ let wentSilent = false;
42
+ const disarm = () => {
43
+ if (timer === undefined)
44
+ return;
45
+ clock.clearTimeout(timer);
46
+ timer = undefined;
47
+ };
48
+ const arm = () => {
49
+ disarm();
50
+ timer = clock.setTimeout(() => {
51
+ wentSilent = true;
52
+ attempt.abort(new Error(LIVENESS_ABORT));
53
+ }, keepaliveMs * livenessFactor);
54
+ };
55
+ try {
56
+ const events = await options.subscribe({
57
+ signal: attemptSignal,
58
+ lastEventId,
59
+ });
60
+ arm();
61
+ if (connected)
62
+ await options.onReconnect();
63
+ connected = true;
64
+ for await (const event of events) {
65
+ retryAttempt = 0;
66
+ if (isRealtimeHeartbeat(event)) {
67
+ if (typeof event.intervalMs === 'number' && event.intervalMs > 0)
68
+ keepaliveMs = event.intervalMs;
69
+ arm();
70
+ continue;
71
+ }
72
+ arm();
73
+ const id = getEventMeta(event)?.id;
74
+ if (id)
75
+ lastEventId = id;
76
+ try {
77
+ options.onChange(event);
78
+ }
79
+ catch (error) {
80
+ options.onError?.(error);
81
+ }
82
+ if (signal.aborted)
83
+ break;
84
+ }
85
+ }
86
+ catch (error) {
87
+ if (!signal.aborted && !wentSilent)
88
+ options.onError?.(error);
89
+ }
90
+ finally {
91
+ disarm();
92
+ }
93
+ if (!signal.aborted) {
94
+ const attemptNumber = retryAttempt + 1;
95
+ const ceiling = Math.min(maxRetryMs, retryMs * 2 ** retryAttempt);
96
+ const delayMs = Math.floor(random() * ceiling);
97
+ retryAttempt = attemptNumber;
98
+ options.onRetry?.({ attempt: attemptNumber, delayMs });
99
+ await wait(delayMs);
100
+ }
101
+ }
102
+ })();
103
+ return { close: () => controller.abort(), done };
104
+ }
105
+ //# sourceMappingURL=realtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"realtime.js","sourceRoot":"","sources":["../../src/client/realtime.ts"],"names":[],"mappings":"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","sourcesContent":["import { getEventMeta } from '@standardserver/core'\n\nexport type RealtimeAction = 'create' | 'update' | 'delete'\nexport type RealtimeChange = {\n table: string\n action: RealtimeAction\n record: Record<string, unknown>\n operationId?: string\n}\nexport type RealtimeHeartbeat = { type: 'heartbeat'; intervalMs?: number }\nexport type RealtimeEvent = RealtimeChange | RealtimeHeartbeat\nexport type RealtimeProcedure = {\n call(\n input: { tables: string[] },\n options?: { signal?: AbortSignal; lastEventId?: string },\n ): Promise<AsyncIterable<RealtimeEvent>>\n}\nexport type RealtimeSyncHandle = { close(): void; done: Promise<void> }\nexport type RealtimeClock = {\n setTimeout(fn: () => void, ms: number): unknown\n clearTimeout(handle: unknown): void\n}\nexport const DEFAULT_KEEPALIVE_MS = 5_000\nexport const DEFAULT_LIVENESS_FACTOR = 2.5\n\nconst systemClock: RealtimeClock = {\n setTimeout: (fn, ms) => setTimeout(fn, ms),\n clearTimeout: (handle) =>\n clearTimeout(handle as ReturnType<typeof setTimeout>),\n}\n\nexport type RealtimeStreamOptions = {\n subscribe: (options: {\n signal: AbortSignal\n lastEventId?: string\n }) => Promise<AsyncIterable<RealtimeEvent>>\n onChange: (change: RealtimeChange) => void\n onReconnect: () => void | Promise<void>\n onError?: (error: unknown) => void\n onRetry?: (retry: { attempt: number; delayMs: number }) => void\n signal: AbortSignal\n retryMs?: number\n maxRetryMs?: number\n livenessFactor?: number\n defaultKeepaliveMs?: number\n clock?: RealtimeClock\n random?: () => number\n}\n\nexport function isRealtimeHeartbeat(\n event: RealtimeEvent,\n): event is RealtimeHeartbeat {\n return 'type' in event && event.type === 'heartbeat'\n}\n\nconst LIVENESS_ABORT = 'bunderstack: realtime stream went silent'\n\n/** Shared reconnecting transport used by LiveView, Query, and DB adapters. */\nexport function openRealtimeStream(\n options: RealtimeStreamOptions,\n): RealtimeSyncHandle {\n const controller = new AbortController()\n const signal = AbortSignal.any([controller.signal, options.signal])\n const clock = options.clock ?? systemClock\n const random = options.random ?? Math.random\n const retryMs = Math.max(0, options.retryMs ?? 1_000)\n const maxRetryMs = Math.max(retryMs, options.maxRetryMs ?? 30_000)\n const livenessFactor = options.livenessFactor ?? DEFAULT_LIVENESS_FACTOR\n let keepaliveMs = options.defaultKeepaliveMs ?? DEFAULT_KEEPALIVE_MS\n\n const wait = (ms: number) =>\n new Promise<void>((resolve) => {\n if (signal.aborted) return resolve()\n const timer = clock.setTimeout(done, ms)\n function done() {\n clock.clearTimeout(timer)\n signal.removeEventListener('abort', done)\n resolve()\n }\n signal.addEventListener('abort', done, { once: true })\n })\n\n const done = (async () => {\n let connected = false\n let lastEventId: string | undefined\n let retryAttempt = 0\n while (!signal.aborted) {\n const attempt = new AbortController()\n const attemptSignal = AbortSignal.any([signal, attempt.signal])\n let timer: unknown\n let wentSilent = false\n const disarm = () => {\n if (timer === undefined) return\n clock.clearTimeout(timer)\n timer = undefined\n }\n const arm = () => {\n disarm()\n timer = clock.setTimeout(() => {\n wentSilent = true\n attempt.abort(new Error(LIVENESS_ABORT))\n }, keepaliveMs * livenessFactor)\n }\n try {\n const events = await options.subscribe({\n signal: attemptSignal,\n lastEventId,\n })\n arm()\n if (connected) await options.onReconnect()\n connected = true\n for await (const event of events) {\n retryAttempt = 0\n if (isRealtimeHeartbeat(event)) {\n if (typeof event.intervalMs === 'number' && event.intervalMs > 0)\n keepaliveMs = event.intervalMs\n arm()\n continue\n }\n arm()\n const id = getEventMeta(event)?.id\n if (id) lastEventId = id\n try {\n options.onChange(event)\n } catch (error) {\n options.onError?.(error)\n }\n if (signal.aborted) break\n }\n } catch (error) {\n if (!signal.aborted && !wentSilent) options.onError?.(error)\n } finally {\n disarm()\n }\n if (!signal.aborted) {\n const attemptNumber = retryAttempt + 1\n const ceiling = Math.min(maxRetryMs, retryMs * 2 ** retryAttempt)\n const delayMs = Math.floor(random() * ceiling)\n retryAttempt = attemptNumber\n options.onRetry?.({ attempt: attemptNumber, delayMs })\n await wait(delayMs)\n }\n }\n })()\n return { close: () => controller.abort(), done }\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export { createRestClient, type ClientOf, type RestClientOptions, type RouteCallOptions, type RouteMethod, type RouteOperation, } from './client.js';
2
+ //# sourceMappingURL=rest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rest.d.ts","sourceRoot":"","sources":["../../src/client/rest.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,KAAK,QAAQ,EACb,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,KAAK,cAAc,GACpB,MAAM,UAAU,CAAA"}
@@ -0,0 +1,2 @@
1
+ export { createRestClient, } from './client.js';
2
+ //# sourceMappingURL=rest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rest.js","sourceRoot":"","sources":["../../src/client/rest.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,GAMjB,MAAM,UAAU,CAAA","sourcesContent":["export {\n createRestClient,\n type ClientOf,\n type RestClientOptions,\n type RouteCallOptions,\n type RouteMethod,\n type RouteOperation,\n} from './client'\n"]}
@@ -0,0 +1,38 @@
1
+ import type { AnyRouter, RouterClient } from '@orpc/server';
2
+ export declare const OPERATION_ID_HEADER = "x-bunderstack-operation-id";
3
+ export type Fetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
4
+ export type ClientOptions = {
5
+ baseUrl?: string;
6
+ fetch?: Fetch;
7
+ headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
8
+ };
9
+ export type CallOptions = {
10
+ signal?: AbortSignal;
11
+ lastEventId?: string;
12
+ operationId?: string;
13
+ headers?: HeadersInit;
14
+ };
15
+ export type ClientCarrier = {
16
+ api: AnyRouter;
17
+ buckets: string;
18
+ };
19
+ export type AnyBunderstackApp = {
20
+ $inferClient?: ClientCarrier | undefined;
21
+ };
22
+ export type InferApiRouter<TApp extends AnyBunderstackApp> = NonNullable<TApp['$inferClient']>['api'];
23
+ type RpcContext = {
24
+ operationId?: string;
25
+ headers?: HeadersInit;
26
+ };
27
+ type AdaptCallArgs<TArgs extends unknown[]> = {
28
+ [K in keyof TArgs]: K extends 1 | '1' ? CallOptions : TArgs[K];
29
+ };
30
+ export type DirectClient<T> = T extends (...args: infer TArgs) => infer TResult ? (...args: AdaptCallArgs<TArgs>) => TResult : {
31
+ [K in keyof T]: DirectClient<T[K]>;
32
+ };
33
+ export type RpcClient<TRouter extends AnyRouter> = DirectClient<RouterClient<TRouter, RpcContext>>;
34
+ export type BunderstackClient<TApp extends AnyBunderstackApp> = RpcClient<InferApiRouter<TApp>>;
35
+ export declare function createRpcClient<TRouter extends AnyRouter>(options?: ClientOptions): RpcClient<TRouter>;
36
+ export declare function createClient<TApp extends AnyBunderstackApp>(options?: ClientOptions): BunderstackClient<TApp>;
37
+ export {};
38
+ //# sourceMappingURL=rpc-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc-client.d.ts","sourceRoot":"","sources":["../../src/client/rpc-client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAK3D,eAAO,MAAM,mBAAmB,+BAA+B,CAAA;AAE/D,MAAM,MAAM,KAAK,GAAG,CAClB,KAAK,EAAE,WAAW,GAAG,GAAG,EACxB,IAAI,CAAC,EAAE,WAAW,KACf,OAAO,CAAC,QAAQ,CAAC,CAAA;AAEtB,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,KAAK,CAAA;IACb,OAAO,CAAC,EAAE,WAAW,GAAG,CAAC,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAA;CACnE,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,OAAO,CAAC,EAAE,WAAW,CAAA;CACtB,CAAA;AAED,MAAM,MAAM,aAAa,GAAG;IAC1B,GAAG,EAAE,SAAS,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,YAAY,CAAC,EAAE,aAAa,GAAG,SAAS,CAAA;CACzC,CAAA;AAED,MAAM,MAAM,cAAc,CAAC,IAAI,SAAS,iBAAiB,IAAI,WAAW,CACtE,IAAI,CAAC,cAAc,CAAC,CACrB,CAAC,KAAK,CAAC,CAAA;AAER,KAAK,UAAU,GAAG;IAChB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,OAAO,CAAC,EAAE,WAAW,CAAA;CACtB,CAAA;AAED,KAAK,aAAa,CAAC,KAAK,SAAS,OAAO,EAAE,IAAI;KAC3C,CAAC,IAAI,MAAM,KAAK,GAAG,CAAC,SAAS,CAAC,GAAG,GAAG,GAAG,WAAW,GAAG,KAAK,CAAC,CAAC,CAAC;CAC/D,CAAA;AAED,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,MAAM,KAAK,KAAK,MAAM,OAAO,GAC3E,CAAC,GAAG,IAAI,EAAE,aAAa,CAAC,KAAK,CAAC,KAAK,OAAO,GAC1C;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAAE,CAAA;AAE1C,MAAM,MAAM,SAAS,CAAC,OAAO,SAAS,SAAS,IAAI,YAAY,CAC7D,YAAY,CAAC,OAAO,EAAE,UAAU,CAAC,CAClC,CAAA;AAED,MAAM,MAAM,iBAAiB,CAAC,IAAI,SAAS,iBAAiB,IAAI,SAAS,CACvE,cAAc,CAAC,IAAI,CAAC,CACrB,CAAA;AA6CD,wBAAgB,eAAe,CAAC,OAAO,SAAS,SAAS,EACvD,OAAO,GAAE,aAAkB,GAC1B,SAAS,CAAC,OAAO,CAAC,CAuBpB;AAED,wBAAgB,YAAY,CAAC,IAAI,SAAS,iBAAiB,EACzD,OAAO,GAAE,aAAkB,GAC1B,iBAAiB,CAAC,IAAI,CAAC,CAEzB"}
@@ -0,0 +1,59 @@
1
+ import { createORPCClient } from '@orpc/client';
2
+ import { RPCLink } from '@orpc/client/fetch';
3
+ export const OPERATION_ID_HEADER = 'x-bunderstack-operation-id';
4
+ function rpcUrl(baseUrl = '/api') {
5
+ return (baseUrl.endsWith('/') ? `${baseUrl}rpc` : `${baseUrl}/rpc`);
6
+ }
7
+ function adaptClient(client) {
8
+ const cache = new Map();
9
+ return new Proxy(function () { }, {
10
+ get(_target, property) {
11
+ if (!cache.has(property)) {
12
+ const value = Reflect.get(client, property);
13
+ cache.set(property, value === null ||
14
+ (typeof value !== 'object' && typeof value !== 'function')
15
+ ? value
16
+ : adaptClient(value));
17
+ }
18
+ return cache.get(property);
19
+ },
20
+ apply(_target, _thisArg, args) {
21
+ const [input, options = {}] = args;
22
+ return Reflect.apply(client, undefined, [
23
+ input,
24
+ {
25
+ signal: options.signal,
26
+ lastEventId: options.lastEventId,
27
+ context: {
28
+ operationId: options.operationId,
29
+ headers: options.headers,
30
+ },
31
+ },
32
+ ]);
33
+ },
34
+ });
35
+ }
36
+ export function createRpcClient(options = {}) {
37
+ const fetcher = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
38
+ const link = new RPCLink({
39
+ url: rpcUrl(options.baseUrl),
40
+ fetch: (url, init) => fetcher(url, init),
41
+ headers: async ({ context }) => {
42
+ const defaults = typeof options.headers === 'function'
43
+ ? await options.headers()
44
+ : options.headers;
45
+ const headers = new Headers(defaults);
46
+ new Headers(context.headers).forEach((value, key) => headers.set(key, value));
47
+ if (context.operationId) {
48
+ headers.set(OPERATION_ID_HEADER, context.operationId);
49
+ }
50
+ return headers;
51
+ },
52
+ });
53
+ const client = createORPCClient(link);
54
+ return adaptClient(client);
55
+ }
56
+ export function createClient(options = {}) {
57
+ return createRpcClient(options);
58
+ }
59
+ //# sourceMappingURL=rpc-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc-client.js","sourceRoot":"","sources":["../../src/client/rpc-client.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAA;AAE5C,MAAM,CAAC,MAAM,mBAAmB,GAAG,4BAA4B,CAAA;AAsD/D,SAAS,MAAM,CAAC,OAAO,GAAG,MAAM;IAC9B,OAAO,CACL,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,KAAK,CAAC,CAAC,CAAC,GAAG,OAAO,MAAM,CAC5C,CAAA;AAClB,CAAC;AAED,SAAS,WAAW,CAAI,MAAS;IAC/B,MAAM,KAAK,GAAG,IAAI,GAAG,EAAwB,CAAA;IAC7C,OAAO,IAAI,KAAK,CAAC,cAAa,CAAoC,EAAE;QAClE,GAAG,CAAC,OAAO,EAAE,QAAQ;YACnB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACzB,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,MAAgB,EAAE,QAAQ,CAAC,CAAA;gBACrD,KAAK,CAAC,GAAG,CACP,QAAQ,EACR,KAAK,KAAK,IAAI;oBACZ,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,UAAU,CAAC;oBAC1D,CAAC,CAAC,KAAK;oBACP,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,CACvB,CAAA;YACH,CAAC;YACD,OAAO,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;QAC5B,CAAC;QACD,KAAK,CAAC,OAAO,EAAE,QAAQ,EAAE,IAA6B;YACpD,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,EAAE,CAAC,GAAG,IAAI,CAAA;YAClC,OAAO,OAAO,CAAC,KAAK,CAClB,MAAyC,EACzC,SAAS,EACT;gBACE,KAAK;gBACL;oBACE,MAAM,EAAE,OAAO,CAAC,MAAM;oBACtB,WAAW,EAAE,OAAO,CAAC,WAAW;oBAChC,OAAO,EAAE;wBACP,WAAW,EAAE,OAAO,CAAC,WAAW;wBAChC,OAAO,EAAE,OAAO,CAAC,OAAO;qBACzB;iBACF;aACF,CACF,CAAA;QACH,CAAC;KACF,CAA+B,CAAA;AAClC,CAAC;AAED,MAAM,UAAU,eAAe,CAC7B,UAAyB,EAAE;IAE3B,MAAM,OAAO,GACX,OAAO,CAAC,KAAK,IAAI,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAA;IACnE,MAAM,IAAI,GAAG,IAAI,OAAO,CAAa;QACnC,GAAG,EAAE,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC;QAC5B,KAAK,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC;QACxC,OAAO,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;YAC7B,MAAM,QAAQ,GACZ,OAAO,OAAO,CAAC,OAAO,KAAK,UAAU;gBACnC,CAAC,CAAC,MAAM,OAAO,CAAC,OAAO,EAAE;gBACzB,CAAC,CAAC,OAAO,CAAC,OAAO,CAAA;YACrB,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,QAAQ,CAAC,CAAA;YACrC,IAAI,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,CAClD,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CACxB,CAAA;YACD,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;gBACxB,OAAO,CAAC,GAAG,CAAC,mBAAmB,EAAE,OAAO,CAAC,WAAW,CAAC,CAAA;YACvD,CAAC;YACD,OAAO,OAAO,CAAA;QAChB,CAAC;KACF,CAAC,CAAA;IACF,MAAM,MAAM,GAAG,gBAAgB,CAAoC,IAAI,CAAC,CAAA;IACxE,OAAO,WAAW,CAAC,MAAM,CAAC,CAAA;AAC5B,CAAC;AAED,MAAM,UAAU,YAAY,CAC1B,UAAyB,EAAE;IAE3B,OAAO,eAAe,CAAuB,OAAO,CAAC,CAAA;AACvD,CAAC","sourcesContent":["import type { StandardUrl } from '@orpc/client/standard'\nimport type { AnyRouter, RouterClient } from '@orpc/server'\n\nimport { createORPCClient } from '@orpc/client'\nimport { RPCLink } from '@orpc/client/fetch'\n\nexport const OPERATION_ID_HEADER = 'x-bunderstack-operation-id'\n\nexport type Fetch = (\n input: RequestInfo | URL,\n init?: RequestInit,\n) => Promise<Response>\n\nexport type ClientOptions = {\n baseUrl?: string\n fetch?: Fetch\n headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>)\n}\n\nexport type CallOptions = {\n signal?: AbortSignal\n lastEventId?: string\n operationId?: string\n headers?: HeadersInit\n}\n\nexport type ClientCarrier = {\n api: AnyRouter\n buckets: string\n}\n\nexport type AnyBunderstackApp = {\n $inferClient?: ClientCarrier | undefined\n}\n\nexport type InferApiRouter<TApp extends AnyBunderstackApp> = NonNullable<\n TApp['$inferClient']\n>['api']\n\ntype RpcContext = {\n operationId?: string\n headers?: HeadersInit\n}\n\ntype AdaptCallArgs<TArgs extends unknown[]> = {\n [K in keyof TArgs]: K extends 1 | '1' ? CallOptions : TArgs[K]\n}\n\nexport type DirectClient<T> = T extends (...args: infer TArgs) => infer TResult\n ? (...args: AdaptCallArgs<TArgs>) => TResult\n : { [K in keyof T]: DirectClient<T[K]> }\n\nexport type RpcClient<TRouter extends AnyRouter> = DirectClient<\n RouterClient<TRouter, RpcContext>\n>\n\nexport type BunderstackClient<TApp extends AnyBunderstackApp> = RpcClient<\n InferApiRouter<TApp>\n>\n\nfunction rpcUrl(baseUrl = '/api'): StandardUrl {\n return (\n baseUrl.endsWith('/') ? `${baseUrl}rpc` : `${baseUrl}/rpc`\n ) as StandardUrl\n}\n\nfunction adaptClient<T>(client: T): DirectClient<T> {\n const cache = new Map<PropertyKey, unknown>()\n return new Proxy(function () {} as (...args: unknown[]) => unknown, {\n get(_target, property) {\n if (!cache.has(property)) {\n const value = Reflect.get(client as object, property)\n cache.set(\n property,\n value === null ||\n (typeof value !== 'object' && typeof value !== 'function')\n ? value\n : adaptClient(value),\n )\n }\n return cache.get(property)\n },\n apply(_target, _thisArg, args: [unknown, CallOptions?]) {\n const [input, options = {}] = args\n return Reflect.apply(\n client as (...args: unknown[]) => unknown,\n undefined,\n [\n input,\n {\n signal: options.signal,\n lastEventId: options.lastEventId,\n context: {\n operationId: options.operationId,\n headers: options.headers,\n },\n },\n ],\n )\n },\n }) as unknown as DirectClient<T>\n}\n\nexport function createRpcClient<TRouter extends AnyRouter>(\n options: ClientOptions = {},\n): RpcClient<TRouter> {\n const fetcher =\n options.fetch ?? ((input, init) => globalThis.fetch(input, init))\n const link = new RPCLink<RpcContext>({\n url: rpcUrl(options.baseUrl),\n fetch: (url, init) => fetcher(url, init),\n headers: async ({ context }) => {\n const defaults =\n typeof options.headers === 'function'\n ? await options.headers()\n : options.headers\n const headers = new Headers(defaults)\n new Headers(context.headers).forEach((value, key) =>\n headers.set(key, value),\n )\n if (context.operationId) {\n headers.set(OPERATION_ID_HEADER, context.operationId)\n }\n return headers\n },\n })\n const client = createORPCClient<RouterClient<TRouter, RpcContext>>(link)\n return adaptClient(client)\n}\n\nexport function createClient<TApp extends AnyBunderstackApp>(\n options: ClientOptions = {},\n): BunderstackClient<TApp> {\n return createRpcClient<InferApiRouter<TApp>>(options)\n}\n"]}
@@ -0,0 +1,7 @@
1
+ import { type Store } from 'solid-js';
2
+ import type { LiveView, LiveViewSnapshot } from './live-view.js';
3
+ /** Mirrors confirmed LiveView snapshots into a keyed Solid store. */
4
+ export declare function createLiveStore<T extends {
5
+ id: unknown;
6
+ }>(view: LiveView<T>): Store<LiveViewSnapshot<T>>;
7
+ //# sourceMappingURL=solid.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"solid.d.ts","sourceRoot":"","sources":["../../src/client/solid.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqC,KAAK,KAAK,EAAE,MAAM,UAAU,CAAA;AAExE,OAAO,KAAK,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAE7D,qEAAqE;AACrE,wBAAgB,eAAe,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,OAAO,CAAA;CAAE,EACvD,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,GAChB,KAAK,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAiB5B"}
@@ -0,0 +1,21 @@
1
+ import { createStore, onCleanup, reconcile } from 'solid-js';
2
+ /** Mirrors confirmed LiveView snapshots into a keyed Solid store. */
3
+ export function createLiveStore(view) {
4
+ const initial = view.getSnapshot();
5
+ const [state, setState] = createStore({
6
+ items: [...initial.items],
7
+ status: initial.status,
8
+ error: initial.error,
9
+ });
10
+ const unsubscribe = view.subscribe(() => {
11
+ const next = view.getSnapshot();
12
+ setState((draft) => {
13
+ reconcile([...next.items], (item) => item.id)(draft.items);
14
+ draft.status = next.status;
15
+ draft.error = next.error;
16
+ });
17
+ });
18
+ onCleanup(unsubscribe);
19
+ return state;
20
+ }
21
+ //# sourceMappingURL=solid.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"solid.js","sourceRoot":"","sources":["../../src/client/solid.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,SAAS,EAAc,MAAM,UAAU,CAAA;AAIxE,qEAAqE;AACrE,MAAM,UAAU,eAAe,CAC7B,IAAiB;IAEjB,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,EAAE,CAAA;IAClC,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,WAAW,CAAC;QACpC,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC;QACzB,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,KAAK,EAAE,OAAO,CAAC,KAAK;KACrB,CAAC,CAAA;IACF,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE;QACtC,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,EAAE,CAAA;QAC/B,QAAQ,CAAC,CAAC,KAAK,EAAE,EAAE;YACjB,SAAS,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,IAAO,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;YAC7D,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAA;YAC1B,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAA;QAC1B,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IACF,SAAS,CAAC,WAAW,CAAC,CAAA;IACtB,OAAO,KAAmC,CAAA;AAC5C,CAAC","sourcesContent":["import { createStore, onCleanup, reconcile, type Store } from 'solid-js'\n\nimport type { LiveView, LiveViewSnapshot } from './live-view'\n\n/** Mirrors confirmed LiveView snapshots into a keyed Solid store. */\nexport function createLiveStore<T extends { id: unknown }>(\n view: LiveView<T>,\n): Store<LiveViewSnapshot<T>> {\n const initial = view.getSnapshot()\n const [state, setState] = createStore({\n items: [...initial.items],\n status: initial.status,\n error: initial.error,\n })\n const unsubscribe = view.subscribe(() => {\n const next = view.getSnapshot()\n setState((draft) => {\n reconcile([...next.items], (item: T) => item.id)(draft.items)\n draft.status = next.status\n draft.error = next.error\n })\n })\n onCleanup(unsubscribe)\n return state as Store<LiveViewSnapshot<T>>\n}\n"]}
@@ -0,0 +1,6 @@
1
+ import type { LiveView, LiveViewSnapshot } from './live-view.js';
2
+ export type Readable<T> = {
3
+ subscribe(run: (value: T) => void): () => void;
4
+ };
5
+ export declare function liveStore<T>(view: LiveView<T>): Readable<LiveViewSnapshot<T>>;
6
+ //# sourceMappingURL=svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"svelte.d.ts","sourceRoot":"","sources":["../../src/client/svelte.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAE7D,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI;IACxB,SAAS,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,GAAG,MAAM,IAAI,CAAA;CAC/C,CAAA;AAED,wBAAgB,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAO7E"}
@@ -0,0 +1,9 @@
1
+ export function liveStore(view) {
2
+ return {
3
+ subscribe(run) {
4
+ run(view.getSnapshot());
5
+ return view.subscribe(() => run(view.getSnapshot()));
6
+ },
7
+ };
8
+ }
9
+ //# sourceMappingURL=svelte.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"svelte.js","sourceRoot":"","sources":["../../src/client/svelte.ts"],"names":[],"mappings":"AAMA,MAAM,UAAU,SAAS,CAAI,IAAiB;IAC5C,OAAO;QACL,SAAS,CAAC,GAAG;YACX,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAA;YACvB,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAA;QACtD,CAAC;KACF,CAAA;AACH,CAAC","sourcesContent":["import type { LiveView, LiveViewSnapshot } from './live-view'\n\nexport type Readable<T> = {\n subscribe(run: (value: T) => void): () => void\n}\n\nexport function liveStore<T>(view: LiveView<T>): Readable<LiveViewSnapshot<T>> {\n return {\n subscribe(run) {\n run(view.getSnapshot())\n return view.subscribe(() => run(view.getSnapshot()))\n },\n }\n}\n"]}
@@ -0,0 +1,5 @@
1
+ import { type ShallowRef } from 'vue';
2
+ import type { LiveView, LiveViewSnapshot } from './live-view.js';
3
+ /** Vue owns reactivity; LiveView supplies immutable external snapshots. */
4
+ export declare function useLiveView<T>(view: LiveView<T>): ShallowRef<LiveViewSnapshot<T>>;
5
+ //# sourceMappingURL=vue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vue.d.ts","sourceRoot":"","sources":["../../src/client/vue.ts"],"names":[],"mappings":"AAAA,OAAO,EAA8B,KAAK,UAAU,EAAE,MAAM,KAAK,CAAA;AAEjE,OAAO,KAAK,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAE7D,2EAA2E;AAC3E,wBAAgB,WAAW,CAAC,CAAC,EAC3B,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,GAChB,UAAU,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CASjC"}
@@ -0,0 +1,11 @@
1
+ import { onScopeDispose, shallowRef } from 'vue';
2
+ /** Vue owns reactivity; LiveView supplies immutable external snapshots. */
3
+ export function useLiveView(view) {
4
+ const state = shallowRef(view.getSnapshot());
5
+ const unsubscribe = view.subscribe(() => {
6
+ state.value = view.getSnapshot();
7
+ });
8
+ onScopeDispose(unsubscribe);
9
+ return state;
10
+ }
11
+ //# sourceMappingURL=vue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vue.js","sourceRoot":"","sources":["../../src/client/vue.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,UAAU,EAAmB,MAAM,KAAK,CAAA;AAIjE,2EAA2E;AAC3E,MAAM,UAAU,WAAW,CACzB,IAAiB;IAEjB,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,WAAW,EAAE,CAE1C,CAAA;IACD,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE;QACtC,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,CAAA;IAClC,CAAC,CAAC,CAAA;IACF,cAAc,CAAC,WAAW,CAAC,CAAA;IAC3B,OAAO,KAAK,CAAA;AACd,CAAC","sourcesContent":["import { onScopeDispose, shallowRef, type ShallowRef } from 'vue'\n\nimport type { LiveView, LiveViewSnapshot } from './live-view'\n\n/** Vue owns reactivity; LiveView supplies immutable external snapshots. */\nexport function useLiveView<T>(\n view: LiveView<T>,\n): ShallowRef<LiveViewSnapshot<T>> {\n const state = shallowRef(view.getSnapshot()) as ShallowRef<\n LiveViewSnapshot<T>\n >\n const unsubscribe = view.subscribe(() => {\n state.value = view.getSnapshot()\n })\n onScopeDispose(unsubscribe)\n return state\n}\n"]}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Typed route-map generation from the app's OpenAPI document.
3
+ *
4
+ * `generateRouteMap` turns `/api/openapi.json` into a small standalone module:
5
+ * an `ApiRoutes` type — one `Op` entry per operation, carrying its method,
6
+ * path literal, query/body/output types and whether it streams — plus a
7
+ * runtime `routes` descriptor object for that same list.
8
+ *
9
+ * The transport itself is deliberately NOT generated: a ~90-line handwritten
10
+ * client (`createRestClient`) consumes any route map. Types change with every API
11
+ * edit and are regenerated; the client never changes at all.
12
+ */
13
+ type JsonSchema = {
14
+ $ref?: string;
15
+ type?: string | string[];
16
+ anyOf?: JsonSchema[];
17
+ oneOf?: JsonSchema[];
18
+ allOf?: JsonSchema[];
19
+ const?: unknown;
20
+ enum?: unknown[];
21
+ items?: JsonSchema;
22
+ properties?: Record<string, JsonSchema>;
23
+ required?: string[];
24
+ additionalProperties?: boolean | JsonSchema;
25
+ };
26
+ /** JSON-schema subset → TypeScript type expression, with $refs inlined. */
27
+ export declare function schemaToType(schema: JsonSchema | undefined, components?: Record<string, JsonSchema>, depth?: number): string;
28
+ /**
29
+ * Deterministic operation names from the path shape:
30
+ * `/api/posts` GET → `postsList`; `/api/posts/{id}` PATCH → `postsUpdate`;
31
+ * `/api/posts/live` GET → `postsLive`; `/api/enrich` POST → `enrichCreate`.
32
+ */
33
+ export declare function operationName(path: string, method: string): string;
34
+ export declare function generateRouteMap(input: unknown): string;
35
+ export {};
36
+ //# sourceMappingURL=codegen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codegen.d.ts","sourceRoot":"","sources":["../src/codegen.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,KAAK,UAAU,GAAG;IAChB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IACxB,KAAK,CAAC,EAAE,UAAU,EAAE,CAAA;IACpB,KAAK,CAAC,EAAE,UAAU,EAAE,CAAA;IACpB,KAAK,CAAC,EAAE,UAAU,EAAE,CAAA;IACpB,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,IAAI,CAAC,EAAE,OAAO,EAAE,CAAA;IAChB,KAAK,CAAC,EAAE,UAAU,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;IACvC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;IACnB,oBAAoB,CAAC,EAAE,OAAO,GAAG,UAAU,CAAA;CAC5C,CAAA;AAoCD,2EAA2E;AAC3E,wBAAgB,YAAY,CAC1B,MAAM,EAAE,UAAU,GAAG,SAAS,EAC9B,UAAU,GAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAM,EAC3C,KAAK,SAAI,GACR,MAAM,CA+DR;AAUD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAgBlE;AA+FD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAwDvD"}
@@ -0,0 +1,235 @@
1
+ /**
2
+ * Typed route-map generation from the app's OpenAPI document.
3
+ *
4
+ * `generateRouteMap` turns `/api/openapi.json` into a small standalone module:
5
+ * an `ApiRoutes` type — one `Op` entry per operation, carrying its method,
6
+ * path literal, query/body/output types and whether it streams — plus a
7
+ * runtime `routes` descriptor object for that same list.
8
+ *
9
+ * The transport itself is deliberately NOT generated: a ~90-line handwritten
10
+ * client (`createRestClient`) consumes any route map. Types change with every API
11
+ * edit and are regenerated; the client never changes at all.
12
+ */
13
+ function pascal(value) {
14
+ return value
15
+ .split(/[^a-zA-Z0-9]+/)
16
+ .filter(Boolean)
17
+ .map((part) => part[0].toUpperCase() + part.slice(1))
18
+ .join('');
19
+ }
20
+ function camel(value) {
21
+ const asPascal = pascal(value);
22
+ return asPascal ? asPascal[0].toLowerCase() + asPascal.slice(1) : 'root';
23
+ }
24
+ /** JSON-schema subset → TypeScript type expression, with $refs inlined. */
25
+ export function schemaToType(schema, components = {}, depth = 0) {
26
+ if (!schema || depth > 4)
27
+ return 'unknown';
28
+ if (schema.$ref) {
29
+ const name = schema.$ref.split('/').pop() ?? '';
30
+ return schemaToType(components[name], components, depth + 1);
31
+ }
32
+ if (schema.const !== undefined)
33
+ return JSON.stringify(schema.const);
34
+ if (schema.enum) {
35
+ return [...new Set(schema.enum.map((value) => JSON.stringify(value)))].join(' | ');
36
+ }
37
+ const variants = schema.anyOf ?? schema.oneOf;
38
+ if (variants) {
39
+ const members = [
40
+ ...new Set(variants.map((member) => schemaToType(member, components, depth))),
41
+ ];
42
+ const withoutNull = members.filter((member) => member !== 'null');
43
+ const joined = withoutNull.join(' | ');
44
+ const core = withoutNull.length > 1 ? `(${joined})` : joined || 'unknown';
45
+ return members.length !== withoutNull.length ? `${core} | null` : core;
46
+ }
47
+ if (schema.allOf) {
48
+ return schema.allOf
49
+ .map((member) => schemaToType(member, components, depth))
50
+ .join(' & ');
51
+ }
52
+ if (Array.isArray(schema.type)) {
53
+ return schema.type
54
+ .map((entry) => (entry === 'integer' ? 'number' : entry))
55
+ .join(' | ');
56
+ }
57
+ switch (schema.type) {
58
+ case 'string':
59
+ return 'string';
60
+ case 'integer':
61
+ case 'number':
62
+ return 'number';
63
+ case 'boolean':
64
+ return 'boolean';
65
+ case 'null':
66
+ return 'null';
67
+ case 'array':
68
+ return `${schemaToType(schema.items, components, depth)}[]`;
69
+ case 'object': {
70
+ const properties = schema.properties ?? {};
71
+ const required = new Set(schema.required ?? []);
72
+ if (Object.keys(properties).length === 0) {
73
+ return schema.additionalProperties === false
74
+ ? 'Record<string, never>'
75
+ : 'Record<string, unknown>';
76
+ }
77
+ const fields = Object.entries(properties).map(([key, child]) => {
78
+ const optional = required.has(key) ? '' : '?';
79
+ return `${JSON.stringify(key)}${optional}: ${schemaToType(child, components, depth)}`;
80
+ });
81
+ return `{ ${fields.join('; ')} }`;
82
+ }
83
+ default:
84
+ return 'unknown';
85
+ }
86
+ }
87
+ const METHOD_ACTIONS = {
88
+ get: 'Get',
89
+ post: 'Create',
90
+ patch: 'Update',
91
+ put: 'Update',
92
+ delete: 'Delete',
93
+ };
94
+ /**
95
+ * Deterministic operation names from the path shape:
96
+ * `/api/posts` GET → `postsList`; `/api/posts/{id}` PATCH → `postsUpdate`;
97
+ * `/api/posts/live` GET → `postsLive`; `/api/enrich` POST → `enrichCreate`.
98
+ */
99
+ export function operationName(path, method) {
100
+ const segments = path.split('/').filter(Boolean);
101
+ if (segments[0] === 'api')
102
+ segments.shift();
103
+ const last = segments[segments.length - 1] ?? '';
104
+ const action = METHOD_ACTIONS[method] ?? pascal(method);
105
+ if (last.startsWith('{')) {
106
+ return `${camel(segments.slice(0, -1).join('_'))}${action}`;
107
+ }
108
+ const statics = segments.filter((segment) => !segment.startsWith('{'));
109
+ if (statics.length === 1 && method === 'get')
110
+ return `${camel(last)}List`;
111
+ if (statics.length === 1 && method === 'post')
112
+ return `${camel(last)}Create`;
113
+ // A deeper static path reads as its own name (`/api/posts/live`) — the
114
+ // method suffix would only add noise.
115
+ if (method === 'get')
116
+ return camel(statics.join('_'));
117
+ return `${camel(statics.join('_'))}${action}`;
118
+ }
119
+ const HTTP_METHODS = ['get', 'post', 'patch', 'put', 'delete'];
120
+ function collectOperations(spec) {
121
+ const components = spec.components?.schemas ?? {};
122
+ const operations = [];
123
+ for (const [path, methods] of Object.entries(spec.paths ?? {})) {
124
+ // Better Auth owns its client contract. Keeping auth endpoints out of the
125
+ // generic REST artifact avoids a second, less capable auth API surface.
126
+ if (path === '/api/auth' || path.startsWith('/api/auth/'))
127
+ continue;
128
+ for (const rawMethod of Object.keys(methods)) {
129
+ if (!HTTP_METHODS.includes(rawMethod))
130
+ continue;
131
+ const operation = methods[rawMethod];
132
+ if (!operation)
133
+ continue;
134
+ const parameters = operation.parameters ?? [];
135
+ const queryParameters = parameters.filter((parameter) => parameter.in === 'query');
136
+ let queryType;
137
+ if (queryParameters.length > 0) {
138
+ const fields = queryParameters.map((parameter) => {
139
+ const schema = parameter.schema ?? parameter.content?.['application/json']?.schema;
140
+ return `${JSON.stringify(parameter.name)}?: ${schemaToType(schema, components)}`;
141
+ });
142
+ queryType = `{ ${fields.join('; ')} }`;
143
+ }
144
+ const requestSchema = operation.requestBody?.content?.['application/json']?.schema;
145
+ const rawBodyType = requestSchema
146
+ ? schemaToType(requestSchema, components)
147
+ : undefined;
148
+ // oRPC emits an empty-object request body even for body-less
149
+ // procedures like DELETE; a free-form record there is noise.
150
+ const bodyType = rawBodyType === 'Record<string, unknown>' && rawMethod === 'delete'
151
+ ? undefined
152
+ : rawBodyType;
153
+ const okResponse = operation.responses?.['200'] ?? operation.responses?.['201'];
154
+ const jsonSchema = okResponse?.content?.['application/json']?.schema;
155
+ const eventSchema = okResponse?.content?.['text/event-stream']?.schema;
156
+ let outputType = 'void';
157
+ let isStream = false;
158
+ if (eventSchema) {
159
+ isStream = true;
160
+ // oRPC wraps each SSE data frame as {event, data}; unwrap the payload.
161
+ // Wrapper variants that carry no data (or map to `unknown`) would
162
+ // swallow the whole union, so they are dropped here.
163
+ const wrapper = eventSchema.oneOf ?? eventSchema.anyOf ?? [];
164
+ outputType =
165
+ [
166
+ ...new Set(wrapper
167
+ .map((variant) => variant.properties?.['data'])
168
+ .filter((variant) => Boolean(variant))
169
+ .map((variant) => schemaToType(variant, components))
170
+ .filter((type) => type !== 'unknown')),
171
+ ].join(' | ') || 'void';
172
+ }
173
+ else if (jsonSchema) {
174
+ outputType = schemaToType(jsonSchema, components);
175
+ }
176
+ operations.push({
177
+ name: operationName(path, rawMethod),
178
+ method: rawMethod.toUpperCase(),
179
+ path,
180
+ queryType,
181
+ bodyType,
182
+ outputType,
183
+ isStream,
184
+ });
185
+ }
186
+ }
187
+ return operations;
188
+ }
189
+ export function generateRouteMap(input) {
190
+ const spec = input;
191
+ const operations = collectOperations(spec);
192
+ const lines = [];
193
+ lines.push('// Generated by bunderstack codegen from /api/openapi.json.');
194
+ lines.push('// Regenerate with `bun run gen`; do not edit by hand.');
195
+ lines.push('');
196
+ lines.push('/** One API operation: literals describe the wire, phantoms the shapes. */');
197
+ lines.push('export interface Op<');
198
+ lines.push(' M extends string,');
199
+ lines.push(' P extends string,');
200
+ lines.push(' Query = undefined,');
201
+ lines.push(' Body = undefined,');
202
+ lines.push(' Output = void,');
203
+ lines.push(' Stream extends boolean = false,');
204
+ lines.push('> {');
205
+ lines.push(' method: M');
206
+ lines.push(' path: P');
207
+ lines.push(' stream: Stream');
208
+ lines.push(' query?: Query');
209
+ lines.push(' body?: Body');
210
+ lines.push(' output?: Output');
211
+ lines.push('}');
212
+ lines.push('');
213
+ lines.push('export type ApiRoutes = {');
214
+ const runtimeEntries = [];
215
+ for (const operation of operations) {
216
+ const generics = [
217
+ `'${operation.method}'`,
218
+ `'${operation.path}'`,
219
+ operation.queryType ?? 'undefined',
220
+ operation.bodyType ?? 'undefined',
221
+ operation.isStream ? operation.outputType : operation.outputType,
222
+ String(operation.isStream),
223
+ ].join(', ');
224
+ lines.push(` ${operation.name}: Op<${generics}>`);
225
+ runtimeEntries.push(` ${operation.name}: { method: '${operation.method}', path: '${operation.path}'${operation.isStream ? ', stream: true' : ''} },`);
226
+ }
227
+ lines.push('}');
228
+ lines.push('');
229
+ lines.push('/** Wire descriptors for createRestClient — same keys, runtime values. */');
230
+ lines.push('export const routes = {');
231
+ lines.push(...runtimeEntries);
232
+ lines.push('} as const');
233
+ return lines.join('\n');
234
+ }
235
+ //# sourceMappingURL=codegen.js.map