@astrojs/db 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +59 -0
- package/components/Renderer.astro +14 -0
- package/components/astro-env.d.ts +1 -0
- package/components/index.ts +2 -0
- package/components/tsconfig.json +7 -0
- package/config-augment.d.ts +4 -0
- package/dist/cli/commands/push/index.d.ts +6 -0
- package/dist/cli/commands/push/index.js +144 -0
- package/dist/cli/commands/sync/index.d.ts +6 -0
- package/dist/cli/commands/sync/index.js +45 -0
- package/dist/cli/commands/verify/index.d.ts +6 -0
- package/dist/cli/commands/verify/index.js +25 -0
- package/dist/cli/index.d.ts +6 -0
- package/dist/cli/index.js +24 -0
- package/dist/cli/queries.d.ts +19 -0
- package/dist/cli/queries.js +453 -0
- package/dist/cli/seed.d.ts +6 -0
- package/dist/cli/sync/admin.d.ts +33 -0
- package/dist/cli/sync/index.d.ts +1 -0
- package/dist/cli/sync/index.js +0 -0
- package/dist/cli/sync/migrate.d.ts +1 -0
- package/dist/cli/sync/migrate.js +0 -0
- package/dist/cli/sync/queries.d.ts +19 -0
- package/dist/cli/sync/remote-db.d.ts +1 -0
- package/dist/config.d.ts +1149 -0
- package/dist/config.js +53 -0
- package/dist/consts.d.ts +6 -0
- package/dist/consts.js +19 -0
- package/dist/error-map.d.ts +6 -0
- package/dist/error-map.js +79 -0
- package/dist/errors.d.ts +3 -0
- package/dist/errors.js +15 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +10 -0
- package/dist/integration.d.ts +2 -0
- package/dist/integration.js +68 -0
- package/dist/internal-drizzle.d.ts +1 -0
- package/dist/internal-drizzle.js +48 -0
- package/dist/internal.d.ts +50 -0
- package/dist/internal.js +250 -0
- package/dist/load-astro-config.d.ts +6 -0
- package/dist/load-astro-config.js +79 -0
- package/dist/migrations.d.ts +9 -0
- package/dist/migrations.js +41 -0
- package/dist/typegen.d.ts +5 -0
- package/dist/typegen.js +57 -0
- package/dist/types.d.ts +1367 -0
- package/dist/types.js +58 -0
- package/dist/utils.d.ts +59 -0
- package/dist/utils.js +84 -0
- package/dist/vite-plugin-db.d.ts +19 -0
- package/dist/vite-plugin-db.js +66 -0
- package/dist/vite-plugin-inject-env-ts.d.ts +9 -0
- package/dist/vite-plugin-inject-env-ts.js +49 -0
- package/index.d.ts +3 -0
- package/package.json +72 -0
package/dist/config.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import {
|
|
2
|
+
collectionsSchema
|
|
3
|
+
} from "./types.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
const dbConfigSchema = z.object({
|
|
6
|
+
studio: z.boolean().optional(),
|
|
7
|
+
collections: collectionsSchema.optional()
|
|
8
|
+
});
|
|
9
|
+
const astroConfigWithDbSchema = z.object({
|
|
10
|
+
db: dbConfigSchema.optional()
|
|
11
|
+
});
|
|
12
|
+
function defineCollection(userConfig) {
|
|
13
|
+
return {
|
|
14
|
+
...userConfig,
|
|
15
|
+
writable: false
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function defineWritableCollection(userConfig) {
|
|
19
|
+
return {
|
|
20
|
+
...userConfig,
|
|
21
|
+
writable: true
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const baseDefaults = {
|
|
25
|
+
optional: false,
|
|
26
|
+
unique: false,
|
|
27
|
+
label: void 0,
|
|
28
|
+
default: void 0
|
|
29
|
+
};
|
|
30
|
+
const field = {
|
|
31
|
+
number(opts = {}) {
|
|
32
|
+
return { type: "number", ...baseDefaults, ...opts };
|
|
33
|
+
},
|
|
34
|
+
boolean(opts = {}) {
|
|
35
|
+
return { type: "boolean", ...baseDefaults, ...opts };
|
|
36
|
+
},
|
|
37
|
+
text(opts = {}) {
|
|
38
|
+
return { type: "text", multiline: false, ...baseDefaults, ...opts };
|
|
39
|
+
},
|
|
40
|
+
date(opts = {}) {
|
|
41
|
+
return { type: "date", ...baseDefaults, ...opts };
|
|
42
|
+
},
|
|
43
|
+
json(opts = {}) {
|
|
44
|
+
return { type: "json", ...baseDefaults, ...opts };
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
export {
|
|
48
|
+
astroConfigWithDbSchema,
|
|
49
|
+
dbConfigSchema,
|
|
50
|
+
defineCollection,
|
|
51
|
+
defineWritableCollection,
|
|
52
|
+
field
|
|
53
|
+
};
|
package/dist/consts.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const PACKAGE_NAME: any;
|
|
2
|
+
export declare const INTERNAL_MOD_IMPORT: string;
|
|
3
|
+
export declare const DRIZZLE_MOD_IMPORT: string;
|
|
4
|
+
export declare const DB_TYPES_FILE = "db-types.d.ts";
|
|
5
|
+
export declare const VIRTUAL_MODULE_ID = "astro:db";
|
|
6
|
+
export declare function getLocalDbUrl(root: URL): URL;
|
package/dist/consts.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
const PACKAGE_NAME = JSON.parse(
|
|
3
|
+
readFileSync(new URL("../package.json", import.meta.url), "utf8")
|
|
4
|
+
).name;
|
|
5
|
+
const INTERNAL_MOD_IMPORT = JSON.stringify(`${PACKAGE_NAME}/internal`);
|
|
6
|
+
const DRIZZLE_MOD_IMPORT = JSON.stringify(`${PACKAGE_NAME}/internal-drizzle`);
|
|
7
|
+
const DB_TYPES_FILE = "db-types.d.ts";
|
|
8
|
+
const VIRTUAL_MODULE_ID = "astro:db";
|
|
9
|
+
function getLocalDbUrl(root) {
|
|
10
|
+
return new URL(".astro/content.db", root);
|
|
11
|
+
}
|
|
12
|
+
export {
|
|
13
|
+
DB_TYPES_FILE,
|
|
14
|
+
DRIZZLE_MOD_IMPORT,
|
|
15
|
+
INTERNAL_MOD_IMPORT,
|
|
16
|
+
PACKAGE_NAME,
|
|
17
|
+
VIRTUAL_MODULE_ID,
|
|
18
|
+
getLocalDbUrl
|
|
19
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
const errorMap = (baseError, ctx) => {
|
|
2
|
+
const baseErrorPath = flattenErrorPath(baseError.path);
|
|
3
|
+
if (baseError.code === "invalid_union") {
|
|
4
|
+
const typeOrLiteralErrByPath = /* @__PURE__ */ new Map();
|
|
5
|
+
for (const unionError of baseError.unionErrors.flatMap((e) => e.errors)) {
|
|
6
|
+
if (unionError.code === "invalid_type" || unionError.code === "invalid_literal") {
|
|
7
|
+
const flattenedErrorPath = flattenErrorPath(unionError.path);
|
|
8
|
+
const typeOrLiteralErr = typeOrLiteralErrByPath.get(flattenedErrorPath);
|
|
9
|
+
if (typeOrLiteralErr) {
|
|
10
|
+
typeOrLiteralErr.expected.push(unionError.expected);
|
|
11
|
+
} else {
|
|
12
|
+
typeOrLiteralErrByPath.set(flattenedErrorPath, {
|
|
13
|
+
code: unionError.code,
|
|
14
|
+
received: unionError.received,
|
|
15
|
+
expected: [unionError.expected]
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const messages = [
|
|
21
|
+
prefix(
|
|
22
|
+
baseErrorPath,
|
|
23
|
+
typeOrLiteralErrByPath.size ? "Did not match union:" : "Did not match union."
|
|
24
|
+
)
|
|
25
|
+
];
|
|
26
|
+
return {
|
|
27
|
+
message: messages.concat(
|
|
28
|
+
[...typeOrLiteralErrByPath.entries()].filter(([, error]) => error.expected.length === baseError.unionErrors.length).map(
|
|
29
|
+
([key, error]) => (
|
|
30
|
+
// Avoid printing the key again if it's a base error
|
|
31
|
+
key === baseErrorPath ? `> ${getTypeOrLiteralMsg(error)}` : `> ${prefix(key, getTypeOrLiteralMsg(error))}`
|
|
32
|
+
)
|
|
33
|
+
)
|
|
34
|
+
).join("\n")
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
if (baseError.code === "invalid_literal" || baseError.code === "invalid_type") {
|
|
38
|
+
return {
|
|
39
|
+
message: prefix(
|
|
40
|
+
baseErrorPath,
|
|
41
|
+
getTypeOrLiteralMsg({
|
|
42
|
+
code: baseError.code,
|
|
43
|
+
received: baseError.received,
|
|
44
|
+
expected: [baseError.expected]
|
|
45
|
+
})
|
|
46
|
+
)
|
|
47
|
+
};
|
|
48
|
+
} else if (baseError.message) {
|
|
49
|
+
return { message: prefix(baseErrorPath, baseError.message) };
|
|
50
|
+
} else {
|
|
51
|
+
return { message: prefix(baseErrorPath, ctx.defaultError) };
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
const getTypeOrLiteralMsg = (error) => {
|
|
55
|
+
if (error.received === "undefined")
|
|
56
|
+
return "Required";
|
|
57
|
+
const expectedDeduped = new Set(error.expected);
|
|
58
|
+
switch (error.code) {
|
|
59
|
+
case "invalid_type":
|
|
60
|
+
return `Expected type \`${unionExpectedVals(expectedDeduped)}\`, received ${JSON.stringify(
|
|
61
|
+
error.received
|
|
62
|
+
)}`;
|
|
63
|
+
case "invalid_literal":
|
|
64
|
+
return `Expected \`${unionExpectedVals(expectedDeduped)}\`, received ${JSON.stringify(
|
|
65
|
+
error.received
|
|
66
|
+
)}`;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
const prefix = (key, msg) => key.length ? `**${key}**: ${msg}` : msg;
|
|
70
|
+
const unionExpectedVals = (expectedVals) => [...expectedVals].map((expectedVal, idx) => {
|
|
71
|
+
if (idx === 0)
|
|
72
|
+
return JSON.stringify(expectedVal);
|
|
73
|
+
const sep = " | ";
|
|
74
|
+
return `${sep}${JSON.stringify(expectedVal)}`;
|
|
75
|
+
}).join("");
|
|
76
|
+
const flattenErrorPath = (errorPath) => errorPath.join(".");
|
|
77
|
+
export {
|
|
78
|
+
errorMap
|
|
79
|
+
};
|
package/dist/errors.d.ts
ADDED
package/dist/errors.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { red } from "kleur/colors";
|
|
2
|
+
const unexpectedAstroAdminError = `${red(
|
|
3
|
+
"Unexpected response from Astro Studio servers."
|
|
4
|
+
)} Try updating your package version. If the problem persists, please contact support.`;
|
|
5
|
+
const authenticationError = `${red(
|
|
6
|
+
"\u26A0\uFE0F Login session invalid or expired."
|
|
7
|
+
)} Please run \`astro login\` again.`;
|
|
8
|
+
const appTokenError = `${red(
|
|
9
|
+
"\u26A0\uFE0F App token invalid or expired."
|
|
10
|
+
)} Please generate a new one from your the Studio dashboard under project settings.`;
|
|
11
|
+
export {
|
|
12
|
+
appTokenError,
|
|
13
|
+
authenticationError,
|
|
14
|
+
unexpectedAstroAdminError
|
|
15
|
+
};
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { defineCollection, defineWritableCollection, field } from "./config.js";
|
|
2
|
+
import { cli } from "./cli/index.js";
|
|
3
|
+
import { integration } from "./integration.js";
|
|
4
|
+
export {
|
|
5
|
+
cli,
|
|
6
|
+
integration as default,
|
|
7
|
+
defineCollection,
|
|
8
|
+
defineWritableCollection,
|
|
9
|
+
field
|
|
10
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { vitePluginDb } from "./vite-plugin-db.js";
|
|
2
|
+
import { vitePluginInjectEnvTs } from "./vite-plugin-inject-env-ts.js";
|
|
3
|
+
import { typegen } from "./typegen.js";
|
|
4
|
+
import { existsSync } from "fs";
|
|
5
|
+
import { mkdir, rm, writeFile } from "fs/promises";
|
|
6
|
+
import { getLocalDbUrl } from "./consts.js";
|
|
7
|
+
import { createLocalDatabaseClient, setupDbTables } from "./internal.js";
|
|
8
|
+
import { astroConfigWithDbSchema } from "./config.js";
|
|
9
|
+
import { getAstroStudioEnv } from "./utils.js";
|
|
10
|
+
import { appTokenError } from "./errors.js";
|
|
11
|
+
import { errorMap } from "./error-map.js";
|
|
12
|
+
import { dirname } from "path";
|
|
13
|
+
import { fileURLToPath } from "url";
|
|
14
|
+
import { bold } from "kleur/colors";
|
|
15
|
+
function integration() {
|
|
16
|
+
return {
|
|
17
|
+
name: "astro:db",
|
|
18
|
+
hooks: {
|
|
19
|
+
async "astro:config:setup"({ logger, updateConfig, config, command }) {
|
|
20
|
+
if (command === "preview")
|
|
21
|
+
return;
|
|
22
|
+
const configWithDb = astroConfigWithDbSchema.parse(config, { errorMap });
|
|
23
|
+
const collections = configWithDb.db?.collections ?? {};
|
|
24
|
+
const studio = configWithDb.db?.studio ?? false;
|
|
25
|
+
if (!studio && Object.values(collections).some((c) => c.writable)) {
|
|
26
|
+
logger.warn(
|
|
27
|
+
`Writable collections should only be used with Astro Studio. Did you set the ${bold(
|
|
28
|
+
"studio"
|
|
29
|
+
)} flag in your astro config?`
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
let dbPlugin;
|
|
33
|
+
if (studio && command === "build") {
|
|
34
|
+
const appToken = getAstroStudioEnv().ASTRO_STUDIO_APP_TOKEN;
|
|
35
|
+
if (!appToken) {
|
|
36
|
+
logger.error(appTokenError);
|
|
37
|
+
process.exit(0);
|
|
38
|
+
}
|
|
39
|
+
dbPlugin = vitePluginDb({ connectToStudio: true, collections, appToken });
|
|
40
|
+
} else {
|
|
41
|
+
const dbUrl = getLocalDbUrl(config.root);
|
|
42
|
+
if (existsSync(dbUrl)) {
|
|
43
|
+
await rm(dbUrl);
|
|
44
|
+
}
|
|
45
|
+
await mkdir(dirname(fileURLToPath(dbUrl)), { recursive: true });
|
|
46
|
+
await writeFile(dbUrl, "");
|
|
47
|
+
const db = await createLocalDatabaseClient({
|
|
48
|
+
collections,
|
|
49
|
+
dbUrl: dbUrl.href,
|
|
50
|
+
seeding: true
|
|
51
|
+
});
|
|
52
|
+
await setupDbTables({ db, collections, logger });
|
|
53
|
+
logger.info("Collections set up \u{1F680}");
|
|
54
|
+
dbPlugin = vitePluginDb({ connectToStudio: false, collections, dbUrl: dbUrl.href });
|
|
55
|
+
}
|
|
56
|
+
updateConfig({
|
|
57
|
+
vite: {
|
|
58
|
+
plugins: [dbPlugin, vitePluginInjectEnvTs(config)]
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
await typegen({ collections, root: config.root });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
export {
|
|
67
|
+
integration
|
|
68
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { sql, eq, gt, gte, lt, lte, ne, isNull, isNotNull, inArray, notInArray, exists, notExists, between, notBetween, like, notIlike, not, asc, desc, and, or, } from 'drizzle-orm';
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import {
|
|
2
|
+
sql,
|
|
3
|
+
eq,
|
|
4
|
+
gt,
|
|
5
|
+
gte,
|
|
6
|
+
lt,
|
|
7
|
+
lte,
|
|
8
|
+
ne,
|
|
9
|
+
isNull,
|
|
10
|
+
isNotNull,
|
|
11
|
+
inArray,
|
|
12
|
+
notInArray,
|
|
13
|
+
exists,
|
|
14
|
+
notExists,
|
|
15
|
+
between,
|
|
16
|
+
notBetween,
|
|
17
|
+
like,
|
|
18
|
+
notIlike,
|
|
19
|
+
not,
|
|
20
|
+
asc,
|
|
21
|
+
desc,
|
|
22
|
+
and,
|
|
23
|
+
or
|
|
24
|
+
} from "drizzle-orm";
|
|
25
|
+
export {
|
|
26
|
+
and,
|
|
27
|
+
asc,
|
|
28
|
+
between,
|
|
29
|
+
desc,
|
|
30
|
+
eq,
|
|
31
|
+
exists,
|
|
32
|
+
gt,
|
|
33
|
+
gte,
|
|
34
|
+
inArray,
|
|
35
|
+
isNotNull,
|
|
36
|
+
isNull,
|
|
37
|
+
like,
|
|
38
|
+
lt,
|
|
39
|
+
lte,
|
|
40
|
+
ne,
|
|
41
|
+
not,
|
|
42
|
+
notBetween,
|
|
43
|
+
notExists,
|
|
44
|
+
notIlike,
|
|
45
|
+
notInArray,
|
|
46
|
+
or,
|
|
47
|
+
sql
|
|
48
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { SqliteRemoteDatabase } from 'drizzle-orm/sqlite-proxy';
|
|
2
|
+
import { type DBCollection, type DBCollections } from './types.js';
|
|
3
|
+
import { type LibSQLDatabase } from 'drizzle-orm/libsql';
|
|
4
|
+
import { type ColumnDataType } from 'drizzle-orm';
|
|
5
|
+
import type { AstroIntegrationLogger } from 'astro';
|
|
6
|
+
export type SqliteDB = SqliteRemoteDatabase;
|
|
7
|
+
export type { AstroTable, AstroText, AstroDate, AstroBoolean, AstroNumber, AstroJson, AstroId, } from './types.js';
|
|
8
|
+
export { createRemoteDatabaseClient } from './utils.js';
|
|
9
|
+
export declare function createLocalDatabaseClient({ collections, dbUrl, seeding, }: {
|
|
10
|
+
dbUrl: string;
|
|
11
|
+
collections: DBCollections;
|
|
12
|
+
seeding: boolean;
|
|
13
|
+
}): Promise<LibSQLDatabase<Record<string, never>>>;
|
|
14
|
+
export declare function setupDbTables({ db, collections, logger, }: {
|
|
15
|
+
db: LibSQLDatabase;
|
|
16
|
+
collections: DBCollections;
|
|
17
|
+
logger: AstroIntegrationLogger;
|
|
18
|
+
}): Promise<void>;
|
|
19
|
+
export declare function getCreateTableQuery(collectionName: string, collection: DBCollection): string;
|
|
20
|
+
export declare function collectionToTable(name: string, collection: DBCollection, isJsonSerializable?: boolean): import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
|
|
21
|
+
name: string;
|
|
22
|
+
schema: undefined;
|
|
23
|
+
columns: {
|
|
24
|
+
[x: string]: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
25
|
+
name: string;
|
|
26
|
+
tableName: string;
|
|
27
|
+
dataType: ColumnDataType;
|
|
28
|
+
columnType: string;
|
|
29
|
+
data: unknown;
|
|
30
|
+
driverParam: unknown;
|
|
31
|
+
notNull: false;
|
|
32
|
+
hasDefault: false;
|
|
33
|
+
enumValues: string[] | undefined;
|
|
34
|
+
baseColumn: never;
|
|
35
|
+
}, object>;
|
|
36
|
+
id: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
37
|
+
name: "id";
|
|
38
|
+
tableName: string;
|
|
39
|
+
dataType: "string";
|
|
40
|
+
columnType: "SQLiteText";
|
|
41
|
+
data: string;
|
|
42
|
+
driverParam: string;
|
|
43
|
+
notNull: true;
|
|
44
|
+
hasDefault: true;
|
|
45
|
+
enumValues: [string, ...string[]];
|
|
46
|
+
baseColumn: never;
|
|
47
|
+
}, object>;
|
|
48
|
+
};
|
|
49
|
+
dialect: "sqlite";
|
|
50
|
+
}>;
|
package/dist/internal.js
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { createClient } from "@libsql/client";
|
|
2
|
+
import {
|
|
3
|
+
} from "./types.js";
|
|
4
|
+
import { drizzle } from "drizzle-orm/libsql";
|
|
5
|
+
import { bold } from "kleur/colors";
|
|
6
|
+
import {
|
|
7
|
+
sql,
|
|
8
|
+
getTableName
|
|
9
|
+
} from "drizzle-orm";
|
|
10
|
+
import {
|
|
11
|
+
SQLiteAsyncDialect,
|
|
12
|
+
customType,
|
|
13
|
+
integer,
|
|
14
|
+
sqliteTable,
|
|
15
|
+
text
|
|
16
|
+
} from "drizzle-orm/sqlite-core";
|
|
17
|
+
import { z } from "zod";
|
|
18
|
+
import { nanoid } from "nanoid";
|
|
19
|
+
const sqlite = new SQLiteAsyncDialect();
|
|
20
|
+
function isReadableCollection(collection) {
|
|
21
|
+
return !collection.writable;
|
|
22
|
+
}
|
|
23
|
+
function checkIfModificationIsAllowed(collections, Table) {
|
|
24
|
+
const tableName = getTableName(Table);
|
|
25
|
+
const collection = collections[tableName];
|
|
26
|
+
if (!collection.writable) {
|
|
27
|
+
throw new Error(`The [${tableName}] collection is read-only.`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
import { createRemoteDatabaseClient } from "./utils.js";
|
|
31
|
+
async function createLocalDatabaseClient({
|
|
32
|
+
collections,
|
|
33
|
+
dbUrl,
|
|
34
|
+
seeding
|
|
35
|
+
}) {
|
|
36
|
+
const client = createClient({ url: dbUrl });
|
|
37
|
+
const db = drizzle(client);
|
|
38
|
+
if (seeding)
|
|
39
|
+
return db;
|
|
40
|
+
const { insert: drizzleInsert, update: drizzleUpdate, delete: drizzleDelete } = db;
|
|
41
|
+
return Object.assign(db, {
|
|
42
|
+
insert(Table) {
|
|
43
|
+
checkIfModificationIsAllowed(collections, Table);
|
|
44
|
+
return drizzleInsert.call(this, Table);
|
|
45
|
+
},
|
|
46
|
+
update(Table) {
|
|
47
|
+
checkIfModificationIsAllowed(collections, Table);
|
|
48
|
+
return drizzleUpdate.call(this, Table);
|
|
49
|
+
},
|
|
50
|
+
delete(Table) {
|
|
51
|
+
checkIfModificationIsAllowed(collections, Table);
|
|
52
|
+
return drizzleDelete.call(this, Table);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
async function setupDbTables({
|
|
57
|
+
db,
|
|
58
|
+
collections,
|
|
59
|
+
logger
|
|
60
|
+
}) {
|
|
61
|
+
const setupQueries = [];
|
|
62
|
+
for (const [name, collection] of Object.entries(collections)) {
|
|
63
|
+
const dropQuery = sql.raw(`DROP TABLE IF EXISTS ${name}`);
|
|
64
|
+
const createQuery = sql.raw(getCreateTableQuery(name, collection));
|
|
65
|
+
setupQueries.push(dropQuery, createQuery);
|
|
66
|
+
}
|
|
67
|
+
for (const q of setupQueries) {
|
|
68
|
+
await db.run(q);
|
|
69
|
+
}
|
|
70
|
+
for (const [name, collection] of Object.entries(collections)) {
|
|
71
|
+
if (!isReadableCollection(collection) || !collection.data)
|
|
72
|
+
continue;
|
|
73
|
+
const table = collectionToTable(name, collection);
|
|
74
|
+
try {
|
|
75
|
+
await db.insert(table).values(await collection.data());
|
|
76
|
+
} catch (e) {
|
|
77
|
+
logger.error(
|
|
78
|
+
`Failed to seed ${bold(
|
|
79
|
+
name
|
|
80
|
+
)} data. Did you update to match recent schema changes? Full error:
|
|
81
|
+
|
|
82
|
+
${e}`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function getCreateTableQuery(collectionName, collection) {
|
|
88
|
+
let query = `CREATE TABLE ${sqlite.escapeName(collectionName)} (`;
|
|
89
|
+
const colQueries = ['"id" text PRIMARY KEY'];
|
|
90
|
+
for (const [columnName, column] of Object.entries(collection.fields)) {
|
|
91
|
+
const colQuery = `${sqlite.escapeName(columnName)} ${schemaTypeToSqlType(
|
|
92
|
+
column.type
|
|
93
|
+
)}${getModifiers(columnName, column)}`;
|
|
94
|
+
colQueries.push(colQuery);
|
|
95
|
+
}
|
|
96
|
+
query += colQueries.join(", ") + ")";
|
|
97
|
+
return query;
|
|
98
|
+
}
|
|
99
|
+
function schemaTypeToSqlType(type) {
|
|
100
|
+
switch (type) {
|
|
101
|
+
case "date":
|
|
102
|
+
case "text":
|
|
103
|
+
case "json":
|
|
104
|
+
return "text";
|
|
105
|
+
case "number":
|
|
106
|
+
case "boolean":
|
|
107
|
+
return "integer";
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function getModifiers(columnName, column) {
|
|
111
|
+
let modifiers = "";
|
|
112
|
+
if (!column.optional) {
|
|
113
|
+
modifiers += " NOT NULL";
|
|
114
|
+
}
|
|
115
|
+
if (column.unique) {
|
|
116
|
+
modifiers += " UNIQUE";
|
|
117
|
+
}
|
|
118
|
+
if (hasDefault(column)) {
|
|
119
|
+
modifiers += ` DEFAULT ${getDefaultValueSql(columnName, column)}`;
|
|
120
|
+
}
|
|
121
|
+
return modifiers;
|
|
122
|
+
}
|
|
123
|
+
function hasDefault(field) {
|
|
124
|
+
return field.default !== void 0;
|
|
125
|
+
}
|
|
126
|
+
function getDefaultValueSql(columnName, column) {
|
|
127
|
+
switch (column.type) {
|
|
128
|
+
case "boolean":
|
|
129
|
+
return column.default ? "TRUE" : "FALSE";
|
|
130
|
+
case "number":
|
|
131
|
+
return `${column.default}`;
|
|
132
|
+
case "text":
|
|
133
|
+
return sqlite.escapeString(column.default);
|
|
134
|
+
case "date":
|
|
135
|
+
return column.default === "now" ? "CURRENT_TIMESTAMP" : sqlite.escapeString(column.default);
|
|
136
|
+
case "json": {
|
|
137
|
+
let stringified = "";
|
|
138
|
+
try {
|
|
139
|
+
stringified = JSON.stringify(column.default);
|
|
140
|
+
} catch (e) {
|
|
141
|
+
console.info(
|
|
142
|
+
`Invalid default value for column ${bold(
|
|
143
|
+
columnName
|
|
144
|
+
)}. Defaults must be valid JSON when using the \`json()\` type.`
|
|
145
|
+
);
|
|
146
|
+
process.exit(0);
|
|
147
|
+
}
|
|
148
|
+
return sqlite.escapeString(stringified);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function generateId() {
|
|
153
|
+
return nanoid(12);
|
|
154
|
+
}
|
|
155
|
+
const dateType = customType({
|
|
156
|
+
dataType() {
|
|
157
|
+
return "text";
|
|
158
|
+
},
|
|
159
|
+
toDriver(value) {
|
|
160
|
+
return value.toISOString();
|
|
161
|
+
},
|
|
162
|
+
fromDriver(value) {
|
|
163
|
+
return new Date(value);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
const jsonType = customType({
|
|
167
|
+
dataType() {
|
|
168
|
+
return "text";
|
|
169
|
+
},
|
|
170
|
+
toDriver(value) {
|
|
171
|
+
return JSON.stringify(value);
|
|
172
|
+
},
|
|
173
|
+
fromDriver(value) {
|
|
174
|
+
return JSON.parse(value);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
const initialColumns = {
|
|
178
|
+
id: text("id").primaryKey().$default(() => generateId())
|
|
179
|
+
};
|
|
180
|
+
function collectionToTable(name, collection, isJsonSerializable = true) {
|
|
181
|
+
const columns = {
|
|
182
|
+
// Spread to avoid mutating `initialColumns`
|
|
183
|
+
...initialColumns
|
|
184
|
+
};
|
|
185
|
+
for (const [fieldName, field] of Object.entries(collection.fields)) {
|
|
186
|
+
columns[fieldName] = columnMapper(fieldName, field, isJsonSerializable);
|
|
187
|
+
}
|
|
188
|
+
const table = sqliteTable(name, columns);
|
|
189
|
+
return table;
|
|
190
|
+
}
|
|
191
|
+
function columnMapper(fieldName, field, isJsonSerializable) {
|
|
192
|
+
let c;
|
|
193
|
+
switch (field.type) {
|
|
194
|
+
case "text": {
|
|
195
|
+
c = text(fieldName);
|
|
196
|
+
if (field.default !== void 0)
|
|
197
|
+
c = c.default(field.default);
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
case "number": {
|
|
201
|
+
c = integer(fieldName);
|
|
202
|
+
if (field.default !== void 0)
|
|
203
|
+
c = c.default(field.default);
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
case "boolean": {
|
|
207
|
+
c = integer(fieldName, { mode: "boolean" });
|
|
208
|
+
if (field.default !== void 0)
|
|
209
|
+
c = c.default(field.default);
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
case "json":
|
|
213
|
+
c = jsonType(fieldName);
|
|
214
|
+
if (field.default !== void 0)
|
|
215
|
+
c = c.default(field.default);
|
|
216
|
+
break;
|
|
217
|
+
case "date": {
|
|
218
|
+
if (isJsonSerializable) {
|
|
219
|
+
c = text(fieldName);
|
|
220
|
+
if (field.default !== void 0) {
|
|
221
|
+
c = c.default(field.default === "now" ? sql`CURRENT_TIMESTAMP` : field.default);
|
|
222
|
+
}
|
|
223
|
+
} else {
|
|
224
|
+
c = dateType(fieldName);
|
|
225
|
+
if (field.default !== void 0) {
|
|
226
|
+
c = c.default(
|
|
227
|
+
field.default === "now" ? sql`CURRENT_TIMESTAMP` : (
|
|
228
|
+
// default comes pre-transformed to an ISO string for D1 storage.
|
|
229
|
+
// parse back to a Date for Drizzle.
|
|
230
|
+
z.coerce.date().parse(field.default)
|
|
231
|
+
)
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (!field.optional)
|
|
239
|
+
c = c.notNull();
|
|
240
|
+
if (field.unique)
|
|
241
|
+
c = c.unique();
|
|
242
|
+
return c;
|
|
243
|
+
}
|
|
244
|
+
export {
|
|
245
|
+
collectionToTable,
|
|
246
|
+
createLocalDatabaseClient,
|
|
247
|
+
createRemoteDatabaseClient,
|
|
248
|
+
getCreateTableQuery,
|
|
249
|
+
setupDbTables
|
|
250
|
+
};
|