@prisma-next/sql-contract-ts 0.12.0-dev.59 → 0.12.0-dev.60

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.
@@ -1,4 +1,4 @@
1
- import { t as buildSqlContractFromDefinition } from "./build-contract-C9dZNMRg.mjs";
1
+ import { t as buildSqlContractFromDefinition } from "./build-contract-6Rvs461H.mjs";
2
2
  import { blindCast } from "@prisma-next/utils/casts";
3
3
  import { ifDefined } from "@prisma-next/utils/defined";
4
4
  import { isColumnDefault } from "@prisma-next/contract/types";
@@ -147,6 +147,15 @@ var ScalarFieldBuilder = class ScalarFieldBuilder {
147
147
  constructor(state) {
148
148
  this.state = state;
149
149
  }
150
+ /**
151
+ * Returns the physical column name when `.column(name)` was called, or
152
+ * `undefined` when the field uses the default (logical field name) mapping.
153
+ * Used by cross-space FK lowering to stamp the physical column name onto
154
+ * `TargetFieldRef.columnName` so FK target columns are resolved correctly.
155
+ */
156
+ get physicalColumnName() {
157
+ return this.state.columnName;
158
+ }
150
159
  optional() {
151
160
  return new ScalarFieldBuilder({
152
161
  ...this.state,
@@ -268,10 +277,16 @@ function normalizeTargetFieldRefInput(input) {
268
277
  const [first] = refs;
269
278
  if (!first) throw new Error("Expected at least one target ref");
270
279
  if (refs.some((ref) => ref.modelName !== first.modelName)) throw new Error("All target refs in a foreign key must point to the same model");
280
+ if (refs.some((ref) => ref.spaceId !== first.spaceId)) throw new Error(`All target refs in a compound foreign key must share the same spaceId (found mismatch: "${first.spaceId ?? "<local>"}" vs "${refs.find((r) => r.spaceId !== first.spaceId)?.spaceId ?? "<local>"}")`);
281
+ if (refs.some((ref) => ref.namespaceId !== first.namespaceId)) throw new Error("All target refs in a compound foreign key must share the same namespaceId (found mismatch)");
282
+ if (refs.some((ref) => ref.tableName !== first.tableName)) throw new Error("All target refs in a compound foreign key must share the same tableName (found mismatch)");
271
283
  return {
272
284
  modelName: first.modelName,
273
- fieldNames: refs.map((ref) => ref.fieldName),
274
- source: refs.some((ref) => ref.source === "string") ? "string" : "token"
285
+ fieldNames: refs.map((ref) => ref.columnName ?? ref.fieldName),
286
+ source: refs.some((ref) => ref.source === "string") ? "string" : "token",
287
+ spaceId: first.spaceId,
288
+ namespaceId: first.namespaceId,
289
+ tableName: first.tableName
275
290
  };
276
291
  }
277
292
  function createConstraintsDsl() {
@@ -314,6 +329,9 @@ function createConstraintsDsl() {
314
329
  targetModel: normalizedTarget.modelName,
315
330
  targetFields: normalizedTarget.fieldNames,
316
331
  targetSource: normalizedTarget.source,
332
+ ...normalizedTarget.spaceId !== void 0 ? { targetSpaceId: normalizedTarget.spaceId } : {},
333
+ ...normalizedTarget.namespaceId !== void 0 ? { targetNamespaceId: normalizedTarget.namespaceId } : {},
334
+ ...normalizedTarget.tableName !== void 0 ? { targetTableName: normalizedTarget.tableName } : {},
317
335
  ...options?.name ? { name: options.name } : {},
318
336
  ...options?.onDelete ? { onDelete: options.onDelete } : {},
319
337
  ...options?.onUpdate ? { onUpdate: options.onUpdate } : {},
@@ -337,14 +355,23 @@ function createFieldRefs(fields) {
337
355
  };
338
356
  return refs;
339
357
  }
340
- function createModelTokenRefs(modelName, fields) {
358
+ function createModelTokenRefs(modelName, fields, crossSpaceCoordinate) {
341
359
  const refs = {};
342
- for (const fieldName of Object.keys(fields)) refs[fieldName] = {
343
- kind: "targetFieldRef",
344
- source: "token",
345
- modelName,
346
- fieldName
347
- };
360
+ for (const [fieldName, fieldBuilder] of Object.entries(fields)) {
361
+ const physicalColumn = crossSpaceCoordinate !== void 0 ? fieldBuilder.physicalColumnName : void 0;
362
+ refs[fieldName] = {
363
+ kind: "targetFieldRef",
364
+ source: "token",
365
+ modelName,
366
+ fieldName,
367
+ ...crossSpaceCoordinate !== void 0 ? {
368
+ spaceId: crossSpaceCoordinate.spaceId,
369
+ ...crossSpaceCoordinate.namespaceId !== void 0 ? { namespaceId: crossSpaceCoordinate.namespaceId } : {},
370
+ ...crossSpaceCoordinate.tableName !== void 0 ? { tableName: crossSpaceCoordinate.tableName } : {},
371
+ ...physicalColumn !== void 0 ? { columnName: physicalColumn } : {}
372
+ } : {}
373
+ };
374
+ }
348
375
  return refs;
349
376
  }
350
377
  function buildStageSpec(stageInput, context) {
@@ -376,12 +403,21 @@ var ContractModelBuilder = class ContractModelBuilder {
376
403
  stageOne;
377
404
  attributesFactory;
378
405
  sqlFactory;
406
+ spaceId;
407
+ tableName;
379
408
  refs;
380
- constructor(stageOne, attributesFactory, sqlFactory) {
409
+ constructor(stageOne, attributesFactory, sqlFactory, spaceId, tableName) {
381
410
  this.stageOne = stageOne;
382
411
  this.attributesFactory = attributesFactory;
383
412
  this.sqlFactory = sqlFactory;
384
- this.refs = stageOne.modelName ? createModelTokenRefs(stageOne.modelName, stageOne.fields) : void 0;
413
+ this.spaceId = spaceId;
414
+ this.tableName = tableName;
415
+ const crossSpaceCoordinate = spaceId !== void 0 ? {
416
+ spaceId,
417
+ ...stageOne.namespace !== void 0 ? { namespaceId: stageOne.namespace } : {},
418
+ ...tableName !== void 0 ? { tableName } : {}
419
+ } : void 0;
420
+ this.refs = blindCast(stageOne.modelName ? createModelTokenRefs(stageOne.modelName, stageOne.fields, crossSpaceCoordinate) : void 0);
385
421
  }
386
422
  ref(fieldName) {
387
423
  const modelName = this.stageOne.modelName;
@@ -402,13 +438,14 @@ var ContractModelBuilder = class ContractModelBuilder {
402
438
  ...this.stageOne.relations,
403
439
  ...relations
404
440
  }
405
- }, this.attributesFactory, this.sqlFactory);
441
+ }, this.attributesFactory, this.sqlFactory, this.spaceId, this.tableName);
406
442
  }
407
443
  attributes(specOrFactory) {
408
- return new ContractModelBuilder(this.stageOne, specOrFactory, this.sqlFactory);
444
+ return new ContractModelBuilder(this.stageOne, specOrFactory, this.sqlFactory, this.spaceId, this.tableName);
409
445
  }
410
446
  sql(specOrFactory) {
411
- return new ContractModelBuilder(this.stageOne, this.attributesFactory, specOrFactory);
447
+ const nextTableName = typeof specOrFactory !== "function" ? specOrFactory.table : this.tableName;
448
+ return blindCast(new ContractModelBuilder(this.stageOne, this.attributesFactory, specOrFactory, this.spaceId, nextTableName));
412
449
  }
413
450
  buildAttributesSpec() {
414
451
  if (!this.attributesFactory) return;
@@ -460,12 +497,47 @@ function model(modelNameOrInput, maybeInput) {
460
497
  relations: input.relations ?? {}
461
498
  });
462
499
  }
500
+ /**
501
+ * Factory for building a standalone branded extension model handle.
502
+ *
503
+ * Use this instead of `new ContractModelBuilder(…)` when constructing handles
504
+ * for models that live in a foreign contract space (e.g. a Supabase extension
505
+ * model referenced by a user's contract). The `spaceId` brands the returned
506
+ * handle so `refs.<field>.spaceId` carries the foreign space identifier.
507
+ *
508
+ * @param name - The domain model name as declared in the foreign contract
509
+ * (e.g. `'AuthUser'`, not a bare table alias like `'User'`).
510
+ * @param input.namespace - The namespace within the foreign space (e.g. `'auth'`).
511
+ * @param input.fields - Field definitions (use `field.column(…)`).
512
+ * @param input.table - The physical table name in the foreign schema.
513
+ * @param spaceId - The extension space identifier (e.g. `'supabase'`).
514
+ */
515
+ function extensionModel(name, input, spaceId) {
516
+ return new ContractModelBuilder({
517
+ modelName: name,
518
+ namespace: input.namespace,
519
+ fields: input.fields,
520
+ relations: {}
521
+ }, void 0, void 0, spaceId, input.table);
522
+ }
523
+ function isCrossSpaceHandle(value) {
524
+ if (typeof value !== "object" || value === null) return false;
525
+ const rec = blindCast(value);
526
+ return typeof rec["spaceId"] === "string" && typeof rec["stageOne"] === "object" && rec["stageOne"] !== null;
527
+ }
463
528
  function belongsTo(toModel, options) {
529
+ const resolvedModel = typeof toModel === "function" ? toModel() : toModel;
530
+ const crossSpaceCoordinate = isCrossSpaceHandle(resolvedModel) ? {
531
+ spaceId: resolvedModel.spaceId,
532
+ ...resolvedModel.tableName !== void 0 ? { tableName: resolvedModel.tableName } : {},
533
+ ...resolvedModel.stageOne.namespace !== void 0 ? { namespaceId: resolvedModel.stageOne.namespace } : {}
534
+ } : void 0;
464
535
  return new RelationBuilder({
465
536
  kind: "belongsTo",
466
537
  toModel: normalizeRelationModelSource(toModel),
467
538
  from: options.from,
468
- to: options.to
539
+ to: options.to,
540
+ ...crossSpaceCoordinate !== void 0 ? crossSpaceCoordinate : {}
469
541
  });
470
542
  }
471
543
  function hasMany(toModel, options) {
@@ -815,6 +887,33 @@ function resolveRelationForeignKeys(spec, allSpecs) {
815
887
  const relation = relationBuilder.build();
816
888
  if (relation.kind !== "belongsTo" || !relation.sql?.fk) continue;
817
889
  const targetModelName = resolveRelationModelName(relation.toModel);
890
+ if (relation.spaceId !== void 0) {
891
+ const fields = normalizeRelationFieldNames(relation.from);
892
+ const targetFields = normalizeRelationFieldNames(relation.to);
893
+ assertRelationFieldArity({
894
+ modelName: spec.modelName,
895
+ relationName,
896
+ leftLabel: "source",
897
+ leftFields: fields,
898
+ rightLabel: "target",
899
+ rightFields: targetFields
900
+ });
901
+ foreignKeys.push({
902
+ kind: "fk",
903
+ fields,
904
+ targetModel: targetModelName,
905
+ targetFields,
906
+ targetSpaceId: relation.spaceId,
907
+ ...relation.namespaceId !== void 0 ? { targetNamespaceId: relation.namespaceId } : {},
908
+ ...relation.tableName !== void 0 ? { targetTableName: relation.tableName } : {},
909
+ ...relation.sql.fk.name ? { name: relation.sql.fk.name } : {},
910
+ ...relation.sql.fk.onDelete ? { onDelete: relation.sql.fk.onDelete } : {},
911
+ ...relation.sql.fk.onUpdate ? { onUpdate: relation.sql.fk.onUpdate } : {},
912
+ ...relation.sql.fk.constraint !== void 0 ? { constraint: relation.sql.fk.constraint } : {},
913
+ ...relation.sql.fk.index !== void 0 ? { index: relation.sql.fk.index } : {}
914
+ });
915
+ continue;
916
+ }
818
917
  if (!allSpecs.has(targetModelName)) throw new Error(`Relation "${spec.modelName}.${relationName}" references unknown model "${targetModelName}"`);
819
918
  const fields = normalizeRelationFieldNames(relation.from);
820
919
  const targetFields = normalizeRelationFieldNames(relation.to);
@@ -846,10 +945,8 @@ function resolveRelationAnchorFields(spec) {
846
945
  if ("id" in spec.fieldToColumn) return ["id"];
847
946
  throw new Error(`Model "${spec.modelName}" needs an explicit id or an "id" field to anchor non-owning relations`);
848
947
  }
849
- function lowerBelongsToRelation(relationName, relation, currentSpec, allSpecs) {
948
+ function lowerBelongsToRelation(relationName, relation, currentSpec, allSpecs, extensionPacks) {
850
949
  const targetModelName = resolveRelationModelName(relation.toModel);
851
- const targetSpec = allSpecs.get(targetModelName);
852
- if (!targetSpec) throw new Error(`Relation "${currentSpec.modelName}.${relationName}" references unknown model "${targetModelName}"`);
853
950
  const fromFields = normalizeRelationFieldNames(relation.from);
854
951
  const toFields = normalizeRelationFieldNames(relation.to);
855
952
  assertRelationFieldArity({
@@ -860,6 +957,27 @@ function lowerBelongsToRelation(relationName, relation, currentSpec, allSpecs) {
860
957
  rightLabel: "target",
861
958
  rightFields: toFields
862
959
  });
960
+ if (relation.spaceId !== void 0) {
961
+ assertKnownExtensionPack(extensionPacks, relation.spaceId, `Relation "${currentSpec.modelName}.${relationName}"`);
962
+ const targetTable = relation.tableName ?? targetModelName.toLowerCase();
963
+ const parentColumns = mapFieldNamesToColumnNames(currentSpec.modelName, fromFields, currentSpec.fieldToColumn);
964
+ return {
965
+ fieldName: relationName,
966
+ toModel: targetModelName,
967
+ toTable: targetTable,
968
+ cardinality: "N:1",
969
+ spaceId: relation.spaceId,
970
+ ...relation.namespaceId !== void 0 ? { namespaceId: relation.namespaceId } : {},
971
+ on: {
972
+ parentTable: currentSpec.tableName,
973
+ parentColumns,
974
+ childTable: targetTable,
975
+ childColumns: toFields
976
+ }
977
+ };
978
+ }
979
+ const targetSpec = allSpecs.get(targetModelName);
980
+ if (!targetSpec) throw new Error(`Relation "${currentSpec.modelName}.${relationName}" references unknown model "${targetModelName}"`);
863
981
  return {
864
982
  fieldName: relationName,
865
983
  toModel: targetModelName,
@@ -930,12 +1048,12 @@ function lowerManyToManyRelation(relationName, relation, currentSpec, allSpecs)
930
1048
  }
931
1049
  };
932
1050
  }
933
- function resolveRelationNode(relationName, relation, currentSpec, allSpecs) {
934
- if (relation.kind === "belongsTo") return lowerBelongsToRelation(relationName, relation, currentSpec, allSpecs);
1051
+ function resolveRelationNode(relationName, relation, currentSpec, allSpecs, extensionPacks) {
1052
+ if (relation.kind === "belongsTo") return lowerBelongsToRelation(relationName, relation, currentSpec, allSpecs, extensionPacks);
935
1053
  if (relation.kind === "hasMany" || relation.kind === "hasOne") return lowerHasOwnershipRelation(relationName, relation, currentSpec, allSpecs);
936
1054
  return lowerManyToManyRelation(relationName, relation, currentSpec, allSpecs);
937
1055
  }
938
- function lowerForeignKeyNode(spec, targetSpec, foreignKey) {
1056
+ function lowerLocalForeignKeyNode(spec, targetSpec, foreignKey) {
939
1057
  return {
940
1058
  columns: mapFieldNamesToColumnNames(spec.modelName, foreignKey.fields, spec.fieldToColumn),
941
1059
  references: {
@@ -950,20 +1068,55 @@ function lowerForeignKeyNode(spec, targetSpec, foreignKey) {
950
1068
  ...foreignKey.index !== void 0 ? { index: foreignKey.index } : {}
951
1069
  };
952
1070
  }
953
- function resolveForeignKeyNodes(spec, allSpecs) {
1071
+ function lowerCrossSpaceForeignKeyNode(spec, foreignKey) {
1072
+ return {
1073
+ columns: mapFieldNamesToColumnNames(spec.modelName, foreignKey.fields, spec.fieldToColumn),
1074
+ references: {
1075
+ model: foreignKey.targetModel,
1076
+ table: foreignKey.targetTableName ?? foreignKey.targetModel.toLowerCase(),
1077
+ columns: foreignKey.targetFields,
1078
+ ...foreignKey.targetNamespaceId !== void 0 ? { namespaceId: foreignKey.targetNamespaceId } : {},
1079
+ spaceId: foreignKey.targetSpaceId
1080
+ },
1081
+ ...foreignKey.name ? { name: foreignKey.name } : {},
1082
+ ...foreignKey.onDelete ? { onDelete: foreignKey.onDelete } : {},
1083
+ ...foreignKey.onUpdate ? { onUpdate: foreignKey.onUpdate } : {},
1084
+ ...foreignKey.constraint !== void 0 ? { constraint: foreignKey.constraint } : {},
1085
+ ...foreignKey.index !== void 0 ? { index: foreignKey.index } : {}
1086
+ };
1087
+ }
1088
+ function assertKnownExtensionPack(extensionPacks, spaceId, context) {
1089
+ if (extensionPacks !== void 0 && Object.hasOwn(extensionPacks, spaceId)) return;
1090
+ throw new Error(`${context} references contract space "${spaceId}" but "${spaceId}" is not declared in extensionPacks. Add the pack to extensionPacks.`);
1091
+ }
1092
+ function resolveForeignKeyNodes(spec, allSpecs, extensionPacks) {
954
1093
  const relationForeignKeys = resolveRelationForeignKeys(spec, allSpecs).map((foreignKey) => {
1094
+ if (foreignKey.targetSpaceId !== void 0) {
1095
+ assertKnownExtensionPack(extensionPacks, foreignKey.targetSpaceId, `Relation-derived foreign key on "${spec.modelName}"`);
1096
+ return lowerCrossSpaceForeignKeyNode(spec, {
1097
+ ...foreignKey,
1098
+ targetSpaceId: foreignKey.targetSpaceId
1099
+ });
1100
+ }
955
1101
  const targetSpec = allSpecs.get(foreignKey.targetModel);
956
1102
  if (!targetSpec) throw new Error(`Foreign key on "${spec.modelName}" references unknown model "${foreignKey.targetModel}"`);
957
- return lowerForeignKeyNode(spec, targetSpec, foreignKey);
1103
+ return lowerLocalForeignKeyNode(spec, targetSpec, foreignKey);
958
1104
  });
959
1105
  const sqlForeignKeys = (spec.sqlSpec?.foreignKeys ?? []).map((foreignKey) => {
1106
+ if (foreignKey.targetSpaceId !== void 0) {
1107
+ assertKnownExtensionPack(extensionPacks, foreignKey.targetSpaceId, `Foreign key on "${spec.modelName}"`);
1108
+ return lowerCrossSpaceForeignKeyNode(spec, {
1109
+ ...foreignKey,
1110
+ targetSpaceId: foreignKey.targetSpaceId
1111
+ });
1112
+ }
960
1113
  const targetSpec = allSpecs.get(foreignKey.targetModel);
961
1114
  if (!targetSpec) throw new Error(`Foreign key on "${spec.modelName}" references unknown model "${foreignKey.targetModel}"`);
962
- return lowerForeignKeyNode(spec, targetSpec, foreignKey);
1115
+ return lowerLocalForeignKeyNode(spec, targetSpec, foreignKey);
963
1116
  });
964
1117
  return [...relationForeignKeys, ...sqlForeignKeys];
965
1118
  }
966
- function resolveModelNode(spec, allSpecs, storageTypes, storageTypeReverseLookup) {
1119
+ function resolveModelNode(spec, allSpecs, storageTypes, storageTypeReverseLookup, extensionPacks) {
967
1120
  const fields = [];
968
1121
  for (const [fieldName, fieldBuilder] of Object.entries(spec.fieldBuilders)) {
969
1122
  const fieldState = fieldBuilder.build();
@@ -992,8 +1145,8 @@ function resolveModelNode(spec, allSpecs, storageTypes, storageTypeReverseLookup
992
1145
  ...ifDefined("type", index.type),
993
1146
  ...ifDefined("options", index.options)
994
1147
  }));
995
- const foreignKeys = resolveForeignKeyNodes(spec, allSpecs);
996
- const relations = Object.entries(spec.relations).map(([relationName, relationBuilder]) => resolveRelationNode(relationName, relationBuilder.build(), spec, allSpecs));
1148
+ const foreignKeys = resolveForeignKeyNodes(spec, allSpecs, extensionPacks);
1149
+ const relations = Object.entries(spec.relations).map(([relationName, relationBuilder]) => resolveRelationNode(relationName, relationBuilder.build(), spec, allSpecs, extensionPacks));
997
1150
  return {
998
1151
  modelName: spec.modelName,
999
1152
  tableName: spec.tableName,
@@ -1058,14 +1211,14 @@ function collectRuntimeModelSpecs(definition) {
1058
1211
  modelSpecs
1059
1212
  };
1060
1213
  }
1061
- function lowerModels(collection) {
1214
+ function lowerModels(collection, extensionPacks) {
1062
1215
  emitTypedCrossModelFallbackWarnings(collection);
1063
1216
  const storageTypeReverseLookup = buildStorageTypeReverseLookup(collection.storageTypes);
1064
- return Array.from(collection.modelSpecs.values()).map((spec) => resolveModelNode(spec, collection.modelSpecs, collection.storageTypes, storageTypeReverseLookup));
1217
+ return Array.from(collection.modelSpecs.values()).map((spec) => resolveModelNode(spec, collection.modelSpecs, collection.storageTypes, storageTypeReverseLookup, extensionPacks));
1065
1218
  }
1066
1219
  function buildContractDefinition(definition) {
1067
1220
  const collection = collectRuntimeModelSpecs(definition);
1068
- const models = lowerModels(collection);
1221
+ const models = lowerModels(collection, definition.extensionPacks);
1069
1222
  return {
1070
1223
  target: definition.target,
1071
1224
  ...ifDefined("defaultControlPolicy", definition.defaultControlPolicy),
@@ -1190,6 +1343,6 @@ function defineContract(definition, factory) {
1190
1343
  return buildBoundContract(definition.family, definition.target, definition);
1191
1344
  }
1192
1345
  //#endregion
1193
- export { buildBoundContract, buildSqlContractFromDefinition, defineContract, enumType, field, member, model, rel };
1346
+ export { buildBoundContract, buildSqlContractFromDefinition, defineContract, enumType, extensionModel, field, member, model, rel };
1194
1347
 
1195
1348
  //# sourceMappingURL=contract-builder.mjs.map