@stratal/framework 0.0.26 → 0.1.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 (47) hide show
  1. package/CHANGELOG.md +596 -0
  2. package/README.md +166 -24
  3. package/dist/access-control/index.d.mts +43 -16
  4. package/dist/access-control/index.d.mts.map +1 -1
  5. package/dist/access-control/index.mjs +5 -5
  6. package/dist/access-control/index.mjs.map +1 -1
  7. package/dist/{access.service-BmDhE-re.mjs → access.service-BjmnWBEo.mjs} +35 -17
  8. package/dist/access.service-BjmnWBEo.mjs.map +1 -0
  9. package/dist/auth/index.d.mts +105 -103
  10. package/dist/auth/index.d.mts.map +1 -1
  11. package/dist/auth/index.mjs +123 -24
  12. package/dist/auth/index.mjs.map +1 -1
  13. package/dist/{auth-context-CGVbiSX3.d.mts → auth-context-C1om3Zsr.d.mts} +1 -2
  14. package/dist/auth-context-C1om3Zsr.d.mts.map +1 -0
  15. package/dist/{auth-context-C8NBfiMa.mjs → auth-context-cNSS1rmh.mjs} +2 -2
  16. package/dist/{auth-context-C8NBfiMa.mjs.map → auth-context-cNSS1rmh.mjs.map} +1 -1
  17. package/dist/auth.service-Onf3JkyL.d.mts +44 -0
  18. package/dist/auth.service-Onf3JkyL.d.mts.map +1 -0
  19. package/dist/context/index.d.mts +4 -5
  20. package/dist/context/index.d.mts.map +1 -1
  21. package/dist/context/index.mjs +1 -1
  22. package/dist/database/index.d.mts +3 -3
  23. package/dist/database/index.mjs +413 -34
  24. package/dist/database/index.mjs.map +1 -1
  25. package/dist/{decorate-B7nr7eBl.mjs → decorate-RQD1h28J.mjs} +1 -1
  26. package/dist/{decorateParam-DwV9LSPl.mjs → decorateParam-xwTkq9gO.mjs} +2 -2
  27. package/dist/{decorateParam-DwV9LSPl.mjs.map → decorateParam-xwTkq9gO.mjs.map} +1 -1
  28. package/dist/factory/index.d.mts +3 -5
  29. package/dist/factory/index.d.mts.map +1 -1
  30. package/dist/factory/index.mjs.map +1 -1
  31. package/dist/guards/index.d.mts +3 -4
  32. package/dist/guards/index.d.mts.map +1 -1
  33. package/dist/guards/index.mjs +4 -4
  34. package/dist/guards/index.mjs.map +1 -1
  35. package/dist/index-e_u1SRyd.d.mts +921 -0
  36. package/dist/index-e_u1SRyd.d.mts.map +1 -0
  37. package/dist/index.d.mts +1 -1
  38. package/dist/{types-CWZ9q74G.d.mts → types-B35g-lXi.d.mts} +18 -4
  39. package/dist/types-B35g-lXi.d.mts.map +1 -0
  40. package/package.json +31 -27
  41. package/dist/access.service-BmDhE-re.mjs.map +0 -1
  42. package/dist/auth-context-CGVbiSX3.d.mts.map +0 -1
  43. package/dist/index-Dt0YUA7r.d.mts +0 -446
  44. package/dist/index-Dt0YUA7r.d.mts.map +0 -1
  45. package/dist/types-CWZ9q74G.d.mts.map +0 -1
  46. package/dist/types-DabF8LGz.d.mts +0 -11
  47. package/dist/types-DabF8LGz.d.mts.map +0 -1
