@assemora/database 0.1.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/LICENSE +202 -0
- package/README.md +163 -0
- package/dist/adapter.d.ts +133 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +26 -0
- package/dist/adapter.js.map +1 -0
- package/dist/errors.d.ts +48 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +54 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/join-table.d.ts +66 -0
- package/dist/join-table.d.ts.map +1 -0
- package/dist/join-table.js +265 -0
- package/dist/join-table.js.map +1 -0
- package/dist/memory.d.ts +22 -0
- package/dist/memory.d.ts.map +1 -0
- package/dist/memory.js +398 -0
- package/dist/memory.js.map +1 -0
- package/dist/query-ast.d.ts +79 -0
- package/dist/query-ast.d.ts.map +1 -0
- package/dist/query-ast.js +29 -0
- package/dist/query-ast.js.map +1 -0
- package/dist/schema-diff.d.ts +187 -0
- package/dist/schema-diff.d.ts.map +1 -0
- package/dist/schema-diff.js +473 -0
- package/dist/schema-diff.js.map +1 -0
- package/package.json +36 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@assemora/database` — the neutral contract between the data layer and a database.
|
|
3
|
+
*
|
|
4
|
+
* It owns the Query AST (SPEC.md §30), the adapter interface (SPEC.md §31) and the
|
|
5
|
+
* schema diff a migration generator works from (SPEC.md §34), and nothing in it is
|
|
6
|
+
* specific to any engine. `@assemora/database-postgres` implements the contract with
|
|
7
|
+
* Drizzle inside; the in-memory adapter here implements the same contract for tests
|
|
8
|
+
* and for the phases where PostgreSQL does not exist yet.
|
|
9
|
+
*/
|
|
10
|
+
export type { ColumnDescriptor, ColumnType, DatabaseAdapter, DatabaseContext, DatabaseSchema, RelationDescriptor, RelationKind, TableDescriptor, } from './adapter.js';
|
|
11
|
+
export { LOCALE_COLUMN, TRANSLATION_OF_COLUMN, UNSPECIFIED_LOCALE } from './adapter.js';
|
|
12
|
+
export { isSchemaNotApplied, SCHEMA_NOT_APPLIED, schemaNotApplied, } from './errors.js';
|
|
13
|
+
export { joinTableDescriptor, type PivotAddress, pivotAddress, withJoinTables, } from './join-table.js';
|
|
14
|
+
export { createMemoryAdapter, type MemoryAdapter, type Row } from './memory.js';
|
|
15
|
+
export { type Combinator, type Comparison, type ComparisonOperator, type Condition, type ConditionGroup, comparison, emptyQuery, group, type JsonComparison, jsonContains, jsonEquals, jsonLike, type Order, orComparison, type QueryAst, type QueryOperation, type RelationLoad, type SortDirection, } from './query-ast.js';
|
|
16
|
+
export { type ChangeRisk, type ColumnAdded, type ColumnEnumChanged, type ColumnNullabilityChanged, type ColumnRemoved, type ColumnTypeChanged, type ColumnUniquenessChanged, describeChange, diffSchema, type ForeignKeyAdded, type ForeignKeyRemoved, type IndexAdded, type IndexRemoved, isDestructive, mayFailOnExistingRows, type PrimaryKeyMoved, type SchemaChange, type SchemaDiff, type TableAdded, type TableRemoved, } from './schema-diff.js';
|
|
17
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,YAAY,EACV,gBAAgB,EAChB,UAAU,EACV,eAAe,EACf,eAAe,EACf,cAAc,EACd,kBAAkB,EAClB,YAAY,EACZ,eAAe,GAChB,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AACvF,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,aAAa,CAAA;AACpB,OAAO,EACL,mBAAmB,EACnB,KAAK,YAAY,EACjB,YAAY,EACZ,cAAc,GACf,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,mBAAmB,EAAE,KAAK,aAAa,EAAE,KAAK,GAAG,EAAE,MAAM,aAAa,CAAA;AAC/E,OAAO,EACL,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,kBAAkB,EACvB,KAAK,SAAS,EACd,KAAK,cAAc,EACnB,UAAU,EACV,UAAU,EACV,KAAK,EACL,KAAK,cAAc,EACnB,YAAY,EACZ,UAAU,EACV,QAAQ,EACR,KAAK,KAAK,EACV,YAAY,EACZ,KAAK,QAAQ,EACb,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,aAAa,GACnB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EACL,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACtB,KAAK,wBAAwB,EAC7B,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,uBAAuB,EAC5B,cAAc,EACd,UAAU,EACV,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,aAAa,EACb,qBAAqB,EACrB,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,YAAY,GAClB,MAAM,kBAAkB,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@assemora/database` — the neutral contract between the data layer and a database.
|
|
3
|
+
*
|
|
4
|
+
* It owns the Query AST (SPEC.md §30), the adapter interface (SPEC.md §31) and the
|
|
5
|
+
* schema diff a migration generator works from (SPEC.md §34), and nothing in it is
|
|
6
|
+
* specific to any engine. `@assemora/database-postgres` implements the contract with
|
|
7
|
+
* Drizzle inside; the in-memory adapter here implements the same contract for tests
|
|
8
|
+
* and for the phases where PostgreSQL does not exist yet.
|
|
9
|
+
*/
|
|
10
|
+
export { LOCALE_COLUMN, TRANSLATION_OF_COLUMN, UNSPECIFIED_LOCALE } from './adapter.js';
|
|
11
|
+
export { isSchemaNotApplied, SCHEMA_NOT_APPLIED, schemaNotApplied, } from './errors.js';
|
|
12
|
+
export { joinTableDescriptor, pivotAddress, withJoinTables, } from './join-table.js';
|
|
13
|
+
export { createMemoryAdapter } from './memory.js';
|
|
14
|
+
export { comparison, emptyQuery, group, jsonContains, jsonEquals, jsonLike, orComparison, } from './query-ast.js';
|
|
15
|
+
export { describeChange, diffSchema, isDestructive, mayFailOnExistingRows, } from './schema-diff.js';
|
|
16
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAYH,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AACvF,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,aAAa,CAAA;AACpB,OAAO,EACL,mBAAmB,EAEnB,YAAY,EACZ,cAAc,GACf,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,mBAAmB,EAAgC,MAAM,aAAa,CAAA;AAC/E,OAAO,EAML,UAAU,EACV,UAAU,EACV,KAAK,EAEL,YAAY,EACZ,UAAU,EACV,QAAQ,EAER,YAAY,GAKb,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAQL,cAAc,EACd,UAAU,EAKV,aAAa,EACb,qBAAqB,GAMtB,MAAM,kBAAkB,CAAA"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { RelationDescriptor, TableDescriptor } from './adapter.js';
|
|
2
|
+
/**
|
|
3
|
+
* The join table a `belongsToMany` is stored in (SPEC.md §23).
|
|
4
|
+
*
|
|
5
|
+
* Named by `through` when the relation declares one, and by the two table names
|
|
6
|
+
* otherwise — sorted, so both sides of a mutual relation name the same table.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* joinTableDescriptor(User.descriptor, rolesRelation, Role.descriptor)
|
|
10
|
+
* // { name: 'roles_users', columns: [roleId, userId], uniqueTogether: [['roleId', 'userId']] }
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export declare const joinTableDescriptor: (owner: TableDescriptor, relation: RelationDescriptor, target?: TableDescriptor) => TableDescriptor;
|
|
14
|
+
/**
|
|
15
|
+
* Where one row's links live, and what identifies them.
|
|
16
|
+
*
|
|
17
|
+
* Everything the pivot verbs of SPEC.md §24 need, and nothing an adapter has to learn:
|
|
18
|
+
* `attach` is an insert of the two columns, `detach` a delete of the two conditions,
|
|
19
|
+
* `sync` both inside one transaction — all of it ordinary Query AST.
|
|
20
|
+
*/
|
|
21
|
+
export type PivotAddress = {
|
|
22
|
+
/** The join table, ready to be handed to `DatabaseContext.table`. */
|
|
23
|
+
readonly table: TableDescriptor;
|
|
24
|
+
/** The column holding the owner's key. */
|
|
25
|
+
readonly ownerColumn: string;
|
|
26
|
+
/** The column holding the related row's key. */
|
|
27
|
+
readonly relatedColumn: string;
|
|
28
|
+
/** What the owner's row stores in the key the link points at. */
|
|
29
|
+
readonly ownerValue: unknown;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Addresses the join table for one row (SPEC.md §24).
|
|
33
|
+
*
|
|
34
|
+
* ```ts
|
|
35
|
+
* const pivot = pivotAddress(User.descriptor, roles, user.toJSON(), Role.descriptor)
|
|
36
|
+
*
|
|
37
|
+
* await adapter.execute(
|
|
38
|
+
* {
|
|
39
|
+
* ...emptyQuery(pivot.table.name, 'insert'),
|
|
40
|
+
* data: { [pivot.ownerColumn]: pivot.ownerValue, [pivot.relatedColumn]: roleId },
|
|
41
|
+
* },
|
|
42
|
+
* { table: pivot.table },
|
|
43
|
+
* )
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export declare const pivotAddress: (owner: TableDescriptor, relation: RelationDescriptor, row: Readonly<Record<string, unknown>>, target?: TableDescriptor) => PivotAddress;
|
|
47
|
+
/**
|
|
48
|
+
* The tables a schema really has: the declared ones, plus a join table per
|
|
49
|
+
* `belongsToMany` (SPEC.md §23, §34).
|
|
50
|
+
*
|
|
51
|
+
* Idempotent, and that is what makes it safe to call anywhere. A join table declares
|
|
52
|
+
* no `belongsToMany` of its own, so expanding an expanded schema adds nothing — a
|
|
53
|
+
* snapshot that already holds the join table compares clean against a registry that
|
|
54
|
+
* derives it.
|
|
55
|
+
*
|
|
56
|
+
* A model declared for a table a relation derives is refused, rather than allowed to
|
|
57
|
+
* win. Only the DDL would ever read it: the pivot verbs of SPEC.md §24 write the two
|
|
58
|
+
* derived columns and nothing else, so a declared pivot carrying a surrogate key or a
|
|
59
|
+
* `joinedAt` is a table `attach` cannot fill, and one carrying exactly the two keys is
|
|
60
|
+
* a second descriptor for a name the adapter already builds. Keeping the declaration
|
|
61
|
+
* for the DDL and deriving the writes is the disagreement this whole file exists to
|
|
62
|
+
* prevent — a pivot with columns of its own is a model like any other, and it is
|
|
63
|
+
* declared with two `belongsTo` relations rather than with `through`.
|
|
64
|
+
*/
|
|
65
|
+
export declare const withJoinTables: (tables: readonly TableDescriptor[]) => readonly TableDescriptor[];
|
|
66
|
+
//# sourceMappingURL=join-table.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"join-table.d.ts","sourceRoot":"","sources":["../src/join-table.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAGV,kBAAkB,EAClB,eAAe,EAChB,MAAM,cAAc,CAAA;AAmKrB;;;;;;;;;;GAUG;AACH,eAAO,MAAM,mBAAmB,UACvB,eAAe,YACZ,kBAAkB,WACnB,eAAe,KACvB,eAAwF,CAAA;AAE3F;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,qEAAqE;IACrE,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAA;IAC/B,0CAA0C;IAC1C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,gDAAgD;IAChD,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAC9B,iEAAiE;IACjE,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAA;CAC7B,CAAA;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,YAAY,UAChB,eAAe,YACZ,kBAAkB,OACvB,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,WAC7B,eAAe,KACvB,YAoBF,CAAA;AAoCD;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,cAAc,WAAY,SAAS,eAAe,EAAE,KAAG,SAAS,eAAe,EAyD3F,CAAA"}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Join tables (SPEC.md §23, §24).
|
|
3
|
+
*
|
|
4
|
+
* `belongsToMany` is the one relation that stores nothing on either table it links:
|
|
5
|
+
* the pairs live in a third table no model declares. Three subsystems need that
|
|
6
|
+
* table — the data layer writes the pivot verbs to it, the DDL creates it, the diff
|
|
7
|
+
* notices it arriving — and deriving it three times is how they come to disagree. So
|
|
8
|
+
* it is derived once, here, as an ordinary `TableDescriptor`. Everything that already
|
|
9
|
+
* knows what to do with a table knows what to do with this one, and a pivot write is
|
|
10
|
+
* an ordinary insert through the Query AST rather than an operation of its own
|
|
11
|
+
* (ADR-0001, ADR-0013).
|
|
12
|
+
*
|
|
13
|
+
* The derivation is symmetric on purpose. Both sides of a mutual `belongsToMany`
|
|
14
|
+
* derive the same name, the same two columns in the same order and the same foreign
|
|
15
|
+
* keys, so a schema holding `users.roles` and `roles.users` holds one join table
|
|
16
|
+
* rather than two that disagree.
|
|
17
|
+
*/
|
|
18
|
+
import { AssemoraError } from '@assemora/core';
|
|
19
|
+
/**
|
|
20
|
+
* `users` → `userId`. Crude on purpose, and the same rule a `hasMany` foreign key
|
|
21
|
+
* follows, so a join column is named the way every other reference to that table is.
|
|
22
|
+
* `foreignPivotKey` and `relatedPivotKey` are there for where it does not fit.
|
|
23
|
+
*/
|
|
24
|
+
const pivotColumnFor = (table) => `${table.endsWith('s') ? table.slice(0, -1) : table}Id`;
|
|
25
|
+
/**
|
|
26
|
+
* What a key column holds when the descriptor holding it is not to hand.
|
|
27
|
+
*
|
|
28
|
+
* `model()` names `id` as the primary key of a table that marks no column primary,
|
|
29
|
+
* and a primary key nobody typed is a `uuid` in every starter (SPEC.md §17).
|
|
30
|
+
*/
|
|
31
|
+
const DEFAULT_KEY = 'id';
|
|
32
|
+
const DEFAULT_KEY_TYPE = 'uuid';
|
|
33
|
+
const typeOfKey = (table, key) => table?.columns.find((column) => column.name === key)?.type ?? DEFAULT_KEY_TYPE;
|
|
34
|
+
const assertBelongsToMany = (owner, relation) => {
|
|
35
|
+
if (relation.kind === 'belongsToMany')
|
|
36
|
+
return;
|
|
37
|
+
throw new AssemoraError('NOT_A_JOIN_TABLE', `"${owner.name}.${relation.name}" is a ${relation.kind} relation, and only belongsToMany is stored in a join table`, { status: 500 });
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* The two ends of the link, in the order they were declared in.
|
|
41
|
+
*
|
|
42
|
+
* `ownerKey` means here what it means for every other kind — the column on the owner
|
|
43
|
+
* that the reference points at — so the only thing `belongsToMany` needs of its own is
|
|
44
|
+
* a name for each of the two join columns.
|
|
45
|
+
*
|
|
46
|
+
* `target` is optional because a relation names the other table but does not carry it.
|
|
47
|
+
* Passing it is what makes the related column take the type of the key it holds;
|
|
48
|
+
* `withJoinTables` always does, and a caller with one table in its hand gets the
|
|
49
|
+
* default rather than an error.
|
|
50
|
+
*/
|
|
51
|
+
const sidesOf = (owner, relation, target) => {
|
|
52
|
+
assertBelongsToMany(owner, relation);
|
|
53
|
+
const relatedKey = target?.primaryKey ?? DEFAULT_KEY;
|
|
54
|
+
const sides = {
|
|
55
|
+
owner: {
|
|
56
|
+
table: owner.name,
|
|
57
|
+
column: relation.foreignPivotKey ?? pivotColumnFor(owner.name),
|
|
58
|
+
key: relation.ownerKey,
|
|
59
|
+
type: typeOfKey(owner, relation.ownerKey),
|
|
60
|
+
},
|
|
61
|
+
related: {
|
|
62
|
+
table: relation.target,
|
|
63
|
+
column: relation.relatedPivotKey ?? pivotColumnFor(relation.target),
|
|
64
|
+
key: relatedKey,
|
|
65
|
+
type: typeOfKey(target, relatedKey),
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
if (sides.owner.column === sides.related.column) {
|
|
69
|
+
throw new AssemoraError('INVALID_JOIN_TABLE', `The join table for "${owner.name}.${relation.name}" would hold two columns named "${sides.owner.column}", ` +
|
|
70
|
+
'so the two sides of the link cannot be told apart. Name them with `foreignPivotKey` and ' +
|
|
71
|
+
'`relatedPivotKey` — a relation whose target is its own table always has to.', { status: 500 });
|
|
72
|
+
}
|
|
73
|
+
return sides;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* The two ends in a canonical order, which is what makes the derivation symmetric.
|
|
77
|
+
*
|
|
78
|
+
* Sorted by table name, and by column name where one table links to itself: both
|
|
79
|
+
* declarations of a mutual relation then produce the identical descriptor, down to the
|
|
80
|
+
* order of the columns, so `withJoinTables` can recognise them as one table instead of
|
|
81
|
+
* reporting two.
|
|
82
|
+
*
|
|
83
|
+
* Compared by code point rather than by `localeCompare`, because the name this decides
|
|
84
|
+
* is written into a migration and read back on another machine. A locale is not a
|
|
85
|
+
* shared fact, and one that orders two tables the other way round would rename a table
|
|
86
|
+
* that nobody touched.
|
|
87
|
+
*/
|
|
88
|
+
const ordered = (first, second) => {
|
|
89
|
+
const inOrder = first.table === second.table ? first.column <= second.column : first.table < second.table;
|
|
90
|
+
return inOrder ? [first, second] : [second, first];
|
|
91
|
+
};
|
|
92
|
+
const pivotColumn = (side) => ({
|
|
93
|
+
name: side.column,
|
|
94
|
+
type: side.type,
|
|
95
|
+
isPrimary: false,
|
|
96
|
+
isNullable: false,
|
|
97
|
+
// Neither key is unique on its own — a user holds many roles and a role many users.
|
|
98
|
+
// Only the pair is, and `uniqueTogether` on the table is what says so.
|
|
99
|
+
isUnique: false,
|
|
100
|
+
// The foreign key below brings the index with it: a generator indexes the
|
|
101
|
+
// referencing side of every relation it creates, and a second index on the same
|
|
102
|
+
// column would only cost writes.
|
|
103
|
+
isIndexed: false,
|
|
104
|
+
hasDefault: false,
|
|
105
|
+
});
|
|
106
|
+
/**
|
|
107
|
+
* A `belongsTo` per side, so the join table is created with real foreign keys.
|
|
108
|
+
*
|
|
109
|
+
* Without them a deleted role leaves its pivot rows behind, and the next load of
|
|
110
|
+
* `user.roles` returns ids pointing at nothing. It also means every generator that
|
|
111
|
+
* already turns a `belongsTo` into a constraint and an index needs nothing new.
|
|
112
|
+
*
|
|
113
|
+
* The relation is named after its column rather than after the table it points at: a
|
|
114
|
+
* table linked to itself has two sides with the same target, and a descriptor holding
|
|
115
|
+
* two relations of one name is one that nothing can address.
|
|
116
|
+
*/
|
|
117
|
+
const pivotRelation = (side) => ({
|
|
118
|
+
name: side.column,
|
|
119
|
+
kind: 'belongsTo',
|
|
120
|
+
target: side.table,
|
|
121
|
+
foreignKey: side.column,
|
|
122
|
+
ownerKey: side.key,
|
|
123
|
+
});
|
|
124
|
+
const describeJoinTable = (sides, through) => {
|
|
125
|
+
const [first, second] = ordered(sides.owner, sides.related);
|
|
126
|
+
return {
|
|
127
|
+
name: through ?? `${first.table}_${second.table}`,
|
|
128
|
+
// A join table has no key of its own: the pair is its identity, and `primaryKey`
|
|
129
|
+
// names a single column. Naming half the pair would tell a generator to make one
|
|
130
|
+
// side unique, which is the opposite of what a link table is for.
|
|
131
|
+
primaryKey: '',
|
|
132
|
+
columns: [pivotColumn(first), pivotColumn(second)],
|
|
133
|
+
relations: [pivotRelation(first), pivotRelation(second)],
|
|
134
|
+
uniqueTogether: [[first.column, second.column]],
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
/**
|
|
138
|
+
* The join table a `belongsToMany` is stored in (SPEC.md §23).
|
|
139
|
+
*
|
|
140
|
+
* Named by `through` when the relation declares one, and by the two table names
|
|
141
|
+
* otherwise — sorted, so both sides of a mutual relation name the same table.
|
|
142
|
+
*
|
|
143
|
+
* ```ts
|
|
144
|
+
* joinTableDescriptor(User.descriptor, rolesRelation, Role.descriptor)
|
|
145
|
+
* // { name: 'roles_users', columns: [roleId, userId], uniqueTogether: [['roleId', 'userId']] }
|
|
146
|
+
* ```
|
|
147
|
+
*/
|
|
148
|
+
export const joinTableDescriptor = (owner, relation, target) => describeJoinTable(sidesOf(owner, relation, target), relation.through);
|
|
149
|
+
/**
|
|
150
|
+
* Addresses the join table for one row (SPEC.md §24).
|
|
151
|
+
*
|
|
152
|
+
* ```ts
|
|
153
|
+
* const pivot = pivotAddress(User.descriptor, roles, user.toJSON(), Role.descriptor)
|
|
154
|
+
*
|
|
155
|
+
* await adapter.execute(
|
|
156
|
+
* {
|
|
157
|
+
* ...emptyQuery(pivot.table.name, 'insert'),
|
|
158
|
+
* data: { [pivot.ownerColumn]: pivot.ownerValue, [pivot.relatedColumn]: roleId },
|
|
159
|
+
* },
|
|
160
|
+
* { table: pivot.table },
|
|
161
|
+
* )
|
|
162
|
+
* ```
|
|
163
|
+
*/
|
|
164
|
+
export const pivotAddress = (owner, relation, row, target) => {
|
|
165
|
+
const sides = sidesOf(owner, relation, target);
|
|
166
|
+
const ownerValue = row[sides.owner.key];
|
|
167
|
+
// A link to a row that was never stored points at nothing, and inserting it writes a
|
|
168
|
+
// pivot row no read can ever join. Refusing here catches it once for every caller.
|
|
169
|
+
if (ownerValue === undefined || ownerValue === null) {
|
|
170
|
+
throw new AssemoraError('UNSAVED_RECORD', `Cannot address "${owner.name}.${relation.name}": the row has no ${sides.owner.key}. Save it first.`, { status: 409 });
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
table: describeJoinTable(sides, relation.through),
|
|
174
|
+
ownerColumn: sides.owner.column,
|
|
175
|
+
relatedColumn: sides.related.column,
|
|
176
|
+
ownerValue,
|
|
177
|
+
};
|
|
178
|
+
};
|
|
179
|
+
/** What two derivations of one join table have to agree on, as one sentence. */
|
|
180
|
+
const signatureOf = (table) => table.relations
|
|
181
|
+
.map((relation) => `${relation.foreignKey} -> ${relation.target}.${relation.ownerKey}`)
|
|
182
|
+
.join(', ');
|
|
183
|
+
/**
|
|
184
|
+
* Everything about a table that is not its name, written down in a fixed order.
|
|
185
|
+
*
|
|
186
|
+
* Two descriptors that agree on this describe the identical table. Used to recognise
|
|
187
|
+
* a derived join table handed back to `withJoinTables` — the expansion is idempotent,
|
|
188
|
+
* so the join table it added last time arrives among the declared ones and must not be
|
|
189
|
+
* mistaken for a model of it.
|
|
190
|
+
*/
|
|
191
|
+
const shapeOf = (table) => [
|
|
192
|
+
table.primaryKey,
|
|
193
|
+
table.softDeleteColumn ?? '',
|
|
194
|
+
table.columns
|
|
195
|
+
.map((column) => `${column.name}:${column.type}:${column.isPrimary}:${column.isNullable}:` +
|
|
196
|
+
`${column.isUnique}:${column.isIndexed}:${column.hasDefault}`)
|
|
197
|
+
.join(','),
|
|
198
|
+
table.relations
|
|
199
|
+
.map((relation) => `${relation.name}:${relation.kind}:${relation.target}:${relation.foreignKey}:${relation.ownerKey}`)
|
|
200
|
+
.join(','),
|
|
201
|
+
(table.uniqueTogether ?? []).map((group) => group.join('+')).join(','),
|
|
202
|
+
].join(' | ');
|
|
203
|
+
/**
|
|
204
|
+
* The tables a schema really has: the declared ones, plus a join table per
|
|
205
|
+
* `belongsToMany` (SPEC.md §23, §34).
|
|
206
|
+
*
|
|
207
|
+
* Idempotent, and that is what makes it safe to call anywhere. A join table declares
|
|
208
|
+
* no `belongsToMany` of its own, so expanding an expanded schema adds nothing — a
|
|
209
|
+
* snapshot that already holds the join table compares clean against a registry that
|
|
210
|
+
* derives it.
|
|
211
|
+
*
|
|
212
|
+
* A model declared for a table a relation derives is refused, rather than allowed to
|
|
213
|
+
* win. Only the DDL would ever read it: the pivot verbs of SPEC.md §24 write the two
|
|
214
|
+
* derived columns and nothing else, so a declared pivot carrying a surrogate key or a
|
|
215
|
+
* `joinedAt` is a table `attach` cannot fill, and one carrying exactly the two keys is
|
|
216
|
+
* a second descriptor for a name the adapter already builds. Keeping the declaration
|
|
217
|
+
* for the DDL and deriving the writes is the disagreement this whole file exists to
|
|
218
|
+
* prevent — a pivot with columns of its own is a model like any other, and it is
|
|
219
|
+
* declared with two `belongsTo` relations rather than with `through`.
|
|
220
|
+
*/
|
|
221
|
+
export const withJoinTables = (tables) => {
|
|
222
|
+
const declared = new Map(tables.map((table) => [table.name, table]));
|
|
223
|
+
const derived = new Map();
|
|
224
|
+
for (const owner of tables) {
|
|
225
|
+
for (const relation of owner.relations) {
|
|
226
|
+
if (relation.kind !== 'belongsToMany')
|
|
227
|
+
continue;
|
|
228
|
+
const from = `${owner.name}.${relation.name}`;
|
|
229
|
+
const table = joinTableDescriptor(owner, relation, declared.get(relation.target));
|
|
230
|
+
const model = declared.get(table.name);
|
|
231
|
+
if (model !== undefined) {
|
|
232
|
+
// The expansion is idempotent, so the join table added by an earlier call
|
|
233
|
+
// arrives here among the declared tables. It is this derivation, not a model
|
|
234
|
+
// of it, and it is skipped exactly as it was before there was a rule.
|
|
235
|
+
if (shapeOf(model) === shapeOf(table))
|
|
236
|
+
continue;
|
|
237
|
+
throw new AssemoraError('DECLARED_JOIN_TABLE', `"${from}" derives the join table "${table.name}", and a model declares it too. ` +
|
|
238
|
+
'A derived pivot holds the two key columns and nothing else, which is all `attach`, ' +
|
|
239
|
+
'`detach` and `sync` write — so a model of it is a table those verbs cannot fill. ' +
|
|
240
|
+
'Either drop the model and let the relation derive the table, or keep the model, ' +
|
|
241
|
+
'give it two `belongsTo` relations and address it as an ordinary table instead of ' +
|
|
242
|
+
'through `belongsToMany`.', { status: 500 });
|
|
243
|
+
}
|
|
244
|
+
const existing = derived.get(table.name);
|
|
245
|
+
if (existing === undefined) {
|
|
246
|
+
derived.set(table.name, { table, from });
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
// Both sides of a mutual relation derive the same table from the same two model
|
|
250
|
+
// names, so a disagreement is always an override stated on one side only. Left
|
|
251
|
+
// alone it reaches the schema diff as two tables with one name, and is reported
|
|
252
|
+
// there as a duplicate descriptor rather than as the declaration that caused it.
|
|
253
|
+
if (signatureOf(existing.table) !== signatureOf(table)) {
|
|
254
|
+
throw new AssemoraError('CONFLICTING_JOIN_TABLE', `"${existing.from}" and "${from}" describe the join table "${table.name}" differently: ` +
|
|
255
|
+
`(${signatureOf(existing.table)}) against (${signatureOf(table)}). ` +
|
|
256
|
+
'Mirror `through`, `foreignPivotKey`, `relatedPivotKey` and `ownerKey` on both sides, ' +
|
|
257
|
+
'or declare a model for the join table.', { status: 500 });
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (derived.size === 0)
|
|
262
|
+
return tables;
|
|
263
|
+
return [...tables, ...[...derived.values()].map((entry) => entry.table)];
|
|
264
|
+
};
|
|
265
|
+
//# sourceMappingURL=join-table.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"join-table.js","sourceRoot":"","sources":["../src/join-table.ts"],"names":[],"mappings":"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"}
|
package/dist/memory.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { DatabaseAdapter } from './adapter.js';
|
|
2
|
+
export type Row = Record<string, unknown>;
|
|
3
|
+
export type MemoryAdapter = DatabaseAdapter & {
|
|
4
|
+
/** The rows a table currently holds, as stored. */
|
|
5
|
+
rows(table: string): readonly Row[];
|
|
6
|
+
seed(table: string, rows: readonly Row[]): void;
|
|
7
|
+
reset(): void;
|
|
8
|
+
/**
|
|
9
|
+
* Counters the tests read; not part of ordinary use.
|
|
10
|
+
*
|
|
11
|
+
* `scanCount()` is what `statementCount()` is on the PostgreSQL adapter: one pass
|
|
12
|
+
* over one table. A relation loaded in a batch costs a fixed number of passes
|
|
13
|
+
* however many rows it is loaded for, and one loaded row by row costs a pass per
|
|
14
|
+
* row — which is how SPEC.md §89 asks for an N+1 to be caught.
|
|
15
|
+
*/
|
|
16
|
+
readonly diagnostics: {
|
|
17
|
+
scanCount(): number;
|
|
18
|
+
reset(): void;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
export declare const createMemoryAdapter: (seed?: Readonly<Record<string, readonly Row[]>>) => MemoryAdapter;
|
|
22
|
+
//# sourceMappingURL=memory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../src/memory.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,eAAe,EAKhB,MAAM,cAAc,CAAA;AAWrB,MAAM,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAEzC,MAAM,MAAM,aAAa,GAAG,eAAe,GAAG;IAC5C,mDAAmD;IACnD,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,EAAE,CAAA;IACnC,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,EAAE,GAAG,IAAI,CAAA;IAC/C,KAAK,IAAI,IAAI,CAAA;IACb;;;;;;;OAOG;IACH,QAAQ,CAAC,WAAW,EAAE;QACpB,SAAS,IAAI,MAAM,CAAA;QACnB,KAAK,IAAI,IAAI,CAAA;KACd,CAAA;CACF,CAAA;AA8ND,eAAO,MAAM,mBAAmB,UACxB,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,GAAG,EAAE,CAAC,CAAC,KAC7C,aAyRF,CAAA"}
|