@astrojs/db 0.3.4 → 0.3.5

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 { DBCollection, DBSnapshot } from '../types.js';
2
2
  interface PromptResponses {
3
3
  allowDataLoss: boolean;
4
- fieldRenames: Record<string, string | false>;
4
+ columnRenames: Record<string, string | false>;
5
5
  collectionRenames: Record<string, string | false>;
6
6
  }
7
7
  export declare function getMigrationQueries({ oldSnapshot, newSnapshot, promptResponses, }: {
@@ -1,7 +1,7 @@
1
1
  import type { DBCollection, DBCollections } from '../../types.js';
2
2
  interface PromptResponses {
3
3
  allowDataLoss: boolean;
4
- fieldRenames: Record<string, string | false>;
4
+ columnRenames: Record<string, string | false>;
5
5
  collectionRenames: Record<string, string | false>;
6
6
  }
7
7
  export declare function getMigrationQueries({ oldCollections, newCollections, promptResponses, }: {
package/dist/config.d.ts CHANGED
@@ -1,18 +1,18 @@
1
1
  import type { SQLiteInsertValue } from 'drizzle-orm/sqlite-core';
2
2
  import type { SqliteDB, Table } from './internal.js';
3
3
  import type { MaybeArray, collectionSchema } from './types.js';
4
- import { type BooleanField, type DBFieldInput, type DateFieldInput, type JsonField, type NumberField, type TextField, type indexSchema, type MaybePromise } from './types.js';
4
+ import { type BooleanColumn, type DBColumnInput, type DateColumnInput, type JsonColumn, type NumberColumn, type TextColumn, type indexSchema, type MaybePromise } from './types.js';
5
5
  import { z } from 'zod';
6
6
  export type DBDataContext = {
7
7
  db: SqliteDB;
8
- seed<TFields extends FieldsConfig>(collection: ResolvedCollectionConfig<TFields>, data: MaybeArray<SQLiteInsertValue<Table<string,
9
- /** TODO: true type inference */ Record<Extract<keyof TFields, string>, FieldsConfig[number]>>>>): Promise<any> /** TODO: type output */;
8
+ seed<TColumns extends ColumnsConfig>(collection: ResolvedCollectionConfig<TColumns>, data: MaybeArray<SQLiteInsertValue<Table<string,
9
+ /** TODO: true type inference */ Record<Extract<keyof TColumns, string>, ColumnsConfig[number]>>>>): Promise<any> /** TODO: type output */;
10
10
  mode: 'dev' | 'build';
11
11
  };
12
12
  export declare const dbConfigSchema: z.ZodObject<{
13
13
  studio: z.ZodOptional<z.ZodBoolean>;
14
14
  collections: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodObject<{
15
- fields: z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodObject<{
15
+ columns: z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodObject<{
16
16
  label: z.ZodOptional<z.ZodString>;
17
17
  optional: z.ZodOptional<z.ZodBoolean>;
18
18
  unique: z.ZodOptional<z.ZodBoolean>;
@@ -126,7 +126,7 @@ export declare const dbConfigSchema: z.ZodObject<{
126
126
  _setMeta: z.ZodOptional<z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodUnknown>>;
127
127
  writable: z.ZodLiteral<false>;
128
128
  }, "strip", z.ZodTypeAny, {
129
- fields: Record<string, {
129
+ columns: Record<string, {
130
130
  type: "boolean";
131
131
  label?: string | undefined;
132
132
  optional?: boolean | undefined;
@@ -168,7 +168,7 @@ export declare const dbConfigSchema: z.ZodObject<{
168
168
  table?: any;
169
169
  _setMeta?: ((...args: unknown[]) => unknown) | undefined;
170
170
  }, {
171
- fields: Record<string, {
171
+ columns: Record<string, {
172
172
  type: "boolean";
173
173
  label?: string | undefined;
174
174
  optional?: boolean | undefined;
@@ -210,7 +210,7 @@ export declare const dbConfigSchema: z.ZodObject<{
210
210
  table?: any;
211
211
  _setMeta?: ((...args: unknown[]) => unknown) | undefined;
212
212
  }>, z.ZodObject<{
213
- fields: z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodObject<{
213
+ columns: z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodObject<{
214
214
  label: z.ZodOptional<z.ZodString>;
215
215
  optional: z.ZodOptional<z.ZodBoolean>;
216
216
  unique: z.ZodOptional<z.ZodBoolean>;
@@ -324,7 +324,7 @@ export declare const dbConfigSchema: z.ZodObject<{
324
324
  _setMeta: z.ZodOptional<z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodUnknown>>;
325
325
  writable: z.ZodLiteral<true>;
326
326
  }, "strip", z.ZodTypeAny, {
327
- fields: Record<string, {
327
+ columns: Record<string, {
328
328
  type: "boolean";
329
329
  label?: string | undefined;
330
330
  optional?: boolean | undefined;
@@ -366,7 +366,7 @@ export declare const dbConfigSchema: z.ZodObject<{
366
366
  table?: any;
367
367
  _setMeta?: ((...args: unknown[]) => unknown) | undefined;
368
368
  }, {
369
- fields: Record<string, {
369
+ columns: Record<string, {
370
370
  type: "boolean";
371
371
  label?: string | undefined;
372
372
  optional?: boolean | undefined;
@@ -412,7 +412,7 @@ export declare const dbConfigSchema: z.ZodObject<{
412
412
  }, "strip", z.ZodTypeAny, {
413
413
  studio?: boolean | undefined;
414
414
  collections?: Record<string, {
415
- fields: Record<string, {
415
+ columns: Record<string, {
416
416
  type: "boolean";
417
417
  label?: string | undefined;
418
418
  optional?: boolean | undefined;
@@ -454,7 +454,7 @@ export declare const dbConfigSchema: z.ZodObject<{
454
454
  table?: any;
455
455
  _setMeta?: ((...args: unknown[]) => unknown) | undefined;
456
456
  } | {
457
- fields: Record<string, {
457
+ columns: Record<string, {
458
458
  type: "boolean";
459
459
  label?: string | undefined;
460
460
  optional?: boolean | undefined;
@@ -500,7 +500,7 @@ export declare const dbConfigSchema: z.ZodObject<{
500
500
  }, {
501
501
  studio?: boolean | undefined;
502
502
  collections?: Record<string, {
503
- fields: Record<string, {
503
+ columns: Record<string, {
504
504
  type: "boolean";
505
505
  label?: string | undefined;
506
506
  optional?: boolean | undefined;
@@ -542,7 +542,7 @@ export declare const dbConfigSchema: z.ZodObject<{
542
542
  table?: any;
543
543
  _setMeta?: ((...args: unknown[]) => unknown) | undefined;
544
544
  } | {
545
- fields: Record<string, {
545
+ columns: Record<string, {
546
546
  type: "boolean";
547
547
  label?: string | undefined;
548
548
  optional?: boolean | undefined;
@@ -593,7 +593,7 @@ export declare const astroConfigWithDbSchema: z.ZodObject<{
593
593
  db: z.ZodOptional<z.ZodObject<{
594
594
  studio: z.ZodOptional<z.ZodBoolean>;
595
595
  collections: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodObject<{
596
- fields: z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodObject<{
596
+ columns: z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodObject<{
597
597
  label: z.ZodOptional<z.ZodString>;
598
598
  optional: z.ZodOptional<z.ZodBoolean>;
599
599
  unique: z.ZodOptional<z.ZodBoolean>;
@@ -707,7 +707,7 @@ export declare const astroConfigWithDbSchema: z.ZodObject<{
707
707
  _setMeta: z.ZodOptional<z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodUnknown>>;
708
708
  writable: z.ZodLiteral<false>;
709
709
  }, "strip", z.ZodTypeAny, {
710
- fields: Record<string, {
710
+ columns: Record<string, {
711
711
  type: "boolean";
712
712
  label?: string | undefined;
713
713
  optional?: boolean | undefined;
@@ -749,7 +749,7 @@ export declare const astroConfigWithDbSchema: z.ZodObject<{
749
749
  table?: any;
750
750
  _setMeta?: ((...args: unknown[]) => unknown) | undefined;
751
751
  }, {
752
- fields: Record<string, {
752
+ columns: Record<string, {
753
753
  type: "boolean";
754
754
  label?: string | undefined;
755
755
  optional?: boolean | undefined;
@@ -791,7 +791,7 @@ export declare const astroConfigWithDbSchema: z.ZodObject<{
791
791
  table?: any;
792
792
  _setMeta?: ((...args: unknown[]) => unknown) | undefined;
793
793
  }>, z.ZodObject<{
794
- fields: z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodObject<{
794
+ columns: z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodObject<{
795
795
  label: z.ZodOptional<z.ZodString>;
796
796
  optional: z.ZodOptional<z.ZodBoolean>;
797
797
  unique: z.ZodOptional<z.ZodBoolean>;
@@ -905,7 +905,7 @@ export declare const astroConfigWithDbSchema: z.ZodObject<{
905
905
  _setMeta: z.ZodOptional<z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodUnknown>>;
906
906
  writable: z.ZodLiteral<true>;
907
907
  }, "strip", z.ZodTypeAny, {
908
- fields: Record<string, {
908
+ columns: Record<string, {
909
909
  type: "boolean";
910
910
  label?: string | undefined;
911
911
  optional?: boolean | undefined;
@@ -947,7 +947,7 @@ export declare const astroConfigWithDbSchema: z.ZodObject<{
947
947
  table?: any;
948
948
  _setMeta?: ((...args: unknown[]) => unknown) | undefined;
949
949
  }, {
950
- fields: Record<string, {
950
+ columns: Record<string, {
951
951
  type: "boolean";
952
952
  label?: string | undefined;
953
953
  optional?: boolean | undefined;
@@ -993,7 +993,7 @@ export declare const astroConfigWithDbSchema: z.ZodObject<{
993
993
  }, "strip", z.ZodTypeAny, {
994
994
  studio?: boolean | undefined;
995
995
  collections?: Record<string, {
996
- fields: Record<string, {
996
+ columns: Record<string, {
997
997
  type: "boolean";
998
998
  label?: string | undefined;
999
999
  optional?: boolean | undefined;
@@ -1035,7 +1035,7 @@ export declare const astroConfigWithDbSchema: z.ZodObject<{
1035
1035
  table?: any;
1036
1036
  _setMeta?: ((...args: unknown[]) => unknown) | undefined;
1037
1037
  } | {
1038
- fields: Record<string, {
1038
+ columns: Record<string, {
1039
1039
  type: "boolean";
1040
1040
  label?: string | undefined;
1041
1041
  optional?: boolean | undefined;
@@ -1081,7 +1081,7 @@ export declare const astroConfigWithDbSchema: z.ZodObject<{
1081
1081
  }, {
1082
1082
  studio?: boolean | undefined;
1083
1083
  collections?: Record<string, {
1084
- fields: Record<string, {
1084
+ columns: Record<string, {
1085
1085
  type: "boolean";
1086
1086
  label?: string | undefined;
1087
1087
  optional?: boolean | undefined;
@@ -1123,7 +1123,7 @@ export declare const astroConfigWithDbSchema: z.ZodObject<{
1123
1123
  table?: any;
1124
1124
  _setMeta?: ((...args: unknown[]) => unknown) | undefined;
1125
1125
  } | {
1126
- fields: Record<string, {
1126
+ columns: Record<string, {
1127
1127
  type: "boolean";
1128
1128
  label?: string | undefined;
1129
1129
  optional?: boolean | undefined;
@@ -1171,7 +1171,7 @@ export declare const astroConfigWithDbSchema: z.ZodObject<{
1171
1171
  db?: {
1172
1172
  studio?: boolean | undefined;
1173
1173
  collections?: Record<string, {
1174
- fields: Record<string, {
1174
+ columns: Record<string, {
1175
1175
  type: "boolean";
1176
1176
  label?: string | undefined;
1177
1177
  optional?: boolean | undefined;
@@ -1213,7 +1213,7 @@ export declare const astroConfigWithDbSchema: z.ZodObject<{
1213
1213
  table?: any;
1214
1214
  _setMeta?: ((...args: unknown[]) => unknown) | undefined;
1215
1215
  } | {
1216
- fields: Record<string, {
1216
+ columns: Record<string, {
1217
1217
  type: "boolean";
1218
1218
  label?: string | undefined;
1219
1219
  optional?: boolean | undefined;
@@ -1261,7 +1261,7 @@ export declare const astroConfigWithDbSchema: z.ZodObject<{
1261
1261
  db?: {
1262
1262
  studio?: boolean | undefined;
1263
1263
  collections?: Record<string, {
1264
- fields: Record<string, {
1264
+ columns: Record<string, {
1265
1265
  type: "boolean";
1266
1266
  label?: string | undefined;
1267
1267
  optional?: boolean | undefined;
@@ -1303,7 +1303,7 @@ export declare const astroConfigWithDbSchema: z.ZodObject<{
1303
1303
  table?: any;
1304
1304
  _setMeta?: ((...args: unknown[]) => unknown) | undefined;
1305
1305
  } | {
1306
- fields: Record<string, {
1306
+ columns: Record<string, {
1307
1307
  type: "boolean";
1308
1308
  label?: string | undefined;
1309
1309
  optional?: boolean | undefined;
@@ -1348,27 +1348,27 @@ export declare const astroConfigWithDbSchema: z.ZodObject<{
1348
1348
  data?: ((...args: unknown[]) => void | Promise<void>) | undefined;
1349
1349
  } | undefined;
1350
1350
  }>;
1351
- export type FieldsConfig = z.input<typeof collectionSchema>['fields'];
1352
- interface CollectionConfig<TFields extends FieldsConfig> extends Pick<z.input<typeof collectionSchema>, 'fields' | 'indexes'> {
1353
- fields: TFields;
1354
- indexes?: Record<string, IndexConfig<TFields>>;
1351
+ export type ColumnsConfig = z.input<typeof collectionSchema>['columns'];
1352
+ interface CollectionConfig<TColumns extends ColumnsConfig> extends Pick<z.input<typeof collectionSchema>, 'columns' | 'indexes'> {
1353
+ columns: TColumns;
1354
+ indexes?: Record<string, IndexConfig<TColumns>>;
1355
1355
  }
1356
- interface IndexConfig<TFields extends FieldsConfig> extends z.input<typeof indexSchema> {
1357
- on: MaybeArray<Extract<keyof TFields, string>>;
1356
+ interface IndexConfig<TColumns extends ColumnsConfig> extends z.input<typeof indexSchema> {
1357
+ on: MaybeArray<Extract<keyof TColumns, string>>;
1358
1358
  }
1359
- export type ResolvedCollectionConfig<TFields extends FieldsConfig = FieldsConfig, Writable extends boolean = boolean> = CollectionConfig<TFields> & {
1359
+ export type ResolvedCollectionConfig<TColumns extends ColumnsConfig = ColumnsConfig, Writable extends boolean = boolean> = CollectionConfig<TColumns> & {
1360
1360
  writable: Writable;
1361
- table: Table<string, TFields>;
1361
+ table: Table<string, TColumns>;
1362
1362
  };
1363
- export declare function defineCollection<TFields extends FieldsConfig>(userConfig: CollectionConfig<TFields>): ResolvedCollectionConfig<TFields, false>;
1364
- export declare function defineWritableCollection<TFields extends FieldsConfig>(userConfig: CollectionConfig<TFields>): ResolvedCollectionConfig<TFields, true>;
1363
+ export declare function defineCollection<TColumns extends ColumnsConfig>(userConfig: CollectionConfig<TColumns>): ResolvedCollectionConfig<TColumns, false>;
1364
+ export declare function defineWritableCollection<TColumns extends ColumnsConfig>(userConfig: CollectionConfig<TColumns>): ResolvedCollectionConfig<TColumns, true>;
1365
1365
  export type AstroConfigWithDB = z.infer<typeof astroConfigWithDbSchema>;
1366
- type FieldOpts<T extends DBFieldInput> = Omit<T, 'type'>;
1367
- export declare const field: {
1368
- number(opts?: FieldOpts<NumberField>): NumberField;
1369
- boolean(opts?: FieldOpts<BooleanField>): BooleanField;
1370
- text(opts?: FieldOpts<TextField>): TextField;
1371
- date(opts?: FieldOpts<DateFieldInput>): DateFieldInput;
1372
- json(opts?: FieldOpts<JsonField>): JsonField;
1366
+ type ColumnOpts<T extends DBColumnInput> = Omit<T, 'type'>;
1367
+ export declare const column: {
1368
+ number(opts?: ColumnOpts<NumberColumn>): NumberColumn;
1369
+ boolean(opts?: ColumnOpts<BooleanColumn>): BooleanColumn;
1370
+ text(opts?: ColumnOpts<TextColumn>): TextColumn;
1371
+ date(opts?: ColumnOpts<DateColumnInput>): DateColumnInput;
1372
+ json(opts?: ColumnOpts<JsonColumn>): JsonColumn;
1373
1373
  };
1374
1374
  export {};
@@ -0,0 +1,6 @@
1
+ import type { AstroConfig } from 'astro';
2
+ import type { Arguments } from 'yargs-parser';
3
+ export declare function cmd({ config }: {
4
+ config: AstroConfig;
5
+ flags: Arguments;
6
+ }): Promise<void>;
@@ -1,4 +1,9 @@
1
- import { getMigrationStatus, MIGRATION_NEEDED, MIGRATIONS_NOT_INITIALIZED, MIGRATIONS_UP_TO_DATE } from "../../migrations.js";
1
+ import {
2
+ getMigrationStatus,
3
+ MIGRATION_NEEDED,
4
+ MIGRATIONS_NOT_INITIALIZED,
5
+ MIGRATIONS_UP_TO_DATE
6
+ } from "../../migrations.js";
2
7
  import { getMigrationQueries } from "../../migration-queries.js";
3
8
  async function cmd({ config, flags }) {
4
9
  const status = await getMigrationStatus(config);
@@ -1,6 +1,7 @@
1
1
  import { STUDIO_CONFIG_MISSING_CLI_ERROR } from "../errors.js";
2
2
  async function cli({ flags, config }) {
3
- const command = flags._[3];
3
+ const args = flags._;
4
+ const command = args[2] === "db" ? args[3] : args[2];
4
5
  if (!config.db?.studio) {
5
6
  console.log(STUDIO_CONFIG_MISSING_CLI_ERROR);
6
7
  process.exit(1);
@@ -10,8 +11,9 @@ async function cli({ flags, config }) {
10
11
  const { cmd } = await import("./commands/shell/index.js");
11
12
  return await cmd({ config, flags });
12
13
  }
14
+ case "gen":
13
15
  case "sync": {
14
- const { cmd } = await import("./commands/sync/index.js");
16
+ const { cmd } = await import("./commands/gen/index.js");
15
17
  return await cmd({ config, flags });
16
18
  }
17
19
  case "push": {
@@ -52,11 +54,11 @@ ${showHelp()}`);
52
54
 
53
55
  Usage:
54
56
 
55
- astro db login Authenticate your machine with Astro Studio
56
- astro db logout End your authenticated session with Astro Studio
57
- astro db link Link this directory to an Astro Studio project
57
+ astro login Authenticate your machine with Astro Studio
58
+ astro logout End your authenticated session with Astro Studio
59
+ astro link Link this directory to an Astro Studio project
58
60
 
59
- astro db sync Creates snapshot based on your schema
61
+ astro db gen Creates snapshot based on your schema
60
62
  astro db push Pushes migrations to Astro Studio
61
63
  astro db verify Verifies migrations have been pushed and errors if not`;
62
64
  }
@@ -1,4 +1,4 @@
1
- import type { DBCollection, DBSnapshot } from '../types.js';
1
+ import { type DBCollection, type DBSnapshot } from '../types.js';
2
2
  /** Dependency injected for unit testing */
3
3
  type AmbiguityResponses = {
4
4
  collectionRenames: Record<string, string>;
@@ -1,5 +1,8 @@
1
1
  import * as color from "kleur/colors";
2
2
  import deepDiff from "deep-diff";
3
+ import {
4
+ fieldSchema
5
+ } from "../types.js";
3
6
  import { SQLiteAsyncDialect } from "drizzle-orm/sqlite-core";
4
7
  import { customAlphabet } from "nanoid";
5
8
  import prompts from "prompts";
@@ -377,16 +380,19 @@ function getUpdated(oldObj, newObj) {
377
380
  function getUpdatedFields(oldFields, newFields) {
378
381
  const updated = {};
379
382
  for (const [key, newField] of Object.entries(newFields)) {
380
- const oldField = oldFields[key];
383
+ let oldField = oldFields[key];
381
384
  if (!oldField)
382
385
  continue;
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
- });
386
+ if (oldField.type !== newField.type && canChangeTypeWithoutQuery(oldField, newField)) {
387
+ const asNewField = fieldSchema.safeParse({
388
+ type: newField.type,
389
+ schema: oldField.schema
390
+ });
391
+ if (asNewField.success) {
392
+ oldField = asNewField.data;
393
+ }
394
+ }
395
+ const diff = deepDiff(oldField, newField);
390
396
  if (diff) {
391
397
  updated[key] = { old: oldField, new: newField };
392
398
  }
@@ -397,11 +403,6 @@ const typeChangesWithoutQuery = [
397
403
  { from: "boolean", to: "number" },
398
404
  { from: "date", to: "text" },
399
405
  { from: "json", to: "text" }
400
- // TODO: decide on these. They *could* work with SQLite CAST
401
- // { from: 'boolean', to: 'text' },
402
- // { from: 'boolean', to: 'json' },
403
- // { from: 'number', to: 'text' },
404
- // { from: 'number', to: 'json' },
405
406
  ];
406
407
  function canChangeTypeWithoutQuery(oldField, newField) {
407
408
  return typeChangesWithoutQuery.some(
@@ -1,5 +1,7 @@
1
1
  export declare const MISSING_SESSION_ID_ERROR: string;
2
2
  export declare const MISSING_PROJECT_ID_ERROR: string;
3
3
  export declare const STUDIO_CONFIG_MISSING_WRITABLE_COLLECTIONS_ERROR: (collectionName: string) => string;
4
+ export declare const UNSAFE_WRITABLE_WARNING: string;
4
5
  export declare const STUDIO_CONFIG_MISSING_CLI_ERROR: string;
5
6
  export declare const MIGRATIONS_NOT_INITIALIZED: string;
7
+ export declare const SEED_WRITABLE_IN_PROD_ERROR: (collectionName: string) => string;
@@ -1,37 +1,50 @@
1
1
  import { cyan, bold, red, green, yellow } from "kleur/colors";
2
- const MISSING_SESSION_ID_ERROR = `${red(
3
- "\u25B6 Login required!"
4
- )}
2
+ const MISSING_SESSION_ID_ERROR = `${red("\u25B6 Login required!")}
5
3
 
6
4
  To authenticate with Astro Studio, run
7
5
  ${cyan("astro db login")}
8
6
  `;
9
- const MISSING_PROJECT_ID_ERROR = `${red(
10
- "\u25B6 Directory not linked."
11
- )}
7
+ const MISSING_PROJECT_ID_ERROR = `${red("\u25B6 Directory not linked.")}
12
8
 
13
9
  To link this directory to an Astro Studio project, run
14
10
  ${cyan("astro db link")}
15
11
  `;
16
- const STUDIO_CONFIG_MISSING_WRITABLE_COLLECTIONS_ERROR = (collectionName) => `${red(`\u25B6 Writable collection ${bold(collectionName)} requires Astro Studio.`)}
12
+ const STUDIO_CONFIG_MISSING_WRITABLE_COLLECTIONS_ERROR = (collectionName) => `${red(
13
+ `\u25B6 Writable collection ${bold(collectionName)} requires Astro Studio or the ${yellow("unsafeWritable")} option.`
14
+ )}
17
15
 
18
16
  Visit ${cyan("https://astro.build/studio")} to create your account
19
17
  and set ${green("studio: true")} in your astro.config.mjs file to enable Studio.
20
18
  `;
19
+ const UNSAFE_WRITABLE_WARNING = `${yellow("unsafeWritable")} option is enabled and you are using writable collections.
20
+ Redeploying your app may result in wiping away your database.
21
+ I hope you know what you are doing.
22
+ `;
21
23
  const STUDIO_CONFIG_MISSING_CLI_ERROR = `${red("\u25B6 This command requires Astro Studio.")}
22
24
 
23
25
  Visit ${cyan("https://astro.build/studio")} to create your account
24
26
  and set ${green("studio: true")} in your astro.config.mjs file to enable Studio.
25
27
  `;
26
- const MIGRATIONS_NOT_INITIALIZED = `${yellow("\u25B6 No migrations found!")}
28
+ const MIGRATIONS_NOT_INITIALIZED = `${yellow(
29
+ "\u25B6 No migrations found!"
30
+ )}
27
31
 
28
32
  To scaffold your migrations folder, run
29
33
  ${cyan("astro db sync")}
30
34
  `;
35
+ const SEED_WRITABLE_IN_PROD_ERROR = (collectionName) => {
36
+ return `${red(
37
+ `Writable collections should not be seeded in production with data().`
38
+ )} You can seed ${bold(
39
+ collectionName
40
+ )} in development mode only using the "mode" flag. See the docs for more: https://www.notion.so/astroinc/astrojs-db-README-dcf6fa10de9a4f528be56cee96e8c054?pvs=4#278aed3fc37e4cec80240d1552ff6ac5`;
41
+ };
31
42
  export {
32
43
  MIGRATIONS_NOT_INITIALIZED,
33
44
  MISSING_PROJECT_ID_ERROR,
34
45
  MISSING_SESSION_ID_ERROR,
46
+ SEED_WRITABLE_IN_PROD_ERROR,
35
47
  STUDIO_CONFIG_MISSING_CLI_ERROR,
36
- STUDIO_CONFIG_MISSING_WRITABLE_COLLECTIONS_ERROR
48
+ STUDIO_CONFIG_MISSING_WRITABLE_COLLECTIONS_ERROR,
49
+ UNSAFE_WRITABLE_WARNING
37
50
  };
@@ -7,7 +7,7 @@ import { DB_PATH } from "../consts.js";
7
7
  import { createLocalDatabaseClient } from "../../runtime/db-client.js";
8
8
  import { astroConfigWithDbSchema } from "../types.js";
9
9
  import {} from "../utils.js";
10
- import { STUDIO_CONFIG_MISSING_WRITABLE_COLLECTIONS_ERROR } from "../errors.js";
10
+ import { STUDIO_CONFIG_MISSING_WRITABLE_COLLECTIONS_ERROR, UNSAFE_WRITABLE_WARNING } from "../errors.js";
11
11
  import { errorMap } from "./error-map.js";
12
12
  import { dirname } from "path";
13
13
  import { fileURLToPath } from "url";
@@ -27,12 +27,16 @@ function astroDBIntegration() {
27
27
  const configWithDb = astroConfigWithDbSchema.parse(config, { errorMap });
28
28
  const collections = configWithDb.db?.collections ?? {};
29
29
  const studio = configWithDb.db?.studio ?? false;
30
+ const unsafeWritable = Boolean(configWithDb.db?.unsafeWritable);
30
31
  const foundWritableCollection = Object.entries(collections).find(([, c]) => c.writable);
31
- if (!studio && foundWritableCollection) {
32
+ const writableAllowed = studio || unsafeWritable;
33
+ if (!writableAllowed && foundWritableCollection) {
32
34
  logger.error(
33
35
  STUDIO_CONFIG_MISSING_WRITABLE_COLLECTIONS_ERROR(foundWritableCollection[0])
34
36
  );
35
37
  process.exit(1);
38
+ } else if (unsafeWritable && foundWritableCollection) {
39
+ logger.warn(UNSAFE_WRITABLE_WARNING);
36
40
  }
37
41
  let dbPlugin;
38
42
  if (studio && command === "build" && process.env.ASTRO_DB_TEST_ENV !== "1") {
@@ -20,20 +20,20 @@ export declare function getModifiers(fieldName: string, field: DBField): string;
20
20
  export declare function getReferencesConfig(field: DBField): {
21
21
  type: "number";
22
22
  schema: ({
23
+ unique: boolean;
23
24
  name?: string | undefined;
24
25
  label?: string | undefined;
25
- unique?: boolean | undefined;
26
26
  collection?: string | undefined;
27
27
  } & {
28
- primaryKey?: false | undefined;
29
- optional?: boolean | undefined;
28
+ optional: boolean;
29
+ primaryKey: false;
30
30
  default?: number | import("../runtime/types.js").SerializedSQL | undefined;
31
31
  } & {
32
32
  references?: any | undefined;
33
33
  }) | ({
34
+ unique: boolean;
34
35
  name?: string | undefined;
35
36
  label?: string | undefined;
36
- unique?: boolean | undefined;
37
37
  collection?: string | undefined;
38
38
  } & {
39
39
  primaryKey: true;
@@ -45,21 +45,21 @@ export declare function getReferencesConfig(field: DBField): {
45
45
  } | {
46
46
  type: "text";
47
47
  schema: ({
48
+ unique: boolean;
48
49
  name?: string | undefined;
49
50
  label?: string | undefined;
50
- unique?: boolean | undefined;
51
51
  collection?: string | undefined;
52
52
  default?: string | import("../runtime/types.js").SerializedSQL | undefined;
53
53
  multiline?: boolean | undefined;
54
54
  } & {
55
- primaryKey?: false | undefined;
56
- optional?: boolean | undefined;
55
+ optional: boolean;
56
+ primaryKey: false;
57
57
  } & {
58
58
  references?: any | undefined;
59
59
  }) | ({
60
+ unique: boolean;
60
61
  name?: string | undefined;
61
62
  label?: string | undefined;
62
- unique?: boolean | undefined;
63
63
  collection?: string | undefined;
64
64
  default?: string | import("../runtime/types.js").SerializedSQL | undefined;
65
65
  multiline?: boolean | undefined;
@@ -1,10 +1,11 @@
1
1
  import {
2
2
  } from "../core/types.js";
3
- import { bold } from "kleur/colors";
4
- import { sql } from "drizzle-orm";
3
+ import { bold, red } from "kleur/colors";
4
+ import { sql, getTableName } from "drizzle-orm";
5
5
  import { SQLiteAsyncDialect } from "drizzle-orm/sqlite-core";
6
6
  import { hasPrimaryKey } from "../runtime/index.js";
7
7
  import { isSerializedSQL } from "../runtime/types.js";
8
+ import { SEED_WRITABLE_IN_PROD_ERROR } from "./errors.js";
8
9
  const sqlite = new SQLiteAsyncDialect();
9
10
  async function recreateTables({
10
11
  db,
@@ -29,8 +30,22 @@ async function seedData({
29
30
  }) {
30
31
  try {
31
32
  await data({
32
- seed: async ({ table }, values) => {
33
- const result = Array.isArray(values) ? db.insert(table).values(values).returning() : db.insert(table).values(values).returning().get();
33
+ seed: async ({ table, writable }, values) => {
34
+ if (writable && mode === "build" && process.env.ASTRO_DB_TEST_ENV !== "1") {
35
+ (logger ?? console).error(SEED_WRITABLE_IN_PROD_ERROR(getTableName(table)));
36
+ process.exit(1);
37
+ }
38
+ await db.insert(table).values(values);
39
+ },
40
+ seedReturning: async ({ table, writable }, values) => {
41
+ if (writable && mode === "build" && process.env.ASTRO_DB_TEST_ENV !== "1") {
42
+ (logger ?? console).error(SEED_WRITABLE_IN_PROD_ERROR(getTableName(table)));
43
+ process.exit(1);
44
+ }
45
+ let result = db.insert(table).values(values).returning();
46
+ if (!Array.isArray(values)) {
47
+ result = result.get();
48
+ }
34
49
  return result;
35
50
  },
36
51
  db,
@@ -129,7 +144,7 @@ function getModifiers(fieldName, field) {
129
144
  const { collection, name } = references.schema;
130
145
  if (!collection || !name) {
131
146
  throw new Error(
132
- `Invalid reference for field ${fieldName}. This is an unexpected error that should be reported to the Astro team.`
147
+ `Field ${collection}.${name} references a collection that does not exist. Did you apply the referenced collection to the \`collections\` object in your Astro config?`
133
148
  );
134
149
  }
135
150
  modifiers += ` REFERENCES ${sqlite.escapeName(collection)} (${sqlite.escapeName(name)})`;