@@ -0,0 +1,921 @@
1
+ import { a as InferAnySchema, n as DeclaredMembers, o as InferConnectionExtensions, r as DefaultConnectionName, s as InferConnectionSchema, t as ConnectionName } from "./types-B35g-lXi.mjs";
2
+ import { AsyncModuleOptions, DynamicModule, ModuleContext, OnInitialize, OnShutdown } from "stratal/module";
3
+ import { ApplicationError, HttpException } from "stratal/errors";
4
+ import { Command } from "stratal/quarry";
5
+ import { AggregateArgs, AllCrudOperations, AnyPlugin, ClientContract, ClientOptions, ComputedFieldsOptions, CountArgs, CreateArgs, CreateManyArgs, DeleteArgs, DeleteManyArgs, EntityMutationHooksDef, FindFirstArgs, FindManyArgs, FindUniqueArgs, GroupByArgs, ModelResult, QueryOptions, RuntimePlugin, SelectSubset, SimplifiedPlainResult, TransactionClientContract, TransactionIsolationLevel, UpdateArgs, UpdateManyArgs, UpsertArgs } from "@zenstackhq/orm";
6
+ import { SchemaDef } from "@zenstackhq/schema";
7
+ import { GetModels, SchemaDef as SchemaDef$1 } from "@zenstackhq/orm/schema";
8
+ import { IEventRegistry } from "stratal/events";
9
+ //#region src/database/database.module.d.ts
10
+ interface DatabaseConnectionConfig<Schema extends SchemaDef = SchemaDef, Name extends ConnectionName = ConnectionName> {
11
+ name: Name;
12
+ schema: Schema;
13
+ dialect: () => ClientOptions<SchemaDef>['dialect'];
14
+ plugins?: AnyPlugin[];
15
+ /**
16
+ * Schema-level @computed field implementations. Required when the schema
17
+ * declares any `@computed` fields. Keyed by uncapitalized model name; values
18
+ * map field name to a Kysely-expression compute callback.
19
+ */
20
+ computedFields?: ComputedFieldsOptions<Schema>;
21
+ }
22
+ interface DatabaseModuleConfig {
23
+ default: DefaultConnectionName;
24
+ connections: DatabaseConnectionConfig[];
25
+ }
26
+ declare class DatabaseModule implements OnInitialize, OnShutdown {
27
+ private readonly services;
28
+ static forRoot(config: DatabaseModuleConfig): DynamicModule;
29
+ static forRootAsync(options: AsyncModuleOptions<DatabaseModuleConfig>): DynamicModule;
30
+ onInitialize(context: ModuleContext): Promise<void>;
31
+ onShutdown(context: ModuleContext): Promise<void>;
32
+ }
33
+ //#endregion
34
+ //#region src/database/pagination/cursor.d.ts
35
+ /**
36
+ * Reading rows from a position in an ordering.
37
+ *
38
+ * A primitive on its own terms, not a variant of anything: it answers "the N
39
+ * rows after this row", and that is the whole contract. There are no page
40
+ * numbers, no total, and no last page, because a cursor names a row rather than
41
+ * an offset into a result set. It requires an ordering — a total one — which is
42
+ * the price of addressing a row at all.
43
+ *
44
+ * What that buys: rows inserted or deleted around the reader do not move the
45
+ * position. `LIMIT/OFFSET` addresses a place in a result set, so a row added
46
+ * above the window shifts everything under it and the reader silently skips one
47
+ * or sees one twice. Nothing here shifts.
48
+ *
49
+ * Knows nothing of HTTP or Inertia. A route can return the result as JSON, a
50
+ * client can walk it, and `ctx.scroll()` can read it — none of which this file
51
+ * is aware of.
52
+ */
53
+ /** Sort direction for one ordering column. */
54
+ type CursorSortOrder = 'asc' | 'desc';
55
+ /**
56
+ * One ordering column. Several are combined left to right, as in SQL.
57
+ *
58
+ * Values may be `undefined` so an array of clause literals keeps its ordinary
59
+ * inferred type — TypeScript widens `[{ updatedAt: 'desc' }, { id: 'desc' }]`
60
+ * to a union whose members carry the other's key as `undefined`. A direction
61
+ * that really is `undefined` at runtime still raises `CursorOrderingError`.
62
+ */
63
+ type CursorOrderBy = Record<string, CursorSortOrder | undefined>;
64
+ interface CursorPageArgs {
65
+ /**
66
+ * The cursor to read from, or `null` to start at the beginning of the
67
+ * ordering. Opaque: it is minted by a previous result and passed back
68
+ * verbatim. Never construct one.
69
+ */
70
+ cursor?: string | null;
71
+ /** Rows per page. The last page may hold fewer. */
72
+ take: number;
73
+ /**
74
+ * Ordering, applied left to right. Required — a cursor addresses a row's
75
+ * position in an ordering, so without one there is no position to address.
76
+ * The set must include {@link CursorPageArgs.uniqueBy}.
77
+ */
78
+ orderBy: CursorOrderBy | CursorOrderBy[];
79
+ /**
80
+ * The ordering column that makes the ordering total. Defaults to `id`.
81
+ *
82
+ * Ordering on a non-unique column alone leaves ties, and a cursor cannot
83
+ * address a position inside a tie — which reintroduces exactly the skipping
84
+ * that cursors exist to prevent. `updatedAt desc` needs an `id desc` after it.
85
+ */
86
+ uniqueBy?: string;
87
+ /** Filter, combined with the cursor's own condition. */
88
+ where?: Record<string, unknown>;
89
+ /**
90
+ * Relations to load, field selection, and fields to drop — handed to the
91
+ * delegate untouched. `select` and `omit` must not drop an ordering column,
92
+ * because the cursor is built from those.
93
+ *
94
+ * Deliberately not mutually exclusive here, unlike on `db.$cursor`, because
95
+ * the delegate decides what they mean: a model delegate already refuses the
96
+ * combination itself (`"select" and "omit" cannot be used together`), and a
97
+ * hand-written one owns its `findMany` and may honour any combination.
98
+ */
99
+ include?: Record<string, unknown>;
100
+ select?: Record<string, unknown>;
101
+ omit?: Record<string, unknown>;
102
+ /** Query parameter name a caller should send the cursor under. Defaults to `cursor`. */
103
+ cursorName?: string;
104
+ }
105
+ /**
106
+ * One page of rows read by cursor.
107
+ *
108
+ * Carries no `path` or page URLs, unlike Laravel's `CursorPaginator`. Building
109
+ * a URL is a routing decision — which route, which of the current query
110
+ * parameters to keep, whether a trailing slash is canonical — and a paginator
111
+ * holds none of that. A route that wants links builds them from its own route
112
+ * helper plus `nextCursor` / `prevCursor`.
113
+ */
114
+ interface CursorPageResult<TRow> {
115
+ /** The rows for this page, in display order. */
116
+ data: TRow[];
117
+ /** Rows requested per page. The last page may hold fewer. */
118
+ perPage: number;
119
+ /** Query parameter name the cursor travels under. */
120
+ cursorName: string;
121
+ /**
122
+ * The cursor this page was read from, or `null` on the first page.
123
+ *
124
+ * Laravel's `CursorPaginator::toArray()` omits both this and the cursor name
125
+ * because it recovers them from the request. A result object has no request,
126
+ * so it carries them.
127
+ */
128
+ cursor: string | null;
129
+ /** Cursor for the following page, or `null` when this is the last. */
130
+ nextCursor: string | null;
131
+ /** Cursor for the preceding page, or `null` when this is the first. */
132
+ prevCursor: string | null;
133
+ }
134
+ /** Whether a cursor points forward (the next rows) or backward (the previous ones). */
135
+ type CursorDirection = 'next' | 'prev';
136
+ interface DecodedCursor {
137
+ /** The ordering column values of the row the cursor addresses. */
138
+ values: Record<string, unknown>;
139
+ direction: CursorDirection;
140
+ }
141
+ /**
142
+ * The query {@link readCursorPage} builds — the single declaration of it, used
143
+ * both as the delegate's parameter type and as the type of the object handed to
144
+ * `findMany`.
145
+ *
146
+ * Every key here must also be a key of a ZenStack `findMany` arg, because that
147
+ * is what makes a model delegate satisfy {@link CursorPageDelegate}: ZenStack
148
+ * types `findMany` as `<T extends FindManyArgs>(args?: SelectSubset<T, …>)`,
149
+ * and `SelectSubset` maps each key of `T` to `never` unless the model's own
150
+ * args declare it. A widened stand-in — `Record<string, unknown>` — infers `T`
151
+ * to itself, collapses the whole parameter to `{ [x: string]: never }`, and no
152
+ * delegate can satisfy it in either direction.
153
+ */
154
+ interface CursorFindManyArgs {
155
+ where?: Record<string, unknown>;
156
+ orderBy?: CursorOrderBy[];
157
+ take?: number;
158
+ include?: Record<string, unknown>;
159
+ select?: Record<string, unknown>;
160
+ omit?: Record<string, unknown>;
161
+ }
162
+ /**
163
+ * The minimum a model delegate has to offer.
164
+ *
165
+ * A ZenStack model delegate satisfies this structurally — `db.thread` is passed
166
+ * as-is — and so does a hand-written `findMany`, which is how a query the ORM
167
+ * cannot express (a `UNION`, a raw statement) is paged by the same primitive.
168
+ */
169
+ interface CursorPageDelegate<TRow> {
170
+ findMany(args: CursorFindManyArgs): PromiseLike<TRow[]>;
171
+ }
172
+ /**
173
+ * Encodes the ordering values of a row plus the direction it points.
174
+ *
175
+ * The whole ordering tuple travels, not a row id, so the cursor still resolves
176
+ * after the row it was built from is deleted — which is the case cursors are
177
+ * for. The direction rides along because a caller sends every cursor under one
178
+ * query parameter name and nothing else distinguishes forward from backward.
179
+ */
180
+ declare function encodeCursor(values: Record<string, unknown>, direction: CursorDirection): string;
181
+ /** Reads a cursor minted by {@link encodeCursor}. Throws on anything else. */
182
+ declare function decodeCursor(cursor: string): DecodedCursor;
183
+ //#endregion
184
+ //#region src/database/pagination/cursor-reader.d.ts
185
+ /**
186
+ * Per-model cursor reading: `db.$cursor.thread.findMany({ … })`.
187
+ *
188
+ * A namespace of its own rather than an argument on the model's own `findMany`,
189
+ * because ZenStack already has a `cursor` there and it means something else. Its
190
+ * `cursor` takes a `WhereUniqueInput` and resolves the row's ordering values by
191
+ * subquery at query time, so the row must still exist, still pass the query's
192
+ * own `where`, and still hold the ordering values it held when the cursor was
193
+ * minted. Two arguments spelled `cursor` on one call, one of which answers an
194
+ * empty page when its row is deleted, is not a distinction a caller should have
195
+ * to hold. In here there is exactly one: {@link CursorReaderArgs} omits
196
+ * ZenStack's `cursor` and the `skip` that goes with it.
197
+ */
198
+ /**
199
+ * The query-relevant options the result and argument types read.
200
+ *
201
+ * `QueryOptions` is what ZenStack's own `SimplifiedPlainResult` and `FindManyArgs` default this
202
+ * slot to. Narrowing `ClientOptions` to the keys they share yields that same type, but reaches
203
+ * it through `keyof ClientOptions`, which instantiates the client-only `computedFields` map
204
+ * across every model and field in the schema for a result that never reads it.
205
+ */
206
+ type Options<Schema extends SchemaDef$1> = QueryOptions<Schema>;
207
+ /** The client keys that address a model — `Post` in the schema is `post` here. */
208
+ type CursorModelKey<Schema extends SchemaDef$1> = Uncapitalize<GetModels<Schema>>;
209
+ /** The schema model a client key addresses. */
210
+ type ModelOf<Schema extends SchemaDef$1, Key extends CursorModelKey<Schema>> = Extract<GetModels<Schema>, { [M in GetModels<Schema>]: Uncapitalize<M> extends Key ? M : never; }[GetModels<Schema>]>;
211
+ /** One row of a model, before `select`/`include` narrow it. */
212
+ type PlainRow<Schema extends SchemaDef$1, Key extends CursorModelKey<Schema>> = SimplifiedPlainResult<Schema, ModelOf<Schema, Key>, {}, Options<Schema>>;
213
+ /**
214
+ * The ordering, restricted to the model's own scalar columns.
215
+ *
216
+ * Narrower than ZenStack's `orderBy` on purpose: a cursor is built from the
217
+ * ordering values of a row, so an ordering that reaches through a relation or
218
+ * sorts on a computed relevance score has no value to carry. Those are
219
+ * unrepresentable here rather than accepted and failed on at runtime.
220
+ */
221
+ type CursorOrderByOf<Schema extends SchemaDef$1, Key extends CursorModelKey<Schema>> = Partial<Record<keyof PlainRow<Schema, Key> & string, CursorSortOrder>>;
222
+ /**
223
+ * A model's `findMany` arguments, with cursor paging in place of offset paging.
224
+ *
225
+ * `skip` goes with ZenStack's `cursor` and addresses a place in a result set,
226
+ * which is the thing a cursor exists not to do. `distinct` changes which row
227
+ * represents a group, so no row has a stable position to address.
228
+ *
229
+ * Dropping an argument here only stops it being declared; what refuses it at a
230
+ * call site is {@link CursorModelReader.findMany} passing this through
231
+ * ZenStack's `SelectSubset`.
232
+ */
233
+ type CursorReaderArgs<Schema extends SchemaDef$1, Key extends CursorModelKey<Schema>> = Omit<FindManyArgs<Schema, ModelOf<Schema, Key>, Options<Schema>>, 'cursor' | 'skip' | 'take' | 'orderBy' | 'distinct'> & {
234
+ /**
235
+ * The cursor to read from, or `null`/absent to start at the beginning of
236
+ * the ordering. Opaque: it is minted by a previous result and passed back
237
+ * verbatim. Never construct one.
238
+ */
239
+ cursor?: string | null;
240
+ /** Rows per page. The last page may hold fewer. */
241
+ take: number;
242
+ /**
243
+ * Ordering, applied left to right. Required, and must end in a unique
244
+ * column — a cursor addresses a row's position in an ordering, and a
245
+ * position inside a tie cannot be addressed.
246
+ */
247
+ orderBy: CursorOrderByOf<Schema, Key> | CursorOrderByOf<Schema, Key>[];
248
+ /** The ordering column that makes the ordering total. Defaults to `id`. */
249
+ uniqueBy?: keyof PlainRow<Schema, Key> & string;
250
+ /** Query parameter name a caller should send the cursor under. Defaults to `cursor`. */
251
+ cursorName?: string;
252
+ };
253
+ /**
254
+ * One model's cursor reader.
255
+ *
256
+ * The argument goes through ZenStack's own `SelectSubset`, which is what a
257
+ * model's `findMany` uses and is the whole of the refusal machinery here. It
258
+ * maps any key the arguments do not declare to `never` — restoring the
259
+ * excess-property refusal a bare type parameter switches off, so `skip`,
260
+ * `distinct` and the native `cursor` are rejected — and it carries ZenStack's
261
+ * conditional messages for `select` with `include` and `select` with `omit`.
262
+ * Restating those exclusions by hand would answer the same mistake with a worse
263
+ * message; rebuilding the argument type without it drops them silently, which
264
+ * is what an earlier revision of this file did.
265
+ *
266
+ * `Args` is still inferred from the call, so `select` narrows the row type.
267
+ */
268
+ interface CursorModelReader<Schema extends SchemaDef$1, Key extends CursorModelKey<Schema>> {
269
+ findMany<Args extends CursorReaderArgs<Schema, Key>>(args: SelectSubset<Args, CursorReaderArgs<Schema, Key>>): Promise<CursorPageResult<SimplifiedPlainResult<Schema, ModelOf<Schema, Key>, Args, Options<Schema>>>>;
270
+ }
271
+ /**
272
+ * Arguments for a page read from a caller-supplied `findMany`.
273
+ *
274
+ * The row type comes from the delegate — the framework has no schema for a
275
+ * `UNION` — and the ordering is keyed to it, so ordering on a column the query
276
+ * does not return is a compile error instead of the `CursorOrderingError` it
277
+ * would raise once the first row came back.
278
+ *
279
+ * No `select`, `include` or `omit`. They are ZenStack model arguments; a
280
+ * caller-written `findMany` decides for itself what to return, and the framework
281
+ * can neither type them against the row nor make the delegate honour them — an
282
+ * option that may be silently ignored is worse than one that is absent. A
283
+ * caller that wants fewer columns selects fewer inside its own `findMany`.
284
+ *
285
+ * `where` stays, and is not a passthrough: the paginator combines it with the
286
+ * keyset condition it builds, so it is how the page is positioned at all. It
287
+ * cannot be typed here because its shape is the delegate's own filter language.
288
+ */
289
+ interface CursorSourceArgs<TRow> {
290
+ /**
291
+ * The cursor to read from, or `null`/absent to start at the beginning of the
292
+ * ordering. Opaque: it is minted by a previous result and passed back
293
+ * verbatim. Never construct one.
294
+ */
295
+ cursor?: string | null;
296
+ /** Rows per page. The last page may hold fewer. */
297
+ take: number;
298
+ /**
299
+ * Ordering, applied left to right. Required, and must end in a unique column
300
+ * — a cursor addresses a row's position in an ordering, and a position inside
301
+ * a tie cannot be addressed.
302
+ */
303
+ orderBy: CursorSourceOrderBy<TRow> | CursorSourceOrderBy<TRow>[];
304
+ /** The ordering column that makes the ordering total. Defaults to `id`. */
305
+ uniqueBy?: CursorSourceColumn<TRow>;
306
+ /** Filter, combined with the cursor's own condition and handed to the delegate. */
307
+ where?: Record<string, unknown>;
308
+ /** Query parameter name a caller should send the cursor under. Defaults to `cursor`. */
309
+ cursorName?: string;
310
+ }
311
+ /**
312
+ * A column of the delegate's row.
313
+ *
314
+ * `DeclaredMembers` strips the index signature that {@link CursorPageDelegate}'s
315
+ * row constraint obliges a caller to write (`interface Row extends
316
+ * Record<string, unknown>`). Without it `keyof` is `string` and every name
317
+ * typechecks, which is the whole of the guarantee here.
318
+ */
319
+ type CursorSourceColumn<TRow> = keyof DeclaredMembers<TRow> & string;
320
+ /** An ordering clause over the delegate's own columns. */
321
+ type CursorSourceOrderBy<TRow> = Partial<Record<CursorSourceColumn<TRow>, CursorSortOrder>>;
322
+ /**
323
+ * Reads a page from something that is not a model — a `UNION`, a raw statement —
324
+ * by supplying the `findMany` yourself.
325
+ *
326
+ * Sits beside the model keys rather than in a separate export because it is the
327
+ * same operation on a different source, and because a free function has no
328
+ * client to belong to: on a transaction client, `tx.$cursor.$from(…)` reads
329
+ * inside the transaction, which an imported function could not do.
330
+ *
331
+ * The `$` prefix is what keeps it from colliding with a model: a client key is
332
+ * an uncapitalized model name, and ZenStack reserves the prefix for members of
333
+ * the client itself.
334
+ */
335
+ interface CursorSourceReader {
336
+ $from<TRow extends Record<string, unknown>>(delegate: CursorPageDelegate<TRow>, args: CursorSourceArgs<NoInfer<TRow>>): Promise<CursorPageResult<TRow>>;
337
+ }
338
+ /** `db.$cursor` — one reader per model in the schema, plus `$from` for anything else. */
339
+ type CursorReader<Schema extends SchemaDef$1> = { [Key in CursorModelKey<Schema>]: CursorModelReader<Schema, Key>; } & CursorSourceReader;
340
+ /** The client members {@link createCursorReader} contributes. */
341
+ interface CursorClientMembers<Schema extends SchemaDef$1> {
342
+ /**
343
+ * Reads one page of a model's rows by cursor, for a list that changes while
344
+ * it is read.
345
+ *
346
+ * @example
347
+ * ```typescript
348
+ * const page = await db.$cursor.thread.findMany({
349
+ * cursor: ctx.query('cursor'),
350
+ * take: 20,
351
+ * orderBy: [{ updatedAt: 'desc' }, { id: 'desc' }],
352
+ * where: { boardId },
353
+ * })
354
+ * ```
355
+ */
356
+ $cursor: CursorReader<Schema>;
357
+ }
358
+ /**
359
+ * Builds the reader eagerly, one entry per model the schema declares.
360
+ *
361
+ * A plain object rather than a `Proxy`: the model list is known here, and a
362
+ * proxy would answer every property — `then`, `constructor`, an inspector's
363
+ * probe — with something that looks like a reader.
364
+ *
365
+ * The delegate is resolved per call, not captured: ZenStack builds a fresh CRUD
366
+ * handler on each model access, and holding one would pin it for the life of
367
+ * the client.
368
+ */
369
+ declare function createCursorReader<Schema extends SchemaDef$1>(client: unknown, schema: Schema): CursorReader<Schema>;
370
+ //#endregion
371
+ //#region src/database/database.service.d.ts
372
+ /**
373
+ * Client members every connection carries, because `createDatabaseService`
374
+ * defines them on every client it builds — the base client and the transaction
375
+ * client alike.
376
+ *
377
+ * Intersected onto the client from outside rather than passed as its
378
+ * `ExtClientMembers` argument: that slot is constrained to
379
+ * `Record<string, unknown>`, and the index signature it forces survives
380
+ * `$transaction`'s `Omit` and collapses every model delegate on a transaction
381
+ * client to `unknown`.
382
+ */
383
+ type BuiltInClientMembers<K extends ConnectionName> = CursorClientMembers<InferConnectionSchema<K>>;
384
+ /** The ZenStack client for a connection, before the built-in members are added. */
385
+ type ConnectionClient<K extends ConnectionName> = ClientContract<InferConnectionSchema<K>, ClientOptions<InferConnectionSchema<K>>, InferConnectionExtensions<K>['extQueryArgs'], InferConnectionExtensions<K>['extClientMembers'], InferConnectionExtensions<K>['extResult']>;
386
+ /** The client a `$transaction` callback receives, carrying the same built-in members. */
387
+ type TransactionService<K extends ConnectionName = DefaultConnectionName> = TransactionClientContract<InferConnectionSchema<K>, ClientOptions<InferConnectionSchema<K>>, InferConnectionExtensions<K>['extQueryArgs'], InferConnectionExtensions<K>['extClientMembers'], InferConnectionExtensions<K>['extResult']> & BuiltInClientMembers<K>;
388
+ /** `{ [I]: Awaited<P[I]> }`, matching what the sequential form of `$transaction` resolves to. */
389
+ type UnwrapPromises<P extends readonly unknown[]> = { [I in keyof P]: Awaited<P[I]>; };
390
+ /**
391
+ * `$transaction`, restated so the interactive form hands back a client carrying
392
+ * the built-in members. ZenStack types its callback with its own
393
+ * `TransactionClientContract`, which knows nothing of them.
394
+ *
395
+ * Intersected ahead of the client rather than replacing its `$transaction`,
396
+ * because overload resolution across an intersection tries the constituents in
397
+ * order and these have to be reached first. `Omit`ing the original is not the
398
+ * alternative it looks like: with an unresolved `SchemaDef` the client's model
399
+ * delegates are an index signature, so `keyof` is `string`, `Omit` collapses
400
+ * every named member into it, and `$queryRawUnsafe` starts resolving to a model
401
+ * delegate.
402
+ *
403
+ * The sequential form is restated unchanged — it is only here because an
404
+ * overload set has to be declared together.
405
+ */
406
+ interface TransactionWithBuiltIns<K extends ConnectionName> {
407
+ $transaction<T>(callback: (tx: TransactionService<K>) => Promise<T>, options?: {
408
+ isolationLevel?: TransactionIsolationLevel;
409
+ }): Promise<T>;
410
+ $transaction<P extends readonly PromiseLike<unknown>[]>(arg: [...P], options?: {
411
+ isolationLevel?: TransactionIsolationLevel;
412
+ }): Promise<UnwrapPromises<P>>;
413
+ }
414
+ /**
415
+ * DatabaseService type
416
+ *
417
+ * Each connection has its own schema and plugin extensions.
418
+ * Plugin extension types are automatically inferred from `StratalDatabase.plugins`.
419
+ *
420
+ * @example
421
+ * ```typescript
422
+ * // Typed to default connection (includes plugin extensions)
423
+ * constructor(@inject(DI_TOKENS.Database) private db: DatabaseService) {}
424
+ *
425
+ * // Typed to a specific named connection
426
+ * constructor(@InjectDB('analytics') private analytics: DatabaseService<'analytics'>) {}
427
+ * ```
428
+ */
429
+ type DatabaseService<K extends ConnectionName = DefaultConnectionName> = TransactionWithBuiltIns<K> & ConnectionClient<K> & BuiltInClientMembers<K>;
430
+ //#endregion
431
+ //#region src/database/database.tokens.d.ts
432
+ declare const DATABASE_TOKENS: {
433
+ readonly Options: symbol;
434
+ readonly Services: symbol;
435
+ };
436
+ declare function connectionSymbol(name: ConnectionName): symbol;
437
+ //#endregion
438
+ //#region src/database/pool.d.ts
439
+ /**
440
+ * Env binding the framework reads to decide DB connection topology. Set by
441
+ * `@stratal/testing` (the test harness runs against a DIRECT Postgres with no
442
+ * Hyperdrive in front); never set in dev / staging / production.
443
+ */
444
+ declare const DB_SHARED_POOL_ENV = "STRATAL_DB_SHARED_POOL";
445
+ /**
446
+ * Build the lazy pool factory a consumer hands to its ZenStack dialect,
447
+ * choosing the connection topology from the environment instead of hard-coding
448
+ * one. The consumer writes `dialect: () => new PostgresDialect({ pool })` where
449
+ * `pool = createPoolFactory(env, () => new Pool(poolConfig))` — so the prod-vs-
450
+ * test decision lives here, in the framework, not as an `IS_TEST` branch in app
451
+ * config.
452
+ *
453
+ * - **Default (dev / staging / production):** returns a FRESH pool on every
454
+ * call. The dialect is rebuilt per request resolution, so each request owns
455
+ * its own pool/socket — mandatory on workerd, where a pool opened in one
456
+ * request's I/O context cannot be reused by another (the cross-request I/O is
457
+ * cancelled and the request hangs forever). Hyperdrive fronts these pools and
458
+ * multiplexes the real server connections, so they never accumulate.
459
+ * - **Shared (`DB_SHARED_POOL_ENV === 'true'`):** memoizes ONE pool per
460
+ * connection, reused across every resolution. `@stratal/testing` sets this
461
+ * because the test harness hits a direct Postgres with no Hyperdrive — a fresh
462
+ * pool per resolution would exhaust `max_connections` across parallel test
463
+ * files. One shared pool per connection mirrors what Hyperdrive does in prod.
464
+ *
465
+ * This single pool is forced **persistent** ({@link withPersistentConnection}):
466
+ * its idle reaper is disabled regardless of the config `makePool` passed. A pool
467
+ * reused for the whole worker run must never idle-close — a non-zero
468
+ * `idleTimeoutMillis` reaps its connection between operations, and under real
469
+ * network latency (a CI Postgres service, not a local socket) that eviction
470
+ * races in-flight and subsequent queries → "Connection terminated unexpectedly",
471
+ * cascading into half-applied writes and cross-test row leakage. It passes on a
472
+ * fast local socket and only surfaces under latency, so consumers can't be
473
+ * trusted to configure it right — the shared branch enforces it. The consumer's
474
+ * own idle settings still apply to the fresh-per-resolution prod pools (below).
475
+ *
476
+ * Either way the pool is created LAZILY: `makePool` is invoked by Kysely on the
477
+ * first query, inside the request's I/O context — never at module-eval / global
478
+ * scope, which workerd forbids ("Disallowed operation within global scope").
479
+ */
480
+ declare function createPoolFactory<TPool>(env: object, makePool: () => TPool): () => Promise<TPool>;
481
+ //#endregion
482
+ //#region src/database/decorators/inject-db.decorator.d.ts
483
+ declare function InjectDB(name: ConnectionName): ParameterDecorator;
484
+ //#endregion
485
+ //#region src/database/errors/cursor-model-unavailable.error.d.ts
486
+ /**
487
+ * Raised when `db.$cursor.<model>` cannot reach a model delegate on the client
488
+ * it was built for — the schema declares the model, the client does not answer
489
+ * for it.
490
+ *
491
+ * This is a **programming error**, not bad input: the reader is built from the
492
+ * schema's own model list, so reaching it means the client and the schema it was
493
+ * given have come apart — a model sliced out of the client's options, or a
494
+ * hand-assembled client. No request recovers from it and no retry helps. It
495
+ * deliberately carries no HTTP status, so it is never mistaken for something the
496
+ * client sent wrong.
497
+ *
498
+ * `model` names the client key at fault, and is reported to observability so the
499
+ * raise site stays distinguishable without matching on message text.
500
+ */
501
+ declare class CursorModelUnavailableError extends ApplicationError {
502
+ readonly model: string;
503
+ constructor(model: string);
504
+ reportContext(): Record<string, unknown> | undefined;
505
+ }
506
+ //#endregion
507
+ //#region src/database/errors/cursor-ordering.error.d.ts
508
+ /**
509
+ * Raised when a `$cursor` read is asked for an ordering it cannot address a row
510
+ * in: no `orderBy`, a clause with no `asc`/`desc` direction, an ordering
511
+ * without the unique column that breaks ties, or an ordering column that is
512
+ * absent from the returned row or null on it.
513
+ *
514
+ * This is a **programming error**, not bad input — no request can recover from
515
+ * it and no retry helps, because the query as written cannot produce a stable
516
+ * position. Handlers should let it surface as a `500` and report it; the fix is
517
+ * always in the caller's `orderBy`, `uniqueBy` or `select`. It deliberately
518
+ * carries no HTTP status, so it is never mistaken for something the client sent
519
+ * wrong.
520
+ *
521
+ * `field` names the ordering column at fault where one is identifiable, and is
522
+ * reported to observability so the raise sites stay distinguishable without
523
+ * matching on message text.
524
+ */
525
+ declare class CursorOrderingError extends ApplicationError {
526
+ readonly field?: string | undefined;
527
+ constructor(message: string, field?: string | undefined);
528
+ reportContext(): Record<string, unknown> | undefined;
529
+ }
530
+ //#endregion
531
+ //#region src/database/errors/malformed-cursor.error.d.ts
532
+ /**
533
+ * Raised when a pagination cursor cannot be read: it is not the base64url
534
+ * payload `$cursor` mints, or it decodes to something that is not a cursor.
535
+ *
536
+ * This is **bad input**, not a broken query. A cursor travels in a query string,
537
+ * so a truncated link, a hand-edited URL, or one minted by an older build all
538
+ * land here, and the request itself is still answerable. It carries its own
539
+ * `400`, so a handler lets it surface rather than catching it to serve the
540
+ * first page — the same refusal any other damaged parameter gets.
541
+ */
542
+ declare class MalformedCursorError extends HttpException {
543
+ constructor(cause?: unknown);
544
+ }
545
+ //#endregion
546
+ //#region src/database/errors/record-not-found.error.d.ts
547
+ declare class RecordNotFoundError extends HttpException {
548
+ readonly details?: string | undefined;
549
+ constructor(details?: string | undefined, cause?: unknown);
550
+ }
551
+ //#endregion
552
+ //#region src/database/errors/unique-constraint.error.d.ts
553
+ declare class UniqueConstraintError extends HttpException {
554
+ readonly fields?: string[] | undefined;
555
+ constructor(fields?: string[] | undefined, cause?: unknown);
556
+ }
557
+ //#endregion
558
+ //#region src/database/errors/from-zenstack-error.d.ts
559
+ declare function fromZenStackError(error: unknown): ApplicationError;
560
+ //#endregion
561
+ //#region src/database/event-types.d.ts
562
+ /**
563
+ * Event phase: before or after operation
564
+ */
565
+ type EventPhase = 'before' | 'after';
566
+ /**
567
+ * All database operations that can trigger events
568
+ */
569
+ type DatabaseOperation = AllCrudOperations;
570
+ /**
571
+ * Distributive helper — extracts model names from each schema member individually.
572
+ * Using a naked type parameter ensures TypeScript distributes over unions:
573
+ * `_ExtractModelNames<A | B>` = `_ExtractModelNames<A> | _ExtractModelNames<B>`
574
+ */
575
+ type _ExtractModelNames<S> = S extends {
576
+ models: infer M;
577
+ } ? Extract<keyof M, string> : never;
578
+ /**
579
+ * Model names derived from the shared database schema.
580
+ * Distributes over all schema types so models from every connection are included.
581
+ */
582
+ type ModelName = _ExtractModelNames<InferAnySchema>;
583
+ /**
584
+ * Database event names with all supported patterns.
585
+ */
586
+ type DatabaseEventName = `${EventPhase}.${ModelName}.${DatabaseOperation}` | `${EventPhase}.${ModelName}` | `${EventPhase}.${DatabaseOperation}` | EventPhase;
587
+ /**
588
+ * Map operation name to ZenStack Args type for a given schema and model
589
+ */
590
+ type OperationArgsMap<S extends SchemaDef, M extends Extract<keyof S['models'], string>, O extends DatabaseOperation> = O extends 'create' ? CreateArgs<S, M> : O extends 'createMany' ? CreateManyArgs<S, M> : O extends 'update' ? UpdateArgs<S, M> : O extends 'updateMany' ? UpdateManyArgs<S, M> : O extends 'delete' ? DeleteArgs<S, M> : O extends 'deleteMany' ? DeleteManyArgs<S, M> : O extends 'findUnique' ? FindUniqueArgs<S, M> : O extends 'findFirst' ? FindFirstArgs<S, M> : O extends 'findMany' ? FindManyArgs<S, M> : O extends 'upsert' ? UpsertArgs<S, M> : O extends 'count' ? CountArgs<S, M> : O extends 'aggregate' ? AggregateArgs<S, M> : O extends 'groupBy' ? GroupByArgs<S, M> : never;
591
+ /**
592
+ * Distributive helper — resolves data/where args for a model against each schema individually.
593
+ */
594
+ type _ExtractData<S, M extends string, O extends DatabaseOperation> = S extends SchemaDef ? M extends Extract<keyof S['models'], string> ? OperationArgsMap<S, M, O> extends {
595
+ data: infer D;
596
+ } ? D : OperationArgsMap<S, M, O> extends {
597
+ where: infer W;
598
+ } ? W : OperationArgsMap<S, M, O> : never : never;
599
+ /**
600
+ * Extract the data/where property from operation args.
601
+ * Distributes over all schemas to find the matching model.
602
+ */
603
+ type GetData<M extends ModelName, O extends DatabaseOperation> = _ExtractData<InferAnySchema, M, O> extends never ? unknown : _ExtractData<InferAnySchema, M, O>;
604
+ /**
605
+ * Distributive helper — resolves result type for a model against each schema individually.
606
+ */
607
+ type _ExtractResult<S, M extends string, O extends DatabaseOperation> = S extends SchemaDef ? M extends Extract<keyof S['models'], string> ? O extends 'findMany' | 'createMany' | 'updateMany' | 'deleteMany' ? ModelResult<S, M>[] : O extends 'count' ? number : ModelResult<S, M> : never : never;
608
+ /**
609
+ * Extract result type for a model operation.
610
+ * Distributes over all schemas to find the matching model.
611
+ */
612
+ type GetResult<M extends ModelName, O extends DatabaseOperation> = _ExtractResult<InferAnySchema, M, O> extends never ? unknown : _ExtractResult<InferAnySchema, M, O>;
613
+ /**
614
+ * Verb suffix for entity-mutation events (`entity.{Model}.{verb}`).
615
+ */
616
+ type EntityMutationVerb = 'created' | 'updated' | 'deleted';
617
+ /**
618
+ * Entity-mutation event names with all supported patterns.
619
+ *
620
+ * Unlike `before.*`/`after.*` (raw query args/result), entity events carry
621
+ * full entity snapshots: `created` has `after`, `updated` has `before` and
622
+ * `after`, `deleted` has `before`. The pre-mutation snapshot is loaded inside
623
+ * the mutation's transaction, and only when a listener matches — a wildcard
624
+ * subscription (`entity`) makes every model pay that pre-read, so subscribe
625
+ * per model when cost matters.
626
+ */
627
+ type EntityEventName = `entity.${ModelName}.${EntityMutationVerb}` | `entity.${ModelName}` | `entity.${EntityMutationVerb}` | 'entity';
628
+ /**
629
+ * Distributive helper — resolves the full entity shape for a model.
630
+ */
631
+ type _ExtractEntity<S, M extends string> = S extends SchemaDef ? M extends Extract<keyof S['models'], string> ? ModelResult<S, M> : never : never;
632
+ /**
633
+ * Full entity snapshot type for a model, across all connections' schemas.
634
+ */
635
+ type GetEntity<M extends ModelName> = _ExtractEntity<InferAnySchema, M> extends never ? unknown : _ExtractEntity<InferAnySchema, M>;
636
+ interface EntityCreatedEventContext<M extends ModelName> {
637
+ model: M;
638
+ action: 'created';
639
+ before: undefined;
640
+ after: GetEntity<M>;
641
+ }
642
+ interface EntityUpdatedEventContext<M extends ModelName> {
643
+ model: M;
644
+ action: 'updated';
645
+ before: GetEntity<M>;
646
+ after: GetEntity<M>;
647
+ }
648
+ interface EntityDeletedEventContext<M extends ModelName> {
649
+ model: M;
650
+ action: 'deleted';
651
+ before: GetEntity<M>;
652
+ after: undefined;
653
+ }
654
+ /** Context for model wildcard subscriptions (e.g. "entity.User") */
655
+ interface EntityModelWildcardContext<M extends ModelName> {
656
+ model: M;
657
+ action: EntityMutationVerb;
658
+ before: GetEntity<M> | undefined;
659
+ after: GetEntity<M> | undefined;
660
+ }
661
+ /** Context for verb wildcard subscriptions (e.g. "entity.updated") */
662
+ interface EntityVerbWildcardContext<V extends EntityMutationVerb> {
663
+ model: ModelName;
664
+ action: V;
665
+ before: V extends 'created' ? undefined : unknown;
666
+ after: V extends 'deleted' ? undefined : unknown;
667
+ }
668
+ /** Context for the global wildcard subscription ("entity") */
669
+ interface EntityWildcardContext {
670
+ model: ModelName;
671
+ action: EntityMutationVerb;
672
+ before: unknown;
673
+ after: unknown;
674
+ }
675
+ type EntityEventContext<E extends string> = E extends `entity.${infer M extends ModelName}.created` ? EntityCreatedEventContext<M> : E extends `entity.${infer M extends ModelName}.updated` ? EntityUpdatedEventContext<M> : E extends `entity.${infer M extends ModelName}.deleted` ? EntityDeletedEventContext<M> : E extends `entity.${infer M extends ModelName}` ? EntityModelWildcardContext<M> : E extends `entity.${infer V extends EntityMutationVerb}` ? EntityVerbWildcardContext<V> : EntityWildcardContext;
676
+ /**
677
+ * Mapped type producing all entity-mutation event name → context pairs.
678
+ */
679
+ type EntityEvents = { [E in EntityEventName]: EntityEventContext<E>; };
680
+ /**
681
+ * Parse event string into structured type for discriminated unions
682
+ */
683
+ type ParseEvent<E extends string> = E extends `${infer Phase extends EventPhase}.${infer Model extends ModelName}.${infer Op extends DatabaseOperation}` ? {
684
+ phase: Phase;
685
+ model: Model;
686
+ operation: Op;
687
+ type: 'exact';
688
+ } : E extends `${infer Phase extends EventPhase}.${infer Second}` ? Second extends ModelName ? {
689
+ phase: Phase;
690
+ model: Second;
691
+ type: 'model-wildcard';
692
+ } : Second extends DatabaseOperation ? {
693
+ phase: Phase;
694
+ operation: Second;
695
+ type: 'operation-wildcard';
696
+ } : never : E extends EventPhase ? {
697
+ phase: E;
698
+ type: 'phase-wildcard';
699
+ } : never;
700
+ /** Base context fields present in all events */
701
+ interface BaseEventContext {}
702
+ /** Context for exact database events (e.g., "after.User.create") */
703
+ interface ExactDatabaseEventContext<M extends ModelName, O extends DatabaseOperation, Phase extends EventPhase> extends BaseEventContext {
704
+ data: Phase extends 'before' ? GetData<M, O> : Readonly<GetData<M, O>>;
705
+ result: Phase extends 'after' ? GetResult<M, O> : undefined;
706
+ }
707
+ /** Context for model wildcard events (e.g., "after.User") */
708
+ interface ModelWildcardEventContext<Phase extends EventPhase> extends BaseEventContext {
709
+ operation: DatabaseOperation;
710
+ data: Phase extends 'before' ? unknown : Readonly<unknown>;
711
+ result: Phase extends 'after' ? unknown : undefined;
712
+ }
713
+ /** Context for operation wildcard events (e.g., "after.create") */
714
+ interface OperationWildcardEventContext<Phase extends EventPhase> extends BaseEventContext {
715
+ model: ModelName;
716
+ data: Phase extends 'before' ? unknown : Readonly<unknown>;
717
+ result: Phase extends 'after' ? unknown : undefined;
718
+ }
719
+ /** Context for phase wildcard events (e.g., "after" or "before") */
720
+ interface PhaseWildcardEventContext<Phase extends EventPhase> extends BaseEventContext {
721
+ model: ModelName;
722
+ operation: DatabaseOperation;
723
+ data: Phase extends 'before' ? unknown : Readonly<unknown>;
724
+ result: Phase extends 'after' ? unknown : undefined;
725
+ }
726
+ /**
727
+ * Type-safe event context with discriminated unions.
728
+ */
729
+ type DatabaseEventContext<E extends string> = ParseEvent<E> extends {
730
+ phase: infer P extends EventPhase;
731
+ model: infer M extends ModelName;
732
+ operation: infer O extends DatabaseOperation;
733
+ type: 'exact';
734
+ } ? ExactDatabaseEventContext<M, O, P> : ParseEvent<E> extends {
735
+ phase: infer P extends EventPhase;
736
+ model: infer _M extends ModelName;
737
+ type: 'model-wildcard';
738
+ } ? ModelWildcardEventContext<P> : ParseEvent<E> extends {
739
+ phase: infer P extends EventPhase;
740
+ operation: infer _O extends DatabaseOperation;
741
+ type: 'operation-wildcard';
742
+ } ? OperationWildcardEventContext<P> : ParseEvent<E> extends {
743
+ phase: infer P extends EventPhase;
744
+ type: 'phase-wildcard';
745
+ } ? PhaseWildcardEventContext<P> : BaseEventContext;
746
+ /**
747
+ * Mapped type that produces all database event name to context pairs.
748
+ *
749
+ * Used to augment core's `CustomEventRegistry`:
750
+ *
751
+ * @example
752
+ * ```typescript
753
+ * declare module 'stratal/events' {
754
+ * interface CustomEventRegistry extends DatabaseEvents {}
755
+ * }
756
+ * ```
757
+ */
758
+ type DatabaseEvents = { [E in DatabaseEventName]: DatabaseEventContext<E>; };
759
+ declare module 'stratal/events' {
760
+ interface CustomEventRegistry extends DatabaseEvents, EntityEvents {}
761
+ }
762
+ //#endregion
763
+ //#region src/database/i18n/en.d.ts
764
+ declare const databaseMessages: {
765
+ readonly en: {
766
+ readonly connectionNameRequired: 'Connection name is required';
767
+ readonly defaultConnectionRequired: 'Default connection name is required';
768
+ readonly connectionRequired: 'At least one connection is required';
769
+ readonly duplicateConnections: 'Duplicate connection names found';
770
+ readonly defaultConnectionNotFound: 'Default connection not found in connections';
771
+ };
772
+ };
773
+ declare module 'stratal/i18n' {
774
+ interface AppMessageNamespaces {
775
+ database: typeof databaseMessages['en'];
776
+ }
777
+ }
778
+ //#endregion
779
+ //#region src/database/plugins/error-handler.plugin.d.ts
780
+ /**
781
+ * ZenStack runtime plugin that transforms ORM errors into ApplicationError instances.
782
+ *
783
+ * @example
784
+ * ```typescript
785
+ * super(schema, {
786
+ * dialect: new PostgresDialect({ pool }),
787
+ * plugins: [new ErrorHandlerPlugin()]
788
+ * })
789
+ * ```
790
+ */
791
+ declare class ErrorHandlerPlugin implements RuntimePlugin<SchemaDef$1, Record<string, unknown>, Record<string, unknown>, {}> {
792
+ readonly id = "error-handler";
793
+ onQuery: ({ args, proceed }: {
794
+ args: Record<string, unknown> | undefined;
795
+ proceed: (args: Record<string, unknown> | undefined) => Promise<unknown>;
796
+ }) => Promise<unknown>;
797
+ }
798
+ //#endregion
799
+ //#region src/database/plugins/event-emitter.plugin.d.ts
800
+ interface EventEmitterPluginOptions {
801
+ eventRegistry: IEventRegistry;
802
+ }
803
+ /**
804
+ * ZenStack runtime plugin that emits before/after events for database operations.
805
+ *
806
+ * Emits events in the format:
807
+ * - `before.{Model}.{operation}` - Before the database operation
808
+ * - `after.{Model}.{operation}` - After the database operation
809
+ *
810
+ * Additionally emits entity-mutation events carrying full entity snapshots:
811
+ * - `entity.{Model}.created` - `{ after }`
812
+ * - `entity.{Model}.updated` - `{ before, after }`
813
+ * - `entity.{Model}.deleted` - `{ before }`
814
+ *
815
+ * Entity events are listener-driven: the pre-mutation snapshot is only
816
+ * loaded (inside the mutation's transaction) when `hasListeners()` reports
817
+ * a matching subscription, so models nobody observes pay no cost. Note that
818
+ * a wildcard subscription (`entity`) therefore makes every model pay the
819
+ * pre-read — subscribe per model when cost matters.
820
+ *
821
+ * @example
822
+ * ```typescript
823
+ * super(schema, {
824
+ * dialect: new PostgresDialect({ pool }),
825
+ * plugins: [
826
+ * new EventEmitterPlugin({
827
+ * eventRegistry,
828
+ * })
829
+ * ]
830
+ * })
831
+ * ```
832
+ */
833
+ declare class EventEmitterPlugin implements RuntimePlugin<SchemaDef$1, Record<string, unknown>, Record<string, unknown>, {}> {
834
+ private options;
835
+ readonly id = "event-emitter";
836
+ constructor(options: EventEmitterPluginOptions);
837
+ onEntityMutation: EntityMutationHooksDef<SchemaDef$1>;
838
+ onQuery: ({ model, operation, args, proceed }: {
839
+ model: string;
840
+ operation: string;
841
+ args: Record<string, unknown> | undefined;
842
+ proceed: (args: Record<string, unknown> | undefined) => Promise<unknown>;
843
+ }) => Promise<unknown>;
844
+ }
845
+ //#endregion
846
+ //#region src/database/plugins/schema-switcher.d.ts
847
+ /**
848
+ * Switches the active schema on a ZenStack/Kysely database client by mutating
849
+ * `$schema.provider.defaultSchema`. This causes ZenStack's QueryNameMapper to
850
+ * generate fully-qualified table references (e.g. `"tenant_123"."User"`).
851
+ *
852
+ * Must be called BEFORE any queries are made on the client.
853
+ *
854
+ * Note: The ZenStack RuntimePlugin `onQuery` hook fires after table names are
855
+ * already resolved, so a plugin-based approach cannot set the schema prefix.
856
+ * Direct client mutation is the only supported method.
857
+ */
858
+ declare class SchemaSwitcher {
859
+ static apply<T>(client: T, schemaName: string): T;
860
+ }
861
+ //#endregion
862
+ //#region src/database/commands/zenstack.command.d.ts
863
+ /**
864
+ * Base command for ZenStack CLI wrappers.
865
+ * Uses execFileSync with array arguments to prevent shell injection.
866
+ */
867
+ declare abstract class ZenStackCommand extends Command {
868
+ protected zenstack(args: string[]): Promise<number>;
869
+ }
870
+ //#endregion
871
+ //#region src/database/commands/db-generate.command.d.ts
872
+ declare class DbGenerateCommand extends ZenStackCommand {
873
+ static command: string;
874
+ static description: string;
875
+ handle(): Promise<number>;
876
+ }
877
+ //#endregion
878
+ //#region src/database/commands/db-pull.command.d.ts
879
+ declare class DbPullCommand extends ZenStackCommand {
880
+ static command: string;
881
+ static description: string;
882
+ handle(): Promise<number>;
883
+ }
884
+ //#endregion
885
+ //#region src/database/commands/db-push.command.d.ts
886
+ declare class DbPushCommand extends ZenStackCommand {
887
+ static command: string;
888
+ static description: string;
889
+ handle(): Promise<number>;
890
+ }
891
+ //#endregion
892
+ //#region src/database/commands/migrate-deploy.command.d.ts
893
+ declare class MigrateDeployCommand extends ZenStackCommand {
894
+ static command: string;
895
+ static description: string;
896
+ handle(): Promise<number>;
897
+ }
898
+ //#endregion
899
+ //#region src/database/commands/migrate-dev.command.d.ts
900
+ declare class MigrateDevCommand extends ZenStackCommand {
901
+ static command: string;
902
+ static description: string;
903
+ handle(): Promise<number>;
904
+ }
905
+ //#endregion
906
+ //#region src/database/commands/migrate-reset.command.d.ts
907
+ declare class MigrateResetCommand extends ZenStackCommand {
908
+ static command: string;
909
+ static description: string;
910
+ handle(): Promise<number>;
911
+ }
912
+ //#endregion
913
+ //#region src/database/commands/migrate-status.command.d.ts
914
+ declare class MigrateStatusCommand extends ZenStackCommand {
915
+ static command: string;
916
+ static description: string;
917
+ handle(): Promise<number>;
918
+ }
919
+ //#endregion
920
+ export { CursorPageDelegate as $, CursorOrderingError as A, CursorModelKey as B, GetResult as C, UniqueConstraintError as D, fromZenStackError as E, DATABASE_TOKENS as F, CursorSourceArgs as G, CursorOrderByOf as H, connectionSymbol as I, CursorSourceReader as J, CursorSourceColumn as K, DatabaseService as L, InjectDB as M, DB_SHARED_POOL_ENV as N, RecordNotFoundError as O, createPoolFactory as P, CursorPageArgs as Q, TransactionService as R, GetEntity as S, ParseEvent as T, CursorReader as U, CursorModelReader as V, CursorReaderArgs as W, CursorFindManyArgs as X, createCursorReader as Y, CursorOrderBy as Z, EntityEventName as _, DbPushCommand as a, DatabaseModule as at, EventPhase as b, ZenStackCommand as c, EventEmitterPluginOptions as d, CursorPageResult as et, ErrorHandlerPlugin as f, DatabaseOperation as g, DatabaseEvents as h, MigrateDeployCommand as i, DatabaseConnectionConfig as it, CursorModelUnavailableError as j, MalformedCursorError as k, SchemaSwitcher as l, DatabaseEventName as m, MigrateResetCommand as n, decodeCursor as nt, DbPullCommand as o, DatabaseModuleConfig as ot, databaseMessages as p, CursorSourceOrderBy as q, MigrateDevCommand as r, encodeCursor as rt, DbGenerateCommand as s, MigrateStatusCommand as t, CursorSortOrder as tt, EventEmitterPlugin as u, EntityEvents as v, ModelName as w, GetData as x, EntityMutationVerb as y, CursorClientMembers as z };
921
+ //# sourceMappingURL=index-e_u1SRyd.d.mts.map