@biref/scanner 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +1109 -0
- package/bin/biref.mjs +7 -0
- package/dist/codegen/cli.js +7435 -0
- package/dist/codegen/cli.js.map +1 -0
- package/dist/index.cjs +2271 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1507 -0
- package/dist/index.d.ts +1507 -0
- package/dist/index.js +2248 -0
- package/dist/index.js.map +1 -0
- package/package.json +79 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,1507 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural type for the database client the Postgres adapter consumes.
|
|
3
|
+
*
|
|
4
|
+
* Declares a single `query` method that takes a SQL string plus
|
|
5
|
+
* optional parameters and returns an object with a `rows` array.
|
|
6
|
+
* `pg.Client`, `pg.Pool`, and any structurally compatible library
|
|
7
|
+
* satisfy the shape without the SDK importing `pg`.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* import pg from 'pg';
|
|
12
|
+
* const client = new pg.Client({ ... });
|
|
13
|
+
* await client.connect();
|
|
14
|
+
* postgresAdapter.create(client);
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
interface PostgresClient {
|
|
18
|
+
query<TRow = unknown>(text: string, params?: readonly unknown[]): Promise<{
|
|
19
|
+
rows: TRow[];
|
|
20
|
+
}>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The paradigm of an underlying data store.
|
|
25
|
+
*
|
|
26
|
+
* Used as a discriminator on `DataModel` so consumers can branch on the
|
|
27
|
+
* paradigm without inspecting adapter-specific details. New paradigms can
|
|
28
|
+
* be added without breaking existing consumers; fall back to `'unknown'`
|
|
29
|
+
* if you do not recognize a value.
|
|
30
|
+
*/
|
|
31
|
+
type EngineKind = 'relational' | 'document' | 'key-value' | 'graph' | 'wide-column' | 'time-series' | 'unknown';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Kind of constraint declared on an entity.
|
|
35
|
+
*
|
|
36
|
+
* Categories:
|
|
37
|
+
* 'unique': enforces uniqueness on a set of fields
|
|
38
|
+
* 'check': evaluates an expression that must be true for every row
|
|
39
|
+
* 'exclusion': pg-style exclusion constraint
|
|
40
|
+
* 'custom': paradigm-specific constraint that does not fit the above
|
|
41
|
+
*/
|
|
42
|
+
type ConstraintKind = 'unique' | 'check' | 'exclusion' | 'custom';
|
|
43
|
+
/**
|
|
44
|
+
* A constraint declared on an entity.
|
|
45
|
+
*
|
|
46
|
+
* Excludes the primary key, which is exposed through the entity's
|
|
47
|
+
* `identifier` instead. Excludes foreign keys, which are exposed through
|
|
48
|
+
* `relationships`.
|
|
49
|
+
*
|
|
50
|
+
* Fields:
|
|
51
|
+
* name: constraint name as reported by the data store
|
|
52
|
+
* kind: category of constraint
|
|
53
|
+
* fields: field names the constraint applies to
|
|
54
|
+
* expression: textual definition (e.g. CHECK clause body) or null
|
|
55
|
+
*/
|
|
56
|
+
interface Constraint {
|
|
57
|
+
readonly name: string;
|
|
58
|
+
readonly kind: ConstraintKind;
|
|
59
|
+
readonly fields: readonly string[];
|
|
60
|
+
readonly expression: string | null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Normalized category of a field's type, abstracted from any specific
|
|
65
|
+
* data store's native type system.
|
|
66
|
+
*
|
|
67
|
+
* Adapters are responsible for mapping their native types onto these
|
|
68
|
+
* categories. Consumers should branch on `category` rather than
|
|
69
|
+
* `nativeType` for portable, paradigm-neutral logic (e.g. "render a
|
|
70
|
+
* date picker for `date` fields").
|
|
71
|
+
*/
|
|
72
|
+
type FieldTypeCategory = 'string' | 'integer' | 'decimal' | 'boolean' | 'date' | 'timestamp' | 'time' | 'json' | 'uuid' | 'binary' | 'enum' | 'array' | 'reference' | 'unknown';
|
|
73
|
+
/**
|
|
74
|
+
* A field's type, normalized across data store paradigms.
|
|
75
|
+
*
|
|
76
|
+
* `nativeType` is the raw string the adapter reported, kept for
|
|
77
|
+
* transparency and edge cases. Optional properties carry additional
|
|
78
|
+
* structure when meaningful for the category.
|
|
79
|
+
*/
|
|
80
|
+
interface FieldType {
|
|
81
|
+
readonly category: FieldTypeCategory;
|
|
82
|
+
readonly nativeType: string;
|
|
83
|
+
/** Maximum length, when applicable (e.g. varchar(255)). */
|
|
84
|
+
readonly length?: number;
|
|
85
|
+
/** Numeric precision, when applicable. */
|
|
86
|
+
readonly precision?: number;
|
|
87
|
+
/** Numeric scale, when applicable. */
|
|
88
|
+
readonly scale?: number;
|
|
89
|
+
/** Allowed values, for enum-typed fields. */
|
|
90
|
+
readonly enumValues?: readonly string[];
|
|
91
|
+
/** Element type, for array/collection fields. */
|
|
92
|
+
readonly elementType?: FieldType;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* A single field within an entity.
|
|
97
|
+
*
|
|
98
|
+
* Maps to:
|
|
99
|
+
* - relational: column
|
|
100
|
+
* - document: document field
|
|
101
|
+
* - graph: node/edge property
|
|
102
|
+
*
|
|
103
|
+
* `nullable` and `defaultValue` may be best-effort approximations in
|
|
104
|
+
* paradigms without strict schemas. Adapters document how they determine
|
|
105
|
+
* each value.
|
|
106
|
+
*/
|
|
107
|
+
interface Field {
|
|
108
|
+
readonly name: string;
|
|
109
|
+
readonly type: FieldType;
|
|
110
|
+
readonly nullable: boolean;
|
|
111
|
+
/**
|
|
112
|
+
* True if this field participates in the entity's identifier.
|
|
113
|
+
* Paradigm-neutral replacement for "primary key".
|
|
114
|
+
*/
|
|
115
|
+
readonly isIdentifier: boolean;
|
|
116
|
+
readonly defaultValue: string | null;
|
|
117
|
+
readonly description: string | null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Kind of index physically created on an entity.
|
|
122
|
+
*
|
|
123
|
+
* Includes the common Postgres families. Adapters that target other
|
|
124
|
+
* stores map their native index types onto these or report 'unknown'.
|
|
125
|
+
*/
|
|
126
|
+
type IndexKind = 'btree' | 'hash' | 'gin' | 'gist' | 'brin' | 'spgist' | 'unknown';
|
|
127
|
+
/**
|
|
128
|
+
* An index defined on an entity.
|
|
129
|
+
*
|
|
130
|
+
* Excludes the index that backs the primary key, since that information
|
|
131
|
+
* is already available through the entity's `identifier`.
|
|
132
|
+
*
|
|
133
|
+
* Fields:
|
|
134
|
+
* name: index name as reported by the data store
|
|
135
|
+
* fields: field names the index covers, in order
|
|
136
|
+
* unique: true if the index enforces uniqueness
|
|
137
|
+
* kind: physical index type
|
|
138
|
+
* partial: true if the index has a WHERE clause
|
|
139
|
+
* definition: textual definition (e.g. CREATE INDEX statement) or null
|
|
140
|
+
*/
|
|
141
|
+
interface Index {
|
|
142
|
+
readonly name: string;
|
|
143
|
+
readonly fields: readonly string[];
|
|
144
|
+
readonly unique: boolean;
|
|
145
|
+
readonly kind: IndexKind;
|
|
146
|
+
readonly partial: boolean;
|
|
147
|
+
readonly definition: string | null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Action a data store takes when a referenced row/document is updated or
|
|
152
|
+
* deleted. `null` indicates the paradigm does not enforce referential
|
|
153
|
+
* actions (e.g. document stores).
|
|
154
|
+
*/
|
|
155
|
+
type ReferentialAction = 'no-action' | 'restrict' | 'cascade' | 'set-null' | 'set-default';
|
|
156
|
+
/**
|
|
157
|
+
* A qualified pointer to an entity, used to identify the source and
|
|
158
|
+
* target of a `Reference` without embedding the full entity.
|
|
159
|
+
*/
|
|
160
|
+
interface EntityRef {
|
|
161
|
+
readonly namespace: string;
|
|
162
|
+
readonly name: string;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* A pointer from one set of fields in one entity to another set of
|
|
166
|
+
* fields in another entity.
|
|
167
|
+
*
|
|
168
|
+
* Paradigm-neutral abstraction:
|
|
169
|
+
* - relational: built from a declared FOREIGN KEY constraint
|
|
170
|
+
* - document: inferred from naming conventions and value sampling
|
|
171
|
+
* - graph: built from declared edge definitions
|
|
172
|
+
*
|
|
173
|
+
* `confidence` is `1` for declared references (e.g. SQL foreign keys)
|
|
174
|
+
* and less than `1` for inferred references. Adapters that only ever
|
|
175
|
+
* return declared references should always set this to `1`.
|
|
176
|
+
*/
|
|
177
|
+
interface Reference {
|
|
178
|
+
readonly name: string;
|
|
179
|
+
readonly fromEntity: EntityRef;
|
|
180
|
+
readonly fromFields: readonly string[];
|
|
181
|
+
readonly toEntity: EntityRef;
|
|
182
|
+
readonly toFields: readonly string[];
|
|
183
|
+
readonly confidence: number;
|
|
184
|
+
readonly onUpdate: ReferentialAction | null;
|
|
185
|
+
readonly onDelete: ReferentialAction | null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Direction of a relationship from the perspective of the entity that
|
|
190
|
+
* owns it.
|
|
191
|
+
*
|
|
192
|
+
* - 'outbound': this entity holds the reference and points outward.
|
|
193
|
+
* - 'inbound': another entity holds a reference that points to this one.
|
|
194
|
+
*
|
|
195
|
+
* Storing both directions on each entity is the central feature of the
|
|
196
|
+
* SDK. Most introspection tools only expose outbound relationships,
|
|
197
|
+
* forcing consumers to know which other entities reference theirs ahead
|
|
198
|
+
* of time.
|
|
199
|
+
*/
|
|
200
|
+
type RelationshipDirection = 'outbound' | 'inbound';
|
|
201
|
+
/**
|
|
202
|
+
* A relationship as seen from a specific entity, with a direction.
|
|
203
|
+
*
|
|
204
|
+
* The same underlying `Reference` appears as 'outbound' on the source
|
|
205
|
+
* entity and 'inbound' on the target entity.
|
|
206
|
+
*/
|
|
207
|
+
interface Relationship {
|
|
208
|
+
readonly direction: RelationshipDirection;
|
|
209
|
+
readonly reference: Reference;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* A unit of data with named fields, identifier, relationships,
|
|
214
|
+
* constraints, and indexes.
|
|
215
|
+
*
|
|
216
|
+
* Maps to:
|
|
217
|
+
* relational: table or view
|
|
218
|
+
* document: collection
|
|
219
|
+
* graph: node label
|
|
220
|
+
* key-value: bucket
|
|
221
|
+
*
|
|
222
|
+
* Fields:
|
|
223
|
+
* namespace: schema or database the entity lives in
|
|
224
|
+
* name: entity name
|
|
225
|
+
* fields: the entity's fields, in declaration order
|
|
226
|
+
* identifier: field names that uniquely identify a row of this entity;
|
|
227
|
+
* contains all fields involved in a composite identifier;
|
|
228
|
+
* empty when the entity has no identifier
|
|
229
|
+
* relationships: all relationships involving this entity, in BOTH
|
|
230
|
+
* directions; filter by `direction` to get outbound or
|
|
231
|
+
* inbound separately
|
|
232
|
+
* constraints: constraints declared on the entity (unique, check,
|
|
233
|
+
* exclusion, custom); excludes the primary key
|
|
234
|
+
* indexes: indexes defined on the entity; excludes the index
|
|
235
|
+
* that backs the primary key
|
|
236
|
+
* description: human-readable comment, when available
|
|
237
|
+
*/
|
|
238
|
+
interface Entity {
|
|
239
|
+
readonly namespace: string;
|
|
240
|
+
readonly name: string;
|
|
241
|
+
readonly fields: readonly Field[];
|
|
242
|
+
readonly identifier: readonly string[];
|
|
243
|
+
readonly relationships: readonly Relationship[];
|
|
244
|
+
readonly constraints: readonly Constraint[];
|
|
245
|
+
readonly indexes: readonly Index[];
|
|
246
|
+
readonly description: string | null;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* The aggregate root for an introspected data store.
|
|
251
|
+
*
|
|
252
|
+
* Holds all entities discovered by an `Introspector`, indexed for
|
|
253
|
+
* efficient lookup, and exposes navigation helpers around relationships.
|
|
254
|
+
*
|
|
255
|
+
* Paradigm-neutral: the same shape is returned for relational, document,
|
|
256
|
+
* graph, and other data stores. Use `kind` to branch when needed.
|
|
257
|
+
*/
|
|
258
|
+
declare class DataModel {
|
|
259
|
+
readonly kind: EngineKind;
|
|
260
|
+
readonly entities: readonly Entity[];
|
|
261
|
+
private readonly index;
|
|
262
|
+
constructor(kind: EngineKind, entities: readonly Entity[]);
|
|
263
|
+
/**
|
|
264
|
+
* Build the lookup key for an entity. Exposed as a static helper so
|
|
265
|
+
* consumers can compute keys consistently when building their own
|
|
266
|
+
* indexes on top of a `DataModel`.
|
|
267
|
+
*/
|
|
268
|
+
static qualifiedName(namespace: string, name: string): string;
|
|
269
|
+
getEntity(namespace: string, name: string): Entity | undefined;
|
|
270
|
+
hasEntity(namespace: string, name: string): boolean;
|
|
271
|
+
/**
|
|
272
|
+
* All relationships an entity participates in, in both directions.
|
|
273
|
+
* Returns an empty array if the entity is unknown.
|
|
274
|
+
*/
|
|
275
|
+
relationshipsOf(namespace: string, name: string): readonly Relationship[];
|
|
276
|
+
/**
|
|
277
|
+
* Outbound relationships: this entity holds the reference.
|
|
278
|
+
* In a relational store these correspond to FOREIGN KEY constraints
|
|
279
|
+
* declared by the entity itself.
|
|
280
|
+
*/
|
|
281
|
+
outboundRelationshipsOf(namespace: string, name: string): readonly Relationship[];
|
|
282
|
+
/**
|
|
283
|
+
* Inbound relationships: other entities hold references pointing here.
|
|
284
|
+
*
|
|
285
|
+
* This is the differentiating feature of the SDK. Most introspection
|
|
286
|
+
* tools only surface outbound relationships, requiring consumers to
|
|
287
|
+
* know which other entities reference theirs ahead of time.
|
|
288
|
+
*/
|
|
289
|
+
inboundRelationshipsOf(namespace: string, name: string): readonly Relationship[];
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Port: a paradigm-specific introspector capable of producing a
|
|
294
|
+
* `DataModel` from an underlying data store.
|
|
295
|
+
*
|
|
296
|
+
* Implementations are provided by adapters, registered with an
|
|
297
|
+
* `AdapterRegistry`, and resolved through the public `Scanner` facade.
|
|
298
|
+
*
|
|
299
|
+
* Adapters are responsible for accepting their own native client/driver
|
|
300
|
+
* in their constructor. The SDK does not impose a unified client
|
|
301
|
+
* abstraction because the shape of "a client" varies wildly across
|
|
302
|
+
* paradigms.
|
|
303
|
+
*/
|
|
304
|
+
interface Introspector {
|
|
305
|
+
/** Stable adapter identifier (e.g. 'postgres', 'mongo'). */
|
|
306
|
+
readonly name: string;
|
|
307
|
+
/** Paradigm of the underlying data store. */
|
|
308
|
+
readonly kind: EngineKind;
|
|
309
|
+
/** Read the data store and produce a paradigm-neutral `DataModel`. */
|
|
310
|
+
introspect(options?: IntrospectOptions): Promise<DataModel>;
|
|
311
|
+
}
|
|
312
|
+
interface IntrospectOptions {
|
|
313
|
+
/**
|
|
314
|
+
* Namespaces to include. The adapter chooses a default if omitted
|
|
315
|
+
* (Postgres picks `['public']`). Pass the literal string `'all'`
|
|
316
|
+
* to scan every non-system namespace the adapter can discover -
|
|
317
|
+
* useful when bootstrapping codegen against an unfamiliar
|
|
318
|
+
* database.
|
|
319
|
+
*/
|
|
320
|
+
readonly namespaces?: readonly string[] | 'all';
|
|
321
|
+
/** Optional entity allowlist (matched after namespace filtering). */
|
|
322
|
+
readonly includeEntities?: readonly string[];
|
|
323
|
+
/** Optional entity denylist (matched after namespace filtering). */
|
|
324
|
+
readonly excludeEntities?: readonly string[];
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Postgres implementation of the `Introspector` port.
|
|
329
|
+
*
|
|
330
|
+
* Runs six queries against `pg_catalog` in parallel (tables, columns,
|
|
331
|
+
* primary keys, foreign keys, indexes, constraints) and hands the
|
|
332
|
+
* result to `EntityAssembler`, which stitches them into a paradigm-
|
|
333
|
+
* neutral `DataModel`.
|
|
334
|
+
*
|
|
335
|
+
* What it returns for each entity:
|
|
336
|
+
* - fields with normalized type categories
|
|
337
|
+
* - identifier (primary key columns)
|
|
338
|
+
* - relationships in BOTH directions
|
|
339
|
+
* - constraints (unique, check, exclusion)
|
|
340
|
+
* - indexes (excluding the primary key index)
|
|
341
|
+
*
|
|
342
|
+
* Accepts any client that satisfies `PostgresClient`, so it works with
|
|
343
|
+
* `pg.Client`, `pg.Pool`, or any compatible library without the SDK
|
|
344
|
+
* importing `pg`.
|
|
345
|
+
*/
|
|
346
|
+
declare class PostgresIntrospector implements Introspector {
|
|
347
|
+
private readonly client;
|
|
348
|
+
readonly name = "postgres";
|
|
349
|
+
readonly kind: "relational";
|
|
350
|
+
constructor(client: PostgresClient);
|
|
351
|
+
introspect(options?: IntrospectOptions): Promise<DataModel>;
|
|
352
|
+
private resolveNamespaces;
|
|
353
|
+
private static readonly ALL_NAMESPACES_SQL;
|
|
354
|
+
private static applyEntityFilters;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Adapter metadata constants for Postgres.
|
|
359
|
+
*
|
|
360
|
+
* Centralized so the rest of the adapter never references the literal
|
|
361
|
+
* string 'postgres' or the URL schemes by hand. Anywhere those values
|
|
362
|
+
* are needed, import the constant.
|
|
363
|
+
*/
|
|
364
|
+
declare const POSTGRES_ADAPTER_NAME = "postgres";
|
|
365
|
+
/** Literal type for the Postgres adapter name, used for discrimination. */
|
|
366
|
+
type PostgresAdapterName = typeof POSTGRES_ADAPTER_NAME;
|
|
367
|
+
declare const POSTGRES_URL_SCHEMES: readonly string[];
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Output of `QueryEngine.build`.
|
|
371
|
+
*
|
|
372
|
+
* Holds:
|
|
373
|
+
* command: the engine-specific instruction to execute
|
|
374
|
+
* params: the bound parameter values
|
|
375
|
+
* metadata: information about the engine and parameter count
|
|
376
|
+
*
|
|
377
|
+
* The SDK never executes a `BuiltQuery`. The consumer passes `command`
|
|
378
|
+
* and `params` to their own driver.
|
|
379
|
+
*
|
|
380
|
+
* `TCommand` is the shape of the command for a given engine:
|
|
381
|
+
* relational engines -> `BuiltQuery<string>` (SQL string)
|
|
382
|
+
* document engines -> `BuiltQuery<DocFilter>` (filter object)
|
|
383
|
+
*/
|
|
384
|
+
interface BuiltQuery<TCommand = unknown> {
|
|
385
|
+
readonly command: TCommand;
|
|
386
|
+
readonly params: readonly unknown[];
|
|
387
|
+
readonly metadata: BuiltQueryMetadata;
|
|
388
|
+
}
|
|
389
|
+
interface BuiltQueryMetadata {
|
|
390
|
+
/** Engine that produced this query (e.g. 'postgres', 'mongo'). */
|
|
391
|
+
readonly engine: string;
|
|
392
|
+
/** Number of parameters bound. */
|
|
393
|
+
readonly paramCount: number;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Declarative description of a query against a `DataModel`.
|
|
398
|
+
*
|
|
399
|
+
* Paradigm-neutral: the same `QuerySpec` can be handed to a relational
|
|
400
|
+
* `QueryEngine` (which produces a SQL string) or a document
|
|
401
|
+
* `QueryEngine` (which produces a Mongo filter, etc).
|
|
402
|
+
*
|
|
403
|
+
* Engines are responsible for raising clear errors when a spec contains
|
|
404
|
+
* something they cannot represent in their target language.
|
|
405
|
+
*/
|
|
406
|
+
interface QuerySpec {
|
|
407
|
+
readonly namespace: string;
|
|
408
|
+
readonly entity: string;
|
|
409
|
+
/** Field names to project. `undefined` means "all fields". */
|
|
410
|
+
readonly select?: readonly string[];
|
|
411
|
+
readonly filters?: readonly Filter[];
|
|
412
|
+
readonly orderBy?: readonly OrderBy[];
|
|
413
|
+
readonly limit?: number;
|
|
414
|
+
readonly offset?: number;
|
|
415
|
+
}
|
|
416
|
+
interface Filter {
|
|
417
|
+
readonly field: string;
|
|
418
|
+
readonly operator: FilterOperator;
|
|
419
|
+
readonly value: unknown;
|
|
420
|
+
}
|
|
421
|
+
type FilterOperator = 'eq' | 'neq' | 'lt' | 'lte' | 'gt' | 'gte' | 'like' | 'ilike' | 'in' | 'not-in' | 'is-null' | 'is-not-null' | 'between';
|
|
422
|
+
interface OrderBy {
|
|
423
|
+
readonly field: string;
|
|
424
|
+
readonly direction: 'asc' | 'desc';
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Port: a paradigm-specific engine that turns a `QuerySpec` into a
|
|
429
|
+
* ready-to-execute `BuiltQuery`.
|
|
430
|
+
*
|
|
431
|
+
* `TCommand` lets each engine pick the right command shape for its
|
|
432
|
+
* paradigm: a SQL string, a Mongo filter, a Cypher query, etc. The
|
|
433
|
+
* default of `unknown` is intentional. Code that holds an
|
|
434
|
+
* engine-agnostic reference cannot accidentally assume a command shape.
|
|
435
|
+
*/
|
|
436
|
+
interface QueryEngine<TCommand = unknown> {
|
|
437
|
+
/** Stable engine identifier (e.g. 'postgres', 'mongo'). */
|
|
438
|
+
readonly name: string;
|
|
439
|
+
/** Paradigm this engine targets. */
|
|
440
|
+
readonly kind: EngineKind;
|
|
441
|
+
/**
|
|
442
|
+
* Build a query from a declarative spec, validating it against the
|
|
443
|
+
* provided `DataModel`. Engines must throw on unknown entities or
|
|
444
|
+
* fields rather than silently producing broken queries.
|
|
445
|
+
*/
|
|
446
|
+
build(spec: QuerySpec, model: DataModel): BuiltQuery<TCommand>;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Postgres implementation of the `QueryEngine` port.
|
|
451
|
+
*
|
|
452
|
+
* Produces parameterized SQL using `$1`, `$2`, ... placeholders and
|
|
453
|
+
* double-quoted identifiers. Validates the spec against the
|
|
454
|
+
* `DataModel` before emitting any SQL: unknown entities or fields
|
|
455
|
+
* throw rather than yielding a broken query.
|
|
456
|
+
*
|
|
457
|
+
* Scope:
|
|
458
|
+
* - SELECT against a single entity
|
|
459
|
+
* - WHERE filters (every `FilterOperator`)
|
|
460
|
+
* - ORDER BY
|
|
461
|
+
* - LIMIT and OFFSET
|
|
462
|
+
* - No joins, group by, or aggregates yet
|
|
463
|
+
*/
|
|
464
|
+
declare class PostgresQueryEngine implements QueryEngine<string> {
|
|
465
|
+
readonly name = "postgres";
|
|
466
|
+
readonly kind: "relational";
|
|
467
|
+
build(spec: QuerySpec, model: DataModel): BuiltQuery<string>;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* The set of JS values a `RecordParser` produces after coercing raw
|
|
472
|
+
* driver output. Covers the common cases without pulling in
|
|
473
|
+
* framework-specific types.
|
|
474
|
+
*
|
|
475
|
+
* `null` represents both SQL NULL and a missing document field.
|
|
476
|
+
*/
|
|
477
|
+
type ParsedValue = string | number | bigint | boolean | Date | null | ParsedValue[] | {
|
|
478
|
+
readonly [key: string]: ParsedValue;
|
|
479
|
+
};
|
|
480
|
+
/**
|
|
481
|
+
* A single record after parsing. A map of field name to coerced value.
|
|
482
|
+
*
|
|
483
|
+
* Returned by `RecordParser.parse`. Consumed by `Formatter.serialize`.
|
|
484
|
+
*/
|
|
485
|
+
interface ParsedRecord {
|
|
486
|
+
readonly [field: string]: ParsedValue;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Port: turns raw rows from a database driver into typed `ParsedRecord`s
|
|
491
|
+
* using an `Entity`'s field metadata to coerce values.
|
|
492
|
+
*
|
|
493
|
+
* Implementations live in the SDK (default) or can be provided by users
|
|
494
|
+
* who need custom coercion (e.g. mapping `decimal` columns to a Big.js
|
|
495
|
+
* type instead of the default string).
|
|
496
|
+
*/
|
|
497
|
+
interface RecordParser {
|
|
498
|
+
/** Parse a single raw row using the entity's field definitions. */
|
|
499
|
+
parse(entity: Entity, row: Readonly<Record<string, unknown>>): ParsedRecord;
|
|
500
|
+
/** Parse many rows. Convenience wrapper around `parse`. */
|
|
501
|
+
parseMany(entity: Entity, rows: readonly Readonly<Record<string, unknown>>[]): readonly ParsedRecord[];
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Default `RecordParser` used when no adapter-specific parser is needed.
|
|
506
|
+
*
|
|
507
|
+
* Coerces raw driver output to JS values using the field type
|
|
508
|
+
* categories from the `DataModel`:
|
|
509
|
+
* string | uuid | enum -> JS string
|
|
510
|
+
* integer -> JS number (or bigint if raw is bigint)
|
|
511
|
+
* decimal -> JS string (preserves precision)
|
|
512
|
+
* boolean -> JS boolean
|
|
513
|
+
* date | timestamp | time -> JS Date
|
|
514
|
+
* json -> pass-through
|
|
515
|
+
* binary -> pass-through
|
|
516
|
+
* array -> pass-through if already an array, else []
|
|
517
|
+
* reference -> pass-through
|
|
518
|
+
* unknown -> pass-through
|
|
519
|
+
*
|
|
520
|
+
* Null and undefined raw values become `null`. Fields on the entity
|
|
521
|
+
* that are absent from the row are omitted from the result, so a
|
|
522
|
+
* partial projection only emits the keys that were selected.
|
|
523
|
+
*
|
|
524
|
+
* The `coerce` method is `protected` so adapter-specific subclasses
|
|
525
|
+
* (for example `PostgresRecordParser`) can override or extend it.
|
|
526
|
+
*/
|
|
527
|
+
declare class DefaultRecordParser implements RecordParser {
|
|
528
|
+
parse(entity: Entity, row: Readonly<Record<string, unknown>>): ParsedRecord;
|
|
529
|
+
parseMany(entity: Entity, rows: readonly Readonly<Record<string, unknown>>[]): readonly ParsedRecord[];
|
|
530
|
+
protected coerce(field: Field, raw: unknown): ParsedValue;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Postgres-aware `RecordParser`.
|
|
535
|
+
*
|
|
536
|
+
* Extends `DefaultRecordParser` with behavior that matches how the
|
|
537
|
+
* `pg` driver returns Postgres values:
|
|
538
|
+
*
|
|
539
|
+
* - `int8` / `bigint` columns are returned by `pg` as strings by
|
|
540
|
+
* default (to preserve precision). This parser converts them to
|
|
541
|
+
* JS `bigint` so downstream code does not silently lose digits.
|
|
542
|
+
*
|
|
543
|
+
* - `json` / `jsonb` columns are usually already parsed by `pg`,
|
|
544
|
+
* but when they arrive as strings (for example from drivers that
|
|
545
|
+
* do not auto-parse) this parser parses them.
|
|
546
|
+
*
|
|
547
|
+
* - All other categories fall through to `DefaultRecordParser`.
|
|
548
|
+
*
|
|
549
|
+
* Use this parser when consuming rows that come from a `pg.Client` or
|
|
550
|
+
* `pg.Pool`. For adapters that handle these concerns themselves, the
|
|
551
|
+
* default parser is still appropriate.
|
|
552
|
+
*/
|
|
553
|
+
declare class PostgresRecordParser extends DefaultRecordParser {
|
|
554
|
+
protected coerce(field: Field, raw: unknown): ParsedValue;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Port: executes a `BuiltQuery` against an underlying driver and
|
|
559
|
+
* returns the raw rows.
|
|
560
|
+
*
|
|
561
|
+
* Sits one layer below `RecordParser`: the runner only hands back
|
|
562
|
+
* untyped row objects, and the parser turns them into `ParsedRecord`s
|
|
563
|
+
* using the entity's field metadata.
|
|
564
|
+
*
|
|
565
|
+
* Each adapter implements this against its own client (for example
|
|
566
|
+
* `PostgresRawQueryRunner` wraps a `PostgresClient`). Used by
|
|
567
|
+
* `QueryPlanExecutor` to drive the typed query API.
|
|
568
|
+
*
|
|
569
|
+
* @example
|
|
570
|
+
* ```ts
|
|
571
|
+
* const rows = await runner.run(built);
|
|
572
|
+
* ```
|
|
573
|
+
*/
|
|
574
|
+
interface RawQueryRunner {
|
|
575
|
+
run<TRow = unknown>(built: BuiltQuery): Promise<readonly TRow[]>;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Names of the adapters that ship with the SDK.
|
|
580
|
+
*
|
|
581
|
+
* Grows as new adapters are added. Consumers can still register custom
|
|
582
|
+
* adapters with any name; the type accepts arbitrary strings in
|
|
583
|
+
* addition to the known ones (see `AdapterName`).
|
|
584
|
+
*/
|
|
585
|
+
type KnownAdapterName = 'postgres';
|
|
586
|
+
/**
|
|
587
|
+
* Adapter name type.
|
|
588
|
+
*
|
|
589
|
+
* Known adapter names appear in IDE autocomplete when calling
|
|
590
|
+
* `Biref.scan(...)`, `AdapterRegistry.get(...)`, and similar APIs.
|
|
591
|
+
* Any other string is still accepted so consumers can register
|
|
592
|
+
* adapters the SDK does not know about.
|
|
593
|
+
*
|
|
594
|
+
* Pattern: `KnownAdapterName | (string & {})` preserves literal
|
|
595
|
+
* suggestions while also accepting the broader string type.
|
|
596
|
+
*/
|
|
597
|
+
type AdapterName = KnownAdapterName | (string & {});
|
|
598
|
+
/**
|
|
599
|
+
* An adapter packaged for registration with `Biref` or `AdapterRegistry`.
|
|
600
|
+
*
|
|
601
|
+
* Generic over `TName` so each adapter declares its name as a literal
|
|
602
|
+
* string type. Consumers narrow by comparing `adapter.name` against an
|
|
603
|
+
* exported constant from the adapter module:
|
|
604
|
+
*
|
|
605
|
+
* import { POSTGRES_ADAPTER_NAME } from '@biref/scanner';
|
|
606
|
+
*
|
|
607
|
+
* if (adapter.name === POSTGRES_ADAPTER_NAME) {
|
|
608
|
+
* // adapter is narrowed to Adapter<'postgres'>
|
|
609
|
+
* }
|
|
610
|
+
*
|
|
611
|
+
* Fields:
|
|
612
|
+
* name: literal name of the adapter (e.g. 'postgres')
|
|
613
|
+
* introspector: produces a `DataModel` from the data store
|
|
614
|
+
* engine: turns a `QuerySpec` into a `BuiltQuery`
|
|
615
|
+
* runner: executes a `BuiltQuery` and returns raw rows
|
|
616
|
+
* parser: coerces raw rows into typed `ParsedRecord`s
|
|
617
|
+
* urlSchemes: URL schemes the adapter handles, for auto-detection
|
|
618
|
+
*
|
|
619
|
+
* `runner` and `parser` are optional on the interface so older adapter
|
|
620
|
+
* modules stay source-compatible. The typed query API requires both.
|
|
621
|
+
*/
|
|
622
|
+
interface Adapter<TName extends AdapterName = AdapterName> {
|
|
623
|
+
readonly name: TName;
|
|
624
|
+
readonly introspector: Introspector;
|
|
625
|
+
readonly engine: QueryEngine;
|
|
626
|
+
readonly runner?: RawQueryRunner;
|
|
627
|
+
readonly parser?: RecordParser;
|
|
628
|
+
readonly urlSchemes?: readonly string[];
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Registry of `Adapter` instances keyed by name.
|
|
632
|
+
*
|
|
633
|
+
* Looked up by `Biref` and the lower-level facades. Registration is
|
|
634
|
+
* explicit: the SDK has zero runtime dependencies and cannot know
|
|
635
|
+
* which adapters are installed.
|
|
636
|
+
*/
|
|
637
|
+
declare class AdapterRegistry {
|
|
638
|
+
private readonly adapters;
|
|
639
|
+
register(adapter: Adapter): void;
|
|
640
|
+
unregister(name: AdapterName): boolean;
|
|
641
|
+
get(name: AdapterName): Adapter;
|
|
642
|
+
has(name: AdapterName): boolean;
|
|
643
|
+
list(): readonly string[];
|
|
644
|
+
/**
|
|
645
|
+
* Find an adapter that claims the given URL scheme. Case-insensitive.
|
|
646
|
+
* Returns `undefined` if no adapter handles it.
|
|
647
|
+
*/
|
|
648
|
+
findByUrlScheme(scheme: string): Adapter | undefined;
|
|
649
|
+
/**
|
|
650
|
+
* Same as `findByUrlScheme` but throws a helpful error listing the
|
|
651
|
+
* known schemes when no match is found.
|
|
652
|
+
*/
|
|
653
|
+
getByUrlScheme(scheme: string): Adapter;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* Common interface every adapter module exports.
|
|
658
|
+
*
|
|
659
|
+
* Describes how to construct an `Adapter` for a specific data store.
|
|
660
|
+
*
|
|
661
|
+
* Type parameters:
|
|
662
|
+
* TClient: shape of the driver client the factory expects (for
|
|
663
|
+
* example `PostgresClient` for the Postgres adapter)
|
|
664
|
+
* TName: literal name type, exported alongside the factory for
|
|
665
|
+
* type-safe discrimination at call sites
|
|
666
|
+
*
|
|
667
|
+
* Fields:
|
|
668
|
+
* name: literal adapter name (e.g. 'postgres')
|
|
669
|
+
* urlSchemes: URL schemes the adapter handles
|
|
670
|
+
* create: builds an `Adapter<TName>` bound to the given client
|
|
671
|
+
*
|
|
672
|
+
* @example
|
|
673
|
+
* ```ts
|
|
674
|
+
* import { Biref, postgresAdapter } from '@biref/scanner';
|
|
675
|
+
*
|
|
676
|
+
* const biref = Biref.builder()
|
|
677
|
+
* .withAdapter(postgresAdapter.create(client))
|
|
678
|
+
* .build();
|
|
679
|
+
* ```
|
|
680
|
+
*/
|
|
681
|
+
interface AdapterFactory<TClient = unknown, TName extends AdapterName = AdapterName> {
|
|
682
|
+
readonly name: TName;
|
|
683
|
+
readonly urlSchemes: readonly string[];
|
|
684
|
+
create(client: TClient): Adapter<TName>;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/**
|
|
688
|
+
* Postgres adapter factory.
|
|
689
|
+
*
|
|
690
|
+
* Implements `AdapterFactory<PostgresClient, PostgresAdapterName>`.
|
|
691
|
+
* `create` builds an `Adapter` bound to the user-provided client.
|
|
692
|
+
*
|
|
693
|
+
* @example
|
|
694
|
+
* ```ts
|
|
695
|
+
* import pg from 'pg';
|
|
696
|
+
* import { Biref, postgresAdapter } from '@biref/scanner';
|
|
697
|
+
*
|
|
698
|
+
* const client = new pg.Client({ ... });
|
|
699
|
+
* await client.connect();
|
|
700
|
+
*
|
|
701
|
+
* const biref = Biref.builder()
|
|
702
|
+
* .withAdapter(postgresAdapter.create(client))
|
|
703
|
+
* .build();
|
|
704
|
+
*
|
|
705
|
+
* const model = await biref.scan();
|
|
706
|
+
* ```
|
|
707
|
+
*/
|
|
708
|
+
declare const postgresAdapter: AdapterFactory<PostgresClient, PostgresAdapterName>;
|
|
709
|
+
/**
|
|
710
|
+
* Type guard: narrows a generic `Adapter` to an `Adapter<PostgresAdapterName>`.
|
|
711
|
+
*
|
|
712
|
+
* @example
|
|
713
|
+
* ```ts
|
|
714
|
+
* if (isPostgresAdapter(adapter)) {
|
|
715
|
+
* // adapter.name is now typed as 'postgres'
|
|
716
|
+
* }
|
|
717
|
+
* ```
|
|
718
|
+
*/
|
|
719
|
+
declare function isPostgresAdapter(adapter: Adapter): adapter is Adapter<PostgresAdapterName>;
|
|
720
|
+
|
|
721
|
+
/**
|
|
722
|
+
* Content of the first-run overrides file scaffold.
|
|
723
|
+
*
|
|
724
|
+
* The CLI only writes this if the target path does not already
|
|
725
|
+
* exist - once the user has started editing it, regenerating the
|
|
726
|
+
* schema never overwrites their work.
|
|
727
|
+
*/
|
|
728
|
+
declare function overridesScaffold(): string;
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* A single file produced by split-mode codegen.
|
|
732
|
+
*
|
|
733
|
+
* Paths are always relative to the output folder the user passed to
|
|
734
|
+
* the CLI (or to `writeSchemaFiles`) and use forward slashes. The
|
|
735
|
+
* caller is responsible for joining them onto an absolute path and
|
|
736
|
+
* creating any intermediate directories.
|
|
737
|
+
*/
|
|
738
|
+
interface SchemaFile {
|
|
739
|
+
readonly path: string;
|
|
740
|
+
readonly content: string;
|
|
741
|
+
}
|
|
742
|
+
/**
|
|
743
|
+
* Emit a TypeScript schema declaration from a scanned `DataModel` as
|
|
744
|
+
* a single self-contained `.ts` file.
|
|
745
|
+
*
|
|
746
|
+
* The output exports:
|
|
747
|
+
*
|
|
748
|
+
* - `RawBirefSchema`: the literal schema shape the model discovered
|
|
749
|
+
* - `BirefSchema`: `RawBirefSchema` with user overrides applied
|
|
750
|
+
*
|
|
751
|
+
* Deterministic: the same `DataModel` input always produces the same
|
|
752
|
+
* string, suitable for snapshot tests and stable git diffs.
|
|
753
|
+
*
|
|
754
|
+
* Use this when you want everything in one file. For larger schemas
|
|
755
|
+
* `generateSchemaFiles` emits one file per entity plus an index and
|
|
756
|
+
* tends to be easier to browse.
|
|
757
|
+
*/
|
|
758
|
+
declare function generateSchema(model: DataModel): string;
|
|
759
|
+
/**
|
|
760
|
+
* Emit a folder of split schema files from a scanned `DataModel`.
|
|
761
|
+
*
|
|
762
|
+
* Layout:
|
|
763
|
+
*
|
|
764
|
+
* index.ts Root file re-exporting every per-entity
|
|
765
|
+
* interface and composing `RawBirefSchema`
|
|
766
|
+
* plus `BirefSchema`.
|
|
767
|
+
* <namespace>/<entity>.ts One file per entity with its field
|
|
768
|
+
* descriptor, identifier tuple, and
|
|
769
|
+
* relations map.
|
|
770
|
+
*
|
|
771
|
+
* The overrides file is **not** emitted by this function - it is
|
|
772
|
+
* scaffolded once by the CLI and never touched again.
|
|
773
|
+
*
|
|
774
|
+
* Returns the files in a deterministic order so consumers can write
|
|
775
|
+
* them or snapshot-test them safely.
|
|
776
|
+
*/
|
|
777
|
+
declare function generateSchemaFiles(model: DataModel): readonly SchemaFile[];
|
|
778
|
+
|
|
779
|
+
/**
|
|
780
|
+
* Maps a paradigm-neutral `FieldType` to the TypeScript type literal
|
|
781
|
+
* string emitted into the codegen-generated schema.
|
|
782
|
+
*
|
|
783
|
+
* JSON / JSONB categories map to `unknown` (users replace them via
|
|
784
|
+
* the sibling overrides file). Enum categories with known values map
|
|
785
|
+
* to a union of string literals. Array categories recurse on the
|
|
786
|
+
* element type. Everything else follows the fixed table described in
|
|
787
|
+
* the README.
|
|
788
|
+
*/
|
|
789
|
+
declare function tsTypeFor(type: FieldType, nullable: boolean): string;
|
|
790
|
+
|
|
791
|
+
/**
|
|
792
|
+
* A node in the typed query builder's plan tree.
|
|
793
|
+
*
|
|
794
|
+
* Each node wraps a single-entity `QuerySpec` (executed against the
|
|
795
|
+
* adapter's engine) plus any child includes to resolve recursively.
|
|
796
|
+
* `QueryPlanExecutor` walks this tree to execute and stitch.
|
|
797
|
+
*/
|
|
798
|
+
interface QueryPlan {
|
|
799
|
+
readonly spec: QuerySpec;
|
|
800
|
+
readonly includes: readonly QueryInclude[];
|
|
801
|
+
}
|
|
802
|
+
/**
|
|
803
|
+
* A nested include on a parent plan.
|
|
804
|
+
*
|
|
805
|
+
* `relationName` is the name the user wrote in `.include('orders', ...)`
|
|
806
|
+
* on the builder (either a friendly name computed from the model or,
|
|
807
|
+
* on collisions, the underlying foreign key constraint name).
|
|
808
|
+
*
|
|
809
|
+
* `direction` mirrors `Relationship.direction`. `parentKeys` are the
|
|
810
|
+
* columns on the parent entity used to filter children; `childKeys`
|
|
811
|
+
* are their counterparts on the child entity. For outbound includes
|
|
812
|
+
* (FK → target PK) the parent holds the FK columns; for inbound the
|
|
813
|
+
* parent holds the PK columns.
|
|
814
|
+
*
|
|
815
|
+
* `cardinality` drives how the executor stitches children into the
|
|
816
|
+
* parent record: `'one'` picks the first match, `'many'` returns the
|
|
817
|
+
* full array.
|
|
818
|
+
*/
|
|
819
|
+
interface QueryInclude {
|
|
820
|
+
readonly relationName: string;
|
|
821
|
+
readonly plan: QueryPlan;
|
|
822
|
+
readonly direction: 'inbound' | 'outbound';
|
|
823
|
+
readonly parentKeys: readonly string[];
|
|
824
|
+
readonly childKeys: readonly string[];
|
|
825
|
+
readonly cardinality: 'one' | 'many';
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
/**
|
|
829
|
+
* Executes a `QueryPlan` tree against an adapter's engine, runner, and
|
|
830
|
+
* parser, stitching include results into nested shapes on the parent.
|
|
831
|
+
*
|
|
832
|
+
* Runs one query per plan node - root first, then one query per
|
|
833
|
+
* include level, filtered to the parent keys it just collected. This
|
|
834
|
+
* matches Prisma's approach: sequential sub-queries with in-process
|
|
835
|
+
* hydration, rather than SQL JOINs. It keeps the engine layer
|
|
836
|
+
* paradigm-neutral and works without any JOIN support in the
|
|
837
|
+
* adapter's `QueryEngine`.
|
|
838
|
+
*
|
|
839
|
+
* Projection honoring: join-key columns are transparently added to
|
|
840
|
+
* both the parent's and each child's SELECT so the stitcher can read
|
|
841
|
+
* them, then removed from the final hydrated records so the caller
|
|
842
|
+
* sees only the columns they originally asked for.
|
|
843
|
+
*/
|
|
844
|
+
declare class QueryPlanExecutor {
|
|
845
|
+
private readonly engine;
|
|
846
|
+
private readonly runner;
|
|
847
|
+
private readonly parser;
|
|
848
|
+
constructor(engine: QueryEngine, runner: RawQueryRunner, parser: RecordParser);
|
|
849
|
+
execute(plan: QueryPlan, model: DataModel): Promise<readonly ParsedRecord[]>;
|
|
850
|
+
private attachIncludes;
|
|
851
|
+
private static prepareChildPlan;
|
|
852
|
+
private static injectFields;
|
|
853
|
+
private static collectParentKeys;
|
|
854
|
+
/**
|
|
855
|
+
* Compute the set of keys a hydrated record should expose to the
|
|
856
|
+
* caller: the user's selected fields (or every entity field when
|
|
857
|
+
* no select was given) plus every nested include's relation name
|
|
858
|
+
* so attached children survive the final filter step.
|
|
859
|
+
*/
|
|
860
|
+
private static resolveExposedKeys;
|
|
861
|
+
/**
|
|
862
|
+
* Resolve the exposed-keys set for an include's nested plan.
|
|
863
|
+
*
|
|
864
|
+
* Uses the ORIGINAL include.plan (not the mutated child plan we
|
|
865
|
+
* handed to the executor) so injected join keys do not leak into
|
|
866
|
+
* the final output.
|
|
867
|
+
*/
|
|
868
|
+
private static resolveChildExposedKeys;
|
|
869
|
+
private static project;
|
|
870
|
+
private static indexByKey;
|
|
871
|
+
private static buildKey;
|
|
872
|
+
private static filterKeys;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
/**
|
|
876
|
+
* Runtime context threaded through every `ChainBuilder` instance.
|
|
877
|
+
*
|
|
878
|
+
* Holds the scanned `DataModel` used for validation and the
|
|
879
|
+
* `QueryPlanExecutor` that actually runs terminal methods like
|
|
880
|
+
* `findMany` / `findFirst`.
|
|
881
|
+
*/
|
|
882
|
+
interface ChainBuilderContext {
|
|
883
|
+
readonly model: DataModel;
|
|
884
|
+
readonly executor: QueryPlanExecutor;
|
|
885
|
+
}
|
|
886
|
+
/**
|
|
887
|
+
* Immutable fluent builder that accumulates a `QueryPlan`.
|
|
888
|
+
*
|
|
889
|
+
* Each mutating method (`select`, `where`, `orderBy`, `limit`,
|
|
890
|
+
* `offset`, `include`) returns a **new** `ChainBuilder` - chains are
|
|
891
|
+
* safe to fork.
|
|
892
|
+
*
|
|
893
|
+
* Field and relation names are validated against the `DataModel` at
|
|
894
|
+
* call time so mistakes throw immediately with a clear message.
|
|
895
|
+
* Compile-time typing comes from the generic parameters (see
|
|
896
|
+
* `src/query/types/*`); the runtime implementation here is
|
|
897
|
+
* schema-agnostic.
|
|
898
|
+
*/
|
|
899
|
+
declare class ChainBuilder {
|
|
900
|
+
private readonly ctx;
|
|
901
|
+
private readonly plan;
|
|
902
|
+
constructor(ctx: ChainBuilderContext, plan: QueryPlan);
|
|
903
|
+
/**
|
|
904
|
+
* Narrow the projection to a specific set of fields.
|
|
905
|
+
*
|
|
906
|
+
* .select('id', 'email') -- only these two columns
|
|
907
|
+
* .select('*') -- every field of the entity
|
|
908
|
+
* .select() -- equivalent to '*', kept for ergonomic parity
|
|
909
|
+
*
|
|
910
|
+
* Unknown field names throw immediately with a clear error; the
|
|
911
|
+
* wildcard sentinel skips validation and leaves the plan's `select`
|
|
912
|
+
* undefined so the engine emits every column the adapter reported.
|
|
913
|
+
*/
|
|
914
|
+
select(...fields: readonly string[]): ChainBuilder;
|
|
915
|
+
where(field: string, operator: FilterOperator, value?: unknown): ChainBuilder;
|
|
916
|
+
orderBy(field: string, direction?: 'asc' | 'desc'): ChainBuilder;
|
|
917
|
+
limit(count: number): ChainBuilder;
|
|
918
|
+
offset(count: number): ChainBuilder;
|
|
919
|
+
/**
|
|
920
|
+
* Attach a nested include to the plan.
|
|
921
|
+
*
|
|
922
|
+
* Three call shapes are supported:
|
|
923
|
+
*
|
|
924
|
+
* .include('orders') -- all fields, no nested
|
|
925
|
+
* .include('orders', (q) => q.select('id', 'total')) -- narrow the child
|
|
926
|
+
* .include('*') -- every relation on this entity
|
|
927
|
+
*
|
|
928
|
+
* The callback is optional. When omitted, the child plan uses the
|
|
929
|
+
* default projection (all fields of the related entity) and no
|
|
930
|
+
* further includes. Pass a callback when you want to narrow the
|
|
931
|
+
* projection or chain additional nested includes.
|
|
932
|
+
*
|
|
933
|
+
* The wildcard `'*'` expands to one include per relation discovered
|
|
934
|
+
* on the current entity. Each expanded include uses the default
|
|
935
|
+
* projection. Combining `'*'` with a callback is not supported -
|
|
936
|
+
* the sub-builder's shape differs per relation, so a single
|
|
937
|
+
* callback cannot narrow all of them coherently.
|
|
938
|
+
*/
|
|
939
|
+
include(relationName: string, build?: (q: ChainBuilder) => ChainBuilder): ChainBuilder;
|
|
940
|
+
findMany(): Promise<readonly ParsedRecord[]>;
|
|
941
|
+
findFirst(): Promise<ParsedRecord | null>;
|
|
942
|
+
/**
|
|
943
|
+
* Escape hatch for advanced callers (tests, tooling): exposes the
|
|
944
|
+
* accumulated plan without executing it. The typed builder's public
|
|
945
|
+
* surface does not reference this directly.
|
|
946
|
+
*/
|
|
947
|
+
toPlan(): QueryPlan;
|
|
948
|
+
private requireEntity;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
/**
|
|
952
|
+
* Compile-time descriptor for a single field in a codegen schema.
|
|
953
|
+
*
|
|
954
|
+
* `ts` is the TypeScript type the field's values will be hydrated as
|
|
955
|
+
* (e.g. `string`, `number`, `Date`, or a user-provided override type).
|
|
956
|
+
* `nullable` and `isIdentifier` mirror the runtime `Field`. `category`
|
|
957
|
+
* carries the original `FieldTypeCategory` so the typed builder can
|
|
958
|
+
* pick the allowed `where` operator set for this field.
|
|
959
|
+
*/
|
|
960
|
+
interface SchemaFieldDescriptor<TS = unknown, Nullable extends boolean = boolean, Identifier extends boolean = boolean, Category extends FieldTypeCategory = FieldTypeCategory> {
|
|
961
|
+
readonly ts: TS;
|
|
962
|
+
readonly nullable: Nullable;
|
|
963
|
+
readonly isIdentifier: Identifier;
|
|
964
|
+
readonly category: Category;
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* Compile-time descriptor for a single relation in a codegen schema.
|
|
968
|
+
*
|
|
969
|
+
* `target` is a qualified entity name `'namespace.entity'`. The typed
|
|
970
|
+
* builder splits on the dot to resolve the target entity at compile
|
|
971
|
+
* time.
|
|
972
|
+
*/
|
|
973
|
+
interface SchemaRelationDescriptor<Direction extends 'inbound' | 'outbound' = 'inbound' | 'outbound', Target extends `${string}.${string}` = `${string}.${string}`, FromFields extends readonly string[] = readonly string[], ToFields extends readonly string[] = readonly string[], Cardinality extends 'one' | 'many' = 'one' | 'many'> {
|
|
974
|
+
readonly direction: Direction;
|
|
975
|
+
readonly target: Target;
|
|
976
|
+
readonly fromFields: FromFields;
|
|
977
|
+
readonly toFields: ToFields;
|
|
978
|
+
readonly cardinality: Cardinality;
|
|
979
|
+
}
|
|
980
|
+
/**
|
|
981
|
+
* Compile-time descriptor for one entity: the fields map, the
|
|
982
|
+
* identifier tuple, and the relations map.
|
|
983
|
+
*/
|
|
984
|
+
interface SchemaEntityDescriptor {
|
|
985
|
+
readonly fields: {
|
|
986
|
+
readonly [field: string]: SchemaFieldDescriptor;
|
|
987
|
+
};
|
|
988
|
+
readonly identifier: readonly string[];
|
|
989
|
+
readonly relations: {
|
|
990
|
+
readonly [relation: string]: SchemaRelationDescriptor;
|
|
991
|
+
};
|
|
992
|
+
}
|
|
993
|
+
/**
|
|
994
|
+
* Compile-time descriptor for a namespace.
|
|
995
|
+
*/
|
|
996
|
+
type SchemaNamespaceDescriptor = {
|
|
997
|
+
readonly [entity: string]: SchemaEntityDescriptor;
|
|
998
|
+
};
|
|
999
|
+
/**
|
|
1000
|
+
* Loose structural constraint for schema types passed to
|
|
1001
|
+
* `biref.query<Schema>()`.
|
|
1002
|
+
*
|
|
1003
|
+
* A concrete interface with literal namespace / entity / field keys
|
|
1004
|
+
* satisfies `object`, so users can hand a codegen-generated
|
|
1005
|
+
* `BirefSchema` directly without opting into a string index
|
|
1006
|
+
* signature. The actual narrowing (field map, relation target, etc.)
|
|
1007
|
+
* is handled by the type-level helpers in `src/query/types/*`, which
|
|
1008
|
+
* match via conditional types and fall back to `never` when the
|
|
1009
|
+
* schema does not line up.
|
|
1010
|
+
*/
|
|
1011
|
+
type BirefSchemaShape = object;
|
|
1012
|
+
|
|
1013
|
+
/**
|
|
1014
|
+
* Split a `'namespace.entity'` string literal on the dot.
|
|
1015
|
+
*
|
|
1016
|
+
* Used to resolve the target entity of a relation at compile time from
|
|
1017
|
+
* its qualified `target` string. The result is a tuple `[Ns, E]`.
|
|
1018
|
+
*/
|
|
1019
|
+
type Split<T extends string, Sep extends string> = T extends `${infer L}${Sep}${infer R}` ? [L, R] : never;
|
|
1020
|
+
/**
|
|
1021
|
+
* Relations map of a given entity - indexable by relation name.
|
|
1022
|
+
*/
|
|
1023
|
+
type RelationsOfEntity<S extends BirefSchemaShape, Ns extends keyof S, E extends keyof S[Ns]> = S[Ns][E] extends {
|
|
1024
|
+
readonly relations: infer R;
|
|
1025
|
+
} ? R : never;
|
|
1026
|
+
/**
|
|
1027
|
+
* The descriptor for a specific relation on a specific entity.
|
|
1028
|
+
*/
|
|
1029
|
+
type RelationDescriptor<S extends BirefSchemaShape, Ns extends keyof S, E extends keyof S[Ns], R extends keyof RelationsOfEntity<S, Ns, E>> = RelationsOfEntity<S, Ns, E>[R];
|
|
1030
|
+
/**
|
|
1031
|
+
* Extract the target namespace literal from a relation descriptor's
|
|
1032
|
+
* `target: 'ns.entity'` field.
|
|
1033
|
+
*/
|
|
1034
|
+
type TargetNs<S extends BirefSchemaShape, Ns extends keyof S, E extends keyof S[Ns], R extends keyof RelationsOfEntity<S, Ns, E>> = RelationDescriptor<S, Ns, E, R> extends SchemaRelationDescriptor<infer _Dir, infer Target, infer _From, infer _To, infer _Card> ? Split<Target, '.'>[0] extends keyof S ? Split<Target, '.'>[0] : never : never;
|
|
1035
|
+
/**
|
|
1036
|
+
* Extract the target entity literal from a relation descriptor.
|
|
1037
|
+
*/
|
|
1038
|
+
type TargetE<S extends BirefSchemaShape, Ns extends keyof S, E extends keyof S[Ns], R extends keyof RelationsOfEntity<S, Ns, E>> = RelationDescriptor<S, Ns, E, R> extends SchemaRelationDescriptor<infer _Dir, infer Target, infer _From, infer _To, infer _Card> ? TargetNs<S, Ns, E, R> extends keyof S ? Split<Target, '.'>[1] extends keyof S[TargetNs<S, Ns, E, R>] ? Split<Target, '.'>[1] : never : never : never;
|
|
1039
|
+
/**
|
|
1040
|
+
* Extract the cardinality (`'one' | 'many'`) from a relation
|
|
1041
|
+
* descriptor, used to decide whether an include field wraps its
|
|
1042
|
+
* result in an array.
|
|
1043
|
+
*/
|
|
1044
|
+
type CardinalityOf<S extends BirefSchemaShape, Ns extends keyof S, E extends keyof S[Ns], R extends keyof RelationsOfEntity<S, Ns, E>> = RelationDescriptor<S, Ns, E, R> extends SchemaRelationDescriptor<infer _Dir, infer _Target, infer _From, infer _To, infer Card> ? Card : never;
|
|
1045
|
+
|
|
1046
|
+
/**
|
|
1047
|
+
* Compile-time helpers that project row shapes out of a codegen schema.
|
|
1048
|
+
*
|
|
1049
|
+
* These types accept an arbitrary `Schema` (which is usually the
|
|
1050
|
+
* `BirefSchema` generated by codegen) plus a namespace / entity / field
|
|
1051
|
+
* path, and produce the shapes the typed builder needs: field maps,
|
|
1052
|
+
* per-field TS types (with nullability applied), categories for
|
|
1053
|
+
* operator selection, default and narrowed row shapes, and the final
|
|
1054
|
+
* hydrated row that merges projected columns with nested include
|
|
1055
|
+
* results.
|
|
1056
|
+
*/
|
|
1057
|
+
type FieldsOf<S extends BirefSchemaShape, Ns extends keyof S, E extends keyof S[Ns]> = S[Ns][E] extends {
|
|
1058
|
+
readonly fields: infer F;
|
|
1059
|
+
} ? F : never;
|
|
1060
|
+
type RelationsOf<S extends BirefSchemaShape, Ns extends keyof S, E extends keyof S[Ns]> = S[Ns][E] extends {
|
|
1061
|
+
readonly relations: infer R;
|
|
1062
|
+
} ? R : never;
|
|
1063
|
+
type CategoryOf<S extends BirefSchemaShape, Ns extends keyof S, E extends keyof S[Ns], F extends keyof FieldsOf<S, Ns, E>> = FieldsOf<S, Ns, E>[F] extends SchemaFieldDescriptor<infer _TS, infer _Null, infer _Ident, infer Category> ? Category : never;
|
|
1064
|
+
type TypeOf<S extends BirefSchemaShape, Ns extends keyof S, E extends keyof S[Ns], F extends keyof FieldsOf<S, Ns, E>> = FieldsOf<S, Ns, E>[F] extends SchemaFieldDescriptor<infer TS, infer Nullable, infer _Ident, infer _Category> ? Nullable extends true ? TS | null : TS : never;
|
|
1065
|
+
/**
|
|
1066
|
+
* Selection shape: a map from selected field name to its TS type,
|
|
1067
|
+
* with nullability applied. Produced by `select(...)`.
|
|
1068
|
+
*/
|
|
1069
|
+
type Selection = {
|
|
1070
|
+
readonly [field: string]: unknown;
|
|
1071
|
+
};
|
|
1072
|
+
/**
|
|
1073
|
+
* The default selection when `select()` is not called: every field on
|
|
1074
|
+
* the entity, keyed by its declared name.
|
|
1075
|
+
*/
|
|
1076
|
+
type DefaultSelect<S extends BirefSchemaShape, Ns extends keyof S, E extends keyof S[Ns]> = {
|
|
1077
|
+
readonly [F in keyof FieldsOf<S, Ns, E>]: TypeOf<S, Ns, E, F>;
|
|
1078
|
+
};
|
|
1079
|
+
/**
|
|
1080
|
+
* Narrowed selection from a `select('id', 'email')` call.
|
|
1081
|
+
*/
|
|
1082
|
+
type PickSelect<S extends BirefSchemaShape, Ns extends keyof S, E extends keyof S[Ns], Keys extends readonly (keyof FieldsOf<S, Ns, E>)[]> = {
|
|
1083
|
+
readonly [K in Keys[number]]: TypeOf<S, Ns, E, K>;
|
|
1084
|
+
};
|
|
1085
|
+
/**
|
|
1086
|
+
* An accumulated include map on a chain builder. Each key is a
|
|
1087
|
+
* relation name; each value is the resolved nested row type.
|
|
1088
|
+
*/
|
|
1089
|
+
type IncludeMap = {
|
|
1090
|
+
readonly [relation: string]: unknown;
|
|
1091
|
+
};
|
|
1092
|
+
/**
|
|
1093
|
+
* The final hydrated row: a merge of the projected selection plus all
|
|
1094
|
+
* nested include results. Each include key maps to either a single
|
|
1095
|
+
* nested row (`cardinality: 'one'`) or an array (`cardinality:
|
|
1096
|
+
* 'many'`).
|
|
1097
|
+
*/
|
|
1098
|
+
type HydratedRow<Sel extends Selection, Inc extends IncludeMap> = {
|
|
1099
|
+
readonly [K in keyof Sel]: Sel[K];
|
|
1100
|
+
} & {
|
|
1101
|
+
readonly [K in keyof Inc]: Inc[K];
|
|
1102
|
+
};
|
|
1103
|
+
|
|
1104
|
+
/**
|
|
1105
|
+
* Operators valid for each `FieldTypeCategory`.
|
|
1106
|
+
*
|
|
1107
|
+
* Equality + nullability operators are always valid. Comparable
|
|
1108
|
+
* categories (integer, decimal, date, timestamp, time) additionally
|
|
1109
|
+
* support ordered comparisons and `between`. String-like categories
|
|
1110
|
+
* support `like` / `ilike`. Arrays of the same element type support
|
|
1111
|
+
* `in` / `not-in`.
|
|
1112
|
+
*
|
|
1113
|
+
* This type is consumed by the typed builder's `where` signature so
|
|
1114
|
+
* the operator argument narrows by the field's category. Accepts an
|
|
1115
|
+
* unconstrained `Category` so callers can pass the output of
|
|
1116
|
+
* `CategoryOfField<...>` without having to widen it manually.
|
|
1117
|
+
*/
|
|
1118
|
+
type OpsFor<Category> = Category extends 'string' | 'uuid' | 'enum' ? 'eq' | 'neq' | 'in' | 'not-in' | 'like' | 'ilike' | 'is-null' | 'is-not-null' : Category extends 'integer' | 'decimal' | 'date' | 'timestamp' | 'time' ? 'eq' | 'neq' | 'lt' | 'lte' | 'gt' | 'gte' | 'between' | 'in' | 'not-in' | 'is-null' | 'is-not-null' : Category extends 'boolean' ? 'eq' | 'neq' | 'is-null' | 'is-not-null' : 'eq' | 'neq' | 'is-null' | 'is-not-null';
|
|
1119
|
+
/**
|
|
1120
|
+
* Value shape required for a given operator and a given field TS
|
|
1121
|
+
* type:
|
|
1122
|
+
*
|
|
1123
|
+
* `in` / `not-in` → readonly T[]
|
|
1124
|
+
* `between` → readonly [T, T]
|
|
1125
|
+
* `is-null` / `is-not-null` → value is not supplied (the where
|
|
1126
|
+
* overload drops the argument)
|
|
1127
|
+
* everything else → T
|
|
1128
|
+
*/
|
|
1129
|
+
type ValueFor<Op extends string, T> = Op extends 'in' | 'not-in' ? readonly T[] : Op extends 'between' ? readonly [T, T] : T;
|
|
1130
|
+
/**
|
|
1131
|
+
* Operators that take no value argument.
|
|
1132
|
+
*/
|
|
1133
|
+
type NullaryOps = 'is-null' | 'is-not-null';
|
|
1134
|
+
|
|
1135
|
+
/**
|
|
1136
|
+
* Type-narrowed fluent interface for the typed query builder.
|
|
1137
|
+
*
|
|
1138
|
+
* Mirrors the runtime `ChainBuilder` class method-for-method but
|
|
1139
|
+
* layers compile-time narrowing on top: `select(...)` narrows the row
|
|
1140
|
+
* shape, `include(...)` appends nested include results, `where(...)`
|
|
1141
|
+
* only accepts operators valid for the field's category. The runtime
|
|
1142
|
+
* `ChainBuilder` instance is cast to this interface by
|
|
1143
|
+
* `NamespaceProxy`.
|
|
1144
|
+
*
|
|
1145
|
+
* Generic parameters:
|
|
1146
|
+
*
|
|
1147
|
+
* S - the user's schema (usually codegen-emitted `BirefSchema`)
|
|
1148
|
+
* Ns - current namespace key
|
|
1149
|
+
* E - current entity key
|
|
1150
|
+
* Sel - accumulated projection (starts as `DefaultSelect`)
|
|
1151
|
+
* Inc - accumulated include map
|
|
1152
|
+
*/
|
|
1153
|
+
interface TypedChain<S extends BirefSchemaShape, Ns extends keyof S, E extends keyof S[Ns], Sel extends Selection = DefaultSelect<S, Ns, E>, Inc extends IncludeMap = Record<string, never>> {
|
|
1154
|
+
/**
|
|
1155
|
+
* Narrow the projection to a specific set of field names. Each name
|
|
1156
|
+
* must exist on the entity; unknowns throw at chain time.
|
|
1157
|
+
*/
|
|
1158
|
+
select<const K extends readonly (keyof FieldsOf<S, Ns, E>)[]>(...fields: K): TypedChain<S, Ns, E, PickSelect<S, Ns, E, K>, Inc>;
|
|
1159
|
+
/**
|
|
1160
|
+
* Explicit wildcard: every field of the entity, same shape as
|
|
1161
|
+
* calling `findMany()` without `select` at all. Kept as a
|
|
1162
|
+
* discoverable sentinel so `.select('*')` reads clearly in code.
|
|
1163
|
+
*/
|
|
1164
|
+
select(wildcard: '*'): TypedChain<S, Ns, E, DefaultSelect<S, Ns, E>, Inc>;
|
|
1165
|
+
/**
|
|
1166
|
+
* Zero-argument shorthand for the wildcard.
|
|
1167
|
+
*/
|
|
1168
|
+
select(): TypedChain<S, Ns, E, DefaultSelect<S, Ns, E>, Inc>;
|
|
1169
|
+
where<F extends keyof FieldsOf<S, Ns, E>, Op extends Exclude<OpsFor<CategoryOfField<S, Ns, E, F>>, NullaryOps>>(field: F, operator: Op, value: ValueFor<Op, TypeOf<S, Ns, E, F>>): TypedChain<S, Ns, E, Sel, Inc>;
|
|
1170
|
+
where<F extends keyof FieldsOf<S, Ns, E>>(field: F, operator: Extract<OpsFor<CategoryOfField<S, Ns, E, F>>, NullaryOps>): TypedChain<S, Ns, E, Sel, Inc>;
|
|
1171
|
+
orderBy<F extends keyof FieldsOf<S, Ns, E>>(field: F, direction?: 'asc' | 'desc'): TypedChain<S, Ns, E, Sel, Inc>;
|
|
1172
|
+
limit(count: number): TypedChain<S, Ns, E, Sel, Inc>;
|
|
1173
|
+
offset(count: number): TypedChain<S, Ns, E, Sel, Inc>;
|
|
1174
|
+
/**
|
|
1175
|
+
* Wildcard include: expand every relation discovered on the
|
|
1176
|
+
* current entity, each with the default projection. No callback
|
|
1177
|
+
* is accepted - the sub-builder shape varies per relation, so a
|
|
1178
|
+
* single callback cannot narrow all of them coherently. Use
|
|
1179
|
+
* named includes for per-relation narrowing.
|
|
1180
|
+
*
|
|
1181
|
+
* .include('*') // every inbound and outbound relation, flat
|
|
1182
|
+
*
|
|
1183
|
+
* Listed first so TypeScript's overload resolver picks it before
|
|
1184
|
+
* the generic single-arg form for the literal `'*'` argument.
|
|
1185
|
+
*/
|
|
1186
|
+
include(relation: '*'): TypedChain<S, Ns, E, Sel, Inc & {
|
|
1187
|
+
readonly [K in keyof RelationsOfEntity<S, Ns, E>]: WrapForCardinality<CardinalityOf<S, Ns, E, K>, HydratedRow<DefaultSelect<S, TargetNs<S, Ns, E, K>, TargetE<S, Ns, E, K>>, Record<string, never>>>;
|
|
1188
|
+
}>;
|
|
1189
|
+
/**
|
|
1190
|
+
* Shorthand include: attach the relation with default projection
|
|
1191
|
+
* (every field of the related entity, no nested includes).
|
|
1192
|
+
*
|
|
1193
|
+
* .include('orders') // every column of each order, flat
|
|
1194
|
+
*
|
|
1195
|
+
* For narrowing or nesting, use the two-argument overload below.
|
|
1196
|
+
*/
|
|
1197
|
+
include<R extends keyof RelationsOfEntity<S, Ns, E>>(relation: R): TypedChain<S, Ns, E, Sel, Inc & {
|
|
1198
|
+
readonly [K in R]: WrapForCardinality<CardinalityOf<S, Ns, E, R>, HydratedRow<DefaultSelect<S, TargetNs<S, Ns, E, R>, TargetE<S, Ns, E, R>>, Record<string, never>>>;
|
|
1199
|
+
}>;
|
|
1200
|
+
/**
|
|
1201
|
+
* Full-control include: the callback receives a zero-state
|
|
1202
|
+
* sub-builder and returns one with the shape you want hydrated
|
|
1203
|
+
* for this include. Supports nested `.include(...)` calls to any
|
|
1204
|
+
* depth, constrained by TypeScript recursion depth.
|
|
1205
|
+
*/
|
|
1206
|
+
include<R extends keyof RelationsOfEntity<S, Ns, E>, SubSel extends Selection, SubInc extends IncludeMap>(relation: R, build: (q: TypedChain<S, TargetNs<S, Ns, E, R>, TargetE<S, Ns, E, R>, DefaultSelect<S, TargetNs<S, Ns, E, R>, TargetE<S, Ns, E, R>>, Record<string, never>>) => TypedChain<S, TargetNs<S, Ns, E, R>, TargetE<S, Ns, E, R>, SubSel, SubInc>): TypedChain<S, Ns, E, Sel, Inc & {
|
|
1207
|
+
readonly [K in R]: WrapForCardinality<CardinalityOf<S, Ns, E, R>, HydratedRow<SubSel, SubInc>>;
|
|
1208
|
+
}>;
|
|
1209
|
+
findMany(): Promise<readonly HydratedRow<Sel, Inc>[]>;
|
|
1210
|
+
findFirst(): Promise<HydratedRow<Sel, Inc> | null>;
|
|
1211
|
+
toPlan(): QueryPlan;
|
|
1212
|
+
}
|
|
1213
|
+
/**
|
|
1214
|
+
* Wraps a hydrated nested row in an array for `cardinality: 'many'`
|
|
1215
|
+
* relations, or yields `T | null` for `cardinality: 'one'`.
|
|
1216
|
+
*/
|
|
1217
|
+
type WrapForCardinality<Card, T> = Card extends 'many' ? readonly T[] : T | null;
|
|
1218
|
+
/**
|
|
1219
|
+
* Shortcut for extracting the category of a field whose key type is
|
|
1220
|
+
* unioned, used inside the `where` overloads above.
|
|
1221
|
+
*/
|
|
1222
|
+
type CategoryOfField<S extends BirefSchemaShape, Ns extends keyof S, E extends keyof S[Ns], F extends keyof FieldsOf<S, Ns, E>> = FieldsOf<S, Ns, E>[F] extends {
|
|
1223
|
+
readonly category: infer C;
|
|
1224
|
+
} ? C : never;
|
|
1225
|
+
/**
|
|
1226
|
+
* Two-layer typed root returned from `biref.query<Schema>(model)`.
|
|
1227
|
+
*
|
|
1228
|
+
* Top level keyed by namespace, inner level keyed by entity. Each
|
|
1229
|
+
* leaf is a zero-state `TypedChain` bound to that namespace/entity.
|
|
1230
|
+
*/
|
|
1231
|
+
type TypedQueryRoot<S extends BirefSchemaShape> = {
|
|
1232
|
+
readonly [Ns in keyof S]: {
|
|
1233
|
+
readonly [E in keyof S[Ns]]: TypedChain<S, Ns, E>;
|
|
1234
|
+
};
|
|
1235
|
+
};
|
|
1236
|
+
|
|
1237
|
+
/**
|
|
1238
|
+
* Top-level facade for the SDK.
|
|
1239
|
+
*
|
|
1240
|
+
* Wraps a configured `AdapterRegistry` and exposes scanning and the
|
|
1241
|
+
* typed query builder. The API is ergonomic for the common case of a
|
|
1242
|
+
* single registered adapter and still supports multi-adapter
|
|
1243
|
+
* scenarios via explicit names or URL scheme detection.
|
|
1244
|
+
*
|
|
1245
|
+
* @example
|
|
1246
|
+
* ```ts
|
|
1247
|
+
* const biref = Biref.builder()
|
|
1248
|
+
* .withAdapter(somePostgresAdapter)
|
|
1249
|
+
* .build();
|
|
1250
|
+
* const model = await biref.scan();
|
|
1251
|
+
* const rows = await biref.query(model).public.users.findMany();
|
|
1252
|
+
* ```
|
|
1253
|
+
*/
|
|
1254
|
+
declare class Biref {
|
|
1255
|
+
private readonly registry;
|
|
1256
|
+
constructor(registry: AdapterRegistry);
|
|
1257
|
+
/** Entry point for the fluent builder. */
|
|
1258
|
+
static builder(): BirefBuilder;
|
|
1259
|
+
/**
|
|
1260
|
+
* Introspect a data store. With a single registered adapter, no
|
|
1261
|
+
* arguments are needed. Pass `IntrospectOptions` to customize the
|
|
1262
|
+
* scan, or an adapter name when more than one adapter is registered.
|
|
1263
|
+
*/
|
|
1264
|
+
scan(): Promise<DataModel>;
|
|
1265
|
+
scan(options: IntrospectOptions): Promise<DataModel>;
|
|
1266
|
+
scan(adapterName: AdapterName, options?: IntrospectOptions): Promise<DataModel>;
|
|
1267
|
+
/**
|
|
1268
|
+
* Introspect a data store using whichever registered adapter handles
|
|
1269
|
+
* the URL's scheme. The URL is only used to pick the adapter; the
|
|
1270
|
+
* actual connection is still owned by the client the user passed to
|
|
1271
|
+
* the adapter at construction time.
|
|
1272
|
+
*/
|
|
1273
|
+
scanByUrl(url: string, options?: IntrospectOptions): Promise<DataModel>;
|
|
1274
|
+
/**
|
|
1275
|
+
* Typed query entry point. Returns a namespace-level Proxy over the
|
|
1276
|
+
* given `DataModel`:
|
|
1277
|
+
*
|
|
1278
|
+
* biref.query(model).public.users
|
|
1279
|
+
* .select('id', 'email')
|
|
1280
|
+
* .where('active', 'eq', true)
|
|
1281
|
+
* .include('orders', (q) => q.select('id', 'total'))
|
|
1282
|
+
* .findMany();
|
|
1283
|
+
*
|
|
1284
|
+
* The Proxy layers (`namespace` → `entity`) enumerate what's
|
|
1285
|
+
* actually in the scanned model, so `Object.keys(biref.query(model))`
|
|
1286
|
+
* returns the discovered namespaces. Access to a non-existent
|
|
1287
|
+
* namespace or entity returns `undefined`; the chain methods throw
|
|
1288
|
+
* with a clear error when given unknown fields or relations.
|
|
1289
|
+
*
|
|
1290
|
+
* With a single registered adapter, the adapter is picked
|
|
1291
|
+
* automatically. Pass `adapterName` explicitly to target a specific
|
|
1292
|
+
* adapter when more than one is registered.
|
|
1293
|
+
*/
|
|
1294
|
+
query<Schema extends BirefSchemaShape = never>(model: DataModel, adapterName?: AdapterName): [Schema] extends [never] ? UntypedQueryRoot : TypedQueryRoot<Schema>;
|
|
1295
|
+
/** Direct access to the underlying registry. */
|
|
1296
|
+
get adapters(): AdapterRegistry;
|
|
1297
|
+
private executorFor;
|
|
1298
|
+
private resolveScanArgs;
|
|
1299
|
+
private requireSingleAdapter;
|
|
1300
|
+
}
|
|
1301
|
+
/**
|
|
1302
|
+
* Fallback return type for `biref.query(model)` when called without a
|
|
1303
|
+
* schema generic. Degrades to a two-layer record of dynamic
|
|
1304
|
+
* `ChainBuilder`s so chains still work at runtime - callers just
|
|
1305
|
+
* don't get the narrowed compile-time shapes.
|
|
1306
|
+
*/
|
|
1307
|
+
type UntypedQueryRoot = Readonly<Record<string, Readonly<Record<string, ChainBuilder>>>>;
|
|
1308
|
+
/**
|
|
1309
|
+
* Fluent builder for `Biref`. Collects adapters and produces a wired
|
|
1310
|
+
* `Biref` facade. Call `build()` once and discard the builder.
|
|
1311
|
+
*/
|
|
1312
|
+
declare class BirefBuilder {
|
|
1313
|
+
private readonly pending;
|
|
1314
|
+
/**
|
|
1315
|
+
* Register an adapter. The adapter's `name` is used as the lookup
|
|
1316
|
+
* key when calling `Biref.scan(name)`. Its optional `urlSchemes` are
|
|
1317
|
+
* used by `Biref.scanByUrl(url)`.
|
|
1318
|
+
*/
|
|
1319
|
+
withAdapter(adapter: Adapter): this;
|
|
1320
|
+
/** Convenience for registering multiple adapters in one call. */
|
|
1321
|
+
withAdapters(...adapters: readonly Adapter[]): this;
|
|
1322
|
+
/**
|
|
1323
|
+
* Materialize the builder into a `Biref` facade. Throws if any
|
|
1324
|
+
* adapter has a duplicate name.
|
|
1325
|
+
*/
|
|
1326
|
+
build(): Biref;
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
/**
|
|
1330
|
+
* Public facade for introspection.
|
|
1331
|
+
*
|
|
1332
|
+
* Takes an `AdapterRegistry`, looks up an adapter by name, and runs
|
|
1333
|
+
* its introspector to produce a `DataModel`. Used for low-level
|
|
1334
|
+
* scenarios where `Biref` is too high-level; most consumers should
|
|
1335
|
+
* prefer `Biref` instead.
|
|
1336
|
+
*
|
|
1337
|
+
* @example
|
|
1338
|
+
* ```ts
|
|
1339
|
+
* const registry = new AdapterRegistry();
|
|
1340
|
+
* registry.register(somePostgresAdapter);
|
|
1341
|
+
* const scanner = new Scanner(registry);
|
|
1342
|
+
* const model = await scanner.scan('postgres');
|
|
1343
|
+
* ```
|
|
1344
|
+
*/
|
|
1345
|
+
declare class Scanner {
|
|
1346
|
+
private readonly registry;
|
|
1347
|
+
constructor(registry: AdapterRegistry);
|
|
1348
|
+
/**
|
|
1349
|
+
* Scan a data store using the named adapter and return a paradigm-
|
|
1350
|
+
* neutral `DataModel`.
|
|
1351
|
+
*/
|
|
1352
|
+
scan(adapterName: string, options?: IntrospectOptions): Promise<DataModel>;
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
/**
|
|
1356
|
+
* Port: turns a stream of `ParsedRecord`s into a representation suitable
|
|
1357
|
+
* for transport, storage, or display.
|
|
1358
|
+
*
|
|
1359
|
+
* `TOutput` is generic so different formatters can produce different
|
|
1360
|
+
* shapes:
|
|
1361
|
+
* - `Formatter<string>`: JSON, CSV, NDJSON, etc.
|
|
1362
|
+
* - `Formatter<readonly ParsedRecord[]>`: pass-through (raw)
|
|
1363
|
+
* - `Formatter<Uint8Array>`: binary formats (e.g. parquet)
|
|
1364
|
+
*
|
|
1365
|
+
* Formatters are stateless and pure: the same input always produces the
|
|
1366
|
+
* same output. They do not depend on any database driver.
|
|
1367
|
+
*/
|
|
1368
|
+
interface Formatter<TOutput = string> {
|
|
1369
|
+
/** Stable format identifier (e.g. 'json', 'csv', 'raw'). */
|
|
1370
|
+
readonly format: string;
|
|
1371
|
+
/** MIME type, for HTTP responses or file naming. */
|
|
1372
|
+
readonly contentType: string;
|
|
1373
|
+
/** Serialize a stream of records into the formatter's target shape. */
|
|
1374
|
+
serialize(records: readonly ParsedRecord[], options?: SerializeOptions): TOutput;
|
|
1375
|
+
}
|
|
1376
|
+
interface SerializeOptions {
|
|
1377
|
+
/**
|
|
1378
|
+
* Explicit field order for formatters where column order matters
|
|
1379
|
+
* (CSV, fixed-width). If omitted, the formatter derives order from
|
|
1380
|
+
* the keys of the first record.
|
|
1381
|
+
*/
|
|
1382
|
+
readonly fields?: readonly string[];
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
interface CsvFormatterOptions {
|
|
1386
|
+
/** Delimiter character. Defaults to ','. */
|
|
1387
|
+
readonly delimiter?: string;
|
|
1388
|
+
/** Whether to emit a header row. Defaults to true. */
|
|
1389
|
+
readonly includeHeader?: boolean;
|
|
1390
|
+
/** Line terminator. Defaults to '\n'. */
|
|
1391
|
+
readonly lineTerminator?: string;
|
|
1392
|
+
}
|
|
1393
|
+
/**
|
|
1394
|
+
* Serializes parsed records to a CSV string.
|
|
1395
|
+
*
|
|
1396
|
+
* Field order:
|
|
1397
|
+
* 1. `options.fields` from the call site, if provided
|
|
1398
|
+
* 2. The keys of the first record, otherwise
|
|
1399
|
+
* 3. Empty string when there are no records
|
|
1400
|
+
*
|
|
1401
|
+
* Escaping follows RFC 4180: cells containing the delimiter, double
|
|
1402
|
+
* quotes, or newlines are wrapped in double quotes, with internal double
|
|
1403
|
+
* quotes doubled.
|
|
1404
|
+
*
|
|
1405
|
+
* Non-string cell values are coerced as follows:
|
|
1406
|
+
* - `Date` → ISO 8601 string
|
|
1407
|
+
* - `bigint` → decimal string
|
|
1408
|
+
* - object/array → JSON-encoded string
|
|
1409
|
+
* - `null` / `undefined` → empty cell
|
|
1410
|
+
*/
|
|
1411
|
+
declare class CsvFormatter implements Formatter<string> {
|
|
1412
|
+
private readonly options;
|
|
1413
|
+
readonly format = "csv";
|
|
1414
|
+
readonly contentType = "text/csv";
|
|
1415
|
+
constructor(options?: CsvFormatterOptions);
|
|
1416
|
+
serialize(records: readonly ParsedRecord[], options?: SerializeOptions): string;
|
|
1417
|
+
private resolveFields;
|
|
1418
|
+
private serializeCell;
|
|
1419
|
+
private escape;
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
interface JsonFormatterOptions {
|
|
1423
|
+
/** Indent the output with 2 spaces if true. Defaults to false. */
|
|
1424
|
+
readonly pretty?: boolean;
|
|
1425
|
+
}
|
|
1426
|
+
/**
|
|
1427
|
+
* Serializes parsed records to a JSON array string.
|
|
1428
|
+
*
|
|
1429
|
+
* Handles values that `JSON.stringify` does not natively support:
|
|
1430
|
+
* - `Date` → ISO 8601 string
|
|
1431
|
+
* - `bigint` → decimal string
|
|
1432
|
+
*/
|
|
1433
|
+
declare class JsonFormatter implements Formatter<string> {
|
|
1434
|
+
private readonly options;
|
|
1435
|
+
readonly format = "json";
|
|
1436
|
+
readonly contentType = "application/json";
|
|
1437
|
+
constructor(options?: JsonFormatterOptions);
|
|
1438
|
+
serialize(records: readonly ParsedRecord[]): string;
|
|
1439
|
+
private static replacer;
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
/**
|
|
1443
|
+
* "Raw" formatter: returns the parsed records as-is, without
|
|
1444
|
+
* serialization. Useful when the consumer wants to keep working with
|
|
1445
|
+
* native JS values (e.g. piping into another JS function) instead of
|
|
1446
|
+
* converting to a string format.
|
|
1447
|
+
*
|
|
1448
|
+
* Unlike the other built-in formatters, this one's `TOutput` is the
|
|
1449
|
+
* record array itself rather than a string. The `Formatter` interface
|
|
1450
|
+
* is generic precisely so this case fits without special-casing.
|
|
1451
|
+
*/
|
|
1452
|
+
declare class RawFormatter implements Formatter<readonly ParsedRecord[]> {
|
|
1453
|
+
readonly format = "raw";
|
|
1454
|
+
readonly contentType = "application/javascript";
|
|
1455
|
+
serialize(records: readonly ParsedRecord[]): readonly ParsedRecord[];
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
/**
|
|
1459
|
+
* Shape of the sibling `biref.schema.overrides.ts` file.
|
|
1460
|
+
*
|
|
1461
|
+
* Keyed by qualified entity name `'namespace.entity'`, each entry
|
|
1462
|
+
* maps field names to the TS type the user wants for that column.
|
|
1463
|
+
* Typical use: forcing a concrete shape on a jsonb column whose
|
|
1464
|
+
* structure the scanner cannot see.
|
|
1465
|
+
*
|
|
1466
|
+
* @example
|
|
1467
|
+
* ```ts
|
|
1468
|
+
* export interface Overrides {
|
|
1469
|
+
* 'identity.users': {
|
|
1470
|
+
* profile: { plan: 'free' | 'pro'; prefs: { darkMode: boolean } };
|
|
1471
|
+
* };
|
|
1472
|
+
* }
|
|
1473
|
+
* ```
|
|
1474
|
+
*/
|
|
1475
|
+
interface BirefOverridesShape {
|
|
1476
|
+
readonly [qualifiedEntity: `${string}.${string}`]: {
|
|
1477
|
+
readonly [field: string]: unknown;
|
|
1478
|
+
};
|
|
1479
|
+
}
|
|
1480
|
+
/**
|
|
1481
|
+
* Deep-merge a user `Overrides` map onto a `RawBirefSchema` produced
|
|
1482
|
+
* by codegen, replacing the `ts` slot on matching fields while
|
|
1483
|
+
* preserving every other descriptor property.
|
|
1484
|
+
*
|
|
1485
|
+
* The override key is the qualified entity name
|
|
1486
|
+
* `'namespace.entity'`, matching the single-level shape of
|
|
1487
|
+
* `BirefOverridesShape`. Entities or fields absent from the overrides
|
|
1488
|
+
* pass through unchanged.
|
|
1489
|
+
*/
|
|
1490
|
+
type ApplySchemaOverrides<Raw extends BirefSchemaShape, Overrides extends BirefOverridesShape> = {
|
|
1491
|
+
readonly [Ns in keyof Raw]: {
|
|
1492
|
+
readonly [E in keyof Raw[Ns]]: Raw[Ns][E] extends {
|
|
1493
|
+
readonly fields: infer F;
|
|
1494
|
+
readonly identifier: infer I;
|
|
1495
|
+
readonly relations: infer R;
|
|
1496
|
+
} ? {
|
|
1497
|
+
readonly fields: {
|
|
1498
|
+
readonly [FieldName in keyof F]: `${Ns & string}.${E & string}` extends keyof Overrides ? FieldName extends keyof Overrides[`${Ns & string}.${E & string}`] ? ReplaceFieldTs<F[FieldName], Overrides[`${Ns & string}.${E & string}`][FieldName]> : F[FieldName] : F[FieldName];
|
|
1499
|
+
};
|
|
1500
|
+
readonly identifier: I;
|
|
1501
|
+
readonly relations: R;
|
|
1502
|
+
} : Raw[Ns][E];
|
|
1503
|
+
};
|
|
1504
|
+
};
|
|
1505
|
+
type ReplaceFieldTs<Field, NewTs> = Field extends SchemaFieldDescriptor<infer _OldTs, infer Nullable, infer Identifier, infer Category> ? SchemaFieldDescriptor<NewTs, Nullable, Identifier, Category> : Field;
|
|
1506
|
+
|
|
1507
|
+
export { type Adapter, type AdapterFactory, type AdapterName, AdapterRegistry, type ApplySchemaOverrides, Biref, BirefBuilder, type BirefOverridesShape, type BirefSchemaShape, type BuiltQuery, type BuiltQueryMetadata, type CardinalityOf, type CategoryOf, type CategoryOfField, ChainBuilder, type ChainBuilderContext, type Constraint, type ConstraintKind, CsvFormatter, type CsvFormatterOptions, DataModel, DefaultRecordParser, type DefaultSelect, type EngineKind, type Entity, type EntityRef, type Field, type FieldType, type FieldTypeCategory, type FieldsOf, type Filter, type FilterOperator, type Formatter, type HydratedRow, type IncludeMap, type Index, type IndexKind, type IntrospectOptions, type Introspector, JsonFormatter, type JsonFormatterOptions, type KnownAdapterName, type NullaryOps, type OpsFor, type OrderBy, POSTGRES_ADAPTER_NAME, POSTGRES_URL_SCHEMES, type ParsedRecord, type ParsedValue, type PickSelect, type PostgresAdapterName, type PostgresClient, PostgresIntrospector, PostgresQueryEngine, PostgresRecordParser, type QueryEngine, type QueryInclude, type QueryPlan, QueryPlanExecutor, type QuerySpec, RawFormatter, type RawQueryRunner, type RecordParser, type Reference, type ReferentialAction, type RelationsOf, type RelationsOfEntity, type Relationship, type RelationshipDirection, Scanner, type SchemaEntityDescriptor, type SchemaFieldDescriptor, type SchemaFile, type SchemaNamespaceDescriptor, type SchemaRelationDescriptor, type Selection, type SerializeOptions, type Split, type TargetE, type TargetNs, type TypeOf, type TypedChain, type TypedQueryRoot, type UntypedQueryRoot, type ValueFor, type WrapForCardinality, generateSchema, generateSchemaFiles, isPostgresAdapter, overridesScaffold, postgresAdapter, tsTypeFor };
|