@prisma-next/emitter 0.3.0-dev.34 → 0.3.0-dev.36
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/README.md +6 -4
- package/dist/exports/index.d.mts +3 -0
- package/dist/exports/index.mjs +3 -0
- package/dist/test/{utils.d.ts → utils.d.mts} +10 -5
- package/dist/test/utils.d.mts.map +1 -0
- package/dist/test/utils.mjs +59 -0
- package/dist/test/utils.mjs.map +1 -0
- package/package.json +18 -12
- package/test/canonicalization.test.ts +93 -1
- package/test/emitter.integration.test.ts +65 -61
- package/test/emitter.roundtrip.test.ts +4 -4
- package/test/emitter.test.ts +6 -4
- package/test/factories.test.ts +10 -10
- package/test/hashing.test.ts +5 -5
- package/test/utils.ts +9 -9
- package/dist/exports/index.js +0 -6
- package/dist/exports/index.js.map +0 -1
- package/dist/src/exports/index.d.ts +0 -4
- package/dist/src/exports/index.d.ts.map +0 -1
- package/dist/src/target-family.d.ts +0 -2
- package/dist/src/target-family.d.ts.map +0 -1
- package/dist/test/utils.d.ts.map +0 -1
- package/dist/test/utils.js +0 -78
- package/dist/test/utils.js.map +0 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ Contract emission engine that transforms authored data models into canonical JSO
|
|
|
6
6
|
|
|
7
7
|
The emitter is the core of Prisma Next's contract-first architecture. It takes authored data models (from PSL or TypeScript builders) and produces two deterministic artifacts:
|
|
8
8
|
|
|
9
|
-
1. **`contract.json`** — Canonical JSON representation of the data contract with embedded `
|
|
9
|
+
1. **`contract.json`** — Canonical JSON representation of the data contract with embedded `storageHash` and optional `executionHash`/`profileHash`. Callers may add `_generated` metadata field to indicate it's a generated artifact (excluded from canonicalization/hashing).
|
|
10
10
|
2. **`contract.d.ts`** — TypeScript type definitions used by query builders and tooling (types-only, no runtime code). Includes warning header comments generated by target family hooks to indicate it's a generated file.
|
|
11
11
|
|
|
12
12
|
The emitter is target-family-agnostic and uses a pluggable hook system (`TargetFamilyHook`) to handle family-specific validation and type generation. This keeps the core thin while allowing SQL, Document, and other target families to extend emission behavior.
|
|
@@ -19,7 +19,7 @@ Provide a deterministic, verifiable representation of the application's data con
|
|
|
19
19
|
|
|
20
20
|
- **Parse**: Accept contract IR (Intermediate Representation) from authoring surfaces
|
|
21
21
|
- **Validate**: Core structure validation plus family-specific type and structure validation via hooks
|
|
22
|
-
- **Canonicalize**: Compute `
|
|
22
|
+
- **Canonicalize**: Compute `storageHash` (schema meaning), `executionHash` (execution defaults), and `profileHash` (capabilities/pins) from canonical JSON
|
|
23
23
|
- **Emit**: Generate `contract.json` and `contract.d.ts` with family-specific type generation
|
|
24
24
|
- **Descriptor-Agnostic**: The emitter is completely agnostic to how descriptors are produced. It receives pre-assembled `OperationRegistry`, `codecTypeImports`, `operationTypeImports`, and `extensionIds` from the CLI or family helpers—no pack manifest parsing happens inside the emitter.
|
|
25
25
|
|
|
@@ -90,7 +90,8 @@ flowchart TD
|
|
|
90
90
|
- **Note**: `TargetFamilyHook`, `ValidationContext`, and `TypesImportSpec` types are defined in `@prisma-next/contract/types` (shared plane) and re-exported from this package for backward compatibility.
|
|
91
91
|
|
|
92
92
|
### Hashing (`hashing.ts`)
|
|
93
|
-
- `
|
|
93
|
+
- `computeStorageHash`: SHA-256 of schema structure (models, storage, relations)
|
|
94
|
+
- `computeExecutionHash`: SHA-256 of execution defaults
|
|
94
95
|
- `computeProfileHash`: SHA-256 of capabilities and adapter pins
|
|
95
96
|
|
|
96
97
|
### Canonicalization (`canonicalization.ts`)
|
|
@@ -156,7 +157,8 @@ const result = await emit(ir, {
|
|
|
156
157
|
|
|
157
158
|
// result.contractJson: string (JSON) - canonical JSON without _generated metadata
|
|
158
159
|
// result.contractDts: string (TypeScript definitions) - includes warning header
|
|
159
|
-
// result.
|
|
160
|
+
// result.storageHash: string
|
|
161
|
+
// result.executionHash?: string
|
|
160
162
|
// result.profileHash?: string
|
|
161
163
|
```
|
|
162
164
|
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { EmitOptions, EmitResult, emit } from "@prisma-next/core-control-plane/emission";
|
|
2
|
+
import { TargetFamilyHook, TypesImportSpec, ValidationContext } from "@prisma-next/contract/types";
|
|
3
|
+
export { type EmitOptions, type EmitResult, type TargetFamilyHook, type TypesImportSpec, type ValidationContext, emit };
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ContractIR } from "@prisma-next/contract/ir";
|
|
2
|
+
|
|
3
|
+
//#region test/utils.d.ts
|
|
4
|
+
|
|
2
5
|
/**
|
|
3
6
|
* Factory function for creating ContractIR objects in tests.
|
|
4
7
|
* Provides sensible defaults and allows overriding specific fields.
|
|
@@ -7,8 +10,10 @@ import { type ContractIR } from '@prisma-next/contract/ir';
|
|
|
7
10
|
* If a field is explicitly set to `undefined` in overrides, it will be omitted
|
|
8
11
|
* from the result (useful for testing validation of missing fields).
|
|
9
12
|
*/
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
declare function createContractIR(overrides?: Partial<ContractIR> & {
|
|
14
|
+
storageHash?: string;
|
|
15
|
+
profileHash?: string;
|
|
13
16
|
}): ContractIR;
|
|
14
|
-
//#
|
|
17
|
+
//#endregion
|
|
18
|
+
export { createContractIR };
|
|
19
|
+
//# sourceMappingURL=utils.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.mts","names":[],"sources":["../../test/utils.ts"],"sourcesContent":[],"mappings":";;;;;;AAUA;;;;;;iBAAgB,gBAAA,aACH,QAAQ;;;IAClB"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { irHeader, irMeta } from "@prisma-next/contract/ir";
|
|
2
|
+
|
|
3
|
+
//#region test/utils.ts
|
|
4
|
+
/**
|
|
5
|
+
* Factory function for creating ContractIR objects in tests.
|
|
6
|
+
* Provides sensible defaults and allows overriding specific fields.
|
|
7
|
+
* Uses the emitter factories internally for consistency.
|
|
8
|
+
*
|
|
9
|
+
* If a field is explicitly set to `undefined` in overrides, it will be omitted
|
|
10
|
+
* from the result (useful for testing validation of missing fields).
|
|
11
|
+
*/
|
|
12
|
+
function createContractIR(overrides = {}) {
|
|
13
|
+
const hasTarget = "target" in overrides;
|
|
14
|
+
const hasTargetFamily = "targetFamily" in overrides;
|
|
15
|
+
const hasStorageHash = "storageHash" in overrides;
|
|
16
|
+
const hasSchemaVersion = "schemaVersion" in overrides;
|
|
17
|
+
const hasModels = "models" in overrides;
|
|
18
|
+
const hasRelations = "relations" in overrides;
|
|
19
|
+
const hasStorage = "storage" in overrides;
|
|
20
|
+
const hasCapabilities = "capabilities" in overrides;
|
|
21
|
+
const hasExtensionPacks = "extensionPacks" in overrides;
|
|
22
|
+
const hasMeta = "meta" in overrides;
|
|
23
|
+
const hasSources = "sources" in overrides;
|
|
24
|
+
const headerOpts = {};
|
|
25
|
+
if (hasTarget && overrides.target !== void 0) headerOpts.target = overrides.target;
|
|
26
|
+
else if (!hasTarget) headerOpts.target = "postgres";
|
|
27
|
+
if (hasTargetFamily && overrides.targetFamily !== void 0) headerOpts.targetFamily = overrides.targetFamily;
|
|
28
|
+
else if (!hasTargetFamily) headerOpts.targetFamily = "sql";
|
|
29
|
+
if (hasStorageHash && overrides.storageHash !== void 0) headerOpts.storageHash = overrides.storageHash;
|
|
30
|
+
else if (!hasStorageHash) headerOpts.storageHash = "sha256:test";
|
|
31
|
+
if (overrides.profileHash !== void 0) headerOpts.profileHash = overrides.profileHash;
|
|
32
|
+
const header = irHeader(headerOpts);
|
|
33
|
+
const metaOpts = {};
|
|
34
|
+
if (hasCapabilities && overrides.capabilities !== void 0) metaOpts.capabilities = overrides.capabilities;
|
|
35
|
+
else if (!hasCapabilities) metaOpts.capabilities = {};
|
|
36
|
+
if (hasExtensionPacks && overrides.extensionPacks !== void 0) metaOpts.extensionPacks = overrides.extensionPacks;
|
|
37
|
+
else if (!hasExtensionPacks) metaOpts.extensionPacks = {};
|
|
38
|
+
if (hasMeta && overrides.meta !== void 0) metaOpts.meta = overrides.meta;
|
|
39
|
+
else if (!hasMeta) metaOpts.meta = {};
|
|
40
|
+
if (hasSources && overrides.sources !== void 0) metaOpts.sources = overrides.sources;
|
|
41
|
+
else if (!hasSources) metaOpts.sources = {};
|
|
42
|
+
const meta = irMeta(Object.keys(metaOpts).length > 0 ? metaOpts : void 0);
|
|
43
|
+
return {
|
|
44
|
+
schemaVersion: hasSchemaVersion && overrides.schemaVersion !== void 0 ? overrides.schemaVersion : hasSchemaVersion && overrides.schemaVersion === void 0 ? void 0 : header.schemaVersion,
|
|
45
|
+
target: header.target,
|
|
46
|
+
targetFamily: header.targetFamily,
|
|
47
|
+
capabilities: hasCapabilities && overrides.capabilities === void 0 ? void 0 : !hasCapabilities || overrides.capabilities !== void 0 ? meta.capabilities : {},
|
|
48
|
+
extensionPacks: hasExtensionPacks && overrides.extensionPacks === void 0 ? void 0 : !hasExtensionPacks || overrides.extensionPacks !== void 0 ? meta.extensionPacks : {},
|
|
49
|
+
meta: hasMeta && overrides.meta === void 0 ? void 0 : !hasMeta || overrides.meta !== void 0 ? meta.meta : {},
|
|
50
|
+
sources: hasSources && overrides.sources === void 0 ? void 0 : !hasSources || overrides.sources !== void 0 ? meta.sources : {},
|
|
51
|
+
storage: hasStorage && overrides.storage === void 0 ? void 0 : hasStorage && overrides.storage !== void 0 ? overrides.storage : !hasStorage ? { tables: {} } : {},
|
|
52
|
+
models: hasModels && overrides.models === void 0 ? void 0 : hasModels && overrides.models !== void 0 ? overrides.models : !hasModels ? {} : {},
|
|
53
|
+
relations: hasRelations && overrides.relations === void 0 ? void 0 : hasRelations && overrides.relations !== void 0 ? overrides.relations : !hasRelations ? {} : {}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
//#endregion
|
|
58
|
+
export { createContractIR };
|
|
59
|
+
//# sourceMappingURL=utils.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.mjs","names":["headerOpts: {\n target?: string;\n targetFamily?: string;\n storageHash?: string;\n profileHash?: string;\n }","metaOpts: {\n capabilities?: Record<string, Record<string, boolean>>;\n extensionPacks?: Record<string, unknown>;\n meta?: Record<string, unknown>;\n sources?: Record<string, unknown>;\n }"],"sources":["../../test/utils.ts"],"sourcesContent":["import { type ContractIR, irHeader, irMeta } from '@prisma-next/contract/ir';\n\n/**\n * Factory function for creating ContractIR objects in tests.\n * Provides sensible defaults and allows overriding specific fields.\n * Uses the emitter factories internally for consistency.\n *\n * If a field is explicitly set to `undefined` in overrides, it will be omitted\n * from the result (useful for testing validation of missing fields).\n */\nexport function createContractIR(\n overrides: Partial<ContractIR> & { storageHash?: string; profileHash?: string } = {},\n): ContractIR {\n // Check if fields are explicitly undefined (not just missing)\n const hasTarget = 'target' in overrides;\n const hasTargetFamily = 'targetFamily' in overrides;\n const hasStorageHash = 'storageHash' in overrides;\n const hasSchemaVersion = 'schemaVersion' in overrides;\n const hasModels = 'models' in overrides;\n const hasRelations = 'relations' in overrides;\n const hasStorage = 'storage' in overrides;\n const hasCapabilities = 'capabilities' in overrides;\n const hasExtensionPacks = 'extensionPacks' in overrides;\n const hasMeta = 'meta' in overrides;\n const hasSources = 'sources' in overrides;\n\n // Build header, omitting fields that are explicitly undefined\n const headerOpts: {\n target?: string;\n targetFamily?: string;\n storageHash?: string;\n profileHash?: string;\n } = {};\n\n if (hasTarget && overrides.target !== undefined) {\n headerOpts.target = overrides.target;\n } else if (!hasTarget) {\n headerOpts.target = 'postgres';\n }\n\n if (hasTargetFamily && overrides.targetFamily !== undefined) {\n headerOpts.targetFamily = overrides.targetFamily;\n } else if (!hasTargetFamily) {\n headerOpts.targetFamily = 'sql';\n }\n\n if (hasStorageHash && overrides.storageHash !== undefined) {\n headerOpts.storageHash = overrides.storageHash;\n } else if (!hasStorageHash) {\n headerOpts.storageHash = 'sha256:test';\n }\n\n // profileHash is not part of ContractIR, but we can accept it for header creation\n if (overrides.profileHash !== undefined) {\n headerOpts.profileHash = overrides.profileHash;\n }\n\n const header = irHeader(\n headerOpts as {\n target: string;\n targetFamily: string;\n storageHash: string;\n profileHash?: string;\n },\n );\n\n // Build meta, handling explicitly undefined fields\n // If a field is explicitly undefined, we'll omit it from the result later\n const metaOpts: {\n capabilities?: Record<string, Record<string, boolean>>;\n extensionPacks?: Record<string, unknown>;\n meta?: Record<string, unknown>;\n sources?: Record<string, unknown>;\n } = {};\n\n if (hasCapabilities && overrides.capabilities !== undefined) {\n metaOpts.capabilities = overrides.capabilities;\n } else if (!hasCapabilities) {\n metaOpts.capabilities = {};\n }\n\n if (hasExtensionPacks && overrides.extensionPacks !== undefined) {\n metaOpts.extensionPacks = overrides.extensionPacks;\n } else if (!hasExtensionPacks) {\n metaOpts.extensionPacks = {};\n }\n\n if (hasMeta && overrides.meta !== undefined) {\n metaOpts.meta = overrides.meta;\n } else if (!hasMeta) {\n metaOpts.meta = {};\n }\n\n if (hasSources && overrides.sources !== undefined) {\n metaOpts.sources = overrides.sources;\n } else if (!hasSources) {\n metaOpts.sources = {};\n }\n\n const meta = irMeta(Object.keys(metaOpts).length > 0 ? metaOpts : undefined);\n\n // Build result by constructing the object directly (ContractIR doesn't include storageHash/profileHash)\n // When fields are explicitly undefined, include them as undefined (tests use type assertions to bypass TS)\n const result = {\n schemaVersion:\n hasSchemaVersion && overrides.schemaVersion !== undefined\n ? overrides.schemaVersion\n : hasSchemaVersion && overrides.schemaVersion === undefined\n ? (undefined as unknown as string)\n : header.schemaVersion,\n target: header.target,\n targetFamily: header.targetFamily,\n // Only include meta fields if they're not explicitly undefined\n capabilities:\n hasCapabilities && overrides.capabilities === undefined\n ? (undefined as unknown as Record<string, Record<string, boolean>>)\n : !hasCapabilities || overrides.capabilities !== undefined\n ? meta.capabilities\n : ({} as Record<string, Record<string, boolean>>),\n extensionPacks:\n hasExtensionPacks && overrides.extensionPacks === undefined\n ? (undefined as unknown as Record<string, unknown>)\n : !hasExtensionPacks || overrides.extensionPacks !== undefined\n ? meta.extensionPacks\n : ({} as Record<string, unknown>),\n meta:\n hasMeta && overrides.meta === undefined\n ? (undefined as unknown as Record<string, unknown>)\n : !hasMeta || overrides.meta !== undefined\n ? meta.meta\n : ({} as Record<string, unknown>),\n sources:\n hasSources && overrides.sources === undefined\n ? (undefined as unknown as Record<string, unknown>)\n : !hasSources || overrides.sources !== undefined\n ? meta.sources\n : ({} as Record<string, unknown>),\n // Only include family sections if they're not explicitly undefined\n storage:\n hasStorage && overrides.storage === undefined\n ? (undefined as unknown as Record<string, unknown>)\n : hasStorage && overrides.storage !== undefined\n ? (overrides.storage as Record<string, unknown>)\n : !hasStorage\n ? ({ tables: {} } as Record<string, unknown>)\n : ({} as Record<string, unknown>),\n models:\n hasModels && overrides.models === undefined\n ? (undefined as unknown as Record<string, unknown>)\n : hasModels && overrides.models !== undefined\n ? (overrides.models as Record<string, unknown>)\n : !hasModels\n ? {}\n : ({} as Record<string, unknown>),\n relations:\n hasRelations && overrides.relations === undefined\n ? (undefined as unknown as Record<string, unknown>)\n : hasRelations && overrides.relations !== undefined\n ? (overrides.relations as Record<string, unknown>)\n : !hasRelations\n ? {}\n : ({} as Record<string, unknown>),\n } as ContractIR;\n\n return result;\n}\n"],"mappings":";;;;;;;;;;;AAUA,SAAgB,iBACd,YAAkF,EAAE,EACxE;CAEZ,MAAM,YAAY,YAAY;CAC9B,MAAM,kBAAkB,kBAAkB;CAC1C,MAAM,iBAAiB,iBAAiB;CACxC,MAAM,mBAAmB,mBAAmB;CAC5C,MAAM,YAAY,YAAY;CAC9B,MAAM,eAAe,eAAe;CACpC,MAAM,aAAa,aAAa;CAChC,MAAM,kBAAkB,kBAAkB;CAC1C,MAAM,oBAAoB,oBAAoB;CAC9C,MAAM,UAAU,UAAU;CAC1B,MAAM,aAAa,aAAa;CAGhC,MAAMA,aAKF,EAAE;AAEN,KAAI,aAAa,UAAU,WAAW,OACpC,YAAW,SAAS,UAAU;UACrB,CAAC,UACV,YAAW,SAAS;AAGtB,KAAI,mBAAmB,UAAU,iBAAiB,OAChD,YAAW,eAAe,UAAU;UAC3B,CAAC,gBACV,YAAW,eAAe;AAG5B,KAAI,kBAAkB,UAAU,gBAAgB,OAC9C,YAAW,cAAc,UAAU;UAC1B,CAAC,eACV,YAAW,cAAc;AAI3B,KAAI,UAAU,gBAAgB,OAC5B,YAAW,cAAc,UAAU;CAGrC,MAAM,SAAS,SACb,WAMD;CAID,MAAMC,WAKF,EAAE;AAEN,KAAI,mBAAmB,UAAU,iBAAiB,OAChD,UAAS,eAAe,UAAU;UACzB,CAAC,gBACV,UAAS,eAAe,EAAE;AAG5B,KAAI,qBAAqB,UAAU,mBAAmB,OACpD,UAAS,iBAAiB,UAAU;UAC3B,CAAC,kBACV,UAAS,iBAAiB,EAAE;AAG9B,KAAI,WAAW,UAAU,SAAS,OAChC,UAAS,OAAO,UAAU;UACjB,CAAC,QACV,UAAS,OAAO,EAAE;AAGpB,KAAI,cAAc,UAAU,YAAY,OACtC,UAAS,UAAU,UAAU;UACpB,CAAC,WACV,UAAS,UAAU,EAAE;CAGvB,MAAM,OAAO,OAAO,OAAO,KAAK,SAAS,CAAC,SAAS,IAAI,WAAW,OAAU;AAiE5E,QA7De;EACb,eACE,oBAAoB,UAAU,kBAAkB,SAC5C,UAAU,gBACV,oBAAoB,UAAU,kBAAkB,SAC7C,SACD,OAAO;EACf,QAAQ,OAAO;EACf,cAAc,OAAO;EAErB,cACE,mBAAmB,UAAU,iBAAiB,SACzC,SACD,CAAC,mBAAmB,UAAU,iBAAiB,SAC7C,KAAK,eACJ,EAAE;EACX,gBACE,qBAAqB,UAAU,mBAAmB,SAC7C,SACD,CAAC,qBAAqB,UAAU,mBAAmB,SACjD,KAAK,iBACJ,EAAE;EACX,MACE,WAAW,UAAU,SAAS,SACzB,SACD,CAAC,WAAW,UAAU,SAAS,SAC7B,KAAK,OACJ,EAAE;EACX,SACE,cAAc,UAAU,YAAY,SAC/B,SACD,CAAC,cAAc,UAAU,YAAY,SACnC,KAAK,UACJ,EAAE;EAEX,SACE,cAAc,UAAU,YAAY,SAC/B,SACD,cAAc,UAAU,YAAY,SACjC,UAAU,UACX,CAAC,aACE,EAAE,QAAQ,EAAE,EAAE,GACd,EAAE;EACb,QACE,aAAa,UAAU,WAAW,SAC7B,SACD,aAAa,UAAU,WAAW,SAC/B,UAAU,SACX,CAAC,YACC,EAAE,GACD,EAAE;EACb,WACE,gBAAgB,UAAU,cAAc,SACnC,SACD,gBAAgB,UAAU,cAAc,SACrC,UAAU,YACX,CAAC,eACC,EAAE,GACD,EAAE;EACd"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prisma-next/emitter",
|
|
3
|
-
"version": "0.3.0-dev.
|
|
3
|
+
"version": "0.3.0-dev.36",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"files": [
|
|
@@ -10,30 +10,36 @@
|
|
|
10
10
|
],
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"arktype": "^2.0.0",
|
|
13
|
-
"@prisma-next/contract": "0.3.0-dev.
|
|
14
|
-
"@prisma-next/core-control-plane": "0.3.0-dev.
|
|
13
|
+
"@prisma-next/contract": "0.3.0-dev.36",
|
|
14
|
+
"@prisma-next/core-control-plane": "0.3.0-dev.36"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
17
|
"@types/node": "24.10.4",
|
|
18
|
-
"
|
|
18
|
+
"tsdown": "0.18.4",
|
|
19
19
|
"typescript": "5.9.3",
|
|
20
|
-
"vitest": "4.0.
|
|
21
|
-
"@prisma-next/operations": "0.3.0-dev.
|
|
20
|
+
"vitest": "4.0.17",
|
|
21
|
+
"@prisma-next/operations": "0.3.0-dev.36",
|
|
22
22
|
"@prisma-next/test-utils": "0.0.1",
|
|
23
|
-
"@prisma-next/tsconfig": "0.0.0"
|
|
23
|
+
"@prisma-next/tsconfig": "0.0.0",
|
|
24
|
+
"@prisma-next/tsdown": "0.0.0"
|
|
24
25
|
},
|
|
25
26
|
"exports": {
|
|
26
27
|
".": {
|
|
27
|
-
"types": "./dist/
|
|
28
|
-
"import": "./dist/exports/index.
|
|
28
|
+
"types": "./dist/exports/index.d.mts",
|
|
29
|
+
"import": "./dist/exports/index.mjs"
|
|
29
30
|
},
|
|
30
31
|
"./test/utils": {
|
|
31
|
-
"types": "./dist/test/utils.d.
|
|
32
|
-
"import": "./dist/test/utils.
|
|
32
|
+
"types": "./dist/test/utils.d.mts",
|
|
33
|
+
"import": "./dist/test/utils.mjs"
|
|
33
34
|
}
|
|
34
35
|
},
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "https://github.com/prisma/prisma-next.git",
|
|
39
|
+
"directory": "packages/1-framework/3-tooling/emitter"
|
|
40
|
+
},
|
|
35
41
|
"scripts": {
|
|
36
|
-
"build": "
|
|
42
|
+
"build": "tsdown",
|
|
37
43
|
"test": "vitest run",
|
|
38
44
|
"test:coverage": "vitest run --coverage",
|
|
39
45
|
"typecheck": "tsc --project tsconfig.json --noEmit",
|
|
@@ -55,6 +55,44 @@ describe('canonicalization', () => {
|
|
|
55
55
|
expect(email['nullable']).toBe(true);
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
+
it.each([
|
|
59
|
+
{ nullable: false },
|
|
60
|
+
{ nullable: undefined },
|
|
61
|
+
])('keeps nullable false for columns with defaults', ({ nullable }) => {
|
|
62
|
+
const ir = createContractIR({
|
|
63
|
+
storage: {
|
|
64
|
+
tables: {
|
|
65
|
+
user: {
|
|
66
|
+
columns: {
|
|
67
|
+
created_at: {
|
|
68
|
+
codecId: 'pg/timestamptz@1',
|
|
69
|
+
nativeType: 'timestamptz',
|
|
70
|
+
nullable,
|
|
71
|
+
default: { kind: 'function', expression: 'now()' },
|
|
72
|
+
},
|
|
73
|
+
updated_at: {
|
|
74
|
+
codecId: 'pg/timestamptz@1',
|
|
75
|
+
nativeType: 'timestamptz',
|
|
76
|
+
nullable: true,
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const result = canonicalizeContract(ir);
|
|
85
|
+
const parsed = JSON.parse(result) as Record<string, unknown>;
|
|
86
|
+
const storage = parsed['storage'] as Record<string, unknown>;
|
|
87
|
+
const tables = storage['tables'] as Record<string, unknown>;
|
|
88
|
+
const user = tables['user'] as Record<string, unknown>;
|
|
89
|
+
const columns = user['columns'] as Record<string, unknown>;
|
|
90
|
+
const createdAt = columns['created_at'] as Record<string, unknown>;
|
|
91
|
+
const updatedAt = columns['updated_at'] as Record<string, unknown>;
|
|
92
|
+
expect(createdAt['nullable']).toBe(false);
|
|
93
|
+
expect(updatedAt['nullable']).toBe(true);
|
|
94
|
+
});
|
|
95
|
+
|
|
58
96
|
it('omits empty arrays and objects except required ones', () => {
|
|
59
97
|
const ir = createContractIR();
|
|
60
98
|
|
|
@@ -117,7 +155,7 @@ describe('canonicalization', () => {
|
|
|
117
155
|
expect(result1).not.toBe(result2);
|
|
118
156
|
});
|
|
119
157
|
|
|
120
|
-
it('sorts
|
|
158
|
+
it('sorts indexes by canonical name', () => {
|
|
121
159
|
const ir = createContractIR({
|
|
122
160
|
storage: {
|
|
123
161
|
tables: {
|
|
@@ -144,6 +182,60 @@ describe('canonicalization', () => {
|
|
|
144
182
|
expect(indexNames).toEqual(['user_email_idx', 'user_name_idx']);
|
|
145
183
|
});
|
|
146
184
|
|
|
185
|
+
it('sorts uniques by canonical name', () => {
|
|
186
|
+
const ir = createContractIR({
|
|
187
|
+
storage: {
|
|
188
|
+
tables: {
|
|
189
|
+
user: {
|
|
190
|
+
columns: {
|
|
191
|
+
id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false },
|
|
192
|
+
email: { codecId: 'pg/text@1', nativeType: 'text', nullable: false },
|
|
193
|
+
username: { codecId: 'pg/text@1', nativeType: 'text', nullable: false },
|
|
194
|
+
},
|
|
195
|
+
uniques: [
|
|
196
|
+
{ columns: ['username'], name: 'user_username_key' },
|
|
197
|
+
{ columns: ['email'], name: 'user_email_key' },
|
|
198
|
+
],
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const result = canonicalizeContract(ir);
|
|
205
|
+
const parsed = JSON.parse(result) as Record<string, unknown>;
|
|
206
|
+
const storage = parsed['storage'] as Record<string, unknown>;
|
|
207
|
+
const tables = storage['tables'] as Record<string, unknown>;
|
|
208
|
+
const user = tables['user'] as Record<string, unknown>;
|
|
209
|
+
const uniques = user['uniques'] as Array<{ name: string }>;
|
|
210
|
+
const uniqueNames = uniques.map((u) => u.name);
|
|
211
|
+
expect(uniqueNames).toEqual(['user_email_key', 'user_username_key']);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('preserves column order in composite unique constraints', () => {
|
|
215
|
+
const ir = createContractIR({
|
|
216
|
+
storage: {
|
|
217
|
+
tables: {
|
|
218
|
+
user: {
|
|
219
|
+
columns: {
|
|
220
|
+
id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false },
|
|
221
|
+
first_name: { codecId: 'pg/text@1', nativeType: 'text', nullable: false },
|
|
222
|
+
last_name: { codecId: 'pg/text@1', nativeType: 'text', nullable: false },
|
|
223
|
+
},
|
|
224
|
+
uniques: [{ columns: ['last_name', 'first_name'], name: 'user_name_key' }],
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
const result = canonicalizeContract(ir);
|
|
231
|
+
const parsed = JSON.parse(result) as Record<string, unknown>;
|
|
232
|
+
const storage = parsed['storage'] as Record<string, unknown>;
|
|
233
|
+
const tables = storage['tables'] as Record<string, unknown>;
|
|
234
|
+
const user = tables['user'] as Record<string, unknown>;
|
|
235
|
+
const uniques = user['uniques'] as Array<{ columns: string[] }>;
|
|
236
|
+
expect(uniques[0]!.columns).toEqual(['last_name', 'first_name']);
|
|
237
|
+
});
|
|
238
|
+
|
|
147
239
|
it('sorts nested object keys lexicographically', () => {
|
|
148
240
|
const ir = createContractIR({
|
|
149
241
|
storage: {
|
|
@@ -128,7 +128,7 @@ describe('emitter integration', () => {
|
|
|
128
128
|
|
|
129
129
|
const result = await emit(ir, options, mockSqlHook);
|
|
130
130
|
|
|
131
|
-
expect(result.
|
|
131
|
+
expect(result.storageHash).toMatch(/^sha256:[a-f0-9]{64}$/);
|
|
132
132
|
expect(result.contractDts).toContain('export type Contract');
|
|
133
133
|
expect(result.contractDts).toContain('CodecTypes');
|
|
134
134
|
expect(result.contractDts).toContain('LaneCodecTypes');
|
|
@@ -138,7 +138,7 @@ describe('emitter integration', () => {
|
|
|
138
138
|
schemaVersion: '1',
|
|
139
139
|
targetFamily: 'sql',
|
|
140
140
|
target: 'postgres',
|
|
141
|
-
|
|
141
|
+
storageHash: result.storageHash,
|
|
142
142
|
storage: {
|
|
143
143
|
tables: {
|
|
144
144
|
user: expect.anything(),
|
|
@@ -197,78 +197,82 @@ describe('emitter integration', () => {
|
|
|
197
197
|
const result1 = await emit(ir, options, mockSqlHook);
|
|
198
198
|
const result2 = await emit(ir, options, mockSqlHook);
|
|
199
199
|
|
|
200
|
-
expect(result1.
|
|
200
|
+
expect(result1.storageHash).toBe(result2.storageHash);
|
|
201
201
|
expect(result1.contractDts).toBe(result2.contractDts);
|
|
202
202
|
expect(result1.contractJson).toBe(result2.contractJson);
|
|
203
203
|
});
|
|
204
204
|
|
|
205
|
-
it(
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
205
|
+
it(
|
|
206
|
+
'round-trip: IR → JSON → parse JSON → compare',
|
|
207
|
+
async () => {
|
|
208
|
+
const ir = createContractIR({
|
|
209
|
+
models: {
|
|
210
|
+
User: {
|
|
211
|
+
storage: { table: 'user' },
|
|
212
|
+
fields: {
|
|
213
|
+
id: { column: 'id' },
|
|
214
|
+
email: { column: 'email' },
|
|
215
|
+
},
|
|
216
|
+
relations: {},
|
|
213
217
|
},
|
|
214
|
-
relations: {},
|
|
215
218
|
},
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
219
|
+
storage: {
|
|
220
|
+
tables: {
|
|
221
|
+
user: {
|
|
222
|
+
columns: {
|
|
223
|
+
id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false },
|
|
224
|
+
email: { codecId: 'pg/text@1', nativeType: 'text', nullable: false },
|
|
225
|
+
},
|
|
226
|
+
primaryKey: { columns: ['id'] },
|
|
227
|
+
uniques: [],
|
|
228
|
+
indexes: [],
|
|
229
|
+
foreignKeys: [],
|
|
223
230
|
},
|
|
224
|
-
primaryKey: { columns: ['id'] },
|
|
225
|
-
uniques: [],
|
|
226
|
-
indexes: [],
|
|
227
|
-
foreignKeys: [],
|
|
228
231
|
},
|
|
229
232
|
},
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
233
|
+
extensionPacks: {
|
|
234
|
+
postgres: {
|
|
235
|
+
version: '0.0.1',
|
|
236
|
+
},
|
|
237
|
+
pg: {},
|
|
234
238
|
},
|
|
235
|
-
|
|
236
|
-
},
|
|
237
|
-
});
|
|
239
|
+
});
|
|
238
240
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
241
|
+
// Create minimal test data (emitter tests don't load packs)
|
|
242
|
+
const operationRegistry = createOperationRegistry();
|
|
243
|
+
const codecTypeImports: TypesImportSpec[] = [];
|
|
244
|
+
const operationTypeImports: TypesImportSpec[] = [];
|
|
245
|
+
const extensionIds = ['postgres', 'pg'];
|
|
246
|
+
const options: EmitOptions = {
|
|
247
|
+
outputDir: '',
|
|
248
|
+
operationRegistry,
|
|
249
|
+
codecTypeImports,
|
|
250
|
+
operationTypeImports,
|
|
251
|
+
extensionIds,
|
|
252
|
+
};
|
|
251
253
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
+
const result1 = await emit(ir, options, mockSqlHook);
|
|
255
|
+
const contractJson1 = JSON.parse(result1.contractJson) as Record<string, unknown>;
|
|
254
256
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
257
|
+
const ir2 = createContractIR({
|
|
258
|
+
schemaVersion: contractJson1['schemaVersion'] as string,
|
|
259
|
+
targetFamily: contractJson1['targetFamily'] as string,
|
|
260
|
+
target: contractJson1['target'] as string,
|
|
261
|
+
models: contractJson1['models'] as Record<string, unknown>,
|
|
262
|
+
relations: (contractJson1['relations'] as Record<string, unknown>) || {},
|
|
263
|
+
storage: contractJson1['storage'] as Record<string, unknown>,
|
|
264
|
+
extensionPacks: contractJson1['extensionPacks'] as Record<string, unknown>,
|
|
265
|
+
capabilities:
|
|
266
|
+
(contractJson1['capabilities'] as Record<string, Record<string, boolean>>) || {},
|
|
267
|
+
meta: (contractJson1['meta'] as Record<string, unknown>) || {},
|
|
268
|
+
sources: (contractJson1['sources'] as Record<string, unknown>) || {},
|
|
269
|
+
});
|
|
268
270
|
|
|
269
|
-
|
|
271
|
+
const result2 = await emit(ir2, options, mockSqlHook);
|
|
270
272
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
273
|
+
expect(result1.contractJson).toBe(result2.contractJson);
|
|
274
|
+
expect(result1.storageHash).toBe(result2.storageHash);
|
|
275
|
+
},
|
|
276
|
+
timeouts.typeScriptCompilation,
|
|
277
|
+
);
|
|
274
278
|
});
|
|
@@ -133,7 +133,7 @@ describe('emitter round-trip', () => {
|
|
|
133
133
|
const result2 = await emit(ir2, options, mockSqlHook);
|
|
134
134
|
|
|
135
135
|
expect(result1.contractJson).toBe(result2.contractJson);
|
|
136
|
-
expect(result1.
|
|
136
|
+
expect(result1.storageHash).toBe(result2.storageHash);
|
|
137
137
|
},
|
|
138
138
|
timeouts.typeScriptCompilation,
|
|
139
139
|
);
|
|
@@ -230,7 +230,7 @@ describe('emitter round-trip', () => {
|
|
|
230
230
|
const result2 = await emit(ir2, options, mockSqlHook);
|
|
231
231
|
|
|
232
232
|
expect(result1.contractJson).toBe(result2.contractJson);
|
|
233
|
-
expect(result1.
|
|
233
|
+
expect(result1.storageHash).toBe(result2.storageHash);
|
|
234
234
|
});
|
|
235
235
|
|
|
236
236
|
it('round-trip with nullable fields', async () => {
|
|
@@ -289,7 +289,7 @@ describe('emitter round-trip', () => {
|
|
|
289
289
|
const result2 = await emit(ir2, options, mockSqlHook);
|
|
290
290
|
|
|
291
291
|
expect(result1.contractJson).toBe(result2.contractJson);
|
|
292
|
-
expect(result1.
|
|
292
|
+
expect(result1.storageHash).toBe(result2.storageHash);
|
|
293
293
|
|
|
294
294
|
const parsed2 = JSON.parse(result2.contractJson) as Record<string, unknown>;
|
|
295
295
|
const storage = parsed2['storage'] as Record<string, unknown>;
|
|
@@ -364,7 +364,7 @@ describe('emitter round-trip', () => {
|
|
|
364
364
|
const result2 = await emit(ir2, options, mockSqlHook);
|
|
365
365
|
|
|
366
366
|
expect(result1.contractJson).toBe(result2.contractJson);
|
|
367
|
-
expect(result1.
|
|
367
|
+
expect(result1.storageHash).toBe(result2.storageHash);
|
|
368
368
|
expect(result1.profileHash).toBe(result2.profileHash);
|
|
369
369
|
});
|
|
370
370
|
});
|
package/test/emitter.test.ts
CHANGED
|
@@ -46,11 +46,12 @@ const mockSqlHook: TargetFamilyHook = {
|
|
|
46
46
|
throw new Error(`Expected targetFamily "sql", got "${ir.targetFamily}"`);
|
|
47
47
|
}
|
|
48
48
|
},
|
|
49
|
-
generateContractTypes: (ir: ContractIR, _codecTypeImports, _operationTypeImports) => {
|
|
49
|
+
generateContractTypes: (ir: ContractIR, _codecTypeImports, _operationTypeImports, _hashes) => {
|
|
50
50
|
// Access ir properties to satisfy lint rules, but we don't use them in the mock
|
|
51
51
|
void ir;
|
|
52
52
|
void _codecTypeImports;
|
|
53
53
|
void _operationTypeImports;
|
|
54
|
+
void _hashes;
|
|
54
55
|
return `// Generated contract types
|
|
55
56
|
export type CodecTypes = Record<string, never>;
|
|
56
57
|
export type LaneCodecTypes = CodecTypes;
|
|
@@ -110,7 +111,7 @@ describe('emitter', () => {
|
|
|
110
111
|
};
|
|
111
112
|
|
|
112
113
|
const result = await emit(ir, options, mockSqlHook);
|
|
113
|
-
expect(result.
|
|
114
|
+
expect(result.storageHash).toMatch(/^sha256:[a-f0-9]{64}$/);
|
|
114
115
|
expect(result.contractDts).toContain('export type Contract');
|
|
115
116
|
expect(result.contractDts).toContain('CodecTypes');
|
|
116
117
|
|
|
@@ -624,9 +625,10 @@ describe('emitter', () => {
|
|
|
624
625
|
throw new Error(`Expected targetFamily "sql", got "${ir.targetFamily}"`);
|
|
625
626
|
}
|
|
626
627
|
},
|
|
627
|
-
generateContractTypes: (_ir, _codecTypeImports, _operationTypeImports) => {
|
|
628
|
+
generateContractTypes: (_ir, _codecTypeImports, _operationTypeImports, _hashes) => {
|
|
628
629
|
void _codecTypeImports;
|
|
629
630
|
void _operationTypeImports;
|
|
631
|
+
void _hashes;
|
|
630
632
|
return `// Generated contract types
|
|
631
633
|
export type CodecTypes = Record<string, never>;
|
|
632
634
|
export type LaneCodecTypes = CodecTypes;
|
|
@@ -662,7 +664,7 @@ export type Contract = unknown;
|
|
|
662
664
|
id: 'sql',
|
|
663
665
|
validateTypes: () => {},
|
|
664
666
|
validateStructure: () => {},
|
|
665
|
-
generateContractTypes: (_ir, _codecTypeImports, _operationTypeImports, options) => {
|
|
667
|
+
generateContractTypes: (_ir, _codecTypeImports, _operationTypeImports, _hashes, options) => {
|
|
666
668
|
receivedOptions = options;
|
|
667
669
|
return `// Generated contract types
|
|
668
670
|
export type CodecTypes = Record<string, never>;
|
package/test/factories.test.ts
CHANGED
|
@@ -7,13 +7,13 @@ describe('emitter factories', () => {
|
|
|
7
7
|
const header = irHeader({
|
|
8
8
|
target: 'postgres',
|
|
9
9
|
targetFamily: 'sql',
|
|
10
|
-
|
|
10
|
+
storageHash: 'sha256:abc123',
|
|
11
11
|
});
|
|
12
12
|
expect(header).toEqual({
|
|
13
13
|
schemaVersion: '1',
|
|
14
14
|
target: 'postgres',
|
|
15
15
|
targetFamily: 'sql',
|
|
16
|
-
|
|
16
|
+
storageHash: 'sha256:abc123',
|
|
17
17
|
});
|
|
18
18
|
});
|
|
19
19
|
|
|
@@ -21,14 +21,14 @@ describe('emitter factories', () => {
|
|
|
21
21
|
const header = irHeader({
|
|
22
22
|
target: 'postgres',
|
|
23
23
|
targetFamily: 'sql',
|
|
24
|
-
|
|
24
|
+
storageHash: 'sha256:abc123',
|
|
25
25
|
profileHash: 'sha256:def456',
|
|
26
26
|
});
|
|
27
27
|
expect(header).toEqual({
|
|
28
28
|
schemaVersion: '1',
|
|
29
29
|
target: 'postgres',
|
|
30
30
|
targetFamily: 'sql',
|
|
31
|
-
|
|
31
|
+
storageHash: 'sha256:abc123',
|
|
32
32
|
profileHash: 'sha256:def456',
|
|
33
33
|
});
|
|
34
34
|
});
|
|
@@ -37,7 +37,7 @@ describe('emitter factories', () => {
|
|
|
37
37
|
const header = irHeader({
|
|
38
38
|
target: 'mongodb',
|
|
39
39
|
targetFamily: 'document',
|
|
40
|
-
|
|
40
|
+
storageHash: 'sha256:xyz789',
|
|
41
41
|
});
|
|
42
42
|
expect(header.targetFamily).toBe('document');
|
|
43
43
|
expect(header.target).toBe('mongodb');
|
|
@@ -149,7 +149,7 @@ describe('emitter factories', () => {
|
|
|
149
149
|
const header = irHeader({
|
|
150
150
|
target: 'postgres',
|
|
151
151
|
targetFamily: 'sql',
|
|
152
|
-
|
|
152
|
+
storageHash: 'sha256:abc123',
|
|
153
153
|
});
|
|
154
154
|
const meta = irMeta({
|
|
155
155
|
capabilities: {
|
|
@@ -171,7 +171,7 @@ describe('emitter factories', () => {
|
|
|
171
171
|
expect(ir.schemaVersion).toBe('1');
|
|
172
172
|
expect(ir.target).toBe('postgres');
|
|
173
173
|
expect(ir.targetFamily).toBe('sql');
|
|
174
|
-
// Note:
|
|
174
|
+
// Note: storageHash is not part of ContractIR (it's computed by emitter)
|
|
175
175
|
expect(ir.storage).toEqual(storage);
|
|
176
176
|
expect(ir.models).toEqual(models);
|
|
177
177
|
expect(ir.relations).toEqual(relations);
|
|
@@ -187,7 +187,7 @@ describe('emitter factories', () => {
|
|
|
187
187
|
const header = irHeader({
|
|
188
188
|
target: 'postgres',
|
|
189
189
|
targetFamily: 'sql',
|
|
190
|
-
|
|
190
|
+
storageHash: 'sha256:abc123',
|
|
191
191
|
profileHash: 'sha256:def456',
|
|
192
192
|
});
|
|
193
193
|
const meta = irMeta({});
|
|
@@ -213,7 +213,7 @@ describe('emitter factories', () => {
|
|
|
213
213
|
const header = irHeader({
|
|
214
214
|
target: 'postgres',
|
|
215
215
|
targetFamily: 'sql',
|
|
216
|
-
|
|
216
|
+
storageHash: 'sha256:abc123',
|
|
217
217
|
});
|
|
218
218
|
const meta = irMeta({
|
|
219
219
|
capabilities: {
|
|
@@ -251,7 +251,7 @@ describe('emitter factories', () => {
|
|
|
251
251
|
const header = irHeader({
|
|
252
252
|
target: 'mongodb',
|
|
253
253
|
targetFamily: 'document',
|
|
254
|
-
|
|
254
|
+
storageHash: 'sha256:xyz789',
|
|
255
255
|
});
|
|
256
256
|
const meta = irMeta({});
|
|
257
257
|
const storage = { document: { collections: {} } };
|
package/test/hashing.test.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { computeProfileHash, computeStorageHash } from '@prisma-next/core-control-plane/emission';
|
|
2
2
|
import { describe, expect, it } from 'vitest';
|
|
3
3
|
|
|
4
4
|
describe('hashing', () => {
|
|
5
|
-
it('computes
|
|
5
|
+
it('computes storage hash', () => {
|
|
6
6
|
const contract = {
|
|
7
7
|
schemaVersion: '1',
|
|
8
8
|
targetFamily: 'sql',
|
|
@@ -16,7 +16,7 @@ describe('hashing', () => {
|
|
|
16
16
|
sources: {},
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
-
const hash =
|
|
19
|
+
const hash = computeStorageHash(contract);
|
|
20
20
|
expect(hash).toMatch(/^sha256:[a-f0-9]{64}$/);
|
|
21
21
|
});
|
|
22
22
|
|
|
@@ -52,8 +52,8 @@ describe('hashing', () => {
|
|
|
52
52
|
sources: {},
|
|
53
53
|
};
|
|
54
54
|
|
|
55
|
-
const hash1 =
|
|
56
|
-
const hash2 =
|
|
55
|
+
const hash1 = computeStorageHash(contract);
|
|
56
|
+
const hash2 = computeStorageHash(contract);
|
|
57
57
|
expect(hash1).toBe(hash2);
|
|
58
58
|
});
|
|
59
59
|
});
|
package/test/utils.ts
CHANGED
|
@@ -9,12 +9,12 @@ import { type ContractIR, irHeader, irMeta } from '@prisma-next/contract/ir';
|
|
|
9
9
|
* from the result (useful for testing validation of missing fields).
|
|
10
10
|
*/
|
|
11
11
|
export function createContractIR(
|
|
12
|
-
overrides: Partial<ContractIR> & {
|
|
12
|
+
overrides: Partial<ContractIR> & { storageHash?: string; profileHash?: string } = {},
|
|
13
13
|
): ContractIR {
|
|
14
14
|
// Check if fields are explicitly undefined (not just missing)
|
|
15
15
|
const hasTarget = 'target' in overrides;
|
|
16
16
|
const hasTargetFamily = 'targetFamily' in overrides;
|
|
17
|
-
const
|
|
17
|
+
const hasStorageHash = 'storageHash' in overrides;
|
|
18
18
|
const hasSchemaVersion = 'schemaVersion' in overrides;
|
|
19
19
|
const hasModels = 'models' in overrides;
|
|
20
20
|
const hasRelations = 'relations' in overrides;
|
|
@@ -28,7 +28,7 @@ export function createContractIR(
|
|
|
28
28
|
const headerOpts: {
|
|
29
29
|
target?: string;
|
|
30
30
|
targetFamily?: string;
|
|
31
|
-
|
|
31
|
+
storageHash?: string;
|
|
32
32
|
profileHash?: string;
|
|
33
33
|
} = {};
|
|
34
34
|
|
|
@@ -44,10 +44,10 @@ export function createContractIR(
|
|
|
44
44
|
headerOpts.targetFamily = 'sql';
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
if (
|
|
48
|
-
headerOpts.
|
|
49
|
-
} else if (!
|
|
50
|
-
headerOpts.
|
|
47
|
+
if (hasStorageHash && overrides.storageHash !== undefined) {
|
|
48
|
+
headerOpts.storageHash = overrides.storageHash;
|
|
49
|
+
} else if (!hasStorageHash) {
|
|
50
|
+
headerOpts.storageHash = 'sha256:test';
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
// profileHash is not part of ContractIR, but we can accept it for header creation
|
|
@@ -59,7 +59,7 @@ export function createContractIR(
|
|
|
59
59
|
headerOpts as {
|
|
60
60
|
target: string;
|
|
61
61
|
targetFamily: string;
|
|
62
|
-
|
|
62
|
+
storageHash: string;
|
|
63
63
|
profileHash?: string;
|
|
64
64
|
},
|
|
65
65
|
);
|
|
@@ -99,7 +99,7 @@ export function createContractIR(
|
|
|
99
99
|
|
|
100
100
|
const meta = irMeta(Object.keys(metaOpts).length > 0 ? metaOpts : undefined);
|
|
101
101
|
|
|
102
|
-
// Build result by constructing the object directly (ContractIR doesn't include
|
|
102
|
+
// Build result by constructing the object directly (ContractIR doesn't include storageHash/profileHash)
|
|
103
103
|
// When fields are explicitly undefined, include them as undefined (tests use type assertions to bypass TS)
|
|
104
104
|
const result = {
|
|
105
105
|
schemaVersion:
|
package/dist/exports/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/exports/index.ts"],"sourcesContent":["// Re-export types from @prisma-next/contract for backward compatibility\nexport type {\n TargetFamilyHook,\n TypesImportSpec,\n ValidationContext,\n} from '@prisma-next/contract/types';\nexport type { EmitOptions, EmitResult } from '@prisma-next/core-control-plane/emission';\n// Re-export emit function and types from core-control-plane\nexport { emit } from '@prisma-next/core-control-plane/emission';\n"],"mappings":";AAQA,SAAS,YAAY;","names":[]}
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
export type { TargetFamilyHook, TypesImportSpec, ValidationContext, } from '@prisma-next/contract/types';
|
|
2
|
-
export type { EmitOptions, EmitResult } from '@prisma-next/core-control-plane/emission';
|
|
3
|
-
export { emit } from '@prisma-next/core-control-plane/emission';
|
|
4
|
-
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/exports/index.ts"],"names":[],"mappings":"AACA,YAAY,EACV,gBAAgB,EAChB,eAAe,EACf,iBAAiB,GAClB,MAAM,6BAA6B,CAAC;AACrC,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,0CAA0C,CAAC;AAExF,OAAO,EAAE,IAAI,EAAE,MAAM,0CAA0C,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"target-family.d.ts","sourceRoot":"","sources":["../../src/target-family.ts"],"names":[],"mappings":"AAEA,YAAY,EACV,gBAAgB,EAChB,eAAe,EACf,iBAAiB,GAClB,MAAM,6BAA6B,CAAC"}
|
package/dist/test/utils.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../test/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,UAAU,EAAoB,MAAM,0BAA0B,CAAC;AAE7E;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,SAAS,GAAE,OAAO,CAAC,UAAU,CAAC,GAAG;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAO,GAChF,UAAU,CAyJZ"}
|
package/dist/test/utils.js
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
// test/utils.ts
|
|
2
|
-
import { irHeader, irMeta } from "@prisma-next/contract/ir";
|
|
3
|
-
function createContractIR(overrides = {}) {
|
|
4
|
-
const hasTarget = "target" in overrides;
|
|
5
|
-
const hasTargetFamily = "targetFamily" in overrides;
|
|
6
|
-
const hasCoreHash = "coreHash" in overrides;
|
|
7
|
-
const hasSchemaVersion = "schemaVersion" in overrides;
|
|
8
|
-
const hasModels = "models" in overrides;
|
|
9
|
-
const hasRelations = "relations" in overrides;
|
|
10
|
-
const hasStorage = "storage" in overrides;
|
|
11
|
-
const hasCapabilities = "capabilities" in overrides;
|
|
12
|
-
const hasExtensionPacks = "extensionPacks" in overrides;
|
|
13
|
-
const hasMeta = "meta" in overrides;
|
|
14
|
-
const hasSources = "sources" in overrides;
|
|
15
|
-
const headerOpts = {};
|
|
16
|
-
if (hasTarget && overrides.target !== void 0) {
|
|
17
|
-
headerOpts.target = overrides.target;
|
|
18
|
-
} else if (!hasTarget) {
|
|
19
|
-
headerOpts.target = "postgres";
|
|
20
|
-
}
|
|
21
|
-
if (hasTargetFamily && overrides.targetFamily !== void 0) {
|
|
22
|
-
headerOpts.targetFamily = overrides.targetFamily;
|
|
23
|
-
} else if (!hasTargetFamily) {
|
|
24
|
-
headerOpts.targetFamily = "sql";
|
|
25
|
-
}
|
|
26
|
-
if (hasCoreHash && overrides.coreHash !== void 0) {
|
|
27
|
-
headerOpts.coreHash = overrides.coreHash;
|
|
28
|
-
} else if (!hasCoreHash) {
|
|
29
|
-
headerOpts.coreHash = "sha256:test";
|
|
30
|
-
}
|
|
31
|
-
if (overrides.profileHash !== void 0) {
|
|
32
|
-
headerOpts.profileHash = overrides.profileHash;
|
|
33
|
-
}
|
|
34
|
-
const header = irHeader(
|
|
35
|
-
headerOpts
|
|
36
|
-
);
|
|
37
|
-
const metaOpts = {};
|
|
38
|
-
if (hasCapabilities && overrides.capabilities !== void 0) {
|
|
39
|
-
metaOpts.capabilities = overrides.capabilities;
|
|
40
|
-
} else if (!hasCapabilities) {
|
|
41
|
-
metaOpts.capabilities = {};
|
|
42
|
-
}
|
|
43
|
-
if (hasExtensionPacks && overrides.extensionPacks !== void 0) {
|
|
44
|
-
metaOpts.extensionPacks = overrides.extensionPacks;
|
|
45
|
-
} else if (!hasExtensionPacks) {
|
|
46
|
-
metaOpts.extensionPacks = {};
|
|
47
|
-
}
|
|
48
|
-
if (hasMeta && overrides.meta !== void 0) {
|
|
49
|
-
metaOpts.meta = overrides.meta;
|
|
50
|
-
} else if (!hasMeta) {
|
|
51
|
-
metaOpts.meta = {};
|
|
52
|
-
}
|
|
53
|
-
if (hasSources && overrides.sources !== void 0) {
|
|
54
|
-
metaOpts.sources = overrides.sources;
|
|
55
|
-
} else if (!hasSources) {
|
|
56
|
-
metaOpts.sources = {};
|
|
57
|
-
}
|
|
58
|
-
const meta = irMeta(Object.keys(metaOpts).length > 0 ? metaOpts : void 0);
|
|
59
|
-
const result = {
|
|
60
|
-
schemaVersion: hasSchemaVersion && overrides.schemaVersion !== void 0 ? overrides.schemaVersion : hasSchemaVersion && overrides.schemaVersion === void 0 ? void 0 : header.schemaVersion,
|
|
61
|
-
target: header.target,
|
|
62
|
-
targetFamily: header.targetFamily,
|
|
63
|
-
// Only include meta fields if they're not explicitly undefined
|
|
64
|
-
capabilities: hasCapabilities && overrides.capabilities === void 0 ? void 0 : !hasCapabilities || overrides.capabilities !== void 0 ? meta.capabilities : {},
|
|
65
|
-
extensionPacks: hasExtensionPacks && overrides.extensionPacks === void 0 ? void 0 : !hasExtensionPacks || overrides.extensionPacks !== void 0 ? meta.extensionPacks : {},
|
|
66
|
-
meta: hasMeta && overrides.meta === void 0 ? void 0 : !hasMeta || overrides.meta !== void 0 ? meta.meta : {},
|
|
67
|
-
sources: hasSources && overrides.sources === void 0 ? void 0 : !hasSources || overrides.sources !== void 0 ? meta.sources : {},
|
|
68
|
-
// Only include family sections if they're not explicitly undefined
|
|
69
|
-
storage: hasStorage && overrides.storage === void 0 ? void 0 : hasStorage && overrides.storage !== void 0 ? overrides.storage : !hasStorage ? { tables: {} } : {},
|
|
70
|
-
models: hasModels && overrides.models === void 0 ? void 0 : hasModels && overrides.models !== void 0 ? overrides.models : !hasModels ? {} : {},
|
|
71
|
-
relations: hasRelations && overrides.relations === void 0 ? void 0 : hasRelations && overrides.relations !== void 0 ? overrides.relations : !hasRelations ? {} : {}
|
|
72
|
-
};
|
|
73
|
-
return result;
|
|
74
|
-
}
|
|
75
|
-
export {
|
|
76
|
-
createContractIR
|
|
77
|
-
};
|
|
78
|
-
//# sourceMappingURL=utils.js.map
|
package/dist/test/utils.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../test/utils.ts"],"sourcesContent":["import { type ContractIR, irHeader, irMeta } from '@prisma-next/contract/ir';\n\n/**\n * Factory function for creating ContractIR objects in tests.\n * Provides sensible defaults and allows overriding specific fields.\n * Uses the emitter factories internally for consistency.\n *\n * If a field is explicitly set to `undefined` in overrides, it will be omitted\n * from the result (useful for testing validation of missing fields).\n */\nexport function createContractIR(\n overrides: Partial<ContractIR> & { coreHash?: string; profileHash?: string } = {},\n): ContractIR {\n // Check if fields are explicitly undefined (not just missing)\n const hasTarget = 'target' in overrides;\n const hasTargetFamily = 'targetFamily' in overrides;\n const hasCoreHash = 'coreHash' in overrides;\n const hasSchemaVersion = 'schemaVersion' in overrides;\n const hasModels = 'models' in overrides;\n const hasRelations = 'relations' in overrides;\n const hasStorage = 'storage' in overrides;\n const hasCapabilities = 'capabilities' in overrides;\n const hasExtensionPacks = 'extensionPacks' in overrides;\n const hasMeta = 'meta' in overrides;\n const hasSources = 'sources' in overrides;\n\n // Build header, omitting fields that are explicitly undefined\n const headerOpts: {\n target?: string;\n targetFamily?: string;\n coreHash?: string;\n profileHash?: string;\n } = {};\n\n if (hasTarget && overrides.target !== undefined) {\n headerOpts.target = overrides.target;\n } else if (!hasTarget) {\n headerOpts.target = 'postgres';\n }\n\n if (hasTargetFamily && overrides.targetFamily !== undefined) {\n headerOpts.targetFamily = overrides.targetFamily;\n } else if (!hasTargetFamily) {\n headerOpts.targetFamily = 'sql';\n }\n\n if (hasCoreHash && overrides.coreHash !== undefined) {\n headerOpts.coreHash = overrides.coreHash;\n } else if (!hasCoreHash) {\n headerOpts.coreHash = 'sha256:test';\n }\n\n // profileHash is not part of ContractIR, but we can accept it for header creation\n if (overrides.profileHash !== undefined) {\n headerOpts.profileHash = overrides.profileHash;\n }\n\n const header = irHeader(\n headerOpts as {\n target: string;\n targetFamily: string;\n coreHash: string;\n profileHash?: string;\n },\n );\n\n // Build meta, handling explicitly undefined fields\n // If a field is explicitly undefined, we'll omit it from the result later\n const metaOpts: {\n capabilities?: Record<string, Record<string, boolean>>;\n extensionPacks?: Record<string, unknown>;\n meta?: Record<string, unknown>;\n sources?: Record<string, unknown>;\n } = {};\n\n if (hasCapabilities && overrides.capabilities !== undefined) {\n metaOpts.capabilities = overrides.capabilities;\n } else if (!hasCapabilities) {\n metaOpts.capabilities = {};\n }\n\n if (hasExtensionPacks && overrides.extensionPacks !== undefined) {\n metaOpts.extensionPacks = overrides.extensionPacks;\n } else if (!hasExtensionPacks) {\n metaOpts.extensionPacks = {};\n }\n\n if (hasMeta && overrides.meta !== undefined) {\n metaOpts.meta = overrides.meta;\n } else if (!hasMeta) {\n metaOpts.meta = {};\n }\n\n if (hasSources && overrides.sources !== undefined) {\n metaOpts.sources = overrides.sources;\n } else if (!hasSources) {\n metaOpts.sources = {};\n }\n\n const meta = irMeta(Object.keys(metaOpts).length > 0 ? metaOpts : undefined);\n\n // Build result by constructing the object directly (ContractIR doesn't include coreHash/profileHash)\n // When fields are explicitly undefined, include them as undefined (tests use type assertions to bypass TS)\n const result = {\n schemaVersion:\n hasSchemaVersion && overrides.schemaVersion !== undefined\n ? overrides.schemaVersion\n : hasSchemaVersion && overrides.schemaVersion === undefined\n ? (undefined as unknown as string)\n : header.schemaVersion,\n target: header.target,\n targetFamily: header.targetFamily,\n // Only include meta fields if they're not explicitly undefined\n capabilities:\n hasCapabilities && overrides.capabilities === undefined\n ? (undefined as unknown as Record<string, Record<string, boolean>>)\n : !hasCapabilities || overrides.capabilities !== undefined\n ? meta.capabilities\n : ({} as Record<string, Record<string, boolean>>),\n extensionPacks:\n hasExtensionPacks && overrides.extensionPacks === undefined\n ? (undefined as unknown as Record<string, unknown>)\n : !hasExtensionPacks || overrides.extensionPacks !== undefined\n ? meta.extensionPacks\n : ({} as Record<string, unknown>),\n meta:\n hasMeta && overrides.meta === undefined\n ? (undefined as unknown as Record<string, unknown>)\n : !hasMeta || overrides.meta !== undefined\n ? meta.meta\n : ({} as Record<string, unknown>),\n sources:\n hasSources && overrides.sources === undefined\n ? (undefined as unknown as Record<string, unknown>)\n : !hasSources || overrides.sources !== undefined\n ? meta.sources\n : ({} as Record<string, unknown>),\n // Only include family sections if they're not explicitly undefined\n storage:\n hasStorage && overrides.storage === undefined\n ? (undefined as unknown as Record<string, unknown>)\n : hasStorage && overrides.storage !== undefined\n ? (overrides.storage as Record<string, unknown>)\n : !hasStorage\n ? ({ tables: {} } as Record<string, unknown>)\n : ({} as Record<string, unknown>),\n models:\n hasModels && overrides.models === undefined\n ? (undefined as unknown as Record<string, unknown>)\n : hasModels && overrides.models !== undefined\n ? (overrides.models as Record<string, unknown>)\n : !hasModels\n ? {}\n : ({} as Record<string, unknown>),\n relations:\n hasRelations && overrides.relations === undefined\n ? (undefined as unknown as Record<string, unknown>)\n : hasRelations && overrides.relations !== undefined\n ? (overrides.relations as Record<string, unknown>)\n : !hasRelations\n ? {}\n : ({} as Record<string, unknown>),\n } as ContractIR;\n\n return result;\n}\n"],"mappings":";AAAA,SAA0B,UAAU,cAAc;AAU3C,SAAS,iBACd,YAA+E,CAAC,GACpE;AAEZ,QAAM,YAAY,YAAY;AAC9B,QAAM,kBAAkB,kBAAkB;AAC1C,QAAM,cAAc,cAAc;AAClC,QAAM,mBAAmB,mBAAmB;AAC5C,QAAM,YAAY,YAAY;AAC9B,QAAM,eAAe,eAAe;AACpC,QAAM,aAAa,aAAa;AAChC,QAAM,kBAAkB,kBAAkB;AAC1C,QAAM,oBAAoB,oBAAoB;AAC9C,QAAM,UAAU,UAAU;AAC1B,QAAM,aAAa,aAAa;AAGhC,QAAM,aAKF,CAAC;AAEL,MAAI,aAAa,UAAU,WAAW,QAAW;AAC/C,eAAW,SAAS,UAAU;AAAA,EAChC,WAAW,CAAC,WAAW;AACrB,eAAW,SAAS;AAAA,EACtB;AAEA,MAAI,mBAAmB,UAAU,iBAAiB,QAAW;AAC3D,eAAW,eAAe,UAAU;AAAA,EACtC,WAAW,CAAC,iBAAiB;AAC3B,eAAW,eAAe;AAAA,EAC5B;AAEA,MAAI,eAAe,UAAU,aAAa,QAAW;AACnD,eAAW,WAAW,UAAU;AAAA,EAClC,WAAW,CAAC,aAAa;AACvB,eAAW,WAAW;AAAA,EACxB;AAGA,MAAI,UAAU,gBAAgB,QAAW;AACvC,eAAW,cAAc,UAAU;AAAA,EACrC;AAEA,QAAM,SAAS;AAAA,IACb;AAAA,EAMF;AAIA,QAAM,WAKF,CAAC;AAEL,MAAI,mBAAmB,UAAU,iBAAiB,QAAW;AAC3D,aAAS,eAAe,UAAU;AAAA,EACpC,WAAW,CAAC,iBAAiB;AAC3B,aAAS,eAAe,CAAC;AAAA,EAC3B;AAEA,MAAI,qBAAqB,UAAU,mBAAmB,QAAW;AAC/D,aAAS,iBAAiB,UAAU;AAAA,EACtC,WAAW,CAAC,mBAAmB;AAC7B,aAAS,iBAAiB,CAAC;AAAA,EAC7B;AAEA,MAAI,WAAW,UAAU,SAAS,QAAW;AAC3C,aAAS,OAAO,UAAU;AAAA,EAC5B,WAAW,CAAC,SAAS;AACnB,aAAS,OAAO,CAAC;AAAA,EACnB;AAEA,MAAI,cAAc,UAAU,YAAY,QAAW;AACjD,aAAS,UAAU,UAAU;AAAA,EAC/B,WAAW,CAAC,YAAY;AACtB,aAAS,UAAU,CAAC;AAAA,EACtB;AAEA,QAAM,OAAO,OAAO,OAAO,KAAK,QAAQ,EAAE,SAAS,IAAI,WAAW,MAAS;AAI3E,QAAM,SAAS;AAAA,IACb,eACE,oBAAoB,UAAU,kBAAkB,SAC5C,UAAU,gBACV,oBAAoB,UAAU,kBAAkB,SAC7C,SACD,OAAO;AAAA,IACf,QAAQ,OAAO;AAAA,IACf,cAAc,OAAO;AAAA;AAAA,IAErB,cACE,mBAAmB,UAAU,iBAAiB,SACzC,SACD,CAAC,mBAAmB,UAAU,iBAAiB,SAC7C,KAAK,eACJ,CAAC;AAAA,IACV,gBACE,qBAAqB,UAAU,mBAAmB,SAC7C,SACD,CAAC,qBAAqB,UAAU,mBAAmB,SACjD,KAAK,iBACJ,CAAC;AAAA,IACV,MACE,WAAW,UAAU,SAAS,SACzB,SACD,CAAC,WAAW,UAAU,SAAS,SAC7B,KAAK,OACJ,CAAC;AAAA,IACV,SACE,cAAc,UAAU,YAAY,SAC/B,SACD,CAAC,cAAc,UAAU,YAAY,SACnC,KAAK,UACJ,CAAC;AAAA;AAAA,IAEV,SACE,cAAc,UAAU,YAAY,SAC/B,SACD,cAAc,UAAU,YAAY,SACjC,UAAU,UACX,CAAC,aACE,EAAE,QAAQ,CAAC,EAAE,IACb,CAAC;AAAA,IACZ,QACE,aAAa,UAAU,WAAW,SAC7B,SACD,aAAa,UAAU,WAAW,SAC/B,UAAU,SACX,CAAC,YACC,CAAC,IACA,CAAC;AAAA,IACZ,WACE,gBAAgB,UAAU,cAAc,SACnC,SACD,gBAAgB,UAAU,cAAc,SACrC,UAAU,YACX,CAAC,eACC,CAAC,IACA,CAAC;AAAA,EACd;AAEA,SAAO;AACT;","names":[]}
|