@atscript/db 0.1.140 → 0.1.142
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/dist/agg.d.cts +1 -1
- package/dist/agg.d.mts +1 -1
- package/dist/{buckets-C-27xmtq.d.cts → buckets-Bv4pah66.d.cts} +225 -22
- package/dist/{buckets-BFG2RYRW.d.mts → buckets-CjL7F-hp.d.mts} +225 -22
- package/dist/{column-diff-CgxgFKzx.cjs → column-diff-CfPNcP6e.cjs} +663 -239
- package/dist/{column-diff-BwOA5101.mjs → column-diff-CmFNXV8C.mjs} +638 -220
- package/dist/column-diff-DiBbXyLA.d.cts +211 -0
- package/dist/column-diff-n-k5KY0u.d.mts +211 -0
- package/dist/derived-rules-0sKn4f5C.mjs +44 -0
- package/dist/derived-rules-YstgIxG-.cjs +67 -0
- package/dist/index.cjs +38 -4
- package/dist/index.d.cts +23 -6
- package/dist/index.d.mts +23 -6
- package/dist/index.mjs +34 -4
- package/dist/{nested-writer-FWD5oOYh.mjs → nested-writer-BO3vhbkP.mjs} +8 -4
- package/dist/{nested-writer-BZNCuqI6.cjs → nested-writer-DYsRxZ5f.cjs} +8 -4
- package/dist/object-DSN0h9lB.d.cts +30 -0
- package/dist/object-DSN0h9lB.d.mts +30 -0
- package/dist/plugin.cjs +392 -139
- package/dist/plugin.mjs +392 -139
- package/dist/rel.cjs +2 -2
- package/dist/rel.d.cts +2 -2
- package/dist/rel.d.mts +2 -2
- package/dist/rel.mjs +2 -2
- package/dist/{relation-helpers-D3Zu0Mta.d.mts → relation-helpers-B59to_dG.d.mts} +5 -4
- package/dist/{relation-helpers-DxrvS6ar.d.cts → relation-helpers-DQ_nRsV9.d.cts} +5 -4
- package/dist/{relation-loader-6ZB_5KFq.cjs → relation-loader-CgJ8bK6X.cjs} +1 -1
- package/dist/{relation-loader-CTFaZpVa.mjs → relation-loader-CuhEBzFU.mjs} +1 -1
- package/dist/shared.cjs +6 -1
- package/dist/shared.d.cts +48 -9
- package/dist/shared.d.mts +48 -9
- package/dist/shared.mjs +2 -2
- package/dist/sync.cjs +331 -105
- package/dist/sync.d.cts +62 -163
- package/dist/sync.d.mts +62 -163
- package/dist/sync.mjs +331 -105
- package/dist/{validation-utils-B4h-GW4d.mjs → validation-utils-CMR4fe2M.mjs} +99 -34
- package/dist/{validation-utils-Dg0hW6dn.cjs → validation-utils-DOsB4e6G.cjs} +128 -33
- package/dist/{validator-Drb2N-YL.d.cts → validator-Bw6ks9Hy.d.cts} +1 -11
- package/dist/{validator-Drb2N-YL.d.mts → validator-Bw6ks9Hy.d.mts} +1 -11
- package/dist/{validator-Ch7UIQl9.mjs → validator-D8bPsXPN.mjs} +54 -2
- package/dist/{validator-BtZbcLN2.cjs → validator-DASnXf1j.cjs} +77 -1
- package/dist/validator.cjs +1 -1
- package/dist/validator.d.cts +2 -1
- package/dist/validator.d.mts +2 -1
- package/dist/validator.mjs +1 -1
- package/package.json +6 -6
- package/dist/column-diff-BmqvgBWw.d.cts +0 -24
- package/dist/column-diff-DPkbZIVE.d.mts +0 -24
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { Bt as AtscriptDbView, D as TColumnDiff, Gt as AtscriptQueryFieldRef, I as TDbDefaultValue, Kt as AtscriptQueryNode, R as TDbFieldMeta, X as TDbStorageType, at as TExistingColumn, rt as TDerivedColumn, st as TExistingTableOption, tn as AtscriptDbReadable } from "./buckets-Bv4pah66.cjs";
|
|
2
|
+
|
|
3
|
+
//#region src/schema/schema-hash.d.ts
|
|
4
|
+
interface TFieldSnapshot {
|
|
5
|
+
physicalName: string;
|
|
6
|
+
designType: string;
|
|
7
|
+
optional: boolean;
|
|
8
|
+
isPrimaryKey: boolean;
|
|
9
|
+
storage: TDbStorageType;
|
|
10
|
+
defaultValue?: TDbDefaultValue;
|
|
11
|
+
/** Adapter-specific mapped type (e.g., "VARCHAR(255)", "INTEGER"). */
|
|
12
|
+
mappedType?: string;
|
|
13
|
+
/** `@db.encrypted` — toggling encryption changes the snapshot hash on every adapter. */
|
|
14
|
+
encrypted?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* `@db.column.derived` — what the generated column reads (physical JSON
|
|
17
|
+
* column, path, leaf type). Present for derived fields only, so a table
|
|
18
|
+
* without one hashes exactly as before; the column diff compares it with
|
|
19
|
+
* the model to detect an expression change (engines normalize the stored
|
|
20
|
+
* expression text, so the snapshot is the baseline).
|
|
21
|
+
* @since 0.1.141
|
|
22
|
+
*/
|
|
23
|
+
derived?: Omit<TDerivedColumn, "sourcePath">;
|
|
24
|
+
}
|
|
25
|
+
interface TIndexSnapshot {
|
|
26
|
+
key: string;
|
|
27
|
+
type: string;
|
|
28
|
+
fields: Array<{
|
|
29
|
+
name: string;
|
|
30
|
+
sort: string;
|
|
31
|
+
}>;
|
|
32
|
+
}
|
|
33
|
+
interface TForeignKeySnapshot {
|
|
34
|
+
fields: string[];
|
|
35
|
+
targetTable: string;
|
|
36
|
+
targetFields: string[];
|
|
37
|
+
onDelete?: string;
|
|
38
|
+
onUpdate?: string;
|
|
39
|
+
}
|
|
40
|
+
interface TTableSnapshot {
|
|
41
|
+
tableName: string;
|
|
42
|
+
fields: TFieldSnapshot[];
|
|
43
|
+
indexes: TIndexSnapshot[];
|
|
44
|
+
foreignKeys: TForeignKeySnapshot[];
|
|
45
|
+
/** Adapter-specific table-level options (e.g., MySQL engine/charset, MongoDB capped). */
|
|
46
|
+
tableOptions?: TExistingTableOption[];
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* One join of a managed view as stored in its snapshot.
|
|
50
|
+
* @since 0.1.128 — `joinTables` elements were bare target-table names before;
|
|
51
|
+
* the ON predicate is now part of the view definition.
|
|
52
|
+
*/
|
|
53
|
+
interface TViewJoinSnapshot {
|
|
54
|
+
/**
|
|
55
|
+
* Scope name of the join: the physical table / view, or the `@db.alias`
|
|
56
|
+
* type name when the join is aliased (then {@link table} holds the physical name).
|
|
57
|
+
*/
|
|
58
|
+
targetTable: string;
|
|
59
|
+
/**
|
|
60
|
+
* Physical table / view of an aliased join. Emitted only when the target
|
|
61
|
+
* is a `@db.alias` type — a plain join serializes exactly as before.
|
|
62
|
+
* @since 0.1.141
|
|
63
|
+
*/
|
|
64
|
+
table?: string;
|
|
65
|
+
/** Canonical JSON of the join condition (see {@link canonicalizeQueryNode}). */
|
|
66
|
+
condition: string;
|
|
67
|
+
/** Emitted only for `"left"` — an inner join (the default) carries no key. @since 0.1.136 */
|
|
68
|
+
kind?: "inner" | "left";
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* One column of a managed view as stored in its snapshot — the PHYSICAL
|
|
72
|
+
* source it reads, so a source rename (`@db.column`, flattening, a moved
|
|
73
|
+
* JSON leaf) or an aggregate change recreates the view.
|
|
74
|
+
* @since 0.1.136
|
|
75
|
+
*/
|
|
76
|
+
interface TViewColumnSnapshot {
|
|
77
|
+
column: string;
|
|
78
|
+
sourceTable: string;
|
|
79
|
+
sourceColumn: string;
|
|
80
|
+
/** JSON array of the path segments inside a JSON source column. */
|
|
81
|
+
jsonPath?: string;
|
|
82
|
+
jsonType?: string;
|
|
83
|
+
aggFn?: string;
|
|
84
|
+
aggField?: string;
|
|
85
|
+
/** Canonical JSON of a conditional aggregate's predicate. */
|
|
86
|
+
aggFilter?: string;
|
|
87
|
+
}
|
|
88
|
+
interface TViewSnapshot {
|
|
89
|
+
tableName: string;
|
|
90
|
+
viewType: "V" | "M" | "E";
|
|
91
|
+
entryTable?: string;
|
|
92
|
+
/**
|
|
93
|
+
* Joins in declaration order. The key keeps its historical name so a
|
|
94
|
+
* join-less view (`[]`) serializes byte-identically to older snapshots.
|
|
95
|
+
*/
|
|
96
|
+
joinTables?: TViewJoinSnapshot[];
|
|
97
|
+
/** @since 0.1.136 — view columns and their physical sources, sorted by `column`. */
|
|
98
|
+
columns?: TViewColumnSnapshot[];
|
|
99
|
+
filterHash?: string;
|
|
100
|
+
/** @since 0.1.128 — hash of the canonical `@db.view.having` predicate. */
|
|
101
|
+
havingHash?: string;
|
|
102
|
+
materialized?: boolean;
|
|
103
|
+
/**
|
|
104
|
+
* @since 0.1.137 — the adapter's `viewRenderRevision()`, present only when
|
|
105
|
+
* the adapter defines one (managed views only).
|
|
106
|
+
*/
|
|
107
|
+
renderRevision?: string;
|
|
108
|
+
fields: TFieldSnapshot[];
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Extracts a canonical, serializable snapshot from a readable's metadata.
|
|
112
|
+
* Sorted deterministically so the hash is stable across runs.
|
|
113
|
+
*
|
|
114
|
+
* @param readable - The table/view readable.
|
|
115
|
+
* @param typeMapper - Optional adapter-specific type mapper. When provided,
|
|
116
|
+
* each field's mapped type (e.g., "VARCHAR(255)") is stored in the snapshot
|
|
117
|
+
* for precise type change detection.
|
|
118
|
+
*/
|
|
119
|
+
declare function computeTableSnapshot(readable: AtscriptDbReadable, typeMapper?: (field: TDbFieldMeta) => string, tableOptions?: TExistingTableOption[]): TTableSnapshot;
|
|
120
|
+
/**
|
|
121
|
+
* Extracts a canonical, serializable snapshot from a view's metadata.
|
|
122
|
+
* Captures view plan (entry table, joins, filter, materialization) for
|
|
123
|
+
* detecting view definition changes.
|
|
124
|
+
*/
|
|
125
|
+
declare function computeViewSnapshot(view: AtscriptDbView): TViewSnapshot;
|
|
126
|
+
/** Canonical (table-qualified, fixed-key-order) form of a view predicate. */
|
|
127
|
+
type TCanonicalQueryNode = {
|
|
128
|
+
and: TCanonicalQueryNode[];
|
|
129
|
+
} | {
|
|
130
|
+
or: TCanonicalQueryNode[];
|
|
131
|
+
} | {
|
|
132
|
+
not: TCanonicalQueryNode;
|
|
133
|
+
} /** `r` is `{ f: "<table>.<field>" }` for a field-to-field comparison, else the literal. */ | {
|
|
134
|
+
l: string;
|
|
135
|
+
op: string;
|
|
136
|
+
r?: unknown;
|
|
137
|
+
};
|
|
138
|
+
/**
|
|
139
|
+
* Converts a view predicate (join condition, `@db.view.filter`,
|
|
140
|
+
* `@db.view.having`) into a serializable structure whose JSON is a stable
|
|
141
|
+
* function of its MEANING: field refs become `qualify(ref)` — the view's
|
|
142
|
+
* `resolveFieldRef(ref, (n) => n)`, i.e. `"<table>.<field>"`, so a predicate
|
|
143
|
+
* retargeted to another table with the same field name changes — operators
|
|
144
|
+
* and literal values are kept as-is, `$and`/`$or` keep declaration order, and
|
|
145
|
+
* no function references survive. Two identical models produce byte-identical
|
|
146
|
+
* JSON.
|
|
147
|
+
* @since 0.1.128
|
|
148
|
+
*/
|
|
149
|
+
declare function canonicalizeQueryNode(node: AtscriptQueryNode, qualify: (ref: AtscriptQueryFieldRef) => string): TCanonicalQueryNode;
|
|
150
|
+
/**
|
|
151
|
+
* Computes a deterministic hash string from multiple table snapshots.
|
|
152
|
+
* Uses FNV-1a for speed — not cryptographic, just needs stability + collision resistance.
|
|
153
|
+
*/
|
|
154
|
+
declare function computeSchemaHash(snapshots: Array<TTableSnapshot | TViewSnapshot>): string;
|
|
155
|
+
/**
|
|
156
|
+
* Computes a hash for a single table/view snapshot.
|
|
157
|
+
* Used for per-table change detection via stored snapshots.
|
|
158
|
+
*/
|
|
159
|
+
declare function computeTableHash(snapshot: TTableSnapshot | TViewSnapshot): string;
|
|
160
|
+
/**
|
|
161
|
+
* Converts stored snapshot fields to `TExistingColumn[]` format
|
|
162
|
+
* for use with `computeColumnDiff`. Used by adapters that lack
|
|
163
|
+
* native column introspection (e.g., MongoDB).
|
|
164
|
+
*
|
|
165
|
+
* The `type` field uses `mappedType` when available (adapter-specific),
|
|
166
|
+
* falling back to `designType`. Derived fields are left out (since 0.1.141):
|
|
167
|
+
* an adapter without column introspection stores no derived column, and a
|
|
168
|
+
* `physicalName` of one is its SOURCE path — reporting it as an existing
|
|
169
|
+
* column would have the diff drop the source leaf.
|
|
170
|
+
*
|
|
171
|
+
* With `readable`, the subfields of its navigation properties are left out
|
|
172
|
+
* too: a document adapter's snapshot lists them (for hash stability — see
|
|
173
|
+
* `snapshotFields`) although nothing is stored under a nav field — they are
|
|
174
|
+
* not columns to drop.
|
|
175
|
+
*/
|
|
176
|
+
declare function snapshotToExistingColumns(snapshot: TTableSnapshot, readable?: AtscriptDbReadable): TExistingColumn[];
|
|
177
|
+
/**
|
|
178
|
+
* Extracts table options from a stored snapshot for diff comparison.
|
|
179
|
+
* Used as fallback when an adapter lacks native table option introspection.
|
|
180
|
+
*/
|
|
181
|
+
declare function snapshotToExistingTableOptions(snapshot: TTableSnapshot): TExistingTableOption[];
|
|
182
|
+
//#endregion
|
|
183
|
+
//#region src/schema/column-diff.d.ts
|
|
184
|
+
/**
|
|
185
|
+
* Whether a live column's type differs from the type the adapter's
|
|
186
|
+
* `typeMapper` gives the field — the one rule schema sync diffs column types
|
|
187
|
+
* by (case-insensitive). Exported for adapters that must agree with it (the
|
|
188
|
+
* PostgreSQL recreate converts exactly the columns this reports changed).
|
|
189
|
+
* @since 0.1.137
|
|
190
|
+
*/
|
|
191
|
+
declare function isColumnTypeChanged(existingType: string, expectedType: string): boolean;
|
|
192
|
+
/**
|
|
193
|
+
* Computes the difference between desired schema fields and existing database columns.
|
|
194
|
+
*
|
|
195
|
+
* @param desired - Field descriptors from the Atscript type (after flattening) —
|
|
196
|
+
* the readable's `columnDescriptors`; ignored descriptors are skipped.
|
|
197
|
+
* @param existing - Columns currently in the database (from introspection).
|
|
198
|
+
* @param typeMapper - Optional function to map field metadata to DB-native type strings.
|
|
199
|
+
* Receives the full field meta (design type, annotations, PK status, etc.)
|
|
200
|
+
* so adapters can produce context-aware types (e.g., `VARCHAR(255)` from maxLength).
|
|
201
|
+
* Required for type change detection.
|
|
202
|
+
* @param opts.snapshot - The table's stored snapshot (since 0.1.141) — the baseline a
|
|
203
|
+
* derived column's expression is compared with (the engine normalizes
|
|
204
|
+
* the expression it stores, so the live column cannot be). Without
|
|
205
|
+
* one, only a kind or type drift is seen.
|
|
206
|
+
*/
|
|
207
|
+
declare function computeColumnDiff(desired: readonly TDbFieldMeta[], existing: TExistingColumn[], typeMapper?: (field: TDbFieldMeta) => string, opts?: {
|
|
208
|
+
snapshot?: TTableSnapshot | null;
|
|
209
|
+
}): TColumnDiff;
|
|
210
|
+
//#endregion
|
|
211
|
+
export { TTableSnapshot as a, TViewSnapshot as c, computeTableHash as d, computeTableSnapshot as f, snapshotToExistingTableOptions as h, TForeignKeySnapshot as i, canonicalizeQueryNode as l, snapshotToExistingColumns as m, isColumnTypeChanged as n, TViewColumnSnapshot as o, computeViewSnapshot as p, TFieldSnapshot as r, TViewJoinSnapshot as s, computeColumnDiff as t, computeSchemaHash as u };
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { Bt as AtscriptDbView, D as TColumnDiff, Gt as AtscriptQueryFieldRef, I as TDbDefaultValue, Kt as AtscriptQueryNode, R as TDbFieldMeta, X as TDbStorageType, at as TExistingColumn, rt as TDerivedColumn, st as TExistingTableOption, tn as AtscriptDbReadable } from "./buckets-CjL7F-hp.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/schema/schema-hash.d.ts
|
|
4
|
+
interface TFieldSnapshot {
|
|
5
|
+
physicalName: string;
|
|
6
|
+
designType: string;
|
|
7
|
+
optional: boolean;
|
|
8
|
+
isPrimaryKey: boolean;
|
|
9
|
+
storage: TDbStorageType;
|
|
10
|
+
defaultValue?: TDbDefaultValue;
|
|
11
|
+
/** Adapter-specific mapped type (e.g., "VARCHAR(255)", "INTEGER"). */
|
|
12
|
+
mappedType?: string;
|
|
13
|
+
/** `@db.encrypted` — toggling encryption changes the snapshot hash on every adapter. */
|
|
14
|
+
encrypted?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* `@db.column.derived` — what the generated column reads (physical JSON
|
|
17
|
+
* column, path, leaf type). Present for derived fields only, so a table
|
|
18
|
+
* without one hashes exactly as before; the column diff compares it with
|
|
19
|
+
* the model to detect an expression change (engines normalize the stored
|
|
20
|
+
* expression text, so the snapshot is the baseline).
|
|
21
|
+
* @since 0.1.141
|
|
22
|
+
*/
|
|
23
|
+
derived?: Omit<TDerivedColumn, "sourcePath">;
|
|
24
|
+
}
|
|
25
|
+
interface TIndexSnapshot {
|
|
26
|
+
key: string;
|
|
27
|
+
type: string;
|
|
28
|
+
fields: Array<{
|
|
29
|
+
name: string;
|
|
30
|
+
sort: string;
|
|
31
|
+
}>;
|
|
32
|
+
}
|
|
33
|
+
interface TForeignKeySnapshot {
|
|
34
|
+
fields: string[];
|
|
35
|
+
targetTable: string;
|
|
36
|
+
targetFields: string[];
|
|
37
|
+
onDelete?: string;
|
|
38
|
+
onUpdate?: string;
|
|
39
|
+
}
|
|
40
|
+
interface TTableSnapshot {
|
|
41
|
+
tableName: string;
|
|
42
|
+
fields: TFieldSnapshot[];
|
|
43
|
+
indexes: TIndexSnapshot[];
|
|
44
|
+
foreignKeys: TForeignKeySnapshot[];
|
|
45
|
+
/** Adapter-specific table-level options (e.g., MySQL engine/charset, MongoDB capped). */
|
|
46
|
+
tableOptions?: TExistingTableOption[];
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* One join of a managed view as stored in its snapshot.
|
|
50
|
+
* @since 0.1.128 — `joinTables` elements were bare target-table names before;
|
|
51
|
+
* the ON predicate is now part of the view definition.
|
|
52
|
+
*/
|
|
53
|
+
interface TViewJoinSnapshot {
|
|
54
|
+
/**
|
|
55
|
+
* Scope name of the join: the physical table / view, or the `@db.alias`
|
|
56
|
+
* type name when the join is aliased (then {@link table} holds the physical name).
|
|
57
|
+
*/
|
|
58
|
+
targetTable: string;
|
|
59
|
+
/**
|
|
60
|
+
* Physical table / view of an aliased join. Emitted only when the target
|
|
61
|
+
* is a `@db.alias` type — a plain join serializes exactly as before.
|
|
62
|
+
* @since 0.1.141
|
|
63
|
+
*/
|
|
64
|
+
table?: string;
|
|
65
|
+
/** Canonical JSON of the join condition (see {@link canonicalizeQueryNode}). */
|
|
66
|
+
condition: string;
|
|
67
|
+
/** Emitted only for `"left"` — an inner join (the default) carries no key. @since 0.1.136 */
|
|
68
|
+
kind?: "inner" | "left";
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* One column of a managed view as stored in its snapshot — the PHYSICAL
|
|
72
|
+
* source it reads, so a source rename (`@db.column`, flattening, a moved
|
|
73
|
+
* JSON leaf) or an aggregate change recreates the view.
|
|
74
|
+
* @since 0.1.136
|
|
75
|
+
*/
|
|
76
|
+
interface TViewColumnSnapshot {
|
|
77
|
+
column: string;
|
|
78
|
+
sourceTable: string;
|
|
79
|
+
sourceColumn: string;
|
|
80
|
+
/** JSON array of the path segments inside a JSON source column. */
|
|
81
|
+
jsonPath?: string;
|
|
82
|
+
jsonType?: string;
|
|
83
|
+
aggFn?: string;
|
|
84
|
+
aggField?: string;
|
|
85
|
+
/** Canonical JSON of a conditional aggregate's predicate. */
|
|
86
|
+
aggFilter?: string;
|
|
87
|
+
}
|
|
88
|
+
interface TViewSnapshot {
|
|
89
|
+
tableName: string;
|
|
90
|
+
viewType: "V" | "M" | "E";
|
|
91
|
+
entryTable?: string;
|
|
92
|
+
/**
|
|
93
|
+
* Joins in declaration order. The key keeps its historical name so a
|
|
94
|
+
* join-less view (`[]`) serializes byte-identically to older snapshots.
|
|
95
|
+
*/
|
|
96
|
+
joinTables?: TViewJoinSnapshot[];
|
|
97
|
+
/** @since 0.1.136 — view columns and their physical sources, sorted by `column`. */
|
|
98
|
+
columns?: TViewColumnSnapshot[];
|
|
99
|
+
filterHash?: string;
|
|
100
|
+
/** @since 0.1.128 — hash of the canonical `@db.view.having` predicate. */
|
|
101
|
+
havingHash?: string;
|
|
102
|
+
materialized?: boolean;
|
|
103
|
+
/**
|
|
104
|
+
* @since 0.1.137 — the adapter's `viewRenderRevision()`, present only when
|
|
105
|
+
* the adapter defines one (managed views only).
|
|
106
|
+
*/
|
|
107
|
+
renderRevision?: string;
|
|
108
|
+
fields: TFieldSnapshot[];
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Extracts a canonical, serializable snapshot from a readable's metadata.
|
|
112
|
+
* Sorted deterministically so the hash is stable across runs.
|
|
113
|
+
*
|
|
114
|
+
* @param readable - The table/view readable.
|
|
115
|
+
* @param typeMapper - Optional adapter-specific type mapper. When provided,
|
|
116
|
+
* each field's mapped type (e.g., "VARCHAR(255)") is stored in the snapshot
|
|
117
|
+
* for precise type change detection.
|
|
118
|
+
*/
|
|
119
|
+
declare function computeTableSnapshot(readable: AtscriptDbReadable, typeMapper?: (field: TDbFieldMeta) => string, tableOptions?: TExistingTableOption[]): TTableSnapshot;
|
|
120
|
+
/**
|
|
121
|
+
* Extracts a canonical, serializable snapshot from a view's metadata.
|
|
122
|
+
* Captures view plan (entry table, joins, filter, materialization) for
|
|
123
|
+
* detecting view definition changes.
|
|
124
|
+
*/
|
|
125
|
+
declare function computeViewSnapshot(view: AtscriptDbView): TViewSnapshot;
|
|
126
|
+
/** Canonical (table-qualified, fixed-key-order) form of a view predicate. */
|
|
127
|
+
type TCanonicalQueryNode = {
|
|
128
|
+
and: TCanonicalQueryNode[];
|
|
129
|
+
} | {
|
|
130
|
+
or: TCanonicalQueryNode[];
|
|
131
|
+
} | {
|
|
132
|
+
not: TCanonicalQueryNode;
|
|
133
|
+
} /** `r` is `{ f: "<table>.<field>" }` for a field-to-field comparison, else the literal. */ | {
|
|
134
|
+
l: string;
|
|
135
|
+
op: string;
|
|
136
|
+
r?: unknown;
|
|
137
|
+
};
|
|
138
|
+
/**
|
|
139
|
+
* Converts a view predicate (join condition, `@db.view.filter`,
|
|
140
|
+
* `@db.view.having`) into a serializable structure whose JSON is a stable
|
|
141
|
+
* function of its MEANING: field refs become `qualify(ref)` — the view's
|
|
142
|
+
* `resolveFieldRef(ref, (n) => n)`, i.e. `"<table>.<field>"`, so a predicate
|
|
143
|
+
* retargeted to another table with the same field name changes — operators
|
|
144
|
+
* and literal values are kept as-is, `$and`/`$or` keep declaration order, and
|
|
145
|
+
* no function references survive. Two identical models produce byte-identical
|
|
146
|
+
* JSON.
|
|
147
|
+
* @since 0.1.128
|
|
148
|
+
*/
|
|
149
|
+
declare function canonicalizeQueryNode(node: AtscriptQueryNode, qualify: (ref: AtscriptQueryFieldRef) => string): TCanonicalQueryNode;
|
|
150
|
+
/**
|
|
151
|
+
* Computes a deterministic hash string from multiple table snapshots.
|
|
152
|
+
* Uses FNV-1a for speed — not cryptographic, just needs stability + collision resistance.
|
|
153
|
+
*/
|
|
154
|
+
declare function computeSchemaHash(snapshots: Array<TTableSnapshot | TViewSnapshot>): string;
|
|
155
|
+
/**
|
|
156
|
+
* Computes a hash for a single table/view snapshot.
|
|
157
|
+
* Used for per-table change detection via stored snapshots.
|
|
158
|
+
*/
|
|
159
|
+
declare function computeTableHash(snapshot: TTableSnapshot | TViewSnapshot): string;
|
|
160
|
+
/**
|
|
161
|
+
* Converts stored snapshot fields to `TExistingColumn[]` format
|
|
162
|
+
* for use with `computeColumnDiff`. Used by adapters that lack
|
|
163
|
+
* native column introspection (e.g., MongoDB).
|
|
164
|
+
*
|
|
165
|
+
* The `type` field uses `mappedType` when available (adapter-specific),
|
|
166
|
+
* falling back to `designType`. Derived fields are left out (since 0.1.141):
|
|
167
|
+
* an adapter without column introspection stores no derived column, and a
|
|
168
|
+
* `physicalName` of one is its SOURCE path — reporting it as an existing
|
|
169
|
+
* column would have the diff drop the source leaf.
|
|
170
|
+
*
|
|
171
|
+
* With `readable`, the subfields of its navigation properties are left out
|
|
172
|
+
* too: a document adapter's snapshot lists them (for hash stability — see
|
|
173
|
+
* `snapshotFields`) although nothing is stored under a nav field — they are
|
|
174
|
+
* not columns to drop.
|
|
175
|
+
*/
|
|
176
|
+
declare function snapshotToExistingColumns(snapshot: TTableSnapshot, readable?: AtscriptDbReadable): TExistingColumn[];
|
|
177
|
+
/**
|
|
178
|
+
* Extracts table options from a stored snapshot for diff comparison.
|
|
179
|
+
* Used as fallback when an adapter lacks native table option introspection.
|
|
180
|
+
*/
|
|
181
|
+
declare function snapshotToExistingTableOptions(snapshot: TTableSnapshot): TExistingTableOption[];
|
|
182
|
+
//#endregion
|
|
183
|
+
//#region src/schema/column-diff.d.ts
|
|
184
|
+
/**
|
|
185
|
+
* Whether a live column's type differs from the type the adapter's
|
|
186
|
+
* `typeMapper` gives the field — the one rule schema sync diffs column types
|
|
187
|
+
* by (case-insensitive). Exported for adapters that must agree with it (the
|
|
188
|
+
* PostgreSQL recreate converts exactly the columns this reports changed).
|
|
189
|
+
* @since 0.1.137
|
|
190
|
+
*/
|
|
191
|
+
declare function isColumnTypeChanged(existingType: string, expectedType: string): boolean;
|
|
192
|
+
/**
|
|
193
|
+
* Computes the difference between desired schema fields and existing database columns.
|
|
194
|
+
*
|
|
195
|
+
* @param desired - Field descriptors from the Atscript type (after flattening) —
|
|
196
|
+
* the readable's `columnDescriptors`; ignored descriptors are skipped.
|
|
197
|
+
* @param existing - Columns currently in the database (from introspection).
|
|
198
|
+
* @param typeMapper - Optional function to map field metadata to DB-native type strings.
|
|
199
|
+
* Receives the full field meta (design type, annotations, PK status, etc.)
|
|
200
|
+
* so adapters can produce context-aware types (e.g., `VARCHAR(255)` from maxLength).
|
|
201
|
+
* Required for type change detection.
|
|
202
|
+
* @param opts.snapshot - The table's stored snapshot (since 0.1.141) — the baseline a
|
|
203
|
+
* derived column's expression is compared with (the engine normalizes
|
|
204
|
+
* the expression it stores, so the live column cannot be). Without
|
|
205
|
+
* one, only a kind or type drift is seen.
|
|
206
|
+
*/
|
|
207
|
+
declare function computeColumnDiff(desired: readonly TDbFieldMeta[], existing: TExistingColumn[], typeMapper?: (field: TDbFieldMeta) => string, opts?: {
|
|
208
|
+
snapshot?: TTableSnapshot | null;
|
|
209
|
+
}): TColumnDiff;
|
|
210
|
+
//#endregion
|
|
211
|
+
export { TTableSnapshot as a, TViewSnapshot as c, computeTableHash as d, computeTableSnapshot as f, snapshotToExistingTableOptions as h, TForeignKeySnapshot as i, canonicalizeQueryNode as l, snapshotToExistingColumns as m, isColumnTypeChanged as n, TViewColumnSnapshot as o, computeViewSnapshot as p, TFieldSnapshot as r, TViewJoinSnapshot as s, computeColumnDiff as t, computeSchemaHash as u };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
//#region src/shared/derived-rules.ts
|
|
2
|
+
/**
|
|
3
|
+
* The rules the compiler plugin and the runtime share about entities and
|
|
4
|
+
* `@db.column.derived` fields — one dependency-free list each, so a
|
|
5
|
+
* compile-time diagnostic and its runtime mirror can never disagree.
|
|
6
|
+
* @since 0.1.141
|
|
7
|
+
*/
|
|
8
|
+
/** The annotations that make a declaration a DB entity: a table, or a managed / external view. */
|
|
9
|
+
const DB_ENTITY_ANNOTATIONS = [
|
|
10
|
+
"db.table",
|
|
11
|
+
"db.view",
|
|
12
|
+
"db.view.for"
|
|
13
|
+
];
|
|
14
|
+
/** Primitive leaf types a JSON-stored path may end at (view JSON leaves, derived columns). */
|
|
15
|
+
const JSON_LEAF_TYPES = new Set([
|
|
16
|
+
"string",
|
|
17
|
+
"number",
|
|
18
|
+
"boolean"
|
|
19
|
+
]);
|
|
20
|
+
/** Whether `type` (a resolved design type) is one of {@link JSON_LEAF_TYPES}. */
|
|
21
|
+
function isJsonLeafType(type) {
|
|
22
|
+
return JSON_LEAF_TYPES.has(type);
|
|
23
|
+
}
|
|
24
|
+
/** Annotations a `@db.column.derived` field cannot carry (D8), with the reason. */
|
|
25
|
+
const DERIVED_INCOMPATIBLE = [
|
|
26
|
+
["meta.id", "a computed column cannot identify the row"],
|
|
27
|
+
["db.rel.FK", "a foreign key needs a stored, writable column"],
|
|
28
|
+
["db.default", "the value is computed, never defaulted"],
|
|
29
|
+
["db.default.increment", "the value is computed, never defaulted"],
|
|
30
|
+
["db.default.uuid", "the value is computed, never defaulted"],
|
|
31
|
+
["db.default.now", "the value is computed, never defaulted"],
|
|
32
|
+
["db.column.version", "the version column is adapter-managed"],
|
|
33
|
+
["db.encrypted", "the value is a cleartext extraction of its source"],
|
|
34
|
+
["db.json", "a derived column is a primitive leaf, not a JSON value"],
|
|
35
|
+
["db.ignore", "an ignored field has no column to derive"],
|
|
36
|
+
["db.writeOnly", "a derived column is never written"],
|
|
37
|
+
["db.index.fulltext", "fulltext indexes need a stored text column"],
|
|
38
|
+
["db.index.geo", "a geo index needs a db.geoPoint column"],
|
|
39
|
+
["db.search.vector", "a vector index needs a stored embedding column"],
|
|
40
|
+
["db.mongo.search.text", "Atlas Search indexes the stored document, which holds no derived field"],
|
|
41
|
+
["db.mongo.search.autocomplete", "Atlas Search indexes the stored document, which holds no derived field"]
|
|
42
|
+
];
|
|
43
|
+
//#endregion
|
|
44
|
+
export { isJsonLeafType as i, DERIVED_INCOMPATIBLE as n, JSON_LEAF_TYPES as r, DB_ENTITY_ANNOTATIONS as t };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
//#region src/shared/derived-rules.ts
|
|
2
|
+
/**
|
|
3
|
+
* The rules the compiler plugin and the runtime share about entities and
|
|
4
|
+
* `@db.column.derived` fields — one dependency-free list each, so a
|
|
5
|
+
* compile-time diagnostic and its runtime mirror can never disagree.
|
|
6
|
+
* @since 0.1.141
|
|
7
|
+
*/
|
|
8
|
+
/** The annotations that make a declaration a DB entity: a table, or a managed / external view. */
|
|
9
|
+
const DB_ENTITY_ANNOTATIONS = [
|
|
10
|
+
"db.table",
|
|
11
|
+
"db.view",
|
|
12
|
+
"db.view.for"
|
|
13
|
+
];
|
|
14
|
+
/** Primitive leaf types a JSON-stored path may end at (view JSON leaves, derived columns). */
|
|
15
|
+
const JSON_LEAF_TYPES = new Set([
|
|
16
|
+
"string",
|
|
17
|
+
"number",
|
|
18
|
+
"boolean"
|
|
19
|
+
]);
|
|
20
|
+
/** Whether `type` (a resolved design type) is one of {@link JSON_LEAF_TYPES}. */
|
|
21
|
+
function isJsonLeafType(type) {
|
|
22
|
+
return JSON_LEAF_TYPES.has(type);
|
|
23
|
+
}
|
|
24
|
+
/** Annotations a `@db.column.derived` field cannot carry (D8), with the reason. */
|
|
25
|
+
const DERIVED_INCOMPATIBLE = [
|
|
26
|
+
["meta.id", "a computed column cannot identify the row"],
|
|
27
|
+
["db.rel.FK", "a foreign key needs a stored, writable column"],
|
|
28
|
+
["db.default", "the value is computed, never defaulted"],
|
|
29
|
+
["db.default.increment", "the value is computed, never defaulted"],
|
|
30
|
+
["db.default.uuid", "the value is computed, never defaulted"],
|
|
31
|
+
["db.default.now", "the value is computed, never defaulted"],
|
|
32
|
+
["db.column.version", "the version column is adapter-managed"],
|
|
33
|
+
["db.encrypted", "the value is a cleartext extraction of its source"],
|
|
34
|
+
["db.json", "a derived column is a primitive leaf, not a JSON value"],
|
|
35
|
+
["db.ignore", "an ignored field has no column to derive"],
|
|
36
|
+
["db.writeOnly", "a derived column is never written"],
|
|
37
|
+
["db.index.fulltext", "fulltext indexes need a stored text column"],
|
|
38
|
+
["db.index.geo", "a geo index needs a db.geoPoint column"],
|
|
39
|
+
["db.search.vector", "a vector index needs a stored embedding column"],
|
|
40
|
+
["db.mongo.search.text", "Atlas Search indexes the stored document, which holds no derived field"],
|
|
41
|
+
["db.mongo.search.autocomplete", "Atlas Search indexes the stored document, which holds no derived field"]
|
|
42
|
+
];
|
|
43
|
+
//#endregion
|
|
44
|
+
Object.defineProperty(exports, "DB_ENTITY_ANNOTATIONS", {
|
|
45
|
+
enumerable: true,
|
|
46
|
+
get: function() {
|
|
47
|
+
return DB_ENTITY_ANNOTATIONS;
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
Object.defineProperty(exports, "DERIVED_INCOMPATIBLE", {
|
|
51
|
+
enumerable: true,
|
|
52
|
+
get: function() {
|
|
53
|
+
return DERIVED_INCOMPATIBLE;
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
Object.defineProperty(exports, "JSON_LEAF_TYPES", {
|
|
57
|
+
enumerable: true,
|
|
58
|
+
get: function() {
|
|
59
|
+
return JSON_LEAF_TYPES;
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
Object.defineProperty(exports, "isJsonLeafType", {
|
|
63
|
+
enumerable: true,
|
|
64
|
+
get: function() {
|
|
65
|
+
return isJsonLeafType;
|
|
66
|
+
}
|
|
67
|
+
});
|
package/dist/index.cjs
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_db_error = require("./db-error-DTkkeu5b.cjs");
|
|
3
|
-
const require_column_diff = require("./column-diff-
|
|
3
|
+
const require_column_diff = require("./column-diff-CfPNcP6e.cjs");
|
|
4
4
|
const require_aggregate_fns = require("./aggregate-fns-CGBv3E8S.cjs");
|
|
5
|
-
const require_nested_writer = require("./nested-writer-
|
|
6
|
-
const
|
|
5
|
+
const require_nested_writer = require("./nested-writer-DYsRxZ5f.cjs");
|
|
6
|
+
const require_derived_rules = require("./derived-rules-YstgIxG-.cjs");
|
|
7
|
+
const require_validator = require("./validator-DASnXf1j.cjs");
|
|
7
8
|
const require_consts = require("./consts-BzRfCcH2.cjs");
|
|
8
9
|
const require_ops = require("./ops.cjs");
|
|
10
|
+
let _atscript_typescript_utils = require("@atscript/typescript/utils");
|
|
9
11
|
let _uniqu_core = require("@uniqu/core");
|
|
10
12
|
let node_crypto = require("node:crypto");
|
|
11
13
|
//#region src/encryption.ts
|
|
@@ -203,6 +205,14 @@ async function withOptimisticRetry(table, filter, mutator, opts) {
|
|
|
203
205
|
//#endregion
|
|
204
206
|
//#region src/table/db-space.ts
|
|
205
207
|
/**
|
|
208
|
+
* A `@db.alias` type only names a join scope inside a view definition — it
|
|
209
|
+
* is never a table or view of its own (since 0.1.141).
|
|
210
|
+
*/
|
|
211
|
+
function assertNotAlias(type) {
|
|
212
|
+
const target = require_column_diff.aliasTargetOf(type);
|
|
213
|
+
if (target) throw new Error(`"${type.id ?? ""}" is a @db.alias of "${target.id ?? ""}" — a join scope, not a table or view; register "${target.id ?? ""}" instead`);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
206
216
|
* A database space — a registry of tables and views sharing the same adapter type and driver.
|
|
207
217
|
*
|
|
208
218
|
* `DbSpace` solves the cross-table discovery problem: when table A has a relation
|
|
@@ -259,6 +269,7 @@ var DbSpace = class {
|
|
|
259
269
|
getTable(type, logger) {
|
|
260
270
|
let readable = this._readables.get(type);
|
|
261
271
|
if (!readable) {
|
|
272
|
+
assertNotAlias(type);
|
|
262
273
|
readable = new require_column_diff.AtscriptDbTable(type, this._createAdapter(), logger || this.logger, (t) => this.get(t), (t) => {
|
|
263
274
|
const resolved = this.get(t);
|
|
264
275
|
return resolved instanceof require_column_diff.AtscriptDbTable ? resolved : void 0;
|
|
@@ -278,6 +289,7 @@ var DbSpace = class {
|
|
|
278
289
|
getView(type, logger) {
|
|
279
290
|
let readable = this._readables.get(type);
|
|
280
291
|
if (!readable) {
|
|
292
|
+
assertNotAlias(type);
|
|
281
293
|
readable = new require_column_diff.AtscriptDbView(type, this._createAdapter(), logger || this.logger, (t) => this.get(t));
|
|
282
294
|
readable.setEncryption(this._encryption);
|
|
283
295
|
this._readables.set(type, readable);
|
|
@@ -392,6 +404,24 @@ function translateQueryTree(node, resolveField) {
|
|
|
392
404
|
return { [leftField]: { [comp.op]: comp.right } };
|
|
393
405
|
}
|
|
394
406
|
//#endregion
|
|
407
|
+
//#region src/table/db-entity.ts
|
|
408
|
+
/**
|
|
409
|
+
* Whether `value` is a compiled type a `DbSpace` accepts: an annotated type
|
|
410
|
+
* whose own declaration carries `@db.table`, `@db.view` or `@db.view.for`.
|
|
411
|
+
* A `@db.alias` type is not one, and neither is a plain `export type X = Table`
|
|
412
|
+
* or a field typed with a table (`customer: Customer`) — since 0.1.141 the
|
|
413
|
+
* entity annotations stay on the declaring interface instead of travelling
|
|
414
|
+
* with every reference.
|
|
415
|
+
*
|
|
416
|
+
* Filters a module namespace before a sync:
|
|
417
|
+
* `syncSchema(db, Object.values(models).filter(isDbEntityType))`.
|
|
418
|
+
* @since 0.1.141
|
|
419
|
+
*/
|
|
420
|
+
function isDbEntityType(value) {
|
|
421
|
+
if (!(0, _atscript_typescript_utils.isAnnotatedType)(value)) return false;
|
|
422
|
+
return require_derived_rules.DB_ENTITY_ANNOTATIONS.some((name) => value.metadata.has(name));
|
|
423
|
+
}
|
|
424
|
+
//#endregion
|
|
395
425
|
exports.$cas = require_ops.$cas;
|
|
396
426
|
exports.$dec = require_ops.$dec;
|
|
397
427
|
exports.$inc = require_ops.$inc;
|
|
@@ -425,6 +455,7 @@ exports.RelationalFieldMapper = require_column_diff.RelationalFieldMapper;
|
|
|
425
455
|
exports.TableMetadata = require_column_diff.TableMetadata;
|
|
426
456
|
exports.UniquSelect = require_column_diff.UniquSelect;
|
|
427
457
|
exports.acceptedOperatorsHint = require_column_diff.acceptedOperatorsHint;
|
|
458
|
+
exports.aliasTargetOf = require_column_diff.aliasTargetOf;
|
|
428
459
|
exports.assertGeoPoint = require_column_diff.assertGeoPoint;
|
|
429
460
|
exports.assertNoVersionWrites = require_column_diff.assertNoVersionWrites;
|
|
430
461
|
exports.bucketSourceVerdict = require_column_diff.bucketSourceVerdict;
|
|
@@ -444,10 +475,12 @@ Object.defineProperty(exports, "computeInsights", {
|
|
|
444
475
|
exports.createDbValidatorPlugin = require_validator.createDbValidatorPlugin;
|
|
445
476
|
exports.createFailureCollector = require_column_diff.createFailureCollector;
|
|
446
477
|
exports.decomposePatch = require_column_diff.decomposePatch;
|
|
447
|
-
exports.
|
|
478
|
+
exports.deletePath = require_validator.deletePath;
|
|
479
|
+
exports.findAncestorInSet = require_validator.findAncestorInSet;
|
|
448
480
|
exports.forceNavNonOptional = require_validator.forceNavNonOptional;
|
|
449
481
|
exports.getDbFieldOp = require_ops.getDbFieldOp;
|
|
450
482
|
exports.getKeyProps = require_validator.getKeyProps;
|
|
483
|
+
exports.getPath = require_validator.getPath;
|
|
451
484
|
exports.guardAggregate = require_column_diff.guardAggregate;
|
|
452
485
|
exports.guardFilter = require_column_diff.guardFilter;
|
|
453
486
|
exports.guardPath = require_column_diff.guardPath;
|
|
@@ -456,6 +489,7 @@ exports.guardQuery = require_column_diff.guardQuery;
|
|
|
456
489
|
exports.isAtscriptDbView = require_column_diff.isAtscriptDbView;
|
|
457
490
|
exports.isBucketableField = require_column_diff.isBucketableField;
|
|
458
491
|
exports.isColumnTypeChanged = require_column_diff.isColumnTypeChanged;
|
|
492
|
+
exports.isDbEntityType = isDbEntityType;
|
|
459
493
|
exports.isDbFieldOp = require_ops.isDbFieldOp;
|
|
460
494
|
exports.isEmptyObject = require_validator.isEmptyObject;
|
|
461
495
|
exports.isFieldRef = isFieldRef;
|