@objectstack/driver-sql 10.2.0 → 10.3.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 CHANGED
@@ -476,6 +476,30 @@ declare class SqlDriver implements IDataDriver {
476
476
  analyzeQuery(object: string, query: any, options?: DriverOptions): Promise<any>;
477
477
  syncSchema(object: string, schema: unknown, _options?: DriverOptions): Promise<void>;
478
478
  dropTable(object: string, _options?: DriverOptions): Promise<void>;
479
+ /**
480
+ * Resolve the per-table tenant-isolation column for a schema, honoring an
481
+ * explicit tenancy opt-out. Single source of truth for both {@link initObjects}
482
+ * and {@link registerExternalObject} (they previously inlined this logic and
483
+ * drifted).
484
+ *
485
+ * Precedence:
486
+ * 1. `tenancy.enabled === false` → `null` (NO driver-level org scope), even
487
+ * when the object carries an `organization_id` column. Platform-global
488
+ * objects (e.g. `sys_license`) keep an optional, often-NULL org FK but must
489
+ * NOT be tenant-scoped: otherwise an authenticated caller's active-org
490
+ * `DriverOptions.tenantId` injects `WHERE organization_id = <org>` and every
491
+ * NULL-org / cross-org row silently disappears (the platform admin then
492
+ * reads zero licenses while an unscoped/anonymous read still sees them).
493
+ * The declarative branch below already respected `enabled !== false`; the
494
+ * implicit `organization_id` fallback did not — this closes that gap.
495
+ * 2. Declared `tenancy.tenantField` (when that field exists on the object).
496
+ * 3. Implicit `organization_id` column detection (legacy objects whose
497
+ * multi-tenant column was injected by the kernel without a spec migration).
498
+ */
499
+ protected computeTenantField(schema: {
500
+ fields?: Record<string, any>;
501
+ tenancy?: any;
502
+ }): string | null;
479
503
  /**
480
504
  * Batch-initialise tables from an array of object definitions.
481
505
  */
package/dist/index.d.ts CHANGED
@@ -476,6 +476,30 @@ declare class SqlDriver implements IDataDriver {
476
476
  analyzeQuery(object: string, query: any, options?: DriverOptions): Promise<any>;
477
477
  syncSchema(object: string, schema: unknown, _options?: DriverOptions): Promise<void>;
478
478
  dropTable(object: string, _options?: DriverOptions): Promise<void>;
479
+ /**
480
+ * Resolve the per-table tenant-isolation column for a schema, honoring an
481
+ * explicit tenancy opt-out. Single source of truth for both {@link initObjects}
482
+ * and {@link registerExternalObject} (they previously inlined this logic and
483
+ * drifted).
484
+ *
485
+ * Precedence:
486
+ * 1. `tenancy.enabled === false` → `null` (NO driver-level org scope), even
487
+ * when the object carries an `organization_id` column. Platform-global
488
+ * objects (e.g. `sys_license`) keep an optional, often-NULL org FK but must
489
+ * NOT be tenant-scoped: otherwise an authenticated caller's active-org
490
+ * `DriverOptions.tenantId` injects `WHERE organization_id = <org>` and every
491
+ * NULL-org / cross-org row silently disappears (the platform admin then
492
+ * reads zero licenses while an unscoped/anonymous read still sees them).
493
+ * The declarative branch below already respected `enabled !== false`; the
494
+ * implicit `organization_id` fallback did not — this closes that gap.
495
+ * 2. Declared `tenancy.tenantField` (when that field exists on the object).
496
+ * 3. Implicit `organization_id` column detection (legacy objects whose
497
+ * multi-tenant column was injected by the kernel without a spec migration).
498
+ */
499
+ protected computeTenantField(schema: {
500
+ fields?: Record<string, any>;
501
+ tenancy?: any;
502
+ }): string | null;
479
503
  /**
480
504
  * Batch-initialise tables from an array of object definitions.
481
505
  */
package/dist/index.js CHANGED
@@ -1088,6 +1088,37 @@ var SqlDriver = class {
1088
1088
  this.assertSchemaMutable("dropTable");
1089
1089
  await this.knex.schema.dropTableIfExists(object);
1090
1090
  }
1091
+ /**
1092
+ * Resolve the per-table tenant-isolation column for a schema, honoring an
1093
+ * explicit tenancy opt-out. Single source of truth for both {@link initObjects}
1094
+ * and {@link registerExternalObject} (they previously inlined this logic and
1095
+ * drifted).
1096
+ *
1097
+ * Precedence:
1098
+ * 1. `tenancy.enabled === false` → `null` (NO driver-level org scope), even
1099
+ * when the object carries an `organization_id` column. Platform-global
1100
+ * objects (e.g. `sys_license`) keep an optional, often-NULL org FK but must
1101
+ * NOT be tenant-scoped: otherwise an authenticated caller's active-org
1102
+ * `DriverOptions.tenantId` injects `WHERE organization_id = <org>` and every
1103
+ * NULL-org / cross-org row silently disappears (the platform admin then
1104
+ * reads zero licenses while an unscoped/anonymous read still sees them).
1105
+ * The declarative branch below already respected `enabled !== false`; the
1106
+ * implicit `organization_id` fallback did not — this closes that gap.
1107
+ * 2. Declared `tenancy.tenantField` (when that field exists on the object).
1108
+ * 3. Implicit `organization_id` column detection (legacy objects whose
1109
+ * multi-tenant column was injected by the kernel without a spec migration).
1110
+ */
1111
+ computeTenantField(schema) {
1112
+ const tenancyDecl = schema?.tenancy;
1113
+ if (tenancyDecl?.enabled === false) return null;
1114
+ const fields = schema?.fields;
1115
+ if (tenancyDecl?.tenantField) {
1116
+ const declared = String(tenancyDecl.tenantField);
1117
+ if (fields && Object.prototype.hasOwnProperty.call(fields, declared)) return declared;
1118
+ }
1119
+ if (fields && Object.prototype.hasOwnProperty.call(fields, "organization_id")) return "organization_id";
1120
+ return null;
1121
+ }
1091
1122
  /**
1092
1123
  * Batch-initialise tables from an array of object definitions.
1093
1124
  */
@@ -1139,18 +1170,7 @@ var SqlDriver = class {
1139
1170
  const dateCols = [];
1140
1171
  const datetimeCols = [];
1141
1172
  const autoNumberCols = [];
1142
- const tenancyDecl = schema?.tenancy;
1143
- let tenantField = null;
1144
- if (tenancyDecl && tenancyDecl.enabled !== false && tenancyDecl.tenantField) {
1145
- const declared = String(tenancyDecl.tenantField);
1146
- if (schema.fields && Object.prototype.hasOwnProperty.call(schema.fields, declared)) {
1147
- tenantField = declared;
1148
- }
1149
- }
1150
- if (!tenantField) {
1151
- const hasOrgField = !!(schema.fields && Object.prototype.hasOwnProperty.call(schema.fields, "organization_id"));
1152
- tenantField = hasOrgField ? "organization_id" : null;
1153
- }
1173
+ const tenantField = this.computeTenantField(schema);
1154
1174
  if (schema.fields) {
1155
1175
  for (const [name, field] of Object.entries(schema.fields)) {
1156
1176
  const type = field.type || "string";
@@ -1188,18 +1208,7 @@ var SqlDriver = class {
1188
1208
  const booleanCols = [];
1189
1209
  const numericCols = [];
1190
1210
  const autoNumberCols = [];
1191
- const tenancyDecl = obj?.tenancy;
1192
- let tenantField = null;
1193
- if (tenancyDecl && tenancyDecl.enabled !== false && tenancyDecl.tenantField) {
1194
- const declared = String(tenancyDecl.tenantField);
1195
- if (obj.fields && Object.prototype.hasOwnProperty.call(obj.fields, declared)) {
1196
- tenantField = declared;
1197
- }
1198
- }
1199
- if (!tenantField) {
1200
- const hasOrgField = !!(obj.fields && Object.prototype.hasOwnProperty.call(obj.fields, "organization_id"));
1201
- tenantField = hasOrgField ? "organization_id" : null;
1202
- }
1211
+ const tenantField = this.computeTenantField(obj);
1203
1212
  if (obj.fields) {
1204
1213
  for (const [name, field] of Object.entries(obj.fields)) {
1205
1214
  const type = field.type || "string";