@blotout/qinto-cli 0.1.8 → 0.1.10
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/dist/index.js +3262 -2483
- package/dist/index.js.map +1 -1
- package/dist/worker-artifact/app-runtime/index.js +547 -48
- package/dist/worker-artifact/main/9026260a7a674b710223f296c5d2e7a01930cfed-q.js.txt +1 -0
- package/dist/worker-artifact/main/index.js +549 -51
- package/dist/worker-artifact/version.json +6 -6
- package/package.json +1 -1
- package/templates/app/full/ui/src/pages/index.astro +0 -2
- package/templates/ui/src/pages/index.astro +1 -2
- package/dist/worker-artifact/main/9dcfd236748459af038da772477100c4bb0223b7-q.js.txt +0 -1
|
@@ -739,6 +739,10 @@ var MAX_APPS_Q_BYTES = 4 * 1024 * 1024;
|
|
|
739
739
|
// ../../libs/app-manifest/src/cron.ts
|
|
740
740
|
var MAX_CRON_LENGTH = 128;
|
|
741
741
|
|
|
742
|
+
// ../../libs/app-manifest/src/tool-names.ts
|
|
743
|
+
var MAX_APP_TOOL_NAME_LENGTH = 32;
|
|
744
|
+
var APP_TOOL_NAME_PATTERN = /^[a-zA-Z][a-zA-Z0-9_]*$/;
|
|
745
|
+
|
|
742
746
|
// ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
|
|
743
747
|
var external_exports = {};
|
|
744
748
|
__export(external_exports, {
|
|
@@ -15853,6 +15857,12 @@ var validateEnvelopeResponse = /* @__PURE__ */ __name((raw2, installCount) => {
|
|
|
15853
15857
|
var CONTROL_ENVELOPE_VERSION = 1;
|
|
15854
15858
|
var MAX_CONTROL_BYTES = 256 * 1024;
|
|
15855
15859
|
var MAX_MIGRATION_CHECKPOINT_BYTES = 16 * 1024;
|
|
15860
|
+
var MAX_TOOL_ARGUMENTS_BYTES = 32 * 1024;
|
|
15861
|
+
var MAX_TOOL_RESULT_BYTES = 64 * 1024;
|
|
15862
|
+
var MAX_TOOL_DATA_DEPTH = 8;
|
|
15863
|
+
var MAX_TOOL_DATA_NODES = 5e3;
|
|
15864
|
+
var CONTROL_FORWARD_TIMEOUT_MS = 1e4;
|
|
15865
|
+
var CONTROL_TOOL_FORWARD_TIMEOUT_MS = 5e4;
|
|
15856
15866
|
var installSchema2 = external_exports.object({
|
|
15857
15867
|
id: external_exports.string().min(1).max(64),
|
|
15858
15868
|
ord: external_exports.number().int().min(1),
|
|
@@ -15926,15 +15936,32 @@ var scheduleSchema = external_exports.object({
|
|
|
15926
15936
|
}).strict()
|
|
15927
15937
|
).min(1).max(MAX_INSTALLS_PER_APP)
|
|
15928
15938
|
}).strict();
|
|
15939
|
+
var toolSchema = external_exports.object({
|
|
15940
|
+
// The manifest's tools[].name, never the `<slug>_` composed MCP name: the slug is already in
|
|
15941
|
+
// `app.slug`, and carrying it twice would give the app worker two sources for one fact.
|
|
15942
|
+
name: external_exports.string().min(1).max(MAX_APP_TOOL_NAME_LENGTH).regex(APP_TOOL_NAME_PATTERN),
|
|
15943
|
+
// Who asked, and what they may do on this site. Derived from the bearer by the portal, never
|
|
15944
|
+
// from the model's arguments.
|
|
15945
|
+
viewer: external_exports.object({ userId: external_exports.string().min(1).max(64), access: external_exports.enum(["read", "write"]) }).strict(),
|
|
15946
|
+
// Already validated against the tool's declared inputSchema, with the reserved routing names
|
|
15947
|
+
// stripped. Every declared input schema has an object root, so this is an object too — and a
|
|
15948
|
+
// call that names no arguments normalizes to `{}` rather than reaching the handler as undefined.
|
|
15949
|
+
arguments: external_exports.record(external_exports.string(), external_exports.unknown()).default({}),
|
|
15950
|
+
// Stable across a collapsed retry, so an app can deduplicate its external effects on it.
|
|
15951
|
+
callId: external_exports.string().min(1).max(64)
|
|
15952
|
+
}).strict().refine((tool) => {
|
|
15953
|
+
return new TextEncoder().encode(JSON.stringify(tool.arguments)).length <= MAX_TOOL_ARGUMENTS_BYTES;
|
|
15954
|
+
}, "Tool arguments exceed the size cap");
|
|
15929
15955
|
var controlEnvelopeSchema = external_exports.object({
|
|
15930
15956
|
v: external_exports.literal(CONTROL_ENVELOPE_VERSION),
|
|
15931
|
-
kind: external_exports.enum(["noop", "lifecycle", "migration", "schedule"]),
|
|
15957
|
+
kind: external_exports.enum(["noop", "lifecycle", "migration", "schedule", "tool"]),
|
|
15932
15958
|
site: external_exports.object({ origin: external_exports.string().min(1).max(255) }).strict(),
|
|
15933
15959
|
app: external_exports.object({ slug: external_exports.string().min(1).max(30), version: external_exports.string().min(1).max(32) }).strict(),
|
|
15934
15960
|
install: external_exports.object({ id: external_exports.string().min(1).max(64), ord: external_exports.number().int().min(1), label: external_exports.string().max(100) }).strict().optional(),
|
|
15935
15961
|
lifecycle: lifecycleEventSchema.optional(),
|
|
15936
15962
|
migration: migrationSchema.optional(),
|
|
15937
|
-
schedule: scheduleSchema.optional()
|
|
15963
|
+
schedule: scheduleSchema.optional(),
|
|
15964
|
+
tool: toolSchema.optional()
|
|
15938
15965
|
}).strict().superRefine((envelope, ctx) => {
|
|
15939
15966
|
if (envelope.kind === "lifecycle" !== (envelope.lifecycle !== void 0)) {
|
|
15940
15967
|
ctx.addIssue({ code: "custom", path: ["lifecycle"], message: "Lifecycle payload must match the control kind" });
|
|
@@ -15945,13 +15972,50 @@ var controlEnvelopeSchema = external_exports.object({
|
|
|
15945
15972
|
if (envelope.kind === "schedule" !== (envelope.schedule !== void 0)) {
|
|
15946
15973
|
ctx.addIssue({ code: "custom", path: ["schedule"], message: "Schedule payload must match the control kind" });
|
|
15947
15974
|
}
|
|
15975
|
+
if (envelope.kind === "tool" !== (envelope.tool !== void 0)) {
|
|
15976
|
+
ctx.addIssue({ code: "custom", path: ["tool"], message: "Tool payload must match the control kind" });
|
|
15977
|
+
}
|
|
15948
15978
|
if (envelope.kind === "lifecycle" && envelope.install === void 0) {
|
|
15949
15979
|
ctx.addIssue({ code: "custom", path: ["install"], message: "Lifecycle control needs an install" });
|
|
15950
15980
|
}
|
|
15981
|
+
if (envelope.kind === "tool" && envelope.install === void 0) {
|
|
15982
|
+
ctx.addIssue({ code: "custom", path: ["install"], message: "Tool control needs an install" });
|
|
15983
|
+
}
|
|
15951
15984
|
});
|
|
15985
|
+
var toolDataWithinLimits = /* @__PURE__ */ __name((value) => {
|
|
15986
|
+
const stack = [{ value, depth: 0 }];
|
|
15987
|
+
let seen = 0;
|
|
15988
|
+
while (stack.length > 0) {
|
|
15989
|
+
const next = stack.pop();
|
|
15990
|
+
seen += 1;
|
|
15991
|
+
if (next.depth > MAX_TOOL_DATA_DEPTH || seen > MAX_TOOL_DATA_NODES) {
|
|
15992
|
+
return false;
|
|
15993
|
+
}
|
|
15994
|
+
if (next.value !== null && typeof next.value === "object") {
|
|
15995
|
+
for (const child of Object.values(next.value)) {
|
|
15996
|
+
stack.push({ value: child, depth: next.depth + 1 });
|
|
15997
|
+
}
|
|
15998
|
+
}
|
|
15999
|
+
}
|
|
16000
|
+
return true;
|
|
16001
|
+
}, "toolDataWithinLimits");
|
|
16002
|
+
var toolResultSchema = external_exports.object({
|
|
16003
|
+
content: external_exports.string().max(MAX_TOOL_RESULT_BYTES),
|
|
16004
|
+
// MCP defines `structuredContent` as an object, so a bare scalar or array is refused here rather
|
|
16005
|
+
// than surfacing as a protocol error at the client.
|
|
16006
|
+
data: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
|
|
16007
|
+
// True when the handler chose to fail with `ctx.fail`: `content` is then the app's own message
|
|
16008
|
+
// for the model. A thrown handler is `ok: false` with no `tool` at all.
|
|
16009
|
+
failed: external_exports.boolean()
|
|
16010
|
+
}).strict().refine((result) => {
|
|
16011
|
+
return toolDataWithinLimits(result.data);
|
|
16012
|
+
}, "Tool result nests too deeply or carries too many values").refine((result) => {
|
|
16013
|
+
return new TextEncoder().encode(JSON.stringify({ content: result.content, data: result.data ?? null })).length <= MAX_TOOL_RESULT_BYTES;
|
|
16014
|
+
}, "Tool result exceeds the size cap");
|
|
15952
16015
|
var controlResponseSchema = external_exports.object({
|
|
15953
16016
|
v: external_exports.literal(CONTROL_ENVELOPE_VERSION),
|
|
15954
16017
|
ok: external_exports.boolean(),
|
|
16018
|
+
tool: toolResultSchema.optional(),
|
|
15955
16019
|
migration: external_exports.union([
|
|
15956
16020
|
external_exports.object({ done: external_exports.literal(true) }).strict(),
|
|
15957
16021
|
external_exports.object({ done: external_exports.literal(false), checkpoint: external_exports.unknown() }).strict().refine((result) => {
|
|
@@ -15966,12 +16030,21 @@ var ENDPOINT_CONTEXT_HEADER = "x-qinto-endpoint-context";
|
|
|
15966
16030
|
var ENDPOINT_OUTCOMES_HEADER = "x-qinto-endpoint-outcomes";
|
|
15967
16031
|
var MAX_ENDPOINT_BODY_BYTES = 1024 * 1024;
|
|
15968
16032
|
var MAX_ENDPOINT_OUTCOMES_BYTES = 64 * 1024;
|
|
16033
|
+
var MAX_ENDPOINT_PATH_LENGTH = 16 * 1024;
|
|
15969
16034
|
var installSchema3 = external_exports.object({ id: external_exports.string().min(1).max(64), ord: external_exports.number().int().min(1), label: external_exports.string().max(100) }).strict();
|
|
15970
16035
|
var endpointContextSchema = external_exports.object({
|
|
15971
|
-
path
|
|
16036
|
+
// The declared template, which keys the bundle's handler map; `path` is what the caller asked
|
|
16037
|
+
// for. Only `route` may key the rate limiter — a concrete path gives every parameter value its
|
|
16038
|
+
// own bucket, which is no limit at all.
|
|
16039
|
+
route: external_exports.string().min(1).max(255),
|
|
16040
|
+
path: external_exports.string().min(1).max(MAX_ENDPOINT_PATH_LENGTH),
|
|
16041
|
+
// A name is cut from `route`, so the route's own cap bounds it; anything tighter would reject a
|
|
16042
|
+
// parameter the manifest grammar accepts and the worker already matched.
|
|
16043
|
+
params: external_exports.record(external_exports.string().min(1).max(255), external_exports.string().max(MAX_ENDPOINT_PATH_LENGTH)).default({}),
|
|
15972
16044
|
method: external_exports.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]),
|
|
15973
16045
|
mode: external_exports.enum(["webhook", "api"]),
|
|
15974
16046
|
version: external_exports.string().min(1).max(32),
|
|
16047
|
+
site: external_exports.object({ origin: external_exports.string().min(1).max(255) }).strict(),
|
|
15975
16048
|
installs: external_exports.array(installSchema3).min(1).max(15)
|
|
15976
16049
|
}).strict().refine((context) => {
|
|
15977
16050
|
return context.mode === "webhook" || context.installs.length === 1;
|
|
@@ -15998,6 +16071,71 @@ var aggregateEndpointOutcomes = /* @__PURE__ */ __name((outcomes) => {
|
|
|
15998
16071
|
return { status: failed ? 502 : 200 };
|
|
15999
16072
|
}, "aggregateEndpointOutcomes");
|
|
16000
16073
|
|
|
16074
|
+
// ../../libs/app-envelope/src/observability.ts
|
|
16075
|
+
var APP_OBSERVABILITY_HEADER = "x-qinto-app-observability";
|
|
16076
|
+
var APP_OBSERVABILITY_VERSION = 1;
|
|
16077
|
+
var MAX_APP_OBSERVABILITY_EVENTS = 20;
|
|
16078
|
+
var MAX_APP_SAVED_ERRORS = 5;
|
|
16079
|
+
var MAX_APP_EVENT_NAME_BYTES = 128;
|
|
16080
|
+
var MAX_APP_ACTION_NAME_BYTES = 256;
|
|
16081
|
+
var MAX_APP_ERROR_DESCRIPTION_BYTES = 2048;
|
|
16082
|
+
var MAX_APP_ERROR_PAYLOAD_BYTES = 4096;
|
|
16083
|
+
var MAX_APP_DATA_POINT_BLOB_BYTES = 16 * 1024;
|
|
16084
|
+
var MAX_APP_OBSERVABILITY_BYTES = 32 * 1024;
|
|
16085
|
+
var APP_OBSERVABILITY_JSON_BUDGET = MAX_APP_OBSERVABILITY_BYTES / 4 * 3;
|
|
16086
|
+
var MAX_APP_DATA_POINTS_PER_INVOCATION = 250;
|
|
16087
|
+
var APP_EVENT_STATUSES = ["success", "error"];
|
|
16088
|
+
var APP_ERROR_CATEGORIES = ["authorization", "configuration", "validation", "rate_limit", "upstream", "internal"];
|
|
16089
|
+
var encoder3 = new TextEncoder();
|
|
16090
|
+
var decoder2 = new TextDecoder();
|
|
16091
|
+
var observabilityEventSchema = external_exports.object({ ord: external_exports.number().int().min(1), name: external_exports.string().min(1).max(MAX_APP_EVENT_NAME_BYTES), status: external_exports.enum(APP_EVENT_STATUSES) }).strict();
|
|
16092
|
+
var savedErrorSchema = external_exports.object({
|
|
16093
|
+
ord: external_exports.number().int().min(1),
|
|
16094
|
+
category: external_exports.enum(APP_ERROR_CATEGORIES),
|
|
16095
|
+
action: external_exports.string().min(1).max(MAX_APP_ACTION_NAME_BYTES),
|
|
16096
|
+
description: external_exports.string().min(1).max(MAX_APP_ERROR_DESCRIPTION_BYTES),
|
|
16097
|
+
payload: external_exports.string().max(MAX_APP_ERROR_PAYLOAD_BYTES).optional(),
|
|
16098
|
+
error: external_exports.string().max(MAX_APP_ERROR_PAYLOAD_BYTES).optional()
|
|
16099
|
+
}).strict();
|
|
16100
|
+
var appObservabilityRecordSchema = external_exports.object({
|
|
16101
|
+
v: external_exports.literal(APP_OBSERVABILITY_VERSION),
|
|
16102
|
+
truncated: external_exports.boolean().optional(),
|
|
16103
|
+
events: external_exports.array(observabilityEventSchema).max(MAX_APP_OBSERVABILITY_EVENTS),
|
|
16104
|
+
errors: external_exports.array(savedErrorSchema).max(MAX_APP_SAVED_ERRORS)
|
|
16105
|
+
}).strict();
|
|
16106
|
+
var utf8Length = /* @__PURE__ */ __name((value) => {
|
|
16107
|
+
return encoder3.encode(value).length;
|
|
16108
|
+
}, "utf8Length");
|
|
16109
|
+
var truncateUtf8 = /* @__PURE__ */ __name((value, maxBytes) => {
|
|
16110
|
+
if (utf8Length(value) <= maxBytes) {
|
|
16111
|
+
return value;
|
|
16112
|
+
}
|
|
16113
|
+
let kept = "";
|
|
16114
|
+
let used = 0;
|
|
16115
|
+
for (const character of value) {
|
|
16116
|
+
const size = utf8Length(character);
|
|
16117
|
+
if (used + size > maxBytes) {
|
|
16118
|
+
break;
|
|
16119
|
+
}
|
|
16120
|
+
kept += character;
|
|
16121
|
+
used += size;
|
|
16122
|
+
}
|
|
16123
|
+
return kept;
|
|
16124
|
+
}, "truncateUtf8");
|
|
16125
|
+
var parseAppObservability = /* @__PURE__ */ __name((header) => {
|
|
16126
|
+
if (header === null || header.length > MAX_APP_OBSERVABILITY_BYTES) {
|
|
16127
|
+
return null;
|
|
16128
|
+
}
|
|
16129
|
+
let parsed;
|
|
16130
|
+
try {
|
|
16131
|
+
parsed = JSON.parse(decoder2.decode(Uint8Array.from(atob(header), (character) => character.charCodeAt(0))));
|
|
16132
|
+
} catch {
|
|
16133
|
+
return null;
|
|
16134
|
+
}
|
|
16135
|
+
const result = appObservabilityRecordSchema.safeParse(parsed);
|
|
16136
|
+
return result.success ? result.data : null;
|
|
16137
|
+
}, "parseAppObservability");
|
|
16138
|
+
|
|
16001
16139
|
// ../../libs/app-envelope/src/ui.ts
|
|
16002
16140
|
var UI_CONTEXT_HEADER = "x-qinto-ui-context";
|
|
16003
16141
|
var MAX_UI_BODY_BYTES = 1024 * 1024;
|
|
@@ -16204,13 +16342,13 @@ var getPattern = /* @__PURE__ */ __name((label, next) => {
|
|
|
16204
16342
|
}
|
|
16205
16343
|
return null;
|
|
16206
16344
|
}, "getPattern");
|
|
16207
|
-
var tryDecode = /* @__PURE__ */ __name((str,
|
|
16345
|
+
var tryDecode = /* @__PURE__ */ __name((str, decoder3) => {
|
|
16208
16346
|
try {
|
|
16209
|
-
return
|
|
16347
|
+
return decoder3(str);
|
|
16210
16348
|
} catch {
|
|
16211
16349
|
return str.replace(/(?:%[0-9A-Fa-f]{2})+/g, (match2) => {
|
|
16212
16350
|
try {
|
|
16213
|
-
return
|
|
16351
|
+
return decoder3(match2);
|
|
16214
16352
|
} catch {
|
|
16215
16353
|
return match2;
|
|
16216
16354
|
}
|
|
@@ -18977,6 +19115,118 @@ var readableContext = /* @__PURE__ */ __name((own, shared) => {
|
|
|
18977
19115
|
return { private: own === void 0 ? null : wireEntry(own), shared: sharedContextWire(shared) };
|
|
18978
19116
|
}, "readableContext");
|
|
18979
19117
|
|
|
19118
|
+
// src/apps/observability.ts
|
|
19119
|
+
var APP_ERROR_DATA_POINT_RESERVE = 50;
|
|
19120
|
+
var observabilityApp = /* @__PURE__ */ __name((slug, app2) => {
|
|
19121
|
+
return { slug, name: app2.name, version: app2.version };
|
|
19122
|
+
}, "observabilityApp");
|
|
19123
|
+
var createDataPointBudget = /* @__PURE__ */ __name(() => {
|
|
19124
|
+
let events = 0;
|
|
19125
|
+
let total = 0;
|
|
19126
|
+
return {
|
|
19127
|
+
take: /* @__PURE__ */ __name((kind2) => {
|
|
19128
|
+
if (total >= MAX_APP_DATA_POINTS_PER_INVOCATION) {
|
|
19129
|
+
return false;
|
|
19130
|
+
}
|
|
19131
|
+
if (kind2 === "event") {
|
|
19132
|
+
if (events >= MAX_APP_DATA_POINTS_PER_INVOCATION - APP_ERROR_DATA_POINT_RESERVE) {
|
|
19133
|
+
return false;
|
|
19134
|
+
}
|
|
19135
|
+
events += 1;
|
|
19136
|
+
}
|
|
19137
|
+
total += 1;
|
|
19138
|
+
return true;
|
|
19139
|
+
}, "take")
|
|
19140
|
+
};
|
|
19141
|
+
}, "createDataPointBudget");
|
|
19142
|
+
var totalLength = /* @__PURE__ */ __name((blobs) => {
|
|
19143
|
+
return blobs.reduce((sum, blob) => {
|
|
19144
|
+
return sum + utf8Length(blob);
|
|
19145
|
+
}, 0);
|
|
19146
|
+
}, "totalLength");
|
|
19147
|
+
var errorBlobs = /* @__PURE__ */ __name((app2, install, record2, rowId) => {
|
|
19148
|
+
const fixed = [app2.slug, app2.name, install.id, install.label, app2.version, record2.category, record2.action, rowId];
|
|
19149
|
+
let remaining = MAX_APP_DATA_POINT_BLOB_BYTES - totalLength(fixed);
|
|
19150
|
+
const description = truncateUtf8(record2.description, Math.max(0, remaining));
|
|
19151
|
+
remaining -= utf8Length(description);
|
|
19152
|
+
const payload = truncateUtf8(record2.payload ?? "", Math.max(0, remaining));
|
|
19153
|
+
remaining -= utf8Length(payload);
|
|
19154
|
+
const error51 = truncateUtf8(record2.error ?? "", Math.max(0, remaining));
|
|
19155
|
+
return [app2.slug, app2.name, install.id, install.label, app2.version, record2.category, record2.action, description, payload, error51, rowId];
|
|
19156
|
+
}, "errorBlobs");
|
|
19157
|
+
var eventBlobs = /* @__PURE__ */ __name((app2, install, record2) => {
|
|
19158
|
+
return [app2.slug, app2.name, install.id, install.label, app2.version, record2.name, record2.status];
|
|
19159
|
+
}, "eventBlobs");
|
|
19160
|
+
var writeEvents = /* @__PURE__ */ __name((env, target, byOrd, events) => {
|
|
19161
|
+
for (const [index, event] of events.entries()) {
|
|
19162
|
+
const install = byOrd.get(event.ord);
|
|
19163
|
+
if (install === void 0) {
|
|
19164
|
+
addSpanEvent("app observability names an install the site did not send", { level: "warn", attrs: { app: target.app.slug } });
|
|
19165
|
+
continue;
|
|
19166
|
+
}
|
|
19167
|
+
if (!target.budget.take("event")) {
|
|
19168
|
+
addSpanEvent("app observability dropped events at the data-point cap", {
|
|
19169
|
+
level: "warn",
|
|
19170
|
+
attrs: { app: target.app.slug, skipped: events.length - index }
|
|
19171
|
+
});
|
|
19172
|
+
break;
|
|
19173
|
+
}
|
|
19174
|
+
env.APP_EVENTS.writeDataPoint({ indexes: [`${install.id}:${event.status}`], blobs: eventBlobs(target.app, install, event) });
|
|
19175
|
+
}
|
|
19176
|
+
}, "writeEvents");
|
|
19177
|
+
var writeErrors = /* @__PURE__ */ __name((env, target, byOrd, errors) => {
|
|
19178
|
+
for (const [index, saved] of errors.entries()) {
|
|
19179
|
+
const install = byOrd.get(saved.ord);
|
|
19180
|
+
if (install === void 0) {
|
|
19181
|
+
addSpanEvent("app observability names an install the site did not send", { level: "warn", attrs: { app: target.app.slug } });
|
|
19182
|
+
continue;
|
|
19183
|
+
}
|
|
19184
|
+
if (!target.budget.take("error")) {
|
|
19185
|
+
addSpanEvent("app observability dropped saved errors at the data-point cap", {
|
|
19186
|
+
level: "warn",
|
|
19187
|
+
attrs: { app: target.app.slug, skipped: errors.length - index }
|
|
19188
|
+
});
|
|
19189
|
+
break;
|
|
19190
|
+
}
|
|
19191
|
+
env.APP_ERRORS.writeDataPoint({
|
|
19192
|
+
indexes: [`${install.id}:${saved.category}`],
|
|
19193
|
+
blobs: errorBlobs(target.app, install, saved, ulid3())
|
|
19194
|
+
});
|
|
19195
|
+
}
|
|
19196
|
+
}, "writeErrors");
|
|
19197
|
+
var guarded = /* @__PURE__ */ __name((target, write) => {
|
|
19198
|
+
try {
|
|
19199
|
+
write();
|
|
19200
|
+
return true;
|
|
19201
|
+
} catch (error51) {
|
|
19202
|
+
addSpanEvent("app observability write failed", { level: "warn", attrs: { app: target.app.slug, error: String(error51) } });
|
|
19203
|
+
return false;
|
|
19204
|
+
}
|
|
19205
|
+
}, "guarded");
|
|
19206
|
+
var writeAppObservability = /* @__PURE__ */ __name((env, response, target) => {
|
|
19207
|
+
const record2 = parseAppObservability(response.headers.get(APP_OBSERVABILITY_HEADER));
|
|
19208
|
+
if (record2 === null) {
|
|
19209
|
+
return;
|
|
19210
|
+
}
|
|
19211
|
+
const byOrd = new Map(
|
|
19212
|
+
target.installs.map((install) => {
|
|
19213
|
+
return [install.ord, install];
|
|
19214
|
+
})
|
|
19215
|
+
);
|
|
19216
|
+
const eventsWritten = guarded(target, () => {
|
|
19217
|
+
writeEvents(env, target, byOrd, record2.events);
|
|
19218
|
+
});
|
|
19219
|
+
const errorsWritten = guarded(target, () => {
|
|
19220
|
+
writeErrors(env, target, byOrd, record2.errors);
|
|
19221
|
+
});
|
|
19222
|
+
if (!eventsWritten || !errorsWritten) {
|
|
19223
|
+
return;
|
|
19224
|
+
}
|
|
19225
|
+
if (record2.truncated === true) {
|
|
19226
|
+
addSpanEvent("the app recorded more observability than one invocation carries", { level: "warn", attrs: { app: target.app.slug } });
|
|
19227
|
+
}
|
|
19228
|
+
}, "writeAppObservability");
|
|
19229
|
+
|
|
18980
19230
|
// src/apps/read-bounded.ts
|
|
18981
19231
|
var readBoundedBody = /* @__PURE__ */ __name(async (message, maxBytes) => {
|
|
18982
19232
|
const reader = message.body?.getReader();
|
|
@@ -19027,7 +19277,7 @@ var adoptAppSpans = /* @__PURE__ */ __name((response) => {
|
|
|
19027
19277
|
}, "adoptAppSpans");
|
|
19028
19278
|
|
|
19029
19279
|
// src/apps/invoke.ts
|
|
19030
|
-
var
|
|
19280
|
+
var APP_INVOCATION_TIMEOUT_MS = 1e4;
|
|
19031
19281
|
var MAX_EMIT_DEPTH = 2;
|
|
19032
19282
|
var HOOK_URL = "https://app/__qinto/hook";
|
|
19033
19283
|
var invocationContext = /* @__PURE__ */ __name((env) => {
|
|
@@ -19233,19 +19483,24 @@ var hookResponseBody = /* @__PURE__ */ __name(async (response, slug) => {
|
|
|
19233
19483
|
}
|
|
19234
19484
|
return new TextDecoder().decode(bytes);
|
|
19235
19485
|
}, "hookResponseBody");
|
|
19236
|
-
var runInvokeApp = /* @__PURE__ */ __name(async (env, { target, trigger, snapshot, context, deliverPush, writeAttribution: writeAttribution2, writeContext: writeContext2 }) => {
|
|
19486
|
+
var runInvokeApp = /* @__PURE__ */ __name(async (env, { target, trigger, snapshot, context, deliverPush, writeAttribution: writeAttribution2, writeContext: writeContext2, budget }) => {
|
|
19237
19487
|
try {
|
|
19238
19488
|
const key = await deriveAppInternalKey(context.master, context.domainId, target.slug);
|
|
19239
19489
|
const response = await postHookEnvelope(env, {
|
|
19240
19490
|
binding: target.app.binding,
|
|
19241
19491
|
key,
|
|
19242
19492
|
envelope: buildEnvelope(target, trigger, snapshot, context),
|
|
19243
|
-
timeoutMs:
|
|
19493
|
+
timeoutMs: APP_INVOCATION_TIMEOUT_MS
|
|
19244
19494
|
});
|
|
19245
19495
|
if (response === void 0) {
|
|
19246
19496
|
addSpanEvent("app service binding is missing", { level: "warn", attrs: { app: target.slug, binding: target.app.binding } });
|
|
19247
19497
|
return;
|
|
19248
19498
|
}
|
|
19499
|
+
writeAppObservability(env, response, {
|
|
19500
|
+
app: observabilityApp(target.slug, target.app),
|
|
19501
|
+
installs: target.installs,
|
|
19502
|
+
budget
|
|
19503
|
+
});
|
|
19249
19504
|
setSpanAttributes({ "app.status": response.status });
|
|
19250
19505
|
const body = await hookResponseBody(response, target.slug);
|
|
19251
19506
|
if (body === void 0) {
|
|
@@ -19329,6 +19584,9 @@ var installSchema4 = external_exports.object({
|
|
|
19329
19584
|
});
|
|
19330
19585
|
var appSchema = external_exports.object({
|
|
19331
19586
|
binding: external_exports.string().min(1),
|
|
19587
|
+
// Required, with no default, for the same reason `events` is: the display name is stored on every
|
|
19588
|
+
// observability data point, and a default would be a compatibility path for an older deploy.
|
|
19589
|
+
name: external_exports.string().min(1).max(100),
|
|
19332
19590
|
version: external_exports.string().min(1),
|
|
19333
19591
|
// Absent from an old embedded registry; the default never equals a real claim, so the ui proxy
|
|
19334
19592
|
// refuses stale registries with token_stale instead of turning the app off.
|
|
@@ -19616,11 +19874,16 @@ var runRelayRpc = /* @__PURE__ */ __name(async (env, { context, body, callee, in
|
|
|
19616
19874
|
// JSON.stringify drops an undefined key, and the envelope requires `args` to be present.
|
|
19617
19875
|
rpc: { caller: body.slug, fn: body.fn, args: body.args ?? null }
|
|
19618
19876
|
},
|
|
19619
|
-
timeoutMs:
|
|
19877
|
+
timeoutMs: APP_INVOCATION_TIMEOUT_MS
|
|
19620
19878
|
});
|
|
19621
19879
|
if (response === void 0) {
|
|
19622
19880
|
return calleeFailed(body.callee, "The callee service binding is missing.");
|
|
19623
19881
|
}
|
|
19882
|
+
writeAppObservability(env, response, {
|
|
19883
|
+
app: observabilityApp(body.callee, callee),
|
|
19884
|
+
installs: [install],
|
|
19885
|
+
budget: createDataPointBudget()
|
|
19886
|
+
});
|
|
19624
19887
|
setSpanAttributes({ "app.status": response.status });
|
|
19625
19888
|
if (!response.ok) {
|
|
19626
19889
|
return calleeFailed(body.callee, `The callee answered status ${response.status}.`);
|
|
@@ -19888,6 +20151,18 @@ var controlTargets = /* @__PURE__ */ __name(() => {
|
|
|
19888
20151
|
// src/apps/control.ts
|
|
19889
20152
|
var CONTROL_PATH = "/__qinto/control";
|
|
19890
20153
|
var MAX_CLOCK_SKEW_MS = 5 * 60 * 1e3;
|
|
20154
|
+
var writeControlObservability = /* @__PURE__ */ __name((env, upstream, envelope) => {
|
|
20155
|
+
const installs = envelope.schedule?.installs ?? (envelope.install === void 0 ? [] : [envelope.install]);
|
|
20156
|
+
const app2 = siteRegistry().registry.apps[envelope.app.slug];
|
|
20157
|
+
if (installs.length === 0 || app2 === void 0) {
|
|
20158
|
+
return;
|
|
20159
|
+
}
|
|
20160
|
+
writeAppObservability(env, upstream, {
|
|
20161
|
+
app: observabilityApp(envelope.app.slug, { name: app2.name, version: envelope.app.version }),
|
|
20162
|
+
installs,
|
|
20163
|
+
budget: createDataPointBudget()
|
|
20164
|
+
});
|
|
20165
|
+
}, "writeControlObservability");
|
|
19891
20166
|
var textBody = /* @__PURE__ */ __name(async (request) => {
|
|
19892
20167
|
const bytes = await readBoundedBody(request, MAX_CONTROL_BYTES);
|
|
19893
20168
|
return bytes === null ? null : new TextDecoder().decode(bytes);
|
|
@@ -19953,9 +20228,12 @@ var handleControl = /* @__PURE__ */ __name(async (request, env) => {
|
|
|
19953
20228
|
method: "POST",
|
|
19954
20229
|
headers,
|
|
19955
20230
|
body,
|
|
19956
|
-
|
|
20231
|
+
// Strictly inside the portal's own deadline for the same kind, so a slow tool times out here
|
|
20232
|
+
// first and the failure is attributable to the app worker rather than to the hop above it.
|
|
20233
|
+
signal: AbortSignal.timeout(envelope.kind === "tool" ? CONTROL_TOOL_FORWARD_TIMEOUT_MS : CONTROL_FORWARD_TIMEOUT_MS)
|
|
19957
20234
|
});
|
|
19958
20235
|
adoptAppSpans(upstream);
|
|
20236
|
+
writeControlObservability(env, upstream, envelope);
|
|
19959
20237
|
const responseBytes = await readBoundedBody(upstream, MAX_CONTROL_BYTES);
|
|
19960
20238
|
if (responseBytes === null) {
|
|
19961
20239
|
return Response.json({ error: "invalid_response" }, { status: 502 });
|
|
@@ -19974,6 +20252,73 @@ var appControlRoutes = new Hono2().post(CONTROL_PATH, (c) => {
|
|
|
19974
20252
|
return handleControl(c.req.raw, c.env);
|
|
19975
20253
|
});
|
|
19976
20254
|
|
|
20255
|
+
// ../../libs/app-manifest/src/route.ts
|
|
20256
|
+
var isParam = /* @__PURE__ */ __name((segment) => {
|
|
20257
|
+
return segment.startsWith(":");
|
|
20258
|
+
}, "isParam");
|
|
20259
|
+
var splitRoute = /* @__PURE__ */ __name((route) => {
|
|
20260
|
+
return route === "/" ? [] : route.slice(1).split("/");
|
|
20261
|
+
}, "splitRoute");
|
|
20262
|
+
var splitRequest = /* @__PURE__ */ __name((requestPath) => {
|
|
20263
|
+
if (requestPath === "/") {
|
|
20264
|
+
return [];
|
|
20265
|
+
}
|
|
20266
|
+
const decoded = [];
|
|
20267
|
+
for (const segment of requestPath.slice(1).split("/")) {
|
|
20268
|
+
try {
|
|
20269
|
+
decoded.push(decodeURIComponent(segment));
|
|
20270
|
+
} catch {
|
|
20271
|
+
return null;
|
|
20272
|
+
}
|
|
20273
|
+
}
|
|
20274
|
+
return decoded;
|
|
20275
|
+
}, "splitRequest");
|
|
20276
|
+
var matches = /* @__PURE__ */ __name((parts, segments) => {
|
|
20277
|
+
if (parts.length !== segments.length) {
|
|
20278
|
+
return false;
|
|
20279
|
+
}
|
|
20280
|
+
return parts.every((part, index) => {
|
|
20281
|
+
const segment = segments[index];
|
|
20282
|
+
return isParam(part) ? segment.length > 0 : part === segment;
|
|
20283
|
+
});
|
|
20284
|
+
}, "matches");
|
|
20285
|
+
var isMoreSpecific = /* @__PURE__ */ __name((candidate, current) => {
|
|
20286
|
+
for (const [index, part] of candidate.entries()) {
|
|
20287
|
+
const candidateParam = isParam(part);
|
|
20288
|
+
const currentParam = isParam(current[index]);
|
|
20289
|
+
if (candidateParam !== currentParam) {
|
|
20290
|
+
return !candidateParam;
|
|
20291
|
+
}
|
|
20292
|
+
}
|
|
20293
|
+
return false;
|
|
20294
|
+
}, "isMoreSpecific");
|
|
20295
|
+
var extract = /* @__PURE__ */ __name((parts, segments) => {
|
|
20296
|
+
const params = {};
|
|
20297
|
+
for (const [index, part] of parts.entries()) {
|
|
20298
|
+
if (isParam(part)) {
|
|
20299
|
+
params[part.slice(1)] = segments[index];
|
|
20300
|
+
}
|
|
20301
|
+
}
|
|
20302
|
+
return params;
|
|
20303
|
+
}, "extract");
|
|
20304
|
+
var matchEndpointPath = /* @__PURE__ */ __name((routes, requestPath) => {
|
|
20305
|
+
const segments = splitRequest(requestPath);
|
|
20306
|
+
if (segments === null) {
|
|
20307
|
+
return void 0;
|
|
20308
|
+
}
|
|
20309
|
+
let best;
|
|
20310
|
+
for (const route of routes) {
|
|
20311
|
+
const parts = splitRoute(route);
|
|
20312
|
+
if (matches(parts, segments) && (best === void 0 || isMoreSpecific(parts, best.parts))) {
|
|
20313
|
+
best = { route, parts };
|
|
20314
|
+
}
|
|
20315
|
+
}
|
|
20316
|
+
if (best === void 0) {
|
|
20317
|
+
return void 0;
|
|
20318
|
+
}
|
|
20319
|
+
return { route: best.route, params: extract(best.parts, segments) };
|
|
20320
|
+
}, "matchEndpointPath");
|
|
20321
|
+
|
|
19977
20322
|
// ../../libs/worker-access-token/src/endpoints.ts
|
|
19978
20323
|
var APP_ENDPOINT_PREFIX = "/app/";
|
|
19979
20324
|
var MAX_CUSTOM_ENDPOINT_LENGTH = 128;
|
|
@@ -20209,7 +20554,76 @@ var requireScope = /* @__PURE__ */ __name((bit) => {
|
|
|
20209
20554
|
};
|
|
20210
20555
|
}, "requireScope");
|
|
20211
20556
|
|
|
20557
|
+
// src/apps/stream-deadlines.ts
|
|
20558
|
+
var streamWithDeadlines = /* @__PURE__ */ __name((source, { idleMs, totalMs }) => {
|
|
20559
|
+
if (source === null) {
|
|
20560
|
+
return null;
|
|
20561
|
+
}
|
|
20562
|
+
const reader = source.getReader();
|
|
20563
|
+
let target;
|
|
20564
|
+
let totalTimer;
|
|
20565
|
+
let idleTimer = null;
|
|
20566
|
+
let stopped = false;
|
|
20567
|
+
const clearIdleTimer = /* @__PURE__ */ __name(() => {
|
|
20568
|
+
if (idleTimer !== null) {
|
|
20569
|
+
clearTimeout(idleTimer);
|
|
20570
|
+
idleTimer = null;
|
|
20571
|
+
}
|
|
20572
|
+
}, "clearIdleTimer");
|
|
20573
|
+
const clearTimers = /* @__PURE__ */ __name(() => {
|
|
20574
|
+
clearTimeout(totalTimer);
|
|
20575
|
+
clearIdleTimer();
|
|
20576
|
+
}, "clearTimers");
|
|
20577
|
+
const stop = /* @__PURE__ */ __name((message) => {
|
|
20578
|
+
stopped = true;
|
|
20579
|
+
clearTimers();
|
|
20580
|
+
const error51 = new DOMException(message, "TimeoutError");
|
|
20581
|
+
target.error(error51);
|
|
20582
|
+
void reader.cancel(error51).catch(() => void 0);
|
|
20583
|
+
}, "stop");
|
|
20584
|
+
return new ReadableStream({
|
|
20585
|
+
start: /* @__PURE__ */ __name((controller) => {
|
|
20586
|
+
target = controller;
|
|
20587
|
+
totalTimer = setTimeout(() => {
|
|
20588
|
+
stop("The app endpoint stream exceeded its maximum duration.");
|
|
20589
|
+
}, totalMs);
|
|
20590
|
+
}, "start"),
|
|
20591
|
+
pull: /* @__PURE__ */ __name(async (controller) => {
|
|
20592
|
+
idleTimer = setTimeout(() => {
|
|
20593
|
+
stop("The app endpoint stream stopped producing data.");
|
|
20594
|
+
}, idleMs);
|
|
20595
|
+
let result;
|
|
20596
|
+
try {
|
|
20597
|
+
result = await reader.read();
|
|
20598
|
+
} catch (error51) {
|
|
20599
|
+
stopped = true;
|
|
20600
|
+
clearTimers();
|
|
20601
|
+
throw error51;
|
|
20602
|
+
}
|
|
20603
|
+
clearIdleTimer();
|
|
20604
|
+
if (stopped) {
|
|
20605
|
+
return;
|
|
20606
|
+
}
|
|
20607
|
+
if (result.done) {
|
|
20608
|
+
stopped = true;
|
|
20609
|
+
clearTimers();
|
|
20610
|
+
controller.close();
|
|
20611
|
+
return;
|
|
20612
|
+
}
|
|
20613
|
+
controller.enqueue(result.value);
|
|
20614
|
+
}, "pull"),
|
|
20615
|
+
cancel: /* @__PURE__ */ __name(async (reason) => {
|
|
20616
|
+
stopped = true;
|
|
20617
|
+
clearTimers();
|
|
20618
|
+
await reader.cancel(reason);
|
|
20619
|
+
}, "cancel")
|
|
20620
|
+
});
|
|
20621
|
+
}, "streamWithDeadlines");
|
|
20622
|
+
|
|
20212
20623
|
// src/apps/endpoint-proxy.ts
|
|
20624
|
+
var API_ENDPOINT_START_TIMEOUT_MS = 1e4;
|
|
20625
|
+
var API_ENDPOINT_STREAM_IDLE_TIMEOUT_MS = 15e4;
|
|
20626
|
+
var API_ENDPOINT_STREAM_MAX_DURATION_MS = 3e5;
|
|
20213
20627
|
var DENIED_REQUEST_HEADERS = /* @__PURE__ */ new Set(["authorization", "cookie", "x-cf-data"]);
|
|
20214
20628
|
var refuse = /* @__PURE__ */ __name((status, error51) => {
|
|
20215
20629
|
return Response.json({ error: error51 }, { status });
|
|
@@ -20225,16 +20639,23 @@ var matchEndpoint = /* @__PURE__ */ __name((app2, path, method) => {
|
|
|
20225
20639
|
if (!isHttpMethod(method)) {
|
|
20226
20640
|
return void 0;
|
|
20227
20641
|
}
|
|
20228
|
-
const
|
|
20229
|
-
return endpoint.
|
|
20642
|
+
const serving = app2.endpoints.filter((endpoint) => {
|
|
20643
|
+
return endpoint.methods.includes(method);
|
|
20230
20644
|
});
|
|
20231
|
-
|
|
20645
|
+
const matched = matchEndpointPath(
|
|
20646
|
+
serving.map((endpoint) => endpoint.path),
|
|
20647
|
+
path
|
|
20648
|
+
);
|
|
20649
|
+
if (matched === void 0) {
|
|
20232
20650
|
return void 0;
|
|
20233
20651
|
}
|
|
20652
|
+
const declared = serving.find((endpoint) => {
|
|
20653
|
+
return endpoint.path === matched.route;
|
|
20654
|
+
});
|
|
20234
20655
|
if (declared.auth !== "public" && declared.auth !== "token" || declared.mode !== "webhook" && declared.mode !== "api") {
|
|
20235
20656
|
return void 0;
|
|
20236
20657
|
}
|
|
20237
|
-
return {
|
|
20658
|
+
return { route: declared.path, path, params: matched.params, method, auth: declared.auth, mode: declared.mode };
|
|
20238
20659
|
}, "matchEndpoint");
|
|
20239
20660
|
var authorizeEndpoint = /* @__PURE__ */ __name(async (c, slug, endpoint) => {
|
|
20240
20661
|
if (endpoint.auth === "public") {
|
|
@@ -20266,12 +20687,17 @@ var passThroughResponse = /* @__PURE__ */ __name((upstream) => {
|
|
|
20266
20687
|
const headers = new Headers(upstream.headers);
|
|
20267
20688
|
headers.delete("set-cookie");
|
|
20268
20689
|
headers.delete(APP_SPANS_HEADER);
|
|
20690
|
+
headers.delete(APP_OBSERVABILITY_HEADER);
|
|
20269
20691
|
for (const name of [...headers.keys()]) {
|
|
20270
20692
|
if (name.startsWith("access-control-")) {
|
|
20271
20693
|
headers.delete(name);
|
|
20272
20694
|
}
|
|
20273
20695
|
}
|
|
20274
|
-
|
|
20696
|
+
const body = streamWithDeadlines(upstream.body, {
|
|
20697
|
+
idleMs: API_ENDPOINT_STREAM_IDLE_TIMEOUT_MS,
|
|
20698
|
+
totalMs: API_ENDPOINT_STREAM_MAX_DURATION_MS
|
|
20699
|
+
});
|
|
20700
|
+
return new Response(body, { status: upstream.status, headers });
|
|
20275
20701
|
}, "passThroughResponse");
|
|
20276
20702
|
var parseJson3 = /* @__PURE__ */ __name((text) => {
|
|
20277
20703
|
try {
|
|
@@ -20301,6 +20727,17 @@ var webhookAck = /* @__PURE__ */ __name(async (slug, upstream) => {
|
|
|
20301
20727
|
}
|
|
20302
20728
|
return refuse(502, "upstream");
|
|
20303
20729
|
}, "webhookAck");
|
|
20730
|
+
var fetchApiResponse = /* @__PURE__ */ __name(async (service, url2, init) => {
|
|
20731
|
+
const controller = new AbortController();
|
|
20732
|
+
const timer = setTimeout(() => {
|
|
20733
|
+
controller.abort(new DOMException("The app endpoint did not start within its deadline.", "TimeoutError"));
|
|
20734
|
+
}, API_ENDPOINT_START_TIMEOUT_MS);
|
|
20735
|
+
try {
|
|
20736
|
+
return await service.fetch(url2, { ...init, signal: controller.signal });
|
|
20737
|
+
} finally {
|
|
20738
|
+
clearTimeout(timer);
|
|
20739
|
+
}
|
|
20740
|
+
}, "fetchApiResponse");
|
|
20304
20741
|
var runForwardToApp = /* @__PURE__ */ __name(async (c, input) => {
|
|
20305
20742
|
const { slug, app: app2, endpoint, installs, body, search } = input;
|
|
20306
20743
|
const context = invocationContext(c.env);
|
|
@@ -20320,25 +20757,30 @@ var runForwardToApp = /* @__PURE__ */ __name(async (c, input) => {
|
|
|
20320
20757
|
headers.set(
|
|
20321
20758
|
ENDPOINT_CONTEXT_HEADER,
|
|
20322
20759
|
JSON.stringify({
|
|
20760
|
+
route: endpoint.route,
|
|
20323
20761
|
path: endpoint.path,
|
|
20762
|
+
params: endpoint.params,
|
|
20324
20763
|
method: endpoint.method,
|
|
20325
20764
|
mode: endpoint.mode,
|
|
20326
20765
|
version: app2.version,
|
|
20766
|
+
site: { origin: context.origin },
|
|
20327
20767
|
installs: installs.map((install) => ({ id: install.id, ord: install.ord, label: install.label }))
|
|
20328
20768
|
})
|
|
20329
20769
|
);
|
|
20330
20770
|
let upstream;
|
|
20331
20771
|
try {
|
|
20332
|
-
|
|
20333
|
-
|
|
20334
|
-
|
|
20335
|
-
body,
|
|
20336
|
-
signal: AbortSignal.timeout(HOOK_TIMEOUT_MS)
|
|
20337
|
-
});
|
|
20772
|
+
const url2 = `https://app/__qinto/endpoint${endpoint.route}${search}`;
|
|
20773
|
+
const init = { method: endpoint.method, headers, body };
|
|
20774
|
+
upstream = endpoint.mode === "api" ? await fetchApiResponse(service, url2, init) : await service.fetch(url2, { ...init, signal: AbortSignal.timeout(APP_INVOCATION_TIMEOUT_MS) });
|
|
20338
20775
|
} catch (error51) {
|
|
20339
20776
|
return upstreamFailed(slug, String(error51));
|
|
20340
20777
|
}
|
|
20341
20778
|
adoptAppSpans(upstream);
|
|
20779
|
+
writeAppObservability(c.env, upstream, {
|
|
20780
|
+
app: observabilityApp(slug, app2),
|
|
20781
|
+
installs,
|
|
20782
|
+
budget: createDataPointBudget()
|
|
20783
|
+
});
|
|
20342
20784
|
setSpanAttributes({ "app.status": upstream.status });
|
|
20343
20785
|
return endpoint.mode === "api" ? passThroughResponse(upstream) : webhookAck(slug, upstream);
|
|
20344
20786
|
}, "runForwardToApp");
|
|
@@ -20351,7 +20793,7 @@ var forwardToApp = /* @__PURE__ */ __name(async (c, input) => {
|
|
|
20351
20793
|
"app.slug": slug,
|
|
20352
20794
|
"app.version": app2.version,
|
|
20353
20795
|
"app.binding": app2.binding,
|
|
20354
|
-
"app.hook": `endpoint.${endpoint.method} ${endpoint.
|
|
20796
|
+
"app.hook": `endpoint.${endpoint.method} ${endpoint.route}`,
|
|
20355
20797
|
"app.endpoint_mode": endpoint.mode,
|
|
20356
20798
|
"app.installs": installs.length
|
|
20357
20799
|
}
|
|
@@ -20367,7 +20809,11 @@ var handleAppEndpoint = /* @__PURE__ */ __name(async (c, slug) => {
|
|
|
20367
20809
|
return refuse(404, "not_found");
|
|
20368
20810
|
}
|
|
20369
20811
|
const url2 = new URL(c.req.url);
|
|
20370
|
-
const
|
|
20812
|
+
const prefix = `/app/${slug}`;
|
|
20813
|
+
if (url2.pathname !== prefix && !url2.pathname.startsWith(`${prefix}/`)) {
|
|
20814
|
+
return refuse(404, "not_found");
|
|
20815
|
+
}
|
|
20816
|
+
const endpoint = matchEndpoint(app2, url2.pathname.slice(prefix.length) || "/", c.req.method);
|
|
20371
20817
|
if (endpoint === void 0) {
|
|
20372
20818
|
return refuse(404, "not_found");
|
|
20373
20819
|
}
|
|
@@ -20380,7 +20826,7 @@ var handleAppEndpoint = /* @__PURE__ */ __name(async (c, slug) => {
|
|
|
20380
20826
|
if (first === void 0) {
|
|
20381
20827
|
return refuse(503, "inactive");
|
|
20382
20828
|
}
|
|
20383
|
-
const key = endpoint.mode === "api" ? `${slug}:${first.id}:endpoint` : `${slug}:${endpoint.
|
|
20829
|
+
const key = endpoint.mode === "api" ? `${slug}:${first.id}:endpoint` : `${slug}:${endpoint.route}:endpoint`;
|
|
20384
20830
|
if (await rateLimited(c.env, key)) {
|
|
20385
20831
|
return refuse(429, "rate_limited");
|
|
20386
20832
|
}
|
|
@@ -20650,16 +21096,22 @@ var runForwardToApp2 = /* @__PURE__ */ __name(async (c, input) => {
|
|
|
20650
21096
|
method: c.req.method,
|
|
20651
21097
|
headers,
|
|
20652
21098
|
body,
|
|
20653
|
-
signal: AbortSignal.timeout(
|
|
21099
|
+
signal: AbortSignal.timeout(APP_INVOCATION_TIMEOUT_MS)
|
|
20654
21100
|
});
|
|
20655
21101
|
} catch (error51) {
|
|
20656
21102
|
return upstreamFailed2(origin, slug, String(error51));
|
|
20657
21103
|
}
|
|
20658
21104
|
adoptAppSpans(upstream);
|
|
21105
|
+
writeAppObservability(c.env, upstream, {
|
|
21106
|
+
app: observabilityApp(slug, app2),
|
|
21107
|
+
installs: [{ id: install.id, ord: install.ord, label: install.label }],
|
|
21108
|
+
budget: createDataPointBudget()
|
|
21109
|
+
});
|
|
20659
21110
|
setSpanAttributes({ "app.status": upstream.status });
|
|
20660
21111
|
const responseHeaders = new Headers(upstream.headers);
|
|
20661
21112
|
responseHeaders.delete("set-cookie");
|
|
20662
21113
|
responseHeaders.delete(APP_SPANS_HEADER);
|
|
21114
|
+
responseHeaders.delete(APP_OBSERVABILITY_HEADER);
|
|
20663
21115
|
for (const name of [...responseHeaders.keys()]) {
|
|
20664
21116
|
if (name.startsWith("access-control-")) {
|
|
20665
21117
|
responseHeaders.delete(name);
|
|
@@ -23693,6 +24145,21 @@ var withoutQuery = /* @__PURE__ */ __name((url2) => {
|
|
|
23693
24145
|
parsed.search = "";
|
|
23694
24146
|
return parsed.toString();
|
|
23695
24147
|
}, "withoutQuery");
|
|
24148
|
+
var browserGeoFrom = /* @__PURE__ */ __name((geo) => {
|
|
24149
|
+
if (geo === void 0) {
|
|
24150
|
+
return void 0;
|
|
24151
|
+
}
|
|
24152
|
+
return {
|
|
24153
|
+
country: geo.country ?? null,
|
|
24154
|
+
// EdgeTag's geoRegion is Cloudflare's subdivision code.
|
|
24155
|
+
region: geo.regionCode ?? null,
|
|
24156
|
+
state: geo.region ?? null,
|
|
24157
|
+
isEU: geo.isEUCountry === "yes"
|
|
24158
|
+
};
|
|
24159
|
+
}, "browserGeoFrom");
|
|
24160
|
+
var attachmentFieldBytes = /* @__PURE__ */ __name((name, value) => {
|
|
24161
|
+
return value === void 0 ? 0 : new TextEncoder().encode(`,"${name}":${JSON.stringify(value)}`).length;
|
|
24162
|
+
}, "attachmentFieldBytes");
|
|
23696
24163
|
var connectionAttachment = /* @__PURE__ */ __name((request, geo) => {
|
|
23697
24164
|
const userAgent = request.headers.get("user-agent");
|
|
23698
24165
|
const attrs = {
|
|
@@ -23708,15 +24175,18 @@ var connectionAttachment = /* @__PURE__ */ __name((request, geo) => {
|
|
|
23708
24175
|
locale: raw2.locale !== void 0 && raw2.locale.length <= MAX_HOST_LOCALE_LENGTH ? raw2.locale : void 0
|
|
23709
24176
|
};
|
|
23710
24177
|
const host = bounded.ip === void 0 && bounded.userAgent === void 0 && bounded.locale === void 0 ? void 0 : bounded;
|
|
23711
|
-
const hostBytes =
|
|
24178
|
+
const hostBytes = attachmentFieldBytes("host", host);
|
|
24179
|
+
const browserGeo = browserGeoFrom(geo);
|
|
24180
|
+
const browserGeoBytes = attachmentFieldBytes("browserGeo", browserGeo);
|
|
23712
24181
|
const cf = forwardedCf(request);
|
|
23713
24182
|
const cfSuffix = cf === void 0 ? "" : `,"cf":${JSON.stringify(cf)}`;
|
|
23714
24183
|
const cfBytes = new TextEncoder().encode(cfSuffix).length;
|
|
23715
24184
|
const emptyAttrsBytes = new TextEncoder().encode(JSON.stringify({ attrs: {} })).length;
|
|
23716
|
-
const includedCf = cfBytes <= MAX_ATTACHMENT_BYTES - emptyAttrsBytes - hostBytes ? cf : void 0;
|
|
23717
|
-
const budgeted = budgetAttrs(attrs, MAX_ATTACHMENT_BYTES - hostBytes - (includedCf === void 0 ? 0 : cfBytes));
|
|
24185
|
+
const includedCf = cfBytes <= MAX_ATTACHMENT_BYTES - emptyAttrsBytes - hostBytes - browserGeoBytes ? cf : void 0;
|
|
24186
|
+
const budgeted = budgetAttrs(attrs, MAX_ATTACHMENT_BYTES - hostBytes - browserGeoBytes - (includedCf === void 0 ? 0 : cfBytes));
|
|
23718
24187
|
return {
|
|
23719
24188
|
...budgeted,
|
|
24189
|
+
...browserGeo === void 0 ? {} : { browserGeo },
|
|
23720
24190
|
...includedCf === void 0 ? {} : { cf: includedCf },
|
|
23721
24191
|
...host === void 0 ? {} : { host }
|
|
23722
24192
|
};
|
|
@@ -23803,14 +24273,14 @@ var open = /* @__PURE__ */ __name(async (c) => {
|
|
|
23803
24273
|
|
|
23804
24274
|
// browser-artifact/meta.json
|
|
23805
24275
|
var meta_default = {
|
|
23806
|
-
version: "0.1.
|
|
23807
|
-
gitSha: "
|
|
23808
|
-
builtAt: "2026-08-
|
|
23809
|
-
hash: "
|
|
24276
|
+
version: "0.1.3",
|
|
24277
|
+
gitSha: "66e1bc45acbe4891948cbb3851f3f43ce06abc84",
|
|
24278
|
+
builtAt: "2026-08-28T17:17:19.113Z",
|
|
24279
|
+
hash: "050bb7dd702ac407623ce130c324f3342f048b8a201e2975994d5ef54de7f19f"
|
|
23810
24280
|
};
|
|
23811
24281
|
|
|
23812
24282
|
// src/paths/q-js.ts
|
|
23813
|
-
import qScript from "./
|
|
24283
|
+
import qScript from "./9026260a7a674b710223f296c5d2e7a01930cfed-q.js.txt";
|
|
23814
24284
|
|
|
23815
24285
|
// src/apps-q.ts
|
|
23816
24286
|
import appsQScript from "./apps-q.json";
|
|
@@ -23867,6 +24337,7 @@ var appsMetadata = /* @__PURE__ */ __name(() => {
|
|
|
23867
24337
|
return Object.fromEntries(
|
|
23868
24338
|
Object.entries(registry3.apps).map(([slug, app2]) => {
|
|
23869
24339
|
const capture = declaredCapture(app2);
|
|
24340
|
+
const geo = app2.data.includes("geo") ? { geo: true } : {};
|
|
23870
24341
|
const sharedContext = app2.data.includes(SHARED_CONTEXT_CAPABILITY) ? { sharedContext: true } : {};
|
|
23871
24342
|
return [
|
|
23872
24343
|
slug,
|
|
@@ -23874,6 +24345,7 @@ var appsMetadata = /* @__PURE__ */ __name(() => {
|
|
|
23874
24345
|
category: app2.consent.category,
|
|
23875
24346
|
eventResults: app2.eventResults,
|
|
23876
24347
|
...capture === void 0 ? {} : { capture },
|
|
24348
|
+
...geo,
|
|
23877
24349
|
...sharedContext
|
|
23878
24350
|
}
|
|
23879
24351
|
];
|
|
@@ -24520,7 +24992,7 @@ var runPool = /* @__PURE__ */ __name(async (limit, tasks) => {
|
|
|
24520
24992
|
}
|
|
24521
24993
|
await Promise.all(running);
|
|
24522
24994
|
}, "runPool");
|
|
24523
|
-
var fanOutToApps = /* @__PURE__ */ __name(async (env, trigger, snapshot, { deliverPush, writeAttribution: writeAttribution2, writeContext: writeContext2, trace }) => {
|
|
24995
|
+
var fanOutToApps = /* @__PURE__ */ __name(async (env, trigger, snapshot, { deliverPush, writeAttribution: writeAttribution2, writeContext: writeContext2, budget, trace }) => {
|
|
24524
24996
|
const { registry: registry3, error: error51 } = siteRegistry();
|
|
24525
24997
|
const targets = eligibleApps(registry3, trigger, snapshot.consent, consentEnforced(env));
|
|
24526
24998
|
if (error51 === void 0 && targets.length === 0) {
|
|
@@ -24548,7 +25020,7 @@ var fanOutToApps = /* @__PURE__ */ __name(async (env, trigger, snapshot, { deliv
|
|
|
24548
25020
|
await runPool(
|
|
24549
25021
|
FAN_OUT_CONCURRENCY,
|
|
24550
25022
|
capped.map((target) => {
|
|
24551
|
-
return () => invokeApp(env, { target, trigger, snapshot, context, deliverPush, writeAttribution: writeAttribution2, writeContext: writeContext2 });
|
|
25023
|
+
return () => invokeApp(env, { target, trigger, snapshot, context, deliverPush, writeAttribution: writeAttribution2, writeContext: writeContext2, budget });
|
|
24552
25024
|
})
|
|
24553
25025
|
);
|
|
24554
25026
|
}
|
|
@@ -24605,7 +25077,7 @@ var withReplacedPayload = /* @__PURE__ */ __name((entry, action, payload) => {
|
|
|
24605
25077
|
}
|
|
24606
25078
|
return { ...action, payload: parsed.data };
|
|
24607
25079
|
}, "withReplacedPayload");
|
|
24608
|
-
var postTransform = /* @__PURE__ */ __name(async (env, entry, post) => {
|
|
25080
|
+
var postTransform = /* @__PURE__ */ __name(async (env, entry, post, dataPoints) => {
|
|
24609
25081
|
let response;
|
|
24610
25082
|
try {
|
|
24611
25083
|
response = await postHookEnvelope(env, { binding: entry.app.binding, ...post });
|
|
@@ -24615,6 +25087,11 @@ var postTransform = /* @__PURE__ */ __name(async (env, entry, post) => {
|
|
|
24615
25087
|
if (response === void 0) {
|
|
24616
25088
|
throw entryFailure(entry, `the ${entry.app.binding} service binding is missing`);
|
|
24617
25089
|
}
|
|
25090
|
+
writeAppObservability(env, response, {
|
|
25091
|
+
app: observabilityApp(entry.slug, entry.app),
|
|
25092
|
+
installs: [entry.install],
|
|
25093
|
+
budget: dataPoints
|
|
25094
|
+
});
|
|
24618
25095
|
setSpanAttributes({ "app.status": response.status });
|
|
24619
25096
|
if (!response.ok) {
|
|
24620
25097
|
throw entryFailure(entry, `the hook answered status ${response.status}`);
|
|
@@ -24629,14 +25106,14 @@ var postTransform = /* @__PURE__ */ __name(async (env, entry, post) => {
|
|
|
24629
25106
|
throw entryFailure(entry, String(error51));
|
|
24630
25107
|
}
|
|
24631
25108
|
}, "postTransform");
|
|
24632
|
-
var runTransformEntry = /* @__PURE__ */ __name(async (env, { entry, action, snapshot, context, timeoutMs }) => {
|
|
25109
|
+
var runTransformEntry = /* @__PURE__ */ __name(async (env, { entry, action, snapshot, context, timeoutMs, dataPoints }) => {
|
|
24633
25110
|
const target = { slug: entry.slug, app: entry.app, installs: [entry.install] };
|
|
24634
25111
|
const envelope = {
|
|
24635
25112
|
...buildEnvelope(target, { kind: "event", action }, snapshot, context),
|
|
24636
25113
|
kind: "transform"
|
|
24637
25114
|
};
|
|
24638
25115
|
const key = await deriveAppInternalKey(context.master, context.domainId, entry.slug);
|
|
24639
|
-
const verdict = validateEnvelopeResponse(await postTransform(env, entry, { key, envelope, timeoutMs }), 1);
|
|
25116
|
+
const verdict = validateEnvelopeResponse(await postTransform(env, entry, { key, envelope, timeoutMs }, dataPoints), 1);
|
|
24640
25117
|
if (!verdict.ok) {
|
|
24641
25118
|
throw entryFailure(entry, verdict.reason);
|
|
24642
25119
|
}
|
|
@@ -24704,7 +25181,7 @@ var buildRelease = /* @__PURE__ */ __name((env, { collected, action, deliverPush
|
|
|
24704
25181
|
}
|
|
24705
25182
|
};
|
|
24706
25183
|
}, "buildRelease");
|
|
24707
|
-
var runTransformChain = /* @__PURE__ */ __name(async (env, { action, snapshot, deliverPush, writeAttribution: writeAttribution2, writeContext: writeContext2 }) => {
|
|
25184
|
+
var runTransformChain = /* @__PURE__ */ __name(async (env, { action, snapshot, deliverPush, writeAttribution: writeAttribution2, writeContext: writeContext2, dataPoints }) => {
|
|
24708
25185
|
const { registry: registry3, error: error51 } = siteRegistry();
|
|
24709
25186
|
if (error51 !== void 0) {
|
|
24710
25187
|
throw new TransformChainError(`the apps registry is malformed: ${error51}`);
|
|
@@ -24724,7 +25201,14 @@ var runTransformChain = /* @__PURE__ */ __name(async (env, { action, snapshot, d
|
|
|
24724
25201
|
for (const entry of entries) {
|
|
24725
25202
|
const stepped = entryBudget(budget, entry, Date.now());
|
|
24726
25203
|
budget = stepped.budget;
|
|
24727
|
-
const verdict = await invokeTransformEntry(env, {
|
|
25204
|
+
const verdict = await invokeTransformEntry(env, {
|
|
25205
|
+
entry,
|
|
25206
|
+
action: current,
|
|
25207
|
+
snapshot,
|
|
25208
|
+
context,
|
|
25209
|
+
timeoutMs: stepped.timeoutMs,
|
|
25210
|
+
dataPoints
|
|
25211
|
+
});
|
|
24728
25212
|
if (verdict.effects.accepted.length > 0) {
|
|
24729
25213
|
collected.push({ slug: entry.slug, installs: [entry.install], groups: verdict.effects.accepted });
|
|
24730
25214
|
}
|
|
@@ -25705,7 +26189,11 @@ var AnonDO = class extends DurableObject2 {
|
|
|
25705
26189
|
const attachment = connectionAttachment(request, geo);
|
|
25706
26190
|
server.serializeAttachment(attachment);
|
|
25707
26191
|
this.deliverSharedContext(server);
|
|
25708
|
-
this.appFanOut(
|
|
26192
|
+
this.appFanOut(
|
|
26193
|
+
{ kind: "connect", ...attachment.cf === void 0 ? {} : { geo: attachment.cf }, host: attachment.host },
|
|
26194
|
+
userId,
|
|
26195
|
+
createDataPointBudget()
|
|
26196
|
+
);
|
|
25709
26197
|
return new Response(null, { status: 101, webSocket: client });
|
|
25710
26198
|
});
|
|
25711
26199
|
}
|
|
@@ -25760,7 +26248,14 @@ var AnonDO = class extends DurableObject2 {
|
|
|
25760
26248
|
return;
|
|
25761
26249
|
}
|
|
25762
26250
|
if (parsed.frame.t === "getSession") {
|
|
25763
|
-
this.respond(ws, {
|
|
26251
|
+
this.respond(ws, {
|
|
26252
|
+
t: "session",
|
|
26253
|
+
ref: parsed.ref,
|
|
26254
|
+
anonId: userId,
|
|
26255
|
+
globalId: linkedGlobalId(this.kv),
|
|
26256
|
+
ip: attachment?.host?.ip ?? null,
|
|
26257
|
+
geo: attachment?.browserGeo ?? null
|
|
26258
|
+
});
|
|
25764
26259
|
return;
|
|
25765
26260
|
}
|
|
25766
26261
|
if (parsed.frame.t === "capture") {
|
|
@@ -26046,12 +26541,13 @@ var AnonDO = class extends DurableObject2 {
|
|
|
26046
26541
|
if (isNoopAction(action)) {
|
|
26047
26542
|
return;
|
|
26048
26543
|
}
|
|
26049
|
-
const
|
|
26544
|
+
const dataPoints = createDataPointBudget();
|
|
26545
|
+
const transformed = await this.transformAction(action, dataPoints);
|
|
26050
26546
|
if (transformed === null) {
|
|
26051
26547
|
return;
|
|
26052
26548
|
}
|
|
26053
26549
|
recorded = transformed.action;
|
|
26054
|
-
await this.runAction(transformed.action, transformed.releaseEmissions);
|
|
26550
|
+
await this.runAction(transformed.action, transformed.releaseEmissions, dataPoints);
|
|
26055
26551
|
} finally {
|
|
26056
26552
|
this.recordActionBlocks(recorded);
|
|
26057
26553
|
}
|
|
@@ -26061,9 +26557,9 @@ var AnonDO = class extends DurableObject2 {
|
|
|
26061
26557
|
actionBlocks(action, this.appsSnapshot(action.userId), this.wiped ? [] : allIdentifiers(this.sql, this.identifiersMirror))
|
|
26062
26558
|
);
|
|
26063
26559
|
}
|
|
26064
|
-
async runAction(current, releaseEmissions) {
|
|
26560
|
+
async runAction(current, releaseEmissions, dataPoints) {
|
|
26065
26561
|
if (current.type !== "alias" && isDeliveryOnlyAction(current)) {
|
|
26066
|
-
this.appFanOut({ kind: "event", action: current }, current.userId);
|
|
26562
|
+
this.appFanOut({ kind: "event", action: current }, current.userId, dataPoints);
|
|
26067
26563
|
this.releaseTransformEmissions(releaseEmissions);
|
|
26068
26564
|
return;
|
|
26069
26565
|
}
|
|
@@ -26099,7 +26595,7 @@ var AnonDO = class extends DurableObject2 {
|
|
|
26099
26595
|
} else {
|
|
26100
26596
|
await this.ingestConsent(current);
|
|
26101
26597
|
}
|
|
26102
|
-
this.appFanOut({ kind: "event", action: current }, current.userId);
|
|
26598
|
+
this.appFanOut({ kind: "event", action: current }, current.userId, dataPoints);
|
|
26103
26599
|
this.releaseTransformEmissions(releaseEmissions);
|
|
26104
26600
|
} finally {
|
|
26105
26601
|
if (novelIdentifier) {
|
|
@@ -26108,7 +26604,7 @@ var AnonDO = class extends DurableObject2 {
|
|
|
26108
26604
|
}
|
|
26109
26605
|
}
|
|
26110
26606
|
// null means a transform dropped the action: the turn succeeds with nothing persisted (§5.4).
|
|
26111
|
-
async transformAction(action) {
|
|
26607
|
+
async transformAction(action, dataPoints) {
|
|
26112
26608
|
if (action.type !== "track" && action.type !== "identify") {
|
|
26113
26609
|
return { action, releaseEmissions: null };
|
|
26114
26610
|
}
|
|
@@ -26117,7 +26613,8 @@ var AnonDO = class extends DurableObject2 {
|
|
|
26117
26613
|
snapshot: this.appsSnapshot(action.userId),
|
|
26118
26614
|
deliverPush: this.pushDelivery,
|
|
26119
26615
|
writeAttribution: this.attributionDelivery,
|
|
26120
|
-
writeContext: this.contextDelivery
|
|
26616
|
+
writeContext: this.contextDelivery,
|
|
26617
|
+
dataPoints
|
|
26121
26618
|
});
|
|
26122
26619
|
if (outcome.dropped) {
|
|
26123
26620
|
this.releaseTransformEmissions(outcome.releaseEmissions);
|
|
@@ -26153,13 +26650,14 @@ var AnonDO = class extends DurableObject2 {
|
|
|
26153
26650
|
}
|
|
26154
26651
|
this.ctx.waitUntil(release().catch((error51) => console.warn("transform emission release failed", { error: error51 })));
|
|
26155
26652
|
}
|
|
26156
|
-
appFanOut(trigger, anonId) {
|
|
26653
|
+
appFanOut(trigger, anonId, dataPoints) {
|
|
26157
26654
|
const snapshot = this.appsSnapshot(anonId);
|
|
26158
26655
|
this.ctx.waitUntil(
|
|
26159
26656
|
fanOutToApps(this.env, trigger, snapshot, {
|
|
26160
26657
|
deliverPush: this.pushDelivery,
|
|
26161
26658
|
writeAttribution: this.attributionDelivery,
|
|
26162
26659
|
writeContext: this.contextDelivery,
|
|
26660
|
+
budget: dataPoints,
|
|
26163
26661
|
trace: currentTraceContext()
|
|
26164
26662
|
}).catch((error51) => console.warn("app fan-out failed", { error: error51 }))
|
|
26165
26663
|
);
|