@fougere/adapter-sql 0.2.0-alpha.2 → 0.4.0-alpha.0
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 +1 -1
- package/dist/crud.d.ts +14 -3
- package/dist/crud.d.ts.map +1 -1
- package/dist/crud.js +33 -4
- package/dist/crud.js.map +1 -1
- package/dist/ddl.d.ts.map +1 -1
- package/dist/ddl.js +2 -2
- package/dist/ddl.js.map +1 -1
- package/dist/dialect.d.ts +20 -0
- package/dist/dialect.d.ts.map +1 -1
- package/dist/dialect.js +27 -1
- package/dist/dialect.js.map +1 -1
- package/dist/diff.d.ts.map +1 -1
- package/dist/diff.js +2 -2
- package/dist/diff.js.map +1 -1
- package/dist/fields.d.ts +22 -0
- package/dist/fields.d.ts.map +1 -0
- package/dist/fields.js +2 -0
- package/dist/fields.js.map +1 -0
- package/dist/index.d.ts +6 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -2
- package/dist/index.js.map +1 -1
- package/dist/setup.d.ts +18 -10
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +10 -23
- package/dist/setup.js.map +1 -1
- package/dist/sqlite.d.ts +12 -0
- package/dist/sqlite.d.ts.map +1 -0
- package/dist/sqlite.js +34 -0
- package/dist/sqlite.js.map +1 -0
- package/dist/step.d.ts +78 -0
- package/dist/step.d.ts.map +1 -0
- package/dist/step.js +233 -0
- package/dist/step.js.map +1 -0
- package/dist/table.d.ts +19 -10
- package/dist/table.d.ts.map +1 -1
- package/dist/table.js +36 -30
- package/dist/table.js.map +1 -1
- package/package.json +11 -4
- package/src/check.ts +76 -0
- package/src/crud.ts +570 -0
- package/src/ddl.ts +242 -0
- package/src/dialect.ts +204 -0
- package/src/diff.ts +196 -0
- package/src/fields.ts +24 -0
- package/src/index.ts +40 -0
- package/src/setup.ts +63 -0
- package/src/sqlite.ts +43 -0
- package/src/step.ts +287 -0
- package/src/table.ts +447 -0
- package/src/values.ts +105 -0
package/dist/table.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
|
+
import { Lifecycle, Role } from '@fougere/schema';
|
|
1
2
|
/**
|
|
2
3
|
* Entity → table description, with no SQL in sight.
|
|
3
4
|
*
|
|
4
5
|
* This is the neutral middle term: one projection reads the entity's axes and
|
|
5
6
|
* produces a `TableDef`; a `Dialect` turns that into SQL. Neither half knows the
|
|
6
|
-
* other — the
|
|
7
|
-
*
|
|
7
|
+
* other — the dialect never mentions a field. Adding a dialect touches only the
|
|
8
|
+
* second half.
|
|
9
|
+
*
|
|
10
|
+
* `ColumnDef.stated` is the one member the axes did not produce. It names one engine's
|
|
11
|
+
* column type, so dropping it leaves every column describable.
|
|
8
12
|
*/
|
|
9
|
-
import { Anatomy, fieldsOf,
|
|
13
|
+
import { Anatomy, FieldGroup, Unique, fieldsOf, lowerFirst, schemaOf } from '@fougere/schema';
|
|
10
14
|
import { boundsOf } from './check.js';
|
|
11
15
|
/** camelCase → snake_case */
|
|
12
16
|
export function toSnakeCase(str) {
|
|
@@ -17,19 +21,18 @@ export function toSnakeCase(str) {
|
|
|
17
21
|
* other field becomes exactly one column.
|
|
18
22
|
*/
|
|
19
23
|
function isStored(field) {
|
|
20
|
-
return field.
|
|
24
|
+
return !Role.of(field).isCollection;
|
|
21
25
|
}
|
|
22
26
|
/**
|
|
23
27
|
* The target's primary key column. A live thunk (an in-process entity) answers
|
|
24
|
-
* for real; a relation reconstructed from a lone
|
|
25
|
-
* bundle — `packages/schema/src/projections/card.ts:18-22`) has lost it to a
|
|
28
|
+
* for real; a relation reconstructed from a lone `Card` (without a `Bundle`) has lost it to a
|
|
26
29
|
* name stand-in with no `getFields` — the convention there is to assume `id`.
|
|
27
30
|
*/
|
|
28
31
|
function primaryColumnOf(target) {
|
|
29
32
|
if (typeof target.getFields !== 'function')
|
|
30
33
|
return 'id';
|
|
31
34
|
for (const [name, field] of Object.entries(target.getFields())) {
|
|
32
|
-
if (field.
|
|
35
|
+
if (Role.of(field).isPrimary)
|
|
33
36
|
return toSnakeCase(name);
|
|
34
37
|
}
|
|
35
38
|
return 'id'; // declared no primary() field — defensive, shouldn't happen
|
|
@@ -53,7 +56,7 @@ function primaryColumnOf(target) {
|
|
|
53
56
|
* the default convention, wrong if the app ALSO overrides `tableName` for it.
|
|
54
57
|
*/
|
|
55
58
|
function referenceFor(field, resolve, tableNameOf, hosted) {
|
|
56
|
-
const relation = field.
|
|
59
|
+
const relation = Role.of(field).relation;
|
|
57
60
|
if (!relation || relation.kind !== 'one')
|
|
58
61
|
return undefined;
|
|
59
62
|
const target = relation.to();
|
|
@@ -72,7 +75,7 @@ function referenceFor(field, resolve, tableNameOf, hosted) {
|
|
|
72
75
|
// `ref(Subscription)` while the table was in the very batch being built. Everything
|
|
73
76
|
// else that resolves a relation target already resolves it by name, for the same
|
|
74
77
|
// reason — a target rebuilt from a card is a `{ name }` stand-in.
|
|
75
|
-
const key =
|
|
78
|
+
const key = lowerFirst(target.name ?? '');
|
|
76
79
|
if (hosted.elsewhere.has(key))
|
|
77
80
|
return undefined;
|
|
78
81
|
if (!hosted.here.has(key)) {
|
|
@@ -80,41 +83,43 @@ function referenceFor(field, resolve, tableNameOf, hosted) {
|
|
|
80
83
|
`Check the entity is scanned, and that \`sources\` spells its name the same way.`);
|
|
81
84
|
}
|
|
82
85
|
}
|
|
83
|
-
const table = mapped ?? resolve(
|
|
86
|
+
const table = mapped ?? resolve(lowerFirst(target.name ?? ''));
|
|
84
87
|
const column = primaryColumnOf(target);
|
|
85
88
|
return relation.onDelete ? { table, column, onDelete: relation.onDelete } : { table, column };
|
|
86
89
|
}
|
|
87
|
-
function toColumn(fieldName, field, resolve, tableNameOf, hosted) {
|
|
90
|
+
function toColumn(fieldName, field, resolve, tableNameOf, hosted, stated) {
|
|
88
91
|
// The column type comes from the `shape` axis alone. `anatomy` strips the
|
|
89
92
|
// nullable union so a nullable integer stays an integer instead of falling
|
|
90
93
|
// through to text.
|
|
91
94
|
const { base, nullable } = Anatomy.of(field.shape);
|
|
92
|
-
const
|
|
95
|
+
const lifecycle = Lifecycle.of(field);
|
|
93
96
|
const column = {
|
|
94
97
|
field: fieldName,
|
|
95
98
|
name: toSnakeCase(fieldName),
|
|
96
99
|
shape: base,
|
|
97
100
|
nullable,
|
|
98
|
-
primary: field.
|
|
101
|
+
primary: Role.of(field).isPrimary,
|
|
99
102
|
};
|
|
100
103
|
const bounds = boundsOf(base);
|
|
101
104
|
if (bounds)
|
|
102
105
|
column.bounds = bounds;
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
+
const literal = lifecycle.literal;
|
|
107
|
+
if (literal)
|
|
108
|
+
column.default = literal.value;
|
|
106
109
|
// A primary key is already unique and already indexed — saying it twice would emit a
|
|
107
110
|
// redundant constraint on every engine. So would indexing what `unique` constrains.
|
|
108
111
|
// Only a constraint of ONE becomes a column constraint; a group of several is a table
|
|
109
112
|
// constraint, emitted once from `uniqueGroups` rather than once per member column.
|
|
110
|
-
const soleUnique = (field
|
|
113
|
+
const soleUnique = FieldGroup.on(field, Unique).some((group) => group.members.length <= 1);
|
|
111
114
|
if (soleUnique && !column.primary)
|
|
112
115
|
column.unique = true;
|
|
113
|
-
if (field.
|
|
116
|
+
if (Role.of(field).isIndexed && !column.primary && !column.unique)
|
|
114
117
|
column.index = true;
|
|
115
118
|
const references = referenceFor(field, resolve, tableNameOf, hosted);
|
|
116
119
|
if (references)
|
|
117
120
|
column.references = references;
|
|
121
|
+
if (stated)
|
|
122
|
+
column.stated = stated;
|
|
118
123
|
return column;
|
|
119
124
|
}
|
|
120
125
|
/**
|
|
@@ -126,17 +131,19 @@ function toColumn(fieldName, field, resolve, tableNameOf, hosted) {
|
|
|
126
131
|
*
|
|
127
132
|
* A lone card has no live relation targets, so a `ref()` falls back to the conventions
|
|
128
133
|
* `referenceFor`/`primaryColumnOf` already document (name-derived table, `id` as the key).
|
|
129
|
-
* Pass a
|
|
134
|
+
* Pass a descriptor through `Bundle.toSchemas` first when the FKs matter — it resolves the
|
|
130
135
|
* targets, and its output is the live-class case again.
|
|
131
136
|
*/
|
|
132
137
|
export function toTable(tableName, entity, relations) {
|
|
133
138
|
const resolve = relations?.resolve ?? toTableName;
|
|
134
139
|
const fields = fieldsOf(entity);
|
|
140
|
+
// Read off the entity, since that is where it is declared and addressed by field key.
|
|
141
|
+
const stated = schemaOf(entity).getAdapters()?.sql;
|
|
135
142
|
const columns = [];
|
|
136
143
|
for (const [fieldName, field] of Object.entries(fields)) {
|
|
137
144
|
if (!isStored(field))
|
|
138
145
|
continue;
|
|
139
|
-
columns.push(toColumn(fieldName, field, resolve, relations?.tableNameOf, relations?.hosted));
|
|
146
|
+
columns.push(toColumn(fieldName, field, resolve, relations?.tableNameOf, relations?.hosted, stated?.[fieldName]));
|
|
140
147
|
}
|
|
141
148
|
const primaries = columns.filter((column) => column.primary).map((column) => column.name);
|
|
142
149
|
const stored = new Set(columns.map((column) => column.name));
|
|
@@ -148,8 +155,8 @@ export function toTable(tableName, entity, relations) {
|
|
|
148
155
|
// against a column that will not exist.
|
|
149
156
|
const groups = new Map();
|
|
150
157
|
for (const [fieldName, field] of Object.entries(fields)) {
|
|
151
|
-
for (const group of field
|
|
152
|
-
const members =
|
|
158
|
+
for (const group of FieldGroup.on(field, Unique)) {
|
|
159
|
+
const members = group.resolvedOn(fieldName).members.map(toSnakeCase);
|
|
153
160
|
if (members.length > 1 && members.every((column) => stored.has(column))) {
|
|
154
161
|
groups.set(members.join(' '), members);
|
|
155
162
|
}
|
|
@@ -184,11 +191,11 @@ export function toTableName(name) {
|
|
|
184
191
|
}
|
|
185
192
|
/**
|
|
186
193
|
* Does this schema come from another one? `Post` answers no, `Post.pick(…)` answers
|
|
187
|
-
* `Post` — a
|
|
188
|
-
*
|
|
194
|
+
* `Post` — a card carries the same origin for every projection. Recognised by that FORM,
|
|
195
|
+
* never by a brand.
|
|
189
196
|
*/
|
|
190
197
|
function isDerivation(source) {
|
|
191
|
-
return source.
|
|
198
|
+
return schemaOf(source).derivation !== undefined;
|
|
192
199
|
}
|
|
193
200
|
/**
|
|
194
201
|
* A stored derivation must be able to say HOW OLD it is.
|
|
@@ -202,19 +209,19 @@ function isDerivation(source) {
|
|
|
202
209
|
* An entity is untouched: it is not a copy of anything, and its rows are the truth.
|
|
203
210
|
*/
|
|
204
211
|
function refuseUndated(name, source) {
|
|
205
|
-
const dated = Object.values(fieldsOf(source)).some((field) => field.
|
|
212
|
+
const dated = Object.values(fieldsOf(source)).some((field) => Lifecycle.of(field).stampedOnUpdate);
|
|
206
213
|
if (dated)
|
|
207
214
|
return;
|
|
208
215
|
throw new Error(`${name} is stored as a derivation but carries no \`updated()\` field — a copy that ` +
|
|
209
216
|
`cannot say when it was pulled reads exactly like live rows. Add one, or drop it from \`sources\`.`);
|
|
210
217
|
}
|
|
211
218
|
function collectEntities(app) {
|
|
212
|
-
const stored = new Set((app.materialize ?? []).map((name) =>
|
|
219
|
+
const stored = new Set((app.materialize ?? []).map((name) => lowerFirst(name)));
|
|
213
220
|
const entries = [];
|
|
214
221
|
for (const frond of app.fronds) {
|
|
215
222
|
for (const entry of frond.entities) {
|
|
216
223
|
if (isDerivation(entry.entityClass)) {
|
|
217
|
-
if (!stored.has(
|
|
224
|
+
if (!stored.has(lowerFirst(entry.name)))
|
|
218
225
|
continue;
|
|
219
226
|
refuseUndated(entry.name, entry.entityClass);
|
|
220
227
|
}
|
|
@@ -236,9 +243,8 @@ function collectEntities(app) {
|
|
|
236
243
|
export function toTables(app, resolve) {
|
|
237
244
|
const entries = collectEntities(app);
|
|
238
245
|
const tableNameOf = new Map(entries.map((entry) => [entry.entityClass, resolve(entry.name)]));
|
|
239
|
-
const lower = (name) => name.charAt(0).toLowerCase() + name.slice(1);
|
|
240
246
|
const hosted = app.elsewhere
|
|
241
|
-
? { here: new Set(entries.map((entry) =>
|
|
247
|
+
? { here: new Set(entries.map((entry) => lowerFirst(entry.name))), elsewhere: new Set(app.elsewhere.map(lowerFirst)) }
|
|
242
248
|
: undefined;
|
|
243
249
|
return entries.map((entry) => toTable(resolve(entry.name), entry.entityClass, { resolve, tableNameOf, hosted }));
|
|
244
250
|
}
|
package/dist/table.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"table.js","sourceRoot":"","sources":["../src/table.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"table.js","sourceRoot":"","sources":["../src/table.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fougere/adapter-sql",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0-alpha.0",
|
|
4
4
|
"description": "Entity → SQL tables through Kysely (SQLite, PostgreSQL, MySQL, SQL Server).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"fougere",
|
|
@@ -26,14 +26,21 @@
|
|
|
26
26
|
".": {
|
|
27
27
|
"types": "./dist/index.d.ts",
|
|
28
28
|
"import": "./dist/index.js"
|
|
29
|
+
},
|
|
30
|
+
"./sqlite": {
|
|
31
|
+
"types": "./dist/sqlite.d.ts",
|
|
32
|
+
"import": "./dist/sqlite.js"
|
|
29
33
|
}
|
|
30
34
|
},
|
|
31
35
|
"files": [
|
|
32
|
-
"dist"
|
|
36
|
+
"dist",
|
|
37
|
+
"src"
|
|
33
38
|
],
|
|
34
39
|
"dependencies": {
|
|
35
40
|
"better-sqlite3": "^13.0.3",
|
|
36
|
-
"
|
|
41
|
+
"dequal": "^2.0.3",
|
|
42
|
+
"@fougere/core": "0.4.0-alpha.0",
|
|
43
|
+
"@fougere/schema": "0.4.0-alpha.0"
|
|
37
44
|
},
|
|
38
45
|
"peerDependencies": {
|
|
39
46
|
"kysely": ">=0.28.17"
|
|
@@ -42,7 +49,7 @@
|
|
|
42
49
|
"@types/better-sqlite3": "^7.6.13",
|
|
43
50
|
"@types/node": "^22.0.0",
|
|
44
51
|
"kysely": "^0.28.17",
|
|
45
|
-
"@fougere/core": "0.
|
|
52
|
+
"@fougere/core": "0.4.0-alpha.0"
|
|
46
53
|
},
|
|
47
54
|
"publishConfig": {
|
|
48
55
|
"access": "public"
|
package/src/check.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shape → CHECK constraints.
|
|
3
|
+
*
|
|
4
|
+
* `oneOf`, `min`, `max` were declared on the field and read by the façade alone: a
|
|
5
|
+
* handler writing through the ORM put `status: 'brouillon'` in a column that declares
|
|
6
|
+
* two values, and nothing said a word. The rule was in the schema; no one held it on
|
|
7
|
+
* that path.
|
|
8
|
+
*
|
|
9
|
+
* So the storage learns what the shape already says. `validate` judges what a client
|
|
10
|
+
* proposes; this holds what anyone writes — including us.
|
|
11
|
+
*
|
|
12
|
+
* Only the keywords a database can decide alone. `pattern` and `format` are left to
|
|
13
|
+
* the façade: regex dialects diverge (POSIX here, PCRE there, nothing in SQLite
|
|
14
|
+
* without an extension), and a constraint that means something different per engine
|
|
15
|
+
* is worse than none.
|
|
16
|
+
*
|
|
17
|
+
* Every value is inlined with `sql.lit`, not bound: SQLite answers `parameters
|
|
18
|
+
* prohibited in CHECK constraints`, and a constraint is part of the schema rather
|
|
19
|
+
* than of a query. The values are the author's own literals — `oneOf('draft', …)`,
|
|
20
|
+
* `max: 160` — never anything a request carried, and Kysely escapes them.
|
|
21
|
+
*/
|
|
22
|
+
import { sql, type Expression, type SqlBool } from 'kysely';
|
|
23
|
+
import type { ColumnDef } from './table.js';
|
|
24
|
+
|
|
25
|
+
/** What a column's shape can be checked for, beyond its type. */
|
|
26
|
+
export interface ShapeBounds {
|
|
27
|
+
enum?: readonly (string | number)[];
|
|
28
|
+
minLength?: number;
|
|
29
|
+
maxLength?: number;
|
|
30
|
+
minimum?: number;
|
|
31
|
+
maximum?: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The CHECK expression for one column, or nothing when its shape bounds nothing.
|
|
36
|
+
*
|
|
37
|
+
* A nullable column passes when it holds `null`: `NOT NULL` is the axis that decides
|
|
38
|
+
* presence, and stacking the two would make `optional()` unwritable.
|
|
39
|
+
*/
|
|
40
|
+
export function checkFor(column: ColumnDef): Expression<SqlBool> | undefined {
|
|
41
|
+
const bounds = column.bounds;
|
|
42
|
+
if (!bounds) return undefined;
|
|
43
|
+
|
|
44
|
+
const col = sql.ref(column.name);
|
|
45
|
+
const terms: Expression<SqlBool>[] = [];
|
|
46
|
+
|
|
47
|
+
if (bounds.enum?.length) {
|
|
48
|
+
terms.push(sql<SqlBool>`${col} in (${sql.join(bounds.enum.map((v) => sql.lit(v)))})`);
|
|
49
|
+
}
|
|
50
|
+
// Length, not value: a text bound is about the string, and `maxLength` is already
|
|
51
|
+
// spent on the column type where the dialect uses it (varchar(n)) — stating it
|
|
52
|
+
// again costs nothing and holds where the type does not (text columns).
|
|
53
|
+
if (bounds.minLength !== undefined) terms.push(sql<SqlBool>`length(${col}) >= ${sql.lit(bounds.minLength)}`);
|
|
54
|
+
if (bounds.maxLength !== undefined) terms.push(sql<SqlBool>`length(${col}) <= ${sql.lit(bounds.maxLength)}`);
|
|
55
|
+
if (bounds.minimum !== undefined) terms.push(sql<SqlBool>`${col} >= ${sql.lit(bounds.minimum)}`);
|
|
56
|
+
if (bounds.maximum !== undefined) terms.push(sql<SqlBool>`${col} <= ${sql.lit(bounds.maximum)}`);
|
|
57
|
+
|
|
58
|
+
if (terms.length === 0) return undefined;
|
|
59
|
+
|
|
60
|
+
const all = terms.reduce((left, right) => sql<SqlBool>`${left} and ${right}`);
|
|
61
|
+
|
|
62
|
+
return column.nullable ? sql<SqlBool>`${col} is null or (${all})` : all;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Read the bounds off a field's base shape — the keywords a database can decide. */
|
|
66
|
+
export function boundsOf(shape: Record<string, unknown> | undefined): ShapeBounds | undefined {
|
|
67
|
+
if (!shape) return undefined;
|
|
68
|
+
|
|
69
|
+
const bounds: ShapeBounds = {};
|
|
70
|
+
if (Array.isArray(shape.enum) && shape.enum.length > 0) bounds.enum = shape.enum as string[];
|
|
71
|
+
for (const key of ['minLength', 'maxLength', 'minimum', 'maximum'] as const) {
|
|
72
|
+
if (typeof shape[key] === 'number') bounds[key] = shape[key] as number;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return Object.keys(bounds).length > 0 ? bounds : undefined;
|
|
76
|
+
}
|