@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 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`) - TypeScript builder for creating SQL contracts programmatically
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 `defineContract()` builder API for creating SQL contracts programmatically with type safety, including pack-ref based `.target()` and `.extensionPacks()` helpers
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
-