bunderstack 0.19.2 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/dist/api/crud-router.d.ts +19 -24
- package/dist/api/crud-router.d.ts.map +1 -1
- package/dist/api/crud-router.js +39 -18
- package/dist/api/crud-router.js.map +1 -1
- package/dist/api/live-window.d.ts.map +1 -1
- package/dist/api/live-window.js +13 -3
- package/dist/api/live-window.js.map +1 -1
- package/dist/api/realtime-router.d.ts +2 -0
- package/dist/api/realtime-router.d.ts.map +1 -1
- package/dist/api/realtime-router.js +1 -0
- package/dist/api/realtime-router.js.map +1 -1
- package/dist/api/types.d.ts +2 -2
- package/dist/api/types.d.ts.map +1 -1
- package/dist/api/types.js.map +1 -1
- package/dist/codegen.d.ts +36 -0
- package/dist/codegen.d.ts.map +1 -0
- package/dist/codegen.js +235 -0
- package/dist/codegen.js.map +1 -0
- package/dist/crud-operations.d.ts.map +1 -1
- package/dist/crud-operations.js +7 -3
- package/dist/crud-operations.js.map +1 -1
- package/dist/index.d.ts +9 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/live/protocol.d.ts +3 -0
- package/dist/live/protocol.d.ts.map +1 -1
- package/dist/live/protocol.js.map +1 -1
- package/dist/realtime/facade.d.ts +3 -1
- package/dist/realtime/facade.d.ts.map +1 -1
- package/dist/realtime/facade.js +2 -1
- package/dist/realtime/facade.js.map +1 -1
- package/dist/realtime/filter.d.ts.map +1 -1
- package/dist/realtime/filter.js +1 -0
- package/dist/realtime/filter.js.map +1 -1
- package/dist/realtime/publisher.d.ts +2 -0
- package/dist/realtime/publisher.d.ts.map +1 -1
- package/dist/realtime/publisher.js.map +1 -1
- package/package.json +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,28 @@ All notable changes to `bunderstack` will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.20.0] - 2026-08-25
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **Framework-neutral typed client.** The new `bunderstack-client` package
|
|
12
|
+
exposes `createClient<App>()`, confirmed realtime `LiveView`, and thin Solid,
|
|
13
|
+
React, Vue, and Svelte adapters. TanStack Query and TanStack DB integrations
|
|
14
|
+
reuse the same transport and realtime primitives.
|
|
15
|
+
- **Confirmed mutation correlation.** CRUD writes propagate an opaque
|
|
16
|
+
`operationId` into live-view frames. Reconnect snapshots settle successful
|
|
17
|
+
writes whose delta was lost, and a heartbeat watchdog reconnects silent
|
|
18
|
+
streams.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- **Direct oRPC CRUD inputs.** Update procedures now accept
|
|
23
|
+
`{ id, ...changes }`; REST keeps `PATCH /api/{table}/{id}` and rejects an
|
|
24
|
+
immutable `id` in the body. Live frames preserve the table row type.
|
|
25
|
+
- **Optional REST artifact.** OpenAPI route-map generation is now the fallback
|
|
26
|
+
for separate frontend repositories and deliberately excludes Better Auth
|
|
27
|
+
routes, which continue to use the official Better Auth client.
|
|
28
|
+
|
|
7
29
|
## [0.19.2] - 2026-08-24
|
|
8
30
|
|
|
9
31
|
### Added
|
|
@@ -20,7 +20,6 @@ export type CrudApiRouterOptions<TSchema extends Record<string, unknown> = Recor
|
|
|
20
20
|
*/
|
|
21
21
|
livePublisher?: RealtimePublisher;
|
|
22
22
|
};
|
|
23
|
-
type CrudUpdate<TTable extends Table> = Partial<Omit<TTable['$inferInsert'], 'id'>>;
|
|
24
23
|
/** A filter accepts one value (`=`), a list (`IN`), or null (`IS NULL`). */
|
|
25
24
|
export type ListFilterValue<T> = T | readonly T[] | null | 'null';
|
|
26
25
|
export type ListFilters<TTable extends Table, TFilterable extends string> = {
|
|
@@ -249,14 +248,11 @@ export declare function buildTableCrudProcedures<TSchema extends Record<string,
|
|
|
249
248
|
}>;
|
|
250
249
|
};
|
|
251
250
|
}, Extract<TTable["$inferSelect"], import("@orpc/server").AnyORPCError>>;
|
|
252
|
-
update: import("@orpc/server").DecoratedProcedure<import("./context.js").ApiContext<TSchema, Record<string, unknown>> & object, object, v.
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
readonly headers: v.OptionalSchema<v.RecordSchema<v.StringSchema<undefined>, v.UnknownSchema, undefined>, {}>;
|
|
258
|
-
readonly body: v.GenericSchema<CrudUpdate<TTable>, CrudUpdate<TTable>>;
|
|
259
|
-
}, undefined>, v.GenericSchema<TTable["$inferSelect"], TTable["$inferSelect"]>, {
|
|
251
|
+
update: import("@orpc/server").DecoratedProcedure<import("./context.js").ApiContext<TSchema, Record<string, unknown>> & object, object, v.GenericSchema<{
|
|
252
|
+
id: string;
|
|
253
|
+
} & Partial<Omit<TTable["$inferInsert"], "id">>, {
|
|
254
|
+
id: string;
|
|
255
|
+
} & Partial<Omit<TTable["$inferInsert"], "id">>>, v.GenericSchema<TTable["$inferSelect"], TTable["$inferSelect"]>, {
|
|
260
256
|
readonly BAD_REQUEST: {
|
|
261
257
|
readonly data: v.OptionalSchema<v.StrictObjectSchema<{
|
|
262
258
|
readonly code: v.OptionalSchema<v.LiteralSchema<"BAD_REQUEST", undefined>, "BAD_REQUEST">;
|
|
@@ -376,43 +372,41 @@ export declare function buildTableCrudProcedures<TSchema extends Record<string,
|
|
|
376
372
|
}, never>;
|
|
377
373
|
live: import("@orpc/server").DecoratedProcedure<import("./context.js").ApiContext<TSchema, Record<string, unknown>> & object, object, v.GenericSchema<LiveInputFor<TTable, TFilterable, TSortable>, LiveInputFor<TTable, TFilterable, TSortable>>, import("@orpc/contract").Schema<AsyncIteratorObject<{
|
|
378
374
|
type: "snapshot";
|
|
379
|
-
items:
|
|
380
|
-
[x: string]: unknown;
|
|
381
|
-
}[];
|
|
375
|
+
items: TTable["$inferSelect"][];
|
|
382
376
|
sort: string;
|
|
383
377
|
order: "asc" | "desc";
|
|
384
378
|
limit: number;
|
|
385
379
|
hasMore: boolean;
|
|
380
|
+
operationId?: string | undefined;
|
|
386
381
|
} | {
|
|
387
382
|
type: "upsert";
|
|
388
|
-
record:
|
|
389
|
-
[x: string]: unknown;
|
|
390
|
-
};
|
|
383
|
+
record: TTable["$inferSelect"];
|
|
391
384
|
afterId: string | null;
|
|
385
|
+
operationId?: string | undefined;
|
|
392
386
|
} | {
|
|
393
387
|
type: "remove";
|
|
394
388
|
id: string;
|
|
389
|
+
operationId?: string | undefined;
|
|
395
390
|
} | {
|
|
396
391
|
type: "heartbeat";
|
|
397
392
|
intervalMs: number;
|
|
398
393
|
}, unknown, void>, import("@orpc/server").AsyncIteratorClass<{
|
|
399
394
|
type: "snapshot";
|
|
400
|
-
items:
|
|
401
|
-
[x: string]: unknown;
|
|
402
|
-
}[];
|
|
395
|
+
items: TTable["$inferSelect"][];
|
|
403
396
|
sort: string;
|
|
404
397
|
order: "asc" | "desc";
|
|
405
398
|
limit: number;
|
|
406
399
|
hasMore: boolean;
|
|
400
|
+
operationId?: string | undefined;
|
|
407
401
|
} | {
|
|
408
402
|
type: "upsert";
|
|
409
|
-
record:
|
|
410
|
-
[x: string]: unknown;
|
|
411
|
-
};
|
|
403
|
+
record: TTable["$inferSelect"];
|
|
412
404
|
afterId: string | null;
|
|
405
|
+
operationId?: string | undefined;
|
|
413
406
|
} | {
|
|
414
407
|
type: "remove";
|
|
415
408
|
id: string;
|
|
409
|
+
operationId?: string | undefined;
|
|
416
410
|
} | {
|
|
417
411
|
type: "heartbeat";
|
|
418
412
|
intervalMs: number;
|
|
@@ -475,7 +469,8 @@ export declare function buildTableCrudProcedures<TSchema extends Record<string,
|
|
|
475
469
|
};
|
|
476
470
|
}, never>;
|
|
477
471
|
};
|
|
478
|
-
export type TableCrudProcedures<TTable extends Table, TFilterable extends string = string, TSortable extends string = string> = ReturnType<typeof buildTableCrudProcedures<Record<string, unknown>, TTable, TFilterable, TSortable
|
|
479
|
-
|
|
480
|
-
|
|
472
|
+
export type TableCrudProcedures<TTable extends Table, TFilterable extends string = string, TSortable extends string = string, TLive extends boolean = false> = Omit<ReturnType<typeof buildTableCrudProcedures<Record<string, unknown>, TTable, TFilterable, TSortable>>, 'live'> & (TLive extends true ? {
|
|
473
|
+
live: ReturnType<typeof buildTableCrudProcedures<Record<string, unknown>, TTable, TFilterable, TSortable>>['live'];
|
|
474
|
+
} : {});
|
|
475
|
+
export declare function buildCrudApiRouter<TSchema extends Record<string, unknown>, TAccess extends Record<string, TableAccessInput> | undefined = undefined, TLive extends boolean = false>(schema: TSchema, db: AnyDb, options: CrudApiRouterOptions<TSchema>): CrudApiRouterFor<TSchema, TAccess, TLive>;
|
|
481
476
|
//# sourceMappingURL=crud-router.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crud-router.d.ts","sourceRoot":"","sources":["../../src/api/crud-router.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"crud-router.d.ts","sourceRoot":"","sources":["../../src/api/crud-router.ts"],"names":[],"mappings":"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"}
|
package/dist/api/crud-router.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
+
import { eventIterator } from '@orpc/server';
|
|
1
2
|
import { getTableColumns, getTableName, isTable } from 'drizzle-orm';
|
|
2
|
-
import { createInsertSchema, createSelectSchema, createUpdateSchema, } from 'drizzle-valibot';
|
|
3
3
|
import '@orpc/openapi/extensions/route';
|
|
4
|
-
import {
|
|
4
|
+
import { createInsertSchema, createSelectSchema, createUpdateSchema, } from 'drizzle-valibot';
|
|
5
5
|
import * as v from 'valibot';
|
|
6
6
|
import { tableEntryForName, } from '../access.js';
|
|
7
7
|
import { createCrudOperations } from '../crud-operations.js';
|
|
8
|
+
import { BunderstackError } from '../errors.js';
|
|
8
9
|
import { filterTableChanges } from '../realtime/filter.js';
|
|
9
10
|
import { REALTIME_HEARTBEAT_INTERVAL_MS, withRealtimeHeartbeat, } from '../realtime/heartbeat.js';
|
|
10
11
|
import { createApiBuilder } from './builder.js';
|
|
11
|
-
import { createLiveWindow } from './live-window.js';
|
|
12
12
|
import { buildListInputSchema, buildLiveInputSchema } from './list-input-schema.js';
|
|
13
|
+
import { createLiveWindow } from './live-window.js';
|
|
13
14
|
function strictObject(schema) {
|
|
14
15
|
return v.strictObject(schema.entries);
|
|
15
16
|
}
|
|
@@ -47,13 +48,12 @@ export function buildTableCrudProcedures(args) {
|
|
|
47
48
|
const updateBodySchema = v.omit(generatedUpdateSchema, [
|
|
48
49
|
'id',
|
|
49
50
|
]);
|
|
51
|
+
// Compact input is transport-neutral: direct oRPC callers pass
|
|
52
|
+
// `{ id, ...changes }`, while OpenAPI still maps `id` to the path and the
|
|
53
|
+
// remaining fields to the PATCH body.
|
|
50
54
|
const updateInputSchema = v.strictObject({
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
headers: v.optional(v.record(v.string(), v.unknown()), {}),
|
|
54
|
-
// Same reason as `selectSchema`: state the type instead of letting
|
|
55
|
-
// declaration emit inline drizzle-valibot's generics.
|
|
56
|
-
body: strictObject(updateBodySchema),
|
|
55
|
+
id: v.string(),
|
|
56
|
+
...updateBodySchema.entries,
|
|
57
57
|
});
|
|
58
58
|
// One filter field per allowed column, typed by the column itself: a scalar
|
|
59
59
|
// for `=`, a list for `IN`, and `null` for `IS NULL`. Query strings are
|
|
@@ -155,18 +155,28 @@ export function buildTableCrudProcedures(args) {
|
|
|
155
155
|
path: `/api/${name}/{id}`,
|
|
156
156
|
summary: `Update ${name}`,
|
|
157
157
|
tags: [name],
|
|
158
|
-
inputStructure: 'detailed',
|
|
159
158
|
})
|
|
160
159
|
.input(updateInputSchema)
|
|
161
160
|
.output(selectSchema)
|
|
162
161
|
.handler(async ({ input, context }) => {
|
|
162
|
+
const requestPath = new URL(context.request.url).pathname;
|
|
163
|
+
if (!requestPath.startsWith('/api/rpc')) {
|
|
164
|
+
const rawBody = await context.getRawBody();
|
|
165
|
+
if (rawBody) {
|
|
166
|
+
const body = JSON.parse(rawBody);
|
|
167
|
+
if (Object.hasOwn(body, 'id')) {
|
|
168
|
+
throw new BunderstackError('BAD_REQUEST', 'The id field is immutable');
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
163
172
|
const session = await context.getSession();
|
|
164
173
|
const execCtx = {
|
|
165
174
|
request: context.request,
|
|
166
175
|
user: session.user,
|
|
167
176
|
session: { activeOrganizationId: session.activeOrganizationId },
|
|
168
177
|
};
|
|
169
|
-
|
|
178
|
+
const { id, ...changes } = input;
|
|
179
|
+
return (await operations.update(name, id, changes, execCtx));
|
|
170
180
|
});
|
|
171
181
|
// 5. DELETE procedure
|
|
172
182
|
const deleteProc = builder.public
|
|
@@ -197,22 +207,27 @@ export function buildTableCrudProcedures(args) {
|
|
|
197
207
|
filterableColumns: access.filterableColumns,
|
|
198
208
|
sortableColumns: access.sortableColumns,
|
|
199
209
|
});
|
|
200
|
-
const recordSchema = v.record(v.string(), v.unknown());
|
|
201
210
|
const liveFrameSchema = v.union([
|
|
202
211
|
v.strictObject({
|
|
203
212
|
type: v.literal('snapshot'),
|
|
204
|
-
items: v.array(
|
|
213
|
+
items: v.array(selectSchema),
|
|
205
214
|
sort: v.string(),
|
|
206
215
|
order: v.picklist(['asc', 'desc']),
|
|
207
216
|
limit: v.number(),
|
|
208
217
|
hasMore: v.boolean(),
|
|
218
|
+
operationId: v.optional(v.string()),
|
|
209
219
|
}),
|
|
210
220
|
v.strictObject({
|
|
211
221
|
type: v.literal('upsert'),
|
|
212
|
-
record:
|
|
222
|
+
record: selectSchema,
|
|
213
223
|
afterId: v.union([v.string(), v.null()]),
|
|
224
|
+
operationId: v.optional(v.string()),
|
|
225
|
+
}),
|
|
226
|
+
v.strictObject({
|
|
227
|
+
type: v.literal('remove'),
|
|
228
|
+
id: v.string(),
|
|
229
|
+
operationId: v.optional(v.string()),
|
|
214
230
|
}),
|
|
215
|
-
v.strictObject({ type: v.literal('remove'), id: v.string() }),
|
|
216
231
|
v.strictObject({ type: v.literal('heartbeat'), intervalMs: v.number() }),
|
|
217
232
|
]);
|
|
218
233
|
const live = builder.public
|
|
@@ -252,7 +267,7 @@ export function buildTableCrudProcedures(args) {
|
|
|
252
267
|
session: { activeOrganizationId: session.activeOrganizationId },
|
|
253
268
|
};
|
|
254
269
|
let view;
|
|
255
|
-
const readSnapshot = async () => {
|
|
270
|
+
const readSnapshot = async (operationId) => {
|
|
256
271
|
const result = await operations.list(name, input ?? {}, execCtx);
|
|
257
272
|
view = createLiveWindow({
|
|
258
273
|
sort: result.sort,
|
|
@@ -268,15 +283,21 @@ export function buildTableCrudProcedures(args) {
|
|
|
268
283
|
order: result.order,
|
|
269
284
|
limit: result.limit,
|
|
270
285
|
hasMore: result.hasMore,
|
|
286
|
+
...(operationId ? { operationId } : {}),
|
|
271
287
|
};
|
|
272
288
|
};
|
|
273
289
|
yield await readSnapshot();
|
|
274
290
|
for await (const change of changes) {
|
|
275
291
|
const outcome = view.apply(change);
|
|
276
|
-
if (outcome.type === 'none')
|
|
292
|
+
if (outcome.type === 'none') {
|
|
293
|
+
// The write succeeded but did not affect this view. A snapshot
|
|
294
|
+
// still acknowledges the operation without inventing a row.
|
|
295
|
+
if (change.operationId)
|
|
296
|
+
yield await readSnapshot(change.operationId);
|
|
277
297
|
continue;
|
|
298
|
+
}
|
|
278
299
|
if (outcome.type === 'resnapshot') {
|
|
279
|
-
yield await readSnapshot();
|
|
300
|
+
yield await readSnapshot(change.operationId);
|
|
280
301
|
continue;
|
|
281
302
|
}
|
|
282
303
|
for (const frame of outcome.frames)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crud-router.js","sourceRoot":"","sources":["../../src/api/crud-router.ts"],"names":[],"mappings":"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","sourcesContent":["import { getTableColumns, getTableName, isTable, type Table } from 'drizzle-orm'\nimport {\n createInsertSchema,\n createSelectSchema,\n createUpdateSchema,\n} from 'drizzle-valibot'\nimport '@orpc/openapi/extensions/route'\nimport { eventIterator } from '@orpc/server'\nimport * as v from 'valibot'\n\nimport type { AnyDb } from '../dialect'\nimport type { IdempotencyConfig } from '../idempotency'\nimport type { LiveSnapshotFrame } from '../live/protocol'\nimport type { RealtimeFacade } from '../realtime/facade'\nimport type { RealtimePublisher } from '../realtime/publisher'\nimport type { CrudApiRouterFor } from './types'\n\nimport {\n tableEntryForName,\n type ResolvedAccess,\n type ResolvedTableAccess,\n type TableAccessInput,\n} from '../access'\nimport { createCrudOperations, type CrudOperations } from '../crud-operations'\nimport { filterTableChanges } from '../realtime/filter'\nimport {\n REALTIME_HEARTBEAT_INTERVAL_MS,\n withRealtimeHeartbeat,\n} from '../realtime/heartbeat'\nimport { createApiBuilder } from './builder'\nimport { createLiveWindow } from './live-window'\nimport { buildListInputSchema, buildLiveInputSchema } from './list-input-schema'\n\nexport type CrudApiRouterOptions<\n TSchema extends Record<string, unknown> = Record<string, unknown>,\n> = {\n access: ResolvedAccess\n idempotency?: boolean | IdempotencyConfig\n realtime?: RealtimeFacade<TSchema>\n /**\n * The raw realtime publisher, present when realtime is enabled. The CRUD\n * router subscribes on behalf of live views (`GET /{table}:live`);\n * publishing stays behind the facade.\n */\n livePublisher?: RealtimePublisher\n}\n\nfunction strictObject<TEntries extends v.ObjectEntries>(schema: {\n entries: TEntries\n}) {\n return v.strictObject(schema.entries)\n}\n\ntype CrudInsert<TTable extends Table> = Partial<TTable['$inferInsert']>\ntype CrudUpdate<TTable extends Table> = Partial<\n Omit<TTable['$inferInsert'], 'id'>\n>\n\n/** A filter accepts one value (`=`), a list (`IN`), or null (`IS NULL`). */\nexport type ListFilterValue<T> = T | readonly T[] | null | 'null'\n\nexport type ListFilters<TTable extends Table, TFilterable extends string> = {\n [K in Extract<keyof TTable['$inferSelect'], TFilterable>]?: ListFilterValue<\n TTable['$inferSelect'][K]\n >\n}\n\n/**\n * The `list` input as callers see it. Filter columns and sortable columns come\n * from the table's `access` entry, so `filters` autocompletes to real columns\n * and rejects wrong value types at compile time.\n */\nexport type ListInputFor<\n TTable extends Table,\n TFilterable extends string,\n TSortable extends string,\n> =\n | {\n limit?: number\n offset?: number\n cursor?: string\n sort?: TSortable\n order?: 'asc' | 'desc'\n q?: string\n count?: boolean\n filters?: ListFilters<TTable, TFilterable>\n }\n | undefined\n\n/**\n * The live-view input as callers see it: the list contract narrowed to what a\n * stream can honor — no text search, no pagination.\n */\nexport type LiveInputFor<\n TTable extends Table,\n TFilterable extends string,\n TSortable extends string,\n> =\n | {\n limit?: number\n sort?: TSortable\n order?: 'asc' | 'desc'\n filters?: ListFilters<TTable, TFilterable>\n }\n | undefined\n\nexport type BuildTableCrudProceduresArgs<\n TSchema extends Record<string, unknown>,\n TTable extends Table,\n> = {\n table: TTable\n operations: CrudOperations\n builder: ReturnType<typeof createApiBuilder<TSchema>>\n access: ResolvedTableAccess\n /** The schema key events are published under. */\n schemaKey: string\n /** Present when realtime is enabled; without it no live procedure is built. */\n livePublisher?: RealtimePublisher\n}\n\nexport function buildTableCrudProcedures<\n TSchema extends Record<string, unknown>,\n TTable extends Table,\n TFilterable extends string = string,\n TSortable extends string = string,\n>(args: BuildTableCrudProceduresArgs<TSchema, TTable>) {\n const { table, operations, builder, access, schemaKey, livePublisher } =\n args\n const name = getTableName(table)\n\n // drizzle-valibot infers its schemas through internal generics. Left as-is,\n // declaration emit inlines those generics by name into the published `.d.ts`,\n // where they are unbound — every column silently becomes optional for the\n // consumer. Restating the type keeps the runtime schema and makes the emitted\n // one both correct and readable.\n const selectSchema = strictObject(\n createSelectSchema(table),\n ) as unknown as v.GenericSchema<\n TTable['$inferSelect'],\n TTable['$inferSelect']\n >\n const generatedInsertSchema = strictObject(createInsertSchema(table))\n const generatedColumns = Object.keys(generatedInsertSchema.entries)\n const columns = getTableColumns(table)\n const serverManagedColumns = [\n ...access.readonlyColumns,\n ...(access.writeScope && generatedColumns.includes('organizationId')\n ? ['organizationId']\n : []),\n ...Object.entries(columns)\n .filter(([, column]) => !column.notNull || column.hasDefault)\n .map(([column]) => column),\n ].filter((column) => generatedColumns.includes(column))\n const insertEntries: Record<\n string,\n v.BaseSchema<unknown, unknown, v.BaseIssue<unknown>>\n > = {\n ...generatedInsertSchema.entries,\n }\n for (const column of serverManagedColumns) {\n const schema = insertEntries[column]\n if (schema) insertEntries[column] = v.optional(schema)\n }\n const insertSchema = v.strictObject(\n insertEntries,\n ) as unknown as v.GenericSchema<CrudInsert<TTable>, CrudInsert<TTable>>\n const generatedUpdateSchema = createUpdateSchema(table)\n const updateBodySchema = v.omit(generatedUpdateSchema, [\n 'id' as keyof typeof generatedUpdateSchema.entries,\n ])\n const updateInputSchema = v.strictObject({\n params: v.strictObject({ id: v.string() }),\n query: v.optional(v.record(v.string(), v.unknown()), {}),\n headers: v.optional(v.record(v.string(), v.unknown()), {}),\n // Same reason as `selectSchema`: state the type instead of letting\n // declaration emit inline drizzle-valibot's generics.\n body: strictObject(updateBodySchema) as unknown as v.GenericSchema<\n CrudUpdate<TTable>,\n CrudUpdate<TTable>\n >,\n })\n\n // One filter field per allowed column, typed by the column itself: a scalar\n // for `=`, a list for `IN`, and `null` for `IS NULL`. Query strings are\n // coerced to these types by SmartCoercionHandlerPlugin, so REST and RPC share\n // one contract and nothing has to re-read the raw URL.\n // The cast restates the schema with the literals the caller's `access`\n // config carries, which the runtime column lists cannot name on their own.\n const listQuerySchema = buildListInputSchema(table, {\n filterableColumns: access.filterableColumns,\n sortableColumns: access.sortableColumns,\n }) as unknown as v.GenericSchema<\n ListInputFor<TTable, TFilterable, TSortable>,\n ListInputFor<TTable, TFilterable, TSortable>\n >\n\n const listOutputSchema = v.strictObject({\n items: v.array(selectSchema),\n nextCursor: v.optional(v.string()),\n hasMore: v.boolean(),\n total: v.optional(v.number()),\n limit: v.optional(v.number()),\n offset: v.optional(v.number()),\n cursor: v.optional(v.string()),\n q: v.optional(v.string()),\n sort: v.optional(v.string()),\n order: v.optional(v.string()),\n })\n\n // 1. LIST procedure\n const list = builder.public\n .route({\n method: 'GET',\n path: `/api/${name}`,\n summary: `List ${name}`,\n tags: [name],\n })\n .input(listQuerySchema)\n .output(listOutputSchema)\n .handler(async ({ input, context }) => {\n const session = await context.getSession()\n const execCtx = {\n request: context.request,\n user: session.user,\n session: { activeOrganizationId: session.activeOrganizationId },\n }\n const result = await operations.list(name, input ?? {}, execCtx)\n return {\n ...result,\n items: result.items as TTable['$inferSelect'][],\n }\n })\n\n // 2. GET procedure\n const get = builder.public\n .route({\n method: 'GET',\n path: `/api/${name}/{id}`,\n summary: `Get ${name} by ID`,\n tags: [name],\n })\n .input(v.strictObject({ id: v.string() }))\n .output(selectSchema)\n .handler(async ({ input, context }) => {\n const session = await context.getSession()\n const execCtx = {\n request: context.request,\n user: session.user,\n session: { activeOrganizationId: session.activeOrganizationId },\n }\n return (await operations.get(\n name,\n input.id,\n execCtx,\n )) as TTable['$inferSelect']\n })\n\n // 3. CREATE procedure\n const create = builder.public\n .route({\n method: 'POST',\n path: `/api/${name}`,\n summary: `Create ${name}`,\n tags: [name],\n successStatus: 201,\n })\n .input(insertSchema)\n .output(selectSchema)\n .handler(async ({ input, context }) => {\n const session = await context.getSession()\n const execCtx = {\n request: context.request,\n user: session.user,\n session: { activeOrganizationId: session.activeOrganizationId },\n }\n const idempotencyKey = context.request.headers\n .get('Idempotency-Key')\n ?.trim()\n const rawBody = await context.getRawBody()\n\n const res = await operations.create(\n name,\n input,\n rawBody,\n idempotencyKey,\n execCtx,\n )\n if (res.type === 'replay') {\n context.resHeaders.set('Idempotency-Replayed', 'true')\n return res.record as TTable['$inferSelect']\n }\n return res.record as TTable['$inferSelect']\n })\n\n // 4. UPDATE procedure\n const update = builder.public\n .route({\n method: 'PATCH',\n path: `/api/${name}/{id}`,\n summary: `Update ${name}`,\n tags: [name],\n inputStructure: 'detailed',\n })\n .input(updateInputSchema)\n .output(selectSchema)\n .handler(async ({ input, context }) => {\n const session = await context.getSession()\n const execCtx = {\n request: context.request,\n user: session.user,\n session: { activeOrganizationId: session.activeOrganizationId },\n }\n\n return (await operations.update(\n name,\n input.params.id,\n input.body,\n execCtx,\n )) as TTable['$inferSelect']\n })\n\n // 5. DELETE procedure\n const deleteProc = builder.public\n .route({\n method: 'DELETE',\n path: `/api/${name}/{id}`,\n summary: `Delete ${name}`,\n tags: [name],\n successStatus: 204,\n })\n .input(v.strictObject({ id: v.string() }))\n .output(v.undefined())\n .handler(async ({ input, context }) => {\n const session = await context.getSession()\n const execCtx = {\n request: context.request,\n user: session.user,\n session: { activeOrganizationId: session.activeOrganizationId },\n }\n await operations.delete(name, input.id, execCtx)\n return undefined\n })\n\n // 6. LIVE procedure — one list query as a stream. A snapshot first, then the\n // changes this view cares about, placed by the server. Every connection\n // opens with a snapshot, so a reconnect is its own recovery: no client-side\n // event buffer, no Last-Event-ID bookkeeping, and no refetch path.\n const liveQuerySchema = buildLiveInputSchema(table, {\n filterableColumns: access.filterableColumns,\n sortableColumns: access.sortableColumns,\n }) as unknown as v.GenericSchema<\n LiveInputFor<TTable, TFilterable, TSortable>,\n LiveInputFor<TTable, TFilterable, TSortable>\n >\n\n const recordSchema = v.record(v.string(), v.unknown())\n const liveFrameSchema = v.union([\n v.strictObject({\n type: v.literal('snapshot'),\n items: v.array(recordSchema),\n sort: v.string(),\n order: v.picklist(['asc', 'desc']),\n limit: v.number(),\n hasMore: v.boolean(),\n }),\n v.strictObject({\n type: v.literal('upsert'),\n record: recordSchema,\n afterId: v.union([v.string(), v.null()]),\n }),\n v.strictObject({ type: v.literal('remove'), id: v.string() }),\n v.strictObject({ type: v.literal('heartbeat'), intervalMs: v.number() }),\n ])\n\n const live = builder.public\n .route({\n method: 'GET',\n // A path of its own, not a child of the table: `/{table}/live`\n // would make the id \"live\" unreachable on the get route, and a\n // colon suffix is a wildcard parameter in the oRPC matcher.\n path: `/api/live/${name}`,\n summary: `Live view of ${name}`,\n tags: [name],\n // Filters arrive as one URL-encoded JSON value, so no per-key query\n // parsing rule is needed.\n queryStyles: { filters: 'json' },\n })\n .input(liveQuerySchema)\n .output(eventIterator(liveFrameSchema))\n .handler(({ input, context, signal }) => {\n if (!livePublisher) {\n throw new Error(`Realtime is not enabled for live view of ${name}`)\n }\n // Subscribe before anything awaits, so no change slips between the\n // snapshot read and the start of the stream; events that arrive\n // during the query buffer in the publisher and replay on first pull.\n const changes = filterTableChanges(\n livePublisher.subscribe('change', { signal }),\n {\n tableName: schemaKey,\n entry: access,\n rule: access.list,\n request: context.request,\n getSession: context.getSession,\n },\n )\n\n return withRealtimeHeartbeat(\n (async function* () {\n const session = await context.getSession()\n const execCtx = {\n request: context.request,\n user: session.user,\n session: { activeOrganizationId: session.activeOrganizationId },\n }\n let view: ReturnType<typeof createLiveWindow> | undefined\n\n const readSnapshot = async (): Promise<LiveSnapshotFrame> => {\n const result = await operations.list(name, input ?? {}, execCtx)\n view = createLiveWindow({\n sort: result.sort,\n order: result.order,\n limit: result.limit,\n filters: input?.filters as\n | Record<string, unknown>\n | undefined,\n })\n view.reset(result.items, result.hasMore)\n return {\n type: 'snapshot',\n items: result.items,\n sort: result.sort,\n order: result.order,\n limit: result.limit,\n hasMore: result.hasMore,\n }\n }\n\n yield await readSnapshot()\n\n for await (const change of changes) {\n const outcome = view!.apply(change)\n if (outcome.type === 'none') continue\n if (outcome.type === 'resnapshot') {\n yield await readSnapshot()\n continue\n }\n for (const frame of outcome.frames) yield frame\n }\n })(),\n { intervalMs: REALTIME_HEARTBEAT_INTERVAL_MS, signal },\n )\n })\n\n const procedures = {\n list,\n get,\n create,\n update,\n delete: deleteProc,\n live,\n }\n\n if (!livePublisher) {\n delete (procedures as { live?: unknown }).live\n }\n\n return procedures\n}\n\nexport type TableCrudProcedures<\n TTable extends Table,\n TFilterable extends string = string,\n TSortable extends string = string,\n> = ReturnType<\n typeof buildTableCrudProcedures<\n Record<string, unknown>,\n TTable,\n TFilterable,\n TSortable\n >\n>\n\nexport function buildCrudApiRouter<\n TSchema extends Record<string, unknown>,\n TAccess extends Record<string, TableAccessInput> | undefined = undefined,\n>(\n schema: TSchema,\n db: AnyDb,\n options: CrudApiRouterOptions<TSchema>,\n): CrudApiRouterFor<TSchema, TAccess> {\n const { access, realtime, idempotency, livePublisher } = options\n const builder = createApiBuilder<TSchema>()\n const operations = createCrudOperations({\n schema,\n db,\n access,\n idempotency,\n realtime,\n })\n\n const routerObj: Record<string, unknown> = {}\n\n for (const [tableKey, table] of Object.entries(schema)) {\n if (!isTable(table)) continue\n\n const name = getTableName(table)\n const tableAccess = tableEntryForName(access, name)\n if (!tableAccess?.enabled) continue\n\n const idCol = getTableColumns(table)['id']\n if (!idCol) continue\n\n const procedures = buildTableCrudProcedures({\n table: table as Table,\n operations,\n builder,\n access: tableAccess,\n schemaKey: tableKey,\n livePublisher,\n })\n\n routerObj[tableKey] = procedures\n }\n\n return routerObj as CrudApiRouterFor<TSchema, TAccess>\n}\n"]}
|
|
1
|
+
{"version":3,"file":"crud-router.js","sourceRoot":"","sources":["../../src/api/crud-router.ts"],"names":[],"mappings":"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","sourcesContent":["import { eventIterator } from '@orpc/server'\nimport { getTableColumns, getTableName, isTable, type Table } from 'drizzle-orm'\nimport '@orpc/openapi/extensions/route'\nimport {\n createInsertSchema,\n createSelectSchema,\n createUpdateSchema,\n} from 'drizzle-valibot'\nimport * as v from 'valibot'\n\nimport type { AnyDb } from '../dialect'\nimport type { IdempotencyConfig } from '../idempotency'\nimport type { LiveSnapshotFrame } from '../live/protocol'\nimport type { RealtimeFacade } from '../realtime/facade'\nimport type { RealtimePublisher } from '../realtime/publisher'\nimport type { CrudApiRouterFor } from './types'\n\nimport {\n tableEntryForName,\n type ResolvedAccess,\n type ResolvedTableAccess,\n type TableAccessInput,\n} from '../access'\nimport { createCrudOperations, type CrudOperations } from '../crud-operations'\nimport { BunderstackError } from '../errors'\nimport { filterTableChanges } from '../realtime/filter'\nimport {\n REALTIME_HEARTBEAT_INTERVAL_MS,\n withRealtimeHeartbeat,\n} from '../realtime/heartbeat'\nimport { createApiBuilder } from './builder'\nimport { buildListInputSchema, buildLiveInputSchema } from './list-input-schema'\nimport { createLiveWindow } from './live-window'\n\nexport type CrudApiRouterOptions<\n TSchema extends Record<string, unknown> = Record<string, unknown>,\n> = {\n access: ResolvedAccess\n idempotency?: boolean | IdempotencyConfig\n realtime?: RealtimeFacade<TSchema>\n /**\n * The raw realtime publisher, present when realtime is enabled. The CRUD\n * router subscribes on behalf of live views (`GET /{table}:live`);\n * publishing stays behind the facade.\n */\n livePublisher?: RealtimePublisher\n}\n\nfunction strictObject<TEntries extends v.ObjectEntries>(schema: {\n entries: TEntries\n}) {\n return v.strictObject(schema.entries)\n}\n\ntype CrudInsert<TTable extends Table> = Partial<TTable['$inferInsert']>\ntype CrudUpdate<TTable extends Table> = Partial<\n Omit<TTable['$inferInsert'], 'id'>\n>\n\n/** A filter accepts one value (`=`), a list (`IN`), or null (`IS NULL`). */\nexport type ListFilterValue<T> = T | readonly T[] | null | 'null'\n\nexport type ListFilters<TTable extends Table, TFilterable extends string> = {\n [K in Extract<keyof TTable['$inferSelect'], TFilterable>]?: ListFilterValue<\n TTable['$inferSelect'][K]\n >\n}\n\n/**\n * The `list` input as callers see it. Filter columns and sortable columns come\n * from the table's `access` entry, so `filters` autocompletes to real columns\n * and rejects wrong value types at compile time.\n */\nexport type ListInputFor<\n TTable extends Table,\n TFilterable extends string,\n TSortable extends string,\n> =\n | {\n limit?: number\n offset?: number\n cursor?: string\n sort?: TSortable\n order?: 'asc' | 'desc'\n q?: string\n count?: boolean\n filters?: ListFilters<TTable, TFilterable>\n }\n | undefined\n\n/**\n * The live-view input as callers see it: the list contract narrowed to what a\n * stream can honor — no text search, no pagination.\n */\nexport type LiveInputFor<\n TTable extends Table,\n TFilterable extends string,\n TSortable extends string,\n> =\n | {\n limit?: number\n sort?: TSortable\n order?: 'asc' | 'desc'\n filters?: ListFilters<TTable, TFilterable>\n }\n | undefined\n\nexport type BuildTableCrudProceduresArgs<\n TSchema extends Record<string, unknown>,\n TTable extends Table,\n> = {\n table: TTable\n operations: CrudOperations\n builder: ReturnType<typeof createApiBuilder<TSchema>>\n access: ResolvedTableAccess\n /** The schema key events are published under. */\n schemaKey: string\n /** Present when realtime is enabled; without it no live procedure is built. */\n livePublisher?: RealtimePublisher\n}\n\nexport function buildTableCrudProcedures<\n TSchema extends Record<string, unknown>,\n TTable extends Table,\n TFilterable extends string = string,\n TSortable extends string = string,\n>(args: BuildTableCrudProceduresArgs<TSchema, TTable>) {\n const { table, operations, builder, access, schemaKey, livePublisher } = args\n const name = getTableName(table)\n\n // drizzle-valibot infers its schemas through internal generics. Left as-is,\n // declaration emit inlines those generics by name into the published `.d.ts`,\n // where they are unbound — every column silently becomes optional for the\n // consumer. Restating the type keeps the runtime schema and makes the emitted\n // one both correct and readable.\n const selectSchema = strictObject(\n createSelectSchema(table),\n ) as unknown as v.GenericSchema<\n TTable['$inferSelect'],\n TTable['$inferSelect']\n >\n const generatedInsertSchema = strictObject(createInsertSchema(table))\n const generatedColumns = Object.keys(generatedInsertSchema.entries)\n const columns = getTableColumns(table)\n const serverManagedColumns = [\n ...access.readonlyColumns,\n ...(access.writeScope && generatedColumns.includes('organizationId')\n ? ['organizationId']\n : []),\n ...Object.entries(columns)\n .filter(([, column]) => !column.notNull || column.hasDefault)\n .map(([column]) => column),\n ].filter((column) => generatedColumns.includes(column))\n const insertEntries: Record<\n string,\n v.BaseSchema<unknown, unknown, v.BaseIssue<unknown>>\n > = {\n ...generatedInsertSchema.entries,\n }\n for (const column of serverManagedColumns) {\n const schema = insertEntries[column]\n if (schema) insertEntries[column] = v.optional(schema)\n }\n const insertSchema = v.strictObject(\n insertEntries,\n ) as unknown as v.GenericSchema<CrudInsert<TTable>, CrudInsert<TTable>>\n const generatedUpdateSchema = createUpdateSchema(table)\n const updateBodySchema = v.omit(generatedUpdateSchema, [\n 'id' as keyof typeof generatedUpdateSchema.entries,\n ])\n // Compact input is transport-neutral: direct oRPC callers pass\n // `{ id, ...changes }`, while OpenAPI still maps `id` to the path and the\n // remaining fields to the PATCH body.\n const updateInputSchema = v.strictObject({\n id: v.string(),\n ...updateBodySchema.entries,\n }) as unknown as v.GenericSchema<\n { id: string } & CrudUpdate<TTable>,\n { id: string } & CrudUpdate<TTable>\n >\n\n // One filter field per allowed column, typed by the column itself: a scalar\n // for `=`, a list for `IN`, and `null` for `IS NULL`. Query strings are\n // coerced to these types by SmartCoercionHandlerPlugin, so REST and RPC share\n // one contract and nothing has to re-read the raw URL.\n // The cast restates the schema with the literals the caller's `access`\n // config carries, which the runtime column lists cannot name on their own.\n const listQuerySchema = buildListInputSchema(table, {\n filterableColumns: access.filterableColumns,\n sortableColumns: access.sortableColumns,\n }) as unknown as v.GenericSchema<\n ListInputFor<TTable, TFilterable, TSortable>,\n ListInputFor<TTable, TFilterable, TSortable>\n >\n\n const listOutputSchema = v.strictObject({\n items: v.array(selectSchema),\n nextCursor: v.optional(v.string()),\n hasMore: v.boolean(),\n total: v.optional(v.number()),\n limit: v.optional(v.number()),\n offset: v.optional(v.number()),\n cursor: v.optional(v.string()),\n q: v.optional(v.string()),\n sort: v.optional(v.string()),\n order: v.optional(v.string()),\n })\n\n // 1. LIST procedure\n const list = builder.public\n .route({\n method: 'GET',\n path: `/api/${name}`,\n summary: `List ${name}`,\n tags: [name],\n })\n .input(listQuerySchema)\n .output(listOutputSchema)\n .handler(async ({ input, context }) => {\n const session = await context.getSession()\n const execCtx = {\n request: context.request,\n user: session.user,\n session: { activeOrganizationId: session.activeOrganizationId },\n }\n const result = await operations.list(name, input ?? {}, execCtx)\n return {\n ...result,\n items: result.items as TTable['$inferSelect'][],\n }\n })\n\n // 2. GET procedure\n const get = builder.public\n .route({\n method: 'GET',\n path: `/api/${name}/{id}`,\n summary: `Get ${name} by ID`,\n tags: [name],\n })\n .input(v.strictObject({ id: v.string() }))\n .output(selectSchema)\n .handler(async ({ input, context }) => {\n const session = await context.getSession()\n const execCtx = {\n request: context.request,\n user: session.user,\n session: { activeOrganizationId: session.activeOrganizationId },\n }\n return (await operations.get(\n name,\n input.id,\n execCtx,\n )) as TTable['$inferSelect']\n })\n\n // 3. CREATE procedure\n const create = builder.public\n .route({\n method: 'POST',\n path: `/api/${name}`,\n summary: `Create ${name}`,\n tags: [name],\n successStatus: 201,\n })\n .input(insertSchema)\n .output(selectSchema)\n .handler(async ({ input, context }) => {\n const session = await context.getSession()\n const execCtx = {\n request: context.request,\n user: session.user,\n session: { activeOrganizationId: session.activeOrganizationId },\n }\n const idempotencyKey = context.request.headers\n .get('Idempotency-Key')\n ?.trim()\n const rawBody = await context.getRawBody()\n\n const res = await operations.create(\n name,\n input,\n rawBody,\n idempotencyKey,\n execCtx,\n )\n if (res.type === 'replay') {\n context.resHeaders.set('Idempotency-Replayed', 'true')\n return res.record as TTable['$inferSelect']\n }\n return res.record as TTable['$inferSelect']\n })\n\n // 4. UPDATE procedure\n const update = builder.public\n .route({\n method: 'PATCH',\n path: `/api/${name}/{id}`,\n summary: `Update ${name}`,\n tags: [name],\n })\n .input(updateInputSchema)\n .output(selectSchema)\n .handler(async ({ input, context }) => {\n const requestPath = new URL(context.request.url).pathname\n if (!requestPath.startsWith('/api/rpc')) {\n const rawBody = await context.getRawBody()\n if (rawBody) {\n const body = JSON.parse(rawBody) as Record<string, unknown>\n if (Object.hasOwn(body, 'id')) {\n throw new BunderstackError(\n 'BAD_REQUEST',\n 'The id field is immutable',\n )\n }\n }\n }\n const session = await context.getSession()\n const execCtx = {\n request: context.request,\n user: session.user,\n session: { activeOrganizationId: session.activeOrganizationId },\n }\n\n const { id, ...changes } = input\n return (await operations.update(\n name,\n id,\n changes,\n execCtx,\n )) as TTable['$inferSelect']\n })\n\n // 5. DELETE procedure\n const deleteProc = builder.public\n .route({\n method: 'DELETE',\n path: `/api/${name}/{id}`,\n summary: `Delete ${name}`,\n tags: [name],\n successStatus: 204,\n })\n .input(v.strictObject({ id: v.string() }))\n .output(v.undefined())\n .handler(async ({ input, context }) => {\n const session = await context.getSession()\n const execCtx = {\n request: context.request,\n user: session.user,\n session: { activeOrganizationId: session.activeOrganizationId },\n }\n await operations.delete(name, input.id, execCtx)\n return undefined\n })\n\n // 6. LIVE procedure — one list query as a stream. A snapshot first, then the\n // changes this view cares about, placed by the server. Every connection\n // opens with a snapshot, so a reconnect is its own recovery: no client-side\n // event buffer, no Last-Event-ID bookkeeping, and no refetch path.\n const liveQuerySchema = buildLiveInputSchema(table, {\n filterableColumns: access.filterableColumns,\n sortableColumns: access.sortableColumns,\n }) as unknown as v.GenericSchema<\n LiveInputFor<TTable, TFilterable, TSortable>,\n LiveInputFor<TTable, TFilterable, TSortable>\n >\n\n const liveFrameSchema = v.union([\n v.strictObject({\n type: v.literal('snapshot'),\n items: v.array(selectSchema),\n sort: v.string(),\n order: v.picklist(['asc', 'desc']),\n limit: v.number(),\n hasMore: v.boolean(),\n operationId: v.optional(v.string()),\n }),\n v.strictObject({\n type: v.literal('upsert'),\n record: selectSchema,\n afterId: v.union([v.string(), v.null()]),\n operationId: v.optional(v.string()),\n }),\n v.strictObject({\n type: v.literal('remove'),\n id: v.string(),\n operationId: v.optional(v.string()),\n }),\n v.strictObject({ type: v.literal('heartbeat'), intervalMs: v.number() }),\n ])\n\n const live = builder.public\n .route({\n method: 'GET',\n // A path of its own, not a child of the table: `/{table}/live`\n // would make the id \"live\" unreachable on the get route, and a\n // colon suffix is a wildcard parameter in the oRPC matcher.\n path: `/api/live/${name}`,\n summary: `Live view of ${name}`,\n tags: [name],\n // Filters arrive as one URL-encoded JSON value, so no per-key query\n // parsing rule is needed.\n queryStyles: { filters: 'json' },\n })\n .input(liveQuerySchema)\n .output(eventIterator(liveFrameSchema))\n .handler(({ input, context, signal }) => {\n if (!livePublisher) {\n throw new Error(`Realtime is not enabled for live view of ${name}`)\n }\n // Subscribe before anything awaits, so no change slips between the\n // snapshot read and the start of the stream; events that arrive\n // during the query buffer in the publisher and replay on first pull.\n const changes = filterTableChanges(\n livePublisher.subscribe('change', { signal }),\n {\n tableName: schemaKey,\n entry: access,\n rule: access.list,\n request: context.request,\n getSession: context.getSession,\n },\n )\n\n return withRealtimeHeartbeat(\n (async function* () {\n const session = await context.getSession()\n const execCtx = {\n request: context.request,\n user: session.user,\n session: { activeOrganizationId: session.activeOrganizationId },\n }\n let view: ReturnType<typeof createLiveWindow> | undefined\n\n const readSnapshot = async (\n operationId?: string,\n ): Promise<LiveSnapshotFrame<TTable['$inferSelect']>> => {\n const result = await operations.list(name, input ?? {}, execCtx)\n view = createLiveWindow({\n sort: result.sort,\n order: result.order,\n limit: result.limit,\n filters: input?.filters as Record<string, unknown> | undefined,\n })\n view.reset(result.items, result.hasMore)\n return {\n type: 'snapshot',\n items: result.items,\n sort: result.sort,\n order: result.order,\n limit: result.limit,\n hasMore: result.hasMore,\n ...(operationId ? { operationId } : {}),\n }\n }\n\n yield await readSnapshot()\n\n for await (const change of changes) {\n const outcome = view!.apply(change)\n if (outcome.type === 'none') {\n // The write succeeded but did not affect this view. A snapshot\n // still acknowledges the operation without inventing a row.\n if (change.operationId)\n yield await readSnapshot(change.operationId)\n continue\n }\n if (outcome.type === 'resnapshot') {\n yield await readSnapshot(change.operationId)\n continue\n }\n for (const frame of outcome.frames) yield frame\n }\n })(),\n { intervalMs: REALTIME_HEARTBEAT_INTERVAL_MS, signal },\n )\n })\n\n const procedures = {\n list,\n get,\n create,\n update,\n delete: deleteProc,\n live,\n }\n\n if (!livePublisher) {\n delete (procedures as { live?: unknown }).live\n }\n\n return procedures\n}\n\nexport type TableCrudProcedures<\n TTable extends Table,\n TFilterable extends string = string,\n TSortable extends string = string,\n TLive extends boolean = false,\n> = Omit<\n ReturnType<\n typeof buildTableCrudProcedures<\n Record<string, unknown>,\n TTable,\n TFilterable,\n TSortable\n >\n >,\n 'live'\n> &\n (TLive extends true\n ? {\n live: ReturnType<\n typeof buildTableCrudProcedures<\n Record<string, unknown>,\n TTable,\n TFilterable,\n TSortable\n >\n >['live']\n }\n : {})\n\nexport function buildCrudApiRouter<\n TSchema extends Record<string, unknown>,\n TAccess extends Record<string, TableAccessInput> | undefined = undefined,\n TLive extends boolean = false,\n>(\n schema: TSchema,\n db: AnyDb,\n options: CrudApiRouterOptions<TSchema>,\n): CrudApiRouterFor<TSchema, TAccess, TLive> {\n const { access, realtime, idempotency, livePublisher } = options\n const builder = createApiBuilder<TSchema>()\n const operations = createCrudOperations({\n schema,\n db,\n access,\n idempotency,\n realtime,\n })\n\n const routerObj: Record<string, unknown> = {}\n\n for (const [tableKey, table] of Object.entries(schema)) {\n if (!isTable(table)) continue\n\n const name = getTableName(table)\n const tableAccess = tableEntryForName(access, name)\n if (!tableAccess?.enabled) continue\n\n const idCol = getTableColumns(table)['id']\n if (!idCol) continue\n\n const procedures = buildTableCrudProcedures({\n table: table as Table,\n operations,\n builder,\n access: tableAccess,\n schemaKey: tableKey,\n livePublisher,\n })\n\n routerObj[tableKey] = procedures\n }\n\n return routerObj as CrudApiRouterFor<TSchema, TAccess, TLive>\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"live-window.d.ts","sourceRoot":"","sources":["../../src/api/live-window.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AACtD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAI3D;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,KAAK,GAAG,MAAM,CAAA;IACrB,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAClC,CAAA;AAED,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,cAAc,EAAE,CAAA;CAAE,GAC5C;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,CAAA;AAE1B,MAAM,MAAM,UAAU,GAAG;IACvB,gDAAgD;IAChD,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;IACnE,4CAA4C;IAC5C,KAAK,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,gBAAgB,CAAA;CACpD,CAAA;AAmBD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,UAAU,
|
|
1
|
+
{"version":3,"file":"live-window.d.ts","sourceRoot":"","sources":["../../src/api/live-window.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AACtD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAI3D;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,KAAK,GAAG,MAAM,CAAA;IACrB,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAClC,CAAA;AAED,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,cAAc,EAAE,CAAA;CAAE,GAC5C;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,CAAA;AAE1B,MAAM,MAAM,UAAU,GAAG;IACvB,gDAAgD;IAChD,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;IACnE,4CAA4C;IAC5C,KAAK,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,gBAAgB,CAAA;CACpD,CAAA;AAmBD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,UAAU,CAgFvE"}
|
package/dist/api/live-window.js
CHANGED
|
@@ -38,6 +38,9 @@ export function createLiveWindow(options) {
|
|
|
38
38
|
},
|
|
39
39
|
apply(change) {
|
|
40
40
|
const id = String(change.record.id);
|
|
41
|
+
const metadata = change.operationId
|
|
42
|
+
? { operationId: change.operationId }
|
|
43
|
+
: {};
|
|
41
44
|
const index = rows.findIndex((row) => row.id === id);
|
|
42
45
|
const held = index !== -1;
|
|
43
46
|
// The row left the view: deleted, or no longer a match for the filters.
|
|
@@ -50,7 +53,10 @@ export function createLiveWindow(options) {
|
|
|
50
53
|
// database knows which one.
|
|
51
54
|
if (hasMore)
|
|
52
55
|
return { type: 'resnapshot' };
|
|
53
|
-
return {
|
|
56
|
+
return {
|
|
57
|
+
type: 'frames',
|
|
58
|
+
frames: [{ type: 'remove', id, ...metadata }],
|
|
59
|
+
};
|
|
54
60
|
}
|
|
55
61
|
if (held)
|
|
56
62
|
rows.splice(index, 1);
|
|
@@ -61,7 +67,10 @@ export function createLiveWindow(options) {
|
|
|
61
67
|
if (!held)
|
|
62
68
|
return { type: 'none' };
|
|
63
69
|
hasMore = true;
|
|
64
|
-
return {
|
|
70
|
+
return {
|
|
71
|
+
type: 'frames',
|
|
72
|
+
frames: [{ type: 'remove', id, ...metadata }],
|
|
73
|
+
};
|
|
65
74
|
}
|
|
66
75
|
rows.splice(place, 0, row);
|
|
67
76
|
const frames = [
|
|
@@ -69,12 +78,13 @@ export function createLiveWindow(options) {
|
|
|
69
78
|
type: 'upsert',
|
|
70
79
|
record: change.record,
|
|
71
80
|
afterId: place === 0 ? null : rows[place - 1].id,
|
|
81
|
+
...metadata,
|
|
72
82
|
},
|
|
73
83
|
];
|
|
74
84
|
if (rows.length > options.limit) {
|
|
75
85
|
const evicted = rows.pop();
|
|
76
86
|
hasMore = true;
|
|
77
|
-
frames.push({ type: 'remove', id: evicted.id });
|
|
87
|
+
frames.push({ type: 'remove', id: evicted.id, ...metadata });
|
|
78
88
|
}
|
|
79
89
|
return { type: 'frames', frames };
|
|
80
90
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"live-window.js","sourceRoot":"","sources":["../../src/api/live-window.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"live-window.js","sourceRoot":"","sources":["../../src/api/live-window.ts"],"names":[],"mappings":"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","sourcesContent":["import type { LiveDeltaFrame } from '../live/protocol'\nimport type { RealtimeChange } from '../realtime/publisher'\n\nimport { matchesLiveFilters } from './live-view'\n\n/**\n * The server-side state of one live-view connection: which rows the client\n * holds, in view order, and whether rows exist below the window.\n *\n * The window is what lets the server place a row for the client (`afterId`)\n * instead of making the browser repeat `ORDER BY`. It is also what makes a\n * removal from a truncated view ask for a fresh snapshot instead of silently\n * shrinking the view.\n *\n * Ordering follows `buildOrderBy` in `../list-query`: the sort column first,\n * then `id`, both in the query's direction. Values are compared in the process\n * on raw column values. That is exact for numbers, dates, and booleans, and\n * matches the database for ordinary text. A collation that differs from\n * code-unit order, or a NULLS rule other than \"nulls first\", can place a row\n * inside the window differently from the database.\n */\n\nexport type LiveWindowOptions = {\n sort: string\n order: 'asc' | 'desc'\n limit: number\n filters?: Record<string, unknown>\n}\n\nexport type LiveWindowResult =\n | { type: 'none' }\n | { type: 'frames'; frames: LiveDeltaFrame[] }\n | { type: 'resnapshot' }\n\nexport type LiveWindow = {\n /** Adopt a query result as the current view. */\n reset: (items: Record<string, unknown>[], hasMore: boolean) => void\n /** What this change means for this view. */\n apply: (change: RealtimeChange) => LiveWindowResult\n}\n\ntype WindowRow = { id: string; key: unknown }\n\nfunction compareValues(a: unknown, b: unknown): number {\n if (a === b) return 0\n const aNull = a === null || a === undefined\n const bNull = b === null || b === undefined\n if (aNull || bNull) return aNull ? (bNull ? 0 : -1) : 1\n if (a instanceof Date && b instanceof Date) return a.getTime() - b.getTime()\n if (typeof a === 'number' && typeof b === 'number') return a - b\n if (typeof a === 'boolean' && typeof b === 'boolean') {\n return Number(a) - Number(b)\n }\n const left = String(a)\n const right = String(b)\n return left < right ? -1 : left > right ? 1 : 0\n}\n\nexport function createLiveWindow(options: LiveWindowOptions): LiveWindow {\n const direction = options.order === 'desc' ? -1 : 1\n let rows: WindowRow[] = []\n let hasMore = false\n\n const compareRows = (a: WindowRow, b: WindowRow): number =>\n direction * (compareValues(a.key, b.key) || compareValues(a.id, b.id))\n\n const rowOf = (record: Record<string, unknown>): WindowRow => ({\n id: String(record.id),\n key: record[options.sort],\n })\n\n /** Where this row belongs among rows that are already in order. */\n const placeOf = (row: WindowRow): number => {\n const index = rows.findIndex((current) => compareRows(row, current) < 0)\n return index === -1 ? rows.length : index\n }\n\n return {\n reset(items, nextHasMore) {\n rows = items.map(rowOf)\n hasMore = nextHasMore\n },\n\n apply(change) {\n const id = String(change.record.id)\n const metadata = change.operationId\n ? { operationId: change.operationId }\n : {}\n const index = rows.findIndex((row) => row.id === id)\n const held = index !== -1\n\n // The row left the view: deleted, or no longer a match for the filters.\n if (\n change.action === 'delete' ||\n !matchesLiveFilters(change.record, options.filters)\n ) {\n if (!held) return { type: 'none' }\n rows.splice(index, 1)\n // A row from below the window must take the free place, and only the\n // database knows which one.\n if (hasMore) return { type: 'resnapshot' }\n return {\n type: 'frames',\n frames: [{ type: 'remove', id, ...metadata }],\n }\n }\n\n if (held) rows.splice(index, 1)\n const row = rowOf(change.record)\n const place = placeOf(row)\n\n if (place >= options.limit) {\n // It sorts below the window. If the client holds it, it moved out.\n if (!held) return { type: 'none' }\n hasMore = true\n return {\n type: 'frames',\n frames: [{ type: 'remove', id, ...metadata }],\n }\n }\n\n rows.splice(place, 0, row)\n const frames: LiveDeltaFrame[] = [\n {\n type: 'upsert',\n record: change.record,\n afterId: place === 0 ? null : rows[place - 1]!.id,\n ...metadata,\n },\n ]\n if (rows.length > options.limit) {\n const evicted = rows.pop()!\n hasMore = true\n frames.push({ type: 'remove', id: evicted.id, ...metadata })\n }\n return { type: 'frames', frames }\n },\n }\n}\n"]}
|
|
@@ -15,6 +15,7 @@ export declare function buildRealtimeApiRouter(publisher: RealtimePublisher | un
|
|
|
15
15
|
record: {
|
|
16
16
|
[x: string]: unknown;
|
|
17
17
|
};
|
|
18
|
+
operationId?: string | undefined;
|
|
18
19
|
} | {
|
|
19
20
|
type: "heartbeat";
|
|
20
21
|
intervalMs: number;
|
|
@@ -24,6 +25,7 @@ export declare function buildRealtimeApiRouter(publisher: RealtimePublisher | un
|
|
|
24
25
|
record: {
|
|
25
26
|
[x: string]: unknown;
|
|
26
27
|
};
|
|
28
|
+
operationId?: string | undefined;
|
|
27
29
|
} | {
|
|
28
30
|
type: "heartbeat";
|
|
29
31
|
intervalMs: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"realtime-router.d.ts","sourceRoot":"","sources":["../../src/api/realtime-router.ts"],"names":[],"mappings":"AACA,OAAO,gCAAgC,CAAA;AACvC,OAAO,KAAK,CAAC,MAAM,SAAS,CAAA;AAE5B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAC/C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;
|
|
1
|
+
{"version":3,"file":"realtime-router.d.ts","sourceRoot":"","sources":["../../src/api/realtime-router.ts"],"names":[],"mappings":"AACA,OAAO,gCAAgC,CAAA;AACvC,OAAO,KAAK,CAAC,MAAM,SAAS,CAAA;AAE5B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAC/C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAyB9D,KAAK,qBAAqB,GAAG;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,CAAA;AAED,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,iBAAiB,GAAG,SAAS,EACxC,MAAM,EAAE,cAAc,EACtB,OAAO,GAAE,qBAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAqCpC;AAED,MAAM,MAAM,iBAAiB,GAAG,WAAW,CACzC,UAAU,CAAC,OAAO,sBAAsB,CAAC,CAC1C,CAAA"}
|
|
@@ -9,6 +9,7 @@ const changeSchema = v.strictObject({
|
|
|
9
9
|
table: v.string(),
|
|
10
10
|
action: v.picklist(['create', 'update', 'delete']),
|
|
11
11
|
record: v.record(v.string(), v.unknown()),
|
|
12
|
+
operationId: v.optional(v.string()),
|
|
12
13
|
});
|
|
13
14
|
const heartbeatSchema = v.strictObject({
|
|
14
15
|
type: v.literal('heartbeat'),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"realtime-router.js","sourceRoot":"","sources":["../../src/api/realtime-router.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAC5C,OAAO,gCAAgC,CAAA;AACvC,OAAO,KAAK,CAAC,MAAM,SAAS,CAAA;AAK5B,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAA;AAC1D,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAE5C,MAAM,YAAY,GAAG,CAAC,CAAC,IAAI,CACzB,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAC1C,CAAC,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CACjE,CAAA;AAED,MAAM,YAAY,GAAG,CAAC,CAAC,YAAY,CAAC;IAClC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IACjB,MAAM,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAClD,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"realtime-router.js","sourceRoot":"","sources":["../../src/api/realtime-router.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAC5C,OAAO,gCAAgC,CAAA;AACvC,OAAO,KAAK,CAAC,MAAM,SAAS,CAAA;AAK5B,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAA;AAC1D,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAE5C,MAAM,YAAY,GAAG,CAAC,CAAC,IAAI,CACzB,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAC1C,CAAC,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CACjE,CAAA;AAED,MAAM,YAAY,GAAG,CAAC,CAAC,YAAY,CAAC;IAClC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IACjB,MAAM,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAClD,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;IACzC,WAAW,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;CACpC,CAAC,CAAA;AAEF,MAAM,eAAe,GAAG,CAAC,CAAC,YAAY,CAAC;IACrC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC;IAC5B,wEAAwE;IACxE,sDAAsD;IACtD,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;CACvB,CAAC,CAAA;AAMF,MAAM,UAAU,sBAAsB,CACpC,SAAwC,EACxC,MAAsB,EACtB,UAAiC,EAAE;IAEnC,IAAI,CAAC,SAAS;QAAE,OAAO,SAAS,CAAA;IAChC,MAAM,OAAO,GAAG,gBAAgB,EAG7B,CAAA;IAEH,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM;SAC3B,KAAK,CAAC;QACL,MAAM,EAAE,KAAK;QACb,IAAI,EAAE,eAAe;QACrB,OAAO,EAAE,+BAA+B;QACxC,IAAI,EAAE,CAAC,UAAU,CAAC;QAClB,WAAW,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE;KACjC,CAAC;SACD,KAAK,CAAC,CAAC,CAAC,YAAY,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC;SAC/C,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,YAAY,EAAE,eAAe,CAAC,CAAC,CAAC,CAAC;SAC/D,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE,EAAE,CACnD,qBAAqB,CACnB,qBAAqB,CACnB,SAAS,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,EACtD;QACE,aAAa,EAAE,KAAK,CAAC,MAAM;QAC3B,MAAM;QACN,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,UAAU,EAAE,OAAO,CAAC,UAAU;KAC/B,CACF,EACD;QACE,UAAU,EAAE,OAAO,CAAC,WAAW;QAC/B,MAAM;KACP,CACF,CACF,CAAA;IAEH,OAAO,EAAE,QAAQ,EAAE,EAAE,OAAO,EAAE,EAAE,CAAA;AAClC,CAAC","sourcesContent":["import { eventIterator } from '@orpc/server'\nimport '@orpc/openapi/extensions/route'\nimport * as v from 'valibot'\n\nimport type { ResolvedAccess } from '../access'\nimport type { RealtimePublisher } from '../realtime/publisher'\n\nimport { filterRealtimeChanges } from '../realtime/filter'\nimport { withRealtimeHeartbeat } from '../realtime/heartbeat'\nimport { createApiBuilder } from './builder'\n\nconst tablesSchema = v.pipe(\n v.union([v.string(), v.array(v.string())]),\n v.transform((value) => (Array.isArray(value) ? value : [value])),\n)\n\nconst changeSchema = v.strictObject({\n table: v.string(),\n action: v.picklist(['create', 'update', 'delete']),\n record: v.record(v.string(), v.unknown()),\n operationId: v.optional(v.string()),\n})\n\nconst heartbeatSchema = v.strictObject({\n type: v.literal('heartbeat'),\n // The cadence this stream sends heartbeats at, so a client can size its\n // dead-stream timeout from the server's real setting.\n intervalMs: v.number(),\n})\n\ntype RealtimeRouterOptions = {\n heartbeatMs?: number\n}\n\nexport function buildRealtimeApiRouter(\n publisher: RealtimePublisher | undefined,\n access: ResolvedAccess,\n options: RealtimeRouterOptions = {},\n) {\n if (!publisher) return undefined\n const builder = createApiBuilder<\n Record<string, unknown>,\n Record<string, unknown>\n >()\n\n const changes = builder.public\n .route({\n method: 'GET',\n path: '/api/realtime',\n summary: 'Subscribe to realtime changes',\n tags: ['realtime'],\n queryStyles: { tables: 'array' },\n })\n .input(v.strictObject({ tables: tablesSchema }))\n .output(eventIterator(v.union([changeSchema, heartbeatSchema])))\n .handler(({ input, context, signal, lastEventId }) =>\n withRealtimeHeartbeat(\n filterRealtimeChanges(\n publisher.subscribe('change', { signal, lastEventId }),\n {\n subscriptions: input.tables,\n access,\n request: context.request,\n getSession: context.getSession,\n },\n ),\n {\n intervalMs: options.heartbeatMs,\n signal,\n },\n ),\n )\n\n return { realtime: { changes } }\n}\n\nexport type RealtimeApiRouter = NonNullable<\n ReturnType<typeof buildRealtimeApiRouter>\n>\n"]}
|
package/dist/api/types.d.ts
CHANGED
|
@@ -41,8 +41,8 @@ type FilterableOf<TAccess, K extends string> = K extends keyof TAccess ? TAccess
|
|
|
41
41
|
type SortableOf<TAccess, K extends string> = K extends keyof TAccess ? TAccess[K] extends {
|
|
42
42
|
sortableColumns: readonly (infer S extends string)[];
|
|
43
43
|
} ? S : 'id' : 'id';
|
|
44
|
-
export type CrudApiRouterFor<TSchema extends Record<string, unknown>, TAccess = undefined> = {
|
|
45
|
-
[K in ExposedApiTables<TSchema, TAccess> as TSchema[K] extends Table ? K : never]: TableCrudProcedures<Extract<TSchema[K], Table>, FilterableOf<TAccess, K>, SortableOf<TAccess, K
|
|
44
|
+
export type CrudApiRouterFor<TSchema extends Record<string, unknown>, TAccess = undefined, TLive extends boolean = false> = {
|
|
45
|
+
[K in ExposedApiTables<TSchema, TAccess> as TSchema[K] extends Table ? K : never]: TableCrudProcedures<Extract<TSchema[K], Table>, FilterableOf<TAccess, K>, SortableOf<TAccess, K>, TLive>;
|
|
46
46
|
};
|
|
47
47
|
type IsProcedure<T> = T extends {
|
|
48
48
|
'~orpc': unknown;
|
package/dist/api/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/api/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAC7C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AAExC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAC3C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA;AAExD,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,UAAU,CAAA;IAChB,OAAO,EAAE;QACP,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAA;KACpC,CAAA;CACF;AAED,KAAK,aAAa,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,cAAc,CAAA;AACpE,KAAK,WAAW,CAAC,CAAC,IAAI,CAAC,SAAS;IAAE,YAAY,EAAE,MAAM,CAAC,CAAA;CAAE,GAAG,CAAC,GAAG,KAAK,CAAA;AAErE,KAAK,YAAY,CAAC,OAAO,IAAI;KAC1B,CAAC,IAAI,MAAM,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,SAAS;QAAE,IAAI,EAAE,KAAK,CAAA;KAAE,GAAG,CAAC,GAAG,KAAK;CAC9E,CAAC,MAAM,OAAO,GAAG,MAAM,CAAC,CAAA;AAEzB,KAAK,YAAY,CAAC,OAAO,EAAE,OAAO,IAAI;KACnC,CAAC,IAAI,MAAM,OAAO,GAAG,MAAM,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,SAAS;QAChE,IAAI,EAAE,KAAK,CAAA;KACZ,GACG,KAAK,GACL,CAAC,SAAS,aAAa,GACrB,OAAO,CAAC,CAAC,CAAC,SAAS;QAAE,eAAe,EAAE,IAAI,CAAA;KAAE,GAC1C,CAAC,SAAS,MAAM,GACd,CAAC,GACD,KAAK,GACP,KAAK,GACP,CAAC;CACR,CAAC,MAAM,OAAO,GAAG,MAAM,OAAO,GAAG,MAAM,CAAC,CAAA;AAEzC,KAAK,cAAc,CAAC,OAAO,IAAI;KAC5B,CAAC,IAAI,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,SAAS,aAAa,GAClD,KAAK,GACL,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS;QAAE,MAAM,EAAE,OAAO,CAAA;KAAE,GACjD,CAAC,GACD,KAAK;CACZ,CAAC,MAAM,OAAO,GAAG,MAAM,CAAC,CAAA;AAEzB,MAAM,MAAM,gBAAgB,CAAC,OAAO,EAAE,OAAO,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,SAAS,CAAC,GAC1E,cAAc,CAAC,OAAO,CAAC,GAEnB,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,GAC9B,OAAO,CACL,cAAc,CAAC,OAAO,CAAC,EACvB,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,OAAO,GAAG,MAAM,CAAC,CACjD,CAAA;AAET;;;;GAIG;AACH,KAAK,YAAY,CAAC,OAAO,EAAE,CAAC,SAAS,MAAM,IAAI,CAAC,SAAS,MAAM,OAAO,GAClE,OAAO,CAAC,CAAC,CAAC,SAAS;IACjB,iBAAiB,EAAE,SAAS,CAAC,MAAM,CAAC,SAAS,MAAM,CAAC,EAAE,CAAA;CACvD,GACC,CAAC,GACD,KAAK,GACP,KAAK,CAAA;AAET,KAAK,UAAU,CAAC,OAAO,EAAE,CAAC,SAAS,MAAM,IAAI,CAAC,SAAS,MAAM,OAAO,GAChE,OAAO,CAAC,CAAC,CAAC,SAAS;IAAE,eAAe,EAAE,SAAS,CAAC,MAAM,CAAC,SAAS,MAAM,CAAC,EAAE,CAAA;CAAE,GACzE,CAAC,GACD,IAAI,GACN,IAAI,CAAA;AAER,MAAM,MAAM,gBAAgB,CAC1B,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACvC,OAAO,GAAG,SAAS,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/api/types.ts"],"names":[],"mappings":"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"}
|