rwsdk 1.2.11 → 1.2.12
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/dist/runtime/lib/db/DOSqliteIntrospector.d.ts +8 -0
- package/dist/runtime/lib/db/DOSqliteIntrospector.js +129 -0
- package/dist/runtime/lib/db/DOWorkerDialect.d.ts +3 -2
- package/dist/runtime/lib/db/DOWorkerDialect.js +3 -2
- package/dist/runtime/lib/db/SqliteDurableObject.js +10 -1
- package/package.json +1 -1
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type DatabaseMetadata, type DatabaseMetadataOptions, type Kysely, type SchemaMetadata, type TableMetadata } from "kysely";
|
|
2
|
+
export declare class DOSqliteIntrospector {
|
|
3
|
+
#private;
|
|
4
|
+
constructor(db: Kysely<any>);
|
|
5
|
+
getSchemas(): Promise<SchemaMetadata[]>;
|
|
6
|
+
getTables(options?: DatabaseMetadataOptions): Promise<TableMetadata[]>;
|
|
7
|
+
getMetadata(options: DatabaseMetadataOptions): Promise<DatabaseMetadata>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// NOTE(justinvdm, 9 Jun 2026): This file copies Kysely's SqliteIntrospector.
|
|
2
|
+
// Before trying to simplify it, read this comment.
|
|
3
|
+
//
|
|
4
|
+
// Problem: Cloudflare's DO SQLite adds internal tables (_cf_KV, _cf_METADATA)
|
|
5
|
+
// when storage.put/setAlarm is used. Kysely's SqliteIntrospector discovers
|
|
6
|
+
// these via sqlite_master, then runs PRAGMA table_info on each. Cloudflare's
|
|
7
|
+
// authorizer rejects PRAGMA on _cf_* tables with SQLITE_AUTH, breaking
|
|
8
|
+
// migrations and rendering the DO unusable.
|
|
9
|
+
//
|
|
10
|
+
// Why we copied instead of composed:
|
|
11
|
+
// - Kysely's SqliteIntrospector uses JS private fields (#db, #tablesQuery,
|
|
12
|
+
// #getTableMetadata). Private fields are truly private — subclasses cannot
|
|
13
|
+
// override them, and wrappers cannot intercept internal calls.
|
|
14
|
+
// - We considered a lighter approach: query sqlite_master with Kysely's
|
|
15
|
+
// builder, then run PRAGMA table_info per-table. This is lighter but
|
|
16
|
+
// requires inlining the table name into raw SQL. Even with escaping,
|
|
17
|
+
// any injection risk is unacceptable. The CTE approach below uses
|
|
18
|
+
// `pragma_table_info(tl.name)` where `tl.name` is a column reference,
|
|
19
|
+
// not an inlined value — zero injection surface.
|
|
20
|
+
// - We also considered Kysely plugins (AST rewriting, SQL string rewriting)
|
|
21
|
+
// but these are fragile: they depend on Kysely's internal AST shape and
|
|
22
|
+
// SQL formatting, both of which can change between releases.
|
|
23
|
+
//
|
|
24
|
+
// This is a copy of Kysely's SqliteIntrospector (MIT licensed) with one
|
|
25
|
+
// change: `.where('name', 'not like', '_cf_%')` to exclude Cloudflare tables.
|
|
26
|
+
//
|
|
27
|
+
// See: https://github.com/redwoodjs/sdk/issues/1219
|
|
28
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
29
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
30
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
31
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
32
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
33
|
+
};
|
|
34
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
35
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
36
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
37
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
38
|
+
};
|
|
39
|
+
var _DOSqliteIntrospector_instances, _DOSqliteIntrospector_db, _DOSqliteIntrospector_tablesQuery, _DOSqliteIntrospector_getTableMetadata;
|
|
40
|
+
import { DEFAULT_MIGRATION_LOCK_TABLE, DEFAULT_MIGRATION_TABLE, sql, } from "kysely";
|
|
41
|
+
export class DOSqliteIntrospector {
|
|
42
|
+
constructor(db) {
|
|
43
|
+
_DOSqliteIntrospector_instances.add(this);
|
|
44
|
+
_DOSqliteIntrospector_db.set(this, void 0);
|
|
45
|
+
__classPrivateFieldSet(this, _DOSqliteIntrospector_db, db, "f");
|
|
46
|
+
}
|
|
47
|
+
async getSchemas() {
|
|
48
|
+
// Sqlite doesn't support schemas.
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
async getTables(options = { withInternalKyselyTables: false }) {
|
|
52
|
+
return await __classPrivateFieldGet(this, _DOSqliteIntrospector_instances, "m", _DOSqliteIntrospector_getTableMetadata).call(this, options);
|
|
53
|
+
}
|
|
54
|
+
async getMetadata(options) {
|
|
55
|
+
return {
|
|
56
|
+
tables: await this.getTables(options),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
_DOSqliteIntrospector_db = new WeakMap(), _DOSqliteIntrospector_instances = new WeakSet(), _DOSqliteIntrospector_tablesQuery = function _DOSqliteIntrospector_tablesQuery(qb, options) {
|
|
61
|
+
let tablesQuery = qb
|
|
62
|
+
.selectFrom("sqlite_master")
|
|
63
|
+
.where("type", "in", ["table", "view"])
|
|
64
|
+
.where("name", "not like", "sqlite_%")
|
|
65
|
+
// context(justinvdm, 9 Jun 2026): Exclude Cloudflare internal tables.
|
|
66
|
+
// These are added by the DO runtime and cannot be introspected.
|
|
67
|
+
.where("name", "not like", "_cf_%")
|
|
68
|
+
.select(["name", "sql", "type"])
|
|
69
|
+
.orderBy("name");
|
|
70
|
+
if (!options.withInternalKyselyTables) {
|
|
71
|
+
tablesQuery = tablesQuery
|
|
72
|
+
.where("name", "!=", DEFAULT_MIGRATION_TABLE)
|
|
73
|
+
.where("name", "!=", DEFAULT_MIGRATION_LOCK_TABLE);
|
|
74
|
+
}
|
|
75
|
+
return tablesQuery;
|
|
76
|
+
}, _DOSqliteIntrospector_getTableMetadata = async function _DOSqliteIntrospector_getTableMetadata(options) {
|
|
77
|
+
const tablesResult = await __classPrivateFieldGet(this, _DOSqliteIntrospector_instances, "m", _DOSqliteIntrospector_tablesQuery).call(this, __classPrivateFieldGet(this, _DOSqliteIntrospector_db, "f"), options).execute();
|
|
78
|
+
const tableMetadata = await __classPrivateFieldGet(this, _DOSqliteIntrospector_db, "f")
|
|
79
|
+
.with("table_list", (qb) => __classPrivateFieldGet(this, _DOSqliteIntrospector_instances, "m", _DOSqliteIntrospector_tablesQuery).call(this, qb, options))
|
|
80
|
+
.selectFrom([
|
|
81
|
+
"table_list as tl",
|
|
82
|
+
sql `pragma_table_info(tl.name)`.as("p"),
|
|
83
|
+
])
|
|
84
|
+
.select([
|
|
85
|
+
"tl.name as table",
|
|
86
|
+
"p.cid",
|
|
87
|
+
"p.name",
|
|
88
|
+
"p.type",
|
|
89
|
+
"p.notnull",
|
|
90
|
+
"p.dflt_value",
|
|
91
|
+
"p.pk",
|
|
92
|
+
])
|
|
93
|
+
.orderBy("tl.name")
|
|
94
|
+
.orderBy("p.cid")
|
|
95
|
+
.execute();
|
|
96
|
+
const columnsByTable = {};
|
|
97
|
+
for (const row of tableMetadata) {
|
|
98
|
+
columnsByTable[row.table] ??= [];
|
|
99
|
+
columnsByTable[row.table].push(row);
|
|
100
|
+
}
|
|
101
|
+
return tablesResult.map(({ name, sql: tableSql, type }) => {
|
|
102
|
+
let autoIncrementCol = tableSql
|
|
103
|
+
?.split(/[\(\),]/)
|
|
104
|
+
?.find((it) => it.toLowerCase().includes("autoincrement"))
|
|
105
|
+
?.trimStart()
|
|
106
|
+
?.split(/\s+/)?.[0]
|
|
107
|
+
?.replace(/["`]/g, "");
|
|
108
|
+
const columns = columnsByTable[name] ?? [];
|
|
109
|
+
if (!autoIncrementCol) {
|
|
110
|
+
const pkCols = columns.filter((r) => r.pk > 0);
|
|
111
|
+
if (pkCols.length === 1 &&
|
|
112
|
+
pkCols[0].type.toLowerCase() === "integer") {
|
|
113
|
+
autoIncrementCol = pkCols[0].name;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
name: name,
|
|
118
|
+
isView: type === "view",
|
|
119
|
+
columns: columns.map((col) => ({
|
|
120
|
+
name: col.name,
|
|
121
|
+
dataType: col.type,
|
|
122
|
+
isNullable: !col.notnull,
|
|
123
|
+
isAutoIncrementing: col.name === autoIncrementCol,
|
|
124
|
+
hasDefaultValue: col.dflt_value != null,
|
|
125
|
+
comment: undefined,
|
|
126
|
+
})),
|
|
127
|
+
};
|
|
128
|
+
});
|
|
129
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { DatabaseConnection, Driver, QueryResult, SqliteAdapter,
|
|
1
|
+
import { DatabaseConnection, Driver, QueryResult, SqliteAdapter, SqliteQueryCompiler } from "kysely";
|
|
2
|
+
import { DOSqliteIntrospector } from "./DOSqliteIntrospector.js";
|
|
2
3
|
type DOWorkerDialectConfig = {
|
|
3
4
|
kyselyExecuteQuery: (compiledQuery: {
|
|
4
5
|
sql: string;
|
|
@@ -11,7 +12,7 @@ export declare class DOWorkerDialect {
|
|
|
11
12
|
createAdapter(): SqliteAdapter;
|
|
12
13
|
createDriver(): DOWorkerDriver;
|
|
13
14
|
createQueryCompiler(): SqliteQueryCompiler;
|
|
14
|
-
createIntrospector(db: any):
|
|
15
|
+
createIntrospector(db: any): DOSqliteIntrospector;
|
|
15
16
|
}
|
|
16
17
|
declare class DOWorkerDriver implements Driver {
|
|
17
18
|
config: DOWorkerDialectConfig;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { SqliteAdapter,
|
|
1
|
+
import { SqliteAdapter, SqliteQueryCompiler, } from "kysely";
|
|
2
2
|
import debug from "../debug";
|
|
3
|
+
import { DOSqliteIntrospector } from "./DOSqliteIntrospector.js";
|
|
3
4
|
const log = debug("sdk:db:do-worker-dialect");
|
|
4
5
|
export class DOWorkerDialect {
|
|
5
6
|
constructor(config) {
|
|
@@ -15,7 +16,7 @@ export class DOWorkerDialect {
|
|
|
15
16
|
return new SqliteQueryCompiler();
|
|
16
17
|
}
|
|
17
18
|
createIntrospector(db) {
|
|
18
|
-
return new
|
|
19
|
+
return new DOSqliteIntrospector(db);
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
22
|
class DOWorkerDriver {
|
|
@@ -2,7 +2,16 @@ import { DurableObject } from "cloudflare:workers";
|
|
|
2
2
|
import { DODialect } from "kysely-do";
|
|
3
3
|
import { Kysely, ParseJSONResultsPlugin, } from "kysely";
|
|
4
4
|
import debug from "../debug.js";
|
|
5
|
+
import { DOSqliteIntrospector } from "./DOSqliteIntrospector.js";
|
|
5
6
|
import { createMigrator } from "./index.js";
|
|
7
|
+
// context(justinvdm, 9 Jun 2026): Wrapper around kysely-do's DODialect that
|
|
8
|
+
// uses our custom introspector to avoid SQLITE_AUTH on Cloudflare internal
|
|
9
|
+
// _cf_* tables. See DOSqliteIntrospector for details.
|
|
10
|
+
class DODialectWithCustomIntrospector extends DODialect {
|
|
11
|
+
createIntrospector(db) {
|
|
12
|
+
return new DOSqliteIntrospector(db);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
6
15
|
const log = debug("sdk:do-db");
|
|
7
16
|
// Base class for Durable Objects that need Kysely database access
|
|
8
17
|
export class SqliteDurableObject extends DurableObject {
|
|
@@ -12,7 +21,7 @@ export class SqliteDurableObject extends DurableObject {
|
|
|
12
21
|
this.migrations = migrations;
|
|
13
22
|
this.migrationTableName = migrationTableName;
|
|
14
23
|
this.kysely = new Kysely({
|
|
15
|
-
dialect: new
|
|
24
|
+
dialect: new DODialectWithCustomIntrospector({ ctx }),
|
|
16
25
|
plugins: plugins,
|
|
17
26
|
});
|
|
18
27
|
}
|