@fougere/adapter-sql 0.2.0-alpha.2
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/LICENSE +21 -0
- package/README.md +18 -0
- package/dist/check.d.ts +41 -0
- package/dist/check.d.ts.map +1 -0
- package/dist/check.js +67 -0
- package/dist/check.js.map +1 -0
- package/dist/crud.d.ts +153 -0
- package/dist/crud.d.ts.map +1 -0
- package/dist/crud.js +458 -0
- package/dist/crud.js.map +1 -0
- package/dist/ddl.d.ts +87 -0
- package/dist/ddl.d.ts.map +1 -0
- package/dist/ddl.js +194 -0
- package/dist/ddl.js.map +1 -0
- package/dist/dialect.d.ts +54 -0
- package/dist/dialect.d.ts.map +1 -0
- package/dist/dialect.js +109 -0
- package/dist/dialect.js.map +1 -0
- package/dist/diff.d.ts +86 -0
- package/dist/diff.d.ts.map +1 -0
- package/dist/diff.js +168 -0
- package/dist/diff.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/setup.d.ts +37 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +40 -0
- package/dist/setup.js.map +1 -0
- package/dist/table.d.ts +169 -0
- package/dist/table.d.ts.map +1 -0
- package/dist/table.js +292 -0
- package/dist/table.js.map +1 -0
- package/dist/values.d.ts +32 -0
- package/dist/values.d.ts.map +1 -0
- package/dist/values.js +70 -0
- package/dist/values.js.map +1 -0
- package/package.json +56 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { createTableSQL, createIndexSQL, indexSQL, addForeignKeyConstraintSQL, generateSQL, autoMigrate, compiler } from './ddl.js';
|
|
2
|
+
export type { GenerateOptions, SqlSink } from './ddl.js';
|
|
3
|
+
export { toTable, toTables, toTableName, toSnakeCase, isKeyed, orderTables } from './table.js';
|
|
4
|
+
export type { TableDef, ColumnDef, ColumnShape, ColumnReference, RelationResolve, FkEdge, TableOrder, EntityEntry, FrondLike, AppLike, } from './table.js';
|
|
5
|
+
export { dialects, resolveDialect, sqliteDialect, pgDialect, mysqlDialect, mssqlDialect, } from './dialect.js';
|
|
6
|
+
export type { Dialect, DialectName } from './dialect.js';
|
|
7
|
+
export { SqlEntityOrm, createOrmFactory } from './crud.js';
|
|
8
|
+
export type { OrmFactoryOptions } from './crud.js';
|
|
9
|
+
export { codecFor, codecsOf } from './values.js';
|
|
10
|
+
export type { ValueCodec } from './values.js';
|
|
11
|
+
export { setupSqlite, setupKysely, sqlSink } from './setup.js';
|
|
12
|
+
export type { Setup, SetupOptions, SqliteSetup, SqliteSetupOptions } from './setup.js';
|
|
13
|
+
export { actualState, desiredTables, delta, orderChanges, changeSQL, planMigration, migrate } from './diff.js';
|
|
14
|
+
export type { SchemaState, Change } from './diff.js';
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,QAAQ,EAAE,0BAA0B,EAAE,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACpI,YAAY,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACzD,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC/F,YAAY,EACV,QAAQ,EACR,SAAS,EACT,WAAW,EACX,eAAe,EACf,eAAe,EACf,MAAM,EACN,UAAU,EACV,WAAW,EACX,SAAS,EACT,OAAO,GACR,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,QAAQ,EACR,cAAc,EACd,aAAa,EACb,SAAS,EACT,YAAY,EACZ,YAAY,GACb,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC3D,YAAY,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACjD,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAC/D,YAAY,EAAE,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACvF,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC/G,YAAY,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { createTableSQL, createIndexSQL, indexSQL, addForeignKeyConstraintSQL, generateSQL, autoMigrate, compiler } from './ddl.js';
|
|
2
|
+
export { toTable, toTables, toTableName, toSnakeCase, isKeyed, orderTables } from './table.js';
|
|
3
|
+
export { dialects, resolveDialect, sqliteDialect, pgDialect, mysqlDialect, mssqlDialect, } from './dialect.js';
|
|
4
|
+
export { SqlEntityOrm, createOrmFactory } from './crud.js';
|
|
5
|
+
export { codecFor, codecsOf } from './values.js';
|
|
6
|
+
export { setupSqlite, setupKysely, sqlSink } from './setup.js';
|
|
7
|
+
export { actualState, desiredTables, delta, orderChanges, changeSQL, planMigration, migrate } from './diff.js';
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,QAAQ,EAAE,0BAA0B,EAAE,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEpI,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAa/F,OAAO,EACL,QAAQ,EACR,cAAc,EACd,aAAa,EACb,SAAS,EACT,YAAY,EACZ,YAAY,GACb,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAE3D,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEjD,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE/D,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC"}
|
package/dist/setup.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Kysely, type Dialect as KyselyDialect } from 'kysely';
|
|
2
|
+
import Database from 'better-sqlite3';
|
|
3
|
+
import { createOrmFactory, type OrmFactoryOptions } from './crud.js';
|
|
4
|
+
import type { DialectName } from './dialect.js';
|
|
5
|
+
import type { SqlSink } from './ddl.js';
|
|
6
|
+
export interface SetupOptions {
|
|
7
|
+
/** Override naming for specific entities (e.g. better-auth wants singular table names). */
|
|
8
|
+
ormFactoryOptions?: OrmFactoryOptions;
|
|
9
|
+
}
|
|
10
|
+
export interface SqliteSetupOptions extends SetupOptions {
|
|
11
|
+
/** Filesystem path to the database. Defaults to a project-local file. */
|
|
12
|
+
path?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface Setup {
|
|
15
|
+
dialect: DialectName;
|
|
16
|
+
ormFactory: ReturnType<typeof createOrmFactory>;
|
|
17
|
+
/** Runs raw statements — what `autoMigrate` writes through. */
|
|
18
|
+
sink: SqlSink;
|
|
19
|
+
/**
|
|
20
|
+
* The Kysely instance, for what precedes any entity: `migrate(app, setup)` writes the
|
|
21
|
+
* schema through it, and a script may need it before a container exists.
|
|
22
|
+
*
|
|
23
|
+
* It is not the way to reach data from inside an app — that is the injected `EntityOrm`,
|
|
24
|
+
* whose `client` gives the same handle while keeping the scope of its entity.
|
|
25
|
+
*/
|
|
26
|
+
db: Kysely<any>;
|
|
27
|
+
}
|
|
28
|
+
export interface SqliteSetup extends Setup {
|
|
29
|
+
/** The raw handle, for pragmas and synchronous exec. */
|
|
30
|
+
sqlite: Database.Database;
|
|
31
|
+
}
|
|
32
|
+
/** A sink that runs statements through Kysely — works on every engine. */
|
|
33
|
+
export declare function sqlSink(db: Kysely<any>): SqlSink;
|
|
34
|
+
/** Wrap any Kysely dialect — Postgres, MySQL, SQL Server. */
|
|
35
|
+
export declare function setupKysely(kyselyDialect: KyselyDialect, dialect: DialectName, opts?: SetupOptions): Setup;
|
|
36
|
+
export declare function setupSqlite(opts?: SqliteSetupOptions): SqliteSetup;
|
|
37
|
+
//# sourceMappingURL=setup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,MAAM,EAAsB,KAAK,OAAO,IAAI,aAAa,EAAE,MAAM,QAAQ,CAAC;AACnF,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,gBAAgB,EAAE,KAAK,iBAAiB,EAAE,MAAM,WAAW,CAAC;AACrE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAExC,MAAM,WAAW,YAAY;IAC3B,2FAA2F;IAC3F,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;CACvC;AAED,MAAM,WAAW,kBAAmB,SAAQ,YAAY;IACtD,yEAAyE;IACzE,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,KAAK;IACpB,OAAO,EAAE,WAAW,CAAC;IACrB,UAAU,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;IAChD,+DAA+D;IAC/D,IAAI,EAAE,OAAO,CAAC;IACd;;;;;;OAMG;IACH,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;CACjB;AAED,MAAM,WAAW,WAAY,SAAQ,KAAK;IACxC,wDAAwD;IACxD,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC;CAC3B;AAED,0EAA0E;AAC1E,wBAAgB,OAAO,CAAC,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,GAAG,OAAO,CAEhD;AAED,6DAA6D;AAC7D,wBAAgB,WAAW,CACzB,aAAa,EAAE,aAAa,EAC5B,OAAO,EAAE,WAAW,EACpB,IAAI,GAAE,YAAiB,GACtB,KAAK,CAGP;AAED,wBAAgB,WAAW,CAAC,IAAI,GAAE,kBAAuB,GAAG,WAAW,CAetE"}
|
package/dist/setup.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage setup — convention for SQLite, an escape hatch for the rest.
|
|
3
|
+
*
|
|
4
|
+
* SQLite is the default a first run meets, so it gets a concrete helper that
|
|
5
|
+
* owns its driver. Every other engine needs a driver Fougere has no business
|
|
6
|
+
* choosing (`pg`, `mysql2`, `tedious`), so the caller builds the Kysely dialect
|
|
7
|
+
* and hands it over — no dynamic import, no optional dependency.
|
|
8
|
+
*/
|
|
9
|
+
import { mkdirSync } from 'node:fs';
|
|
10
|
+
import { dirname } from 'node:path';
|
|
11
|
+
import { Kysely, SqliteDialect, sql } from 'kysely';
|
|
12
|
+
import Database from 'better-sqlite3';
|
|
13
|
+
import { createOrmFactory } from './crud.js';
|
|
14
|
+
/** A sink that runs statements through Kysely — works on every engine. */
|
|
15
|
+
export function sqlSink(db) {
|
|
16
|
+
return { execute: (statement) => sql.raw(statement).execute(db) };
|
|
17
|
+
}
|
|
18
|
+
/** Wrap any Kysely dialect — Postgres, MySQL, SQL Server. */
|
|
19
|
+
export function setupKysely(kyselyDialect, dialect, opts = {}) {
|
|
20
|
+
const db = new Kysely({ dialect: kyselyDialect });
|
|
21
|
+
return { db, dialect, ormFactory: createOrmFactory(db, opts.ormFactoryOptions, dialect), sink: sqlSink(db) };
|
|
22
|
+
}
|
|
23
|
+
export function setupSqlite(opts = {}) {
|
|
24
|
+
const path = opts.path ?? 'fougere.db';
|
|
25
|
+
// A file-backed DB needs its directory — SQLite won't create it.
|
|
26
|
+
if (path !== ':memory:')
|
|
27
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
28
|
+
const sqlite = new Database(path);
|
|
29
|
+
sqlite.pragma('journal_mode = WAL');
|
|
30
|
+
sqlite.pragma('foreign_keys = ON');
|
|
31
|
+
const db = new Kysely({ dialect: new SqliteDialect({ database: sqlite }) });
|
|
32
|
+
return {
|
|
33
|
+
db,
|
|
34
|
+
sqlite,
|
|
35
|
+
dialect: 'sqlite',
|
|
36
|
+
ormFactory: createOrmFactory(db, opts.ormFactoryOptions, 'sqlite'),
|
|
37
|
+
sink: sqlSink(db),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=setup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,EAAiC,MAAM,QAAQ,CAAC;AACnF,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,gBAAgB,EAA0B,MAAM,WAAW,CAAC;AAkCrE,0EAA0E;AAC1E,MAAM,UAAU,OAAO,CAAC,EAAe;IACrC,OAAO,EAAE,OAAO,EAAE,CAAC,SAAiB,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC;AAC5E,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,WAAW,CACzB,aAA4B,EAC5B,OAAoB,EACpB,IAAI,GAAiB,EAAE;IAEvB,MAAM,EAAE,GAAG,IAAI,MAAM,CAAM,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC,CAAC;IACvD,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,gBAAgB,CAAC,EAAE,EAAE,IAAI,CAAC,iBAAiB,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC;AAC/G,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,IAAI,GAAuB,EAAE;IACvD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,YAAY,CAAC;IACvC,iEAAiE;IACjE,IAAI,IAAI,KAAK,UAAU;QAAE,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACvE,MAAM,MAAM,GAAG,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC;IAClC,MAAM,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;IACpC,MAAM,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC;IACnC,MAAM,EAAE,GAAG,IAAI,MAAM,CAAM,EAAE,OAAO,EAAE,IAAI,aAAa,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;IACjF,OAAO;QACL,EAAE;QACF,MAAM;QACN,OAAO,EAAE,QAAQ;QACjB,UAAU,EAAE,gBAAgB,CAAC,EAAE,EAAE,IAAI,CAAC,iBAAiB,EAAE,QAAQ,CAAC;QAClE,IAAI,EAAE,OAAO,CAAC,EAAE,CAAC;KAClB,CAAC;AACJ,CAAC"}
|
package/dist/table.d.ts
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entity → table description, with no SQL in sight.
|
|
3
|
+
*
|
|
4
|
+
* This is the neutral middle term: one projection reads the entity's axes and
|
|
5
|
+
* produces a `TableDef`; a `Dialect` turns that into SQL. Neither half knows the
|
|
6
|
+
* other — the entity never mentions a column type, the dialect never mentions a
|
|
7
|
+
* field. Adding a dialect touches only the second half.
|
|
8
|
+
*/
|
|
9
|
+
import { type SchemaSource } from '@fougere/schema';
|
|
10
|
+
import { type ShapeBounds } from './check.js';
|
|
11
|
+
/** The shape keywords a dialect needs to choose a column type. */
|
|
12
|
+
export interface ColumnShape {
|
|
13
|
+
type?: string;
|
|
14
|
+
format?: string;
|
|
15
|
+
maxLength?: number;
|
|
16
|
+
}
|
|
17
|
+
/** One column, described by the axes — never by a SQL type. */
|
|
18
|
+
export interface ColumnDef {
|
|
19
|
+
/** Field key on the entity. */
|
|
20
|
+
field: string;
|
|
21
|
+
/** SQL column name (snake_case). */
|
|
22
|
+
name: string;
|
|
23
|
+
/** The value shape, nullable union already unwrapped. */
|
|
24
|
+
shape?: ColumnShape;
|
|
25
|
+
nullable: boolean;
|
|
26
|
+
primary: boolean;
|
|
27
|
+
/** A literal default (`lifecycle.create.value`), when the field declares one. */
|
|
28
|
+
default?: unknown;
|
|
29
|
+
/** `role.unique` — realized as a column constraint the database enforces. */
|
|
30
|
+
unique?: boolean;
|
|
31
|
+
/** `role.index` — realized as a separate `CREATE INDEX`, never a constraint. */
|
|
32
|
+
index?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* What the shape bounds beyond its type — `oneOf`, `min`, `max`. Realized as a
|
|
35
|
+
* `CHECK`, so the rule holds on every write and not only at the façade.
|
|
36
|
+
*/
|
|
37
|
+
bounds?: ShapeBounds;
|
|
38
|
+
/** The FK target, from `role.relation` when it's a `ref()` (kind `'one'`). */
|
|
39
|
+
references?: ColumnReference;
|
|
40
|
+
}
|
|
41
|
+
export interface ColumnReference {
|
|
42
|
+
table: string;
|
|
43
|
+
column: string;
|
|
44
|
+
onDelete?: 'cascade' | 'restrict' | 'set null';
|
|
45
|
+
}
|
|
46
|
+
export interface TableDef {
|
|
47
|
+
name: string;
|
|
48
|
+
columns: ColumnDef[];
|
|
49
|
+
/** PK column names when the key is composite — empty for a simple key. */
|
|
50
|
+
compositePrimary: string[];
|
|
51
|
+
/**
|
|
52
|
+
* Column groups unique together, from `entity(fields, { unique: [...] })`.
|
|
53
|
+
* A single-field group is left to the column's own `unique` — this is the
|
|
54
|
+
* table-level form, for facts no column can hold alone.
|
|
55
|
+
*/
|
|
56
|
+
uniqueGroups: string[][];
|
|
57
|
+
}
|
|
58
|
+
/** camelCase → snake_case */
|
|
59
|
+
export declare function toSnakeCase(str: string): string;
|
|
60
|
+
/** How a `ref()` field's target table+column is resolved — see {@link referenceFor}. */
|
|
61
|
+
export interface RelationResolve {
|
|
62
|
+
/** Same resolver used for every entity's own table (default or a custom `tableName`). */
|
|
63
|
+
resolve: (name: string) => string;
|
|
64
|
+
/** Live entity class → its already-resolved table name, reused instead of re-derived. */
|
|
65
|
+
tableNameOf?: Map<SchemaSource, string>;
|
|
66
|
+
/** Which entities this batch holds and which live in another source — decided by NAME. */
|
|
67
|
+
hosted?: HostedNames;
|
|
68
|
+
}
|
|
69
|
+
/** The two name sets a cross-source batch is read against — see {@link referenceFor}. */
|
|
70
|
+
export interface HostedNames {
|
|
71
|
+
/** Registration names in THIS batch. */
|
|
72
|
+
here: ReadonlySet<string>;
|
|
73
|
+
/** Registration names the app hosts in another source — see {@link AppLike.elsewhere}. */
|
|
74
|
+
elsewhere: ReadonlySet<string>;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Describe one entity as a table — the single reader of the axes.
|
|
78
|
+
*
|
|
79
|
+
* Takes the entity as a live class or as a card. Read ONCE into `fields`: `fieldsOf`
|
|
80
|
+
* reconstructs a descriptor on each call, so re-reading per loop would rebuild the schema
|
|
81
|
+
* as many times as this function iterates.
|
|
82
|
+
*
|
|
83
|
+
* A lone card has no live relation targets, so a `ref()` falls back to the conventions
|
|
84
|
+
* `referenceFor`/`primaryColumnOf` already document (name-derived table, `id` as the key).
|
|
85
|
+
* Pass a bundle through `reconstructSet` first when the FKs matter — it resolves the
|
|
86
|
+
* targets, and its output is the live-class case again.
|
|
87
|
+
*/
|
|
88
|
+
export declare function toTable(tableName: string, entity: SchemaSource, relations?: RelationResolve): TableDef;
|
|
89
|
+
/**
|
|
90
|
+
* Is this column part of a key? MySQL and SQL Server refuse an unbounded text
|
|
91
|
+
* column in a primary key or an index, so the dialect needs to know.
|
|
92
|
+
*
|
|
93
|
+
* `unique` and `index` count, and until they could be declared nothing did but the
|
|
94
|
+
* primary key — the comment above already said "or an index" while the code answered
|
|
95
|
+
* for the key alone, because no vocabulary word produced one to answer for.
|
|
96
|
+
*/
|
|
97
|
+
export declare function isKeyed(table: TableDef, column: ColumnDef): boolean;
|
|
98
|
+
/** camelCase → snake_case + plural */
|
|
99
|
+
export declare function toTableName(name: string): string;
|
|
100
|
+
export interface EntityEntry {
|
|
101
|
+
name: string;
|
|
102
|
+
/** A live class in-process, a card from a frond whose class never crossed. */
|
|
103
|
+
entityClass: SchemaSource;
|
|
104
|
+
}
|
|
105
|
+
export interface FrondLike {
|
|
106
|
+
name: string;
|
|
107
|
+
entities: EntityEntry[];
|
|
108
|
+
}
|
|
109
|
+
export interface AppLike {
|
|
110
|
+
fronds: FrondLike[];
|
|
111
|
+
/** Auth runtime entities are migrated alongside scanned fronds when present. */
|
|
112
|
+
auth?: {
|
|
113
|
+
entities: Record<string, SchemaSource>;
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* Entities this app hosts in ANOTHER source — named so a miss can be read.
|
|
117
|
+
*
|
|
118
|
+
* Without it every miss looked alike, so a `ref()` fell back to a derived table name
|
|
119
|
+
* and the constraint was emitted against a table that might not exist. Absent means
|
|
120
|
+
* one source, where a miss can only be a mistake.
|
|
121
|
+
*/
|
|
122
|
+
elsewhere?: string[];
|
|
123
|
+
/**
|
|
124
|
+
* The DERIVATIONS this app stores — registration names.
|
|
125
|
+
*
|
|
126
|
+
* A derivation makes no table by default: `Post.pick('id','title')` describes an
|
|
127
|
+
* answer, not a place rows live, and one dropped under `entities/` used to get a
|
|
128
|
+
* table of its own — measured on a real app, where a projection of an archived
|
|
129
|
+
* entity created a duplicate in the OTHER database.
|
|
130
|
+
*
|
|
131
|
+
* Naming it in `sources:` is the opt-in, and it changes what the thing is: a stored
|
|
132
|
+
* derivation is a dated COPY, not a projection, and it owes what any copy owes —
|
|
133
|
+
* who fills it, and how old it is.
|
|
134
|
+
*/
|
|
135
|
+
materialize?: string[];
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Every entity an app hosts, as FK-aware tables — the shared middle step behind
|
|
139
|
+
* `generateSQL` (a from-scratch create pass) and `desiredTables` (the diff's
|
|
140
|
+
* target state). Builds the identity map once (see `referenceFor`'s doc) so a
|
|
141
|
+
* `ref()` target reuses the SAME resolved name as the entity's own table.
|
|
142
|
+
*/
|
|
143
|
+
export declare function toTables(app: AppLike, resolve: (name: string) => string): TableDef[];
|
|
144
|
+
export interface FkEdge {
|
|
145
|
+
table: TableDef;
|
|
146
|
+
column: ColumnDef;
|
|
147
|
+
}
|
|
148
|
+
export interface TableOrder {
|
|
149
|
+
/** Tables in dependency order — a `ref()` target always precedes its referrer. */
|
|
150
|
+
ordered: TableDef[];
|
|
151
|
+
/** FK columns whose target could not be ordered first — a cycle. Constrain after creation. */
|
|
152
|
+
deferred: FkEdge[];
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Order a table set so a `ref()`'s target always exists before the table that
|
|
156
|
+
* points at it — required by every engine except SQLite, which resolves FK
|
|
157
|
+
* targets lazily and accepts any order (and has no `ALTER TABLE ADD CONSTRAINT`
|
|
158
|
+
* to close a cycle with — a caller on that dialect skips this function entirely).
|
|
159
|
+
*
|
|
160
|
+
* A cycle (`Post → Author → Post`, legal in the model — role.ts's relation
|
|
161
|
+
* thunk exists precisely so two entities can reference each other) has no such
|
|
162
|
+
* order: the loop is broken by deferring ONE of its edges per remaining cycle —
|
|
163
|
+
* that FK is added after every table exists, instead of inline. A self-reference
|
|
164
|
+
* (`parentId: ref(() => Category)`) is not a cycle here: a table may always
|
|
165
|
+
* reference its own not-yet-populated rows inline, standard support across
|
|
166
|
+
* every engine — so it's excluded from the dependency graph entirely.
|
|
167
|
+
*/
|
|
168
|
+
export declare function orderTables(tables: TableDef[]): TableOrder;
|
|
169
|
+
//# sourceMappingURL=table.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"table.d.ts","sourceRoot":"","sources":["../src/table.ts"],"names":[],"mappings":"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"}
|
package/dist/table.js
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entity → table description, with no SQL in sight.
|
|
3
|
+
*
|
|
4
|
+
* This is the neutral middle term: one projection reads the entity's axes and
|
|
5
|
+
* produces a `TableDef`; a `Dialect` turns that into SQL. Neither half knows the
|
|
6
|
+
* other — the entity never mentions a column type, the dialect never mentions a
|
|
7
|
+
* field. Adding a dialect touches only the second half.
|
|
8
|
+
*/
|
|
9
|
+
import { Anatomy, fieldsOf, registrationKeyOf, uniqueMembers } from '@fougere/schema';
|
|
10
|
+
import { boundsOf } from './check.js';
|
|
11
|
+
/** camelCase → snake_case */
|
|
12
|
+
export function toSnakeCase(str) {
|
|
13
|
+
return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* A `many` relation owns no column — the join lives on the other side. Every
|
|
17
|
+
* other field becomes exactly one column.
|
|
18
|
+
*/
|
|
19
|
+
function isStored(field) {
|
|
20
|
+
return field.role?.relation?.kind !== 'many';
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The target's primary key column. A live thunk (an in-process entity) answers
|
|
24
|
+
* for real; a relation reconstructed from a lone card (`reconstruct()`, no
|
|
25
|
+
* bundle — `packages/schema/src/projections/card.ts:18-22`) has lost it to a
|
|
26
|
+
* name stand-in with no `getFields` — the convention there is to assume `id`.
|
|
27
|
+
*/
|
|
28
|
+
function primaryColumnOf(target) {
|
|
29
|
+
if (typeof target.getFields !== 'function')
|
|
30
|
+
return 'id';
|
|
31
|
+
for (const [name, field] of Object.entries(target.getFields())) {
|
|
32
|
+
if (field.role?.primary)
|
|
33
|
+
return toSnakeCase(name);
|
|
34
|
+
}
|
|
35
|
+
return 'id'; // declared no primary() field — defensive, shouldn't happen
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The FK target for a `ref()` field: the table it points at plus its PK column.
|
|
39
|
+
*
|
|
40
|
+
* `tableNameOf` is an identity map — built once per app generation pass, see
|
|
41
|
+
* {@link toTables} — from a LIVE entity class to the table name already resolved
|
|
42
|
+
* for it. Reusing that name (instead of re-deriving one from the class name) is
|
|
43
|
+
* what keeps a custom `tableName` resolver honest: `demos/schema-ecommerce`
|
|
44
|
+
* names `Category`'s table `"categories"` (an irregular plural its resolver
|
|
45
|
+
* special-cases) — re-deriving from `Category.name` through the DEFAULT
|
|
46
|
+
* convention would silently produce `"categorys"` instead.
|
|
47
|
+
*
|
|
48
|
+
* A miss (the target isn't part of this batch — a cross-frond target, or a live
|
|
49
|
+
* class the app substituted for one a package hardcoded — e.g.
|
|
50
|
+
* `@fougere/auth-better`'s `AuthSession` always points at its own default
|
|
51
|
+
* `AuthUser`, never at whatever `opts.user` the app actually registered) falls
|
|
52
|
+
* back to deriving the name from the class — correct when that class follows
|
|
53
|
+
* the default convention, wrong if the app ALSO overrides `tableName` for it.
|
|
54
|
+
*/
|
|
55
|
+
function referenceFor(field, resolve, tableNameOf, hosted) {
|
|
56
|
+
const relation = field.role?.relation;
|
|
57
|
+
if (!relation || relation.kind !== 'one')
|
|
58
|
+
return undefined;
|
|
59
|
+
const target = relation.to();
|
|
60
|
+
const mapped = tableNameOf?.get(target);
|
|
61
|
+
if (mapped === undefined && hosted !== undefined) {
|
|
62
|
+
// Three answers, and only the first two are ordinary. Two databases share no
|
|
63
|
+
// constraint, so a target that lives in another source gets a column and no
|
|
64
|
+
// foreign key — the relation survives, the pretence does not. A target no source
|
|
65
|
+
// hosts is a mistake, and staying silent would turn a bad registration into what
|
|
66
|
+
// reads exactly like a source boundary.
|
|
67
|
+
//
|
|
68
|
+
// Decided on the NAME and never on object identity: a target reached through two
|
|
69
|
+
// specifiers (`./Subscription.js` from a sibling entity, `Subscription.ts` from
|
|
70
|
+
// the scan) is TWO class objects for one entity, so the identity map misses on an
|
|
71
|
+
// entity that is right there. Measured on a real app, where this threw on
|
|
72
|
+
// `ref(Subscription)` while the table was in the very batch being built. Everything
|
|
73
|
+
// else that resolves a relation target already resolves it by name, for the same
|
|
74
|
+
// reason — a target rebuilt from a card is a `{ name }` stand-in.
|
|
75
|
+
const key = registrationKeyOf(target.name ?? '');
|
|
76
|
+
if (hosted.elsewhere.has(key))
|
|
77
|
+
return undefined;
|
|
78
|
+
if (!hosted.here.has(key)) {
|
|
79
|
+
throw new Error(`ref(${target.name ?? '?'}): no source hosts it — it is in neither this batch nor another one. ` +
|
|
80
|
+
`Check the entity is scanned, and that \`sources\` spells its name the same way.`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const table = mapped ?? resolve(registrationKeyOf(target.name ?? ''));
|
|
84
|
+
const column = primaryColumnOf(target);
|
|
85
|
+
return relation.onDelete ? { table, column, onDelete: relation.onDelete } : { table, column };
|
|
86
|
+
}
|
|
87
|
+
function toColumn(fieldName, field, resolve, tableNameOf, hosted) {
|
|
88
|
+
// The column type comes from the `shape` axis alone. `anatomy` strips the
|
|
89
|
+
// nullable union so a nullable integer stays an integer instead of falling
|
|
90
|
+
// through to text.
|
|
91
|
+
const { base, nullable } = Anatomy.of(field.shape);
|
|
92
|
+
const create = field.lifecycle?.create;
|
|
93
|
+
const column = {
|
|
94
|
+
field: fieldName,
|
|
95
|
+
name: toSnakeCase(fieldName),
|
|
96
|
+
shape: base,
|
|
97
|
+
nullable,
|
|
98
|
+
primary: field.role?.primary === true,
|
|
99
|
+
};
|
|
100
|
+
const bounds = boundsOf(base);
|
|
101
|
+
if (bounds)
|
|
102
|
+
column.bounds = bounds;
|
|
103
|
+
if (typeof create === 'object' && create !== null && 'value' in create) {
|
|
104
|
+
column.default = create.value;
|
|
105
|
+
}
|
|
106
|
+
// A primary key is already unique and already indexed — saying it twice would emit a
|
|
107
|
+
// redundant constraint on every engine. So would indexing what `unique` constrains.
|
|
108
|
+
// Only a constraint of ONE becomes a column constraint; a group of several is a table
|
|
109
|
+
// constraint, emitted once from `uniqueGroups` rather than once per member column.
|
|
110
|
+
const soleUnique = (field.role?.unique ?? []).some((group) => group.length <= 1);
|
|
111
|
+
if (soleUnique && !column.primary)
|
|
112
|
+
column.unique = true;
|
|
113
|
+
if (field.role?.index === true && !column.primary && !column.unique)
|
|
114
|
+
column.index = true;
|
|
115
|
+
const references = referenceFor(field, resolve, tableNameOf, hosted);
|
|
116
|
+
if (references)
|
|
117
|
+
column.references = references;
|
|
118
|
+
return column;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Describe one entity as a table — the single reader of the axes.
|
|
122
|
+
*
|
|
123
|
+
* Takes the entity as a live class or as a card. Read ONCE into `fields`: `fieldsOf`
|
|
124
|
+
* reconstructs a descriptor on each call, so re-reading per loop would rebuild the schema
|
|
125
|
+
* as many times as this function iterates.
|
|
126
|
+
*
|
|
127
|
+
* A lone card has no live relation targets, so a `ref()` falls back to the conventions
|
|
128
|
+
* `referenceFor`/`primaryColumnOf` already document (name-derived table, `id` as the key).
|
|
129
|
+
* Pass a bundle through `reconstructSet` first when the FKs matter — it resolves the
|
|
130
|
+
* targets, and its output is the live-class case again.
|
|
131
|
+
*/
|
|
132
|
+
export function toTable(tableName, entity, relations) {
|
|
133
|
+
const resolve = relations?.resolve ?? toTableName;
|
|
134
|
+
const fields = fieldsOf(entity);
|
|
135
|
+
const columns = [];
|
|
136
|
+
for (const [fieldName, field] of Object.entries(fields)) {
|
|
137
|
+
if (!isStored(field))
|
|
138
|
+
continue;
|
|
139
|
+
columns.push(toColumn(fieldName, field, resolve, relations?.tableNameOf, relations?.hosted));
|
|
140
|
+
}
|
|
141
|
+
const primaries = columns.filter((column) => column.primary).map((column) => column.name);
|
|
142
|
+
const stored = new Set(columns.map((column) => column.name));
|
|
143
|
+
// Read off the fields, not off `getUnique()`: a card has no entity-level declaration to
|
|
144
|
+
// offer, and the members carry the same fact either way. One reader for both forms.
|
|
145
|
+
//
|
|
146
|
+
// Declared in field names, realized in column names — and a group that names a field the
|
|
147
|
+
// storage does not keep is not enforceable, so it is dropped here rather than emitted
|
|
148
|
+
// against a column that will not exist.
|
|
149
|
+
const groups = new Map();
|
|
150
|
+
for (const [fieldName, field] of Object.entries(fields)) {
|
|
151
|
+
for (const group of field.role?.unique ?? []) {
|
|
152
|
+
const members = uniqueMembers(group, fieldName).map(toSnakeCase);
|
|
153
|
+
if (members.length > 1 && members.every((column) => stored.has(column))) {
|
|
154
|
+
groups.set(members.join(' '), members);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const uniqueGroups = [...groups.values()];
|
|
159
|
+
return {
|
|
160
|
+
name: tableName,
|
|
161
|
+
columns,
|
|
162
|
+
compositePrimary: primaries.length > 1 ? primaries : [],
|
|
163
|
+
uniqueGroups,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Is this column part of a key? MySQL and SQL Server refuse an unbounded text
|
|
168
|
+
* column in a primary key or an index, so the dialect needs to know.
|
|
169
|
+
*
|
|
170
|
+
* `unique` and `index` count, and until they could be declared nothing did but the
|
|
171
|
+
* primary key — the comment above already said "or an index" while the code answered
|
|
172
|
+
* for the key alone, because no vocabulary word produced one to answer for.
|
|
173
|
+
*/
|
|
174
|
+
export function isKeyed(table, column) {
|
|
175
|
+
return column.primary
|
|
176
|
+
|| column.unique === true
|
|
177
|
+
|| column.index === true
|
|
178
|
+
|| table.compositePrimary.includes(column.name);
|
|
179
|
+
}
|
|
180
|
+
// ─── App-wide entity collection — shared by generateSQL and desiredTables ──
|
|
181
|
+
/** camelCase → snake_case + plural */
|
|
182
|
+
export function toTableName(name) {
|
|
183
|
+
return name.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`) + 's';
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Does this schema come from another one? `Post` answers no, `Post.pick(…)` answers
|
|
187
|
+
* `Post` — a derivation carries its origin, which `sourceNameOf` already reads for two
|
|
188
|
+
* other projections. Recognised by that FORM, never by a brand.
|
|
189
|
+
*/
|
|
190
|
+
function isDerivation(source) {
|
|
191
|
+
return source.source !== undefined;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* A stored derivation must be able to say HOW OLD it is.
|
|
195
|
+
*
|
|
196
|
+
* It is a copy, and a copy read as if it were live is the silent loss this whole
|
|
197
|
+
* design exists to refuse: rows from yesterday typed exactly like rows from now. The
|
|
198
|
+
* vocabulary already carries the answer — a field with `update: 'now'` records when
|
|
199
|
+
* this row last changed HERE, which for a copy is when it was last pulled. So nothing
|
|
200
|
+
* new is declared; what is new is that forgetting it is refused, at boot, by name.
|
|
201
|
+
*
|
|
202
|
+
* An entity is untouched: it is not a copy of anything, and its rows are the truth.
|
|
203
|
+
*/
|
|
204
|
+
function refuseUndated(name, source) {
|
|
205
|
+
const dated = Object.values(fieldsOf(source)).some((field) => field.lifecycle?.update === 'now');
|
|
206
|
+
if (dated)
|
|
207
|
+
return;
|
|
208
|
+
throw new Error(`${name} is stored as a derivation but carries no \`updated()\` field — a copy that ` +
|
|
209
|
+
`cannot say when it was pulled reads exactly like live rows. Add one, or drop it from \`sources\`.`);
|
|
210
|
+
}
|
|
211
|
+
function collectEntities(app) {
|
|
212
|
+
const stored = new Set((app.materialize ?? []).map((name) => registrationKeyOf(name)));
|
|
213
|
+
const entries = [];
|
|
214
|
+
for (const frond of app.fronds) {
|
|
215
|
+
for (const entry of frond.entities) {
|
|
216
|
+
if (isDerivation(entry.entityClass)) {
|
|
217
|
+
if (!stored.has(registrationKeyOf(entry.name)))
|
|
218
|
+
continue;
|
|
219
|
+
refuseUndated(entry.name, entry.entityClass);
|
|
220
|
+
}
|
|
221
|
+
entries.push(entry);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (app.auth?.entities) {
|
|
225
|
+
for (const [name, entityClass] of Object.entries(app.auth.entities))
|
|
226
|
+
entries.push({ name, entityClass });
|
|
227
|
+
}
|
|
228
|
+
return entries;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Every entity an app hosts, as FK-aware tables — the shared middle step behind
|
|
232
|
+
* `generateSQL` (a from-scratch create pass) and `desiredTables` (the diff's
|
|
233
|
+
* target state). Builds the identity map once (see `referenceFor`'s doc) so a
|
|
234
|
+
* `ref()` target reuses the SAME resolved name as the entity's own table.
|
|
235
|
+
*/
|
|
236
|
+
export function toTables(app, resolve) {
|
|
237
|
+
const entries = collectEntities(app);
|
|
238
|
+
const tableNameOf = new Map(entries.map((entry) => [entry.entityClass, resolve(entry.name)]));
|
|
239
|
+
const lower = (name) => name.charAt(0).toLowerCase() + name.slice(1);
|
|
240
|
+
const hosted = app.elsewhere
|
|
241
|
+
? { here: new Set(entries.map((entry) => lower(entry.name))), elsewhere: new Set(app.elsewhere.map(lower)) }
|
|
242
|
+
: undefined;
|
|
243
|
+
return entries.map((entry) => toTable(resolve(entry.name), entry.entityClass, { resolve, tableNameOf, hosted }));
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Order a table set so a `ref()`'s target always exists before the table that
|
|
247
|
+
* points at it — required by every engine except SQLite, which resolves FK
|
|
248
|
+
* targets lazily and accepts any order (and has no `ALTER TABLE ADD CONSTRAINT`
|
|
249
|
+
* to close a cycle with — a caller on that dialect skips this function entirely).
|
|
250
|
+
*
|
|
251
|
+
* A cycle (`Post → Author → Post`, legal in the model — role.ts's relation
|
|
252
|
+
* thunk exists precisely so two entities can reference each other) has no such
|
|
253
|
+
* order: the loop is broken by deferring ONE of its edges per remaining cycle —
|
|
254
|
+
* that FK is added after every table exists, instead of inline. A self-reference
|
|
255
|
+
* (`parentId: ref(() => Category)`) is not a cycle here: a table may always
|
|
256
|
+
* reference its own not-yet-populated rows inline, standard support across
|
|
257
|
+
* every engine — so it's excluded from the dependency graph entirely.
|
|
258
|
+
*/
|
|
259
|
+
export function orderTables(tables) {
|
|
260
|
+
const byName = new Map(tables.map((table) => [table.name, table]));
|
|
261
|
+
const needs = new Map(tables.map((table) => [
|
|
262
|
+
table.name,
|
|
263
|
+
new Set(table.columns
|
|
264
|
+
.filter((c) => c.references && c.references.table !== table.name && byName.has(c.references.table))
|
|
265
|
+
.map((c) => c.references.table)),
|
|
266
|
+
]));
|
|
267
|
+
const ordered = [];
|
|
268
|
+
const deferred = [];
|
|
269
|
+
const done = new Set();
|
|
270
|
+
while (needs.size > 0) {
|
|
271
|
+
const ready = [...needs.keys()].find((name) => [...needs.get(name)].every((dep) => done.has(dep)));
|
|
272
|
+
if (ready) {
|
|
273
|
+
ordered.push(byName.get(ready));
|
|
274
|
+
done.add(ready);
|
|
275
|
+
needs.delete(ready);
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
// Every table left waits on another table left — a cycle. Break it by
|
|
279
|
+
// deferring one edge: every column of the first remaining table that points
|
|
280
|
+
// at its first unmet dependency.
|
|
281
|
+
const [name, deps] = [...needs.entries()][0];
|
|
282
|
+
const dep = [...deps].find((d) => !done.has(d));
|
|
283
|
+
const table = byName.get(name);
|
|
284
|
+
for (const column of table.columns) {
|
|
285
|
+
if (column.references?.table === dep)
|
|
286
|
+
deferred.push({ table, column });
|
|
287
|
+
}
|
|
288
|
+
deps.delete(dep);
|
|
289
|
+
}
|
|
290
|
+
return { ordered, deferred };
|
|
291
|
+
}
|
|
292
|
+
//# sourceMappingURL=table.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"table.js","sourceRoot":"","sources":["../src/table.ts"],"names":[],"mappings":"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"}
|
package/dist/values.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The values a driver accepts, and the values an entity declares.
|
|
3
|
+
*
|
|
4
|
+
* A driver binds numbers, strings, buffers and null — nothing else. An entity declares
|
|
5
|
+
* booleans, dates, lists and objects. Something has to sit between the two, and until
|
|
6
|
+
* now nothing did: `done: bool()` threw at insert ("SQLite3 can only bind numbers,
|
|
7
|
+
* strings, bigints, buffers, and null"), a `date()` could only be written as the ISO
|
|
8
|
+
* string its own type forbids, and both came back as whatever the column held.
|
|
9
|
+
*
|
|
10
|
+
* The pair below is derived from the column's shape alone — no new declaration, no axis
|
|
11
|
+
* to read. It lives in the storage adapter because that is where the driver's limits are
|
|
12
|
+
* known; the entity keeps saying `boolean` and `Date`.
|
|
13
|
+
*
|
|
14
|
+
* Drivers differ in how much they already do (Postgres hands back real booleans and
|
|
15
|
+
* Dates, SQLite hands back 0/1 and text), so every read guards on what it actually got
|
|
16
|
+
* instead of assuming.
|
|
17
|
+
*/
|
|
18
|
+
import type { ColumnShape } from './table.js';
|
|
19
|
+
export interface ValueCodec {
|
|
20
|
+
/** Entity value → what the driver can bind. */
|
|
21
|
+
write(value: unknown): unknown;
|
|
22
|
+
/** What the driver returned → the value the entity declares. */
|
|
23
|
+
read(value: unknown): unknown;
|
|
24
|
+
}
|
|
25
|
+
/** The pair a column's shape calls for — identity when the driver already accepts it. */
|
|
26
|
+
export declare function codecFor(shape?: ColumnShape): ValueCodec;
|
|
27
|
+
/** Field name → codec, for every column that needs one. Identity columns are omitted. */
|
|
28
|
+
export declare function codecsOf(columns: {
|
|
29
|
+
field: string;
|
|
30
|
+
shape?: ColumnShape;
|
|
31
|
+
}[]): Map<string, ValueCodec>;
|
|
32
|
+
//# sourceMappingURL=values.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"values.d.ts","sourceRoot":"","sources":["../src/values.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,MAAM,WAAW,UAAU;IACzB,+CAA+C;IAC/C,KAAK,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;IAC/B,gEAAgE;IAChE,IAAI,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;CAC/B;AAsDD,yFAAyF;AACzF,wBAAgB,QAAQ,CAAC,KAAK,CAAC,EAAE,WAAW,GAAG,UAAU,CAexD;AAED,yFAAyF;AACzF,wBAAgB,QAAQ,CAAC,OAAO,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,WAAW,CAAA;CAAE,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAOnG"}
|