deepspace 0.23.1 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/README.md +6 -2
- package/dist/build.js +11 -0
- package/dist/build.js.map +1 -1
- package/dist/{chunk-UUCAFDYW.js → chunk-2U2VG6L5.js} +2 -2
- package/dist/{chunk-UUCAFDYW.js.map → chunk-2U2VG6L5.js.map} +1 -1
- package/dist/chunk-OFDOJGFS.js +1957 -0
- package/dist/chunk-OFDOJGFS.js.map +1 -0
- package/dist/cli.js +1714 -1797
- package/dist/cli.js.map +1 -1
- package/dist/documentation-client-core.js +1 -1
- package/dist/documentation-default-renderer.cjs +1 -1
- package/dist/documentation-react.js +1 -1
- package/dist/documentation-runtime.js +1 -1
- package/dist/documentation-server-core.js +1 -1
- package/dist/documentation.js +1 -1
- package/dist/documentation.js.map +1 -1
- package/dist/index.d.ts +30 -4
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/dist/schema.d.ts +3 -19
- package/dist/schema.js +1 -207
- package/dist/schema.js.map +1 -1
- package/dist/server.d.ts +153 -48
- package/dist/server.js +516 -380
- package/dist/server.js.map +1 -1
- package/dist/worker.d.ts +178 -48
- package/dist/worker.js +576 -434
- package/dist/worker.js.map +1 -1
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -111,7 +111,7 @@ var BUILT_IN_TOOLS = [
|
|
|
111
111
|
description: `Query records from a collection with optional filtering, ordering, and pagination. Returns at most \`limit\` records (default ${DEFAULT_QUERY_LIMIT}). Large result sets are capped at ~30KB: an oversized page is truncated to the leading records that fit and flagged with \`{ truncated, returned, total }\`, so narrow with a \`where\` filter (or page via \`orderBy\` + \`limit\`) to see the rest.`,
|
|
112
112
|
params: {
|
|
113
113
|
collection: { type: "string", description: "Collection name to query", required: true },
|
|
114
|
-
where: { type: "object", description: "Filter object with field=value equality conditions", required: false },
|
|
114
|
+
where: { type: "object", description: "Filter object with field=value equality conditions; every key must be recordId, createdBy, or a schema column (an unknown key is refused, not ignored)", required: false },
|
|
115
115
|
orderBy: { type: "string", description: 'Field to order by (or "createdAt"/"updatedAt")', required: false },
|
|
116
116
|
orderDir: { type: "string", description: 'Order direction: "asc" or "desc" (default: "desc")', required: false },
|
|
117
117
|
limit: { type: "number", description: `Maximum number of records to return (default: ${DEFAULT_QUERY_LIMIT}). Oversized pages are still capped at ~30KB and truncated with returned/total flags.`, required: false }
|
|
@@ -181,7 +181,7 @@ var BUILT_IN_TOOLS = [
|
|
|
181
181
|
},
|
|
182
182
|
{
|
|
183
183
|
name: "user.list",
|
|
184
|
-
description: "List
|
|
184
|
+
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.",
|
|
185
185
|
params: {}
|
|
186
186
|
},
|
|
187
187
|
// Yjs tools (collaborative document access)
|
|
@@ -762,8 +762,8 @@ function overPartBound(bytes) {
|
|
|
762
762
|
if (bytes <= UPLOAD_PART_BYTES) return null;
|
|
763
763
|
return fail2(413, oversizePartMessage(bytes), "too_large", { maxBytes: UPLOAD_PART_BYTES });
|
|
764
764
|
}
|
|
765
|
-
function declaredLength(
|
|
766
|
-
const raw =
|
|
765
|
+
function declaredLength(request2, what) {
|
|
766
|
+
const raw = request2.headers.get("content-length");
|
|
767
767
|
const declared = raw === null ? NaN : Number(raw);
|
|
768
768
|
if (!Number.isSafeInteger(declared) || declared < 0) {
|
|
769
769
|
return fail2(
|
|
@@ -841,7 +841,7 @@ function multipartFailure(error) {
|
|
|
841
841
|
}
|
|
842
842
|
function createScopedR2Handler(config) {
|
|
843
843
|
const requireAuthForMutations = config.requireAuthForMutations ?? true;
|
|
844
|
-
return async (
|
|
844
|
+
return async (request2, url, bucket, auth) => {
|
|
845
845
|
let subpathRaw;
|
|
846
846
|
try {
|
|
847
847
|
subpathRaw = decodeURIComponent(url.pathname.replace("/api/files", "").replace(/^\//, ""));
|
|
@@ -870,26 +870,26 @@ function createScopedR2Handler(config) {
|
|
|
870
870
|
}
|
|
871
871
|
const prefix = result.prefix;
|
|
872
872
|
const excludedPrefixes = result.excludedPrefixes ?? [];
|
|
873
|
-
if (isFilesVerbPath(
|
|
873
|
+
if (isFilesVerbPath(request2.method, subpath)) {
|
|
874
874
|
if (requireAuthForMutations && !auth.userId) {
|
|
875
|
-
await
|
|
875
|
+
await request2.body?.cancel().catch(() => void 0);
|
|
876
876
|
return fail2(401, "Authentication required", "unauthorized");
|
|
877
877
|
}
|
|
878
|
-
return subpath === "upload" ? handleUpload(
|
|
878
|
+
return subpath === "upload" ? handleUpload(request2, url, bucket, prefix, excludedPrefixes, auth, scope) : handleMultipart(request2, url, bucket, prefix, excludedPrefixes, auth, scope, subpath);
|
|
879
879
|
}
|
|
880
|
-
if (!subpath &&
|
|
880
|
+
if (!subpath && request2.method === "GET") {
|
|
881
881
|
return handleList(bucket, prefix, excludedPrefixes, url, scope, auth);
|
|
882
882
|
}
|
|
883
|
-
if (subpath &&
|
|
883
|
+
if (subpath && request2.method === "GET") {
|
|
884
884
|
if (!isKeyWithinScope(subpath, prefix, excludedPrefixes)) {
|
|
885
885
|
return Response.json(
|
|
886
886
|
{ error: "Access denied: key outside scope" },
|
|
887
887
|
{ status: 403, headers: CORS_HEADERS2 }
|
|
888
888
|
);
|
|
889
889
|
}
|
|
890
|
-
return handleDownload(
|
|
890
|
+
return handleDownload(request2, bucket, subpath, scope);
|
|
891
891
|
}
|
|
892
|
-
if (subpath &&
|
|
892
|
+
if (subpath && request2.method === "DELETE") {
|
|
893
893
|
if (requireAuthForMutations && !auth.userId) {
|
|
894
894
|
return Response.json(
|
|
895
895
|
{ error: "Authentication required" },
|
|
@@ -915,18 +915,18 @@ function createScopedR2Handler(config) {
|
|
|
915
915
|
return Response.json({ error: "Method not allowed" }, { status: 405, headers: CORS_HEADERS2 });
|
|
916
916
|
};
|
|
917
917
|
}
|
|
918
|
-
async function handleUpload(
|
|
918
|
+
async function handleUpload(request2, url, bucket, prefix, excludedPrefixes, auth, scope) {
|
|
919
919
|
try {
|
|
920
|
-
const declaredBytes = declaredLength(
|
|
920
|
+
const declaredBytes = declaredLength(request2, "File uploads");
|
|
921
921
|
if (declaredBytes instanceof Response) return declaredBytes;
|
|
922
922
|
const tooLargeRequest = overRequestBound(declaredBytes, 8 * 1024);
|
|
923
923
|
if (tooLargeRequest) return tooLargeRequest;
|
|
924
|
-
const contentType =
|
|
924
|
+
const contentType = request2.headers.get("content-type") || "";
|
|
925
925
|
let fileData;
|
|
926
926
|
let fileName;
|
|
927
927
|
let rawMimeType;
|
|
928
928
|
if (contentType.includes("multipart/form-data")) {
|
|
929
|
-
const formData = await
|
|
929
|
+
const formData = await request2.formData();
|
|
930
930
|
const file = formData.get("file");
|
|
931
931
|
if (!file) return fail2(400, "No file provided", "bad_request");
|
|
932
932
|
const tooBig = overRequestBound(file.size);
|
|
@@ -935,7 +935,7 @@ async function handleUpload(request, url, bucket, prefix, excludedPrefixes, auth
|
|
|
935
935
|
fileName = formData.get("name")?.toString() || file.name;
|
|
936
936
|
rawMimeType = file.type || "application/octet-stream";
|
|
937
937
|
} else {
|
|
938
|
-
const body = await
|
|
938
|
+
const body = await request2.json();
|
|
939
939
|
if (!body.data || !body.name) return fail2(400, "Missing data or name", "bad_request");
|
|
940
940
|
const base64Data = body.data.replace(/^data:[^;]+;base64,/, "");
|
|
941
941
|
const tooBig = overRequestBound(Math.floor(base64Data.length * 3 / 4));
|
|
@@ -1016,15 +1016,15 @@ function storedFileJson(key, prefix, name, url, scope) {
|
|
|
1016
1016
|
);
|
|
1017
1017
|
}
|
|
1018
1018
|
var MAX_CONTROL_BODY_BYTES = 64 * 1024;
|
|
1019
|
-
async function readControlJson(
|
|
1020
|
-
const declared = declaredLength(
|
|
1019
|
+
async function readControlJson(request2) {
|
|
1020
|
+
const declared = declaredLength(request2, "This request");
|
|
1021
1021
|
if (declared instanceof Response) return declared;
|
|
1022
1022
|
if (declared > MAX_CONTROL_BODY_BYTES) {
|
|
1023
1023
|
return fail2(413, `Request body exceeds ${MAX_CONTROL_BODY_BYTES} bytes`, "too_large");
|
|
1024
1024
|
}
|
|
1025
1025
|
let parsed;
|
|
1026
1026
|
try {
|
|
1027
|
-
parsed = await
|
|
1027
|
+
parsed = await request2.json();
|
|
1028
1028
|
} catch {
|
|
1029
1029
|
return fail2(400, "Body must be JSON", "bad_request");
|
|
1030
1030
|
}
|
|
@@ -1076,7 +1076,7 @@ function multipartLayoutError(declaredBytes) {
|
|
|
1076
1076
|
{ partSize: UPLOAD_PART_BYTES, partCount: multipartPartCount(declaredBytes) }
|
|
1077
1077
|
);
|
|
1078
1078
|
}
|
|
1079
|
-
async function streamPart(
|
|
1079
|
+
async function streamPart(upload2, partNumber, body, expectedBytes) {
|
|
1080
1080
|
let observedBytes = 0;
|
|
1081
1081
|
const sized = new FixedLengthStream(expectedBytes);
|
|
1082
1082
|
const pumped = body.pipeThrough(
|
|
@@ -1088,7 +1088,7 @@ async function streamPart(upload, partNumber, body, expectedBytes) {
|
|
|
1088
1088
|
})
|
|
1089
1089
|
).pipeTo(sized.writable);
|
|
1090
1090
|
const [uploaded, drained] = await Promise.allSettled([
|
|
1091
|
-
|
|
1091
|
+
upload2.uploadPart(partNumber, sized.readable),
|
|
1092
1092
|
pumped
|
|
1093
1093
|
]);
|
|
1094
1094
|
if (uploaded.status === "rejected" || drained.status === "rejected") {
|
|
@@ -1097,20 +1097,20 @@ async function streamPart(upload, partNumber, body, expectedBytes) {
|
|
|
1097
1097
|
}
|
|
1098
1098
|
return { ok: true, part: uploaded.value };
|
|
1099
1099
|
}
|
|
1100
|
-
async function handleMultipart(
|
|
1101
|
-
if (subpath === "multipart" &&
|
|
1102
|
-
return multipartInit(
|
|
1100
|
+
async function handleMultipart(request2, url, bucket, prefix, excludedPrefixes, auth, scope, subpath) {
|
|
1101
|
+
if (subpath === "multipart" && request2.method === "POST") {
|
|
1102
|
+
return multipartInit(request2, url, bucket, prefix, excludedPrefixes, auth);
|
|
1103
1103
|
}
|
|
1104
1104
|
if (subpath === "multipart/part") {
|
|
1105
|
-
return multipartPart(
|
|
1105
|
+
return multipartPart(request2, url, bucket, prefix, excludedPrefixes, auth);
|
|
1106
1106
|
}
|
|
1107
1107
|
if (subpath === "multipart/complete") {
|
|
1108
|
-
return multipartComplete(
|
|
1108
|
+
return multipartComplete(request2, url, bucket, prefix, excludedPrefixes, auth, scope);
|
|
1109
1109
|
}
|
|
1110
1110
|
return multipartAbort(url, bucket, prefix, excludedPrefixes, auth);
|
|
1111
1111
|
}
|
|
1112
|
-
async function multipartInit(
|
|
1113
|
-
const body = await readControlJson(
|
|
1112
|
+
async function multipartInit(request2, url, bucket, prefix, excludedPrefixes, auth) {
|
|
1113
|
+
const body = await readControlJson(request2);
|
|
1114
1114
|
if (body instanceof Response) return body;
|
|
1115
1115
|
const size = body.size;
|
|
1116
1116
|
if (!Number.isSafeInteger(size) || size <= 0) {
|
|
@@ -1151,9 +1151,9 @@ async function multipartInit(request, url, bucket, prefix, excludedPrefixes, aut
|
|
|
1151
1151
|
"request_id_conflict"
|
|
1152
1152
|
);
|
|
1153
1153
|
}
|
|
1154
|
-
let
|
|
1154
|
+
let upload2;
|
|
1155
1155
|
try {
|
|
1156
|
-
|
|
1156
|
+
upload2 = await bucket.createMultipartUpload(located.key, {
|
|
1157
1157
|
httpMetadata: { contentType: mimeType },
|
|
1158
1158
|
customMetadata: fileMetadata(name, auth.userId, requestId, size)
|
|
1159
1159
|
});
|
|
@@ -1165,7 +1165,7 @@ async function multipartInit(request, url, bucket, prefix, excludedPrefixes, aut
|
|
|
1165
1165
|
body: JSON.stringify({
|
|
1166
1166
|
version: 1,
|
|
1167
1167
|
reservationId: requestId,
|
|
1168
|
-
uploadId:
|
|
1168
|
+
uploadId: upload2.uploadId,
|
|
1169
1169
|
objectKey: located.key,
|
|
1170
1170
|
uploadKey: located.subkey,
|
|
1171
1171
|
admitted: false,
|
|
@@ -1179,7 +1179,7 @@ async function multipartInit(request, url, bucket, prefix, excludedPrefixes, aut
|
|
|
1179
1179
|
const reservedBytes = Math.max(size, 5 * 1024 * 1024);
|
|
1180
1180
|
const admission = await admitStorage(bucket, auth.storage, reservedBytes, 0, marker);
|
|
1181
1181
|
if (admission instanceof Response) {
|
|
1182
|
-
await
|
|
1182
|
+
await upload2.abort().catch(() => void 0);
|
|
1183
1183
|
if (admission.status === 409 && auth.storage) {
|
|
1184
1184
|
const winner = await readMultipartReservation(bucket, auth.storage, requestId);
|
|
1185
1185
|
if (winner && winner !== "invalid" && winner.objectKey === located.key && winner.uploadKey === located.subkey && winner.declaredBytes === size && winner.admitted) {
|
|
@@ -1189,11 +1189,11 @@ async function multipartInit(request, url, bucket, prefix, excludedPrefixes, aut
|
|
|
1189
1189
|
return admission;
|
|
1190
1190
|
}
|
|
1191
1191
|
if (auth.storage && !await activateMultipartReservation(bucket, auth.storage, requestId)) {
|
|
1192
|
-
await
|
|
1192
|
+
await upload2.abort().catch(() => void 0);
|
|
1193
1193
|
await releaseQuotaMarker(bucket, auth.storage, admission.markerKey, true);
|
|
1194
1194
|
return quotaUnavailable("This upload reservation could not be finalized. Retry shortly.");
|
|
1195
1195
|
}
|
|
1196
|
-
return multipartSessionJson(
|
|
1196
|
+
return multipartSessionJson(upload2.uploadId, located.subkey, requestId);
|
|
1197
1197
|
}
|
|
1198
1198
|
async function abandonMultipart(bucket, session, auth, reservation) {
|
|
1199
1199
|
let result = "aborted";
|
|
@@ -1211,12 +1211,12 @@ async function abandonMultipart(bucket, session, auth, reservation) {
|
|
|
1211
1211
|
}
|
|
1212
1212
|
return result;
|
|
1213
1213
|
}
|
|
1214
|
-
async function multipartPart(
|
|
1214
|
+
async function multipartPart(request2, url, bucket, prefix, excludedPrefixes, auth) {
|
|
1215
1215
|
const refuse = async (response) => {
|
|
1216
|
-
await
|
|
1216
|
+
await request2.body?.cancel().catch(() => void 0);
|
|
1217
1217
|
return response;
|
|
1218
1218
|
};
|
|
1219
|
-
const declaredBytes = declaredLength(
|
|
1219
|
+
const declaredBytes = declaredLength(request2, "Part uploads");
|
|
1220
1220
|
if (declaredBytes instanceof Response) return refuse(declaredBytes);
|
|
1221
1221
|
const tooBig = overPartBound(declaredBytes);
|
|
1222
1222
|
if (tooBig) return refuse(tooBig);
|
|
@@ -1228,7 +1228,7 @@ async function multipartPart(request, url, bucket, prefix, excludedPrefixes, aut
|
|
|
1228
1228
|
}
|
|
1229
1229
|
const session = resolveSession(url, prefix, excludedPrefixes);
|
|
1230
1230
|
if (session instanceof Response) return refuse(session);
|
|
1231
|
-
if (!
|
|
1231
|
+
if (!request2.body) return fail2(400, "A part body is required", "bad_request");
|
|
1232
1232
|
const reservation = await readMultipartReservation(bucket, auth.storage, session.reservationId);
|
|
1233
1233
|
if (reservation === "invalid") {
|
|
1234
1234
|
return refuse(quotaUnavailable("This upload reservation could not be read. Retry shortly."));
|
|
@@ -1270,8 +1270,8 @@ async function multipartPart(request, url, bucket, prefix, excludedPrefixes, aut
|
|
|
1270
1270
|
if (refreshed === "busy") {
|
|
1271
1271
|
return refuse(quotaUnavailable("This upload reservation is busy. Retry the part shortly."));
|
|
1272
1272
|
}
|
|
1273
|
-
const
|
|
1274
|
-
const stored = await streamPart(
|
|
1273
|
+
const upload2 = bucket.resumeMultipartUpload(session.key, session.uploadId);
|
|
1274
|
+
const stored = await streamPart(upload2, partNumber, request2.body, declaredBytes);
|
|
1275
1275
|
if (!stored.ok) {
|
|
1276
1276
|
if (r2ErrorCode(stored.error) === 10024 && reservation) {
|
|
1277
1277
|
await releaseQuotaMarker(bucket, auth.storage, reservation.key, true);
|
|
@@ -1327,8 +1327,8 @@ async function finishCompletedMultipart(bucket, session, reservation, object, au
|
|
|
1327
1327
|
const name = object.customMetadata?.originalName ?? session.uploadKey.split("/").pop() ?? "file";
|
|
1328
1328
|
return storedFileJson(session.key, prefix, name, url, scope);
|
|
1329
1329
|
}
|
|
1330
|
-
async function multipartComplete(
|
|
1331
|
-
const body = await readControlJson(
|
|
1330
|
+
async function multipartComplete(request2, url, bucket, prefix, excludedPrefixes, auth, scope) {
|
|
1331
|
+
const body = await readControlJson(request2);
|
|
1332
1332
|
if (body instanceof Response) return body;
|
|
1333
1333
|
const session = resolveSession(url, prefix, excludedPrefixes);
|
|
1334
1334
|
if (session instanceof Response) return session;
|
|
@@ -1487,7 +1487,7 @@ async function handleList(bucket, prefix, excludedPrefixes, url, scope, auth) {
|
|
|
1487
1487
|
{ headers: CORS_HEADERS2 }
|
|
1488
1488
|
);
|
|
1489
1489
|
}
|
|
1490
|
-
async function handleDownload(
|
|
1490
|
+
async function handleDownload(request2, bucket, key, scope) {
|
|
1491
1491
|
const object = await bucket.get(key);
|
|
1492
1492
|
if (!object) {
|
|
1493
1493
|
return Response.json({ error: "File not found" }, { status: 404, headers: CORS_HEADERS2 });
|
|
@@ -1509,7 +1509,7 @@ async function handleDownload(request, bucket, key, scope) {
|
|
|
1509
1509
|
contentDisposition(originalName, mustDownload ? "attachment" : "inline")
|
|
1510
1510
|
);
|
|
1511
1511
|
if (mustDownload) headers.set("Content-Security-Policy", "sandbox; default-src 'none'");
|
|
1512
|
-
if (scope === "app" &&
|
|
1512
|
+
if (scope === "app" && request2.headers.get("if-none-match") === object.httpEtag) {
|
|
1513
1513
|
return new Response(null, { status: 304, headers });
|
|
1514
1514
|
}
|
|
1515
1515
|
return new Response(object.body, { headers });
|
|
@@ -2075,8 +2075,8 @@ async function composioTools(env, options) {
|
|
|
2075
2075
|
if (!list.ok || !list.success) {
|
|
2076
2076
|
throw new Error(`composioTools: failed to list Composio tools: ${list.detail}`);
|
|
2077
2077
|
}
|
|
2078
|
-
const
|
|
2079
|
-
const items = Array.isArray(
|
|
2078
|
+
const listItems2 = list.data?.items;
|
|
2079
|
+
const items = Array.isArray(listItems2) ? listItems2 : [];
|
|
2080
2080
|
const tools = {};
|
|
2081
2081
|
for (const item of items) {
|
|
2082
2082
|
if (!item?.slug) continue;
|
|
@@ -2739,18 +2739,272 @@ var COST_RATES = {
|
|
|
2739
2739
|
queriedPerDim: VECTORIZE_QUERIED_USD_PER_M_DIMS / 1e6,
|
|
2740
2740
|
/** USD per stored dimension per month. */
|
|
2741
2741
|
storedPerDimPerMonth: VECTORIZE_STORED_USD_PER_100M_DIMS / 1e8
|
|
2742
|
+
},
|
|
2743
|
+
aiSearch: {
|
|
2744
|
+
ingestPerToken: 0.75 / 1e6,
|
|
2745
|
+
ingestImagePerToken: 0.5 / 1e6,
|
|
2746
|
+
storagePerByteMonth: 2 / 1e9,
|
|
2747
|
+
hybridOrSemanticPerQuery: 0.75 / 1e3,
|
|
2748
|
+
fulltextPerQuery: 0.1 / 1e3
|
|
2742
2749
|
}
|
|
2743
2750
|
};
|
|
2744
2751
|
function priceBindingUsageEvent(kind, op, units) {
|
|
2752
|
+
if (!Number.isFinite(units) || units < 0) return 0;
|
|
2745
2753
|
if (kind === "ai" && op === "input") {
|
|
2746
2754
|
return units * COST_RATES.ai.embedInputPerChar;
|
|
2747
2755
|
}
|
|
2748
2756
|
if (kind === "vectorize" && op === "query") {
|
|
2749
2757
|
return units * COST_RATES.vectorize.queriedPerDim;
|
|
2750
2758
|
}
|
|
2759
|
+
if (kind === "ai_search") {
|
|
2760
|
+
if (op === "ingest") return units * COST_RATES.aiSearch.ingestPerToken;
|
|
2761
|
+
if (op === "ingest-image") return units * COST_RATES.aiSearch.ingestImagePerToken;
|
|
2762
|
+
if (op === "storage") return units * COST_RATES.aiSearch.storagePerByteMonth;
|
|
2763
|
+
if (op === "search-hybrid" || op === "search-semantic")
|
|
2764
|
+
return units * COST_RATES.aiSearch.hybridOrSemanticPerQuery;
|
|
2765
|
+
if (op === "search-fulltext") return units * COST_RATES.aiSearch.fulltextPerQuery;
|
|
2766
|
+
}
|
|
2751
2767
|
return 0;
|
|
2752
2768
|
}
|
|
2753
2769
|
|
|
2770
|
+
// src/server/utils/knowledge-base.ts
|
|
2771
|
+
var MAX_UPLOAD_BYTES = 4 * 1024 * 1024;
|
|
2772
|
+
var TEXT_MIME = /^(text\/|application\/(json|xml|javascript|x-(javascript|typescript)))/iu;
|
|
2773
|
+
var MAX_QUERY_CHARS = 4096;
|
|
2774
|
+
function hasControlChar(value) {
|
|
2775
|
+
for (let i = 0; i < value.length; i++) {
|
|
2776
|
+
const code = value.charCodeAt(i);
|
|
2777
|
+
if (code < 32 || code === 127) return true;
|
|
2778
|
+
}
|
|
2779
|
+
return false;
|
|
2780
|
+
}
|
|
2781
|
+
var KnowledgeError = class extends Error {
|
|
2782
|
+
constructor(status, code, message, uploadedItems) {
|
|
2783
|
+
super(message);
|
|
2784
|
+
this.status = status;
|
|
2785
|
+
this.code = code;
|
|
2786
|
+
this.uploadedItems = uploadedItems;
|
|
2787
|
+
this.name = "KnowledgeError";
|
|
2788
|
+
}
|
|
2789
|
+
};
|
|
2790
|
+
function normalizeKnowledgeFolder(folder) {
|
|
2791
|
+
if (!folder || folder.includes("\\") || hasControlChar(folder) || folder.startsWith("/"))
|
|
2792
|
+
throw new KnowledgeError(400, "invalid_folder", "Folder must be a relative forward-slash path");
|
|
2793
|
+
const withoutTrailingSlash = folder.endsWith("/") ? folder.slice(0, -1) : folder;
|
|
2794
|
+
if (!withoutTrailingSlash || withoutTrailingSlash.endsWith("/"))
|
|
2795
|
+
throw new KnowledgeError(400, "invalid_folder", "Folder cannot contain empty segments");
|
|
2796
|
+
const parts = withoutTrailingSlash.split("/");
|
|
2797
|
+
if (parts.some((part) => !part || part === "." || part === ".."))
|
|
2798
|
+
throw new KnowledgeError(
|
|
2799
|
+
400,
|
|
2800
|
+
"invalid_folder",
|
|
2801
|
+
"Folder cannot contain empty, dot, or parent segments"
|
|
2802
|
+
);
|
|
2803
|
+
const normalized = `${parts.join("/")}/`;
|
|
2804
|
+
if (normalized.length > 128)
|
|
2805
|
+
throw new KnowledgeError(400, "invalid_folder", "Folder is too long for an AI Search item key");
|
|
2806
|
+
return normalized;
|
|
2807
|
+
}
|
|
2808
|
+
function validateKnowledgeFilename(name) {
|
|
2809
|
+
if (!name || name === "." || name === ".." || name.includes("/") || name.includes("\\") || hasControlChar(name)) {
|
|
2810
|
+
throw new KnowledgeError(
|
|
2811
|
+
400,
|
|
2812
|
+
"invalid_filename",
|
|
2813
|
+
"File name must be a non-empty base name without path separators"
|
|
2814
|
+
);
|
|
2815
|
+
}
|
|
2816
|
+
}
|
|
2817
|
+
function folderFilter(folder) {
|
|
2818
|
+
const value = normalizeKnowledgeFolder(folder);
|
|
2819
|
+
return { folder: { $gte: value, $lt: `${value.slice(0, -1)}0` } };
|
|
2820
|
+
}
|
|
2821
|
+
function knowledge(env) {
|
|
2822
|
+
const add = (file, options = {}) => addFile(env, file, options);
|
|
2823
|
+
const list = (options = {}) => listItems(env, options);
|
|
2824
|
+
const search = (query, options = {}) => searchItems(env, query, options);
|
|
2825
|
+
return {
|
|
2826
|
+
add,
|
|
2827
|
+
list,
|
|
2828
|
+
search,
|
|
2829
|
+
async remove(itemId) {
|
|
2830
|
+
if (!/^[A-Za-z0-9_-]{1,256}$/u.test(itemId))
|
|
2831
|
+
throw new KnowledgeError(400, "invalid_item_id", "Invalid knowledge item id");
|
|
2832
|
+
await request(env, `/api/knowledge/items/${encodeURIComponent(itemId)}`, { method: "DELETE" });
|
|
2833
|
+
},
|
|
2834
|
+
scoped({ folder }) {
|
|
2835
|
+
const fixed = normalizeKnowledgeFolder(folder);
|
|
2836
|
+
return {
|
|
2837
|
+
add: (file) => add(file, { folder: fixed }),
|
|
2838
|
+
list: (options = {}) => list({ ...options, folder: fixed }),
|
|
2839
|
+
search: (query, options = {}) => search(query, { ...options, folder: fixed })
|
|
2840
|
+
};
|
|
2841
|
+
}
|
|
2842
|
+
};
|
|
2843
|
+
}
|
|
2844
|
+
async function addFile(env, file, options) {
|
|
2845
|
+
validateKnowledgeFilename(file.name);
|
|
2846
|
+
const folder = options.folder === void 0 ? void 0 : normalizeKnowledgeFolder(options.folder);
|
|
2847
|
+
if ((folder?.length ?? 0) + file.name.length > 128)
|
|
2848
|
+
throw new KnowledgeError(
|
|
2849
|
+
400,
|
|
2850
|
+
"invalid_key",
|
|
2851
|
+
"Folder and filename together exceed the 128-character AI Search key limit"
|
|
2852
|
+
);
|
|
2853
|
+
if (file.size <= MAX_UPLOAD_BYTES) return upload(env, file, folder);
|
|
2854
|
+
if (!TEXT_MIME.test(file.type) && !/\.(txt|md|mdx|csv|json|xml|html?|ya?ml|tsx?|jsx?|css)$/iu.test(file.name))
|
|
2855
|
+
throw new KnowledgeError(
|
|
2856
|
+
413,
|
|
2857
|
+
"file_too_large",
|
|
2858
|
+
"This file exceeds 4 MB. Split the source document with a format-aware tool before uploading."
|
|
2859
|
+
);
|
|
2860
|
+
const bytes = new Uint8Array(await file.arrayBuffer());
|
|
2861
|
+
const parts = splitUtf8(bytes, MAX_UPLOAD_BYTES);
|
|
2862
|
+
const uploaded = [];
|
|
2863
|
+
try {
|
|
2864
|
+
for (let i = 0; i < parts.length; i++) {
|
|
2865
|
+
const bytes2 = parts[i];
|
|
2866
|
+
const contents = bytes2.buffer.slice(
|
|
2867
|
+
bytes2.byteOffset,
|
|
2868
|
+
bytes2.byteOffset + bytes2.byteLength
|
|
2869
|
+
);
|
|
2870
|
+
const part = new File([contents], partName(file.name, i + 1), {
|
|
2871
|
+
type: file.type || "text/plain"
|
|
2872
|
+
});
|
|
2873
|
+
if ((folder?.length ?? 0) + part.name.length > 128)
|
|
2874
|
+
throw new KnowledgeError(
|
|
2875
|
+
400,
|
|
2876
|
+
"invalid_key",
|
|
2877
|
+
"Folder and split filename together exceed the 128-character AI Search key limit"
|
|
2878
|
+
);
|
|
2879
|
+
uploaded.push(...(await upload(env, part, folder)).items);
|
|
2880
|
+
}
|
|
2881
|
+
} catch (error) {
|
|
2882
|
+
if (error instanceof KnowledgeError)
|
|
2883
|
+
throw new KnowledgeError(error.status, error.code, error.message, uploaded);
|
|
2884
|
+
throw new KnowledgeError(502, "knowledge_upload_failed", "Knowledge upload failed", uploaded);
|
|
2885
|
+
}
|
|
2886
|
+
return { items: uploaded };
|
|
2887
|
+
}
|
|
2888
|
+
function splitUtf8(bytes, max) {
|
|
2889
|
+
const out = [];
|
|
2890
|
+
for (let start = 0; start < bytes.length; ) {
|
|
2891
|
+
let end = Math.min(start + max, bytes.length);
|
|
2892
|
+
if (end < bytes.length) {
|
|
2893
|
+
let cut = end;
|
|
2894
|
+
while (cut > start && (bytes[cut] & 192) === 128) cut--;
|
|
2895
|
+
for (let i = cut - 1; i > Math.max(start, cut - 8192); i--)
|
|
2896
|
+
if (bytes[i] === 10 && (i === start || bytes[i - 1] === 10)) {
|
|
2897
|
+
cut = i + 1;
|
|
2898
|
+
break;
|
|
2899
|
+
}
|
|
2900
|
+
end = cut > start ? cut : end;
|
|
2901
|
+
}
|
|
2902
|
+
out.push(bytes.slice(start, end));
|
|
2903
|
+
start = end;
|
|
2904
|
+
}
|
|
2905
|
+
return out;
|
|
2906
|
+
}
|
|
2907
|
+
function partName(name, n) {
|
|
2908
|
+
const dot = name.lastIndexOf(".");
|
|
2909
|
+
return dot > 0 ? `${name.slice(0, dot)}.part-${n}${name.slice(dot)}` : `${name}.part-${n}`;
|
|
2910
|
+
}
|
|
2911
|
+
async function upload(env, file, folder) {
|
|
2912
|
+
const body = new FormData();
|
|
2913
|
+
body.append("file", file);
|
|
2914
|
+
if (folder) body.append("folder", folder);
|
|
2915
|
+
return await request(env, "/api/knowledge/items", {
|
|
2916
|
+
method: "POST",
|
|
2917
|
+
body
|
|
2918
|
+
});
|
|
2919
|
+
}
|
|
2920
|
+
async function listItems(env, options) {
|
|
2921
|
+
if (options.page !== void 0 && (!Number.isInteger(options.page) || options.page < 1))
|
|
2922
|
+
throw new KnowledgeError(400, "invalid_page", "List page must be a positive integer");
|
|
2923
|
+
if (options.perPage !== void 0 && (!Number.isInteger(options.perPage) || options.perPage < 1 || options.perPage > 50))
|
|
2924
|
+
throw new KnowledgeError(
|
|
2925
|
+
400,
|
|
2926
|
+
"invalid_per_page",
|
|
2927
|
+
"List perPage must be an integer from 1 to 50"
|
|
2928
|
+
);
|
|
2929
|
+
if (options.search !== void 0 && options.search.length > 256)
|
|
2930
|
+
throw new KnowledgeError(400, "invalid_search", "List search cannot exceed 256 characters");
|
|
2931
|
+
if (options.status !== void 0 && !["queued", "running", "completed", "error", "skipped", "outdated"].includes(options.status))
|
|
2932
|
+
throw new KnowledgeError(400, "invalid_status", "Invalid knowledge item status");
|
|
2933
|
+
const q = new URLSearchParams();
|
|
2934
|
+
if (options.folder) q.set("folder", normalizeKnowledgeFolder(options.folder));
|
|
2935
|
+
if (options.page !== void 0) q.set("page", String(options.page));
|
|
2936
|
+
if (options.perPage !== void 0) q.set("perPage", String(options.perPage));
|
|
2937
|
+
if (options.status) q.set("status", options.status);
|
|
2938
|
+
if (options.search) q.set("search", options.search);
|
|
2939
|
+
return await request(env, `/api/knowledge/items${q.size ? `?${q}` : ""}`);
|
|
2940
|
+
}
|
|
2941
|
+
async function searchItems(env, query, options) {
|
|
2942
|
+
const normalizedQuery = query.trim();
|
|
2943
|
+
if (!normalizedQuery) throw new KnowledgeError(400, "invalid_query", "Search query is required");
|
|
2944
|
+
if (normalizedQuery.length > MAX_QUERY_CHARS)
|
|
2945
|
+
throw new KnowledgeError(
|
|
2946
|
+
400,
|
|
2947
|
+
"invalid_query",
|
|
2948
|
+
`Search query cannot exceed ${MAX_QUERY_CHARS} characters`
|
|
2949
|
+
);
|
|
2950
|
+
if (options.mode !== void 0 && options.mode !== "hybrid" && options.mode !== "semantic" && options.mode !== "fulltext")
|
|
2951
|
+
throw new KnowledgeError(400, "invalid_mode", "Invalid knowledge search mode");
|
|
2952
|
+
if (options.limit !== void 0 && (!Number.isInteger(options.limit) || options.limit < 1 || options.limit > 50))
|
|
2953
|
+
throw new KnowledgeError(
|
|
2954
|
+
400,
|
|
2955
|
+
"invalid_limit",
|
|
2956
|
+
"Search limit must be an integer from 1 through 50"
|
|
2957
|
+
);
|
|
2958
|
+
if (options.matchThreshold !== void 0 && (!Number.isFinite(options.matchThreshold) || options.matchThreshold < 0 || options.matchThreshold > 1))
|
|
2959
|
+
throw new KnowledgeError(
|
|
2960
|
+
400,
|
|
2961
|
+
"invalid_match_threshold",
|
|
2962
|
+
"Match threshold must be between 0 and 1"
|
|
2963
|
+
);
|
|
2964
|
+
if (options.queryRewrite !== void 0 && typeof options.queryRewrite !== "boolean")
|
|
2965
|
+
throw new KnowledgeError(
|
|
2966
|
+
400,
|
|
2967
|
+
"invalid_query_rewrite",
|
|
2968
|
+
"queryRewrite must be a boolean when provided"
|
|
2969
|
+
);
|
|
2970
|
+
const folder = options.folder === void 0 ? void 0 : normalizeKnowledgeFolder(options.folder);
|
|
2971
|
+
return await request(env, "/api/knowledge/search", {
|
|
2972
|
+
method: "POST",
|
|
2973
|
+
headers: { "content-type": "application/json" },
|
|
2974
|
+
body: JSON.stringify({
|
|
2975
|
+
query: normalizedQuery,
|
|
2976
|
+
mode: options.mode ?? "hybrid",
|
|
2977
|
+
limit: options.limit ?? 10,
|
|
2978
|
+
queryRewrite: options.queryRewrite ?? false,
|
|
2979
|
+
...options.matchThreshold !== void 0 && {
|
|
2980
|
+
matchThreshold: options.matchThreshold
|
|
2981
|
+
},
|
|
2982
|
+
...folder && { folder }
|
|
2983
|
+
})
|
|
2984
|
+
});
|
|
2985
|
+
}
|
|
2986
|
+
async function request(env, path, init = {}) {
|
|
2987
|
+
const headers = new Headers(init.headers);
|
|
2988
|
+
appendAppIdentity(headers, env);
|
|
2989
|
+
const response = await apiWorkerFetch(env, path, { ...init, headers });
|
|
2990
|
+
const text = await response.text();
|
|
2991
|
+
let data;
|
|
2992
|
+
try {
|
|
2993
|
+
data = text ? JSON.parse(text) : void 0;
|
|
2994
|
+
} catch {
|
|
2995
|
+
data = void 0;
|
|
2996
|
+
}
|
|
2997
|
+
if (!response.ok) {
|
|
2998
|
+
const body = data && typeof data === "object" ? data : {};
|
|
2999
|
+
throw new KnowledgeError(
|
|
3000
|
+
response.status,
|
|
3001
|
+
typeof body.code === "string" ? body.code : "knowledge_request_failed",
|
|
3002
|
+
typeof body.error === "string" ? body.error : "Knowledge request failed"
|
|
3003
|
+
);
|
|
3004
|
+
}
|
|
3005
|
+
return data;
|
|
3006
|
+
}
|
|
3007
|
+
|
|
2754
3008
|
// src/server/utils/d1-migrations.ts
|
|
2755
3009
|
var META_TABLE = "_dpc_migrations";
|
|
2756
3010
|
var META_BOOTSTRAP_SQL = `CREATE TABLE IF NOT EXISTS ${META_TABLE} (idx INTEGER PRIMARY KEY, applied_at TEXT NOT NULL)`;
|
|
@@ -2796,13 +3050,18 @@ function decodeRoomIdentityHeader(value) {
|
|
|
2796
3050
|
}
|
|
2797
3051
|
|
|
2798
3052
|
// src/server/rooms/base-room.ts
|
|
2799
|
-
function connectionAttachmentFromRequest(
|
|
3053
|
+
function connectionAttachmentFromRequest(request2) {
|
|
3054
|
+
if (!request2.headers.has("x-user-id") && new URL(request2.url).searchParams.has("userId")) {
|
|
3055
|
+
console.warn(
|
|
3056
|
+
"[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."
|
|
3057
|
+
);
|
|
3058
|
+
}
|
|
2800
3059
|
return {
|
|
2801
|
-
userId: decodeRoomIdentityHeader(
|
|
2802
|
-
userName: decodeRoomIdentityHeader(
|
|
2803
|
-
userEmail: decodeRoomIdentityHeader(
|
|
2804
|
-
userImageUrl: decodeRoomIdentityHeader(
|
|
2805
|
-
role: decodeRoomIdentityHeader(
|
|
3060
|
+
userId: decodeRoomIdentityHeader(request2.headers.get("x-user-id")) || `${ANONYMOUS_USER_ID_PREFIX}${crypto.randomUUID()}`,
|
|
3061
|
+
userName: decodeRoomIdentityHeader(request2.headers.get("x-user-name")) || "Anonymous",
|
|
3062
|
+
userEmail: decodeRoomIdentityHeader(request2.headers.get("x-user-email")) || "",
|
|
3063
|
+
userImageUrl: decodeRoomIdentityHeader(request2.headers.get("x-user-image-url")),
|
|
3064
|
+
role: decodeRoomIdentityHeader(request2.headers.get("x-user-role"))
|
|
2806
3065
|
};
|
|
2807
3066
|
}
|
|
2808
3067
|
var BaseRoom = class {
|
|
@@ -2818,15 +3077,15 @@ var BaseRoom = class {
|
|
|
2818
3077
|
// ==========================================================================
|
|
2819
3078
|
// HTTP Entry Point
|
|
2820
3079
|
// ==========================================================================
|
|
2821
|
-
async fetch(
|
|
2822
|
-
const url = new URL(
|
|
2823
|
-
const internal = await this.handleInternalRequest(
|
|
3080
|
+
async fetch(request2) {
|
|
3081
|
+
const url = new URL(request2.url);
|
|
3082
|
+
const internal = await this.handleInternalRequest(request2, url);
|
|
2824
3083
|
if (internal) return internal;
|
|
2825
|
-
if (
|
|
2826
|
-
return this.handleWebSocketUpgrade(
|
|
3084
|
+
if (request2.headers.get("Upgrade") === "websocket") {
|
|
3085
|
+
return this.handleWebSocketUpgrade(request2, url);
|
|
2827
3086
|
}
|
|
2828
3087
|
if (this.onRequest) {
|
|
2829
|
-
return this.onRequest(
|
|
3088
|
+
return this.onRequest(request2);
|
|
2830
3089
|
}
|
|
2831
3090
|
return new Response("Not Found", { status: 404 });
|
|
2832
3091
|
}
|
|
@@ -2847,11 +3106,11 @@ var BaseRoom = class {
|
|
|
2847
3106
|
* See the security note on `fetch()`: this path is only reachable via DO
|
|
2848
3107
|
* stub fetch from the app worker, never from the public internet.
|
|
2849
3108
|
*/
|
|
2850
|
-
async handleInternalRequest(
|
|
2851
|
-
if (
|
|
3109
|
+
async handleInternalRequest(request2, url) {
|
|
3110
|
+
if (request2.method === "POST" && url.pathname === "/internal/disconnect-sockets") {
|
|
2852
3111
|
let options;
|
|
2853
3112
|
try {
|
|
2854
|
-
const text = await
|
|
3113
|
+
const text = await request2.text();
|
|
2855
3114
|
if (text) options = JSON.parse(text);
|
|
2856
3115
|
} catch {
|
|
2857
3116
|
}
|
|
@@ -2863,11 +3122,11 @@ var BaseRoom = class {
|
|
|
2863
3122
|
// ==========================================================================
|
|
2864
3123
|
// WebSocket Connection
|
|
2865
3124
|
// ==========================================================================
|
|
2866
|
-
async handleWebSocketUpgrade(
|
|
3125
|
+
async handleWebSocketUpgrade(request2, _url) {
|
|
2867
3126
|
const pair = new WebSocketPair();
|
|
2868
3127
|
const [client, server] = Object.values(pair);
|
|
2869
3128
|
this.state.acceptWebSocket(server);
|
|
2870
|
-
const attachment = connectionAttachmentFromRequest(
|
|
3129
|
+
const attachment = connectionAttachmentFromRequest(request2);
|
|
2871
3130
|
const augmented = await this.onConnect(server, attachment) ?? attachment;
|
|
2872
3131
|
server.serializeAttachment(augmented);
|
|
2873
3132
|
return new Response(null, { status: 101, webSocket: client });
|
|
@@ -3103,7 +3362,8 @@ function dataToColumnValues(data, columns) {
|
|
|
3103
3362
|
return values;
|
|
3104
3363
|
}
|
|
3105
3364
|
function coerceValue(value, storage, interpretation) {
|
|
3106
|
-
if (value === null || value === void 0
|
|
3365
|
+
if (value === null || value === void 0) return null;
|
|
3366
|
+
if (value === "") return storage === "text" ? "" : null;
|
|
3107
3367
|
if (storage === "text") {
|
|
3108
3368
|
if (interpretation.kind === "json") {
|
|
3109
3369
|
if (typeof value === "string") return value;
|
|
@@ -3137,7 +3397,7 @@ function coerceValue(value, storage, interpretation) {
|
|
|
3137
3397
|
}
|
|
3138
3398
|
if (interpretation.kind === "date" || interpretation.kind === "datetime") {
|
|
3139
3399
|
const d = new Date(s);
|
|
3140
|
-
if (!isNaN(d.getTime())) return
|
|
3400
|
+
if (!isNaN(d.getTime())) return epochSeconds(d);
|
|
3141
3401
|
return s;
|
|
3142
3402
|
}
|
|
3143
3403
|
const cleaned = s.replace(/[$€£¥,\s]/g, "");
|
|
@@ -3145,6 +3405,9 @@ function coerceValue(value, storage, interpretation) {
|
|
|
3145
3405
|
if (!isNaN(n) && isFinite(n)) return n;
|
|
3146
3406
|
return s;
|
|
3147
3407
|
}
|
|
3408
|
+
function epochSeconds(date = /* @__PURE__ */ new Date()) {
|
|
3409
|
+
return Math.floor(date.getTime() / 1e3);
|
|
3410
|
+
}
|
|
3148
3411
|
function buildTableSelect(collectionName, columns) {
|
|
3149
3412
|
const tbl = collectionTableName(collectionName);
|
|
3150
3413
|
const parts = ["_row_id", "_created_by", "_created_at", "_updated_at"];
|
|
@@ -3579,6 +3842,9 @@ var serverBuild = {
|
|
|
3579
3842
|
function getResolvedColumns(schema) {
|
|
3580
3843
|
return (schema.columns ?? []).map(resolveColumn);
|
|
3581
3844
|
}
|
|
3845
|
+
function triggerTimestamp(col) {
|
|
3846
|
+
return col.storage === "number" ? epochSeconds() : (/* @__PURE__ */ new Date()).toISOString();
|
|
3847
|
+
}
|
|
3582
3848
|
function systemManagedColumnError(column, isUpdate) {
|
|
3583
3849
|
const roleHint = column === "role" ? ", or the admin-gated useUsers().setRole() to change a role" : "";
|
|
3584
3850
|
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.`;
|
|
@@ -3697,7 +3963,7 @@ function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRb
|
|
|
3697
3963
|
if (!isUpdate) {
|
|
3698
3964
|
if (mergedData[triggerField] !== void 0 && mergedData[triggerField] !== null) {
|
|
3699
3965
|
if (triggerValue === void 0 || mergedData[triggerField] === triggerValue) {
|
|
3700
|
-
mergedData[col.name] = (
|
|
3966
|
+
mergedData[col.name] = triggerTimestamp(col);
|
|
3701
3967
|
}
|
|
3702
3968
|
}
|
|
3703
3969
|
} else {
|
|
@@ -3705,7 +3971,7 @@ function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRb
|
|
|
3705
3971
|
const newVal = mergedData[triggerField];
|
|
3706
3972
|
if (newVal !== oldVal) {
|
|
3707
3973
|
if (triggerValue === void 0 || newVal === triggerValue) {
|
|
3708
|
-
mergedData[col.name] = (
|
|
3974
|
+
mergedData[col.name] = triggerTimestamp(col);
|
|
3709
3975
|
}
|
|
3710
3976
|
}
|
|
3711
3977
|
}
|
|
@@ -3750,27 +4016,27 @@ function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRb
|
|
|
3750
4016
|
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)) {
|
|
3751
4017
|
return { success: false, error: systemManagedColumnError(col.name, isUpdate) };
|
|
3752
4018
|
}
|
|
3753
|
-
if (current !== void 0)
|
|
4019
|
+
if (current !== void 0)
|
|
4020
|
+
finalData[col.name] = current;
|
|
3754
4021
|
else delete finalData[col.name];
|
|
3755
4022
|
}
|
|
3756
4023
|
}
|
|
3757
4024
|
const colValues = dataToColumnValues(finalData, columns);
|
|
3758
4025
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3759
4026
|
const tbl = collectionTableName(collection);
|
|
3760
|
-
if (
|
|
3761
|
-
const
|
|
3762
|
-
const
|
|
3763
|
-
return
|
|
3764
|
-
});
|
|
3765
|
-
const uniqueParams = schema.uniqueOn.map((fieldName) => {
|
|
3766
|
-
const val = finalData[fieldName];
|
|
3767
|
-
return val !== void 0 ? val : null;
|
|
4027
|
+
if (schema.uniqueOn && schema.uniqueOn.length > 0) {
|
|
4028
|
+
const uniqueColumns = schema.uniqueOn.map((fieldName) => {
|
|
4029
|
+
const id = columns.find((column) => column.name === fieldName)?.id ?? columnId(fieldName);
|
|
4030
|
+
return { id, value: colValues[id] ?? null };
|
|
3768
4031
|
});
|
|
3769
|
-
const
|
|
3770
|
-
|
|
4032
|
+
const uniqueWhere = uniqueColumns.map(({ id }) => `"${id}" = ?`);
|
|
4033
|
+
const uniqueParams = uniqueColumns.map(({ value }) => value);
|
|
4034
|
+
if (isUpdate) uniqueParams.push(recordId);
|
|
4035
|
+
const conflict = ctx.sql.exec(
|
|
4036
|
+
`SELECT _row_id FROM "${tbl}" WHERE ${uniqueWhere.join(" AND ")}${isUpdate ? " AND _row_id <> ?" : ""} LIMIT 1`,
|
|
3771
4037
|
...uniqueParams
|
|
3772
4038
|
);
|
|
3773
|
-
if (
|
|
4039
|
+
if (conflict.toArray().length > 0) {
|
|
3774
4040
|
const fields = schema.uniqueOn.map((f) => `${f}=${finalData[f] ?? "null"}`).join(", ");
|
|
3775
4041
|
return {
|
|
3776
4042
|
success: false,
|
|
@@ -3836,21 +4102,22 @@ function deleteRecord(ctx, collection, recordId, userId, userRole, skipUserRbac
|
|
|
3836
4102
|
notifyTeamMembershipChange(ctx, collection, record);
|
|
3837
4103
|
return { success: true, data: { deleted: true } };
|
|
3838
4104
|
}
|
|
3839
|
-
function
|
|
3840
|
-
const
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
4105
|
+
function refuseUnknownWhere(collection, schema, where) {
|
|
4106
|
+
const columns = (schema?.columns ?? []).map(resolveColumn);
|
|
4107
|
+
const filterable = ["recordId", "createdBy", ...columns.map((c) => c.name)];
|
|
4108
|
+
if (typeof where !== "object" || where === null || Array.isArray(where)) {
|
|
4109
|
+
const got = where === null ? "null" : Array.isArray(where) ? "an array" : typeof where;
|
|
3844
4110
|
return {
|
|
3845
4111
|
success: false,
|
|
3846
|
-
error: `
|
|
4112
|
+
error: `where for "${collection}" must be an object of field=value pairs, not ${got} \u2014 filterable fields: ${filterable.join(", ")}`
|
|
3847
4113
|
};
|
|
3848
4114
|
}
|
|
3849
|
-
if (!
|
|
3850
|
-
return {
|
|
4115
|
+
if (!schema && Object.keys(where).length > 0) {
|
|
4116
|
+
return {
|
|
4117
|
+
success: false,
|
|
4118
|
+
error: `"${collection}" has no schema, so it has no filterable fields \u2014 query it without \`where\``
|
|
4119
|
+
};
|
|
3851
4120
|
}
|
|
3852
|
-
const columns = (schema.columns ?? []).map(resolveColumn);
|
|
3853
|
-
const filterable = ["recordId", "createdBy", ...columns.map((c) => c.name)];
|
|
3854
4121
|
const unknown = Object.keys(where).filter(
|
|
3855
4122
|
(key) => key !== "recordId" && key !== "createdBy" && !columns.some((c) => c.id === columnId(key) || c.name === key)
|
|
3856
4123
|
);
|
|
@@ -3867,6 +4134,26 @@ function deleteWhere(ctx, collection, where, limit, userId, userRole, skipUserRb
|
|
|
3867
4134
|
error: `where values must be primitives (equality only): ${nonPrimitive.map(([k]) => k).join(", ")}`
|
|
3868
4135
|
};
|
|
3869
4136
|
}
|
|
4137
|
+
return null;
|
|
4138
|
+
}
|
|
4139
|
+
function deleteWhere(ctx, collection, where, limit, userId, userRole, skipUserRbac = false) {
|
|
4140
|
+
const resolved = resolveCollection(ctx, collection);
|
|
4141
|
+
if (!resolved.ok) return { success: false, error: resolved.error };
|
|
4142
|
+
const schema = resolved.schema;
|
|
4143
|
+
if (!schema) {
|
|
4144
|
+
return {
|
|
4145
|
+
success: false,
|
|
4146
|
+
error: `records.deleteWhere: collection "${collection}" has no schema, so it has no filterable fields \u2014 delete its rows by recordId with records.delete`
|
|
4147
|
+
};
|
|
4148
|
+
}
|
|
4149
|
+
const whereRefusal = refuseUnknownWhere(collection, schema, where);
|
|
4150
|
+
if (whereRefusal) return whereRefusal;
|
|
4151
|
+
if (Object.keys(where).length === 0) {
|
|
4152
|
+
return {
|
|
4153
|
+
success: false,
|
|
4154
|
+
error: "Missing required param: where (must match at least one field)"
|
|
4155
|
+
};
|
|
4156
|
+
}
|
|
3870
4157
|
if (limit !== void 0 && !(typeof limit === "number" && Number.isFinite(limit))) {
|
|
3871
4158
|
return { success: false, error: "limit must be a finite number" };
|
|
3872
4159
|
}
|
|
@@ -3874,32 +4161,27 @@ function deleteWhere(ctx, collection, where, limit, userId, userRole, skipUserRb
|
|
|
3874
4161
|
Math.max(1, Math.floor(limit ?? DEFAULT_DELETE_WHERE_LIMIT)),
|
|
3875
4162
|
MAX_DELETE_WHERE_LIMIT
|
|
3876
4163
|
);
|
|
3877
|
-
const scan = { capped: false };
|
|
3878
4164
|
const matched = executeQuery(
|
|
3879
4165
|
ctx,
|
|
3880
4166
|
{ collection, where, limit: page },
|
|
3881
4167
|
userId,
|
|
3882
4168
|
userRole,
|
|
3883
|
-
|
|
3884
|
-
scan
|
|
4169
|
+
true
|
|
3885
4170
|
);
|
|
3886
|
-
if (
|
|
3887
|
-
return {
|
|
3888
|
-
success: false,
|
|
3889
|
-
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`
|
|
3890
|
-
};
|
|
3891
|
-
}
|
|
3892
|
-
if (!skipUserRbac && schema) {
|
|
4171
|
+
if (!skipUserRbac) {
|
|
3893
4172
|
const permCtx = ctx.getPermissionContext();
|
|
3894
4173
|
for (const record of matched) {
|
|
3895
4174
|
if (!canDelete(schema, userRole, record, userId, permCtx)) {
|
|
3896
|
-
return {
|
|
4175
|
+
return {
|
|
4176
|
+
success: false,
|
|
4177
|
+
error: `DELETE DENIED: role=${userRole}, collection=${collection}`
|
|
4178
|
+
};
|
|
3897
4179
|
}
|
|
3898
4180
|
}
|
|
3899
4181
|
}
|
|
3900
4182
|
let deleted = 0;
|
|
3901
4183
|
for (const record of matched) {
|
|
3902
|
-
if (deleteRecord(ctx, collection, record.recordId, userId, userRole,
|
|
4184
|
+
if (deleteRecord(ctx, collection, record.recordId, userId, userRole, true).success) {
|
|
3903
4185
|
deleted++;
|
|
3904
4186
|
}
|
|
3905
4187
|
}
|
|
@@ -4598,17 +4880,17 @@ async function registerUser(sql, userId, name, email, imageUrl, isAdmin, default
|
|
|
4598
4880
|
);
|
|
4599
4881
|
return { id: userId, email, name, imageUrl, role, createdAt: now, lastSeenAt: now };
|
|
4600
4882
|
}
|
|
4601
|
-
function rosterFor(ctx, schema, records, publicRoster,
|
|
4602
|
-
if (isAnonymousUserId(
|
|
4603
|
-
if (
|
|
4883
|
+
function rosterFor(ctx, schema, records, publicRoster, userId, role) {
|
|
4884
|
+
if (!userId || isAnonymousUserId(userId)) return [];
|
|
4885
|
+
if (role === "admin") {
|
|
4604
4886
|
const permissionContext = ctx.getPermissionContext();
|
|
4605
|
-
return records.filter((record) => canRead(schema, "admin", record,
|
|
4887
|
+
return records.filter((record) => canRead(schema, "admin", record, userId, permissionContext)).map((record) => ({ id: record.recordId, ...record.data }));
|
|
4606
4888
|
}
|
|
4607
|
-
if (!getRolePermissions(schema,
|
|
4889
|
+
if (!getRolePermissions(schema, role).read) return [];
|
|
4608
4890
|
if (schema.roster === "read-policy") {
|
|
4609
4891
|
const permissionContext = ctx.getPermissionContext();
|
|
4610
4892
|
const readable = new Set(
|
|
4611
|
-
records.filter((record) => canRead(schema,
|
|
4893
|
+
records.filter((record) => canRead(schema, role, record, userId, permissionContext)).map((record) => record.recordId)
|
|
4612
4894
|
);
|
|
4613
4895
|
return publicRoster.filter((user) => readable.has(user.id));
|
|
4614
4896
|
}
|
|
@@ -4619,17 +4901,18 @@ function publicIdentities(records) {
|
|
|
4619
4901
|
id: recordId,
|
|
4620
4902
|
name: data.name,
|
|
4621
4903
|
...data.imageUrl ? { imageUrl: data.imageUrl } : {},
|
|
4622
|
-
role: data.role
|
|
4904
|
+
role: data.role,
|
|
4905
|
+
lastSeenAt: data.lastSeenAt
|
|
4623
4906
|
}));
|
|
4624
4907
|
}
|
|
4625
|
-
function
|
|
4908
|
+
function rosterForCaller(ctx, userId, role) {
|
|
4626
4909
|
const schema = ctx.schemaRegistry.get("users");
|
|
4627
|
-
if (!schema)
|
|
4628
|
-
ctx.send(ws, { type: MSG.USER_LIST, payload: { users: [] } });
|
|
4629
|
-
return;
|
|
4630
|
-
}
|
|
4910
|
+
if (!schema) return [];
|
|
4631
4911
|
const records = getAllUserRecords(ctx.sql, ctx.schemaRegistry);
|
|
4632
|
-
|
|
4912
|
+
return rosterFor(ctx, schema, records, publicIdentities(records), userId, role);
|
|
4913
|
+
}
|
|
4914
|
+
function handleUserList(ctx, ws, attachment) {
|
|
4915
|
+
const users = rosterForCaller(ctx, attachment.userId, attachment.role);
|
|
4633
4916
|
ctx.send(ws, { type: MSG.USER_LIST, payload: { users } });
|
|
4634
4917
|
}
|
|
4635
4918
|
function broadcastUserList(ctx, except) {
|
|
@@ -4644,7 +4927,9 @@ function broadcastUserList(ctx, except) {
|
|
|
4644
4927
|
if (!attachment) continue;
|
|
4645
4928
|
ctx.send(other, {
|
|
4646
4929
|
type: MSG.USER_LIST,
|
|
4647
|
-
payload: {
|
|
4930
|
+
payload: {
|
|
4931
|
+
users: rosterFor(ctx, schema, records, publicRoster, attachment.userId, attachment.role)
|
|
4932
|
+
}
|
|
4648
4933
|
});
|
|
4649
4934
|
}
|
|
4650
4935
|
}
|
|
@@ -4697,8 +4982,8 @@ var CORS_HEADERS3 = {
|
|
|
4697
4982
|
"Access-Control-Allow-Headers": "Content-Type, X-User-Id, X-App-Action",
|
|
4698
4983
|
"Content-Type": "application/json"
|
|
4699
4984
|
};
|
|
4700
|
-
async function handleToolsRequest(ctx,
|
|
4701
|
-
if (
|
|
4985
|
+
async function handleToolsRequest(ctx, request2, path) {
|
|
4986
|
+
if (request2.method === "OPTIONS") {
|
|
4702
4987
|
return new Response(null, { headers: CORS_HEADERS3 });
|
|
4703
4988
|
}
|
|
4704
4989
|
if (path === "tools/list") {
|
|
@@ -4715,15 +5000,15 @@ async function handleToolsRequest(ctx, request, path) {
|
|
|
4715
5000
|
}
|
|
4716
5001
|
return Response.json({ tool: tool2 }, { headers: CORS_HEADERS3 });
|
|
4717
5002
|
}
|
|
4718
|
-
if (path.startsWith("tools/execute") &&
|
|
4719
|
-
return handleToolExecute(ctx,
|
|
5003
|
+
if (path.startsWith("tools/execute") && request2.method === "POST") {
|
|
5004
|
+
return handleToolExecute(ctx, request2);
|
|
4720
5005
|
}
|
|
4721
5006
|
return Response.json({ error: "Not found" }, { status: 404, headers: CORS_HEADERS3 });
|
|
4722
5007
|
}
|
|
4723
|
-
async function handleToolExecute(ctx,
|
|
5008
|
+
async function handleToolExecute(ctx, request2) {
|
|
4724
5009
|
let body;
|
|
4725
5010
|
try {
|
|
4726
|
-
body = await
|
|
5011
|
+
body = await request2.json();
|
|
4727
5012
|
} catch {
|
|
4728
5013
|
return Response.json(
|
|
4729
5014
|
{ success: false, error: "Invalid JSON body" },
|
|
@@ -4737,8 +5022,8 @@ async function handleToolExecute(ctx, request) {
|
|
|
4737
5022
|
{ status: 400, headers: CORS_HEADERS3 }
|
|
4738
5023
|
);
|
|
4739
5024
|
}
|
|
4740
|
-
const userId =
|
|
4741
|
-
const isAppAction =
|
|
5025
|
+
const userId = request2.headers.get("x-user-id") ?? "";
|
|
5026
|
+
const isAppAction = request2.headers.get("x-app-action")?.toLowerCase() === "true";
|
|
4742
5027
|
let userRole = "viewer";
|
|
4743
5028
|
if (userId && ctx.ownerUserId && userId === ctx.ownerUserId) {
|
|
4744
5029
|
userRole = "admin";
|
|
@@ -4773,9 +5058,14 @@ async function executeTool(ctx, toolName, params, userId, userRole, skipUserRbac
|
|
|
4773
5058
|
if (!collection) return { success: false, error: "Missing required param: collection" };
|
|
4774
5059
|
const resolved = resolveCollection(ctx, collection);
|
|
4775
5060
|
if (!resolved.ok) return { success: false, error: resolved.error };
|
|
5061
|
+
if (params.where !== void 0) {
|
|
5062
|
+
const refusal = refuseUnknownWhere(collection, resolved.schema, params.where);
|
|
5063
|
+
if (refusal) return refusal;
|
|
5064
|
+
}
|
|
5065
|
+
const where = params.where;
|
|
4776
5066
|
const query = {
|
|
4777
5067
|
collection,
|
|
4778
|
-
where
|
|
5068
|
+
where,
|
|
4779
5069
|
orderBy: params.orderBy,
|
|
4780
5070
|
orderDir: params.orderDir,
|
|
4781
5071
|
limit: params.limit
|
|
@@ -4881,8 +5171,16 @@ async function executeTool(ctx, toolName, params, userId, userRole, skipUserRbac
|
|
|
4881
5171
|
if (!user) return { success: false, error: "User not found" };
|
|
4882
5172
|
return { success: true, data: { user } };
|
|
4883
5173
|
}
|
|
5174
|
+
// The same roster the socket's `user.list` returns, for the same caller.
|
|
5175
|
+
// Reading the users table directly here handed any member every peer's
|
|
5176
|
+
// email regardless of the room's users policy, while their own socket
|
|
5177
|
+
// roster and `useQuery('users')` obeyed it.
|
|
5178
|
+
//
|
|
5179
|
+
// An app action (`X-App-Action`) is the app's own server-side code, which
|
|
5180
|
+
// is already the trust boundary and reads every other collection RBAC-off;
|
|
5181
|
+
// it keeps the full rows.
|
|
4884
5182
|
case "user.list": {
|
|
4885
|
-
const users = getAllUsers(ctx.sql, ctx.schemaRegistry);
|
|
5183
|
+
const users = skipUserRbac ? getAllUsers(ctx.sql, ctx.schemaRegistry) : rosterForCaller(ctx, userId, userRole);
|
|
4886
5184
|
return { success: true, data: { users } };
|
|
4887
5185
|
}
|
|
4888
5186
|
default:
|
|
@@ -5017,25 +5315,25 @@ var CORS_HEADERS4 = {
|
|
|
5017
5315
|
"Access-Control-Allow-Headers": "Content-Type",
|
|
5018
5316
|
"Content-Type": "application/json"
|
|
5019
5317
|
};
|
|
5020
|
-
async function handleApiRequest(ctx,
|
|
5021
|
-
if (
|
|
5318
|
+
async function handleApiRequest(ctx, request2, url) {
|
|
5319
|
+
if (request2.method === "OPTIONS") {
|
|
5022
5320
|
return new Response(null, { headers: CORS_HEADERS4 });
|
|
5023
5321
|
}
|
|
5024
5322
|
const path = url.pathname.replace("/api/", "");
|
|
5025
5323
|
if (path.startsWith("tools/")) {
|
|
5026
|
-
return handleToolsRequest(ctx,
|
|
5324
|
+
return handleToolsRequest(ctx, request2, path);
|
|
5027
5325
|
}
|
|
5028
5326
|
if (path === "debug/status") {
|
|
5029
5327
|
return handleDebugStatus(ctx);
|
|
5030
5328
|
}
|
|
5031
5329
|
if (path === "debug/sql") {
|
|
5032
|
-
return handleDebugSql(ctx,
|
|
5330
|
+
return handleDebugSql(ctx, request2, url);
|
|
5033
5331
|
}
|
|
5034
5332
|
if (path === "debug/user-role") {
|
|
5035
5333
|
return handleDebugUserRole(ctx, url);
|
|
5036
5334
|
}
|
|
5037
|
-
if (path === "debug/set-role" &&
|
|
5038
|
-
return handleDebugSetRole(ctx,
|
|
5335
|
+
if (path === "debug/set-role" && request2.method === "POST") {
|
|
5336
|
+
return handleDebugSetRole(ctx, request2);
|
|
5039
5337
|
}
|
|
5040
5338
|
if (path.startsWith("debug/records/")) {
|
|
5041
5339
|
return handleDebugRecords(ctx, path);
|
|
@@ -5066,8 +5364,8 @@ function handleDebugUserRole(ctx, url) {
|
|
|
5066
5364
|
role: user.role
|
|
5067
5365
|
}, { headers: CORS_HEADERS4 });
|
|
5068
5366
|
}
|
|
5069
|
-
async function handleDebugSetRole(ctx,
|
|
5070
|
-
const body = await
|
|
5367
|
+
async function handleDebugSetRole(ctx, request2) {
|
|
5368
|
+
const body = await request2.json();
|
|
5071
5369
|
if (!body.role) {
|
|
5072
5370
|
return Response.json({ error: "role is required" }, { status: 400, headers: CORS_HEADERS4 });
|
|
5073
5371
|
}
|
|
@@ -5175,11 +5473,11 @@ function handleDebugQuery(ctx, url) {
|
|
|
5175
5473
|
records
|
|
5176
5474
|
}, { headers: CORS_HEADERS4 });
|
|
5177
5475
|
}
|
|
5178
|
-
async function handleDebugSql(ctx,
|
|
5476
|
+
async function handleDebugSql(ctx, request2, url) {
|
|
5179
5477
|
let sql;
|
|
5180
5478
|
let params = [];
|
|
5181
|
-
if (
|
|
5182
|
-
const body = await
|
|
5479
|
+
if (request2.method === "POST") {
|
|
5480
|
+
const body = await request2.json();
|
|
5183
5481
|
sql = body.sql;
|
|
5184
5482
|
params = body.params ?? [];
|
|
5185
5483
|
} else {
|
|
@@ -5299,10 +5597,10 @@ var RecordRoom = class extends BaseRoom {
|
|
|
5299
5597
|
// ============================================================================
|
|
5300
5598
|
// HTTP Entry Point (overrides BaseRoom.fetch)
|
|
5301
5599
|
// ============================================================================
|
|
5302
|
-
async fetch(
|
|
5303
|
-
const url = new URL(
|
|
5600
|
+
async fetch(request2) {
|
|
5601
|
+
const url = new URL(request2.url);
|
|
5304
5602
|
if (url.pathname.startsWith("/internal/")) {
|
|
5305
|
-
const internal = await this.handleInternalRequest(
|
|
5603
|
+
const internal = await this.handleInternalRequest(request2, url);
|
|
5306
5604
|
if (internal) return internal;
|
|
5307
5605
|
}
|
|
5308
5606
|
const fetchStart = Date.now();
|
|
@@ -5315,11 +5613,11 @@ var RecordRoom = class extends BaseRoom {
|
|
|
5315
5613
|
if (url.pathname.startsWith("/api/debug/") && !this.debugRoutesEnabled) {
|
|
5316
5614
|
return new Response("Not Found", { status: 404 });
|
|
5317
5615
|
}
|
|
5318
|
-
return handleApiRequest(this.createHandlerContext(),
|
|
5616
|
+
return handleApiRequest(this.createHandlerContext(), request2, url);
|
|
5319
5617
|
}
|
|
5320
|
-
if (
|
|
5618
|
+
if (request2.headers.get("Upgrade") === "websocket") {
|
|
5321
5619
|
this._fetchTiming = { fetchStart, isColdStart, needsInit, initMs };
|
|
5322
|
-
return super.fetch(
|
|
5620
|
+
return super.fetch(request2);
|
|
5323
5621
|
}
|
|
5324
5622
|
return new Response("Not Found", { status: 404 });
|
|
5325
5623
|
}
|
|
@@ -5901,9 +6199,9 @@ var CanvasRoom = class extends BaseRoom {
|
|
|
5901
6199
|
// --------------------------------------------------------------------------
|
|
5902
6200
|
// BaseRoom Lifecycle
|
|
5903
6201
|
// --------------------------------------------------------------------------
|
|
5904
|
-
async fetch(
|
|
6202
|
+
async fetch(request2) {
|
|
5905
6203
|
this.ensureInitialized();
|
|
5906
|
-
return super.fetch(
|
|
6204
|
+
return super.fetch(request2);
|
|
5907
6205
|
}
|
|
5908
6206
|
onConnect(ws, user) {
|
|
5909
6207
|
this.ensureInitialized();
|
|
@@ -6382,6 +6680,7 @@ function cronMatches(parsed, timezone, date) {
|
|
|
6382
6680
|
}
|
|
6383
6681
|
|
|
6384
6682
|
// src/server/rooms/cron-room.ts
|
|
6683
|
+
var CRON_TASKS_INTERNAL_PATH = "/internal/cron/tasks";
|
|
6385
6684
|
var CRON_WRITE_TYPES = /* @__PURE__ */ new Set([
|
|
6386
6685
|
MSG.CRON_TRIGGER,
|
|
6387
6686
|
MSG.CRON_PAUSE,
|
|
@@ -6456,9 +6755,15 @@ var CronRoom = class extends BaseRoom {
|
|
|
6456
6755
|
// ==========================================================================
|
|
6457
6756
|
// BaseRoom Lifecycle
|
|
6458
6757
|
// ==========================================================================
|
|
6459
|
-
async fetch(
|
|
6758
|
+
async fetch(request2) {
|
|
6460
6759
|
this.ensureInitialized();
|
|
6461
|
-
return super.fetch(
|
|
6760
|
+
return super.fetch(request2);
|
|
6761
|
+
}
|
|
6762
|
+
async handleInternalRequest(request2, url) {
|
|
6763
|
+
if (request2.method === "GET" && url.pathname === CRON_TASKS_INTERNAL_PATH) {
|
|
6764
|
+
return Response.json({ tasks: this.getTaskStates() });
|
|
6765
|
+
}
|
|
6766
|
+
return super.handleInternalRequest(request2, url);
|
|
6462
6767
|
}
|
|
6463
6768
|
onConnect(ws, user) {
|
|
6464
6769
|
this.ensureInitialized();
|
|
@@ -6606,15 +6911,22 @@ var CronRoom = class extends BaseRoom {
|
|
|
6606
6911
|
const start = Date.now();
|
|
6607
6912
|
let success = true;
|
|
6608
6913
|
let error;
|
|
6914
|
+
let thrown;
|
|
6609
6915
|
try {
|
|
6610
6916
|
await this.onTask(taskName);
|
|
6611
6917
|
} catch (e) {
|
|
6612
6918
|
success = false;
|
|
6919
|
+
thrown = e;
|
|
6613
6920
|
error = e instanceof Error ? e.message : String(e);
|
|
6614
|
-
console.error(`[CronRoom] Task "${taskName}" failed:`, e);
|
|
6615
6921
|
}
|
|
6616
6922
|
const durationMs = Date.now() - start;
|
|
6617
6923
|
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
6924
|
+
if (success) {
|
|
6925
|
+
console.log(`[cron] ${taskName} ok ${durationMs}ms`);
|
|
6926
|
+
} else {
|
|
6927
|
+
const stack = thrown instanceof Error ? thrown.stack : void 0;
|
|
6928
|
+
console.error(`[cron] ${taskName} failed ${durationMs}ms: ${error}`, ...stack ? [stack] : []);
|
|
6929
|
+
}
|
|
6618
6930
|
this.sql.exec(
|
|
6619
6931
|
`INSERT INTO cron_history (task_name, started_at, completed_at, success, duration_ms, error, created_at)
|
|
6620
6932
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
@@ -6723,6 +7035,19 @@ var CronRoom = class extends BaseRoom {
|
|
|
6723
7035
|
);
|
|
6724
7036
|
}
|
|
6725
7037
|
};
|
|
7038
|
+
var armedCronRooms = /* @__PURE__ */ new Set();
|
|
7039
|
+
function armCronRoom(ctx, namespace, roomId, tasks) {
|
|
7040
|
+
if (tasks.length === 0 || armedCronRooms.has(roomId)) return;
|
|
7041
|
+
armedCronRooms.add(roomId);
|
|
7042
|
+
ctx.waitUntil(
|
|
7043
|
+
namespace.get(namespace.idFromName(roomId)).fetch(`https://internal${CRON_TASKS_INTERNAL_PATH}`).then((response) => {
|
|
7044
|
+
if (!response.ok) armedCronRooms.delete(roomId);
|
|
7045
|
+
return response;
|
|
7046
|
+
}).catch(() => {
|
|
7047
|
+
armedCronRooms.delete(roomId);
|
|
7048
|
+
})
|
|
7049
|
+
);
|
|
7050
|
+
}
|
|
6726
7051
|
function computeNextRunAt(task, from) {
|
|
6727
7052
|
if (task.interval_minutes) {
|
|
6728
7053
|
return new Date(from.getTime() + task.interval_minutes * 60 * 1e3).toISOString();
|
|
@@ -6795,9 +7120,9 @@ var JobRoom = class extends BaseRoom {
|
|
|
6795
7120
|
// ==========================================================================
|
|
6796
7121
|
// BaseRoom Lifecycle
|
|
6797
7122
|
// ==========================================================================
|
|
6798
|
-
async fetch(
|
|
7123
|
+
async fetch(request2) {
|
|
6799
7124
|
this.ensureInitialized();
|
|
6800
|
-
return super.fetch(
|
|
7125
|
+
return super.fetch(request2);
|
|
6801
7126
|
}
|
|
6802
7127
|
async onConnect(ws, user) {
|
|
6803
7128
|
this.ensureInitialized();
|
|
@@ -6884,13 +7209,13 @@ var JobRoom = class extends BaseRoom {
|
|
|
6884
7209
|
* Subclasses overriding `onRequest` should call `super.onRequest(req)`
|
|
6885
7210
|
* for paths they don't handle.
|
|
6886
7211
|
*/
|
|
6887
|
-
async onRequest(
|
|
7212
|
+
async onRequest(request2) {
|
|
6888
7213
|
this.ensureInitialized();
|
|
6889
|
-
const url = new URL(
|
|
6890
|
-
if (
|
|
7214
|
+
const url = new URL(request2.url);
|
|
7215
|
+
if (request2.method !== "POST" || !url.pathname.endsWith("/enqueue")) {
|
|
6891
7216
|
return new Response("Not Found", { status: 404 });
|
|
6892
7217
|
}
|
|
6893
|
-
const body = await
|
|
7218
|
+
const body = await request2.json().catch(() => null);
|
|
6894
7219
|
if (!body || typeof body.type !== "string" || !body.type) {
|
|
6895
7220
|
return new Response(JSON.stringify({ error: "type required" }), {
|
|
6896
7221
|
status: 400,
|
|
@@ -7056,7 +7381,8 @@ var JobRoom = class extends BaseRoom {
|
|
|
7056
7381
|
} catch (e) {
|
|
7057
7382
|
outcome = "failed";
|
|
7058
7383
|
errorMessage = e instanceof Error ? e.message : String(e);
|
|
7059
|
-
|
|
7384
|
+
const stack = e instanceof Error ? e.stack : void 0;
|
|
7385
|
+
console.error(`[jobs] ${row.type} (${row.id}) failed: ${errorMessage}`, ...stack ? [stack] : []);
|
|
7060
7386
|
} finally {
|
|
7061
7387
|
this.inFlight.delete(row.id);
|
|
7062
7388
|
}
|
|
@@ -7370,7 +7696,8 @@ var AUTO_PROVISIONABLE_TYPES = /* @__PURE__ */ new Set([
|
|
|
7370
7696
|
"kv_namespace",
|
|
7371
7697
|
"vectorize",
|
|
7372
7698
|
"r2_bucket",
|
|
7373
|
-
"queue"
|
|
7699
|
+
"queue",
|
|
7700
|
+
"ai_search"
|
|
7374
7701
|
]);
|
|
7375
7702
|
var VECTORIZE_METRICS = /* @__PURE__ */ new Set(["cosine", "euclidean", "dot-product"]);
|
|
7376
7703
|
function isAutoProvision(b) {
|
|
@@ -7385,6 +7712,8 @@ function isAutoProvision(b) {
|
|
|
7385
7712
|
return b.bucket_name === AUTO_PROVISION_SENTINEL;
|
|
7386
7713
|
case "queue":
|
|
7387
7714
|
return b.queue_name === AUTO_PROVISION_SENTINEL;
|
|
7715
|
+
case "ai_search":
|
|
7716
|
+
return b.instance_name === AUTO_PROVISION_SENTINEL;
|
|
7388
7717
|
default:
|
|
7389
7718
|
return false;
|
|
7390
7719
|
}
|
|
@@ -7399,7 +7728,8 @@ var ALLOWED_BINDING_TYPES = /* @__PURE__ */ new Set([
|
|
|
7399
7728
|
"browser_rendering",
|
|
7400
7729
|
"analytics_engine",
|
|
7401
7730
|
"hyperdrive",
|
|
7402
|
-
"ratelimit"
|
|
7731
|
+
"ratelimit",
|
|
7732
|
+
"ai_search"
|
|
7403
7733
|
]);
|
|
7404
7734
|
var RESERVED_BINDING_NAMES = /* @__PURE__ */ new Set([
|
|
7405
7735
|
"ASSETS",
|
|
@@ -7423,6 +7753,7 @@ function validateBindingManifest(manifest) {
|
|
|
7423
7753
|
}
|
|
7424
7754
|
const errors = [];
|
|
7425
7755
|
const seenNames = /* @__PURE__ */ new Set();
|
|
7756
|
+
let aiSearchCount = 0;
|
|
7426
7757
|
for (const entry of manifest) {
|
|
7427
7758
|
if (!entry || typeof entry !== "object") {
|
|
7428
7759
|
errors.push({
|
|
@@ -7453,14 +7784,23 @@ function validateBindingManifest(manifest) {
|
|
|
7453
7784
|
}
|
|
7454
7785
|
seenNames.add(name);
|
|
7455
7786
|
const binding = entry;
|
|
7787
|
+
if (binding.type === "ai_search") aiSearchCount++;
|
|
7456
7788
|
const fieldErr = requiredFieldError(binding);
|
|
7457
7789
|
if (fieldErr) errors.push({ binding, reason: fieldErr });
|
|
7458
7790
|
}
|
|
7791
|
+
if (aiSearchCount > 1) {
|
|
7792
|
+
errors.push({ reason: "Only one ai_search binding may be declared per app" });
|
|
7793
|
+
}
|
|
7459
7794
|
if (errors.length) return { valid: false, errors };
|
|
7460
7795
|
return { valid: true, bindings: manifest };
|
|
7461
7796
|
}
|
|
7462
7797
|
function requiredFieldError(b) {
|
|
7463
7798
|
switch (b.type) {
|
|
7799
|
+
case "ai_search":
|
|
7800
|
+
if (b.instance_name !== AUTO_PROVISION_SENTINEL) {
|
|
7801
|
+
return `ai_search binding "${b.name}" must set instance_name = "auto"; managed instances cannot be selected directly`;
|
|
7802
|
+
}
|
|
7803
|
+
return null;
|
|
7464
7804
|
case "vectorize": {
|
|
7465
7805
|
if (!b.index_name) return `vectorize binding "${b.name}" missing index_name (or "auto")`;
|
|
7466
7806
|
if (b.index_name === AUTO_PROVISION_SENTINEL) {
|
|
@@ -7522,6 +7862,13 @@ function requiredFieldError(b) {
|
|
|
7522
7862
|
}
|
|
7523
7863
|
}
|
|
7524
7864
|
function bindingManifestFromOutputConfig(outputConfig) {
|
|
7865
|
+
const aiSearchNamespaces = outputConfig.ai_search_namespaces;
|
|
7866
|
+
const declaresAiSearchNamespace = Array.isArray(aiSearchNamespaces) ? aiSearchNamespaces.length > 0 : aiSearchNamespaces !== void 0 && aiSearchNamespaces !== null;
|
|
7867
|
+
if (declaresAiSearchNamespace) {
|
|
7868
|
+
throw new Error(
|
|
7869
|
+
'ai_search_namespaces is not supported by DeepSpace; declare one [[ai_search]] instance_name = "auto" instead'
|
|
7870
|
+
);
|
|
7871
|
+
}
|
|
7525
7872
|
const out = [];
|
|
7526
7873
|
const aiBinding = pluckString(outputConfig.ai, "binding");
|
|
7527
7874
|
if (aiBinding) out.push({ type: "ai", name: aiBinding });
|
|
@@ -7539,6 +7886,11 @@ function bindingManifestFromOutputConfig(outputConfig) {
|
|
|
7539
7886
|
...metric && (metric === "cosine" || metric === "euclidean" || metric === "dot-product") && { metric }
|
|
7540
7887
|
});
|
|
7541
7888
|
}
|
|
7889
|
+
for (const a of asObjectArray(outputConfig.ai_search)) {
|
|
7890
|
+
const name = pluckString(a, "binding");
|
|
7891
|
+
const instance_name = pluckString(a, "instance_name");
|
|
7892
|
+
if (name && instance_name) out.push({ type: "ai_search", name, instance_name });
|
|
7893
|
+
}
|
|
7542
7894
|
for (const r of asObjectArray(outputConfig.r2_buckets)) {
|
|
7543
7895
|
const name = pluckString(r, "binding");
|
|
7544
7896
|
const bucket_name = pluckString(r, "bucket_name");
|
|
@@ -8074,218 +8426,8 @@ var DIRECTORY_SCHEMAS = [
|
|
|
8074
8426
|
postsSchema
|
|
8075
8427
|
];
|
|
8076
8428
|
|
|
8077
|
-
// src/server/schemas/workspace.ts
|
|
8078
|
-
var workspaceTeamsSchema = {
|
|
8079
|
-
name: "teams",
|
|
8080
|
-
columns: [
|
|
8081
|
-
{ name: "Name", storage: "text", interpretation: "plain" },
|
|
8082
|
-
{ name: "CreatedBy", storage: "text", interpretation: "plain" },
|
|
8083
|
-
{ name: "IsOpen", storage: "number", interpretation: { kind: "boolean" } }
|
|
8084
|
-
],
|
|
8085
|
-
teamField: "_rowId",
|
|
8086
|
-
permissions: {
|
|
8087
|
-
admin: { read: true, create: true, update: true, delete: true },
|
|
8088
|
-
member: { read: "team", create: true, update: "team", delete: "own" }
|
|
8089
|
-
}
|
|
8090
|
-
};
|
|
8091
|
-
var workspaceTeamMembersSchema = {
|
|
8092
|
-
name: "team_members",
|
|
8093
|
-
columns: [
|
|
8094
|
-
{ name: "TeamId", storage: "text", interpretation: "plain" },
|
|
8095
|
-
{ name: "UserId", storage: "text", interpretation: "plain" },
|
|
8096
|
-
{ name: "RoleInTeam", storage: "text", interpretation: { kind: "select", options: ["admin", "member"] } },
|
|
8097
|
-
{ name: "JoinedAt", storage: "text", interpretation: { kind: "datetime" } },
|
|
8098
|
-
{ name: "Email", storage: "text", interpretation: { kind: "email" } },
|
|
8099
|
-
{ name: "Status", storage: "text", interpretation: { kind: "select", options: ["active", "invited"] } }
|
|
8100
|
-
],
|
|
8101
|
-
teamField: "TeamId",
|
|
8102
|
-
permissions: {
|
|
8103
|
-
admin: { read: true, create: true, update: true, delete: true },
|
|
8104
|
-
member: { read: "team", create: true, update: "team", delete: "team" }
|
|
8105
|
-
}
|
|
8106
|
-
};
|
|
8107
|
-
var workspaceTasksSchema = {
|
|
8108
|
-
name: "tasks",
|
|
8109
|
-
columns: [
|
|
8110
|
-
{ name: "TeamId", storage: "text", interpretation: "plain" },
|
|
8111
|
-
{ name: "Title", storage: "text", interpretation: "plain" },
|
|
8112
|
-
{ name: "Notes", storage: "text", interpretation: "plain" },
|
|
8113
|
-
{ name: "Completed", storage: "number", interpretation: { kind: "boolean" } },
|
|
8114
|
-
{ name: "CompletedAt", storage: "number", interpretation: "plain" },
|
|
8115
|
-
{ name: "Deleted", storage: "number", interpretation: { kind: "boolean" } },
|
|
8116
|
-
{ name: "DeletedAt", storage: "number", interpretation: "plain" },
|
|
8117
|
-
{ name: "Priority", storage: "text", interpretation: { kind: "select", options: ["none", "low", "medium", "high"] } },
|
|
8118
|
-
{ name: "DueDate", storage: "text", interpretation: "plain" },
|
|
8119
|
-
{ name: "ProjectId", storage: "text", interpretation: "plain" },
|
|
8120
|
-
{ name: "KanbanStatus", storage: "text", interpretation: { kind: "select", options: ["backlog", "ready", "in_progress", "review", "done"] } },
|
|
8121
|
-
{ name: "Order", storage: "number", interpretation: "plain" },
|
|
8122
|
-
{ name: "AssignedUser", storage: "text", interpretation: { kind: "json" } },
|
|
8123
|
-
{ name: "AssignedBy", storage: "text", interpretation: { kind: "json" } },
|
|
8124
|
-
{ name: "TagIds", storage: "text", interpretation: { kind: "json" } },
|
|
8125
|
-
{ name: "CreatedAt", storage: "number", interpretation: "plain" }
|
|
8126
|
-
],
|
|
8127
|
-
teamField: "TeamId",
|
|
8128
|
-
permissions: {
|
|
8129
|
-
admin: { read: true, create: true, update: true, delete: true },
|
|
8130
|
-
member: { read: "team", create: true, update: "team", delete: "team" },
|
|
8131
|
-
viewer: { read: false, create: false, update: false, delete: false }
|
|
8132
|
-
}
|
|
8133
|
-
};
|
|
8134
|
-
var workspaceProjectsSchema = {
|
|
8135
|
-
name: "projects",
|
|
8136
|
-
columns: [
|
|
8137
|
-
{ name: "TeamId", storage: "text", interpretation: "plain" },
|
|
8138
|
-
{ name: "Title", storage: "text", interpretation: "plain" },
|
|
8139
|
-
{ name: "Notes", storage: "text", interpretation: "plain" },
|
|
8140
|
-
{ name: "Color", storage: "text", interpretation: "plain" },
|
|
8141
|
-
{ name: "ParentId", storage: "text", interpretation: "plain" },
|
|
8142
|
-
{ name: "Order", storage: "number", interpretation: "plain" },
|
|
8143
|
-
{ name: "CreatedAt", storage: "number", interpretation: "plain" }
|
|
8144
|
-
],
|
|
8145
|
-
teamField: "TeamId",
|
|
8146
|
-
permissions: {
|
|
8147
|
-
admin: { read: true, create: true, update: true, delete: true },
|
|
8148
|
-
member: { read: "team", create: true, update: "team", delete: "team" },
|
|
8149
|
-
viewer: { read: false, create: false, update: false, delete: false }
|
|
8150
|
-
}
|
|
8151
|
-
};
|
|
8152
|
-
var workspaceTagsSchema = {
|
|
8153
|
-
name: "tags",
|
|
8154
|
-
columns: [
|
|
8155
|
-
{ name: "TeamId", storage: "text", interpretation: "plain" },
|
|
8156
|
-
{ name: "Name", storage: "text", interpretation: "plain" },
|
|
8157
|
-
{ name: "Color", storage: "text", interpretation: "plain" },
|
|
8158
|
-
{ name: "CreatedAt", storage: "number", interpretation: "plain" }
|
|
8159
|
-
],
|
|
8160
|
-
teamField: "TeamId",
|
|
8161
|
-
permissions: {
|
|
8162
|
-
admin: { read: true, create: true, update: true, delete: true },
|
|
8163
|
-
member: { read: "team", create: true, update: "team", delete: "team" },
|
|
8164
|
-
viewer: { read: false, create: false, update: false, delete: false }
|
|
8165
|
-
}
|
|
8166
|
-
};
|
|
8167
|
-
var workspacePeopleSchema = {
|
|
8168
|
-
name: "people",
|
|
8169
|
-
columns: [
|
|
8170
|
-
{ name: "Name", storage: "text", interpretation: "plain" },
|
|
8171
|
-
{ name: "Email", storage: "text", interpretation: { kind: "email" } },
|
|
8172
|
-
{ name: "Type", storage: "text", interpretation: { kind: "select", options: ["employee", "customer", "vendor", "contact"] } },
|
|
8173
|
-
{ name: "Status", storage: "text", interpretation: { kind: "select", options: ["active", "inactive", "archived"] } },
|
|
8174
|
-
{ name: "CompanyId", storage: "text", interpretation: "plain" },
|
|
8175
|
-
{ name: "Department", storage: "text", interpretation: "plain" },
|
|
8176
|
-
{ name: "Title", storage: "text", interpretation: "plain" },
|
|
8177
|
-
{ name: "LastContactedAt", storage: "text", interpretation: { kind: "datetime" } },
|
|
8178
|
-
{ name: "Metadata", storage: "text", interpretation: { kind: "json" } }
|
|
8179
|
-
],
|
|
8180
|
-
permissions: {
|
|
8181
|
-
"*": { read: true, create: true, update: true, delete: false },
|
|
8182
|
-
admin: { read: true, create: true, update: true, delete: true }
|
|
8183
|
-
}
|
|
8184
|
-
};
|
|
8185
|
-
var workspaceTransactionsSchema = {
|
|
8186
|
-
name: "transactions",
|
|
8187
|
-
columns: [
|
|
8188
|
-
{ name: "Type", storage: "text", interpretation: { kind: "select", options: ["revenue", "expense", "payment", "transfer", "invoice", "refund"] } },
|
|
8189
|
-
{ name: "Amount", storage: "number", interpretation: { kind: "currency", symbol: "$", decimals: 2 } },
|
|
8190
|
-
{ name: "Currency", storage: "text", interpretation: "plain" },
|
|
8191
|
-
{ name: "Description", storage: "text", interpretation: "plain" },
|
|
8192
|
-
{ name: "Category", storage: "text", interpretation: "plain" },
|
|
8193
|
-
{ name: "CounterpartyRef", storage: "text", interpretation: "plain" },
|
|
8194
|
-
{ name: "OrderRef", storage: "text", interpretation: "plain" },
|
|
8195
|
-
{ name: "Account", storage: "text", interpretation: "plain" }
|
|
8196
|
-
],
|
|
8197
|
-
permissions: {
|
|
8198
|
-
"*": { read: true, create: true, update: true, delete: false },
|
|
8199
|
-
admin: { read: true, create: true, update: true, delete: true }
|
|
8200
|
-
}
|
|
8201
|
-
};
|
|
8202
|
-
var workspaceAccountsSchema = {
|
|
8203
|
-
name: "accounts",
|
|
8204
|
-
columns: [
|
|
8205
|
-
{ name: "Name", storage: "text", interpretation: "plain" },
|
|
8206
|
-
{ name: "Balance", storage: "number", interpretation: { kind: "currency", symbol: "$", decimals: 2 } },
|
|
8207
|
-
{ name: "Currency", storage: "text", interpretation: "plain" }
|
|
8208
|
-
],
|
|
8209
|
-
permissions: {
|
|
8210
|
-
"*": { read: true, create: true, update: true, delete: false },
|
|
8211
|
-
admin: { read: true, create: true, update: true, delete: true }
|
|
8212
|
-
}
|
|
8213
|
-
};
|
|
8214
|
-
var workspaceContentSharesSchema = {
|
|
8215
|
-
name: "content_shares",
|
|
8216
|
-
ownerField: "OwnerId",
|
|
8217
|
-
columns: [
|
|
8218
|
-
{ name: "ContentType", storage: "text", interpretation: { kind: "select", options: ["document", "slide", "spreadsheet"] } },
|
|
8219
|
-
{ name: "ContentId", storage: "text", interpretation: "plain" },
|
|
8220
|
-
{ name: "OwnerId", storage: "text", interpretation: "plain" },
|
|
8221
|
-
{ name: "OwnerName", storage: "text", interpretation: "plain" },
|
|
8222
|
-
{ name: "Title", storage: "text", interpretation: "plain" },
|
|
8223
|
-
{ name: "ShareType", storage: "text", interpretation: { kind: "select", options: ["self", "channel", "team", "direct", "link", "org"] } },
|
|
8224
|
-
{ name: "ShareTarget", storage: "text", interpretation: "plain" },
|
|
8225
|
-
{ name: "Permission", storage: "text", interpretation: { kind: "select", options: ["view", "edit"] } },
|
|
8226
|
-
{ name: "SharedAt", storage: "text", interpretation: { kind: "datetime" } },
|
|
8227
|
-
{ name: "SharedBy", storage: "text", interpretation: "plain" },
|
|
8228
|
-
{ name: "SourceApp", storage: "text", interpretation: "plain" },
|
|
8229
|
-
{ name: "WordCount", storage: "number", interpretation: "plain" },
|
|
8230
|
-
{ name: "LastEditedAt", storage: "text", interpretation: { kind: "datetime" } }
|
|
8231
|
-
],
|
|
8232
|
-
permissions: {
|
|
8233
|
-
"*": { read: true, create: true, update: true, delete: "own" },
|
|
8234
|
-
admin: { read: true, create: true, update: true, delete: true }
|
|
8235
|
-
}
|
|
8236
|
-
};
|
|
8237
|
-
var workspaceFormResponsesSchema = {
|
|
8238
|
-
name: "form_responses",
|
|
8239
|
-
columns: [
|
|
8240
|
-
{ name: "FormId", storage: "text", interpretation: "plain" },
|
|
8241
|
-
{ name: "FormTitle", storage: "text", interpretation: "plain" },
|
|
8242
|
-
{ name: "RespondentId", storage: "text", interpretation: "plain" },
|
|
8243
|
-
{ name: "RespondentEmail", storage: "text", interpretation: { kind: "email" } },
|
|
8244
|
-
{ name: "RespondentName", storage: "text", interpretation: "plain" },
|
|
8245
|
-
{ name: "Data", storage: "text", interpretation: { kind: "json" } },
|
|
8246
|
-
{ name: "SubmittedAt", storage: "text", interpretation: { kind: "datetime" } }
|
|
8247
|
-
],
|
|
8248
|
-
permissions: {
|
|
8249
|
-
"*": { read: true, create: true, update: false, delete: false },
|
|
8250
|
-
admin: { read: true, create: true, update: true, delete: true }
|
|
8251
|
-
}
|
|
8252
|
-
};
|
|
8253
|
-
var workspaceEmailHandlesSchema = {
|
|
8254
|
-
name: "email_handles",
|
|
8255
|
-
columns: [
|
|
8256
|
-
{ name: "UserId", storage: "text", interpretation: "plain" },
|
|
8257
|
-
{ name: "Handle", storage: "text", interpretation: "plain" },
|
|
8258
|
-
{ name: "EmailAddress", storage: "text", interpretation: { kind: "email" } },
|
|
8259
|
-
{ name: "Status", storage: "text", interpretation: { kind: "select", options: ["active", "disabled"] } }
|
|
8260
|
-
],
|
|
8261
|
-
uniqueOn: ["Handle"],
|
|
8262
|
-
ownerField: "UserId",
|
|
8263
|
-
permissions: {
|
|
8264
|
-
"*": { read: true, create: true, update: "own", delete: false },
|
|
8265
|
-
admin: { read: true, create: true, update: true, delete: true }
|
|
8266
|
-
}
|
|
8267
|
-
};
|
|
8268
|
-
var WORKSPACE_SCHEMAS = [
|
|
8269
|
-
workspaceTeamsSchema,
|
|
8270
|
-
workspaceTeamMembersSchema,
|
|
8271
|
-
workspaceTasksSchema,
|
|
8272
|
-
workspaceProjectsSchema,
|
|
8273
|
-
workspaceTagsSchema,
|
|
8274
|
-
workspacePeopleSchema,
|
|
8275
|
-
workspaceTransactionsSchema,
|
|
8276
|
-
workspaceAccountsSchema,
|
|
8277
|
-
workspaceContentSharesSchema,
|
|
8278
|
-
workspaceFormResponsesSchema,
|
|
8279
|
-
workspaceEmailHandlesSchema
|
|
8280
|
-
];
|
|
8281
|
-
|
|
8282
8429
|
// src/server/schemas/shared-do.ts
|
|
8283
8430
|
var GLOBAL_DO_TYPES = [
|
|
8284
|
-
{
|
|
8285
|
-
name: "workspace",
|
|
8286
|
-
schemas: WORKSPACE_SCHEMAS,
|
|
8287
|
-
description: "Shared workspace data (teams, tasks, projects, people, ledger)"
|
|
8288
|
-
},
|
|
8289
8431
|
{
|
|
8290
8432
|
name: "conv",
|
|
8291
8433
|
schemas: [...CONVERSATION_SCHEMAS, ...VOTING_SCHEMAS],
|
|
@@ -8795,6 +8937,7 @@ export {
|
|
|
8795
8937
|
GLOBAL_DO_TYPES,
|
|
8796
8938
|
GLOBAL_DO_TYPE_NAMES,
|
|
8797
8939
|
JobRoom,
|
|
8940
|
+
KnowledgeError,
|
|
8798
8941
|
MAX_APP_FILE_BYTES,
|
|
8799
8942
|
MAX_DELETE_WHERE_LIMIT,
|
|
8800
8943
|
MAX_DEPLOY_ASSET_FILE_BYTES,
|
|
@@ -8823,13 +8966,13 @@ export {
|
|
|
8823
8966
|
UPLOAD_PART_BYTES,
|
|
8824
8967
|
USERS_COLUMNS,
|
|
8825
8968
|
VOTING_SCHEMAS,
|
|
8826
|
-
WORKSPACE_SCHEMAS,
|
|
8827
8969
|
YjsRoom,
|
|
8828
8970
|
analyzePermissions,
|
|
8829
8971
|
apiWorkerFetch,
|
|
8830
8972
|
appendMessage,
|
|
8831
8973
|
applyAiToolDefaults,
|
|
8832
8974
|
applySlidingWindow,
|
|
8975
|
+
armCronRoom,
|
|
8833
8976
|
authWorkerFetch,
|
|
8834
8977
|
bindingManifestFromOutputConfig,
|
|
8835
8978
|
broadcastChange,
|
|
@@ -8864,7 +9007,9 @@ export {
|
|
|
8864
9007
|
deleteChatCascade,
|
|
8865
9008
|
deleteRecord,
|
|
8866
9009
|
enqueueJob,
|
|
9010
|
+
epochSeconds,
|
|
8867
9011
|
executeQuery,
|
|
9012
|
+
folderFilter,
|
|
8868
9013
|
formatBytes,
|
|
8869
9014
|
getAllUsers,
|
|
8870
9015
|
getChat,
|
|
@@ -8894,6 +9039,7 @@ export {
|
|
|
8894
9039
|
isTestAccountClaims,
|
|
8895
9040
|
isTestAccountEmail,
|
|
8896
9041
|
isTestAccountTier,
|
|
9042
|
+
knowledge,
|
|
8897
9043
|
lintSchema,
|
|
8898
9044
|
lintSchemaSet,
|
|
8899
9045
|
lintSchemas,
|
|
@@ -8906,6 +9052,7 @@ export {
|
|
|
8906
9052
|
mintAppId,
|
|
8907
9053
|
mintUlid,
|
|
8908
9054
|
noopPermissionContext,
|
|
9055
|
+
normalizeKnowledgeFolder,
|
|
8909
9056
|
parseQuotaLimit,
|
|
8910
9057
|
platformCorsPatterns,
|
|
8911
9058
|
platformWorkerFetch,
|
|
@@ -8938,17 +9085,6 @@ export {
|
|
|
8938
9085
|
validateAppName,
|
|
8939
9086
|
validateBindingManifest,
|
|
8940
9087
|
validateDoManifest,
|
|
8941
|
-
verifyJwt
|
|
8942
|
-
workspaceAccountsSchema,
|
|
8943
|
-
workspaceContentSharesSchema,
|
|
8944
|
-
workspaceEmailHandlesSchema,
|
|
8945
|
-
workspaceFormResponsesSchema,
|
|
8946
|
-
workspacePeopleSchema,
|
|
8947
|
-
workspaceProjectsSchema,
|
|
8948
|
-
workspaceTagsSchema,
|
|
8949
|
-
workspaceTasksSchema,
|
|
8950
|
-
workspaceTeamMembersSchema,
|
|
8951
|
-
workspaceTeamsSchema,
|
|
8952
|
-
workspaceTransactionsSchema
|
|
9088
|
+
verifyJwt
|
|
8953
9089
|
};
|
|
8954
9090
|
//# sourceMappingURL=server.js.map
|