@objectstack/driver-sql 9.11.0 → 10.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +61 -0
- package/dist/index.d.ts +61 -0
- package/dist/index.js +173 -16
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +173 -16
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -7
package/dist/index.d.mts
CHANGED
|
@@ -101,6 +101,20 @@ declare class SqlDriver implements IDataDriver {
|
|
|
101
101
|
protected numericFields: Record<string, string[]>;
|
|
102
102
|
protected dateFields: Record<string, Set<string>>;
|
|
103
103
|
protected datetimeFields: Record<string, Set<string>>;
|
|
104
|
+
/**
|
|
105
|
+
* Federation read path (ADR-0015). For external objects whose physical
|
|
106
|
+
* remote table differs from the object name, these map between the two so
|
|
107
|
+
* {@link getBuilder} targets the remote table while the coercion maps above
|
|
108
|
+
* stay keyed by OBJECT name (matching formatInput/formatOutput). Empty for
|
|
109
|
+
* managed objects, so the managed query path is unchanged.
|
|
110
|
+
*/
|
|
111
|
+
protected physicalTableByObject: Record<string, string>;
|
|
112
|
+
protected physicalSchemaByObject: Record<string, string>;
|
|
113
|
+
protected objectByPhysicalTable: Record<string, string>;
|
|
114
|
+
/** External columnMap (ADR-0015): logical field -> physical remote column (for WHERE/ORDER BY/writes). */
|
|
115
|
+
protected fieldColumnByObject: Record<string, Record<string, string>>;
|
|
116
|
+
/** External columnMap inverse: physical remote column -> logical field (for read output remap). */
|
|
117
|
+
protected columnFieldByObject: Record<string, Record<string, string>>;
|
|
104
118
|
protected tablesWithTimestamps: Set<string>;
|
|
105
119
|
/**
|
|
106
120
|
* Autonumber field configs per table, captured during initObjects.
|
|
@@ -334,6 +348,30 @@ declare class SqlDriver implements IDataDriver {
|
|
|
334
348
|
/**
|
|
335
349
|
* Batch-initialise tables from an array of object definitions.
|
|
336
350
|
*/
|
|
351
|
+
/**
|
|
352
|
+
* DDL-free metadata registration for a federated (external) object — the
|
|
353
|
+
* read-path counterpart to {@link initObjects} (ADR-0015 federation).
|
|
354
|
+
*
|
|
355
|
+
* `initObjects` is gated by `assertSchemaMutable` and therefore throws for
|
|
356
|
+
* any non-`managed` driver, which left external objects with NO read-coercion
|
|
357
|
+
* metadata and the query path resolving to a table named after the object
|
|
358
|
+
* instead of its remote table. This populates the same coercion maps (keyed
|
|
359
|
+
* by OBJECT name, matching formatInput/formatOutput/coerceFilterValue) and
|
|
360
|
+
* records the physical remote table (`external.remoteName`, optionally
|
|
361
|
+
* `external.remoteSchema`) so {@link getBuilder} targets it — WITHOUT running
|
|
362
|
+
* any DDL (createTable/alterTable/columnInfo). Keep the field-classification
|
|
363
|
+
* below in sync with initObjects() if the field-type -> storage mapping changes.
|
|
364
|
+
*/
|
|
365
|
+
registerExternalObject(schema: {
|
|
366
|
+
name: string;
|
|
367
|
+
fields?: Record<string, any>;
|
|
368
|
+
tenancy?: any;
|
|
369
|
+
external?: {
|
|
370
|
+
remoteName?: string;
|
|
371
|
+
remoteSchema?: string;
|
|
372
|
+
columnMap?: Record<string, string>;
|
|
373
|
+
};
|
|
374
|
+
}): void;
|
|
337
375
|
initObjects(objects: Array<{
|
|
338
376
|
name: string;
|
|
339
377
|
fields?: Record<string, any>;
|
|
@@ -439,6 +477,16 @@ declare class SqlDriver implements IDataDriver {
|
|
|
439
477
|
* `initObjects`). Returns null when the builder is not table-scoped yet.
|
|
440
478
|
*/
|
|
441
479
|
protected tableNameForBuilder(builder: any): string | null;
|
|
480
|
+
/**
|
|
481
|
+
* Coercion-map key for a builder. Coercion maps (date/datetime) are keyed by
|
|
482
|
+
* OBJECT name, but after the federation change {@link getBuilder} targets the
|
|
483
|
+
* physical remote table, so a builder reports the remote name. Map it back to
|
|
484
|
+
* the object name for external objects; identity for managed ones (no reverse
|
|
485
|
+
* entry). Note datetime coercion is a SQLite-only concern (see
|
|
486
|
+
* coerceFilterValue), and SQLite external tables are bare-named, so this is
|
|
487
|
+
* exact where it matters.
|
|
488
|
+
*/
|
|
489
|
+
protected coercionKey(builder: any): string | null;
|
|
442
490
|
/**
|
|
443
491
|
* Collapse a `Field.date` value to a timezone-naive `YYYY-MM-DD`
|
|
444
492
|
* calendar-day string (ADR-0053 Phase 1). A `Date` collapses to its UTC
|
|
@@ -498,6 +546,19 @@ declare class SqlDriver implements IDataDriver {
|
|
|
498
546
|
private applyContainsLike;
|
|
499
547
|
protected applyFilterCondition(builder: Knex.QueryBuilder, condition: any, logicalOp?: 'and' | 'or', tableHint?: string | null): void;
|
|
500
548
|
protected mapSortField(field: string): string;
|
|
549
|
+
/**
|
|
550
|
+
* Physical column for a logical field on an external object that declares an
|
|
551
|
+
* `external.columnMap` (ADR-0015). Returns `fallback` (the caller's existing
|
|
552
|
+
* per-site resolution) when the object has no columnMap, so managed objects
|
|
553
|
+
* and external objects without a columnMap are byte-for-byte unchanged.
|
|
554
|
+
*/
|
|
555
|
+
protected remoteColumn(object: string | null | undefined, field: string, fallback: string): string;
|
|
556
|
+
/**
|
|
557
|
+
* Remap a write payload's logical field keys to physical remote columns for an
|
|
558
|
+
* external object with a columnMap. No-op otherwise. Applied AFTER formatInput
|
|
559
|
+
* (whose value coercion is keyed by logical field name).
|
|
560
|
+
*/
|
|
561
|
+
protected applyWriteColumnMap(object: string, data: any): any;
|
|
501
562
|
protected mapAggregateFunc(func: string): string;
|
|
502
563
|
protected buildWindowFunction(spec: any): string;
|
|
503
564
|
protected createColumn(table: Knex.CreateTableBuilder, name: string, field: any): void;
|
package/dist/index.d.ts
CHANGED
|
@@ -101,6 +101,20 @@ declare class SqlDriver implements IDataDriver {
|
|
|
101
101
|
protected numericFields: Record<string, string[]>;
|
|
102
102
|
protected dateFields: Record<string, Set<string>>;
|
|
103
103
|
protected datetimeFields: Record<string, Set<string>>;
|
|
104
|
+
/**
|
|
105
|
+
* Federation read path (ADR-0015). For external objects whose physical
|
|
106
|
+
* remote table differs from the object name, these map between the two so
|
|
107
|
+
* {@link getBuilder} targets the remote table while the coercion maps above
|
|
108
|
+
* stay keyed by OBJECT name (matching formatInput/formatOutput). Empty for
|
|
109
|
+
* managed objects, so the managed query path is unchanged.
|
|
110
|
+
*/
|
|
111
|
+
protected physicalTableByObject: Record<string, string>;
|
|
112
|
+
protected physicalSchemaByObject: Record<string, string>;
|
|
113
|
+
protected objectByPhysicalTable: Record<string, string>;
|
|
114
|
+
/** External columnMap (ADR-0015): logical field -> physical remote column (for WHERE/ORDER BY/writes). */
|
|
115
|
+
protected fieldColumnByObject: Record<string, Record<string, string>>;
|
|
116
|
+
/** External columnMap inverse: physical remote column -> logical field (for read output remap). */
|
|
117
|
+
protected columnFieldByObject: Record<string, Record<string, string>>;
|
|
104
118
|
protected tablesWithTimestamps: Set<string>;
|
|
105
119
|
/**
|
|
106
120
|
* Autonumber field configs per table, captured during initObjects.
|
|
@@ -334,6 +348,30 @@ declare class SqlDriver implements IDataDriver {
|
|
|
334
348
|
/**
|
|
335
349
|
* Batch-initialise tables from an array of object definitions.
|
|
336
350
|
*/
|
|
351
|
+
/**
|
|
352
|
+
* DDL-free metadata registration for a federated (external) object — the
|
|
353
|
+
* read-path counterpart to {@link initObjects} (ADR-0015 federation).
|
|
354
|
+
*
|
|
355
|
+
* `initObjects` is gated by `assertSchemaMutable` and therefore throws for
|
|
356
|
+
* any non-`managed` driver, which left external objects with NO read-coercion
|
|
357
|
+
* metadata and the query path resolving to a table named after the object
|
|
358
|
+
* instead of its remote table. This populates the same coercion maps (keyed
|
|
359
|
+
* by OBJECT name, matching formatInput/formatOutput/coerceFilterValue) and
|
|
360
|
+
* records the physical remote table (`external.remoteName`, optionally
|
|
361
|
+
* `external.remoteSchema`) so {@link getBuilder} targets it — WITHOUT running
|
|
362
|
+
* any DDL (createTable/alterTable/columnInfo). Keep the field-classification
|
|
363
|
+
* below in sync with initObjects() if the field-type -> storage mapping changes.
|
|
364
|
+
*/
|
|
365
|
+
registerExternalObject(schema: {
|
|
366
|
+
name: string;
|
|
367
|
+
fields?: Record<string, any>;
|
|
368
|
+
tenancy?: any;
|
|
369
|
+
external?: {
|
|
370
|
+
remoteName?: string;
|
|
371
|
+
remoteSchema?: string;
|
|
372
|
+
columnMap?: Record<string, string>;
|
|
373
|
+
};
|
|
374
|
+
}): void;
|
|
337
375
|
initObjects(objects: Array<{
|
|
338
376
|
name: string;
|
|
339
377
|
fields?: Record<string, any>;
|
|
@@ -439,6 +477,16 @@ declare class SqlDriver implements IDataDriver {
|
|
|
439
477
|
* `initObjects`). Returns null when the builder is not table-scoped yet.
|
|
440
478
|
*/
|
|
441
479
|
protected tableNameForBuilder(builder: any): string | null;
|
|
480
|
+
/**
|
|
481
|
+
* Coercion-map key for a builder. Coercion maps (date/datetime) are keyed by
|
|
482
|
+
* OBJECT name, but after the federation change {@link getBuilder} targets the
|
|
483
|
+
* physical remote table, so a builder reports the remote name. Map it back to
|
|
484
|
+
* the object name for external objects; identity for managed ones (no reverse
|
|
485
|
+
* entry). Note datetime coercion is a SQLite-only concern (see
|
|
486
|
+
* coerceFilterValue), and SQLite external tables are bare-named, so this is
|
|
487
|
+
* exact where it matters.
|
|
488
|
+
*/
|
|
489
|
+
protected coercionKey(builder: any): string | null;
|
|
442
490
|
/**
|
|
443
491
|
* Collapse a `Field.date` value to a timezone-naive `YYYY-MM-DD`
|
|
444
492
|
* calendar-day string (ADR-0053 Phase 1). A `Date` collapses to its UTC
|
|
@@ -498,6 +546,19 @@ declare class SqlDriver implements IDataDriver {
|
|
|
498
546
|
private applyContainsLike;
|
|
499
547
|
protected applyFilterCondition(builder: Knex.QueryBuilder, condition: any, logicalOp?: 'and' | 'or', tableHint?: string | null): void;
|
|
500
548
|
protected mapSortField(field: string): string;
|
|
549
|
+
/**
|
|
550
|
+
* Physical column for a logical field on an external object that declares an
|
|
551
|
+
* `external.columnMap` (ADR-0015). Returns `fallback` (the caller's existing
|
|
552
|
+
* per-site resolution) when the object has no columnMap, so managed objects
|
|
553
|
+
* and external objects without a columnMap are byte-for-byte unchanged.
|
|
554
|
+
*/
|
|
555
|
+
protected remoteColumn(object: string | null | undefined, field: string, fallback: string): string;
|
|
556
|
+
/**
|
|
557
|
+
* Remap a write payload's logical field keys to physical remote columns for an
|
|
558
|
+
* external object with a columnMap. No-op otherwise. Applied AFTER formatInput
|
|
559
|
+
* (whose value coercion is keyed by logical field name).
|
|
560
|
+
*/
|
|
561
|
+
protected applyWriteColumnMap(object: string, data: any): any;
|
|
501
562
|
protected mapAggregateFunc(func: string): string;
|
|
502
563
|
protected buildWindowFunction(spec: any): string;
|
|
503
564
|
protected createColumn(table: Knex.CreateTableBuilder, name: string, field: any): void;
|
package/dist/index.js
CHANGED
|
@@ -86,6 +86,20 @@ var SqlDriver = class {
|
|
|
86
86
|
this.numericFields = {};
|
|
87
87
|
this.dateFields = {};
|
|
88
88
|
this.datetimeFields = {};
|
|
89
|
+
/**
|
|
90
|
+
* Federation read path (ADR-0015). For external objects whose physical
|
|
91
|
+
* remote table differs from the object name, these map between the two so
|
|
92
|
+
* {@link getBuilder} targets the remote table while the coercion maps above
|
|
93
|
+
* stay keyed by OBJECT name (matching formatInput/formatOutput). Empty for
|
|
94
|
+
* managed objects, so the managed query path is unchanged.
|
|
95
|
+
*/
|
|
96
|
+
this.physicalTableByObject = {};
|
|
97
|
+
this.physicalSchemaByObject = {};
|
|
98
|
+
this.objectByPhysicalTable = {};
|
|
99
|
+
/** External columnMap (ADR-0015): logical field -> physical remote column (for WHERE/ORDER BY/writes). */
|
|
100
|
+
this.fieldColumnByObject = {};
|
|
101
|
+
/** External columnMap inverse: physical remote column -> logical field (for read output remap). */
|
|
102
|
+
this.columnFieldByObject = {};
|
|
89
103
|
this.tablesWithTimestamps = /* @__PURE__ */ new Set();
|
|
90
104
|
/**
|
|
91
105
|
* Autonumber field configs per table, captured during initObjects.
|
|
@@ -310,6 +324,13 @@ var SqlDriver = class {
|
|
|
310
324
|
// ===================================
|
|
311
325
|
async connect() {
|
|
312
326
|
await this.ensureDatabaseExists();
|
|
327
|
+
if (this.isSqlite) {
|
|
328
|
+
try {
|
|
329
|
+
await this.knex.raw("PRAGMA auto_vacuum = INCREMENTAL");
|
|
330
|
+
} catch (e) {
|
|
331
|
+
this.logger.warn("Failed to set PRAGMA auto_vacuum=INCREMENTAL", e);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
313
334
|
}
|
|
314
335
|
async checkHealth() {
|
|
315
336
|
try {
|
|
@@ -335,7 +356,7 @@ var SqlDriver = class {
|
|
|
335
356
|
if (query.orderBy && Array.isArray(query.orderBy)) {
|
|
336
357
|
for (const item of query.orderBy) {
|
|
337
358
|
if (item.field) {
|
|
338
|
-
b.orderBy(this.mapSortField(item.field), item.order || "asc");
|
|
359
|
+
b.orderBy(this.remoteColumn(object, item.field, this.mapSortField(item.field)), item.order || "asc");
|
|
339
360
|
}
|
|
340
361
|
}
|
|
341
362
|
}
|
|
@@ -412,7 +433,7 @@ var SqlDriver = class {
|
|
|
412
433
|
this.injectTenantOnInsert(object, toInsert, options);
|
|
413
434
|
await this.fillAutoNumberFields(object, toInsert, options);
|
|
414
435
|
const builder = this.getBuilder(object, options);
|
|
415
|
-
const formatted = this.formatInput(object, toInsert);
|
|
436
|
+
const formatted = this.applyWriteColumnMap(object, this.formatInput(object, toInsert));
|
|
416
437
|
const result = await builder.insert(formatted).returning("*");
|
|
417
438
|
return this.formatOutput(object, result[0]);
|
|
418
439
|
}
|
|
@@ -609,8 +630,8 @@ var SqlDriver = class {
|
|
|
609
630
|
* tenant scopes it for isolation.
|
|
610
631
|
*/
|
|
611
632
|
async fillAutoNumberFields(object, row, options) {
|
|
612
|
-
const tableName = import_system.StorageNameMapping.resolveTableName({ name: object });
|
|
613
|
-
const cfgs = this.autoNumberFields[
|
|
633
|
+
const tableName = this.physicalTableByObject[object] ?? import_system.StorageNameMapping.resolveTableName({ name: object });
|
|
634
|
+
const cfgs = this.autoNumberFields[object] || this.autoNumberFields[tableName];
|
|
614
635
|
if (!cfgs || cfgs.length === 0) return;
|
|
615
636
|
const parentTrx = options?.transaction;
|
|
616
637
|
const timezone = options?.timezone;
|
|
@@ -644,7 +665,7 @@ var SqlDriver = class {
|
|
|
644
665
|
this.auditMissingTenant(object, "update", options);
|
|
645
666
|
const builder = this.getBuilder(object, options).where("id", id);
|
|
646
667
|
this.applyTenantScope(builder, object, options);
|
|
647
|
-
const formatted = this.formatInput(object, data);
|
|
668
|
+
const formatted = this.applyWriteColumnMap(object, this.formatInput(object, data));
|
|
648
669
|
if (this.tablesWithTimestamps.has(object)) {
|
|
649
670
|
if (this.isSqlite) {
|
|
650
671
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -670,7 +691,7 @@ var SqlDriver = class {
|
|
|
670
691
|
this.auditMissingTenant(object, "upsert", options);
|
|
671
692
|
this.injectTenantOnInsert(object, toUpsert, options);
|
|
672
693
|
await this.fillAutoNumberFields(object, toUpsert, options);
|
|
673
|
-
const formatted = this.formatInput(object, toUpsert);
|
|
694
|
+
const formatted = this.applyWriteColumnMap(object, this.formatInput(object, toUpsert));
|
|
674
695
|
const mergeKeys = conflictKeys && conflictKeys.length > 0 ? conflictKeys : ["id"];
|
|
675
696
|
const builder = this.getBuilder(object, options);
|
|
676
697
|
await builder.insert(formatted).onConflict(mergeKeys).merge();
|
|
@@ -953,6 +974,89 @@ var SqlDriver = class {
|
|
|
953
974
|
/**
|
|
954
975
|
* Batch-initialise tables from an array of object definitions.
|
|
955
976
|
*/
|
|
977
|
+
/**
|
|
978
|
+
* DDL-free metadata registration for a federated (external) object — the
|
|
979
|
+
* read-path counterpart to {@link initObjects} (ADR-0015 federation).
|
|
980
|
+
*
|
|
981
|
+
* `initObjects` is gated by `assertSchemaMutable` and therefore throws for
|
|
982
|
+
* any non-`managed` driver, which left external objects with NO read-coercion
|
|
983
|
+
* metadata and the query path resolving to a table named after the object
|
|
984
|
+
* instead of its remote table. This populates the same coercion maps (keyed
|
|
985
|
+
* by OBJECT name, matching formatInput/formatOutput/coerceFilterValue) and
|
|
986
|
+
* records the physical remote table (`external.remoteName`, optionally
|
|
987
|
+
* `external.remoteSchema`) so {@link getBuilder} targets it — WITHOUT running
|
|
988
|
+
* any DDL (createTable/alterTable/columnInfo). Keep the field-classification
|
|
989
|
+
* below in sync with initObjects() if the field-type -> storage mapping changes.
|
|
990
|
+
*/
|
|
991
|
+
registerExternalObject(schema) {
|
|
992
|
+
const key = schema.name;
|
|
993
|
+
const remoteName = schema.external?.remoteName || schema.name;
|
|
994
|
+
const remoteSchema = schema.external?.remoteSchema;
|
|
995
|
+
this.physicalTableByObject[key] = remoteName;
|
|
996
|
+
this.objectByPhysicalTable[remoteName] = key;
|
|
997
|
+
if (remoteSchema) {
|
|
998
|
+
if (this.isSqlite) {
|
|
999
|
+
this.logger.warn(
|
|
1000
|
+
`[sql-driver] external object "${key}" declares remoteSchema="${remoteSchema}" but SQLite has no schema namespace; ignoring (treating "${remoteName}" as a bare table).`
|
|
1001
|
+
);
|
|
1002
|
+
} else {
|
|
1003
|
+
this.physicalSchemaByObject[key] = remoteSchema;
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
const columnMap = schema.external?.columnMap;
|
|
1007
|
+
if (columnMap && typeof columnMap === "object" && Object.keys(columnMap).length > 0) {
|
|
1008
|
+
const fieldToCol = {};
|
|
1009
|
+
const colToField = {};
|
|
1010
|
+
for (const [remoteCol, localField] of Object.entries(columnMap)) {
|
|
1011
|
+
if (typeof localField === "string" && localField) {
|
|
1012
|
+
fieldToCol[localField] = remoteCol;
|
|
1013
|
+
colToField[remoteCol] = localField;
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
this.fieldColumnByObject[key] = fieldToCol;
|
|
1017
|
+
this.columnFieldByObject[key] = colToField;
|
|
1018
|
+
}
|
|
1019
|
+
const jsonCols = [];
|
|
1020
|
+
const booleanCols = [];
|
|
1021
|
+
const numericCols = [];
|
|
1022
|
+
const dateCols = [];
|
|
1023
|
+
const datetimeCols = [];
|
|
1024
|
+
const autoNumberCols = [];
|
|
1025
|
+
const tenancyDecl = schema?.tenancy;
|
|
1026
|
+
let tenantField = null;
|
|
1027
|
+
if (tenancyDecl && tenancyDecl.enabled !== false && tenancyDecl.tenantField) {
|
|
1028
|
+
const declared = String(tenancyDecl.tenantField);
|
|
1029
|
+
if (schema.fields && Object.prototype.hasOwnProperty.call(schema.fields, declared)) {
|
|
1030
|
+
tenantField = declared;
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
if (!tenantField) {
|
|
1034
|
+
const hasOrgField = !!(schema.fields && Object.prototype.hasOwnProperty.call(schema.fields, "organization_id"));
|
|
1035
|
+
tenantField = hasOrgField ? "organization_id" : null;
|
|
1036
|
+
}
|
|
1037
|
+
if (schema.fields) {
|
|
1038
|
+
for (const [name, field] of Object.entries(schema.fields)) {
|
|
1039
|
+
const type = field.type || "string";
|
|
1040
|
+
if (this.isJsonField(type, field)) jsonCols.push(name);
|
|
1041
|
+
if (type === "boolean" || type === "toggle") booleanCols.push(name);
|
|
1042
|
+
if (NUMERIC_SCALAR_TYPES.has(type) && !field.multiple) numericCols.push(name);
|
|
1043
|
+
if (type === "date") dateCols.push(name);
|
|
1044
|
+
if (type === "datetime") datetimeCols.push(name);
|
|
1045
|
+
if (type === "auto_number" || type === "autonumber") {
|
|
1046
|
+
const rawFmt = typeof field.autonumberFormat === "string" && field.autonumberFormat ? field.autonumberFormat : typeof field.format === "string" && field.format ? field.format : "";
|
|
1047
|
+
const fmt = rawFmt || "{0000}";
|
|
1048
|
+
autoNumberCols.push({ name, format: fmt, tokens: (0, import_data.parseAutonumberFormat)(fmt), tenantField });
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
this.jsonFields[key] = jsonCols;
|
|
1053
|
+
this.booleanFields[key] = booleanCols;
|
|
1054
|
+
this.numericFields[key] = numericCols;
|
|
1055
|
+
this.autoNumberFields[key] = autoNumberCols;
|
|
1056
|
+
this.tenantFieldByTable[key] = tenantField;
|
|
1057
|
+
if (dateCols.length) this.dateFields[key] = new Set(dateCols);
|
|
1058
|
+
if (datetimeCols.length) this.datetimeFields[key] = new Set(datetimeCols);
|
|
1059
|
+
}
|
|
956
1060
|
async initObjects(objects) {
|
|
957
1061
|
var _a, _b;
|
|
958
1062
|
this.assertSchemaMutable("initObjects");
|
|
@@ -1194,7 +1298,12 @@ var SqlDriver = class {
|
|
|
1194
1298
|
return this.knex;
|
|
1195
1299
|
}
|
|
1196
1300
|
getBuilder(object, options) {
|
|
1197
|
-
|
|
1301
|
+
const physical = this.physicalTableByObject[object] ?? object;
|
|
1302
|
+
let builder = this.knex(physical);
|
|
1303
|
+
const remoteSchema = this.physicalSchemaByObject[object];
|
|
1304
|
+
if (remoteSchema) {
|
|
1305
|
+
builder = builder.withSchema(remoteSchema);
|
|
1306
|
+
}
|
|
1198
1307
|
if (options?.transaction) {
|
|
1199
1308
|
builder = builder.transacting(options.transaction);
|
|
1200
1309
|
}
|
|
@@ -1293,6 +1402,20 @@ var SqlDriver = class {
|
|
|
1293
1402
|
if (typeof t === "string") return t;
|
|
1294
1403
|
return null;
|
|
1295
1404
|
}
|
|
1405
|
+
/**
|
|
1406
|
+
* Coercion-map key for a builder. Coercion maps (date/datetime) are keyed by
|
|
1407
|
+
* OBJECT name, but after the federation change {@link getBuilder} targets the
|
|
1408
|
+
* physical remote table, so a builder reports the remote name. Map it back to
|
|
1409
|
+
* the object name for external objects; identity for managed ones (no reverse
|
|
1410
|
+
* entry). Note datetime coercion is a SQLite-only concern (see
|
|
1411
|
+
* coerceFilterValue), and SQLite external tables are bare-named, so this is
|
|
1412
|
+
* exact where it matters.
|
|
1413
|
+
*/
|
|
1414
|
+
coercionKey(builder) {
|
|
1415
|
+
const physical = this.tableNameForBuilder(builder);
|
|
1416
|
+
if (physical == null) return null;
|
|
1417
|
+
return this.objectByPhysicalTable[physical] ?? physical;
|
|
1418
|
+
}
|
|
1296
1419
|
/**
|
|
1297
1420
|
* Collapse a `Field.date` value to a timezone-naive `YYYY-MM-DD`
|
|
1298
1421
|
* calendar-day string (ADR-0053 Phase 1). A `Date` collapses to its UTC
|
|
@@ -1386,7 +1509,7 @@ var SqlDriver = class {
|
|
|
1386
1509
|
}
|
|
1387
1510
|
applyFilters(builder, filters) {
|
|
1388
1511
|
if (!filters) return;
|
|
1389
|
-
const table = this.
|
|
1512
|
+
const table = this.coercionKey(builder);
|
|
1390
1513
|
if (!Array.isArray(filters) && typeof filters === "object") {
|
|
1391
1514
|
const hasMongoOperators = Object.keys(filters).some(
|
|
1392
1515
|
(k) => k.startsWith("$") || typeof filters[k] === "object" && filters[k] !== null && Object.keys(filters[k]).some((op) => op.startsWith("$"))
|
|
@@ -1397,7 +1520,7 @@ var SqlDriver = class {
|
|
|
1397
1520
|
}
|
|
1398
1521
|
for (const [key, value] of Object.entries(filters)) {
|
|
1399
1522
|
if (["limit", "offset", "fields", "orderBy"].includes(key)) continue;
|
|
1400
|
-
builder.where(key, this.coerceFilterValue(table, key, value));
|
|
1523
|
+
builder.where(this.remoteColumn(table, key, key), this.coerceFilterValue(table, key, value));
|
|
1401
1524
|
}
|
|
1402
1525
|
return;
|
|
1403
1526
|
}
|
|
@@ -1413,8 +1536,9 @@ var SqlDriver = class {
|
|
|
1413
1536
|
const [fieldRaw, op, value] = item;
|
|
1414
1537
|
const isCriterion = typeof fieldRaw === "string" && typeof op === "string";
|
|
1415
1538
|
if (isCriterion) {
|
|
1416
|
-
const
|
|
1417
|
-
const
|
|
1539
|
+
const localField = this.mapSortField(fieldRaw);
|
|
1540
|
+
const field = this.remoteColumn(table, fieldRaw, localField);
|
|
1541
|
+
const coerced = this.coerceFilterValue(table, localField, value);
|
|
1418
1542
|
const apply = (b) => {
|
|
1419
1543
|
const method = nextJoin === "or" ? "orWhere" : "where";
|
|
1420
1544
|
const methodIn = nextJoin === "or" ? "orWhereIn" : "whereIn";
|
|
@@ -1466,7 +1590,7 @@ var SqlDriver = class {
|
|
|
1466
1590
|
}
|
|
1467
1591
|
applyFilterCondition(builder, condition, logicalOp = "and", tableHint) {
|
|
1468
1592
|
if (!condition || typeof condition !== "object") return;
|
|
1469
|
-
const table = tableHint ?? this.
|
|
1593
|
+
const table = tableHint ?? this.coercionKey(builder);
|
|
1470
1594
|
for (const [key, value] of Object.entries(condition)) {
|
|
1471
1595
|
if (key === "$and" && Array.isArray(value)) {
|
|
1472
1596
|
builder.where((qb) => {
|
|
@@ -1486,10 +1610,11 @@ var SqlDriver = class {
|
|
|
1486
1610
|
}
|
|
1487
1611
|
});
|
|
1488
1612
|
} else if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
1489
|
-
const
|
|
1613
|
+
const localField = this.mapSortField(key);
|
|
1614
|
+
const field = this.remoteColumn(table, key, localField);
|
|
1490
1615
|
for (const [op, opValue] of Object.entries(value)) {
|
|
1491
1616
|
const method = logicalOp === "or" ? "orWhere" : "where";
|
|
1492
|
-
const coerced = this.coerceFilterValue(table,
|
|
1617
|
+
const coerced = this.coerceFilterValue(table, localField, opValue);
|
|
1493
1618
|
switch (op) {
|
|
1494
1619
|
case "$eq":
|
|
1495
1620
|
builder[method](field, coerced);
|
|
@@ -1527,9 +1652,10 @@ var SqlDriver = class {
|
|
|
1527
1652
|
}
|
|
1528
1653
|
}
|
|
1529
1654
|
} else {
|
|
1530
|
-
const
|
|
1655
|
+
const localField = this.mapSortField(key);
|
|
1656
|
+
const field = this.remoteColumn(table, key, localField);
|
|
1531
1657
|
const method = logicalOp === "or" ? "orWhere" : "where";
|
|
1532
|
-
builder[method](field, this.coerceFilterValue(table,
|
|
1658
|
+
builder[method](field, this.coerceFilterValue(table, localField, value));
|
|
1533
1659
|
}
|
|
1534
1660
|
}
|
|
1535
1661
|
}
|
|
@@ -1539,6 +1665,28 @@ var SqlDriver = class {
|
|
|
1539
1665
|
if (field === "updatedAt") return "updated_at";
|
|
1540
1666
|
return field;
|
|
1541
1667
|
}
|
|
1668
|
+
/**
|
|
1669
|
+
* Physical column for a logical field on an external object that declares an
|
|
1670
|
+
* `external.columnMap` (ADR-0015). Returns `fallback` (the caller's existing
|
|
1671
|
+
* per-site resolution) when the object has no columnMap, so managed objects
|
|
1672
|
+
* and external objects without a columnMap are byte-for-byte unchanged.
|
|
1673
|
+
*/
|
|
1674
|
+
remoteColumn(object, field, fallback) {
|
|
1675
|
+
const m = object ? this.fieldColumnByObject[object] : void 0;
|
|
1676
|
+
return m && m[field] || fallback;
|
|
1677
|
+
}
|
|
1678
|
+
/**
|
|
1679
|
+
* Remap a write payload's logical field keys to physical remote columns for an
|
|
1680
|
+
* external object with a columnMap. No-op otherwise. Applied AFTER formatInput
|
|
1681
|
+
* (whose value coercion is keyed by logical field name).
|
|
1682
|
+
*/
|
|
1683
|
+
applyWriteColumnMap(object, data) {
|
|
1684
|
+
const m = this.fieldColumnByObject[object];
|
|
1685
|
+
if (!m || !data || typeof data !== "object") return data;
|
|
1686
|
+
const out = {};
|
|
1687
|
+
for (const [k, v] of Object.entries(data)) out[m[k] ?? k] = v;
|
|
1688
|
+
return out;
|
|
1689
|
+
}
|
|
1542
1690
|
mapAggregateFunc(func) {
|
|
1543
1691
|
switch (func) {
|
|
1544
1692
|
case "count":
|
|
@@ -1795,6 +1943,15 @@ var SqlDriver = class {
|
|
|
1795
1943
|
}
|
|
1796
1944
|
formatOutput(object, data) {
|
|
1797
1945
|
if (!data) return data;
|
|
1946
|
+
const colToField = this.columnFieldByObject[object];
|
|
1947
|
+
if (colToField && typeof data === "object") {
|
|
1948
|
+
for (const [remoteCol, localField] of Object.entries(colToField)) {
|
|
1949
|
+
if (remoteCol !== localField && Object.prototype.hasOwnProperty.call(data, remoteCol)) {
|
|
1950
|
+
data[localField] = data[remoteCol];
|
|
1951
|
+
delete data[remoteCol];
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1798
1955
|
if (this.isSqlite) {
|
|
1799
1956
|
const jsonFields = this.jsonFields[object];
|
|
1800
1957
|
if (jsonFields && jsonFields.length > 0) {
|