deepspace 0.23.2 → 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 +23 -0
- package/README.md +6 -2
- package/dist/build.js +11 -0
- package/dist/build.js.map +1 -1
- package/dist/chunk-OFDOJGFS.js +1957 -0
- package/dist/chunk-OFDOJGFS.js.map +1 -0
- package/dist/cli.js +1583 -1688
- 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 +501 -381
- package/dist/server.js.map +1 -1
- package/dist/worker.d.ts +178 -48
- package/dist/worker.js +561 -435
- package/dist/worker.js.map +1 -1
- package/package.json +1 -1
package/dist/worker.js
CHANGED
|
@@ -105,11 +105,11 @@ function decodeRoomIdentityHeader(value) {
|
|
|
105
105
|
return void 0;
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
|
-
function authenticatedRoomRequest(
|
|
109
|
-
const url = new URL(
|
|
108
|
+
function authenticatedRoomRequest(request2, auth, identity) {
|
|
109
|
+
const url = new URL(request2.url);
|
|
110
110
|
url.searchParams.delete("token");
|
|
111
111
|
for (const key of IDENTITY_QUERY_PARAMS) url.searchParams.delete(key);
|
|
112
|
-
const forwarded = new Request(url.toString(),
|
|
112
|
+
const forwarded = new Request(url.toString(), request2);
|
|
113
113
|
for (const header of IDENTITY_HEADERS) forwarded.headers.delete(header);
|
|
114
114
|
if (!auth) return forwarded;
|
|
115
115
|
forwarded.headers.set("x-user-id", encodeRoomIdentityHeader(auth.userId));
|
|
@@ -129,13 +129,18 @@ function authenticatedRoomRequest(request, auth, identity) {
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
// src/server/rooms/base-room.ts
|
|
132
|
-
function connectionAttachmentFromRequest(
|
|
132
|
+
function connectionAttachmentFromRequest(request2) {
|
|
133
|
+
if (!request2.headers.has("x-user-id") && new URL(request2.url).searchParams.has("userId")) {
|
|
134
|
+
console.warn(
|
|
135
|
+
"[BaseRoom] Connection carries identity in the URL (?userId=) but no x-user-id header, so this user joins as anonymous. The app worker's room proxy predates 0.19 or forwards the client's URL unstripped: forward with authenticatedRoomRequest() from deepspace/worker."
|
|
136
|
+
);
|
|
137
|
+
}
|
|
133
138
|
return {
|
|
134
|
-
userId: decodeRoomIdentityHeader(
|
|
135
|
-
userName: decodeRoomIdentityHeader(
|
|
136
|
-
userEmail: decodeRoomIdentityHeader(
|
|
137
|
-
userImageUrl: decodeRoomIdentityHeader(
|
|
138
|
-
role: decodeRoomIdentityHeader(
|
|
139
|
+
userId: decodeRoomIdentityHeader(request2.headers.get("x-user-id")) || `${ANONYMOUS_USER_ID_PREFIX}${crypto.randomUUID()}`,
|
|
140
|
+
userName: decodeRoomIdentityHeader(request2.headers.get("x-user-name")) || "Anonymous",
|
|
141
|
+
userEmail: decodeRoomIdentityHeader(request2.headers.get("x-user-email")) || "",
|
|
142
|
+
userImageUrl: decodeRoomIdentityHeader(request2.headers.get("x-user-image-url")),
|
|
143
|
+
role: decodeRoomIdentityHeader(request2.headers.get("x-user-role"))
|
|
139
144
|
};
|
|
140
145
|
}
|
|
141
146
|
var BaseRoom = class {
|
|
@@ -151,15 +156,15 @@ var BaseRoom = class {
|
|
|
151
156
|
// ==========================================================================
|
|
152
157
|
// HTTP Entry Point
|
|
153
158
|
// ==========================================================================
|
|
154
|
-
async fetch(
|
|
155
|
-
const url = new URL(
|
|
156
|
-
const internal = await this.handleInternalRequest(
|
|
159
|
+
async fetch(request2) {
|
|
160
|
+
const url = new URL(request2.url);
|
|
161
|
+
const internal = await this.handleInternalRequest(request2, url);
|
|
157
162
|
if (internal) return internal;
|
|
158
|
-
if (
|
|
159
|
-
return this.handleWebSocketUpgrade(
|
|
163
|
+
if (request2.headers.get("Upgrade") === "websocket") {
|
|
164
|
+
return this.handleWebSocketUpgrade(request2, url);
|
|
160
165
|
}
|
|
161
166
|
if (this.onRequest) {
|
|
162
|
-
return this.onRequest(
|
|
167
|
+
return this.onRequest(request2);
|
|
163
168
|
}
|
|
164
169
|
return new Response("Not Found", { status: 404 });
|
|
165
170
|
}
|
|
@@ -180,11 +185,11 @@ var BaseRoom = class {
|
|
|
180
185
|
* See the security note on `fetch()`: this path is only reachable via DO
|
|
181
186
|
* stub fetch from the app worker, never from the public internet.
|
|
182
187
|
*/
|
|
183
|
-
async handleInternalRequest(
|
|
184
|
-
if (
|
|
188
|
+
async handleInternalRequest(request2, url) {
|
|
189
|
+
if (request2.method === "POST" && url.pathname === "/internal/disconnect-sockets") {
|
|
185
190
|
let options;
|
|
186
191
|
try {
|
|
187
|
-
const text = await
|
|
192
|
+
const text = await request2.text();
|
|
188
193
|
if (text) options = JSON.parse(text);
|
|
189
194
|
} catch {
|
|
190
195
|
}
|
|
@@ -196,11 +201,11 @@ var BaseRoom = class {
|
|
|
196
201
|
// ==========================================================================
|
|
197
202
|
// WebSocket Connection
|
|
198
203
|
// ==========================================================================
|
|
199
|
-
async handleWebSocketUpgrade(
|
|
204
|
+
async handleWebSocketUpgrade(request2, _url) {
|
|
200
205
|
const pair = new WebSocketPair();
|
|
201
206
|
const [client, server] = Object.values(pair);
|
|
202
207
|
this.state.acceptWebSocket(server);
|
|
203
|
-
const attachment = connectionAttachmentFromRequest(
|
|
208
|
+
const attachment = connectionAttachmentFromRequest(request2);
|
|
204
209
|
const augmented = await this.onConnect(server, attachment) ?? attachment;
|
|
205
210
|
server.serializeAttachment(augmented);
|
|
206
211
|
return new Response(null, { status: 101, webSocket: client });
|
|
@@ -436,7 +441,8 @@ function dataToColumnValues(data, columns) {
|
|
|
436
441
|
return values;
|
|
437
442
|
}
|
|
438
443
|
function coerceValue(value, storage, interpretation) {
|
|
439
|
-
if (value === null || value === void 0
|
|
444
|
+
if (value === null || value === void 0) return null;
|
|
445
|
+
if (value === "") return storage === "text" ? "" : null;
|
|
440
446
|
if (storage === "text") {
|
|
441
447
|
if (interpretation.kind === "json") {
|
|
442
448
|
if (typeof value === "string") return value;
|
|
@@ -470,7 +476,7 @@ function coerceValue(value, storage, interpretation) {
|
|
|
470
476
|
}
|
|
471
477
|
if (interpretation.kind === "date" || interpretation.kind === "datetime") {
|
|
472
478
|
const d = new Date(s);
|
|
473
|
-
if (!isNaN(d.getTime())) return
|
|
479
|
+
if (!isNaN(d.getTime())) return epochSeconds(d);
|
|
474
480
|
return s;
|
|
475
481
|
}
|
|
476
482
|
const cleaned = s.replace(/[$€£¥,\s]/g, "");
|
|
@@ -478,6 +484,9 @@ function coerceValue(value, storage, interpretation) {
|
|
|
478
484
|
if (!isNaN(n) && isFinite(n)) return n;
|
|
479
485
|
return s;
|
|
480
486
|
}
|
|
487
|
+
function epochSeconds(date = /* @__PURE__ */ new Date()) {
|
|
488
|
+
return Math.floor(date.getTime() / 1e3);
|
|
489
|
+
}
|
|
481
490
|
function buildTableSelect(collectionName, columns) {
|
|
482
491
|
const tbl = collectionTableName(collectionName);
|
|
483
492
|
const parts = ["_row_id", "_created_by", "_created_at", "_updated_at"];
|
|
@@ -1142,7 +1151,7 @@ var BUILT_IN_TOOLS = [
|
|
|
1142
1151
|
},
|
|
1143
1152
|
{
|
|
1144
1153
|
name: "user.list",
|
|
1145
|
-
description: "List
|
|
1154
|
+
description: "List the users this caller may see, exactly as the room's roster does: an admin gets whole rows (including email and any app-defined column) through the users collection's read policy; everyone else gets the public identity projection (id, name, imageUrl, role, lastSeenAt) of the roster the users policy allows them \u2014 nothing if that policy denies reads, and only their own scope if the schema sets `roster: 'read-policy'`. A caller with no identity gets an empty list. Called from a server action (X-App-Action), it returns whole rows like the other RBAC-off tools.",
|
|
1146
1155
|
params: {}
|
|
1147
1156
|
},
|
|
1148
1157
|
// Yjs tools (collaborative document access)
|
|
@@ -1316,6 +1325,9 @@ function encode(message) {
|
|
|
1316
1325
|
function getResolvedColumns(schema) {
|
|
1317
1326
|
return (schema.columns ?? []).map(resolveColumn);
|
|
1318
1327
|
}
|
|
1328
|
+
function triggerTimestamp(col) {
|
|
1329
|
+
return col.storage === "number" ? epochSeconds() : (/* @__PURE__ */ new Date()).toISOString();
|
|
1330
|
+
}
|
|
1319
1331
|
function systemManagedColumnError(column, isUpdate) {
|
|
1320
1332
|
const roleHint = column === "role" ? ", or the admin-gated useUsers().setRole() to change a role" : "";
|
|
1321
1333
|
const remedy = isUpdate ? `Re-sending the current value of '${column}' unchanged is accepted and ignored.` : `A users row is created by registration, so no value for '${column}' is accepted here \u2014 omit it.`;
|
|
@@ -1434,7 +1446,7 @@ function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRb
|
|
|
1434
1446
|
if (!isUpdate) {
|
|
1435
1447
|
if (mergedData[triggerField] !== void 0 && mergedData[triggerField] !== null) {
|
|
1436
1448
|
if (triggerValue === void 0 || mergedData[triggerField] === triggerValue) {
|
|
1437
|
-
mergedData[col.name] = (
|
|
1449
|
+
mergedData[col.name] = triggerTimestamp(col);
|
|
1438
1450
|
}
|
|
1439
1451
|
}
|
|
1440
1452
|
} else {
|
|
@@ -1442,7 +1454,7 @@ function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRb
|
|
|
1442
1454
|
const newVal = mergedData[triggerField];
|
|
1443
1455
|
if (newVal !== oldVal) {
|
|
1444
1456
|
if (triggerValue === void 0 || newVal === triggerValue) {
|
|
1445
|
-
mergedData[col.name] = (
|
|
1457
|
+
mergedData[col.name] = triggerTimestamp(col);
|
|
1446
1458
|
}
|
|
1447
1459
|
}
|
|
1448
1460
|
}
|
|
@@ -1487,27 +1499,27 @@ function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRb
|
|
|
1487
1499
|
if (SYSTEM_ASSIGNED_COLUMNS.has(col.name) && Object.hasOwn(data, col.name) && coerceValue(data[col.name], col.storage, col.interpretation) !== coerceValue(current, col.storage, col.interpretation)) {
|
|
1488
1500
|
return { success: false, error: systemManagedColumnError(col.name, isUpdate) };
|
|
1489
1501
|
}
|
|
1490
|
-
if (current !== void 0)
|
|
1502
|
+
if (current !== void 0)
|
|
1503
|
+
finalData[col.name] = current;
|
|
1491
1504
|
else delete finalData[col.name];
|
|
1492
1505
|
}
|
|
1493
1506
|
}
|
|
1494
1507
|
const colValues = dataToColumnValues(finalData, columns);
|
|
1495
1508
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1496
1509
|
const tbl = collectionTableName(collection);
|
|
1497
|
-
if (
|
|
1498
|
-
const
|
|
1499
|
-
const
|
|
1500
|
-
return
|
|
1501
|
-
});
|
|
1502
|
-
const uniqueParams = schema.uniqueOn.map((fieldName) => {
|
|
1503
|
-
const val = finalData[fieldName];
|
|
1504
|
-
return val !== void 0 ? val : null;
|
|
1510
|
+
if (schema.uniqueOn && schema.uniqueOn.length > 0) {
|
|
1511
|
+
const uniqueColumns = schema.uniqueOn.map((fieldName) => {
|
|
1512
|
+
const id = columns.find((column) => column.name === fieldName)?.id ?? columnId(fieldName);
|
|
1513
|
+
return { id, value: colValues[id] ?? null };
|
|
1505
1514
|
});
|
|
1506
|
-
const
|
|
1507
|
-
|
|
1515
|
+
const uniqueWhere = uniqueColumns.map(({ id }) => `"${id}" = ?`);
|
|
1516
|
+
const uniqueParams = uniqueColumns.map(({ value }) => value);
|
|
1517
|
+
if (isUpdate) uniqueParams.push(recordId);
|
|
1518
|
+
const conflict = ctx.sql.exec(
|
|
1519
|
+
`SELECT _row_id FROM "${tbl}" WHERE ${uniqueWhere.join(" AND ")}${isUpdate ? " AND _row_id <> ?" : ""} LIMIT 1`,
|
|
1508
1520
|
...uniqueParams
|
|
1509
1521
|
);
|
|
1510
|
-
if (
|
|
1522
|
+
if (conflict.toArray().length > 0) {
|
|
1511
1523
|
const fields = schema.uniqueOn.map((f) => `${f}=${finalData[f] ?? "null"}`).join(", ");
|
|
1512
1524
|
return {
|
|
1513
1525
|
success: false,
|
|
@@ -1574,8 +1586,21 @@ function deleteRecord(ctx, collection, recordId, userId, userRole, skipUserRbac
|
|
|
1574
1586
|
return { success: true, data: { deleted: true } };
|
|
1575
1587
|
}
|
|
1576
1588
|
function refuseUnknownWhere(collection, schema, where) {
|
|
1577
|
-
const columns = (schema
|
|
1589
|
+
const columns = (schema?.columns ?? []).map(resolveColumn);
|
|
1578
1590
|
const filterable = ["recordId", "createdBy", ...columns.map((c) => c.name)];
|
|
1591
|
+
if (typeof where !== "object" || where === null || Array.isArray(where)) {
|
|
1592
|
+
const got = where === null ? "null" : Array.isArray(where) ? "an array" : typeof where;
|
|
1593
|
+
return {
|
|
1594
|
+
success: false,
|
|
1595
|
+
error: `where for "${collection}" must be an object of field=value pairs, not ${got} \u2014 filterable fields: ${filterable.join(", ")}`
|
|
1596
|
+
};
|
|
1597
|
+
}
|
|
1598
|
+
if (!schema && Object.keys(where).length > 0) {
|
|
1599
|
+
return {
|
|
1600
|
+
success: false,
|
|
1601
|
+
error: `"${collection}" has no schema, so it has no filterable fields \u2014 query it without \`where\``
|
|
1602
|
+
};
|
|
1603
|
+
}
|
|
1579
1604
|
const unknown = Object.keys(where).filter(
|
|
1580
1605
|
(key) => key !== "recordId" && key !== "createdBy" && !columns.some((c) => c.id === columnId(key) || c.name === key)
|
|
1581
1606
|
);
|
|
@@ -1604,11 +1629,14 @@ function deleteWhere(ctx, collection, where, limit, userId, userRole, skipUserRb
|
|
|
1604
1629
|
error: `records.deleteWhere: collection "${collection}" has no schema, so it has no filterable fields \u2014 delete its rows by recordId with records.delete`
|
|
1605
1630
|
};
|
|
1606
1631
|
}
|
|
1607
|
-
if (!where || typeof where !== "object" || Array.isArray(where) || Object.keys(where).length === 0) {
|
|
1608
|
-
return { success: false, error: "Missing required param: where (must match at least one field)" };
|
|
1609
|
-
}
|
|
1610
1632
|
const whereRefusal = refuseUnknownWhere(collection, schema, where);
|
|
1611
1633
|
if (whereRefusal) return whereRefusal;
|
|
1634
|
+
if (Object.keys(where).length === 0) {
|
|
1635
|
+
return {
|
|
1636
|
+
success: false,
|
|
1637
|
+
error: "Missing required param: where (must match at least one field)"
|
|
1638
|
+
};
|
|
1639
|
+
}
|
|
1612
1640
|
if (limit !== void 0 && !(typeof limit === "number" && Number.isFinite(limit))) {
|
|
1613
1641
|
return { success: false, error: "limit must be a finite number" };
|
|
1614
1642
|
}
|
|
@@ -1616,32 +1644,27 @@ function deleteWhere(ctx, collection, where, limit, userId, userRole, skipUserRb
|
|
|
1616
1644
|
Math.max(1, Math.floor(limit ?? DEFAULT_DELETE_WHERE_LIMIT)),
|
|
1617
1645
|
MAX_DELETE_WHERE_LIMIT
|
|
1618
1646
|
);
|
|
1619
|
-
const scan = { capped: false };
|
|
1620
1647
|
const matched = executeQuery(
|
|
1621
1648
|
ctx,
|
|
1622
1649
|
{ collection, where, limit: page },
|
|
1623
1650
|
userId,
|
|
1624
1651
|
userRole,
|
|
1625
|
-
|
|
1626
|
-
scan
|
|
1652
|
+
true
|
|
1627
1653
|
);
|
|
1628
|
-
if (
|
|
1629
|
-
return {
|
|
1630
|
-
success: false,
|
|
1631
|
-
error: `records.deleteWhere scanned ${MAX_FILTERED_SCAN_ROWS} rows without filling a page of readable matches in "${collection}" \u2014 narrow \`where\` (or delete by recordId) and retry`
|
|
1632
|
-
};
|
|
1633
|
-
}
|
|
1634
|
-
if (!skipUserRbac && schema) {
|
|
1654
|
+
if (!skipUserRbac) {
|
|
1635
1655
|
const permCtx = ctx.getPermissionContext();
|
|
1636
1656
|
for (const record of matched) {
|
|
1637
1657
|
if (!canDelete(schema, userRole, record, userId, permCtx)) {
|
|
1638
|
-
return {
|
|
1658
|
+
return {
|
|
1659
|
+
success: false,
|
|
1660
|
+
error: `DELETE DENIED: role=${userRole}, collection=${collection}`
|
|
1661
|
+
};
|
|
1639
1662
|
}
|
|
1640
1663
|
}
|
|
1641
1664
|
}
|
|
1642
1665
|
let deleted = 0;
|
|
1643
1666
|
for (const record of matched) {
|
|
1644
|
-
if (deleteRecord(ctx, collection, record.recordId, userId, userRole,
|
|
1667
|
+
if (deleteRecord(ctx, collection, record.recordId, userId, userRole, true).success) {
|
|
1645
1668
|
deleted++;
|
|
1646
1669
|
}
|
|
1647
1670
|
}
|
|
@@ -2340,17 +2363,17 @@ async function registerUser(sql, userId, name, email, imageUrl, isAdmin, default
|
|
|
2340
2363
|
);
|
|
2341
2364
|
return { id: userId, email, name, imageUrl, role, createdAt: now, lastSeenAt: now };
|
|
2342
2365
|
}
|
|
2343
|
-
function rosterFor(ctx, schema, records, publicRoster,
|
|
2344
|
-
if (isAnonymousUserId(
|
|
2345
|
-
if (
|
|
2366
|
+
function rosterFor(ctx, schema, records, publicRoster, userId, role) {
|
|
2367
|
+
if (!userId || isAnonymousUserId(userId)) return [];
|
|
2368
|
+
if (role === "admin") {
|
|
2346
2369
|
const permissionContext = ctx.getPermissionContext();
|
|
2347
|
-
return records.filter((record) => canRead(schema, "admin", record,
|
|
2370
|
+
return records.filter((record) => canRead(schema, "admin", record, userId, permissionContext)).map((record) => ({ id: record.recordId, ...record.data }));
|
|
2348
2371
|
}
|
|
2349
|
-
if (!getRolePermissions(schema,
|
|
2372
|
+
if (!getRolePermissions(schema, role).read) return [];
|
|
2350
2373
|
if (schema.roster === "read-policy") {
|
|
2351
2374
|
const permissionContext = ctx.getPermissionContext();
|
|
2352
2375
|
const readable = new Set(
|
|
2353
|
-
records.filter((record) => canRead(schema,
|
|
2376
|
+
records.filter((record) => canRead(schema, role, record, userId, permissionContext)).map((record) => record.recordId)
|
|
2354
2377
|
);
|
|
2355
2378
|
return publicRoster.filter((user) => readable.has(user.id));
|
|
2356
2379
|
}
|
|
@@ -2361,17 +2384,18 @@ function publicIdentities(records) {
|
|
|
2361
2384
|
id: recordId,
|
|
2362
2385
|
name: data.name,
|
|
2363
2386
|
...data.imageUrl ? { imageUrl: data.imageUrl } : {},
|
|
2364
|
-
role: data.role
|
|
2387
|
+
role: data.role,
|
|
2388
|
+
lastSeenAt: data.lastSeenAt
|
|
2365
2389
|
}));
|
|
2366
2390
|
}
|
|
2367
|
-
function
|
|
2391
|
+
function rosterForCaller(ctx, userId, role) {
|
|
2368
2392
|
const schema = ctx.schemaRegistry.get("users");
|
|
2369
|
-
if (!schema)
|
|
2370
|
-
ctx.send(ws, { type: MSG.USER_LIST, payload: { users: [] } });
|
|
2371
|
-
return;
|
|
2372
|
-
}
|
|
2393
|
+
if (!schema) return [];
|
|
2373
2394
|
const records = getAllUserRecords(ctx.sql, ctx.schemaRegistry);
|
|
2374
|
-
|
|
2395
|
+
return rosterFor(ctx, schema, records, publicIdentities(records), userId, role);
|
|
2396
|
+
}
|
|
2397
|
+
function handleUserList(ctx, ws, attachment) {
|
|
2398
|
+
const users = rosterForCaller(ctx, attachment.userId, attachment.role);
|
|
2375
2399
|
ctx.send(ws, { type: MSG.USER_LIST, payload: { users } });
|
|
2376
2400
|
}
|
|
2377
2401
|
function broadcastUserList(ctx, except) {
|
|
@@ -2386,7 +2410,9 @@ function broadcastUserList(ctx, except) {
|
|
|
2386
2410
|
if (!attachment) continue;
|
|
2387
2411
|
ctx.send(other, {
|
|
2388
2412
|
type: MSG.USER_LIST,
|
|
2389
|
-
payload: {
|
|
2413
|
+
payload: {
|
|
2414
|
+
users: rosterFor(ctx, schema, records, publicRoster, attachment.userId, attachment.role)
|
|
2415
|
+
}
|
|
2390
2416
|
});
|
|
2391
2417
|
}
|
|
2392
2418
|
}
|
|
@@ -2439,8 +2465,8 @@ var CORS_HEADERS = {
|
|
|
2439
2465
|
"Access-Control-Allow-Headers": "Content-Type, X-User-Id, X-App-Action",
|
|
2440
2466
|
"Content-Type": "application/json"
|
|
2441
2467
|
};
|
|
2442
|
-
async function handleToolsRequest(ctx,
|
|
2443
|
-
if (
|
|
2468
|
+
async function handleToolsRequest(ctx, request2, path) {
|
|
2469
|
+
if (request2.method === "OPTIONS") {
|
|
2444
2470
|
return new Response(null, { headers: CORS_HEADERS });
|
|
2445
2471
|
}
|
|
2446
2472
|
if (path === "tools/list") {
|
|
@@ -2457,15 +2483,15 @@ async function handleToolsRequest(ctx, request, path) {
|
|
|
2457
2483
|
}
|
|
2458
2484
|
return Response.json({ tool: tool3 }, { headers: CORS_HEADERS });
|
|
2459
2485
|
}
|
|
2460
|
-
if (path.startsWith("tools/execute") &&
|
|
2461
|
-
return handleToolExecute(ctx,
|
|
2486
|
+
if (path.startsWith("tools/execute") && request2.method === "POST") {
|
|
2487
|
+
return handleToolExecute(ctx, request2);
|
|
2462
2488
|
}
|
|
2463
2489
|
return Response.json({ error: "Not found" }, { status: 404, headers: CORS_HEADERS });
|
|
2464
2490
|
}
|
|
2465
|
-
async function handleToolExecute(ctx,
|
|
2491
|
+
async function handleToolExecute(ctx, request2) {
|
|
2466
2492
|
let body;
|
|
2467
2493
|
try {
|
|
2468
|
-
body = await
|
|
2494
|
+
body = await request2.json();
|
|
2469
2495
|
} catch {
|
|
2470
2496
|
return Response.json(
|
|
2471
2497
|
{ success: false, error: "Invalid JSON body" },
|
|
@@ -2479,8 +2505,8 @@ async function handleToolExecute(ctx, request) {
|
|
|
2479
2505
|
{ status: 400, headers: CORS_HEADERS }
|
|
2480
2506
|
);
|
|
2481
2507
|
}
|
|
2482
|
-
const userId =
|
|
2483
|
-
const isAppAction =
|
|
2508
|
+
const userId = request2.headers.get("x-user-id") ?? "";
|
|
2509
|
+
const isAppAction = request2.headers.get("x-app-action")?.toLowerCase() === "true";
|
|
2484
2510
|
let userRole = "viewer";
|
|
2485
2511
|
if (userId && ctx.ownerUserId && userId === ctx.ownerUserId) {
|
|
2486
2512
|
userRole = "admin";
|
|
@@ -2515,17 +2541,11 @@ async function executeTool(ctx, toolName, params, userId, userRole, skipUserRbac
|
|
|
2515
2541
|
if (!collection) return { success: false, error: "Missing required param: collection" };
|
|
2516
2542
|
const resolved = resolveCollection(ctx, collection);
|
|
2517
2543
|
if (!resolved.ok) return { success: false, error: resolved.error };
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
if (!resolved.schema) {
|
|
2521
|
-
return {
|
|
2522
|
-
success: false,
|
|
2523
|
-
error: `records.query: collection "${collection}" has no schema, so it has no filterable fields \u2014 query it without \`where\``
|
|
2524
|
-
};
|
|
2525
|
-
}
|
|
2526
|
-
const refusal = refuseUnknownWhere(collection, resolved.schema, where);
|
|
2544
|
+
if (params.where !== void 0) {
|
|
2545
|
+
const refusal = refuseUnknownWhere(collection, resolved.schema, params.where);
|
|
2527
2546
|
if (refusal) return refusal;
|
|
2528
2547
|
}
|
|
2548
|
+
const where = params.where;
|
|
2529
2549
|
const query = {
|
|
2530
2550
|
collection,
|
|
2531
2551
|
where,
|
|
@@ -2634,8 +2654,16 @@ async function executeTool(ctx, toolName, params, userId, userRole, skipUserRbac
|
|
|
2634
2654
|
if (!user) return { success: false, error: "User not found" };
|
|
2635
2655
|
return { success: true, data: { user } };
|
|
2636
2656
|
}
|
|
2657
|
+
// The same roster the socket's `user.list` returns, for the same caller.
|
|
2658
|
+
// Reading the users table directly here handed any member every peer's
|
|
2659
|
+
// email regardless of the room's users policy, while their own socket
|
|
2660
|
+
// roster and `useQuery('users')` obeyed it.
|
|
2661
|
+
//
|
|
2662
|
+
// An app action (`X-App-Action`) is the app's own server-side code, which
|
|
2663
|
+
// is already the trust boundary and reads every other collection RBAC-off;
|
|
2664
|
+
// it keeps the full rows.
|
|
2637
2665
|
case "user.list": {
|
|
2638
|
-
const users = getAllUsers(ctx.sql, ctx.schemaRegistry);
|
|
2666
|
+
const users = skipUserRbac ? getAllUsers(ctx.sql, ctx.schemaRegistry) : rosterForCaller(ctx, userId, userRole);
|
|
2639
2667
|
return { success: true, data: { users } };
|
|
2640
2668
|
}
|
|
2641
2669
|
default:
|
|
@@ -2770,25 +2798,25 @@ var CORS_HEADERS2 = {
|
|
|
2770
2798
|
"Access-Control-Allow-Headers": "Content-Type",
|
|
2771
2799
|
"Content-Type": "application/json"
|
|
2772
2800
|
};
|
|
2773
|
-
async function handleApiRequest(ctx,
|
|
2774
|
-
if (
|
|
2801
|
+
async function handleApiRequest(ctx, request2, url) {
|
|
2802
|
+
if (request2.method === "OPTIONS") {
|
|
2775
2803
|
return new Response(null, { headers: CORS_HEADERS2 });
|
|
2776
2804
|
}
|
|
2777
2805
|
const path = url.pathname.replace("/api/", "");
|
|
2778
2806
|
if (path.startsWith("tools/")) {
|
|
2779
|
-
return handleToolsRequest(ctx,
|
|
2807
|
+
return handleToolsRequest(ctx, request2, path);
|
|
2780
2808
|
}
|
|
2781
2809
|
if (path === "debug/status") {
|
|
2782
2810
|
return handleDebugStatus(ctx);
|
|
2783
2811
|
}
|
|
2784
2812
|
if (path === "debug/sql") {
|
|
2785
|
-
return handleDebugSql(ctx,
|
|
2813
|
+
return handleDebugSql(ctx, request2, url);
|
|
2786
2814
|
}
|
|
2787
2815
|
if (path === "debug/user-role") {
|
|
2788
2816
|
return handleDebugUserRole(ctx, url);
|
|
2789
2817
|
}
|
|
2790
|
-
if (path === "debug/set-role" &&
|
|
2791
|
-
return handleDebugSetRole(ctx,
|
|
2818
|
+
if (path === "debug/set-role" && request2.method === "POST") {
|
|
2819
|
+
return handleDebugSetRole(ctx, request2);
|
|
2792
2820
|
}
|
|
2793
2821
|
if (path.startsWith("debug/records/")) {
|
|
2794
2822
|
return handleDebugRecords(ctx, path);
|
|
@@ -2819,8 +2847,8 @@ function handleDebugUserRole(ctx, url) {
|
|
|
2819
2847
|
role: user.role
|
|
2820
2848
|
}, { headers: CORS_HEADERS2 });
|
|
2821
2849
|
}
|
|
2822
|
-
async function handleDebugSetRole(ctx,
|
|
2823
|
-
const body = await
|
|
2850
|
+
async function handleDebugSetRole(ctx, request2) {
|
|
2851
|
+
const body = await request2.json();
|
|
2824
2852
|
if (!body.role) {
|
|
2825
2853
|
return Response.json({ error: "role is required" }, { status: 400, headers: CORS_HEADERS2 });
|
|
2826
2854
|
}
|
|
@@ -2928,11 +2956,11 @@ function handleDebugQuery(ctx, url) {
|
|
|
2928
2956
|
records
|
|
2929
2957
|
}, { headers: CORS_HEADERS2 });
|
|
2930
2958
|
}
|
|
2931
|
-
async function handleDebugSql(ctx,
|
|
2959
|
+
async function handleDebugSql(ctx, request2, url) {
|
|
2932
2960
|
let sql;
|
|
2933
2961
|
let params = [];
|
|
2934
|
-
if (
|
|
2935
|
-
const body = await
|
|
2962
|
+
if (request2.method === "POST") {
|
|
2963
|
+
const body = await request2.json();
|
|
2936
2964
|
sql = body.sql;
|
|
2937
2965
|
params = body.params ?? [];
|
|
2938
2966
|
} else {
|
|
@@ -3052,10 +3080,10 @@ var RecordRoom = class extends BaseRoom {
|
|
|
3052
3080
|
// ============================================================================
|
|
3053
3081
|
// HTTP Entry Point (overrides BaseRoom.fetch)
|
|
3054
3082
|
// ============================================================================
|
|
3055
|
-
async fetch(
|
|
3056
|
-
const url = new URL(
|
|
3083
|
+
async fetch(request2) {
|
|
3084
|
+
const url = new URL(request2.url);
|
|
3057
3085
|
if (url.pathname.startsWith("/internal/")) {
|
|
3058
|
-
const internal = await this.handleInternalRequest(
|
|
3086
|
+
const internal = await this.handleInternalRequest(request2, url);
|
|
3059
3087
|
if (internal) return internal;
|
|
3060
3088
|
}
|
|
3061
3089
|
const fetchStart = Date.now();
|
|
@@ -3068,11 +3096,11 @@ var RecordRoom = class extends BaseRoom {
|
|
|
3068
3096
|
if (url.pathname.startsWith("/api/debug/") && !this.debugRoutesEnabled) {
|
|
3069
3097
|
return new Response("Not Found", { status: 404 });
|
|
3070
3098
|
}
|
|
3071
|
-
return handleApiRequest(this.createHandlerContext(),
|
|
3099
|
+
return handleApiRequest(this.createHandlerContext(), request2, url);
|
|
3072
3100
|
}
|
|
3073
|
-
if (
|
|
3101
|
+
if (request2.headers.get("Upgrade") === "websocket") {
|
|
3074
3102
|
this._fetchTiming = { fetchStart, isColdStart, needsInit, initMs };
|
|
3075
|
-
return super.fetch(
|
|
3103
|
+
return super.fetch(request2);
|
|
3076
3104
|
}
|
|
3077
3105
|
return new Response("Not Found", { status: 404 });
|
|
3078
3106
|
}
|
|
@@ -3654,9 +3682,9 @@ var CanvasRoom = class extends BaseRoom {
|
|
|
3654
3682
|
// --------------------------------------------------------------------------
|
|
3655
3683
|
// BaseRoom Lifecycle
|
|
3656
3684
|
// --------------------------------------------------------------------------
|
|
3657
|
-
async fetch(
|
|
3685
|
+
async fetch(request2) {
|
|
3658
3686
|
this.ensureInitialized();
|
|
3659
|
-
return super.fetch(
|
|
3687
|
+
return super.fetch(request2);
|
|
3660
3688
|
}
|
|
3661
3689
|
onConnect(ws, user) {
|
|
3662
3690
|
this.ensureInitialized();
|
|
@@ -4135,6 +4163,7 @@ function cronMatches(parsed, timezone, date) {
|
|
|
4135
4163
|
}
|
|
4136
4164
|
|
|
4137
4165
|
// src/server/rooms/cron-room.ts
|
|
4166
|
+
var CRON_TASKS_INTERNAL_PATH = "/internal/cron/tasks";
|
|
4138
4167
|
var CRON_WRITE_TYPES = /* @__PURE__ */ new Set([
|
|
4139
4168
|
MSG.CRON_TRIGGER,
|
|
4140
4169
|
MSG.CRON_PAUSE,
|
|
@@ -4209,9 +4238,15 @@ var CronRoom = class extends BaseRoom {
|
|
|
4209
4238
|
// ==========================================================================
|
|
4210
4239
|
// BaseRoom Lifecycle
|
|
4211
4240
|
// ==========================================================================
|
|
4212
|
-
async fetch(
|
|
4241
|
+
async fetch(request2) {
|
|
4213
4242
|
this.ensureInitialized();
|
|
4214
|
-
return super.fetch(
|
|
4243
|
+
return super.fetch(request2);
|
|
4244
|
+
}
|
|
4245
|
+
async handleInternalRequest(request2, url) {
|
|
4246
|
+
if (request2.method === "GET" && url.pathname === CRON_TASKS_INTERNAL_PATH) {
|
|
4247
|
+
return Response.json({ tasks: this.getTaskStates() });
|
|
4248
|
+
}
|
|
4249
|
+
return super.handleInternalRequest(request2, url);
|
|
4215
4250
|
}
|
|
4216
4251
|
onConnect(ws, user) {
|
|
4217
4252
|
this.ensureInitialized();
|
|
@@ -4359,15 +4394,22 @@ var CronRoom = class extends BaseRoom {
|
|
|
4359
4394
|
const start = Date.now();
|
|
4360
4395
|
let success = true;
|
|
4361
4396
|
let error;
|
|
4397
|
+
let thrown;
|
|
4362
4398
|
try {
|
|
4363
4399
|
await this.onTask(taskName);
|
|
4364
4400
|
} catch (e) {
|
|
4365
4401
|
success = false;
|
|
4402
|
+
thrown = e;
|
|
4366
4403
|
error = e instanceof Error ? e.message : String(e);
|
|
4367
|
-
console.error(`[CronRoom] Task "${taskName}" failed:`, e);
|
|
4368
4404
|
}
|
|
4369
4405
|
const durationMs = Date.now() - start;
|
|
4370
4406
|
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4407
|
+
if (success) {
|
|
4408
|
+
console.log(`[cron] ${taskName} ok ${durationMs}ms`);
|
|
4409
|
+
} else {
|
|
4410
|
+
const stack = thrown instanceof Error ? thrown.stack : void 0;
|
|
4411
|
+
console.error(`[cron] ${taskName} failed ${durationMs}ms: ${error}`, ...stack ? [stack] : []);
|
|
4412
|
+
}
|
|
4371
4413
|
this.sql.exec(
|
|
4372
4414
|
`INSERT INTO cron_history (task_name, started_at, completed_at, success, duration_ms, error, created_at)
|
|
4373
4415
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
@@ -4476,6 +4518,19 @@ var CronRoom = class extends BaseRoom {
|
|
|
4476
4518
|
);
|
|
4477
4519
|
}
|
|
4478
4520
|
};
|
|
4521
|
+
var armedCronRooms = /* @__PURE__ */ new Set();
|
|
4522
|
+
function armCronRoom(ctx, namespace, roomId, tasks) {
|
|
4523
|
+
if (tasks.length === 0 || armedCronRooms.has(roomId)) return;
|
|
4524
|
+
armedCronRooms.add(roomId);
|
|
4525
|
+
ctx.waitUntil(
|
|
4526
|
+
namespace.get(namespace.idFromName(roomId)).fetch(`https://internal${CRON_TASKS_INTERNAL_PATH}`).then((response) => {
|
|
4527
|
+
if (!response.ok) armedCronRooms.delete(roomId);
|
|
4528
|
+
return response;
|
|
4529
|
+
}).catch(() => {
|
|
4530
|
+
armedCronRooms.delete(roomId);
|
|
4531
|
+
})
|
|
4532
|
+
);
|
|
4533
|
+
}
|
|
4479
4534
|
function computeNextRunAt(task, from) {
|
|
4480
4535
|
if (task.interval_minutes) {
|
|
4481
4536
|
return new Date(from.getTime() + task.interval_minutes * 60 * 1e3).toISOString();
|
|
@@ -4548,9 +4603,9 @@ var JobRoom = class extends BaseRoom {
|
|
|
4548
4603
|
// ==========================================================================
|
|
4549
4604
|
// BaseRoom Lifecycle
|
|
4550
4605
|
// ==========================================================================
|
|
4551
|
-
async fetch(
|
|
4606
|
+
async fetch(request2) {
|
|
4552
4607
|
this.ensureInitialized();
|
|
4553
|
-
return super.fetch(
|
|
4608
|
+
return super.fetch(request2);
|
|
4554
4609
|
}
|
|
4555
4610
|
async onConnect(ws, user) {
|
|
4556
4611
|
this.ensureInitialized();
|
|
@@ -4637,13 +4692,13 @@ var JobRoom = class extends BaseRoom {
|
|
|
4637
4692
|
* Subclasses overriding `onRequest` should call `super.onRequest(req)`
|
|
4638
4693
|
* for paths they don't handle.
|
|
4639
4694
|
*/
|
|
4640
|
-
async onRequest(
|
|
4695
|
+
async onRequest(request2) {
|
|
4641
4696
|
this.ensureInitialized();
|
|
4642
|
-
const url = new URL(
|
|
4643
|
-
if (
|
|
4697
|
+
const url = new URL(request2.url);
|
|
4698
|
+
if (request2.method !== "POST" || !url.pathname.endsWith("/enqueue")) {
|
|
4644
4699
|
return new Response("Not Found", { status: 404 });
|
|
4645
4700
|
}
|
|
4646
|
-
const body = await
|
|
4701
|
+
const body = await request2.json().catch(() => null);
|
|
4647
4702
|
if (!body || typeof body.type !== "string" || !body.type) {
|
|
4648
4703
|
return new Response(JSON.stringify({ error: "type required" }), {
|
|
4649
4704
|
status: 400,
|
|
@@ -4809,7 +4864,8 @@ var JobRoom = class extends BaseRoom {
|
|
|
4809
4864
|
} catch (e) {
|
|
4810
4865
|
outcome = "failed";
|
|
4811
4866
|
errorMessage = e instanceof Error ? e.message : String(e);
|
|
4812
|
-
|
|
4867
|
+
const stack = e instanceof Error ? e.stack : void 0;
|
|
4868
|
+
console.error(`[jobs] ${row.type} (${row.id}) failed: ${errorMessage}`, ...stack ? [stack] : []);
|
|
4813
4869
|
} finally {
|
|
4814
4870
|
this.inFlight.delete(row.id);
|
|
4815
4871
|
}
|
|
@@ -5123,7 +5179,8 @@ var AUTO_PROVISIONABLE_TYPES = /* @__PURE__ */ new Set([
|
|
|
5123
5179
|
"kv_namespace",
|
|
5124
5180
|
"vectorize",
|
|
5125
5181
|
"r2_bucket",
|
|
5126
|
-
"queue"
|
|
5182
|
+
"queue",
|
|
5183
|
+
"ai_search"
|
|
5127
5184
|
]);
|
|
5128
5185
|
var VECTORIZE_METRICS = /* @__PURE__ */ new Set(["cosine", "euclidean", "dot-product"]);
|
|
5129
5186
|
function isAutoProvision(b) {
|
|
@@ -5138,6 +5195,8 @@ function isAutoProvision(b) {
|
|
|
5138
5195
|
return b.bucket_name === AUTO_PROVISION_SENTINEL;
|
|
5139
5196
|
case "queue":
|
|
5140
5197
|
return b.queue_name === AUTO_PROVISION_SENTINEL;
|
|
5198
|
+
case "ai_search":
|
|
5199
|
+
return b.instance_name === AUTO_PROVISION_SENTINEL;
|
|
5141
5200
|
default:
|
|
5142
5201
|
return false;
|
|
5143
5202
|
}
|
|
@@ -5152,7 +5211,8 @@ var ALLOWED_BINDING_TYPES = /* @__PURE__ */ new Set([
|
|
|
5152
5211
|
"browser_rendering",
|
|
5153
5212
|
"analytics_engine",
|
|
5154
5213
|
"hyperdrive",
|
|
5155
|
-
"ratelimit"
|
|
5214
|
+
"ratelimit",
|
|
5215
|
+
"ai_search"
|
|
5156
5216
|
]);
|
|
5157
5217
|
var RESERVED_BINDING_NAMES = /* @__PURE__ */ new Set([
|
|
5158
5218
|
"ASSETS",
|
|
@@ -5176,6 +5236,7 @@ function validateBindingManifest(manifest) {
|
|
|
5176
5236
|
}
|
|
5177
5237
|
const errors = [];
|
|
5178
5238
|
const seenNames = /* @__PURE__ */ new Set();
|
|
5239
|
+
let aiSearchCount = 0;
|
|
5179
5240
|
for (const entry of manifest) {
|
|
5180
5241
|
if (!entry || typeof entry !== "object") {
|
|
5181
5242
|
errors.push({
|
|
@@ -5206,14 +5267,23 @@ function validateBindingManifest(manifest) {
|
|
|
5206
5267
|
}
|
|
5207
5268
|
seenNames.add(name);
|
|
5208
5269
|
const binding = entry;
|
|
5270
|
+
if (binding.type === "ai_search") aiSearchCount++;
|
|
5209
5271
|
const fieldErr = requiredFieldError(binding);
|
|
5210
5272
|
if (fieldErr) errors.push({ binding, reason: fieldErr });
|
|
5211
5273
|
}
|
|
5274
|
+
if (aiSearchCount > 1) {
|
|
5275
|
+
errors.push({ reason: "Only one ai_search binding may be declared per app" });
|
|
5276
|
+
}
|
|
5212
5277
|
if (errors.length) return { valid: false, errors };
|
|
5213
5278
|
return { valid: true, bindings: manifest };
|
|
5214
5279
|
}
|
|
5215
5280
|
function requiredFieldError(b) {
|
|
5216
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;
|
|
5217
5287
|
case "vectorize": {
|
|
5218
5288
|
if (!b.index_name) return `vectorize binding "${b.name}" missing index_name (or "auto")`;
|
|
5219
5289
|
if (b.index_name === AUTO_PROVISION_SENTINEL) {
|
|
@@ -5275,6 +5345,13 @@ function requiredFieldError(b) {
|
|
|
5275
5345
|
}
|
|
5276
5346
|
}
|
|
5277
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
|
+
}
|
|
5278
5355
|
const out = [];
|
|
5279
5356
|
const aiBinding = pluckString(outputConfig.ai, "binding");
|
|
5280
5357
|
if (aiBinding) out.push({ type: "ai", name: aiBinding });
|
|
@@ -5292,6 +5369,11 @@ function bindingManifestFromOutputConfig(outputConfig) {
|
|
|
5292
5369
|
...metric && (metric === "cosine" || metric === "euclidean" || metric === "dot-product") && { metric }
|
|
5293
5370
|
});
|
|
5294
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
|
+
}
|
|
5295
5377
|
for (const r of asObjectArray(outputConfig.r2_buckets)) {
|
|
5296
5378
|
const name = pluckString(r, "binding");
|
|
5297
5379
|
const bucket_name = pluckString(r, "bucket_name");
|
|
@@ -5827,218 +5909,8 @@ var DIRECTORY_SCHEMAS = [
|
|
|
5827
5909
|
postsSchema
|
|
5828
5910
|
];
|
|
5829
5911
|
|
|
5830
|
-
// src/server/schemas/workspace.ts
|
|
5831
|
-
var workspaceTeamsSchema = {
|
|
5832
|
-
name: "teams",
|
|
5833
|
-
columns: [
|
|
5834
|
-
{ name: "Name", storage: "text", interpretation: "plain" },
|
|
5835
|
-
{ name: "CreatedBy", storage: "text", interpretation: "plain" },
|
|
5836
|
-
{ name: "IsOpen", storage: "number", interpretation: { kind: "boolean" } }
|
|
5837
|
-
],
|
|
5838
|
-
teamField: "_rowId",
|
|
5839
|
-
permissions: {
|
|
5840
|
-
admin: { read: true, create: true, update: true, delete: true },
|
|
5841
|
-
member: { read: "team", create: true, update: "team", delete: "own" }
|
|
5842
|
-
}
|
|
5843
|
-
};
|
|
5844
|
-
var workspaceTeamMembersSchema = {
|
|
5845
|
-
name: "team_members",
|
|
5846
|
-
columns: [
|
|
5847
|
-
{ name: "TeamId", storage: "text", interpretation: "plain" },
|
|
5848
|
-
{ name: "UserId", storage: "text", interpretation: "plain" },
|
|
5849
|
-
{ name: "RoleInTeam", storage: "text", interpretation: { kind: "select", options: ["admin", "member"] } },
|
|
5850
|
-
{ name: "JoinedAt", storage: "text", interpretation: { kind: "datetime" } },
|
|
5851
|
-
{ name: "Email", storage: "text", interpretation: { kind: "email" } },
|
|
5852
|
-
{ name: "Status", storage: "text", interpretation: { kind: "select", options: ["active", "invited"] } }
|
|
5853
|
-
],
|
|
5854
|
-
teamField: "TeamId",
|
|
5855
|
-
permissions: {
|
|
5856
|
-
admin: { read: true, create: true, update: true, delete: true },
|
|
5857
|
-
member: { read: "team", create: true, update: "team", delete: "team" }
|
|
5858
|
-
}
|
|
5859
|
-
};
|
|
5860
|
-
var workspaceTasksSchema = {
|
|
5861
|
-
name: "tasks",
|
|
5862
|
-
columns: [
|
|
5863
|
-
{ name: "TeamId", storage: "text", interpretation: "plain" },
|
|
5864
|
-
{ name: "Title", storage: "text", interpretation: "plain" },
|
|
5865
|
-
{ name: "Notes", storage: "text", interpretation: "plain" },
|
|
5866
|
-
{ name: "Completed", storage: "number", interpretation: { kind: "boolean" } },
|
|
5867
|
-
{ name: "CompletedAt", storage: "number", interpretation: "plain" },
|
|
5868
|
-
{ name: "Deleted", storage: "number", interpretation: { kind: "boolean" } },
|
|
5869
|
-
{ name: "DeletedAt", storage: "number", interpretation: "plain" },
|
|
5870
|
-
{ name: "Priority", storage: "text", interpretation: { kind: "select", options: ["none", "low", "medium", "high"] } },
|
|
5871
|
-
{ name: "DueDate", storage: "text", interpretation: "plain" },
|
|
5872
|
-
{ name: "ProjectId", storage: "text", interpretation: "plain" },
|
|
5873
|
-
{ name: "KanbanStatus", storage: "text", interpretation: { kind: "select", options: ["backlog", "ready", "in_progress", "review", "done"] } },
|
|
5874
|
-
{ name: "Order", storage: "number", interpretation: "plain" },
|
|
5875
|
-
{ name: "AssignedUser", storage: "text", interpretation: { kind: "json" } },
|
|
5876
|
-
{ name: "AssignedBy", storage: "text", interpretation: { kind: "json" } },
|
|
5877
|
-
{ name: "TagIds", storage: "text", interpretation: { kind: "json" } },
|
|
5878
|
-
{ name: "CreatedAt", storage: "number", interpretation: "plain" }
|
|
5879
|
-
],
|
|
5880
|
-
teamField: "TeamId",
|
|
5881
|
-
permissions: {
|
|
5882
|
-
admin: { read: true, create: true, update: true, delete: true },
|
|
5883
|
-
member: { read: "team", create: true, update: "team", delete: "team" },
|
|
5884
|
-
viewer: { read: false, create: false, update: false, delete: false }
|
|
5885
|
-
}
|
|
5886
|
-
};
|
|
5887
|
-
var workspaceProjectsSchema = {
|
|
5888
|
-
name: "projects",
|
|
5889
|
-
columns: [
|
|
5890
|
-
{ name: "TeamId", storage: "text", interpretation: "plain" },
|
|
5891
|
-
{ name: "Title", storage: "text", interpretation: "plain" },
|
|
5892
|
-
{ name: "Notes", storage: "text", interpretation: "plain" },
|
|
5893
|
-
{ name: "Color", storage: "text", interpretation: "plain" },
|
|
5894
|
-
{ name: "ParentId", storage: "text", interpretation: "plain" },
|
|
5895
|
-
{ name: "Order", storage: "number", interpretation: "plain" },
|
|
5896
|
-
{ name: "CreatedAt", storage: "number", interpretation: "plain" }
|
|
5897
|
-
],
|
|
5898
|
-
teamField: "TeamId",
|
|
5899
|
-
permissions: {
|
|
5900
|
-
admin: { read: true, create: true, update: true, delete: true },
|
|
5901
|
-
member: { read: "team", create: true, update: "team", delete: "team" },
|
|
5902
|
-
viewer: { read: false, create: false, update: false, delete: false }
|
|
5903
|
-
}
|
|
5904
|
-
};
|
|
5905
|
-
var workspaceTagsSchema = {
|
|
5906
|
-
name: "tags",
|
|
5907
|
-
columns: [
|
|
5908
|
-
{ name: "TeamId", storage: "text", interpretation: "plain" },
|
|
5909
|
-
{ name: "Name", storage: "text", interpretation: "plain" },
|
|
5910
|
-
{ name: "Color", storage: "text", interpretation: "plain" },
|
|
5911
|
-
{ name: "CreatedAt", storage: "number", interpretation: "plain" }
|
|
5912
|
-
],
|
|
5913
|
-
teamField: "TeamId",
|
|
5914
|
-
permissions: {
|
|
5915
|
-
admin: { read: true, create: true, update: true, delete: true },
|
|
5916
|
-
member: { read: "team", create: true, update: "team", delete: "team" },
|
|
5917
|
-
viewer: { read: false, create: false, update: false, delete: false }
|
|
5918
|
-
}
|
|
5919
|
-
};
|
|
5920
|
-
var workspacePeopleSchema = {
|
|
5921
|
-
name: "people",
|
|
5922
|
-
columns: [
|
|
5923
|
-
{ name: "Name", storage: "text", interpretation: "plain" },
|
|
5924
|
-
{ name: "Email", storage: "text", interpretation: { kind: "email" } },
|
|
5925
|
-
{ name: "Type", storage: "text", interpretation: { kind: "select", options: ["employee", "customer", "vendor", "contact"] } },
|
|
5926
|
-
{ name: "Status", storage: "text", interpretation: { kind: "select", options: ["active", "inactive", "archived"] } },
|
|
5927
|
-
{ name: "CompanyId", storage: "text", interpretation: "plain" },
|
|
5928
|
-
{ name: "Department", storage: "text", interpretation: "plain" },
|
|
5929
|
-
{ name: "Title", storage: "text", interpretation: "plain" },
|
|
5930
|
-
{ name: "LastContactedAt", storage: "text", interpretation: { kind: "datetime" } },
|
|
5931
|
-
{ name: "Metadata", storage: "text", interpretation: { kind: "json" } }
|
|
5932
|
-
],
|
|
5933
|
-
permissions: {
|
|
5934
|
-
"*": { read: true, create: true, update: true, delete: false },
|
|
5935
|
-
admin: { read: true, create: true, update: true, delete: true }
|
|
5936
|
-
}
|
|
5937
|
-
};
|
|
5938
|
-
var workspaceTransactionsSchema = {
|
|
5939
|
-
name: "transactions",
|
|
5940
|
-
columns: [
|
|
5941
|
-
{ name: "Type", storage: "text", interpretation: { kind: "select", options: ["revenue", "expense", "payment", "transfer", "invoice", "refund"] } },
|
|
5942
|
-
{ name: "Amount", storage: "number", interpretation: { kind: "currency", symbol: "$", decimals: 2 } },
|
|
5943
|
-
{ name: "Currency", storage: "text", interpretation: "plain" },
|
|
5944
|
-
{ name: "Description", storage: "text", interpretation: "plain" },
|
|
5945
|
-
{ name: "Category", storage: "text", interpretation: "plain" },
|
|
5946
|
-
{ name: "CounterpartyRef", storage: "text", interpretation: "plain" },
|
|
5947
|
-
{ name: "OrderRef", storage: "text", interpretation: "plain" },
|
|
5948
|
-
{ name: "Account", storage: "text", interpretation: "plain" }
|
|
5949
|
-
],
|
|
5950
|
-
permissions: {
|
|
5951
|
-
"*": { read: true, create: true, update: true, delete: false },
|
|
5952
|
-
admin: { read: true, create: true, update: true, delete: true }
|
|
5953
|
-
}
|
|
5954
|
-
};
|
|
5955
|
-
var workspaceAccountsSchema = {
|
|
5956
|
-
name: "accounts",
|
|
5957
|
-
columns: [
|
|
5958
|
-
{ name: "Name", storage: "text", interpretation: "plain" },
|
|
5959
|
-
{ name: "Balance", storage: "number", interpretation: { kind: "currency", symbol: "$", decimals: 2 } },
|
|
5960
|
-
{ name: "Currency", storage: "text", interpretation: "plain" }
|
|
5961
|
-
],
|
|
5962
|
-
permissions: {
|
|
5963
|
-
"*": { read: true, create: true, update: true, delete: false },
|
|
5964
|
-
admin: { read: true, create: true, update: true, delete: true }
|
|
5965
|
-
}
|
|
5966
|
-
};
|
|
5967
|
-
var workspaceContentSharesSchema = {
|
|
5968
|
-
name: "content_shares",
|
|
5969
|
-
ownerField: "OwnerId",
|
|
5970
|
-
columns: [
|
|
5971
|
-
{ name: "ContentType", storage: "text", interpretation: { kind: "select", options: ["document", "slide", "spreadsheet"] } },
|
|
5972
|
-
{ name: "ContentId", storage: "text", interpretation: "plain" },
|
|
5973
|
-
{ name: "OwnerId", storage: "text", interpretation: "plain" },
|
|
5974
|
-
{ name: "OwnerName", storage: "text", interpretation: "plain" },
|
|
5975
|
-
{ name: "Title", storage: "text", interpretation: "plain" },
|
|
5976
|
-
{ name: "ShareType", storage: "text", interpretation: { kind: "select", options: ["self", "channel", "team", "direct", "link", "org"] } },
|
|
5977
|
-
{ name: "ShareTarget", storage: "text", interpretation: "plain" },
|
|
5978
|
-
{ name: "Permission", storage: "text", interpretation: { kind: "select", options: ["view", "edit"] } },
|
|
5979
|
-
{ name: "SharedAt", storage: "text", interpretation: { kind: "datetime" } },
|
|
5980
|
-
{ name: "SharedBy", storage: "text", interpretation: "plain" },
|
|
5981
|
-
{ name: "SourceApp", storage: "text", interpretation: "plain" },
|
|
5982
|
-
{ name: "WordCount", storage: "number", interpretation: "plain" },
|
|
5983
|
-
{ name: "LastEditedAt", storage: "text", interpretation: { kind: "datetime" } }
|
|
5984
|
-
],
|
|
5985
|
-
permissions: {
|
|
5986
|
-
"*": { read: true, create: true, update: true, delete: "own" },
|
|
5987
|
-
admin: { read: true, create: true, update: true, delete: true }
|
|
5988
|
-
}
|
|
5989
|
-
};
|
|
5990
|
-
var workspaceFormResponsesSchema = {
|
|
5991
|
-
name: "form_responses",
|
|
5992
|
-
columns: [
|
|
5993
|
-
{ name: "FormId", storage: "text", interpretation: "plain" },
|
|
5994
|
-
{ name: "FormTitle", storage: "text", interpretation: "plain" },
|
|
5995
|
-
{ name: "RespondentId", storage: "text", interpretation: "plain" },
|
|
5996
|
-
{ name: "RespondentEmail", storage: "text", interpretation: { kind: "email" } },
|
|
5997
|
-
{ name: "RespondentName", storage: "text", interpretation: "plain" },
|
|
5998
|
-
{ name: "Data", storage: "text", interpretation: { kind: "json" } },
|
|
5999
|
-
{ name: "SubmittedAt", storage: "text", interpretation: { kind: "datetime" } }
|
|
6000
|
-
],
|
|
6001
|
-
permissions: {
|
|
6002
|
-
"*": { read: true, create: true, update: false, delete: false },
|
|
6003
|
-
admin: { read: true, create: true, update: true, delete: true }
|
|
6004
|
-
}
|
|
6005
|
-
};
|
|
6006
|
-
var workspaceEmailHandlesSchema = {
|
|
6007
|
-
name: "email_handles",
|
|
6008
|
-
columns: [
|
|
6009
|
-
{ name: "UserId", storage: "text", interpretation: "plain" },
|
|
6010
|
-
{ name: "Handle", storage: "text", interpretation: "plain" },
|
|
6011
|
-
{ name: "EmailAddress", storage: "text", interpretation: { kind: "email" } },
|
|
6012
|
-
{ name: "Status", storage: "text", interpretation: { kind: "select", options: ["active", "disabled"] } }
|
|
6013
|
-
],
|
|
6014
|
-
uniqueOn: ["Handle"],
|
|
6015
|
-
ownerField: "UserId",
|
|
6016
|
-
permissions: {
|
|
6017
|
-
"*": { read: true, create: true, update: "own", delete: false },
|
|
6018
|
-
admin: { read: true, create: true, update: true, delete: true }
|
|
6019
|
-
}
|
|
6020
|
-
};
|
|
6021
|
-
var WORKSPACE_SCHEMAS = [
|
|
6022
|
-
workspaceTeamsSchema,
|
|
6023
|
-
workspaceTeamMembersSchema,
|
|
6024
|
-
workspaceTasksSchema,
|
|
6025
|
-
workspaceProjectsSchema,
|
|
6026
|
-
workspaceTagsSchema,
|
|
6027
|
-
workspacePeopleSchema,
|
|
6028
|
-
workspaceTransactionsSchema,
|
|
6029
|
-
workspaceAccountsSchema,
|
|
6030
|
-
workspaceContentSharesSchema,
|
|
6031
|
-
workspaceFormResponsesSchema,
|
|
6032
|
-
workspaceEmailHandlesSchema
|
|
6033
|
-
];
|
|
6034
|
-
|
|
6035
5912
|
// src/server/schemas/shared-do.ts
|
|
6036
5913
|
var GLOBAL_DO_TYPES = [
|
|
6037
|
-
{
|
|
6038
|
-
name: "workspace",
|
|
6039
|
-
schemas: WORKSPACE_SCHEMAS,
|
|
6040
|
-
description: "Shared workspace data (teams, tasks, projects, people, ledger)"
|
|
6041
|
-
},
|
|
6042
5914
|
{
|
|
6043
5915
|
name: "conv",
|
|
6044
5916
|
schemas: [...CONVERSATION_SCHEMAS, ...VOTING_SCHEMAS],
|
|
@@ -6066,6 +5938,9 @@ var APP_MIGRATION_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/;
|
|
|
6066
5938
|
var MAX_APP_MIGRATION_IDS = 256;
|
|
6067
5939
|
var WORKER_OWNED_NOT_FOUND_MIGRATION_ID = "2026-08-worker-owned-not-found";
|
|
6068
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";
|
|
6069
5944
|
function validateAppMigrationIds(value) {
|
|
6070
5945
|
if (!Array.isArray(value)) {
|
|
6071
5946
|
return { valid: false, reason: "app migrations must be a JSON string array" };
|
|
@@ -6934,8 +6809,8 @@ function overPartBound(bytes) {
|
|
|
6934
6809
|
if (bytes <= UPLOAD_PART_BYTES) return null;
|
|
6935
6810
|
return fail2(413, oversizePartMessage(bytes), "too_large", { maxBytes: UPLOAD_PART_BYTES });
|
|
6936
6811
|
}
|
|
6937
|
-
function declaredLength(
|
|
6938
|
-
const raw =
|
|
6812
|
+
function declaredLength(request2, what) {
|
|
6813
|
+
const raw = request2.headers.get("content-length");
|
|
6939
6814
|
const declared = raw === null ? NaN : Number(raw);
|
|
6940
6815
|
if (!Number.isSafeInteger(declared) || declared < 0) {
|
|
6941
6816
|
return fail2(
|
|
@@ -7013,7 +6888,7 @@ function multipartFailure(error) {
|
|
|
7013
6888
|
}
|
|
7014
6889
|
function createScopedR2Handler(config) {
|
|
7015
6890
|
const requireAuthForMutations = config.requireAuthForMutations ?? true;
|
|
7016
|
-
return async (
|
|
6891
|
+
return async (request2, url, bucket, auth) => {
|
|
7017
6892
|
let subpathRaw;
|
|
7018
6893
|
try {
|
|
7019
6894
|
subpathRaw = decodeURIComponent(url.pathname.replace("/api/files", "").replace(/^\//, ""));
|
|
@@ -7042,26 +6917,26 @@ function createScopedR2Handler(config) {
|
|
|
7042
6917
|
}
|
|
7043
6918
|
const prefix = result.prefix;
|
|
7044
6919
|
const excludedPrefixes = result.excludedPrefixes ?? [];
|
|
7045
|
-
if (isFilesVerbPath(
|
|
6920
|
+
if (isFilesVerbPath(request2.method, subpath)) {
|
|
7046
6921
|
if (requireAuthForMutations && !auth.userId) {
|
|
7047
|
-
await
|
|
6922
|
+
await request2.body?.cancel().catch(() => void 0);
|
|
7048
6923
|
return fail2(401, "Authentication required", "unauthorized");
|
|
7049
6924
|
}
|
|
7050
|
-
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);
|
|
7051
6926
|
}
|
|
7052
|
-
if (!subpath &&
|
|
6927
|
+
if (!subpath && request2.method === "GET") {
|
|
7053
6928
|
return handleList(bucket, prefix, excludedPrefixes, url, scope, auth);
|
|
7054
6929
|
}
|
|
7055
|
-
if (subpath &&
|
|
6930
|
+
if (subpath && request2.method === "GET") {
|
|
7056
6931
|
if (!isKeyWithinScope(subpath, prefix, excludedPrefixes)) {
|
|
7057
6932
|
return Response.json(
|
|
7058
6933
|
{ error: "Access denied: key outside scope" },
|
|
7059
6934
|
{ status: 403, headers: CORS_HEADERS4 }
|
|
7060
6935
|
);
|
|
7061
6936
|
}
|
|
7062
|
-
return handleDownload(
|
|
6937
|
+
return handleDownload(request2, bucket, subpath, scope);
|
|
7063
6938
|
}
|
|
7064
|
-
if (subpath &&
|
|
6939
|
+
if (subpath && request2.method === "DELETE") {
|
|
7065
6940
|
if (requireAuthForMutations && !auth.userId) {
|
|
7066
6941
|
return Response.json(
|
|
7067
6942
|
{ error: "Authentication required" },
|
|
@@ -7087,18 +6962,18 @@ function createScopedR2Handler(config) {
|
|
|
7087
6962
|
return Response.json({ error: "Method not allowed" }, { status: 405, headers: CORS_HEADERS4 });
|
|
7088
6963
|
};
|
|
7089
6964
|
}
|
|
7090
|
-
async function handleUpload(
|
|
6965
|
+
async function handleUpload(request2, url, bucket, prefix, excludedPrefixes, auth, scope) {
|
|
7091
6966
|
try {
|
|
7092
|
-
const declaredBytes = declaredLength(
|
|
6967
|
+
const declaredBytes = declaredLength(request2, "File uploads");
|
|
7093
6968
|
if (declaredBytes instanceof Response) return declaredBytes;
|
|
7094
6969
|
const tooLargeRequest = overRequestBound(declaredBytes, 8 * 1024);
|
|
7095
6970
|
if (tooLargeRequest) return tooLargeRequest;
|
|
7096
|
-
const contentType =
|
|
6971
|
+
const contentType = request2.headers.get("content-type") || "";
|
|
7097
6972
|
let fileData;
|
|
7098
6973
|
let fileName;
|
|
7099
6974
|
let rawMimeType;
|
|
7100
6975
|
if (contentType.includes("multipart/form-data")) {
|
|
7101
|
-
const formData = await
|
|
6976
|
+
const formData = await request2.formData();
|
|
7102
6977
|
const file = formData.get("file");
|
|
7103
6978
|
if (!file) return fail2(400, "No file provided", "bad_request");
|
|
7104
6979
|
const tooBig = overRequestBound(file.size);
|
|
@@ -7107,7 +6982,7 @@ async function handleUpload(request, url, bucket, prefix, excludedPrefixes, auth
|
|
|
7107
6982
|
fileName = formData.get("name")?.toString() || file.name;
|
|
7108
6983
|
rawMimeType = file.type || "application/octet-stream";
|
|
7109
6984
|
} else {
|
|
7110
|
-
const body = await
|
|
6985
|
+
const body = await request2.json();
|
|
7111
6986
|
if (!body.data || !body.name) return fail2(400, "Missing data or name", "bad_request");
|
|
7112
6987
|
const base64Data = body.data.replace(/^data:[^;]+;base64,/, "");
|
|
7113
6988
|
const tooBig = overRequestBound(Math.floor(base64Data.length * 3 / 4));
|
|
@@ -7188,15 +7063,15 @@ function storedFileJson(key, prefix, name, url, scope) {
|
|
|
7188
7063
|
);
|
|
7189
7064
|
}
|
|
7190
7065
|
var MAX_CONTROL_BODY_BYTES = 64 * 1024;
|
|
7191
|
-
async function readControlJson(
|
|
7192
|
-
const declared = declaredLength(
|
|
7066
|
+
async function readControlJson(request2) {
|
|
7067
|
+
const declared = declaredLength(request2, "This request");
|
|
7193
7068
|
if (declared instanceof Response) return declared;
|
|
7194
7069
|
if (declared > MAX_CONTROL_BODY_BYTES) {
|
|
7195
7070
|
return fail2(413, `Request body exceeds ${MAX_CONTROL_BODY_BYTES} bytes`, "too_large");
|
|
7196
7071
|
}
|
|
7197
7072
|
let parsed;
|
|
7198
7073
|
try {
|
|
7199
|
-
parsed = await
|
|
7074
|
+
parsed = await request2.json();
|
|
7200
7075
|
} catch {
|
|
7201
7076
|
return fail2(400, "Body must be JSON", "bad_request");
|
|
7202
7077
|
}
|
|
@@ -7248,7 +7123,7 @@ function multipartLayoutError(declaredBytes) {
|
|
|
7248
7123
|
{ partSize: UPLOAD_PART_BYTES, partCount: multipartPartCount(declaredBytes) }
|
|
7249
7124
|
);
|
|
7250
7125
|
}
|
|
7251
|
-
async function streamPart(
|
|
7126
|
+
async function streamPart(upload2, partNumber, body, expectedBytes) {
|
|
7252
7127
|
let observedBytes = 0;
|
|
7253
7128
|
const sized = new FixedLengthStream(expectedBytes);
|
|
7254
7129
|
const pumped = body.pipeThrough(
|
|
@@ -7260,7 +7135,7 @@ async function streamPart(upload, partNumber, body, expectedBytes) {
|
|
|
7260
7135
|
})
|
|
7261
7136
|
).pipeTo(sized.writable);
|
|
7262
7137
|
const [uploaded, drained] = await Promise.allSettled([
|
|
7263
|
-
|
|
7138
|
+
upload2.uploadPart(partNumber, sized.readable),
|
|
7264
7139
|
pumped
|
|
7265
7140
|
]);
|
|
7266
7141
|
if (uploaded.status === "rejected" || drained.status === "rejected") {
|
|
@@ -7269,20 +7144,20 @@ async function streamPart(upload, partNumber, body, expectedBytes) {
|
|
|
7269
7144
|
}
|
|
7270
7145
|
return { ok: true, part: uploaded.value };
|
|
7271
7146
|
}
|
|
7272
|
-
async function handleMultipart(
|
|
7273
|
-
if (subpath === "multipart" &&
|
|
7274
|
-
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);
|
|
7275
7150
|
}
|
|
7276
7151
|
if (subpath === "multipart/part") {
|
|
7277
|
-
return multipartPart(
|
|
7152
|
+
return multipartPart(request2, url, bucket, prefix, excludedPrefixes, auth);
|
|
7278
7153
|
}
|
|
7279
7154
|
if (subpath === "multipart/complete") {
|
|
7280
|
-
return multipartComplete(
|
|
7155
|
+
return multipartComplete(request2, url, bucket, prefix, excludedPrefixes, auth, scope);
|
|
7281
7156
|
}
|
|
7282
7157
|
return multipartAbort(url, bucket, prefix, excludedPrefixes, auth);
|
|
7283
7158
|
}
|
|
7284
|
-
async function multipartInit(
|
|
7285
|
-
const body = await readControlJson(
|
|
7159
|
+
async function multipartInit(request2, url, bucket, prefix, excludedPrefixes, auth) {
|
|
7160
|
+
const body = await readControlJson(request2);
|
|
7286
7161
|
if (body instanceof Response) return body;
|
|
7287
7162
|
const size = body.size;
|
|
7288
7163
|
if (!Number.isSafeInteger(size) || size <= 0) {
|
|
@@ -7323,9 +7198,9 @@ async function multipartInit(request, url, bucket, prefix, excludedPrefixes, aut
|
|
|
7323
7198
|
"request_id_conflict"
|
|
7324
7199
|
);
|
|
7325
7200
|
}
|
|
7326
|
-
let
|
|
7201
|
+
let upload2;
|
|
7327
7202
|
try {
|
|
7328
|
-
|
|
7203
|
+
upload2 = await bucket.createMultipartUpload(located.key, {
|
|
7329
7204
|
httpMetadata: { contentType: mimeType },
|
|
7330
7205
|
customMetadata: fileMetadata(name, auth.userId, requestId, size)
|
|
7331
7206
|
});
|
|
@@ -7337,7 +7212,7 @@ async function multipartInit(request, url, bucket, prefix, excludedPrefixes, aut
|
|
|
7337
7212
|
body: JSON.stringify({
|
|
7338
7213
|
version: 1,
|
|
7339
7214
|
reservationId: requestId,
|
|
7340
|
-
uploadId:
|
|
7215
|
+
uploadId: upload2.uploadId,
|
|
7341
7216
|
objectKey: located.key,
|
|
7342
7217
|
uploadKey: located.subkey,
|
|
7343
7218
|
admitted: false,
|
|
@@ -7351,7 +7226,7 @@ async function multipartInit(request, url, bucket, prefix, excludedPrefixes, aut
|
|
|
7351
7226
|
const reservedBytes = Math.max(size, 5 * 1024 * 1024);
|
|
7352
7227
|
const admission = await admitStorage(bucket, auth.storage, reservedBytes, 0, marker);
|
|
7353
7228
|
if (admission instanceof Response) {
|
|
7354
|
-
await
|
|
7229
|
+
await upload2.abort().catch(() => void 0);
|
|
7355
7230
|
if (admission.status === 409 && auth.storage) {
|
|
7356
7231
|
const winner = await readMultipartReservation(bucket, auth.storage, requestId);
|
|
7357
7232
|
if (winner && winner !== "invalid" && winner.objectKey === located.key && winner.uploadKey === located.subkey && winner.declaredBytes === size && winner.admitted) {
|
|
@@ -7361,11 +7236,11 @@ async function multipartInit(request, url, bucket, prefix, excludedPrefixes, aut
|
|
|
7361
7236
|
return admission;
|
|
7362
7237
|
}
|
|
7363
7238
|
if (auth.storage && !await activateMultipartReservation(bucket, auth.storage, requestId)) {
|
|
7364
|
-
await
|
|
7239
|
+
await upload2.abort().catch(() => void 0);
|
|
7365
7240
|
await releaseQuotaMarker(bucket, auth.storage, admission.markerKey, true);
|
|
7366
7241
|
return quotaUnavailable("This upload reservation could not be finalized. Retry shortly.");
|
|
7367
7242
|
}
|
|
7368
|
-
return multipartSessionJson(
|
|
7243
|
+
return multipartSessionJson(upload2.uploadId, located.subkey, requestId);
|
|
7369
7244
|
}
|
|
7370
7245
|
async function abandonMultipart(bucket, session, auth, reservation) {
|
|
7371
7246
|
let result = "aborted";
|
|
@@ -7383,12 +7258,12 @@ async function abandonMultipart(bucket, session, auth, reservation) {
|
|
|
7383
7258
|
}
|
|
7384
7259
|
return result;
|
|
7385
7260
|
}
|
|
7386
|
-
async function multipartPart(
|
|
7261
|
+
async function multipartPart(request2, url, bucket, prefix, excludedPrefixes, auth) {
|
|
7387
7262
|
const refuse = async (response) => {
|
|
7388
|
-
await
|
|
7263
|
+
await request2.body?.cancel().catch(() => void 0);
|
|
7389
7264
|
return response;
|
|
7390
7265
|
};
|
|
7391
|
-
const declaredBytes = declaredLength(
|
|
7266
|
+
const declaredBytes = declaredLength(request2, "Part uploads");
|
|
7392
7267
|
if (declaredBytes instanceof Response) return refuse(declaredBytes);
|
|
7393
7268
|
const tooBig = overPartBound(declaredBytes);
|
|
7394
7269
|
if (tooBig) return refuse(tooBig);
|
|
@@ -7400,7 +7275,7 @@ async function multipartPart(request, url, bucket, prefix, excludedPrefixes, aut
|
|
|
7400
7275
|
}
|
|
7401
7276
|
const session = resolveSession(url, prefix, excludedPrefixes);
|
|
7402
7277
|
if (session instanceof Response) return refuse(session);
|
|
7403
|
-
if (!
|
|
7278
|
+
if (!request2.body) return fail2(400, "A part body is required", "bad_request");
|
|
7404
7279
|
const reservation = await readMultipartReservation(bucket, auth.storage, session.reservationId);
|
|
7405
7280
|
if (reservation === "invalid") {
|
|
7406
7281
|
return refuse(quotaUnavailable("This upload reservation could not be read. Retry shortly."));
|
|
@@ -7442,8 +7317,8 @@ async function multipartPart(request, url, bucket, prefix, excludedPrefixes, aut
|
|
|
7442
7317
|
if (refreshed === "busy") {
|
|
7443
7318
|
return refuse(quotaUnavailable("This upload reservation is busy. Retry the part shortly."));
|
|
7444
7319
|
}
|
|
7445
|
-
const
|
|
7446
|
-
const stored = await streamPart(
|
|
7320
|
+
const upload2 = bucket.resumeMultipartUpload(session.key, session.uploadId);
|
|
7321
|
+
const stored = await streamPart(upload2, partNumber, request2.body, declaredBytes);
|
|
7447
7322
|
if (!stored.ok) {
|
|
7448
7323
|
if (r2ErrorCode(stored.error) === 10024 && reservation) {
|
|
7449
7324
|
await releaseQuotaMarker(bucket, auth.storage, reservation.key, true);
|
|
@@ -7499,8 +7374,8 @@ async function finishCompletedMultipart(bucket, session, reservation, object, au
|
|
|
7499
7374
|
const name = object.customMetadata?.originalName ?? session.uploadKey.split("/").pop() ?? "file";
|
|
7500
7375
|
return storedFileJson(session.key, prefix, name, url, scope);
|
|
7501
7376
|
}
|
|
7502
|
-
async function multipartComplete(
|
|
7503
|
-
const body = await readControlJson(
|
|
7377
|
+
async function multipartComplete(request2, url, bucket, prefix, excludedPrefixes, auth, scope) {
|
|
7378
|
+
const body = await readControlJson(request2);
|
|
7504
7379
|
if (body instanceof Response) return body;
|
|
7505
7380
|
const session = resolveSession(url, prefix, excludedPrefixes);
|
|
7506
7381
|
if (session instanceof Response) return session;
|
|
@@ -7659,7 +7534,7 @@ async function handleList(bucket, prefix, excludedPrefixes, url, scope, auth) {
|
|
|
7659
7534
|
{ headers: CORS_HEADERS4 }
|
|
7660
7535
|
);
|
|
7661
7536
|
}
|
|
7662
|
-
async function handleDownload(
|
|
7537
|
+
async function handleDownload(request2, bucket, key, scope) {
|
|
7663
7538
|
const object = await bucket.get(key);
|
|
7664
7539
|
if (!object) {
|
|
7665
7540
|
return Response.json({ error: "File not found" }, { status: 404, headers: CORS_HEADERS4 });
|
|
@@ -7681,7 +7556,7 @@ async function handleDownload(request, bucket, key, scope) {
|
|
|
7681
7556
|
contentDisposition(originalName, mustDownload ? "attachment" : "inline")
|
|
7682
7557
|
);
|
|
7683
7558
|
if (mustDownload) headers.set("Content-Security-Policy", "sandbox; default-src 'none'");
|
|
7684
|
-
if (scope === "app" &&
|
|
7559
|
+
if (scope === "app" && request2.headers.get("if-none-match") === object.httpEtag) {
|
|
7685
7560
|
return new Response(null, { status: 304, headers });
|
|
7686
7561
|
}
|
|
7687
7562
|
return new Response(object.body, { headers });
|
|
@@ -8134,8 +8009,8 @@ async function composioTools(env, options) {
|
|
|
8134
8009
|
if (!list.ok || !list.success) {
|
|
8135
8010
|
throw new Error(`composioTools: failed to list Composio tools: ${list.detail}`);
|
|
8136
8011
|
}
|
|
8137
|
-
const
|
|
8138
|
-
const items = Array.isArray(
|
|
8012
|
+
const listItems2 = list.data?.items;
|
|
8013
|
+
const items = Array.isArray(listItems2) ? listItems2 : [];
|
|
8139
8014
|
const tools = {};
|
|
8140
8015
|
for (const item of items) {
|
|
8141
8016
|
if (!item?.slug) continue;
|
|
@@ -8720,18 +8595,272 @@ var COST_RATES = {
|
|
|
8720
8595
|
queriedPerDim: VECTORIZE_QUERIED_USD_PER_M_DIMS / 1e6,
|
|
8721
8596
|
/** USD per stored dimension per month. */
|
|
8722
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
|
|
8723
8605
|
}
|
|
8724
8606
|
};
|
|
8725
8607
|
function priceBindingUsageEvent(kind, op, units) {
|
|
8608
|
+
if (!Number.isFinite(units) || units < 0) return 0;
|
|
8726
8609
|
if (kind === "ai" && op === "input") {
|
|
8727
8610
|
return units * COST_RATES.ai.embedInputPerChar;
|
|
8728
8611
|
}
|
|
8729
8612
|
if (kind === "vectorize" && op === "query") {
|
|
8730
8613
|
return units * COST_RATES.vectorize.queriedPerDim;
|
|
8731
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
|
+
}
|
|
8732
8623
|
return 0;
|
|
8733
8624
|
}
|
|
8734
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
|
+
|
|
8735
8864
|
// src/server/utils/d1-migrations.ts
|
|
8736
8865
|
var META_TABLE = "_dpc_migrations";
|
|
8737
8866
|
var META_BOOTSTRAP_SQL = `CREATE TABLE IF NOT EXISTS ${META_TABLE} (idx INTEGER PRIMARY KEY, applied_at TEXT NOT NULL)`;
|
|
@@ -9242,16 +9371,16 @@ var RequestBodyTooLargeError = class extends Error {
|
|
|
9242
9371
|
this.name = "RequestBodyTooLargeError";
|
|
9243
9372
|
}
|
|
9244
9373
|
};
|
|
9245
|
-
async function readBoundedRequestText(
|
|
9374
|
+
async function readBoundedRequestText(request2, maxBytes) {
|
|
9246
9375
|
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) {
|
|
9247
9376
|
throw new TypeError("maxBytes must be a positive safe integer");
|
|
9248
9377
|
}
|
|
9249
|
-
const declared =
|
|
9378
|
+
const declared = request2.headers.get("content-length");
|
|
9250
9379
|
if (declared && /^\d+$/.test(declared.trim()) && Number(declared) > maxBytes) {
|
|
9251
9380
|
throw new RequestBodyTooLargeError(maxBytes);
|
|
9252
9381
|
}
|
|
9253
|
-
if (!
|
|
9254
|
-
const reader =
|
|
9382
|
+
if (!request2.body) return "";
|
|
9383
|
+
const reader = request2.body.getReader();
|
|
9255
9384
|
const decoder = new TextDecoder();
|
|
9256
9385
|
let bytes = 0;
|
|
9257
9386
|
let text = "";
|
|
@@ -9465,10 +9594,10 @@ function documentationRequestPath(pathname, publicBasePath) {
|
|
|
9465
9594
|
const clean = logical === "/" ? "/" : logical.replace(/\/$/, "");
|
|
9466
9595
|
return canonicalNativeDocumentationRequestPath(clean);
|
|
9467
9596
|
}
|
|
9468
|
-
async function fetchDocumentationAsset(env,
|
|
9597
|
+
async function fetchDocumentationAsset(env, request2, initialUrl, assetBasePath) {
|
|
9469
9598
|
let url = initialUrl;
|
|
9470
9599
|
for (let redirects = 0; redirects < 3; redirects++) {
|
|
9471
|
-
const response = await env.ASSETS.fetch(new Request(url,
|
|
9600
|
+
const response = await env.ASSETS.fetch(new Request(url, request2));
|
|
9472
9601
|
const location = response.headers.get("Location");
|
|
9473
9602
|
if (response.status < 300 || response.status >= 400 || !location) return response;
|
|
9474
9603
|
const next = new URL(location, url);
|
|
@@ -9508,11 +9637,11 @@ function secureDocumentationResponse(response, status, pathname) {
|
|
|
9508
9637
|
var MCP_SERVER_VERSION = "1.0.0";
|
|
9509
9638
|
var CURRENT_PROTOCOL_VERSION = "2025-11-25";
|
|
9510
9639
|
var SUPPORTED_PROTOCOL_VERSIONS = /* @__PURE__ */ new Set([CURRENT_PROTOCOL_VERSION, "2025-06-18", "2025-03-26"]);
|
|
9511
|
-
function initializeResponse(
|
|
9512
|
-
const params = objectValue(
|
|
9640
|
+
function initializeResponse(request2) {
|
|
9641
|
+
const params = objectValue(request2.params);
|
|
9513
9642
|
const requested = typeof params?.protocolVersion === "string" ? params.protocolVersion : CURRENT_PROTOCOL_VERSION;
|
|
9514
9643
|
const protocolVersion = SUPPORTED_PROTOCOL_VERSIONS.has(requested) ? requested : CURRENT_PROTOCOL_VERSION;
|
|
9515
|
-
return mcpResult(
|
|
9644
|
+
return mcpResult(request2.id, {
|
|
9516
9645
|
protocolVersion,
|
|
9517
9646
|
capabilities: {
|
|
9518
9647
|
tools: { listChanged: false },
|
|
@@ -9526,11 +9655,11 @@ function initializeResponse(request) {
|
|
|
9526
9655
|
instructions: "Search the published documentation before answering, then read exact pages when full context is needed."
|
|
9527
9656
|
});
|
|
9528
9657
|
}
|
|
9529
|
-
function hasValidOrigin(
|
|
9530
|
-
const origin =
|
|
9658
|
+
function hasValidOrigin(request2) {
|
|
9659
|
+
const origin = request2.headers.get("Origin");
|
|
9531
9660
|
if (!origin) return true;
|
|
9532
9661
|
try {
|
|
9533
|
-
return new URL(origin).origin === new URL(
|
|
9662
|
+
return new URL(origin).origin === new URL(request2.url).origin;
|
|
9534
9663
|
} catch {
|
|
9535
9664
|
return false;
|
|
9536
9665
|
}
|
|
@@ -9656,8 +9785,8 @@ var DOCUMENTATION_TOOLS = [
|
|
|
9656
9785
|
}
|
|
9657
9786
|
}
|
|
9658
9787
|
];
|
|
9659
|
-
async function callDocumentationTool(rawRequest,
|
|
9660
|
-
const params = objectValue(
|
|
9788
|
+
async function callDocumentationTool(rawRequest, request2, corpus, env, basePath = "/docs", assetBasePath = "/_documentation") {
|
|
9789
|
+
const params = objectValue(request2.params);
|
|
9661
9790
|
const name = typeof params?.name === "string" ? params.name : "";
|
|
9662
9791
|
const args = objectValue(params?.arguments) ?? {};
|
|
9663
9792
|
const origin = new URL(rawRequest.url).origin;
|
|
@@ -9665,13 +9794,13 @@ async function callDocumentationTool(rawRequest, request, corpus, env, basePath
|
|
|
9665
9794
|
const query = typeof args.query === "string" ? args.query.trim() : "";
|
|
9666
9795
|
const limit = args.limit === void 0 ? 6 : args.limit;
|
|
9667
9796
|
if (query.length < 2 || query.length > 300 || !Number.isInteger(limit) || Number(limit) < 1 || Number(limit) > 10) {
|
|
9668
|
-
return mcpError(
|
|
9797
|
+
return mcpError(request2.id, -32602, "Invalid documentation_search arguments");
|
|
9669
9798
|
}
|
|
9670
9799
|
const results = searchDocumentationCorpus(corpus, query, Number(limit)).map((result) => ({
|
|
9671
9800
|
...result,
|
|
9672
9801
|
url: new URL(documentationPublicPath(basePath, result.route), origin).toString()
|
|
9673
9802
|
}));
|
|
9674
|
-
return toolResult(
|
|
9803
|
+
return toolResult(request2.id, {
|
|
9675
9804
|
...results[0]?.termCoverage === 1 ? {} : {
|
|
9676
9805
|
notice: "No section matches every search term \u2014 the topic may be absent or spread across sections."
|
|
9677
9806
|
},
|
|
@@ -9681,7 +9810,7 @@ async function callDocumentationTool(rawRequest, request, corpus, env, basePath
|
|
|
9681
9810
|
if (name === "documentation_read") {
|
|
9682
9811
|
const route = typeof args.route === "string" ? args.route.trim() : "";
|
|
9683
9812
|
if (!route || route.length > 500) {
|
|
9684
|
-
return mcpError(
|
|
9813
|
+
return mcpError(request2.id, -32602, "Invalid documentation_read arguments");
|
|
9685
9814
|
}
|
|
9686
9815
|
const hash = route.indexOf("#");
|
|
9687
9816
|
const fragment = hash === -1 ? "" : route.slice(hash + 1);
|
|
@@ -9693,9 +9822,9 @@ async function callDocumentationTool(rawRequest, request, corpus, env, basePath
|
|
|
9693
9822
|
rawRequest.url,
|
|
9694
9823
|
assetBasePath
|
|
9695
9824
|
);
|
|
9696
|
-
if (!page) return toolError(
|
|
9825
|
+
if (!page) return toolError(request2.id, `No published documentation page exists at ${route}`);
|
|
9697
9826
|
const section = fragment ? documentationPageSection(page.markdown, fragment) : null;
|
|
9698
|
-
return toolResult(
|
|
9827
|
+
return toolResult(request2.id, {
|
|
9699
9828
|
...fragment && section === null ? { notice: `The fragment #${fragment} was not found; returning the whole page.` } : {},
|
|
9700
9829
|
...page,
|
|
9701
9830
|
...section === null ? {} : { markdown: section },
|
|
@@ -9703,10 +9832,10 @@ async function callDocumentationTool(rawRequest, request, corpus, env, basePath
|
|
|
9703
9832
|
});
|
|
9704
9833
|
} catch (error) {
|
|
9705
9834
|
console.error("[documentation-mcp] page unavailable:", error);
|
|
9706
|
-
return toolError(
|
|
9835
|
+
return toolError(request2.id, "The documentation page could not be read");
|
|
9707
9836
|
}
|
|
9708
9837
|
}
|
|
9709
|
-
return mcpError(
|
|
9838
|
+
return mcpError(request2.id, -32602, `Unknown documentation tool: ${name || "<missing>"}`);
|
|
9710
9839
|
}
|
|
9711
9840
|
|
|
9712
9841
|
// src/documentation/worker/mcp-routes.ts
|
|
@@ -9743,27 +9872,27 @@ function registerDocumentationMcpRoutes(app, publicBasePath = DOCUMENTATION_BASE
|
|
|
9743
9872
|
}
|
|
9744
9873
|
const parsed = parseRequest(raw);
|
|
9745
9874
|
if (!parsed.ok) return mcpError(null, -32700, parsed.message, 400);
|
|
9746
|
-
const
|
|
9747
|
-
if (
|
|
9875
|
+
const request2 = parsed.request;
|
|
9876
|
+
if (request2.method !== "initialize") {
|
|
9748
9877
|
const protocolVersion = c.req.header("MCP-Protocol-Version");
|
|
9749
9878
|
if (protocolVersion && !SUPPORTED_PROTOCOL_VERSIONS.has(protocolVersion)) {
|
|
9750
|
-
return mcpError(
|
|
9879
|
+
return mcpError(request2.id ?? null, -32600, "Unsupported MCP-Protocol-Version", 400);
|
|
9751
9880
|
}
|
|
9752
9881
|
}
|
|
9753
|
-
const requestId =
|
|
9882
|
+
const requestId = request2.id;
|
|
9754
9883
|
if (requestId === void 0) return new Response(null, { status: 202 });
|
|
9755
|
-
const requestWithId = { ...
|
|
9756
|
-
if (
|
|
9757
|
-
if (
|
|
9758
|
-
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") {
|
|
9759
9888
|
return mcpResult(requestId, { tools: DOCUMENTATION_TOOLS });
|
|
9760
9889
|
}
|
|
9761
|
-
if (
|
|
9890
|
+
if (request2.method === "resources/list") {
|
|
9762
9891
|
return mcpResult(requestId, {
|
|
9763
9892
|
resources: [skillResource(c.req.raw, manifest, publicBasePath)]
|
|
9764
9893
|
});
|
|
9765
9894
|
}
|
|
9766
|
-
if (
|
|
9895
|
+
if (request2.method === "resources/read") {
|
|
9767
9896
|
return readSkillResource(
|
|
9768
9897
|
c.req.raw,
|
|
9769
9898
|
requestWithId,
|
|
@@ -9773,8 +9902,8 @@ function registerDocumentationMcpRoutes(app, publicBasePath = DOCUMENTATION_BASE
|
|
|
9773
9902
|
assetBasePath
|
|
9774
9903
|
);
|
|
9775
9904
|
}
|
|
9776
|
-
if (
|
|
9777
|
-
return mcpError(requestId, -32601, `Method not found: ${
|
|
9905
|
+
if (request2.method !== "tools/call") {
|
|
9906
|
+
return mcpError(requestId, -32601, `Method not found: ${request2.method}`);
|
|
9778
9907
|
}
|
|
9779
9908
|
const clientKey = await documentationPublicClientKey(
|
|
9780
9909
|
c.req.header("CF-Connecting-IP") ?? "unknown"
|
|
@@ -9831,44 +9960,44 @@ function registerDocumentationMcpRoutes(app, publicBasePath = DOCUMENTATION_BASE
|
|
|
9831
9960
|
}
|
|
9832
9961
|
);
|
|
9833
9962
|
}
|
|
9834
|
-
async function readSkillResource(rawRequest,
|
|
9835
|
-
const params = objectValue(
|
|
9963
|
+
async function readSkillResource(rawRequest, request2, manifest, env, publicBasePath, assetBasePath) {
|
|
9964
|
+
const params = objectValue(request2.params);
|
|
9836
9965
|
const requestedUri = typeof params?.uri === "string" ? params.uri : "";
|
|
9837
9966
|
const resource = skillResource(rawRequest, manifest, publicBasePath);
|
|
9838
9967
|
if (requestedUri !== resource.uri) {
|
|
9839
|
-
return mcpError(
|
|
9968
|
+
return mcpError(request2.id, -32602, "Unknown documentation resource");
|
|
9840
9969
|
}
|
|
9841
9970
|
const assetUrl = new URL(`${assetBasePath}/skill.md`, rawRequest.url);
|
|
9842
9971
|
const response = await env.ASSETS.fetch(new Request(assetUrl, { method: "GET" }));
|
|
9843
|
-
if (!response.ok) return mcpError(
|
|
9972
|
+
if (!response.ok) return mcpError(request2.id, -32002, "Documentation skill is unavailable", 503);
|
|
9844
9973
|
const text = await response.text();
|
|
9845
9974
|
if (text.length > MAX_SKILL_CHARS) {
|
|
9846
|
-
return mcpError(
|
|
9975
|
+
return mcpError(request2.id, -32002, "Documentation skill exceeds the published size limit", 503);
|
|
9847
9976
|
}
|
|
9848
|
-
return mcpResult(
|
|
9977
|
+
return mcpResult(request2.id, {
|
|
9849
9978
|
contents: [{ uri: resource.uri, mimeType: resource.mimeType, text }]
|
|
9850
9979
|
});
|
|
9851
9980
|
}
|
|
9852
|
-
function skillResource(
|
|
9981
|
+
function skillResource(request2, manifest, publicBasePath) {
|
|
9853
9982
|
const subject = documentationSubject(manifest.name);
|
|
9854
9983
|
return {
|
|
9855
|
-
uri: new URL(`${publicBasePath}/skill.md`,
|
|
9984
|
+
uri: new URL(`${publicBasePath}/skill.md`, request2.url).toString(),
|
|
9856
9985
|
name: `${subject} skill`,
|
|
9857
9986
|
title: `Use ${manifest.name}`,
|
|
9858
9987
|
description: `Agent instructions grounded in the published ${subject}.`,
|
|
9859
9988
|
mimeType: "text/markdown"
|
|
9860
9989
|
};
|
|
9861
9990
|
}
|
|
9862
|
-
async function publicManifest(
|
|
9863
|
-
const manifest = await loadDocumentationPublishedManifest(env,
|
|
9991
|
+
async function publicManifest(request2, env, assetBasePath) {
|
|
9992
|
+
const manifest = await loadDocumentationPublishedManifest(env, request2.url, assetBasePath);
|
|
9864
9993
|
return manifest?.mcp.access === "public" ? manifest : null;
|
|
9865
9994
|
}
|
|
9866
|
-
function discoveryResponse(
|
|
9867
|
-
return jsonResponse(serverCard(
|
|
9995
|
+
function discoveryResponse(request2, manifest, publicBasePath) {
|
|
9996
|
+
return jsonResponse(serverCard(request2, manifest, publicBasePath), 200, {
|
|
9868
9997
|
"Cache-Control": "public, max-age=300"
|
|
9869
9998
|
});
|
|
9870
9999
|
}
|
|
9871
|
-
function serverCard(
|
|
10000
|
+
function serverCard(request2, manifest, publicBasePath) {
|
|
9872
10001
|
return {
|
|
9873
10002
|
name: manifest.name,
|
|
9874
10003
|
description: `Search and read the published ${documentationSubject(manifest.name)}.`,
|
|
@@ -9881,7 +10010,7 @@ function serverCard(request, manifest, publicBasePath) {
|
|
|
9881
10010
|
protocolVersions: [...SUPPORTED_PROTOCOL_VERSIONS],
|
|
9882
10011
|
transport: {
|
|
9883
10012
|
type: "streamable-http",
|
|
9884
|
-
url: new URL(`${publicBasePath}/mcp`,
|
|
10013
|
+
url: new URL(`${publicBasePath}/mcp`, request2.url).toString()
|
|
9885
10014
|
},
|
|
9886
10015
|
authentication: { required: false },
|
|
9887
10016
|
capabilities: {
|
|
@@ -9890,7 +10019,7 @@ function serverCard(request, manifest, publicBasePath) {
|
|
|
9890
10019
|
title,
|
|
9891
10020
|
description
|
|
9892
10021
|
})),
|
|
9893
|
-
resources: [skillResource(
|
|
10022
|
+
resources: [skillResource(request2, manifest, publicBasePath)]
|
|
9894
10023
|
}
|
|
9895
10024
|
};
|
|
9896
10025
|
}
|
|
@@ -10313,21 +10442,21 @@ function jsonResponse2(body, status) {
|
|
|
10313
10442
|
headers: { "Content-Type": "application/json" }
|
|
10314
10443
|
});
|
|
10315
10444
|
}
|
|
10316
|
-
async function handleClientErrorReport(
|
|
10317
|
-
const site =
|
|
10445
|
+
async function handleClientErrorReport(request2) {
|
|
10446
|
+
const site = request2.headers.get("sec-fetch-site");
|
|
10318
10447
|
if (site && site !== "same-origin" && site !== "none") {
|
|
10319
10448
|
return new Response(null, { status: 403 });
|
|
10320
10449
|
}
|
|
10321
10450
|
if (shouldThrottleClientError()) {
|
|
10322
10451
|
return new Response(null, { status: 429, headers: { "Retry-After": "1" } });
|
|
10323
10452
|
}
|
|
10324
|
-
const declared = Number(
|
|
10453
|
+
const declared = Number(request2.headers.get("content-length"));
|
|
10325
10454
|
if (Number.isFinite(declared) && declared > MAX_BODY_BYTES) {
|
|
10326
10455
|
return jsonResponse2({ error: "payload_too_large" }, 413);
|
|
10327
10456
|
}
|
|
10328
10457
|
let body;
|
|
10329
10458
|
try {
|
|
10330
|
-
body = await
|
|
10459
|
+
body = await request2.json();
|
|
10331
10460
|
} catch {
|
|
10332
10461
|
return jsonResponse2({ error: "invalid_json" }, 400);
|
|
10333
10462
|
}
|
|
@@ -10355,6 +10484,8 @@ var APP_LOG_EVENT_KEYS = [
|
|
|
10355
10484
|
];
|
|
10356
10485
|
export {
|
|
10357
10486
|
ACCOUNT_STORAGE_LIMIT_BYTES,
|
|
10487
|
+
ACTION_ROUTES_BEARER_GUARD_MIGRATION_ID,
|
|
10488
|
+
ACTION_TOOLS_DELETE_WHERE_MIGRATION_ID,
|
|
10358
10489
|
AI_CHATS_SCHEMA,
|
|
10359
10490
|
AI_MESSAGES_SCHEMA,
|
|
10360
10491
|
ALLOWED_BINDING_TYPES,
|
|
@@ -10368,6 +10499,7 @@ export {
|
|
|
10368
10499
|
Awareness,
|
|
10369
10500
|
BASE_USERS_SCHEMA,
|
|
10370
10501
|
BROWSER_PROXY_ROUTES,
|
|
10502
|
+
BUILD_INJECTED_APP_ID_MIGRATION_ID,
|
|
10371
10503
|
BUILT_IN_TOOLS,
|
|
10372
10504
|
BaseRoom,
|
|
10373
10505
|
CHANNELS_SCHEMA,
|
|
@@ -10393,6 +10525,7 @@ export {
|
|
|
10393
10525
|
GLOBAL_DO_TYPES,
|
|
10394
10526
|
GLOBAL_DO_TYPE_NAMES,
|
|
10395
10527
|
JobRoom,
|
|
10528
|
+
KnowledgeError,
|
|
10396
10529
|
LOG_LEVELS,
|
|
10397
10530
|
MAX_APP_FILE_BYTES,
|
|
10398
10531
|
MAX_DELETE_WHERE_LIMIT,
|
|
@@ -10439,13 +10572,13 @@ export {
|
|
|
10439
10572
|
USERS_COLUMNS,
|
|
10440
10573
|
VOTING_SCHEMAS,
|
|
10441
10574
|
WORKER_OWNED_NOT_FOUND_MIGRATION_ID,
|
|
10442
|
-
WORKSPACE_SCHEMAS,
|
|
10443
10575
|
YjsRoom,
|
|
10444
10576
|
analyzePermissions,
|
|
10445
10577
|
apiWorkerFetch,
|
|
10446
10578
|
appendMessage,
|
|
10447
10579
|
applyAiToolDefaults,
|
|
10448
10580
|
applySlidingWindow,
|
|
10581
|
+
armCronRoom,
|
|
10449
10582
|
authWorkerFetch,
|
|
10450
10583
|
authenticatedRoomRequest,
|
|
10451
10584
|
bindingManifestFromOutputConfig,
|
|
@@ -10485,6 +10618,8 @@ export {
|
|
|
10485
10618
|
encodeSyncStep2,
|
|
10486
10619
|
encodeUpdate,
|
|
10487
10620
|
enqueueJob,
|
|
10621
|
+
epochSeconds,
|
|
10622
|
+
folderFilter,
|
|
10488
10623
|
formatBytes,
|
|
10489
10624
|
getChat,
|
|
10490
10625
|
getDeepSpaceAIModel,
|
|
@@ -10505,6 +10640,7 @@ export {
|
|
|
10505
10640
|
isTestAccountClaims,
|
|
10506
10641
|
isTestAccountEmail,
|
|
10507
10642
|
isTestAccountTier,
|
|
10643
|
+
knowledge,
|
|
10508
10644
|
lintSchema,
|
|
10509
10645
|
lintSchemaSet,
|
|
10510
10646
|
lintSchemas,
|
|
@@ -10518,6 +10654,7 @@ export {
|
|
|
10518
10654
|
mintAppId,
|
|
10519
10655
|
mintUlid,
|
|
10520
10656
|
noopPermissionContext,
|
|
10657
|
+
normalizeKnowledgeFolder,
|
|
10521
10658
|
parseQuotaLimit,
|
|
10522
10659
|
platformCorsPatterns,
|
|
10523
10660
|
platformWorkerFetch,
|
|
@@ -10553,17 +10690,6 @@ export {
|
|
|
10553
10690
|
validateBindingManifest,
|
|
10554
10691
|
validateDoManifest,
|
|
10555
10692
|
verifyJwt,
|
|
10556
|
-
workspaceAccountsSchema,
|
|
10557
|
-
workspaceContentSharesSchema,
|
|
10558
|
-
workspaceEmailHandlesSchema,
|
|
10559
|
-
workspaceFormResponsesSchema,
|
|
10560
|
-
workspacePeopleSchema,
|
|
10561
|
-
workspaceProjectsSchema,
|
|
10562
|
-
workspaceTagsSchema,
|
|
10563
|
-
workspaceTasksSchema,
|
|
10564
|
-
workspaceTeamMembersSchema,
|
|
10565
|
-
workspaceTeamsSchema,
|
|
10566
|
-
workspaceTransactionsSchema,
|
|
10567
10693
|
writeVarUint,
|
|
10568
10694
|
writeVarUint8Array
|
|
10569
10695
|
};
|