deepspace 0.23.2 → 0.24.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.
- package/CHANGELOG.md +30 -0
- package/README.md +6 -2
- package/dist/build.js +11 -0
- package/dist/build.js.map +1 -1
- package/dist/{chunk-2U2VG6L5.js → chunk-6RQ4GDEH.js} +2 -2
- package/dist/{chunk-2U2VG6L5.js.map → chunk-6RQ4GDEH.js.map} +1 -1
- package/dist/cli.js +1932 -1703
- package/dist/cli.js.map +1 -1
- package/dist/documentation-client-core.js +1 -1
- package/dist/documentation-default-renderer.cjs +1 -1
- package/dist/documentation-react.js +1 -1
- package/dist/documentation-runtime.js +1 -1
- package/dist/documentation-server-core.js +1 -1
- package/dist/documentation.js +1 -1
- package/dist/documentation.js.map +1 -1
- package/dist/index.d.ts +30 -4
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/dist/schema.d.ts +3 -19
- package/dist/schema.js +1 -207
- package/dist/schema.js.map +1 -1
- package/dist/server.d.ts +167 -48
- package/dist/server.js +547 -381
- package/dist/server.js.map +1 -1
- package/dist/testing.d.ts +3 -3
- package/dist/testing.js +3 -0
- package/dist/testing.js.map +1 -1
- package/dist/worker.d.ts +192 -48
- package/dist/worker.js +607 -435
- package/dist/worker.js.map +1 -1
- package/package.json +1 -1
package/dist/worker.js
CHANGED
|
@@ -105,11 +105,11 @@ function decodeRoomIdentityHeader(value) {
|
|
|
105
105
|
return void 0;
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
|
-
function authenticatedRoomRequest(
|
|
109
|
-
const url = new URL(
|
|
108
|
+
function authenticatedRoomRequest(request2, auth, identity) {
|
|
109
|
+
const url = new URL(request2.url);
|
|
110
110
|
url.searchParams.delete("token");
|
|
111
111
|
for (const key of IDENTITY_QUERY_PARAMS) url.searchParams.delete(key);
|
|
112
|
-
const forwarded = new Request(url.toString(),
|
|
112
|
+
const forwarded = new Request(url.toString(), request2);
|
|
113
113
|
for (const header of IDENTITY_HEADERS) forwarded.headers.delete(header);
|
|
114
114
|
if (!auth) return forwarded;
|
|
115
115
|
forwarded.headers.set("x-user-id", encodeRoomIdentityHeader(auth.userId));
|
|
@@ -129,13 +129,18 @@ function authenticatedRoomRequest(request, auth, identity) {
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
// src/server/rooms/base-room.ts
|
|
132
|
-
function connectionAttachmentFromRequest(
|
|
132
|
+
function connectionAttachmentFromRequest(request2) {
|
|
133
|
+
if (!request2.headers.has("x-user-id") && new URL(request2.url).searchParams.has("userId")) {
|
|
134
|
+
console.warn(
|
|
135
|
+
"[BaseRoom] Connection carries identity in the URL (?userId=) but no x-user-id header, so this user joins as anonymous. The app worker's room proxy predates 0.19 or forwards the client's URL unstripped: forward with authenticatedRoomRequest() from deepspace/worker."
|
|
136
|
+
);
|
|
137
|
+
}
|
|
133
138
|
return {
|
|
134
|
-
userId: decodeRoomIdentityHeader(
|
|
135
|
-
userName: decodeRoomIdentityHeader(
|
|
136
|
-
userEmail: decodeRoomIdentityHeader(
|
|
137
|
-
userImageUrl: decodeRoomIdentityHeader(
|
|
138
|
-
role: decodeRoomIdentityHeader(
|
|
139
|
+
userId: decodeRoomIdentityHeader(request2.headers.get("x-user-id")) || `${ANONYMOUS_USER_ID_PREFIX}${crypto.randomUUID()}`,
|
|
140
|
+
userName: decodeRoomIdentityHeader(request2.headers.get("x-user-name")) || "Anonymous",
|
|
141
|
+
userEmail: decodeRoomIdentityHeader(request2.headers.get("x-user-email")) || "",
|
|
142
|
+
userImageUrl: decodeRoomIdentityHeader(request2.headers.get("x-user-image-url")),
|
|
143
|
+
role: decodeRoomIdentityHeader(request2.headers.get("x-user-role"))
|
|
139
144
|
};
|
|
140
145
|
}
|
|
141
146
|
var BaseRoom = class {
|
|
@@ -151,15 +156,15 @@ var BaseRoom = class {
|
|
|
151
156
|
// ==========================================================================
|
|
152
157
|
// HTTP Entry Point
|
|
153
158
|
// ==========================================================================
|
|
154
|
-
async fetch(
|
|
155
|
-
const url = new URL(
|
|
156
|
-
const internal = await this.handleInternalRequest(
|
|
159
|
+
async fetch(request2) {
|
|
160
|
+
const url = new URL(request2.url);
|
|
161
|
+
const internal = await this.handleInternalRequest(request2, url);
|
|
157
162
|
if (internal) return internal;
|
|
158
|
-
if (
|
|
159
|
-
return this.handleWebSocketUpgrade(
|
|
163
|
+
if (request2.headers.get("Upgrade") === "websocket") {
|
|
164
|
+
return this.handleWebSocketUpgrade(request2, url);
|
|
160
165
|
}
|
|
161
166
|
if (this.onRequest) {
|
|
162
|
-
return this.onRequest(
|
|
167
|
+
return this.onRequest(request2);
|
|
163
168
|
}
|
|
164
169
|
return new Response("Not Found", { status: 404 });
|
|
165
170
|
}
|
|
@@ -180,11 +185,11 @@ var BaseRoom = class {
|
|
|
180
185
|
* See the security note on `fetch()`: this path is only reachable via DO
|
|
181
186
|
* stub fetch from the app worker, never from the public internet.
|
|
182
187
|
*/
|
|
183
|
-
async handleInternalRequest(
|
|
184
|
-
if (
|
|
188
|
+
async handleInternalRequest(request2, url) {
|
|
189
|
+
if (request2.method === "POST" && url.pathname === "/internal/disconnect-sockets") {
|
|
185
190
|
let options;
|
|
186
191
|
try {
|
|
187
|
-
const text = await
|
|
192
|
+
const text = await request2.text();
|
|
188
193
|
if (text) options = JSON.parse(text);
|
|
189
194
|
} catch {
|
|
190
195
|
}
|
|
@@ -196,11 +201,11 @@ var BaseRoom = class {
|
|
|
196
201
|
// ==========================================================================
|
|
197
202
|
// WebSocket Connection
|
|
198
203
|
// ==========================================================================
|
|
199
|
-
async handleWebSocketUpgrade(
|
|
204
|
+
async handleWebSocketUpgrade(request2, _url) {
|
|
200
205
|
const pair = new WebSocketPair();
|
|
201
206
|
const [client, server] = Object.values(pair);
|
|
202
207
|
this.state.acceptWebSocket(server);
|
|
203
|
-
const attachment = connectionAttachmentFromRequest(
|
|
208
|
+
const attachment = connectionAttachmentFromRequest(request2);
|
|
204
209
|
const augmented = await this.onConnect(server, attachment) ?? attachment;
|
|
205
210
|
server.serializeAttachment(augmented);
|
|
206
211
|
return new Response(null, { status: 101, webSocket: client });
|
|
@@ -436,7 +441,8 @@ function dataToColumnValues(data, columns) {
|
|
|
436
441
|
return values;
|
|
437
442
|
}
|
|
438
443
|
function coerceValue(value, storage, interpretation) {
|
|
439
|
-
if (value === null || value === void 0
|
|
444
|
+
if (value === null || value === void 0) return null;
|
|
445
|
+
if (value === "") return storage === "text" ? "" : null;
|
|
440
446
|
if (storage === "text") {
|
|
441
447
|
if (interpretation.kind === "json") {
|
|
442
448
|
if (typeof value === "string") return value;
|
|
@@ -470,7 +476,7 @@ function coerceValue(value, storage, interpretation) {
|
|
|
470
476
|
}
|
|
471
477
|
if (interpretation.kind === "date" || interpretation.kind === "datetime") {
|
|
472
478
|
const d = new Date(s);
|
|
473
|
-
if (!isNaN(d.getTime())) return
|
|
479
|
+
if (!isNaN(d.getTime())) return epochSeconds(d);
|
|
474
480
|
return s;
|
|
475
481
|
}
|
|
476
482
|
const cleaned = s.replace(/[$€£¥,\s]/g, "");
|
|
@@ -478,6 +484,9 @@ function coerceValue(value, storage, interpretation) {
|
|
|
478
484
|
if (!isNaN(n) && isFinite(n)) return n;
|
|
479
485
|
return s;
|
|
480
486
|
}
|
|
487
|
+
function epochSeconds(date = /* @__PURE__ */ new Date()) {
|
|
488
|
+
return Math.floor(date.getTime() / 1e3);
|
|
489
|
+
}
|
|
481
490
|
function buildTableSelect(collectionName, columns) {
|
|
482
491
|
const tbl = collectionTableName(collectionName);
|
|
483
492
|
const parts = ["_row_id", "_created_by", "_created_at", "_updated_at"];
|
|
@@ -1142,7 +1151,7 @@ var BUILT_IN_TOOLS = [
|
|
|
1142
1151
|
},
|
|
1143
1152
|
{
|
|
1144
1153
|
name: "user.list",
|
|
1145
|
-
description: "List
|
|
1154
|
+
description: "List the users this caller may see, exactly as the room's roster does: an admin gets whole rows (including email and any app-defined column) through the users collection's read policy; everyone else gets the public identity projection (id, name, imageUrl, role, lastSeenAt) of the roster the users policy allows them \u2014 nothing if that policy denies reads, and only their own scope if the schema sets `roster: 'read-policy'`. A caller with no identity gets an empty list. Called from a server action (X-App-Action), it returns whole rows like the other RBAC-off tools.",
|
|
1146
1155
|
params: {}
|
|
1147
1156
|
},
|
|
1148
1157
|
// Yjs tools (collaborative document access)
|
|
@@ -1316,6 +1325,9 @@ function encode(message) {
|
|
|
1316
1325
|
function getResolvedColumns(schema) {
|
|
1317
1326
|
return (schema.columns ?? []).map(resolveColumn);
|
|
1318
1327
|
}
|
|
1328
|
+
function triggerTimestamp(col) {
|
|
1329
|
+
return col.storage === "number" ? epochSeconds() : (/* @__PURE__ */ new Date()).toISOString();
|
|
1330
|
+
}
|
|
1319
1331
|
function systemManagedColumnError(column, isUpdate) {
|
|
1320
1332
|
const roleHint = column === "role" ? ", or the admin-gated useUsers().setRole() to change a role" : "";
|
|
1321
1333
|
const remedy = isUpdate ? `Re-sending the current value of '${column}' unchanged is accepted and ignored.` : `A users row is created by registration, so no value for '${column}' is accepted here \u2014 omit it.`;
|
|
@@ -1434,7 +1446,7 @@ function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRb
|
|
|
1434
1446
|
if (!isUpdate) {
|
|
1435
1447
|
if (mergedData[triggerField] !== void 0 && mergedData[triggerField] !== null) {
|
|
1436
1448
|
if (triggerValue === void 0 || mergedData[triggerField] === triggerValue) {
|
|
1437
|
-
mergedData[col.name] = (
|
|
1449
|
+
mergedData[col.name] = triggerTimestamp(col);
|
|
1438
1450
|
}
|
|
1439
1451
|
}
|
|
1440
1452
|
} else {
|
|
@@ -1442,7 +1454,7 @@ function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRb
|
|
|
1442
1454
|
const newVal = mergedData[triggerField];
|
|
1443
1455
|
if (newVal !== oldVal) {
|
|
1444
1456
|
if (triggerValue === void 0 || newVal === triggerValue) {
|
|
1445
|
-
mergedData[col.name] = (
|
|
1457
|
+
mergedData[col.name] = triggerTimestamp(col);
|
|
1446
1458
|
}
|
|
1447
1459
|
}
|
|
1448
1460
|
}
|
|
@@ -1487,27 +1499,27 @@ function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRb
|
|
|
1487
1499
|
if (SYSTEM_ASSIGNED_COLUMNS.has(col.name) && Object.hasOwn(data, col.name) && coerceValue(data[col.name], col.storage, col.interpretation) !== coerceValue(current, col.storage, col.interpretation)) {
|
|
1488
1500
|
return { success: false, error: systemManagedColumnError(col.name, isUpdate) };
|
|
1489
1501
|
}
|
|
1490
|
-
if (current !== void 0)
|
|
1502
|
+
if (current !== void 0)
|
|
1503
|
+
finalData[col.name] = current;
|
|
1491
1504
|
else delete finalData[col.name];
|
|
1492
1505
|
}
|
|
1493
1506
|
}
|
|
1494
1507
|
const colValues = dataToColumnValues(finalData, columns);
|
|
1495
1508
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1496
1509
|
const tbl = collectionTableName(collection);
|
|
1497
|
-
if (
|
|
1498
|
-
const
|
|
1499
|
-
const
|
|
1500
|
-
return
|
|
1501
|
-
});
|
|
1502
|
-
const uniqueParams = schema.uniqueOn.map((fieldName) => {
|
|
1503
|
-
const val = finalData[fieldName];
|
|
1504
|
-
return val !== void 0 ? val : null;
|
|
1510
|
+
if (schema.uniqueOn && schema.uniqueOn.length > 0) {
|
|
1511
|
+
const uniqueColumns = schema.uniqueOn.map((fieldName) => {
|
|
1512
|
+
const id = columns.find((column) => column.name === fieldName)?.id ?? columnId(fieldName);
|
|
1513
|
+
return { id, value: colValues[id] ?? null };
|
|
1505
1514
|
});
|
|
1506
|
-
const
|
|
1507
|
-
|
|
1515
|
+
const uniqueWhere = uniqueColumns.map(({ id }) => `"${id}" = ?`);
|
|
1516
|
+
const uniqueParams = uniqueColumns.map(({ value }) => value);
|
|
1517
|
+
if (isUpdate) uniqueParams.push(recordId);
|
|
1518
|
+
const conflict = ctx.sql.exec(
|
|
1519
|
+
`SELECT _row_id FROM "${tbl}" WHERE ${uniqueWhere.join(" AND ")}${isUpdate ? " AND _row_id <> ?" : ""} LIMIT 1`,
|
|
1508
1520
|
...uniqueParams
|
|
1509
1521
|
);
|
|
1510
|
-
if (
|
|
1522
|
+
if (conflict.toArray().length > 0) {
|
|
1511
1523
|
const fields = schema.uniqueOn.map((f) => `${f}=${finalData[f] ?? "null"}`).join(", ");
|
|
1512
1524
|
return {
|
|
1513
1525
|
success: false,
|
|
@@ -1574,8 +1586,21 @@ function deleteRecord(ctx, collection, recordId, userId, userRole, skipUserRbac
|
|
|
1574
1586
|
return { success: true, data: { deleted: true } };
|
|
1575
1587
|
}
|
|
1576
1588
|
function refuseUnknownWhere(collection, schema, where) {
|
|
1577
|
-
const columns = (schema
|
|
1589
|
+
const columns = (schema?.columns ?? []).map(resolveColumn);
|
|
1578
1590
|
const filterable = ["recordId", "createdBy", ...columns.map((c) => c.name)];
|
|
1591
|
+
if (typeof where !== "object" || where === null || Array.isArray(where)) {
|
|
1592
|
+
const got = where === null ? "null" : Array.isArray(where) ? "an array" : typeof where;
|
|
1593
|
+
return {
|
|
1594
|
+
success: false,
|
|
1595
|
+
error: `where for "${collection}" must be an object of field=value pairs, not ${got} \u2014 filterable fields: ${filterable.join(", ")}`
|
|
1596
|
+
};
|
|
1597
|
+
}
|
|
1598
|
+
if (!schema && Object.keys(where).length > 0) {
|
|
1599
|
+
return {
|
|
1600
|
+
success: false,
|
|
1601
|
+
error: `"${collection}" has no schema, so it has no filterable fields \u2014 query it without \`where\``
|
|
1602
|
+
};
|
|
1603
|
+
}
|
|
1579
1604
|
const unknown = Object.keys(where).filter(
|
|
1580
1605
|
(key) => key !== "recordId" && key !== "createdBy" && !columns.some((c) => c.id === columnId(key) || c.name === key)
|
|
1581
1606
|
);
|
|
@@ -1604,11 +1629,14 @@ function deleteWhere(ctx, collection, where, limit, userId, userRole, skipUserRb
|
|
|
1604
1629
|
error: `records.deleteWhere: collection "${collection}" has no schema, so it has no filterable fields \u2014 delete its rows by recordId with records.delete`
|
|
1605
1630
|
};
|
|
1606
1631
|
}
|
|
1607
|
-
if (!where || typeof where !== "object" || Array.isArray(where) || Object.keys(where).length === 0) {
|
|
1608
|
-
return { success: false, error: "Missing required param: where (must match at least one field)" };
|
|
1609
|
-
}
|
|
1610
1632
|
const whereRefusal = refuseUnknownWhere(collection, schema, where);
|
|
1611
1633
|
if (whereRefusal) return whereRefusal;
|
|
1634
|
+
if (Object.keys(where).length === 0) {
|
|
1635
|
+
return {
|
|
1636
|
+
success: false,
|
|
1637
|
+
error: "Missing required param: where (must match at least one field)"
|
|
1638
|
+
};
|
|
1639
|
+
}
|
|
1612
1640
|
if (limit !== void 0 && !(typeof limit === "number" && Number.isFinite(limit))) {
|
|
1613
1641
|
return { success: false, error: "limit must be a finite number" };
|
|
1614
1642
|
}
|
|
@@ -1616,32 +1644,27 @@ function deleteWhere(ctx, collection, where, limit, userId, userRole, skipUserRb
|
|
|
1616
1644
|
Math.max(1, Math.floor(limit ?? DEFAULT_DELETE_WHERE_LIMIT)),
|
|
1617
1645
|
MAX_DELETE_WHERE_LIMIT
|
|
1618
1646
|
);
|
|
1619
|
-
const scan = { capped: false };
|
|
1620
1647
|
const matched = executeQuery(
|
|
1621
1648
|
ctx,
|
|
1622
1649
|
{ collection, where, limit: page },
|
|
1623
1650
|
userId,
|
|
1624
1651
|
userRole,
|
|
1625
|
-
|
|
1626
|
-
scan
|
|
1652
|
+
true
|
|
1627
1653
|
);
|
|
1628
|
-
if (
|
|
1629
|
-
return {
|
|
1630
|
-
success: false,
|
|
1631
|
-
error: `records.deleteWhere scanned ${MAX_FILTERED_SCAN_ROWS} rows without filling a page of readable matches in "${collection}" \u2014 narrow \`where\` (or delete by recordId) and retry`
|
|
1632
|
-
};
|
|
1633
|
-
}
|
|
1634
|
-
if (!skipUserRbac && schema) {
|
|
1654
|
+
if (!skipUserRbac) {
|
|
1635
1655
|
const permCtx = ctx.getPermissionContext();
|
|
1636
1656
|
for (const record of matched) {
|
|
1637
1657
|
if (!canDelete(schema, userRole, record, userId, permCtx)) {
|
|
1638
|
-
return {
|
|
1658
|
+
return {
|
|
1659
|
+
success: false,
|
|
1660
|
+
error: `DELETE DENIED: role=${userRole}, collection=${collection}`
|
|
1661
|
+
};
|
|
1639
1662
|
}
|
|
1640
1663
|
}
|
|
1641
1664
|
}
|
|
1642
1665
|
let deleted = 0;
|
|
1643
1666
|
for (const record of matched) {
|
|
1644
|
-
if (deleteRecord(ctx, collection, record.recordId, userId, userRole,
|
|
1667
|
+
if (deleteRecord(ctx, collection, record.recordId, userId, userRole, true).success) {
|
|
1645
1668
|
deleted++;
|
|
1646
1669
|
}
|
|
1647
1670
|
}
|
|
@@ -2340,17 +2363,17 @@ async function registerUser(sql, userId, name, email, imageUrl, isAdmin, default
|
|
|
2340
2363
|
);
|
|
2341
2364
|
return { id: userId, email, name, imageUrl, role, createdAt: now, lastSeenAt: now };
|
|
2342
2365
|
}
|
|
2343
|
-
function rosterFor(ctx, schema, records, publicRoster,
|
|
2344
|
-
if (isAnonymousUserId(
|
|
2345
|
-
if (
|
|
2366
|
+
function rosterFor(ctx, schema, records, publicRoster, userId, role) {
|
|
2367
|
+
if (!userId || isAnonymousUserId(userId)) return [];
|
|
2368
|
+
if (role === "admin") {
|
|
2346
2369
|
const permissionContext = ctx.getPermissionContext();
|
|
2347
|
-
return records.filter((record) => canRead(schema, "admin", record,
|
|
2370
|
+
return records.filter((record) => canRead(schema, "admin", record, userId, permissionContext)).map((record) => ({ id: record.recordId, ...record.data }));
|
|
2348
2371
|
}
|
|
2349
|
-
if (!getRolePermissions(schema,
|
|
2372
|
+
if (!getRolePermissions(schema, role).read) return [];
|
|
2350
2373
|
if (schema.roster === "read-policy") {
|
|
2351
2374
|
const permissionContext = ctx.getPermissionContext();
|
|
2352
2375
|
const readable = new Set(
|
|
2353
|
-
records.filter((record) => canRead(schema,
|
|
2376
|
+
records.filter((record) => canRead(schema, role, record, userId, permissionContext)).map((record) => record.recordId)
|
|
2354
2377
|
);
|
|
2355
2378
|
return publicRoster.filter((user) => readable.has(user.id));
|
|
2356
2379
|
}
|
|
@@ -2361,17 +2384,18 @@ function publicIdentities(records) {
|
|
|
2361
2384
|
id: recordId,
|
|
2362
2385
|
name: data.name,
|
|
2363
2386
|
...data.imageUrl ? { imageUrl: data.imageUrl } : {},
|
|
2364
|
-
role: data.role
|
|
2387
|
+
role: data.role,
|
|
2388
|
+
lastSeenAt: data.lastSeenAt
|
|
2365
2389
|
}));
|
|
2366
2390
|
}
|
|
2367
|
-
function
|
|
2391
|
+
function rosterForCaller(ctx, userId, role) {
|
|
2368
2392
|
const schema = ctx.schemaRegistry.get("users");
|
|
2369
|
-
if (!schema)
|
|
2370
|
-
ctx.send(ws, { type: MSG.USER_LIST, payload: { users: [] } });
|
|
2371
|
-
return;
|
|
2372
|
-
}
|
|
2393
|
+
if (!schema) return [];
|
|
2373
2394
|
const records = getAllUserRecords(ctx.sql, ctx.schemaRegistry);
|
|
2374
|
-
|
|
2395
|
+
return rosterFor(ctx, schema, records, publicIdentities(records), userId, role);
|
|
2396
|
+
}
|
|
2397
|
+
function handleUserList(ctx, ws, attachment) {
|
|
2398
|
+
const users = rosterForCaller(ctx, attachment.userId, attachment.role);
|
|
2375
2399
|
ctx.send(ws, { type: MSG.USER_LIST, payload: { users } });
|
|
2376
2400
|
}
|
|
2377
2401
|
function broadcastUserList(ctx, except) {
|
|
@@ -2386,7 +2410,9 @@ function broadcastUserList(ctx, except) {
|
|
|
2386
2410
|
if (!attachment) continue;
|
|
2387
2411
|
ctx.send(other, {
|
|
2388
2412
|
type: MSG.USER_LIST,
|
|
2389
|
-
payload: {
|
|
2413
|
+
payload: {
|
|
2414
|
+
users: rosterFor(ctx, schema, records, publicRoster, attachment.userId, attachment.role)
|
|
2415
|
+
}
|
|
2390
2416
|
});
|
|
2391
2417
|
}
|
|
2392
2418
|
}
|
|
@@ -2439,8 +2465,8 @@ var CORS_HEADERS = {
|
|
|
2439
2465
|
"Access-Control-Allow-Headers": "Content-Type, X-User-Id, X-App-Action",
|
|
2440
2466
|
"Content-Type": "application/json"
|
|
2441
2467
|
};
|
|
2442
|
-
async function handleToolsRequest(ctx,
|
|
2443
|
-
if (
|
|
2468
|
+
async function handleToolsRequest(ctx, request2, path) {
|
|
2469
|
+
if (request2.method === "OPTIONS") {
|
|
2444
2470
|
return new Response(null, { headers: CORS_HEADERS });
|
|
2445
2471
|
}
|
|
2446
2472
|
if (path === "tools/list") {
|
|
@@ -2457,15 +2483,15 @@ async function handleToolsRequest(ctx, request, path) {
|
|
|
2457
2483
|
}
|
|
2458
2484
|
return Response.json({ tool: tool3 }, { headers: CORS_HEADERS });
|
|
2459
2485
|
}
|
|
2460
|
-
if (path.startsWith("tools/execute") &&
|
|
2461
|
-
return handleToolExecute(ctx,
|
|
2486
|
+
if (path.startsWith("tools/execute") && request2.method === "POST") {
|
|
2487
|
+
return handleToolExecute(ctx, request2);
|
|
2462
2488
|
}
|
|
2463
2489
|
return Response.json({ error: "Not found" }, { status: 404, headers: CORS_HEADERS });
|
|
2464
2490
|
}
|
|
2465
|
-
async function handleToolExecute(ctx,
|
|
2491
|
+
async function handleToolExecute(ctx, request2) {
|
|
2466
2492
|
let body;
|
|
2467
2493
|
try {
|
|
2468
|
-
body = await
|
|
2494
|
+
body = await request2.json();
|
|
2469
2495
|
} catch {
|
|
2470
2496
|
return Response.json(
|
|
2471
2497
|
{ success: false, error: "Invalid JSON body" },
|
|
@@ -2479,8 +2505,8 @@ async function handleToolExecute(ctx, request) {
|
|
|
2479
2505
|
{ status: 400, headers: CORS_HEADERS }
|
|
2480
2506
|
);
|
|
2481
2507
|
}
|
|
2482
|
-
const userId =
|
|
2483
|
-
const isAppAction =
|
|
2508
|
+
const userId = request2.headers.get("x-user-id") ?? "";
|
|
2509
|
+
const isAppAction = request2.headers.get("x-app-action")?.toLowerCase() === "true";
|
|
2484
2510
|
let userRole = "viewer";
|
|
2485
2511
|
if (userId && ctx.ownerUserId && userId === ctx.ownerUserId) {
|
|
2486
2512
|
userRole = "admin";
|
|
@@ -2515,17 +2541,11 @@ async function executeTool(ctx, toolName, params, userId, userRole, skipUserRbac
|
|
|
2515
2541
|
if (!collection) return { success: false, error: "Missing required param: collection" };
|
|
2516
2542
|
const resolved = resolveCollection(ctx, collection);
|
|
2517
2543
|
if (!resolved.ok) return { success: false, error: resolved.error };
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
if (!resolved.schema) {
|
|
2521
|
-
return {
|
|
2522
|
-
success: false,
|
|
2523
|
-
error: `records.query: collection "${collection}" has no schema, so it has no filterable fields \u2014 query it without \`where\``
|
|
2524
|
-
};
|
|
2525
|
-
}
|
|
2526
|
-
const refusal = refuseUnknownWhere(collection, resolved.schema, where);
|
|
2544
|
+
if (params.where !== void 0) {
|
|
2545
|
+
const refusal = refuseUnknownWhere(collection, resolved.schema, params.where);
|
|
2527
2546
|
if (refusal) return refusal;
|
|
2528
2547
|
}
|
|
2548
|
+
const where = params.where;
|
|
2529
2549
|
const query = {
|
|
2530
2550
|
collection,
|
|
2531
2551
|
where,
|
|
@@ -2634,8 +2654,16 @@ async function executeTool(ctx, toolName, params, userId, userRole, skipUserRbac
|
|
|
2634
2654
|
if (!user) return { success: false, error: "User not found" };
|
|
2635
2655
|
return { success: true, data: { user } };
|
|
2636
2656
|
}
|
|
2657
|
+
// The same roster the socket's `user.list` returns, for the same caller.
|
|
2658
|
+
// Reading the users table directly here handed any member every peer's
|
|
2659
|
+
// email regardless of the room's users policy, while their own socket
|
|
2660
|
+
// roster and `useQuery('users')` obeyed it.
|
|
2661
|
+
//
|
|
2662
|
+
// An app action (`X-App-Action`) is the app's own server-side code, which
|
|
2663
|
+
// is already the trust boundary and reads every other collection RBAC-off;
|
|
2664
|
+
// it keeps the full rows.
|
|
2637
2665
|
case "user.list": {
|
|
2638
|
-
const users = getAllUsers(ctx.sql, ctx.schemaRegistry);
|
|
2666
|
+
const users = skipUserRbac ? getAllUsers(ctx.sql, ctx.schemaRegistry) : rosterForCaller(ctx, userId, userRole);
|
|
2639
2667
|
return { success: true, data: { users } };
|
|
2640
2668
|
}
|
|
2641
2669
|
default:
|
|
@@ -2770,25 +2798,25 @@ var CORS_HEADERS2 = {
|
|
|
2770
2798
|
"Access-Control-Allow-Headers": "Content-Type",
|
|
2771
2799
|
"Content-Type": "application/json"
|
|
2772
2800
|
};
|
|
2773
|
-
async function handleApiRequest(ctx,
|
|
2774
|
-
if (
|
|
2801
|
+
async function handleApiRequest(ctx, request2, url) {
|
|
2802
|
+
if (request2.method === "OPTIONS") {
|
|
2775
2803
|
return new Response(null, { headers: CORS_HEADERS2 });
|
|
2776
2804
|
}
|
|
2777
2805
|
const path = url.pathname.replace("/api/", "");
|
|
2778
2806
|
if (path.startsWith("tools/")) {
|
|
2779
|
-
return handleToolsRequest(ctx,
|
|
2807
|
+
return handleToolsRequest(ctx, request2, path);
|
|
2780
2808
|
}
|
|
2781
2809
|
if (path === "debug/status") {
|
|
2782
2810
|
return handleDebugStatus(ctx);
|
|
2783
2811
|
}
|
|
2784
2812
|
if (path === "debug/sql") {
|
|
2785
|
-
return handleDebugSql(ctx,
|
|
2813
|
+
return handleDebugSql(ctx, request2, url);
|
|
2786
2814
|
}
|
|
2787
2815
|
if (path === "debug/user-role") {
|
|
2788
2816
|
return handleDebugUserRole(ctx, url);
|
|
2789
2817
|
}
|
|
2790
|
-
if (path === "debug/set-role" &&
|
|
2791
|
-
return handleDebugSetRole(ctx,
|
|
2818
|
+
if (path === "debug/set-role" && request2.method === "POST") {
|
|
2819
|
+
return handleDebugSetRole(ctx, request2);
|
|
2792
2820
|
}
|
|
2793
2821
|
if (path.startsWith("debug/records/")) {
|
|
2794
2822
|
return handleDebugRecords(ctx, path);
|
|
@@ -2819,8 +2847,8 @@ function handleDebugUserRole(ctx, url) {
|
|
|
2819
2847
|
role: user.role
|
|
2820
2848
|
}, { headers: CORS_HEADERS2 });
|
|
2821
2849
|
}
|
|
2822
|
-
async function handleDebugSetRole(ctx,
|
|
2823
|
-
const body = await
|
|
2850
|
+
async function handleDebugSetRole(ctx, request2) {
|
|
2851
|
+
const body = await request2.json();
|
|
2824
2852
|
if (!body.role) {
|
|
2825
2853
|
return Response.json({ error: "role is required" }, { status: 400, headers: CORS_HEADERS2 });
|
|
2826
2854
|
}
|
|
@@ -2928,11 +2956,11 @@ function handleDebugQuery(ctx, url) {
|
|
|
2928
2956
|
records
|
|
2929
2957
|
}, { headers: CORS_HEADERS2 });
|
|
2930
2958
|
}
|
|
2931
|
-
async function handleDebugSql(ctx,
|
|
2959
|
+
async function handleDebugSql(ctx, request2, url) {
|
|
2932
2960
|
let sql;
|
|
2933
2961
|
let params = [];
|
|
2934
|
-
if (
|
|
2935
|
-
const body = await
|
|
2962
|
+
if (request2.method === "POST") {
|
|
2963
|
+
const body = await request2.json();
|
|
2936
2964
|
sql = body.sql;
|
|
2937
2965
|
params = body.params ?? [];
|
|
2938
2966
|
} else {
|
|
@@ -3052,10 +3080,10 @@ var RecordRoom = class extends BaseRoom {
|
|
|
3052
3080
|
// ============================================================================
|
|
3053
3081
|
// HTTP Entry Point (overrides BaseRoom.fetch)
|
|
3054
3082
|
// ============================================================================
|
|
3055
|
-
async fetch(
|
|
3056
|
-
const url = new URL(
|
|
3083
|
+
async fetch(request2) {
|
|
3084
|
+
const url = new URL(request2.url);
|
|
3057
3085
|
if (url.pathname.startsWith("/internal/")) {
|
|
3058
|
-
const internal = await this.handleInternalRequest(
|
|
3086
|
+
const internal = await this.handleInternalRequest(request2, url);
|
|
3059
3087
|
if (internal) return internal;
|
|
3060
3088
|
}
|
|
3061
3089
|
const fetchStart = Date.now();
|
|
@@ -3068,11 +3096,11 @@ var RecordRoom = class extends BaseRoom {
|
|
|
3068
3096
|
if (url.pathname.startsWith("/api/debug/") && !this.debugRoutesEnabled) {
|
|
3069
3097
|
return new Response("Not Found", { status: 404 });
|
|
3070
3098
|
}
|
|
3071
|
-
return handleApiRequest(this.createHandlerContext(),
|
|
3099
|
+
return handleApiRequest(this.createHandlerContext(), request2, url);
|
|
3072
3100
|
}
|
|
3073
|
-
if (
|
|
3101
|
+
if (request2.headers.get("Upgrade") === "websocket") {
|
|
3074
3102
|
this._fetchTiming = { fetchStart, isColdStart, needsInit, initMs };
|
|
3075
|
-
return super.fetch(
|
|
3103
|
+
return super.fetch(request2);
|
|
3076
3104
|
}
|
|
3077
3105
|
return new Response("Not Found", { status: 404 });
|
|
3078
3106
|
}
|
|
@@ -3654,9 +3682,9 @@ var CanvasRoom = class extends BaseRoom {
|
|
|
3654
3682
|
// --------------------------------------------------------------------------
|
|
3655
3683
|
// BaseRoom Lifecycle
|
|
3656
3684
|
// --------------------------------------------------------------------------
|
|
3657
|
-
async fetch(
|
|
3685
|
+
async fetch(request2) {
|
|
3658
3686
|
this.ensureInitialized();
|
|
3659
|
-
return super.fetch(
|
|
3687
|
+
return super.fetch(request2);
|
|
3660
3688
|
}
|
|
3661
3689
|
onConnect(ws, user) {
|
|
3662
3690
|
this.ensureInitialized();
|
|
@@ -4135,6 +4163,7 @@ function cronMatches(parsed, timezone, date) {
|
|
|
4135
4163
|
}
|
|
4136
4164
|
|
|
4137
4165
|
// src/server/rooms/cron-room.ts
|
|
4166
|
+
var CRON_TASKS_INTERNAL_PATH = "/internal/cron/tasks";
|
|
4138
4167
|
var CRON_WRITE_TYPES = /* @__PURE__ */ new Set([
|
|
4139
4168
|
MSG.CRON_TRIGGER,
|
|
4140
4169
|
MSG.CRON_PAUSE,
|
|
@@ -4209,9 +4238,15 @@ var CronRoom = class extends BaseRoom {
|
|
|
4209
4238
|
// ==========================================================================
|
|
4210
4239
|
// BaseRoom Lifecycle
|
|
4211
4240
|
// ==========================================================================
|
|
4212
|
-
async fetch(
|
|
4241
|
+
async fetch(request2) {
|
|
4213
4242
|
this.ensureInitialized();
|
|
4214
|
-
return super.fetch(
|
|
4243
|
+
return super.fetch(request2);
|
|
4244
|
+
}
|
|
4245
|
+
async handleInternalRequest(request2, url) {
|
|
4246
|
+
if (request2.method === "GET" && url.pathname === CRON_TASKS_INTERNAL_PATH) {
|
|
4247
|
+
return Response.json({ tasks: this.getTaskStates() });
|
|
4248
|
+
}
|
|
4249
|
+
return super.handleInternalRequest(request2, url);
|
|
4215
4250
|
}
|
|
4216
4251
|
onConnect(ws, user) {
|
|
4217
4252
|
this.ensureInitialized();
|
|
@@ -4359,15 +4394,22 @@ var CronRoom = class extends BaseRoom {
|
|
|
4359
4394
|
const start = Date.now();
|
|
4360
4395
|
let success = true;
|
|
4361
4396
|
let error;
|
|
4397
|
+
let thrown;
|
|
4362
4398
|
try {
|
|
4363
4399
|
await this.onTask(taskName);
|
|
4364
4400
|
} catch (e) {
|
|
4365
4401
|
success = false;
|
|
4402
|
+
thrown = e;
|
|
4366
4403
|
error = e instanceof Error ? e.message : String(e);
|
|
4367
|
-
console.error(`[CronRoom] Task "${taskName}" failed:`, e);
|
|
4368
4404
|
}
|
|
4369
4405
|
const durationMs = Date.now() - start;
|
|
4370
4406
|
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4407
|
+
if (success) {
|
|
4408
|
+
console.log(`[cron] ${taskName} ok ${durationMs}ms`);
|
|
4409
|
+
} else {
|
|
4410
|
+
const stack = thrown instanceof Error ? thrown.stack : void 0;
|
|
4411
|
+
console.error(`[cron] ${taskName} failed ${durationMs}ms: ${error}`, ...stack ? [stack] : []);
|
|
4412
|
+
}
|
|
4371
4413
|
this.sql.exec(
|
|
4372
4414
|
`INSERT INTO cron_history (task_name, started_at, completed_at, success, duration_ms, error, created_at)
|
|
4373
4415
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
@@ -4476,6 +4518,19 @@ var CronRoom = class extends BaseRoom {
|
|
|
4476
4518
|
);
|
|
4477
4519
|
}
|
|
4478
4520
|
};
|
|
4521
|
+
var armedCronRooms = /* @__PURE__ */ new Set();
|
|
4522
|
+
function armCronRoom(ctx, namespace, roomId, tasks) {
|
|
4523
|
+
if (tasks.length === 0 || armedCronRooms.has(roomId)) return;
|
|
4524
|
+
armedCronRooms.add(roomId);
|
|
4525
|
+
ctx.waitUntil(
|
|
4526
|
+
namespace.get(namespace.idFromName(roomId)).fetch(`https://internal${CRON_TASKS_INTERNAL_PATH}`).then((response) => {
|
|
4527
|
+
if (!response.ok) armedCronRooms.delete(roomId);
|
|
4528
|
+
return response;
|
|
4529
|
+
}).catch(() => {
|
|
4530
|
+
armedCronRooms.delete(roomId);
|
|
4531
|
+
})
|
|
4532
|
+
);
|
|
4533
|
+
}
|
|
4479
4534
|
function computeNextRunAt(task, from) {
|
|
4480
4535
|
if (task.interval_minutes) {
|
|
4481
4536
|
return new Date(from.getTime() + task.interval_minutes * 60 * 1e3).toISOString();
|
|
@@ -4548,9 +4603,9 @@ var JobRoom = class extends BaseRoom {
|
|
|
4548
4603
|
// ==========================================================================
|
|
4549
4604
|
// BaseRoom Lifecycle
|
|
4550
4605
|
// ==========================================================================
|
|
4551
|
-
async fetch(
|
|
4606
|
+
async fetch(request2) {
|
|
4552
4607
|
this.ensureInitialized();
|
|
4553
|
-
return super.fetch(
|
|
4608
|
+
return super.fetch(request2);
|
|
4554
4609
|
}
|
|
4555
4610
|
async onConnect(ws, user) {
|
|
4556
4611
|
this.ensureInitialized();
|
|
@@ -4637,13 +4692,13 @@ var JobRoom = class extends BaseRoom {
|
|
|
4637
4692
|
* Subclasses overriding `onRequest` should call `super.onRequest(req)`
|
|
4638
4693
|
* for paths they don't handle.
|
|
4639
4694
|
*/
|
|
4640
|
-
async onRequest(
|
|
4695
|
+
async onRequest(request2) {
|
|
4641
4696
|
this.ensureInitialized();
|
|
4642
|
-
const url = new URL(
|
|
4643
|
-
if (
|
|
4697
|
+
const url = new URL(request2.url);
|
|
4698
|
+
if (request2.method !== "POST" || !url.pathname.endsWith("/enqueue")) {
|
|
4644
4699
|
return new Response("Not Found", { status: 404 });
|
|
4645
4700
|
}
|
|
4646
|
-
const body = await
|
|
4701
|
+
const body = await request2.json().catch(() => null);
|
|
4647
4702
|
if (!body || typeof body.type !== "string" || !body.type) {
|
|
4648
4703
|
return new Response(JSON.stringify({ error: "type required" }), {
|
|
4649
4704
|
status: 400,
|
|
@@ -4809,7 +4864,8 @@ var JobRoom = class extends BaseRoom {
|
|
|
4809
4864
|
} catch (e) {
|
|
4810
4865
|
outcome = "failed";
|
|
4811
4866
|
errorMessage = e instanceof Error ? e.message : String(e);
|
|
4812
|
-
|
|
4867
|
+
const stack = e instanceof Error ? e.stack : void 0;
|
|
4868
|
+
console.error(`[jobs] ${row.type} (${row.id}) failed: ${errorMessage}`, ...stack ? [stack] : []);
|
|
4813
4869
|
} finally {
|
|
4814
4870
|
this.inFlight.delete(row.id);
|
|
4815
4871
|
}
|
|
@@ -5117,13 +5173,15 @@ function validateDoManifest(manifest) {
|
|
|
5117
5173
|
}
|
|
5118
5174
|
|
|
5119
5175
|
// src/server/rooms/binding-manifest.ts
|
|
5176
|
+
var MAX_ENV_VAR_BYTES = 5 * 1024;
|
|
5120
5177
|
var AUTO_PROVISION_SENTINEL = "auto";
|
|
5121
5178
|
var AUTO_PROVISIONABLE_TYPES = /* @__PURE__ */ new Set([
|
|
5122
5179
|
"d1",
|
|
5123
5180
|
"kv_namespace",
|
|
5124
5181
|
"vectorize",
|
|
5125
5182
|
"r2_bucket",
|
|
5126
|
-
"queue"
|
|
5183
|
+
"queue",
|
|
5184
|
+
"ai_search"
|
|
5127
5185
|
]);
|
|
5128
5186
|
var VECTORIZE_METRICS = /* @__PURE__ */ new Set(["cosine", "euclidean", "dot-product"]);
|
|
5129
5187
|
function isAutoProvision(b) {
|
|
@@ -5138,11 +5196,15 @@ function isAutoProvision(b) {
|
|
|
5138
5196
|
return b.bucket_name === AUTO_PROVISION_SENTINEL;
|
|
5139
5197
|
case "queue":
|
|
5140
5198
|
return b.queue_name === AUTO_PROVISION_SENTINEL;
|
|
5199
|
+
case "ai_search":
|
|
5200
|
+
return b.instance_name === AUTO_PROVISION_SENTINEL;
|
|
5141
5201
|
default:
|
|
5142
5202
|
return false;
|
|
5143
5203
|
}
|
|
5144
5204
|
}
|
|
5145
5205
|
var ALLOWED_BINDING_TYPES = /* @__PURE__ */ new Set([
|
|
5206
|
+
"plain_text",
|
|
5207
|
+
"json",
|
|
5146
5208
|
"vectorize",
|
|
5147
5209
|
"ai",
|
|
5148
5210
|
"r2_bucket",
|
|
@@ -5152,7 +5214,8 @@ var ALLOWED_BINDING_TYPES = /* @__PURE__ */ new Set([
|
|
|
5152
5214
|
"browser_rendering",
|
|
5153
5215
|
"analytics_engine",
|
|
5154
5216
|
"hyperdrive",
|
|
5155
|
-
"ratelimit"
|
|
5217
|
+
"ratelimit",
|
|
5218
|
+
"ai_search"
|
|
5156
5219
|
]);
|
|
5157
5220
|
var RESERVED_BINDING_NAMES = /* @__PURE__ */ new Set([
|
|
5158
5221
|
"ASSETS",
|
|
@@ -5176,6 +5239,7 @@ function validateBindingManifest(manifest) {
|
|
|
5176
5239
|
}
|
|
5177
5240
|
const errors = [];
|
|
5178
5241
|
const seenNames = /* @__PURE__ */ new Set();
|
|
5242
|
+
let aiSearchCount = 0;
|
|
5179
5243
|
for (const entry of manifest) {
|
|
5180
5244
|
if (!entry || typeof entry !== "object") {
|
|
5181
5245
|
errors.push({
|
|
@@ -5206,14 +5270,34 @@ function validateBindingManifest(manifest) {
|
|
|
5206
5270
|
}
|
|
5207
5271
|
seenNames.add(name);
|
|
5208
5272
|
const binding = entry;
|
|
5273
|
+
if (binding.type === "ai_search") aiSearchCount++;
|
|
5209
5274
|
const fieldErr = requiredFieldError(binding);
|
|
5210
5275
|
if (fieldErr) errors.push({ binding, reason: fieldErr });
|
|
5211
5276
|
}
|
|
5277
|
+
if (aiSearchCount > 1) {
|
|
5278
|
+
errors.push({ reason: "Only one ai_search binding may be declared per app" });
|
|
5279
|
+
}
|
|
5212
5280
|
if (errors.length) return { valid: false, errors };
|
|
5213
5281
|
return { valid: true, bindings: manifest };
|
|
5214
5282
|
}
|
|
5215
5283
|
function requiredFieldError(b) {
|
|
5216
5284
|
switch (b.type) {
|
|
5285
|
+
case "plain_text":
|
|
5286
|
+
if (typeof b.text !== "string") {
|
|
5287
|
+
return `plain_text binding "${b.name}" missing text (string)`;
|
|
5288
|
+
}
|
|
5289
|
+
return envVarSizeError(b.name, b.text);
|
|
5290
|
+
case "json": {
|
|
5291
|
+
if (!isBindingJsonValue(b.json)) {
|
|
5292
|
+
return `json binding "${b.name}" must contain a JSON-serializable value`;
|
|
5293
|
+
}
|
|
5294
|
+
return envVarSizeError(b.name, JSON.stringify(b.json));
|
|
5295
|
+
}
|
|
5296
|
+
case "ai_search":
|
|
5297
|
+
if (b.instance_name !== AUTO_PROVISION_SENTINEL) {
|
|
5298
|
+
return `ai_search binding "${b.name}" must set instance_name = "auto"; managed instances cannot be selected directly`;
|
|
5299
|
+
}
|
|
5300
|
+
return null;
|
|
5217
5301
|
case "vectorize": {
|
|
5218
5302
|
if (!b.index_name) return `vectorize binding "${b.name}" missing index_name (or "auto")`;
|
|
5219
5303
|
if (b.index_name === AUTO_PROVISION_SENTINEL) {
|
|
@@ -5275,7 +5359,26 @@ function requiredFieldError(b) {
|
|
|
5275
5359
|
}
|
|
5276
5360
|
}
|
|
5277
5361
|
function bindingManifestFromOutputConfig(outputConfig) {
|
|
5362
|
+
const aiSearchNamespaces = outputConfig.ai_search_namespaces;
|
|
5363
|
+
const declaresAiSearchNamespace = Array.isArray(aiSearchNamespaces) ? aiSearchNamespaces.length > 0 : aiSearchNamespaces !== void 0 && aiSearchNamespaces !== null;
|
|
5364
|
+
if (declaresAiSearchNamespace) {
|
|
5365
|
+
throw new Error(
|
|
5366
|
+
'ai_search_namespaces is not supported by DeepSpace; declare one [[ai_search]] instance_name = "auto" instead'
|
|
5367
|
+
);
|
|
5368
|
+
}
|
|
5278
5369
|
const out = [];
|
|
5370
|
+
const vars = isObject(outputConfig.vars) ? outputConfig.vars : null;
|
|
5371
|
+
for (const [name, value] of Object.entries(vars ?? {})) {
|
|
5372
|
+
if (name === "DEEPSPACE_APP_ID" || name === "APP_NAME") continue;
|
|
5373
|
+
if (typeof value === "string") {
|
|
5374
|
+
out.push({ type: "plain_text", name, text: value });
|
|
5375
|
+
continue;
|
|
5376
|
+
}
|
|
5377
|
+
if (!isBindingJsonValue(value)) {
|
|
5378
|
+
throw new Error(`Wrangler var "${name}" is not a JSON-serializable value`);
|
|
5379
|
+
}
|
|
5380
|
+
out.push({ type: "json", name, json: value });
|
|
5381
|
+
}
|
|
5279
5382
|
const aiBinding = pluckString(outputConfig.ai, "binding");
|
|
5280
5383
|
if (aiBinding) out.push({ type: "ai", name: aiBinding });
|
|
5281
5384
|
for (const v of asObjectArray(outputConfig.vectorize)) {
|
|
@@ -5292,6 +5395,11 @@ function bindingManifestFromOutputConfig(outputConfig) {
|
|
|
5292
5395
|
...metric && (metric === "cosine" || metric === "euclidean" || metric === "dot-product") && { metric }
|
|
5293
5396
|
});
|
|
5294
5397
|
}
|
|
5398
|
+
for (const a of asObjectArray(outputConfig.ai_search)) {
|
|
5399
|
+
const name = pluckString(a, "binding");
|
|
5400
|
+
const instance_name = pluckString(a, "instance_name");
|
|
5401
|
+
if (name && instance_name) out.push({ type: "ai_search", name, instance_name });
|
|
5402
|
+
}
|
|
5295
5403
|
for (const r of asObjectArray(outputConfig.r2_buckets)) {
|
|
5296
5404
|
const name = pluckString(r, "binding");
|
|
5297
5405
|
const bucket_name = pluckString(r, "bucket_name");
|
|
@@ -5353,6 +5461,25 @@ function bindingManifestFromOutputConfig(outputConfig) {
|
|
|
5353
5461
|
function isObject(v) {
|
|
5354
5462
|
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
5355
5463
|
}
|
|
5464
|
+
function isBindingJsonValue(value, ancestors = /* @__PURE__ */ new Set()) {
|
|
5465
|
+
if (value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)) {
|
|
5466
|
+
return true;
|
|
5467
|
+
}
|
|
5468
|
+
if (typeof value !== "object") return false;
|
|
5469
|
+
if (ancestors.has(value)) return false;
|
|
5470
|
+
if (!Array.isArray(value)) {
|
|
5471
|
+
const prototype = Object.getPrototypeOf(value);
|
|
5472
|
+
if (prototype !== Object.prototype && prototype !== null) return false;
|
|
5473
|
+
}
|
|
5474
|
+
ancestors.add(value);
|
|
5475
|
+
const valid = Array.isArray(value) ? value.every((entry) => isBindingJsonValue(entry, ancestors)) : Object.values(value).every((entry) => isBindingJsonValue(entry, ancestors));
|
|
5476
|
+
ancestors.delete(value);
|
|
5477
|
+
return valid;
|
|
5478
|
+
}
|
|
5479
|
+
function envVarSizeError(name, serializedValue) {
|
|
5480
|
+
const size = new TextEncoder().encode(serializedValue).byteLength;
|
|
5481
|
+
return size <= MAX_ENV_VAR_BYTES ? null : `Environment variable binding "${name}" is ${size} bytes; maximum is ${MAX_ENV_VAR_BYTES}`;
|
|
5482
|
+
}
|
|
5356
5483
|
function pluckString(v, key) {
|
|
5357
5484
|
if (!isObject(v)) return null;
|
|
5358
5485
|
const val = v[key];
|
|
@@ -5827,218 +5954,8 @@ var DIRECTORY_SCHEMAS = [
|
|
|
5827
5954
|
postsSchema
|
|
5828
5955
|
];
|
|
5829
5956
|
|
|
5830
|
-
// src/server/schemas/workspace.ts
|
|
5831
|
-
var workspaceTeamsSchema = {
|
|
5832
|
-
name: "teams",
|
|
5833
|
-
columns: [
|
|
5834
|
-
{ name: "Name", storage: "text", interpretation: "plain" },
|
|
5835
|
-
{ name: "CreatedBy", storage: "text", interpretation: "plain" },
|
|
5836
|
-
{ name: "IsOpen", storage: "number", interpretation: { kind: "boolean" } }
|
|
5837
|
-
],
|
|
5838
|
-
teamField: "_rowId",
|
|
5839
|
-
permissions: {
|
|
5840
|
-
admin: { read: true, create: true, update: true, delete: true },
|
|
5841
|
-
member: { read: "team", create: true, update: "team", delete: "own" }
|
|
5842
|
-
}
|
|
5843
|
-
};
|
|
5844
|
-
var workspaceTeamMembersSchema = {
|
|
5845
|
-
name: "team_members",
|
|
5846
|
-
columns: [
|
|
5847
|
-
{ name: "TeamId", storage: "text", interpretation: "plain" },
|
|
5848
|
-
{ name: "UserId", storage: "text", interpretation: "plain" },
|
|
5849
|
-
{ name: "RoleInTeam", storage: "text", interpretation: { kind: "select", options: ["admin", "member"] } },
|
|
5850
|
-
{ name: "JoinedAt", storage: "text", interpretation: { kind: "datetime" } },
|
|
5851
|
-
{ name: "Email", storage: "text", interpretation: { kind: "email" } },
|
|
5852
|
-
{ name: "Status", storage: "text", interpretation: { kind: "select", options: ["active", "invited"] } }
|
|
5853
|
-
],
|
|
5854
|
-
teamField: "TeamId",
|
|
5855
|
-
permissions: {
|
|
5856
|
-
admin: { read: true, create: true, update: true, delete: true },
|
|
5857
|
-
member: { read: "team", create: true, update: "team", delete: "team" }
|
|
5858
|
-
}
|
|
5859
|
-
};
|
|
5860
|
-
var workspaceTasksSchema = {
|
|
5861
|
-
name: "tasks",
|
|
5862
|
-
columns: [
|
|
5863
|
-
{ name: "TeamId", storage: "text", interpretation: "plain" },
|
|
5864
|
-
{ name: "Title", storage: "text", interpretation: "plain" },
|
|
5865
|
-
{ name: "Notes", storage: "text", interpretation: "plain" },
|
|
5866
|
-
{ name: "Completed", storage: "number", interpretation: { kind: "boolean" } },
|
|
5867
|
-
{ name: "CompletedAt", storage: "number", interpretation: "plain" },
|
|
5868
|
-
{ name: "Deleted", storage: "number", interpretation: { kind: "boolean" } },
|
|
5869
|
-
{ name: "DeletedAt", storage: "number", interpretation: "plain" },
|
|
5870
|
-
{ name: "Priority", storage: "text", interpretation: { kind: "select", options: ["none", "low", "medium", "high"] } },
|
|
5871
|
-
{ name: "DueDate", storage: "text", interpretation: "plain" },
|
|
5872
|
-
{ name: "ProjectId", storage: "text", interpretation: "plain" },
|
|
5873
|
-
{ name: "KanbanStatus", storage: "text", interpretation: { kind: "select", options: ["backlog", "ready", "in_progress", "review", "done"] } },
|
|
5874
|
-
{ name: "Order", storage: "number", interpretation: "plain" },
|
|
5875
|
-
{ name: "AssignedUser", storage: "text", interpretation: { kind: "json" } },
|
|
5876
|
-
{ name: "AssignedBy", storage: "text", interpretation: { kind: "json" } },
|
|
5877
|
-
{ name: "TagIds", storage: "text", interpretation: { kind: "json" } },
|
|
5878
|
-
{ name: "CreatedAt", storage: "number", interpretation: "plain" }
|
|
5879
|
-
],
|
|
5880
|
-
teamField: "TeamId",
|
|
5881
|
-
permissions: {
|
|
5882
|
-
admin: { read: true, create: true, update: true, delete: true },
|
|
5883
|
-
member: { read: "team", create: true, update: "team", delete: "team" },
|
|
5884
|
-
viewer: { read: false, create: false, update: false, delete: false }
|
|
5885
|
-
}
|
|
5886
|
-
};
|
|
5887
|
-
var workspaceProjectsSchema = {
|
|
5888
|
-
name: "projects",
|
|
5889
|
-
columns: [
|
|
5890
|
-
{ name: "TeamId", storage: "text", interpretation: "plain" },
|
|
5891
|
-
{ name: "Title", storage: "text", interpretation: "plain" },
|
|
5892
|
-
{ name: "Notes", storage: "text", interpretation: "plain" },
|
|
5893
|
-
{ name: "Color", storage: "text", interpretation: "plain" },
|
|
5894
|
-
{ name: "ParentId", storage: "text", interpretation: "plain" },
|
|
5895
|
-
{ name: "Order", storage: "number", interpretation: "plain" },
|
|
5896
|
-
{ name: "CreatedAt", storage: "number", interpretation: "plain" }
|
|
5897
|
-
],
|
|
5898
|
-
teamField: "TeamId",
|
|
5899
|
-
permissions: {
|
|
5900
|
-
admin: { read: true, create: true, update: true, delete: true },
|
|
5901
|
-
member: { read: "team", create: true, update: "team", delete: "team" },
|
|
5902
|
-
viewer: { read: false, create: false, update: false, delete: false }
|
|
5903
|
-
}
|
|
5904
|
-
};
|
|
5905
|
-
var workspaceTagsSchema = {
|
|
5906
|
-
name: "tags",
|
|
5907
|
-
columns: [
|
|
5908
|
-
{ name: "TeamId", storage: "text", interpretation: "plain" },
|
|
5909
|
-
{ name: "Name", storage: "text", interpretation: "plain" },
|
|
5910
|
-
{ name: "Color", storage: "text", interpretation: "plain" },
|
|
5911
|
-
{ name: "CreatedAt", storage: "number", interpretation: "plain" }
|
|
5912
|
-
],
|
|
5913
|
-
teamField: "TeamId",
|
|
5914
|
-
permissions: {
|
|
5915
|
-
admin: { read: true, create: true, update: true, delete: true },
|
|
5916
|
-
member: { read: "team", create: true, update: "team", delete: "team" },
|
|
5917
|
-
viewer: { read: false, create: false, update: false, delete: false }
|
|
5918
|
-
}
|
|
5919
|
-
};
|
|
5920
|
-
var workspacePeopleSchema = {
|
|
5921
|
-
name: "people",
|
|
5922
|
-
columns: [
|
|
5923
|
-
{ name: "Name", storage: "text", interpretation: "plain" },
|
|
5924
|
-
{ name: "Email", storage: "text", interpretation: { kind: "email" } },
|
|
5925
|
-
{ name: "Type", storage: "text", interpretation: { kind: "select", options: ["employee", "customer", "vendor", "contact"] } },
|
|
5926
|
-
{ name: "Status", storage: "text", interpretation: { kind: "select", options: ["active", "inactive", "archived"] } },
|
|
5927
|
-
{ name: "CompanyId", storage: "text", interpretation: "plain" },
|
|
5928
|
-
{ name: "Department", storage: "text", interpretation: "plain" },
|
|
5929
|
-
{ name: "Title", storage: "text", interpretation: "plain" },
|
|
5930
|
-
{ name: "LastContactedAt", storage: "text", interpretation: { kind: "datetime" } },
|
|
5931
|
-
{ name: "Metadata", storage: "text", interpretation: { kind: "json" } }
|
|
5932
|
-
],
|
|
5933
|
-
permissions: {
|
|
5934
|
-
"*": { read: true, create: true, update: true, delete: false },
|
|
5935
|
-
admin: { read: true, create: true, update: true, delete: true }
|
|
5936
|
-
}
|
|
5937
|
-
};
|
|
5938
|
-
var workspaceTransactionsSchema = {
|
|
5939
|
-
name: "transactions",
|
|
5940
|
-
columns: [
|
|
5941
|
-
{ name: "Type", storage: "text", interpretation: { kind: "select", options: ["revenue", "expense", "payment", "transfer", "invoice", "refund"] } },
|
|
5942
|
-
{ name: "Amount", storage: "number", interpretation: { kind: "currency", symbol: "$", decimals: 2 } },
|
|
5943
|
-
{ name: "Currency", storage: "text", interpretation: "plain" },
|
|
5944
|
-
{ name: "Description", storage: "text", interpretation: "plain" },
|
|
5945
|
-
{ name: "Category", storage: "text", interpretation: "plain" },
|
|
5946
|
-
{ name: "CounterpartyRef", storage: "text", interpretation: "plain" },
|
|
5947
|
-
{ name: "OrderRef", storage: "text", interpretation: "plain" },
|
|
5948
|
-
{ name: "Account", storage: "text", interpretation: "plain" }
|
|
5949
|
-
],
|
|
5950
|
-
permissions: {
|
|
5951
|
-
"*": { read: true, create: true, update: true, delete: false },
|
|
5952
|
-
admin: { read: true, create: true, update: true, delete: true }
|
|
5953
|
-
}
|
|
5954
|
-
};
|
|
5955
|
-
var workspaceAccountsSchema = {
|
|
5956
|
-
name: "accounts",
|
|
5957
|
-
columns: [
|
|
5958
|
-
{ name: "Name", storage: "text", interpretation: "plain" },
|
|
5959
|
-
{ name: "Balance", storage: "number", interpretation: { kind: "currency", symbol: "$", decimals: 2 } },
|
|
5960
|
-
{ name: "Currency", storage: "text", interpretation: "plain" }
|
|
5961
|
-
],
|
|
5962
|
-
permissions: {
|
|
5963
|
-
"*": { read: true, create: true, update: true, delete: false },
|
|
5964
|
-
admin: { read: true, create: true, update: true, delete: true }
|
|
5965
|
-
}
|
|
5966
|
-
};
|
|
5967
|
-
var workspaceContentSharesSchema = {
|
|
5968
|
-
name: "content_shares",
|
|
5969
|
-
ownerField: "OwnerId",
|
|
5970
|
-
columns: [
|
|
5971
|
-
{ name: "ContentType", storage: "text", interpretation: { kind: "select", options: ["document", "slide", "spreadsheet"] } },
|
|
5972
|
-
{ name: "ContentId", storage: "text", interpretation: "plain" },
|
|
5973
|
-
{ name: "OwnerId", storage: "text", interpretation: "plain" },
|
|
5974
|
-
{ name: "OwnerName", storage: "text", interpretation: "plain" },
|
|
5975
|
-
{ name: "Title", storage: "text", interpretation: "plain" },
|
|
5976
|
-
{ name: "ShareType", storage: "text", interpretation: { kind: "select", options: ["self", "channel", "team", "direct", "link", "org"] } },
|
|
5977
|
-
{ name: "ShareTarget", storage: "text", interpretation: "plain" },
|
|
5978
|
-
{ name: "Permission", storage: "text", interpretation: { kind: "select", options: ["view", "edit"] } },
|
|
5979
|
-
{ name: "SharedAt", storage: "text", interpretation: { kind: "datetime" } },
|
|
5980
|
-
{ name: "SharedBy", storage: "text", interpretation: "plain" },
|
|
5981
|
-
{ name: "SourceApp", storage: "text", interpretation: "plain" },
|
|
5982
|
-
{ name: "WordCount", storage: "number", interpretation: "plain" },
|
|
5983
|
-
{ name: "LastEditedAt", storage: "text", interpretation: { kind: "datetime" } }
|
|
5984
|
-
],
|
|
5985
|
-
permissions: {
|
|
5986
|
-
"*": { read: true, create: true, update: true, delete: "own" },
|
|
5987
|
-
admin: { read: true, create: true, update: true, delete: true }
|
|
5988
|
-
}
|
|
5989
|
-
};
|
|
5990
|
-
var workspaceFormResponsesSchema = {
|
|
5991
|
-
name: "form_responses",
|
|
5992
|
-
columns: [
|
|
5993
|
-
{ name: "FormId", storage: "text", interpretation: "plain" },
|
|
5994
|
-
{ name: "FormTitle", storage: "text", interpretation: "plain" },
|
|
5995
|
-
{ name: "RespondentId", storage: "text", interpretation: "plain" },
|
|
5996
|
-
{ name: "RespondentEmail", storage: "text", interpretation: { kind: "email" } },
|
|
5997
|
-
{ name: "RespondentName", storage: "text", interpretation: "plain" },
|
|
5998
|
-
{ name: "Data", storage: "text", interpretation: { kind: "json" } },
|
|
5999
|
-
{ name: "SubmittedAt", storage: "text", interpretation: { kind: "datetime" } }
|
|
6000
|
-
],
|
|
6001
|
-
permissions: {
|
|
6002
|
-
"*": { read: true, create: true, update: false, delete: false },
|
|
6003
|
-
admin: { read: true, create: true, update: true, delete: true }
|
|
6004
|
-
}
|
|
6005
|
-
};
|
|
6006
|
-
var workspaceEmailHandlesSchema = {
|
|
6007
|
-
name: "email_handles",
|
|
6008
|
-
columns: [
|
|
6009
|
-
{ name: "UserId", storage: "text", interpretation: "plain" },
|
|
6010
|
-
{ name: "Handle", storage: "text", interpretation: "plain" },
|
|
6011
|
-
{ name: "EmailAddress", storage: "text", interpretation: { kind: "email" } },
|
|
6012
|
-
{ name: "Status", storage: "text", interpretation: { kind: "select", options: ["active", "disabled"] } }
|
|
6013
|
-
],
|
|
6014
|
-
uniqueOn: ["Handle"],
|
|
6015
|
-
ownerField: "UserId",
|
|
6016
|
-
permissions: {
|
|
6017
|
-
"*": { read: true, create: true, update: "own", delete: false },
|
|
6018
|
-
admin: { read: true, create: true, update: true, delete: true }
|
|
6019
|
-
}
|
|
6020
|
-
};
|
|
6021
|
-
var WORKSPACE_SCHEMAS = [
|
|
6022
|
-
workspaceTeamsSchema,
|
|
6023
|
-
workspaceTeamMembersSchema,
|
|
6024
|
-
workspaceTasksSchema,
|
|
6025
|
-
workspaceProjectsSchema,
|
|
6026
|
-
workspaceTagsSchema,
|
|
6027
|
-
workspacePeopleSchema,
|
|
6028
|
-
workspaceTransactionsSchema,
|
|
6029
|
-
workspaceAccountsSchema,
|
|
6030
|
-
workspaceContentSharesSchema,
|
|
6031
|
-
workspaceFormResponsesSchema,
|
|
6032
|
-
workspaceEmailHandlesSchema
|
|
6033
|
-
];
|
|
6034
|
-
|
|
6035
5957
|
// src/server/schemas/shared-do.ts
|
|
6036
5958
|
var GLOBAL_DO_TYPES = [
|
|
6037
|
-
{
|
|
6038
|
-
name: "workspace",
|
|
6039
|
-
schemas: WORKSPACE_SCHEMAS,
|
|
6040
|
-
description: "Shared workspace data (teams, tasks, projects, people, ledger)"
|
|
6041
|
-
},
|
|
6042
5959
|
{
|
|
6043
5960
|
name: "conv",
|
|
6044
5961
|
schemas: [...CONVERSATION_SCHEMAS, ...VOTING_SCHEMAS],
|
|
@@ -6066,6 +5983,9 @@ var APP_MIGRATION_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/;
|
|
|
6066
5983
|
var MAX_APP_MIGRATION_IDS = 256;
|
|
6067
5984
|
var WORKER_OWNED_NOT_FOUND_MIGRATION_ID = "2026-08-worker-owned-not-found";
|
|
6068
5985
|
var SECURE_ROOM_BOUNDARIES_MIGRATION_ID = "2026-08-secure-room-boundaries";
|
|
5986
|
+
var ACTION_TOOLS_DELETE_WHERE_MIGRATION_ID = "2026-08-action-tools-delete-where";
|
|
5987
|
+
var BUILD_INJECTED_APP_ID_MIGRATION_ID = "2026-08-build-injected-app-id";
|
|
5988
|
+
var ACTION_ROUTES_BEARER_GUARD_MIGRATION_ID = "2026-08-action-routes-bearer-guard";
|
|
6069
5989
|
function validateAppMigrationIds(value) {
|
|
6070
5990
|
if (!Array.isArray(value)) {
|
|
6071
5991
|
return { valid: false, reason: "app migrations must be a JSON string array" };
|
|
@@ -6934,8 +6854,8 @@ function overPartBound(bytes) {
|
|
|
6934
6854
|
if (bytes <= UPLOAD_PART_BYTES) return null;
|
|
6935
6855
|
return fail2(413, oversizePartMessage(bytes), "too_large", { maxBytes: UPLOAD_PART_BYTES });
|
|
6936
6856
|
}
|
|
6937
|
-
function declaredLength(
|
|
6938
|
-
const raw =
|
|
6857
|
+
function declaredLength(request2, what) {
|
|
6858
|
+
const raw = request2.headers.get("content-length");
|
|
6939
6859
|
const declared = raw === null ? NaN : Number(raw);
|
|
6940
6860
|
if (!Number.isSafeInteger(declared) || declared < 0) {
|
|
6941
6861
|
return fail2(
|
|
@@ -7013,7 +6933,7 @@ function multipartFailure(error) {
|
|
|
7013
6933
|
}
|
|
7014
6934
|
function createScopedR2Handler(config) {
|
|
7015
6935
|
const requireAuthForMutations = config.requireAuthForMutations ?? true;
|
|
7016
|
-
return async (
|
|
6936
|
+
return async (request2, url, bucket, auth) => {
|
|
7017
6937
|
let subpathRaw;
|
|
7018
6938
|
try {
|
|
7019
6939
|
subpathRaw = decodeURIComponent(url.pathname.replace("/api/files", "").replace(/^\//, ""));
|
|
@@ -7042,26 +6962,26 @@ function createScopedR2Handler(config) {
|
|
|
7042
6962
|
}
|
|
7043
6963
|
const prefix = result.prefix;
|
|
7044
6964
|
const excludedPrefixes = result.excludedPrefixes ?? [];
|
|
7045
|
-
if (isFilesVerbPath(
|
|
6965
|
+
if (isFilesVerbPath(request2.method, subpath)) {
|
|
7046
6966
|
if (requireAuthForMutations && !auth.userId) {
|
|
7047
|
-
await
|
|
6967
|
+
await request2.body?.cancel().catch(() => void 0);
|
|
7048
6968
|
return fail2(401, "Authentication required", "unauthorized");
|
|
7049
6969
|
}
|
|
7050
|
-
return subpath === "upload" ? handleUpload(
|
|
6970
|
+
return subpath === "upload" ? handleUpload(request2, url, bucket, prefix, excludedPrefixes, auth, scope) : handleMultipart(request2, url, bucket, prefix, excludedPrefixes, auth, scope, subpath);
|
|
7051
6971
|
}
|
|
7052
|
-
if (!subpath &&
|
|
6972
|
+
if (!subpath && request2.method === "GET") {
|
|
7053
6973
|
return handleList(bucket, prefix, excludedPrefixes, url, scope, auth);
|
|
7054
6974
|
}
|
|
7055
|
-
if (subpath &&
|
|
6975
|
+
if (subpath && request2.method === "GET") {
|
|
7056
6976
|
if (!isKeyWithinScope(subpath, prefix, excludedPrefixes)) {
|
|
7057
6977
|
return Response.json(
|
|
7058
6978
|
{ error: "Access denied: key outside scope" },
|
|
7059
6979
|
{ status: 403, headers: CORS_HEADERS4 }
|
|
7060
6980
|
);
|
|
7061
6981
|
}
|
|
7062
|
-
return handleDownload(
|
|
6982
|
+
return handleDownload(request2, bucket, subpath, scope);
|
|
7063
6983
|
}
|
|
7064
|
-
if (subpath &&
|
|
6984
|
+
if (subpath && request2.method === "DELETE") {
|
|
7065
6985
|
if (requireAuthForMutations && !auth.userId) {
|
|
7066
6986
|
return Response.json(
|
|
7067
6987
|
{ error: "Authentication required" },
|
|
@@ -7087,18 +7007,18 @@ function createScopedR2Handler(config) {
|
|
|
7087
7007
|
return Response.json({ error: "Method not allowed" }, { status: 405, headers: CORS_HEADERS4 });
|
|
7088
7008
|
};
|
|
7089
7009
|
}
|
|
7090
|
-
async function handleUpload(
|
|
7010
|
+
async function handleUpload(request2, url, bucket, prefix, excludedPrefixes, auth, scope) {
|
|
7091
7011
|
try {
|
|
7092
|
-
const declaredBytes = declaredLength(
|
|
7012
|
+
const declaredBytes = declaredLength(request2, "File uploads");
|
|
7093
7013
|
if (declaredBytes instanceof Response) return declaredBytes;
|
|
7094
7014
|
const tooLargeRequest = overRequestBound(declaredBytes, 8 * 1024);
|
|
7095
7015
|
if (tooLargeRequest) return tooLargeRequest;
|
|
7096
|
-
const contentType =
|
|
7016
|
+
const contentType = request2.headers.get("content-type") || "";
|
|
7097
7017
|
let fileData;
|
|
7098
7018
|
let fileName;
|
|
7099
7019
|
let rawMimeType;
|
|
7100
7020
|
if (contentType.includes("multipart/form-data")) {
|
|
7101
|
-
const formData = await
|
|
7021
|
+
const formData = await request2.formData();
|
|
7102
7022
|
const file = formData.get("file");
|
|
7103
7023
|
if (!file) return fail2(400, "No file provided", "bad_request");
|
|
7104
7024
|
const tooBig = overRequestBound(file.size);
|
|
@@ -7107,7 +7027,7 @@ async function handleUpload(request, url, bucket, prefix, excludedPrefixes, auth
|
|
|
7107
7027
|
fileName = formData.get("name")?.toString() || file.name;
|
|
7108
7028
|
rawMimeType = file.type || "application/octet-stream";
|
|
7109
7029
|
} else {
|
|
7110
|
-
const body = await
|
|
7030
|
+
const body = await request2.json();
|
|
7111
7031
|
if (!body.data || !body.name) return fail2(400, "Missing data or name", "bad_request");
|
|
7112
7032
|
const base64Data = body.data.replace(/^data:[^;]+;base64,/, "");
|
|
7113
7033
|
const tooBig = overRequestBound(Math.floor(base64Data.length * 3 / 4));
|
|
@@ -7188,15 +7108,15 @@ function storedFileJson(key, prefix, name, url, scope) {
|
|
|
7188
7108
|
);
|
|
7189
7109
|
}
|
|
7190
7110
|
var MAX_CONTROL_BODY_BYTES = 64 * 1024;
|
|
7191
|
-
async function readControlJson(
|
|
7192
|
-
const declared = declaredLength(
|
|
7111
|
+
async function readControlJson(request2) {
|
|
7112
|
+
const declared = declaredLength(request2, "This request");
|
|
7193
7113
|
if (declared instanceof Response) return declared;
|
|
7194
7114
|
if (declared > MAX_CONTROL_BODY_BYTES) {
|
|
7195
7115
|
return fail2(413, `Request body exceeds ${MAX_CONTROL_BODY_BYTES} bytes`, "too_large");
|
|
7196
7116
|
}
|
|
7197
7117
|
let parsed;
|
|
7198
7118
|
try {
|
|
7199
|
-
parsed = await
|
|
7119
|
+
parsed = await request2.json();
|
|
7200
7120
|
} catch {
|
|
7201
7121
|
return fail2(400, "Body must be JSON", "bad_request");
|
|
7202
7122
|
}
|
|
@@ -7248,7 +7168,7 @@ function multipartLayoutError(declaredBytes) {
|
|
|
7248
7168
|
{ partSize: UPLOAD_PART_BYTES, partCount: multipartPartCount(declaredBytes) }
|
|
7249
7169
|
);
|
|
7250
7170
|
}
|
|
7251
|
-
async function streamPart(
|
|
7171
|
+
async function streamPart(upload2, partNumber, body, expectedBytes) {
|
|
7252
7172
|
let observedBytes = 0;
|
|
7253
7173
|
const sized = new FixedLengthStream(expectedBytes);
|
|
7254
7174
|
const pumped = body.pipeThrough(
|
|
@@ -7260,7 +7180,7 @@ async function streamPart(upload, partNumber, body, expectedBytes) {
|
|
|
7260
7180
|
})
|
|
7261
7181
|
).pipeTo(sized.writable);
|
|
7262
7182
|
const [uploaded, drained] = await Promise.allSettled([
|
|
7263
|
-
|
|
7183
|
+
upload2.uploadPart(partNumber, sized.readable),
|
|
7264
7184
|
pumped
|
|
7265
7185
|
]);
|
|
7266
7186
|
if (uploaded.status === "rejected" || drained.status === "rejected") {
|
|
@@ -7269,20 +7189,20 @@ async function streamPart(upload, partNumber, body, expectedBytes) {
|
|
|
7269
7189
|
}
|
|
7270
7190
|
return { ok: true, part: uploaded.value };
|
|
7271
7191
|
}
|
|
7272
|
-
async function handleMultipart(
|
|
7273
|
-
if (subpath === "multipart" &&
|
|
7274
|
-
return multipartInit(
|
|
7192
|
+
async function handleMultipart(request2, url, bucket, prefix, excludedPrefixes, auth, scope, subpath) {
|
|
7193
|
+
if (subpath === "multipart" && request2.method === "POST") {
|
|
7194
|
+
return multipartInit(request2, url, bucket, prefix, excludedPrefixes, auth);
|
|
7275
7195
|
}
|
|
7276
7196
|
if (subpath === "multipart/part") {
|
|
7277
|
-
return multipartPart(
|
|
7197
|
+
return multipartPart(request2, url, bucket, prefix, excludedPrefixes, auth);
|
|
7278
7198
|
}
|
|
7279
7199
|
if (subpath === "multipart/complete") {
|
|
7280
|
-
return multipartComplete(
|
|
7200
|
+
return multipartComplete(request2, url, bucket, prefix, excludedPrefixes, auth, scope);
|
|
7281
7201
|
}
|
|
7282
7202
|
return multipartAbort(url, bucket, prefix, excludedPrefixes, auth);
|
|
7283
7203
|
}
|
|
7284
|
-
async function multipartInit(
|
|
7285
|
-
const body = await readControlJson(
|
|
7204
|
+
async function multipartInit(request2, url, bucket, prefix, excludedPrefixes, auth) {
|
|
7205
|
+
const body = await readControlJson(request2);
|
|
7286
7206
|
if (body instanceof Response) return body;
|
|
7287
7207
|
const size = body.size;
|
|
7288
7208
|
if (!Number.isSafeInteger(size) || size <= 0) {
|
|
@@ -7323,9 +7243,9 @@ async function multipartInit(request, url, bucket, prefix, excludedPrefixes, aut
|
|
|
7323
7243
|
"request_id_conflict"
|
|
7324
7244
|
);
|
|
7325
7245
|
}
|
|
7326
|
-
let
|
|
7246
|
+
let upload2;
|
|
7327
7247
|
try {
|
|
7328
|
-
|
|
7248
|
+
upload2 = await bucket.createMultipartUpload(located.key, {
|
|
7329
7249
|
httpMetadata: { contentType: mimeType },
|
|
7330
7250
|
customMetadata: fileMetadata(name, auth.userId, requestId, size)
|
|
7331
7251
|
});
|
|
@@ -7337,7 +7257,7 @@ async function multipartInit(request, url, bucket, prefix, excludedPrefixes, aut
|
|
|
7337
7257
|
body: JSON.stringify({
|
|
7338
7258
|
version: 1,
|
|
7339
7259
|
reservationId: requestId,
|
|
7340
|
-
uploadId:
|
|
7260
|
+
uploadId: upload2.uploadId,
|
|
7341
7261
|
objectKey: located.key,
|
|
7342
7262
|
uploadKey: located.subkey,
|
|
7343
7263
|
admitted: false,
|
|
@@ -7351,7 +7271,7 @@ async function multipartInit(request, url, bucket, prefix, excludedPrefixes, aut
|
|
|
7351
7271
|
const reservedBytes = Math.max(size, 5 * 1024 * 1024);
|
|
7352
7272
|
const admission = await admitStorage(bucket, auth.storage, reservedBytes, 0, marker);
|
|
7353
7273
|
if (admission instanceof Response) {
|
|
7354
|
-
await
|
|
7274
|
+
await upload2.abort().catch(() => void 0);
|
|
7355
7275
|
if (admission.status === 409 && auth.storage) {
|
|
7356
7276
|
const winner = await readMultipartReservation(bucket, auth.storage, requestId);
|
|
7357
7277
|
if (winner && winner !== "invalid" && winner.objectKey === located.key && winner.uploadKey === located.subkey && winner.declaredBytes === size && winner.admitted) {
|
|
@@ -7361,11 +7281,11 @@ async function multipartInit(request, url, bucket, prefix, excludedPrefixes, aut
|
|
|
7361
7281
|
return admission;
|
|
7362
7282
|
}
|
|
7363
7283
|
if (auth.storage && !await activateMultipartReservation(bucket, auth.storage, requestId)) {
|
|
7364
|
-
await
|
|
7284
|
+
await upload2.abort().catch(() => void 0);
|
|
7365
7285
|
await releaseQuotaMarker(bucket, auth.storage, admission.markerKey, true);
|
|
7366
7286
|
return quotaUnavailable("This upload reservation could not be finalized. Retry shortly.");
|
|
7367
7287
|
}
|
|
7368
|
-
return multipartSessionJson(
|
|
7288
|
+
return multipartSessionJson(upload2.uploadId, located.subkey, requestId);
|
|
7369
7289
|
}
|
|
7370
7290
|
async function abandonMultipart(bucket, session, auth, reservation) {
|
|
7371
7291
|
let result = "aborted";
|
|
@@ -7383,12 +7303,12 @@ async function abandonMultipart(bucket, session, auth, reservation) {
|
|
|
7383
7303
|
}
|
|
7384
7304
|
return result;
|
|
7385
7305
|
}
|
|
7386
|
-
async function multipartPart(
|
|
7306
|
+
async function multipartPart(request2, url, bucket, prefix, excludedPrefixes, auth) {
|
|
7387
7307
|
const refuse = async (response) => {
|
|
7388
|
-
await
|
|
7308
|
+
await request2.body?.cancel().catch(() => void 0);
|
|
7389
7309
|
return response;
|
|
7390
7310
|
};
|
|
7391
|
-
const declaredBytes = declaredLength(
|
|
7311
|
+
const declaredBytes = declaredLength(request2, "Part uploads");
|
|
7392
7312
|
if (declaredBytes instanceof Response) return refuse(declaredBytes);
|
|
7393
7313
|
const tooBig = overPartBound(declaredBytes);
|
|
7394
7314
|
if (tooBig) return refuse(tooBig);
|
|
@@ -7400,7 +7320,7 @@ async function multipartPart(request, url, bucket, prefix, excludedPrefixes, aut
|
|
|
7400
7320
|
}
|
|
7401
7321
|
const session = resolveSession(url, prefix, excludedPrefixes);
|
|
7402
7322
|
if (session instanceof Response) return refuse(session);
|
|
7403
|
-
if (!
|
|
7323
|
+
if (!request2.body) return fail2(400, "A part body is required", "bad_request");
|
|
7404
7324
|
const reservation = await readMultipartReservation(bucket, auth.storage, session.reservationId);
|
|
7405
7325
|
if (reservation === "invalid") {
|
|
7406
7326
|
return refuse(quotaUnavailable("This upload reservation could not be read. Retry shortly."));
|
|
@@ -7442,8 +7362,8 @@ async function multipartPart(request, url, bucket, prefix, excludedPrefixes, aut
|
|
|
7442
7362
|
if (refreshed === "busy") {
|
|
7443
7363
|
return refuse(quotaUnavailable("This upload reservation is busy. Retry the part shortly."));
|
|
7444
7364
|
}
|
|
7445
|
-
const
|
|
7446
|
-
const stored = await streamPart(
|
|
7365
|
+
const upload2 = bucket.resumeMultipartUpload(session.key, session.uploadId);
|
|
7366
|
+
const stored = await streamPart(upload2, partNumber, request2.body, declaredBytes);
|
|
7447
7367
|
if (!stored.ok) {
|
|
7448
7368
|
if (r2ErrorCode(stored.error) === 10024 && reservation) {
|
|
7449
7369
|
await releaseQuotaMarker(bucket, auth.storage, reservation.key, true);
|
|
@@ -7499,8 +7419,8 @@ async function finishCompletedMultipart(bucket, session, reservation, object, au
|
|
|
7499
7419
|
const name = object.customMetadata?.originalName ?? session.uploadKey.split("/").pop() ?? "file";
|
|
7500
7420
|
return storedFileJson(session.key, prefix, name, url, scope);
|
|
7501
7421
|
}
|
|
7502
|
-
async function multipartComplete(
|
|
7503
|
-
const body = await readControlJson(
|
|
7422
|
+
async function multipartComplete(request2, url, bucket, prefix, excludedPrefixes, auth, scope) {
|
|
7423
|
+
const body = await readControlJson(request2);
|
|
7504
7424
|
if (body instanceof Response) return body;
|
|
7505
7425
|
const session = resolveSession(url, prefix, excludedPrefixes);
|
|
7506
7426
|
if (session instanceof Response) return session;
|
|
@@ -7659,7 +7579,7 @@ async function handleList(bucket, prefix, excludedPrefixes, url, scope, auth) {
|
|
|
7659
7579
|
{ headers: CORS_HEADERS4 }
|
|
7660
7580
|
);
|
|
7661
7581
|
}
|
|
7662
|
-
async function handleDownload(
|
|
7582
|
+
async function handleDownload(request2, bucket, key, scope) {
|
|
7663
7583
|
const object = await bucket.get(key);
|
|
7664
7584
|
if (!object) {
|
|
7665
7585
|
return Response.json({ error: "File not found" }, { status: 404, headers: CORS_HEADERS4 });
|
|
@@ -7681,7 +7601,7 @@ async function handleDownload(request, bucket, key, scope) {
|
|
|
7681
7601
|
contentDisposition(originalName, mustDownload ? "attachment" : "inline")
|
|
7682
7602
|
);
|
|
7683
7603
|
if (mustDownload) headers.set("Content-Security-Policy", "sandbox; default-src 'none'");
|
|
7684
|
-
if (scope === "app" &&
|
|
7604
|
+
if (scope === "app" && request2.headers.get("if-none-match") === object.httpEtag) {
|
|
7685
7605
|
return new Response(null, { status: 304, headers });
|
|
7686
7606
|
}
|
|
7687
7607
|
return new Response(object.body, { headers });
|
|
@@ -8134,8 +8054,8 @@ async function composioTools(env, options) {
|
|
|
8134
8054
|
if (!list.ok || !list.success) {
|
|
8135
8055
|
throw new Error(`composioTools: failed to list Composio tools: ${list.detail}`);
|
|
8136
8056
|
}
|
|
8137
|
-
const
|
|
8138
|
-
const items = Array.isArray(
|
|
8057
|
+
const listItems2 = list.data?.items;
|
|
8058
|
+
const items = Array.isArray(listItems2) ? listItems2 : [];
|
|
8139
8059
|
const tools = {};
|
|
8140
8060
|
for (const item of items) {
|
|
8141
8061
|
if (!item?.slug) continue;
|
|
@@ -8720,18 +8640,272 @@ var COST_RATES = {
|
|
|
8720
8640
|
queriedPerDim: VECTORIZE_QUERIED_USD_PER_M_DIMS / 1e6,
|
|
8721
8641
|
/** USD per stored dimension per month. */
|
|
8722
8642
|
storedPerDimPerMonth: VECTORIZE_STORED_USD_PER_100M_DIMS / 1e8
|
|
8643
|
+
},
|
|
8644
|
+
aiSearch: {
|
|
8645
|
+
ingestPerToken: 0.75 / 1e6,
|
|
8646
|
+
ingestImagePerToken: 0.5 / 1e6,
|
|
8647
|
+
storagePerByteMonth: 2 / 1e9,
|
|
8648
|
+
hybridOrSemanticPerQuery: 0.75 / 1e3,
|
|
8649
|
+
fulltextPerQuery: 0.1 / 1e3
|
|
8723
8650
|
}
|
|
8724
8651
|
};
|
|
8725
8652
|
function priceBindingUsageEvent(kind, op, units) {
|
|
8653
|
+
if (!Number.isFinite(units) || units < 0) return 0;
|
|
8726
8654
|
if (kind === "ai" && op === "input") {
|
|
8727
8655
|
return units * COST_RATES.ai.embedInputPerChar;
|
|
8728
8656
|
}
|
|
8729
8657
|
if (kind === "vectorize" && op === "query") {
|
|
8730
8658
|
return units * COST_RATES.vectorize.queriedPerDim;
|
|
8731
8659
|
}
|
|
8660
|
+
if (kind === "ai_search") {
|
|
8661
|
+
if (op === "ingest") return units * COST_RATES.aiSearch.ingestPerToken;
|
|
8662
|
+
if (op === "ingest-image") return units * COST_RATES.aiSearch.ingestImagePerToken;
|
|
8663
|
+
if (op === "storage") return units * COST_RATES.aiSearch.storagePerByteMonth;
|
|
8664
|
+
if (op === "search-hybrid" || op === "search-semantic")
|
|
8665
|
+
return units * COST_RATES.aiSearch.hybridOrSemanticPerQuery;
|
|
8666
|
+
if (op === "search-fulltext") return units * COST_RATES.aiSearch.fulltextPerQuery;
|
|
8667
|
+
}
|
|
8732
8668
|
return 0;
|
|
8733
8669
|
}
|
|
8734
8670
|
|
|
8671
|
+
// src/server/utils/knowledge-base.ts
|
|
8672
|
+
var MAX_UPLOAD_BYTES = 4 * 1024 * 1024;
|
|
8673
|
+
var TEXT_MIME = /^(text\/|application\/(json|xml|javascript|x-(javascript|typescript)))/iu;
|
|
8674
|
+
var MAX_QUERY_CHARS = 4096;
|
|
8675
|
+
function hasControlChar(value) {
|
|
8676
|
+
for (let i = 0; i < value.length; i++) {
|
|
8677
|
+
const code = value.charCodeAt(i);
|
|
8678
|
+
if (code < 32 || code === 127) return true;
|
|
8679
|
+
}
|
|
8680
|
+
return false;
|
|
8681
|
+
}
|
|
8682
|
+
var KnowledgeError = class extends Error {
|
|
8683
|
+
constructor(status, code, message, uploadedItems) {
|
|
8684
|
+
super(message);
|
|
8685
|
+
this.status = status;
|
|
8686
|
+
this.code = code;
|
|
8687
|
+
this.uploadedItems = uploadedItems;
|
|
8688
|
+
this.name = "KnowledgeError";
|
|
8689
|
+
}
|
|
8690
|
+
};
|
|
8691
|
+
function normalizeKnowledgeFolder(folder) {
|
|
8692
|
+
if (!folder || folder.includes("\\") || hasControlChar(folder) || folder.startsWith("/"))
|
|
8693
|
+
throw new KnowledgeError(400, "invalid_folder", "Folder must be a relative forward-slash path");
|
|
8694
|
+
const withoutTrailingSlash = folder.endsWith("/") ? folder.slice(0, -1) : folder;
|
|
8695
|
+
if (!withoutTrailingSlash || withoutTrailingSlash.endsWith("/"))
|
|
8696
|
+
throw new KnowledgeError(400, "invalid_folder", "Folder cannot contain empty segments");
|
|
8697
|
+
const parts = withoutTrailingSlash.split("/");
|
|
8698
|
+
if (parts.some((part) => !part || part === "." || part === ".."))
|
|
8699
|
+
throw new KnowledgeError(
|
|
8700
|
+
400,
|
|
8701
|
+
"invalid_folder",
|
|
8702
|
+
"Folder cannot contain empty, dot, or parent segments"
|
|
8703
|
+
);
|
|
8704
|
+
const normalized = `${parts.join("/")}/`;
|
|
8705
|
+
if (normalized.length > 128)
|
|
8706
|
+
throw new KnowledgeError(400, "invalid_folder", "Folder is too long for an AI Search item key");
|
|
8707
|
+
return normalized;
|
|
8708
|
+
}
|
|
8709
|
+
function validateKnowledgeFilename(name) {
|
|
8710
|
+
if (!name || name === "." || name === ".." || name.includes("/") || name.includes("\\") || hasControlChar(name)) {
|
|
8711
|
+
throw new KnowledgeError(
|
|
8712
|
+
400,
|
|
8713
|
+
"invalid_filename",
|
|
8714
|
+
"File name must be a non-empty base name without path separators"
|
|
8715
|
+
);
|
|
8716
|
+
}
|
|
8717
|
+
}
|
|
8718
|
+
function folderFilter(folder) {
|
|
8719
|
+
const value = normalizeKnowledgeFolder(folder);
|
|
8720
|
+
return { folder: { $gte: value, $lt: `${value.slice(0, -1)}0` } };
|
|
8721
|
+
}
|
|
8722
|
+
function knowledge(env) {
|
|
8723
|
+
const add = (file, options = {}) => addFile(env, file, options);
|
|
8724
|
+
const list = (options = {}) => listItems(env, options);
|
|
8725
|
+
const search = (query, options = {}) => searchItems(env, query, options);
|
|
8726
|
+
return {
|
|
8727
|
+
add,
|
|
8728
|
+
list,
|
|
8729
|
+
search,
|
|
8730
|
+
async remove(itemId) {
|
|
8731
|
+
if (!/^[A-Za-z0-9_-]{1,256}$/u.test(itemId))
|
|
8732
|
+
throw new KnowledgeError(400, "invalid_item_id", "Invalid knowledge item id");
|
|
8733
|
+
await request(env, `/api/knowledge/items/${encodeURIComponent(itemId)}`, { method: "DELETE" });
|
|
8734
|
+
},
|
|
8735
|
+
scoped({ folder }) {
|
|
8736
|
+
const fixed = normalizeKnowledgeFolder(folder);
|
|
8737
|
+
return {
|
|
8738
|
+
add: (file) => add(file, { folder: fixed }),
|
|
8739
|
+
list: (options = {}) => list({ ...options, folder: fixed }),
|
|
8740
|
+
search: (query, options = {}) => search(query, { ...options, folder: fixed })
|
|
8741
|
+
};
|
|
8742
|
+
}
|
|
8743
|
+
};
|
|
8744
|
+
}
|
|
8745
|
+
async function addFile(env, file, options) {
|
|
8746
|
+
validateKnowledgeFilename(file.name);
|
|
8747
|
+
const folder = options.folder === void 0 ? void 0 : normalizeKnowledgeFolder(options.folder);
|
|
8748
|
+
if ((folder?.length ?? 0) + file.name.length > 128)
|
|
8749
|
+
throw new KnowledgeError(
|
|
8750
|
+
400,
|
|
8751
|
+
"invalid_key",
|
|
8752
|
+
"Folder and filename together exceed the 128-character AI Search key limit"
|
|
8753
|
+
);
|
|
8754
|
+
if (file.size <= MAX_UPLOAD_BYTES) return upload(env, file, folder);
|
|
8755
|
+
if (!TEXT_MIME.test(file.type) && !/\.(txt|md|mdx|csv|json|xml|html?|ya?ml|tsx?|jsx?|css)$/iu.test(file.name))
|
|
8756
|
+
throw new KnowledgeError(
|
|
8757
|
+
413,
|
|
8758
|
+
"file_too_large",
|
|
8759
|
+
"This file exceeds 4 MB. Split the source document with a format-aware tool before uploading."
|
|
8760
|
+
);
|
|
8761
|
+
const bytes = new Uint8Array(await file.arrayBuffer());
|
|
8762
|
+
const parts = splitUtf8(bytes, MAX_UPLOAD_BYTES);
|
|
8763
|
+
const uploaded = [];
|
|
8764
|
+
try {
|
|
8765
|
+
for (let i = 0; i < parts.length; i++) {
|
|
8766
|
+
const bytes2 = parts[i];
|
|
8767
|
+
const contents = bytes2.buffer.slice(
|
|
8768
|
+
bytes2.byteOffset,
|
|
8769
|
+
bytes2.byteOffset + bytes2.byteLength
|
|
8770
|
+
);
|
|
8771
|
+
const part = new File([contents], partName(file.name, i + 1), {
|
|
8772
|
+
type: file.type || "text/plain"
|
|
8773
|
+
});
|
|
8774
|
+
if ((folder?.length ?? 0) + part.name.length > 128)
|
|
8775
|
+
throw new KnowledgeError(
|
|
8776
|
+
400,
|
|
8777
|
+
"invalid_key",
|
|
8778
|
+
"Folder and split filename together exceed the 128-character AI Search key limit"
|
|
8779
|
+
);
|
|
8780
|
+
uploaded.push(...(await upload(env, part, folder)).items);
|
|
8781
|
+
}
|
|
8782
|
+
} catch (error) {
|
|
8783
|
+
if (error instanceof KnowledgeError)
|
|
8784
|
+
throw new KnowledgeError(error.status, error.code, error.message, uploaded);
|
|
8785
|
+
throw new KnowledgeError(502, "knowledge_upload_failed", "Knowledge upload failed", uploaded);
|
|
8786
|
+
}
|
|
8787
|
+
return { items: uploaded };
|
|
8788
|
+
}
|
|
8789
|
+
function splitUtf8(bytes, max) {
|
|
8790
|
+
const out = [];
|
|
8791
|
+
for (let start = 0; start < bytes.length; ) {
|
|
8792
|
+
let end = Math.min(start + max, bytes.length);
|
|
8793
|
+
if (end < bytes.length) {
|
|
8794
|
+
let cut = end;
|
|
8795
|
+
while (cut > start && (bytes[cut] & 192) === 128) cut--;
|
|
8796
|
+
for (let i = cut - 1; i > Math.max(start, cut - 8192); i--)
|
|
8797
|
+
if (bytes[i] === 10 && (i === start || bytes[i - 1] === 10)) {
|
|
8798
|
+
cut = i + 1;
|
|
8799
|
+
break;
|
|
8800
|
+
}
|
|
8801
|
+
end = cut > start ? cut : end;
|
|
8802
|
+
}
|
|
8803
|
+
out.push(bytes.slice(start, end));
|
|
8804
|
+
start = end;
|
|
8805
|
+
}
|
|
8806
|
+
return out;
|
|
8807
|
+
}
|
|
8808
|
+
function partName(name, n) {
|
|
8809
|
+
const dot = name.lastIndexOf(".");
|
|
8810
|
+
return dot > 0 ? `${name.slice(0, dot)}.part-${n}${name.slice(dot)}` : `${name}.part-${n}`;
|
|
8811
|
+
}
|
|
8812
|
+
async function upload(env, file, folder) {
|
|
8813
|
+
const body = new FormData();
|
|
8814
|
+
body.append("file", file);
|
|
8815
|
+
if (folder) body.append("folder", folder);
|
|
8816
|
+
return await request(env, "/api/knowledge/items", {
|
|
8817
|
+
method: "POST",
|
|
8818
|
+
body
|
|
8819
|
+
});
|
|
8820
|
+
}
|
|
8821
|
+
async function listItems(env, options) {
|
|
8822
|
+
if (options.page !== void 0 && (!Number.isInteger(options.page) || options.page < 1))
|
|
8823
|
+
throw new KnowledgeError(400, "invalid_page", "List page must be a positive integer");
|
|
8824
|
+
if (options.perPage !== void 0 && (!Number.isInteger(options.perPage) || options.perPage < 1 || options.perPage > 50))
|
|
8825
|
+
throw new KnowledgeError(
|
|
8826
|
+
400,
|
|
8827
|
+
"invalid_per_page",
|
|
8828
|
+
"List perPage must be an integer from 1 to 50"
|
|
8829
|
+
);
|
|
8830
|
+
if (options.search !== void 0 && options.search.length > 256)
|
|
8831
|
+
throw new KnowledgeError(400, "invalid_search", "List search cannot exceed 256 characters");
|
|
8832
|
+
if (options.status !== void 0 && !["queued", "running", "completed", "error", "skipped", "outdated"].includes(options.status))
|
|
8833
|
+
throw new KnowledgeError(400, "invalid_status", "Invalid knowledge item status");
|
|
8834
|
+
const q = new URLSearchParams();
|
|
8835
|
+
if (options.folder) q.set("folder", normalizeKnowledgeFolder(options.folder));
|
|
8836
|
+
if (options.page !== void 0) q.set("page", String(options.page));
|
|
8837
|
+
if (options.perPage !== void 0) q.set("perPage", String(options.perPage));
|
|
8838
|
+
if (options.status) q.set("status", options.status);
|
|
8839
|
+
if (options.search) q.set("search", options.search);
|
|
8840
|
+
return await request(env, `/api/knowledge/items${q.size ? `?${q}` : ""}`);
|
|
8841
|
+
}
|
|
8842
|
+
async function searchItems(env, query, options) {
|
|
8843
|
+
const normalizedQuery = query.trim();
|
|
8844
|
+
if (!normalizedQuery) throw new KnowledgeError(400, "invalid_query", "Search query is required");
|
|
8845
|
+
if (normalizedQuery.length > MAX_QUERY_CHARS)
|
|
8846
|
+
throw new KnowledgeError(
|
|
8847
|
+
400,
|
|
8848
|
+
"invalid_query",
|
|
8849
|
+
`Search query cannot exceed ${MAX_QUERY_CHARS} characters`
|
|
8850
|
+
);
|
|
8851
|
+
if (options.mode !== void 0 && options.mode !== "hybrid" && options.mode !== "semantic" && options.mode !== "fulltext")
|
|
8852
|
+
throw new KnowledgeError(400, "invalid_mode", "Invalid knowledge search mode");
|
|
8853
|
+
if (options.limit !== void 0 && (!Number.isInteger(options.limit) || options.limit < 1 || options.limit > 50))
|
|
8854
|
+
throw new KnowledgeError(
|
|
8855
|
+
400,
|
|
8856
|
+
"invalid_limit",
|
|
8857
|
+
"Search limit must be an integer from 1 through 50"
|
|
8858
|
+
);
|
|
8859
|
+
if (options.matchThreshold !== void 0 && (!Number.isFinite(options.matchThreshold) || options.matchThreshold < 0 || options.matchThreshold > 1))
|
|
8860
|
+
throw new KnowledgeError(
|
|
8861
|
+
400,
|
|
8862
|
+
"invalid_match_threshold",
|
|
8863
|
+
"Match threshold must be between 0 and 1"
|
|
8864
|
+
);
|
|
8865
|
+
if (options.queryRewrite !== void 0 && typeof options.queryRewrite !== "boolean")
|
|
8866
|
+
throw new KnowledgeError(
|
|
8867
|
+
400,
|
|
8868
|
+
"invalid_query_rewrite",
|
|
8869
|
+
"queryRewrite must be a boolean when provided"
|
|
8870
|
+
);
|
|
8871
|
+
const folder = options.folder === void 0 ? void 0 : normalizeKnowledgeFolder(options.folder);
|
|
8872
|
+
return await request(env, "/api/knowledge/search", {
|
|
8873
|
+
method: "POST",
|
|
8874
|
+
headers: { "content-type": "application/json" },
|
|
8875
|
+
body: JSON.stringify({
|
|
8876
|
+
query: normalizedQuery,
|
|
8877
|
+
mode: options.mode ?? "hybrid",
|
|
8878
|
+
limit: options.limit ?? 10,
|
|
8879
|
+
queryRewrite: options.queryRewrite ?? false,
|
|
8880
|
+
...options.matchThreshold !== void 0 && {
|
|
8881
|
+
matchThreshold: options.matchThreshold
|
|
8882
|
+
},
|
|
8883
|
+
...folder && { folder }
|
|
8884
|
+
})
|
|
8885
|
+
});
|
|
8886
|
+
}
|
|
8887
|
+
async function request(env, path, init = {}) {
|
|
8888
|
+
const headers = new Headers(init.headers);
|
|
8889
|
+
appendAppIdentity(headers, env);
|
|
8890
|
+
const response = await apiWorkerFetch(env, path, { ...init, headers });
|
|
8891
|
+
const text = await response.text();
|
|
8892
|
+
let data;
|
|
8893
|
+
try {
|
|
8894
|
+
data = text ? JSON.parse(text) : void 0;
|
|
8895
|
+
} catch {
|
|
8896
|
+
data = void 0;
|
|
8897
|
+
}
|
|
8898
|
+
if (!response.ok) {
|
|
8899
|
+
const body = data && typeof data === "object" ? data : {};
|
|
8900
|
+
throw new KnowledgeError(
|
|
8901
|
+
response.status,
|
|
8902
|
+
typeof body.code === "string" ? body.code : "knowledge_request_failed",
|
|
8903
|
+
typeof body.error === "string" ? body.error : "Knowledge request failed"
|
|
8904
|
+
);
|
|
8905
|
+
}
|
|
8906
|
+
return data;
|
|
8907
|
+
}
|
|
8908
|
+
|
|
8735
8909
|
// src/server/utils/d1-migrations.ts
|
|
8736
8910
|
var META_TABLE = "_dpc_migrations";
|
|
8737
8911
|
var META_BOOTSTRAP_SQL = `CREATE TABLE IF NOT EXISTS ${META_TABLE} (idx INTEGER PRIMARY KEY, applied_at TEXT NOT NULL)`;
|
|
@@ -9242,16 +9416,16 @@ var RequestBodyTooLargeError = class extends Error {
|
|
|
9242
9416
|
this.name = "RequestBodyTooLargeError";
|
|
9243
9417
|
}
|
|
9244
9418
|
};
|
|
9245
|
-
async function readBoundedRequestText(
|
|
9419
|
+
async function readBoundedRequestText(request2, maxBytes) {
|
|
9246
9420
|
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) {
|
|
9247
9421
|
throw new TypeError("maxBytes must be a positive safe integer");
|
|
9248
9422
|
}
|
|
9249
|
-
const declared =
|
|
9423
|
+
const declared = request2.headers.get("content-length");
|
|
9250
9424
|
if (declared && /^\d+$/.test(declared.trim()) && Number(declared) > maxBytes) {
|
|
9251
9425
|
throw new RequestBodyTooLargeError(maxBytes);
|
|
9252
9426
|
}
|
|
9253
|
-
if (!
|
|
9254
|
-
const reader =
|
|
9427
|
+
if (!request2.body) return "";
|
|
9428
|
+
const reader = request2.body.getReader();
|
|
9255
9429
|
const decoder = new TextDecoder();
|
|
9256
9430
|
let bytes = 0;
|
|
9257
9431
|
let text = "";
|
|
@@ -9465,10 +9639,10 @@ function documentationRequestPath(pathname, publicBasePath) {
|
|
|
9465
9639
|
const clean = logical === "/" ? "/" : logical.replace(/\/$/, "");
|
|
9466
9640
|
return canonicalNativeDocumentationRequestPath(clean);
|
|
9467
9641
|
}
|
|
9468
|
-
async function fetchDocumentationAsset(env,
|
|
9642
|
+
async function fetchDocumentationAsset(env, request2, initialUrl, assetBasePath) {
|
|
9469
9643
|
let url = initialUrl;
|
|
9470
9644
|
for (let redirects = 0; redirects < 3; redirects++) {
|
|
9471
|
-
const response = await env.ASSETS.fetch(new Request(url,
|
|
9645
|
+
const response = await env.ASSETS.fetch(new Request(url, request2));
|
|
9472
9646
|
const location = response.headers.get("Location");
|
|
9473
9647
|
if (response.status < 300 || response.status >= 400 || !location) return response;
|
|
9474
9648
|
const next = new URL(location, url);
|
|
@@ -9508,11 +9682,11 @@ function secureDocumentationResponse(response, status, pathname) {
|
|
|
9508
9682
|
var MCP_SERVER_VERSION = "1.0.0";
|
|
9509
9683
|
var CURRENT_PROTOCOL_VERSION = "2025-11-25";
|
|
9510
9684
|
var SUPPORTED_PROTOCOL_VERSIONS = /* @__PURE__ */ new Set([CURRENT_PROTOCOL_VERSION, "2025-06-18", "2025-03-26"]);
|
|
9511
|
-
function initializeResponse(
|
|
9512
|
-
const params = objectValue(
|
|
9685
|
+
function initializeResponse(request2) {
|
|
9686
|
+
const params = objectValue(request2.params);
|
|
9513
9687
|
const requested = typeof params?.protocolVersion === "string" ? params.protocolVersion : CURRENT_PROTOCOL_VERSION;
|
|
9514
9688
|
const protocolVersion = SUPPORTED_PROTOCOL_VERSIONS.has(requested) ? requested : CURRENT_PROTOCOL_VERSION;
|
|
9515
|
-
return mcpResult(
|
|
9689
|
+
return mcpResult(request2.id, {
|
|
9516
9690
|
protocolVersion,
|
|
9517
9691
|
capabilities: {
|
|
9518
9692
|
tools: { listChanged: false },
|
|
@@ -9526,11 +9700,11 @@ function initializeResponse(request) {
|
|
|
9526
9700
|
instructions: "Search the published documentation before answering, then read exact pages when full context is needed."
|
|
9527
9701
|
});
|
|
9528
9702
|
}
|
|
9529
|
-
function hasValidOrigin(
|
|
9530
|
-
const origin =
|
|
9703
|
+
function hasValidOrigin(request2) {
|
|
9704
|
+
const origin = request2.headers.get("Origin");
|
|
9531
9705
|
if (!origin) return true;
|
|
9532
9706
|
try {
|
|
9533
|
-
return new URL(origin).origin === new URL(
|
|
9707
|
+
return new URL(origin).origin === new URL(request2.url).origin;
|
|
9534
9708
|
} catch {
|
|
9535
9709
|
return false;
|
|
9536
9710
|
}
|
|
@@ -9656,8 +9830,8 @@ var DOCUMENTATION_TOOLS = [
|
|
|
9656
9830
|
}
|
|
9657
9831
|
}
|
|
9658
9832
|
];
|
|
9659
|
-
async function callDocumentationTool(rawRequest,
|
|
9660
|
-
const params = objectValue(
|
|
9833
|
+
async function callDocumentationTool(rawRequest, request2, corpus, env, basePath = "/docs", assetBasePath = "/_documentation") {
|
|
9834
|
+
const params = objectValue(request2.params);
|
|
9661
9835
|
const name = typeof params?.name === "string" ? params.name : "";
|
|
9662
9836
|
const args = objectValue(params?.arguments) ?? {};
|
|
9663
9837
|
const origin = new URL(rawRequest.url).origin;
|
|
@@ -9665,13 +9839,13 @@ async function callDocumentationTool(rawRequest, request, corpus, env, basePath
|
|
|
9665
9839
|
const query = typeof args.query === "string" ? args.query.trim() : "";
|
|
9666
9840
|
const limit = args.limit === void 0 ? 6 : args.limit;
|
|
9667
9841
|
if (query.length < 2 || query.length > 300 || !Number.isInteger(limit) || Number(limit) < 1 || Number(limit) > 10) {
|
|
9668
|
-
return mcpError(
|
|
9842
|
+
return mcpError(request2.id, -32602, "Invalid documentation_search arguments");
|
|
9669
9843
|
}
|
|
9670
9844
|
const results = searchDocumentationCorpus(corpus, query, Number(limit)).map((result) => ({
|
|
9671
9845
|
...result,
|
|
9672
9846
|
url: new URL(documentationPublicPath(basePath, result.route), origin).toString()
|
|
9673
9847
|
}));
|
|
9674
|
-
return toolResult(
|
|
9848
|
+
return toolResult(request2.id, {
|
|
9675
9849
|
...results[0]?.termCoverage === 1 ? {} : {
|
|
9676
9850
|
notice: "No section matches every search term \u2014 the topic may be absent or spread across sections."
|
|
9677
9851
|
},
|
|
@@ -9681,7 +9855,7 @@ async function callDocumentationTool(rawRequest, request, corpus, env, basePath
|
|
|
9681
9855
|
if (name === "documentation_read") {
|
|
9682
9856
|
const route = typeof args.route === "string" ? args.route.trim() : "";
|
|
9683
9857
|
if (!route || route.length > 500) {
|
|
9684
|
-
return mcpError(
|
|
9858
|
+
return mcpError(request2.id, -32602, "Invalid documentation_read arguments");
|
|
9685
9859
|
}
|
|
9686
9860
|
const hash = route.indexOf("#");
|
|
9687
9861
|
const fragment = hash === -1 ? "" : route.slice(hash + 1);
|
|
@@ -9693,9 +9867,9 @@ async function callDocumentationTool(rawRequest, request, corpus, env, basePath
|
|
|
9693
9867
|
rawRequest.url,
|
|
9694
9868
|
assetBasePath
|
|
9695
9869
|
);
|
|
9696
|
-
if (!page) return toolError(
|
|
9870
|
+
if (!page) return toolError(request2.id, `No published documentation page exists at ${route}`);
|
|
9697
9871
|
const section = fragment ? documentationPageSection(page.markdown, fragment) : null;
|
|
9698
|
-
return toolResult(
|
|
9872
|
+
return toolResult(request2.id, {
|
|
9699
9873
|
...fragment && section === null ? { notice: `The fragment #${fragment} was not found; returning the whole page.` } : {},
|
|
9700
9874
|
...page,
|
|
9701
9875
|
...section === null ? {} : { markdown: section },
|
|
@@ -9703,10 +9877,10 @@ async function callDocumentationTool(rawRequest, request, corpus, env, basePath
|
|
|
9703
9877
|
});
|
|
9704
9878
|
} catch (error) {
|
|
9705
9879
|
console.error("[documentation-mcp] page unavailable:", error);
|
|
9706
|
-
return toolError(
|
|
9880
|
+
return toolError(request2.id, "The documentation page could not be read");
|
|
9707
9881
|
}
|
|
9708
9882
|
}
|
|
9709
|
-
return mcpError(
|
|
9883
|
+
return mcpError(request2.id, -32602, `Unknown documentation tool: ${name || "<missing>"}`);
|
|
9710
9884
|
}
|
|
9711
9885
|
|
|
9712
9886
|
// src/documentation/worker/mcp-routes.ts
|
|
@@ -9743,27 +9917,27 @@ function registerDocumentationMcpRoutes(app, publicBasePath = DOCUMENTATION_BASE
|
|
|
9743
9917
|
}
|
|
9744
9918
|
const parsed = parseRequest(raw);
|
|
9745
9919
|
if (!parsed.ok) return mcpError(null, -32700, parsed.message, 400);
|
|
9746
|
-
const
|
|
9747
|
-
if (
|
|
9920
|
+
const request2 = parsed.request;
|
|
9921
|
+
if (request2.method !== "initialize") {
|
|
9748
9922
|
const protocolVersion = c.req.header("MCP-Protocol-Version");
|
|
9749
9923
|
if (protocolVersion && !SUPPORTED_PROTOCOL_VERSIONS.has(protocolVersion)) {
|
|
9750
|
-
return mcpError(
|
|
9924
|
+
return mcpError(request2.id ?? null, -32600, "Unsupported MCP-Protocol-Version", 400);
|
|
9751
9925
|
}
|
|
9752
9926
|
}
|
|
9753
|
-
const requestId =
|
|
9927
|
+
const requestId = request2.id;
|
|
9754
9928
|
if (requestId === void 0) return new Response(null, { status: 202 });
|
|
9755
|
-
const requestWithId = { ...
|
|
9756
|
-
if (
|
|
9757
|
-
if (
|
|
9758
|
-
if (
|
|
9929
|
+
const requestWithId = { ...request2, id: requestId };
|
|
9930
|
+
if (request2.method === "initialize") return initializeResponse(requestWithId);
|
|
9931
|
+
if (request2.method === "ping") return mcpResult(requestId, {});
|
|
9932
|
+
if (request2.method === "tools/list") {
|
|
9759
9933
|
return mcpResult(requestId, { tools: DOCUMENTATION_TOOLS });
|
|
9760
9934
|
}
|
|
9761
|
-
if (
|
|
9935
|
+
if (request2.method === "resources/list") {
|
|
9762
9936
|
return mcpResult(requestId, {
|
|
9763
9937
|
resources: [skillResource(c.req.raw, manifest, publicBasePath)]
|
|
9764
9938
|
});
|
|
9765
9939
|
}
|
|
9766
|
-
if (
|
|
9940
|
+
if (request2.method === "resources/read") {
|
|
9767
9941
|
return readSkillResource(
|
|
9768
9942
|
c.req.raw,
|
|
9769
9943
|
requestWithId,
|
|
@@ -9773,8 +9947,8 @@ function registerDocumentationMcpRoutes(app, publicBasePath = DOCUMENTATION_BASE
|
|
|
9773
9947
|
assetBasePath
|
|
9774
9948
|
);
|
|
9775
9949
|
}
|
|
9776
|
-
if (
|
|
9777
|
-
return mcpError(requestId, -32601, `Method not found: ${
|
|
9950
|
+
if (request2.method !== "tools/call") {
|
|
9951
|
+
return mcpError(requestId, -32601, `Method not found: ${request2.method}`);
|
|
9778
9952
|
}
|
|
9779
9953
|
const clientKey = await documentationPublicClientKey(
|
|
9780
9954
|
c.req.header("CF-Connecting-IP") ?? "unknown"
|
|
@@ -9831,44 +10005,44 @@ function registerDocumentationMcpRoutes(app, publicBasePath = DOCUMENTATION_BASE
|
|
|
9831
10005
|
}
|
|
9832
10006
|
);
|
|
9833
10007
|
}
|
|
9834
|
-
async function readSkillResource(rawRequest,
|
|
9835
|
-
const params = objectValue(
|
|
10008
|
+
async function readSkillResource(rawRequest, request2, manifest, env, publicBasePath, assetBasePath) {
|
|
10009
|
+
const params = objectValue(request2.params);
|
|
9836
10010
|
const requestedUri = typeof params?.uri === "string" ? params.uri : "";
|
|
9837
10011
|
const resource = skillResource(rawRequest, manifest, publicBasePath);
|
|
9838
10012
|
if (requestedUri !== resource.uri) {
|
|
9839
|
-
return mcpError(
|
|
10013
|
+
return mcpError(request2.id, -32602, "Unknown documentation resource");
|
|
9840
10014
|
}
|
|
9841
10015
|
const assetUrl = new URL(`${assetBasePath}/skill.md`, rawRequest.url);
|
|
9842
10016
|
const response = await env.ASSETS.fetch(new Request(assetUrl, { method: "GET" }));
|
|
9843
|
-
if (!response.ok) return mcpError(
|
|
10017
|
+
if (!response.ok) return mcpError(request2.id, -32002, "Documentation skill is unavailable", 503);
|
|
9844
10018
|
const text = await response.text();
|
|
9845
10019
|
if (text.length > MAX_SKILL_CHARS) {
|
|
9846
|
-
return mcpError(
|
|
10020
|
+
return mcpError(request2.id, -32002, "Documentation skill exceeds the published size limit", 503);
|
|
9847
10021
|
}
|
|
9848
|
-
return mcpResult(
|
|
10022
|
+
return mcpResult(request2.id, {
|
|
9849
10023
|
contents: [{ uri: resource.uri, mimeType: resource.mimeType, text }]
|
|
9850
10024
|
});
|
|
9851
10025
|
}
|
|
9852
|
-
function skillResource(
|
|
10026
|
+
function skillResource(request2, manifest, publicBasePath) {
|
|
9853
10027
|
const subject = documentationSubject(manifest.name);
|
|
9854
10028
|
return {
|
|
9855
|
-
uri: new URL(`${publicBasePath}/skill.md`,
|
|
10029
|
+
uri: new URL(`${publicBasePath}/skill.md`, request2.url).toString(),
|
|
9856
10030
|
name: `${subject} skill`,
|
|
9857
10031
|
title: `Use ${manifest.name}`,
|
|
9858
10032
|
description: `Agent instructions grounded in the published ${subject}.`,
|
|
9859
10033
|
mimeType: "text/markdown"
|
|
9860
10034
|
};
|
|
9861
10035
|
}
|
|
9862
|
-
async function publicManifest(
|
|
9863
|
-
const manifest = await loadDocumentationPublishedManifest(env,
|
|
10036
|
+
async function publicManifest(request2, env, assetBasePath) {
|
|
10037
|
+
const manifest = await loadDocumentationPublishedManifest(env, request2.url, assetBasePath);
|
|
9864
10038
|
return manifest?.mcp.access === "public" ? manifest : null;
|
|
9865
10039
|
}
|
|
9866
|
-
function discoveryResponse(
|
|
9867
|
-
return jsonResponse(serverCard(
|
|
10040
|
+
function discoveryResponse(request2, manifest, publicBasePath) {
|
|
10041
|
+
return jsonResponse(serverCard(request2, manifest, publicBasePath), 200, {
|
|
9868
10042
|
"Cache-Control": "public, max-age=300"
|
|
9869
10043
|
});
|
|
9870
10044
|
}
|
|
9871
|
-
function serverCard(
|
|
10045
|
+
function serverCard(request2, manifest, publicBasePath) {
|
|
9872
10046
|
return {
|
|
9873
10047
|
name: manifest.name,
|
|
9874
10048
|
description: `Search and read the published ${documentationSubject(manifest.name)}.`,
|
|
@@ -9881,7 +10055,7 @@ function serverCard(request, manifest, publicBasePath) {
|
|
|
9881
10055
|
protocolVersions: [...SUPPORTED_PROTOCOL_VERSIONS],
|
|
9882
10056
|
transport: {
|
|
9883
10057
|
type: "streamable-http",
|
|
9884
|
-
url: new URL(`${publicBasePath}/mcp`,
|
|
10058
|
+
url: new URL(`${publicBasePath}/mcp`, request2.url).toString()
|
|
9885
10059
|
},
|
|
9886
10060
|
authentication: { required: false },
|
|
9887
10061
|
capabilities: {
|
|
@@ -9890,7 +10064,7 @@ function serverCard(request, manifest, publicBasePath) {
|
|
|
9890
10064
|
title,
|
|
9891
10065
|
description
|
|
9892
10066
|
})),
|
|
9893
|
-
resources: [skillResource(
|
|
10067
|
+
resources: [skillResource(request2, manifest, publicBasePath)]
|
|
9894
10068
|
}
|
|
9895
10069
|
};
|
|
9896
10070
|
}
|
|
@@ -10313,21 +10487,21 @@ function jsonResponse2(body, status) {
|
|
|
10313
10487
|
headers: { "Content-Type": "application/json" }
|
|
10314
10488
|
});
|
|
10315
10489
|
}
|
|
10316
|
-
async function handleClientErrorReport(
|
|
10317
|
-
const site =
|
|
10490
|
+
async function handleClientErrorReport(request2) {
|
|
10491
|
+
const site = request2.headers.get("sec-fetch-site");
|
|
10318
10492
|
if (site && site !== "same-origin" && site !== "none") {
|
|
10319
10493
|
return new Response(null, { status: 403 });
|
|
10320
10494
|
}
|
|
10321
10495
|
if (shouldThrottleClientError()) {
|
|
10322
10496
|
return new Response(null, { status: 429, headers: { "Retry-After": "1" } });
|
|
10323
10497
|
}
|
|
10324
|
-
const declared = Number(
|
|
10498
|
+
const declared = Number(request2.headers.get("content-length"));
|
|
10325
10499
|
if (Number.isFinite(declared) && declared > MAX_BODY_BYTES) {
|
|
10326
10500
|
return jsonResponse2({ error: "payload_too_large" }, 413);
|
|
10327
10501
|
}
|
|
10328
10502
|
let body;
|
|
10329
10503
|
try {
|
|
10330
|
-
body = await
|
|
10504
|
+
body = await request2.json();
|
|
10331
10505
|
} catch {
|
|
10332
10506
|
return jsonResponse2({ error: "invalid_json" }, 400);
|
|
10333
10507
|
}
|
|
@@ -10355,6 +10529,8 @@ var APP_LOG_EVENT_KEYS = [
|
|
|
10355
10529
|
];
|
|
10356
10530
|
export {
|
|
10357
10531
|
ACCOUNT_STORAGE_LIMIT_BYTES,
|
|
10532
|
+
ACTION_ROUTES_BEARER_GUARD_MIGRATION_ID,
|
|
10533
|
+
ACTION_TOOLS_DELETE_WHERE_MIGRATION_ID,
|
|
10358
10534
|
AI_CHATS_SCHEMA,
|
|
10359
10535
|
AI_MESSAGES_SCHEMA,
|
|
10360
10536
|
ALLOWED_BINDING_TYPES,
|
|
@@ -10368,6 +10544,7 @@ export {
|
|
|
10368
10544
|
Awareness,
|
|
10369
10545
|
BASE_USERS_SCHEMA,
|
|
10370
10546
|
BROWSER_PROXY_ROUTES,
|
|
10547
|
+
BUILD_INJECTED_APP_ID_MIGRATION_ID,
|
|
10371
10548
|
BUILT_IN_TOOLS,
|
|
10372
10549
|
BaseRoom,
|
|
10373
10550
|
CHANNELS_SCHEMA,
|
|
@@ -10393,10 +10570,12 @@ export {
|
|
|
10393
10570
|
GLOBAL_DO_TYPES,
|
|
10394
10571
|
GLOBAL_DO_TYPE_NAMES,
|
|
10395
10572
|
JobRoom,
|
|
10573
|
+
KnowledgeError,
|
|
10396
10574
|
LOG_LEVELS,
|
|
10397
10575
|
MAX_APP_FILE_BYTES,
|
|
10398
10576
|
MAX_DELETE_WHERE_LIMIT,
|
|
10399
10577
|
MAX_DEPLOY_ASSET_FILE_BYTES,
|
|
10578
|
+
MAX_ENV_VAR_BYTES,
|
|
10400
10579
|
MAX_UPLOAD_PARTS,
|
|
10401
10580
|
MAX_UPLOAD_REQUEST_BYTES,
|
|
10402
10581
|
MESSAGES_SCHEMA,
|
|
@@ -10439,13 +10618,13 @@ export {
|
|
|
10439
10618
|
USERS_COLUMNS,
|
|
10440
10619
|
VOTING_SCHEMAS,
|
|
10441
10620
|
WORKER_OWNED_NOT_FOUND_MIGRATION_ID,
|
|
10442
|
-
WORKSPACE_SCHEMAS,
|
|
10443
10621
|
YjsRoom,
|
|
10444
10622
|
analyzePermissions,
|
|
10445
10623
|
apiWorkerFetch,
|
|
10446
10624
|
appendMessage,
|
|
10447
10625
|
applyAiToolDefaults,
|
|
10448
10626
|
applySlidingWindow,
|
|
10627
|
+
armCronRoom,
|
|
10449
10628
|
authWorkerFetch,
|
|
10450
10629
|
authenticatedRoomRequest,
|
|
10451
10630
|
bindingManifestFromOutputConfig,
|
|
@@ -10485,6 +10664,8 @@ export {
|
|
|
10485
10664
|
encodeSyncStep2,
|
|
10486
10665
|
encodeUpdate,
|
|
10487
10666
|
enqueueJob,
|
|
10667
|
+
epochSeconds,
|
|
10668
|
+
folderFilter,
|
|
10488
10669
|
formatBytes,
|
|
10489
10670
|
getChat,
|
|
10490
10671
|
getDeepSpaceAIModel,
|
|
@@ -10505,6 +10686,7 @@ export {
|
|
|
10505
10686
|
isTestAccountClaims,
|
|
10506
10687
|
isTestAccountEmail,
|
|
10507
10688
|
isTestAccountTier,
|
|
10689
|
+
knowledge,
|
|
10508
10690
|
lintSchema,
|
|
10509
10691
|
lintSchemaSet,
|
|
10510
10692
|
lintSchemas,
|
|
@@ -10518,6 +10700,7 @@ export {
|
|
|
10518
10700
|
mintAppId,
|
|
10519
10701
|
mintUlid,
|
|
10520
10702
|
noopPermissionContext,
|
|
10703
|
+
normalizeKnowledgeFolder,
|
|
10521
10704
|
parseQuotaLimit,
|
|
10522
10705
|
platformCorsPatterns,
|
|
10523
10706
|
platformWorkerFetch,
|
|
@@ -10553,17 +10736,6 @@ export {
|
|
|
10553
10736
|
validateBindingManifest,
|
|
10554
10737
|
validateDoManifest,
|
|
10555
10738
|
verifyJwt,
|
|
10556
|
-
workspaceAccountsSchema,
|
|
10557
|
-
workspaceContentSharesSchema,
|
|
10558
|
-
workspaceEmailHandlesSchema,
|
|
10559
|
-
workspaceFormResponsesSchema,
|
|
10560
|
-
workspacePeopleSchema,
|
|
10561
|
-
workspaceProjectsSchema,
|
|
10562
|
-
workspaceTagsSchema,
|
|
10563
|
-
workspaceTasksSchema,
|
|
10564
|
-
workspaceTeamMembersSchema,
|
|
10565
|
-
workspaceTeamsSchema,
|
|
10566
|
-
workspaceTransactionsSchema,
|
|
10567
10739
|
writeVarUint,
|
|
10568
10740
|
writeVarUint8Array
|
|
10569
10741
|
};
|