@astrojs/db 0.2.2 → 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.
Files changed (39) hide show
  1. package/config-augment.d.ts +1 -1
  2. package/dist/core/cli/commands/link/index.d.ts +8 -0
  3. package/dist/core/cli/commands/link/index.js +64 -0
  4. package/dist/core/cli/commands/login/index.d.ts +6 -0
  5. package/dist/core/cli/commands/login/index.js +46 -0
  6. package/dist/core/cli/commands/logout/index.d.ts +6 -0
  7. package/dist/core/cli/commands/logout/index.js +9 -0
  8. package/dist/core/cli/commands/push/index.js +71 -43
  9. package/dist/core/cli/commands/shell/index.js +5 -8
  10. package/dist/core/cli/commands/sync/index.js +22 -29
  11. package/dist/core/cli/commands/verify/index.d.ts +1 -1
  12. package/dist/core/cli/commands/verify/index.js +20 -16
  13. package/dist/core/cli/index.js +29 -8
  14. package/dist/core/cli/migration-queries.d.ts +19 -11
  15. package/dist/core/cli/migration-queries.js +124 -161
  16. package/dist/core/cli/migrations.d.ts +22 -1
  17. package/dist/core/cli/migrations.js +66 -2
  18. package/dist/core/errors.d.ts +5 -1
  19. package/dist/core/errors.js +35 -5
  20. package/dist/core/integration/index.js +32 -17
  21. package/dist/core/integration/typegen.js +2 -2
  22. package/dist/core/integration/vite-plugin-db.d.ts +1 -1
  23. package/dist/core/integration/vite-plugin-db.js +6 -4
  24. package/dist/core/queries.d.ts +57 -1
  25. package/dist/core/queries.js +70 -23
  26. package/dist/core/tokens.d.ts +11 -0
  27. package/dist/core/tokens.js +131 -0
  28. package/dist/core/types.d.ts +7049 -1903
  29. package/dist/core/types.js +133 -60
  30. package/dist/core/utils.d.ts +1 -0
  31. package/dist/core/utils.js +5 -0
  32. package/dist/index.d.ts +2 -1
  33. package/dist/index.js +8 -2
  34. package/dist/runtime/db-client.js +1 -1
  35. package/dist/runtime/index.d.ts +5 -1
  36. package/dist/runtime/index.js +36 -21
  37. package/dist/runtime/types.d.ts +13 -3
  38. package/dist/runtime/types.js +8 -0
  39. package/package.json +5 -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
- promptResponses
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 collectionChangeQueries = await getCollectionChangeQueries({
50
+ const result = await getCollectionChangeQueries({
52
51
  collectionName,
53
52
  oldCollection,
54
- newCollection,
55
- promptResponses
53
+ newCollection
56
54
  });
57
- queries.push(...collectionChangeQueries);
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
- promptResponses
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
- if (isEmpty(updated) && isEmpty(added) && isEmpty(dropped)) {
72
- return getChangeIndexQueries({
73
- collectionName,
74
- oldIndexes: oldCollection.indexes,
75
- newIndexes: newCollection.indexes
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": `Adding required ${color.blue(
109
- color.bold(collectionName)
110
- )} field ${color.blue(color.bold(fieldName))}. ${color.red(
111
- "This will delete all existing data in the collection!"
112
- )} We recommend setting a default value to avoid data loss.`,
113
- "added-unique": `Adding unique ${color.blue(color.bold(collectionName))} field ${color.blue(
114
- color.bold(fieldName)
115
- )}. ${color.red("This will delete all existing data in the collection!")}`,
116
- "updated-type": `Changing the type of ${color.blue(
117
- color.bold(collectionName)
118
- )} field ${color.blue(color.bold(fieldName))}. ${color.red(
119
- "This will delete all existing data in the collection!"
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
- if (allowDataLoss === void 0) {
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 addedPrimaryKey = Object.entries(added).find(([, field]) => hasPrimaryKey(field));
139
- const droppedPrimaryKey = Object.entries(dropped).find(([, field]) => hasPrimaryKey(field));
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: !addedPrimaryKey && !droppedPrimaryKey && !updatedPrimaryKey
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, renamePromptResponses) {
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
- const promptResponse = renamePromptResponses?.[fieldName];
177
- if (promptResponse === false) {
178
- added[fieldName] = field;
179
- continue;
180
- } else if (promptResponse) {
181
- renamed.push({ from: promptResponse, to: fieldName });
182
- continue;
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
- const res = await prompts({
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
- continue;
185
+ } else {
186
+ renamed.push({ from: oldFieldName, to: fieldName });
199
187
  }
200
- const choices = Object.keys(mightDrop).filter((key) => !(key in renamed)).map((key) => ({ title: key, value: key }));
201
- const { oldFieldName } = await prompts({
202
- type: "select",
203
- name: "oldFieldName",
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, renamePromptResponses) {
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
- const promptResponse = renamePromptResponses?.[collectionName];
225
- if (promptResponse === false) {
226
- added[collectionName] = collection;
227
- continue;
228
- } else if (promptResponse) {
229
- renamed.push({ from: promptResponse, to: collectionName });
230
- continue;
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
- const res = await prompts({
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
- continue;
223
+ } else {
224
+ renamed.push({ from: oldCollectionName, to: collectionName });
247
225
  }
248
- const choices = Object.keys(mightDrop).filter((key) => !(key in renamed)).map((key) => ({ title: key, value: key }));
249
- const { oldCollectionName } = await prompts({
250
- type: "select",
251
- name: "oldCollectionName",
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
- if (objShallowEqual(oldField, newField))
413
- continue;
414
- const oldFieldSqlType = { ...oldField, type: schemaTypeToSqlType(oldField.type) };
415
- const newFieldSqlType = { ...newField, type: schemaTypeToSqlType(newField.type) };
416
- const isSafeTypeUpdate = objShallowEqual(oldFieldSqlType, newFieldSqlType) && canChangeTypeWithoutQuery(oldField, newField);
417
- if (isSafeTypeUpdate)
418
- continue;
419
- updated[key] = { old: oldField, new: newField };
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.type === "date" && field.default === "now";
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 type { DBSnapshot } from '../types.js';
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
- const { applyChange } = deepDiff;
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 schema = JSON.parse(JSON.stringify(config.db?.collections ?? {}));
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,
@@ -1 +1,5 @@
1
- export declare const appTokenError: string;
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;
@@ -1,7 +1,37 @@
1
- import { red } from "kleur/colors";
2
- const appTokenError = `${red(
3
- "\u26A0\uFE0F App token invalid or expired."
4
- )} Please generate a new one from your the Studio dashboard under project settings.`;
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
- appTokenError
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
  };