@prisma-next/contract 0.11.0-dev.49 → 0.11.0-dev.50

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.
@@ -0,0 +1,69 @@
1
+ import { t as Contract } from "./contract-types-OSHtK6P5.mjs";
2
+ import { JsonObject } from "@prisma-next/utils/json";
3
+
4
+ //#region src/canonicalization.d.ts
5
+ /**
6
+ * Per-target contract serializer hook. The framework canonicalizer uses
7
+ * this to convert an in-memory contract (which may carry class-instance
8
+ * IR nodes whose runtime-only fields must not appear in the on-disk
9
+ * envelope) into a plain JsonObject before applying the family-agnostic
10
+ * canonical-key ordering / default-omission / sort steps. Targets whose
11
+ * contract is JSON-clean by construction return the contract unchanged.
12
+ */
13
+ type SerializeContract = (contract: Contract) => JsonObject;
14
+ /**
15
+ * Family-contributed predicate for the default-omission walk. Called when
16
+ * a value at `path` is a default (empty object/array or `false`); if this
17
+ * returns `true` the value is kept rather than stripped.
18
+ *
19
+ * The framework only calls the predicate inside the `isDefaultValue` branch,
20
+ * so there is no need to guard against non-default values.
21
+ */
22
+ type PreserveEmptyPredicate = (path: readonly string[]) => boolean;
23
+ /**
24
+ * Family-contributed storage sort. Applied to the serialized `storage`
25
+ * subtree after the default-omission walk; the result replaces the
26
+ * `storage` field before the final key sort. Use to establish a
27
+ * deterministic order for storage arrays (indexes, uniques) that the
28
+ * family-agnostic `sortObjectKeys` pass cannot handle.
29
+ */
30
+ type StorageSort = (storage: unknown) => unknown;
31
+ interface CanonicalizeContractOptions {
32
+ readonly schemaVersion?: string;
33
+ /**
34
+ * Per-target hook that converts the in-memory contract (which may
35
+ * carry class-instance IR nodes) into a plain JsonObject before the
36
+ * family-agnostic canonicalization steps run.
37
+ *
38
+ * Routing through the hook is what lets each target decide which
39
+ * fields appear in the on-disk envelope; runtime-only class API
40
+ * fields stay invisible to the canonicalization walk by virtue of
41
+ * the per-target serializer not putting them in the JSON shape.
42
+ */
43
+ readonly serializeContract: SerializeContract;
44
+ /**
45
+ * Family-contributed preserve-empty predicate. When the walk encounters a
46
+ * default value (empty object/array or `false`) at `path`, calling this
47
+ * with the full path allows the family to veto the omission. If absent,
48
+ * only the framework's family-agnostic required-slot rules apply.
49
+ */
50
+ readonly shouldPreserveEmpty?: PreserveEmptyPredicate;
51
+ /**
52
+ * Family-contributed storage sort. Applied to the serialized `storage`
53
+ * subtree after the default-omission walk, before the final key sort.
54
+ * SQL family uses this to impose a deterministic order on `indexes` and
55
+ * `uniques` arrays within each namespace table. Families that require no
56
+ * special storage ordering omit this hook.
57
+ */
58
+ readonly sortStorage?: StorageSort;
59
+ }
60
+ /**
61
+ * Object-form variant of {@link canonicalizeContract}. Exported because the
62
+ * emitter writes the canonical contract through a separate JSON-stringify
63
+ * pass and consumes the structured object directly.
64
+ */
65
+ declare function canonicalizeContractToObject(contract: Contract, options: CanonicalizeContractOptions): Record<string, unknown>;
66
+ declare function canonicalizeContract(contract: Contract, options: CanonicalizeContractOptions): string;
67
+ //#endregion
68
+ export { canonicalizeContract as a, StorageSort as i, PreserveEmptyPredicate as n, canonicalizeContractToObject as o, SerializeContract as r, CanonicalizeContractOptions as t };
69
+ //# sourceMappingURL=canonicalization-j__G4o7F.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canonicalization-j__G4o7F.d.mts","names":[],"sources":["../src/canonicalization.ts"],"mappings":";;;;;;AAcA;;;;;;KAAY,iBAAA,IAAqB,QAAA,EAAU,QAAA,KAAa,UAAU;;AAAA;AAUlE;;;;AAA6D;AAS7D;KATY,sBAAA,IAA0B,IAAuB;;;AASlB;AA2J3C;;;;KA3JY,WAAA,IAAe,OAAgB;AAAA,UA2J1B,2BAAA;EAAA,SACN,aAAA;EAAA;;;;;;;;AA0ByB;AAQpC;EAlCW,SAWA,iBAAA,EAAmB,iBAAA;;;;;;;WAOnB,mBAAA,GAAsB,sBAAA;EAiB/B;;;;;AAEO;AA4BT;EA9BE,SATS,WAAA,GAAc,WAAA;AAAA;;;;;;iBAQT,4BAAA,CACd,QAAA,EAAU,QAAA,EACV,OAAA,EAAS,2BAAA,GACR,MAAA;AAAA,iBA4Ba,oBAAA,CACd,QAAA,EAAU,QAAA,EACV,OAAA,EAAS,2BAA2B"}
@@ -25,9 +25,9 @@ function isDefaultValue(value) {
25
25
  if (typeof value === "object" && value !== null) return Object.keys(value).length === 0;
26
26
  return false;
27
27
  }
28
- function omitDefaults(obj, path) {
28
+ function omitDefaults(obj, path, shouldPreserveEmpty) {
29
29
  if (obj === null || typeof obj !== "object") return obj;
30
- if (Array.isArray(obj)) return obj.map((item) => omitDefaults(item, path));
30
+ if (Array.isArray(obj)) return obj.map((item) => omitDefaults(item, path, shouldPreserveEmpty));
31
31
  const result = {};
32
32
  for (const [key, value] of Object.entries(obj)) {
33
33
  const currentPath = [...path, key];
@@ -38,8 +38,6 @@ function omitDefaults(obj, path) {
38
38
  const isRequiredModels = isArrayEqual(currentPath, ["models"]);
39
39
  const isRequiredNamespaces = isArrayEqual(currentPath, ["storage", "namespaces"]);
40
40
  const isNamespaceSlot = currentPath.length === 3 && isArrayEqual([currentPath[0], currentPath[1]], ["storage", "namespaces"]);
41
- const isRequiredNamespaceTables = currentPath.length === 4 && currentPath[0] === "storage" && currentPath[1] === "namespaces" && currentPath[3] === "tables";
42
- const isNamespaceTableEntry = currentPath.length === 5 && currentPath[0] === "storage" && currentPath[1] === "namespaces" && currentPath[3] === "tables";
43
41
  const isRequiredRoots = isArrayEqual(currentPath, ["roots"]);
44
42
  const isRequiredExtensionPacks = isArrayEqual(currentPath, ["extensionPacks"]);
45
43
  const isRequiredCapabilities = isArrayEqual(currentPath, ["capabilities"]);
@@ -52,15 +50,12 @@ function omitDefaults(obj, path) {
52
50
  const isExtensionNamespace = currentPath.length === 2 && currentPath[0] === "extensionPacks";
53
51
  const isModelRelations = currentPath.length === 3 && isArrayEqual([currentPath[0], currentPath[2]], ["models", "relations"]);
54
52
  const isModelStorage = currentPath.length === 3 && isArrayEqual([currentPath[0], currentPath[2]], ["models", "storage"]);
55
- const isNamespaceTableUniques = currentPath.length === 6 && currentPath[0] === "storage" && currentPath[1] === "namespaces" && currentPath[3] === "tables" && currentPath[5] === "uniques";
56
- const isNamespaceTableIndexes = currentPath.length === 6 && currentPath[0] === "storage" && currentPath[1] === "namespaces" && currentPath[3] === "tables" && currentPath[5] === "indexes";
57
- const isNamespaceTableForeignKeys = currentPath.length === 6 && currentPath[0] === "storage" && currentPath[1] === "namespaces" && currentPath[3] === "tables" && currentPath[5] === "foreignKeys";
58
- const isStorageTypeTypeParams = currentPath.length === 4 && currentPath[0] === "storage" && currentPath[1] === "types" && key === "typeParams";
59
53
  const isDomainUnboundTypeParams = currentPath.length === 5 && currentPath[0] === "domain" && currentPath[2] === "types" && key === "typeParams";
60
- const isFkBooleanField = currentPath.length === 7 && currentPath[0] === "storage" && currentPath[1] === "namespaces" && currentPath[3] === "tables" && currentPath[5] === "foreignKeys" && (key === "constraint" || key === "index");
61
- if (!isRequiredModels && !isRequiredNamespaces && !isNamespaceSlot && !isRequiredNamespaceTables && !isNamespaceTableEntry && !isRequiredRoots && !isRequiredExtensionPacks && !isRequiredCapabilities && !isRequiredMeta && !isRequiredExecutionDefaults && !isExtensionNamespace && !isModelRelations && !isModelStorage && !isNamespaceTableUniques && !isNamespaceTableIndexes && !isNamespaceTableForeignKeys && !isFkBooleanField && !(key === "nullable") && !isStorageTypeTypeParams && !isDomainUnboundTypeParams) continue;
54
+ const isNullableField = key === "nullable";
55
+ const isFamilyPreserved = shouldPreserveEmpty?.(currentPath) ?? false;
56
+ if (!isRequiredModels && !isRequiredNamespaces && !isNamespaceSlot && !isRequiredRoots && !isRequiredExtensionPacks && !isRequiredCapabilities && !isRequiredMeta && !isRequiredExecutionDefaults && !isExtensionNamespace && !isModelRelations && !isModelStorage && !isNullableField && !isDomainUnboundTypeParams && !isFamilyPreserved) continue;
62
57
  }
63
- result[key] = omitDefaults(value, currentPath);
58
+ result[key] = omitDefaults(value, currentPath, shouldPreserveEmpty);
64
59
  }
65
60
  return result;
66
61
  }
@@ -72,58 +67,6 @@ function sortObjectKeys(obj) {
72
67
  for (const key of keys) sorted[key] = sortObjectKeys(obj[key]);
73
68
  return sorted;
74
69
  }
75
- function sortTableArrays(tableObj) {
76
- const sortedTable = { ...tableObj };
77
- if (Array.isArray(tableObj.indexes)) sortedTable.indexes = [...tableObj.indexes].sort((a, b) => {
78
- const nameA = a?.name || "";
79
- const nameB = b?.name || "";
80
- return nameA.localeCompare(nameB);
81
- });
82
- if (Array.isArray(tableObj.uniques)) sortedTable.uniques = [...tableObj.uniques].sort((a, b) => {
83
- const nameA = a?.name || "";
84
- const nameB = b?.name || "";
85
- return nameA.localeCompare(nameB);
86
- });
87
- return sortedTable;
88
- }
89
- function sortIndexesAndUniques(storage) {
90
- if (!storage || typeof storage !== "object") return storage;
91
- const storageObj = storage;
92
- if (!storageObj.namespaces || typeof storageObj.namespaces !== "object") return storage;
93
- const namespaces = storageObj.namespaces;
94
- const result = {
95
- ...storageObj,
96
- namespaces: {}
97
- };
98
- const resultNamespaces = result.namespaces;
99
- for (const nsId of Object.keys(namespaces)) {
100
- const ns = namespaces[nsId];
101
- if (!ns || typeof ns !== "object") {
102
- resultNamespaces[nsId] = ns;
103
- continue;
104
- }
105
- const nsObj = ns;
106
- if (!nsObj.tables || typeof nsObj.tables !== "object") {
107
- resultNamespaces[nsId] = ns;
108
- continue;
109
- }
110
- const sortedTables = {};
111
- const sortedTableNames = Object.keys(nsObj.tables).sort();
112
- for (const tableName of sortedTableNames) {
113
- const table = nsObj.tables[tableName];
114
- if (!table || typeof table !== "object") {
115
- sortedTables[tableName] = table;
116
- continue;
117
- }
118
- sortedTables[tableName] = sortTableArrays(table);
119
- }
120
- resultNamespaces[nsId] = {
121
- ...nsObj,
122
- tables: sortedTables
123
- };
124
- }
125
- return result;
126
- }
127
70
  function orderTopLevel(obj) {
128
71
  const ordered = {};
129
72
  const remaining = new Set(Object.keys(obj));
@@ -155,12 +98,11 @@ function canonicalizeContractToObject(contract, options) {
155
98
  extensionPacks: serialized["extensionPacks"],
156
99
  capabilities: serialized["capabilities"],
157
100
  meta: serialized["meta"]
158
- }, []);
159
- const withSortedIndexes = sortIndexesAndUniques(withDefaultsOmitted["storage"]);
160
- return orderTopLevel(sortObjectKeys({
101
+ }, [], options.shouldPreserveEmpty);
102
+ return orderTopLevel(sortObjectKeys(options.sortStorage ? {
161
103
  ...withDefaultsOmitted,
162
- storage: withSortedIndexes
163
- }));
104
+ storage: options.sortStorage(withDefaultsOmitted["storage"])
105
+ } : withDefaultsOmitted));
164
106
  }
