@prisma-next/family-sql 0.15.0 → 0.16.0-dev.10
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/{control-adapter-BQgad8Zc.d.mts → control-adapter-CW7FdxeS.d.mts} +1 -2
- package/dist/control-adapter-CW7FdxeS.d.mts.map +1 -0
- package/dist/control-adapter.d.mts +1 -1
- package/dist/control.d.mts +127 -5
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +71 -33
- package/dist/control.mjs.map +1 -1
- package/dist/diff.d.mts +3 -4
- package/dist/diff.d.mts.map +1 -1
- package/dist/diff.mjs +1 -1
- package/dist/ir.d.mts +0 -1
- package/dist/ir.d.mts.map +1 -1
- package/dist/migration.d.mts +1 -2
- package/dist/migration.d.mts.map +1 -1
- package/dist/pack.d.mts.map +1 -1
- package/dist/psl-infer.d.mts +2 -2
- package/dist/psl-infer.d.mts.map +1 -1
- package/dist/psl-infer.mjs +3 -4
- package/dist/psl-infer.mjs.map +1 -1
- package/dist/runtime.d.mts +0 -1
- package/dist/runtime.d.mts.map +1 -1
- package/dist/runtime.mjs +1 -1
- package/dist/{schema-differ-DnoopSXm.d.mts → schema-differ-C8OHo1V5.d.mts} +1 -2
- package/dist/schema-differ-C8OHo1V5.d.mts.map +1 -0
- package/dist/{schema-verify-W3r631Jh.mjs → schema-verify-DY21bMwy.mjs} +8 -8
- package/dist/schema-verify-DY21bMwy.mjs.map +1 -0
- package/dist/{timestamp-now-generator-CloimujU.mjs → timestamp-now-generator-DRXygu32.mjs} +88 -2
- package/dist/timestamp-now-generator-DRXygu32.mjs.map +1 -0
- package/dist/{types-BPv_y7iS.d.mts → types-DhAmOoxA.d.mts} +13 -7
- package/dist/types-DhAmOoxA.d.mts.map +1 -0
- package/dist/verify.d.mts +0 -1
- package/dist/verify.d.mts.map +1 -1
- package/package.json +25 -23
- package/src/core/diff/schema-verify.ts +5 -5
- package/src/core/diff/verifier-disposition.ts +3 -3
- package/src/core/migrations/contract-to-schema-ir.ts +96 -29
- package/src/core/migrations/types.ts +20 -13
- package/src/core/psl-contract-infer/name-transforms.ts +3 -13
- package/src/core/psl-contract-infer/relation-inference.ts +4 -1
- package/src/core/timestamp-now-generator.ts +88 -0
- package/src/exports/control.ts +3 -0
- package/dist/control-adapter-BQgad8Zc.d.mts.map +0 -1
- package/dist/foreign-key-index-backing-BP71iI-Q.mjs +0 -32
- package/dist/foreign-key-index-backing-BP71iI-Q.mjs.map +0 -1
- package/dist/schema-differ-DnoopSXm.d.mts.map +0 -1
- package/dist/schema-verify-W3r631Jh.mjs.map +0 -1
- package/dist/timestamp-now-generator-CloimujU.mjs.map +0 -1
- package/dist/types-BPv_y7iS.d.mts.map +0 -1
- package/src/core/foreign-key-index-backing.ts +0 -37
package/package.json
CHANGED
|
@@ -1,35 +1,37 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prisma-next/family-sql",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0-dev.10",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"description": "SQL family descriptor for Prisma Next",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@prisma-next/contract": "0.
|
|
10
|
-
"@prisma-next/emitter": "0.
|
|
11
|
-
"@prisma-next/framework-components": "0.
|
|
12
|
-
"@prisma-next/migration-tools": "0.
|
|
13
|
-
"@prisma-next/operations": "0.
|
|
14
|
-
"@prisma-next/sql-contract": "0.
|
|
15
|
-
"@prisma-next/sql-contract-emitter": "0.
|
|
16
|
-
"@prisma-next/sql-contract-ts": "0.
|
|
17
|
-
"@prisma-next/sql-operations": "0.
|
|
18
|
-
"@prisma-next/sql-relational-core": "0.
|
|
19
|
-
"@prisma-next/sql-runtime": "0.
|
|
20
|
-
"@prisma-next/sql-schema-ir": "0.
|
|
21
|
-
"@prisma-next/utils": "0.
|
|
22
|
-
"arktype": "^2.2.2"
|
|
9
|
+
"@prisma-next/contract": "0.16.0-dev.10",
|
|
10
|
+
"@prisma-next/emitter": "0.16.0-dev.10",
|
|
11
|
+
"@prisma-next/framework-components": "0.16.0-dev.10",
|
|
12
|
+
"@prisma-next/migration-tools": "0.16.0-dev.10",
|
|
13
|
+
"@prisma-next/operations": "0.16.0-dev.10",
|
|
14
|
+
"@prisma-next/sql-contract": "0.16.0-dev.10",
|
|
15
|
+
"@prisma-next/sql-contract-emitter": "0.16.0-dev.10",
|
|
16
|
+
"@prisma-next/sql-contract-ts": "0.16.0-dev.10",
|
|
17
|
+
"@prisma-next/sql-operations": "0.16.0-dev.10",
|
|
18
|
+
"@prisma-next/sql-relational-core": "0.16.0-dev.10",
|
|
19
|
+
"@prisma-next/sql-runtime": "0.16.0-dev.10",
|
|
20
|
+
"@prisma-next/sql-schema-ir": "0.16.0-dev.10",
|
|
21
|
+
"@prisma-next/utils": "0.16.0-dev.10",
|
|
22
|
+
"arktype": "^2.2.2",
|
|
23
|
+
"pluralize": "^8.0.0"
|
|
23
24
|
},
|
|
24
25
|
"devDependencies": {
|
|
25
|
-
"@prisma-next/driver-postgres": "0.
|
|
26
|
-
"@prisma-next/psl-parser": "0.
|
|
27
|
-
"@prisma-next/psl-printer": "0.
|
|
28
|
-
"@prisma-next/sql-contract-psl": "0.
|
|
29
|
-
"@prisma-next/test-utils": "0.
|
|
30
|
-
"@prisma-next/tsconfig": "0.
|
|
31
|
-
"@prisma-next/tsdown": "0.
|
|
32
|
-
"
|
|
26
|
+
"@prisma-next/driver-postgres": "0.16.0-dev.10",
|
|
27
|
+
"@prisma-next/psl-parser": "0.16.0-dev.10",
|
|
28
|
+
"@prisma-next/psl-printer": "0.16.0-dev.10",
|
|
29
|
+
"@prisma-next/sql-contract-psl": "0.16.0-dev.10",
|
|
30
|
+
"@prisma-next/test-utils": "0.16.0-dev.10",
|
|
31
|
+
"@prisma-next/tsconfig": "0.16.0-dev.10",
|
|
32
|
+
"@prisma-next/tsdown": "0.16.0-dev.10",
|
|
33
|
+
"@types/pluralize": "^0.0.33",
|
|
34
|
+
"tsdown": "0.22.8",
|
|
33
35
|
"typescript": "5.9.3",
|
|
34
36
|
"vitest": "4.1.10"
|
|
35
37
|
},
|
|
@@ -23,7 +23,7 @@ import type {
|
|
|
23
23
|
VerifierOutcome,
|
|
24
24
|
VerifyDatabaseSchemaResult,
|
|
25
25
|
} from '@prisma-next/framework-components/control';
|
|
26
|
-
import { dispositionForCategory } from '@prisma-next/framework-components/control';
|
|
26
|
+
import { dispositionForCategory, issueOutcome } from '@prisma-next/framework-components/control';
|
|
27
27
|
import { isStorageTypeInstance, type SqlStorage } from '@prisma-next/sql-contract/types';
|
|
28
28
|
import { RelationalSchemaNodeKind, type SqlSchemaIRNode } from '@prisma-next/sql-schema-ir/types';
|
|
29
29
|
import { blindCast } from '@prisma-next/utils/casts';
|
|
@@ -101,10 +101,10 @@ export function classifySqlDiffIssue(
|
|
|
101
101
|
issue: SchemaDiffIssue,
|
|
102
102
|
granularityOf: (nodeKind: string) => DiffSubjectGranularity,
|
|
103
103
|
): VerifierIssueCategory {
|
|
104
|
-
if (issue
|
|
104
|
+
if (issueOutcome(issue) === 'not-found') {
|
|
105
105
|
return 'declaredMissing';
|
|
106
106
|
}
|
|
107
|
-
if (issue
|
|
107
|
+
if (issueOutcome(issue) === 'not-expected') {
|
|
108
108
|
const granularity = classifyDiffSubjectGranularity(issue, granularityOf);
|
|
109
109
|
if (granularity === 'entity' || granularity === 'namespace') {
|
|
110
110
|
return 'extraTopLevelObject';
|
|
@@ -173,7 +173,7 @@ export function computeSqlDiffVerdict(input: SqlDiffVerdictInput): SqlDiffVerdic
|
|
|
173
173
|
for (const issue of input.issues) {
|
|
174
174
|
if (
|
|
175
175
|
!input.strict &&
|
|
176
|
-
issue
|
|
176
|
+
issueOutcome(issue) === 'not-expected' &&
|
|
177
177
|
isStrictOnlyExtra(issue, input.granularityOf)
|
|
178
178
|
) {
|
|
179
179
|
continue;
|
|
@@ -300,7 +300,7 @@ export function verifySqlSchemaByDiff(
|
|
|
300
300
|
: undefined;
|
|
301
301
|
return {
|
|
302
302
|
ok,
|
|
303
|
-
...(ok ? {} : { code: '
|
|
303
|
+
...(ok ? {} : { code: 'CONTRACT.SCHEMA_VERIFICATION_FAILED' }),
|
|
304
304
|
summary: ok
|
|
305
305
|
? 'Database schema satisfies contract'
|
|
306
306
|
: `Database schema does not satisfy contract (${failCount} failure${failCount === 1 ? '' : 's'})`,
|
|
@@ -4,7 +4,7 @@ import type {
|
|
|
4
4
|
VerifierIssueCategory,
|
|
5
5
|
VerifierOutcome,
|
|
6
6
|
} from '@prisma-next/framework-components/control';
|
|
7
|
-
import { dispositionForCategory } from '@prisma-next/framework-components/control';
|
|
7
|
+
import { dispositionForCategory, issueOutcome } from '@prisma-next/framework-components/control';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Classifies a codec `verifyType` hook finding into the target-neutral
|
|
@@ -13,10 +13,10 @@ import { dispositionForCategory } from '@prisma-next/framework-components/contro
|
|
|
13
13
|
* to its value set, so a paired mismatch always classifies as `valueDrift`.
|
|
14
14
|
*/
|
|
15
15
|
export function classifyStorageTypeDiffIssue(issue: SchemaDiffIssue): VerifierIssueCategory {
|
|
16
|
-
if (issue
|
|
16
|
+
if (issueOutcome(issue) === 'not-found') {
|
|
17
17
|
return 'declaredMissing';
|
|
18
18
|
}
|
|
19
|
-
if (issue
|
|
19
|
+
if (issueOutcome(issue) === 'not-expected') {
|
|
20
20
|
return 'extraAuxiliary';
|
|
21
21
|
}
|
|
22
22
|
return 'valueDrift';
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type { ColumnDefault, Contract, JsonValue } from '@prisma-next/contract/types';
|
|
2
2
|
import type { CodecRef } from '@prisma-next/framework-components/codec';
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
MigrationPlannerConflict,
|
|
5
|
+
SchemaNodeRef,
|
|
6
|
+
} from '@prisma-next/framework-components/control';
|
|
4
7
|
import {
|
|
5
8
|
type CheckConstraint,
|
|
6
9
|
type ForeignKey,
|
|
@@ -12,8 +15,8 @@ import {
|
|
|
12
15
|
type StorageTypeInstance,
|
|
13
16
|
type UniqueConstraint,
|
|
14
17
|
} from '@prisma-next/sql-contract/types';
|
|
15
|
-
import { defaultIndexName } from '@prisma-next/sql-schema-ir/naming';
|
|
16
18
|
import {
|
|
19
|
+
RelationalSchemaNodeKind,
|
|
17
20
|
type SqlAnnotations,
|
|
18
21
|
type SqlCheckConstraintIRInput,
|
|
19
22
|
type SqlColumnIRInput,
|
|
@@ -25,7 +28,6 @@ import {
|
|
|
25
28
|
} from '@prisma-next/sql-schema-ir/types';
|
|
26
29
|
import { blindCast } from '@prisma-next/utils/casts';
|
|
27
30
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
28
|
-
import { backingIndexColumnKeys } from '../foreign-key-index-backing';
|
|
29
31
|
|
|
30
32
|
/**
|
|
31
33
|
* Target-specific callback that expands a column's base `nativeType` and optional
|
|
@@ -54,6 +56,17 @@ export type NativeTypeExpander = (input: {
|
|
|
54
56
|
*/
|
|
55
57
|
export type DefaultRenderer = (def: ColumnDefault, column: StorageColumn) => string;
|
|
56
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Target-supplied hook (same IoC seam as `NativeTypeExpander`/`DefaultRenderer`)
|
|
61
|
+
* that normalizes a contract-declared `ColumnDefault` into the resolved shape
|
|
62
|
+
* the target's introspection parses from the live database — e.g. a
|
|
63
|
+
* `dbgenerated("'{}'::jsonb")` function call and the literal Postgres reports
|
|
64
|
+
* are the same value in different shapes, and `resolvedDefaultsEqual`
|
|
65
|
+
* compares `kind` before content. When omitted, the contract's raw default
|
|
66
|
+
* is the resolved default unchanged.
|
|
67
|
+
*/
|
|
68
|
+
export type DefaultResolver = (def: ColumnDefault, resolvedNativeType: string) => ColumnDefault;
|
|
69
|
+
|
|
57
70
|
/**
|
|
58
71
|
* Target-supplied callback that resolves a contract namespace to the live
|
|
59
72
|
* database schema its enums are stored under.
|
|
@@ -75,6 +88,7 @@ function convertColumn(
|
|
|
75
88
|
storageTypes: ResolvedStorageTypes,
|
|
76
89
|
expandNativeType: NativeTypeExpander | undefined,
|
|
77
90
|
renderDefault: DefaultRenderer | undefined,
|
|
91
|
+
resolveDefault: DefaultResolver | undefined,
|
|
78
92
|
): SqlColumnIRInput {
|
|
79
93
|
// Resolve `typeRef` so columns that delegate their `nativeType`/`codecId`/
|
|
80
94
|
// `typeParams` to a named `storage.types` entry expand the same way as
|
|
@@ -105,6 +119,11 @@ function convertColumn(
|
|
|
105
119
|
// agree on as the comparable "expanded" type.
|
|
106
120
|
const nativeType = baseNativeType;
|
|
107
121
|
const resolvedNativeType = column.many ? `${baseNativeType}[]` : baseNativeType;
|
|
122
|
+
const rawColumnDefault = column.default ?? undefined;
|
|
123
|
+
const resolvedColumnDefault =
|
|
124
|
+
rawColumnDefault !== undefined && resolveDefault
|
|
125
|
+
? resolveDefault(rawColumnDefault, resolvedNativeType)
|
|
126
|
+
: rawColumnDefault;
|
|
108
127
|
return {
|
|
109
128
|
name,
|
|
110
129
|
nativeType,
|
|
@@ -115,11 +134,14 @@ function convertColumn(
|
|
|
115
134
|
column.default != null && renderDefault ? renderDefault(column.default, column) : undefined,
|
|
116
135
|
),
|
|
117
136
|
// Contract-derived columns are resolved by construction: the computed
|
|
118
|
-
// full native type doubles as the resolved value
|
|
119
|
-
// structured default
|
|
120
|
-
//
|
|
137
|
+
// full native type doubles as the resolved value. The contract's raw
|
|
138
|
+
// structured default becomes the resolved default after passing through
|
|
139
|
+
// the target's `resolveDefault` hook (when supplied), so a default the
|
|
140
|
+
// target's introspection side would normalize differently (e.g. a
|
|
141
|
+
// `dbgenerated(...)` function call that is actually a literal) compares
|
|
142
|
+
// equal instead of drifting on `kind` alone.
|
|
121
143
|
resolvedNativeType,
|
|
122
|
-
...ifDefined('resolvedDefault',
|
|
144
|
+
...ifDefined('resolvedDefault', resolvedColumnDefault),
|
|
123
145
|
// The column's codec identity, carried the same way the query AST
|
|
124
146
|
// carries `CodecRef` (TML-2456) — the migration planner's op-builders
|
|
125
147
|
// resolve DDL rendering from this at plan time (Decision 5), instead of
|
|
@@ -246,14 +268,15 @@ function convertCheck(check: CheckConstraint, storage: SqlStorage): SqlCheckCons
|
|
|
246
268
|
};
|
|
247
269
|
}
|
|
248
270
|
|
|
249
|
-
function convertUnique(unique: UniqueConstraint): SqlUniqueIRInput {
|
|
271
|
+
function convertUnique(unique: UniqueConstraint, tableName: string): SqlUniqueIRInput {
|
|
250
272
|
return {
|
|
251
273
|
columns: unique.columns,
|
|
252
274
|
...ifDefined('name', unique.name),
|
|
275
|
+
dependsOn: flatColumnDependsOn(tableName, unique.columns),
|
|
253
276
|
};
|
|
254
277
|
}
|
|
255
278
|
|
|
256
|
-
function convertIndex(index: Index): SqlIndexIRInput {
|
|
279
|
+
function convertIndex(index: Index, tableName: string): SqlIndexIRInput {
|
|
257
280
|
return {
|
|
258
281
|
columns: index.columns,
|
|
259
282
|
unique: false,
|
|
@@ -262,9 +285,42 @@ function convertIndex(index: Index): SqlIndexIRInput {
|
|
|
262
285
|
// introspected side (the legacy walk read them from the contract).
|
|
263
286
|
...ifDefined('type', index.type),
|
|
264
287
|
...ifDefined('options', index.options),
|
|
288
|
+
dependsOn: flatColumnDependsOn(tableName, index.columns),
|
|
265
289
|
};
|
|
266
290
|
}
|
|
267
291
|
|
|
292
|
+
/**
|
|
293
|
+
* The referenced table's chain in the flat (single-schema) tree
|
|
294
|
+
* `contractToSchemaIR`/`contractNamespaceToSchemaIR` build: the root
|
|
295
|
+
* (`SqlSchemaIR`, fixed `'database'` id) followed by the table's own id.
|
|
296
|
+
* Postgres discards this when it re-derives the FK against its own
|
|
297
|
+
* multi-schema tree shape (`contractToPostgresDatabaseSchemaNode`); SQLite's
|
|
298
|
+
* flat tree uses it as-is.
|
|
299
|
+
*/
|
|
300
|
+
function flatSchemaDependsOn(tableName: string): SchemaNodeRef {
|
|
301
|
+
return [
|
|
302
|
+
{ nodeKind: RelationalSchemaNodeKind.schema, id: 'database' },
|
|
303
|
+
{ nodeKind: RelationalSchemaNodeKind.table, id: tableName },
|
|
304
|
+
];
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* The chains from a table-child object (foreign key, index, unique, primary
|
|
309
|
+
* key) to each of the own columns it is built on, in the flat tree. Dropping
|
|
310
|
+
* a covered column auto-drops the object, so the object's drop must precede
|
|
311
|
+
* the column's; the graph derives that direction from these edges.
|
|
312
|
+
*/
|
|
313
|
+
function flatColumnDependsOn(
|
|
314
|
+
tableName: string,
|
|
315
|
+
columns: readonly string[],
|
|
316
|
+
): readonly SchemaNodeRef[] {
|
|
317
|
+
return columns.map((column) => [
|
|
318
|
+
{ nodeKind: RelationalSchemaNodeKind.schema, id: 'database' },
|
|
319
|
+
{ nodeKind: RelationalSchemaNodeKind.table, id: tableName },
|
|
320
|
+
{ nodeKind: RelationalSchemaNodeKind.column, id: `column:${column}` },
|
|
321
|
+
]);
|
|
322
|
+
}
|
|
323
|
+
|
|
268
324
|
/**
|
|
269
325
|
* The FK's referenced-namespace identity comes from the target's namespace
|
|
270
326
|
* node, not the raw namespace-id string. An unbound target namespace stamps
|
|
@@ -274,6 +330,10 @@ function convertIndex(index: Index): SqlIndexIRInput {
|
|
|
274
330
|
* cross-space target whose namespace lives in another contract's storage)
|
|
275
331
|
* stamps its coordinate verbatim; namespaced targets (Postgres) resolve the
|
|
276
332
|
* real DDL schema downstream.
|
|
333
|
+
*
|
|
334
|
+
* `dependsOn` carries the referenced table (created before the FK, dropped
|
|
335
|
+
* after it) plus the FK's own columns (dropped after the FK, since dropping a
|
|
336
|
+
* column auto-drops the FK built on it).
|
|
277
337
|
*/
|
|
278
338
|
function convertForeignKey(fk: ForeignKey, storage: SqlStorage): SqlForeignKeyIRInput {
|
|
279
339
|
const targetNamespace = storage.namespaces[fk.target.namespaceId];
|
|
@@ -286,6 +346,10 @@ function convertForeignKey(fk: ForeignKey, storage: SqlStorage): SqlForeignKeyIR
|
|
|
286
346
|
...ifDefined('name', fk.name),
|
|
287
347
|
...ifDefined('onDelete', fk.onDelete),
|
|
288
348
|
...ifDefined('onUpdate', fk.onUpdate),
|
|
349
|
+
dependsOn: [
|
|
350
|
+
flatSchemaDependsOn(fk.target.tableName),
|
|
351
|
+
...flatColumnDependsOn(fk.source.tableName, fk.source.columns),
|
|
352
|
+
],
|
|
289
353
|
};
|
|
290
354
|
}
|
|
291
355
|
|
|
@@ -295,6 +359,7 @@ function convertTable(
|
|
|
295
359
|
storageTypes: ResolvedStorageTypes,
|
|
296
360
|
expandNativeType: NativeTypeExpander | undefined,
|
|
297
361
|
renderDefault: DefaultRenderer | undefined,
|
|
362
|
+
resolveDefault: DefaultResolver | undefined,
|
|
298
363
|
storage: SqlStorage,
|
|
299
364
|
): SqlTableIR {
|
|
300
365
|
const columns: Record<string, SqlColumnIRInput> = {};
|
|
@@ -305,37 +370,36 @@ function convertTable(
|
|
|
305
370
|
storageTypes,
|
|
306
371
|
expandNativeType,
|
|
307
372
|
renderDefault,
|
|
373
|
+
resolveDefault,
|
|
308
374
|
);
|
|
309
375
|
}
|
|
310
376
|
|
|
311
|
-
const satisfiedIndexColumns = new Set(backingIndexColumnKeys(table));
|
|
312
|
-
const fkBackingIndexes: SqlIndexIRInput[] = [];
|
|
313
|
-
for (const fk of table.foreignKeys) {
|
|
314
|
-
if (fk.index === false) continue;
|
|
315
|
-
const key = fk.source.columns.join(',');
|
|
316
|
-
if (satisfiedIndexColumns.has(key)) continue;
|
|
317
|
-
fkBackingIndexes.push({
|
|
318
|
-
columns: fk.source.columns,
|
|
319
|
-
unique: false,
|
|
320
|
-
name: defaultIndexName(name, fk.source.columns),
|
|
321
|
-
});
|
|
322
|
-
satisfiedIndexColumns.add(key);
|
|
323
|
-
}
|
|
324
|
-
|
|
325
377
|
const checks: SqlCheckConstraintIRInput[] | undefined =
|
|
326
378
|
table.checks && table.checks.length > 0
|
|
327
379
|
? table.checks.map((c) => convertCheck(c, storage))
|
|
328
380
|
: undefined;
|
|
329
381
|
|
|
382
|
+
const primaryKey =
|
|
383
|
+
table.primaryKey !== undefined
|
|
384
|
+
? {
|
|
385
|
+
columns: table.primaryKey.columns,
|
|
386
|
+
...ifDefined('name', table.primaryKey.name),
|
|
387
|
+
dependsOn: flatColumnDependsOn(name, table.primaryKey.columns),
|
|
388
|
+
}
|
|
389
|
+
: undefined;
|
|
390
|
+
|
|
330
391
|
return new SqlTableIR({
|
|
331
392
|
name,
|
|
332
393
|
columns,
|
|
333
|
-
...ifDefined('primaryKey',
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
394
|
+
...ifDefined('primaryKey', primaryKey),
|
|
395
|
+
// #989 persists a `constraint: false` FK's absence and its backing index as
|
|
396
|
+
// discrete entities at contract construction, so every `foreignKeys[]` entry
|
|
397
|
+
// is now constraint-bearing (no filter) and each FK-backing index is already
|
|
398
|
+
// a `table.indexes[]` entry — it flows through `convertIndex` below, carrying
|
|
399
|
+
// the object→own-column `dependsOn` edge like any other index.
|
|
400
|
+
foreignKeys: table.foreignKeys.map((fk) => convertForeignKey(fk, storage)),
|
|
401
|
+
uniques: table.uniques.map((u) => convertUnique(u, name)),
|
|
402
|
+
indexes: table.indexes.map((i) => convertIndex(i, name)),
|
|
339
403
|
...ifDefined('checks', checks),
|
|
340
404
|
});
|
|
341
405
|
}
|
|
@@ -402,6 +466,7 @@ export interface ContractToSchemaIROptions {
|
|
|
402
466
|
readonly annotationNamespace: string;
|
|
403
467
|
readonly expandNativeType?: NativeTypeExpander;
|
|
404
468
|
readonly renderDefault?: DefaultRenderer;
|
|
469
|
+
readonly resolveDefault?: DefaultResolver;
|
|
405
470
|
/**
|
|
406
471
|
* Target-supplied resolver mapping a namespace to the live database schema
|
|
407
472
|
* its enums are stored under. When provided (Postgres), namespace-scoped
|
|
@@ -462,6 +527,7 @@ export function contractNamespaceToSchemaIR(
|
|
|
462
527
|
storageTypes,
|
|
463
528
|
options.expandNativeType,
|
|
464
529
|
options.renderDefault,
|
|
530
|
+
options.resolveDefault,
|
|
465
531
|
storage,
|
|
466
532
|
);
|
|
467
533
|
}
|
|
@@ -498,6 +564,7 @@ export function contractToSchemaIR(
|
|
|
498
564
|
storageTypes,
|
|
499
565
|
options.expandNativeType,
|
|
500
566
|
options.renderDefault,
|
|
567
|
+
options.resolveDefault,
|
|
501
568
|
storage,
|
|
502
569
|
);
|
|
503
570
|
}
|
|
@@ -334,9 +334,9 @@ export interface SqlMigrationPlannerPlanOptions {
|
|
|
334
334
|
* or `null` for reconciliation flows that have no prior contract.
|
|
335
335
|
*
|
|
336
336
|
* Required at every call site so the structural fact "I have a prior
|
|
337
|
-
* contract / I don't" is visible in the type. `migration plan` reads
|
|
338
|
-
*
|
|
339
|
-
* the parsed value; `db update` / `db init` reconcile against the
|
|
337
|
+
* contract / I don't" is visible in the type. `migration plan` reads the
|
|
338
|
+
* predecessor's contract from the snapshot store by its storage hash and
|
|
339
|
+
* passes the parsed value; `db update` / `db init` reconcile against the
|
|
340
340
|
* live schema and pass `null`. Strategies that
|
|
341
341
|
* need from/to column-shape comparisons (unsafe type change, nullability
|
|
342
342
|
* tightening) use this to decide whether to emit `dataTransform`
|
|
@@ -346,6 +346,13 @@ export interface SqlMigrationPlannerPlanOptions {
|
|
|
346
346
|
* plan's `describe()` as `fromContract?.storage.storageHash ?? null`.
|
|
347
347
|
*/
|
|
348
348
|
readonly fromContract: Contract<SqlStorage> | null;
|
|
349
|
+
/**
|
|
350
|
+
* POSIX-relative path from the migration package dir to
|
|
351
|
+
* `migrations/snapshots`, e.g. `'../../snapshots'`. Threaded straight
|
|
352
|
+
* into the produced plan's `renderTypeScript()` metadata so the
|
|
353
|
+
* scaffold's contract-snapshot imports point at the deduplicated store.
|
|
354
|
+
*/
|
|
355
|
+
readonly snapshotsImportPath: string;
|
|
349
356
|
/**
|
|
350
357
|
* Active framework components participating in this composition.
|
|
351
358
|
* Each component is target-bound so SQL targets can dispatch
|
|
@@ -419,16 +426,16 @@ export interface SqlMigrationRunnerExecuteOptions<TTargetDetails> {
|
|
|
419
426
|
}
|
|
420
427
|
|
|
421
428
|
export type SqlMigrationRunnerErrorCode =
|
|
422
|
-
| 'DESTINATION_CONTRACT_MISMATCH'
|
|
423
|
-
| 'LEGACY_MARKER_SHAPE'
|
|
424
|
-
| 'MARKER_ORIGIN_MISMATCH'
|
|
425
|
-
| 'MARKER_CAS_FAILURE'
|
|
426
|
-
| 'POLICY_VIOLATION'
|
|
427
|
-
| 'PRECHECK_FAILED'
|
|
428
|
-
| 'POSTCHECK_FAILED'
|
|
429
|
-
| 'SCHEMA_VERIFY_FAILED'
|
|
430
|
-
| 'FOREIGN_KEY_VIOLATION'
|
|
431
|
-
| 'EXECUTION_FAILED';
|
|
429
|
+
| 'MIGRATION.DESTINATION_CONTRACT_MISMATCH'
|
|
430
|
+
| 'MIGRATION.LEGACY_MARKER_SHAPE'
|
|
431
|
+
| 'MIGRATION.MARKER_ORIGIN_MISMATCH'
|
|
432
|
+
| 'MIGRATION.MARKER_CAS_FAILURE'
|
|
433
|
+
| 'MIGRATION.POLICY_VIOLATION'
|
|
434
|
+
| 'MIGRATION.PRECHECK_FAILED'
|
|
435
|
+
| 'MIGRATION.POSTCHECK_FAILED'
|
|
436
|
+
| 'MIGRATION.SCHEMA_VERIFY_FAILED'
|
|
437
|
+
| 'MIGRATION.FOREIGN_KEY_VIOLATION'
|
|
438
|
+
| 'MIGRATION.EXECUTION_FAILED';
|
|
432
439
|
|
|
433
440
|
export interface SqlMigrationRunnerFailure extends MigrationRunnerFailure {
|
|
434
441
|
readonly code: SqlMigrationRunnerErrorCode;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import pluralizeLib from 'pluralize';
|
|
2
|
+
|
|
1
3
|
const PSL_RESERVED_WORDS = new Set(['model', 'enum', 'types', 'type', 'generator', 'datasource']);
|
|
2
4
|
|
|
3
5
|
const IDENTIFIER_PART_PATTERN = /[A-Za-z0-9]+/g;
|
|
@@ -143,19 +145,7 @@ export function toEnumMemberName(value: string): string {
|
|
|
143
145
|
}
|
|
144
146
|
|
|
145
147
|
export function pluralize(word: string): string {
|
|
146
|
-
|
|
147
|
-
word.endsWith('s') ||
|
|
148
|
-
word.endsWith('x') ||
|
|
149
|
-
word.endsWith('z') ||
|
|
150
|
-
word.endsWith('ch') ||
|
|
151
|
-
word.endsWith('sh')
|
|
152
|
-
) {
|
|
153
|
-
return `${word}es`;
|
|
154
|
-
}
|
|
155
|
-
if (word.endsWith('y') && !/[aeiou]y$/i.test(word)) {
|
|
156
|
-
return `${word.slice(0, -1)}ies`;
|
|
157
|
-
}
|
|
158
|
-
return `${word}s`;
|
|
148
|
+
return pluralizeLib.plural(word);
|
|
159
149
|
}
|
|
160
150
|
|
|
161
151
|
export function deriveRelationFieldName(
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
backingIndexColumnKeys,
|
|
3
|
+
isBackedByColumnKeys,
|
|
4
|
+
} from '@prisma-next/sql-contract/foreign-key-materialization';
|
|
1
5
|
import type { SqlForeignKeyIR, SqlTableIR } from '@prisma-next/sql-schema-ir/types';
|
|
2
|
-
import { backingIndexColumnKeys, isBackedByColumnKeys } from '../foreign-key-index-backing';
|
|
3
6
|
import { deriveBackRelationFieldName, deriveRelationFieldName, pluralize } from './name-transforms';
|
|
4
7
|
import type { RelationField } from './printer-config';
|
|
5
8
|
|
|
@@ -73,3 +73,91 @@ export function temporalAuthoringPresets<
|
|
|
73
73
|
},
|
|
74
74
|
} as const satisfies Record<string, AuthoringFieldPresetDescriptor>;
|
|
75
75
|
}
|
|
76
|
+
|
|
77
|
+
const TEMPORAL_PRECISION_ARG = {
|
|
78
|
+
name: 'precision',
|
|
79
|
+
kind: 'number',
|
|
80
|
+
optional: true,
|
|
81
|
+
integer: true,
|
|
82
|
+
minimum: 0,
|
|
83
|
+
} as const;
|
|
84
|
+
|
|
85
|
+
const TEMPORAL_ON_CREATE_ARG = {
|
|
86
|
+
name: 'onCreate',
|
|
87
|
+
kind: 'option',
|
|
88
|
+
values: ['now'],
|
|
89
|
+
optional: true,
|
|
90
|
+
} as const;
|
|
91
|
+
|
|
92
|
+
const TEMPORAL_ON_UPDATE_ARG = {
|
|
93
|
+
name: 'onUpdate',
|
|
94
|
+
kind: 'option',
|
|
95
|
+
values: ['now'],
|
|
96
|
+
optional: true,
|
|
97
|
+
} as const;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Selects the `timestampNow` generator descriptor for the preset's `now`
|
|
101
|
+
* token. The token is preset vocabulary; the generator id never appears in a
|
|
102
|
+
* user's spelling (ADR 169 — `timestampNow` is preset-only).
|
|
103
|
+
*/
|
|
104
|
+
function temporalPhaseTemplate<const Index extends number>(index: Index) {
|
|
105
|
+
return {
|
|
106
|
+
kind: 'select',
|
|
107
|
+
index,
|
|
108
|
+
cases: { now: { kind: 'generator', id: TIMESTAMP_NOW_GENERATOR_ID } },
|
|
109
|
+
} as const;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Builds a `temporal.<codec>` field preset for a codec that takes a precision
|
|
114
|
+
* parameter (`pg/timestamp@1`, `pg/timestamptz@1`). Arguments change field
|
|
115
|
+
* properties only — never the codec, which the caller fixes here.
|
|
116
|
+
*
|
|
117
|
+
* All three arguments are optional: omitting `precision` omits `typeParams`
|
|
118
|
+
* entirely, and omitting a phase omits that phase (both omitted omits
|
|
119
|
+
* `executionDefaults`).
|
|
120
|
+
*/
|
|
121
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
122
|
+
export function temporalCodecPresetWithPrecision<
|
|
123
|
+
const CodecId extends string,
|
|
124
|
+
const NativeType extends string,
|
|
125
|
+
>(input: { readonly codecId: CodecId; readonly nativeType: NativeType }) {
|
|
126
|
+
return {
|
|
127
|
+
kind: 'fieldPreset',
|
|
128
|
+
args: [TEMPORAL_PRECISION_ARG, TEMPORAL_ON_CREATE_ARG, TEMPORAL_ON_UPDATE_ARG],
|
|
129
|
+
output: {
|
|
130
|
+
codecId: input.codecId,
|
|
131
|
+
nativeType: input.nativeType,
|
|
132
|
+
typeParams: { precision: { kind: 'arg', index: 0 } },
|
|
133
|
+
executionDefaults: {
|
|
134
|
+
onCreate: temporalPhaseTemplate(1),
|
|
135
|
+
onUpdate: temporalPhaseTemplate(2),
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
} as const satisfies AuthoringFieldPresetDescriptor;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Builds a `temporal.<codec>` field preset for a codec with no type
|
|
143
|
+
* parameters (`sqlite/datetime@1`). As with the precision-bearing variant,
|
|
144
|
+
* both phase arguments are optional and omitting one omits that phase.
|
|
145
|
+
*/
|
|
146
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
147
|
+
export function temporalCodecPreset<
|
|
148
|
+
const CodecId extends string,
|
|
149
|
+
const NativeType extends string,
|
|
150
|
+
>(input: { readonly codecId: CodecId; readonly nativeType: NativeType }) {
|
|
151
|
+
return {
|
|
152
|
+
kind: 'fieldPreset',
|
|
153
|
+
args: [TEMPORAL_ON_CREATE_ARG, TEMPORAL_ON_UPDATE_ARG],
|
|
154
|
+
output: {
|
|
155
|
+
codecId: input.codecId,
|
|
156
|
+
nativeType: input.nativeType,
|
|
157
|
+
executionDefaults: {
|
|
158
|
+
onCreate: temporalPhaseTemplate(0),
|
|
159
|
+
onUpdate: temporalPhaseTemplate(1),
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
} as const satisfies AuthoringFieldPresetDescriptor;
|
|
163
|
+
}
|
package/src/exports/control.ts
CHANGED
|
@@ -21,6 +21,7 @@ export type {
|
|
|
21
21
|
export type {
|
|
22
22
|
ContractToSchemaIROptions,
|
|
23
23
|
DefaultRenderer,
|
|
24
|
+
DefaultResolver,
|
|
24
25
|
EnumNamespaceSchemaResolver,
|
|
25
26
|
NativeTypeExpander,
|
|
26
27
|
} from '../core/migrations/contract-to-schema-ir';
|
|
@@ -87,6 +88,8 @@ export type {
|
|
|
87
88
|
} from '../core/migrations/types';
|
|
88
89
|
export {
|
|
89
90
|
temporalAuthoringPresets,
|
|
91
|
+
temporalCodecPreset,
|
|
92
|
+
temporalCodecPresetWithPrecision,
|
|
90
93
|
timestampNowControlDescriptor,
|
|
91
94
|
} from '../core/timestamp-now-generator';
|
|
92
95
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"control-adapter-BQgad8Zc.d.mts","names":[],"sources":["../src/core/diff/sql-schema-diff.ts","../src/core/control-adapter.ts"],"mappings":";;;;;;;;;;;KAeY,iBAAA,IACV,UAAA,UACA,UAAA,aACG,aAAa;;AAAA;AAOlB;;;KAAY,oBAAA,IAAwB,UAAkB;AAAA;AAKtD;;AALsD,iBAKtC,WAAA,CAAY,CAAA,qBAAsB,CAAoB;;;;AAftE;;;;;;UCQiB,OAAA;EACf,KAAA,CAAM,GAAA,EAAK,WAAA,GAAc,OAAA,EAAS,OAAA,EAAS,cAAA,YAA0B,gBAAA;AAAA;ADCvE;;;;AAAsD;AAKtD;AALA,UCQiB,qBAAA,SAA8B,OAAA;EAC7C,qBAAA,CACE,GAAA,EAAK,WAAA,GAAc,OAAA,EACnB,OAAA,GAAU,cAAA,YACT,OAAA,CAAQ,iBAAA;AAAA;ADPyD;;;;ACPtE;;ADOsE,UCgBrD,iBAAA,0CACP,sBAAA,QAA8B,OAAA,GACpC,qBAAA;EAxBS;;;;;;;;;;;;;;AAA0E;EAwCrF,UAAA,CACE,MAAA,EAAQ,wBAAA,CAAyB,OAAA,GACjC,KAAA,WACC,OAAA,CAAQ,oBAAA;EAlC0B;;;;;;;EA2CrC,cAAA,CACE,MAAA,EAAQ,wBAAA,CAAyB,OAAA,IAChC,OAAA,CAAQ,WAAA,SAAoB,oBAAA;EA7CqB;;;;EAmDpD,UAAA,CACE,MAAA,EAAQ,wBAAA,CAAyB,OAAA,GACjC,KAAA,YACC,OAAA,UAAiB,iBAAA;EApDC;;;;;;EA4DrB,YAAA,CACE,MAAA,EAAQ,wBAAA,CAAyB,OAAA,GACjC,KAAA,UACA,WAAA;IAAA,SACW,WAAA;IAAA,SACA,WAAA;IAAA,SACA,UAAA;EAAA,IAEV,OAAA;EAxDmC;;;;;;EAgEtC,UAAA,CACE,MAAA,EAAQ,wBAAA,CAAyB,OAAA,GACjC,KAAA,UACA,WAAA;IAAA,SACW,WAAA;IAAA,SACA,WAAA;IAAA,SACA,UAAA;EAAA,IAEV,OAAA;EAlCO;;;;;;;EA2CV,YAAA,CACE,MAAA,EAAQ,wBAAA,CAAyB,OAAA,GACjC,KAAA,UACA,YAAA,UACA,WAAA;IAAA,SACW,WAAA;IAAA,SACA,WAAA;IAAA,SACA,UAAA;EAAA,IAEV,OAAA;EASgC;;;;;;EADnC,gBAAA,CACE,MAAA,EAAQ,wBAAA,CAAyB,OAAA,GACjC,KAAA,UACA,KAAA;IAAA,SACW,MAAA;IAAA,SACA,IAAA;IAAA,SACA,EAAA;IAAA,SACA,aAAA;IAAA,SACA,aAAA;IAAA,SACA,UAAA;IAAA,SACA,uBAAA;EAAA,IAEV,OAAA;EA/G8B;;;;;;;;;;;;;;;;;EAkIjC,UAAA,CACE,MAAA,EAAQ,wBAAA,CAAyB,OAAA,GACjC,QAAA,YACA,MAAA,YACC,OAAA,CAAQ,eAAA;EAhGX;;;;;EAAA,SAuGS,gBAAA,GAAmB,iBAAA;EApGR;;;;;EAAA,SA2GX,mBAAA,GAAsB,oBAAA;EA/FlB;;;;EAqGb,4BAAA,aAAyC,OAAA;EAzFzC;;;;EA+FA,0BAAA,aAAuC,OAAA;AAAA;;;;;;;UASxB,2BAAA;EAtFb;;;;;;EA6FF,MAAA,CAAO,KAAA,EAAO,YAAA,QAAoB,OAAA,IAAW,iBAAA,CAAkB,OAAA;AAAA"}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
//#region src/core/foreign-key-index-backing.ts
|
|
2
|
-
/**
|
|
3
|
-
* The column-list keys (`"colA,colB"`, order preserved) a table's own
|
|
4
|
-
* indexes, unique constraints, and primary key already back. A foreign key
|
|
5
|
-
* whose source columns join to one of these keys needs no separately
|
|
6
|
-
* derived backing index.
|
|
7
|
-
*
|
|
8
|
-
* Shared by {@link isBackedByColumnKeys}'s two callers, which must agree on
|
|
9
|
-
* what counts as "backed": `contract-to-schema-ir.ts`'s `convertTable`
|
|
10
|
-
* (deriving the FK-backing-index expectation `db verify` checks against a
|
|
11
|
-
* live database) and the postgres PSL inferrer (deciding whether an
|
|
12
|
-
* introspected relation needs an explicit `index: false`).
|
|
13
|
-
*/
|
|
14
|
-
function backingIndexColumnKeys(table) {
|
|
15
|
-
return [
|
|
16
|
-
...table.indexes.map((index) => index.columns.join(",")),
|
|
17
|
-
...table.uniques.map((unique) => unique.columns.join(",")),
|
|
18
|
-
...table.primaryKey ? [table.primaryKey.columns.join(",")] : []
|
|
19
|
-
];
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Whether `columns`, in order, matches one of `backingKeys` (see
|
|
23
|
-
* {@link backingIndexColumnKeys}). Order-sensitive: `(a, b)` does not
|
|
24
|
-
* satisfy a backing index declared as `(b, a)`.
|
|
25
|
-
*/
|
|
26
|
-
function isBackedByColumnKeys(columns, backingKeys) {
|
|
27
|
-
return backingKeys.includes(columns.join(","));
|
|
28
|
-
}
|
|
29
|
-
//#endregion
|
|
30
|
-
export { isBackedByColumnKeys as n, backingIndexColumnKeys as t };
|
|
31
|
-
|
|
32
|
-
//# sourceMappingURL=foreign-key-index-backing-BP71iI-Q.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"foreign-key-index-backing-BP71iI-Q.mjs","names":[],"sources":["../src/core/foreign-key-index-backing.ts"],"sourcesContent":["export type BackingIndexCandidates = {\n readonly indexes: readonly { readonly columns: readonly string[] }[];\n readonly uniques: readonly { readonly columns: readonly string[] }[];\n readonly primaryKey?: { readonly columns: readonly string[] } | undefined;\n};\n\n/**\n * The column-list keys (`\"colA,colB\"`, order preserved) a table's own\n * indexes, unique constraints, and primary key already back. A foreign key\n * whose source columns join to one of these keys needs no separately\n * derived backing index.\n *\n * Shared by {@link isBackedByColumnKeys}'s two callers, which must agree on\n * what counts as \"backed\": `contract-to-schema-ir.ts`'s `convertTable`\n * (deriving the FK-backing-index expectation `db verify` checks against a\n * live database) and the postgres PSL inferrer (deciding whether an\n * introspected relation needs an explicit `index: false`).\n */\nexport function backingIndexColumnKeys(table: BackingIndexCandidates): readonly string[] {\n return [\n ...table.indexes.map((index) => index.columns.join(',')),\n ...table.uniques.map((unique) => unique.columns.join(',')),\n ...(table.primaryKey ? [table.primaryKey.columns.join(',')] : []),\n ];\n}\n\n/**\n * Whether `columns`, in order, matches one of `backingKeys` (see\n * {@link backingIndexColumnKeys}). Order-sensitive: `(a, b)` does not\n * satisfy a backing index declared as `(b, a)`.\n */\nexport function isBackedByColumnKeys(\n columns: readonly string[],\n backingKeys: readonly string[],\n): boolean {\n return backingKeys.includes(columns.join(','));\n}\n"],"mappings":";;;;;;;;;;;;;AAkBA,SAAgB,uBAAuB,OAAkD;CACvF,OAAO;EACL,GAAG,MAAM,QAAQ,KAAK,UAAU,MAAM,QAAQ,KAAK,GAAG,CAAC;EACvD,GAAG,MAAM,QAAQ,KAAK,WAAW,OAAO,QAAQ,KAAK,GAAG,CAAC;EACzD,GAAI,MAAM,aAAa,CAAC,MAAM,WAAW,QAAQ,KAAK,GAAG,CAAC,IAAI,CAAC;CACjE;AACF;;;;;;AAOA,SAAgB,qBACd,SACA,aACS;CACT,OAAO,YAAY,SAAS,QAAQ,KAAK,GAAG,CAAC;AAC/C"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"schema-differ-DnoopSXm.d.mts","names":[],"sources":["../src/core/migrations/schema-differ.ts"],"mappings":";;;;;;;;;AAcA;;;;;;UAAiB,mBAAA;EAiBU;EAAA,SAfhB,MAAA,WAAiB,eAAA;EAeY;;;;;;;;EAAA,SAN7B,oBAAA,GAAuB,KAAA,EAAO,eAAA,KAAoB,aAAA;EAMT;;;AAAuB;AAG3E;EAHoD,SAAzC,cAAA,EAAgB,aAAA;IAAA,SAAyB,MAAA,EAAQ,eAAA;EAAA;AAAA;AAAA,UAG3C,kBAAA;EAAA,SACN,QAAA,EAAU,QAAA,CAAS,UAAA;EAAA,SACnB,MAAA,EAAQ,eAAA;EAAA,SACR,mBAAA,EAAqB,aAAA,CAAc,8BAAA;AAAA;AAAA,KAGlC,eAAA,IAAmB,KAAA,EAAO,kBAAA,KAAuB,mBAAmB"}
|