@prisma-next/emitter 0.0.1
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 +191 -0
- package/dist/exports/index.d.ts +2 -0
- package/dist/exports/index.js +6 -0
- package/dist/exports/index.js.map +1 -0
- package/package.json +36 -0
package/README.md
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# @prisma-next/emitter
|
|
2
|
+
|
|
3
|
+
Contract emission engine that transforms authored data models into canonical JSON contracts and TypeScript type definitions.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
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
|
+
|
|
9
|
+
1. **`contract.json`** — Canonical JSON representation of the data contract with embedded `coreHash` and `profileHash`. Callers may add `_generated` metadata field to indicate it's a generated artifact (excluded from canonicalization/hashing).
|
|
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
|
+
|
|
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.
|
|
13
|
+
|
|
14
|
+
## Purpose
|
|
15
|
+
|
|
16
|
+
Provide a deterministic, verifiable representation of the application's data contract that downstream subsystems consume for planning, verification, and execution.
|
|
17
|
+
|
|
18
|
+
## Responsibilities
|
|
19
|
+
|
|
20
|
+
- **Parse**: Accept contract IR (Intermediate Representation) from authoring surfaces
|
|
21
|
+
- **Validate**: Core structure validation plus family-specific type and structure validation via hooks
|
|
22
|
+
- **Canonicalize**: Compute `coreHash` (schema meaning) and `profileHash` (capabilities/pins) from canonical JSON
|
|
23
|
+
- **Emit**: Generate `contract.json` and `contract.d.ts` with family-specific type generation
|
|
24
|
+
- **Manifest-Agnostic**: The emitter is completely manifest-agnostic. It receives pre-assembled `OperationRegistry`, `codecTypeImports`, `operationTypeImports`, and `extensionIds` from the CLI, not extension packs. Manifest parsing and assembly happens in family instances (e.g., `createSqlFamilyInstance` in `@prisma-next/family-sql`).
|
|
25
|
+
|
|
26
|
+
**Note**: The emitter does NOT normalize contracts. Normalization must happen in the contract builder when the contract is created. The emitter assumes contracts are already normalized (all required fields present, including `schemaVersion`, `models`, `relations`, `storage`, `extensions`, `capabilities`, `meta`, and `sources`). All fields can be empty objects/arrays, but they must be present.
|
|
27
|
+
|
|
28
|
+
**Non-goals:**
|
|
29
|
+
- Migration planning or execution
|
|
30
|
+
- Query compilation or execution
|
|
31
|
+
- Runtime capability discovery
|
|
32
|
+
- Policy enforcement
|
|
33
|
+
|
|
34
|
+
## Architecture
|
|
35
|
+
|
|
36
|
+
```mermaid
|
|
37
|
+
flowchart TD
|
|
38
|
+
subgraph "Authoring Surfaces"
|
|
39
|
+
PSL[PSL Parser]
|
|
40
|
+
TS[TS Builder]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
subgraph "Emitter Core"
|
|
44
|
+
IR[Contract IR]
|
|
45
|
+
VAL[Validate Core Structure]
|
|
46
|
+
HASH[Compute Hashes]
|
|
47
|
+
EMIT[Emit JSON + DTS]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
subgraph "Target Family Hooks"
|
|
51
|
+
SQL[SQL Hook]
|
|
52
|
+
DOC[Document Hook]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
subgraph "Extension Packs"
|
|
56
|
+
PACK1[Adapter Pack]
|
|
57
|
+
PACK2[Extension Pack]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
PSL --> IR
|
|
61
|
+
TS --> IR
|
|
62
|
+
IR --> VAL
|
|
63
|
+
VAL --> SQL
|
|
64
|
+
VAL --> DOC
|
|
65
|
+
SQL --> HASH
|
|
66
|
+
DOC --> HASH
|
|
67
|
+
PACK1 --> SQL
|
|
68
|
+
PACK2 --> DOC
|
|
69
|
+
HASH --> EMIT
|
|
70
|
+
EMIT --> JSON[contract.json]
|
|
71
|
+
EMIT --> DTS[contract.d.ts]
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Components
|
|
75
|
+
|
|
76
|
+
### Core Emitter (`emitter.ts`)
|
|
77
|
+
- Orchestrates validation, hashing, and type generation
|
|
78
|
+
- Returns contract JSON and TypeScript definitions as strings (no file I/O)
|
|
79
|
+
- Pure transformation function
|
|
80
|
+
- Accepts `targetFamily: TargetFamilyHook` as a required parameter (no global registry)
|
|
81
|
+
|
|
82
|
+
### Target Family Hook (`target-family.ts`)
|
|
83
|
+
- SPI interface (`TargetFamilyHook`) for extending emission with family-specific logic:
|
|
84
|
+
- `validateTypes`: Validate type IDs against referenced extensions (receives `ValidationContext` with `operationRegistry` and `extensionIds`)
|
|
85
|
+
- `validateStructure`: Family-specific structural validation
|
|
86
|
+
- `generateContractTypes`: Generate `contract.d.ts` content (receives separate `codecTypeImports` and `operationTypeImports` arrays)
|
|
87
|
+
- Authoring surfaces determine which target family SPI to use based on the contract's `targetFamily` field and pass it directly to `emit()`
|
|
88
|
+
- No global registry or auto-registration - dependencies are explicit and passed directly
|
|
89
|
+
- **Manifest-Agnostic**: Hooks receive pre-assembled context (operation registry, type imports, extension IDs), not extension packs. Manifest parsing and assembly happens in the CLI layer.
|
|
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
|
+
|
|
92
|
+
### Hashing (`hashing.ts`)
|
|
93
|
+
- `computeCoreHash`: SHA-256 of schema structure (models, storage, relations)
|
|
94
|
+
- `computeProfileHash`: SHA-256 of capabilities and adapter pins
|
|
95
|
+
|
|
96
|
+
### Canonicalization (`canonicalization.ts`)
|
|
97
|
+
- `canonicalizeContract`: Normalizes contract IR into stable JSON string for hashing
|
|
98
|
+
- Excludes `_generated` metadata field from canonicalization to ensure determinism
|
|
99
|
+
- Sorts object keys, omits default values, and orders top-level fields consistently
|
|
100
|
+
|
|
101
|
+
**Note**: Extension pack loading and manifest parsing are CLI-only responsibilities. The emitter does not export pack loading functions. Import `loadExtensionPacks` from `@prisma-next/cli` or use the CLI's `pack-loading.ts` module directly. Manifest types (`ExtensionPack`, `ExtensionPackManifest`, `OperationManifest`) are defined in `@prisma-next/control-plane/pack-manifest-types`.
|
|
102
|
+
|
|
103
|
+
**Note**: `TargetFamilyHook`, `ValidationContext`, and `TypesImportSpec` types are defined in `@prisma-next/contract/types` (shared plane) to allow both migration-plane (emitter) and shared-plane (control-plane) packages to import them without violating dependency rules. These types are re-exported from this package for backward compatibility.
|
|
104
|
+
|
|
105
|
+
## Dependencies
|
|
106
|
+
|
|
107
|
+
- **`arktype`**: Runtime type validation for manifests
|
|
108
|
+
|
|
109
|
+
## Package Location
|
|
110
|
+
|
|
111
|
+
This package is part of the **framework domain**, **tooling layer**, **migration plane**:
|
|
112
|
+
- **Domain**: framework (target-agnostic)
|
|
113
|
+
- **Layer**: tooling
|
|
114
|
+
- **Plane**: migration
|
|
115
|
+
- **Path**: `packages/framework/tooling/emitter`
|
|
116
|
+
|
|
117
|
+
## Related Subsystems
|
|
118
|
+
|
|
119
|
+
- **[Contract Emitter & Types](../../../../docs/architecture%20docs/subsystems/2.%20Contract%20Emitter%20&%20Types.md)**: Detailed subsystem specification
|
|
120
|
+
- **[Data Contract](../../../../docs/architecture%20docs/subsystems/1.%20Data%20Contract.md)**: Contract structure and hashing
|
|
121
|
+
|
|
122
|
+
## Related ADRs
|
|
123
|
+
|
|
124
|
+
- [ADR 004 - Core Hash vs Profile Hash](../../../../docs/architecture%20docs/adrs/ADR%20004%20-%20Core%20Hash%20vs%20Profile%20Hash.md)
|
|
125
|
+
- [ADR 006 - Dual Authoring Modes](../../../../docs/architecture%20docs/adrs/ADR%20006%20-%20Dual%20Authoring%20Modes.md)
|
|
126
|
+
- [ADR 007 - Types Only Emission](../../../../docs/architecture%20docs/adrs/ADR%20007%20-%20Types%20Only%20Emission.md)
|
|
127
|
+
- [ADR 010 - Canonicalization Rules](../../../../docs/architecture%20docs/adrs/ADR%20010%20-%20Canonicalization%20Rules.md)
|
|
128
|
+
- [ADR 097 - Tooling runs on canonical JSON only](../../../../docs/architecture%20docs/adrs/ADR%20097%20-%20Tooling%20runs%20on%20canonical%20JSON%20only.md)
|
|
129
|
+
|
|
130
|
+
## Usage
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
import { emit } from '@prisma-next/emitter';
|
|
134
|
+
import type { ContractIR, EmitOptions } from '@prisma-next/emitter';
|
|
135
|
+
import { createOperationRegistry } from '@prisma-next/operations';
|
|
136
|
+
|
|
137
|
+
// Determine target family SPI based on target family
|
|
138
|
+
import { sqlTargetFamilyHook } from '@prisma-next/sql-contract-emitter';
|
|
139
|
+
|
|
140
|
+
// Emit contract
|
|
141
|
+
const ir: ContractIR = {
|
|
142
|
+
schemaVersion: '1',
|
|
143
|
+
targetFamily: 'sql',
|
|
144
|
+
target: 'postgres',
|
|
145
|
+
// ... contract structure
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// Pass pre-assembled context to emit() (pack loading happens in CLI layer)
|
|
149
|
+
const result = await emit(ir, {
|
|
150
|
+
outputDir: './dist',
|
|
151
|
+
operationRegistry: createOperationRegistry(), // Pre-assembled from packs
|
|
152
|
+
codecTypeImports: [], // Extracted from packs (codec types)
|
|
153
|
+
operationTypeImports: [], // Extracted from packs (operation types)
|
|
154
|
+
extensionIds: ['postgres', 'pg'], // Extracted from packs
|
|
155
|
+
}, sqlTargetFamilyHook);
|
|
156
|
+
|
|
157
|
+
// result.contractJson: string (JSON) - canonical JSON without _generated metadata
|
|
158
|
+
// result.contractDts: string (TypeScript definitions) - includes warning header
|
|
159
|
+
// result.coreHash: string
|
|
160
|
+
// result.profileHash?: string
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**Note**: The emitter returns canonical JSON without `_generated` metadata. Callers (e.g., CLI) may add `_generated` metadata to the JSON before writing to disk. The `_generated` field is excluded from canonicalization/hashing to ensure determinism.
|
|
164
|
+
|
|
165
|
+
## Test Utilities
|
|
166
|
+
|
|
167
|
+
When writing tests that create `ContractIR` objects, use the factory function from test utilities:
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
import { createContractIR } from './test/utils';
|
|
171
|
+
|
|
172
|
+
const ir = createContractIR({
|
|
173
|
+
storage: {
|
|
174
|
+
tables: {
|
|
175
|
+
user: {
|
|
176
|
+
columns: {
|
|
177
|
+
id: { type: 'pg/int4@1', nullable: false },
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
This ensures all required fields are present with sensible defaults. See `.cursor/rules/use-contract-ir-factories.mdc` for guidelines.
|
|
186
|
+
|
|
187
|
+
## Exports
|
|
188
|
+
|
|
189
|
+
- `.`: Main emitter API (`emit`, types)
|
|
190
|
+
- **Note**: Pack loading functions (`loadExtensionPacks`, `loadExtensionPackManifest`) are CLI-only and not exported from the emitter. Import them from `@prisma-next/cli` or use the CLI's `pack-loading.ts` module directly.
|
|
191
|
+
|
|
@@ -0,0 +1 @@
|
|
|
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":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@prisma-next/emitter",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"sideEffects": false,
|
|
6
|
+
"files": [
|
|
7
|
+
"dist"
|
|
8
|
+
],
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"arktype": "^2.0.0",
|
|
11
|
+
"@prisma-next/contract": "0.0.1",
|
|
12
|
+
"@prisma-next/core-control-plane": "0.0.1"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@types/node": "^20.0.0",
|
|
16
|
+
"tsup": "^8.3.0",
|
|
17
|
+
"typescript": "^5.9.3",
|
|
18
|
+
"vitest": "^2.1.1",
|
|
19
|
+
"@prisma-next/test-utils": "0.0.1",
|
|
20
|
+
"@prisma-next/operations": "0.0.1"
|
|
21
|
+
},
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/exports/index.d.ts",
|
|
25
|
+
"import": "./dist/exports/index.js"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsup --config tsup.config.ts",
|
|
30
|
+
"test": "vitest run",
|
|
31
|
+
"test:coverage": "vitest run --coverage",
|
|
32
|
+
"typecheck": "tsc --project tsconfig.json --noEmit",
|
|
33
|
+
"lint": "biome check . --config-path ../../../../biome.json --error-on-warnings",
|
|
34
|
+
"clean": "node ../../../../scripts/clean.mjs"
|
|
35
|
+
}
|
|
36
|
+
}
|