@prisma-next/contract 0.7.0 → 0.8.0-dev.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{hashing-Dz-16T7H.mjs → hashing-JryrY8ZF.mjs} +23 -14
- package/dist/hashing-JryrY8ZF.mjs.map +1 -0
- package/dist/hashing.d.mts +32 -7
- package/dist/hashing.d.mts.map +1 -1
- package/dist/hashing.mjs +1 -1
- package/dist/testing.mjs +1 -1
- package/package.json +7 -7
- package/src/canonicalization.ts +46 -14
- package/src/exports/hashing.ts +6 -1
- package/src/exports/validate-contract.ts +5 -0
- package/src/hashing.ts +5 -1
- package/dist/hashing-Dz-16T7H.mjs.map +0 -1
|
@@ -133,20 +133,26 @@ function orderTopLevel(obj) {
|
|
|
133
133
|
for (const key of Array.from(remaining).sort()) ordered[key] = obj[key];
|
|
134
134
|
return ordered;
|
|
135
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* Object-form variant of {@link canonicalizeContract}. Exported because the
|
|
138
|
+
* emitter writes the canonical contract through a separate JSON-stringify
|
|
139
|
+
* pass and consumes the structured object directly.
|
|
140
|
+
*/
|
|
136
141
|
function canonicalizeContractToObject(contract, options) {
|
|
142
|
+
const serialized = options.serializeContract(contract);
|
|
137
143
|
const withDefaultsOmitted = omitDefaults({
|
|
138
|
-
...ifDefined("schemaVersion", options
|
|
139
|
-
targetFamily:
|
|
140
|
-
target:
|
|
141
|
-
profileHash:
|
|
142
|
-
roots:
|
|
143
|
-
models:
|
|
144
|
-
...ifDefined("valueObjects",
|
|
145
|
-
storage:
|
|
146
|
-
...ifDefined("execution",
|
|
147
|
-
extensionPacks:
|
|
148
|
-
capabilities:
|
|
149
|
-
meta:
|
|
144
|
+
...ifDefined("schemaVersion", options.schemaVersion),
|
|
145
|
+
targetFamily: serialized["targetFamily"],
|
|
146
|
+
target: serialized["target"],
|
|
147
|
+
profileHash: serialized["profileHash"],
|
|
148
|
+
roots: serialized["roots"],
|
|
149
|
+
models: serialized["models"],
|
|
150
|
+
...ifDefined("valueObjects", serialized["valueObjects"]),
|
|
151
|
+
storage: serialized["storage"],
|
|
152
|
+
...ifDefined("execution", serialized["execution"]),
|
|
153
|
+
extensionPacks: serialized["extensionPacks"],
|
|
154
|
+
capabilities: serialized["capabilities"],
|
|
155
|
+
meta: serialized["meta"]
|
|
150
156
|
}, []);
|
|
151
157
|
const withSortedIndexes = sortIndexesAndUniques(withDefaultsOmitted["storage"]);
|
|
152
158
|
return orderTopLevel(sortObjectKeys({
|
|
@@ -178,7 +184,10 @@ function hashContract(section) {
|
|
|
178
184
|
meta: {},
|
|
179
185
|
profileHash: "",
|
|
180
186
|
...section
|
|
181
|
-
}, {
|
|
187
|
+
}, {
|
|
188
|
+
schemaVersion: SCHEMA_VERSION,
|
|
189
|
+
serializeContract: (c) => JSON.parse(JSON.stringify(c))
|
|
190
|
+
});
|
|
182
191
|
}
|
|
183
192
|
function computeStorageHash(args) {
|
|
184
193
|
return sha256(hashContract(args));
|
|
@@ -192,4 +201,4 @@ function computeProfileHash(args) {
|
|
|
192
201
|
//#endregion
|
|
193
202
|
export { canonicalizeContractToObject as a, canonicalizeContract as i, computeProfileHash as n, computeStorageHash as r, computeExecutionHash as t };
|
|
194
203
|
|
|
195
|
-
//# sourceMappingURL=hashing-
|
|
204
|
+
//# sourceMappingURL=hashing-JryrY8ZF.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hashing-JryrY8ZF.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 '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 isRequiredTables = isArrayEqual(currentPath, ['storage', 'tables']);\n const isRequiredCollections = isArrayEqual(currentPath, ['storage', 'collections']);\n const isCollectionEntry =\n currentPath.length === 3 &&\n isArrayEqual([currentPath[0], currentPath[1]], ['storage', 'collections']);\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 isTableUniques =\n currentPath.length === 4 &&\n isArrayEqual(\n [currentPath[0], currentPath[1], currentPath[3]],\n ['storage', 'tables', 'uniques'],\n );\n const isTableIndexes =\n currentPath.length === 4 &&\n isArrayEqual(\n [currentPath[0], currentPath[1], currentPath[3]],\n ['storage', 'tables', 'indexes'],\n );\n const isTableForeignKeys =\n currentPath.length === 4 &&\n isArrayEqual(\n [currentPath[0], currentPath[1], currentPath[3]],\n ['storage', 'tables', 'foreignKeys'],\n );\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 isFkBooleanField =\n currentPath.length === 5 &&\n currentPath[0] === 'storage' &&\n currentPath[1] === 'tables' &&\n currentPath[3] === 'foreignKeys' &&\n (key === 'constraint' || key === 'index');\n\n const isNullableField = key === 'nullable';\n\n if (\n !isRequiredModels &&\n !isRequiredTables &&\n !isRequiredCollections &&\n !isCollectionEntry &&\n !isRequiredRoots &&\n !isRequiredExtensionPacks &&\n !isRequiredCapabilities &&\n !isRequiredMeta &&\n !isRequiredExecutionDefaults &&\n !isExtensionNamespace &&\n !isModelRelations &&\n !isModelStorage &&\n !isTableUniques &&\n !isTableIndexes &&\n !isTableForeignKeys &&\n !isFkBooleanField &&\n !isNullableField &&\n !isStorageTypeTypeParams\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 StorageObject = {\n tables?: Record<string, unknown>;\n [key: string]: unknown;\n};\n\ntype TableObject = {\n indexes?: unknown[];\n uniques?: unknown[];\n [key: string]: unknown;\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.tables || typeof storageObj.tables !== 'object') {\n return storage;\n }\n\n const tables = storageObj.tables;\n const result: StorageObject = { ...storageObj };\n\n result.tables = {};\n const sortedTableNames = Object.keys(tables).sort();\n for (const tableName of sortedTableNames) {\n const table = tables[tableName];\n if (!table || typeof table !== 'object') {\n result.tables[tableName] = table;\n continue;\n }\n\n const tableObj = table as 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 result.tables[tableName] = sortedTable;\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 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 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 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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"}
|
package/dist/hashing.d.mts
CHANGED
|
@@ -1,12 +1,37 @@
|
|
|
1
1
|
import { S as ProfileHashBase, T as StorageHashBase, d as ExecutionHashBase, t as Contract } from "./contract-types-Kl86EaEa.mjs";
|
|
2
|
+
import { JsonObject } from "@prisma-next/utils/json";
|
|
2
3
|
|
|
3
4
|
//#region src/canonicalization.d.ts
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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;
|
|
10
35
|
//#endregion
|
|
11
36
|
//#region src/hashing.d.ts
|
|
12
37
|
declare function computeStorageHash(args: {
|
|
@@ -25,5 +50,5 @@ declare function computeProfileHash(args: {
|
|
|
25
50
|
capabilities: Record<string, Record<string, boolean>>;
|
|
26
51
|
}): ProfileHashBase<string>;
|
|
27
52
|
//#endregion
|
|
28
|
-
export { canonicalizeContract, canonicalizeContractToObject, computeExecutionHash, computeProfileHash, computeStorageHash };
|
|
53
|
+
export { type CanonicalizeContractOptions, type SerializeContract, canonicalizeContract, canonicalizeContractToObject, computeExecutionHash, computeProfileHash, computeStorageHash };
|
|
29
54
|
//# sourceMappingURL=hashing.d.mts.map
|
package/dist/hashing.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hashing.d.mts","names":[],"sources":["../src/canonicalization.ts","../src/hashing.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"hashing.d.mts","names":[],"sources":["../src/canonicalization.ts","../src/hashing.ts"],"mappings":";;;;;;AAcA;;;;;;KAAY,iBAAA,IAAqB,QAAA,EAAU,QAAA,KAAa,UAAA;AAAA,UA+OvC,2BAAA;EAAA,SACN,aAAA;EADiC;;;;;;;;AAoB5C;;EApB4C,SAYjC,iBAAA,EAAmB,iBAAA;AAAA;;;;;;iBAQd,4BAAA,CACd,QAAA,EAAU,QAAA,EACV,OAAA,EAAS,2BAAA,GACR,MAAA;AAAA,iBAuBa,oBAAA,CACd,QAAA,EAAU,QAAA,EACV,OAAA,EAAS,2BAAA;;;iBC3QK,kBAAA,CAAmB,IAAA;EACjC,MAAA;EACA,YAAA;EACA,OAAA,EAAS,MAAA;AAAA,IACP,eAAA;AAAA,iBAIY,oBAAA,CAAqB,IAAA;EACnC,MAAA;EACA,YAAA;EACA,SAAA,EAAW,MAAA;AAAA,IACT,iBAAA;AAAA,iBAIY,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-
|
|
1
|
+
import { a as canonicalizeContractToObject, i as canonicalizeContract, n as computeProfileHash, r as computeStorageHash, t as computeExecutionHash } from "./hashing-JryrY8ZF.mjs";
|
|
2
2
|
export { canonicalizeContract, canonicalizeContractToObject, computeExecutionHash, computeProfileHash, computeStorageHash };
|
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-
|
|
2
|
+
import { n as computeProfileHash, r as computeStorageHash, t as computeExecutionHash } from "./hashing-JryrY8ZF.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");
|
package/package.json
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prisma-next/contract",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0-dev.2",
|
|
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.8.0-dev.2",
|
|
9
10
|
"@standard-schema/spec": "^1.1.0",
|
|
10
|
-
"arktype": "^2.1.29"
|
|
11
|
-
"@prisma-next/utils": "0.7.0"
|
|
11
|
+
"arktype": "^2.1.29"
|
|
12
12
|
},
|
|
13
13
|
"devDependencies": {
|
|
14
|
+
"@prisma-next/test-utils": "0.8.0-dev.2",
|
|
15
|
+
"@prisma-next/tsconfig": "0.8.0-dev.2",
|
|
16
|
+
"@prisma-next/tsdown": "0.8.0-dev.2",
|
|
14
17
|
"tsdown": "0.22.0",
|
|
15
18
|
"typescript": "5.9.3",
|
|
16
|
-
"vitest": "4.1.5"
|
|
17
|
-
"@prisma-next/test-utils": "0.7.0",
|
|
18
|
-
"@prisma-next/tsconfig": "0.7.0",
|
|
19
|
-
"@prisma-next/tsdown": "0.7.0"
|
|
19
|
+
"vitest": "4.1.5"
|
|
20
20
|
},
|
|
21
21
|
"files": [
|
|
22
22
|
"dist",
|
package/src/canonicalization.ts
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
import { isArrayEqual } from '@prisma-next/utils/array-equal';
|
|
2
2
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
3
|
+
import type { JsonObject } from '@prisma-next/utils/json';
|
|
3
4
|
|
|
4
5
|
import type { Contract } from './contract-types';
|
|
5
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Per-target contract serializer hook. The framework canonicalizer uses
|
|
9
|
+
* this to convert an in-memory contract (which may carry class-instance
|
|
10
|
+
* IR nodes whose runtime-only fields must not appear in the on-disk
|
|
11
|
+
* envelope) into a plain JsonObject before applying the family-agnostic
|
|
12
|
+
* canonical-key ordering / default-omission / sort steps. Targets whose
|
|
13
|
+
* contract is JSON-clean by construction return the contract unchanged.
|
|
14
|
+
*/
|
|
15
|
+
export type SerializeContract = (contract: Contract) => JsonObject;
|
|
16
|
+
|
|
6
17
|
const TOP_LEVEL_ORDER = [
|
|
7
18
|
'schemaVersion',
|
|
8
19
|
'canonicalVersion',
|
|
@@ -240,23 +251,44 @@ export function orderTopLevel(obj: Record<string, unknown>): Record<string, unkn
|
|
|
240
251
|
return ordered;
|
|
241
252
|
}
|
|
242
253
|
|
|
254
|
+
export interface CanonicalizeContractOptions {
|
|
255
|
+
readonly schemaVersion?: string;
|
|
256
|
+
/**
|
|
257
|
+
* Per-target hook that converts the in-memory contract (which may
|
|
258
|
+
* carry class-instance IR nodes) into a plain JsonObject before the
|
|
259
|
+
* family-agnostic canonicalization steps run.
|
|
260
|
+
*
|
|
261
|
+
* Routing through the hook is what lets each target decide which
|
|
262
|
+
* fields appear in the on-disk envelope; runtime-only class API
|
|
263
|
+
* fields stay invisible to the canonicalization walk by virtue of
|
|
264
|
+
* the per-target serializer not putting them in the JSON shape.
|
|
265
|
+
*/
|
|
266
|
+
readonly serializeContract: SerializeContract;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Object-form variant of {@link canonicalizeContract}. Exported because the
|
|
271
|
+
* emitter writes the canonical contract through a separate JSON-stringify
|
|
272
|
+
* pass and consumes the structured object directly.
|
|
273
|
+
*/
|
|
243
274
|
export function canonicalizeContractToObject(
|
|
244
275
|
contract: Contract,
|
|
245
|
-
options
|
|
276
|
+
options: CanonicalizeContractOptions,
|
|
246
277
|
): Record<string, unknown> {
|
|
278
|
+
const serialized = options.serializeContract(contract);
|
|
247
279
|
const normalized: Record<string, unknown> = {
|
|
248
|
-
...ifDefined('schemaVersion', options
|
|
249
|
-
targetFamily:
|
|
250
|
-
target:
|
|
251
|
-
profileHash:
|
|
252
|
-
roots:
|
|
253
|
-
models:
|
|
254
|
-
...ifDefined('valueObjects',
|
|
255
|
-
storage:
|
|
256
|
-
...ifDefined('execution',
|
|
257
|
-
extensionPacks:
|
|
258
|
-
capabilities:
|
|
259
|
-
meta:
|
|
280
|
+
...ifDefined('schemaVersion', options.schemaVersion),
|
|
281
|
+
targetFamily: serialized['targetFamily'],
|
|
282
|
+
target: serialized['target'],
|
|
283
|
+
profileHash: serialized['profileHash'],
|
|
284
|
+
roots: serialized['roots'],
|
|
285
|
+
models: serialized['models'],
|
|
286
|
+
...ifDefined('valueObjects', serialized['valueObjects']),
|
|
287
|
+
storage: serialized['storage'],
|
|
288
|
+
...ifDefined('execution', serialized['execution']),
|
|
289
|
+
extensionPacks: serialized['extensionPacks'],
|
|
290
|
+
capabilities: serialized['capabilities'],
|
|
291
|
+
meta: serialized['meta'],
|
|
260
292
|
};
|
|
261
293
|
const withDefaultsOmitted = omitDefaults(normalized, []) as Record<string, unknown>;
|
|
262
294
|
const withSortedIndexes = sortIndexesAndUniques(withDefaultsOmitted['storage']);
|
|
@@ -267,7 +299,7 @@ export function canonicalizeContractToObject(
|
|
|
267
299
|
|
|
268
300
|
export function canonicalizeContract(
|
|
269
301
|
contract: Contract,
|
|
270
|
-
options
|
|
302
|
+
options: CanonicalizeContractOptions,
|
|
271
303
|
): string {
|
|
272
304
|
return JSON.stringify(canonicalizeContractToObject(contract, options), null, 2);
|
|
273
305
|
}
|
package/src/exports/hashing.ts
CHANGED
|
@@ -1,2 +1,7 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export {
|
|
2
|
+
type CanonicalizeContractOptions,
|
|
3
|
+
canonicalizeContract,
|
|
4
|
+
canonicalizeContractToObject,
|
|
5
|
+
type SerializeContract,
|
|
6
|
+
} from '../canonicalization';
|
|
2
7
|
export { computeExecutionHash, computeProfileHash, computeStorageHash } from '../hashing';
|
|
@@ -2,5 +2,10 @@ export {
|
|
|
2
2
|
ContractValidationError,
|
|
3
3
|
type ContractValidationPhase,
|
|
4
4
|
type StorageValidator,
|
|
5
|
+
// Framework-internal structural-validation primitive. Not part of the
|
|
6
|
+
// user-facing surface (consumers go through descriptor.contractSerializer);
|
|
7
|
+
// re-exported here so foundation-package consumers (e.g. sql-contract's
|
|
8
|
+
// codec-aware decode wrapper, which backs the SPI's optional decoding
|
|
9
|
+
// path) can compose it.
|
|
5
10
|
validateContract,
|
|
6
11
|
} from '../validate-contract';
|
package/src/hashing.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
+
import type { JsonObject } from '@prisma-next/utils/json';
|
|
2
3
|
import { canonicalizeContract } from './canonicalization';
|
|
3
4
|
import type { Contract } from './contract-types';
|
|
4
5
|
import type { ExecutionHashBase, ProfileHashBase, StorageHashBase } from './types';
|
|
@@ -25,7 +26,10 @@ function hashContract(section: Record<string, unknown>): string {
|
|
|
25
26
|
profileHash: '',
|
|
26
27
|
...section,
|
|
27
28
|
} as Contract;
|
|
28
|
-
return canonicalizeContract(contract, {
|
|
29
|
+
return canonicalizeContract(contract, {
|
|
30
|
+
schemaVersion: SCHEMA_VERSION,
|
|
31
|
+
serializeContract: (c) => JSON.parse(JSON.stringify(c)) as JsonObject,
|
|
32
|
+
});
|
|
29
33
|
}
|
|
30
34
|
|
|
31
35
|
export function computeStorageHash(args: {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"hashing-Dz-16T7H.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';\n\nimport type { Contract } from './contract-types';\n\nconst TOP_LEVEL_ORDER = [\n 'schemaVersion',\n 'canonicalVersion',\n 'targetFamily',\n 'target',\n 'profileHash',\n 'roots',\n 'models',\n 'valueObjects',\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 isRequiredTables = isArrayEqual(currentPath, ['storage', 'tables']);\n const isRequiredCollections = isArrayEqual(currentPath, ['storage', 'collections']);\n const isCollectionEntry =\n currentPath.length === 3 &&\n isArrayEqual([currentPath[0], currentPath[1]], ['storage', 'collections']);\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 isTableUniques =\n currentPath.length === 4 &&\n isArrayEqual(\n [currentPath[0], currentPath[1], currentPath[3]],\n ['storage', 'tables', 'uniques'],\n );\n const isTableIndexes =\n currentPath.length === 4 &&\n isArrayEqual(\n [currentPath[0], currentPath[1], currentPath[3]],\n ['storage', 'tables', 'indexes'],\n );\n const isTableForeignKeys =\n currentPath.length === 4 &&\n isArrayEqual(\n [currentPath[0], currentPath[1], currentPath[3]],\n ['storage', 'tables', 'foreignKeys'],\n );\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 isFkBooleanField =\n currentPath.length === 5 &&\n currentPath[0] === 'storage' &&\n currentPath[1] === 'tables' &&\n currentPath[3] === 'foreignKeys' &&\n (key === 'constraint' || key === 'index');\n\n const isNullableField = key === 'nullable';\n\n if (\n !isRequiredModels &&\n !isRequiredTables &&\n !isRequiredCollections &&\n !isCollectionEntry &&\n !isRequiredRoots &&\n !isRequiredExtensionPacks &&\n !isRequiredCapabilities &&\n !isRequiredMeta &&\n !isRequiredExecutionDefaults &&\n !isExtensionNamespace &&\n !isModelRelations &&\n !isModelStorage &&\n !isTableUniques &&\n !isTableIndexes &&\n !isTableForeignKeys &&\n !isFkBooleanField &&\n !isNullableField &&\n !isStorageTypeTypeParams\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 StorageObject = {\n tables?: Record<string, unknown>;\n [key: string]: unknown;\n};\n\ntype TableObject = {\n indexes?: unknown[];\n uniques?: unknown[];\n [key: string]: unknown;\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.tables || typeof storageObj.tables !== 'object') {\n return storage;\n }\n\n const tables = storageObj.tables;\n const result: StorageObject = { ...storageObj };\n\n result.tables = {};\n const sortedTableNames = Object.keys(tables).sort();\n for (const tableName of sortedTableNames) {\n const table = tables[tableName];\n if (!table || typeof table !== 'object') {\n result.tables[tableName] = table;\n continue;\n }\n\n const tableObj = table as 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 result.tables[tableName] = sortedTable;\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 function canonicalizeContractToObject(\n contract: Contract,\n options?: { schemaVersion?: string },\n): Record<string, unknown> {\n const normalized: Record<string, unknown> = {\n ...ifDefined('schemaVersion', options?.schemaVersion),\n targetFamily: contract.targetFamily,\n target: contract.target,\n profileHash: contract.profileHash,\n roots: contract.roots,\n models: contract.models,\n ...ifDefined('valueObjects', contract.valueObjects),\n storage: contract.storage,\n ...ifDefined('execution', contract.execution),\n extensionPacks: contract.extensionPacks,\n capabilities: contract.capabilities,\n meta: contract.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?: { schemaVersion?: string },\n): string {\n return JSON.stringify(canonicalizeContractToObject(contract, options), null, 2);\n}\n","import { createHash } from 'node:crypto';\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 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 Contract;\n return canonicalizeContract(contract, { schemaVersion: SCHEMA_VERSION });\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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"}
|