165
107
  function canonicalizeContract(contract, options) {
166
108
  return JSON.stringify(canonicalizeContractToObject(contract, options), null, 2);
@@ -174,21 +116,24 @@ function sha256(content) {
174
116
  return `sha256:${hash.digest("hex")}`;
175
117
  }
176
118
  function hashContract(section) {
119
+ const { shouldPreserveEmpty, sortStorage, ...sectionData } = section;
177
120
  return canonicalizeContract({
178
- targetFamily: section["targetFamily"],
179
- target: section["target"],
121
+ targetFamily: sectionData["targetFamily"],
122
+ target: sectionData["target"],
180
123
  roots: {},
181
124
  models: {},
182
- storage: section["storage"] ?? {},
183
- execution: section["execution"],
125
+ storage: sectionData["storage"] ?? {},
126
+ execution: sectionData["execution"],
184
127
  extensionPacks: {},
185
- capabilities: section["capabilities"] ?? {},
128
+ capabilities: sectionData["capabilities"] ?? {},
186
129
  meta: {},
187
130
  profileHash: "",
188
- ...section
131
+ ...sectionData
189
132
  }, {
190
133
  schemaVersion: SCHEMA_VERSION,
191
- serializeContract: (c) => JSON.parse(JSON.stringify(c))
134
+ serializeContract: (c) => JSON.parse(JSON.stringify(c)),
135
+ ...ifDefined("shouldPreserveEmpty", shouldPreserveEmpty),
136
+ ...ifDefined("sortStorage", sortStorage)
192
137
  });
193
138
  }
194
139
  function computeStorageHash(args) {
@@ -203,4 +148,4 @@ function computeProfileHash(args) {
203
148
  //#endregion
204
149
  export { canonicalizeContractToObject as a, canonicalizeContract as i, computeProfileHash as n, computeStorageHash as r, computeExecutionHash as t };
205
150
 
206
- //# sourceMappingURL=hashing-BqnADuDo.mjs.map
151
+ //# sourceMappingURL=hashing-C25nwocN.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hashing-C25nwocN.mjs","names":[],"sources":["../src/canonicalization.ts","../src/hashing.ts"],"sourcesContent":["import { isArrayEqual } from '@prisma-next/utils/array-equal';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport type { JsonObject } from '@prisma-next/utils/json';\n\nimport type { Contract } from './contract-types';\n\n/**\n * Per-target contract serializer hook. The framework canonicalizer uses\n * this to convert an in-memory contract (which may carry class-instance\n * IR nodes whose runtime-only fields must not appear in the on-disk\n * envelope) into a plain JsonObject before applying the family-agnostic\n * canonical-key ordering / default-omission / sort steps. Targets whose\n * contract is JSON-clean by construction return the contract unchanged.\n */\nexport type SerializeContract = (contract: Contract) => JsonObject;\n\n/**\n * Family-contributed predicate for the default-omission walk. Called when\n * a value at `path` is a default (empty object/array or `false`); if this\n * returns `true` the value is kept rather than stripped.\n *\n * The framework only calls the predicate inside the `isDefaultValue` branch,\n * so there is no need to guard against non-default values.\n */\nexport type PreserveEmptyPredicate = (path: readonly string[]) => boolean;\n\n/**\n * Family-contributed storage sort. Applied to the serialized `storage`\n * subtree after the default-omission walk; the result replaces the\n * `storage` field before the final key sort. Use to establish a\n * deterministic order for storage arrays (indexes, uniques) that the\n * family-agnostic `sortObjectKeys` pass cannot handle.\n */\nexport type StorageSort = (storage: unknown) => unknown;\n\nconst TOP_LEVEL_ORDER = [\n 'schemaVersion',\n 'canonicalVersion',\n 'targetFamily',\n 'target',\n 'profileHash',\n 'roots',\n 'models',\n 'valueObjects',\n 'domain',\n 'storage',\n 'execution',\n 'capabilities',\n 'extensionPacks',\n 'meta',\n] as const;\n\nfunction isDefaultValue(value: unknown): boolean {\n if (value === false) return true;\n if (value === null) return false;\n if (Array.isArray(value) && value.length === 0) return true;\n if (typeof value === 'object' && value !== null) {\n const keys = Object.keys(value);\n return keys.length === 0;\n }\n return false;\n}\n\nfunction omitDefaults(\n obj: unknown,\n path: readonly string[],\n shouldPreserveEmpty: PreserveEmptyPredicate | undefined,\n): unknown {\n if (obj === null || typeof obj !== 'object') {\n return obj;\n }\n\n if (Array.isArray(obj)) {\n return obj.map((item) => omitDefaults(item, path, shouldPreserveEmpty));\n }\n\n const result: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(obj)) {\n const currentPath = [...path, key];\n\n if (key === '_generated') {\n continue;\n }\n\n if (key === 'generated' && value === false) {\n continue;\n }\n\n if ((key === 'onDelete' || key === 'onUpdate') && value === 'noAction') {\n continue;\n }\n\n if (isDefaultValue(value)) {\n const isRequiredModels = isArrayEqual(currentPath, ['models']);\n const isRequiredNamespaces = isArrayEqual(currentPath, ['storage', 'namespaces']);\n const isNamespaceSlot =\n currentPath.length === 3 &&\n isArrayEqual([currentPath[0], currentPath[1]], ['storage', 'namespaces']);\n const isRequiredRoots = isArrayEqual(currentPath, ['roots']);\n const isRequiredExtensionPacks = isArrayEqual(currentPath, ['extensionPacks']);\n const isRequiredCapabilities = isArrayEqual(currentPath, ['capabilities']);\n const isRequiredMeta = isArrayEqual(currentPath, ['meta']);\n const isRequiredExecutionDefaults = isArrayEqual(currentPath, [\n 'execution',\n 'mutations',\n 'defaults',\n ]);\n const isExtensionNamespace = currentPath.length === 2 && currentPath[0] === 'extensionPacks';\n const isModelRelations =\n currentPath.length === 3 &&\n isArrayEqual([currentPath[0], currentPath[2]], ['models', 'relations']);\n const isModelStorage =\n currentPath.length === 3 &&\n isArrayEqual([currentPath[0], currentPath[2]], ['models', 'storage']);\n\n const isDomainUnboundTypeParams =\n currentPath.length === 5 &&\n currentPath[0] === 'domain' &&\n currentPath[2] === 'types' &&\n key === 'typeParams';\n\n const isNullableField = key === 'nullable';\n\n const isFamilyPreserved = shouldPreserveEmpty?.(currentPath) ?? false;\n\n if (\n !isRequiredModels &&\n !isRequiredNamespaces &&\n !isNamespaceSlot &&\n !isRequiredRoots &&\n !isRequiredExtensionPacks &&\n !isRequiredCapabilities &&\n !isRequiredMeta &&\n !isRequiredExecutionDefaults &&\n !isExtensionNamespace &&\n !isModelRelations &&\n !isModelStorage &&\n !isNullableField &&\n !isDomainUnboundTypeParams &&\n !isFamilyPreserved\n ) {\n continue;\n }\n }\n\n result[key] = omitDefaults(value, currentPath, shouldPreserveEmpty);\n }\n\n return result;\n}\n\nfunction sortObjectKeys(obj: unknown): unknown {\n if (obj === null || typeof obj !== 'object') {\n return obj;\n }\n\n if (Array.isArray(obj)) {\n return obj.map((item) => sortObjectKeys(item));\n }\n\n const sorted: Record<string, unknown> = {};\n const keys = Object.keys(obj).sort();\n for (const key of keys) {\n sorted[key] = sortObjectKeys((obj as Record<string, unknown>)[key]);\n }\n\n return sorted;\n}\n\nexport function orderTopLevel(obj: Record<string, unknown>): Record<string, unknown> {\n const ordered: Record<string, unknown> = {};\n const remaining = new Set(Object.keys(obj));\n\n for (const key of TOP_LEVEL_ORDER) {\n if (remaining.has(key)) {\n ordered[key] = obj[key];\n remaining.delete(key);\n }\n }\n\n for (const key of Array.from(remaining).sort()) {\n ordered[key] = obj[key];\n }\n\n return ordered;\n}\n\nexport interface CanonicalizeContractOptions {\n readonly schemaVersion?: string;\n /**\n * Per-target hook that converts the in-memory contract (which may\n * carry class-instance IR nodes) into a plain JsonObject before the\n * family-agnostic canonicalization steps run.\n *\n * Routing through the hook is what lets each target decide which\n * fields appear in the on-disk envelope; runtime-only class API\n * fields stay invisible to the canonicalization walk by virtue of\n * the per-target serializer not putting them in the JSON shape.\n */\n readonly serializeContract: SerializeContract;\n /**\n * Family-contributed preserve-empty predicate. When the walk encounters a\n * default value (empty object/array or `false`) at `path`, calling this\n * with the full path allows the family to veto the omission. If absent,\n * only the framework's family-agnostic required-slot rules apply.\n */\n readonly shouldPreserveEmpty?: PreserveEmptyPredicate;\n /**\n * Family-contributed storage sort. Applied to the serialized `storage`\n * subtree after the default-omission walk, before the final key sort.\n * SQL family uses this to impose a deterministic order on `indexes` and\n * `uniques` arrays within each namespace table. Families that require no\n * special storage ordering omit this hook.\n */\n readonly sortStorage?: StorageSort;\n}\n\n/**\n * Object-form variant of {@link canonicalizeContract}. Exported because the\n * emitter writes the canonical contract through a separate JSON-stringify\n * pass and consumes the structured object directly.\n */\nexport function canonicalizeContractToObject(\n contract: Contract,\n options: CanonicalizeContractOptions,\n): Record<string, unknown> {\n const serialized = options.serializeContract(contract);\n const normalized: Record<string, unknown> = {\n ...ifDefined('schemaVersion', options.schemaVersion),\n targetFamily: serialized['targetFamily'],\n target: serialized['target'],\n profileHash: serialized['profileHash'],\n roots: serialized['roots'],\n models: serialized['models'],\n ...ifDefined('valueObjects', serialized['valueObjects']),\n ...ifDefined('domain', serialized['domain']),\n storage: serialized['storage'],\n ...ifDefined('execution', serialized['execution']),\n extensionPacks: serialized['extensionPacks'],\n capabilities: serialized['capabilities'],\n meta: serialized['meta'],\n };\n const withDefaultsOmitted = omitDefaults(normalized, [], options.shouldPreserveEmpty) as Record<\n string,\n unknown\n >;\n const withSortedStorage = options.sortStorage\n ? { ...withDefaultsOmitted, storage: options.sortStorage(withDefaultsOmitted['storage']) }\n : withDefaultsOmitted;\n const withSortedKeys = sortObjectKeys(withSortedStorage) as Record<string, unknown>;\n return orderTopLevel(withSortedKeys);\n}\n\nexport function canonicalizeContract(\n contract: Contract,\n options: CanonicalizeContractOptions,\n): string {\n return JSON.stringify(canonicalizeContractToObject(contract, options), null, 2);\n}\n","import { createHash } from 'node:crypto';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport type { JsonObject } from '@prisma-next/utils/json';\nimport {\n canonicalizeContract,\n type PreserveEmptyPredicate,\n type StorageSort,\n} from './canonicalization';\nimport type { Contract } from './contract-types';\nimport type { ExecutionHashBase, ProfileHashBase, StorageHashBase } from './types';\n\nconst SCHEMA_VERSION = '1';\n\nfunction sha256(content: string): string {\n const hash = createHash('sha256');\n hash.update(content);\n return `sha256:${hash.digest('hex')}`;\n}\n\ntype HashContractSection = Record<string, unknown> & {\n readonly shouldPreserveEmpty?: PreserveEmptyPredicate;\n readonly sortStorage?: StorageSort;\n};\n\nfunction hashContract(section: HashContractSection): string {\n const { shouldPreserveEmpty, sortStorage, ...sectionData } = section;\n // Blind cast: the synthesised object is a hash-only stand-in\n // — never returned to callers, never executed as a Contract.\n // `canonicalizeContract` only walks the storage / execution /\n // capabilities slices, all of which are populated above, so the\n // missing precise Contract typing on the other slots is\n // immaterial for the hash result.\n const contract = {\n targetFamily: sectionData['targetFamily'],\n target: sectionData['target'],\n roots: {},\n models: {},\n storage: sectionData['storage'] ?? {},\n execution: sectionData['execution'],\n extensionPacks: {},\n capabilities: sectionData['capabilities'] ?? {},\n meta: {},\n profileHash: '',\n ...sectionData,\n } as unknown as Contract;\n return canonicalizeContract(contract, {\n schemaVersion: SCHEMA_VERSION,\n serializeContract: (c) => JSON.parse(JSON.stringify(c)) as JsonObject,\n ...ifDefined('shouldPreserveEmpty', shouldPreserveEmpty),\n ...ifDefined('sortStorage', sortStorage),\n });\n}\n\nexport type ComputeStorageHashArgs = {\n target: string;\n targetFamily: string;\n storage: Record<string, unknown>;\n readonly shouldPreserveEmpty?: PreserveEmptyPredicate;\n readonly sortStorage?: StorageSort;\n};\n\nexport function computeStorageHash(args: ComputeStorageHashArgs): StorageHashBase<string> {\n return sha256(hashContract(args)) as StorageHashBase<string>;\n}\n\nexport function computeExecutionHash(args: {\n target: string;\n targetFamily: string;\n execution: Record<string, unknown>;\n}): ExecutionHashBase<string> {\n return sha256(hashContract(args)) as ExecutionHashBase<string>;\n}\n\nexport function computeProfileHash(args: {\n target: string;\n targetFamily: string;\n capabilities: Record<string, Record<string, boolean>>;\n}): ProfileHashBase<string> {\n return sha256(hashContract(args)) as ProfileHashBase<string>;\n}\n"],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import { i as StorageSort, n as PreserveEmptyPredicate } from "./canonicalization-j__G4o7F.mjs";
2
+
3
+ //#region src/canonicalization-path-match.d.ts
4
+ type PathSegment = string | '*' | readonly string[];
5
+ type PathPattern = readonly PathSegment[];
6
+ declare function matchesPathPattern(path: readonly string[], pattern: PathPattern): boolean;
7
+ declare function createPreserveEmptyPredicate(patterns: readonly PathPattern[]): PreserveEmptyPredicate;
8
+ //#endregion
9
+ //#region src/canonicalization-storage-sort.d.ts
10
+ type PathSegment$1 = string | '*';
11
+ interface NamedArraySortTarget {
12
+ readonly path: readonly PathSegment$1[];
13
+ readonly arrayKeys: readonly string[];
14
+ }
15
+ declare function compareByNameProperty(a: unknown, b: unknown): number;
16
+ declare function createStorageSort(targets: readonly NamedArraySortTarget[], compare?: (a: unknown, b: unknown) => number): StorageSort;
17
+ //#endregion
18
+ export { type NamedArraySortTarget, type PathPattern, type PathSegment as PreserveEmptyPathSegment, type PathSegment$1 as StorageSortPathSegment, compareByNameProperty, createPreserveEmptyPredicate, createStorageSort, matchesPathPattern };
19
+ //# sourceMappingURL=hashing-utils.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hashing-utils.d.mts","names":[],"sources":["../src/canonicalization-path-match.ts","../src/canonicalization-storage-sort.ts"],"mappings":";;;KAEY,WAAA;AAAA,KAEA,WAAA,YAAuB,WAAW;AAAA,iBAE9B,kBAAA,CAAmB,IAAA,qBAAyB,OAAA,EAAS,WAAW;AAAA,iBAiChE,4BAAA,CACd,QAAA,WAAmB,WAAA,KAClB,sBAAsB;;;KCvCb,aAAA;AAAA,UAEK,oBAAA;EAAA,SACN,IAAA,WAAe,aAAW;EAAA,SAC1B,SAAA;AAAA;AAAA,iBAOK,qBAAA,CAAsB,CAAA,WAAY,CAAU;AAAA,iBA2D5C,iBAAA,CACd,OAAA,WAAkB,oBAAA,IAClB,OAAA,IAAU,CAAA,WAAY,CAAA,uBACrB,WAAW"}
@@ -0,0 +1,71 @@
1
+ //#region src/canonicalization-path-match.ts
2
+ function matchesPathPattern(path, pattern) {
3
+ if (path.length !== pattern.length) return false;
4
+ for (let i = 0; i < pattern.length; i++) {
5
+ const segment = pattern[i];
6
+ const value = path[i];
7
+ if (segment === void 0 || value === void 0) return false;
8
+ if (segment === "*") continue;
9
+ if (typeof segment === "string") {
10
+ if (value !== segment) return false;
11
+ continue;
12
+ }
13
+ if (Array.isArray(segment)) {
14
+ if (!segment.includes(value)) return false;
15
+ }
16
+ }
17
+ return true;
18
+ }
19
+ function createPreserveEmptyPredicate(patterns) {
20
+ return (path) => patterns.some((pattern) => matchesPathPattern(path, pattern));
21
+ }
22
+ //#endregion
23
+ //#region src/canonicalization-storage-sort.ts
24
+ function isPlainRecord(value) {
25
+ return typeof value === "object" && value !== null && !Array.isArray(value);
26
+ }
27
+ function compareByNameProperty(a, b) {
28
+ const nameA = isPlainRecord(a) && typeof a["name"] === "string" ? a["name"] : "";
29
+ const nameB = isPlainRecord(b) && typeof b["name"] === "string" ? b["name"] : "";
30
+ return nameA.localeCompare(nameB);
31
+ }
32
+ function sortArrayKeysOnRecord(record, arrayKeys, compare) {
33
+ const sorted = { ...record };
34
+ for (const key of arrayKeys) {
35
+ const value = record[key];
36
+ if (Array.isArray(value)) sorted[key] = [...value].sort(compare);
37
+ }
38
+ return sorted;
39
+ }
40
+ function walkAndSort(node, pathSegments, arrayKeys, compare) {
41
+ if (pathSegments.length === 0) {
42
+ if (!isPlainRecord(node)) return node;
43
+ return sortArrayKeysOnRecord(node, arrayKeys, compare);
44
+ }
45
+ if (!isPlainRecord(node)) return node;
46
+ const [head, ...rest] = pathSegments;
47
+ if (head === void 0) return node;
48
+ if (head === "*") {
49
+ const sorted = { ...node };
50
+ for (const key of Object.keys(node)) sorted[key] = walkAndSort(node[key], rest, arrayKeys, compare);
51
+ return sorted;
52
+ }
53
+ const child = node[head];
54
+ if (child === void 0) return node;
55
+ return {
56
+ ...node,
57
+ [head]: walkAndSort(child, rest, arrayKeys, compare)
58
+ };
59
+ }
60
+ function createStorageSort(targets, compare = compareByNameProperty) {
61
+ return (storage) => {
62
+ if (!isPlainRecord(storage)) return storage;
63
+ let result = storage;
64
+ for (const target of targets) result = walkAndSort(result, target.path, target.arrayKeys, compare);
65
+ return result;
66
+ };
67
+ }
68
+ //#endregion
69
+ export { compareByNameProperty, createPreserveEmptyPredicate, createStorageSort, matchesPathPattern };
70
+
71
+ //# sourceMappingURL=hashing-utils.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hashing-utils.mjs","names":[],"sources":["../src/canonicalization-path-match.ts","../src/canonicalization-storage-sort.ts"],"sourcesContent":["import type { PreserveEmptyPredicate } from './canonicalization';\n\nexport type PathSegment = string | '*' | readonly string[];\n\nexport type PathPattern = readonly PathSegment[];\n\nexport function matchesPathPattern(path: readonly string[], pattern: PathPattern): boolean {\n if (path.length !== pattern.length) {\n return false;\n }\n\n for (let i = 0; i < pattern.length; i++) {\n const segment = pattern[i];\n const value = path[i];\n if (segment === undefined || value === undefined) {\n return false;\n }\n\n if (segment === '*') {\n continue;\n }\n\n if (typeof segment === 'string') {\n if (value !== segment) {\n return false;\n }\n continue;\n }\n\n if (Array.isArray(segment)) {\n if (!segment.includes(value)) {\n return false;\n }\n }\n }\n\n return true;\n}\n\nexport function createPreserveEmptyPredicate(\n patterns: readonly PathPattern[],\n): PreserveEmptyPredicate {\n return (path) => patterns.some((pattern) => matchesPathPattern(path, pattern));\n}\n","import type { StorageSort } from './canonicalization';\n\nexport type PathSegment = string | '*';\n\nexport interface NamedArraySortTarget {\n readonly path: readonly PathSegment[];\n readonly arrayKeys: readonly string[];\n}\n\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nexport function compareByNameProperty(a: unknown, b: unknown): number {\n const nameA = isPlainRecord(a) && typeof a['name'] === 'string' ? a['name'] : '';\n const nameB = isPlainRecord(b) && typeof b['name'] === 'string' ? b['name'] : '';\n return nameA.localeCompare(nameB);\n}\n\nfunction sortArrayKeysOnRecord(\n record: Record<string, unknown>,\n arrayKeys: readonly string[],\n compare: (a: unknown, b: unknown) => number,\n): Record<string, unknown> {\n const sorted: Record<string, unknown> = { ...record };\n for (const key of arrayKeys) {\n const value = record[key];\n if (Array.isArray(value)) {\n sorted[key] = [...value].sort(compare);\n }\n }\n return sorted;\n}\n\nfunction walkAndSort(\n node: unknown,\n pathSegments: readonly PathSegment[],\n arrayKeys: readonly string[],\n compare: (a: unknown, b: unknown) => number,\n): unknown {\n if (pathSegments.length === 0) {\n if (!isPlainRecord(node)) {\n return node;\n }\n return sortArrayKeysOnRecord(node, arrayKeys, compare);\n }\n\n if (!isPlainRecord(node)) {\n return node;\n }\n\n const [head, ...rest] = pathSegments;\n if (head === undefined) {\n return node;\n }\n\n if (head === '*') {\n const sorted: Record<string, unknown> = { ...node };\n for (const key of Object.keys(node)) {\n sorted[key] = walkAndSort(node[key], rest, arrayKeys, compare);\n }\n return sorted;\n }\n\n const child = node[head];\n if (child === undefined) {\n return node;\n }\n\n return { ...node, [head]: walkAndSort(child, rest, arrayKeys, compare) };\n}\n\nexport function createStorageSort(\n targets: readonly NamedArraySortTarget[],\n compare: (a: unknown, b: unknown) => number = compareByNameProperty,\n): StorageSort {\n return (storage) => {\n if (!isPlainRecord(storage)) {\n return storage;\n }\n\n let result: unknown = storage;\n for (const target of targets) {\n result = walkAndSort(result, target.path, target.arrayKeys, compare);\n }\n return result;\n };\n}\n"],"mappings":";AAMA,SAAgB,mBAAmB,MAAyB,SAA+B;CACzF,IAAI,KAAK,WAAW,QAAQ,QAC1B,OAAO;CAGT,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;EACvC,MAAM,UAAU,QAAQ;EACxB,MAAM,QAAQ,KAAK;EACnB,IAAI,YAAY,KAAA,KAAa,UAAU,KAAA,GACrC,OAAO;EAGT,IAAI,YAAY,KACd;EAGF,IAAI,OAAO,YAAY,UAAU;GAC/B,IAAI,UAAU,SACZ,OAAO;GAET;EACF;EAEA,IAAI,MAAM,QAAQ,OAAO;OACnB,CAAC,QAAQ,SAAS,KAAK,GACzB,OAAO;EAAA;CAGb;CAEA,OAAO;AACT;AAEA,SAAgB,6BACd,UACwB;CACxB,QAAQ,SAAS,SAAS,MAAM,YAAY,mBAAmB,MAAM,OAAO,CAAC;AAC/E;;;AClCA,SAAS,cAAc,OAAkD;CACvE,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAEA,SAAgB,sBAAsB,GAAY,GAAoB;CACpE,MAAM,QAAQ,cAAc,CAAC,KAAK,OAAO,EAAE,YAAY,WAAW,EAAE,UAAU;CAC9E,MAAM,QAAQ,cAAc,CAAC,KAAK,OAAO,EAAE,YAAY,WAAW,EAAE,UAAU;CAC9E,OAAO,MAAM,cAAc,KAAK;AAClC;AAEA,SAAS,sBACP,QACA,WACA,SACyB;CACzB,MAAM,SAAkC,EAAE,GAAG,OAAO;CACpD,KAAK,MAAM,OAAO,WAAW;EAC3B,MAAM,QAAQ,OAAO;EACrB,IAAI,MAAM,QAAQ,KAAK,GACrB,OAAO,OAAO,CAAC,GAAG,KAAK,EAAE,KAAK,OAAO;CAEzC;CACA,OAAO;AACT;AAEA,SAAS,YACP,MACA,cACA,WACA,SACS;CACT,IAAI,aAAa,WAAW,GAAG;EAC7B,IAAI,CAAC,cAAc,IAAI,GACrB,OAAO;EAET,OAAO,sBAAsB,MAAM,WAAW,OAAO;CACvD;CAEA,IAAI,CAAC,cAAc,IAAI,GACrB,OAAO;CAGT,MAAM,CAAC,MAAM,GAAG,QAAQ;CACxB,IAAI,SAAS,KAAA,GACX,OAAO;CAGT,IAAI,SAAS,KAAK;EAChB,MAAM,SAAkC,EAAE,GAAG,KAAK;EAClD,KAAK,MAAM,OAAO,OAAO,KAAK,IAAI,GAChC,OAAO,OAAO,YAAY,KAAK,MAAM,MAAM,WAAW,OAAO;EAE/D,OAAO;CACT;CAEA,MAAM,QAAQ,KAAK;CACnB,IAAI,UAAU,KAAA,GACZ,OAAO;CAGT,OAAO;EAAE,GAAG;GAAO,OAAO,YAAY,OAAO,MAAM,WAAW,OAAO;CAAE;AACzE;AAEA,SAAgB,kBACd,SACA,UAA8C,uBACjC;CACb,QAAQ,YAAY;EAClB,IAAI,CAAC,cAAc,OAAO,GACxB,OAAO;EAGT,IAAI,SAAkB;EACtB,KAAK,MAAM,UAAU,SACnB,SAAS,YAAY,QAAQ,OAAO,MAAM,OAAO,WAAW,OAAO;EAErE,OAAO;CACT;AACF"}
@@ -1,44 +1,15 @@
1
- import { S as ProfileHashBase, T as StorageHashBase, d as ExecutionHashBase, t as Contract } from "./contract-types-OSHtK6P5.mjs";
2
- import { JsonObject } from "@prisma-next/utils/json";
1
+ import { S as ProfileHashBase, T as StorageHashBase, d as ExecutionHashBase } from "./contract-types-OSHtK6P5.mjs";
2
+ import { a as canonicalizeContract, i as StorageSort, n as PreserveEmptyPredicate, o as canonicalizeContractToObject, r as SerializeContract, t as CanonicalizeContractOptions } from "./canonicalization-j__G4o7F.mjs";
3
3
 
4
- //#region src/canonicalization.d.ts
5
- /**
6
- * Per-target contract serializer hook. The framework canonicalizer uses
7
- * this to convert an in-memory contract (which may carry class-instance
8
- * IR nodes whose runtime-only fields must not appear in the on-disk
9
- * envelope) into a plain JsonObject before applying the family-agnostic
10
- * canonical-key ordering / default-omission / sort steps. Targets whose
11
- * contract is JSON-clean by construction return the contract unchanged.
12
- */
13
- type SerializeContract = (contract: Contract) => JsonObject;
14
- interface CanonicalizeContractOptions {
15
- readonly schemaVersion?: string;
16
- /**
17
- * Per-target hook that converts the in-memory contract (which may
18
- * carry class-instance IR nodes) into a plain JsonObject before the
19
- * family-agnostic canonicalization steps run.
20
- *
21
- * Routing through the hook is what lets each target decide which
22
- * fields appear in the on-disk envelope; runtime-only class API
23
- * fields stay invisible to the canonicalization walk by virtue of
24
- * the per-target serializer not putting them in the JSON shape.
25
- */
26
- readonly serializeContract: SerializeContract;
27
- }
28
- /**
29
- * Object-form variant of {@link canonicalizeContract}. Exported because the
30
- * emitter writes the canonical contract through a separate JSON-stringify
31
- * pass and consumes the structured object directly.
32
- */
33
- declare function canonicalizeContractToObject(contract: Contract, options: CanonicalizeContractOptions): Record<string, unknown>;
34
- declare function canonicalizeContract(contract: Contract, options: CanonicalizeContractOptions): string;
35
- //#endregion
36
4
  //#region src/hashing.d.ts
37
- declare function computeStorageHash(args: {
5
+ type ComputeStorageHashArgs = {
38
6
  target: string;
39
7
  targetFamily: string;
40
8
  storage: Record<string, unknown>;
41
- }): StorageHashBase<string>;
9
+ readonly shouldPreserveEmpty?: PreserveEmptyPredicate;
10
+ readonly sortStorage?: StorageSort;
11
+ };
12
+ declare function computeStorageHash(args: ComputeStorageHashArgs): StorageHashBase<string>;
42
13
  declare function computeExecutionHash(args: {
43
14
  target: string;
44
15
  targetFamily: string;
@@ -50,5 +21,5 @@ declare function computeProfileHash(args: {
50
21
  capabilities: Record<string, Record<string, boolean>>;
51
22
  }): ProfileHashBase<string>;
52
23
  //#endregion
53
- export { type CanonicalizeContractOptions, type SerializeContract, canonicalizeContract, canonicalizeContractToObject, computeExecutionHash, computeProfileHash, computeStorageHash };
24
+ export { type CanonicalizeContractOptions, type PreserveEmptyPredicate, type SerializeContract, type StorageSort, canonicalizeContract, canonicalizeContractToObject, computeExecutionHash, computeProfileHash, computeStorageHash };
54
25
  //# sourceMappingURL=hashing.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"hashing.d.mts","names":[],"sources":["../src/canonicalization.ts","../src/hashing.ts"],"mappings":";;;;;;AAcA;;;;;;KAAY,iBAAA,IAAqB,QAAA,EAAU,QAAA,KAAa,UAAU;AAAA,UA8RjD,2BAAA;EAAA,SACN,aAAA;EADiC;;;;;;;AAYG;AAQ/C;;EApB4C,SAYjC,iBAAA,EAAmB,iBAAiB;AAAA;;;;;;iBAQ/B,4BAAA,CACd,QAAA,EAAU,QAAA,EACV,OAAA,EAAS,2BAAA,GACR,MAAA;AAAA,iBAwBa,oBAAA,CACd,QAAA,EAAU,QAAA,EACV,OAAA,EAAS,2BAA2B;;;iBCrTtB,kBAAA,CAAmB,IAAA;EACjC,MAAA;EACA,YAAA;EACA,OAAA,EAAS,MAAA;AAAA,IACP,eAAe;AAAA,iBAIH,oBAAA,CAAqB,IAAA;EACnC,MAAA;EACA,YAAA;EACA,SAAA,EAAW,MAAA;AAAA,IACT,iBAAiB;AAAA,iBAIL,kBAAA,CAAmB,IAAA;EACjC,MAAA;EACA,YAAA;EACA,YAAA,EAAc,MAAA,SAAe,MAAA;AAAA,IAC3B,eAAA"}
1
+ {"version":3,"file":"hashing.d.mts","names":[],"sources":["../src/hashing.ts"],"mappings":";;;;KAqDY,sBAAA;EACV,MAAA;EACA,YAAA;EACA,OAAA,EAAS,MAAA;EAAA,SACA,mBAAA,GAAsB,sBAAA;EAAA,SACtB,WAAA,GAAc,WAAA;AAAA;AAAA,iBAGT,kBAAA,CAAmB,IAAA,EAAM,sBAAA,GAAyB,eAAe;AAAA,iBAIjE,oBAAA,CAAqB,IAAA;EACnC,MAAA;EACA,YAAA;EACA,SAAA,EAAW,MAAA;AAAA,IACT,iBAAiB;AAAA,iBAIL,kBAAA,CAAmB,IAAA;EACjC,MAAA;EACA,YAAA;EACA,YAAA,EAAc,MAAA,SAAe,MAAA;AAAA,IAC3B,eAAA"}
package/dist/hashing.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { a as canonicalizeContractToObject, i as canonicalizeContract, n as computeProfileHash, r as computeStorageHash, t as computeExecutionHash } from "./hashing-BqnADuDo.mjs";
1
+ import { a as canonicalizeContractToObject, i as canonicalizeContract, n as computeProfileHash, r as computeStorageHash, t as computeExecutionHash } from "./hashing-C25nwocN.mjs";
2
2
  export { canonicalizeContract, canonicalizeContractToObject, computeExecutionHash, computeProfileHash, computeStorageHash };
@@ -1,5 +1,6 @@
1
1
  import { t as CrossReference } from "./cross-reference-t0TDbBTP.mjs";
2
2
  import { I as ContractModel, L as ContractModelBase, S as ProfileHashBase, T as StorageHashBase, V as ContractValueObject, W as ModelStorageBase, h as ExecutionSection, t as Contract, w as StorageBase } from "./contract-types-OSHtK6P5.mjs";
3
+ import { i as StorageSort, n as PreserveEmptyPredicate } from "./canonicalization-j__G4o7F.mjs";
3
4
 
4
5
  //#region src/testing-factories.d.ts
5
6
  type ContractOverrides<TStorage extends StorageBase = StorageBase, TModels extends Record<string, ContractModelBase> = Record<string, ContractModel>> = {
@@ -14,6 +15,8 @@ type ContractOverrides<TStorage extends StorageBase = StorageBase, TModels exten
14
15
  execution?: Omit<ExecutionSection, 'executionHash'>;
15
16
  profileHash?: ProfileHashBase<string>;
16
17
  meta?: Record<string, unknown>;
18
+ shouldPreserveEmpty?: PreserveEmptyPredicate;
19
+ sortStorage?: StorageSort;
17
20
  };
18
21
  declare const DUMMY_HASH: StorageHashBase<"sha256:test">;
19
22
  declare function createContract<TStorage extends StorageBase = StorageBase, TModels extends Record<string, ContractModelBase> = Record<string, ContractModel>>(overrides?: ContractOverrides<TStorage, TModels>): Contract<TStorage, TModels>;
@@ -1 +1 @@
1
- {"version":3,"file":"testing.d.mts","names":[],"sources":["../src/testing-factories.ts"],"mappings":";;;;KAaK,iBAAA,kBACc,WAAA,GAAc,WAAA,kBACf,MAAA,SAAe,iBAAA,IAAqB,MAAA,SAAe,aAAA;EAEnE,MAAA;EACA,YAAA;EACA,KAAA,GAAQ,MAAA,SAAe,cAAA;EACvB,MAAA,GAAS,OAAA;EACT,OAAA,GAAU,IAAA,CAAK,QAAA;EACf,YAAA,GAAe,MAAA,SAAe,mBAAA;EAC9B,YAAA,GAAe,MAAA,SAAe,MAAA;EAC9B,cAAA,GAAiB,MAAA;EACjB,SAAA,GAAY,IAAA,CAAK,gBAAA;EACjB,WAAA,GAAc,eAAA;EACd,IAAA,GAAO,MAAA;AAAA;AAAA,cAGH,UAAA,EAAoC,eAA1B;AAAA,iBAeA,cAAA,kBACG,WAAA,GAAc,WAAA,kBACf,MAAA,SAAe,iBAAA,IAAqB,MAAA,SAAe,aAAA,EAAA,CACnE,SAAA,GAAW,iBAAA,CAAkB,QAAA,EAAU,OAAA,IAAgB,QAAA,CAAS,QAAA,EAAU,OAAA;AAAA,KA+CvE,cAAA,GAAiB,WAAA;EAAA,SACX,UAAA,EAAY,QAAA,CACnB,MAAA;IAAA,SAA0B,EAAA;IAAA,SAAqB,MAAA,EAAQ,QAAA,CAAS,MAAA;EAAA;EAAA,SAEzD,KAAA,GAAQ,MAAA;AAAA;AAAA,KAGd,YAAA,GAAe,aAAa,CAAC,gBAAA;EAAqB,KAAA;AAAA;AAAA,iBAEvC,iBAAA,CACd,SAAA,GAAW,iBAAA,CAAkB,cAAA,EAAgB,MAAA,SAAe,YAAA,KAC3D,QAAA,CAAS,cAAA,EAAgB,MAAA,SAAe,YAAA"}
1
+ {"version":3,"file":"testing.d.mts","names":[],"sources":["../src/testing-factories.ts"],"mappings":";;;;;KAcK,iBAAA,kBACc,WAAA,GAAc,WAAA,kBACf,MAAA,SAAe,iBAAA,IAAqB,MAAA,SAAe,aAAA;EAEnE,MAAA;EACA,YAAA;EACA,KAAA,GAAQ,MAAA,SAAe,cAAA;EACvB,MAAA,GAAS,OAAA;EACT,OAAA,GAAU,IAAA,CAAK,QAAA;EACf,YAAA,GAAe,MAAA,SAAe,mBAAA;EAC9B,YAAA,GAAe,MAAA,SAAe,MAAA;EAC9B,cAAA,GAAiB,MAAA;EACjB,SAAA,GAAY,IAAA,CAAK,gBAAA;EACjB,WAAA,GAAc,eAAA;EACd,IAAA,GAAO,MAAA;EACP,mBAAA,GAAsB,sBAAA;EACtB,WAAA,GAAc,WAAA;AAAA;AAAA,cAGV,UAAA,EAAoC,eAA1B;AAAA,iBAeA,cAAA,kBACG,WAAA,GAAc,WAAA,kBACf,MAAA,SAAe,iBAAA,IAAqB,MAAA,SAAe,aAAA,EAAA,CACnE,SAAA,GAAW,iBAAA,CAAkB,QAAA,EAAU,OAAA,IAAgB,QAAA,CAAS,QAAA,EAAU,OAAA;AAAA,KAiDvE,cAAA,GAAiB,WAAA;EAAA,SACX,UAAA,EAAY,QAAA,CACnB,MAAA;IAAA,SAA0B,EAAA;IAAA,SAAqB,MAAA,EAAQ,QAAA,CAAS,MAAA;EAAA;EAAA,SAEzD,KAAA,GAAQ,MAAA;AAAA;AAAA,KAGd,YAAA,GAAe,aAAa,CAAC,gBAAA;EAAqB,KAAA;AAAA;AAAA,iBAEvC,iBAAA,CACd,SAAA,GAAW,iBAAA,CAAkB,cAAA,EAAgB,MAAA,SAAe,YAAA,KAC3D,QAAA,CAAS,cAAA,EAAgB,MAAA,SAAe,YAAA"}
package/dist/testing.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { t as coreHash } from "./types-CVGwkRLa.mjs";
2
- import { n as computeProfileHash, r as computeStorageHash, t as computeExecutionHash } from "./hashing-BqnADuDo.mjs";
2
+ import { n as computeProfileHash, r as computeStorageHash, t as computeExecutionHash } from "./hashing-C25nwocN.mjs";
3
3
  import { ifDefined } from "@prisma-next/utils/defined";
4
4
  //#region src/testing-factories.ts
5
5
  const DUMMY_HASH = coreHash("sha256:test");
@@ -17,7 +17,9 @@ function createContract(overrides = {}) {
17
17
  const storageHash = computeStorageHash({
18
18
  target,
19
19
  targetFamily,
20
- storage: rawStorage
20
+ storage: rawStorage,
21
+ ...ifDefined("shouldPreserveEmpty", overrides.shouldPreserveEmpty),
22
+ ...ifDefined("sortStorage", overrides.sortStorage)
21
23
  });
22
24
  const storage = {
23
25
  ...rawStorage,
@@ -1 +1 @@
1
- {"version":3,"file":"testing.mjs","names":[],"sources":["../src/testing-factories.ts"],"sourcesContent":["import { ifDefined } from '@prisma-next/utils/defined';\nimport type { Contract } from './contract-types';\nimport type { CrossReference } from './cross-reference';\nimport type {\n ContractModel,\n ContractModelBase,\n ContractValueObject,\n ModelStorageBase,\n} from './domain-types';\nimport { computeExecutionHash, computeProfileHash, computeStorageHash } from './hashing';\nimport type { ExecutionSection, ProfileHashBase, StorageBase } from './types';\nimport { coreHash } from './types';\n\ntype ContractOverrides<\n TStorage extends StorageBase = StorageBase,\n TModels extends Record<string, ContractModelBase> = Record<string, ContractModel>,\n> = {\n target?: string;\n targetFamily?: string;\n roots?: Record<string, CrossReference>;\n models?: TModels;\n storage?: Omit<TStorage, 'storageHash'>;\n valueObjects?: Record<string, ContractValueObject>;\n capabilities?: Record<string, Record<string, boolean>>;\n extensionPacks?: Record<string, unknown>;\n execution?: Omit<ExecutionSection, 'executionHash'>;\n profileHash?: ProfileHashBase<string>;\n meta?: Record<string, unknown>;\n};\n\nconst DUMMY_HASH = coreHash('sha256:test');\n\nconst DEFAULT_FRAMEWORK_STORAGE = { namespaces: {} } as const;\n\nconst UNBOUND_NAMESPACE_ID = '__unbound__' as const;\n\nconst DEFAULT_SQL_STORAGE = {\n namespaces: {\n [UNBOUND_NAMESPACE_ID]: {\n id: UNBOUND_NAMESPACE_ID,\n tables: {},\n },\n },\n} as const;\n\nexport function createContract<\n TStorage extends StorageBase = StorageBase,\n TModels extends Record<string, ContractModelBase> = Record<string, ContractModel>,\n>(overrides: ContractOverrides<TStorage, TModels> = {}): Contract<TStorage, TModels> {\n const target = overrides.target ?? 'postgres';\n const targetFamily = overrides.targetFamily ?? 'sql';\n const capabilities = overrides.capabilities ?? {};\n\n const rawStorage = overrides.storage ?? DEFAULT_FRAMEWORK_STORAGE;\n\n const storageHash = computeStorageHash({\n target,\n targetFamily,\n storage: rawStorage as Record<string, unknown>,\n });\n\n const storage = {\n ...rawStorage,\n storageHash,\n } as TStorage;\n\n const computedProfileHash =\n overrides.profileHash ?? computeProfileHash({ target, targetFamily, capabilities });\n\n return {\n target,\n targetFamily,\n roots: overrides.roots ?? {},\n models: (overrides.models ?? {}) as TModels,\n ...ifDefined('valueObjects', overrides.valueObjects),\n storage,\n capabilities,\n extensionPacks: overrides.extensionPacks ?? {},\n ...(overrides.execution !== undefined\n ? {\n execution: {\n ...overrides.execution,\n executionHash: computeExecutionHash({\n target,\n targetFamily,\n execution: overrides.execution,\n }),\n },\n }\n : {}),\n profileHash: computedProfileHash,\n meta: overrides.meta ?? {},\n };\n}\n\ntype SqlStorageLike = StorageBase & {\n readonly namespaces: Readonly<\n Record<string, { readonly id: string; readonly tables: Readonly<Record<string, unknown>> }>\n >;\n readonly types?: Record<string, unknown>;\n};\n\ntype SqlModelLike = ContractModel<ModelStorageBase & { table: string }>;\n\nexport function createSqlContract(\n overrides: ContractOverrides<SqlStorageLike, Record<string, SqlModelLike>> = {},\n): Contract<SqlStorageLike, Record<string, SqlModelLike>> {\n return createContract<SqlStorageLike, Record<string, SqlModelLike>>({\n ...overrides,\n target: overrides.target ?? 'postgres',\n targetFamily: overrides.targetFamily ?? 'sql',\n storage: overrides.storage ?? DEFAULT_SQL_STORAGE,\n });\n}\n\nexport { DUMMY_HASH };\n"],"mappings":";;;;AA8BA,MAAM,aAAa,SAAS,aAAa;AAEzC,MAAM,4BAA4B,EAAE,YAAY,CAAC,EAAE;AAEnD,MAAM,uBAAuB;AAE7B,MAAM,sBAAsB,EAC1B,YAAY,GACT,uBAAuB;CACtB,IAAI;CACJ,QAAQ,CAAC;AACX,EACF,EACF;AAEA,SAAgB,eAGd,YAAkD,CAAC,GAAgC;CACnF,MAAM,SAAS,UAAU,UAAU;CACnC,MAAM,eAAe,UAAU,gBAAgB;CAC/C,MAAM,eAAe,UAAU,gBAAgB,CAAC;CAEhD,MAAM,aAAa,UAAU,WAAW;CAExC,MAAM,cAAc,mBAAmB;EACrC;EACA;EACA,SAAS;CACX,CAAC;CAED,MAAM,UAAU;EACd,GAAG;EACH;CACF;CAEA,MAAM,sBACJ,UAAU,eAAe,mBAAmB;EAAE;EAAQ;EAAc;CAAa,CAAC;CAEpF,OAAO;EACL;EACA;EACA,OAAO,UAAU,SAAS,CAAC;EAC3B,QAAS,UAAU,UAAU,CAAC;EAC9B,GAAG,UAAU,gBAAgB,UAAU,YAAY;EACnD;EACA;EACA,gBAAgB,UAAU,kBAAkB,CAAC;EAC7C,GAAI,UAAU,cAAc,KAAA,IACxB,EACE,WAAW;GACT,GAAG,UAAU;GACb,eAAe,qBAAqB;IAClC;IACA;IACA,WAAW,UAAU;GACvB,CAAC;EACH,EACF,IACA,CAAC;EACL,aAAa;EACb,MAAM,UAAU,QAAQ,CAAC;CAC3B;AACF;AAWA,SAAgB,kBACd,YAA6E,CAAC,GACtB;CACxD,OAAO,eAA6D;EAClE,GAAG;EACH,QAAQ,UAAU,UAAU;EAC5B,cAAc,UAAU,gBAAgB;EACxC,SAAS,UAAU,WAAW;CAChC,CAAC;AACH"}
1
+ {"version":3,"file":"testing.mjs","names":[],"sources":["../src/testing-factories.ts"],"sourcesContent":["import { ifDefined } from '@prisma-next/utils/defined';\nimport type { PreserveEmptyPredicate, StorageSort } from './canonicalization';\nimport type { Contract } from './contract-types';\nimport type { CrossReference } from './cross-reference';\nimport type {\n ContractModel,\n ContractModelBase,\n ContractValueObject,\n ModelStorageBase,\n} from './domain-types';\nimport { computeExecutionHash, computeProfileHash, computeStorageHash } from './hashing';\nimport type { ExecutionSection, ProfileHashBase, StorageBase } from './types';\nimport { coreHash } from './types';\n\ntype ContractOverrides<\n TStorage extends StorageBase = StorageBase,\n TModels extends Record<string, ContractModelBase> = Record<string, ContractModel>,\n> = {\n target?: string;\n targetFamily?: string;\n roots?: Record<string, CrossReference>;\n models?: TModels;\n storage?: Omit<TStorage, 'storageHash'>;\n valueObjects?: Record<string, ContractValueObject>;\n capabilities?: Record<string, Record<string, boolean>>;\n extensionPacks?: Record<string, unknown>;\n execution?: Omit<ExecutionSection, 'executionHash'>;\n profileHash?: ProfileHashBase<string>;\n meta?: Record<string, unknown>;\n shouldPreserveEmpty?: PreserveEmptyPredicate;\n sortStorage?: StorageSort;\n};\n\nconst DUMMY_HASH = coreHash('sha256:test');\n\nconst DEFAULT_FRAMEWORK_STORAGE = { namespaces: {} } as const;\n\nconst UNBOUND_NAMESPACE_ID = '__unbound__' as const;\n\nconst DEFAULT_SQL_STORAGE = {\n namespaces: {\n [UNBOUND_NAMESPACE_ID]: {\n id: UNBOUND_NAMESPACE_ID,\n tables: {},\n },\n },\n} as const;\n\nexport function createContract<\n TStorage extends StorageBase = StorageBase,\n TModels extends Record<string, ContractModelBase> = Record<string, ContractModel>,\n>(overrides: ContractOverrides<TStorage, TModels> = {}): Contract<TStorage, TModels> {\n const target = overrides.target ?? 'postgres';\n const targetFamily = overrides.targetFamily ?? 'sql';\n const capabilities = overrides.capabilities ?? {};\n\n const rawStorage = overrides.storage ?? DEFAULT_FRAMEWORK_STORAGE;\n\n const storageHash = computeStorageHash({\n target,\n targetFamily,\n storage: rawStorage as Record<string, unknown>,\n ...ifDefined('shouldPreserveEmpty', overrides.shouldPreserveEmpty),\n ...ifDefined('sortStorage', overrides.sortStorage),\n });\n\n const storage = {\n ...rawStorage,\n storageHash,\n } as TStorage;\n\n const computedProfileHash =\n overrides.profileHash ?? computeProfileHash({ target, targetFamily, capabilities });\n\n return {\n target,\n targetFamily,\n roots: overrides.roots ?? {},\n models: (overrides.models ?? {}) as TModels,\n ...ifDefined('valueObjects', overrides.valueObjects),\n storage,\n capabilities,\n extensionPacks: overrides.extensionPacks ?? {},\n ...(overrides.execution !== undefined\n ? {\n execution: {\n ...overrides.execution,\n executionHash: computeExecutionHash({\n target,\n targetFamily,\n execution: overrides.execution,\n }),\n },\n }\n : {}),\n profileHash: computedProfileHash,\n meta: overrides.meta ?? {},\n };\n}\n\ntype SqlStorageLike = StorageBase & {\n readonly namespaces: Readonly<\n Record<string, { readonly id: string; readonly tables: Readonly<Record<string, unknown>> }>\n >;\n readonly types?: Record<string, unknown>;\n};\n\ntype SqlModelLike = ContractModel<ModelStorageBase & { table: string }>;\n\nexport function createSqlContract(\n overrides: ContractOverrides<SqlStorageLike, Record<string, SqlModelLike>> = {},\n): Contract<SqlStorageLike, Record<string, SqlModelLike>> {\n return createContract<SqlStorageLike, Record<string, SqlModelLike>>({\n ...overrides,\n target: overrides.target ?? 'postgres',\n targetFamily: overrides.targetFamily ?? 'sql',\n storage: overrides.storage ?? DEFAULT_SQL_STORAGE,\n });\n}\n\nexport { DUMMY_HASH };\n"],"mappings":";;;;AAiCA,MAAM,aAAa,SAAS,aAAa;AAEzC,MAAM,4BAA4B,EAAE,YAAY,CAAC,EAAE;AAEnD,MAAM,uBAAuB;AAE7B,MAAM,sBAAsB,EAC1B,YAAY,GACT,uBAAuB;CACtB,IAAI;CACJ,QAAQ,CAAC;AACX,EACF,EACF;AAEA,SAAgB,eAGd,YAAkD,CAAC,GAAgC;CACnF,MAAM,SAAS,UAAU,UAAU;CACnC,MAAM,eAAe,UAAU,gBAAgB;CAC/C,MAAM,eAAe,UAAU,gBAAgB,CAAC;CAEhD,MAAM,aAAa,UAAU,WAAW;CAExC,MAAM,cAAc,mBAAmB;EACrC;EACA;EACA,SAAS;EACT,GAAG,UAAU,uBAAuB,UAAU,mBAAmB;EACjE,GAAG,UAAU,eAAe,UAAU,WAAW;CACnD,CAAC;CAED,MAAM,UAAU;EACd,GAAG;EACH;CACF;CAEA,MAAM,sBACJ,UAAU,eAAe,mBAAmB;EAAE;EAAQ;EAAc;CAAa,CAAC;CAEpF,OAAO;EACL;EACA;EACA,OAAO,UAAU,SAAS,CAAC;EAC3B,QAAS,UAAU,UAAU,CAAC;EAC9B,GAAG,UAAU,gBAAgB,UAAU,YAAY;EACnD;EACA;EACA,gBAAgB,UAAU,kBAAkB,CAAC;EAC7C,GAAI,UAAU,cAAc,KAAA,IACxB,EACE,WAAW;GACT,GAAG,UAAU;GACb,eAAe,qBAAqB;IAClC;IACA;IACA,WAAW,UAAU;GACvB,CAAC;EACH,EACF,IACA,CAAC;EACL,aAAa;EACb,MAAM,UAAU,QAAQ,CAAC;CAC3B;AACF;AAWA,SAAgB,kBACd,YAA6E,CAAC,GACtB;CACxD,OAAO,eAA6D;EAClE,GAAG;EACH,QAAQ,UAAU,UAAU;EAC5B,cAAc,UAAU,gBAAgB;EACxC,SAAS,UAAU,WAAW;CAChC,CAAC;AACH"}
package/package.json CHANGED
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "name": "@prisma-next/contract",
3
- "version": "0.11.0-dev.49",
3
+ "version": "0.11.0-dev.50",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "description": "Data contract type definitions and JSON schema for Prisma Next",
8
8
  "dependencies": {
9
- "@prisma-next/utils": "0.11.0-dev.49",
9
+ "@prisma-next/utils": "0.11.0-dev.50",
10
10
  "@standard-schema/spec": "^1.1.0",
11
11
  "arktype": "^2.2.0"
12
12
  },
13
13
  "devDependencies": {
14
- "@prisma-next/test-utils": "0.11.0-dev.49",
15
- "@prisma-next/tsconfig": "0.11.0-dev.49",
16
- "@prisma-next/tsdown": "0.11.0-dev.49",
14
+ "@prisma-next/test-utils": "0.11.0-dev.50",
15
+ "@prisma-next/tsconfig": "0.11.0-dev.50",
16
+ "@prisma-next/tsdown": "0.11.0-dev.50",
17
17
  "tsdown": "0.22.0",
18
18
  "typescript": "5.9.3",
19
19
  "vitest": "4.1.6"
@@ -29,6 +29,7 @@
29
29
  "exports": {
30
30
  "./contract-validation-error": "./dist/contract-validation-error.mjs",
31
31
  "./hashing": "./dist/hashing.mjs",
32
+ "./hashing-utils": "./dist/hashing-utils.mjs",
32
33
  "./testing": "./dist/testing.mjs",
33
34
  "./types": "./dist/types.mjs",
34
35
  "./validate-domain": "./dist/validate-domain.mjs",
@@ -0,0 +1,44 @@
1
+ import type { PreserveEmptyPredicate } from './canonicalization';
2
+
3
+ export type PathSegment = string | '*' | readonly string[];
4
+
5
+ export type PathPattern = readonly PathSegment[];
6
+
7
+ export function matchesPathPattern(path: readonly string[], pattern: PathPattern): boolean {
8
+ if (path.length !== pattern.length) {
9
+ return false;
10
+ }
11
+
12
+ for (let i = 0; i < pattern.length; i++) {
13
+ const segment = pattern[i];
14
+ const value = path[i];
15
+ if (segment === undefined || value === undefined) {
16
+ return false;
17
+ }
18
+
19
+ if (segment === '*') {
20
+ continue;
21
+ }
22
+
23
+ if (typeof segment === 'string') {
24
+ if (value !== segment) {
25
+ return false;
26
+ }
27
+ continue;
28
+ }
29
+
30
+ if (Array.isArray(segment)) {
31
+ if (!segment.includes(value)) {
32
+ return false;
33
+ }
34
+ }
35
+ }
36
+
37
+ return true;
38
+ }
39
+
40
+ export function createPreserveEmptyPredicate(
41
+ patterns: readonly PathPattern[],
42
+ ): PreserveEmptyPredicate {
43
+ return (path) => patterns.some((pattern) => matchesPathPattern(path, pattern));
44
+ }
@@ -0,0 +1,88 @@
1
+ import type { StorageSort } from './canonicalization';
2
+
3
+ export type PathSegment = string | '*';
4
+
5
+ export interface NamedArraySortTarget {
6
+ readonly path: readonly PathSegment[];
7
+ readonly arrayKeys: readonly string[];
8
+ }
9
+
10
+ function isPlainRecord(value: unknown): value is Record<string, unknown> {
11
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
12
+ }
13
+
14
+ export function compareByNameProperty(a: unknown, b: unknown): number {
15
+ const nameA = isPlainRecord(a) && typeof a['name'] === 'string' ? a['name'] : '';
16
+ const nameB = isPlainRecord(b) && typeof b['name'] === 'string' ? b['name'] : '';
17
+ return nameA.localeCompare(nameB);
18
+ }
19
+
20
+ function sortArrayKeysOnRecord(
21
+ record: Record<string, unknown>,
22
+ arrayKeys: readonly string[],
23
+ compare: (a: unknown, b: unknown) => number,
24
+ ): Record<string, unknown> {
25
+ const sorted: Record<string, unknown> = { ...record };
26
+ for (const key of arrayKeys) {
27
+ const value = record[key];
28
+ if (Array.isArray(value)) {
29
+ sorted[key] = [...value].sort(compare);
30
+ }
31
+ }
32
+ return sorted;
33
+ }
34
+
35
+ function walkAndSort(
36
+ node: unknown,
37
+ pathSegments: readonly PathSegment[],
38
+ arrayKeys: readonly string[],
39
+ compare: (a: unknown, b: unknown) => number,
40
+ ): unknown {
41
+ if (pathSegments.length === 0) {
42
+ if (!isPlainRecord(node)) {
43
+ return node;
44
+ }
45
+ return sortArrayKeysOnRecord(node, arrayKeys, compare);
46
+ }
47
+
48
+ if (!isPlainRecord(node)) {
49
+ return node;
50
+ }
51
+
52
+ const [head, ...rest] = pathSegments;
53
+ if (head === undefined) {
54
+ return node;
55
+ }
56
+
57
+ if (head === '*') {
58
+ const sorted: Record<string, unknown> = { ...node };
59
+ for (const key of Object.keys(node)) {
60
+ sorted[key] = walkAndSort(node[key], rest, arrayKeys, compare);
61
+ }
62
+ return sorted;
63
+ }
64
+
65
+ const child = node[head];
66
+ if (child === undefined) {
67
+ return node;
68
+ }
69
+
70
+ return { ...node, [head]: walkAndSort(child, rest, arrayKeys, compare) };
71
+ }
72
+
73
+ export function createStorageSort(
74
+ targets: readonly NamedArraySortTarget[],
75
+ compare: (a: unknown, b: unknown) => number = compareByNameProperty,
76
+ ): StorageSort {
77
+ return (storage) => {
78
+ if (!isPlainRecord(storage)) {
79
+ return storage;
80
+ }
81
+
82
+ let result: unknown = storage;
83
+ for (const target of targets) {
84
+ result = walkAndSort(result, target.path, target.arrayKeys, compare);
85
+ }
86
+ return result;
87
+ };
88
+ }
@@ -14,6 +14,25 @@ import type { Contract } from './contract-types';
14
14
  */
15
15
  export type SerializeContract = (contract: Contract) => JsonObject;
16
16
 
17
+ /**
18
+ * Family-contributed predicate for the default-omission walk. Called when
19
+ * a value at `path` is a default (empty object/array or `false`); if this
20
+ * returns `true` the value is kept rather than stripped.
21
+ *
22
+ * The framework only calls the predicate inside the `isDefaultValue` branch,
23
+ * so there is no need to guard against non-default values.
24
+ */
25
+ export type PreserveEmptyPredicate = (path: readonly string[]) => boolean;
26
+
27
+ /**
28
+ * Family-contributed storage sort. Applied to the serialized `storage`
29
+ * subtree after the default-omission walk; the result replaces the
30
+ * `storage` field before the final key sort. Use to establish a
31
+ * deterministic order for storage arrays (indexes, uniques) that the
32
+ * family-agnostic `sortObjectKeys` pass cannot handle.
33
+ */
34
+ export type StorageSort = (storage: unknown) => unknown;
35
+
17
36
  const TOP_LEVEL_ORDER = [
18
37
  'schemaVersion',
19
38
  'canonicalVersion',
@@ -42,13 +61,17 @@ function isDefaultValue(value: unknown): boolean {
42
61
  return false;
43
62
  }
44
63
 
45
- function omitDefaults(obj: unknown, path: readonly string[]): unknown {
64
+ function omitDefaults(
65
+ obj: unknown,
66
+ path: readonly string[],
67
+ shouldPreserveEmpty: PreserveEmptyPredicate | undefined,
68
+ ): unknown {
46
69
  if (obj === null || typeof obj !== 'object') {
47
70
  return obj;
48
71
  }
49
72
 
50
73
  if (Array.isArray(obj)) {
51
- return obj.map((item) => omitDefaults(item, path));
74
+ return obj.map((item) => omitDefaults(item, path, shouldPreserveEmpty));
52
75
  }
53
76
 
54
77
  const result: Record<string, unknown> = {};
@@ -74,20 +97,6 @@ function omitDefaults(obj: unknown, path: readonly string[]): unknown {
74
97
  const isNamespaceSlot =
75
98
  currentPath.length === 3 &&
76
99
  isArrayEqual([currentPath[0], currentPath[1]], ['storage', 'namespaces']);
77
- const isRequiredNamespaceTables =
78
- currentPath.length === 4 &&
79
- currentPath[0] === 'storage' &&
80
- currentPath[1] === 'namespaces' &&
81
- currentPath[3] === 'tables';
82
- // Preserve per-table payloads even when empty. SQL tables are never
83
- // emitted empty; Mongo collections legitimately are (a declared
84
- // collection with no schema is a valid representation), and the
85
- // family-agnostic canonicalizer must not strip them.
86
- const isNamespaceTableEntry =
87
- currentPath.length === 5 &&
88
- currentPath[0] === 'storage' &&
89
- currentPath[1] === 'namespaces' &&
90
- currentPath[3] === 'tables';
91
100
  const isRequiredRoots = isArrayEqual(currentPath, ['roots']);
92
101
  const isRequiredExtensionPacks = isArrayEqual(currentPath, ['extensionPacks']);
93
102
  const isRequiredCapabilities = isArrayEqual(currentPath, ['capabilities']);
@@ -104,33 +113,6 @@ function omitDefaults(obj: unknown, path: readonly string[]): unknown {
104
113
  const isModelStorage =
105
114
  currentPath.length === 3 &&
106
115
  isArrayEqual([currentPath[0], currentPath[2]], ['models', 'storage']);
107
- const isNamespaceTableUniques =
108
- currentPath.length === 6 &&
109
- currentPath[0] === 'storage' &&
110
- currentPath[1] === 'namespaces' &&
111
- currentPath[3] === 'tables' &&
112
- currentPath[5] === 'uniques';
113
- const isNamespaceTableIndexes =
114
- currentPath.length === 6 &&
115
- currentPath[0] === 'storage' &&
116
- currentPath[1] === 'namespaces' &&
117
- currentPath[3] === 'tables' &&
118
- currentPath[5] === 'indexes';
119
- const isNamespaceTableForeignKeys =
120
- currentPath.length === 6 &&
121
- currentPath[0] === 'storage' &&
122
- currentPath[1] === 'namespaces' &&
123
- currentPath[3] === 'tables' &&
124
- currentPath[5] === 'foreignKeys';
125
-
126
- // `storage.types.<name>.typeParams` is part of the StorageTypeInstance
127
- // shape (validators require it). Preserve it even when empty so the
128
- // emitted contract.json remains structurally valid after a round-trip.
129
- const isStorageTypeTypeParams =
130
- currentPath.length === 4 &&
131
- currentPath[0] === 'storage' &&
132
- currentPath[1] === 'types' &&
133
- key === 'typeParams';
134
116
 
135
117
  const isDomainUnboundTypeParams =
136
118
  currentPath.length === 5 &&
@@ -138,22 +120,14 @@ function omitDefaults(obj: unknown, path: readonly string[]): unknown {
138
120
  currentPath[2] === 'types' &&
139
121
  key === 'typeParams';
140
122
 
141
- const isFkBooleanField =
142
- currentPath.length === 7 &&
143
- currentPath[0] === 'storage' &&
144
- currentPath[1] === 'namespaces' &&
145
- currentPath[3] === 'tables' &&
146
- currentPath[5] === 'foreignKeys' &&
147
- (key === 'constraint' || key === 'index');
148
-
149
123
  const isNullableField = key === 'nullable';
150
124
 
125
+ const isFamilyPreserved = shouldPreserveEmpty?.(currentPath) ?? false;
126
+
151
127
  if (
152
128
  !isRequiredModels &&
153
129
  !isRequiredNamespaces &&
154
130
  !isNamespaceSlot &&
155
- !isRequiredNamespaceTables &&
156
- !isNamespaceTableEntry &&
157
131
  !isRequiredRoots &&
158
132
  !isRequiredExtensionPacks &&
159
133
  !isRequiredCapabilities &&
@@ -162,19 +136,15 @@ function omitDefaults(obj: unknown, path: readonly string[]): unknown {
162
136
  !isExtensionNamespace &&
163
137
  !isModelRelations &&
164
138
  !isModelStorage &&
165
- !isNamespaceTableUniques &&
166
- !isNamespaceTableIndexes &&
167
- !isNamespaceTableForeignKeys &&
168
- !isFkBooleanField &&
169
139
  !isNullableField &&
170
- !isStorageTypeTypeParams &&
171
- !isDomainUnboundTypeParams
140
+ !isDomainUnboundTypeParams &&
141
+ !isFamilyPreserved
172
142
  ) {
173
143
  continue;
174
144
  }
175
145
  }
176
146
 
177
- result[key] = omitDefaults(value, currentPath);
147
+ result[key] = omitDefaults(value, currentPath, shouldPreserveEmpty);
178
148
  }
179
149
 
180
150
  return result;
@@ -198,88 +168,6 @@ function sortObjectKeys(obj: unknown): unknown {
198
168
  return sorted;
199
169
  }
200
170
 
201
- type NamespaceObject = {
202
- tables?: Record<string, unknown>;
203
- [key: string]: unknown;
204
- };
205
-
206
- type StorageObject = {
207
- namespaces?: Record<string, unknown>;
208
- [key: string]: unknown;
209
- };
210
-
211
- type TableObject = {
212
- indexes?: unknown[];
213
- uniques?: unknown[];
214
- [key: string]: unknown;
215
- };
216
-
217
- function sortTableArrays(tableObj: TableObject): TableObject {
218
- const sortedTable: TableObject = { ...tableObj };
219
-
220
- if (Array.isArray(tableObj.indexes)) {
221
- sortedTable.indexes = [...tableObj.indexes].sort((a, b) => {
222
- const nameA = (a as { name?: string })?.name || '';
223
- const nameB = (b as { name?: string })?.name || '';
224
- return nameA.localeCompare(nameB);
225
- });
226
- }
227
-
228
- if (Array.isArray(tableObj.uniques)) {
229
- sortedTable.uniques = [...tableObj.uniques].sort((a, b) => {
230
- const nameA = (a as { name?: string })?.name || '';
231
- const nameB = (b as { name?: string })?.name || '';
232
- return nameA.localeCompare(nameB);
233
- });
234
- }
235
-
236
- return sortedTable;
237
- }
238
-
239
- function sortIndexesAndUniques(storage: unknown): unknown {
240
- if (!storage || typeof storage !== 'object') {
241
- return storage;
242
- }
243
-
244
- const storageObj = storage as StorageObject;
245
- if (!storageObj.namespaces || typeof storageObj.namespaces !== 'object') {
246
- return storage;
247
- }
248
-
249
- const namespaces = storageObj.namespaces;
250
- const result: StorageObject = { ...storageObj, namespaces: {} };
251
- const resultNamespaces = result.namespaces as Record<string, unknown>;
252
-
253
- for (const nsId of Object.keys(namespaces)) {
254
- const ns = namespaces[nsId];
255
- if (!ns || typeof ns !== 'object') {
256
- resultNamespaces[nsId] = ns;
257
- continue;
258
- }
259
-
260
- const nsObj = ns as NamespaceObject;
261
- if (!nsObj.tables || typeof nsObj.tables !== 'object') {
262
- resultNamespaces[nsId] = ns;
263
- continue;
264
- }
265
-
266
- const sortedTables: Record<string, unknown> = {};
267
- const sortedTableNames = Object.keys(nsObj.tables).sort();
268
- for (const tableName of sortedTableNames) {
269
- const table = nsObj.tables[tableName];
270
- if (!table || typeof table !== 'object') {
271
- sortedTables[tableName] = table;
272
- continue;
273
- }
274
- sortedTables[tableName] = sortTableArrays(table as TableObject);
275
- }
276
-
277
- resultNamespaces[nsId] = { ...nsObj, tables: sortedTables };
278
- }
279
-
280
- return result;
281
- }
282
-
283
171
  export function orderTopLevel(obj: Record<string, unknown>): Record<string, unknown> {
284
172
  const ordered: Record<string, unknown> = {};
285
173
  const remaining = new Set(Object.keys(obj));
@@ -311,6 +199,21 @@ export interface CanonicalizeContractOptions {
311
199
  * the per-target serializer not putting them in the JSON shape.
312
200
  */
313
201
  readonly serializeContract: SerializeContract;
202
+ /**
203
+ * Family-contributed preserve-empty predicate. When the walk encounters a
204
+ * default value (empty object/array or `false`) at `path`, calling this
205
+ * with the full path allows the family to veto the omission. If absent,
206
+ * only the framework's family-agnostic required-slot rules apply.
207
+ */
208
+ readonly shouldPreserveEmpty?: PreserveEmptyPredicate;
209
+ /**
210
+ * Family-contributed storage sort. Applied to the serialized `storage`
211
+ * subtree after the default-omission walk, before the final key sort.
212
+ * SQL family uses this to impose a deterministic order on `indexes` and
213
+ * `uniques` arrays within each namespace table. Families that require no
214
+ * special storage ordering omit this hook.
215
+ */
216
+ readonly sortStorage?: StorageSort;
314
217
  }
315
218
 
316
219
  /**
@@ -338,9 +241,13 @@ export function canonicalizeContractToObject(
338
241
  capabilities: serialized['capabilities'],
339
242
  meta: serialized['meta'],
340
243
  };
341
- const withDefaultsOmitted = omitDefaults(normalized, []) as Record<string, unknown>;
342
- const withSortedIndexes = sortIndexesAndUniques(withDefaultsOmitted['storage']);
343
- const withSortedStorage = { ...withDefaultsOmitted, storage: withSortedIndexes };
244
+ const withDefaultsOmitted = omitDefaults(normalized, [], options.shouldPreserveEmpty) as Record<
245
+ string,
246
+ unknown
247
+ >;
248
+ const withSortedStorage = options.sortStorage
249
+ ? { ...withDefaultsOmitted, storage: options.sortStorage(withDefaultsOmitted['storage']) }
250
+ : withDefaultsOmitted;
344
251
  const withSortedKeys = sortObjectKeys(withSortedStorage) as Record<string, unknown>;
345
252
  return orderTopLevel(withSortedKeys);
346
253
  }
@@ -0,0 +1,12 @@
1
+ export {
2
+ createPreserveEmptyPredicate,
3
+ matchesPathPattern,
4
+ type PathPattern,
5
+ type PathSegment as PreserveEmptyPathSegment,
6
+ } from '../canonicalization-path-match';
7
+ export {
8
+ compareByNameProperty,
9
+ createStorageSort,
10
+ type NamedArraySortTarget,
11
+ type PathSegment as StorageSortPathSegment,
12
+ } from '../canonicalization-storage-sort';
@@ -2,6 +2,8 @@ export {
2
2
  type CanonicalizeContractOptions,
3
3
  canonicalizeContract,
4
4
  canonicalizeContractToObject,
5
+ type PreserveEmptyPredicate,
5
6
  type SerializeContract,
7
+ type StorageSort,
6
8
  } from '../canonicalization';
7
9
  export { computeExecutionHash, computeProfileHash, computeStorageHash } from '../hashing';
package/src/hashing.ts CHANGED
@@ -1,6 +1,11 @@
1
1
  import { createHash } from 'node:crypto';
2
+ import { ifDefined } from '@prisma-next/utils/defined';
2
3
  import type { JsonObject } from '@prisma-next/utils/json';
3
- import { canonicalizeContract } from './canonicalization';
4
+ import {
5
+ canonicalizeContract,
6
+ type PreserveEmptyPredicate,
7
+ type StorageSort,
8
+ } from './canonicalization';
4
9
  import type { Contract } from './contract-types';
5
10
  import type { ExecutionHashBase, ProfileHashBase, StorageHashBase } from './types';
6
11
 
@@ -12,7 +17,13 @@ function sha256(content: string): string {
12
17
  return `sha256:${hash.digest('hex')}`;
13
18
  }
14
19
 
15
- function hashContract(section: Record<string, unknown>): string {
20
+ type HashContractSection = Record<string, unknown> & {
21
+ readonly shouldPreserveEmpty?: PreserveEmptyPredicate;
22
+ readonly sortStorage?: StorageSort;
23
+ };
24
+
25
+ function hashContract(section: HashContractSection): string {
26
+ const { shouldPreserveEmpty, sortStorage, ...sectionData } = section;
16
27
  // Blind cast: the synthesised object is a hash-only stand-in
17
28
  // — never returned to callers, never executed as a Contract.
18
29
  // `canonicalizeContract` only walks the storage / execution /
@@ -20,29 +31,35 @@ function hashContract(section: Record<string, unknown>): string {
20
31
  // missing precise Contract typing on the other slots is
21
32
  // immaterial for the hash result.
22
33
  const contract = {
23
- targetFamily: section['targetFamily'],
24
- target: section['target'],
34
+ targetFamily: sectionData['targetFamily'],
35
+ target: sectionData['target'],
25
36
  roots: {},
26
37
  models: {},
27
- storage: section['storage'] ?? {},
28
- execution: section['execution'],
38
+ storage: sectionData['storage'] ?? {},
39
+ execution: sectionData['execution'],
29
40
  extensionPacks: {},
30
- capabilities: section['capabilities'] ?? {},
41
+ capabilities: sectionData['capabilities'] ?? {},
31
42
  meta: {},
32
43
  profileHash: '',
33
- ...section,
44
+ ...sectionData,
34
45
  } as unknown as Contract;
35
46
  return canonicalizeContract(contract, {
36
47
  schemaVersion: SCHEMA_VERSION,
37
48
  serializeContract: (c) => JSON.parse(JSON.stringify(c)) as JsonObject,
49
+ ...ifDefined('shouldPreserveEmpty', shouldPreserveEmpty),
50
+ ...ifDefined('sortStorage', sortStorage),
38
51
  });
39
52
  }
40
53
 
41
- export function computeStorageHash(args: {
54
+ export type ComputeStorageHashArgs = {
42
55
  target: string;
43
56
  targetFamily: string;
44
57
  storage: Record<string, unknown>;
45
- }): StorageHashBase<string> {
58
+ readonly shouldPreserveEmpty?: PreserveEmptyPredicate;
59
+ readonly sortStorage?: StorageSort;
60
+ };
61
+
62
+ export function computeStorageHash(args: ComputeStorageHashArgs): StorageHashBase<string> {
46
63
  return sha256(hashContract(args)) as StorageHashBase<string>;
47
64
  }
48
65
 
@@ -1,4 +1,5 @@
1
1
  import { ifDefined } from '@prisma-next/utils/defined';
2
+ import type { PreserveEmptyPredicate, StorageSort } from './canonicalization';
2
3
  import type { Contract } from './contract-types';
3
4
  import type { CrossReference } from './cross-reference';
4
5
  import type {
@@ -26,6 +27,8 @@ type ContractOverrides<
26
27
  execution?: Omit<ExecutionSection, 'executionHash'>;
27
28
  profileHash?: ProfileHashBase<string>;
28
29
  meta?: Record<string, unknown>;
30
+ shouldPreserveEmpty?: PreserveEmptyPredicate;
31
+ sortStorage?: StorageSort;
29
32
  };
30
33
 
31
34
  const DUMMY_HASH = coreHash('sha256:test');
@@ -57,6 +60,8 @@ export function createContract<
57
60
  target,
58
61
  targetFamily,
59
62
  storage: rawStorage as Record<string, unknown>,
63
+ ...ifDefined('shouldPreserveEmpty', overrides.shouldPreserveEmpty),
64
+ ...ifDefined('sortStorage', overrides.sortStorage),
60
65
  });
61
66
 
62
67
  const storage = {
@@ -1 +0,0 @@
1
- {"version":3,"file":"hashing-BqnADuDo.mjs","names":[],"sources":["../src/canonicalization.ts","../src/hashing.ts"],"sourcesContent":["import { isArrayEqual } from '@prisma-next/utils/array-equal';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport type { JsonObject } from '@prisma-next/utils/json';\n\nimport type { Contract } from './contract-types';\n\n/**\n * Per-target contract serializer hook. The framework canonicalizer uses\n * this to convert an in-memory contract (which may carry class-instance\n * IR nodes whose runtime-only fields must not appear in the on-disk\n * envelope) into a plain JsonObject before applying the family-agnostic\n * canonical-key ordering / default-omission / sort steps. Targets whose\n * contract is JSON-clean by construction return the contract unchanged.\n */\nexport type SerializeContract = (contract: Contract) => JsonObject;\n\nconst TOP_LEVEL_ORDER = [\n 'schemaVersion',\n 'canonicalVersion',\n 'targetFamily',\n 'target',\n 'profileHash',\n 'roots',\n 'models',\n 'valueObjects',\n 'domain',\n 'storage',\n 'execution',\n 'capabilities',\n 'extensionPacks',\n 'meta',\n] as const;\n\nfunction isDefaultValue(value: unknown): boolean {\n if (value === false) return true;\n if (value === null) return false;\n if (Array.isArray(value) && value.length === 0) return true;\n if (typeof value === 'object' && value !== null) {\n const keys = Object.keys(value);\n return keys.length === 0;\n }\n return false;\n}\n\nfunction omitDefaults(obj: unknown, path: readonly string[]): unknown {\n if (obj === null || typeof obj !== 'object') {\n return obj;\n }\n\n if (Array.isArray(obj)) {\n return obj.map((item) => omitDefaults(item, path));\n }\n\n const result: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(obj)) {\n const currentPath = [...path, key];\n\n if (key === '_generated') {\n continue;\n }\n\n if (key === 'generated' && value === false) {\n continue;\n }\n\n if ((key === 'onDelete' || key === 'onUpdate') && value === 'noAction') {\n continue;\n }\n\n if (isDefaultValue(value)) {\n const isRequiredModels = isArrayEqual(currentPath, ['models']);\n const isRequiredNamespaces = isArrayEqual(currentPath, ['storage', 'namespaces']);\n const isNamespaceSlot =\n currentPath.length === 3 &&\n isArrayEqual([currentPath[0], currentPath[1]], ['storage', 'namespaces']);\n const isRequiredNamespaceTables =\n currentPath.length === 4 &&\n currentPath[0] === 'storage' &&\n currentPath[1] === 'namespaces' &&\n currentPath[3] === 'tables';\n // Preserve per-table payloads even when empty. SQL tables are never\n // emitted empty; Mongo collections legitimately are (a declared\n // collection with no schema is a valid representation), and the\n // family-agnostic canonicalizer must not strip them.\n const isNamespaceTableEntry =\n currentPath.length === 5 &&\n currentPath[0] === 'storage' &&\n currentPath[1] === 'namespaces' &&\n currentPath[3] === 'tables';\n const isRequiredRoots = isArrayEqual(currentPath, ['roots']);\n const isRequiredExtensionPacks = isArrayEqual(currentPath, ['extensionPacks']);\n const isRequiredCapabilities = isArrayEqual(currentPath, ['capabilities']);\n const isRequiredMeta = isArrayEqual(currentPath, ['meta']);\n const isRequiredExecutionDefaults = isArrayEqual(currentPath, [\n 'execution',\n 'mutations',\n 'defaults',\n ]);\n const isExtensionNamespace = currentPath.length === 2 && currentPath[0] === 'extensionPacks';\n const isModelRelations =\n currentPath.length === 3 &&\n isArrayEqual([currentPath[0], currentPath[2]], ['models', 'relations']);\n const isModelStorage =\n currentPath.length === 3 &&\n isArrayEqual([currentPath[0], currentPath[2]], ['models', 'storage']);\n const isNamespaceTableUniques =\n currentPath.length === 6 &&\n currentPath[0] === 'storage' &&\n currentPath[1] === 'namespaces' &&\n currentPath[3] === 'tables' &&\n currentPath[5] === 'uniques';\n const isNamespaceTableIndexes =\n currentPath.length === 6 &&\n currentPath[0] === 'storage' &&\n currentPath[1] === 'namespaces' &&\n currentPath[3] === 'tables' &&\n currentPath[5] === 'indexes';\n const isNamespaceTableForeignKeys =\n currentPath.length === 6 &&\n currentPath[0] === 'storage' &&\n currentPath[1] === 'namespaces' &&\n currentPath[3] === 'tables' &&\n currentPath[5] === 'foreignKeys';\n\n // `storage.types.<name>.typeParams` is part of the StorageTypeInstance\n // shape (validators require it). Preserve it even when empty so the\n // emitted contract.json remains structurally valid after a round-trip.\n const isStorageTypeTypeParams =\n currentPath.length === 4 &&\n currentPath[0] === 'storage' &&\n currentPath[1] === 'types' &&\n key === 'typeParams';\n\n const isDomainUnboundTypeParams =\n currentPath.length === 5 &&\n currentPath[0] === 'domain' &&\n currentPath[2] === 'types' &&\n key === 'typeParams';\n\n const isFkBooleanField =\n currentPath.length === 7 &&\n currentPath[0] === 'storage' &&\n currentPath[1] === 'namespaces' &&\n currentPath[3] === 'tables' &&\n currentPath[5] === 'foreignKeys' &&\n (key === 'constraint' || key === 'index');\n\n const isNullableField = key === 'nullable';\n\n if (\n !isRequiredModels &&\n !isRequiredNamespaces &&\n !isNamespaceSlot &&\n !isRequiredNamespaceTables &&\n !isNamespaceTableEntry &&\n !isRequiredRoots &&\n !isRequiredExtensionPacks &&\n !isRequiredCapabilities &&\n !isRequiredMeta &&\n !isRequiredExecutionDefaults &&\n !isExtensionNamespace &&\n !isModelRelations &&\n !isModelStorage &&\n !isNamespaceTableUniques &&\n !isNamespaceTableIndexes &&\n !isNamespaceTableForeignKeys &&\n !isFkBooleanField &&\n !isNullableField &&\n !isStorageTypeTypeParams &&\n !isDomainUnboundTypeParams\n ) {\n continue;\n }\n }\n\n result[key] = omitDefaults(value, currentPath);\n }\n\n return result;\n}\n\nfunction sortObjectKeys(obj: unknown): unknown {\n if (obj === null || typeof obj !== 'object') {\n return obj;\n }\n\n if (Array.isArray(obj)) {\n return obj.map((item) => sortObjectKeys(item));\n }\n\n const sorted: Record<string, unknown> = {};\n const keys = Object.keys(obj).sort();\n for (const key of keys) {\n sorted[key] = sortObjectKeys((obj as Record<string, unknown>)[key]);\n }\n\n return sorted;\n}\n\ntype NamespaceObject = {\n tables?: Record<string, unknown>;\n [key: string]: unknown;\n};\n\ntype StorageObject = {\n namespaces?: Record<string, unknown>;\n [key: string]: unknown;\n};\n\ntype TableObject = {\n indexes?: unknown[];\n uniques?: unknown[];\n [key: string]: unknown;\n};\n\nfunction sortTableArrays(tableObj: TableObject): TableObject {\n const sortedTable: TableObject = { ...tableObj };\n\n if (Array.isArray(tableObj.indexes)) {\n sortedTable.indexes = [...tableObj.indexes].sort((a, b) => {\n const nameA = (a as { name?: string })?.name || '';\n const nameB = (b as { name?: string })?.name || '';\n return nameA.localeCompare(nameB);\n });\n }\n\n if (Array.isArray(tableObj.uniques)) {\n sortedTable.uniques = [...tableObj.uniques].sort((a, b) => {\n const nameA = (a as { name?: string })?.name || '';\n const nameB = (b as { name?: string })?.name || '';\n return nameA.localeCompare(nameB);\n });\n }\n\n return sortedTable;\n}\n\nfunction sortIndexesAndUniques(storage: unknown): unknown {\n if (!storage || typeof storage !== 'object') {\n return storage;\n }\n\n const storageObj = storage as StorageObject;\n if (!storageObj.namespaces || typeof storageObj.namespaces !== 'object') {\n return storage;\n }\n\n const namespaces = storageObj.namespaces;\n const result: StorageObject = { ...storageObj, namespaces: {} };\n const resultNamespaces = result.namespaces as Record<string, unknown>;\n\n for (const nsId of Object.keys(namespaces)) {\n const ns = namespaces[nsId];\n if (!ns || typeof ns !== 'object') {\n resultNamespaces[nsId] = ns;\n continue;\n }\n\n const nsObj = ns as NamespaceObject;\n if (!nsObj.tables || typeof nsObj.tables !== 'object') {\n resultNamespaces[nsId] = ns;\n continue;\n }\n\n const sortedTables: Record<string, unknown> = {};\n const sortedTableNames = Object.keys(nsObj.tables).sort();\n for (const tableName of sortedTableNames) {\n const table = nsObj.tables[tableName];\n if (!table || typeof table !== 'object') {\n sortedTables[tableName] = table;\n continue;\n }\n sortedTables[tableName] = sortTableArrays(table as TableObject);\n }\n\n resultNamespaces[nsId] = { ...nsObj, tables: sortedTables };\n }\n\n return result;\n}\n\nexport function orderTopLevel(obj: Record<string, unknown>): Record<string, unknown> {\n const ordered: Record<string, unknown> = {};\n const remaining = new Set(Object.keys(obj));\n\n for (const key of TOP_LEVEL_ORDER) {\n if (remaining.has(key)) {\n ordered[key] = obj[key];\n remaining.delete(key);\n }\n }\n\n for (const key of Array.from(remaining).sort()) {\n ordered[key] = obj[key];\n }\n\n return ordered;\n}\n\nexport interface CanonicalizeContractOptions {\n readonly schemaVersion?: string;\n /**\n * Per-target hook that converts the in-memory contract (which may\n * carry class-instance IR nodes) into a plain JsonObject before the\n * family-agnostic canonicalization steps run.\n *\n * Routing through the hook is what lets each target decide which\n * fields appear in the on-disk envelope; runtime-only class API\n * fields stay invisible to the canonicalization walk by virtue of\n * the per-target serializer not putting them in the JSON shape.\n */\n readonly serializeContract: SerializeContract;\n}\n\n/**\n * Object-form variant of {@link canonicalizeContract}. Exported because the\n * emitter writes the canonical contract through a separate JSON-stringify\n * pass and consumes the structured object directly.\n */\nexport function canonicalizeContractToObject(\n contract: Contract,\n options: CanonicalizeContractOptions,\n): Record<string, unknown> {\n const serialized = options.serializeContract(contract);\n const normalized: Record<string, unknown> = {\n ...ifDefined('schemaVersion', options.schemaVersion),\n targetFamily: serialized['targetFamily'],\n target: serialized['target'],\n profileHash: serialized['profileHash'],\n roots: serialized['roots'],\n models: serialized['models'],\n ...ifDefined('valueObjects', serialized['valueObjects']),\n ...ifDefined('domain', serialized['domain']),\n storage: serialized['storage'],\n ...ifDefined('execution', serialized['execution']),\n extensionPacks: serialized['extensionPacks'],\n capabilities: serialized['capabilities'],\n meta: serialized['meta'],\n };\n const withDefaultsOmitted = omitDefaults(normalized, []) as Record<string, unknown>;\n const withSortedIndexes = sortIndexesAndUniques(withDefaultsOmitted['storage']);\n const withSortedStorage = { ...withDefaultsOmitted, storage: withSortedIndexes };\n const withSortedKeys = sortObjectKeys(withSortedStorage) as Record<string, unknown>;\n return orderTopLevel(withSortedKeys);\n}\n\nexport function canonicalizeContract(\n contract: Contract,\n options: CanonicalizeContractOptions,\n): string {\n return JSON.stringify(canonicalizeContractToObject(contract, options), null, 2);\n}\n","import { createHash } from 'node:crypto';\nimport type { JsonObject } from '@prisma-next/utils/json';\nimport { canonicalizeContract } from './canonicalization';\nimport type { Contract } from './contract-types';\nimport type { ExecutionHashBase, ProfileHashBase, StorageHashBase } from './types';\n\nconst SCHEMA_VERSION = '1';\n\nfunction sha256(content: string): string {\n const hash = createHash('sha256');\n hash.update(content);\n return `sha256:${hash.digest('hex')}`;\n}\n\nfunction hashContract(section: Record<string, unknown>): string {\n // Blind cast: the synthesised object is a hash-only stand-in\n // — never returned to callers, never executed as a Contract.\n // `canonicalizeContract` only walks the storage / execution /\n // capabilities slices, all of which are populated above, so the\n // missing precise Contract typing on the other slots is\n // immaterial for the hash result.\n const contract = {\n targetFamily: section['targetFamily'],\n target: section['target'],\n roots: {},\n models: {},\n storage: section['storage'] ?? {},\n execution: section['execution'],\n extensionPacks: {},\n capabilities: section['capabilities'] ?? {},\n meta: {},\n profileHash: '',\n ...section,\n } as unknown as Contract;\n return canonicalizeContract(contract, {\n schemaVersion: SCHEMA_VERSION,\n serializeContract: (c) => JSON.parse(JSON.stringify(c)) as JsonObject,\n });\n}\n\nexport function computeStorageHash(args: {\n target: string;\n targetFamily: string;\n storage: Record<string, unknown>;\n}): StorageHashBase<string> {\n return sha256(hashContract(args)) as StorageHashBase<string>;\n}\n\nexport function computeExecutionHash(args: {\n target: string;\n targetFamily: string;\n execution: Record<string, unknown>;\n}): ExecutionHashBase<string> {\n return sha256(hashContract(args)) as ExecutionHashBase<string>;\n}\n\nexport function computeProfileHash(args: {\n target: string;\n targetFamily: string;\n capabilities: Record<string, Record<string, boolean>>;\n}): ProfileHashBase<string> {\n return sha256(hashContract(args)) as ProfileHashBase<string>;\n}\n"],"mappings":"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"}