zenstack-kit 0.1.2 → 0.1.4
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/cli/commands.d.ts +1 -0
- package/dist/cli/commands.d.ts.map +1 -1
- package/dist/cli/commands.js +21 -3
- package/dist/migrations/prisma.d.ts +10 -0
- package/dist/migrations/prisma.d.ts.map +1 -1
- package/dist/migrations/prisma.js +346 -107
- package/dist/schema/pull.d.ts.map +1 -1
- package/dist/schema/pull.js +2 -1
- package/package.json +1 -1
package/dist/cli/commands.d.ts
CHANGED
|
@@ -41,6 +41,7 @@ export declare class CommandError extends Error {
|
|
|
41
41
|
*/
|
|
42
42
|
export declare function resolveConfig(ctx: CommandContext): Promise<{
|
|
43
43
|
config: ZenStackKitConfig;
|
|
44
|
+
configDir: string;
|
|
44
45
|
schemaPath: string;
|
|
45
46
|
outputPath: string;
|
|
46
47
|
dialect: "sqlite" | "postgres" | "mysql";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/cli/commands.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAkBH,OAAO,KAAK,EAAE,YAAY,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE5D,MAAM,MAAM,KAAK,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;AAE9F,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,cAAc,CAAC;IACxB,GAAG,EAAE,KAAK,CAAC;IACX,oBAAoB,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,cAAc,CAAC,CAAC;IAC9D,eAAe,CAAC,EAAE,MAAM,OAAO,CAAC,UAAU,GAAG,gBAAgB,CAAC,CAAC;IAC/D,iBAAiB,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAClE,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;IACxE,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;IACxF,mBAAmB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/D,sBAAsB,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC,sBAAsB,CAAC,CAAC;CACrF;AAED,qBAAa,YAAa,SAAQ,KAAK;gBACzB,OAAO,EAAE,MAAM;CAI5B;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC;IAChE,MAAM,EAAE,iBAAiB,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,QAAQ,GAAG,UAAU,GAAG,OAAO,CAAC;CAC1C,CAAC,CAkBD;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAI7D;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,iBAAiB,EACzB,OAAO,EAAE,QAAQ,GAAG,UAAU,GAAG,OAAO,GACvC,MAAM,GAAG,SAAS,CAKpB;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAoF3E;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/cli/commands.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/commands.js
CHANGED
|
@@ -30,7 +30,7 @@ export async function resolveConfig(ctx) {
|
|
|
30
30
|
// Resolve paths relative to config file location
|
|
31
31
|
const schemaPath = path.resolve(configDir, relativeSchemaPath);
|
|
32
32
|
const outputPath = path.resolve(configDir, relativeOutputPath);
|
|
33
|
-
return { config, schemaPath, outputPath, dialect };
|
|
33
|
+
return { config, configDir, schemaPath, outputPath, dialect };
|
|
34
34
|
}
|
|
35
35
|
/**
|
|
36
36
|
* Validate that the schema file exists (schemaPath should be absolute)
|
|
@@ -177,6 +177,23 @@ export async function runMigrateApply(ctx) {
|
|
|
177
177
|
migrationsTable,
|
|
178
178
|
migrationsSchema,
|
|
179
179
|
});
|
|
180
|
+
// Handle coherence errors
|
|
181
|
+
if (result.coherenceErrors && result.coherenceErrors.length > 0) {
|
|
182
|
+
ctx.log("error", "Migration history is inconsistent");
|
|
183
|
+
ctx.log("error", "");
|
|
184
|
+
ctx.log("error", "The following issues were found:");
|
|
185
|
+
for (const err of result.coherenceErrors) {
|
|
186
|
+
ctx.log("error", ` - ${err.details}`);
|
|
187
|
+
}
|
|
188
|
+
ctx.log("error", "");
|
|
189
|
+
ctx.log("error", "This can happen when:");
|
|
190
|
+
ctx.log("error", " - Migrations were applied manually without using zenstack-kit");
|
|
191
|
+
ctx.log("error", " - The migration log file was modified or deleted");
|
|
192
|
+
ctx.log("error", " - Different migration histories exist across environments");
|
|
193
|
+
ctx.log("error", "");
|
|
194
|
+
ctx.log("error", "To resolve, ensure your migration log matches your database state.");
|
|
195
|
+
throw new CommandError("Migration history is inconsistent");
|
|
196
|
+
}
|
|
180
197
|
if (result.applied.length === 0 && !result.failed) {
|
|
181
198
|
ctx.log("warning", "No pending migrations");
|
|
182
199
|
if (result.alreadyApplied.length > 0) {
|
|
@@ -273,13 +290,14 @@ export async function runInit(ctx) {
|
|
|
273
290
|
* pull command
|
|
274
291
|
*/
|
|
275
292
|
export async function runPull(ctx) {
|
|
276
|
-
const { config, dialect, outputPath: migrationsPath } = await resolveConfig(ctx);
|
|
293
|
+
const { config, configDir, dialect, outputPath: migrationsPath } = await resolveConfig(ctx);
|
|
277
294
|
const connectionUrl = getConnectionUrl(config, dialect);
|
|
278
295
|
if (dialect !== "sqlite" && !connectionUrl) {
|
|
279
296
|
throw new CommandError("Database connection URL is required for non-sqlite dialects.");
|
|
280
297
|
}
|
|
281
298
|
const databasePath = dialect === "sqlite" ? connectionUrl : undefined;
|
|
282
|
-
const
|
|
299
|
+
const relativeSchemaOutputPath = ctx.options.output || config.schema || "./schema.zmodel";
|
|
300
|
+
const schemaOutputPath = path.resolve(configDir, relativeSchemaOutputPath);
|
|
283
301
|
// Check for existing files that would be affected
|
|
284
302
|
const existingFiles = [];
|
|
285
303
|
if (fs.existsSync(schemaOutputPath)) {
|
|
@@ -63,6 +63,16 @@ export interface ApplyPrismaMigrationsResult {
|
|
|
63
63
|
migrationName: string;
|
|
64
64
|
error: string;
|
|
65
65
|
};
|
|
66
|
+
coherenceErrors?: MigrationCoherenceError[];
|
|
67
|
+
}
|
|
68
|
+
export interface MigrationCoherenceError {
|
|
69
|
+
type: "missing_from_log" | "missing_from_db" | "missing_from_disk" | "order_mismatch" | "checksum_mismatch";
|
|
70
|
+
migrationName: string;
|
|
71
|
+
details: string;
|
|
72
|
+
}
|
|
73
|
+
export interface MigrationCoherenceResult {
|
|
74
|
+
isCoherent: boolean;
|
|
75
|
+
errors: MigrationCoherenceError[];
|
|
66
76
|
}
|
|
67
77
|
export interface PreviewPrismaMigrationsResult {
|
|
68
78
|
pending: Array<{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prisma.d.ts","sourceRoot":"","sources":["../../src/migrations/prisma.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAE9D,OAAO,KAAK,EAAE,cAAc,EAA6B,MAAM,uBAAuB,CAAC;AAkBvF,MAAM,WAAW,sBAAsB;IACrC,qBAAqB;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,mCAAmC;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,2CAA2C;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,0CAA0C;IAC1C,OAAO,EAAE,aAAa,CAAC;IACvB,4BAA4B;IAC5B,YAAY,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnD,6BAA6B;IAC7B,aAAa,CAAC,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpE;AAED,MAAM,WAAW,eAAe;IAC9B,6CAA6C;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,oCAAoC;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,gBAAgB;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,4BAA4B;IAC3C,6BAA6B;IAC7B,gBAAgB,EAAE,MAAM,CAAC;IACzB,uBAAuB;IACvB,OAAO,EAAE,aAAa,CAAC;IACvB,8BAA8B;IAC9B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,2BAA2B;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,0DAA0D;IAC1D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,2DAA2D;IAC3D,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,2BAA2B;IAC1C,OAAO,EAAE,KAAK,CAAC;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5D,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,MAAM,CAAC,EAAE;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"prisma.d.ts","sourceRoot":"","sources":["../../src/migrations/prisma.ts"],"names":[],"mappings":"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"}
|
|
@@ -67,6 +67,104 @@ export async function writeSnapshot(snapshotPath, schema) {
|
|
|
67
67
|
/**
|
|
68
68
|
* Diff two schemas and return the changes
|
|
69
69
|
*/
|
|
70
|
+
function diffTableChanges(previousModel, currentModel, tableName) {
|
|
71
|
+
const addedFields = [];
|
|
72
|
+
const removedFields = [];
|
|
73
|
+
const alteredFields = [];
|
|
74
|
+
const addedUniqueConstraints = [];
|
|
75
|
+
const removedUniqueConstraints = [];
|
|
76
|
+
const addedIndexes = [];
|
|
77
|
+
const removedIndexes = [];
|
|
78
|
+
const addedForeignKeys = [];
|
|
79
|
+
const removedForeignKeys = [];
|
|
80
|
+
const primaryKeyChanges = [];
|
|
81
|
+
const previousFields = new Map(previousModel.columns.map((f) => [f.name, f]));
|
|
82
|
+
const currentFields = new Map(currentModel.columns.map((f) => [f.name, f]));
|
|
83
|
+
for (const [columnName, column] of currentFields.entries()) {
|
|
84
|
+
if (!previousFields.has(columnName)) {
|
|
85
|
+
addedFields.push({ tableName, column });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
for (const [columnName, column] of previousFields.entries()) {
|
|
89
|
+
if (!currentFields.has(columnName)) {
|
|
90
|
+
removedFields.push({ tableName, column });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
for (const [columnName, currentColumn] of currentFields.entries()) {
|
|
94
|
+
const previousColumn = previousFields.get(columnName);
|
|
95
|
+
if (!previousColumn)
|
|
96
|
+
continue;
|
|
97
|
+
if (previousColumn.type !== currentColumn.type ||
|
|
98
|
+
previousColumn.notNull !== currentColumn.notNull ||
|
|
99
|
+
previousColumn.default !== currentColumn.default) {
|
|
100
|
+
alteredFields.push({
|
|
101
|
+
tableName,
|
|
102
|
+
columnName,
|
|
103
|
+
previous: previousColumn,
|
|
104
|
+
current: currentColumn,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const prevUnique = new Map(previousModel.uniqueConstraints.map((c) => [c.name, c]));
|
|
109
|
+
const currUnique = new Map(currentModel.uniqueConstraints.map((c) => [c.name, c]));
|
|
110
|
+
for (const [name, constraint] of currUnique.entries()) {
|
|
111
|
+
if (!prevUnique.has(name)) {
|
|
112
|
+
addedUniqueConstraints.push({ tableName, constraint });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
for (const [name, constraint] of prevUnique.entries()) {
|
|
116
|
+
if (!currUnique.has(name)) {
|
|
117
|
+
removedUniqueConstraints.push({ tableName, constraint });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const prevIndexes = new Map(previousModel.indexes.map((i) => [i.name, i]));
|
|
121
|
+
const currIndexes = new Map(currentModel.indexes.map((i) => [i.name, i]));
|
|
122
|
+
for (const [name, index] of currIndexes.entries()) {
|
|
123
|
+
if (!prevIndexes.has(name)) {
|
|
124
|
+
addedIndexes.push({ tableName, index });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
for (const [name, index] of prevIndexes.entries()) {
|
|
128
|
+
if (!currIndexes.has(name)) {
|
|
129
|
+
removedIndexes.push({ tableName, index });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const prevFks = new Map(previousModel.foreignKeys.map((f) => [f.name, f]));
|
|
133
|
+
const currFks = new Map(currentModel.foreignKeys.map((f) => [f.name, f]));
|
|
134
|
+
for (const [name, fk] of currFks.entries()) {
|
|
135
|
+
if (!prevFks.has(name)) {
|
|
136
|
+
addedForeignKeys.push({ tableName, foreignKey: fk });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
for (const [name, fk] of prevFks.entries()) {
|
|
140
|
+
if (!currFks.has(name)) {
|
|
141
|
+
removedForeignKeys.push({ tableName, foreignKey: fk });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const prevPk = previousModel.primaryKey;
|
|
145
|
+
const currPk = currentModel.primaryKey;
|
|
146
|
+
const pkEqual = (prevPk?.name ?? "") === (currPk?.name ?? "") &&
|
|
147
|
+
JSON.stringify(prevPk?.columns ?? []) === JSON.stringify(currPk?.columns ?? []);
|
|
148
|
+
if (!pkEqual) {
|
|
149
|
+
primaryKeyChanges.push({
|
|
150
|
+
tableName,
|
|
151
|
+
previous: prevPk,
|
|
152
|
+
current: currPk,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
addedFields,
|
|
157
|
+
removedFields,
|
|
158
|
+
alteredFields,
|
|
159
|
+
addedUniqueConstraints,
|
|
160
|
+
removedUniqueConstraints,
|
|
161
|
+
addedIndexes,
|
|
162
|
+
removedIndexes,
|
|
163
|
+
addedForeignKeys,
|
|
164
|
+
removedForeignKeys,
|
|
165
|
+
primaryKeyChanges,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
70
168
|
function diffSchemas(previous, current) {
|
|
71
169
|
const previousModels = new Map();
|
|
72
170
|
const currentModels = new Map();
|
|
@@ -100,85 +198,17 @@ function diffSchemas(previous, current) {
|
|
|
100
198
|
const previousModel = previousModels.get(tableName);
|
|
101
199
|
if (!previousModel)
|
|
102
200
|
continue;
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
for (const [columnName, currentColumn] of currentFields.entries()) {
|
|
117
|
-
const previousColumn = previousFields.get(columnName);
|
|
118
|
-
if (!previousColumn)
|
|
119
|
-
continue;
|
|
120
|
-
if (previousColumn.type !== currentColumn.type ||
|
|
121
|
-
previousColumn.notNull !== currentColumn.notNull ||
|
|
122
|
-
previousColumn.default !== currentColumn.default) {
|
|
123
|
-
alteredFields.push({
|
|
124
|
-
tableName,
|
|
125
|
-
columnName,
|
|
126
|
-
previous: previousColumn,
|
|
127
|
-
current: currentColumn,
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
// Unique constraint changes
|
|
132
|
-
const prevUnique = new Map(previousModel.uniqueConstraints.map((c) => [c.name, c]));
|
|
133
|
-
const currUnique = new Map(currentModel.uniqueConstraints.map((c) => [c.name, c]));
|
|
134
|
-
for (const [name, constraint] of currUnique.entries()) {
|
|
135
|
-
if (!prevUnique.has(name)) {
|
|
136
|
-
addedUniqueConstraints.push({ tableName, constraint });
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
for (const [name, constraint] of prevUnique.entries()) {
|
|
140
|
-
if (!currUnique.has(name)) {
|
|
141
|
-
removedUniqueConstraints.push({ tableName, constraint });
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
// Index changes
|
|
145
|
-
const prevIndexes = new Map(previousModel.indexes.map((i) => [i.name, i]));
|
|
146
|
-
const currIndexes = new Map(currentModel.indexes.map((i) => [i.name, i]));
|
|
147
|
-
for (const [name, index] of currIndexes.entries()) {
|
|
148
|
-
if (!prevIndexes.has(name)) {
|
|
149
|
-
addedIndexes.push({ tableName, index });
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
for (const [name, index] of prevIndexes.entries()) {
|
|
153
|
-
if (!currIndexes.has(name)) {
|
|
154
|
-
removedIndexes.push({ tableName, index });
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
// Foreign key changes
|
|
158
|
-
const prevFks = new Map(previousModel.foreignKeys.map((f) => [f.name, f]));
|
|
159
|
-
const currFks = new Map(currentModel.foreignKeys.map((f) => [f.name, f]));
|
|
160
|
-
for (const [name, fk] of currFks.entries()) {
|
|
161
|
-
if (!prevFks.has(name)) {
|
|
162
|
-
addedForeignKeys.push({ tableName, foreignKey: fk });
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
for (const [name, fk] of prevFks.entries()) {
|
|
166
|
-
if (!currFks.has(name)) {
|
|
167
|
-
removedForeignKeys.push({ tableName, foreignKey: fk });
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
// Primary key changes
|
|
171
|
-
const prevPk = previousModel.primaryKey;
|
|
172
|
-
const currPk = currentModel.primaryKey;
|
|
173
|
-
const pkEqual = (prevPk?.name ?? "") === (currPk?.name ?? "") &&
|
|
174
|
-
JSON.stringify(prevPk?.columns ?? []) === JSON.stringify(currPk?.columns ?? []);
|
|
175
|
-
if (!pkEqual) {
|
|
176
|
-
primaryKeyChanges.push({
|
|
177
|
-
tableName,
|
|
178
|
-
previous: prevPk,
|
|
179
|
-
current: currPk,
|
|
180
|
-
});
|
|
181
|
-
}
|
|
201
|
+
const modelDiff = diffTableChanges(previousModel, currentModel, tableName);
|
|
202
|
+
addedFields.push(...modelDiff.addedFields);
|
|
203
|
+
removedFields.push(...modelDiff.removedFields);
|
|
204
|
+
alteredFields.push(...modelDiff.alteredFields);
|
|
205
|
+
addedUniqueConstraints.push(...modelDiff.addedUniqueConstraints);
|
|
206
|
+
removedUniqueConstraints.push(...modelDiff.removedUniqueConstraints);
|
|
207
|
+
addedIndexes.push(...modelDiff.addedIndexes);
|
|
208
|
+
removedIndexes.push(...modelDiff.removedIndexes);
|
|
209
|
+
addedForeignKeys.push(...modelDiff.addedForeignKeys);
|
|
210
|
+
removedForeignKeys.push(...modelDiff.removedForeignKeys);
|
|
211
|
+
primaryKeyChanges.push(...modelDiff.primaryKeyChanges);
|
|
182
212
|
}
|
|
183
213
|
return {
|
|
184
214
|
addedModels,
|
|
@@ -197,6 +227,136 @@ function diffSchemas(previous, current) {
|
|
|
197
227
|
renamedColumns: [],
|
|
198
228
|
};
|
|
199
229
|
}
|
|
230
|
+
function columnsSignature(columns) {
|
|
231
|
+
return columns.join("|");
|
|
232
|
+
}
|
|
233
|
+
function consumeSignature(map, signature) {
|
|
234
|
+
const count = map.get(signature) ?? 0;
|
|
235
|
+
if (count > 0) {
|
|
236
|
+
map.set(signature, count - 1);
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
function buildSignatureCount(items, getSignature) {
|
|
242
|
+
const counts = new Map();
|
|
243
|
+
for (const item of items) {
|
|
244
|
+
const signature = getSignature(item);
|
|
245
|
+
counts.set(signature, (counts.get(signature) ?? 0) + 1);
|
|
246
|
+
}
|
|
247
|
+
return counts;
|
|
248
|
+
}
|
|
249
|
+
function columnsEqual(a, b) {
|
|
250
|
+
return JSON.stringify(a ?? []) === JSON.stringify(b ?? []);
|
|
251
|
+
}
|
|
252
|
+
function filterRenamedConstraintChanges(previousModel, currentModel, modelDiff) {
|
|
253
|
+
const prevUnique = buildSignatureCount(previousModel.uniqueConstraints, (c) => columnsSignature(c.columns));
|
|
254
|
+
const currUnique = buildSignatureCount(currentModel.uniqueConstraints, (c) => columnsSignature(c.columns));
|
|
255
|
+
const prevIndexes = buildSignatureCount(previousModel.indexes, (i) => columnsSignature(i.columns));
|
|
256
|
+
const currIndexes = buildSignatureCount(currentModel.indexes, (i) => columnsSignature(i.columns));
|
|
257
|
+
const prevFks = buildSignatureCount(previousModel.foreignKeys, (f) => `${columnsSignature(f.columns)}->${f.referencedTable}:${columnsSignature(f.referencedColumns)}`);
|
|
258
|
+
const currFks = buildSignatureCount(currentModel.foreignKeys, (f) => `${columnsSignature(f.columns)}->${f.referencedTable}:${columnsSignature(f.referencedColumns)}`);
|
|
259
|
+
const addedUniqueConstraints = modelDiff.addedUniqueConstraints.filter(({ constraint }) => !consumeSignature(prevUnique, columnsSignature(constraint.columns)));
|
|
260
|
+
const removedUniqueConstraints = modelDiff.removedUniqueConstraints.filter(({ constraint }) => !consumeSignature(currUnique, columnsSignature(constraint.columns)));
|
|
261
|
+
const addedIndexes = modelDiff.addedIndexes.filter(({ index }) => !consumeSignature(prevIndexes, columnsSignature(index.columns)));
|
|
262
|
+
const removedIndexes = modelDiff.removedIndexes.filter(({ index }) => !consumeSignature(currIndexes, columnsSignature(index.columns)));
|
|
263
|
+
const addedForeignKeys = modelDiff.addedForeignKeys.filter(({ foreignKey }) => !consumeSignature(prevFks, `${columnsSignature(foreignKey.columns)}->${foreignKey.referencedTable}:${columnsSignature(foreignKey.referencedColumns)}`));
|
|
264
|
+
const removedForeignKeys = modelDiff.removedForeignKeys.filter(({ foreignKey }) => !consumeSignature(currFks, `${columnsSignature(foreignKey.columns)}->${foreignKey.referencedTable}:${columnsSignature(foreignKey.referencedColumns)}`));
|
|
265
|
+
let primaryKeyChanges = modelDiff.primaryKeyChanges;
|
|
266
|
+
if (previousModel.primaryKey && currentModel.primaryKey) {
|
|
267
|
+
if (columnsEqual(previousModel.primaryKey.columns, currentModel.primaryKey.columns)) {
|
|
268
|
+
primaryKeyChanges = [];
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return {
|
|
272
|
+
...modelDiff,
|
|
273
|
+
addedUniqueConstraints,
|
|
274
|
+
removedUniqueConstraints,
|
|
275
|
+
addedIndexes,
|
|
276
|
+
removedIndexes,
|
|
277
|
+
addedForeignKeys,
|
|
278
|
+
removedForeignKeys,
|
|
279
|
+
primaryKeyChanges,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
function applyRenameMappings(diff, renameTables = [], renameColumns = []) {
|
|
283
|
+
const removedModels = [...diff.removedModels];
|
|
284
|
+
const addedModels = [...diff.addedModels];
|
|
285
|
+
const removedFields = [...diff.removedFields];
|
|
286
|
+
const addedFields = [...diff.addedFields];
|
|
287
|
+
const alteredFields = [...diff.alteredFields];
|
|
288
|
+
const addedUniqueConstraints = [...diff.addedUniqueConstraints];
|
|
289
|
+
const removedUniqueConstraints = [...diff.removedUniqueConstraints];
|
|
290
|
+
const addedIndexes = [...diff.addedIndexes];
|
|
291
|
+
const removedIndexes = [...diff.removedIndexes];
|
|
292
|
+
const addedForeignKeys = [...diff.addedForeignKeys];
|
|
293
|
+
const removedForeignKeys = [...diff.removedForeignKeys];
|
|
294
|
+
const primaryKeyChanges = [...diff.primaryKeyChanges];
|
|
295
|
+
const renamedTables = [];
|
|
296
|
+
const renamedColumns = [];
|
|
297
|
+
const renamedTableMap = new Map();
|
|
298
|
+
renameTables.forEach((mapping) => {
|
|
299
|
+
const fromIndex = removedModels.findIndex((model) => model.name === mapping.from);
|
|
300
|
+
const toIndex = addedModels.findIndex((model) => model.name === mapping.to);
|
|
301
|
+
if (fromIndex === -1 || toIndex === -1) {
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
const previousModel = removedModels[fromIndex];
|
|
305
|
+
const currentModel = addedModels[toIndex];
|
|
306
|
+
removedModels.splice(fromIndex, 1);
|
|
307
|
+
addedModels.splice(toIndex, 1);
|
|
308
|
+
renamedTables.push({ from: mapping.from, to: mapping.to });
|
|
309
|
+
renamedTableMap.set(mapping.from, mapping.to);
|
|
310
|
+
const modelDiff = filterRenamedConstraintChanges(previousModel, currentModel, diffTableChanges(previousModel, currentModel, mapping.to));
|
|
311
|
+
addedFields.push(...modelDiff.addedFields);
|
|
312
|
+
removedFields.push(...modelDiff.removedFields);
|
|
313
|
+
alteredFields.push(...modelDiff.alteredFields);
|
|
314
|
+
addedUniqueConstraints.push(...modelDiff.addedUniqueConstraints);
|
|
315
|
+
removedUniqueConstraints.push(...modelDiff.removedUniqueConstraints);
|
|
316
|
+
addedIndexes.push(...modelDiff.addedIndexes);
|
|
317
|
+
removedIndexes.push(...modelDiff.removedIndexes);
|
|
318
|
+
addedForeignKeys.push(...modelDiff.addedForeignKeys);
|
|
319
|
+
removedForeignKeys.push(...modelDiff.removedForeignKeys);
|
|
320
|
+
primaryKeyChanges.push(...modelDiff.primaryKeyChanges);
|
|
321
|
+
});
|
|
322
|
+
const remapTableName = (tableName) => renamedTableMap.get(tableName) ?? tableName;
|
|
323
|
+
const remapTableEntries = (items) => items.map((item) => ({ ...item, tableName: remapTableName(item.tableName) }));
|
|
324
|
+
if (renamedTableMap.size > 0) {
|
|
325
|
+
removedFields.forEach((entry) => {
|
|
326
|
+
const mapped = renamedTableMap.get(entry.tableName);
|
|
327
|
+
if (mapped) {
|
|
328
|
+
entry.tableName = mapped;
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
renameColumns.forEach((mapping) => {
|
|
333
|
+
const mappedTable = remapTableName(mapping.table);
|
|
334
|
+
const removedIdx = removedFields.findIndex((f) => f.tableName === mappedTable && f.column.name === mapping.from);
|
|
335
|
+
const addedIdx = addedFields.findIndex((f) => f.tableName === mappedTable && f.column.name === mapping.to);
|
|
336
|
+
if (removedIdx !== -1 && addedIdx !== -1) {
|
|
337
|
+
removedFields.splice(removedIdx, 1);
|
|
338
|
+
addedFields.splice(addedIdx, 1);
|
|
339
|
+
renamedColumns.push({ tableName: mappedTable, from: mapping.from, to: mapping.to });
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
return {
|
|
343
|
+
...diff,
|
|
344
|
+
removedModels,
|
|
345
|
+
addedModels,
|
|
346
|
+
removedFields: remapTableEntries(removedFields),
|
|
347
|
+
addedFields: remapTableEntries(addedFields),
|
|
348
|
+
alteredFields: remapTableEntries(alteredFields),
|
|
349
|
+
renamedTables,
|
|
350
|
+
renamedColumns,
|
|
351
|
+
addedUniqueConstraints: remapTableEntries(addedUniqueConstraints),
|
|
352
|
+
removedUniqueConstraints: remapTableEntries(removedUniqueConstraints),
|
|
353
|
+
addedIndexes: remapTableEntries(addedIndexes),
|
|
354
|
+
removedIndexes: remapTableEntries(removedIndexes),
|
|
355
|
+
addedForeignKeys: remapTableEntries(addedForeignKeys),
|
|
356
|
+
removedForeignKeys: remapTableEntries(removedForeignKeys),
|
|
357
|
+
primaryKeyChanges: remapTableEntries(primaryKeyChanges),
|
|
358
|
+
};
|
|
359
|
+
}
|
|
200
360
|
/**
|
|
201
361
|
* Topologically sort tables so that referenced tables come before tables that reference them.
|
|
202
362
|
* Tables with no foreign keys come first, then tables that only reference already-ordered tables.
|
|
@@ -357,30 +517,7 @@ export async function createPrismaMigration(options) {
|
|
|
357
517
|
const currentSchema = await generateSchemaSnapshot(options.schemaPath);
|
|
358
518
|
const { snapshotPath } = getSnapshotPaths(options.outputPath);
|
|
359
519
|
const previousSnapshot = await readSnapshot(snapshotPath);
|
|
360
|
-
|
|
361
|
-
// Apply rename mappings
|
|
362
|
-
if (options.renameTables?.length || options.renameColumns?.length) {
|
|
363
|
-
// Handle table renames
|
|
364
|
-
for (const mapping of options.renameTables ?? []) {
|
|
365
|
-
const removedIdx = diff.removedModels.findIndex((m) => m.name === mapping.from);
|
|
366
|
-
const addedIdx = diff.addedModels.findIndex((m) => m.name === mapping.to);
|
|
367
|
-
if (removedIdx !== -1 && addedIdx !== -1) {
|
|
368
|
-
diff.removedModels.splice(removedIdx, 1);
|
|
369
|
-
diff.addedModels.splice(addedIdx, 1);
|
|
370
|
-
diff.renamedTables.push(mapping);
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
// Handle column renames
|
|
374
|
-
for (const mapping of options.renameColumns ?? []) {
|
|
375
|
-
const removedIdx = diff.removedFields.findIndex((f) => f.tableName === mapping.table && f.column.name === mapping.from);
|
|
376
|
-
const addedIdx = diff.addedFields.findIndex((f) => f.tableName === mapping.table && f.column.name === mapping.to);
|
|
377
|
-
if (removedIdx !== -1 && addedIdx !== -1) {
|
|
378
|
-
diff.removedFields.splice(removedIdx, 1);
|
|
379
|
-
diff.addedFields.splice(addedIdx, 1);
|
|
380
|
-
diff.renamedColumns.push({ tableName: mapping.table, from: mapping.from, to: mapping.to });
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
}
|
|
520
|
+
const diff = applyRenameMappings(diffSchemas(previousSnapshot?.schema ?? null, currentSchema), options.renameTables, options.renameColumns);
|
|
384
521
|
const { up, down } = buildSqlStatements(diff, options.dialect);
|
|
385
522
|
if (up.length === 0) {
|
|
386
523
|
return null;
|
|
@@ -554,6 +691,79 @@ async function recordMigration(db, tableName, schema, dialect, migrationName, ch
|
|
|
554
691
|
export function calculateChecksum(sql) {
|
|
555
692
|
return crypto.createHash("sha256").update(sql).digest("hex");
|
|
556
693
|
}
|
|
694
|
+
/**
|
|
695
|
+
* Validate that the database's applied migrations are coherent with the migration log.
|
|
696
|
+
*
|
|
697
|
+
* Coherence rules:
|
|
698
|
+
* 1. Every migration applied in the DB must exist in the migration log
|
|
699
|
+
* 2. Applied migrations must be a prefix of the log (no gaps)
|
|
700
|
+
* 3. Checksums must match for applied migrations
|
|
701
|
+
*/
|
|
702
|
+
function validateMigrationCoherence(appliedMigrations, migrationLog, migrationFolders) {
|
|
703
|
+
const errors = [];
|
|
704
|
+
// Build a set of log migration names for quick lookup
|
|
705
|
+
const logMigrationNames = new Set(migrationLog.map((e) => e.name));
|
|
706
|
+
const folderNames = new Set(migrationFolders);
|
|
707
|
+
for (const entry of migrationLog) {
|
|
708
|
+
if (!folderNames.has(entry.name)) {
|
|
709
|
+
errors.push({
|
|
710
|
+
type: "missing_from_disk",
|
|
711
|
+
migrationName: entry.name,
|
|
712
|
+
details: `Migration "${entry.name}" exists in migration log but not on disk`,
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
// Check 1: Every applied migration must exist in the log
|
|
717
|
+
for (const [migrationName, row] of appliedMigrations) {
|
|
718
|
+
if (!logMigrationNames.has(migrationName)) {
|
|
719
|
+
errors.push({
|
|
720
|
+
type: "missing_from_log",
|
|
721
|
+
migrationName,
|
|
722
|
+
details: `Migration "${migrationName}" exists in database but not in migration log`,
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
// If there are migrations missing from the log, return early
|
|
727
|
+
// (other checks don't make sense if the log is incomplete)
|
|
728
|
+
if (errors.length > 0) {
|
|
729
|
+
return { isCoherent: false, errors };
|
|
730
|
+
}
|
|
731
|
+
// Check 2: Applied migrations should be a continuous prefix of the log
|
|
732
|
+
// i.e., if migration N is applied, all migrations before N in the log must also be applied
|
|
733
|
+
let lastAppliedIndex = -1;
|
|
734
|
+
for (let i = 0; i < migrationLog.length; i++) {
|
|
735
|
+
const logEntry = migrationLog[i];
|
|
736
|
+
const isApplied = appliedMigrations.has(logEntry.name);
|
|
737
|
+
if (isApplied) {
|
|
738
|
+
// Check for gaps: if this is applied, all previous should be applied
|
|
739
|
+
if (lastAppliedIndex !== i - 1) {
|
|
740
|
+
// There's a gap - find the missing migrations
|
|
741
|
+
for (let j = lastAppliedIndex + 1; j < i; j++) {
|
|
742
|
+
const missing = migrationLog[j];
|
|
743
|
+
errors.push({
|
|
744
|
+
type: "order_mismatch",
|
|
745
|
+
migrationName: missing.name,
|
|
746
|
+
details: `Migration "${missing.name}" is in the log but not applied, yet later migration "${logEntry.name}" is applied`,
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
lastAppliedIndex = i;
|
|
751
|
+
// Check 3: Checksum validation for applied migrations
|
|
752
|
+
const dbRow = appliedMigrations.get(logEntry.name);
|
|
753
|
+
if (dbRow.checksum !== logEntry.checksum) {
|
|
754
|
+
errors.push({
|
|
755
|
+
type: "checksum_mismatch",
|
|
756
|
+
migrationName: logEntry.name,
|
|
757
|
+
details: `Checksum mismatch for "${logEntry.name}": database has ${dbRow.checksum.slice(0, 8)}..., log has ${logEntry.checksum.slice(0, 8)}...`,
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
return {
|
|
763
|
+
isCoherent: errors.length === 0,
|
|
764
|
+
errors,
|
|
765
|
+
};
|
|
766
|
+
}
|
|
557
767
|
/**
|
|
558
768
|
* Execute raw SQL using the database driver directly
|
|
559
769
|
* This bypasses Kysely for DDL statements which don't work reliably with sql.raw()
|
|
@@ -573,10 +783,19 @@ async function executeRawSql(dialect, sqlContent, options) {
|
|
|
573
783
|
else if (dialect === "postgres") {
|
|
574
784
|
const { Pool } = await import("pg");
|
|
575
785
|
const pool = new Pool({ connectionString: options.connectionUrl });
|
|
786
|
+
const client = await pool.connect();
|
|
576
787
|
try {
|
|
577
|
-
|
|
788
|
+
// PostgreSQL supports transactional DDL, so wrap migration in a transaction
|
|
789
|
+
await client.query("BEGIN");
|
|
790
|
+
await client.query(sqlContent);
|
|
791
|
+
await client.query("COMMIT");
|
|
792
|
+
}
|
|
793
|
+
catch (error) {
|
|
794
|
+
await client.query("ROLLBACK");
|
|
795
|
+
throw error;
|
|
578
796
|
}
|
|
579
797
|
finally {
|
|
798
|
+
client.release();
|
|
580
799
|
await pool.end();
|
|
581
800
|
}
|
|
582
801
|
}
|
|
@@ -622,11 +841,32 @@ export async function applyPrismaMigrations(options) {
|
|
|
622
841
|
.filter((e) => e.isDirectory() && /^\d{14}_/.test(e.name))
|
|
623
842
|
.map((e) => e.name)
|
|
624
843
|
.sort();
|
|
844
|
+
const migrationFoldersWithSql = [];
|
|
845
|
+
for (const folderName of migrationFolders) {
|
|
846
|
+
const sqlPath = path.join(options.migrationsFolder, folderName, "migration.sql");
|
|
847
|
+
try {
|
|
848
|
+
await fs.access(sqlPath);
|
|
849
|
+
migrationFoldersWithSql.push(folderName);
|
|
850
|
+
}
|
|
851
|
+
catch {
|
|
852
|
+
// Missing migration.sql; coherence check will flag if it's in the log
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
// Read migration log and validate coherence
|
|
856
|
+
const migrationLog = await readMigrationLog(options.migrationsFolder);
|
|
857
|
+
const coherence = validateMigrationCoherence(appliedMigrations, migrationLog, migrationFoldersWithSql);
|
|
858
|
+
if (!coherence.isCoherent) {
|
|
859
|
+
return {
|
|
860
|
+
applied: [],
|
|
861
|
+
alreadyApplied: [],
|
|
862
|
+
coherenceErrors: coherence.errors,
|
|
863
|
+
};
|
|
864
|
+
}
|
|
625
865
|
const result = {
|
|
626
866
|
applied: [],
|
|
627
867
|
alreadyApplied: [],
|
|
628
868
|
};
|
|
629
|
-
for (const folderName of
|
|
869
|
+
for (const folderName of migrationFoldersWithSql) {
|
|
630
870
|
if (appliedMigrations.has(folderName)) {
|
|
631
871
|
result.alreadyApplied.push(folderName);
|
|
632
872
|
continue;
|
|
@@ -640,8 +880,7 @@ export async function applyPrismaMigrations(options) {
|
|
|
640
880
|
continue; // Skip if no migration.sql
|
|
641
881
|
}
|
|
642
882
|
const checksum = calculateChecksum(sqlContent);
|
|
643
|
-
// Verify checksum against migration log
|
|
644
|
-
const migrationLog = await readMigrationLog(options.migrationsFolder);
|
|
883
|
+
// Verify checksum against migration log (migrationLog already read above)
|
|
645
884
|
const logEntry = migrationLog.find((m) => m.name === folderName);
|
|
646
885
|
if (logEntry && logEntry.checksum !== checksum) {
|
|
647
886
|
result.failed = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pull.d.ts","sourceRoot":"","sources":["../../src/schema/pull.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAEnF,MAAM,WAAW,WAAW;IAC1B,uBAAuB;IACvB,OAAO,EAAE,aAAa,CAAC;IACvB,8BAA8B;IAC9B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gDAAgD;IAChD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,6BAA6B;IAC7B,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB;
|
|
1
|
+
{"version":3,"file":"pull.d.ts","sourceRoot":"","sources":["../../src/schema/pull.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAEnF,MAAM,WAAW,WAAW;IAC1B,uBAAuB;IACvB,OAAO,EAAE,aAAa,CAAC;IACvB,8BAA8B;IAC9B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gDAAgD;IAChD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,6BAA6B;IAC7B,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB;AAofD,wBAAsB,UAAU,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAwC1E"}
|
package/dist/schema/pull.js
CHANGED
|
@@ -73,9 +73,10 @@ function normalizeType(dataType) {
|
|
|
73
73
|
return { type: "String", isArray };
|
|
74
74
|
}
|
|
75
75
|
function buildDatasourceBlock(dialect) {
|
|
76
|
+
const provider = dialect === "postgres" ? "postgresql" : dialect;
|
|
76
77
|
return [
|
|
77
78
|
"datasource db {",
|
|
78
|
-
` provider = \"${
|
|
79
|
+
` provider = \"${provider}\"`,
|
|
79
80
|
" url = env(\"DATABASE_URL\")",
|
|
80
81
|
"}",
|
|
81
82
|
"",
|