deepspace 0.23.1 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/README.md +6 -2
- package/dist/build.js +11 -0
- package/dist/build.js.map +1 -1
- package/dist/{chunk-UUCAFDYW.js → chunk-2U2VG6L5.js} +2 -2
- package/dist/{chunk-UUCAFDYW.js.map → chunk-2U2VG6L5.js.map} +1 -1
- package/dist/chunk-OFDOJGFS.js +1957 -0
- package/dist/chunk-OFDOJGFS.js.map +1 -0
- package/dist/cli.js +1714 -1797
- 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 +153 -48
- package/dist/server.js +516 -380
- package/dist/server.js.map +1 -1
- package/dist/worker.d.ts +178 -48
- package/dist/worker.js +576 -434
- 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"];
|
|
@@ -1072,7 +1081,7 @@ var BUILT_IN_TOOLS = [
|
|
|
1072
1081
|
description: `Query records from a collection with optional filtering, ordering, and pagination. Returns at most \`limit\` records (default ${DEFAULT_QUERY_LIMIT}). Large result sets are capped at ~30KB: an oversized page is truncated to the leading records that fit and flagged with \`{ truncated, returned, total }\`, so narrow with a \`where\` filter (or page via \`orderBy\` + \`limit\`) to see the rest.`,
|
|
1073
1082
|
params: {
|
|
1074
1083
|
collection: { type: "string", description: "Collection name to query", required: true },
|
|
1075
|
-
where: { type: "object", description: "Filter object with field=value equality conditions", required: false },
|
|
1084
|
+
where: { type: "object", description: "Filter object with field=value equality conditions; every key must be recordId, createdBy, or a schema column (an unknown key is refused, not ignored)", required: false },
|
|
1076
1085
|
orderBy: { type: "string", description: 'Field to order by (or "createdAt"/"updatedAt")', required: false },
|
|
1077
1086
|
orderDir: { type: "string", description: 'Order direction: "asc" or "desc" (default: "desc")', required: false },
|
|
1078
1087
|
limit: { type: "number", description: `Maximum number of records to return (default: ${DEFAULT_QUERY_LIMIT}). Oversized pages are still capped at ~30KB and truncated with returned/total flags.`, required: false }
|
|
@@ -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,
|
|
@@ -1573,21 +1585,22 @@ function deleteRecord(ctx, collection, recordId, userId, userRole, skipUserRbac
|
|
|
1573
1585
|
notifyTeamMembershipChange(ctx, collection, record);
|
|
1574
1586
|
return { success: true, data: { deleted: true } };
|
|
1575
1587
|
}
|
|
1576
|
-
function
|
|
1577
|
-
const
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1588
|
+
function refuseUnknownWhere(collection, schema, where) {
|
|
1589
|
+
const columns = (schema?.columns ?? []).map(resolveColumn);
|
|
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;
|
|
1581
1593
|
return {
|
|
1582
1594
|
success: false,
|
|
1583
|
-
error: `
|
|
1595
|
+
error: `where for "${collection}" must be an object of field=value pairs, not ${got} \u2014 filterable fields: ${filterable.join(", ")}`
|
|
1584
1596
|
};
|
|
1585
1597
|
}
|
|
1586
|
-
if (!
|
|
1587
|
-
return {
|
|
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
|
+
};
|
|
1588
1603
|
}
|
|
1589
|
-
const columns = (schema.columns ?? []).map(resolveColumn);
|
|
1590
|
-
const filterable = ["recordId", "createdBy", ...columns.map((c) => c.name)];
|
|
1591
1604
|
const unknown = Object.keys(where).filter(
|
|
1592
1605
|
(key) => key !== "recordId" && key !== "createdBy" && !columns.some((c) => c.id === columnId(key) || c.name === key)
|
|
1593
1606
|
);
|
|
@@ -1604,6 +1617,26 @@ function deleteWhere(ctx, collection, where, limit, userId, userRole, skipUserRb
|
|
|
1604
1617
|
error: `where values must be primitives (equality only): ${nonPrimitive.map(([k]) => k).join(", ")}`
|
|
1605
1618
|
};
|
|
1606
1619
|
}
|
|
1620
|
+
return null;
|
|
1621
|
+
}
|
|
1622
|
+
function deleteWhere(ctx, collection, where, limit, userId, userRole, skipUserRbac = false) {
|
|
1623
|
+
const resolved = resolveCollection(ctx, collection);
|
|
1624
|
+
if (!resolved.ok) return { success: false, error: resolved.error };
|
|
1625
|
+
const schema = resolved.schema;
|
|
1626
|
+
if (!schema) {
|
|
1627
|
+
return {
|
|
1628
|
+
success: false,
|
|
1629
|
+
error: `records.deleteWhere: collection "${collection}" has no schema, so it has no filterable fields \u2014 delete its rows by recordId with records.delete`
|
|
1630
|
+
};
|
|
1631
|
+
}
|
|
1632
|
+
const whereRefusal = refuseUnknownWhere(collection, schema, where);
|
|
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
|
+
}
|
|
1607
1640
|
if (limit !== void 0 && !(typeof limit === "number" && Number.isFinite(limit))) {
|
|
1608
1641
|
return { success: false, error: "limit must be a finite number" };
|
|
1609
1642
|
}
|
|
@@ -1611,32 +1644,27 @@ function deleteWhere(ctx, collection, where, limit, userId, userRole, skipUserRb
|
|
|
1611
1644
|
Math.max(1, Math.floor(limit ?? DEFAULT_DELETE_WHERE_LIMIT)),
|
|
1612
1645
|
MAX_DELETE_WHERE_LIMIT
|
|
1613
1646
|
);
|
|
1614
|
-
const scan = { capped: false };
|
|
1615
1647
|
const matched = executeQuery(
|
|
1616
1648
|
ctx,
|
|
1617
1649
|
{ collection, where, limit: page },
|
|
1618
1650
|
userId,
|
|
1619
1651
|
userRole,
|
|
1620
|
-
|
|
1621
|
-
scan
|
|
1652
|
+
true
|
|
1622
1653
|
);
|
|
1623
|
-
if (
|
|
1624
|
-
return {
|
|
1625
|
-
success: false,
|
|
1626
|
-
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`
|
|
1627
|
-
};
|
|
1628
|
-
}
|
|
1629
|
-
if (!skipUserRbac && schema) {
|
|
1654
|
+
if (!skipUserRbac) {
|
|
1630
1655
|
const permCtx = ctx.getPermissionContext();
|
|
1631
1656
|
for (const record of matched) {
|
|
1632
1657
|
if (!canDelete(schema, userRole, record, userId, permCtx)) {
|
|
1633
|
-
return {
|
|
1658
|
+
return {
|
|
1659
|
+
success: false,
|
|
1660
|
+
error: `DELETE DENIED: role=${userRole}, collection=${collection}`
|
|
1661
|
+
};
|
|
1634
1662
|
}
|
|
1635
1663
|
}
|
|
1636
1664
|
}
|
|
1637
1665
|
let deleted = 0;
|
|
1638
1666
|
for (const record of matched) {
|
|
1639
|
-
if (deleteRecord(ctx, collection, record.recordId, userId, userRole,
|
|
1667
|
+
if (deleteRecord(ctx, collection, record.recordId, userId, userRole, true).success) {
|
|
1640
1668
|
deleted++;
|
|
1641
1669
|
}
|
|
1642
1670
|
}
|
|
@@ -2335,17 +2363,17 @@ async function registerUser(sql, userId, name, email, imageUrl, isAdmin, default
|
|
|
2335
2363
|
);
|
|
2336
2364
|
return { id: userId, email, name, imageUrl, role, createdAt: now, lastSeenAt: now };
|
|
2337
2365
|
}
|
|
2338
|
-
function rosterFor(ctx, schema, records, publicRoster,
|
|
2339
|
-
if (isAnonymousUserId(
|
|
2340
|
-
if (
|
|
2366
|
+
function rosterFor(ctx, schema, records, publicRoster, userId, role) {
|
|
2367
|
+
if (!userId || isAnonymousUserId(userId)) return [];
|
|
2368
|
+
if (role === "admin") {
|
|
2341
2369
|
const permissionContext = ctx.getPermissionContext();
|
|
2342
|
-
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 }));
|
|
2343
2371
|
}
|
|
2344
|
-
if (!getRolePermissions(schema,
|
|
2372
|
+
if (!getRolePermissions(schema, role).read) return [];
|
|
2345
2373
|
if (schema.roster === "read-policy") {
|
|
2346
2374
|
const permissionContext = ctx.getPermissionContext();
|
|
2347
2375
|
const readable = new Set(
|
|
2348
|
-
records.filter((record) => canRead(schema,
|
|
2376
|
+
records.filter((record) => canRead(schema, role, record, userId, permissionContext)).map((record) => record.recordId)
|
|
2349
2377
|
);
|
|
2350
2378
|
return publicRoster.filter((user) => readable.has(user.id));
|
|
2351
2379
|
}
|
|
@@ -2356,17 +2384,18 @@ function publicIdentities(records) {
|
|
|
2356
2384
|
id: recordId,
|
|
2357
2385
|
name: data.name,
|
|
2358
2386
|
...data.imageUrl ? { imageUrl: data.imageUrl } : {},
|
|
2359
|
-
role: data.role
|
|
2387
|
+
role: data.role,
|
|
2388
|
+
lastSeenAt: data.lastSeenAt
|
|
2360
2389
|
}));
|
|
2361
2390
|
}
|
|
2362
|
-
function
|
|
2391
|
+
function rosterForCaller(ctx, userId, role) {
|
|
2363
2392
|
const schema = ctx.schemaRegistry.get("users");
|
|
2364
|
-
if (!schema)
|
|
2365
|
-
ctx.send(ws, { type: MSG.USER_LIST, payload: { users: [] } });
|
|
2366
|
-
return;
|
|
2367
|
-
}
|
|
2393
|
+
if (!schema) return [];
|
|
2368
2394
|
const records = getAllUserRecords(ctx.sql, ctx.schemaRegistry);
|
|
2369
|
-
|
|
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);
|
|
2370
2399
|
ctx.send(ws, { type: MSG.USER_LIST, payload: { users } });
|
|
2371
2400
|
}
|
|
2372
2401
|
function broadcastUserList(ctx, except) {
|
|
@@ -2381,7 +2410,9 @@ function broadcastUserList(ctx, except) {
|
|
|
2381
2410
|
if (!attachment) continue;
|
|
2382
2411
|
ctx.send(other, {
|
|
2383
2412
|
type: MSG.USER_LIST,
|
|
2384
|
-
payload: {
|
|
2413
|
+
payload: {
|
|
2414
|
+
users: rosterFor(ctx, schema, records, publicRoster, attachment.userId, attachment.role)
|
|
2415
|
+
}
|
|
2385
2416
|
});
|
|
2386
2417
|
}
|
|
2387
2418
|
}
|
|
@@ -2434,8 +2465,8 @@ var CORS_HEADERS = {
|
|
|
2434
2465
|
"Access-Control-Allow-Headers": "Content-Type, X-User-Id, X-App-Action",
|
|
2435
2466
|
"Content-Type": "application/json"
|
|
2436
2467
|
};
|
|
2437
|
-
async function handleToolsRequest(ctx,
|
|
2438
|
-
if (
|
|
2468
|
+
async function handleToolsRequest(ctx, request2, path) {
|
|
2469
|
+
if (request2.method === "OPTIONS") {
|
|
2439
2470
|
return new Response(null, { headers: CORS_HEADERS });
|
|
2440
2471
|
}
|
|
2441
2472
|
if (path === "tools/list") {
|
|
@@ -2452,15 +2483,15 @@ async function handleToolsRequest(ctx, request, path) {
|
|
|
2452
2483
|
}
|
|
2453
2484
|
return Response.json({ tool: tool3 }, { headers: CORS_HEADERS });
|
|
2454
2485
|
}
|
|
2455
|
-
if (path.startsWith("tools/execute") &&
|
|
2456
|
-
return handleToolExecute(ctx,
|
|
2486
|
+
if (path.startsWith("tools/execute") && request2.method === "POST") {
|
|
2487
|
+
return handleToolExecute(ctx, request2);
|
|
2457
2488
|
}
|
|
2458
2489
|
return Response.json({ error: "Not found" }, { status: 404, headers: CORS_HEADERS });
|
|
2459
2490
|
}
|
|
2460
|
-
async function handleToolExecute(ctx,
|
|
2491
|
+
async function handleToolExecute(ctx, request2) {
|
|
2461
2492
|
let body;
|
|
2462
2493
|
try {
|
|
2463
|
-
body = await
|
|
2494
|
+
body = await request2.json();
|
|
2464
2495
|
} catch {
|
|
2465
2496
|
return Response.json(
|
|
2466
2497
|
{ success: false, error: "Invalid JSON body" },
|
|
@@ -2474,8 +2505,8 @@ async function handleToolExecute(ctx, request) {
|
|
|
2474
2505
|
{ status: 400, headers: CORS_HEADERS }
|
|
2475
2506
|
);
|
|
2476
2507
|
}
|
|
2477
|
-
const userId =
|
|
2478
|
-
const isAppAction =
|
|
2508
|
+
const userId = request2.headers.get("x-user-id") ?? "";
|
|
2509
|
+
const isAppAction = request2.headers.get("x-app-action")?.toLowerCase() === "true";
|
|
2479
2510
|
let userRole = "viewer";
|
|
2480
2511
|
if (userId && ctx.ownerUserId && userId === ctx.ownerUserId) {
|
|
2481
2512
|
userRole = "admin";
|
|
@@ -2510,9 +2541,14 @@ async function executeTool(ctx, toolName, params, userId, userRole, skipUserRbac
|
|
|
2510
2541
|
if (!collection) return { success: false, error: "Missing required param: collection" };
|
|
2511
2542
|
const resolved = resolveCollection(ctx, collection);
|
|
2512
2543
|
if (!resolved.ok) return { success: false, error: resolved.error };
|
|
2544
|
+
if (params.where !== void 0) {
|
|
2545
|
+
const refusal = refuseUnknownWhere(collection, resolved.schema, params.where);
|
|
2546
|
+
if (refusal) return refusal;
|
|
2547
|
+
}
|
|
2548
|
+
const where = params.where;
|
|
2513
2549
|
const query = {
|
|
2514
2550
|
collection,
|
|
2515
|
-
where
|
|
2551
|
+
where,
|
|
2516
2552
|
orderBy: params.orderBy,
|
|
2517
2553
|
orderDir: params.orderDir,
|
|
2518
2554
|
limit: params.limit
|
|
@@ -2618,8 +2654,16 @@ async function executeTool(ctx, toolName, params, userId, userRole, skipUserRbac
|
|
|
2618
2654
|
if (!user) return { success: false, error: "User not found" };
|
|
2619
2655
|
return { success: true, data: { user } };
|
|
2620
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.
|
|
2621
2665
|
case "user.list": {
|
|
2622
|
-
const users = getAllUsers(ctx.sql, ctx.schemaRegistry);
|
|
2666
|
+
const users = skipUserRbac ? getAllUsers(ctx.sql, ctx.schemaRegistry) : rosterForCaller(ctx, userId, userRole);
|
|
2623
2667
|
return { success: true, data: { users } };
|
|
2624
2668
|
}
|
|
2625
2669
|
default:
|
|
@@ -2754,25 +2798,25 @@ var CORS_HEADERS2 = {
|
|
|
2754
2798
|
"Access-Control-Allow-Headers": "Content-Type",
|
|
2755
2799
|
"Content-Type": "application/json"
|
|
2756
2800
|
};
|
|
2757
|
-
async function handleApiRequest(ctx,
|
|
2758
|
-
if (
|
|
2801
|
+
async function handleApiRequest(ctx, request2, url) {
|
|
2802
|
+
if (request2.method === "OPTIONS") {
|
|
2759
2803
|
return new Response(null, { headers: CORS_HEADERS2 });
|
|
2760
2804
|
}
|
|
2761
2805
|
const path = url.pathname.replace("/api/", "");
|
|
2762
2806
|
if (path.startsWith("tools/")) {
|
|
2763
|
-
return handleToolsRequest(ctx,
|
|
2807
|
+
return handleToolsRequest(ctx, request2, path);
|
|
2764
2808
|
}
|
|
2765
2809
|
if (path === "debug/status") {
|
|
2766
2810
|
return handleDebugStatus(ctx);
|
|
2767
2811
|
}
|
|
2768
2812
|
if (path === "debug/sql") {
|
|
2769
|
-
return handleDebugSql(ctx,
|
|
2813
|
+
return handleDebugSql(ctx, request2, url);
|
|
2770
2814
|
}
|
|
2771
2815
|
if (path === "debug/user-role") {
|
|
2772
2816
|
return handleDebugUserRole(ctx, url);
|
|
2773
2817
|
}
|
|
2774
|
-
if (path === "debug/set-role" &&
|
|
2775
|
-
return handleDebugSetRole(ctx,
|
|
2818
|
+
if (path === "debug/set-role" && request2.method === "POST") {
|
|
2819
|
+
return handleDebugSetRole(ctx, request2);
|
|
2776
2820
|
}
|
|
2777
2821
|
if (path.startsWith("debug/records/")) {
|
|
2778
2822
|
return handleDebugRecords(ctx, path);
|
|
@@ -2803,8 +2847,8 @@ function handleDebugUserRole(ctx, url) {
|
|
|
2803
2847
|
role: user.role
|
|
2804
2848
|
}, { headers: CORS_HEADERS2 });
|
|
2805
2849
|
}
|
|
2806
|
-
async function handleDebugSetRole(ctx,
|
|
2807
|
-
const body = await
|
|
2850
|
+
async function handleDebugSetRole(ctx, request2) {
|
|
2851
|
+
const body = await request2.json();
|
|
2808
2852
|
if (!body.role) {
|
|
2809
2853
|
return Response.json({ error: "role is required" }, { status: 400, headers: CORS_HEADERS2 });
|
|
2810
2854
|
}
|
|
@@ -2912,11 +2956,11 @@ function handleDebugQuery(ctx, url) {
|
|
|
2912
2956
|
records
|
|
2913
2957
|
}, { headers: CORS_HEADERS2 });
|
|
2914
2958
|
}
|
|
2915
|
-
async function handleDebugSql(ctx,
|
|
2959
|
+
async function handleDebugSql(ctx, request2, url) {
|
|
2916
2960
|
let sql;
|
|
2917
2961
|
let params = [];
|
|
2918
|
-
if (
|
|
2919
|
-
const body = await
|
|
2962
|
+
if (request2.method === "POST") {
|
|
2963
|
+
const body = await request2.json();
|
|
2920
2964
|
sql = body.sql;
|
|
2921
2965
|
params = body.params ?? [];
|
|
2922
2966
|
} else {
|
|
@@ -3036,10 +3080,10 @@ var RecordRoom = class extends BaseRoom {
|
|
|
3036
3080
|
// ============================================================================
|
|
3037
3081
|
// HTTP Entry Point (overrides BaseRoom.fetch)
|
|
3038
3082
|
// ============================================================================
|
|
3039
|
-
async fetch(
|
|
3040
|
-
const url = new URL(
|
|
3083
|
+
async fetch(request2) {
|
|
3084
|
+
const url = new URL(request2.url);
|
|
3041
3085
|
if (url.pathname.startsWith("/internal/")) {
|
|
3042
|
-
const internal = await this.handleInternalRequest(
|
|
3086
|
+
const internal = await this.handleInternalRequest(request2, url);
|
|
3043
3087
|
if (internal) return internal;
|
|
3044
3088
|
}
|
|
3045
3089
|
const fetchStart = Date.now();
|
|
@@ -3052,11 +3096,11 @@ var RecordRoom = class extends BaseRoom {
|
|
|
3052
3096
|
if (url.pathname.startsWith("/api/debug/") && !this.debugRoutesEnabled) {
|
|
3053
3097
|
return new Response("Not Found", { status: 404 });
|
|
3054
3098
|
}
|
|
3055
|
-
return handleApiRequest(this.createHandlerContext(),
|
|
3099
|
+
return handleApiRequest(this.createHandlerContext(), request2, url);
|
|
3056
3100
|
}
|
|
3057
|
-
if (
|
|
3101
|
+
if (request2.headers.get("Upgrade") === "websocket") {
|
|
3058
3102
|
this._fetchTiming = { fetchStart, isColdStart, needsInit, initMs };
|
|
3059
|
-
return super.fetch(
|
|
3103
|
+
return super.fetch(request2);
|
|
3060
3104
|
}
|
|
3061
3105
|
return new Response("Not Found", { status: 404 });
|
|
3062
3106
|
}
|
|
@@ -3638,9 +3682,9 @@ var CanvasRoom = class extends BaseRoom {
|
|
|
3638
3682
|
// --------------------------------------------------------------------------
|
|
3639
3683
|
// BaseRoom Lifecycle
|
|
3640
3684
|
// --------------------------------------------------------------------------
|
|
3641
|
-
async fetch(
|
|
3685
|
+
async fetch(request2) {
|
|
3642
3686
|
this.ensureInitialized();
|
|
3643
|
-
return super.fetch(
|
|
3687
|
+
return super.fetch(request2);
|
|
3644
3688
|
}
|
|
3645
3689
|
onConnect(ws, user) {
|
|
3646
3690
|
this.ensureInitialized();
|
|
@@ -4119,6 +4163,7 @@ function cronMatches(parsed, timezone, date) {
|
|
|
4119
4163
|
}
|
|
4120
4164
|
|
|
4121
4165
|
// src/server/rooms/cron-room.ts
|
|
4166
|
+
var CRON_TASKS_INTERNAL_PATH = "/internal/cron/tasks";
|
|
4122
4167
|
var CRON_WRITE_TYPES = /* @__PURE__ */ new Set([
|
|
4123
4168
|
MSG.CRON_TRIGGER,
|
|
4124
4169
|
MSG.CRON_PAUSE,
|
|
@@ -4193,9 +4238,15 @@ var CronRoom = class extends BaseRoom {
|
|
|
4193
4238
|
// ==========================================================================
|
|
4194
4239
|
// BaseRoom Lifecycle
|
|
4195
4240
|
// ==========================================================================
|
|
4196
|
-
async fetch(
|
|
4241
|
+
async fetch(request2) {
|
|
4197
4242
|
this.ensureInitialized();
|
|
4198
|
-
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);
|
|
4199
4250
|
}
|
|
4200
4251
|
onConnect(ws, user) {
|
|
4201
4252
|
this.ensureInitialized();
|
|
@@ -4343,15 +4394,22 @@ var CronRoom = class extends BaseRoom {
|
|
|
4343
4394
|
const start = Date.now();
|
|
4344
4395
|
let success = true;
|
|
4345
4396
|
let error;
|
|
4397
|
+
let thrown;
|
|
4346
4398
|
try {
|
|
4347
4399
|
await this.onTask(taskName);
|
|
4348
4400
|
} catch (e) {
|
|
4349
4401
|
success = false;
|
|
4402
|
+
thrown = e;
|
|
4350
4403
|
error = e instanceof Error ? e.message : String(e);
|
|
4351
|
-
console.error(`[CronRoom] Task "${taskName}" failed:`, e);
|
|
4352
4404
|
}
|
|
4353
4405
|
const durationMs = Date.now() - start;
|
|
4354
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
|
+
}
|
|
4355
4413
|
this.sql.exec(
|
|
4356
4414
|
`INSERT INTO cron_history (task_name, started_at, completed_at, success, duration_ms, error, created_at)
|
|
4357
4415
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
@@ -4460,6 +4518,19 @@ var CronRoom = class extends BaseRoom {
|
|
|
4460
4518
|
);
|
|
4461
4519
|
}
|
|
4462
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
|
+
}
|
|
4463
4534
|
function computeNextRunAt(task, from) {
|
|
4464
4535
|
if (task.interval_minutes) {
|
|
4465
4536
|
return new Date(from.getTime() + task.interval_minutes * 60 * 1e3).toISOString();
|
|
@@ -4532,9 +4603,9 @@ var JobRoom = class extends BaseRoom {
|
|
|
4532
4603
|
// ==========================================================================
|
|
4533
4604
|
// BaseRoom Lifecycle
|
|
4534
4605
|
// ==========================================================================
|
|
4535
|
-
async fetch(
|
|
4606
|
+
async fetch(request2) {
|
|
4536
4607
|
this.ensureInitialized();
|
|
4537
|
-
return super.fetch(
|
|
4608
|
+
return super.fetch(request2);
|
|
4538
4609
|
}
|
|
4539
4610
|
async onConnect(ws, user) {
|
|
4540
4611
|
this.ensureInitialized();
|
|
@@ -4621,13 +4692,13 @@ var JobRoom = class extends BaseRoom {
|
|
|
4621
4692
|
* Subclasses overriding `onRequest` should call `super.onRequest(req)`
|
|
4622
4693
|
* for paths they don't handle.
|
|
4623
4694
|
*/
|
|
4624
|
-
async onRequest(
|
|
4695
|
+
async onRequest(request2) {
|
|
4625
4696
|
this.ensureInitialized();
|
|
4626
|
-
const url = new URL(
|
|
4627
|
-
if (
|
|
4697
|
+
const url = new URL(request2.url);
|
|
4698
|
+
if (request2.method !== "POST" || !url.pathname.endsWith("/enqueue")) {
|
|
4628
4699
|
return new Response("Not Found", { status: 404 });
|
|
4629
4700
|
}
|
|
4630
|
-
const body = await
|
|
4701
|
+
const body = await request2.json().catch(() => null);
|
|
4631
4702
|
if (!body || typeof body.type !== "string" || !body.type) {
|
|
4632
4703
|
return new Response(JSON.stringify({ error: "type required" }), {
|
|
4633
4704
|
status: 400,
|
|
@@ -4793,7 +4864,8 @@ var JobRoom = class extends BaseRoom {
|
|
|
4793
4864
|
} catch (e) {
|
|
4794
4865
|
outcome = "failed";
|
|
4795
4866
|
errorMessage = e instanceof Error ? e.message : String(e);
|
|
4796
|
-
|
|
4867
|
+
const stack = e instanceof Error ? e.stack : void 0;
|
|
4868
|
+
console.error(`[jobs] ${row.type} (${row.id}) failed: ${errorMessage}`, ...stack ? [stack] : []);
|
|
4797
4869
|
} finally {
|
|
4798
4870
|
this.inFlight.delete(row.id);
|
|
4799
4871
|
}
|
|
@@ -5107,7 +5179,8 @@ var AUTO_PROVISIONABLE_TYPES = /* @__PURE__ */ new Set([
|
|
|
5107
5179
|
"kv_namespace",
|
|
5108
5180
|
"vectorize",
|
|
5109
5181
|
"r2_bucket",
|
|
5110
|
-
"queue"
|
|
5182
|
+
"queue",
|
|
5183
|
+
"ai_search"
|
|
5111
5184
|
]);
|
|
5112
5185
|
var VECTORIZE_METRICS = /* @__PURE__ */ new Set(["cosine", "euclidean", "dot-product"]);
|
|
5113
5186
|
function isAutoProvision(b) {
|
|
@@ -5122,6 +5195,8 @@ function isAutoProvision(b) {
|
|
|
5122
5195
|
return b.bucket_name === AUTO_PROVISION_SENTINEL;
|
|
5123
5196
|
case "queue":
|
|
5124
5197
|
return b.queue_name === AUTO_PROVISION_SENTINEL;
|
|
5198
|
+
case "ai_search":
|
|
5199
|
+
return b.instance_name === AUTO_PROVISION_SENTINEL;
|
|
5125
5200
|
default:
|
|
5126
5201
|
return false;
|
|
5127
5202
|
}
|
|
@@ -5136,7 +5211,8 @@ var ALLOWED_BINDING_TYPES = /* @__PURE__ */ new Set([
|
|
|
5136
5211
|
"browser_rendering",
|
|
5137
5212
|
"analytics_engine",
|
|
5138
5213
|
"hyperdrive",
|
|
5139
|
-
"ratelimit"
|
|
5214
|
+
"ratelimit",
|
|
5215
|
+
"ai_search"
|
|
5140
5216
|
]);
|
|
5141
5217
|
var RESERVED_BINDING_NAMES = /* @__PURE__ */ new Set([
|
|
5142
5218
|
"ASSETS",
|
|
@@ -5160,6 +5236,7 @@ function validateBindingManifest(manifest) {
|
|
|
5160
5236
|
}
|
|
5161
5237
|
const errors = [];
|
|
5162
5238
|
const seenNames = /* @__PURE__ */ new Set();
|
|
5239
|
+
let aiSearchCount = 0;
|
|
5163
5240
|
for (const entry of manifest) {
|
|
5164
5241
|
if (!entry || typeof entry !== "object") {
|
|
5165
5242
|
errors.push({
|
|
@@ -5190,14 +5267,23 @@ function validateBindingManifest(manifest) {
|
|
|
5190
5267
|
}
|
|
5191
5268
|
seenNames.add(name);
|
|
5192
5269
|
const binding = entry;
|
|
5270
|
+
if (binding.type === "ai_search") aiSearchCount++;
|
|
5193
5271
|
const fieldErr = requiredFieldError(binding);
|
|
5194
5272
|
if (fieldErr) errors.push({ binding, reason: fieldErr });
|
|
5195
5273
|
}
|
|
5274
|
+
if (aiSearchCount > 1) {
|
|
5275
|
+
errors.push({ reason: "Only one ai_search binding may be declared per app" });
|
|
5276
|
+
}
|
|
5196
5277
|
if (errors.length) return { valid: false, errors };
|
|
5197
5278
|
return { valid: true, bindings: manifest };
|
|
5198
5279
|
}
|
|
5199
5280
|
function requiredFieldError(b) {
|
|
5200
5281
|
switch (b.type) {
|
|
5282
|
+
case "ai_search":
|
|
5283
|
+
if (b.instance_name !== AUTO_PROVISION_SENTINEL) {
|
|
5284
|
+
return `ai_search binding "${b.name}" must set instance_name = "auto"; managed instances cannot be selected directly`;
|
|
5285
|
+
}
|
|
5286
|
+
return null;
|
|
5201
5287
|
case "vectorize": {
|
|
5202
5288
|
if (!b.index_name) return `vectorize binding "${b.name}" missing index_name (or "auto")`;
|
|
5203
5289
|
if (b.index_name === AUTO_PROVISION_SENTINEL) {
|
|
@@ -5259,6 +5345,13 @@ function requiredFieldError(b) {
|
|
|
5259
5345
|
}
|
|
5260
5346
|
}
|
|
5261
5347
|
function bindingManifestFromOutputConfig(outputConfig) {
|
|
5348
|
+
const aiSearchNamespaces = outputConfig.ai_search_namespaces;
|
|
5349
|
+
const declaresAiSearchNamespace = Array.isArray(aiSearchNamespaces) ? aiSearchNamespaces.length > 0 : aiSearchNamespaces !== void 0 && aiSearchNamespaces !== null;
|
|
5350
|
+
if (declaresAiSearchNamespace) {
|
|
5351
|
+
throw new Error(
|
|
5352
|
+
'ai_search_namespaces is not supported by DeepSpace; declare one [[ai_search]] instance_name = "auto" instead'
|
|
5353
|
+
);
|
|
5354
|
+
}
|
|
5262
5355
|
const out = [];
|
|
5263
5356
|
const aiBinding = pluckString(outputConfig.ai, "binding");
|
|
5264
5357
|
if (aiBinding) out.push({ type: "ai", name: aiBinding });
|
|
@@ -5276,6 +5369,11 @@ function bindingManifestFromOutputConfig(outputConfig) {
|
|
|
5276
5369
|
...metric && (metric === "cosine" || metric === "euclidean" || metric === "dot-product") && { metric }
|
|
5277
5370
|
});
|
|
5278
5371
|
}
|
|
5372
|
+
for (const a of asObjectArray(outputConfig.ai_search)) {
|
|
5373
|
+
const name = pluckString(a, "binding");
|
|
5374
|
+
const instance_name = pluckString(a, "instance_name");
|
|
5375
|
+
if (name && instance_name) out.push({ type: "ai_search", name, instance_name });
|
|
5376
|
+
}
|
|
5279
5377
|
for (const r of asObjectArray(outputConfig.r2_buckets)) {
|
|
5280
5378
|
const name = pluckString(r, "binding");
|
|
5281
5379
|
const bucket_name = pluckString(r, "bucket_name");
|
|
@@ -5811,218 +5909,8 @@ var DIRECTORY_SCHEMAS = [
|
|
|
5811
5909
|
postsSchema
|
|
5812
5910
|
];
|
|
5813
5911
|
|
|
5814
|
-
// src/server/schemas/workspace.ts
|
|
5815
|
-
var workspaceTeamsSchema = {
|
|
5816
|
-
name: "teams",
|
|
5817
|
-
columns: [
|
|
5818
|
-
{ name: "Name", storage: "text", interpretation: "plain" },
|
|
5819
|
-
{ name: "CreatedBy", storage: "text", interpretation: "plain" },
|
|
5820
|
-
{ name: "IsOpen", storage: "number", interpretation: { kind: "boolean" } }
|
|
5821
|
-
],
|
|
5822
|
-
teamField: "_rowId",
|
|
5823
|
-
permissions: {
|
|
5824
|
-
admin: { read: true, create: true, update: true, delete: true },
|
|
5825
|
-
member: { read: "team", create: true, update: "team", delete: "own" }
|
|
5826
|
-
}
|
|
5827
|
-
};
|
|
5828
|
-
var workspaceTeamMembersSchema = {
|
|
5829
|
-
name: "team_members",
|
|
5830
|
-
columns: [
|
|
5831
|
-
{ name: "TeamId", storage: "text", interpretation: "plain" },
|
|
5832
|
-
{ name: "UserId", storage: "text", interpretation: "plain" },
|
|
5833
|
-
{ name: "RoleInTeam", storage: "text", interpretation: { kind: "select", options: ["admin", "member"] } },
|
|
5834
|
-
{ name: "JoinedAt", storage: "text", interpretation: { kind: "datetime" } },
|
|
5835
|
-
{ name: "Email", storage: "text", interpretation: { kind: "email" } },
|
|
5836
|
-
{ name: "Status", storage: "text", interpretation: { kind: "select", options: ["active", "invited"] } }
|
|
5837
|
-
],
|
|
5838
|
-
teamField: "TeamId",
|
|
5839
|
-
permissions: {
|
|
5840
|
-
admin: { read: true, create: true, update: true, delete: true },
|
|
5841
|
-
member: { read: "team", create: true, update: "team", delete: "team" }
|
|
5842
|
-
}
|
|
5843
|
-
};
|
|
5844
|
-
var workspaceTasksSchema = {
|
|
5845
|
-
name: "tasks",
|
|
5846
|
-
columns: [
|
|
5847
|
-
{ name: "TeamId", storage: "text", interpretation: "plain" },
|
|
5848
|
-
{ name: "Title", storage: "text", interpretation: "plain" },
|
|
5849
|
-
{ name: "Notes", storage: "text", interpretation: "plain" },
|
|
5850
|
-
{ name: "Completed", storage: "number", interpretation: { kind: "boolean" } },
|
|
5851
|
-
{ name: "CompletedAt", storage: "number", interpretation: "plain" },
|
|
5852
|
-
{ name: "Deleted", storage: "number", interpretation: { kind: "boolean" } },
|
|
5853
|
-
{ name: "DeletedAt", storage: "number", interpretation: "plain" },
|
|
5854
|
-
{ name: "Priority", storage: "text", interpretation: { kind: "select", options: ["none", "low", "medium", "high"] } },
|
|
5855
|
-
{ name: "DueDate", storage: "text", interpretation: "plain" },
|
|
5856
|
-
{ name: "ProjectId", storage: "text", interpretation: "plain" },
|
|
5857
|
-
{ name: "KanbanStatus", storage: "text", interpretation: { kind: "select", options: ["backlog", "ready", "in_progress", "review", "done"] } },
|
|
5858
|
-
{ name: "Order", storage: "number", interpretation: "plain" },
|
|
5859
|
-
{ name: "AssignedUser", storage: "text", interpretation: { kind: "json" } },
|
|
5860
|
-
{ name: "AssignedBy", storage: "text", interpretation: { kind: "json" } },
|
|
5861
|
-
{ name: "TagIds", storage: "text", interpretation: { kind: "json" } },
|
|
5862
|
-
{ name: "CreatedAt", storage: "number", interpretation: "plain" }
|
|
5863
|
-
],
|
|
5864
|
-
teamField: "TeamId",
|
|
5865
|
-
permissions: {
|
|
5866
|
-
admin: { read: true, create: true, update: true, delete: true },
|
|
5867
|
-
member: { read: "team", create: true, update: "team", delete: "team" },
|
|
5868
|
-
viewer: { read: false, create: false, update: false, delete: false }
|
|
5869
|
-
}
|
|
5870
|
-
};
|
|
5871
|
-
var workspaceProjectsSchema = {
|
|
5872
|
-
name: "projects",
|
|
5873
|
-
columns: [
|
|
5874
|
-
{ name: "TeamId", storage: "text", interpretation: "plain" },
|
|
5875
|
-
{ name: "Title", storage: "text", interpretation: "plain" },
|
|
5876
|
-
{ name: "Notes", storage: "text", interpretation: "plain" },
|
|
5877
|
-
{ name: "Color", storage: "text", interpretation: "plain" },
|
|
5878
|
-
{ name: "ParentId", storage: "text", interpretation: "plain" },
|
|
5879
|
-
{ name: "Order", storage: "number", interpretation: "plain" },
|
|
5880
|
-
{ name: "CreatedAt", storage: "number", interpretation: "plain" }
|
|
5881
|
-
],
|
|
5882
|
-
teamField: "TeamId",
|
|
5883
|
-
permissions: {
|
|
5884
|
-
admin: { read: true, create: true, update: true, delete: true },
|
|
5885
|
-
member: { read: "team", create: true, update: "team", delete: "team" },
|
|
5886
|
-
viewer: { read: false, create: false, update: false, delete: false }
|
|
5887
|
-
}
|
|
5888
|
-
};
|
|
5889
|
-
var workspaceTagsSchema = {
|
|
5890
|
-
name: "tags",
|
|
5891
|
-
columns: [
|
|
5892
|
-
{ name: "TeamId", storage: "text", interpretation: "plain" },
|
|
5893
|
-
{ name: "Name", storage: "text", interpretation: "plain" },
|
|
5894
|
-
{ name: "Color", storage: "text", interpretation: "plain" },
|
|
5895
|
-
{ name: "CreatedAt", storage: "number", interpretation: "plain" }
|
|
5896
|
-
],
|
|
5897
|
-
teamField: "TeamId",
|
|
5898
|
-
permissions: {
|
|
5899
|
-
admin: { read: true, create: true, update: true, delete: true },
|
|
5900
|
-
member: { read: "team", create: true, update: "team", delete: "team" },
|
|
5901
|
-
viewer: { read: false, create: false, update: false, delete: false }
|
|
5902
|
-
}
|
|
5903
|
-
};
|
|
5904
|
-
var workspacePeopleSchema = {
|
|
5905
|
-
name: "people",
|
|
5906
|
-
columns: [
|
|
5907
|
-
{ name: "Name", storage: "text", interpretation: "plain" },
|
|
5908
|
-
{ name: "Email", storage: "text", interpretation: { kind: "email" } },
|
|
5909
|
-
{ name: "Type", storage: "text", interpretation: { kind: "select", options: ["employee", "customer", "vendor", "contact"] } },
|
|
5910
|
-
{ name: "Status", storage: "text", interpretation: { kind: "select", options: ["active", "inactive", "archived"] } },
|
|
5911
|
-
{ name: "CompanyId", storage: "text", interpretation: "plain" },
|
|
5912
|
-
{ name: "Department", storage: "text", interpretation: "plain" },
|
|
5913
|
-
{ name: "Title", storage: "text", interpretation: "plain" },
|
|
5914
|
-
{ name: "LastContactedAt", storage: "text", interpretation: { kind: "datetime" } },
|
|
5915
|
-
{ name: "Metadata", storage: "text", interpretation: { kind: "json" } }
|
|
5916
|
-
],
|
|
5917
|
-
permissions: {
|
|
5918
|
-
"*": { read: true, create: true, update: true, delete: false },
|
|
5919
|
-
admin: { read: true, create: true, update: true, delete: true }
|
|
5920
|
-
}
|
|
5921
|
-
};
|
|
5922
|
-
var workspaceTransactionsSchema = {
|
|
5923
|
-
name: "transactions",
|
|
5924
|
-
columns: [
|
|
5925
|
-
{ name: "Type", storage: "text", interpretation: { kind: "select", options: ["revenue", "expense", "payment", "transfer", "invoice", "refund"] } },
|
|
5926
|
-
{ name: "Amount", storage: "number", interpretation: { kind: "currency", symbol: "$", decimals: 2 } },
|
|
5927
|
-
{ name: "Currency", storage: "text", interpretation: "plain" },
|
|
5928
|
-
{ name: "Description", storage: "text", interpretation: "plain" },
|
|
5929
|
-
{ name: "Category", storage: "text", interpretation: "plain" },
|
|
5930
|
-
{ name: "CounterpartyRef", storage: "text", interpretation: "plain" },
|
|
5931
|
-
{ name: "OrderRef", storage: "text", interpretation: "plain" },
|
|
5932
|
-
{ name: "Account", storage: "text", interpretation: "plain" }
|
|
5933
|
-
],
|
|
5934
|
-
permissions: {
|
|
5935
|
-
"*": { read: true, create: true, update: true, delete: false },
|
|
5936
|
-
admin: { read: true, create: true, update: true, delete: true }
|
|
5937
|
-
}
|
|
5938
|
-
};
|
|
5939
|
-
var workspaceAccountsSchema = {
|
|
5940
|
-
name: "accounts",
|
|
5941
|
-
columns: [
|
|
5942
|
-
{ name: "Name", storage: "text", interpretation: "plain" },
|
|
5943
|
-
{ name: "Balance", storage: "number", interpretation: { kind: "currency", symbol: "$", decimals: 2 } },
|
|
5944
|
-
{ name: "Currency", storage: "text", interpretation: "plain" }
|
|
5945
|
-
],
|
|
5946
|
-
permissions: {
|
|
5947
|
-
"*": { read: true, create: true, update: true, delete: false },
|
|
5948
|
-
admin: { read: true, create: true, update: true, delete: true }
|
|
5949
|
-
}
|
|
5950
|
-
};
|
|
5951
|
-
var workspaceContentSharesSchema = {
|
|
5952
|
-
name: "content_shares",
|
|
5953
|
-
ownerField: "OwnerId",
|
|
5954
|
-
columns: [
|
|
5955
|
-
{ name: "ContentType", storage: "text", interpretation: { kind: "select", options: ["document", "slide", "spreadsheet"] } },
|
|
5956
|
-
{ name: "ContentId", storage: "text", interpretation: "plain" },
|
|
5957
|
-
{ name: "OwnerId", storage: "text", interpretation: "plain" },
|
|
5958
|
-
{ name: "OwnerName", storage: "text", interpretation: "plain" },
|
|
5959
|
-
{ name: "Title", storage: "text", interpretation: "plain" },
|
|
5960
|
-
{ name: "ShareType", storage: "text", interpretation: { kind: "select", options: ["self", "channel", "team", "direct", "link", "org"] } },
|
|
5961
|
-
{ name: "ShareTarget", storage: "text", interpretation: "plain" },
|
|
5962
|
-
{ name: "Permission", storage: "text", interpretation: { kind: "select", options: ["view", "edit"] } },
|
|
5963
|
-
{ name: "SharedAt", storage: "text", interpretation: { kind: "datetime" } },
|
|
5964
|
-
{ name: "SharedBy", storage: "text", interpretation: "plain" },
|
|
5965
|
-
{ name: "SourceApp", storage: "text", interpretation: "plain" },
|
|
5966
|
-
{ name: "WordCount", storage: "number", interpretation: "plain" },
|
|
5967
|
-
{ name: "LastEditedAt", storage: "text", interpretation: { kind: "datetime" } }
|
|
5968
|
-
],
|
|
5969
|
-
permissions: {
|
|
5970
|
-
"*": { read: true, create: true, update: true, delete: "own" },
|
|
5971
|
-
admin: { read: true, create: true, update: true, delete: true }
|
|
5972
|
-
}
|
|
5973
|
-
};
|
|
5974
|
-
var workspaceFormResponsesSchema = {
|
|
5975
|
-
name: "form_responses",
|
|
5976
|
-
columns: [
|
|
5977
|
-
{ name: "FormId", storage: "text", interpretation: "plain" },
|
|
5978
|
-
{ name: "FormTitle", storage: "text", interpretation: "plain" },
|
|
5979
|
-
{ name: "RespondentId", storage: "text", interpretation: "plain" },
|
|
5980
|
-
{ name: "RespondentEmail", storage: "text", interpretation: { kind: "email" } },
|
|
5981
|
-
{ name: "RespondentName", storage: "text", interpretation: "plain" },
|
|
5982
|
-
{ name: "Data", storage: "text", interpretation: { kind: "json" } },
|
|
5983
|
-
{ name: "SubmittedAt", storage: "text", interpretation: { kind: "datetime" } }
|
|
5984
|
-
],
|
|
5985
|
-
permissions: {
|
|
5986
|
-
"*": { read: true, create: true, update: false, delete: false },
|
|
5987
|
-
admin: { read: true, create: true, update: true, delete: true }
|
|
5988
|
-
}
|
|
5989
|
-
};
|
|
5990
|
-
var workspaceEmailHandlesSchema = {
|
|
5991
|
-
name: "email_handles",
|
|
5992
|
-
columns: [
|
|
5993
|
-
{ name: "UserId", storage: "text", interpretation: "plain" },
|
|
5994
|
-
{ name: "Handle", storage: "text", interpretation: "plain" },
|
|
5995
|
-
{ name: "EmailAddress", storage: "text", interpretation: { kind: "email" } },
|
|
5996
|
-
{ name: "Status", storage: "text", interpretation: { kind: "select", options: ["active", "disabled"] } }
|
|
5997
|
-
],
|
|
5998
|
-
uniqueOn: ["Handle"],
|
|
5999
|
-
ownerField: "UserId",
|
|
6000
|
-
permissions: {
|
|
6001
|
-
"*": { read: true, create: true, update: "own", delete: false },
|
|
6002
|
-
admin: { read: true, create: true, update: true, delete: true }
|
|
6003
|
-
}
|
|
6004
|
-
};
|
|
6005
|
-
var WORKSPACE_SCHEMAS = [
|
|
6006
|
-
workspaceTeamsSchema,
|
|
6007
|
-
workspaceTeamMembersSchema,
|
|
6008
|
-
workspaceTasksSchema,
|
|
6009
|
-
workspaceProjectsSchema,
|
|
6010
|
-
workspaceTagsSchema,
|
|
6011
|
-
workspacePeopleSchema,
|
|
6012
|
-
workspaceTransactionsSchema,
|
|
6013
|
-
workspaceAccountsSchema,
|
|
6014
|
-
workspaceContentSharesSchema,
|
|
6015
|
-
workspaceFormResponsesSchema,
|
|
6016
|
-
workspaceEmailHandlesSchema
|
|
6017
|
-
];
|
|
6018
|
-
|
|
6019
5912
|
// src/server/schemas/shared-do.ts
|
|
6020
5913
|
var GLOBAL_DO_TYPES = [
|
|
6021
|
-
{
|
|
6022
|
-
name: "workspace",
|
|
6023
|
-
schemas: WORKSPACE_SCHEMAS,
|
|
6024
|
-
description: "Shared workspace data (teams, tasks, projects, people, ledger)"
|
|
6025
|
-
},
|
|
6026
5914
|
{
|
|
6027
5915
|
name: "conv",
|
|
6028
5916
|
schemas: [...CONVERSATION_SCHEMAS, ...VOTING_SCHEMAS],
|
|
@@ -6050,6 +5938,9 @@ var APP_MIGRATION_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/;
|
|
|
6050
5938
|
var MAX_APP_MIGRATION_IDS = 256;
|
|
6051
5939
|
var WORKER_OWNED_NOT_FOUND_MIGRATION_ID = "2026-08-worker-owned-not-found";
|
|
6052
5940
|
var SECURE_ROOM_BOUNDARIES_MIGRATION_ID = "2026-08-secure-room-boundaries";
|
|
5941
|
+
var ACTION_TOOLS_DELETE_WHERE_MIGRATION_ID = "2026-08-action-tools-delete-where";
|
|
5942
|
+
var BUILD_INJECTED_APP_ID_MIGRATION_ID = "2026-08-build-injected-app-id";
|
|
5943
|
+
var ACTION_ROUTES_BEARER_GUARD_MIGRATION_ID = "2026-08-action-routes-bearer-guard";
|
|
6053
5944
|
function validateAppMigrationIds(value) {
|
|
6054
5945
|
if (!Array.isArray(value)) {
|
|
6055
5946
|
return { valid: false, reason: "app migrations must be a JSON string array" };
|
|
@@ -6918,8 +6809,8 @@ function overPartBound(bytes) {
|
|
|
6918
6809
|
if (bytes <= UPLOAD_PART_BYTES) return null;
|
|
6919
6810
|
return fail2(413, oversizePartMessage(bytes), "too_large", { maxBytes: UPLOAD_PART_BYTES });
|
|
6920
6811
|
}
|
|
6921
|
-
function declaredLength(
|
|
6922
|
-
const raw =
|
|
6812
|
+
function declaredLength(request2, what) {
|
|
6813
|
+
const raw = request2.headers.get("content-length");
|
|
6923
6814
|
const declared = raw === null ? NaN : Number(raw);
|
|
6924
6815
|
if (!Number.isSafeInteger(declared) || declared < 0) {
|
|
6925
6816
|
return fail2(
|
|
@@ -6997,7 +6888,7 @@ function multipartFailure(error) {
|
|
|
6997
6888
|
}
|
|
6998
6889
|
function createScopedR2Handler(config) {
|
|
6999
6890
|
const requireAuthForMutations = config.requireAuthForMutations ?? true;
|
|
7000
|
-
return async (
|
|
6891
|
+
return async (request2, url, bucket, auth) => {
|
|
7001
6892
|
let subpathRaw;
|
|
7002
6893
|
try {
|
|
7003
6894
|
subpathRaw = decodeURIComponent(url.pathname.replace("/api/files", "").replace(/^\//, ""));
|
|
@@ -7026,26 +6917,26 @@ function createScopedR2Handler(config) {
|
|
|
7026
6917
|
}
|
|
7027
6918
|
const prefix = result.prefix;
|
|
7028
6919
|
const excludedPrefixes = result.excludedPrefixes ?? [];
|
|
7029
|
-
if (isFilesVerbPath(
|
|
6920
|
+
if (isFilesVerbPath(request2.method, subpath)) {
|
|
7030
6921
|
if (requireAuthForMutations && !auth.userId) {
|
|
7031
|
-
await
|
|
6922
|
+
await request2.body?.cancel().catch(() => void 0);
|
|
7032
6923
|
return fail2(401, "Authentication required", "unauthorized");
|
|
7033
6924
|
}
|
|
7034
|
-
return subpath === "upload" ? handleUpload(
|
|
6925
|
+
return subpath === "upload" ? handleUpload(request2, url, bucket, prefix, excludedPrefixes, auth, scope) : handleMultipart(request2, url, bucket, prefix, excludedPrefixes, auth, scope, subpath);
|
|
7035
6926
|
}
|
|
7036
|
-
if (!subpath &&
|
|
6927
|
+
if (!subpath && request2.method === "GET") {
|
|
7037
6928
|
return handleList(bucket, prefix, excludedPrefixes, url, scope, auth);
|
|
7038
6929
|
}
|
|
7039
|
-
if (subpath &&
|
|
6930
|
+
if (subpath && request2.method === "GET") {
|
|
7040
6931
|
if (!isKeyWithinScope(subpath, prefix, excludedPrefixes)) {
|
|
7041
6932
|
return Response.json(
|
|
7042
6933
|
{ error: "Access denied: key outside scope" },
|
|
7043
6934
|
{ status: 403, headers: CORS_HEADERS4 }
|
|
7044
6935
|
);
|
|
7045
6936
|
}
|
|
7046
|
-
return handleDownload(
|
|
6937
|
+
return handleDownload(request2, bucket, subpath, scope);
|
|
7047
6938
|
}
|
|
7048
|
-
if (subpath &&
|
|
6939
|
+
if (subpath && request2.method === "DELETE") {
|
|
7049
6940
|
if (requireAuthForMutations && !auth.userId) {
|
|
7050
6941
|
return Response.json(
|
|
7051
6942
|
{ error: "Authentication required" },
|
|
@@ -7071,18 +6962,18 @@ function createScopedR2Handler(config) {
|
|
|
7071
6962
|
return Response.json({ error: "Method not allowed" }, { status: 405, headers: CORS_HEADERS4 });
|
|
7072
6963
|
};
|
|
7073
6964
|
}
|
|
7074
|
-
async function handleUpload(
|
|
6965
|
+
async function handleUpload(request2, url, bucket, prefix, excludedPrefixes, auth, scope) {
|
|
7075
6966
|
try {
|
|
7076
|
-
const declaredBytes = declaredLength(
|
|
6967
|
+
const declaredBytes = declaredLength(request2, "File uploads");
|
|
7077
6968
|
if (declaredBytes instanceof Response) return declaredBytes;
|
|
7078
6969
|
const tooLargeRequest = overRequestBound(declaredBytes, 8 * 1024);
|
|
7079
6970
|
if (tooLargeRequest) return tooLargeRequest;
|
|
7080
|
-
const contentType =
|
|
6971
|
+
const contentType = request2.headers.get("content-type") || "";
|
|
7081
6972
|
let fileData;
|
|
7082
6973
|
let fileName;
|
|
7083
6974
|
let rawMimeType;
|
|
7084
6975
|
if (contentType.includes("multipart/form-data")) {
|
|
7085
|
-
const formData = await
|
|
6976
|
+
const formData = await request2.formData();
|
|
7086
6977
|
const file = formData.get("file");
|
|
7087
6978
|
if (!file) return fail2(400, "No file provided", "bad_request");
|
|
7088
6979
|
const tooBig = overRequestBound(file.size);
|
|
@@ -7091,7 +6982,7 @@ async function handleUpload(request, url, bucket, prefix, excludedPrefixes, auth
|
|
|
7091
6982
|
fileName = formData.get("name")?.toString() || file.name;
|
|
7092
6983
|
rawMimeType = file.type || "application/octet-stream";
|
|
7093
6984
|
} else {
|
|
7094
|
-
const body = await
|
|
6985
|
+
const body = await request2.json();
|
|
7095
6986
|
if (!body.data || !body.name) return fail2(400, "Missing data or name", "bad_request");
|
|
7096
6987
|
const base64Data = body.data.replace(/^data:[^;]+;base64,/, "");
|
|
7097
6988
|
const tooBig = overRequestBound(Math.floor(base64Data.length * 3 / 4));
|
|
@@ -7172,15 +7063,15 @@ function storedFileJson(key, prefix, name, url, scope) {
|
|
|
7172
7063
|
);
|
|
7173
7064
|
}
|
|
7174
7065
|
var MAX_CONTROL_BODY_BYTES = 64 * 1024;
|
|
7175
|
-
async function readControlJson(
|
|
7176
|
-
const declared = declaredLength(
|
|
7066
|
+
async function readControlJson(request2) {
|
|
7067
|
+
const declared = declaredLength(request2, "This request");
|
|
7177
7068
|
if (declared instanceof Response) return declared;
|
|
7178
7069
|
if (declared > MAX_CONTROL_BODY_BYTES) {
|
|
7179
7070
|
return fail2(413, `Request body exceeds ${MAX_CONTROL_BODY_BYTES} bytes`, "too_large");
|
|
7180
7071
|
}
|
|
7181
7072
|
let parsed;
|
|
7182
7073
|
try {
|
|
7183
|
-
parsed = await
|
|
7074
|
+
parsed = await request2.json();
|
|
7184
7075
|
} catch {
|
|
7185
7076
|
return fail2(400, "Body must be JSON", "bad_request");
|
|
7186
7077
|
}
|
|
@@ -7232,7 +7123,7 @@ function multipartLayoutError(declaredBytes) {
|
|
|
7232
7123
|
{ partSize: UPLOAD_PART_BYTES, partCount: multipartPartCount(declaredBytes) }
|
|
7233
7124
|
);
|
|
7234
7125
|
}
|
|
7235
|
-
async function streamPart(
|
|
7126
|
+
async function streamPart(upload2, partNumber, body, expectedBytes) {
|
|
7236
7127
|
let observedBytes = 0;
|
|
7237
7128
|
const sized = new FixedLengthStream(expectedBytes);
|
|
7238
7129
|
const pumped = body.pipeThrough(
|
|
@@ -7244,7 +7135,7 @@ async function streamPart(upload, partNumber, body, expectedBytes) {
|
|
|
7244
7135
|
})
|
|
7245
7136
|
).pipeTo(sized.writable);
|
|
7246
7137
|
const [uploaded, drained] = await Promise.allSettled([
|
|
7247
|
-
|
|
7138
|
+
upload2.uploadPart(partNumber, sized.readable),
|
|
7248
7139
|
pumped
|
|
7249
7140
|
]);
|
|
7250
7141
|
if (uploaded.status === "rejected" || drained.status === "rejected") {
|
|
@@ -7253,20 +7144,20 @@ async function streamPart(upload, partNumber, body, expectedBytes) {
|
|
|
7253
7144
|
}
|
|
7254
7145
|
return { ok: true, part: uploaded.value };
|
|
7255
7146
|
}
|
|
7256
|
-
async function handleMultipart(
|
|
7257
|
-
if (subpath === "multipart" &&
|
|
7258
|
-
return multipartInit(
|
|
7147
|
+
async function handleMultipart(request2, url, bucket, prefix, excludedPrefixes, auth, scope, subpath) {
|
|
7148
|
+
if (subpath === "multipart" && request2.method === "POST") {
|
|
7149
|
+
return multipartInit(request2, url, bucket, prefix, excludedPrefixes, auth);
|
|
7259
7150
|
}
|
|
7260
7151
|
if (subpath === "multipart/part") {
|
|
7261
|
-
return multipartPart(
|
|
7152
|
+
return multipartPart(request2, url, bucket, prefix, excludedPrefixes, auth);
|
|
7262
7153
|
}
|
|
7263
7154
|
if (subpath === "multipart/complete") {
|
|
7264
|
-
return multipartComplete(
|
|
7155
|
+
return multipartComplete(request2, url, bucket, prefix, excludedPrefixes, auth, scope);
|
|
7265
7156
|
}
|
|
7266
7157
|
return multipartAbort(url, bucket, prefix, excludedPrefixes, auth);
|
|
7267
7158
|
}
|
|
7268
|
-
async function multipartInit(
|
|
7269
|
-
const body = await readControlJson(
|
|
7159
|
+
async function multipartInit(request2, url, bucket, prefix, excludedPrefixes, auth) {
|
|
7160
|
+
const body = await readControlJson(request2);
|
|
7270
7161
|
if (body instanceof Response) return body;
|
|
7271
7162
|
const size = body.size;
|
|
7272
7163
|
if (!Number.isSafeInteger(size) || size <= 0) {
|
|
@@ -7307,9 +7198,9 @@ async function multipartInit(request, url, bucket, prefix, excludedPrefixes, aut
|
|
|
7307
7198
|
"request_id_conflict"
|
|
7308
7199
|
);
|
|
7309
7200
|
}
|
|
7310
|
-
let
|
|
7201
|
+
let upload2;
|
|
7311
7202
|
try {
|
|
7312
|
-
|
|
7203
|
+
upload2 = await bucket.createMultipartUpload(located.key, {
|
|
7313
7204
|
httpMetadata: { contentType: mimeType },
|
|
7314
7205
|
customMetadata: fileMetadata(name, auth.userId, requestId, size)
|
|
7315
7206
|
});
|
|
@@ -7321,7 +7212,7 @@ async function multipartInit(request, url, bucket, prefix, excludedPrefixes, aut
|
|
|
7321
7212
|
body: JSON.stringify({
|
|
7322
7213
|
version: 1,
|
|
7323
7214
|
reservationId: requestId,
|
|
7324
|
-
uploadId:
|
|
7215
|
+
uploadId: upload2.uploadId,
|
|
7325
7216
|
objectKey: located.key,
|
|
7326
7217
|
uploadKey: located.subkey,
|
|
7327
7218
|
admitted: false,
|
|
@@ -7335,7 +7226,7 @@ async function multipartInit(request, url, bucket, prefix, excludedPrefixes, aut
|
|
|
7335
7226
|
const reservedBytes = Math.max(size, 5 * 1024 * 1024);
|
|
7336
7227
|
const admission = await admitStorage(bucket, auth.storage, reservedBytes, 0, marker);
|
|
7337
7228
|
if (admission instanceof Response) {
|
|
7338
|
-
await
|
|
7229
|
+
await upload2.abort().catch(() => void 0);
|
|
7339
7230
|
if (admission.status === 409 && auth.storage) {
|
|
7340
7231
|
const winner = await readMultipartReservation(bucket, auth.storage, requestId);
|
|
7341
7232
|
if (winner && winner !== "invalid" && winner.objectKey === located.key && winner.uploadKey === located.subkey && winner.declaredBytes === size && winner.admitted) {
|
|
@@ -7345,11 +7236,11 @@ async function multipartInit(request, url, bucket, prefix, excludedPrefixes, aut
|
|
|
7345
7236
|
return admission;
|
|
7346
7237
|
}
|
|
7347
7238
|
if (auth.storage && !await activateMultipartReservation(bucket, auth.storage, requestId)) {
|
|
7348
|
-
await
|
|
7239
|
+
await upload2.abort().catch(() => void 0);
|
|
7349
7240
|
await releaseQuotaMarker(bucket, auth.storage, admission.markerKey, true);
|
|
7350
7241
|
return quotaUnavailable("This upload reservation could not be finalized. Retry shortly.");
|
|
7351
7242
|
}
|
|
7352
|
-
return multipartSessionJson(
|
|
7243
|
+
return multipartSessionJson(upload2.uploadId, located.subkey, requestId);
|
|
7353
7244
|
}
|
|
7354
7245
|
async function abandonMultipart(bucket, session, auth, reservation) {
|
|
7355
7246
|
let result = "aborted";
|
|
@@ -7367,12 +7258,12 @@ async function abandonMultipart(bucket, session, auth, reservation) {
|
|
|
7367
7258
|
}
|
|
7368
7259
|
return result;
|
|
7369
7260
|
}
|
|
7370
|
-
async function multipartPart(
|
|
7261
|
+
async function multipartPart(request2, url, bucket, prefix, excludedPrefixes, auth) {
|
|
7371
7262
|
const refuse = async (response) => {
|
|
7372
|
-
await
|
|
7263
|
+
await request2.body?.cancel().catch(() => void 0);
|
|
7373
7264
|
return response;
|
|
7374
7265
|
};
|
|
7375
|
-
const declaredBytes = declaredLength(
|
|
7266
|
+
const declaredBytes = declaredLength(request2, "Part uploads");
|
|
7376
7267
|
if (declaredBytes instanceof Response) return refuse(declaredBytes);
|
|
7377
7268
|
const tooBig = overPartBound(declaredBytes);
|
|
7378
7269
|
if (tooBig) return refuse(tooBig);
|
|
@@ -7384,7 +7275,7 @@ async function multipartPart(request, url, bucket, prefix, excludedPrefixes, aut
|
|
|
7384
7275
|
}
|
|
7385
7276
|
const session = resolveSession(url, prefix, excludedPrefixes);
|
|
7386
7277
|
if (session instanceof Response) return refuse(session);
|
|
7387
|
-
if (!
|
|
7278
|
+
if (!request2.body) return fail2(400, "A part body is required", "bad_request");
|
|
7388
7279
|
const reservation = await readMultipartReservation(bucket, auth.storage, session.reservationId);
|
|
7389
7280
|
if (reservation === "invalid") {
|
|
7390
7281
|
return refuse(quotaUnavailable("This upload reservation could not be read. Retry shortly."));
|
|
@@ -7426,8 +7317,8 @@ async function multipartPart(request, url, bucket, prefix, excludedPrefixes, aut
|
|
|
7426
7317
|
if (refreshed === "busy") {
|
|
7427
7318
|
return refuse(quotaUnavailable("This upload reservation is busy. Retry the part shortly."));
|
|
7428
7319
|
}
|
|
7429
|
-
const
|
|
7430
|
-
const stored = await streamPart(
|
|
7320
|
+
const upload2 = bucket.resumeMultipartUpload(session.key, session.uploadId);
|
|
7321
|
+
const stored = await streamPart(upload2, partNumber, request2.body, declaredBytes);
|
|
7431
7322
|
if (!stored.ok) {
|
|
7432
7323
|
if (r2ErrorCode(stored.error) === 10024 && reservation) {
|
|
7433
7324
|
await releaseQuotaMarker(bucket, auth.storage, reservation.key, true);
|
|
@@ -7483,8 +7374,8 @@ async function finishCompletedMultipart(bucket, session, reservation, object, au
|
|
|
7483
7374
|
const name = object.customMetadata?.originalName ?? session.uploadKey.split("/").pop() ?? "file";
|
|
7484
7375
|
return storedFileJson(session.key, prefix, name, url, scope);
|
|
7485
7376
|
}
|
|
7486
|
-
async function multipartComplete(
|
|
7487
|
-
const body = await readControlJson(
|
|
7377
|
+
async function multipartComplete(request2, url, bucket, prefix, excludedPrefixes, auth, scope) {
|
|
7378
|
+
const body = await readControlJson(request2);
|
|
7488
7379
|
if (body instanceof Response) return body;
|
|
7489
7380
|
const session = resolveSession(url, prefix, excludedPrefixes);
|
|
7490
7381
|
if (session instanceof Response) return session;
|
|
@@ -7643,7 +7534,7 @@ async function handleList(bucket, prefix, excludedPrefixes, url, scope, auth) {
|
|
|
7643
7534
|
{ headers: CORS_HEADERS4 }
|
|
7644
7535
|
);
|
|
7645
7536
|
}
|
|
7646
|
-
async function handleDownload(
|
|
7537
|
+
async function handleDownload(request2, bucket, key, scope) {
|
|
7647
7538
|
const object = await bucket.get(key);
|
|
7648
7539
|
if (!object) {
|
|
7649
7540
|
return Response.json({ error: "File not found" }, { status: 404, headers: CORS_HEADERS4 });
|
|
@@ -7665,7 +7556,7 @@ async function handleDownload(request, bucket, key, scope) {
|
|
|
7665
7556
|
contentDisposition(originalName, mustDownload ? "attachment" : "inline")
|
|
7666
7557
|
);
|
|
7667
7558
|
if (mustDownload) headers.set("Content-Security-Policy", "sandbox; default-src 'none'");
|
|
7668
|
-
if (scope === "app" &&
|
|
7559
|
+
if (scope === "app" && request2.headers.get("if-none-match") === object.httpEtag) {
|
|
7669
7560
|
return new Response(null, { status: 304, headers });
|
|
7670
7561
|
}
|
|
7671
7562
|
return new Response(object.body, { headers });
|
|
@@ -8118,8 +8009,8 @@ async function composioTools(env, options) {
|
|
|
8118
8009
|
if (!list.ok || !list.success) {
|
|
8119
8010
|
throw new Error(`composioTools: failed to list Composio tools: ${list.detail}`);
|
|
8120
8011
|
}
|
|
8121
|
-
const
|
|
8122
|
-
const items = Array.isArray(
|
|
8012
|
+
const listItems2 = list.data?.items;
|
|
8013
|
+
const items = Array.isArray(listItems2) ? listItems2 : [];
|
|
8123
8014
|
const tools = {};
|
|
8124
8015
|
for (const item of items) {
|
|
8125
8016
|
if (!item?.slug) continue;
|
|
@@ -8704,18 +8595,272 @@ var COST_RATES = {
|
|
|
8704
8595
|
queriedPerDim: VECTORIZE_QUERIED_USD_PER_M_DIMS / 1e6,
|
|
8705
8596
|
/** USD per stored dimension per month. */
|
|
8706
8597
|
storedPerDimPerMonth: VECTORIZE_STORED_USD_PER_100M_DIMS / 1e8
|
|
8598
|
+
},
|
|
8599
|
+
aiSearch: {
|
|
8600
|
+
ingestPerToken: 0.75 / 1e6,
|
|
8601
|
+
ingestImagePerToken: 0.5 / 1e6,
|
|
8602
|
+
storagePerByteMonth: 2 / 1e9,
|
|
8603
|
+
hybridOrSemanticPerQuery: 0.75 / 1e3,
|
|
8604
|
+
fulltextPerQuery: 0.1 / 1e3
|
|
8707
8605
|
}
|
|
8708
8606
|
};
|
|
8709
8607
|
function priceBindingUsageEvent(kind, op, units) {
|
|
8608
|
+
if (!Number.isFinite(units) || units < 0) return 0;
|
|
8710
8609
|
if (kind === "ai" && op === "input") {
|
|
8711
8610
|
return units * COST_RATES.ai.embedInputPerChar;
|
|
8712
8611
|
}
|
|
8713
8612
|
if (kind === "vectorize" && op === "query") {
|
|
8714
8613
|
return units * COST_RATES.vectorize.queriedPerDim;
|
|
8715
8614
|
}
|
|
8615
|
+
if (kind === "ai_search") {
|
|
8616
|
+
if (op === "ingest") return units * COST_RATES.aiSearch.ingestPerToken;
|
|
8617
|
+
if (op === "ingest-image") return units * COST_RATES.aiSearch.ingestImagePerToken;
|
|
8618
|
+
if (op === "storage") return units * COST_RATES.aiSearch.storagePerByteMonth;
|
|
8619
|
+
if (op === "search-hybrid" || op === "search-semantic")
|
|
8620
|
+
return units * COST_RATES.aiSearch.hybridOrSemanticPerQuery;
|
|
8621
|
+
if (op === "search-fulltext") return units * COST_RATES.aiSearch.fulltextPerQuery;
|
|
8622
|
+
}
|
|
8716
8623
|
return 0;
|
|
8717
8624
|
}
|
|
8718
8625
|
|
|
8626
|
+
// src/server/utils/knowledge-base.ts
|
|
8627
|
+
var MAX_UPLOAD_BYTES = 4 * 1024 * 1024;
|
|
8628
|
+
var TEXT_MIME = /^(text\/|application\/(json|xml|javascript|x-(javascript|typescript)))/iu;
|
|
8629
|
+
var MAX_QUERY_CHARS = 4096;
|
|
8630
|
+
function hasControlChar(value) {
|
|
8631
|
+
for (let i = 0; i < value.length; i++) {
|
|
8632
|
+
const code = value.charCodeAt(i);
|
|
8633
|
+
if (code < 32 || code === 127) return true;
|
|
8634
|
+
}
|
|
8635
|
+
return false;
|
|
8636
|
+
}
|
|
8637
|
+
var KnowledgeError = class extends Error {
|
|
8638
|
+
constructor(status, code, message, uploadedItems) {
|
|
8639
|
+
super(message);
|
|
8640
|
+
this.status = status;
|
|
8641
|
+
this.code = code;
|
|
8642
|
+
this.uploadedItems = uploadedItems;
|
|
8643
|
+
this.name = "KnowledgeError";
|
|
8644
|
+
}
|
|
8645
|
+
};
|
|
8646
|
+
function normalizeKnowledgeFolder(folder) {
|
|
8647
|
+
if (!folder || folder.includes("\\") || hasControlChar(folder) || folder.startsWith("/"))
|
|
8648
|
+
throw new KnowledgeError(400, "invalid_folder", "Folder must be a relative forward-slash path");
|
|
8649
|
+
const withoutTrailingSlash = folder.endsWith("/") ? folder.slice(0, -1) : folder;
|
|
8650
|
+
if (!withoutTrailingSlash || withoutTrailingSlash.endsWith("/"))
|
|
8651
|
+
throw new KnowledgeError(400, "invalid_folder", "Folder cannot contain empty segments");
|
|
8652
|
+
const parts = withoutTrailingSlash.split("/");
|
|
8653
|
+
if (parts.some((part) => !part || part === "." || part === ".."))
|
|
8654
|
+
throw new KnowledgeError(
|
|
8655
|
+
400,
|
|
8656
|
+
"invalid_folder",
|
|
8657
|
+
"Folder cannot contain empty, dot, or parent segments"
|
|
8658
|
+
);
|
|
8659
|
+
const normalized = `${parts.join("/")}/`;
|
|
8660
|
+
if (normalized.length > 128)
|
|
8661
|
+
throw new KnowledgeError(400, "invalid_folder", "Folder is too long for an AI Search item key");
|
|
8662
|
+
return normalized;
|
|
8663
|
+
}
|
|
8664
|
+
function validateKnowledgeFilename(name) {
|
|
8665
|
+
if (!name || name === "." || name === ".." || name.includes("/") || name.includes("\\") || hasControlChar(name)) {
|
|
8666
|
+
throw new KnowledgeError(
|
|
8667
|
+
400,
|
|
8668
|
+
"invalid_filename",
|
|
8669
|
+
"File name must be a non-empty base name without path separators"
|
|
8670
|
+
);
|
|
8671
|
+
}
|
|
8672
|
+
}
|
|
8673
|
+
function folderFilter(folder) {
|
|
8674
|
+
const value = normalizeKnowledgeFolder(folder);
|
|
8675
|
+
return { folder: { $gte: value, $lt: `${value.slice(0, -1)}0` } };
|
|
8676
|
+
}
|
|
8677
|
+
function knowledge(env) {
|
|
8678
|
+
const add = (file, options = {}) => addFile(env, file, options);
|
|
8679
|
+
const list = (options = {}) => listItems(env, options);
|
|
8680
|
+
const search = (query, options = {}) => searchItems(env, query, options);
|
|
8681
|
+
return {
|
|
8682
|
+
add,
|
|
8683
|
+
list,
|
|
8684
|
+
search,
|
|
8685
|
+
async remove(itemId) {
|
|
8686
|
+
if (!/^[A-Za-z0-9_-]{1,256}$/u.test(itemId))
|
|
8687
|
+
throw new KnowledgeError(400, "invalid_item_id", "Invalid knowledge item id");
|
|
8688
|
+
await request(env, `/api/knowledge/items/${encodeURIComponent(itemId)}`, { method: "DELETE" });
|
|
8689
|
+
},
|
|
8690
|
+
scoped({ folder }) {
|
|
8691
|
+
const fixed = normalizeKnowledgeFolder(folder);
|
|
8692
|
+
return {
|
|
8693
|
+
add: (file) => add(file, { folder: fixed }),
|
|
8694
|
+
list: (options = {}) => list({ ...options, folder: fixed }),
|
|
8695
|
+
search: (query, options = {}) => search(query, { ...options, folder: fixed })
|
|
8696
|
+
};
|
|
8697
|
+
}
|
|
8698
|
+
};
|
|
8699
|
+
}
|
|
8700
|
+
async function addFile(env, file, options) {
|
|
8701
|
+
validateKnowledgeFilename(file.name);
|
|
8702
|
+
const folder = options.folder === void 0 ? void 0 : normalizeKnowledgeFolder(options.folder);
|
|
8703
|
+
if ((folder?.length ?? 0) + file.name.length > 128)
|
|
8704
|
+
throw new KnowledgeError(
|
|
8705
|
+
400,
|
|
8706
|
+
"invalid_key",
|
|
8707
|
+
"Folder and filename together exceed the 128-character AI Search key limit"
|
|
8708
|
+
);
|
|
8709
|
+
if (file.size <= MAX_UPLOAD_BYTES) return upload(env, file, folder);
|
|
8710
|
+
if (!TEXT_MIME.test(file.type) && !/\.(txt|md|mdx|csv|json|xml|html?|ya?ml|tsx?|jsx?|css)$/iu.test(file.name))
|
|
8711
|
+
throw new KnowledgeError(
|
|
8712
|
+
413,
|
|
8713
|
+
"file_too_large",
|
|
8714
|
+
"This file exceeds 4 MB. Split the source document with a format-aware tool before uploading."
|
|
8715
|
+
);
|
|
8716
|
+
const bytes = new Uint8Array(await file.arrayBuffer());
|
|
8717
|
+
const parts = splitUtf8(bytes, MAX_UPLOAD_BYTES);
|
|
8718
|
+
const uploaded = [];
|
|
8719
|
+
try {
|
|
8720
|
+
for (let i = 0; i < parts.length; i++) {
|
|
8721
|
+
const bytes2 = parts[i];
|
|
8722
|
+
const contents = bytes2.buffer.slice(
|
|
8723
|
+
bytes2.byteOffset,
|
|
8724
|
+
bytes2.byteOffset + bytes2.byteLength
|
|
8725
|
+
);
|
|
8726
|
+
const part = new File([contents], partName(file.name, i + 1), {
|
|
8727
|
+
type: file.type || "text/plain"
|
|
8728
|
+
});
|
|
8729
|
+
if ((folder?.length ?? 0) + part.name.length > 128)
|
|
8730
|
+
throw new KnowledgeError(
|
|
8731
|
+
400,
|
|
8732
|
+
"invalid_key",
|
|
8733
|
+
"Folder and split filename together exceed the 128-character AI Search key limit"
|
|
8734
|
+
);
|
|
8735
|
+
uploaded.push(...(await upload(env, part, folder)).items);
|
|
8736
|
+
}
|
|
8737
|
+
} catch (error) {
|
|
8738
|
+
if (error instanceof KnowledgeError)
|
|
8739
|
+
throw new KnowledgeError(error.status, error.code, error.message, uploaded);
|
|
8740
|
+
throw new KnowledgeError(502, "knowledge_upload_failed", "Knowledge upload failed", uploaded);
|
|
8741
|
+
}
|
|
8742
|
+
return { items: uploaded };
|
|
8743
|
+
}
|
|
8744
|
+
function splitUtf8(bytes, max) {
|
|
8745
|
+
const out = [];
|
|
8746
|
+
for (let start = 0; start < bytes.length; ) {
|
|
8747
|
+
let end = Math.min(start + max, bytes.length);
|
|
8748
|
+
if (end < bytes.length) {
|
|
8749
|
+
let cut = end;
|
|
8750
|
+
while (cut > start && (bytes[cut] & 192) === 128) cut--;
|
|
8751
|
+
for (let i = cut - 1; i > Math.max(start, cut - 8192); i--)
|
|
8752
|
+
if (bytes[i] === 10 && (i === start || bytes[i - 1] === 10)) {
|
|
8753
|
+
cut = i + 1;
|
|
8754
|
+
break;
|
|
8755
|
+
}
|
|
8756
|
+
end = cut > start ? cut : end;
|
|
8757
|
+
}
|
|
8758
|
+
out.push(bytes.slice(start, end));
|
|
8759
|
+
start = end;
|
|
8760
|
+
}
|
|
8761
|
+
return out;
|
|
8762
|
+
}
|
|
8763
|
+
function partName(name, n) {
|
|
8764
|
+
const dot = name.lastIndexOf(".");
|
|
8765
|
+
return dot > 0 ? `${name.slice(0, dot)}.part-${n}${name.slice(dot)}` : `${name}.part-${n}`;
|
|
8766
|
+
}
|
|
8767
|
+
async function upload(env, file, folder) {
|
|
8768
|
+
const body = new FormData();
|
|
8769
|
+
body.append("file", file);
|
|
8770
|
+
if (folder) body.append("folder", folder);
|
|
8771
|
+
return await request(env, "/api/knowledge/items", {
|
|
8772
|
+
method: "POST",
|
|
8773
|
+
body
|
|
8774
|
+
});
|
|
8775
|
+
}
|
|
8776
|
+
async function listItems(env, options) {
|
|
8777
|
+
if (options.page !== void 0 && (!Number.isInteger(options.page) || options.page < 1))
|
|
8778
|
+
throw new KnowledgeError(400, "invalid_page", "List page must be a positive integer");
|
|
8779
|
+
if (options.perPage !== void 0 && (!Number.isInteger(options.perPage) || options.perPage < 1 || options.perPage > 50))
|
|
8780
|
+
throw new KnowledgeError(
|
|
8781
|
+
400,
|
|
8782
|
+
"invalid_per_page",
|
|
8783
|
+
"List perPage must be an integer from 1 to 50"
|
|
8784
|
+
);
|
|
8785
|
+
if (options.search !== void 0 && options.search.length > 256)
|
|
8786
|
+
throw new KnowledgeError(400, "invalid_search", "List search cannot exceed 256 characters");
|
|
8787
|
+
if (options.status !== void 0 && !["queued", "running", "completed", "error", "skipped", "outdated"].includes(options.status))
|
|
8788
|
+
throw new KnowledgeError(400, "invalid_status", "Invalid knowledge item status");
|
|
8789
|
+
const q = new URLSearchParams();
|
|
8790
|
+
if (options.folder) q.set("folder", normalizeKnowledgeFolder(options.folder));
|
|
8791
|
+
if (options.page !== void 0) q.set("page", String(options.page));
|
|
8792
|
+
if (options.perPage !== void 0) q.set("perPage", String(options.perPage));
|
|
8793
|
+
if (options.status) q.set("status", options.status);
|
|
8794
|
+
if (options.search) q.set("search", options.search);
|
|
8795
|
+
return await request(env, `/api/knowledge/items${q.size ? `?${q}` : ""}`);
|
|
8796
|
+
}
|
|
8797
|
+
async function searchItems(env, query, options) {
|
|
8798
|
+
const normalizedQuery = query.trim();
|
|
8799
|
+
if (!normalizedQuery) throw new KnowledgeError(400, "invalid_query", "Search query is required");
|
|
8800
|
+
if (normalizedQuery.length > MAX_QUERY_CHARS)
|
|
8801
|
+
throw new KnowledgeError(
|
|
8802
|
+
400,
|
|
8803
|
+
"invalid_query",
|
|
8804
|
+
`Search query cannot exceed ${MAX_QUERY_CHARS} characters`
|
|
8805
|
+
);
|
|
8806
|
+
if (options.mode !== void 0 && options.mode !== "hybrid" && options.mode !== "semantic" && options.mode !== "fulltext")
|
|
8807
|
+
throw new KnowledgeError(400, "invalid_mode", "Invalid knowledge search mode");
|
|
8808
|
+
if (options.limit !== void 0 && (!Number.isInteger(options.limit) || options.limit < 1 || options.limit > 50))
|
|
8809
|
+
throw new KnowledgeError(
|
|
8810
|
+
400,
|
|
8811
|
+
"invalid_limit",
|
|
8812
|
+
"Search limit must be an integer from 1 through 50"
|
|
8813
|
+
);
|
|
8814
|
+
if (options.matchThreshold !== void 0 && (!Number.isFinite(options.matchThreshold) || options.matchThreshold < 0 || options.matchThreshold > 1))
|
|
8815
|
+
throw new KnowledgeError(
|
|
8816
|
+
400,
|
|
8817
|
+
"invalid_match_threshold",
|
|
8818
|
+
"Match threshold must be between 0 and 1"
|
|
8819
|
+
);
|
|
8820
|
+
if (options.queryRewrite !== void 0 && typeof options.queryRewrite !== "boolean")
|
|
8821
|
+
throw new KnowledgeError(
|
|
8822
|
+
400,
|
|
8823
|
+
"invalid_query_rewrite",
|
|
8824
|
+
"queryRewrite must be a boolean when provided"
|
|
8825
|
+
);
|
|
8826
|
+
const folder = options.folder === void 0 ? void 0 : normalizeKnowledgeFolder(options.folder);
|
|
8827
|
+
return await request(env, "/api/knowledge/search", {
|
|
8828
|
+
method: "POST",
|
|
8829
|
+
headers: { "content-type": "application/json" },
|
|
8830
|
+
body: JSON.stringify({
|
|
8831
|
+
query: normalizedQuery,
|
|
8832
|
+
mode: options.mode ?? "hybrid",
|
|
8833
|
+
limit: options.limit ?? 10,
|
|
8834
|
+
queryRewrite: options.queryRewrite ?? false,
|
|
8835
|
+
...options.matchThreshold !== void 0 && {
|
|
8836
|
+
matchThreshold: options.matchThreshold
|
|
8837
|
+
},
|
|
8838
|
+
...folder && { folder }
|
|
8839
|
+
})
|
|
8840
|
+
});
|
|
8841
|
+
}
|
|
8842
|
+
async function request(env, path, init = {}) {
|
|
8843
|
+
const headers = new Headers(init.headers);
|
|
8844
|
+
appendAppIdentity(headers, env);
|
|
8845
|
+
const response = await apiWorkerFetch(env, path, { ...init, headers });
|
|
8846
|
+
const text = await response.text();
|
|
8847
|
+
let data;
|
|
8848
|
+
try {
|
|
8849
|
+
data = text ? JSON.parse(text) : void 0;
|
|
8850
|
+
} catch {
|
|
8851
|
+
data = void 0;
|
|
8852
|
+
}
|
|
8853
|
+
if (!response.ok) {
|
|
8854
|
+
const body = data && typeof data === "object" ? data : {};
|
|
8855
|
+
throw new KnowledgeError(
|
|
8856
|
+
response.status,
|
|
8857
|
+
typeof body.code === "string" ? body.code : "knowledge_request_failed",
|
|
8858
|
+
typeof body.error === "string" ? body.error : "Knowledge request failed"
|
|
8859
|
+
);
|
|
8860
|
+
}
|
|
8861
|
+
return data;
|
|
8862
|
+
}
|
|
8863
|
+
|
|
8719
8864
|
// src/server/utils/d1-migrations.ts
|
|
8720
8865
|
var META_TABLE = "_dpc_migrations";
|
|
8721
8866
|
var META_BOOTSTRAP_SQL = `CREATE TABLE IF NOT EXISTS ${META_TABLE} (idx INTEGER PRIMARY KEY, applied_at TEXT NOT NULL)`;
|
|
@@ -9226,16 +9371,16 @@ var RequestBodyTooLargeError = class extends Error {
|
|
|
9226
9371
|
this.name = "RequestBodyTooLargeError";
|
|
9227
9372
|
}
|
|
9228
9373
|
};
|
|
9229
|
-
async function readBoundedRequestText(
|
|
9374
|
+
async function readBoundedRequestText(request2, maxBytes) {
|
|
9230
9375
|
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) {
|
|
9231
9376
|
throw new TypeError("maxBytes must be a positive safe integer");
|
|
9232
9377
|
}
|
|
9233
|
-
const declared =
|
|
9378
|
+
const declared = request2.headers.get("content-length");
|
|
9234
9379
|
if (declared && /^\d+$/.test(declared.trim()) && Number(declared) > maxBytes) {
|
|
9235
9380
|
throw new RequestBodyTooLargeError(maxBytes);
|
|
9236
9381
|
}
|
|
9237
|
-
if (!
|
|
9238
|
-
const reader =
|
|
9382
|
+
if (!request2.body) return "";
|
|
9383
|
+
const reader = request2.body.getReader();
|
|
9239
9384
|
const decoder = new TextDecoder();
|
|
9240
9385
|
let bytes = 0;
|
|
9241
9386
|
let text = "";
|
|
@@ -9449,10 +9594,10 @@ function documentationRequestPath(pathname, publicBasePath) {
|
|
|
9449
9594
|
const clean = logical === "/" ? "/" : logical.replace(/\/$/, "");
|
|
9450
9595
|
return canonicalNativeDocumentationRequestPath(clean);
|
|
9451
9596
|
}
|
|
9452
|
-
async function fetchDocumentationAsset(env,
|
|
9597
|
+
async function fetchDocumentationAsset(env, request2, initialUrl, assetBasePath) {
|
|
9453
9598
|
let url = initialUrl;
|
|
9454
9599
|
for (let redirects = 0; redirects < 3; redirects++) {
|
|
9455
|
-
const response = await env.ASSETS.fetch(new Request(url,
|
|
9600
|
+
const response = await env.ASSETS.fetch(new Request(url, request2));
|
|
9456
9601
|
const location = response.headers.get("Location");
|
|
9457
9602
|
if (response.status < 300 || response.status >= 400 || !location) return response;
|
|
9458
9603
|
const next = new URL(location, url);
|
|
@@ -9492,11 +9637,11 @@ function secureDocumentationResponse(response, status, pathname) {
|
|
|
9492
9637
|
var MCP_SERVER_VERSION = "1.0.0";
|
|
9493
9638
|
var CURRENT_PROTOCOL_VERSION = "2025-11-25";
|
|
9494
9639
|
var SUPPORTED_PROTOCOL_VERSIONS = /* @__PURE__ */ new Set([CURRENT_PROTOCOL_VERSION, "2025-06-18", "2025-03-26"]);
|
|
9495
|
-
function initializeResponse(
|
|
9496
|
-
const params = objectValue(
|
|
9640
|
+
function initializeResponse(request2) {
|
|
9641
|
+
const params = objectValue(request2.params);
|
|
9497
9642
|
const requested = typeof params?.protocolVersion === "string" ? params.protocolVersion : CURRENT_PROTOCOL_VERSION;
|
|
9498
9643
|
const protocolVersion = SUPPORTED_PROTOCOL_VERSIONS.has(requested) ? requested : CURRENT_PROTOCOL_VERSION;
|
|
9499
|
-
return mcpResult(
|
|
9644
|
+
return mcpResult(request2.id, {
|
|
9500
9645
|
protocolVersion,
|
|
9501
9646
|
capabilities: {
|
|
9502
9647
|
tools: { listChanged: false },
|
|
@@ -9510,11 +9655,11 @@ function initializeResponse(request) {
|
|
|
9510
9655
|
instructions: "Search the published documentation before answering, then read exact pages when full context is needed."
|
|
9511
9656
|
});
|
|
9512
9657
|
}
|
|
9513
|
-
function hasValidOrigin(
|
|
9514
|
-
const origin =
|
|
9658
|
+
function hasValidOrigin(request2) {
|
|
9659
|
+
const origin = request2.headers.get("Origin");
|
|
9515
9660
|
if (!origin) return true;
|
|
9516
9661
|
try {
|
|
9517
|
-
return new URL(origin).origin === new URL(
|
|
9662
|
+
return new URL(origin).origin === new URL(request2.url).origin;
|
|
9518
9663
|
} catch {
|
|
9519
9664
|
return false;
|
|
9520
9665
|
}
|
|
@@ -9640,8 +9785,8 @@ var DOCUMENTATION_TOOLS = [
|
|
|
9640
9785
|
}
|
|
9641
9786
|
}
|
|
9642
9787
|
];
|
|
9643
|
-
async function callDocumentationTool(rawRequest,
|
|
9644
|
-
const params = objectValue(
|
|
9788
|
+
async function callDocumentationTool(rawRequest, request2, corpus, env, basePath = "/docs", assetBasePath = "/_documentation") {
|
|
9789
|
+
const params = objectValue(request2.params);
|
|
9645
9790
|
const name = typeof params?.name === "string" ? params.name : "";
|
|
9646
9791
|
const args = objectValue(params?.arguments) ?? {};
|
|
9647
9792
|
const origin = new URL(rawRequest.url).origin;
|
|
@@ -9649,13 +9794,13 @@ async function callDocumentationTool(rawRequest, request, corpus, env, basePath
|
|
|
9649
9794
|
const query = typeof args.query === "string" ? args.query.trim() : "";
|
|
9650
9795
|
const limit = args.limit === void 0 ? 6 : args.limit;
|
|
9651
9796
|
if (query.length < 2 || query.length > 300 || !Number.isInteger(limit) || Number(limit) < 1 || Number(limit) > 10) {
|
|
9652
|
-
return mcpError(
|
|
9797
|
+
return mcpError(request2.id, -32602, "Invalid documentation_search arguments");
|
|
9653
9798
|
}
|
|
9654
9799
|
const results = searchDocumentationCorpus(corpus, query, Number(limit)).map((result) => ({
|
|
9655
9800
|
...result,
|
|
9656
9801
|
url: new URL(documentationPublicPath(basePath, result.route), origin).toString()
|
|
9657
9802
|
}));
|
|
9658
|
-
return toolResult(
|
|
9803
|
+
return toolResult(request2.id, {
|
|
9659
9804
|
...results[0]?.termCoverage === 1 ? {} : {
|
|
9660
9805
|
notice: "No section matches every search term \u2014 the topic may be absent or spread across sections."
|
|
9661
9806
|
},
|
|
@@ -9665,7 +9810,7 @@ async function callDocumentationTool(rawRequest, request, corpus, env, basePath
|
|
|
9665
9810
|
if (name === "documentation_read") {
|
|
9666
9811
|
const route = typeof args.route === "string" ? args.route.trim() : "";
|
|
9667
9812
|
if (!route || route.length > 500) {
|
|
9668
|
-
return mcpError(
|
|
9813
|
+
return mcpError(request2.id, -32602, "Invalid documentation_read arguments");
|
|
9669
9814
|
}
|
|
9670
9815
|
const hash = route.indexOf("#");
|
|
9671
9816
|
const fragment = hash === -1 ? "" : route.slice(hash + 1);
|
|
@@ -9677,9 +9822,9 @@ async function callDocumentationTool(rawRequest, request, corpus, env, basePath
|
|
|
9677
9822
|
rawRequest.url,
|
|
9678
9823
|
assetBasePath
|
|
9679
9824
|
);
|
|
9680
|
-
if (!page) return toolError(
|
|
9825
|
+
if (!page) return toolError(request2.id, `No published documentation page exists at ${route}`);
|
|
9681
9826
|
const section = fragment ? documentationPageSection(page.markdown, fragment) : null;
|
|
9682
|
-
return toolResult(
|
|
9827
|
+
return toolResult(request2.id, {
|
|
9683
9828
|
...fragment && section === null ? { notice: `The fragment #${fragment} was not found; returning the whole page.` } : {},
|
|
9684
9829
|
...page,
|
|
9685
9830
|
...section === null ? {} : { markdown: section },
|
|
@@ -9687,10 +9832,10 @@ async function callDocumentationTool(rawRequest, request, corpus, env, basePath
|
|
|
9687
9832
|
});
|
|
9688
9833
|
} catch (error) {
|
|
9689
9834
|
console.error("[documentation-mcp] page unavailable:", error);
|
|
9690
|
-
return toolError(
|
|
9835
|
+
return toolError(request2.id, "The documentation page could not be read");
|
|
9691
9836
|
}
|
|
9692
9837
|
}
|
|
9693
|
-
return mcpError(
|
|
9838
|
+
return mcpError(request2.id, -32602, `Unknown documentation tool: ${name || "<missing>"}`);
|
|
9694
9839
|
}
|
|
9695
9840
|
|
|
9696
9841
|
// src/documentation/worker/mcp-routes.ts
|
|
@@ -9727,27 +9872,27 @@ function registerDocumentationMcpRoutes(app, publicBasePath = DOCUMENTATION_BASE
|
|
|
9727
9872
|
}
|
|
9728
9873
|
const parsed = parseRequest(raw);
|
|
9729
9874
|
if (!parsed.ok) return mcpError(null, -32700, parsed.message, 400);
|
|
9730
|
-
const
|
|
9731
|
-
if (
|
|
9875
|
+
const request2 = parsed.request;
|
|
9876
|
+
if (request2.method !== "initialize") {
|
|
9732
9877
|
const protocolVersion = c.req.header("MCP-Protocol-Version");
|
|
9733
9878
|
if (protocolVersion && !SUPPORTED_PROTOCOL_VERSIONS.has(protocolVersion)) {
|
|
9734
|
-
return mcpError(
|
|
9879
|
+
return mcpError(request2.id ?? null, -32600, "Unsupported MCP-Protocol-Version", 400);
|
|
9735
9880
|
}
|
|
9736
9881
|
}
|
|
9737
|
-
const requestId =
|
|
9882
|
+
const requestId = request2.id;
|
|
9738
9883
|
if (requestId === void 0) return new Response(null, { status: 202 });
|
|
9739
|
-
const requestWithId = { ...
|
|
9740
|
-
if (
|
|
9741
|
-
if (
|
|
9742
|
-
if (
|
|
9884
|
+
const requestWithId = { ...request2, id: requestId };
|
|
9885
|
+
if (request2.method === "initialize") return initializeResponse(requestWithId);
|
|
9886
|
+
if (request2.method === "ping") return mcpResult(requestId, {});
|
|
9887
|
+
if (request2.method === "tools/list") {
|
|
9743
9888
|
return mcpResult(requestId, { tools: DOCUMENTATION_TOOLS });
|
|
9744
9889
|
}
|
|
9745
|
-
if (
|
|
9890
|
+
if (request2.method === "resources/list") {
|
|
9746
9891
|
return mcpResult(requestId, {
|
|
9747
9892
|
resources: [skillResource(c.req.raw, manifest, publicBasePath)]
|
|
9748
9893
|
});
|
|
9749
9894
|
}
|
|
9750
|
-
if (
|
|
9895
|
+
if (request2.method === "resources/read") {
|
|
9751
9896
|
return readSkillResource(
|
|
9752
9897
|
c.req.raw,
|
|
9753
9898
|
requestWithId,
|
|
@@ -9757,8 +9902,8 @@ function registerDocumentationMcpRoutes(app, publicBasePath = DOCUMENTATION_BASE
|
|
|
9757
9902
|
assetBasePath
|
|
9758
9903
|
);
|
|
9759
9904
|
}
|
|
9760
|
-
if (
|
|
9761
|
-
return mcpError(requestId, -32601, `Method not found: ${
|
|
9905
|
+
if (request2.method !== "tools/call") {
|
|
9906
|
+
return mcpError(requestId, -32601, `Method not found: ${request2.method}`);
|
|
9762
9907
|
}
|
|
9763
9908
|
const clientKey = await documentationPublicClientKey(
|
|
9764
9909
|
c.req.header("CF-Connecting-IP") ?? "unknown"
|
|
@@ -9815,44 +9960,44 @@ function registerDocumentationMcpRoutes(app, publicBasePath = DOCUMENTATION_BASE
|
|
|
9815
9960
|
}
|
|
9816
9961
|
);
|
|
9817
9962
|
}
|
|
9818
|
-
async function readSkillResource(rawRequest,
|
|
9819
|
-
const params = objectValue(
|
|
9963
|
+
async function readSkillResource(rawRequest, request2, manifest, env, publicBasePath, assetBasePath) {
|
|
9964
|
+
const params = objectValue(request2.params);
|
|
9820
9965
|
const requestedUri = typeof params?.uri === "string" ? params.uri : "";
|
|
9821
9966
|
const resource = skillResource(rawRequest, manifest, publicBasePath);
|
|
9822
9967
|
if (requestedUri !== resource.uri) {
|
|
9823
|
-
return mcpError(
|
|
9968
|
+
return mcpError(request2.id, -32602, "Unknown documentation resource");
|
|
9824
9969
|
}
|
|
9825
9970
|
const assetUrl = new URL(`${assetBasePath}/skill.md`, rawRequest.url);
|
|
9826
9971
|
const response = await env.ASSETS.fetch(new Request(assetUrl, { method: "GET" }));
|
|
9827
|
-
if (!response.ok) return mcpError(
|
|
9972
|
+
if (!response.ok) return mcpError(request2.id, -32002, "Documentation skill is unavailable", 503);
|
|
9828
9973
|
const text = await response.text();
|
|
9829
9974
|
if (text.length > MAX_SKILL_CHARS) {
|
|
9830
|
-
return mcpError(
|
|
9975
|
+
return mcpError(request2.id, -32002, "Documentation skill exceeds the published size limit", 503);
|
|
9831
9976
|
}
|
|
9832
|
-
return mcpResult(
|
|
9977
|
+
return mcpResult(request2.id, {
|
|
9833
9978
|
contents: [{ uri: resource.uri, mimeType: resource.mimeType, text }]
|
|
9834
9979
|
});
|
|
9835
9980
|
}
|
|
9836
|
-
function skillResource(
|
|
9981
|
+
function skillResource(request2, manifest, publicBasePath) {
|
|
9837
9982
|
const subject = documentationSubject(manifest.name);
|
|
9838
9983
|
return {
|
|
9839
|
-
uri: new URL(`${publicBasePath}/skill.md`,
|
|
9984
|
+
uri: new URL(`${publicBasePath}/skill.md`, request2.url).toString(),
|
|
9840
9985
|
name: `${subject} skill`,
|
|
9841
9986
|
title: `Use ${manifest.name}`,
|
|
9842
9987
|
description: `Agent instructions grounded in the published ${subject}.`,
|
|
9843
9988
|
mimeType: "text/markdown"
|
|
9844
9989
|
};
|
|
9845
9990
|
}
|
|
9846
|
-
async function publicManifest(
|
|
9847
|
-
const manifest = await loadDocumentationPublishedManifest(env,
|
|
9991
|
+
async function publicManifest(request2, env, assetBasePath) {
|
|
9992
|
+
const manifest = await loadDocumentationPublishedManifest(env, request2.url, assetBasePath);
|
|
9848
9993
|
return manifest?.mcp.access === "public" ? manifest : null;
|
|
9849
9994
|
}
|
|
9850
|
-
function discoveryResponse(
|
|
9851
|
-
return jsonResponse(serverCard(
|
|
9995
|
+
function discoveryResponse(request2, manifest, publicBasePath) {
|
|
9996
|
+
return jsonResponse(serverCard(request2, manifest, publicBasePath), 200, {
|
|
9852
9997
|
"Cache-Control": "public, max-age=300"
|
|
9853
9998
|
});
|
|
9854
9999
|
}
|
|
9855
|
-
function serverCard(
|
|
10000
|
+
function serverCard(request2, manifest, publicBasePath) {
|
|
9856
10001
|
return {
|
|
9857
10002
|
name: manifest.name,
|
|
9858
10003
|
description: `Search and read the published ${documentationSubject(manifest.name)}.`,
|
|
@@ -9865,7 +10010,7 @@ function serverCard(request, manifest, publicBasePath) {
|
|
|
9865
10010
|
protocolVersions: [...SUPPORTED_PROTOCOL_VERSIONS],
|
|
9866
10011
|
transport: {
|
|
9867
10012
|
type: "streamable-http",
|
|
9868
|
-
url: new URL(`${publicBasePath}/mcp`,
|
|
10013
|
+
url: new URL(`${publicBasePath}/mcp`, request2.url).toString()
|
|
9869
10014
|
},
|
|
9870
10015
|
authentication: { required: false },
|
|
9871
10016
|
capabilities: {
|
|
@@ -9874,7 +10019,7 @@ function serverCard(request, manifest, publicBasePath) {
|
|
|
9874
10019
|
title,
|
|
9875
10020
|
description
|
|
9876
10021
|
})),
|
|
9877
|
-
resources: [skillResource(
|
|
10022
|
+
resources: [skillResource(request2, manifest, publicBasePath)]
|
|
9878
10023
|
}
|
|
9879
10024
|
};
|
|
9880
10025
|
}
|
|
@@ -10297,21 +10442,21 @@ function jsonResponse2(body, status) {
|
|
|
10297
10442
|
headers: { "Content-Type": "application/json" }
|
|
10298
10443
|
});
|
|
10299
10444
|
}
|
|
10300
|
-
async function handleClientErrorReport(
|
|
10301
|
-
const site =
|
|
10445
|
+
async function handleClientErrorReport(request2) {
|
|
10446
|
+
const site = request2.headers.get("sec-fetch-site");
|
|
10302
10447
|
if (site && site !== "same-origin" && site !== "none") {
|
|
10303
10448
|
return new Response(null, { status: 403 });
|
|
10304
10449
|
}
|
|
10305
10450
|
if (shouldThrottleClientError()) {
|
|
10306
10451
|
return new Response(null, { status: 429, headers: { "Retry-After": "1" } });
|
|
10307
10452
|
}
|
|
10308
|
-
const declared = Number(
|
|
10453
|
+
const declared = Number(request2.headers.get("content-length"));
|
|
10309
10454
|
if (Number.isFinite(declared) && declared > MAX_BODY_BYTES) {
|
|
10310
10455
|
return jsonResponse2({ error: "payload_too_large" }, 413);
|
|
10311
10456
|
}
|
|
10312
10457
|
let body;
|
|
10313
10458
|
try {
|
|
10314
|
-
body = await
|
|
10459
|
+
body = await request2.json();
|
|
10315
10460
|
} catch {
|
|
10316
10461
|
return jsonResponse2({ error: "invalid_json" }, 400);
|
|
10317
10462
|
}
|
|
@@ -10339,6 +10484,8 @@ var APP_LOG_EVENT_KEYS = [
|
|
|
10339
10484
|
];
|
|
10340
10485
|
export {
|
|
10341
10486
|
ACCOUNT_STORAGE_LIMIT_BYTES,
|
|
10487
|
+
ACTION_ROUTES_BEARER_GUARD_MIGRATION_ID,
|
|
10488
|
+
ACTION_TOOLS_DELETE_WHERE_MIGRATION_ID,
|
|
10342
10489
|
AI_CHATS_SCHEMA,
|
|
10343
10490
|
AI_MESSAGES_SCHEMA,
|
|
10344
10491
|
ALLOWED_BINDING_TYPES,
|
|
@@ -10352,6 +10499,7 @@ export {
|
|
|
10352
10499
|
Awareness,
|
|
10353
10500
|
BASE_USERS_SCHEMA,
|
|
10354
10501
|
BROWSER_PROXY_ROUTES,
|
|
10502
|
+
BUILD_INJECTED_APP_ID_MIGRATION_ID,
|
|
10355
10503
|
BUILT_IN_TOOLS,
|
|
10356
10504
|
BaseRoom,
|
|
10357
10505
|
CHANNELS_SCHEMA,
|
|
@@ -10377,6 +10525,7 @@ export {
|
|
|
10377
10525
|
GLOBAL_DO_TYPES,
|
|
10378
10526
|
GLOBAL_DO_TYPE_NAMES,
|
|
10379
10527
|
JobRoom,
|
|
10528
|
+
KnowledgeError,
|
|
10380
10529
|
LOG_LEVELS,
|
|
10381
10530
|
MAX_APP_FILE_BYTES,
|
|
10382
10531
|
MAX_DELETE_WHERE_LIMIT,
|
|
@@ -10423,13 +10572,13 @@ export {
|
|
|
10423
10572
|
USERS_COLUMNS,
|
|
10424
10573
|
VOTING_SCHEMAS,
|
|
10425
10574
|
WORKER_OWNED_NOT_FOUND_MIGRATION_ID,
|
|
10426
|
-
WORKSPACE_SCHEMAS,
|
|
10427
10575
|
YjsRoom,
|
|
10428
10576
|
analyzePermissions,
|
|
10429
10577
|
apiWorkerFetch,
|
|
10430
10578
|
appendMessage,
|
|
10431
10579
|
applyAiToolDefaults,
|
|
10432
10580
|
applySlidingWindow,
|
|
10581
|
+
armCronRoom,
|
|
10433
10582
|
authWorkerFetch,
|
|
10434
10583
|
authenticatedRoomRequest,
|
|
10435
10584
|
bindingManifestFromOutputConfig,
|
|
@@ -10469,6 +10618,8 @@ export {
|
|
|
10469
10618
|
encodeSyncStep2,
|
|
10470
10619
|
encodeUpdate,
|
|
10471
10620
|
enqueueJob,
|
|
10621
|
+
epochSeconds,
|
|
10622
|
+
folderFilter,
|
|
10472
10623
|
formatBytes,
|
|
10473
10624
|
getChat,
|
|
10474
10625
|
getDeepSpaceAIModel,
|
|
@@ -10489,6 +10640,7 @@ export {
|
|
|
10489
10640
|
isTestAccountClaims,
|
|
10490
10641
|
isTestAccountEmail,
|
|
10491
10642
|
isTestAccountTier,
|
|
10643
|
+
knowledge,
|
|
10492
10644
|
lintSchema,
|
|
10493
10645
|
lintSchemaSet,
|
|
10494
10646
|
lintSchemas,
|
|
@@ -10502,6 +10654,7 @@ export {
|
|
|
10502
10654
|
mintAppId,
|
|
10503
10655
|
mintUlid,
|
|
10504
10656
|
noopPermissionContext,
|
|
10657
|
+
normalizeKnowledgeFolder,
|
|
10505
10658
|
parseQuotaLimit,
|
|
10506
10659
|
platformCorsPatterns,
|
|
10507
10660
|
platformWorkerFetch,
|
|
@@ -10537,17 +10690,6 @@ export {
|
|
|
10537
10690
|
validateBindingManifest,
|
|
10538
10691
|
validateDoManifest,
|
|
10539
10692
|
verifyJwt,
|
|
10540
|
-
workspaceAccountsSchema,
|
|
10541
|
-
workspaceContentSharesSchema,
|
|
10542
|
-
workspaceEmailHandlesSchema,
|
|
10543
|
-
workspaceFormResponsesSchema,
|
|
10544
|
-
workspacePeopleSchema,
|
|
10545
|
-
workspaceProjectsSchema,
|
|
10546
|
-
workspaceTagsSchema,
|
|
10547
|
-
workspaceTasksSchema,
|
|
10548
|
-
workspaceTeamMembersSchema,
|
|
10549
|
-
workspaceTeamsSchema,
|
|
10550
|
-
workspaceTransactionsSchema,
|
|
10551
10693
|
writeVarUint,
|
|
10552
10694
|
writeVarUint8Array
|
|
10553
10695
|
};
|