@astrojs/db 0.5.0 → 0.6.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.
@@ -1,7 +1,7 @@
1
1
  import type { AstroConfig } from 'astro';
2
2
  import type { Arguments } from 'yargs-parser';
3
3
  import { type DBConfig } from '../../../types.js';
4
- export declare function cmd({ astroConfig, dbConfig, flags, }: {
4
+ export declare function cmd({ dbConfig, flags, }: {
5
5
  astroConfig: AstroConfig;
6
6
  dbConfig: DBConfig;
7
7
  flags: Arguments;
@@ -1,128 +1,61 @@
1
- import { red } from "kleur/colors";
2
- import prompts from "prompts";
3
- import { MISSING_SESSION_ID_ERROR } from "../../../errors.js";
4
1
  import { getManagedAppTokenOrExit } from "../../../tokens.js";
5
2
  import {} from "../../../types.js";
6
- import { getMigrationsDirectoryUrl, getRemoteDatabaseUrl } from "../../../utils.js";
7
- import { getMigrationQueries } from "../../migration-queries.js";
3
+ import { getRemoteDatabaseUrl } from "../../../utils.js";
8
4
  import {
9
- INITIAL_SNAPSHOT,
10
- MIGRATIONS_NOT_INITIALIZED,
11
- MIGRATIONS_UP_TO_DATE,
12
- MIGRATION_NEEDED,
5
+ createCurrentSnapshot,
13
6
  createEmptySnapshot,
14
- getMigrationStatus,
15
- getMigrations,
16
- loadInitialSnapshot,
17
- loadMigration
18
- } from "../../migrations.js";
7
+ getMigrationQueries,
8
+ getProductionCurrentSnapshot
9
+ } from "../../migration-queries.js";
19
10
  async function cmd({
20
- astroConfig,
21
11
  dbConfig,
22
12
  flags
23
13
  }) {
24
14
  const isDryRun = flags.dryRun;
15
+ const isForceReset = flags.forceReset;
25
16
  const appToken = await getManagedAppTokenOrExit(flags.token);
26
- const migration = await getMigrationStatus({ dbConfig, root: astroConfig.root });
27
- if (migration.state === "no-migrations-found") {
28
- console.log(MIGRATIONS_NOT_INITIALIZED);
29
- process.exit(1);
30
- } else if (migration.state === "ahead") {
31
- console.log(MIGRATION_NEEDED);
32
- process.exit(1);
33
- }
34
- const migrationsDir = getMigrationsDirectoryUrl(astroConfig.root);
35
- const allLocalMigrations = await getMigrations(migrationsDir);
36
- let missingMigrations = [];
37
- try {
38
- const { data } = await prepareMigrateQuery({
39
- migrations: allLocalMigrations,
40
- appToken: appToken.token
41
- });
42
- missingMigrations = data;
43
- } catch (error) {
44
- if (error instanceof Error) {
45
- if (error.message.startsWith("{")) {
46
- const { error: { code } = { code: "" } } = JSON.parse(error.message);
47
- if (code === "TOKEN_UNAUTHORIZED") {
48
- console.error(MISSING_SESSION_ID_ERROR);
49
- }
50
- }
51
- }
52
- console.error(error);
53
- process.exit(1);
17
+ const productionSnapshot = await getProductionCurrentSnapshot({ appToken: appToken.token });
18
+ const currentSnapshot = createCurrentSnapshot(dbConfig);
19
+ const isFromScratch = isForceReset || JSON.stringify(productionSnapshot) === "{}";
20
+ const { queries: migrationQueries } = await getMigrationQueries({
21
+ oldSnapshot: isFromScratch ? createEmptySnapshot() : productionSnapshot,
22
+ newSnapshot: currentSnapshot
23
+ });
24
+ if (migrationQueries.length === 0) {
25
+ console.log("Database schema is up to date.");
26
+ } else {
27
+ console.log(`Database schema is out of date.`);
54
28
  }
55
- if (missingMigrations.length === 0) {
56
- console.log(MIGRATIONS_UP_TO_DATE);
29
+ if (isDryRun) {
30
+ console.log("Statements:", JSON.stringify(migrationQueries, void 0, 2));
57
31
  } else {
58
- console.log(`Pushing ${missingMigrations.length} migrations...`);
32
+ console.log(`Pushing database schema updates...`);
59
33
  await pushSchema({
60
- migrations: missingMigrations,
61
- migrationsDir,
34
+ statements: migrationQueries,
62
35
  appToken: appToken.token,
63
36
  isDryRun,
64
- currentSnapshot: migration.currentSnapshot
37
+ currentSnapshot
65
38
  });
66
39
  }
67
40
  await appToken.destroy();
68
41
  console.info("Push complete!");
69
42
  }
70
43
  async function pushSchema({
71
- migrations,
72
- migrationsDir,
44
+ statements,
73
45
  appToken,
74
46
  isDryRun,
75
47
  currentSnapshot
76
48
  }) {
77
- const initialSnapshot = migrations.find((m) => m === INITIAL_SNAPSHOT);
78
- const filteredMigrations = migrations.filter((m) => m !== INITIAL_SNAPSHOT);
79
- const missingMigrationContents = await Promise.all(
80
- filteredMigrations.map((m) => loadMigration(m, migrationsDir))
81
- );
82
- const initialMigrationBatch = initialSnapshot ? (await getMigrationQueries({
83
- oldSnapshot: createEmptySnapshot(),
84
- newSnapshot: await loadInitialSnapshot(migrationsDir)
85
- })).queries : [];
86
- const confirmations = missingMigrationContents.reduce((acc, curr) => {
87
- return [...acc, ...curr.confirm || []];
88
- }, []);
89
- if (confirmations.length > 0) {
90
- const response = await prompts([
91
- ...confirmations.map((message, index) => ({
92
- type: "confirm",
93
- name: String(index),
94
- message: red("Warning: ") + message + "\nContinue?",
95
- initial: true
96
- }))
97
- ]);
98
- if (Object.values(response).length === 0 || Object.values(response).some((value) => value === false)) {
99
- process.exit(1);
100
- }
101
- }
102
- const queries = missingMigrationContents.reduce((acc, curr) => {
103
- return [...acc, ...curr.db];
104
- }, initialMigrationBatch);
105
- await runMigrateQuery({ queries, migrations, snapshot: currentSnapshot, appToken, isDryRun });
106
- }
107
- async function runMigrateQuery({
108
- queries: baseQueries,
109
- migrations,
110
- snapshot,
111
- appToken,
112
- isDryRun
113
- }) {
114
- const queries = ["pragma defer_foreign_keys=true;", ...baseQueries];
115
49
  const requestBody = {
116
- snapshot,
117
- migrations,
118
- sql: queries,
50
+ snapshot: currentSnapshot,
51
+ sql: statements,
119
52
  experimentalVersion: 1
120
53
  };
121
54
  if (isDryRun) {
122
55
  console.info("[DRY RUN] Batch query:", JSON.stringify(requestBody, null, 2));
123
56
  return new Response(null, { status: 200 });
124
57
  }
125
- const url = new URL("/migrations/run", getRemoteDatabaseUrl());
58
+ const url = new URL("/db/push", getRemoteDatabaseUrl());
126
59
  return await fetch(url, {
127
60
  method: "POST",
128
61
  headers: new Headers({
@@ -131,27 +64,6 @@ async function runMigrateQuery({
131
64
  body: JSON.stringify(requestBody)
132
65
  });
133
66
  }
134
- async function prepareMigrateQuery({
135
- migrations,
136
- appToken
137
- }) {
138
- const url = new URL("/migrations/prepare", getRemoteDatabaseUrl());
139
- const requestBody = {
140
- migrations,
141
- experimentalVersion: 1
142
- };
143
- const result = await fetch(url, {
144
- method: "POST",
145
- headers: new Headers({
146
- Authorization: `Bearer ${appToken}`
147
- }),
148
- body: JSON.stringify(requestBody)
149
- });
150
- if (result.status >= 400) {
151
- throw new Error(await result.text());
152
- }
153
- return await result.json();
154
- }
155
67
  export {
156
68
  cmd
157
69
  };
@@ -1,7 +1,7 @@
1
1
  import type { AstroConfig } from 'astro';
2
2
  import type { Arguments } from 'yargs-parser';
3
3
  import type { DBConfig } from '../../../types.js';
4
- export declare function cmd({ astroConfig, dbConfig, flags, }: {
4
+ export declare function cmd({ dbConfig, flags, }: {
5
5
  astroConfig: AstroConfig;
6
6
  dbConfig: DBConfig;
7
7
  flags: Arguments;
@@ -1,46 +1,28 @@
1
- import { getMigrationQueries } from "../../migration-queries.js";
1
+ import { getManagedAppTokenOrExit } from "../../../tokens.js";
2
2
  import {
3
- MIGRATIONS_NOT_INITIALIZED,
4
- MIGRATIONS_UP_TO_DATE,
5
- MIGRATION_NEEDED,
6
- getMigrationStatus
7
- } from "../../migrations.js";
3
+ createCurrentSnapshot,
4
+ createEmptySnapshot,
5
+ getMigrationQueries,
6
+ getProductionCurrentSnapshot
7
+ } from "../../migration-queries.js";
8
8
  async function cmd({
9
- astroConfig,
10
9
  dbConfig,
11
10
  flags
12
11
  }) {
13
- const status = await getMigrationStatus({ dbConfig, root: astroConfig.root });
14
- const { state } = status;
15
- if (flags.json) {
16
- if (state === "ahead") {
17
- const { queries: migrationQueries } = await getMigrationQueries({
18
- oldSnapshot: status.oldSnapshot,
19
- newSnapshot: status.newSnapshot
20
- });
21
- const newFileContent = {
22
- diff: status.diff,
23
- db: migrationQueries
24
- };
25
- status.newFileContent = JSON.stringify(newFileContent, null, 2);
26
- }
27
- console.log(JSON.stringify(status));
28
- process.exit(state === "up-to-date" ? 0 : 1);
29
- }
30
- switch (state) {
31
- case "no-migrations-found": {
32
- console.log(MIGRATIONS_NOT_INITIALIZED);
33
- process.exit(1);
34
- }
35
- case "ahead": {
36
- console.log(MIGRATION_NEEDED);
37
- process.exit(1);
38
- }
39
- case "up-to-date": {
40
- console.log(MIGRATIONS_UP_TO_DATE);
41
- return;
42
- }
12
+ const appToken = await getManagedAppTokenOrExit(flags.token);
13
+ const productionSnapshot = await getProductionCurrentSnapshot({ appToken: appToken.token });
14
+ const currentSnapshot = createCurrentSnapshot(dbConfig);
15
+ const { queries: migrationQueries } = await getMigrationQueries({
16
+ oldSnapshot: JSON.stringify(productionSnapshot) !== "{}" ? productionSnapshot : createEmptySnapshot(),
17
+ newSnapshot: currentSnapshot
18
+ });
19
+ if (migrationQueries.length === 0) {
20
+ console.log(`Database schema is up to date.`);
21
+ } else {
22
+ console.log(`Database schema is out of date.`);
23
+ console.log(`Run 'astro db push' to push up your latest changes.`);
43
24
  }
25
+ await appToken.destroy();
44
26
  }
45
27
  export {
46
28
  cmd
@@ -13,10 +13,13 @@ async function cli({
13
13
  const { cmd } = await import("./commands/shell/index.js");
14
14
  return await cmd({ astroConfig, dbConfig, flags });
15
15
  }
16
- case "gen":
16
+ case "gen": {
17
+ console.log('"astro db gen" is no longer needed! Visit the docs for more information.');
18
+ return;
19
+ }
17
20
  case "sync": {
18
- const { cmd } = await import("./commands/gen/index.js");
19
- return await cmd({ astroConfig, dbConfig, flags });
21
+ console.log('"astro db sync" is no longer needed! Visit the docs for more information.');
22
+ return;
20
23
  }
21
24
  case "push": {
22
25
  const { cmd } = await import("./commands/push/index.js");
@@ -65,8 +68,8 @@ astro logout End your authenticated session with Astro Studio
65
68
  astro link Link this directory to an Astro Studio project
66
69
 
67
70
  astro db gen Creates snapshot based on your schema
68
- astro db push Pushes migrations to Astro Studio
69
- astro db verify Verifies migrations have been pushed and errors if not`;
71
+ astro db push Pushes schema updates to Astro Studio
72
+ astro db verify Tests schema updates /w Astro Studio (good for CI)`;
70
73
  }
71
74
  }
72
75
  export {
@@ -1,26 +1,21 @@
1
- import { type DBSnapshot, type DBTable } from '../types.js';
2
- /** Dependency injected for unit testing */
3
- type AmbiguityResponses = {
4
- collectionRenames: Record<string, string>;
5
- columnRenames: {
6
- [collectionName: string]: Record<string, string>;
7
- };
8
- };
9
- export declare function getMigrationQueries({ oldSnapshot, newSnapshot, ambiguityResponses, }: {
1
+ import { type DBConfig, type DBSnapshot, type DBTable } from '../types.js';
2
+ export declare function getMigrationQueries({ oldSnapshot, newSnapshot, }: {
10
3
  oldSnapshot: DBSnapshot;
11
4
  newSnapshot: DBSnapshot;
12
- ambiguityResponses?: AmbiguityResponses;
13
5
  }): Promise<{
14
6
  queries: string[];
15
7
  confirmations: string[];
16
8
  }>;
17
- export declare function getCollectionChangeQueries({ collectionName, oldCollection, newCollection, ambiguityResponses, }: {
9
+ export declare function getCollectionChangeQueries({ collectionName, oldCollection, newCollection, }: {
18
10
  collectionName: string;
19
11
  oldCollection: DBTable;
20
12
  newCollection: DBTable;
21
- ambiguityResponses?: AmbiguityResponses;
22
13
  }): Promise<{
23
14
  queries: string[];
24
15
  confirmations: string[];
25
16
  }>;
26
- export {};
17
+ export declare function getProductionCurrentSnapshot({ appToken, }: {
18
+ appToken: string;
19
+ }): Promise<DBSnapshot>;
20
+ export declare function createCurrentSnapshot({ tables }: DBConfig): DBSnapshot;
21
+ export declare function createEmptySnapshot(): DBSnapshot;
@@ -2,47 +2,49 @@ import deepDiff from "deep-diff";
2
2
  import { SQLiteAsyncDialect } from "drizzle-orm/sqlite-core";
3
3
  import * as color from "kleur/colors";
4
4
  import { customAlphabet } from "nanoid";
5
- import prompts from "prompts";
6
5
  import { hasPrimaryKey } from "../../runtime/index.js";
7
6
  import {
8
7
  getCreateIndexQueries,
9
8
  getCreateTableQuery,
9
+ getDropTableIfExistsQuery,
10
10
  getModifiers,
11
11
  getReferencesConfig,
12
12
  hasDefault,
13
13
  schemaTypeToSqlType
14
14
  } from "../../runtime/queries.js";
15
15
  import { isSerializedSQL } from "../../runtime/types.js";
16
+ import { RENAME_COLUMN_ERROR, RENAME_TABLE_ERROR } from "../errors.js";
16
17
  import {
17
18
  columnSchema
18
19
  } from "../types.js";
20
+ import { getRemoteDatabaseUrl } from "../utils.js";
19
21
  const sqlite = new SQLiteAsyncDialect();
20
22
  const genTempTableName = customAlphabet("abcdefghijklmnopqrstuvwxyz", 10);
21
23
  async function getMigrationQueries({
22
24
  oldSnapshot,
23
- newSnapshot,
24
- ambiguityResponses
25
+ newSnapshot
25
26
  }) {
26
27
  const queries = [];
27
28
  const confirmations = [];
28
- let added = getAddedCollections(oldSnapshot, newSnapshot);
29
- let dropped = getDroppedCollections(oldSnapshot, newSnapshot);
30
- if (!isEmpty(added) && !isEmpty(dropped)) {
31
- const resolved = await resolveCollectionRenames(added, dropped, ambiguityResponses);
32
- added = resolved.added;
33
- dropped = resolved.dropped;
34
- for (const { from, to } of resolved.renamed) {
35
- const renameQuery = `ALTER TABLE ${sqlite.escapeName(from)} RENAME TO ${sqlite.escapeName(
36
- to
37
- )}`;
38
- queries.push(renameQuery);
39
- }
29
+ const addedCollections = getAddedCollections(oldSnapshot, newSnapshot);
30
+ const droppedTables = getDroppedCollections(oldSnapshot, newSnapshot);
31
+ const notDeprecatedDroppedTables = Object.fromEntries(
32
+ Object.entries(droppedTables).filter(([, table]) => !table.deprecated)
33
+ );
34
+ if (!isEmpty(addedCollections) && !isEmpty(notDeprecatedDroppedTables)) {
35
+ throw new Error(
36
+ RENAME_TABLE_ERROR(
37
+ Object.keys(addedCollections)[0],
38
+ Object.keys(notDeprecatedDroppedTables)[0]
39
+ )
40
+ );
40
41
  }
41
- for (const [collectionName, collection] of Object.entries(added)) {
42
+ for (const [collectionName, collection] of Object.entries(addedCollections)) {
43
+ queries.push(getDropTableIfExistsQuery(collectionName));
42
44
  queries.push(getCreateTableQuery(collectionName, collection));
43
45
  queries.push(...getCreateIndexQueries(collectionName, collection));
44
46
  }
45
- for (const [collectionName] of Object.entries(dropped)) {
47
+ for (const [collectionName] of Object.entries(droppedTables)) {
46
48
  const dropQuery = `DROP TABLE ${sqlite.escapeName(collectionName)}`;
47
49
  queries.push(dropQuery);
48
50
  }
@@ -50,6 +52,19 @@ async function getMigrationQueries({
50
52
  const oldCollection = oldSnapshot.schema[collectionName];
51
53
  if (!oldCollection)
52
54
  continue;
55
+ const addedColumns = getAdded(oldCollection.columns, newCollection.columns);
56
+ const droppedColumns = getDropped(oldCollection.columns, newCollection.columns);
57
+ const notDeprecatedDroppedColumns = Object.fromEntries(
58
+ Object.entries(droppedColumns).filter(([key, col]) => !col.schema.deprecated)
59
+ );
60
+ if (!isEmpty(addedColumns) && !isEmpty(notDeprecatedDroppedColumns)) {
61
+ throw new Error(
62
+ RENAME_COLUMN_ERROR(
63
+ `${collectionName}.${Object.keys(addedColumns)[0]}`,
64
+ `${collectionName}.${Object.keys(notDeprecatedDroppedColumns)[0]}`
65
+ )
66
+ );
67
+ }
53
68
  const result = await getCollectionChangeQueries({
54
69
  collectionName,
55
70
  oldCollection,
@@ -63,14 +78,13 @@ async function getMigrationQueries({
63
78
  async function getCollectionChangeQueries({
64
79
  collectionName,
65
80
  oldCollection,
66
- newCollection,
67
- ambiguityResponses
81
+ newCollection
68
82
  }) {
69
83
  const queries = [];
70
84
  const confirmations = [];
71
85
  const updated = getUpdatedColumns(oldCollection.columns, newCollection.columns);
72
- let added = getAdded(oldCollection.columns, newCollection.columns);
73
- let dropped = getDropped(oldCollection.columns, newCollection.columns);
86
+ const added = getAdded(oldCollection.columns, newCollection.columns);
87
+ const dropped = getDropped(oldCollection.columns, newCollection.columns);
74
88
  const hasForeignKeyChanges = Boolean(
75
89
  deepDiff(oldCollection.foreignKeys, newCollection.foreignKeys)
76
90
  );
@@ -84,12 +98,6 @@ async function getCollectionChangeQueries({
84
98
  confirmations
85
99
  };
86
100
  }
87
- if (!hasForeignKeyChanges && !isEmpty(added) && !isEmpty(dropped)) {
88
- const resolved = await resolveColumnRenames(collectionName, added, dropped, ambiguityResponses);
89
- added = resolved.added;
90
- dropped = resolved.dropped;
91
- queries.push(...getColumnRenameQueries(collectionName, resolved.renamed));
92
- }
93
101
  if (!hasForeignKeyChanges && isEmpty(updated) && Object.values(dropped).every(canAlterTableDropColumn) && Object.values(added).every(canAlterTableAddColumn)) {
94
102
  queries.push(
95
103
  ...getAlterTableQueries(collectionName, added, dropped),
@@ -158,82 +166,6 @@ function getChangeIndexQueries({
158
166
  queries.push(...getCreateIndexQueries(collectionName, { indexes: added }));
159
167
  return queries;
160
168
  }
161
- async function resolveColumnRenames(collectionName, mightAdd, mightDrop, ambiguityResponses) {
162
- const added = {};
163
- const dropped = {};
164
- const renamed = [];
165
- for (const [columnName, column] of Object.entries(mightAdd)) {
166
- let oldColumnName = ambiguityResponses ? ambiguityResponses.columnRenames[collectionName]?.[columnName] ?? "__NEW__" : void 0;
167
- if (!oldColumnName) {
168
- const res = await prompts(
169
- {
170
- type: "select",
171
- name: "columnName",
172
- message: "New column " + color.blue(color.bold(`${collectionName}.${columnName}`)) + " detected. Was this renamed from an existing column?",
173
- choices: [
174
- { title: "New column (not renamed from existing)", value: "__NEW__" },
175
- ...Object.keys(mightDrop).filter((key) => !(key in renamed)).map((key) => ({ title: key, value: key }))
176
- ]
177
- },
178
- {
179
- onCancel: () => {
180
- process.exit(1);
181
- }
182
- }
183
- );
184
- oldColumnName = res.columnName;
185
- }
186
- if (oldColumnName === "__NEW__") {
187
- added[columnName] = column;
188
- } else {
189
- renamed.push({ from: oldColumnName, to: columnName });
190
- }
191
- }
192
- for (const [droppedColumnName, droppedColumn] of Object.entries(mightDrop)) {
193
- if (!renamed.find((r) => r.from === droppedColumnName)) {
194
- dropped[droppedColumnName] = droppedColumn;
195
- }
196
- }
197
- return { added, dropped, renamed };
198
- }
199
- async function resolveCollectionRenames(mightAdd, mightDrop, ambiguityResponses) {
200
- const added = {};
201
- const dropped = {};
202
- const renamed = [];
203
- for (const [collectionName, collection] of Object.entries(mightAdd)) {
204
- let oldCollectionName = ambiguityResponses ? ambiguityResponses.collectionRenames[collectionName] ?? "__NEW__" : void 0;
205
- if (!oldCollectionName) {
206
- const res = await prompts(
207
- {
208
- type: "select",
209
- name: "collectionName",
210
- message: "New collection " + color.blue(color.bold(collectionName)) + " detected. Was this renamed from an existing collection?",
211
- choices: [
212
- { title: "New collection (not renamed from existing)", value: "__NEW__" },
213
- ...Object.keys(mightDrop).filter((key) => !(key in renamed)).map((key) => ({ title: key, value: key }))
214
- ]
215
- },
216
- {
217
- onCancel: () => {
218
- process.exit(1);
219
- }
220
- }
221
- );
222
- oldCollectionName = res.collectionName;
223
- }
224
- if (oldCollectionName === "__NEW__") {
225
- added[collectionName] = collection;
226
- } else {
227
- renamed.push({ from: oldCollectionName, to: collectionName });
228
- }
229
- }
230
- for (const [droppedCollectionName, droppedCollection] of Object.entries(mightDrop)) {
231
- if (!renamed.find((r) => r.from === droppedCollectionName)) {
232
- dropped[droppedCollectionName] = droppedCollection;
233
- }
234
- }
235
- return { added, dropped, renamed };
236
- }
237
169
  function getAddedCollections(oldCollections, newCollections) {
238
170
  const added = {};
239
171
  for (const [key, newCollection] of Object.entries(newCollections.schema)) {
@@ -250,17 +182,6 @@ function getDroppedCollections(oldCollections, newCollections) {
250
182
  }
251
183
  return dropped;
252
184
  }
253
- function getColumnRenameQueries(unescapedCollectionName, renamed) {
254
- const queries = [];
255
- const collectionName = sqlite.escapeName(unescapedCollectionName);
256
- for (const { from, to } of renamed) {
257
- const q = `ALTER TABLE ${collectionName} RENAME COLUMN ${sqlite.escapeName(
258
- from
259
- )} TO ${sqlite.escapeName(to)}`;
260
- queries.push(q);
261
- }
262
- return queries;
263
- }
264
185
  function getAlterTableQueries(unescapedCollectionName, added, dropped) {
265
186
  const queries = [];
266
187
  const collectionName = sqlite.escapeName(unescapedCollectionName);
@@ -412,7 +333,30 @@ function canChangeTypeWithoutQuery(oldColumn, newColumn) {
412
333
  function hasRuntimeDefault(column) {
413
334
  return !!(column.schema.default && isSerializedSQL(column.schema.default));
414
335
  }
336
+ async function getProductionCurrentSnapshot({
337
+ appToken
338
+ }) {
339
+ const url = new URL("/db/schema", getRemoteDatabaseUrl());
340
+ const response = await fetch(url, {
341
+ method: "POST",
342
+ headers: new Headers({
343
+ Authorization: `Bearer ${appToken}`
344
+ })
345
+ });
346
+ const result = await response.json();
347
+ return result.data;
348
+ }
349
+ function createCurrentSnapshot({ tables = {} }) {
350
+ const schema = JSON.parse(JSON.stringify(tables));
351
+ return { experimentalVersion: 1, schema };
352
+ }
353
+ function createEmptySnapshot() {
354
+ return { experimentalVersion: 1, schema: {} };
355
+ }
415
356
  export {
357
+ createCurrentSnapshot,
358
+ createEmptySnapshot,
416
359
  getCollectionChangeQueries,
417
- getMigrationQueries
360
+ getMigrationQueries,
361
+ getProductionCurrentSnapshot
418
362
  };
@@ -1,7 +1,8 @@
1
1
  export declare const MISSING_SESSION_ID_ERROR: string;
2
2
  export declare const MISSING_PROJECT_ID_ERROR: string;
3
- export declare const MIGRATIONS_NOT_INITIALIZED: string;
4
3
  export declare const MISSING_EXECUTE_PATH_ERROR: string;
4
+ export declare const RENAME_TABLE_ERROR: (oldTable: string, newTable: string) => string;
5
+ export declare const RENAME_COLUMN_ERROR: (oldSelector: string, newSelector: string) => string;
5
6
  export declare const FILE_NOT_FOUND_ERROR: (path: string) => string;
6
7
  export declare const SEED_ERROR: (error: string) => string;
7
8
  export declare const REFERENCE_DNE_ERROR: (columnName: string) => string;
@@ -9,17 +9,20 @@ const MISSING_PROJECT_ID_ERROR = `${red("\u25B6 Directory not linked.")}
9
9
  To link this directory to an Astro Studio project, run
10
10
  ${cyan("astro db link")}
11
11
  `;
12
- const MIGRATIONS_NOT_INITIALIZED = `${yellow(
13
- "\u25B6 No migrations found!"
14
- )}
15
-
16
- To scaffold your migrations folder, run
17
- ${cyan("astro db sync")}
18
- `;
19
12
  const MISSING_EXECUTE_PATH_ERROR = `${red(
20
13
  "\u25B6 No file path provided."
21
14
  )} Provide a path by running ${cyan("astro db execute <path>")}
22
15
  `;
16
+ const RENAME_TABLE_ERROR = (oldTable, newTable) => {
17
+ return red("\u25B6 Potential table rename detected: " + oldTable + ", " + newTable) + `
18
+ You cannot add and remove tables in the same schema update batch.
19
+ To resolve, add a 'deprecated: true' flag to '${oldTable}' instead.`;
20
+ };
21
+ const RENAME_COLUMN_ERROR = (oldSelector, newSelector) => {
22
+ return red("\u25B6 Potential column rename detected: " + oldSelector + ", " + newSelector) + `
23
+ You cannot add and remove columns in the same table.
24
+ To resolve, add a 'deprecated: true' flag to '${oldSelector}' instead.`;
25
+ };
23
26
  const FILE_NOT_FOUND_ERROR = (path) => `${red("\u25B6 File not found:")} ${bold(path)}
24
27
  `;
25
28
  const SEED_ERROR = (error) => {
@@ -52,10 +55,11 @@ export {
52
55
  FOREIGN_KEY_DNE_ERROR,
53
56
  FOREIGN_KEY_REFERENCES_EMPTY_ERROR,
54
57
  FOREIGN_KEY_REFERENCES_LENGTH_ERROR,
55
- MIGRATIONS_NOT_INITIALIZED,
56
58
  MISSING_EXECUTE_PATH_ERROR,
57
59
  MISSING_PROJECT_ID_ERROR,
58
60
  MISSING_SESSION_ID_ERROR,
59
61
  REFERENCE_DNE_ERROR,
62
+ RENAME_COLUMN_ERROR,
63
+ RENAME_TABLE_ERROR,
60
64
  SEED_ERROR
61
65
  };
@@ -61,10 +61,7 @@ function fileURLIntegration() {
61
61
  }
62
62
  await Promise.all(unlinks);
63
63
  const assetDir = new URL(config.build.assets, config.outDir);
64
- const assetFiles = await fs.promises.readdir(assetDir);
65
- if (!assetFiles.length) {
66
- await fs.promises.rmdir(assetDir);
67
- }
64
+ await fs.promises.rmdir(assetDir).catch(() => []);
68
65
  } else {
69
66
  const moves = [];
70
67
  for (const fileName of fileNames) {
@@ -18,9 +18,11 @@ ${Object.entries(tables).map(([name, collection]) => generateTableType(name, col
18
18
  await writeFile(new URL(DB_TYPES_FILE, dotAstroDir), content);
19
19
  }
20
20
  function generateTableType(name, collection) {
21
+ const sanitizedColumnsList = Object.entries(collection.columns).filter(([key, val]) => !val.schema.deprecated);
22
+ const sanitizedColumns = Object.fromEntries(sanitizedColumnsList);
21
23
  let tableType = ` export const ${name}: import(${RUNTIME_IMPORT}).Table<
22
24
  ${JSON.stringify(name)},
23
- ${JSON.stringify(collection.columns)}
25
+ ${JSON.stringify(sanitizedColumns)}
24
26
  >;`;
25
27
  return tableType;
26
28
  }