@prisma-next/contract-authoring 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 +47 -0
- package/dist/index.d.ts +164 -0
- package/dist/index.js +187 -0
- package/dist/index.js.map +1 -0
- package/package.json +34 -0
package/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# @prisma-next/contract-authoring
|
|
2
|
+
|
|
3
|
+
**Status:** Phase 2 - Target-agnostic contract authoring core extracted
|
|
4
|
+
|
|
5
|
+
This package contains the target-agnostic contract authoring builder core for Prisma Next.
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
This package provides generic builder primitives that can be composed with target-family specific types (e.g., SQL) to create contract authoring surfaces. It is part of the authoring ring and depends only on `@prisma-next/contract` and core packages.
|
|
10
|
+
|
|
11
|
+
## Responsibilities
|
|
12
|
+
|
|
13
|
+
- **Generic Builder Core**: Provides target-agnostic builder state types and builder classes (`TableBuilder`, `ModelBuilder`, `ContractBuilder`)
|
|
14
|
+
- **State Management**: Manages builder state for tables, columns, models, and relations without target-specific logic
|
|
15
|
+
- **Type Helpers**: Provides generic type-level helpers for transforming builder states into contract structures
|
|
16
|
+
- **Composition Surface**: Enables target-family specific packages (e.g., `@prisma-next/sql-contract-ts`) to compose generic core with family-specific types
|
|
17
|
+
|
|
18
|
+
## Package Status
|
|
19
|
+
|
|
20
|
+
This package was created in Phase 2 of the contract authoring extraction. It contains the extracted target-neutral builder core from `@prisma-next/sql-contract-ts`. The SQL layer (`@prisma-next/sql-contract-ts`) composes this generic core with SQL-specific types.
|
|
21
|
+
|
|
22
|
+
## Architecture
|
|
23
|
+
|
|
24
|
+
- **Builder state types**: Generic state types (`ColumnBuilderState`, `TableBuilderState`, `ModelBuilderState`, `ContractBuilderState`) that don't reference any target-family specific types
|
|
25
|
+
- **Builder classes**: Generic builder classes (`TableBuilder`, `ModelBuilder`, `ContractBuilder`) that handle state management
|
|
26
|
+
- **Type helpers**: Generic type-level helpers for transforming builder states into contract structures
|
|
27
|
+
- **No target-specific logic**: This package must remain target-family agnostic and cannot import from `@prisma-next/sql-*` or other family-specific modules
|
|
28
|
+
|
|
29
|
+
## Dependencies
|
|
30
|
+
|
|
31
|
+
- `@prisma-next/contract` - Core contract types
|
|
32
|
+
- `ts-toolbelt` - Type utilities
|
|
33
|
+
|
|
34
|
+
## Exports
|
|
35
|
+
|
|
36
|
+
- Builder state types: `ColumnBuilderState`, `TableBuilderState`, `ModelBuilderState`, `ContractBuilderState`, `RelationDefinition`, `ColumnBuilder`
|
|
37
|
+
- Builder classes: `TableBuilder`, `ModelBuilder`, `ContractBuilder`
|
|
38
|
+
- Type helpers: `BuildStorageColumn`, `BuildStorage`, `BuildModels`, `BuildRelations`, extract helpers, `Mutable`
|
|
39
|
+
- Factory function: `defineContract()` (generic)
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
43
|
+
This package is intended for use by target-family specific authoring packages (e.g., `@prisma-next/sql-contract-ts`). End users should import from the target-family specific packages, not directly from this package.
|
|
44
|
+
|
|
45
|
+
## See Also
|
|
46
|
+
|
|
47
|
+
- `@prisma-next/sql-contract-ts` - SQL-specific contract authoring surface that composes this generic core
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
interface ColumnBuilderState<Name extends string, Nullable extends boolean, Type extends string> {
|
|
2
|
+
readonly name: Name;
|
|
3
|
+
readonly nullable: Nullable;
|
|
4
|
+
readonly type: Type;
|
|
5
|
+
}
|
|
6
|
+
interface TableBuilderState<Name extends string, Columns extends Record<string, ColumnBuilderState<string, boolean, string>>, PrimaryKey extends readonly string[] | undefined> {
|
|
7
|
+
readonly name: Name;
|
|
8
|
+
readonly columns: Columns;
|
|
9
|
+
readonly primaryKey?: PrimaryKey;
|
|
10
|
+
}
|
|
11
|
+
type RelationDefinition = {
|
|
12
|
+
readonly to: string;
|
|
13
|
+
readonly cardinality: '1:1' | '1:N' | 'N:1' | 'N:M';
|
|
14
|
+
readonly on: {
|
|
15
|
+
readonly parentCols: readonly string[];
|
|
16
|
+
readonly childCols: readonly string[];
|
|
17
|
+
};
|
|
18
|
+
readonly through?: {
|
|
19
|
+
readonly table: string;
|
|
20
|
+
readonly parentCols: readonly string[];
|
|
21
|
+
readonly childCols: readonly string[];
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
interface ModelBuilderState<Name extends string, Table extends string, Fields extends Record<string, string>, Relations extends Record<string, RelationDefinition>> {
|
|
25
|
+
readonly name: Name;
|
|
26
|
+
readonly table: Table;
|
|
27
|
+
readonly fields: Fields;
|
|
28
|
+
readonly relations: Relations;
|
|
29
|
+
}
|
|
30
|
+
interface ContractBuilderState<Target extends string | undefined = string | undefined, Tables extends Record<string, TableBuilderState<string, Record<string, ColumnBuilderState<string, boolean, string>>, readonly string[] | undefined>> = Record<never, TableBuilderState<string, Record<string, ColumnBuilderState<string, boolean, string>>, readonly string[] | undefined>>, Models extends Record<string, ModelBuilderState<string, string, Record<string, string>, Record<string, RelationDefinition>>> = Record<never, ModelBuilderState<string, string, Record<string, string>, Record<string, RelationDefinition>>>, CoreHash extends string | undefined = string | undefined, Extensions extends Record<string, unknown> | undefined = undefined, Capabilities extends Record<string, Record<string, boolean>> | undefined = undefined> {
|
|
31
|
+
readonly target?: Target;
|
|
32
|
+
readonly tables: Tables;
|
|
33
|
+
readonly models: Models;
|
|
34
|
+
readonly coreHash?: CoreHash;
|
|
35
|
+
readonly extensions?: Extensions;
|
|
36
|
+
readonly capabilities?: Capabilities;
|
|
37
|
+
}
|
|
38
|
+
interface ColumnBuilder<Name extends string, Nullable extends boolean, Type extends string> {
|
|
39
|
+
nullable<Value extends boolean>(value?: Value): ColumnBuilder<Name, Value, Type>;
|
|
40
|
+
type<Id extends string>(id: Id): ColumnBuilder<Name, Nullable, Id>;
|
|
41
|
+
build(): ColumnBuilderState<Name, Nullable, Type>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
declare class ModelBuilder<Name extends string, Table extends string, Fields extends Record<string, string> = Record<never, never>, Relations extends Record<string, RelationDefinition> = Record<never, never>> {
|
|
45
|
+
private readonly _name;
|
|
46
|
+
private readonly _table;
|
|
47
|
+
private readonly _fields;
|
|
48
|
+
private readonly _relations;
|
|
49
|
+
constructor(name: Name, table: Table, fields?: Fields, relations?: Relations);
|
|
50
|
+
field<FieldName extends string, ColumnName extends string>(fieldName: FieldName, columnName: ColumnName): ModelBuilder<Name, Table, Fields & Record<FieldName, ColumnName>, Relations>;
|
|
51
|
+
relation<RelationName extends string, ToModel extends string, ToTable extends string>(name: RelationName, options: {
|
|
52
|
+
toModel: ToModel;
|
|
53
|
+
toTable: ToTable;
|
|
54
|
+
cardinality: '1:1' | '1:N' | 'N:1';
|
|
55
|
+
on: {
|
|
56
|
+
parentTable: Table;
|
|
57
|
+
parentColumns: readonly string[];
|
|
58
|
+
childTable: ToTable;
|
|
59
|
+
childColumns: readonly string[];
|
|
60
|
+
};
|
|
61
|
+
}): ModelBuilder<Name, Table, Fields, Relations & Record<RelationName, RelationDefinition>>;
|
|
62
|
+
relation<RelationName extends string, ToModel extends string, ToTable extends string, JunctionTable extends string>(name: RelationName, options: {
|
|
63
|
+
toModel: ToModel;
|
|
64
|
+
toTable: ToTable;
|
|
65
|
+
cardinality: 'N:M';
|
|
66
|
+
through: {
|
|
67
|
+
table: JunctionTable;
|
|
68
|
+
parentColumns: readonly string[];
|
|
69
|
+
childColumns: readonly string[];
|
|
70
|
+
};
|
|
71
|
+
on: {
|
|
72
|
+
parentTable: Table;
|
|
73
|
+
parentColumns: readonly string[];
|
|
74
|
+
childTable: JunctionTable;
|
|
75
|
+
childColumns: readonly string[];
|
|
76
|
+
};
|
|
77
|
+
}): ModelBuilder<Name, Table, Fields, Relations & Record<RelationName, RelationDefinition>>;
|
|
78
|
+
build(): ModelBuilderState<Name, Table, Fields, Relations>;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
declare class TableBuilder<Name extends string, Columns extends Record<string, ColumnBuilderState<string, boolean, string>> = Record<never, ColumnBuilderState<string, boolean, string>>, PrimaryKey extends readonly string[] | undefined = undefined> {
|
|
82
|
+
private readonly _name;
|
|
83
|
+
private readonly _columns;
|
|
84
|
+
private readonly _primaryKey;
|
|
85
|
+
constructor(name: Name, columns?: Columns, primaryKey?: PrimaryKey);
|
|
86
|
+
column<ColName extends string, TypeId extends string, Nullable extends boolean | undefined = undefined>(name: ColName, options: {
|
|
87
|
+
type: TypeId;
|
|
88
|
+
nullable?: Nullable;
|
|
89
|
+
}): TableBuilder<Name, Columns & Record<ColName, ColumnBuilderState<ColName, Nullable extends true ? true : false, TypeId>>, PrimaryKey>;
|
|
90
|
+
primaryKey<PK extends readonly string[]>(columns: PK, _name?: string): TableBuilder<Name, Columns, PK>;
|
|
91
|
+
unique(_columns: readonly string[], _name?: string): TableBuilder<Name, Columns, PrimaryKey>;
|
|
92
|
+
index(_columns: readonly string[], _name?: string): TableBuilder<Name, Columns, PrimaryKey>;
|
|
93
|
+
foreignKey(_columns: readonly string[], _references: {
|
|
94
|
+
table: string;
|
|
95
|
+
columns: readonly string[];
|
|
96
|
+
}, _name?: string): TableBuilder<Name, Columns, PrimaryKey>;
|
|
97
|
+
build(): TableBuilderState<Name, Columns, PrimaryKey>;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
declare class ContractBuilder<Target extends string | undefined = undefined, Tables extends Record<string, TableBuilderState<string, Record<string, {
|
|
101
|
+
readonly name: string;
|
|
102
|
+
readonly nullable: boolean;
|
|
103
|
+
readonly type: string;
|
|
104
|
+
}>, readonly string[] | undefined>> = Record<never, never>, Models extends Record<string, ModelBuilderState<string, string, Record<string, string>, Record<string, RelationDefinition>>> = Record<never, never>, CoreHash extends string | undefined = undefined, Extensions extends Record<string, unknown> | undefined = undefined, Capabilities extends Record<string, Record<string, boolean>> | undefined = undefined> {
|
|
105
|
+
protected readonly state: ContractBuilderState<Target, Tables, Models, CoreHash, Extensions, Capabilities>;
|
|
106
|
+
constructor(state?: ContractBuilderState<Target, Tables, Models, CoreHash, Extensions, Capabilities>);
|
|
107
|
+
target<T extends string>(target: T): ContractBuilder<T, Tables, Models, CoreHash, Extensions, Capabilities>;
|
|
108
|
+
extensions<E extends Record<string, unknown>>(extensions: E): ContractBuilder<Target, Tables, Models, CoreHash, E, Capabilities>;
|
|
109
|
+
capabilities<C extends Record<string, Record<string, boolean>>>(capabilities: C): ContractBuilder<Target, Tables, Models, CoreHash, Extensions, C>;
|
|
110
|
+
table<TableName extends string, T extends TableBuilder<TableName, Record<string, {
|
|
111
|
+
readonly name: string;
|
|
112
|
+
readonly nullable: boolean;
|
|
113
|
+
readonly type: string;
|
|
114
|
+
}>, readonly string[] | undefined>>(name: TableName, callback: (t: TableBuilder<TableName>) => T | undefined): ContractBuilder<Target, Tables & Record<TableName, ReturnType<T['build']>>, Models, CoreHash, Extensions, Capabilities>;
|
|
115
|
+
model<ModelName extends string, TableName extends string, M extends ModelBuilder<ModelName, TableName, Record<string, string>, Record<string, RelationDefinition>>>(name: ModelName, table: TableName, callback: (m: ModelBuilder<ModelName, TableName, Record<string, string>, Record<never, never>>) => M | undefined): ContractBuilder<Target, Tables, Models & Record<ModelName, ReturnType<M['build']>>, CoreHash, Extensions, Capabilities>;
|
|
116
|
+
coreHash<H extends string>(hash: H): ContractBuilder<Target, Tables, Models, H, Extensions, Capabilities>;
|
|
117
|
+
}
|
|
118
|
+
declare function defineContract(): ContractBuilder;
|
|
119
|
+
|
|
120
|
+
type BuildStorageColumn<Nullable extends boolean, Type extends string> = {
|
|
121
|
+
readonly type: Type;
|
|
122
|
+
readonly nullable: Nullable;
|
|
123
|
+
};
|
|
124
|
+
type ExtractColumns<T extends TableBuilderState<string, Record<string, ColumnBuilderState<string, boolean, string>>, readonly string[] | undefined>> = T extends TableBuilderState<string, infer C, readonly string[] | undefined> ? C : never;
|
|
125
|
+
type ExtractPrimaryKey<T extends TableBuilderState<string, Record<string, ColumnBuilderState<string, boolean, string>>, readonly string[] | undefined>> = T extends TableBuilderState<string, Record<string, ColumnBuilderState<string, boolean, string>>, infer PK> ? PK : never;
|
|
126
|
+
type BuildStorage<Tables extends Record<string, TableBuilderState<string, Record<string, ColumnBuilderState<string, boolean, string>>, readonly string[] | undefined>>> = {
|
|
127
|
+
readonly tables: {
|
|
128
|
+
readonly [K in keyof Tables]: {
|
|
129
|
+
readonly columns: {
|
|
130
|
+
readonly [ColK in keyof ExtractColumns<Tables[K]>]: ExtractColumns<Tables[K]>[ColK] extends ColumnBuilderState<string, infer Null, infer TType> ? BuildStorageColumn<Null & boolean, TType> : never;
|
|
131
|
+
};
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
type BuildStorageTables<Tables extends Record<string, TableBuilderState<string, Record<string, ColumnBuilderState<string, boolean, string>>, readonly string[] | undefined>>> = {
|
|
136
|
+
readonly [K in keyof Tables]: {
|
|
137
|
+
readonly columns: {
|
|
138
|
+
readonly [ColK in keyof ExtractColumns<Tables[K]>]: ExtractColumns<Tables[K]>[ColK] extends ColumnBuilderState<string, infer Null, infer TType> ? BuildStorageColumn<Null & boolean, TType> : never;
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
};
|
|
142
|
+
type Mutable<T> = {
|
|
143
|
+
-readonly [K in keyof T]-?: T[K];
|
|
144
|
+
};
|
|
145
|
+
type BuildModelFields<Fields extends Record<string, string>> = {
|
|
146
|
+
readonly [K in keyof Fields]: {
|
|
147
|
+
readonly column: Fields[K];
|
|
148
|
+
};
|
|
149
|
+
};
|
|
150
|
+
type ExtractModelFields<T extends ModelBuilderState<string, string, Record<string, string>, Record<string, RelationDefinition>>> = T extends ModelBuilderState<string, string, infer F, Record<string, RelationDefinition>> ? F : never;
|
|
151
|
+
type ExtractModelRelations<T extends ModelBuilderState<string, string, Record<string, string>, Record<string, RelationDefinition>>> = T extends ModelBuilderState<string, string, Record<string, string>, infer R> ? R : never;
|
|
152
|
+
type BuildModels<Models extends Record<string, ModelBuilderState<string, string, Record<string, string>, Record<string, RelationDefinition>>>> = {
|
|
153
|
+
readonly [K in keyof Models]: {
|
|
154
|
+
readonly storage: {
|
|
155
|
+
readonly table: Models[K]['table'];
|
|
156
|
+
};
|
|
157
|
+
readonly fields: BuildModelFields<ExtractModelFields<Models[K]>>;
|
|
158
|
+
};
|
|
159
|
+
};
|
|
160
|
+
type BuildRelations<Models extends Record<string, ModelBuilderState<string, string, Record<string, string>, Record<string, RelationDefinition>>>> = {
|
|
161
|
+
readonly [K in keyof Models as Models[K]['table']]: ExtractModelRelations<Models[K]>;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
export { type BuildModelFields, type BuildModels, type BuildRelations, type BuildStorage, type BuildStorageColumn, type BuildStorageTables, type ColumnBuilder, type ColumnBuilderState, ContractBuilder, type ContractBuilderState, type ExtractColumns, type ExtractModelFields, type ExtractModelRelations, type ExtractPrimaryKey, ModelBuilder, type ModelBuilderState, type Mutable, type RelationDefinition, TableBuilder, type TableBuilderState, defineContract };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
// src/model-builder.ts
|
|
2
|
+
var ModelBuilder = class _ModelBuilder {
|
|
3
|
+
_name;
|
|
4
|
+
_table;
|
|
5
|
+
_fields;
|
|
6
|
+
_relations;
|
|
7
|
+
constructor(name, table, fields = {}, relations = {}) {
|
|
8
|
+
this._name = name;
|
|
9
|
+
this._table = table;
|
|
10
|
+
this._fields = fields;
|
|
11
|
+
this._relations = relations;
|
|
12
|
+
}
|
|
13
|
+
field(fieldName, columnName) {
|
|
14
|
+
return new _ModelBuilder(
|
|
15
|
+
this._name,
|
|
16
|
+
this._table,
|
|
17
|
+
{
|
|
18
|
+
...this._fields,
|
|
19
|
+
[fieldName]: columnName
|
|
20
|
+
},
|
|
21
|
+
this._relations
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
relation(name, options) {
|
|
25
|
+
if (options.on.parentTable !== this._table) {
|
|
26
|
+
throw new Error(
|
|
27
|
+
`Relation "${name}" parentTable "${options.on.parentTable}" does not match model table "${this._table}"`
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
if (options.cardinality === "N:M") {
|
|
31
|
+
if (!options.through) {
|
|
32
|
+
throw new Error(`Relation "${name}" with cardinality "N:M" requires through field`);
|
|
33
|
+
}
|
|
34
|
+
if (options.on.childTable !== options.through.table) {
|
|
35
|
+
throw new Error(
|
|
36
|
+
`Relation "${name}" childTable "${options.on.childTable}" does not match through.table "${options.through.table}"`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
} else {
|
|
40
|
+
if (options.on.childTable !== options.toTable) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
`Relation "${name}" childTable "${options.on.childTable}" does not match toTable "${options.toTable}"`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const relationDef = {
|
|
47
|
+
to: options.toModel,
|
|
48
|
+
cardinality: options.cardinality,
|
|
49
|
+
on: {
|
|
50
|
+
parentCols: options.on.parentColumns,
|
|
51
|
+
childCols: options.on.childColumns
|
|
52
|
+
},
|
|
53
|
+
...options.through ? {
|
|
54
|
+
through: {
|
|
55
|
+
table: options.through.table,
|
|
56
|
+
parentCols: options.through.parentColumns,
|
|
57
|
+
childCols: options.through.childColumns
|
|
58
|
+
}
|
|
59
|
+
} : void 0
|
|
60
|
+
};
|
|
61
|
+
return new _ModelBuilder(this._name, this._table, this._fields, {
|
|
62
|
+
...this._relations,
|
|
63
|
+
[name]: relationDef
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
build() {
|
|
67
|
+
return {
|
|
68
|
+
name: this._name,
|
|
69
|
+
table: this._table,
|
|
70
|
+
fields: this._fields,
|
|
71
|
+
relations: this._relations
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// src/table-builder.ts
|
|
77
|
+
var TableBuilder = class _TableBuilder {
|
|
78
|
+
_name;
|
|
79
|
+
_columns;
|
|
80
|
+
_primaryKey;
|
|
81
|
+
constructor(name, columns = {}, primaryKey) {
|
|
82
|
+
this._name = name;
|
|
83
|
+
this._columns = columns;
|
|
84
|
+
this._primaryKey = primaryKey;
|
|
85
|
+
}
|
|
86
|
+
column(name, options) {
|
|
87
|
+
if (!options.type || typeof options.type !== "string" || !options.type.includes("@")) {
|
|
88
|
+
throw new Error(`type must be in format "namespace/name@version", got "${options.type}"`);
|
|
89
|
+
}
|
|
90
|
+
const nullable = options.nullable ?? false;
|
|
91
|
+
const type = options.type;
|
|
92
|
+
const columnState = {
|
|
93
|
+
name,
|
|
94
|
+
nullable,
|
|
95
|
+
type
|
|
96
|
+
};
|
|
97
|
+
return new _TableBuilder(
|
|
98
|
+
this._name,
|
|
99
|
+
{ ...this._columns, [name]: columnState },
|
|
100
|
+
this._primaryKey
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
primaryKey(columns, _name) {
|
|
104
|
+
return new _TableBuilder(this._name, this._columns, columns);
|
|
105
|
+
}
|
|
106
|
+
unique(_columns, _name) {
|
|
107
|
+
return this;
|
|
108
|
+
}
|
|
109
|
+
index(_columns, _name) {
|
|
110
|
+
return this;
|
|
111
|
+
}
|
|
112
|
+
foreignKey(_columns, _references, _name) {
|
|
113
|
+
return this;
|
|
114
|
+
}
|
|
115
|
+
build() {
|
|
116
|
+
return {
|
|
117
|
+
name: this._name,
|
|
118
|
+
columns: this._columns,
|
|
119
|
+
...this._primaryKey !== void 0 ? { primaryKey: this._primaryKey } : {}
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// src/contract-builder.ts
|
|
125
|
+
var ContractBuilder = class _ContractBuilder {
|
|
126
|
+
state;
|
|
127
|
+
constructor(state) {
|
|
128
|
+
this.state = state ?? {
|
|
129
|
+
tables: {},
|
|
130
|
+
models: {}
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
target(target) {
|
|
134
|
+
return new _ContractBuilder({
|
|
135
|
+
...this.state,
|
|
136
|
+
target
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
extensions(extensions) {
|
|
140
|
+
return new _ContractBuilder({
|
|
141
|
+
...this.state,
|
|
142
|
+
extensions
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
capabilities(capabilities) {
|
|
146
|
+
return new _ContractBuilder({
|
|
147
|
+
...this.state,
|
|
148
|
+
capabilities
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
table(name, callback) {
|
|
152
|
+
const tableBuilder = new TableBuilder(name);
|
|
153
|
+
const result = callback(tableBuilder);
|
|
154
|
+
const finalBuilder = result instanceof TableBuilder ? result : tableBuilder;
|
|
155
|
+
const tableState = finalBuilder.build();
|
|
156
|
+
return new _ContractBuilder({
|
|
157
|
+
...this.state,
|
|
158
|
+
tables: { ...this.state.tables, [name]: tableState }
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
model(name, table, callback) {
|
|
162
|
+
const modelBuilder = new ModelBuilder(name, table);
|
|
163
|
+
const result = callback(modelBuilder);
|
|
164
|
+
const finalBuilder = result instanceof ModelBuilder ? result : modelBuilder;
|
|
165
|
+
const modelState = finalBuilder.build();
|
|
166
|
+
return new _ContractBuilder({
|
|
167
|
+
...this.state,
|
|
168
|
+
models: { ...this.state.models, [name]: modelState }
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
coreHash(hash) {
|
|
172
|
+
return new _ContractBuilder({
|
|
173
|
+
...this.state,
|
|
174
|
+
coreHash: hash
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
function defineContract() {
|
|
179
|
+
return new ContractBuilder();
|
|
180
|
+
}
|
|
181
|
+
export {
|
|
182
|
+
ContractBuilder,
|
|
183
|
+
ModelBuilder,
|
|
184
|
+
TableBuilder,
|
|
185
|
+
defineContract
|
|
186
|
+
};
|
|
187
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/model-builder.ts","../src/table-builder.ts","../src/contract-builder.ts"],"sourcesContent":["import type { ModelBuilderState, RelationDefinition } from './builder-state';\n\nexport class ModelBuilder<\n Name extends string,\n Table extends string,\n Fields extends Record<string, string> = Record<never, never>,\n Relations extends Record<string, RelationDefinition> = Record<never, never>,\n> {\n private readonly _name: Name;\n private readonly _table: Table;\n private readonly _fields: Fields;\n private readonly _relations: Relations;\n\n constructor(\n name: Name,\n table: Table,\n fields: Fields = {} as Fields,\n relations: Relations = {} as Relations,\n ) {\n this._name = name;\n this._table = table;\n this._fields = fields;\n this._relations = relations;\n }\n\n field<FieldName extends string, ColumnName extends string>(\n fieldName: FieldName,\n columnName: ColumnName,\n ): ModelBuilder<Name, Table, Fields & Record<FieldName, ColumnName>, Relations> {\n return new ModelBuilder(\n this._name,\n this._table,\n {\n ...this._fields,\n [fieldName]: columnName,\n } as Fields & Record<FieldName, ColumnName>,\n this._relations,\n );\n }\n\n relation<RelationName extends string, ToModel extends string, ToTable extends string>(\n name: RelationName,\n options: {\n toModel: ToModel;\n toTable: ToTable;\n cardinality: '1:1' | '1:N' | 'N:1';\n on: {\n parentTable: Table;\n parentColumns: readonly string[];\n childTable: ToTable;\n childColumns: readonly string[];\n };\n },\n ): ModelBuilder<Name, Table, Fields, Relations & Record<RelationName, RelationDefinition>>;\n relation<\n RelationName extends string,\n ToModel extends string,\n ToTable extends string,\n JunctionTable extends string,\n >(\n name: RelationName,\n options: {\n toModel: ToModel;\n toTable: ToTable;\n cardinality: 'N:M';\n through: {\n table: JunctionTable;\n parentColumns: readonly string[];\n childColumns: readonly string[];\n };\n on: {\n parentTable: Table;\n parentColumns: readonly string[];\n childTable: JunctionTable;\n childColumns: readonly string[];\n };\n },\n ): ModelBuilder<Name, Table, Fields, Relations & Record<RelationName, RelationDefinition>>;\n relation<\n RelationName extends string,\n ToModel extends string,\n ToTable extends string,\n JunctionTable extends string = never,\n >(\n name: RelationName,\n options: {\n toModel: ToModel;\n toTable: ToTable;\n cardinality: '1:1' | '1:N' | 'N:1' | 'N:M';\n through?: {\n table: JunctionTable;\n parentColumns: readonly string[];\n childColumns: readonly string[];\n };\n on: {\n parentTable: Table;\n parentColumns: readonly string[];\n childTable: ToTable | JunctionTable;\n childColumns: readonly string[];\n };\n },\n ): ModelBuilder<Name, Table, Fields, Relations & Record<RelationName, RelationDefinition>> {\n // Validate parentTable matches model's table\n if (options.on.parentTable !== this._table) {\n throw new Error(\n `Relation \"${name}\" parentTable \"${options.on.parentTable}\" does not match model table \"${this._table}\"`,\n );\n }\n\n // Validate childTable matches toTable (for non-N:M) or through.table (for N:M)\n if (options.cardinality === 'N:M') {\n if (!options.through) {\n throw new Error(`Relation \"${name}\" with cardinality \"N:M\" requires through field`);\n }\n if (options.on.childTable !== options.through.table) {\n throw new Error(\n `Relation \"${name}\" childTable \"${options.on.childTable}\" does not match through.table \"${options.through.table}\"`,\n );\n }\n } else {\n if (options.on.childTable !== options.toTable) {\n throw new Error(\n `Relation \"${name}\" childTable \"${options.on.childTable}\" does not match toTable \"${options.toTable}\"`,\n );\n }\n }\n\n const relationDef: RelationDefinition = {\n to: options.toModel,\n cardinality: options.cardinality,\n on: {\n parentCols: options.on.parentColumns,\n childCols: options.on.childColumns,\n },\n ...(options.through\n ? {\n through: {\n table: options.through.table,\n parentCols: options.through.parentColumns,\n childCols: options.through.childColumns,\n },\n }\n : undefined),\n };\n\n return new ModelBuilder(this._name, this._table, this._fields, {\n ...this._relations,\n [name]: relationDef,\n } as Relations & Record<RelationName, RelationDefinition>);\n }\n\n build(): ModelBuilderState<Name, Table, Fields, Relations> {\n return {\n name: this._name,\n table: this._table,\n fields: this._fields,\n relations: this._relations,\n };\n }\n}\n","import type { ColumnBuilderState, TableBuilderState } from './builder-state';\n\nexport class TableBuilder<\n Name extends string,\n Columns extends Record<string, ColumnBuilderState<string, boolean, string>> = Record<\n never,\n ColumnBuilderState<string, boolean, string>\n >,\n PrimaryKey extends readonly string[] | undefined = undefined,\n> {\n private readonly _name: Name;\n private readonly _columns: Columns;\n private readonly _primaryKey: PrimaryKey;\n\n constructor(name: Name, columns: Columns = {} as Columns, primaryKey?: PrimaryKey) {\n this._name = name;\n this._columns = columns;\n this._primaryKey = primaryKey as PrimaryKey;\n }\n\n column<\n ColName extends string,\n TypeId extends string,\n Nullable extends boolean | undefined = undefined,\n >(\n name: ColName,\n options: {\n type: TypeId;\n nullable?: Nullable;\n },\n ): TableBuilder<\n Name,\n Columns &\n Record<ColName, ColumnBuilderState<ColName, Nullable extends true ? true : false, TypeId>>,\n PrimaryKey\n > {\n if (!options.type || typeof options.type !== 'string' || !options.type.includes('@')) {\n throw new Error(`type must be in format \"namespace/name@version\", got \"${options.type}\"`);\n }\n const nullable = (options.nullable ?? false) as Nullable extends true ? true : false;\n const type = options.type;\n const columnState = {\n name,\n nullable,\n type,\n } as ColumnBuilderState<ColName, Nullable extends true ? true : false, TypeId>;\n return new TableBuilder(\n this._name,\n { ...this._columns, [name]: columnState } as Columns &\n Record<ColName, ColumnBuilderState<ColName, Nullable extends true ? true : false, TypeId>>,\n this._primaryKey,\n );\n }\n\n primaryKey<PK extends readonly string[]>(\n columns: PK,\n _name?: string,\n ): TableBuilder<Name, Columns, PK> {\n return new TableBuilder(this._name, this._columns, columns);\n }\n\n unique(_columns: readonly string[], _name?: string): TableBuilder<Name, Columns, PrimaryKey> {\n return this;\n }\n\n index(_columns: readonly string[], _name?: string): TableBuilder<Name, Columns, PrimaryKey> {\n return this;\n }\n\n foreignKey(\n _columns: readonly string[],\n _references: { table: string; columns: readonly string[] },\n _name?: string,\n ): TableBuilder<Name, Columns, PrimaryKey> {\n return this;\n }\n\n build(): TableBuilderState<Name, Columns, PrimaryKey> {\n return {\n name: this._name,\n columns: this._columns,\n ...(this._primaryKey !== undefined ? { primaryKey: this._primaryKey } : {}),\n } as TableBuilderState<Name, Columns, PrimaryKey>;\n }\n}\n","import type {\n ContractBuilderState,\n ModelBuilderState,\n RelationDefinition,\n TableBuilderState,\n} from './builder-state';\nimport { ModelBuilder } from './model-builder';\nimport { TableBuilder } from './table-builder';\n\nexport class ContractBuilder<\n Target extends string | undefined = undefined,\n Tables extends Record<\n string,\n TableBuilderState<\n string,\n Record<string, { readonly name: string; readonly nullable: boolean; readonly type: string }>,\n readonly string[] | undefined\n >\n > = Record<never, never>,\n Models extends Record<\n string,\n ModelBuilderState<string, string, Record<string, string>, Record<string, RelationDefinition>>\n > = Record<never, never>,\n CoreHash extends string | undefined = undefined,\n Extensions extends Record<string, unknown> | undefined = undefined,\n Capabilities extends Record<string, Record<string, boolean>> | undefined = undefined,\n> {\n protected readonly state: ContractBuilderState<\n Target,\n Tables,\n Models,\n CoreHash,\n Extensions,\n Capabilities\n >;\n\n constructor(\n state?: ContractBuilderState<Target, Tables, Models, CoreHash, Extensions, Capabilities>,\n ) {\n this.state =\n state ??\n ({\n tables: {},\n models: {},\n } as ContractBuilderState<Target, Tables, Models, CoreHash, Extensions, Capabilities>);\n }\n\n target<T extends string>(\n target: T,\n ): ContractBuilder<T, Tables, Models, CoreHash, Extensions, Capabilities> {\n return new ContractBuilder<T, Tables, Models, CoreHash, Extensions, Capabilities>({\n ...this.state,\n target,\n });\n }\n\n extensions<E extends Record<string, unknown>>(\n extensions: E,\n ): ContractBuilder<Target, Tables, Models, CoreHash, E, Capabilities> {\n return new ContractBuilder<Target, Tables, Models, CoreHash, E, Capabilities>({\n ...this.state,\n extensions,\n });\n }\n\n capabilities<C extends Record<string, Record<string, boolean>>>(\n capabilities: C,\n ): ContractBuilder<Target, Tables, Models, CoreHash, Extensions, C> {\n return new ContractBuilder<Target, Tables, Models, CoreHash, Extensions, C>({\n ...this.state,\n capabilities,\n });\n }\n\n table<\n TableName extends string,\n T extends TableBuilder<\n TableName,\n Record<string, { readonly name: string; readonly nullable: boolean; readonly type: string }>,\n readonly string[] | undefined\n >,\n >(\n name: TableName,\n callback: (t: TableBuilder<TableName>) => T | undefined,\n ): ContractBuilder<\n Target,\n Tables & Record<TableName, ReturnType<T['build']>>,\n Models,\n CoreHash,\n Extensions,\n Capabilities\n > {\n const tableBuilder = new TableBuilder<TableName>(name);\n const result = callback(tableBuilder);\n const finalBuilder = result instanceof TableBuilder ? result : tableBuilder;\n const tableState = finalBuilder.build();\n\n return new ContractBuilder<\n Target,\n Tables & Record<TableName, ReturnType<T['build']>>,\n Models,\n CoreHash,\n Extensions,\n Capabilities\n >({\n ...this.state,\n tables: { ...this.state.tables, [name]: tableState } as Tables &\n Record<TableName, ReturnType<T['build']>>,\n });\n }\n\n model<\n ModelName extends string,\n TableName extends string,\n M extends ModelBuilder<\n ModelName,\n TableName,\n Record<string, string>,\n Record<string, RelationDefinition>\n >,\n >(\n name: ModelName,\n table: TableName,\n callback: (\n m: ModelBuilder<ModelName, TableName, Record<string, string>, Record<never, never>>,\n ) => M | undefined,\n ): ContractBuilder<\n Target,\n Tables,\n Models & Record<ModelName, ReturnType<M['build']>>,\n CoreHash,\n Extensions,\n Capabilities\n > {\n const modelBuilder = new ModelBuilder<ModelName, TableName>(name, table);\n const result = callback(modelBuilder);\n const finalBuilder = result instanceof ModelBuilder ? result : modelBuilder;\n const modelState = finalBuilder.build();\n\n return new ContractBuilder<\n Target,\n Tables,\n Models & Record<ModelName, ReturnType<M['build']>>,\n CoreHash,\n Extensions,\n Capabilities\n >({\n ...this.state,\n models: { ...this.state.models, [name]: modelState } as Models &\n Record<ModelName, ReturnType<M['build']>>,\n });\n }\n\n coreHash<H extends string>(\n hash: H,\n ): ContractBuilder<Target, Tables, Models, H, Extensions, Capabilities> {\n return new ContractBuilder<Target, Tables, Models, H, Extensions, Capabilities>({\n ...this.state,\n coreHash: hash,\n });\n }\n}\n\nexport function defineContract(): ContractBuilder {\n return new ContractBuilder();\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@prisma-next/contract-authoring",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"sideEffects": false,
|
|
6
|
+
"description": "Target-agnostic contract authoring builder core for Prisma Next",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"ts-toolbelt": "^9.6.0",
|
|
9
|
+
"@prisma-next/contract": "0.0.1"
|
|
10
|
+
},
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"@vitest/coverage-v8": "^2.1.1",
|
|
13
|
+
"tsup": "^8.3.0",
|
|
14
|
+
"typescript": "^5.9.3",
|
|
15
|
+
"vitest": "^2.1.1"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"import": "./dist/index.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsup --config tsup.config.ts",
|
|
28
|
+
"test": "vitest run",
|
|
29
|
+
"test:coverage": "vitest run --coverage",
|
|
30
|
+
"typecheck": "tsc --project tsconfig.json --noEmit",
|
|
31
|
+
"lint": "biome check . --config-path ../../../../biome.json --error-on-warnings",
|
|
32
|
+
"clean": "node ../../../../scripts/clean.mjs"
|
|
33
|
+
}
|
|
34
|
+
}
|