@astrojs/db 0.10.7 → 0.11.1

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.
@@ -2,6 +2,7 @@ import type { AstroConfig, AstroIntegration } from 'astro';
2
2
  import type { AstroDbIntegration } from './types.js';
3
3
  export type VitePlugin = Required<AstroConfig['vite']>['plugins'][number];
4
4
  export declare function getAstroStudioEnv(envMode?: string): Record<`ASTRO_STUDIO_${string}`, string>;
5
+ export declare function getAstroEnv(envMode?: string): Record<`ASTRO_${string}`, string>;
5
6
  export declare function getRemoteDatabaseUrl(): string;
6
7
  export declare function getAstroStudioUrl(): string;
7
8
  export declare function getDbDirectoryUrl(root: URL | string): URL;
@@ -1,6 +1,6 @@
1
1
  import type { AstroConfig } from 'astro';
2
2
  import type { Arguments } from 'yargs-parser';
3
- import { type DBConfig } from '../../../types.js';
3
+ import type { DBConfig } from '../../../types.js';
4
4
  export declare function cmd({ astroConfig, dbConfig, flags, }: {
5
5
  astroConfig: AstroConfig;
6
6
  dbConfig: DBConfig;
@@ -13,7 +13,6 @@ import {
13
13
  } from "../../../integration/vite-plugin-db.js";
14
14
  import { bundleFile, importBundledFile } from "../../../load-file.js";
15
15
  import { getManagedAppTokenOrExit } from "../../../tokens.js";
16
- import {} from "../../../types.js";
17
16
  async function cmd({
18
17
  astroConfig,
19
18
  dbConfig,
@@ -41,9 +40,7 @@ async function cmd({
41
40
  } else {
42
41
  virtualModContents = getLocalVirtualModContents({
43
42
  tables: dbConfig.tables ?? {},
44
- root: astroConfig.root,
45
- shouldSeed: false,
46
- seedFiles: []
43
+ root: astroConfig.root
47
44
  });
48
45
  }
49
46
  const { code } = await bundleFile({ virtualModContents, root: astroConfig.root, fileUrl });
@@ -3,10 +3,11 @@ import {
3
3
  createLocalDatabaseClient,
4
4
  createRemoteDatabaseClient
5
5
  } from "../../../../runtime/db-client.js";
6
+ import { normalizeDatabaseUrl } from "../../../../runtime/index.js";
6
7
  import { DB_PATH } from "../../../consts.js";
7
8
  import { SHELL_QUERY_MISSING_ERROR } from "../../../errors.js";
8
9
  import { getManagedAppTokenOrExit } from "../../../tokens.js";
9
- import { getRemoteDatabaseUrl } from "../../../utils.js";
10
+ import { getAstroEnv, getRemoteDatabaseUrl } from "../../../utils.js";
10
11
  async function cmd({
11
12
  flags,
12
13
  astroConfig
@@ -23,7 +24,12 @@ async function cmd({
23
24
  await appToken.destroy();
24
25
  console.log(result);
25
26
  } else {
26
- const db = createLocalDatabaseClient({ dbUrl: new URL(DB_PATH, astroConfig.root).href });
27
+ const { ASTRO_DATABASE_FILE } = getAstroEnv();
28
+ const dbUrl = normalizeDatabaseUrl(
29
+ ASTRO_DATABASE_FILE,
30
+ new URL(DB_PATH, astroConfig.root).href
31
+ );
32
+ const db = createLocalDatabaseClient({ dbUrl });
27
33
  const result = await db.run(sql.raw(query));
28
34
  console.log(result);
29
35
  }
@@ -4,6 +4,10 @@ import * as color from "kleur/colors";
4
4
  import { customAlphabet } from "nanoid";
5
5
  import stripAnsi from "strip-ansi";
6
6
  import { hasPrimaryKey } from "../../runtime/index.js";
7
+ import { isSerializedSQL } from "../../runtime/types.js";
8
+ import { safeFetch } from "../../runtime/utils.js";
9
+ import { MIGRATION_VERSION } from "../consts.js";
10
+ import { RENAME_COLUMN_ERROR, RENAME_TABLE_ERROR } from "../errors.js";
7
11
  import {
8
12
  getCreateIndexQueries,
9
13
  getCreateTableQuery,
@@ -12,11 +16,7 @@ import {
12
16
  getReferencesConfig,
13
17
  hasDefault,
14
18
  schemaTypeToSqlType
15
- } from "../../runtime/queries.js";
16
- import { isSerializedSQL } from "../../runtime/types.js";
17
- import { safeFetch } from "../../runtime/utils.js";
18
- import { MIGRATION_VERSION } from "../consts.js";
19
- import { RENAME_COLUMN_ERROR, RENAME_TABLE_ERROR } from "../errors.js";
19
+ } from "../queries.js";
20
20
  import { columnSchema } from "../schemas.js";
21
21
  import {
22
22
  } from "../types.js";
@@ -55,8 +55,7 @@ async function getMigrationQueries({
55
55
  }
56
56
  for (const [tableName, newTable] of Object.entries(newSnapshot.schema)) {
57
57
  const oldTable = oldSnapshot.schema[tableName];
58
- if (!oldTable)
59
- continue;
58
+ if (!oldTable) continue;
60
59
  const addedColumns = getAdded(oldTable.columns, newTable.columns);
61
60
  const droppedColumns = getDropped(oldTable.columns, newTable.columns);
62
61
  const notDeprecatedDroppedColumns = Object.fromEntries(
@@ -161,16 +160,14 @@ function getChangeIndexQueries({
161
160
  function getAddedTables(oldTables, newTables) {
162
161
  const added = {};
163
162
  for (const [key, newTable] of Object.entries(newTables.schema)) {
164
- if (!(key in oldTables.schema))
165
- added[key] = newTable;
163
+ if (!(key in oldTables.schema)) added[key] = newTable;
166
164
  }
167
165
  return added;
168
166
  }
169
167
  function getDroppedTables(oldTables, newTables) {
170
168
  const dropped = {};
171
169
  for (const [key, oldTable] of Object.entries(oldTables.schema)) {
172
- if (!(key in newTables.schema))
173
- dropped[key] = oldTable;
170
+ if (!(key in newTables.schema)) dropped[key] = oldTable;
174
171
  }
175
172
  return dropped;
176
173
  }
@@ -222,23 +219,16 @@ function isEmpty(obj) {
222
219
  return Object.keys(obj).length === 0;
223
220
  }
224
221
  function canAlterTableAddColumn(column) {
225
- if (column.schema.unique)
226
- return false;
227
- if (hasRuntimeDefault(column))
228
- return false;
229
- if (!column.schema.optional && !hasDefault(column))
230
- return false;
231
- if (hasPrimaryKey(column))
232
- return false;
233
- if (getReferencesConfig(column))
234
- return false;
222
+ if (column.schema.unique) return false;
223
+ if (hasRuntimeDefault(column)) return false;
224
+ if (!column.schema.optional && !hasDefault(column)) return false;
225
+ if (hasPrimaryKey(column)) return false;
226
+ if (getReferencesConfig(column)) return false;
235
227
  return true;
236
228
  }
237
229
  function canAlterTableDropColumn(column) {
238
- if (column.schema.unique)
239
- return false;
240
- if (hasPrimaryKey(column))
241
- return false;
230
+ if (column.schema.unique) return false;
231
+ if (hasPrimaryKey(column)) return false;
242
232
  return true;
243
233
  }
244
234
  function canRecreateTableWithoutDataLoss(added, updated) {
@@ -260,16 +250,14 @@ function canRecreateTableWithoutDataLoss(added, updated) {
260
250
  function getAdded(oldObj, newObj) {
261
251
  const added = {};
262
252
  for (const [key, value] of Object.entries(newObj)) {
263
- if (!(key in oldObj))
264
- added[key] = value;
253
+ if (!(key in oldObj)) added[key] = value;
265
254
  }
266
255
  return added;
267
256
  }
268
257
  function getDropped(oldObj, newObj) {
269
258
  const dropped = {};
270
259
  for (const [key, value] of Object.entries(oldObj)) {
271
- if (!(key in newObj))
272
- dropped[key] = value;
260
+ if (!(key in newObj)) dropped[key] = value;
273
261
  }
274
262
  return dropped;
275
263
  }
@@ -277,10 +265,8 @@ function getUpdated(oldObj, newObj) {
277
265
  const updated = {};
278
266
  for (const [key, value] of Object.entries(newObj)) {
279
267
  const oldValue = oldObj[key];
280
- if (!oldValue)
281
- continue;
282
- if (deepDiff(oldValue, value))
283
- updated[key] = value;
268
+ if (!oldValue) continue;
269
+ if (deepDiff(oldValue, value)) updated[key] = value;
284
270
  }
285
271
  return updated;
286
272
  }
@@ -288,8 +274,7 @@ function getUpdatedColumns(oldColumns, newColumns) {
288
274
  const updated = {};
289
275
  for (const [key, newColumn] of Object.entries(newColumns)) {
290
276
  let oldColumn = oldColumns[key];
291
- if (!oldColumn)
292
- continue;
277
+ if (!oldColumn) continue;
293
278
  if (oldColumn.type !== newColumn.type && canChangeTypeWithoutQuery(oldColumn, newColumn)) {
294
279
  const asNewColumn = columnSchema.safeParse({
295
280
  type: newColumn.type,
@@ -27,7 +27,7 @@ function printHelp({
27
27
  message.push(
28
28
  linebreak(),
29
29
  ` ${bgGreen(black(` ${commandName} `))} ${green(
30
- `v${"0.10.7"}`
30
+ `v${"0.11.1"}`
31
31
  )} ${headline}`
32
32
  );
33
33
  }
@@ -52,8 +52,7 @@ const errorMap = (baseError, ctx) => {
52
52
  }
53
53
  };
54
54
  const getTypeOrLiteralMsg = (error) => {
55
- if (error.received === "undefined")
56
- return "Required";
55
+ if (error.received === "undefined") return "Required";
57
56
  const expectedDeduped = new Set(error.expected);
58
57
  switch (error.code) {
59
58
  case "invalid_type":
@@ -68,8 +67,7 @@ const getTypeOrLiteralMsg = (error) => {
68
67
  };
69
68
  const prefix = (key, msg) => key.length ? `**${key}**: ${msg}` : msg;
70
69
  const unionExpectedVals = (expectedVals) => [...expectedVals].map((expectedVal, idx) => {
71
- if (idx === 0)
72
- return JSON.stringify(expectedVal);
70
+ if (idx === 0) return JSON.stringify(expectedVal);
73
71
  const sep = " | ";
74
72
  return `${sep}${JSON.stringify(expectedVal)}`;
75
73
  }).join("");
@@ -1,25 +1,35 @@
1
1
  import { existsSync } from "fs";
2
2
  import { dirname } from "path";
3
3
  import { fileURLToPath } from "url";
4
+ import { LibsqlError } from "@libsql/client";
4
5
  import { mkdir, writeFile } from "fs/promises";
5
6
  import { blue, yellow } from "kleur/colors";
6
- import { loadEnv } from "vite";
7
+ import {
8
+ createServer,
9
+ loadEnv,
10
+ mergeConfig
11
+ } from "vite";
7
12
  import parseArgs from "yargs-parser";
8
- import { SEED_DEV_FILE_NAME } from "../../runtime/queries.js";
9
13
  import { AstroDbError } from "../../runtime/utils.js";
10
14
  import { CONFIG_FILE_NAMES, DB_PATH } from "../consts.js";
15
+ import { EXEC_DEFAULT_EXPORT_ERROR, EXEC_ERROR } from "../errors.js";
11
16
  import { resolveDbConfig } from "../load-file.js";
17
+ import { SEED_DEV_FILE_NAME } from "../queries.js";
12
18
  import { getManagedAppTokenOrExit } from "../tokens.js";
13
19
  import { getDbDirectoryUrl } from "../utils.js";
14
20
  import { fileURLIntegration } from "./file-url.js";
15
21
  import { typegenInternal } from "./typegen.js";
16
- import { resolved, vitePluginDb } from "./vite-plugin-db.js";
22
+ import {
23
+ resolved,
24
+ vitePluginDb
25
+ } from "./vite-plugin-db.js";
17
26
  import { vitePluginInjectEnvTs } from "./vite-plugin-inject-env-ts.js";
18
27
  function astroDBIntegration() {
19
28
  let connectToStudio = false;
20
29
  let configFileDependencies = [];
21
30
  let root;
22
31
  let appToken;
32
+ let tempViteServer;
23
33
  let tables = {
24
34
  get() {
25
35
  throw new Error("[astro:db] INTERNAL Tables not loaded yet");
@@ -30,6 +40,12 @@ function astroDBIntegration() {
30
40
  throw new Error("[astro:db] INTERNAL Seed files not loaded yet");
31
41
  }
32
42
  };
43
+ let seedHandler = {
44
+ execute: () => {
45
+ throw new Error("[astro:db] INTERNAL Seed handler not loaded yet");
46
+ },
47
+ inProgress: false
48
+ };
33
49
  let command;
34
50
  let output = "server";
35
51
  return {
@@ -39,8 +55,7 @@ function astroDBIntegration() {
39
55
  command = _command;
40
56
  root = config.root;
41
57
  output = config.output;
42
- if (command === "preview")
43
- return;
58
+ if (command === "preview") return;
44
59
  let dbPlugin = void 0;
45
60
  const args = parseArgs(process.argv.slice(3));
46
61
  connectToStudio = process.env.ASTRO_INTERNAL_TEST_REMOTE || args["remote"];
@@ -52,7 +67,8 @@ function astroDBIntegration() {
52
67
  tables,
53
68
  root: config.root,
54
69
  srcDir: config.srcDir,
55
- output: config.output
70
+ output: config.output,
71
+ seedHandler
56
72
  });
57
73
  } else {
58
74
  dbPlugin = vitePluginDb({
@@ -61,7 +77,9 @@ function astroDBIntegration() {
61
77
  seedFiles,
62
78
  root: config.root,
63
79
  srcDir: config.srcDir,
64
- output: config.output
80
+ output: config.output,
81
+ logger,
82
+ seedHandler
65
83
  });
66
84
  }
67
85
  updateConfig({
@@ -72,8 +90,7 @@ function astroDBIntegration() {
72
90
  });
73
91
  },
74
92
  "astro:config:done": async ({ config }) => {
75
- if (command === "preview")
76
- return;
93
+ if (command === "preview") return;
77
94
  const { dbConfig, dependencies, integrationSeedPaths } = await resolveDbConfig(config);
78
95
  tables.get = () => dbConfig.tables;
79
96
  seedFiles.get = () => integrationSeedPaths;
@@ -86,6 +103,9 @@ function astroDBIntegration() {
86
103
  await typegenInternal({ tables: tables.get() ?? {}, root: config.root });
87
104
  },
88
105
  "astro:server:setup": async ({ server, logger }) => {
106
+ seedHandler.execute = async (fileUrl) => {
107
+ await executeSeedFile({ fileUrl, viteServer: server });
108
+ };
89
109
  const filesToWatch = [
90
110
  ...CONFIG_FILE_NAMES.map((c) => new URL(c, getDbDirectoryUrl(root))),
91
111
  ...configFileDependencies.map((c) => new URL(c, root))
@@ -100,38 +120,13 @@ function astroDBIntegration() {
100
120
  logger.info(
101
121
  connectToStudio ? "Connected to remote database." : "New local database created."
102
122
  );
103
- if (connectToStudio)
104
- return;
123
+ if (connectToStudio) return;
105
124
  const localSeedPaths = SEED_DEV_FILE_NAME.map(
106
125
  (name) => new URL(name, getDbDirectoryUrl(root))
107
126
  );
108
- let seedInFlight = false;
109
127
  if (seedFiles.get().length || localSeedPaths.find((path) => existsSync(path))) {
110
- loadSeedModule();
111
- }
112
- const eagerReloadIntegrationSeedPaths = seedFiles.get().map((s) => typeof s === "string" && s.startsWith(".") ? new URL(s, root) : s).filter((s) => s instanceof URL);
113
- const eagerReloadSeedPaths = [...eagerReloadIntegrationSeedPaths, ...localSeedPaths];
114
- server.watcher.on("all", (event, relativeEntry) => {
115
- if (event === "unlink" || event === "unlinkDir")
116
- return;
117
- const entry = new URL(relativeEntry, root);
118
- if (eagerReloadSeedPaths.find((path) => entry.href === path.href)) {
119
- loadSeedModule();
120
- }
121
- });
122
- function loadSeedModule() {
123
- if (seedInFlight)
124
- return;
125
- seedInFlight = true;
126
- const mod = server.moduleGraph.getModuleById(resolved.seedVirtual);
127
- if (mod)
128
- server.moduleGraph.invalidateModule(mod);
129
- server.ssrLoadModule(resolved.seedVirtual).then(() => {
130
- logger.info("Seeded database.");
131
- }).catch((e) => {
128
+ server.ssrLoadModule(resolved.module).catch((e) => {
132
129
  logger.error(e instanceof Error ? e.message : String(e));
133
- }).finally(() => {
134
- seedInFlight = false;
135
130
  });
136
131
  }
137
132
  }, 100);
@@ -144,8 +139,15 @@ function astroDBIntegration() {
144
139
  }
145
140
  logger.info("database: " + (connectToStudio ? yellow("remote") : blue("local database.")));
146
141
  },
142
+ "astro:build:setup": async ({ vite }) => {
143
+ tempViteServer = await getTempViteServer({ viteConfig: vite });
144
+ seedHandler.execute = async (fileUrl) => {
145
+ await executeSeedFile({ fileUrl, viteServer: tempViteServer });
146
+ };
147
+ },
147
148
  "astro:build:done": async ({}) => {
148
149
  await appToken?.destroy();
150
+ await tempViteServer?.close();
149
151
  }
150
152
  }
151
153
  };
@@ -157,6 +159,41 @@ function databaseFileEnvDefined() {
157
159
  function integration() {
158
160
  return [astroDBIntegration(), fileURLIntegration()];
159
161
  }
162
+ async function executeSeedFile({
163
+ fileUrl,
164
+ viteServer
165
+ }) {
166
+ const mod = await viteServer.ssrLoadModule(fileUrl.pathname);
167
+ if (typeof mod.default !== "function") {
168
+ throw new AstroDbError(EXEC_DEFAULT_EXPORT_ERROR(fileURLToPath(fileUrl)));
169
+ }
170
+ try {
171
+ await mod.default();
172
+ } catch (e) {
173
+ if (e instanceof LibsqlError) {
174
+ throw new AstroDbError(EXEC_ERROR(e.message));
175
+ }
176
+ throw e;
177
+ }
178
+ }
179
+ async function getTempViteServer({ viteConfig }) {
180
+ const tempViteServer = await createServer(
181
+ mergeConfig(viteConfig, {
182
+ server: { middlewareMode: true, hmr: false, watch: null },
183
+ optimizeDeps: { noDiscovery: true },
184
+ ssr: { external: [] },
185
+ logLevel: "silent"
186
+ })
187
+ );
188
+ const hotSend = tempViteServer.hot.send;
189
+ tempViteServer.hot.send = (payload) => {
190
+ if (payload.type === "error") {
191
+ throw payload.err;
192
+ }
193
+ return hotSend(payload);
194
+ };
195
+ return tempViteServer;
196
+ }
160
197
  export {
161
198
  integration
162
199
  };
@@ -1,9 +1,9 @@
1
- import type { AstroConfig } from 'astro';
1
+ import type { AstroConfig, AstroIntegrationLogger } from 'astro';
2
2
  import type { DBTables } from '../types.js';
3
3
  import { type VitePlugin } from '../utils.js';
4
4
  export declare const resolved: {
5
- virtual: string;
6
- seedVirtual: string;
5
+ module: string;
6
+ importedFromSeedFile: string;
7
7
  };
8
8
  export type LateTables = {
9
9
  get: () => DBTables;
@@ -11,13 +11,19 @@ export type LateTables = {
11
11
  export type LateSeedFiles = {
12
12
  get: () => Array<string | URL>;
13
13
  };
14
+ export type SeedHandler = {
15
+ inProgress: boolean;
16
+ execute: (fileUrl: URL) => Promise<void>;
17
+ };
14
18
  type VitePluginDBParams = {
15
19
  connectToStudio: false;
16
20
  tables: LateTables;
17
21
  seedFiles: LateSeedFiles;
18
22
  srcDir: URL;
19
23
  root: URL;
24
+ logger?: AstroIntegrationLogger;
20
25
  output: AstroConfig['output'];
26
+ seedHandler: SeedHandler;
21
27
  } | {
22
28
  connectToStudio: true;
23
29
  tables: LateTables;
@@ -25,14 +31,13 @@ type VitePluginDBParams = {
25
31
  srcDir: URL;
26
32
  root: URL;
27
33
  output: AstroConfig['output'];
34
+ seedHandler: SeedHandler;
28
35
  };
29
36
  export declare function vitePluginDb(params: VitePluginDBParams): VitePlugin;
30
37
  export declare function getConfigVirtualModContents(): string;
31
- export declare function getLocalVirtualModContents({ tables, root, seedFiles, shouldSeed, }: {
38
+ export declare function getLocalVirtualModContents({ tables, root, }: {
32
39
  tables: DBTables;
33
- seedFiles: Array<string | URL>;
34
40
  root: URL;
35
- shouldSeed: boolean;
36
41
  }): string;
37
42
  export declare function getStudioVirtualModContents({ tables, appToken, isBuild, output, }: {
38
43
  tables: DBTables;
@@ -1,16 +1,18 @@
1
+ import { existsSync } from "node:fs";
1
2
  import { fileURLToPath } from "node:url";
2
- import { normalizePath } from "vite";
3
- import { SEED_DEV_FILE_NAME } from "../../runtime/queries.js";
3
+ import { sql } from "drizzle-orm";
4
+ import { SQLiteAsyncDialect } from "drizzle-orm/sqlite-core";
5
+ import { createLocalDatabaseClient } from "../../runtime/db-client.js";
6
+ import { normalizeDatabaseUrl } from "../../runtime/index.js";
4
7
  import { DB_PATH, RUNTIME_IMPORT, RUNTIME_VIRTUAL_IMPORT, VIRTUAL_MODULE_ID } from "../consts.js";
5
- import { getDbDirectoryUrl, getRemoteDatabaseUrl } from "../utils.js";
6
- const WITH_SEED_VIRTUAL_MODULE_ID = "astro:db:seed";
8
+ import { getResolvedFileUrl } from "../load-file.js";
9
+ import { SEED_DEV_FILE_NAME, getCreateIndexQueries, getCreateTableQuery } from "../queries.js";
10
+ import { getAstroEnv, getDbDirectoryUrl, getRemoteDatabaseUrl } from "../utils.js";
7
11
  const resolved = {
8
- virtual: "\0" + VIRTUAL_MODULE_ID,
9
- seedVirtual: "\0" + WITH_SEED_VIRTUAL_MODULE_ID
12
+ module: "\0" + VIRTUAL_MODULE_ID,
13
+ importedFromSeedFile: "\0" + VIRTUAL_MODULE_ID + ":seed"
10
14
  };
11
15
  function vitePluginDb(params) {
12
- const srcDirPath = normalizePath(fileURLToPath(params.srcDir));
13
- const dbDirPath = normalizePath(fileURLToPath(getDbDirectoryUrl(params.root)));
14
16
  let command = "build";
15
17
  return {
16
18
  name: "astro:db",
@@ -18,22 +20,15 @@ function vitePluginDb(params) {
18
20
  configResolved(resolvedConfig) {
19
21
  command = resolvedConfig.command;
20
22
  },
21
- async resolveId(id, rawImporter) {
22
- if (id !== VIRTUAL_MODULE_ID)
23
- return;
24
- if (params.connectToStudio)
25
- return resolved.virtual;
26
- const importer = rawImporter ? await this.resolve(rawImporter) : null;
27
- if (!importer)
28
- return resolved.virtual;
29
- if (importer.id.startsWith(srcDirPath) && !importer.id.startsWith(dbDirPath)) {
30
- return resolved.seedVirtual;
23
+ async resolveId(id) {
24
+ if (id !== VIRTUAL_MODULE_ID) return;
25
+ if (params.seedHandler.inProgress) {
26
+ return resolved.importedFromSeedFile;
31
27
  }
32
- return resolved.virtual;
28
+ return resolved.module;
33
29
  },
34
30
  async load(id) {
35
- if (id !== resolved.virtual && id !== resolved.seedVirtual)
36
- return;
31
+ if (id !== resolved.module && id !== resolved.importedFromSeedFile) return;
37
32
  if (params.connectToStudio) {
38
33
  return getStudioVirtualModContents({
39
34
  appToken: params.appToken,
@@ -42,11 +37,28 @@ function vitePluginDb(params) {
42
37
  output: params.output
43
38
  });
44
39
  }
40
+ if (id === resolved.importedFromSeedFile) {
41
+ return getLocalVirtualModContents({
42
+ root: params.root,
43
+ tables: params.tables.get()
44
+ });
45
+ }
46
+ await recreateTables(params);
47
+ const seedFiles = getResolvedSeedFiles(params);
48
+ for await (const seedFile of seedFiles) {
49
+ this.addWatchFile(fileURLToPath(seedFile));
50
+ if (existsSync(seedFile)) {
51
+ params.seedHandler.inProgress = true;
52
+ await params.seedHandler.execute(seedFile);
53
+ }
54
+ }
55
+ if (params.seedHandler.inProgress) {
56
+ (params.logger ?? console).info("Seeded database.");
57
+ params.seedHandler.inProgress = false;
58
+ }
45
59
  return getLocalVirtualModContents({
46
60
  root: params.root,
47
- tables: params.tables.get(),
48
- seedFiles: params.seedFiles.get(),
49
- shouldSeed: id === resolved.seedVirtual
61
+ tables: params.tables.get()
50
62
  });
51
63
  }
52
64
  };
@@ -56,40 +68,15 @@ function getConfigVirtualModContents() {
56
68
  }
57
69
  function getLocalVirtualModContents({
58
70
  tables,
59
- root,
60
- seedFiles,
61
- shouldSeed
71
+ root
62
72
  }) {
63
- const userSeedFilePaths = SEED_DEV_FILE_NAME.map(
64
- // Format as /db/[name].ts
65
- // for Vite import.meta.glob
66
- (name) => new URL(name, getDbDirectoryUrl("file:///")).pathname
67
- );
68
- const resolveId = (id) => id.startsWith(".") ? normalizePath(fileURLToPath(new URL(id, root))) : id;
69
- const integrationSeedImportStatements = [];
70
- const integrationSeedImportNames = [];
71
- seedFiles.forEach((pathOrUrl, index) => {
72
- const path = typeof pathOrUrl === "string" ? resolveId(pathOrUrl) : pathOrUrl.pathname;
73
- const importName = "integration_seed_" + index;
74
- integrationSeedImportStatements.push(`import ${importName} from ${JSON.stringify(path)};`);
75
- integrationSeedImportNames.push(importName);
76
- });
77
73
  const dbUrl = new URL(DB_PATH, root);
78
74
  return `
79
75
  import { asDrizzleTable, createLocalDatabaseClient, normalizeDatabaseUrl } from ${RUNTIME_IMPORT};
80
- ${shouldSeed ? `import { seedLocal } from ${RUNTIME_IMPORT};` : ""}
81
- ${shouldSeed ? integrationSeedImportStatements.join("\n") : ""}
82
76
 
83
77
  const dbUrl = normalizeDatabaseUrl(import.meta.env.ASTRO_DATABASE_FILE, ${JSON.stringify(dbUrl)});
84
78
  export const db = createLocalDatabaseClient({ dbUrl });
85
79
 
86
- ${shouldSeed ? `await seedLocal({
87
- db,
88
- tables: ${JSON.stringify(tables)},
89
- userSeedGlob: import.meta.glob(${JSON.stringify(userSeedFilePaths)}, { eager: true }),
90
- integrationSeedFunctions: [${integrationSeedImportNames.join(",")}],
91
- });` : ""}
92
-
93
80
  export * from ${RUNTIME_VIRTUAL_IMPORT};
94
81
 
95
82
  ${getStringifiedTableExports(tables)}`;
@@ -132,6 +119,31 @@ function getStringifiedTableExports(tables) {
132
119
  )}, false)`
133
120
  ).join("\n");
134
121
  }
122
+ const sqlite = new SQLiteAsyncDialect();
123
+ async function recreateTables({ tables, root }) {
124
+ const { ASTRO_DATABASE_FILE } = getAstroEnv();
125
+ const dbUrl = normalizeDatabaseUrl(ASTRO_DATABASE_FILE, new URL(DB_PATH, root).href);
126
+ const db = createLocalDatabaseClient({ dbUrl });
127
+ const setupQueries = [];
128
+ for (const [name, table] of Object.entries(tables.get() ?? {})) {
129
+ const dropQuery = sql.raw(`DROP TABLE IF EXISTS ${sqlite.escapeName(name)}`);
130
+ const createQuery = sql.raw(getCreateTableQuery(name, table));
131
+ const indexQueries = getCreateIndexQueries(name, table);
132
+ setupQueries.push(dropQuery, createQuery, ...indexQueries.map((s) => sql.raw(s)));
133
+ }
134
+ await db.batch([
135
+ db.run(sql`pragma defer_foreign_keys=true;`),
136
+ ...setupQueries.map((q) => db.run(q))
137
+ ]);
138
+ }
139
+ function getResolvedSeedFiles({
140
+ root,
141
+ seedFiles
142
+ }) {
143
+ const localSeedFiles = SEED_DEV_FILE_NAME.map((name) => new URL(name, getDbDirectoryUrl(root)));
144
+ const integrationSeedFiles = seedFiles.get().map((s) => getResolvedFileUrl(root, s));
145
+ return [...integrationSeedFiles, ...localSeedFiles];
146
+ }
135
147
  export {
136
148
  getConfigVirtualModContents,
137
149
  getLocalVirtualModContents,
@@ -26,8 +26,7 @@ async function setUpEnvTs({
26
26
  if (existsSync(envTsPath)) {
27
27
  let typesEnvContents = await readFile(envTsPath, "utf-8");
28
28
  const dotAstroDir = new URL(".astro/", root);
29
- if (!existsSync(dotAstroDir))
30
- return;
29
+ if (!existsSync(dotAstroDir)) return;
31
30
  const dbTypeReference = getDBTypeReference({ srcDir, dotAstroDir });
32
31
  if (!typesEnvContents.includes(dbTypeReference)) {
33
32
  typesEnvContents = `${dbTypeReference}
@@ -172,6 +172,7 @@ export declare function resolveDbConfig({ root, integrations, }: Pick<AstroConfi
172
172
  /** Additional `astro:db` seed file paths provided by integrations. */
173
173
  integrationSeedPaths: (string | URL)[];
174
174
  }>;
175
+ export declare function getResolvedFileUrl(root: URL, filePathOrUrl: string | URL): URL;
175
176
  /**
176
177
  * Bundle arbitrary `mjs` or `ts` file.
177
178
  * Simplified fork from Vite's `bundleConfigFile` function.
@@ -9,7 +9,7 @@ import { errorMap } from "./integration/error-map.js";
9
9
  import { getConfigVirtualModContents } from "./integration/vite-plugin-db.js";
10
10
  import { dbConfigSchema } from "./schemas.js";
11
11
  import {} from "./types.js";
12
- import { getDbDirectoryUrl } from "./utils.js";
12
+ import { getAstroEnv, getDbDirectoryUrl } from "./utils.js";
13
13
  const isDbIntegration = (integration) => "astro:db:setup" in integration.hooks;
14
14
  async function resolveDbConfig({
15
15
  root,
@@ -21,8 +21,7 @@ async function resolveDbConfig({
21
21
  const integrationDbConfigPaths = [];
22
22
  const integrationSeedPaths = [];
23
23
  for (const integration of integrations) {
24
- if (!isDbIntegration(integration))
25
- continue;
24
+ if (!isDbIntegration(integration)) continue;
26
25
  const { name, hooks } = integration;
27
26
  if (hooks["astro:db:setup"]) {
28
27
  hooks["astro:db:setup"]({
@@ -70,15 +69,16 @@ async function loadUserConfigFile(root) {
70
69
  }
71
70
  return await loadAndBundleDbConfigFile({ root, fileUrl: configFileUrl });
72
71
  }
73
- async function loadIntegrationConfigFile(root, filePathOrUrl) {
74
- let fileUrl;
72
+ function getResolvedFileUrl(root, filePathOrUrl) {
75
73
  if (typeof filePathOrUrl === "string") {
76
74
  const { resolve } = createRequire(root);
77
75
  const resolvedFilePath = resolve(filePathOrUrl);
78
- fileUrl = pathToFileURL(resolvedFilePath);
79
- } else {
80
- fileUrl = filePathOrUrl;
76
+ return pathToFileURL(resolvedFilePath);
81
77
  }
78
+ return filePathOrUrl;
79
+ }
80
+ async function loadIntegrationConfigFile(root, filePathOrUrl) {
81
+ const fileUrl = getResolvedFileUrl(root, filePathOrUrl);
82
82
  return await loadAndBundleDbConfigFile({ root, fileUrl });
83
83
  }
84
84
  async function loadAndBundleDbConfigFile({
@@ -103,6 +103,7 @@ async function bundleFile({
103
103
  root,
104
104
  virtualModContents
105
105
  }) {
106
+ const { ASTRO_DATABASE_FILE } = getAstroEnv();
106
107
  const result = await esbuild({
107
108
  absWorkingDir: process.cwd(),
108
109
  entryPoints: [fileURLToPath(fileUrl)],
@@ -116,7 +117,8 @@ async function bundleFile({
116
117
  sourcemap: "inline",
117
118
  metafile: true,
118
119
  define: {
119
- "import.meta.env.ASTRO_STUDIO_REMOTE_DB_URL": "undefined"
120
+ "import.meta.env.ASTRO_STUDIO_REMOTE_DB_URL": "undefined",
121
+ "import.meta.env.ASTRO_DATABASE_FILE": JSON.stringify(ASTRO_DATABASE_FILE ?? "")
120
122
  },
121
123
  plugins: [
122
124
  {
@@ -165,6 +167,7 @@ async function importBundledFile({
165
167
  }
166
168
  export {
167
169
  bundleFile,
170
+ getResolvedFileUrl,
168
171
  importBundledFile,
169
172
  resolveDbConfig
170
173
  };
@@ -1,4 +1,4 @@
1
- import type { BooleanColumn, ColumnType, DBColumn, DBTable, DateColumn, JsonColumn, NumberColumn, TextColumn } from '../core/types.js';
1
+ import type { BooleanColumn, ColumnType, DBColumn, DBTable, DateColumn, JsonColumn, NumberColumn, TextColumn } from './types.js';
2
2
  export declare const SEED_DEV_FILE_NAME: string[];
3
3
  export declare function getDropTableIfExistsQuery(tableName: string): string;
4
4
  export declare function getCreateTableQuery(tableName: string, table: DBTable): string;
@@ -17,7 +17,7 @@ export declare function getReferencesConfig(column: DBColumn): {
17
17
  } & ({
18
18
  optional: boolean;
19
19
  primaryKey: false;
20
- default?: number | import("./types.js").SerializedSQL | undefined;
20
+ default?: number | import("../runtime/types.js").SerializedSQL | undefined;
21
21
  } | {
22
22
  primaryKey: true;
23
23
  optional?: false | undefined;
@@ -33,7 +33,7 @@ export declare function getReferencesConfig(column: DBColumn): {
33
33
  name?: string | undefined;
34
34
  label?: string | undefined;
35
35
  collection?: string | undefined;
36
- default?: string | import("./types.js").SerializedSQL | undefined;
36
+ default?: string | import("../runtime/types.js").SerializedSQL | undefined;
37
37
  multiline?: boolean | undefined;
38
38
  } & ({
39
39
  optional: boolean;
@@ -6,9 +6,9 @@ import {
6
6
  FOREIGN_KEY_REFERENCES_EMPTY_ERROR,
7
7
  FOREIGN_KEY_REFERENCES_LENGTH_ERROR,
8
8
  REFERENCE_DNE_ERROR
9
- } from "./errors.js";
10
- import { hasPrimaryKey } from "./index.js";
11
- import { isSerializedSQL } from "./types.js";
9
+ } from "../runtime/errors.js";
10
+ import { hasPrimaryKey } from "../runtime/index.js";
11
+ import { isSerializedSQL } from "../runtime/types.js";
12
12
  const sqlite = new SQLiteAsyncDialect();
13
13
  const SEED_DEV_FILE_NAME = ["seed.ts", "seed.js", "seed.mjs", "seed.mts"];
14
14
  function getDropTableIfExistsQuery(tableName) {
@@ -107,8 +107,7 @@ function getModifiers(columnName, column) {
107
107
  }
108
108
  function getReferencesConfig(column) {
109
109
  const canHaveReferences = column.type === "number" || column.type === "text";
110
- if (!canHaveReferences)
111
- return void 0;
110
+ if (!canHaveReferences) return void 0;
112
111
  return column.schema.references;
113
112
  }
114
113
  function hasDefault(column) {
@@ -2,6 +2,7 @@ import type { AstroConfig, AstroIntegration } from 'astro';
2
2
  import type { AstroDbIntegration } from './types.js';
3
3
  export type VitePlugin = Required<AstroConfig['vite']>['plugins'][number];
4
4
  export declare function getAstroStudioEnv(envMode?: string): Record<`ASTRO_STUDIO_${string}`, string>;
5
+ export declare function getAstroEnv(envMode?: string): Record<`ASTRO_${string}`, string>;
5
6
  export declare function getRemoteDatabaseUrl(): string;
6
7
  export declare function getAstroStudioUrl(): string;
7
8
  export declare function getDbDirectoryUrl(root: URL | string): URL;
@@ -3,6 +3,10 @@ function getAstroStudioEnv(envMode = "") {
3
3
  const env = loadEnv(envMode, process.cwd(), "ASTRO_STUDIO_");
4
4
  return env;
5
5
  }
6
+ function getAstroEnv(envMode = "") {
7
+ const env = loadEnv(envMode, process.cwd(), "ASTRO_");
8
+ return env;
9
+ }
6
10
  function getRemoteDatabaseUrl() {
7
11
  const env = getAstroStudioEnv();
8
12
  return env.ASTRO_STUDIO_REMOTE_DB_URL || "https://db.services.astro.build";
@@ -24,6 +28,7 @@ function mapObject(item, callback) {
24
28
  }
25
29
  export {
26
30
  defineDbIntegration,
31
+ getAstroEnv,
27
32
  getAstroStudioEnv,
28
33
  getAstroStudioUrl,
29
34
  getDbDirectoryUrl,
@@ -56,8 +56,7 @@ function createRemoteDatabaseClient(appToken, remoteDbURL) {
56
56
  } catch (e) {
57
57
  throw new AstroDbError(await getUnexpectedResponseMessage(res));
58
58
  }
59
- if (method === "run")
60
- return remoteResult;
59
+ if (method === "run") return remoteResult;
61
60
  const rowValues = [];
62
61
  for (const row of remoteResult.rows) {
63
62
  if (row != null && typeof row === "object") {
@@ -2,7 +2,6 @@ import { type ColumnDataType } from 'drizzle-orm';
2
2
  import type { DBColumn, DBTable } from '../core/types.js';
3
3
  export type { Table } from './types.js';
4
4
  export { createRemoteDatabaseClient, createLocalDatabaseClient } from './db-client.js';
5
- export { seedLocal } from './seed-local.js';
6
5
  export declare function hasPrimaryKey(column: DBColumn): boolean;
7
6
  export declare function asDrizzleTable(name: string, table: DBTable): import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
8
7
  name: string;
@@ -9,10 +9,10 @@ import {
9
9
  import { isSerializedSQL } from "./types.js";
10
10
  import { pathToFileURL } from "./utils.js";
11
11
  import { createRemoteDatabaseClient, createLocalDatabaseClient } from "./db-client.js";
12
- import { seedLocal } from "./seed-local.js";
13
12
  function hasPrimaryKey(column) {
14
13
  return "primaryKey" in column.schema && !!column.schema.primaryKey;
15
14
  }
15
+ const isISODateString = (str) => /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/.test(str);
16
16
  const dateType = customType({
17
17
  dataType() {
18
18
  return "text";
@@ -21,6 +21,9 @@ const dateType = customType({
21
21
  return value.toISOString();
22
22
  },
23
23
  fromDriver(value) {
24
+ if (!isISODateString(value)) {
25
+ value += "Z";
26
+ }
24
27
  return new Date(value);
25
28
  }
26
29
  });
@@ -48,8 +51,7 @@ function asDrizzleTable(name, table) {
48
51
  for (const [indexName, indexProps] of Object.entries(table.indexes ?? {})) {
49
52
  const onColNames = Array.isArray(indexProps.on) ? indexProps.on : [indexProps.on];
50
53
  const onCols = onColNames.map((colName) => ormTable[colName]);
51
- if (!atLeastOne(onCols))
52
- continue;
54
+ if (!atLeastOne(onCols)) continue;
53
55
  indexes[indexName] = index(indexName).on(...onCols);
54
56
  }
55
57
  return indexes;
@@ -66,16 +68,14 @@ function columnMapper(columnName, column) {
66
68
  c = text(columnName);
67
69
  if (column.schema.default !== void 0)
68
70
  c = c.default(handleSerializedSQL(column.schema.default));
69
- if (column.schema.primaryKey === true)
70
- c = c.primaryKey();
71
+ if (column.schema.primaryKey === true) c = c.primaryKey();
71
72
  break;
72
73
  }
73
74
  case "number": {
74
75
  c = integer(columnName);
75
76
  if (column.schema.default !== void 0)
76
77
  c = c.default(handleSerializedSQL(column.schema.default));
77
- if (column.schema.primaryKey === true)
78
- c = c.primaryKey();
78
+ if (column.schema.primaryKey === true) c = c.primaryKey();
79
79
  break;
80
80
  }
81
81
  case "boolean": {
@@ -86,8 +86,7 @@ function columnMapper(columnName, column) {
86
86
  }
87
87
  case "json":
88
88
  c = jsonType(columnName);
89
- if (column.schema.default !== void 0)
90
- c = c.default(column.schema.default);
89
+ if (column.schema.default !== void 0) c = c.default(column.schema.default);
91
90
  break;
92
91
  case "date": {
93
92
  c = dateType(columnName);
@@ -98,10 +97,8 @@ function columnMapper(columnName, column) {
98
97
  break;
99
98
  }
100
99
  }
101
- if (!column.schema.optional)
102
- c = c.notNull();
103
- if (column.schema.unique)
104
- c = c.unique();
100
+ if (!column.schema.optional) c = c.notNull();
101
+ if (column.schema.unique) c = c.unique();
105
102
  return c;
106
103
  }
107
104
  function handleSerializedSQL(def) {
@@ -125,6 +122,5 @@ export {
125
122
  createLocalDatabaseClient,
126
123
  createRemoteDatabaseClient,
127
124
  hasPrimaryKey,
128
- normalizeDatabaseUrl,
129
- seedLocal
125
+ normalizeDatabaseUrl
130
126
  };
@@ -6,5 +6,4 @@ export declare function safeFetch(url: Parameters<typeof fetch>[0], options?: Pa
6
6
  export declare class AstroDbError extends AstroError {
7
7
  name: string;
8
8
  }
9
- export default function slash(path: string): string;
10
9
  export declare function pathToFileURL(path: string): URL;
@@ -31,7 +31,6 @@ function pathToFileURL(path) {
31
31
  }
32
32
  export {
33
33
  AstroDbError,
34
- slash as default,
35
34
  pathToFileURL,
36
35
  safeFetch
37
36
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrojs/db",
3
- "version": "0.10.7",
3
+ "version": "0.11.1",
4
4
  "description": "Add libSQL and Astro Studio support to your Astro site",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -66,7 +66,7 @@
66
66
  "async-listen": "^3.0.1",
67
67
  "ci-info": "^4.0.0",
68
68
  "deep-diff": "^1.0.2",
69
- "drizzle-orm": "^0.30.9",
69
+ "drizzle-orm": "^0.30.10",
70
70
  "github-slugger": "^2.0.0",
71
71
  "kleur": "^4.1.5",
72
72
  "nanoid": "^5.0.7",
@@ -75,12 +75,12 @@
75
75
  "prompts": "^2.4.2",
76
76
  "strip-ansi": "^7.1.0",
77
77
  "yargs-parser": "^21.1.1",
78
- "zod": "^3.23.5"
78
+ "zod": "^3.23.8"
79
79
  },
80
80
  "devDependencies": {
81
- "@types/chai": "^4.3.14",
81
+ "@types/chai": "^4.3.16",
82
82
  "@types/deep-diff": "^1.0.5",
83
- "@types/diff": "^5.2.0",
83
+ "@types/diff": "^5.2.1",
84
84
  "@types/mocha": "^10.0.6",
85
85
  "@types/prompts": "^2.4.9",
86
86
  "@types/yargs-parser": "^21.0.3",
@@ -88,8 +88,8 @@
88
88
  "cheerio": "1.0.0-rc.12",
89
89
  "mocha": "^10.4.0",
90
90
  "typescript": "^5.4.5",
91
- "vite": "^5.2.10",
92
- "astro": "4.7.1",
91
+ "vite": "^5.2.11",
92
+ "astro": "4.8.0",
93
93
  "astro-scripts": "0.0.14"
94
94
  },
95
95
  "scripts": {
@@ -1,10 +0,0 @@
1
- import type { LibSQLDatabase } from 'drizzle-orm/libsql';
2
- import { type DBTables } from '../core/types.js';
3
- export declare function seedLocal({ db, tables, userSeedGlob, integrationSeedFunctions, }: {
4
- db: LibSQLDatabase;
5
- tables: DBTables;
6
- userSeedGlob: Record<string, {
7
- default?: () => Promise<void>;
8
- }>;
9
- integrationSeedFunctions: Array<() => Promise<void>>;
10
- }): Promise<void>;
@@ -1,55 +0,0 @@
1
- import { LibsqlError } from "@libsql/client";
2
- import { sql } from "drizzle-orm";
3
- import { SQLiteAsyncDialect } from "drizzle-orm/sqlite-core";
4
- import {} from "../core/types.js";
5
- import { SEED_DEFAULT_EXPORT_ERROR } from "./errors.js";
6
- import { getCreateIndexQueries, getCreateTableQuery } from "./queries.js";
7
- import { AstroDbError } from "./utils.js";
8
- const sqlite = new SQLiteAsyncDialect();
9
- async function seedLocal({
10
- db,
11
- tables,
12
- // Glob all potential seed files to catch renames and deletions.
13
- userSeedGlob,
14
- integrationSeedFunctions
15
- }) {
16
- await recreateTables({ db, tables });
17
- const seedFunctions = [];
18
- const seedFilePath = Object.keys(userSeedGlob)[0];
19
- if (seedFilePath) {
20
- const mod = userSeedGlob[seedFilePath];
21
- if (!mod.default)
22
- throw new AstroDbError(SEED_DEFAULT_EXPORT_ERROR(seedFilePath));
23
- seedFunctions.push(mod.default);
24
- }
25
- for (const seedFn of integrationSeedFunctions) {
26
- seedFunctions.push(seedFn);
27
- }
28
- for (const seed of seedFunctions) {
29
- try {
30
- await seed();
31
- } catch (e) {
32
- if (e instanceof LibsqlError) {
33
- throw new AstroDbError(`Failed to seed database:
34
- ${e.message}`);
35
- }
36
- throw e;
37
- }
38
- }
39
- }
40
- async function recreateTables({ db, tables }) {
41
- const setupQueries = [];
42
- for (const [name, table] of Object.entries(tables)) {
43
- const dropQuery = sql.raw(`DROP TABLE IF EXISTS ${sqlite.escapeName(name)}`);
44
- const createQuery = sql.raw(getCreateTableQuery(name, table));
45
- const indexQueries = getCreateIndexQueries(name, table);
46
- setupQueries.push(dropQuery, createQuery, ...indexQueries.map((s) => sql.raw(s)));
47
- }
48
- await db.batch([
49
- db.run(sql`pragma defer_foreign_keys=true;`),
50
- ...setupQueries.map((q) => db.run(q))
51
- ]);
52
- }
53
- export {
54
- seedLocal
55
- };