@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/dist/index.js ADDED
@@ -0,0 +1,2248 @@
1
+ // src/adapters/postgres/pgEnums.ts
2
+ var PgTypeCategory = {
3
+ Array: "A"};
4
+ var PgTypeKind = {
5
+ Enum: "e"};
6
+ var PgConstraintKind = {
7
+ Check: "c",
8
+ Unique: "u",
9
+ Exclusion: "x"
10
+ };
11
+ var PgReferentialAction = {
12
+ NoAction: "a",
13
+ Restrict: "r",
14
+ Cascade: "c",
15
+ SetNull: "n",
16
+ SetDefault: "d"
17
+ };
18
+ var PgIndexMethod = {
19
+ BTree: "btree",
20
+ Hash: "hash",
21
+ Gin: "gin",
22
+ Gist: "gist",
23
+ Brin: "brin",
24
+ SpGist: "spgist"
25
+ };
26
+
27
+ // src/adapters/postgres/mapping/ConstraintMapper.ts
28
+ var ConstraintMapper = class _ConstraintMapper {
29
+ static toConstraint(raw) {
30
+ return {
31
+ name: raw.name,
32
+ kind: _ConstraintMapper.toKind(raw.kind_code),
33
+ fields: raw.columns,
34
+ expression: raw.definition
35
+ };
36
+ }
37
+ static toKind(code) {
38
+ switch (code) {
39
+ case PgConstraintKind.Check: {
40
+ return "check";
41
+ }
42
+ case PgConstraintKind.Unique: {
43
+ return "unique";
44
+ }
45
+ case PgConstraintKind.Exclusion: {
46
+ return "exclusion";
47
+ }
48
+ default: {
49
+ return "custom";
50
+ }
51
+ }
52
+ }
53
+ };
54
+
55
+ // src/adapters/postgres/mapping/FieldMapper.ts
56
+ var FieldMapper = class _FieldMapper {
57
+ static toField(col, isIdentifier) {
58
+ return {
59
+ name: col.name,
60
+ type: _FieldMapper.toType(col),
61
+ nullable: col.nullable,
62
+ isIdentifier,
63
+ defaultValue: col.default_value,
64
+ description: col.description
65
+ };
66
+ }
67
+ static toType(col) {
68
+ if (col.type_category === PgTypeCategory.Array) {
69
+ return {
70
+ category: "array",
71
+ nativeType: col.native_type,
72
+ elementType: _FieldMapper.toElementType(col)
73
+ };
74
+ }
75
+ if (col.type_kind === PgTypeKind.Enum) {
76
+ return {
77
+ category: "enum",
78
+ nativeType: col.native_type,
79
+ enumValues: col.enum_values ?? []
80
+ };
81
+ }
82
+ return {
83
+ category: _FieldMapper.categorize(col.udt_name),
84
+ nativeType: col.native_type
85
+ };
86
+ }
87
+ static toElementType(col) {
88
+ const elementUdt = col.element_udt_name ?? (col.udt_name.startsWith("_") ? col.udt_name.slice(1) : col.udt_name);
89
+ if (col.element_type_kind === PgTypeKind.Enum) {
90
+ return {
91
+ category: "enum",
92
+ nativeType: elementUdt,
93
+ enumValues: col.element_enum_values ?? []
94
+ };
95
+ }
96
+ return {
97
+ category: _FieldMapper.categorize(elementUdt),
98
+ nativeType: elementUdt
99
+ };
100
+ }
101
+ static categorize(udt) {
102
+ switch (udt.toLowerCase()) {
103
+ case "varchar":
104
+ case "bpchar":
105
+ case "char":
106
+ case "text":
107
+ case "name":
108
+ case "citext": {
109
+ return "string";
110
+ }
111
+ case "int2":
112
+ case "int4":
113
+ case "int8":
114
+ case "smallint":
115
+ case "integer":
116
+ case "bigint": {
117
+ return "integer";
118
+ }
119
+ case "numeric":
120
+ case "decimal":
121
+ case "float4":
122
+ case "float8":
123
+ case "real":
124
+ case "money": {
125
+ return "decimal";
126
+ }
127
+ case "bool":
128
+ case "boolean": {
129
+ return "boolean";
130
+ }
131
+ case "date": {
132
+ return "date";
133
+ }
134
+ case "timestamp":
135
+ case "timestamptz": {
136
+ return "timestamp";
137
+ }
138
+ case "time":
139
+ case "timetz": {
140
+ return "time";
141
+ }
142
+ case "json":
143
+ case "jsonb": {
144
+ return "json";
145
+ }
146
+ case "uuid": {
147
+ return "uuid";
148
+ }
149
+ case "bytea": {
150
+ return "binary";
151
+ }
152
+ default: {
153
+ return "unknown";
154
+ }
155
+ }
156
+ }
157
+ };
158
+
159
+ // src/adapters/postgres/mapping/IndexMapper.ts
160
+ var IndexMapper = class _IndexMapper {
161
+ static toIndex(raw) {
162
+ return {
163
+ name: raw.name,
164
+ fields: raw.columns,
165
+ unique: raw.unique,
166
+ kind: _IndexMapper.toKind(raw.method),
167
+ partial: raw.partial,
168
+ definition: raw.definition
169
+ };
170
+ }
171
+ static toKind(method) {
172
+ switch (method.toLowerCase()) {
173
+ case PgIndexMethod.BTree: {
174
+ return "btree";
175
+ }
176
+ case PgIndexMethod.Hash: {
177
+ return "hash";
178
+ }
179
+ case PgIndexMethod.Gin: {
180
+ return "gin";
181
+ }
182
+ case PgIndexMethod.Gist: {
183
+ return "gist";
184
+ }
185
+ case PgIndexMethod.Brin: {
186
+ return "brin";
187
+ }
188
+ case PgIndexMethod.SpGist: {
189
+ return "spgist";
190
+ }
191
+ default: {
192
+ return "unknown";
193
+ }
194
+ }
195
+ }
196
+ };
197
+
198
+ // src/adapters/postgres/mapping/ReferenceMapper.ts
199
+ var ReferenceMapper = class _ReferenceMapper {
200
+ static toReference(fk) {
201
+ return {
202
+ name: fk.name,
203
+ fromEntity: { namespace: fk.from_namespace, name: fk.from_table },
204
+ fromFields: fk.from_columns,
205
+ toEntity: { namespace: fk.to_namespace, name: fk.to_table },
206
+ toFields: fk.to_columns,
207
+ confidence: 1,
208
+ onUpdate: _ReferenceMapper.toAction(fk.update_action_code),
209
+ onDelete: _ReferenceMapper.toAction(fk.delete_action_code)
210
+ };
211
+ }
212
+ static toAction(code) {
213
+ switch (code) {
214
+ case PgReferentialAction.NoAction: {
215
+ return "no-action";
216
+ }
217
+ case PgReferentialAction.Restrict: {
218
+ return "restrict";
219
+ }
220
+ case PgReferentialAction.Cascade: {
221
+ return "cascade";
222
+ }
223
+ case PgReferentialAction.SetNull: {
224
+ return "set-null";
225
+ }
226
+ case PgReferentialAction.SetDefault: {
227
+ return "set-default";
228
+ }
229
+ default: {
230
+ return "no-action";
231
+ }
232
+ }
233
+ }
234
+ };
235
+
236
+ // src/adapters/postgres/mapping/EntityAssembler.ts
237
+ var EntityAssembler = class _EntityAssembler {
238
+ static assemble(inputs) {
239
+ const tableKeys = new Set(
240
+ inputs.tables.map((t) => _EntityAssembler.qualified(t.namespace, t.name))
241
+ );
242
+ const pkByEntity = _EntityAssembler.indexPrimaryKeys(inputs.primaryKeys);
243
+ const colsByEntity = _EntityAssembler.indexColumns(inputs.columns);
244
+ const relsByEntity = _EntityAssembler.buildRelationships(
245
+ inputs.foreignKeys,
246
+ tableKeys
247
+ );
248
+ const indexesByEntity = _EntityAssembler.buildIndexes(
249
+ inputs.indexes,
250
+ tableKeys
251
+ );
252
+ const constraintsByEntity = _EntityAssembler.buildConstraints(
253
+ inputs.constraints,
254
+ tableKeys
255
+ );
256
+ return inputs.tables.map((table) => {
257
+ const key = _EntityAssembler.qualified(table.namespace, table.name);
258
+ const pkColumns = pkByEntity.get(key) ?? [];
259
+ const pkSet = new Set(pkColumns);
260
+ const rawCols = colsByEntity.get(key) ?? [];
261
+ const fields = rawCols.map(
262
+ (col) => FieldMapper.toField(col, pkSet.has(col.name))
263
+ );
264
+ return {
265
+ namespace: table.namespace,
266
+ name: table.name,
267
+ fields,
268
+ identifier: pkColumns,
269
+ relationships: relsByEntity.get(key) ?? [],
270
+ constraints: constraintsByEntity.get(key) ?? [],
271
+ indexes: indexesByEntity.get(key) ?? [],
272
+ description: table.description
273
+ };
274
+ });
275
+ }
276
+ static indexPrimaryKeys(primaryKeys) {
277
+ const map = /* @__PURE__ */ new Map();
278
+ for (const pk of primaryKeys) {
279
+ map.set(
280
+ _EntityAssembler.qualified(pk.namespace, pk.table_name),
281
+ pk.columns
282
+ );
283
+ }
284
+ return map;
285
+ }
286
+ static indexColumns(columns) {
287
+ const map = /* @__PURE__ */ new Map();
288
+ for (const col of columns) {
289
+ const key = _EntityAssembler.qualified(col.namespace, col.table_name);
290
+ const list = map.get(key);
291
+ if (list) {
292
+ list.push(col);
293
+ } else {
294
+ map.set(key, [col]);
295
+ }
296
+ }
297
+ return map;
298
+ }
299
+ static buildRelationships(foreignKeys, tableKeys) {
300
+ const map = /* @__PURE__ */ new Map();
301
+ for (const fk of foreignKeys) {
302
+ const reference = ReferenceMapper.toReference(fk);
303
+ const fromKey = _EntityAssembler.qualified(
304
+ fk.from_namespace,
305
+ fk.from_table
306
+ );
307
+ const toKey = _EntityAssembler.qualified(fk.to_namespace, fk.to_table);
308
+ if (tableKeys.has(fromKey)) {
309
+ _EntityAssembler.push(map, fromKey, {
310
+ direction: "outbound",
311
+ reference
312
+ });
313
+ }
314
+ if (tableKeys.has(toKey)) {
315
+ _EntityAssembler.push(map, toKey, {
316
+ direction: "inbound",
317
+ reference
318
+ });
319
+ }
320
+ }
321
+ return map;
322
+ }
323
+ static buildIndexes(indexes, tableKeys) {
324
+ const map = /* @__PURE__ */ new Map();
325
+ for (const idx of indexes) {
326
+ const key = _EntityAssembler.qualified(idx.namespace, idx.table_name);
327
+ if (!tableKeys.has(key)) {
328
+ continue;
329
+ }
330
+ _EntityAssembler.push(map, key, IndexMapper.toIndex(idx));
331
+ }
332
+ return map;
333
+ }
334
+ static buildConstraints(constraints, tableKeys) {
335
+ const map = /* @__PURE__ */ new Map();
336
+ for (const cn of constraints) {
337
+ const key = _EntityAssembler.qualified(cn.namespace, cn.table_name);
338
+ if (!tableKeys.has(key)) {
339
+ continue;
340
+ }
341
+ _EntityAssembler.push(map, key, ConstraintMapper.toConstraint(cn));
342
+ }
343
+ return map;
344
+ }
345
+ static push(map, key, value) {
346
+ const list = map.get(key);
347
+ if (list) {
348
+ list.push(value);
349
+ } else {
350
+ map.set(key, [value]);
351
+ }
352
+ }
353
+ static qualified(namespace, name) {
354
+ return `${namespace}.${name}`;
355
+ }
356
+ };
357
+
358
+ // src/adapters/postgres/PostgresMeta.ts
359
+ var POSTGRES_ADAPTER_NAME = "postgres";
360
+ var POSTGRES_URL_SCHEMES = [
361
+ "postgres",
362
+ "postgresql"
363
+ ];
364
+
365
+ // src/adapters/postgres/queries/ColumnsQuery.ts
366
+ var ColumnsQuery = class _ColumnsQuery {
367
+ static SQL = `
368
+ SELECT
369
+ n.nspname AS namespace,
370
+ c.relname AS table_name,
371
+ a.attname AS name,
372
+ format_type(a.atttypid, a.atttypmod) AS native_type,
373
+ t.typname AS udt_name,
374
+ t.typcategory AS type_category,
375
+ t.typtype AS type_kind,
376
+ NOT a.attnotnull AS nullable,
377
+ pg_get_expr(d.adbin, d.adrelid) AS default_value,
378
+ col_description(c.oid, a.attnum) AS description,
379
+ CASE
380
+ WHEN t.typtype = 'e' THEN (
381
+ SELECT array_agg(enumlabel ORDER BY enumsortorder)::text[]
382
+ FROM pg_enum
383
+ WHERE enumtypid = a.atttypid
384
+ )
385
+ ELSE NULL
386
+ END AS enum_values,
387
+ et.typname AS element_udt_name,
388
+ et.typcategory AS element_type_category,
389
+ et.typtype AS element_type_kind,
390
+ CASE
391
+ WHEN et.typtype = 'e' THEN (
392
+ SELECT array_agg(enumlabel ORDER BY enumsortorder)::text[]
393
+ FROM pg_enum
394
+ WHERE enumtypid = et.oid
395
+ )
396
+ ELSE NULL
397
+ END AS element_enum_values
398
+ FROM pg_attribute a
399
+ JOIN pg_class c ON c.oid = a.attrelid
400
+ JOIN pg_namespace n ON n.oid = c.relnamespace
401
+ JOIN pg_type t ON t.oid = a.atttypid
402
+ LEFT JOIN pg_type et ON et.oid = t.typelem AND t.typcategory = 'A'
403
+ LEFT JOIN pg_attrdef d ON d.adrelid = c.oid AND d.adnum = a.attnum
404
+ WHERE c.relkind IN ('r', 'p')
405
+ AND a.attnum > 0
406
+ AND NOT a.attisdropped
407
+ AND n.nspname = ANY($1)
408
+ ORDER BY n.nspname, c.relname, a.attnum
409
+ `;
410
+ static async fetch(client, namespaces) {
411
+ const result = await client.query(_ColumnsQuery.SQL, [
412
+ namespaces
413
+ ]);
414
+ return result.rows;
415
+ }
416
+ };
417
+
418
+ // src/adapters/postgres/queries/ConstraintsQuery.ts
419
+ var ConstraintsQuery = class _ConstraintsQuery {
420
+ static SQL = `
421
+ SELECT
422
+ n.nspname AS namespace,
423
+ cls.relname AS table_name,
424
+ c.conname AS name,
425
+ c.contype AS kind_code,
426
+ pg_get_constraintdef(c.oid) AS definition,
427
+ CASE
428
+ WHEN array_length(c.conkey, 1) > 0 THEN
429
+ (SELECT array_agg(att.attname ORDER BY u.ord)::text[]
430
+ FROM unnest(c.conkey) WITH ORDINALITY AS u(attnum, ord)
431
+ JOIN pg_attribute att
432
+ ON att.attrelid = c.conrelid AND att.attnum = u.attnum)
433
+ ELSE ARRAY[]::text[]
434
+ END AS columns
435
+ FROM pg_constraint c
436
+ JOIN pg_class cls ON cls.oid = c.conrelid
437
+ JOIN pg_namespace n ON n.oid = cls.relnamespace
438
+ WHERE c.contype IN ('c', 'u', 'x')
439
+ AND n.nspname = ANY($1)
440
+ ORDER BY n.nspname, cls.relname, c.conname
441
+ `;
442
+ static async fetch(client, namespaces) {
443
+ const result = await client.query(_ConstraintsQuery.SQL, [
444
+ namespaces
445
+ ]);
446
+ return result.rows;
447
+ }
448
+ };
449
+
450
+ // src/adapters/postgres/queries/ForeignKeysQuery.ts
451
+ var ForeignKeysQuery = class _ForeignKeysQuery {
452
+ static SQL = `
453
+ SELECT
454
+ c.conname AS name,
455
+ fn.nspname AS from_namespace,
456
+ fc.relname AS from_table,
457
+ (SELECT array_agg(att.attname ORDER BY u.ord)::text[]
458
+ FROM unnest(c.conkey) WITH ORDINALITY AS u(attnum, ord)
459
+ JOIN pg_attribute att
460
+ ON att.attrelid = c.conrelid AND att.attnum = u.attnum
461
+ ) AS from_columns,
462
+ tn.nspname AS to_namespace,
463
+ tc.relname AS to_table,
464
+ (SELECT array_agg(att.attname ORDER BY u.ord)::text[]
465
+ FROM unnest(c.confkey) WITH ORDINALITY AS u(attnum, ord)
466
+ JOIN pg_attribute att
467
+ ON att.attrelid = c.confrelid AND att.attnum = u.attnum
468
+ ) AS to_columns,
469
+ c.confupdtype AS update_action_code,
470
+ c.confdeltype AS delete_action_code
471
+ FROM pg_constraint c
472
+ JOIN pg_class fc ON fc.oid = c.conrelid
473
+ JOIN pg_namespace fn ON fn.oid = fc.relnamespace
474
+ JOIN pg_class tc ON tc.oid = c.confrelid
475
+ JOIN pg_namespace tn ON tn.oid = tc.relnamespace
476
+ WHERE c.contype = 'f'
477
+ AND fn.nspname = ANY($1)
478
+ `;
479
+ static async fetch(client, namespaces) {
480
+ const result = await client.query(_ForeignKeysQuery.SQL, [
481
+ namespaces
482
+ ]);
483
+ return result.rows;
484
+ }
485
+ };
486
+
487
+ // src/adapters/postgres/queries/IndexesQuery.ts
488
+ var IndexesQuery = class _IndexesQuery {
489
+ static SQL = `
490
+ SELECT
491
+ n.nspname AS namespace,
492
+ t.relname AS table_name,
493
+ i.relname AS name,
494
+ am.amname AS method,
495
+ ix.indisunique AS unique,
496
+ (ix.indpred IS NOT NULL) AS partial,
497
+ pg_get_indexdef(ix.indexrelid) AS definition,
498
+ ARRAY(
499
+ SELECT a.attname
500
+ FROM unnest(ix.indkey::int[]) WITH ORDINALITY AS k(attnum, ord)
501
+ JOIN pg_attribute a
502
+ ON a.attrelid = t.oid AND a.attnum = k.attnum
503
+ ORDER BY k.ord
504
+ )::text[] AS columns
505
+ FROM pg_index ix
506
+ JOIN pg_class i ON i.oid = ix.indexrelid
507
+ JOIN pg_class t ON t.oid = ix.indrelid
508
+ JOIN pg_namespace n ON n.oid = t.relnamespace
509
+ JOIN pg_am am ON am.oid = i.relam
510
+ WHERE NOT ix.indisprimary
511
+ AND n.nspname = ANY($1)
512
+ ORDER BY n.nspname, t.relname, i.relname
513
+ `;
514
+ static async fetch(client, namespaces) {
515
+ const result = await client.query(_IndexesQuery.SQL, [namespaces]);
516
+ return result.rows;
517
+ }
518
+ };
519
+
520
+ // src/adapters/postgres/queries/PrimaryKeysQuery.ts
521
+ var PrimaryKeysQuery = class _PrimaryKeysQuery {
522
+ static SQL = `
523
+ SELECT
524
+ n.nspname AS namespace,
525
+ cls.relname AS table_name,
526
+ (SELECT array_agg(att.attname ORDER BY u.ord)::text[]
527
+ FROM unnest(c.conkey) WITH ORDINALITY AS u(attnum, ord)
528
+ JOIN pg_attribute att
529
+ ON att.attrelid = c.conrelid AND att.attnum = u.attnum
530
+ ) AS columns
531
+ FROM pg_constraint c
532
+ JOIN pg_class cls ON cls.oid = c.conrelid
533
+ JOIN pg_namespace n ON n.oid = cls.relnamespace
534
+ WHERE c.contype = 'p'
535
+ AND n.nspname = ANY($1)
536
+ `;
537
+ static async fetch(client, namespaces) {
538
+ const result = await client.query(_PrimaryKeysQuery.SQL, [
539
+ namespaces
540
+ ]);
541
+ return result.rows;
542
+ }
543
+ };
544
+
545
+ // src/adapters/postgres/queries/TablesQuery.ts
546
+ var TablesQuery = class _TablesQuery {
547
+ static SQL = `
548
+ SELECT
549
+ n.nspname AS namespace,
550
+ c.relname AS name,
551
+ obj_description(c.oid, 'pg_class') AS description
552
+ FROM pg_class c
553
+ JOIN pg_namespace n ON n.oid = c.relnamespace
554
+ WHERE c.relkind IN ('r', 'p')
555
+ AND n.nspname = ANY($1)
556
+ ORDER BY n.nspname, c.relname
557
+ `;
558
+ static async fetch(client, namespaces) {
559
+ const result = await client.query(_TablesQuery.SQL, [namespaces]);
560
+ return result.rows;
561
+ }
562
+ };
563
+
564
+ // src/domain/model/DataModel.ts
565
+ var DataModel = class _DataModel {
566
+ constructor(kind, entities) {
567
+ this.kind = kind;
568
+ this.entities = entities;
569
+ this.index = new Map(
570
+ entities.map((e) => [_DataModel.qualifiedName(e.namespace, e.name), e])
571
+ );
572
+ }
573
+ kind;
574
+ entities;
575
+ index;
576
+ /**
577
+ * Build the lookup key for an entity. Exposed as a static helper so
578
+ * consumers can compute keys consistently when building their own
579
+ * indexes on top of a `DataModel`.
580
+ */
581
+ static qualifiedName(namespace, name) {
582
+ return `${namespace}.${name}`;
583
+ }
584
+ getEntity(namespace, name) {
585
+ return this.index.get(_DataModel.qualifiedName(namespace, name));
586
+ }
587
+ hasEntity(namespace, name) {
588
+ return this.index.has(_DataModel.qualifiedName(namespace, name));
589
+ }
590
+ /**
591
+ * All relationships an entity participates in, in both directions.
592
+ * Returns an empty array if the entity is unknown.
593
+ */
594
+ relationshipsOf(namespace, name) {
595
+ return this.getEntity(namespace, name)?.relationships ?? [];
596
+ }
597
+ /**
598
+ * Outbound relationships: this entity holds the reference.
599
+ * In a relational store these correspond to FOREIGN KEY constraints
600
+ * declared by the entity itself.
601
+ */
602
+ outboundRelationshipsOf(namespace, name) {
603
+ return this.relationshipsOf(namespace, name).filter(
604
+ (r) => r.direction === "outbound"
605
+ );
606
+ }
607
+ /**
608
+ * Inbound relationships: other entities hold references pointing here.
609
+ *
610
+ * This is the differentiating feature of the SDK. Most introspection
611
+ * tools only surface outbound relationships, requiring consumers to
612
+ * know which other entities reference theirs ahead of time.
613
+ */
614
+ inboundRelationshipsOf(namespace, name) {
615
+ return this.relationshipsOf(namespace, name).filter(
616
+ (r) => r.direction === "inbound"
617
+ );
618
+ }
619
+ };
620
+
621
+ // src/adapters/postgres/PostgresIntrospector.ts
622
+ var DEFAULT_NAMESPACES = ["public"];
623
+ var PostgresIntrospector = class _PostgresIntrospector {
624
+ constructor(client) {
625
+ this.client = client;
626
+ }
627
+ client;
628
+ name = POSTGRES_ADAPTER_NAME;
629
+ kind = "relational";
630
+ async introspect(options = {}) {
631
+ const namespaces = await this.resolveNamespaces(options.namespaces);
632
+ const [tables, columns, primaryKeys, foreignKeys, indexes, constraints] = await Promise.all([
633
+ TablesQuery.fetch(this.client, namespaces),
634
+ ColumnsQuery.fetch(this.client, namespaces),
635
+ PrimaryKeysQuery.fetch(this.client, namespaces),
636
+ ForeignKeysQuery.fetch(this.client, namespaces),
637
+ IndexesQuery.fetch(this.client, namespaces),
638
+ ConstraintsQuery.fetch(this.client, namespaces)
639
+ ]);
640
+ const filteredTables = _PostgresIntrospector.applyEntityFilters(
641
+ tables,
642
+ options
643
+ );
644
+ const entities = EntityAssembler.assemble({
645
+ tables: filteredTables,
646
+ columns,
647
+ primaryKeys,
648
+ foreignKeys,
649
+ indexes,
650
+ constraints
651
+ });
652
+ return new DataModel("relational", entities);
653
+ }
654
+ async resolveNamespaces(raw) {
655
+ if (raw === void 0) {
656
+ return DEFAULT_NAMESPACES;
657
+ }
658
+ if (raw === "all") {
659
+ const result = await this.client.query(
660
+ _PostgresIntrospector.ALL_NAMESPACES_SQL
661
+ );
662
+ return result.rows.map((row) => row.nspname);
663
+ }
664
+ return raw;
665
+ }
666
+ static ALL_NAMESPACES_SQL = `
667
+ SELECT n.nspname
668
+ FROM pg_namespace n
669
+ WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
670
+ AND n.nspname NOT LIKE 'pg_toast%'
671
+ AND n.nspname NOT LIKE 'pg_temp_%'
672
+ ORDER BY n.nspname
673
+ `;
674
+ static applyEntityFilters(tables, options) {
675
+ const { includeEntities: allow, excludeEntities: deny } = options;
676
+ if (!allow && !deny) {
677
+ return tables;
678
+ }
679
+ return tables.filter((t) => {
680
+ if (allow && !allow.includes(t.name)) {
681
+ return false;
682
+ }
683
+ if (deny?.includes(t.name)) {
684
+ return false;
685
+ }
686
+ return true;
687
+ });
688
+ }
689
+ };
690
+
691
+ // src/adapters/postgres/query/SqlIdentifier.ts
692
+ var SqlIdentifier = class _SqlIdentifier {
693
+ static quote(id) {
694
+ return `"${id.replace(/"/g, '""')}"`;
695
+ }
696
+ static qualified(namespace, name) {
697
+ return `${_SqlIdentifier.quote(namespace)}.${_SqlIdentifier.quote(name)}`;
698
+ }
699
+ };
700
+
701
+ // src/adapters/postgres/query/FilterBuilder.ts
702
+ var FilterBuilder = class {
703
+ constructor(params) {
704
+ this.params = params;
705
+ }
706
+ params;
707
+ build(filter) {
708
+ const col = SqlIdentifier.quote(filter.field);
709
+ switch (filter.operator) {
710
+ case "eq": {
711
+ return `${col} = ${this.params.add(filter.value)}`;
712
+ }
713
+ case "neq": {
714
+ return `${col} <> ${this.params.add(filter.value)}`;
715
+ }
716
+ case "lt": {
717
+ return `${col} < ${this.params.add(filter.value)}`;
718
+ }
719
+ case "lte": {
720
+ return `${col} <= ${this.params.add(filter.value)}`;
721
+ }
722
+ case "gt": {
723
+ return `${col} > ${this.params.add(filter.value)}`;
724
+ }
725
+ case "gte": {
726
+ return `${col} >= ${this.params.add(filter.value)}`;
727
+ }
728
+ case "like": {
729
+ return `${col} LIKE ${this.params.add(filter.value)}`;
730
+ }
731
+ case "ilike": {
732
+ return `${col} ILIKE ${this.params.add(filter.value)}`;
733
+ }
734
+ case "is-null": {
735
+ return `${col} IS NULL`;
736
+ }
737
+ case "is-not-null": {
738
+ return `${col} IS NOT NULL`;
739
+ }
740
+ case "in": {
741
+ return this.buildInClause(col, filter.value, "FALSE", "IN");
742
+ }
743
+ case "not-in": {
744
+ return this.buildInClause(col, filter.value, "TRUE", "NOT IN");
745
+ }
746
+ case "between": {
747
+ return this.buildBetween(col, filter.value);
748
+ }
749
+ default: {
750
+ const exhaustive = filter.operator;
751
+ throw new Error(`Unknown filter operator: ${String(exhaustive)}`);
752
+ }
753
+ }
754
+ }
755
+ buildInClause(col, value, emptyFallback, keyword) {
756
+ if (!Array.isArray(value)) {
757
+ throw new Error(
758
+ `Operator '${keyword.toLowerCase()}' requires an array value.`
759
+ );
760
+ }
761
+ const values = value;
762
+ if (values.length === 0) {
763
+ return emptyFallback;
764
+ }
765
+ const placeholders = this.params.addAll(values);
766
+ return `${col} ${keyword} (${placeholders.join(", ")})`;
767
+ }
768
+ buildBetween(col, value) {
769
+ if (!Array.isArray(value) || value.length !== 2) {
770
+ throw new Error("Operator 'between' requires a [min, max] tuple.");
771
+ }
772
+ const tuple = value;
773
+ const lo = this.params.add(tuple[0]);
774
+ const hi = this.params.add(tuple[1]);
775
+ return `${col} BETWEEN ${lo} AND ${hi}`;
776
+ }
777
+ };
778
+
779
+ // src/adapters/postgres/query/SqlParamList.ts
780
+ var SqlParamList = class {
781
+ values = [];
782
+ /**
783
+ * Append a value and return its placeholder (`$N`, where N is the
784
+ * 1-based position of the value in the list).
785
+ */
786
+ add(value) {
787
+ this.values.push(value);
788
+ return `$${this.values.length}`;
789
+ }
790
+ /**
791
+ * Append many values and return their placeholders in order.
792
+ */
793
+ addAll(values) {
794
+ return values.map((v) => this.add(v));
795
+ }
796
+ /** Number of values currently bound. */
797
+ get size() {
798
+ return this.values.length;
799
+ }
800
+ /**
801
+ * Return a read-only copy of the bound values. The returned array
802
+ * is not backed by the internal storage.
803
+ */
804
+ toArray() {
805
+ return [...this.values];
806
+ }
807
+ };
808
+
809
+ // src/adapters/postgres/PostgresQueryEngine.ts
810
+ var PostgresQueryEngine = class {
811
+ name = POSTGRES_ADAPTER_NAME;
812
+ kind = "relational";
813
+ build(spec, model) {
814
+ const entity = model.getEntity(spec.namespace, spec.entity);
815
+ if (!entity) {
816
+ throw new Error(
817
+ `Entity "${spec.namespace}.${spec.entity}" not found in the data model.`
818
+ );
819
+ }
820
+ const fieldNames = new Set(entity.fields.map((f) => f.name));
821
+ const selectedFields = spec.select && spec.select.length > 0 ? [...spec.select] : entity.fields.map((f) => f.name);
822
+ for (const field of selectedFields) {
823
+ if (!fieldNames.has(field)) {
824
+ throw new Error(
825
+ `Field "${field}" does not exist on "${spec.namespace}.${spec.entity}".`
826
+ );
827
+ }
828
+ }
829
+ const params = new SqlParamList();
830
+ const filterBuilder = new FilterBuilder(params);
831
+ const selectClause = selectedFields.map(SqlIdentifier.quote).join(", ");
832
+ const fromClause = SqlIdentifier.qualified(spec.namespace, spec.entity);
833
+ let sql = `SELECT ${selectClause} FROM ${fromClause}`;
834
+ if (spec.filters && spec.filters.length > 0) {
835
+ const wherePieces = spec.filters.map((filter) => {
836
+ if (!fieldNames.has(filter.field)) {
837
+ throw new Error(
838
+ `Filter field "${filter.field}" does not exist on "${spec.namespace}.${spec.entity}".`
839
+ );
840
+ }
841
+ return filterBuilder.build(filter);
842
+ });
843
+ sql += ` WHERE ${wherePieces.join(" AND ")}`;
844
+ }
845
+ if (spec.orderBy && spec.orderBy.length > 0) {
846
+ const orderPieces = spec.orderBy.map((order) => {
847
+ if (!fieldNames.has(order.field)) {
848
+ throw new Error(
849
+ `Order field "${order.field}" does not exist on "${spec.namespace}.${spec.entity}".`
850
+ );
851
+ }
852
+ return `${SqlIdentifier.quote(order.field)} ${order.direction.toUpperCase()}`;
853
+ });
854
+ sql += ` ORDER BY ${orderPieces.join(", ")}`;
855
+ }
856
+ if (spec.limit !== void 0) {
857
+ sql += ` LIMIT ${params.add(spec.limit)}`;
858
+ }
859
+ if (spec.offset !== void 0) {
860
+ sql += ` OFFSET ${params.add(spec.offset)}`;
861
+ }
862
+ return {
863
+ command: sql,
864
+ params: params.toArray(),
865
+ metadata: {
866
+ engine: POSTGRES_ADAPTER_NAME,
867
+ paramCount: params.size
868
+ }
869
+ };
870
+ }
871
+ };
872
+
873
+ // src/parsing/DefaultRecordParser.ts
874
+ var DefaultRecordParser = class {
875
+ parse(entity, row) {
876
+ const result = {};
877
+ for (const field of entity.fields) {
878
+ if (field.name in row) {
879
+ result[field.name] = this.coerce(field, row[field.name]);
880
+ }
881
+ }
882
+ return result;
883
+ }
884
+ parseMany(entity, rows) {
885
+ return rows.map((row) => this.parse(entity, row));
886
+ }
887
+ coerce(field, raw) {
888
+ if (raw === null || raw === void 0) {
889
+ return null;
890
+ }
891
+ switch (field.type.category) {
892
+ case "string":
893
+ case "uuid":
894
+ case "enum": {
895
+ return String(raw);
896
+ }
897
+ case "integer": {
898
+ if (typeof raw === "bigint") {
899
+ return raw;
900
+ }
901
+ if (typeof raw === "number") {
902
+ return raw;
903
+ }
904
+ return Number(raw);
905
+ }
906
+ case "decimal": {
907
+ return typeof raw === "string" ? raw : String(raw);
908
+ }
909
+ case "boolean": {
910
+ return Boolean(raw);
911
+ }
912
+ case "date":
913
+ case "timestamp":
914
+ case "time": {
915
+ if (raw instanceof Date) {
916
+ return raw;
917
+ }
918
+ if (typeof raw === "string" || typeof raw === "number") {
919
+ return new Date(raw);
920
+ }
921
+ return null;
922
+ }
923
+ case "json":
924
+ case "binary":
925
+ case "reference":
926
+ case "unknown": {
927
+ return raw;
928
+ }
929
+ case "array": {
930
+ return Array.isArray(raw) ? raw : [];
931
+ }
932
+ default: {
933
+ const exhaustive = field.type.category;
934
+ return exhaustive;
935
+ }
936
+ }
937
+ }
938
+ };
939
+
940
+ // src/adapters/postgres/PostgresRecordParser.ts
941
+ var BIGINT_NATIVE_TYPES = /* @__PURE__ */ new Set(["int8", "bigint"]);
942
+ var PostgresRecordParser = class extends DefaultRecordParser {
943
+ coerce(field, raw) {
944
+ if (raw === null || raw === void 0) {
945
+ return null;
946
+ }
947
+ if (field.type.category === "integer" && BIGINT_NATIVE_TYPES.has(field.type.nativeType.toLowerCase())) {
948
+ if (typeof raw === "bigint") {
949
+ return raw;
950
+ }
951
+ if (typeof raw === "string" || typeof raw === "number") {
952
+ return BigInt(raw);
953
+ }
954
+ return null;
955
+ }
956
+ if (field.type.category === "json" && typeof raw === "string") {
957
+ try {
958
+ return JSON.parse(raw);
959
+ } catch {
960
+ return raw;
961
+ }
962
+ }
963
+ return super.coerce(field, raw);
964
+ }
965
+ };
966
+
967
+ // src/adapters/postgres/PostgresRawQueryRunner.ts
968
+ var PostgresRawQueryRunner = class {
969
+ constructor(client) {
970
+ this.client = client;
971
+ }
972
+ client;
973
+ async run(built) {
974
+ const result = await this.client.query(built.command, [
975
+ ...built.params
976
+ ]);
977
+ return result.rows;
978
+ }
979
+ };
980
+
981
+ // src/adapters/postgres/postgresAdapter.ts
982
+ var postgresAdapter = {
983
+ name: POSTGRES_ADAPTER_NAME,
984
+ urlSchemes: POSTGRES_URL_SCHEMES,
985
+ create(client) {
986
+ return {
987
+ name: POSTGRES_ADAPTER_NAME,
988
+ introspector: new PostgresIntrospector(client),
989
+ engine: new PostgresQueryEngine(),
990
+ runner: new PostgresRawQueryRunner(client),
991
+ parser: new PostgresRecordParser(),
992
+ urlSchemes: POSTGRES_URL_SCHEMES
993
+ };
994
+ }
995
+ };
996
+ function isPostgresAdapter(adapter) {
997
+ return adapter.name === POSTGRES_ADAPTER_NAME;
998
+ }
999
+
1000
+ // src/codegen/OverridesScaffold.ts
1001
+ var SCAFFOLD = `// biref.schema.overrides.ts - edit freely, regen will NOT touch this file.
1002
+ //
1003
+ // Use this file to attach concrete TypeScript types to columns whose
1004
+ // shape the database scanner cannot see - typically jsonb payloads.
1005
+ // Key each override by qualified entity name ('namespace.entity')
1006
+ // and list the fields you want to type.
1007
+ //
1008
+ // @example
1009
+ // export interface Overrides {
1010
+ // 'identity.users': {
1011
+ // profile: { plan: 'free' | 'pro'; prefs: { darkMode: boolean } };
1012
+ // };
1013
+ // }
1014
+
1015
+ export interface Overrides {}
1016
+ `;
1017
+ function overridesScaffold() {
1018
+ return SCAFFOLD;
1019
+ }
1020
+
1021
+ // src/codegen/TsTypeMapper.ts
1022
+ function tsTypeFor(type, nullable) {
1023
+ const base = baseType(type);
1024
+ return nullable ? `${base} | null` : base;
1025
+ }
1026
+ function baseType(type) {
1027
+ switch (type.category) {
1028
+ case "string":
1029
+ case "uuid": {
1030
+ return "string";
1031
+ }
1032
+ case "enum": {
1033
+ if (type.enumValues && type.enumValues.length > 0) {
1034
+ return type.enumValues.map((value) => `'${escapeSingleQuote(value)}'`).join(" | ");
1035
+ }
1036
+ return "string";
1037
+ }
1038
+ case "integer": {
1039
+ return "number";
1040
+ }
1041
+ case "decimal": {
1042
+ return "string";
1043
+ }
1044
+ case "boolean": {
1045
+ return "boolean";
1046
+ }
1047
+ case "date":
1048
+ case "timestamp":
1049
+ case "time": {
1050
+ return "Date";
1051
+ }
1052
+ case "json": {
1053
+ return "unknown";
1054
+ }
1055
+ case "binary": {
1056
+ return "Uint8Array";
1057
+ }
1058
+ case "array": {
1059
+ const element = type.elementType ? baseType(type.elementType) : "unknown";
1060
+ return `ReadonlyArray<${element}>`;
1061
+ }
1062
+ case "reference":
1063
+ case "unknown": {
1064
+ return "unknown";
1065
+ }
1066
+ default: {
1067
+ return assertNever(type.category);
1068
+ }
1069
+ }
1070
+ }
1071
+ function escapeSingleQuote(value) {
1072
+ return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
1073
+ }
1074
+ function assertNever(value) {
1075
+ throw new Error(`Unhandled FieldTypeCategory: ${String(value)}`);
1076
+ }
1077
+
1078
+ // src/query/relationNaming.ts
1079
+ function buildRelationNameMap(entity) {
1080
+ const result = /* @__PURE__ */ new Map();
1081
+ const used = /* @__PURE__ */ new Set();
1082
+ const proposals = [];
1083
+ entity.relationships.forEach((rel, order) => {
1084
+ const other = rel.direction === "outbound" ? rel.reference.toEntity : rel.reference.fromEntity;
1085
+ const isSelfRef = other.namespace === entity.namespace && other.name === entity.name;
1086
+ let name;
1087
+ if (isSelfRef) {
1088
+ name = rel.direction === "outbound" ? "parent" : "children";
1089
+ } else if (rel.direction === "outbound") {
1090
+ name = nameFromColumns(rel.reference.fromFields) ?? other.name;
1091
+ } else {
1092
+ name = other.name;
1093
+ }
1094
+ proposals.push({ rel, name, order });
1095
+ });
1096
+ proposals.sort((a, b) => {
1097
+ if (a.rel.direction !== b.rel.direction) {
1098
+ return a.rel.direction === "outbound" ? -1 : 1;
1099
+ }
1100
+ return a.order - b.order;
1101
+ });
1102
+ for (const { rel, name } of proposals) {
1103
+ let final = name;
1104
+ if (used.has(final)) {
1105
+ final = disambiguate(rel, name, used);
1106
+ }
1107
+ used.add(final);
1108
+ result.set(final, rel);
1109
+ }
1110
+ return result;
1111
+ }
1112
+ function nameFromColumns(columns) {
1113
+ if (columns.length === 0) {
1114
+ return null;
1115
+ }
1116
+ const pieces = columns.map(stripIdSuffix).map((piece) => piece.trim()).filter((piece) => piece.length > 0 && piece.toLowerCase() !== "id");
1117
+ if (pieces.length === 0) {
1118
+ return null;
1119
+ }
1120
+ return pieces.join("_");
1121
+ }
1122
+ function stripIdSuffix(column) {
1123
+ const underscore = column.match(/^(.*)_id$/i);
1124
+ if (underscore?.[1] !== void 0) {
1125
+ return underscore[1];
1126
+ }
1127
+ const camel = column.match(/^(.*)Id$/);
1128
+ if (camel?.[1] !== void 0) {
1129
+ return camel[1];
1130
+ }
1131
+ return column;
1132
+ }
1133
+ function disambiguate(rel, base, used) {
1134
+ if (rel.direction === "inbound") {
1135
+ const suffix = nameFromColumns(rel.reference.fromFields);
1136
+ if (suffix) {
1137
+ const candidate2 = `${base}_by_${suffix}`;
1138
+ if (!used.has(candidate2)) {
1139
+ return candidate2;
1140
+ }
1141
+ }
1142
+ } else {
1143
+ const targetName = rel.direction === "outbound" ? rel.reference.toEntity.name : null;
1144
+ if (targetName) {
1145
+ const candidate2 = `${base}_${targetName}`;
1146
+ if (!used.has(candidate2)) {
1147
+ return candidate2;
1148
+ }
1149
+ }
1150
+ }
1151
+ let candidate = rel.reference.name;
1152
+ if (!used.has(candidate)) {
1153
+ return candidate;
1154
+ }
1155
+ let counter = 2;
1156
+ while (used.has(candidate)) {
1157
+ candidate = `${rel.reference.name}_${counter}`;
1158
+ counter += 1;
1159
+ }
1160
+ return candidate;
1161
+ }
1162
+
1163
+ // src/codegen/SchemaEmitter.ts
1164
+ var GENERATED_BANNER = `// This file is generated by @biref/scanner. Do not edit by hand.
1165
+ // Re-run \`biref gen\` to regenerate it from a live database scan.
1166
+ //
1167
+ // Overrides live in the sibling \`biref.schema.overrides.ts\` file
1168
+ // (never regenerated) and are deep-merged into \`BirefSchema\` via
1169
+ // \`ApplySchemaOverrides\`.`;
1170
+ function generateSchema(model) {
1171
+ const namespaces = groupByNamespace(model);
1172
+ const sortedNamespaces = [...namespaces.keys()].sort();
1173
+ const lines = [];
1174
+ lines.push(GENERATED_BANNER);
1175
+ lines.push("");
1176
+ lines.push("import type { ApplySchemaOverrides } from '@biref/scanner';");
1177
+ lines.push("import type { Overrides } from './biref.schema.overrides';");
1178
+ lines.push("");
1179
+ lines.push("export interface RawBirefSchema {");
1180
+ for (const namespace of sortedNamespaces) {
1181
+ const entities = namespaces.get(namespace);
1182
+ if (!entities) {
1183
+ continue;
1184
+ }
1185
+ lines.push(` readonly ${quoteKey(namespace)}: {`);
1186
+ const sortedEntities = [...entities].sort(
1187
+ (a, b) => a.name.localeCompare(b.name)
1188
+ );
1189
+ for (const entity of sortedEntities) {
1190
+ lines.push(` readonly ${quoteKey(entity.name)}: {`);
1191
+ lines.push(" readonly fields: {");
1192
+ for (const field of entity.fields) {
1193
+ lines.push(indent(formatFieldDescriptor(field), 4));
1194
+ }
1195
+ lines.push(" };");
1196
+ lines.push(` readonly identifier: ${formatIdentifier(entity)};`);
1197
+ const relations = sortedRelations(entity);
1198
+ if (relations.length === 0) {
1199
+ lines.push(" readonly relations: Record<string, never>;");
1200
+ } else {
1201
+ lines.push(" readonly relations: {");
1202
+ for (const [name, relationship] of relations) {
1203
+ lines.push(indent(formatRelationDescriptor(name, relationship), 4));
1204
+ }
1205
+ lines.push(" };");
1206
+ }
1207
+ lines.push(" };");
1208
+ }
1209
+ lines.push(" };");
1210
+ }
1211
+ lines.push("}");
1212
+ lines.push("");
1213
+ lines.push(
1214
+ "export type BirefSchema = ApplySchemaOverrides<RawBirefSchema, Overrides>;"
1215
+ );
1216
+ lines.push("");
1217
+ return lines.join("\n");
1218
+ }
1219
+ function generateSchemaFiles(model) {
1220
+ const namespaces = groupByNamespace(model);
1221
+ const sortedNamespaces = [...namespaces.keys()].sort();
1222
+ const files = [];
1223
+ for (const namespace of sortedNamespaces) {
1224
+ const entities = namespaces.get(namespace);
1225
+ if (!entities) {
1226
+ continue;
1227
+ }
1228
+ const sortedEntities = [...entities].sort(
1229
+ (a, b) => a.name.localeCompare(b.name)
1230
+ );
1231
+ for (const entity of sortedEntities) {
1232
+ files.push({
1233
+ path: `${safeSegment(namespace)}/${safeSegment(entity.name)}.ts`,
1234
+ content: emitEntityFile(entity)
1235
+ });
1236
+ }
1237
+ }
1238
+ files.push({
1239
+ path: "index.ts",
1240
+ content: emitIndexFile(model)
1241
+ });
1242
+ return files;
1243
+ }
1244
+ function emitEntityFile(entity) {
1245
+ const interfaceName = entityInterfaceName(entity.namespace, entity.name);
1246
+ const lines = [];
1247
+ lines.push(GENERATED_BANNER);
1248
+ lines.push("");
1249
+ lines.push(`export interface ${interfaceName} {`);
1250
+ lines.push(" readonly fields: {");
1251
+ for (const field of entity.fields) {
1252
+ lines.push(indent(formatFieldDescriptor(field), 2));
1253
+ }
1254
+ lines.push(" };");
1255
+ lines.push(` readonly identifier: ${formatIdentifier(entity)};`);
1256
+ const relations = sortedRelations(entity);
1257
+ if (relations.length === 0) {
1258
+ lines.push(" readonly relations: Record<string, never>;");
1259
+ } else {
1260
+ lines.push(" readonly relations: {");
1261
+ for (const [name, relationship] of relations) {
1262
+ lines.push(indent(formatRelationDescriptor(name, relationship), 2));
1263
+ }
1264
+ lines.push(" };");
1265
+ }
1266
+ lines.push("}");
1267
+ lines.push("");
1268
+ return lines.join("\n");
1269
+ }
1270
+ function emitIndexFile(model) {
1271
+ const namespaces = groupByNamespace(model);
1272
+ const sortedNamespaces = [...namespaces.keys()].sort();
1273
+ const lines = [];
1274
+ lines.push(GENERATED_BANNER);
1275
+ lines.push("");
1276
+ lines.push("import type { ApplySchemaOverrides } from '@biref/scanner';");
1277
+ lines.push("import type { Overrides } from './biref.schema.overrides';");
1278
+ lines.push("");
1279
+ const allEntities = [];
1280
+ for (const namespace of sortedNamespaces) {
1281
+ const entities = namespaces.get(namespace);
1282
+ if (!entities) {
1283
+ continue;
1284
+ }
1285
+ for (const entity of [...entities].sort(
1286
+ (a, b) => a.name.localeCompare(b.name)
1287
+ )) {
1288
+ allEntities.push({ namespace, entity });
1289
+ }
1290
+ }
1291
+ for (const { namespace, entity } of allEntities) {
1292
+ const iface = entityInterfaceName(namespace, entity.name);
1293
+ lines.push(
1294
+ `import type { ${iface} } from './${safeSegment(namespace)}/${safeSegment(entity.name)}';`
1295
+ );
1296
+ }
1297
+ lines.push("");
1298
+ for (const { namespace, entity } of allEntities) {
1299
+ const iface = entityInterfaceName(namespace, entity.name);
1300
+ lines.push(
1301
+ `export type { ${iface} } from './${safeSegment(namespace)}/${safeSegment(entity.name)}';`
1302
+ );
1303
+ }
1304
+ lines.push("");
1305
+ lines.push("export interface RawBirefSchema {");
1306
+ for (const namespace of sortedNamespaces) {
1307
+ const entities = namespaces.get(namespace);
1308
+ if (!entities) {
1309
+ continue;
1310
+ }
1311
+ lines.push(` readonly ${quoteKey(namespace)}: {`);
1312
+ const sortedEntities = [...entities].sort(
1313
+ (a, b) => a.name.localeCompare(b.name)
1314
+ );
1315
+ for (const entity of sortedEntities) {
1316
+ const iface = entityInterfaceName(namespace, entity.name);
1317
+ lines.push(` readonly ${quoteKey(entity.name)}: ${iface};`);
1318
+ }
1319
+ lines.push(" };");
1320
+ }
1321
+ lines.push("}");
1322
+ lines.push("");
1323
+ lines.push(
1324
+ "export type BirefSchema = ApplySchemaOverrides<RawBirefSchema, Overrides>;"
1325
+ );
1326
+ lines.push("");
1327
+ return lines.join("\n");
1328
+ }
1329
+ function groupByNamespace(model) {
1330
+ const map = /* @__PURE__ */ new Map();
1331
+ for (const entity of model.entities) {
1332
+ const bucket = map.get(entity.namespace);
1333
+ if (bucket) {
1334
+ bucket.push(entity);
1335
+ } else {
1336
+ map.set(entity.namespace, [entity]);
1337
+ }
1338
+ }
1339
+ return map;
1340
+ }
1341
+ function sortedRelations(entity) {
1342
+ return [...buildRelationNameMap(entity).entries()].sort(
1343
+ ([a], [b]) => a.localeCompare(b)
1344
+ );
1345
+ }
1346
+ function formatFieldDescriptor(field) {
1347
+ const ts = tsTypeFor(field.type, field.nullable);
1348
+ const nullable = field.nullable ? "true" : "false";
1349
+ const isIdentifier = field.isIdentifier ? "true" : "false";
1350
+ const category = `'${field.type.category}'`;
1351
+ return ` readonly ${quoteKey(field.name)}: { readonly ts: ${ts}; readonly nullable: ${nullable}; readonly isIdentifier: ${isIdentifier}; readonly category: ${category} };`;
1352
+ }
1353
+ function formatIdentifier(entity) {
1354
+ if (entity.identifier.length === 0) {
1355
+ return "readonly []";
1356
+ }
1357
+ const items = entity.identifier.map((name) => `'${escapeString(name)}'`);
1358
+ return `readonly [${items.join(", ")}]`;
1359
+ }
1360
+ function formatRelationDescriptor(name, relationship) {
1361
+ const target = relationship.direction === "outbound" ? relationship.reference.toEntity : relationship.reference.fromEntity;
1362
+ const qualifiedTarget = `'${escapeString(target.namespace)}.${escapeString(target.name)}'`;
1363
+ const fromFields = formatStringArray(relationship.reference.fromFields);
1364
+ const toFields = formatStringArray(relationship.reference.toFields);
1365
+ const cardinality = relationship.direction === "outbound" ? "'one'" : "'many'";
1366
+ const direction = `'${relationship.direction}'`;
1367
+ return ` readonly ${quoteKey(name)}: { readonly direction: ${direction}; readonly target: ${qualifiedTarget}; readonly fromFields: ${fromFields}; readonly toFields: ${toFields}; readonly cardinality: ${cardinality} };`;
1368
+ }
1369
+ function formatStringArray(values) {
1370
+ if (values.length === 0) {
1371
+ return "readonly []";
1372
+ }
1373
+ return `readonly [${values.map((v) => `'${escapeString(v)}'`).join(", ")}]`;
1374
+ }
1375
+ function quoteKey(key) {
1376
+ if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)) {
1377
+ return key;
1378
+ }
1379
+ return `'${escapeString(key)}'`;
1380
+ }
1381
+ function escapeString(value) {
1382
+ return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
1383
+ }
1384
+ function entityInterfaceName(namespace, entity) {
1385
+ return toPascalCase(namespace) + toPascalCase(entity);
1386
+ }
1387
+ function toPascalCase(value) {
1388
+ return value.split(/[^A-Za-z0-9]+/).filter((segment) => segment.length > 0).map(
1389
+ (segment) => segment.charAt(0).toUpperCase() + segment.slice(1).toLowerCase()
1390
+ ).join("");
1391
+ }
1392
+ function safeSegment(name) {
1393
+ return name.replace(/[^A-Za-z0-9_-]/g, "_");
1394
+ }
1395
+ function indent(text, spaces) {
1396
+ const pad = " ".repeat(spaces);
1397
+ return text.split("\n").map((line) => line.length === 0 ? line : `${pad}${line}`).join("\n");
1398
+ }
1399
+
1400
+ // src/core/AdapterRegistry.ts
1401
+ var AdapterRegistry = class {
1402
+ adapters = /* @__PURE__ */ new Map();
1403
+ register(adapter) {
1404
+ if (this.adapters.has(adapter.name)) {
1405
+ throw new Error(`Adapter "${adapter.name}" is already registered.`);
1406
+ }
1407
+ this.adapters.set(adapter.name, adapter);
1408
+ }
1409
+ unregister(name) {
1410
+ return this.adapters.delete(name);
1411
+ }
1412
+ get(name) {
1413
+ const adapter = this.adapters.get(name);
1414
+ if (!adapter) {
1415
+ const known = [...this.adapters.keys()].join(", ") || "(none)";
1416
+ throw new Error(
1417
+ `No adapter registered under "${name}". Registered adapters: ${known}.`
1418
+ );
1419
+ }
1420
+ return adapter;
1421
+ }
1422
+ has(name) {
1423
+ return this.adapters.has(name);
1424
+ }
1425
+ list() {
1426
+ return [...this.adapters.keys()];
1427
+ }
1428
+ /**
1429
+ * Find an adapter that claims the given URL scheme. Case-insensitive.
1430
+ * Returns `undefined` if no adapter handles it.
1431
+ */
1432
+ findByUrlScheme(scheme) {
1433
+ const normalized = scheme.toLowerCase();
1434
+ for (const adapter of this.adapters.values()) {
1435
+ const matches = adapter.urlSchemes?.some(
1436
+ (s) => s.toLowerCase() === normalized
1437
+ );
1438
+ if (matches) {
1439
+ return adapter;
1440
+ }
1441
+ }
1442
+ return void 0;
1443
+ }
1444
+ /**
1445
+ * Same as `findByUrlScheme` but throws a helpful error listing the
1446
+ * known schemes when no match is found.
1447
+ */
1448
+ getByUrlScheme(scheme) {
1449
+ const adapter = this.findByUrlScheme(scheme);
1450
+ if (!adapter) {
1451
+ const known = [...this.adapters.values()].flatMap((a) => a.urlSchemes ?? []).join(", ") || "(none)";
1452
+ throw new Error(
1453
+ `No adapter registered for URL scheme "${scheme}". Known schemes: ${known}.`
1454
+ );
1455
+ }
1456
+ return adapter;
1457
+ }
1458
+ };
1459
+
1460
+ // src/core/QueryPlanExecutor.ts
1461
+ var QueryPlanExecutor = class _QueryPlanExecutor {
1462
+ constructor(engine, runner, parser) {
1463
+ this.engine = engine;
1464
+ this.runner = runner;
1465
+ this.parser = parser;
1466
+ }
1467
+ engine;
1468
+ runner;
1469
+ parser;
1470
+ async execute(plan, model) {
1471
+ const entity = model.getEntity(plan.spec.namespace, plan.spec.entity);
1472
+ if (!entity) {
1473
+ throw new Error(
1474
+ `Entity "${plan.spec.namespace}.${plan.spec.entity}" not found in the data model.`
1475
+ );
1476
+ }
1477
+ const exposedKeys = _QueryPlanExecutor.resolveExposedKeys(
1478
+ plan.spec,
1479
+ entity,
1480
+ plan.includes
1481
+ );
1482
+ const normalizedSpec = _QueryPlanExecutor.injectFields(
1483
+ plan.spec,
1484
+ _QueryPlanExecutor.collectParentKeys(plan.includes)
1485
+ );
1486
+ const built = this.engine.build(normalizedSpec, model);
1487
+ const rawRows = await this.runner.run(built);
1488
+ const parentRecords = this.parser.parseMany(entity, rawRows);
1489
+ if (plan.includes.length === 0) {
1490
+ return _QueryPlanExecutor.project(parentRecords, exposedKeys);
1491
+ }
1492
+ return this.attachIncludes(
1493
+ parentRecords,
1494
+ plan.includes,
1495
+ exposedKeys,
1496
+ model
1497
+ );
1498
+ }
1499
+ async attachIncludes(parents, includes, parentUserSelect, model) {
1500
+ const working = parents.map((p) => ({
1501
+ ...p
1502
+ }));
1503
+ for (const include of includes) {
1504
+ const childPlan = _QueryPlanExecutor.prepareChildPlan(include, working);
1505
+ if (childPlan === null) {
1506
+ for (const parent of working) {
1507
+ parent[include.relationName] = include.cardinality === "one" ? null : [];
1508
+ }
1509
+ continue;
1510
+ }
1511
+ const rawChildren = await this.execute(childPlan, model);
1512
+ const childExposedKeys = _QueryPlanExecutor.resolveChildExposedKeys(
1513
+ include.plan,
1514
+ model
1515
+ );
1516
+ const indexingKeys = new Set(childExposedKeys);
1517
+ for (const key of include.childKeys) {
1518
+ indexingKeys.add(key);
1519
+ }
1520
+ const byKey = _QueryPlanExecutor.indexByKey(
1521
+ rawChildren.map((c) => _QueryPlanExecutor.filterKeys(c, indexingKeys)),
1522
+ include.childKeys
1523
+ );
1524
+ for (const parent of working) {
1525
+ const lookupKey = _QueryPlanExecutor.buildKey(
1526
+ parent,
1527
+ include.parentKeys
1528
+ );
1529
+ const matches = lookupKey === null ? [] : byKey.get(lookupKey) ?? [];
1530
+ const projectedMatches = _QueryPlanExecutor.project(
1531
+ matches,
1532
+ childExposedKeys
1533
+ );
1534
+ parent[include.relationName] = include.cardinality === "one" ? projectedMatches[0] ?? null : [...projectedMatches];
1535
+ }
1536
+ }
1537
+ return working.map(
1538
+ (parent) => _QueryPlanExecutor.filterKeys(parent, parentUserSelect)
1539
+ );
1540
+ }
1541
+ static prepareChildPlan(include, parents) {
1542
+ if (parents.length === 0) {
1543
+ return null;
1544
+ }
1545
+ if (include.parentKeys.length === 0 || include.childKeys.length === 0) {
1546
+ return null;
1547
+ }
1548
+ const collected = include.parentKeys.map(() => []);
1549
+ let anyMatch = false;
1550
+ for (const parent of parents) {
1551
+ let hasAll = true;
1552
+ const tuple = [];
1553
+ for (const key of include.parentKeys) {
1554
+ const value = parent[key];
1555
+ if (value === null || value === void 0) {
1556
+ hasAll = false;
1557
+ break;
1558
+ }
1559
+ tuple.push(value);
1560
+ }
1561
+ if (!hasAll) {
1562
+ continue;
1563
+ }
1564
+ for (let i = 0; i < tuple.length; i += 1) {
1565
+ const bucket = collected[i];
1566
+ if (bucket) {
1567
+ bucket.push(tuple[i]);
1568
+ }
1569
+ }
1570
+ anyMatch = true;
1571
+ }
1572
+ if (!anyMatch) {
1573
+ return null;
1574
+ }
1575
+ let plan = {
1576
+ ...include.plan,
1577
+ spec: _QueryPlanExecutor.injectFields(
1578
+ include.plan.spec,
1579
+ include.childKeys
1580
+ )
1581
+ };
1582
+ for (let i = 0; i < include.childKeys.length; i += 1) {
1583
+ const childKey = include.childKeys[i];
1584
+ const bucket = collected[i];
1585
+ if (childKey === void 0 || bucket === void 0) {
1586
+ return null;
1587
+ }
1588
+ const unique = Array.from(new Set(bucket));
1589
+ const filter = {
1590
+ field: childKey,
1591
+ operator: "in",
1592
+ value: unique
1593
+ };
1594
+ plan = {
1595
+ ...plan,
1596
+ spec: {
1597
+ ...plan.spec,
1598
+ filters: [...plan.spec.filters ?? [], filter]
1599
+ }
1600
+ };
1601
+ }
1602
+ return plan;
1603
+ }
1604
+ static injectFields(spec, fields) {
1605
+ if (!spec.select || spec.select.length === 0) {
1606
+ return spec;
1607
+ }
1608
+ if (fields.length === 0) {
1609
+ return spec;
1610
+ }
1611
+ const seen = new Set(spec.select);
1612
+ const extras = [];
1613
+ for (const field of fields) {
1614
+ if (!seen.has(field)) {
1615
+ seen.add(field);
1616
+ extras.push(field);
1617
+ }
1618
+ }
1619
+ if (extras.length === 0) {
1620
+ return spec;
1621
+ }
1622
+ return { ...spec, select: [...spec.select, ...extras] };
1623
+ }
1624
+ static collectParentKeys(includes) {
1625
+ const keys = [];
1626
+ for (const include of includes) {
1627
+ for (const key of include.parentKeys) {
1628
+ keys.push(key);
1629
+ }
1630
+ }
1631
+ return keys;
1632
+ }
1633
+ /**
1634
+ * Compute the set of keys a hydrated record should expose to the
1635
+ * caller: the user's selected fields (or every entity field when
1636
+ * no select was given) plus every nested include's relation name
1637
+ * so attached children survive the final filter step.
1638
+ */
1639
+ static resolveExposedKeys(spec, entity, includes) {
1640
+ const set = /* @__PURE__ */ new Set();
1641
+ if (spec.select && spec.select.length > 0) {
1642
+ for (const field of spec.select) {
1643
+ set.add(field);
1644
+ }
1645
+ } else {
1646
+ for (const field of entity.fields) {
1647
+ set.add(field.name);
1648
+ }
1649
+ }
1650
+ for (const include of includes) {
1651
+ set.add(include.relationName);
1652
+ }
1653
+ return set;
1654
+ }
1655
+ /**
1656
+ * Resolve the exposed-keys set for an include's nested plan.
1657
+ *
1658
+ * Uses the ORIGINAL include.plan (not the mutated child plan we
1659
+ * handed to the executor) so injected join keys do not leak into
1660
+ * the final output.
1661
+ */
1662
+ static resolveChildExposedKeys(plan, model) {
1663
+ const entity = model.getEntity(plan.spec.namespace, plan.spec.entity);
1664
+ if (!entity) {
1665
+ return /* @__PURE__ */ new Set();
1666
+ }
1667
+ return _QueryPlanExecutor.resolveExposedKeys(
1668
+ plan.spec,
1669
+ entity,
1670
+ plan.includes
1671
+ );
1672
+ }
1673
+ static project(records, allow) {
1674
+ return records.map((record) => _QueryPlanExecutor.filterKeys(record, allow));
1675
+ }
1676
+ static indexByKey(records, keys) {
1677
+ const map = /* @__PURE__ */ new Map();
1678
+ for (const record of records) {
1679
+ const key = _QueryPlanExecutor.buildKey(record, keys);
1680
+ if (key === null) {
1681
+ continue;
1682
+ }
1683
+ const bucket = map.get(key);
1684
+ if (bucket) {
1685
+ bucket.push(record);
1686
+ } else {
1687
+ map.set(key, [record]);
1688
+ }
1689
+ }
1690
+ return map;
1691
+ }
1692
+ static buildKey(record, keys) {
1693
+ if (keys.length === 0) {
1694
+ return "";
1695
+ }
1696
+ const parts = [];
1697
+ for (const key of keys) {
1698
+ const value = record[key];
1699
+ if (value === null || value === void 0) {
1700
+ return null;
1701
+ }
1702
+ parts.push(
1703
+ typeof value === "bigint" ? `bigint:${value.toString()}` : value
1704
+ );
1705
+ }
1706
+ return JSON.stringify(parts);
1707
+ }
1708
+ static filterKeys(record, allow) {
1709
+ const result = {};
1710
+ for (const [key, value] of Object.entries(record)) {
1711
+ if (allow.has(key)) {
1712
+ result[key] = value;
1713
+ }
1714
+ }
1715
+ return result;
1716
+ }
1717
+ };
1718
+
1719
+ // src/query/ChainBuilder.ts
1720
+ var ChainBuilder = class _ChainBuilder {
1721
+ constructor(ctx, plan) {
1722
+ this.ctx = ctx;
1723
+ this.plan = plan;
1724
+ }
1725
+ ctx;
1726
+ plan;
1727
+ /**
1728
+ * Narrow the projection to a specific set of fields.
1729
+ *
1730
+ * .select('id', 'email') -- only these two columns
1731
+ * .select('*') -- every field of the entity
1732
+ * .select() -- equivalent to '*', kept for ergonomic parity
1733
+ *
1734
+ * Unknown field names throw immediately with a clear error; the
1735
+ * wildcard sentinel skips validation and leaves the plan's `select`
1736
+ * undefined so the engine emits every column the adapter reported.
1737
+ */
1738
+ select(...fields) {
1739
+ if (fields.length === 0 || fields.some((f) => f === "*")) {
1740
+ if (fields.some((f) => f !== "*")) {
1741
+ throw new Error(
1742
+ "select('*') cannot be combined with other field names. Pass either '*' or the specific columns you want."
1743
+ );
1744
+ }
1745
+ return new _ChainBuilder(this.ctx, {
1746
+ ...this.plan,
1747
+ spec: { ...this.plan.spec, select: void 0 }
1748
+ });
1749
+ }
1750
+ const entity = this.requireEntity();
1751
+ const known = new Set(entity.fields.map((f) => f.name));
1752
+ for (const field of fields) {
1753
+ if (!known.has(field)) {
1754
+ throw new Error(
1755
+ `Field "${field}" does not exist on "${this.plan.spec.namespace}.${this.plan.spec.entity}".`
1756
+ );
1757
+ }
1758
+ }
1759
+ return new _ChainBuilder(this.ctx, {
1760
+ ...this.plan,
1761
+ spec: { ...this.plan.spec, select: [...fields] }
1762
+ });
1763
+ }
1764
+ where(field, operator, value) {
1765
+ const entity = this.requireEntity();
1766
+ if (!entity.fields.some((f) => f.name === field)) {
1767
+ throw new Error(
1768
+ `Filter field "${field}" does not exist on "${this.plan.spec.namespace}.${this.plan.spec.entity}".`
1769
+ );
1770
+ }
1771
+ const filter = {
1772
+ field,
1773
+ operator,
1774
+ value: value ?? null
1775
+ };
1776
+ return new _ChainBuilder(this.ctx, {
1777
+ ...this.plan,
1778
+ spec: {
1779
+ ...this.plan.spec,
1780
+ filters: [...this.plan.spec.filters ?? [], filter]
1781
+ }
1782
+ });
1783
+ }
1784
+ orderBy(field, direction = "asc") {
1785
+ const entity = this.requireEntity();
1786
+ if (!entity.fields.some((f) => f.name === field)) {
1787
+ throw new Error(
1788
+ `Order field "${field}" does not exist on "${this.plan.spec.namespace}.${this.plan.spec.entity}".`
1789
+ );
1790
+ }
1791
+ const order = { field, direction };
1792
+ return new _ChainBuilder(this.ctx, {
1793
+ ...this.plan,
1794
+ spec: {
1795
+ ...this.plan.spec,
1796
+ orderBy: [...this.plan.spec.orderBy ?? [], order]
1797
+ }
1798
+ });
1799
+ }
1800
+ limit(count) {
1801
+ return new _ChainBuilder(this.ctx, {
1802
+ ...this.plan,
1803
+ spec: { ...this.plan.spec, limit: count }
1804
+ });
1805
+ }
1806
+ offset(count) {
1807
+ return new _ChainBuilder(this.ctx, {
1808
+ ...this.plan,
1809
+ spec: { ...this.plan.spec, offset: count }
1810
+ });
1811
+ }
1812
+ /**
1813
+ * Attach a nested include to the plan.
1814
+ *
1815
+ * Three call shapes are supported:
1816
+ *
1817
+ * .include('orders') -- all fields, no nested
1818
+ * .include('orders', (q) => q.select('id', 'total')) -- narrow the child
1819
+ * .include('*') -- every relation on this entity
1820
+ *
1821
+ * The callback is optional. When omitted, the child plan uses the
1822
+ * default projection (all fields of the related entity) and no
1823
+ * further includes. Pass a callback when you want to narrow the
1824
+ * projection or chain additional nested includes.
1825
+ *
1826
+ * The wildcard `'*'` expands to one include per relation discovered
1827
+ * on the current entity. Each expanded include uses the default
1828
+ * projection. Combining `'*'` with a callback is not supported -
1829
+ * the sub-builder's shape differs per relation, so a single
1830
+ * callback cannot narrow all of them coherently.
1831
+ */
1832
+ include(relationName, build) {
1833
+ const entity = this.requireEntity();
1834
+ const relationMap = buildRelationNameMap(entity);
1835
+ if (relationName === "*") {
1836
+ if (build) {
1837
+ throw new Error(
1838
+ "Wildcard include '*' does not accept a builder callback. Use named includes to narrow individual relations."
1839
+ );
1840
+ }
1841
+ let current = this;
1842
+ for (const name of relationMap.keys()) {
1843
+ current = current.include(name);
1844
+ }
1845
+ return current;
1846
+ }
1847
+ const relationship = relationMap.get(relationName);
1848
+ if (!relationship) {
1849
+ const known = [...relationMap.keys()].join(", ") || "(none)";
1850
+ throw new Error(
1851
+ `Relation "${relationName}" does not exist on "${this.plan.spec.namespace}.${this.plan.spec.entity}". Known relations: ${known}.`
1852
+ );
1853
+ }
1854
+ const target = relationship.direction === "outbound" ? relationship.reference.toEntity : relationship.reference.fromEntity;
1855
+ const childSpec = {
1856
+ namespace: target.namespace,
1857
+ entity: target.name
1858
+ };
1859
+ const childBuilder = new _ChainBuilder(this.ctx, {
1860
+ spec: childSpec,
1861
+ includes: []
1862
+ });
1863
+ const finishedChild = build ? build(childBuilder) : childBuilder;
1864
+ const parentKeys = relationship.direction === "outbound" ? relationship.reference.fromFields : relationship.reference.toFields;
1865
+ const childKeys = relationship.direction === "outbound" ? relationship.reference.toFields : relationship.reference.fromFields;
1866
+ const include = {
1867
+ relationName,
1868
+ plan: finishedChild.plan,
1869
+ direction: relationship.direction,
1870
+ parentKeys,
1871
+ childKeys,
1872
+ cardinality: relationship.direction === "outbound" ? "one" : "many"
1873
+ };
1874
+ return new _ChainBuilder(this.ctx, {
1875
+ ...this.plan,
1876
+ includes: [...this.plan.includes, include]
1877
+ });
1878
+ }
1879
+ async findMany() {
1880
+ return this.ctx.executor.execute(this.plan, this.ctx.model);
1881
+ }
1882
+ async findFirst() {
1883
+ const limited = {
1884
+ ...this.plan,
1885
+ spec: { ...this.plan.spec, limit: 1 }
1886
+ };
1887
+ const rows = await this.ctx.executor.execute(limited, this.ctx.model);
1888
+ return rows[0] ?? null;
1889
+ }
1890
+ /**
1891
+ * Escape hatch for advanced callers (tests, tooling): exposes the
1892
+ * accumulated plan without executing it. The typed builder's public
1893
+ * surface does not reference this directly.
1894
+ */
1895
+ toPlan() {
1896
+ return this.plan;
1897
+ }
1898
+ requireEntity() {
1899
+ const entity = this.ctx.model.getEntity(
1900
+ this.plan.spec.namespace,
1901
+ this.plan.spec.entity
1902
+ );
1903
+ if (!entity) {
1904
+ throw new Error(
1905
+ `Entity "${this.plan.spec.namespace}.${this.plan.spec.entity}" not found in the data model.`
1906
+ );
1907
+ }
1908
+ return entity;
1909
+ }
1910
+ };
1911
+
1912
+ // src/query/NamespaceProxy.ts
1913
+ function createNamespaceProxy(ctx) {
1914
+ const namespaces = collectNamespaces(ctx.model);
1915
+ return new Proxy(/* @__PURE__ */ Object.create(null), {
1916
+ get(_target, prop) {
1917
+ if (typeof prop !== "string") {
1918
+ return void 0;
1919
+ }
1920
+ if (!namespaces.has(prop)) {
1921
+ return void 0;
1922
+ }
1923
+ return createEntityProxy(ctx, prop);
1924
+ },
1925
+ has(_target, prop) {
1926
+ return typeof prop === "string" && namespaces.has(prop);
1927
+ },
1928
+ ownKeys() {
1929
+ return [...namespaces];
1930
+ },
1931
+ getOwnPropertyDescriptor(_target, prop) {
1932
+ if (typeof prop !== "string" || !namespaces.has(prop)) {
1933
+ return void 0;
1934
+ }
1935
+ return {
1936
+ enumerable: true,
1937
+ configurable: true,
1938
+ value: createEntityProxy(ctx, prop)
1939
+ };
1940
+ }
1941
+ });
1942
+ }
1943
+ function createEntityProxy(ctx, namespace) {
1944
+ const entities = collectEntities(ctx.model, namespace);
1945
+ return new Proxy(/* @__PURE__ */ Object.create(null), {
1946
+ get(_target, prop) {
1947
+ if (typeof prop !== "string") {
1948
+ return void 0;
1949
+ }
1950
+ if (!entities.has(prop)) {
1951
+ return void 0;
1952
+ }
1953
+ return new ChainBuilder(ctx, {
1954
+ spec: { namespace, entity: prop },
1955
+ includes: []
1956
+ });
1957
+ },
1958
+ has(_target, prop) {
1959
+ return typeof prop === "string" && entities.has(prop);
1960
+ },
1961
+ ownKeys() {
1962
+ return [...entities];
1963
+ },
1964
+ getOwnPropertyDescriptor(_target, prop) {
1965
+ if (typeof prop !== "string" || !entities.has(prop)) {
1966
+ return void 0;
1967
+ }
1968
+ return {
1969
+ enumerable: true,
1970
+ configurable: true,
1971
+ value: new ChainBuilder(ctx, {
1972
+ spec: { namespace, entity: prop },
1973
+ includes: []
1974
+ })
1975
+ };
1976
+ }
1977
+ });
1978
+ }
1979
+ function collectNamespaces(model) {
1980
+ const namespaces = /* @__PURE__ */ new Set();
1981
+ for (const entity of model.entities) {
1982
+ namespaces.add(entity.namespace);
1983
+ }
1984
+ return namespaces;
1985
+ }
1986
+ function collectEntities(model, namespace) {
1987
+ const entities = /* @__PURE__ */ new Set();
1988
+ for (const entity of model.entities) {
1989
+ if (entity.namespace === namespace) {
1990
+ entities.add(entity.name);
1991
+ }
1992
+ }
1993
+ return entities;
1994
+ }
1995
+
1996
+ // src/core/Biref.ts
1997
+ var Biref = class {
1998
+ constructor(registry) {
1999
+ this.registry = registry;
2000
+ }
2001
+ registry;
2002
+ /** Entry point for the fluent builder. */
2003
+ static builder() {
2004
+ return new BirefBuilder();
2005
+ }
2006
+ async scan(adapterNameOrOptions, maybeOptions) {
2007
+ const { adapter, options } = this.resolveScanArgs(
2008
+ adapterNameOrOptions,
2009
+ maybeOptions
2010
+ );
2011
+ return adapter.introspector.introspect(options);
2012
+ }
2013
+ /**
2014
+ * Introspect a data store using whichever registered adapter handles
2015
+ * the URL's scheme. The URL is only used to pick the adapter; the
2016
+ * actual connection is still owned by the client the user passed to
2017
+ * the adapter at construction time.
2018
+ */
2019
+ async scanByUrl(url, options) {
2020
+ const scheme = parseUrlScheme(url);
2021
+ return this.registry.getByUrlScheme(scheme).introspector.introspect(options);
2022
+ }
2023
+ /**
2024
+ * Typed query entry point. Returns a namespace-level Proxy over the
2025
+ * given `DataModel`:
2026
+ *
2027
+ * biref.query(model).public.users
2028
+ * .select('id', 'email')
2029
+ * .where('active', 'eq', true)
2030
+ * .include('orders', (q) => q.select('id', 'total'))
2031
+ * .findMany();
2032
+ *
2033
+ * The Proxy layers (`namespace` → `entity`) enumerate what's
2034
+ * actually in the scanned model, so `Object.keys(biref.query(model))`
2035
+ * returns the discovered namespaces. Access to a non-existent
2036
+ * namespace or entity returns `undefined`; the chain methods throw
2037
+ * with a clear error when given unknown fields or relations.
2038
+ *
2039
+ * With a single registered adapter, the adapter is picked
2040
+ * automatically. Pass `adapterName` explicitly to target a specific
2041
+ * adapter when more than one is registered.
2042
+ */
2043
+ query(model, adapterName) {
2044
+ const adapter = adapterName ? this.registry.get(adapterName) : this.requireSingleAdapter("query");
2045
+ const executor = this.executorFor(adapter);
2046
+ const proxy = createNamespaceProxy({ model, executor });
2047
+ return proxy;
2048
+ }
2049
+ /** Direct access to the underlying registry. */
2050
+ get adapters() {
2051
+ return this.registry;
2052
+ }
2053
+ executorFor(adapter) {
2054
+ if (!adapter.runner) {
2055
+ throw new Error(
2056
+ `Adapter "${adapter.name}" does not expose a RawQueryRunner. Update the adapter factory to attach one before calling biref.query(...).`
2057
+ );
2058
+ }
2059
+ if (!adapter.parser) {
2060
+ throw new Error(
2061
+ `Adapter "${adapter.name}" does not expose a RecordParser. Update the adapter factory to attach one before calling biref.query(...).`
2062
+ );
2063
+ }
2064
+ return new QueryPlanExecutor(
2065
+ adapter.engine,
2066
+ adapter.runner,
2067
+ adapter.parser
2068
+ );
2069
+ }
2070
+ resolveScanArgs(adapterNameOrOptions, maybeOptions) {
2071
+ if (typeof adapterNameOrOptions === "string") {
2072
+ return {
2073
+ adapter: this.registry.get(adapterNameOrOptions),
2074
+ options: maybeOptions
2075
+ };
2076
+ }
2077
+ return {
2078
+ adapter: this.requireSingleAdapter("scan"),
2079
+ options: adapterNameOrOptions
2080
+ };
2081
+ }
2082
+ requireSingleAdapter(operation) {
2083
+ const names = this.registry.list();
2084
+ if (names.length === 0) {
2085
+ throw new Error(
2086
+ `Biref.${operation}() called with no adapter argument, but no adapters are registered. Register one via Biref.builder().withAdapter(...).`
2087
+ );
2088
+ }
2089
+ if (names.length > 1) {
2090
+ throw new Error(
2091
+ `Biref.${operation}() called with no adapter argument, but multiple adapters are registered (${names.join(", ")}). Pass an adapter name explicitly.`
2092
+ );
2093
+ }
2094
+ const onlyName = names[0];
2095
+ if (onlyName === void 0) {
2096
+ throw new Error(
2097
+ "Internal error: adapter list reported a length of 1 but produced no name."
2098
+ );
2099
+ }
2100
+ return this.registry.get(onlyName);
2101
+ }
2102
+ };
2103
+ var BirefBuilder = class {
2104
+ pending = [];
2105
+ /**
2106
+ * Register an adapter. The adapter's `name` is used as the lookup
2107
+ * key when calling `Biref.scan(name)`. Its optional `urlSchemes` are
2108
+ * used by `Biref.scanByUrl(url)`.
2109
+ */
2110
+ withAdapter(adapter) {
2111
+ this.pending.push(adapter);
2112
+ return this;
2113
+ }
2114
+ /** Convenience for registering multiple adapters in one call. */
2115
+ withAdapters(...adapters) {
2116
+ for (const adapter of adapters) {
2117
+ this.pending.push(adapter);
2118
+ }
2119
+ return this;
2120
+ }
2121
+ /**
2122
+ * Materialize the builder into a `Biref` facade. Throws if any
2123
+ * adapter has a duplicate name.
2124
+ */
2125
+ build() {
2126
+ const registry = new AdapterRegistry();
2127
+ for (const adapter of this.pending) {
2128
+ registry.register(adapter);
2129
+ }
2130
+ return new Biref(registry);
2131
+ }
2132
+ };
2133
+ function parseUrlScheme(url) {
2134
+ const match = /^([a-zA-Z][a-zA-Z0-9+.-]*):/.exec(url);
2135
+ if (!match?.[1]) {
2136
+ throw new Error(`Cannot determine driver scheme from URL: "${url}"`);
2137
+ }
2138
+ return match[1].toLowerCase();
2139
+ }
2140
+
2141
+ // src/core/Scanner.ts
2142
+ var Scanner = class {
2143
+ constructor(registry) {
2144
+ this.registry = registry;
2145
+ }
2146
+ registry;
2147
+ /**
2148
+ * Scan a data store using the named adapter and return a paradigm-
2149
+ * neutral `DataModel`.
2150
+ */
2151
+ async scan(adapterName, options) {
2152
+ return this.registry.get(adapterName).introspector.introspect(options);
2153
+ }
2154
+ };
2155
+
2156
+ // src/output/CsvFormatter.ts
2157
+ var CsvFormatter = class {
2158
+ constructor(options = {}) {
2159
+ this.options = options;
2160
+ }
2161
+ options;
2162
+ format = "csv";
2163
+ contentType = "text/csv";
2164
+ serialize(records, options) {
2165
+ const delimiter = this.options.delimiter ?? ",";
2166
+ const lineTerminator = this.options.lineTerminator ?? "\n";
2167
+ const includeHeader = this.options.includeHeader ?? true;
2168
+ const fields = this.resolveFields(records, options);
2169
+ if (fields.length === 0) {
2170
+ return "";
2171
+ }
2172
+ const lines = [];
2173
+ if (includeHeader) {
2174
+ lines.push(fields.map((f) => this.escape(f, delimiter)).join(delimiter));
2175
+ }
2176
+ for (const record of records) {
2177
+ const cells = fields.map(
2178
+ (field) => this.serializeCell(record[field], delimiter)
2179
+ );
2180
+ lines.push(cells.join(delimiter));
2181
+ }
2182
+ return lines.join(lineTerminator);
2183
+ }
2184
+ resolveFields(records, options) {
2185
+ if (options?.fields && options.fields.length > 0) {
2186
+ return options.fields;
2187
+ }
2188
+ const first = records[0];
2189
+ return first ? Object.keys(first) : [];
2190
+ }
2191
+ serializeCell(value, delimiter) {
2192
+ if (value === null || value === void 0) {
2193
+ return "";
2194
+ }
2195
+ if (value instanceof Date) {
2196
+ return this.escape(value.toISOString(), delimiter);
2197
+ }
2198
+ if (typeof value === "bigint") {
2199
+ return this.escape(value.toString(), delimiter);
2200
+ }
2201
+ if (typeof value === "object") {
2202
+ return this.escape(JSON.stringify(value), delimiter);
2203
+ }
2204
+ return this.escape(String(value), delimiter);
2205
+ }
2206
+ escape(value, delimiter) {
2207
+ if (value.includes(delimiter) || value.includes('"') || value.includes("\n") || value.includes("\r")) {
2208
+ return `"${value.replace(/"/g, '""')}"`;
2209
+ }
2210
+ return value;
2211
+ }
2212
+ };
2213
+
2214
+ // src/output/JsonFormatter.ts
2215
+ var JsonFormatter = class _JsonFormatter {
2216
+ constructor(options = {}) {
2217
+ this.options = options;
2218
+ }
2219
+ options;
2220
+ format = "json";
2221
+ contentType = "application/json";
2222
+ serialize(records) {
2223
+ const indent2 = this.options.pretty ? 2 : void 0;
2224
+ return JSON.stringify(records, _JsonFormatter.replacer, indent2);
2225
+ }
2226
+ static replacer(_key, value) {
2227
+ if (value instanceof Date) {
2228
+ return value.toISOString();
2229
+ }
2230
+ if (typeof value === "bigint") {
2231
+ return value.toString();
2232
+ }
2233
+ return value;
2234
+ }
2235
+ };
2236
+
2237
+ // src/output/RawFormatter.ts
2238
+ var RawFormatter = class {
2239
+ format = "raw";
2240
+ contentType = "application/javascript";
2241
+ serialize(records) {
2242
+ return records;
2243
+ }
2244
+ };
2245
+
2246
+ export { AdapterRegistry, Biref, BirefBuilder, ChainBuilder, CsvFormatter, DataModel, DefaultRecordParser, JsonFormatter, POSTGRES_ADAPTER_NAME, POSTGRES_URL_SCHEMES, PostgresIntrospector, PostgresQueryEngine, PostgresRecordParser, QueryPlanExecutor, RawFormatter, Scanner, generateSchema, generateSchemaFiles, isPostgresAdapter, overridesScaffold, postgresAdapter, tsTypeFor };
2247
+ //# sourceMappingURL=index.js.map
2248
+ //# sourceMappingURL=index.js.map