@astrojs/db 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config-augment.d.ts +1 -1
- package/dist/core/cli/commands/link/index.d.ts +8 -0
- package/dist/core/cli/commands/link/index.js +64 -0
- package/dist/core/cli/commands/login/index.d.ts +6 -0
- package/dist/core/cli/commands/login/index.js +46 -0
- package/dist/core/cli/commands/logout/index.d.ts +6 -0
- package/dist/core/cli/commands/logout/index.js +9 -0
- package/dist/core/cli/commands/push/index.js +71 -43
- package/dist/core/cli/commands/shell/index.js +5 -8
- package/dist/core/cli/commands/sync/index.js +22 -29
- package/dist/core/cli/commands/verify/index.d.ts +1 -1
- package/dist/core/cli/commands/verify/index.js +20 -16
- package/dist/core/cli/index.js +29 -8
- package/dist/core/cli/migration-queries.d.ts +19 -11
- package/dist/core/cli/migration-queries.js +124 -161
- package/dist/core/cli/migrations.d.ts +22 -1
- package/dist/core/cli/migrations.js +66 -2
- package/dist/core/errors.d.ts +5 -1
- package/dist/core/errors.js +35 -5
- package/dist/core/integration/index.js +32 -17
- package/dist/core/integration/typegen.js +2 -2
- package/dist/core/integration/vite-plugin-db.d.ts +1 -1
- package/dist/core/integration/vite-plugin-db.js +6 -4
- package/dist/core/queries.d.ts +57 -1
- package/dist/core/queries.js +70 -23
- package/dist/core/tokens.d.ts +11 -0
- package/dist/core/tokens.js +131 -0
- package/dist/core/types.d.ts +7049 -1903
- package/dist/core/types.js +133 -60
- package/dist/core/utils.d.ts +1 -0
- package/dist/core/utils.js +5 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +8 -2
- package/dist/runtime/db-client.js +1 -1
- package/dist/runtime/index.d.ts +5 -1
- package/dist/runtime/index.js +36 -21
- package/dist/runtime/types.d.ts +13 -3
- package/dist/runtime/types.js +8 -0
- package/package.json +9 -3
|
@@ -7,26 +7,25 @@ import {
|
|
|
7
7
|
getCreateIndexQueries,
|
|
8
8
|
getCreateTableQuery,
|
|
9
9
|
getModifiers,
|
|
10
|
+
getReferencesConfig,
|
|
10
11
|
hasDefault,
|
|
11
12
|
schemaTypeToSqlType
|
|
12
13
|
} from "../queries.js";
|
|
13
14
|
import { hasPrimaryKey } from "../../runtime/index.js";
|
|
15
|
+
import { isSerializedSQL } from "../../runtime/types.js";
|
|
14
16
|
const sqlite = new SQLiteAsyncDialect();
|
|
15
17
|
const genTempTableName = customAlphabet("abcdefghijklmnopqrstuvwxyz", 10);
|
|
16
18
|
async function getMigrationQueries({
|
|
17
19
|
oldSnapshot,
|
|
18
20
|
newSnapshot,
|
|
19
|
-
|
|
21
|
+
ambiguityResponses
|
|
20
22
|
}) {
|
|
21
23
|
const queries = [];
|
|
24
|
+
const confirmations = [];
|
|
22
25
|
let added = getAddedCollections(oldSnapshot, newSnapshot);
|
|
23
26
|
let dropped = getDroppedCollections(oldSnapshot, newSnapshot);
|
|
24
27
|
if (!isEmpty(added) && !isEmpty(dropped)) {
|
|
25
|
-
const resolved = await resolveCollectionRenames(
|
|
26
|
-
added,
|
|
27
|
-
dropped,
|
|
28
|
-
promptResponses?.collectionRenames
|
|
29
|
-
);
|
|
28
|
+
const resolved = await resolveCollectionRenames(added, dropped, ambiguityResponses);
|
|
30
29
|
added = resolved.added;
|
|
31
30
|
dropped = resolved.dropped;
|
|
32
31
|
for (const { from, to } of resolved.renamed) {
|
|
@@ -48,45 +47,47 @@ async function getMigrationQueries({
|
|
|
48
47
|
const oldCollection = oldSnapshot.schema[collectionName];
|
|
49
48
|
if (!oldCollection)
|
|
50
49
|
continue;
|
|
51
|
-
const
|
|
50
|
+
const result = await getCollectionChangeQueries({
|
|
52
51
|
collectionName,
|
|
53
52
|
oldCollection,
|
|
54
|
-
newCollection
|
|
55
|
-
promptResponses
|
|
53
|
+
newCollection
|
|
56
54
|
});
|
|
57
|
-
queries.push(...
|
|
55
|
+
queries.push(...result.queries);
|
|
56
|
+
confirmations.push(...result.confirmations);
|
|
58
57
|
}
|
|
59
|
-
return queries;
|
|
58
|
+
return { queries, confirmations };
|
|
60
59
|
}
|
|
61
60
|
async function getCollectionChangeQueries({
|
|
62
61
|
collectionName,
|
|
63
62
|
oldCollection,
|
|
64
63
|
newCollection,
|
|
65
|
-
|
|
64
|
+
ambiguityResponses
|
|
66
65
|
}) {
|
|
67
66
|
const queries = [];
|
|
67
|
+
const confirmations = [];
|
|
68
68
|
const updated = getUpdatedFields(oldCollection.fields, newCollection.fields);
|
|
69
69
|
let added = getAdded(oldCollection.fields, newCollection.fields);
|
|
70
70
|
let dropped = getDropped(oldCollection.fields, newCollection.fields);
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
71
|
+
const hasForeignKeyChanges = Boolean(
|
|
72
|
+
deepDiff(oldCollection.foreignKeys, newCollection.foreignKeys)
|
|
73
|
+
);
|
|
74
|
+
if (!hasForeignKeyChanges && isEmpty(updated) && isEmpty(added) && isEmpty(dropped)) {
|
|
75
|
+
return {
|
|
76
|
+
queries: getChangeIndexQueries({
|
|
77
|
+
collectionName,
|
|
78
|
+
oldIndexes: oldCollection.indexes,
|
|
79
|
+
newIndexes: newCollection.indexes
|
|
80
|
+
}),
|
|
81
|
+
confirmations
|
|
82
|
+
};
|
|
77
83
|
}
|
|
78
|
-
if (!isEmpty(added) && !isEmpty(dropped)) {
|
|
79
|
-
const resolved = await resolveFieldRenames(
|
|
80
|
-
collectionName,
|
|
81
|
-
added,
|
|
82
|
-
dropped,
|
|
83
|
-
promptResponses?.fieldRenames
|
|
84
|
-
);
|
|
84
|
+
if (!hasForeignKeyChanges && !isEmpty(added) && !isEmpty(dropped)) {
|
|
85
|
+
const resolved = await resolveFieldRenames(collectionName, added, dropped, ambiguityResponses);
|
|
85
86
|
added = resolved.added;
|
|
86
87
|
dropped = resolved.dropped;
|
|
87
88
|
queries.push(...getFieldRenameQueries(collectionName, resolved.renamed));
|
|
88
89
|
}
|
|
89
|
-
if (isEmpty(updated) && Object.values(dropped).every(canAlterTableDropColumn) && Object.values(added).every(canAlterTableAddColumn)) {
|
|
90
|
+
if (!hasForeignKeyChanges && isEmpty(updated) && Object.values(dropped).every(canAlterTableDropColumn) && Object.values(added).every(canAlterTableAddColumn)) {
|
|
90
91
|
queries.push(
|
|
91
92
|
...getAlterTableQueries(collectionName, added, dropped),
|
|
92
93
|
...getChangeIndexQueries({
|
|
@@ -95,60 +96,46 @@ async function getCollectionChangeQueries({
|
|
|
95
96
|
newIndexes: newCollection.indexes
|
|
96
97
|
})
|
|
97
98
|
);
|
|
98
|
-
return queries;
|
|
99
|
+
return { queries, confirmations };
|
|
99
100
|
}
|
|
100
101
|
const dataLossCheck = canRecreateTableWithoutDataLoss(added, updated);
|
|
101
102
|
if (dataLossCheck.dataLoss) {
|
|
102
|
-
let allowDataLoss = promptResponses?.allowDataLoss;
|
|
103
|
-
const nameMsg = `Type the collection name ${color.blue(
|
|
104
|
-
collectionName
|
|
105
|
-
)} to confirm you want to delete all data:`;
|
|
106
103
|
const { reason, fieldName } = dataLossCheck;
|
|
107
104
|
const reasonMsgs = {
|
|
108
|
-
"added-required": `
|
|
109
|
-
|
|
110
|
-
)}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
105
|
+
"added-required": `New field ${color.bold(
|
|
106
|
+
collectionName + "." + fieldName
|
|
107
|
+
)} is required with no default value.
|
|
108
|
+
This requires deleting existing data in the ${color.bold(
|
|
109
|
+
collectionName
|
|
110
|
+
)} collection.`,
|
|
111
|
+
"added-unique": `New field ${color.bold(
|
|
112
|
+
collectionName + "." + fieldName
|
|
113
|
+
)} is marked as unique.
|
|
114
|
+
This requires deleting existing data in the ${color.bold(
|
|
115
|
+
collectionName
|
|
116
|
+
)} collection.`,
|
|
117
|
+
"updated-type": `Updated field ${color.bold(
|
|
118
|
+
collectionName + "." + fieldName
|
|
119
|
+
)} cannot convert data to new field data type.
|
|
120
|
+
This requires deleting existing data in the ${color.bold(
|
|
121
|
+
collectionName
|
|
122
|
+
)} collection.`
|
|
121
123
|
};
|
|
122
|
-
|
|
123
|
-
const res = await prompts({
|
|
124
|
-
type: "text",
|
|
125
|
-
name: "allowDataLoss",
|
|
126
|
-
message: `${reasonMsgs[reason]} ${nameMsg}`,
|
|
127
|
-
validate: (name) => name === collectionName || "Incorrect collection name"
|
|
128
|
-
});
|
|
129
|
-
if (typeof res.allowDataLoss !== "string")
|
|
130
|
-
process.exit(0);
|
|
131
|
-
allowDataLoss = !!res.allowDataLoss;
|
|
132
|
-
}
|
|
133
|
-
if (!allowDataLoss) {
|
|
134
|
-
console.info("Exiting without changes \u{1F44B}");
|
|
135
|
-
process.exit(0);
|
|
136
|
-
}
|
|
124
|
+
confirmations.push(reasonMsgs[reason]);
|
|
137
125
|
}
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
const updatedPrimaryKey = Object.entries(updated).find(
|
|
141
|
-
([, field]) => hasPrimaryKey(field.old) || hasPrimaryKey(field.new)
|
|
126
|
+
const primaryKeyExists = Object.entries(newCollection.fields).find(
|
|
127
|
+
([, field]) => hasPrimaryKey(field)
|
|
142
128
|
);
|
|
129
|
+
const droppedPrimaryKey = Object.entries(dropped).find(([, field]) => hasPrimaryKey(field));
|
|
143
130
|
const recreateTableQueries = getRecreateTableQueries({
|
|
144
131
|
collectionName,
|
|
145
132
|
newCollection,
|
|
146
133
|
added,
|
|
147
134
|
hasDataLoss: dataLossCheck.dataLoss,
|
|
148
|
-
migrateHiddenPrimaryKey: !
|
|
135
|
+
migrateHiddenPrimaryKey: !primaryKeyExists && !droppedPrimaryKey
|
|
149
136
|
});
|
|
150
137
|
queries.push(...recreateTableQueries, ...getCreateIndexQueries(collectionName, newCollection));
|
|
151
|
-
return queries;
|
|
138
|
+
return { queries, confirmations };
|
|
152
139
|
}
|
|
153
140
|
function getChangeIndexQueries({
|
|
154
141
|
collectionName,
|
|
@@ -168,96 +155,78 @@ function getChangeIndexQueries({
|
|
|
168
155
|
queries.push(...getCreateIndexQueries(collectionName, { indexes: added }));
|
|
169
156
|
return queries;
|
|
170
157
|
}
|
|
171
|
-
async function resolveFieldRenames(collectionName, mightAdd, mightDrop,
|
|
158
|
+
async function resolveFieldRenames(collectionName, mightAdd, mightDrop, ambiguityResponses) {
|
|
172
159
|
const added = {};
|
|
173
160
|
const dropped = {};
|
|
174
161
|
const renamed = [];
|
|
175
162
|
for (const [fieldName, field] of Object.entries(mightAdd)) {
|
|
176
|
-
|
|
177
|
-
if (
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
163
|
+
let oldFieldName = ambiguityResponses ? ambiguityResponses.fieldRenames[collectionName]?.[fieldName] ?? "__NEW__" : void 0;
|
|
164
|
+
if (!oldFieldName) {
|
|
165
|
+
const res = await prompts(
|
|
166
|
+
{
|
|
167
|
+
type: "select",
|
|
168
|
+
name: "fieldName",
|
|
169
|
+
message: "New field " + color.blue(color.bold(`${collectionName}.${fieldName}`)) + " detected. Was this renamed from an existing field?",
|
|
170
|
+
choices: [
|
|
171
|
+
{ title: "New field (not renamed from existing)", value: "__NEW__" },
|
|
172
|
+
...Object.keys(mightDrop).filter((key) => !(key in renamed)).map((key) => ({ title: key, value: key }))
|
|
173
|
+
]
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
onCancel: () => {
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
);
|
|
181
|
+
oldFieldName = res.fieldName;
|
|
183
182
|
}
|
|
184
|
-
|
|
185
|
-
type: "toggle",
|
|
186
|
-
name: "isRename",
|
|
187
|
-
message: `Is the field ${color.blue(color.bold(fieldName))} in collection ${color.blue(
|
|
188
|
-
color.bold(collectionName)
|
|
189
|
-
)} a new field, or renaming an existing field?`,
|
|
190
|
-
initial: false,
|
|
191
|
-
active: "Rename",
|
|
192
|
-
inactive: "New field"
|
|
193
|
-
});
|
|
194
|
-
if (typeof res.isRename !== "boolean")
|
|
195
|
-
process.exit(0);
|
|
196
|
-
if (!res.isRename) {
|
|
183
|
+
if (oldFieldName === "__NEW__") {
|
|
197
184
|
added[fieldName] = field;
|
|
198
|
-
|
|
185
|
+
} else {
|
|
186
|
+
renamed.push({ from: oldFieldName, to: fieldName });
|
|
199
187
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
message: `Which field in ${color.blue(
|
|
205
|
-
color.bold(collectionName)
|
|
206
|
-
)} should be renamed to ${color.blue(color.bold(fieldName))}?`,
|
|
207
|
-
choices
|
|
208
|
-
});
|
|
209
|
-
if (typeof oldFieldName !== "string")
|
|
210
|
-
process.exit(0);
|
|
211
|
-
renamed.push({ from: oldFieldName, to: fieldName });
|
|
212
|
-
for (const [droppedFieldName, droppedField] of Object.entries(mightDrop)) {
|
|
213
|
-
if (!renamed.find((r) => r.from === droppedFieldName))
|
|
214
|
-
dropped[droppedFieldName] = droppedField;
|
|
188
|
+
}
|
|
189
|
+
for (const [droppedFieldName, droppedField] of Object.entries(mightDrop)) {
|
|
190
|
+
if (!renamed.find((r) => r.from === droppedFieldName)) {
|
|
191
|
+
dropped[droppedFieldName] = droppedField;
|
|
215
192
|
}
|
|
216
193
|
}
|
|
217
194
|
return { added, dropped, renamed };
|
|
218
195
|
}
|
|
219
|
-
async function resolveCollectionRenames(mightAdd, mightDrop,
|
|
196
|
+
async function resolveCollectionRenames(mightAdd, mightDrop, ambiguityResponses) {
|
|
220
197
|
const added = {};
|
|
221
198
|
const dropped = {};
|
|
222
199
|
const renamed = [];
|
|
223
200
|
for (const [collectionName, collection] of Object.entries(mightAdd)) {
|
|
224
|
-
|
|
225
|
-
if (
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
201
|
+
let oldCollectionName = ambiguityResponses ? ambiguityResponses.collectionRenames[collectionName] ?? "__NEW__" : void 0;
|
|
202
|
+
if (!oldCollectionName) {
|
|
203
|
+
const res = await prompts(
|
|
204
|
+
{
|
|
205
|
+
type: "select",
|
|
206
|
+
name: "collectionName",
|
|
207
|
+
message: "New collection " + color.blue(color.bold(collectionName)) + " detected. Was this renamed from an existing collection?",
|
|
208
|
+
choices: [
|
|
209
|
+
{ title: "New collection (not renamed from existing)", value: "__NEW__" },
|
|
210
|
+
...Object.keys(mightDrop).filter((key) => !(key in renamed)).map((key) => ({ title: key, value: key }))
|
|
211
|
+
]
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
onCancel: () => {
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
);
|
|
219
|
+
oldCollectionName = res.collectionName;
|
|
231
220
|
}
|
|
232
|
-
|
|
233
|
-
type: "toggle",
|
|
234
|
-
name: "isRename",
|
|
235
|
-
message: `Is the collection ${color.blue(
|
|
236
|
-
color.bold(collectionName)
|
|
237
|
-
)} a new collection, or renaming an existing collection?`,
|
|
238
|
-
initial: false,
|
|
239
|
-
active: "Rename",
|
|
240
|
-
inactive: "New collection"
|
|
241
|
-
});
|
|
242
|
-
if (typeof res.isRename !== "boolean")
|
|
243
|
-
process.exit(0);
|
|
244
|
-
if (!res.isRename) {
|
|
221
|
+
if (oldCollectionName === "__NEW__") {
|
|
245
222
|
added[collectionName] = collection;
|
|
246
|
-
|
|
223
|
+
} else {
|
|
224
|
+
renamed.push({ from: oldCollectionName, to: collectionName });
|
|
247
225
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
message: `Which collection should be renamed to ${color.blue(color.bold(collectionName))}?`,
|
|
253
|
-
choices
|
|
254
|
-
});
|
|
255
|
-
if (typeof oldCollectionName !== "string")
|
|
256
|
-
process.exit(0);
|
|
257
|
-
renamed.push({ from: oldCollectionName, to: collectionName });
|
|
258
|
-
for (const [droppedCollectionName, droppedCollection] of Object.entries(mightDrop)) {
|
|
259
|
-
if (!renamed.find((r) => r.from === droppedCollectionName))
|
|
260
|
-
dropped[droppedCollectionName] = droppedCollection;
|
|
226
|
+
}
|
|
227
|
+
for (const [droppedCollectionName, droppedCollection] of Object.entries(mightDrop)) {
|
|
228
|
+
if (!renamed.find((r) => r.from === droppedCollectionName)) {
|
|
229
|
+
dropped[droppedCollectionName] = droppedCollection;
|
|
261
230
|
}
|
|
262
231
|
}
|
|
263
232
|
return { added, dropped, renamed };
|
|
@@ -340,18 +309,20 @@ function isEmpty(obj) {
|
|
|
340
309
|
return Object.keys(obj).length === 0;
|
|
341
310
|
}
|
|
342
311
|
function canAlterTableAddColumn(field) {
|
|
343
|
-
if (field.unique)
|
|
312
|
+
if (field.schema.unique)
|
|
344
313
|
return false;
|
|
345
314
|
if (hasRuntimeDefault(field))
|
|
346
315
|
return false;
|
|
347
|
-
if (!field.optional && !hasDefault(field))
|
|
316
|
+
if (!field.schema.optional && !hasDefault(field))
|
|
348
317
|
return false;
|
|
349
318
|
if (hasPrimaryKey(field))
|
|
350
319
|
return false;
|
|
320
|
+
if (getReferencesConfig(field))
|
|
321
|
+
return false;
|
|
351
322
|
return true;
|
|
352
323
|
}
|
|
353
324
|
function canAlterTableDropColumn(field) {
|
|
354
|
-
if (field.unique)
|
|
325
|
+
if (field.schema.unique)
|
|
355
326
|
return false;
|
|
356
327
|
if (hasPrimaryKey(field))
|
|
357
328
|
return false;
|
|
@@ -362,10 +333,10 @@ function canRecreateTableWithoutDataLoss(added, updated) {
|
|
|
362
333
|
if (hasPrimaryKey(a) && a.type !== "number" && !hasDefault(a)) {
|
|
363
334
|
return { dataLoss: true, fieldName, reason: "added-required" };
|
|
364
335
|
}
|
|
365
|
-
if (!a.optional && !hasDefault(a)) {
|
|
336
|
+
if (!a.schema.optional && !hasDefault(a)) {
|
|
366
337
|
return { dataLoss: true, fieldName, reason: "added-required" };
|
|
367
338
|
}
|
|
368
|
-
if (!a.optional && a.unique) {
|
|
339
|
+
if (!a.schema.optional && a.schema.unique) {
|
|
369
340
|
return { dataLoss: true, fieldName, reason: "added-unique" };
|
|
370
341
|
}
|
|
371
342
|
}
|
|
@@ -409,14 +380,16 @@ function getUpdatedFields(oldFields, newFields) {
|
|
|
409
380
|
const oldField = oldFields[key];
|
|
410
381
|
if (!oldField)
|
|
411
382
|
continue;
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
383
|
+
const diff = deepDiff(oldField, newField, (path, objKey) => {
|
|
384
|
+
const isTypeKey = objKey === "type" && path.length === 0;
|
|
385
|
+
return (
|
|
386
|
+
// If we can safely update the type without a SQL query, ignore the diff
|
|
387
|
+
isTypeKey && oldField.type !== newField.type && canChangeTypeWithoutQuery(oldField, newField)
|
|
388
|
+
);
|
|
389
|
+
});
|
|
390
|
+
if (diff) {
|
|
391
|
+
updated[key] = { old: oldField, new: newField };
|
|
392
|
+
}
|
|
420
393
|
}
|
|
421
394
|
return updated;
|
|
422
395
|
}
|
|
@@ -436,17 +409,7 @@ function canChangeTypeWithoutQuery(oldField, newField) {
|
|
|
436
409
|
);
|
|
437
410
|
}
|
|
438
411
|
function hasRuntimeDefault(field) {
|
|
439
|
-
return field.
|
|
440
|
-
}
|
|
441
|
-
function objShallowEqual(a, b) {
|
|
442
|
-
if (Object.keys(a).length !== Object.keys(b).length)
|
|
443
|
-
return false;
|
|
444
|
-
for (const [key, value] of Object.entries(a)) {
|
|
445
|
-
if (JSON.stringify(b[key]) !== JSON.stringify(value)) {
|
|
446
|
-
return false;
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
return true;
|
|
412
|
+
return !!(field.schema.default && isSerializedSQL(field.schema.default));
|
|
450
413
|
}
|
|
451
414
|
export {
|
|
452
415
|
getCollectionChangeQueries,
|
|
@@ -1,9 +1,30 @@
|
|
|
1
|
-
import
|
|
1
|
+
import deepDiff from 'deep-diff';
|
|
2
|
+
import { type DBSnapshot } from '../types.js';
|
|
2
3
|
import type { AstroConfig } from 'astro';
|
|
4
|
+
export type MigrationStatus = {
|
|
5
|
+
state: 'no-migrations-found';
|
|
6
|
+
currentSnapshot: DBSnapshot;
|
|
7
|
+
} | {
|
|
8
|
+
state: 'ahead';
|
|
9
|
+
oldSnapshot: DBSnapshot;
|
|
10
|
+
newSnapshot: DBSnapshot;
|
|
11
|
+
diff: deepDiff.Diff<DBSnapshot, DBSnapshot>[];
|
|
12
|
+
newFilename: string;
|
|
13
|
+
summary: string;
|
|
14
|
+
} | {
|
|
15
|
+
state: 'up-to-date';
|
|
16
|
+
currentSnapshot: DBSnapshot;
|
|
17
|
+
};
|
|
18
|
+
export declare function getMigrationStatus(config: AstroConfig): Promise<MigrationStatus>;
|
|
19
|
+
export declare const MIGRATIONS_CREATED: string;
|
|
20
|
+
export declare const MIGRATIONS_UP_TO_DATE: string;
|
|
21
|
+
export declare const MIGRATIONS_NOT_INITIALIZED: string;
|
|
22
|
+
export declare const MIGRATION_NEEDED: string;
|
|
3
23
|
export declare function getMigrations(): Promise<string[]>;
|
|
4
24
|
export declare function loadMigration(migration: string): Promise<{
|
|
5
25
|
diff: any[];
|
|
6
26
|
db: string[];
|
|
27
|
+
confirm?: string[];
|
|
7
28
|
}>;
|
|
8
29
|
export declare function loadInitialSnapshot(): Promise<DBSnapshot>;
|
|
9
30
|
export declare function initializeMigrationsDirectory(currentSnapshot: DBSnapshot): Promise<void>;
|
|
@@ -1,6 +1,64 @@
|
|
|
1
1
|
import deepDiff from "deep-diff";
|
|
2
2
|
import { mkdir, readFile, readdir, writeFile } from "fs/promises";
|
|
3
|
-
|
|
3
|
+
import { collectionsSchema } from "../types.js";
|
|
4
|
+
import { cyan, green, yellow } from "kleur/colors";
|
|
5
|
+
const { applyChange, diff: generateDiff } = deepDiff;
|
|
6
|
+
async function getMigrationStatus(config) {
|
|
7
|
+
const currentSnapshot = createCurrentSnapshot(config);
|
|
8
|
+
const allMigrationFiles = await getMigrations();
|
|
9
|
+
if (allMigrationFiles.length === 0) {
|
|
10
|
+
return {
|
|
11
|
+
state: "no-migrations-found",
|
|
12
|
+
currentSnapshot
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
const previousSnapshot = await initializeFromMigrations(allMigrationFiles);
|
|
16
|
+
const diff = generateDiff(previousSnapshot, currentSnapshot);
|
|
17
|
+
if (diff) {
|
|
18
|
+
const n = getNewMigrationNumber(allMigrationFiles);
|
|
19
|
+
const newFilename = `${String(n + 1).padStart(4, "0")}_migration.json`;
|
|
20
|
+
return {
|
|
21
|
+
state: "ahead",
|
|
22
|
+
oldSnapshot: previousSnapshot,
|
|
23
|
+
newSnapshot: currentSnapshot,
|
|
24
|
+
diff,
|
|
25
|
+
newFilename,
|
|
26
|
+
summary: generateDiffSummary(diff)
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
state: "up-to-date",
|
|
31
|
+
currentSnapshot
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
const MIGRATIONS_CREATED = `${green("\u25A0 Migrations initialized!")}
|
|
35
|
+
|
|
36
|
+
To execute your migrations, run
|
|
37
|
+
${cyan("astro db push")}`;
|
|
38
|
+
const MIGRATIONS_UP_TO_DATE = `${green("\u25A0 No migrations needed!")}
|
|
39
|
+
|
|
40
|
+
Your database is up to date.
|
|
41
|
+
`;
|
|
42
|
+
const MIGRATIONS_NOT_INITIALIZED = `${yellow("\u25B6 No migrations found!")}
|
|
43
|
+
|
|
44
|
+
To scaffold your migrations folder, run
|
|
45
|
+
${cyan("astro db sync")}
|
|
46
|
+
`;
|
|
47
|
+
const MIGRATION_NEEDED = `${yellow("\u25B6 Changes detected!")}
|
|
48
|
+
|
|
49
|
+
To create the necessary migration file, run
|
|
50
|
+
${cyan("astro db sync")}
|
|
51
|
+
`;
|
|
52
|
+
function generateDiffSummary(diff) {
|
|
53
|
+
return JSON.stringify(diff, null, 2);
|
|
54
|
+
}
|
|
55
|
+
function getNewMigrationNumber(allMigrationFiles) {
|
|
56
|
+
const len = allMigrationFiles.length - 1;
|
|
57
|
+
return allMigrationFiles.reduce((acc, curr) => {
|
|
58
|
+
const num = Number.parseInt(curr.split("_")[0] ?? len, 10);
|
|
59
|
+
return num > acc ? num : acc;
|
|
60
|
+
}, 0);
|
|
61
|
+
}
|
|
4
62
|
async function getMigrations() {
|
|
5
63
|
const migrationFiles = await readdir("./migrations").catch((err) => {
|
|
6
64
|
if (err.code === "ENOENT") {
|
|
@@ -40,15 +98,21 @@ async function initializeFromMigrations(allMigrationFiles) {
|
|
|
40
98
|
return prevSnapshot;
|
|
41
99
|
}
|
|
42
100
|
function createCurrentSnapshot(config) {
|
|
43
|
-
const
|
|
101
|
+
const collectionsConfig = collectionsSchema.parse(config.db?.collections ?? {});
|
|
102
|
+
const schema = JSON.parse(JSON.stringify(collectionsConfig));
|
|
44
103
|
return { experimentalVersion: 1, schema };
|
|
45
104
|
}
|
|
46
105
|
function createEmptySnapshot() {
|
|
47
106
|
return { experimentalVersion: 1, schema: {} };
|
|
48
107
|
}
|
|
49
108
|
export {
|
|
109
|
+
MIGRATIONS_CREATED,
|
|
110
|
+
MIGRATIONS_NOT_INITIALIZED,
|
|
111
|
+
MIGRATIONS_UP_TO_DATE,
|
|
112
|
+
MIGRATION_NEEDED,
|
|
50
113
|
createCurrentSnapshot,
|
|
51
114
|
createEmptySnapshot,
|
|
115
|
+
getMigrationStatus,
|
|
52
116
|
getMigrations,
|
|
53
117
|
initializeFromMigrations,
|
|
54
118
|
initializeMigrationsDirectory,
|
package/dist/core/errors.d.ts
CHANGED
|
@@ -1 +1,5 @@
|
|
|
1
|
-
export declare const
|
|
1
|
+
export declare const MISSING_SESSION_ID_ERROR: string;
|
|
2
|
+
export declare const MISSING_PROJECT_ID_ERROR: string;
|
|
3
|
+
export declare const STUDIO_CONFIG_MISSING_WRITABLE_COLLECTIONS_ERROR: (collectionName: string) => string;
|
|
4
|
+
export declare const STUDIO_CONFIG_MISSING_CLI_ERROR: string;
|
|
5
|
+
export declare const MIGRATIONS_NOT_INITIALIZED: string;
|
package/dist/core/errors.js
CHANGED
|
@@ -1,7 +1,37 @@
|
|
|
1
|
-
import { red } from "kleur/colors";
|
|
2
|
-
const
|
|
3
|
-
"\
|
|
4
|
-
)}
|
|
1
|
+
import { cyan, bold, red, green, yellow } from "kleur/colors";
|
|
2
|
+
const MISSING_SESSION_ID_ERROR = `${red(
|
|
3
|
+
"\u25B6 Login required!"
|
|
4
|
+
)}
|
|
5
|
+
|
|
6
|
+
To authenticate with Astro Studio, run
|
|
7
|
+
${cyan("astro db login")}
|
|
8
|
+
`;
|
|
9
|
+
const MISSING_PROJECT_ID_ERROR = `${red(
|
|
10
|
+
"\u25B6 Directory not linked."
|
|
11
|
+
)}
|
|
12
|
+
|
|
13
|
+
To link this directory to an Astro Studio project, run
|
|
14
|
+
${cyan("astro db link")}
|
|
15
|
+
`;
|
|
16
|
+
const STUDIO_CONFIG_MISSING_WRITABLE_COLLECTIONS_ERROR = (collectionName) => `${red(`\u25B6 Writable collection ${bold(collectionName)} requires Astro Studio.`)}
|
|
17
|
+
|
|
18
|
+
Visit ${cyan("https://astro.build/studio")} to create your account
|
|
19
|
+
and set ${green("studio: true")} in your astro.config.mjs file to enable Studio.
|
|
20
|
+
`;
|
|
21
|
+
const STUDIO_CONFIG_MISSING_CLI_ERROR = `${red("\u25B6 This command requires Astro Studio.")}
|
|
22
|
+
|
|
23
|
+
Visit ${cyan("https://astro.build/studio")} to create your account
|
|
24
|
+
and set ${green("studio: true")} in your astro.config.mjs file to enable Studio.
|
|
25
|
+
`;
|
|
26
|
+
const MIGRATIONS_NOT_INITIALIZED = `${yellow("\u25B6 No migrations found!")}
|
|
27
|
+
|
|
28
|
+
To scaffold your migrations folder, run
|
|
29
|
+
${cyan("astro db sync")}
|
|
30
|
+
`;
|
|
5
31
|
export {
|
|
6
|
-
|
|
32
|
+
MIGRATIONS_NOT_INITIALIZED,
|
|
33
|
+
MISSING_PROJECT_ID_ERROR,
|
|
34
|
+
MISSING_SESSION_ID_ERROR,
|
|
35
|
+
STUDIO_CONFIG_MISSING_CLI_ERROR,
|
|
36
|
+
STUDIO_CONFIG_MISSING_WRITABLE_COLLECTIONS_ERROR
|
|
7
37
|
};
|