@mcp-b/do-runtime 0.3.6 → 0.5.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/CHANGELOG.md +35 -0
- package/README.md +7 -2
- package/dist/backends/node-sqlite.js +2 -1
- package/dist/backends/node-sqlite.js.map +1 -1
- package/dist/backends/sqlite-wasm.js +2 -1
- package/dist/backends/sqlite-wasm.js.map +1 -1
- package/dist/chunks/{sqlite-DFg92Tgt.js → sqlite-migrations-DsWmLP_B.js} +55 -3
- package/dist/chunks/sqlite-migrations-DsWmLP_B.js.map +1 -0
- package/dist/index.js +424 -54
- package/dist/index.js.map +1 -1
- package/dist/server/alarm-scheduler.js +2 -1
- package/dist/server/alarm-scheduler.js.map +1 -1
- package/dist/src/api/sql.d.ts +32 -9
- package/dist/src/util/sqlite-migrations.d.ts +72 -0
- package/package.json +3 -1
- package/dist/chunks/sqlite-DFg92Tgt.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as hasUserErrorDetail, c as setUserErrorDetail, d as tryCurrentSlice, f as CanceledError, i as captureGateStack, m as OutputGate, n as IoContext, o as isExceptionFromInputGateBroken, p as InputGate, r as atCheckpointEnd, s as requireInputLock, t as BrokenActorError, u as tryCurrentIoContext } from "./chunks/io-context-RmmjNtwm.js";
|
|
2
2
|
import { RpcTarget as RpcTarget$1 } from "./cloudflare-workers.js";
|
|
3
|
-
import { a as
|
|
3
|
+
import { a as SqliteDatabase, c as getText, l as hasCurrentSqliteTable, o as getBlob, s as getInt64, t as ensureRuntimeStorageVersion, u as isNull } from "./chunks/sqlite-migrations-DsWmLP_B.js";
|
|
4
4
|
import { ALARM_RETRY_MAX_TRIES, ALARM_RETRY_START_SECONDS, AlarmScheduler, RETRY_BACKOFF_MAX, RETRY_JITTER_FACTOR, alarmRetryDelayMs } from "./server/alarm-scheduler.js";
|
|
5
5
|
import { deserialize, serialize } from "@ungap/structured-clone";
|
|
6
6
|
import { RpcTarget, newMessagePortRpcSession } from "capnweb";
|
|
@@ -999,14 +999,24 @@ function notSerializableType(value) {
|
|
|
999
999
|
* `abstract` in the types — JSG nested types have no JS constructor — so the
|
|
1000
1000
|
* faithful shape is a constructor that refuses. `sql.Cursor` exists for
|
|
1001
1001
|
* `instanceof`, which is all upstream exposes it for.
|
|
1002
|
-
* 3. **The regulator is ported whole
|
|
1003
|
-
*
|
|
1002
|
+
* 3. **The regulator is ported whole, and that is not the whole authorizer.**
|
|
1003
|
+
* Four of its five members are here as callbacks, and none needed the
|
|
1004
1004
|
* authorizer to compute anything — `isAllowedName` is a prefix test,
|
|
1005
|
-
* `isAllowedTrigger` is `return true`, `allowTransactions` throws
|
|
1006
|
-
*
|
|
1007
|
-
*
|
|
1005
|
+
* `isAllowedTrigger` is `return true`, `allowTransactions` throws,
|
|
1006
|
+
* `shouldAddQueryStats` is a constant. The fifth, `onError`, is not a
|
|
1007
|
+
* callback at all in this port: it is every `throw new Error(message)`
|
|
1008
|
+
* below, which is all its upstream body does with a refusal message.
|
|
1009
|
+
* For those, what the authorizer supplied was the *identifiers*:
|
|
1010
|
+
* with none, the statement text is the only source, so `exec` tokenizes it
|
|
1008
1011
|
* and runs `isAllowedName` over every identifier-shaped token. That is
|
|
1009
1012
|
* deliberately STRICTER than upstream — see `SQL_RESERVED_PREFIX_MESSAGE`.
|
|
1013
|
+
*
|
|
1014
|
+
* But the authorizer also makes decisions no callback ever sees, and those
|
|
1015
|
+
* do NOT arrive with the regulator: `SQLITE_ATTACH` / `SQLITE_DETACH`, the
|
|
1016
|
+
* `SQLITE_CREATE_TEMP_*` family and the `temp` schema, `SQLITE_PRAGMA`, and
|
|
1017
|
+
* the `SQLITE_CREATE_VTABLE` module list. Each is refused from the text in
|
|
1018
|
+
* `refuseUnauthorizedForms` and `requireAllowedPragmas`. `SQLITE_FUNCTION`
|
|
1019
|
+
* is the one still unported — see the README divergence row.
|
|
1010
1020
|
* 4. **`ingest` stays at upstream's SQLite seam.** `SqliteDatabase.ingest()`
|
|
1011
1021
|
* executes every complete statement and returns the partial tail, using the
|
|
1012
1022
|
* same compiled boundaries and regulator as `exec`.
|
|
@@ -1039,8 +1049,15 @@ var SQL_RESERVED_PREFIX_MESSAGE = "not authorized: a SQL statement may not name
|
|
|
1039
1049
|
* `SQLITE_SAVEPOINT`. The same set `util/sqlite.ts` classifies, read here from
|
|
1040
1050
|
* the leading keyword because the untrusted path has to refuse them before the
|
|
1041
1051
|
* trusted one applies them.
|
|
1052
|
+
*
|
|
1053
|
+
* `;` counts as leading trivia here and in every other leading-keyword refusal
|
|
1054
|
+
* below. A statement boundary comes from the backend, and `node:sqlite` reports
|
|
1055
|
+
* an empty leading statement as part of the span it compiled: the `sourceSQL`
|
|
1056
|
+
* for `;ATTACH ...` is the whole string, so an anchor of `^\s*` would read the
|
|
1057
|
+
* keyword as `;` and let the compiled ATTACH through. The browser backend cuts
|
|
1058
|
+
* the same input at the first `;` and refuses the empty statement instead.
|
|
1042
1059
|
*/
|
|
1043
|
-
var TRANSACTION_CONTROL =
|
|
1060
|
+
var TRANSACTION_CONTROL = /^[\s;]*(?:BEGIN|COMMIT|END|ROLLBACK|SAVEPOINT|RELEASE)\b/i;
|
|
1044
1061
|
/** Cheap pre-test, so the tokenizer below runs only on a statement that could fail it. */
|
|
1045
1062
|
var RESERVED_PREFIX_HINT = /_cf_/i;
|
|
1046
1063
|
/** A SQL identifier. Double-quoted and bracketed forms are still identifiers, so only the
|
|
@@ -1057,7 +1074,15 @@ var IDENTIFIER = /[A-Za-z_][A-Za-z0-9_$]*/g;
|
|
|
1057
1074
|
*/
|
|
1058
1075
|
var NOT_CODE = /'(?:[^']|'')*'|--[^\n]*|\/\*[\s\S]*?\*\//g;
|
|
1059
1076
|
/**
|
|
1060
|
-
*
|
|
1077
|
+
* Comments are never code. String literals STAY, for both of this regex's callers: pragma
|
|
1078
|
+
* arguments may be quoted, and SQLite's misquoting feature reads a single-quoted string as an
|
|
1079
|
+
* identifier — `CREATE TABLE 'temp'.t(x)` really creates a temp-schema table — so the checks
|
|
1080
|
+
* that read identifier positions must still see it. `NOT_CODE` blanks literals, which is right
|
|
1081
|
+
* for the token scans and would be a bypass for these callers.
|
|
1082
|
+
*/
|
|
1083
|
+
var NOT_COMMENT = /--[^\n]*|\/\*[\s\S]*?\*\//g;
|
|
1084
|
+
/**
|
|
1085
|
+
* ← `SqlStorageRegulator` (`sql.h:15-22`, `sql.c++:141-165`), whole.
|
|
1061
1086
|
*
|
|
1062
1087
|
* Upstream reaches these through the SQLite authorizer while a statement is
|
|
1063
1088
|
* being compiled. `exec` calls them from the statement text instead, which is
|
|
@@ -1119,6 +1144,340 @@ function refuseTransactionControl(statement) {
|
|
|
1119
1144
|
if (TRANSACTION_CONTROL.test(code)) SqlStorageRegulator.allowTransactions();
|
|
1120
1145
|
}
|
|
1121
1146
|
/**
|
|
1147
|
+
* ← the message a `SQLITE_DENY` from the authorizer surfaces to JavaScript,
|
|
1148
|
+
* byte-identical so a caller matching on it ports unchanged.
|
|
1149
|
+
*/
|
|
1150
|
+
var SQL_NOT_AUTHORIZED_MESSAGE = "not authorized: SQLITE_AUTH";
|
|
1151
|
+
/**
|
|
1152
|
+
* The forms neither the regulator nor any callback sees: the authorizer's own
|
|
1153
|
+
* action codes, plus `VACUUM`, which SQLite itself refuses by precondition.
|
|
1154
|
+
*
|
|
1155
|
+
* Porting `SqlStorageRegulator` whole (point 3 in the file header) carried over its members, but
|
|
1156
|
+
* not the authorizer's own action codes: `SQLITE_ATTACH`, `SQLITE_DETACH`, `SQLITE_CREATE_TEMP_*`
|
|
1157
|
+
* and `SQLITE_CREATE_VTABLE` consult no callback, so nothing here refused them. Every form below
|
|
1158
|
+
* was measured on real workerd through the conformance oracle, not inferred.
|
|
1159
|
+
*
|
|
1160
|
+
* `ATTACH` and `DETACH` are also the isolation boundary rather than a fidelity detail: both
|
|
1161
|
+
* backends open a real file, so on `node:sqlite` an `ATTACH` reads another actor's database and
|
|
1162
|
+
* a `VACUUM INTO` writes anywhere the process can. The reserved-name scan does not cover it,
|
|
1163
|
+
* because that scan tokenizes the SUBMITTED statement — `other._cf_KV` is caught, and every
|
|
1164
|
+
* application table in the same attached database is not.
|
|
1165
|
+
*/
|
|
1166
|
+
var DATABASE_ATTACHMENT = /^[\s;]*(?:ATTACH|DETACH)\b/i;
|
|
1167
|
+
/**
|
|
1168
|
+
* ← the `SQLITE_CREATE_TEMP_*` denials (`sqlite.c++:1323`) and the `dbName == temp` rule
|
|
1169
|
+
* (`sqlite.c++:1073`), which permits a temp-schema database name only `READ` and `UPDATE`.
|
|
1170
|
+
* Upstream's own reason to deny them applies here unchanged: a temporary table makes SQLite
|
|
1171
|
+
* open a separate temporary file that the storage engine knows nothing about.
|
|
1172
|
+
*
|
|
1173
|
+
* Two spellings, one refusal each by its own upstream path: `CREATE TEMP TABLE t(x)` is the
|
|
1174
|
+
* keyword and hits the action codes; `CREATE TABLE temp.t(x)` is the schema qualifier and hits
|
|
1175
|
+
* the `dbName` rule — and the qualifier was the live gap, where the table was created, written
|
|
1176
|
+
* and read back here while workerd refused it outright. The qualifier accepts every quoting
|
|
1177
|
+
* SQLite does, single quotes included, with or without whitespace after the keyword (measured:
|
|
1178
|
+
* workerd refuses `CREATE TABLE 'temp'.t(x)` and `CREATE TABLE"temp".t(x)` the same way), and
|
|
1179
|
+
* is matched only in the object-name position, so an application table merely NAMED `tempest`
|
|
1180
|
+
* or `temporary_log` is untouched. The keyword spelling needs no qualifier arm of its own here:
|
|
1181
|
+
* `TEMP_SCHEMA` already refuses every `CREATE TEMP…` before this pattern is consulted.
|
|
1182
|
+
*
|
|
1183
|
+
* The rest of the `dbName` rule goes unported on purpose: with no way to create a temp-schema
|
|
1184
|
+
* object, `INSERT`/`DELETE`/`DROP` against one die in SQLite as `no such table`, and the one
|
|
1185
|
+
* silent form measures identically — workerd allows `DROP TABLE IF EXISTS temp.ghost` too.
|
|
1186
|
+
*/
|
|
1187
|
+
var TEMP_SCHEMA = /^[\s;]*CREATE\s+(?:TEMP|TEMPORARY)\b/i;
|
|
1188
|
+
var TEMP_QUALIFIED = /^[\s;]*CREATE\s+(?:UNIQUE\s+|VIRTUAL\s+)?(?:TABLE|VIEW|TRIGGER|INDEX)\s*(?:IF\s+NOT\s+EXISTS\s*)?(?:"temp"|'temp'|`temp`|\[temp\]|temp)\s*\./i;
|
|
1189
|
+
/**
|
|
1190
|
+
* ← `SQLITE_CREATE_VTABLE` (`sqlite.c++:1298-1316`): a virtual table is native-code callbacks, so
|
|
1191
|
+
* upstream allows exactly four modules — FTS5 and its `fts5vocab` companion, R*Tree and its
|
|
1192
|
+
* `rtree_i32` variant — and denies every other module SQLite was compiled with.
|
|
1193
|
+
*
|
|
1194
|
+
* `dbstat` is why this is not only fidelity: it reports a row per table with page counts and
|
|
1195
|
+
* byte sizes, so `SELECT name FROM d` enumerates `_cf_KV` and the rest of the runtime's own
|
|
1196
|
+
* tables without the statement ever naming them — around `requireAllowedNames`, which can only
|
|
1197
|
+
* see the text it was given.
|
|
1198
|
+
*
|
|
1199
|
+
* The table name and the module accept every quoting SQLite does — double quotes, backticks,
|
|
1200
|
+
* brackets, and the misquoting feature's single-quoted string, with or without whitespace
|
|
1201
|
+
* before them — because the module has to be read from PAST the name, and a guessed name
|
|
1202
|
+
* boundary is a bypass in both directions: an unparseable name skipped the check, and
|
|
1203
|
+
* `"a USING fts5 b" USING dbstat` read its module out of the quoted name. Measured: workerd
|
|
1204
|
+
* refuses both, refuses `CREATE VIRTUAL TABLE"d"USING dbstat`, resolves `USING "dbstat"` to
|
|
1205
|
+
* the same denial, and allows `USING 'fts5'`. A `CREATE VIRTUAL TABLE` whose module the
|
|
1206
|
+
* pattern cannot read is refused outright — the deliberately stricter direction the
|
|
1207
|
+
* unparseable-PRAGMA fallback below already takes.
|
|
1208
|
+
*/
|
|
1209
|
+
var SQL_IDENTIFIER_SOURCE = /"(?:[^"]|"")*"|'(?:[^']|'')*'|`(?:[^`]|``)*`|\[[^\]]*\]|[A-Za-z_\u0080-\uffff][A-Za-z0-9_$\u0080-\uffff]*/.source;
|
|
1210
|
+
var VIRTUAL_TABLE = /^[\s;]*CREATE\s+VIRTUAL\s+TABLE\b/i;
|
|
1211
|
+
var VIRTUAL_TABLE_MODULE = new RegExp(String.raw`^[\s;]*CREATE\s+VIRTUAL\s+TABLE\s*(?:IF\s+NOT\s+EXISTS\s*)?(?:${SQL_IDENTIFIER_SOURCE})\s*(?:\.\s*(?:${SQL_IDENTIFIER_SOURCE})\s*)?USING\s*(${SQL_IDENTIFIER_SOURCE})`, "i");
|
|
1212
|
+
var ALLOWED_VIRTUAL_TABLE_MODULES = /* @__PURE__ */ new Set([
|
|
1213
|
+
"fts5",
|
|
1214
|
+
"fts5vocab",
|
|
1215
|
+
"rtree",
|
|
1216
|
+
"rtree_i32"
|
|
1217
|
+
]);
|
|
1218
|
+
/**
|
|
1219
|
+
* `VACUUM` has no action code of its own, so the authorizer never sees it. What refuses it
|
|
1220
|
+
* upstream is SQLite's own `cannot VACUUM from within a transaction` precondition, against the
|
|
1221
|
+
* transaction a Durable Object always has open — and that message is what the oracle returned,
|
|
1222
|
+
* for `VACUUM`, `VACUUM main` and `VACUUM INTO` alike. Byte-identical for the same reason
|
|
1223
|
+
* `SQL_NOT_AUTHORIZED_MESSAGE` is: a caller matching on it ports unchanged.
|
|
1224
|
+
*
|
|
1225
|
+
* Refused here unconditionally rather than by transaction state, which is a divergence only in
|
|
1226
|
+
* mechanism: this runtime never runs a statement where upstream would have allowed it.
|
|
1227
|
+
*/
|
|
1228
|
+
var VACUUM_STATEMENT = /^[\s;]*VACUUM\b/i;
|
|
1229
|
+
var SQL_VACUUM_REFUSED_MESSAGE = "cannot VACUUM from within a transaction: SQLITE_ERROR";
|
|
1230
|
+
/** Refuse the authorizer-only forms against one SQLite-decided statement boundary. */
|
|
1231
|
+
function refuseUnauthorizedForms(statement) {
|
|
1232
|
+
const code = statement.replace(NOT_COMMENT, " ");
|
|
1233
|
+
if (VACUUM_STATEMENT.test(code)) throw new Error(SQL_VACUUM_REFUSED_MESSAGE);
|
|
1234
|
+
if (DATABASE_ATTACHMENT.test(code) || TEMP_SCHEMA.test(code) || TEMP_QUALIFIED.test(code)) throw new Error(SQL_NOT_AUTHORIZED_MESSAGE);
|
|
1235
|
+
if (VIRTUAL_TABLE.test(code)) {
|
|
1236
|
+
const moduleName = VIRTUAL_TABLE_MODULE.exec(code)?.[1];
|
|
1237
|
+
if (moduleName === void 0 || !ALLOWED_VIRTUAL_TABLE_MODULES.has(unquoted(moduleName).toLowerCase())) throw new Error(SQL_NOT_AUTHORIZED_MESSAGE);
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
/** Cheap pre-test; `PRAGMA` and the `pragma_` functions both contain it. */
|
|
1241
|
+
var PRAGMA_HINT = /pragma/i;
|
|
1242
|
+
/** `PRAGMA [schema.]name`, then `= value`, `(argument)`, or nothing. */
|
|
1243
|
+
var PRAGMA_STATEMENT = /^[\s;]*PRAGMA\s+(?:[A-Za-z_][A-Za-z0-9_$]*\s*\.\s*)?([A-Za-z_][A-Za-z0-9_$]*)(?:\s*=\s*([\s\S]+?)|\s*\(\s*([\s\S]*?)\s*\))?\s*;?\s*$/i;
|
|
1244
|
+
/**
|
|
1245
|
+
* ← `ALLOWED_PRAGMAS` (`util/sqlite.c++:543-571`) and `PragmaSignature` (`:528-535`),
|
|
1246
|
+
* verbatim. `table_list`, `table_info`, and `table_xinfo` are special-cased
|
|
1247
|
+
* ahead of the table in the authorizer, exactly as upstream's `SQLITE_PRAGMA`
|
|
1248
|
+
* case does (`util/sqlite.c++:1194-1273`).
|
|
1249
|
+
*/
|
|
1250
|
+
var ALLOWED_PRAGMAS = /* @__PURE__ */ new Map([
|
|
1251
|
+
["data_version", "NO_ARG"],
|
|
1252
|
+
["page_size", "NO_ARG"],
|
|
1253
|
+
["case_sensitive_like", "BOOLEAN"],
|
|
1254
|
+
["foreign_keys", "BOOLEAN"],
|
|
1255
|
+
["defer_foreign_keys", "BOOLEAN"],
|
|
1256
|
+
["ignore_check_constraints", "BOOLEAN"],
|
|
1257
|
+
["legacy_alter_table", "BOOLEAN"],
|
|
1258
|
+
["recursive_triggers", "BOOLEAN"],
|
|
1259
|
+
["reverse_unordered_selects", "BOOLEAN"],
|
|
1260
|
+
["foreign_key_check", "OPTIONAL_OBJECT_NAME"],
|
|
1261
|
+
["foreign_key_list", "OBJECT_NAME"],
|
|
1262
|
+
["index_info", "OBJECT_NAME"],
|
|
1263
|
+
["index_list", "OBJECT_NAME"],
|
|
1264
|
+
["index_xinfo", "OBJECT_NAME"],
|
|
1265
|
+
["quick_check", "NULL_NUMBER_OR_OBJECT_NAME"],
|
|
1266
|
+
["optimize", "NULL_OR_NUMBER"]
|
|
1267
|
+
]);
|
|
1268
|
+
/** Upstream compares the eight literal forms as PREFIXES, case-insensitively. */
|
|
1269
|
+
var BOOLEAN_PRAGMA_VALUE = /^(?:true|false|yes|no|on|off|1|0)/i;
|
|
1270
|
+
/**
|
|
1271
|
+
* The pragmas SQLite ships (https://www.sqlite.org/pragma.html), so a
|
|
1272
|
+
* `pragma_X` identifier can be told apart: `X` here means the table-valued
|
|
1273
|
+
* pragma function and follows the allowlist; any other `pragma_`-prefixed
|
|
1274
|
+
* identifier is an ordinary application name, which upstream's authorizer
|
|
1275
|
+
* distinguishes by resolution and the conformance suite pins.
|
|
1276
|
+
*/
|
|
1277
|
+
var SQLITE_PRAGMA_NAMES = /* @__PURE__ */ new Set([
|
|
1278
|
+
"analysis_limit",
|
|
1279
|
+
"application_id",
|
|
1280
|
+
"auto_vacuum",
|
|
1281
|
+
"automatic_index",
|
|
1282
|
+
"busy_timeout",
|
|
1283
|
+
"cache_size",
|
|
1284
|
+
"cache_spill",
|
|
1285
|
+
"case_sensitive_like",
|
|
1286
|
+
"cell_size_check",
|
|
1287
|
+
"checkpoint_fullfsync",
|
|
1288
|
+
"collation_list",
|
|
1289
|
+
"compile_options",
|
|
1290
|
+
"data_version",
|
|
1291
|
+
"database_list",
|
|
1292
|
+
"defer_foreign_keys",
|
|
1293
|
+
"encoding",
|
|
1294
|
+
"foreign_key_check",
|
|
1295
|
+
"foreign_key_list",
|
|
1296
|
+
"foreign_keys",
|
|
1297
|
+
"freelist_count",
|
|
1298
|
+
"full_column_names",
|
|
1299
|
+
"fullfsync",
|
|
1300
|
+
"function_list",
|
|
1301
|
+
"hard_heap_limit",
|
|
1302
|
+
"ignore_check_constraints",
|
|
1303
|
+
"incremental_vacuum",
|
|
1304
|
+
"index_info",
|
|
1305
|
+
"index_list",
|
|
1306
|
+
"index_xinfo",
|
|
1307
|
+
"integrity_check",
|
|
1308
|
+
"journal_mode",
|
|
1309
|
+
"journal_size_limit",
|
|
1310
|
+
"legacy_alter_table",
|
|
1311
|
+
"legacy_file_format",
|
|
1312
|
+
"locking_mode",
|
|
1313
|
+
"max_page_count",
|
|
1314
|
+
"mmap_size",
|
|
1315
|
+
"module_list",
|
|
1316
|
+
"optimize",
|
|
1317
|
+
"page_count",
|
|
1318
|
+
"page_size",
|
|
1319
|
+
"pragma_list",
|
|
1320
|
+
"query_only",
|
|
1321
|
+
"quick_check",
|
|
1322
|
+
"read_uncommitted",
|
|
1323
|
+
"recursive_triggers",
|
|
1324
|
+
"reverse_unordered_selects",
|
|
1325
|
+
"schema_version",
|
|
1326
|
+
"secure_delete",
|
|
1327
|
+
"short_column_names",
|
|
1328
|
+
"shrink_memory",
|
|
1329
|
+
"soft_heap_limit",
|
|
1330
|
+
"synchronous",
|
|
1331
|
+
"table_info",
|
|
1332
|
+
"table_list",
|
|
1333
|
+
"table_xinfo",
|
|
1334
|
+
"temp_store",
|
|
1335
|
+
"threads",
|
|
1336
|
+
"trusted_schema",
|
|
1337
|
+
"user_version",
|
|
1338
|
+
"wal_autocheckpoint",
|
|
1339
|
+
"wal_checkpoint",
|
|
1340
|
+
"writable_schema"
|
|
1341
|
+
]);
|
|
1342
|
+
/** kj's `tryParseAs` is decimal; keep the same acceptance. */
|
|
1343
|
+
var DECIMAL = /^[+-]?\d+$/;
|
|
1344
|
+
/**
|
|
1345
|
+
* One layer of SQL quoting, any of the four forms — a pragma argument, or the module token
|
|
1346
|
+
* `VIRTUAL_TABLE_MODULE` captured. Doubled inner quotes stay doubled, which cannot change a
|
|
1347
|
+
* verdict here: no allowlisted comparison target contains a quote character.
|
|
1348
|
+
*/
|
|
1349
|
+
function unquoted(argument) {
|
|
1350
|
+
const first = argument[0];
|
|
1351
|
+
const last = argument[argument.length - 1];
|
|
1352
|
+
if (argument.length >= 2) {
|
|
1353
|
+
if ((first === "'" || first === "\"" || first === "`") && last === first) return argument.slice(1, -1);
|
|
1354
|
+
if (first === "[" && last === "]") return argument.slice(1, -1);
|
|
1355
|
+
}
|
|
1356
|
+
return argument;
|
|
1357
|
+
}
|
|
1358
|
+
/** ← the `SQLITE_PRAGMA` authorizer case (`util/sqlite.c++:1194-1273`), whole. */
|
|
1359
|
+
function isAllowedPragma(name, argument) {
|
|
1360
|
+
const pragma = name.toLowerCase();
|
|
1361
|
+
if (pragma === "table_list") return true;
|
|
1362
|
+
if (pragma === "table_info" || pragma === "table_xinfo") {
|
|
1363
|
+
if (argument === void 0) return false;
|
|
1364
|
+
return SqlStorageRegulator.isAllowedName(unquoted(argument));
|
|
1365
|
+
}
|
|
1366
|
+
const signature = ALLOWED_PRAGMAS.get(pragma);
|
|
1367
|
+
if (signature === void 0) return false;
|
|
1368
|
+
switch (signature) {
|
|
1369
|
+
case "NO_ARG": return argument === void 0;
|
|
1370
|
+
case "BOOLEAN": return argument === void 0 || BOOLEAN_PRAGMA_VALUE.test(unquoted(argument));
|
|
1371
|
+
case "OBJECT_NAME": return argument !== void 0 && SqlStorageRegulator.isAllowedName(unquoted(argument));
|
|
1372
|
+
case "OPTIONAL_OBJECT_NAME": return argument === void 0 || SqlStorageRegulator.isAllowedName(unquoted(argument));
|
|
1373
|
+
case "NULL_OR_NUMBER": return argument === void 0 || DECIMAL.test(argument);
|
|
1374
|
+
case "NULL_NUMBER_OR_OBJECT_NAME": return argument === void 0 || DECIMAL.test(argument) || SqlStorageRegulator.isAllowedName(unquoted(argument));
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
/**
|
|
1378
|
+
* The text-level stand-in for the authorizer's `SQLITE_PRAGMA` case, against
|
|
1379
|
+
* one SQLite-decided statement boundary. Load-bearing beyond fidelity:
|
|
1380
|
+
* `user_version` is where runtime storage versioning keeps its per-file stamp
|
|
1381
|
+
* (`util/sqlite-migrations.ts`), and `writable_schema` would let application
|
|
1382
|
+
* SQL rewrite `sqlite_master` out from under the `_cf_` reservation.
|
|
1383
|
+
*
|
|
1384
|
+
* The `pragma_` table-valued functions reach the same authorizer path
|
|
1385
|
+
* upstream, so they follow the same allowlist here — by pragma NAME only. An
|
|
1386
|
+
* argument the text cannot see (a string literal or a binding) goes unchecked
|
|
1387
|
+
* where upstream's authorizer sees the resolved value; a `_cf_` name smuggled
|
|
1388
|
+
* that way reads schema whose shape is public source anyway, while identifier
|
|
1389
|
+
* arguments stay covered by `requireAllowedNames`. The README divergence row
|
|
1390
|
+
* records this.
|
|
1391
|
+
*/
|
|
1392
|
+
function requireAllowedPragmas(statement) {
|
|
1393
|
+
if (!PRAGMA_HINT.test(statement)) return;
|
|
1394
|
+
const code = statement.replace(NOT_COMMENT, " ");
|
|
1395
|
+
const direct = code.match(PRAGMA_STATEMENT);
|
|
1396
|
+
if (direct !== null) {
|
|
1397
|
+
const [, name = "", assigned, called] = direct;
|
|
1398
|
+
const argument = (assigned ?? called)?.trim();
|
|
1399
|
+
if (!isAllowedPragma(name, argument === "" ? void 0 : argument)) throw new Error(SQL_NOT_AUTHORIZED_MESSAGE);
|
|
1400
|
+
return;
|
|
1401
|
+
}
|
|
1402
|
+
if (/^[\s;]*PRAGMA\b/i.test(code)) throw new Error(SQL_NOT_AUTHORIZED_MESSAGE);
|
|
1403
|
+
const literalFree = code.replace(NOT_CODE, " ");
|
|
1404
|
+
for (const [token] of literalFree.matchAll(IDENTIFIER)) {
|
|
1405
|
+
if (token.length <= 7 || token.slice(0, 7).toLowerCase() !== "pragma_") continue;
|
|
1406
|
+
const name = token.slice(7).toLowerCase();
|
|
1407
|
+
if (!SQLITE_PRAGMA_NAMES.has(name)) continue;
|
|
1408
|
+
if (name !== "table_list" && name !== "table_info" && name !== "table_xinfo" && !ALLOWED_PRAGMAS.has(name)) throw new Error(SQL_NOT_AUTHORIZED_MESSAGE);
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
/** Everything the untrusted path refuses at one statement boundary. */
|
|
1412
|
+
function regulateUntrustedStatement(statement) {
|
|
1413
|
+
refuseTransactionControl(statement);
|
|
1414
|
+
refuseUnauthorizedForms(statement);
|
|
1415
|
+
requireAllowedPragmas(statement);
|
|
1416
|
+
}
|
|
1417
|
+
/** ← `JSG_INHERIT_INTRINSIC(v8::kIteratorPrototype)` (`jsg/iterator.h:1044`). */
|
|
1418
|
+
var IteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()));
|
|
1419
|
+
/**
|
|
1420
|
+
* ← the `JSG_ITERATOR` types (`jsg/iterator.h:1036-1050`): `next` and
|
|
1421
|
+
* self-iterability on `%IteratorPrototype%` — which is what carries the ES
|
|
1422
|
+
* iterator helpers; `raw().toArray()` is what Drizzle's durable-sqlite driver
|
|
1423
|
+
* calls — and NOTHING else. No `return`, no `throw` (only the async variant
|
|
1424
|
+
* registers `return_`, `:1069-1085`), so `IteratorClose` after a `break`, a
|
|
1425
|
+
* partial destructuring, or a `take()` is a no-op and a retained iterator
|
|
1426
|
+
* resumes. Results are `JSG_STRUCT(done, value)` in that key order
|
|
1427
|
+
* (`jsg/iterator.h:706-710`).
|
|
1428
|
+
*/
|
|
1429
|
+
var RawIterator = class {
|
|
1430
|
+
#pull;
|
|
1431
|
+
constructor(pull) {
|
|
1432
|
+
this.#pull = pull;
|
|
1433
|
+
}
|
|
1434
|
+
next() {
|
|
1435
|
+
const raw = this.#pull();
|
|
1436
|
+
if (raw === void 0) return {
|
|
1437
|
+
done: true,
|
|
1438
|
+
value: void 0
|
|
1439
|
+
};
|
|
1440
|
+
return {
|
|
1441
|
+
done: false,
|
|
1442
|
+
value: asRawRow([...raw])
|
|
1443
|
+
};
|
|
1444
|
+
}
|
|
1445
|
+
[Symbol.iterator]() {
|
|
1446
|
+
return this;
|
|
1447
|
+
}
|
|
1448
|
+
};
|
|
1449
|
+
Object.setPrototypeOf(RawIterator.prototype, IteratorPrototype);
|
|
1450
|
+
Object.defineProperty(RawIterator.prototype, Symbol.toStringTag, {
|
|
1451
|
+
value: "RawIterator",
|
|
1452
|
+
configurable: true
|
|
1453
|
+
});
|
|
1454
|
+
/** ← `RowIterator`, shaped exactly as `RawIterator` above. */
|
|
1455
|
+
var RowIterator = class {
|
|
1456
|
+
#pull;
|
|
1457
|
+
constructor(pull) {
|
|
1458
|
+
this.#pull = pull;
|
|
1459
|
+
}
|
|
1460
|
+
next() {
|
|
1461
|
+
const row = this.#pull();
|
|
1462
|
+
if (row === void 0) return {
|
|
1463
|
+
done: true,
|
|
1464
|
+
value: void 0
|
|
1465
|
+
};
|
|
1466
|
+
return {
|
|
1467
|
+
done: false,
|
|
1468
|
+
value: row
|
|
1469
|
+
};
|
|
1470
|
+
}
|
|
1471
|
+
[Symbol.iterator]() {
|
|
1472
|
+
return this;
|
|
1473
|
+
}
|
|
1474
|
+
};
|
|
1475
|
+
Object.setPrototypeOf(RowIterator.prototype, IteratorPrototype);
|
|
1476
|
+
Object.defineProperty(RowIterator.prototype, Symbol.toStringTag, {
|
|
1477
|
+
value: "RowIterator",
|
|
1478
|
+
configurable: true
|
|
1479
|
+
});
|
|
1480
|
+
/**
|
|
1122
1481
|
* ← `SqlStorage::Cursor`.
|
|
1123
1482
|
*
|
|
1124
1483
|
* `rowsRead` is the one counter that is not upstream's. Upstream reads
|
|
@@ -1130,20 +1489,30 @@ function refuseTransactionControl(statement) {
|
|
|
1130
1489
|
* undercounts any query that scans more rows than it returns.
|
|
1131
1490
|
*/
|
|
1132
1491
|
var Cursor = class {
|
|
1133
|
-
columnNames;
|
|
1492
|
+
#columnNames;
|
|
1134
1493
|
#rawRows;
|
|
1135
1494
|
#rowsWritten;
|
|
1136
1495
|
#position = 0;
|
|
1137
1496
|
constructor(state) {
|
|
1138
1497
|
if (state === void 0) throw new Error(CURSOR_NOT_CONSTRUCTIBLE_MESSAGE);
|
|
1139
|
-
this
|
|
1498
|
+
this.#columnNames = state.columnNames;
|
|
1140
1499
|
this.#rawRows = state.rawRows;
|
|
1141
1500
|
this.#rowsWritten = state.rowsWritten;
|
|
1142
1501
|
}
|
|
1502
|
+
/**
|
|
1503
|
+
* ← `JSG_READONLY_PROTOTYPE_PROPERTY(columnNames)` (`sql.h:210`): a
|
|
1504
|
+
* prototype accessor, not an own field, so a cursor JSON-stringifies to `{}`.
|
|
1505
|
+
*/
|
|
1506
|
+
get columnNames() {
|
|
1507
|
+
return this.#columnNames;
|
|
1508
|
+
}
|
|
1143
1509
|
/** ← `Cursor::next`, whose `RowIterator::Next` is this exact shape. */
|
|
1144
1510
|
next() {
|
|
1145
1511
|
const row = this.#nextRow();
|
|
1146
|
-
if (row === void 0) return {
|
|
1512
|
+
if (row === void 0) return {
|
|
1513
|
+
done: true,
|
|
1514
|
+
value: void 0
|
|
1515
|
+
};
|
|
1147
1516
|
return {
|
|
1148
1517
|
done: false,
|
|
1149
1518
|
value: row
|
|
@@ -1168,45 +1537,16 @@ var Cursor = class {
|
|
|
1168
1537
|
}
|
|
1169
1538
|
return row;
|
|
1170
1539
|
}
|
|
1171
|
-
/**
|
|
1540
|
+
/**
|
|
1541
|
+
* ← `Cursor::raw`, which shares this cursor's position rather than
|
|
1542
|
+
* restarting. The iterator's shape is `RawIterator`'s whole doc comment.
|
|
1543
|
+
*/
|
|
1172
1544
|
raw() {
|
|
1173
|
-
|
|
1174
|
-
[Symbol.iterator]() {
|
|
1175
|
-
return iterator;
|
|
1176
|
-
},
|
|
1177
|
-
next: () => {
|
|
1178
|
-
const raw = this.#nextRaw();
|
|
1179
|
-
if (raw === void 0) return {
|
|
1180
|
-
done: true,
|
|
1181
|
-
value: void 0
|
|
1182
|
-
};
|
|
1183
|
-
return {
|
|
1184
|
-
done: false,
|
|
1185
|
-
value: asRawRow([...raw])
|
|
1186
|
-
};
|
|
1187
|
-
}
|
|
1188
|
-
};
|
|
1189
|
-
return iterator;
|
|
1545
|
+
return new RawIterator(() => this.#nextRaw());
|
|
1190
1546
|
}
|
|
1191
|
-
/** ← `JSG_ITERABLE(rows)
|
|
1547
|
+
/** ← `JSG_ITERABLE(rows)`, yielding through the same shared position. */
|
|
1192
1548
|
[Symbol.iterator]() {
|
|
1193
|
-
|
|
1194
|
-
[Symbol.iterator]() {
|
|
1195
|
-
return iterator;
|
|
1196
|
-
},
|
|
1197
|
-
next: () => {
|
|
1198
|
-
const row = this.#nextRow();
|
|
1199
|
-
if (row === void 0) return {
|
|
1200
|
-
done: true,
|
|
1201
|
-
value: void 0
|
|
1202
|
-
};
|
|
1203
|
-
return {
|
|
1204
|
-
done: false,
|
|
1205
|
-
value: row
|
|
1206
|
-
};
|
|
1207
|
-
}
|
|
1208
|
-
};
|
|
1209
|
-
return iterator;
|
|
1549
|
+
return new RowIterator(() => this.#nextRow());
|
|
1210
1550
|
}
|
|
1211
1551
|
get rowsRead() {
|
|
1212
1552
|
return this.#position;
|
|
@@ -1226,12 +1566,17 @@ var Cursor = class {
|
|
|
1226
1566
|
const raw = this.#nextRaw();
|
|
1227
1567
|
if (raw === void 0) return void 0;
|
|
1228
1568
|
const row = {};
|
|
1229
|
-
this
|
|
1569
|
+
this.#columnNames.forEach((name, index) => {
|
|
1230
1570
|
row[name] = raw[index] ?? null;
|
|
1231
1571
|
});
|
|
1232
1572
|
return asRow(row);
|
|
1233
1573
|
}
|
|
1234
1574
|
};
|
|
1575
|
+
/** ← the jsg resource-type tag every workerd API object carries (`resource.h`). */
|
|
1576
|
+
Object.defineProperty(Cursor.prototype, Symbol.toStringTag, {
|
|
1577
|
+
value: "Cursor",
|
|
1578
|
+
configurable: true
|
|
1579
|
+
});
|
|
1235
1580
|
/**
|
|
1236
1581
|
* ← `SqlStorage::Statement`, which upstream describes as "supported only for
|
|
1237
1582
|
* backwards compatibility ... it is actually just a wrapper around `exec()`".
|
|
@@ -1261,7 +1606,7 @@ var SqlStorage = class {
|
|
|
1261
1606
|
const db = this.#owner.getSqliteDb();
|
|
1262
1607
|
const sqlBindings = bindings.map(toSqlBindingValue);
|
|
1263
1608
|
requireAllowedNames(query);
|
|
1264
|
-
const result = db.run({ regulate:
|
|
1609
|
+
const result = db.run({ regulate: regulateUntrustedStatement }, query, ...sqlBindings);
|
|
1265
1610
|
return new Cursor({
|
|
1266
1611
|
columnNames: [...result.columnNames],
|
|
1267
1612
|
rawRows: result.rawRows.map((row) => row.map(toSqlStorageValue)),
|
|
@@ -1294,7 +1639,7 @@ var SqlStorage = class {
|
|
|
1294
1639
|
ingest(query) {
|
|
1295
1640
|
requireInputLock(this.#ctx, "sql.ingest()");
|
|
1296
1641
|
requireAllowedNames(query);
|
|
1297
|
-
return this.#owner.getSqliteDb().ingest(query,
|
|
1642
|
+
return this.#owner.getSqliteDb().ingest(query, regulateUntrustedStatement);
|
|
1298
1643
|
}
|
|
1299
1644
|
/** ← `SqlStorage::setMaxPageCountForTest`, which is what its name says. */
|
|
1300
1645
|
setMaxPageCountForTest(count) {
|
|
@@ -1459,6 +1804,26 @@ var OP_PUT_ALARM = "setAlarm()";
|
|
|
1459
1804
|
var OP_DELETE = "delete()";
|
|
1460
1805
|
var OP_DELETE_ALARM = "deleteAlarm()";
|
|
1461
1806
|
var OP_ROLLBACK = "rollback()";
|
|
1807
|
+
/** ← `actor-state.c++:455`, verbatim: the one message both overloads' misuse produces. */
|
|
1808
|
+
var PUT_OVERLOAD_MESSAGE = "put() may only be called with a single key-value pair and optional options as put(key, value, options) or with multiple key-value pairs and optional options as put(entries, options)";
|
|
1809
|
+
/**
|
|
1810
|
+
* ← the `kj::OneOf<kj::String, jsg::Dict<…>>` unwrap on put()'s first parameter: `jsg::Dict`
|
|
1811
|
+
* takes any JS object except an Array — functions and Maps included — and `kj::String` takes
|
|
1812
|
+
* everything else by coercion. A type predicate, so the overload split narrows without a cast.
|
|
1813
|
+
*/
|
|
1814
|
+
function isEntriesArgument(value) {
|
|
1815
|
+
return (typeof value === "object" || typeof value === "function") && value !== null && !Array.isArray(value);
|
|
1816
|
+
}
|
|
1817
|
+
/**
|
|
1818
|
+
* ← the struct wrapper (`jsg/struct.h:246-258`), which is NOT the Dict wrapper: `PutOptions` is
|
|
1819
|
+
* all-optional fields, so `null` unwraps to default options, and any object does — arrays and
|
|
1820
|
+
* functions included, because the wrapper checks `IsObject()` with no Array exclusion. Only a
|
|
1821
|
+
* non-null primitive fails to unwrap. Measured on real workerd: `put({k: 1}, null)`, `…, [])`
|
|
1822
|
+
* and `…, function () {})` all write, and `put({k: 1}, "v")` alone is the overload error.
|
|
1823
|
+
*/
|
|
1824
|
+
function isPutOptions(value) {
|
|
1825
|
+
return value === null || typeof value === "object" || typeof value === "function";
|
|
1826
|
+
}
|
|
1462
1827
|
/** The key immediately after `k` in byte order is `k` plus this. */
|
|
1463
1828
|
var NULL_CHARACTER = "\0";
|
|
1464
1829
|
/** ← the `0xff` upstream strips from the tail of a prefix, in UTF-16 code units. */
|
|
@@ -1662,10 +2027,11 @@ var DurableObjectStorageOperations = class {
|
|
|
1662
2027
|
}
|
|
1663
2028
|
put(keyOrEntries, valueOrOptions, maybeOptions) {
|
|
1664
2029
|
requireInputLock(this.ctx, OP_PUT);
|
|
1665
|
-
if (
|
|
2030
|
+
if (!isEntriesArgument(keyOrEntries)) {
|
|
1666
2031
|
if (valueOrOptions === void 0) throw new TypeError("put() called with undefined value.");
|
|
1667
|
-
return this.#putOne(keyOrEntries
|
|
2032
|
+
return this.#putOne(`${keyOrEntries}`, valueOrOptions, { ...maybeOptions });
|
|
1668
2033
|
}
|
|
2034
|
+
if (valueOrOptions !== void 0 && !isPutOptions(valueOrOptions)) throw new TypeError(PUT_OVERLOAD_MESSAGE);
|
|
1669
2035
|
return this.#putMultiple(keyOrEntries, { ...valueOrOptions });
|
|
1670
2036
|
}
|
|
1671
2037
|
delete(keyOrKeys, maybeOptions) {
|
|
@@ -5827,9 +6193,13 @@ var ActorContainerImpl = class {
|
|
|
5827
6193
|
* `state`.
|
|
5828
6194
|
*/
|
|
5829
6195
|
async function createActorContainer(options) {
|
|
5830
|
-
const
|
|
6196
|
+
const actorDb = await options.ports.sql.open(ACTOR_DATABASE_NAME);
|
|
6197
|
+
ensureRuntimeStorageVersion(actorDb, ACTOR_DATABASE_NAME);
|
|
6198
|
+
const db = new SqliteDatabase(actorDb);
|
|
5831
6199
|
if (options.facet !== void 0) return new ActorContainerImpl(options, db, void 0, options.facet.tree);
|
|
5832
|
-
const
|
|
6200
|
+
const facetDb = await options.ports.sql.open(FACET_DATABASE_NAME);
|
|
6201
|
+
ensureRuntimeStorageVersion(facetDb, FACET_DATABASE_NAME);
|
|
6202
|
+
const tree = new ActorTree(facetDb, options.ports.facets);
|
|
5833
6203
|
return new ActorContainerImpl(options, db, tree, tree);
|
|
5834
6204
|
}
|
|
5835
6205
|
//#endregion
|