@prisma-next/sql-contract-ts 0.3.0-dev.134 → 0.3.0-dev.135
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 +141 -4
- package/dist/contract-builder.d.mts +732 -15
- package/dist/contract-builder.d.mts.map +1 -1
- package/dist/contract-builder.mjs +1254 -131
- package/dist/contract-builder.mjs.map +1 -1
- package/package.json +7 -7
- package/src/authoring-helper-runtime.ts +139 -0
- package/src/authoring-type-utils.ts +168 -0
- package/src/composed-authoring-helpers.ts +254 -0
- package/src/contract-builder.ts +236 -335
- package/src/contract-ir-builder.ts +475 -0
- package/src/contract.ts +6 -0
- package/src/exports/contract-builder.ts +24 -2
- package/src/semantic-contract.ts +86 -0
- package/src/staged-contract-dsl.ts +1490 -0
- package/src/staged-contract-lowering.ts +705 -0
- package/src/staged-contract-types.ts +494 -0
- package/src/staged-contract-warnings.ts +245 -0
package/README.md
CHANGED
|
@@ -15,12 +15,14 @@ This package contains the SQL-specific TypeScript contract authoring surface for
|
|
|
15
15
|
## Overview
|
|
16
16
|
|
|
17
17
|
This package is part of the SQL family namespace (`packages/2-sql/2-authoring/contract-ts`) and provides:
|
|
18
|
-
- SQL contract builder (`defineContract`)
|
|
18
|
+
- SQL contract builder (`defineContract`) in two forms:
|
|
19
|
+
- legacy chain builder
|
|
20
|
+
- staged contract DSL object-literal authoring with `model('User', { fields, relations }).attributes(...).sql(...)`
|
|
19
21
|
- SQL contract JSON schema - JSON schema for validating contract structure
|
|
20
22
|
|
|
21
23
|
## Responsibilities
|
|
22
24
|
|
|
23
|
-
- **SQL Contract Builder**: Provides the
|
|
25
|
+
- **SQL Contract Builder**: Provides both the existing chain builder and the staged contract DSL authoring surface for creating SQL contracts programmatically with type safety
|
|
24
26
|
- **Storage Type Authoring**: Supports `storage.types` declarations and `typeRef` columns via the SQL builder
|
|
25
27
|
- **SQL Contract JSON Schema**: Provides JSON schema for validating contract structure in IDEs and tooling
|
|
26
28
|
- **Composition Layer**: Composes the target-agnostic builder core from `@prisma-next/contract-authoring` with SQL-specific types and validation logic
|
|
@@ -50,7 +52,7 @@ This package is part of the package layering architecture:
|
|
|
50
52
|
|
|
51
53
|
## Exports
|
|
52
54
|
|
|
53
|
-
- `./contract-builder` - Contract builder API (`defineContract`, `ColumnBuilder`)
|
|
55
|
+
- `./contract-builder` - Contract builder API (`defineContract`, `field`, `model`, `rel`, `ColumnBuilder`)
|
|
54
56
|
- `./config-types` - TypeScript contract config helper (`typescriptContract`)
|
|
55
57
|
- `./schema-sql` - SQL contract JSON schema (`data-contract-sql-v1.json`)
|
|
56
58
|
|
|
@@ -58,6 +60,140 @@ This package is part of the package layering architecture:
|
|
|
58
60
|
|
|
59
61
|
### Building Contracts
|
|
60
62
|
|
|
63
|
+
#### Staged Contract DSL
|
|
64
|
+
|
|
65
|
+
The refined surface keeps domain meaning close to the model:
|
|
66
|
+
- field-level `id()` and `unique()` for the common single-field case
|
|
67
|
+
- portable helper presets such as `field.id.uuidv4()`, `field.id.uuidv7()`, `field.id.nanoid({ size: 16 })`, `field.uuid()`, `field.nanoid({ size: 16 })`, `field.text()`, and `field.createdAt()`
|
|
68
|
+
- field-local `.sql({ column | id | unique })` and belongsTo-local `.sql({ fk })` overlays for one-off storage detail
|
|
69
|
+
- an optional integrated callback form where `defineContract(config, ({ type, field, model, rel }) => ...)` exposes composition-shaped `type.*` and pack-owned `field.*` helper namespaces
|
|
70
|
+
- `.attributes(...)` for compound `id` and compound `unique`
|
|
71
|
+
- optional staged `.relations(...)` for mutually recursive model graphs
|
|
72
|
+
- model-level `.sql(...)` for table naming, indexes, and advanced fallback storage detail
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
import { defineContract, field, model, rel } from '@prisma-next/sql-contract-ts/contract-builder';
|
|
76
|
+
import postgresPack from '@prisma-next/target-postgres/pack';
|
|
77
|
+
|
|
78
|
+
const User = model('User', {
|
|
79
|
+
fields: {
|
|
80
|
+
id: field.id.uuidv7().sql({ id: { name: 'app_user_pkey' } }),
|
|
81
|
+
email: field.text().unique().sql({ unique: { name: 'app_user_email_key' } }),
|
|
82
|
+
createdAt: field.createdAt(),
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const Post = model('Post', {
|
|
87
|
+
fields: {
|
|
88
|
+
id: field.id.uuidv7(),
|
|
89
|
+
userId: field.uuid(),
|
|
90
|
+
title: field.text(),
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
export const contract = defineContract({
|
|
95
|
+
target: postgresPack,
|
|
96
|
+
naming: { tables: 'snake_case', columns: 'snake_case' },
|
|
97
|
+
models: {
|
|
98
|
+
User: User.relations({
|
|
99
|
+
posts: rel.hasMany(Post, { by: 'userId' }),
|
|
100
|
+
}).sql({
|
|
101
|
+
table: 'app_user',
|
|
102
|
+
}),
|
|
103
|
+
Post: Post.relations({
|
|
104
|
+
user: rel
|
|
105
|
+
.belongsTo(User, { from: 'userId', to: 'id' })
|
|
106
|
+
.sql({ fk: { name: 'blog_post_user_id_fkey', onDelete: 'cascade' } }),
|
|
107
|
+
}).sql({
|
|
108
|
+
table: 'blog_post',
|
|
109
|
+
}),
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
If you want the helper vocabulary to be wired directly into the contract shell, use the callback overload:
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
import pgvector from '@prisma-next/extension-pgvector/pack';
|
|
118
|
+
import { defineContract } from '@prisma-next/sql-contract-ts/contract-builder';
|
|
119
|
+
import postgresPack from '@prisma-next/target-postgres/pack';
|
|
120
|
+
|
|
121
|
+
export const contract = defineContract(
|
|
122
|
+
{
|
|
123
|
+
target: postgresPack,
|
|
124
|
+
extensionPacks: { pgvector },
|
|
125
|
+
},
|
|
126
|
+
({ type, field, model, rel }) => {
|
|
127
|
+
const types = {
|
|
128
|
+
Role: type.enum('role', ['USER', 'ADMIN'] as const),
|
|
129
|
+
Embedding1536: type.pgvector.vector(1536),
|
|
130
|
+
} as const;
|
|
131
|
+
|
|
132
|
+
const User = model('User', {
|
|
133
|
+
fields: {
|
|
134
|
+
id: field.id.uuidv7().sql({ id: { name: 'user_pkey' } }),
|
|
135
|
+
role: field.namedType(types.Role),
|
|
136
|
+
embedding: field.namedType(types.Embedding1536).optional(),
|
|
137
|
+
},
|
|
138
|
+
}).sql({
|
|
139
|
+
table: 'user',
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
types,
|
|
144
|
+
models: {
|
|
145
|
+
User: User.relations({
|
|
146
|
+
posts: rel.hasMany(() => Post, { by: 'authorId' }),
|
|
147
|
+
}),
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
},
|
|
151
|
+
);
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Compound model-level constraints live in `.attributes(...)`:
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
const Membership = model('Membership', {
|
|
158
|
+
fields: {
|
|
159
|
+
orgId: field.column(textColumn).column('org_id'),
|
|
160
|
+
userId: field.column(textColumn).column('user_id'),
|
|
161
|
+
role: field.column(textColumn),
|
|
162
|
+
},
|
|
163
|
+
})
|
|
164
|
+
.attributes(({ fields, constraints }) => ({
|
|
165
|
+
id: constraints.id([fields.orgId, fields.userId], { name: 'membership_pkey' }),
|
|
166
|
+
uniques: [
|
|
167
|
+
constraints.unique([fields.orgId, fields.role], {
|
|
168
|
+
name: 'membership_org_role_key',
|
|
169
|
+
}),
|
|
170
|
+
],
|
|
171
|
+
}))
|
|
172
|
+
.sql({ table: 'membership' });
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
This first slice now includes a small portable helper vocabulary:
|
|
176
|
+
|
|
177
|
+
- use `field.id.uuidv4()` or `field.id.uuidv7()` for single-field UUID primary keys with explicit generator choice
|
|
178
|
+
- use `field.id.nanoid({ size })`, `field.id.ulid()`, `field.id.cuid2()`, and `field.id.ksuid()` for other generated primary-key strategies
|
|
179
|
+
- use `field.uuid()` for portable UUID-shaped foreign keys and other scalar fields
|
|
180
|
+
- use `field.nanoid({ size })`, `field.ulid()`, `field.cuid2()`, and `field.ksuid()` when you want those scalar storage shapes without generation
|
|
181
|
+
- use `field.text()`, `field.timestamp()`, and `field.createdAt()` for portable common SQL scalars
|
|
182
|
+
- use `field.sql({ column | id | unique })` and belongsTo-local `.sql({ fk })` when the storage override belongs next to one field or one FK
|
|
183
|
+
- use pack-provided column descriptors with `field.column(...)` when you need target-specific types
|
|
184
|
+
- use generated-column specs with `field.generated(...)` when you want explicit generator control
|
|
185
|
+
- use root `types` directly with `field.namedType(types.Role)` for `storage.types` references
|
|
186
|
+
- `field.namedType('Role')` still works as a fallback, but when `types.Role` exists in the same contract the builder emits `PN_CONTRACT_TYPED_FALLBACK_AVAILABLE`; prefer `field.namedType(types.Role)` for autocomplete and typed local refs
|
|
187
|
+
- use the callback overload when you want target- and extension-composed `type.*` and pack-owned `field.*` helper autocomplete inside `contract.ts`
|
|
188
|
+
- use named model tokens plus `User.refs.id` or `User.ref('id')` for cross-model foreign-key targets
|
|
189
|
+
- `constraints.ref('Model', 'field')` still works as a fallback, but when the named model token exists in the same contract the builder emits `PN_CONTRACT_TYPED_FALLBACK_AVAILABLE`; prefer `User.refs.id`-style refs for autocomplete and typed model refs
|
|
190
|
+
- string relation targets such as `rel.belongsTo('User', ...)`, `rel.hasMany('Post', ...)`, and `rel.manyToMany('Tag', { through: 'PostTag', ... })` still work, but when named model tokens exist in the same contract the builder emits `PN_CONTRACT_TYPED_FALLBACK_AVAILABLE`; prefer model tokens so cross-model authoring stays typed end-to-end
|
|
191
|
+
- use inline `.id()` for single-field identity and `.attributes(({ fields, constraints }) => ({ id: constraints.id([...]) }))` for compound identity
|
|
192
|
+
- use inline `.unique()` for single-field uniqueness and `.attributes(({ fields, constraints }) => ({ uniques: [constraints.unique([...])] }))` for compound uniqueness
|
|
193
|
+
- duplicate named primary keys, uniques, indexes, and foreign keys are rejected during build/validation instead of silently overriding each other
|
|
194
|
+
|
|
195
|
+
#### Legacy Chain Builder
|
|
196
|
+
|
|
61
197
|
```typescript
|
|
62
198
|
import { defineContract } from '@prisma-next/sql-contract-ts/contract-builder';
|
|
63
199
|
import postgresPack from '@prisma-next/target-postgres/pack';
|
|
@@ -162,6 +298,8 @@ Integration tests that depend on both `sql-contract-ts` and `sql-query` are loca
|
|
|
162
298
|
|
|
163
299
|
## Migration Notes
|
|
164
300
|
|
|
301
|
+
- **Staged contract DSL is the long-term direction**: `defineContract({ ... })` plus `model('User', { fields, relations }).sql(...)`
|
|
302
|
+
- **The first slice keeps the helper vocabulary intentionally small**: prefer pack-provided descriptors over a large built-in preset surface for now
|
|
165
303
|
- **Backward Compatibility**: `@prisma-next/sql-query` re-exports contract authoring functions for backward compatibility (will be removed in Slice 7)
|
|
166
304
|
- **Import Path**: New code should import directly from `@prisma-next/sql-contract-ts`
|
|
167
305
|
- **Phase 2 Complete**: The target-agnostic core has been extracted to `@prisma-next/contract-authoring`. This package composes the generic core with SQL-specific types.
|
|
@@ -171,4 +309,3 @@ Integration tests that depend on both `sql-contract-ts` and `sql-query` are loca
|
|
|
171
309
|
- `@prisma-next/contract-authoring` - Target-agnostic builder core that this package composes
|
|
172
310
|
- `@prisma-next/sql-contract-psl` - PSL parser-output to SQL `ContractIR` interpreter for provider-based flows
|
|
173
311
|
- `@prisma-next/sql-contract-psl/provider` - SQL PSL-first `prismaContract()` helper (read -> parse -> interpret)
|
|
174
|
-
|