@blotout/qinto-cli 0.1.7 → 0.1.9

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.
@@ -15917,7 +15917,14 @@ var scheduleSchema = external_exports.object({
15917
15917
  id: external_exports.string().min(1).max(32),
15918
15918
  cron: external_exports.string().min(1).max(MAX_CRON_LENGTH),
15919
15919
  scheduledTime: external_exports.number().int().nonnegative().safe(),
15920
- installs: external_exports.array(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()).min(1).max(MAX_INSTALLS_PER_APP)
15920
+ installs: external_exports.array(
15921
+ external_exports.object({
15922
+ id: external_exports.string().min(1).max(64),
15923
+ ord: external_exports.number().int().min(1),
15924
+ label: external_exports.string().max(100),
15925
+ catalogQueryTicket: external_exports.string().min(1).max(2048)
15926
+ }).strict()
15927
+ ).min(1).max(MAX_INSTALLS_PER_APP)
15921
15928
  }).strict();
15922
15929
  var controlEnvelopeSchema = external_exports.object({
15923
15930
  v: external_exports.literal(CONTROL_ENVELOPE_VERSION),
@@ -15959,12 +15966,21 @@ var ENDPOINT_CONTEXT_HEADER = "x-qinto-endpoint-context";
15959
15966
  var ENDPOINT_OUTCOMES_HEADER = "x-qinto-endpoint-outcomes";
15960
15967
  var MAX_ENDPOINT_BODY_BYTES = 1024 * 1024;
15961
15968
  var MAX_ENDPOINT_OUTCOMES_BYTES = 64 * 1024;
15969
+ var MAX_ENDPOINT_PATH_LENGTH = 16 * 1024;
15962
15970
  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();
15963
15971
  var endpointContextSchema = external_exports.object({
15964
- path: external_exports.string().min(1).max(255),
15972
+ // The declared template, which keys the bundle's handler map; `path` is what the caller asked
15973
+ // for. Only `route` may key the rate limiter — a concrete path gives every parameter value its
15974
+ // own bucket, which is no limit at all.
15975
+ route: external_exports.string().min(1).max(255),
15976
+ path: external_exports.string().min(1).max(MAX_ENDPOINT_PATH_LENGTH),
15977
+ // A name is cut from `route`, so the route's own cap bounds it; anything tighter would reject a
15978
+ // parameter the manifest grammar accepts and the worker already matched.
15979
+ params: external_exports.record(external_exports.string().min(1).max(255), external_exports.string().max(MAX_ENDPOINT_PATH_LENGTH)).default({}),
15965
15980
  method: external_exports.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]),
15966
15981
  mode: external_exports.enum(["webhook", "api"]),
15967
15982
  version: external_exports.string().min(1).max(32),
15983
+ site: external_exports.object({ origin: external_exports.string().min(1).max(255) }).strict(),
15968
15984
  installs: external_exports.array(installSchema3).min(1).max(15)
15969
15985
  }).strict().refine((context) => {
15970
15986
  return context.mode === "webhook" || context.installs.length === 1;
@@ -15991,6 +16007,71 @@ var aggregateEndpointOutcomes = /* @__PURE__ */ __name((outcomes) => {
15991
16007
  return { status: failed ? 502 : 200 };
15992
16008
  }, "aggregateEndpointOutcomes");
15993
16009
 
16010
+ // ../../libs/app-envelope/src/observability.ts
16011
+ var APP_OBSERVABILITY_HEADER = "x-qinto-app-observability";
16012
+ var APP_OBSERVABILITY_VERSION = 1;
16013
+ var MAX_APP_OBSERVABILITY_EVENTS = 20;
16014
+ var MAX_APP_SAVED_ERRORS = 5;
16015
+ var MAX_APP_EVENT_NAME_BYTES = 128;
16016
+ var MAX_APP_ACTION_NAME_BYTES = 256;
16017
+ var MAX_APP_ERROR_DESCRIPTION_BYTES = 2048;
16018
+ var MAX_APP_ERROR_PAYLOAD_BYTES = 4096;
16019
+ var MAX_APP_DATA_POINT_BLOB_BYTES = 16 * 1024;
16020
+ var MAX_APP_OBSERVABILITY_BYTES = 32 * 1024;
16021
+ var APP_OBSERVABILITY_JSON_BUDGET = MAX_APP_OBSERVABILITY_BYTES / 4 * 3;
16022
+ var MAX_APP_DATA_POINTS_PER_INVOCATION = 250;
16023
+ var APP_EVENT_STATUSES = ["success", "error"];
16024
+ var APP_ERROR_CATEGORIES = ["authorization", "configuration", "validation", "rate_limit", "upstream", "internal"];
16025
+ var encoder3 = new TextEncoder();
16026
+ var decoder2 = new TextDecoder();
16027
+ 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();
16028
+ var savedErrorSchema = external_exports.object({
16029
+ ord: external_exports.number().int().min(1),
16030
+ category: external_exports.enum(APP_ERROR_CATEGORIES),
16031
+ action: external_exports.string().min(1).max(MAX_APP_ACTION_NAME_BYTES),
16032
+ description: external_exports.string().min(1).max(MAX_APP_ERROR_DESCRIPTION_BYTES),
16033
+ payload: external_exports.string().max(MAX_APP_ERROR_PAYLOAD_BYTES).optional(),
16034
+ error: external_exports.string().max(MAX_APP_ERROR_PAYLOAD_BYTES).optional()
16035
+ }).strict();
16036
+ var appObservabilityRecordSchema = external_exports.object({
16037
+ v: external_exports.literal(APP_OBSERVABILITY_VERSION),
16038
+ truncated: external_exports.boolean().optional(),
16039
+ events: external_exports.array(observabilityEventSchema).max(MAX_APP_OBSERVABILITY_EVENTS),
16040
+ errors: external_exports.array(savedErrorSchema).max(MAX_APP_SAVED_ERRORS)
16041
+ }).strict();
16042
+ var utf8Length = /* @__PURE__ */ __name((value) => {
16043
+ return encoder3.encode(value).length;
16044
+ }, "utf8Length");
16045
+ var truncateUtf8 = /* @__PURE__ */ __name((value, maxBytes) => {
16046
+ if (utf8Length(value) <= maxBytes) {
16047
+ return value;
16048
+ }
16049
+ let kept = "";
16050
+ let used = 0;
16051
+ for (const character of value) {
16052
+ const size = utf8Length(character);
16053
+ if (used + size > maxBytes) {
16054
+ break;
16055
+ }
16056
+ kept += character;
16057
+ used += size;
16058
+ }
16059
+ return kept;
16060
+ }, "truncateUtf8");
16061
+ var parseAppObservability = /* @__PURE__ */ __name((header) => {
16062
+ if (header === null || header.length > MAX_APP_OBSERVABILITY_BYTES) {
16063
+ return null;
16064
+ }
16065
+ let parsed;
16066
+ try {
16067
+ parsed = JSON.parse(decoder2.decode(Uint8Array.from(atob(header), (character) => character.charCodeAt(0))));
16068
+ } catch {
16069
+ return null;
16070
+ }
16071
+ const result = appObservabilityRecordSchema.safeParse(parsed);
16072
+ return result.success ? result.data : null;
16073
+ }, "parseAppObservability");
16074
+
15994
16075
  // ../../libs/app-envelope/src/ui.ts
15995
16076
  var UI_CONTEXT_HEADER = "x-qinto-ui-context";
15996
16077
  var MAX_UI_BODY_BYTES = 1024 * 1024;
@@ -16197,13 +16278,13 @@ var getPattern = /* @__PURE__ */ __name((label, next) => {
16197
16278
  }
16198
16279
  return null;
16199
16280
  }, "getPattern");
16200
- var tryDecode = /* @__PURE__ */ __name((str, decoder2) => {
16281
+ var tryDecode = /* @__PURE__ */ __name((str, decoder3) => {
16201
16282
  try {
16202
- return decoder2(str);
16283
+ return decoder3(str);
16203
16284
  } catch {
16204
16285
  return str.replace(/(?:%[0-9A-Fa-f]{2})+/g, (match2) => {
16205
16286
  try {
16206
- return decoder2(match2);
16287
+ return decoder3(match2);
16207
16288
  } catch {
16208
16289
  return match2;
16209
16290
  }
@@ -18970,6 +19051,82 @@ var readableContext = /* @__PURE__ */ __name((own, shared) => {
18970
19051
  return { private: own === void 0 ? null : wireEntry(own), shared: sharedContextWire(shared) };
18971
19052
  }, "readableContext");
18972
19053
 
19054
+ // src/apps/observability.ts
19055
+ var observabilityApp = /* @__PURE__ */ __name((slug, app2) => {
19056
+ return { slug, name: app2.name, version: app2.version };
19057
+ }, "observabilityApp");
19058
+ var createDataPointBudget = /* @__PURE__ */ __name(() => {
19059
+ let spent = 0;
19060
+ return {
19061
+ take: /* @__PURE__ */ __name(() => {
19062
+ if (spent >= MAX_APP_DATA_POINTS_PER_INVOCATION) {
19063
+ return false;
19064
+ }
19065
+ spent += 1;
19066
+ return true;
19067
+ }, "take")
19068
+ };
19069
+ }, "createDataPointBudget");
19070
+ var totalLength = /* @__PURE__ */ __name((blobs) => {
19071
+ return blobs.reduce((sum, blob) => {
19072
+ return sum + utf8Length(blob);
19073
+ }, 0);
19074
+ }, "totalLength");
19075
+ var errorBlobs = /* @__PURE__ */ __name((app2, install, record2, rowId) => {
19076
+ const fixed = [app2.slug, app2.name, install.id, install.label, app2.version, record2.category, record2.action, rowId];
19077
+ let remaining = MAX_APP_DATA_POINT_BLOB_BYTES - totalLength(fixed);
19078
+ const description = truncateUtf8(record2.description, Math.max(0, remaining));
19079
+ remaining -= utf8Length(description);
19080
+ const payload = truncateUtf8(record2.payload ?? "", Math.max(0, remaining));
19081
+ remaining -= utf8Length(payload);
19082
+ const error51 = truncateUtf8(record2.error ?? "", Math.max(0, remaining));
19083
+ return [app2.slug, app2.name, install.id, install.label, app2.version, record2.category, record2.action, description, payload, error51, rowId];
19084
+ }, "errorBlobs");
19085
+ var eventBlobs = /* @__PURE__ */ __name((app2, install, record2) => {
19086
+ return [app2.slug, app2.name, install.id, install.label, app2.version, record2.name, record2.status];
19087
+ }, "eventBlobs");
19088
+ var writeAppObservability = /* @__PURE__ */ __name((env, response, target) => {
19089
+ const record2 = parseAppObservability(response.headers.get(APP_OBSERVABILITY_HEADER));
19090
+ if (record2 === null) {
19091
+ return;
19092
+ }
19093
+ const byOrd = new Map(
19094
+ target.installs.map((install) => {
19095
+ return [install.ord, install];
19096
+ })
19097
+ );
19098
+ for (const event of record2.events) {
19099
+ const install = byOrd.get(event.ord);
19100
+ if (install === void 0) {
19101
+ addSpanEvent("app observability names an install the site did not send", { level: "warn", attrs: { app: target.app.slug } });
19102
+ continue;
19103
+ }
19104
+ if (!target.budget.take()) {
19105
+ addSpanEvent("app observability dropped at the data-point cap", { level: "warn", attrs: { app: target.app.slug } });
19106
+ return;
19107
+ }
19108
+ env.APP_EVENTS.writeDataPoint({ indexes: [`${install.id}:${event.status}`], blobs: eventBlobs(target.app, install, event) });
19109
+ }
19110
+ for (const saved of record2.errors) {
19111
+ const install = byOrd.get(saved.ord);
19112
+ if (install === void 0) {
19113
+ addSpanEvent("app observability names an install the site did not send", { level: "warn", attrs: { app: target.app.slug } });
19114
+ continue;
19115
+ }
19116
+ if (!target.budget.take()) {
19117
+ addSpanEvent("app observability dropped at the data-point cap", { level: "warn", attrs: { app: target.app.slug } });
19118
+ return;
19119
+ }
19120
+ env.APP_ERRORS.writeDataPoint({
19121
+ indexes: [`${install.id}:${saved.category}`],
19122
+ blobs: errorBlobs(target.app, install, saved, ulid3())
19123
+ });
19124
+ }
19125
+ if (record2.truncated === true) {
19126
+ addSpanEvent("the app recorded more observability than one invocation carries", { level: "warn", attrs: { app: target.app.slug } });
19127
+ }
19128
+ }, "writeAppObservability");
19129
+
18973
19130
  // src/apps/read-bounded.ts
18974
19131
  var readBoundedBody = /* @__PURE__ */ __name(async (message, maxBytes) => {
18975
19132
  const reader = message.body?.getReader();
@@ -19020,7 +19177,7 @@ var adoptAppSpans = /* @__PURE__ */ __name((response) => {
19020
19177
  }, "adoptAppSpans");
19021
19178
 
19022
19179
  // src/apps/invoke.ts
19023
- var HOOK_TIMEOUT_MS = 1e4;
19180
+ var APP_INVOCATION_TIMEOUT_MS = 1e4;
19024
19181
  var MAX_EMIT_DEPTH = 2;
19025
19182
  var HOOK_URL = "https://app/__qinto/hook";
19026
19183
  var invocationContext = /* @__PURE__ */ __name((env) => {
@@ -19226,19 +19383,24 @@ var hookResponseBody = /* @__PURE__ */ __name(async (response, slug) => {
19226
19383
  }
19227
19384
  return new TextDecoder().decode(bytes);
19228
19385
  }, "hookResponseBody");
19229
- var runInvokeApp = /* @__PURE__ */ __name(async (env, { target, trigger, snapshot, context, deliverPush, writeAttribution: writeAttribution2, writeContext: writeContext2 }) => {
19386
+ var runInvokeApp = /* @__PURE__ */ __name(async (env, { target, trigger, snapshot, context, deliverPush, writeAttribution: writeAttribution2, writeContext: writeContext2, budget }) => {
19230
19387
  try {
19231
19388
  const key = await deriveAppInternalKey(context.master, context.domainId, target.slug);
19232
19389
  const response = await postHookEnvelope(env, {
19233
19390
  binding: target.app.binding,
19234
19391
  key,
19235
19392
  envelope: buildEnvelope(target, trigger, snapshot, context),
19236
- timeoutMs: HOOK_TIMEOUT_MS
19393
+ timeoutMs: APP_INVOCATION_TIMEOUT_MS
19237
19394
  });
19238
19395
  if (response === void 0) {
19239
19396
  addSpanEvent("app service binding is missing", { level: "warn", attrs: { app: target.slug, binding: target.app.binding } });
19240
19397
  return;
19241
19398
  }
19399
+ writeAppObservability(env, response, {
19400
+ app: observabilityApp(target.slug, target.app),
19401
+ installs: target.installs,
19402
+ budget
19403
+ });
19242
19404
  setSpanAttributes({ "app.status": response.status });
19243
19405
  const body = await hookResponseBody(response, target.slug);
19244
19406
  if (body === void 0) {
@@ -19322,6 +19484,9 @@ var installSchema4 = external_exports.object({
19322
19484
  });
19323
19485
  var appSchema = external_exports.object({
19324
19486
  binding: external_exports.string().min(1),
19487
+ // Required, with no default, for the same reason `events` is: the display name is stored on every
19488
+ // observability data point, and a default would be a compatibility path for an older deploy.
19489
+ name: external_exports.string().min(1).max(100),
19325
19490
  version: external_exports.string().min(1),
19326
19491
  // Absent from an old embedded registry; the default never equals a real claim, so the ui proxy
19327
19492
  // refuses stale registries with token_stale instead of turning the app off.
@@ -19609,11 +19774,16 @@ var runRelayRpc = /* @__PURE__ */ __name(async (env, { context, body, callee, in
19609
19774
  // JSON.stringify drops an undefined key, and the envelope requires `args` to be present.
19610
19775
  rpc: { caller: body.slug, fn: body.fn, args: body.args ?? null }
19611
19776
  },
19612
- timeoutMs: HOOK_TIMEOUT_MS
19777
+ timeoutMs: APP_INVOCATION_TIMEOUT_MS
19613
19778
  });
19614
19779
  if (response === void 0) {
19615
19780
  return calleeFailed(body.callee, "The callee service binding is missing.");
19616
19781
  }
19782
+ writeAppObservability(env, response, {
19783
+ app: observabilityApp(body.callee, callee),
19784
+ installs: [install],
19785
+ budget: createDataPointBudget()
19786
+ });
19617
19787
  setSpanAttributes({ "app.status": response.status });
19618
19788
  if (!response.ok) {
19619
19789
  return calleeFailed(body.callee, `The callee answered status ${response.status}.`);
@@ -19881,6 +20051,18 @@ var controlTargets = /* @__PURE__ */ __name(() => {
19881
20051
  // src/apps/control.ts
19882
20052
  var CONTROL_PATH = "/__qinto/control";
19883
20053
  var MAX_CLOCK_SKEW_MS = 5 * 60 * 1e3;
20054
+ var writeControlObservability = /* @__PURE__ */ __name((env, upstream, envelope) => {
20055
+ const installs = envelope.schedule?.installs ?? (envelope.install === void 0 ? [] : [envelope.install]);
20056
+ const app2 = siteRegistry().registry.apps[envelope.app.slug];
20057
+ if (installs.length === 0 || app2 === void 0) {
20058
+ return;
20059
+ }
20060
+ writeAppObservability(env, upstream, {
20061
+ app: observabilityApp(envelope.app.slug, { name: app2.name, version: envelope.app.version }),
20062
+ installs,
20063
+ budget: createDataPointBudget()
20064
+ });
20065
+ }, "writeControlObservability");
19884
20066
  var textBody = /* @__PURE__ */ __name(async (request) => {
19885
20067
  const bytes = await readBoundedBody(request, MAX_CONTROL_BYTES);
19886
20068
  return bytes === null ? null : new TextDecoder().decode(bytes);
@@ -19949,6 +20131,7 @@ var handleControl = /* @__PURE__ */ __name(async (request, env) => {
19949
20131
  signal: AbortSignal.timeout(1e4)
19950
20132
  });
19951
20133
  adoptAppSpans(upstream);
20134
+ writeControlObservability(env, upstream, envelope);
19952
20135
  const responseBytes = await readBoundedBody(upstream, MAX_CONTROL_BYTES);
19953
20136
  if (responseBytes === null) {
19954
20137
  return Response.json({ error: "invalid_response" }, { status: 502 });
@@ -19967,6 +20150,73 @@ var appControlRoutes = new Hono2().post(CONTROL_PATH, (c) => {
19967
20150
  return handleControl(c.req.raw, c.env);
19968
20151
  });
19969
20152
 
20153
+ // ../../libs/app-manifest/src/route.ts
20154
+ var isParam = /* @__PURE__ */ __name((segment) => {
20155
+ return segment.startsWith(":");
20156
+ }, "isParam");
20157
+ var splitRoute = /* @__PURE__ */ __name((route) => {
20158
+ return route === "/" ? [] : route.slice(1).split("/");
20159
+ }, "splitRoute");
20160
+ var splitRequest = /* @__PURE__ */ __name((requestPath) => {
20161
+ if (requestPath === "/") {
20162
+ return [];
20163
+ }
20164
+ const decoded = [];
20165
+ for (const segment of requestPath.slice(1).split("/")) {
20166
+ try {
20167
+ decoded.push(decodeURIComponent(segment));
20168
+ } catch {
20169
+ return null;
20170
+ }
20171
+ }
20172
+ return decoded;
20173
+ }, "splitRequest");
20174
+ var matches = /* @__PURE__ */ __name((parts, segments) => {
20175
+ if (parts.length !== segments.length) {
20176
+ return false;
20177
+ }
20178
+ return parts.every((part, index) => {
20179
+ const segment = segments[index];
20180
+ return isParam(part) ? segment.length > 0 : part === segment;
20181
+ });
20182
+ }, "matches");
20183
+ var isMoreSpecific = /* @__PURE__ */ __name((candidate, current) => {
20184
+ for (const [index, part] of candidate.entries()) {
20185
+ const candidateParam = isParam(part);
20186
+ const currentParam = isParam(current[index]);
20187
+ if (candidateParam !== currentParam) {
20188
+ return !candidateParam;
20189
+ }
20190
+ }
20191
+ return false;
20192
+ }, "isMoreSpecific");
20193
+ var extract = /* @__PURE__ */ __name((parts, segments) => {
20194
+ const params = {};
20195
+ for (const [index, part] of parts.entries()) {
20196
+ if (isParam(part)) {
20197
+ params[part.slice(1)] = segments[index];
20198
+ }
20199
+ }
20200
+ return params;
20201
+ }, "extract");
20202
+ var matchEndpointPath = /* @__PURE__ */ __name((routes, requestPath) => {
20203
+ const segments = splitRequest(requestPath);
20204
+ if (segments === null) {
20205
+ return void 0;
20206
+ }
20207
+ let best;
20208
+ for (const route of routes) {
20209
+ const parts = splitRoute(route);
20210
+ if (matches(parts, segments) && (best === void 0 || isMoreSpecific(parts, best.parts))) {
20211
+ best = { route, parts };
20212
+ }
20213
+ }
20214
+ if (best === void 0) {
20215
+ return void 0;
20216
+ }
20217
+ return { route: best.route, params: extract(best.parts, segments) };
20218
+ }, "matchEndpointPath");
20219
+
19970
20220
  // ../../libs/worker-access-token/src/endpoints.ts
19971
20221
  var APP_ENDPOINT_PREFIX = "/app/";
19972
20222
  var MAX_CUSTOM_ENDPOINT_LENGTH = 128;
@@ -20202,7 +20452,76 @@ var requireScope = /* @__PURE__ */ __name((bit) => {
20202
20452
  };
20203
20453
  }, "requireScope");
20204
20454
 
20455
+ // src/apps/stream-deadlines.ts
20456
+ var streamWithDeadlines = /* @__PURE__ */ __name((source, { idleMs, totalMs }) => {
20457
+ if (source === null) {
20458
+ return null;
20459
+ }
20460
+ const reader = source.getReader();
20461
+ let target;
20462
+ let totalTimer;
20463
+ let idleTimer = null;
20464
+ let stopped = false;
20465
+ const clearIdleTimer = /* @__PURE__ */ __name(() => {
20466
+ if (idleTimer !== null) {
20467
+ clearTimeout(idleTimer);
20468
+ idleTimer = null;
20469
+ }
20470
+ }, "clearIdleTimer");
20471
+ const clearTimers = /* @__PURE__ */ __name(() => {
20472
+ clearTimeout(totalTimer);
20473
+ clearIdleTimer();
20474
+ }, "clearTimers");
20475
+ const stop = /* @__PURE__ */ __name((message) => {
20476
+ stopped = true;
20477
+ clearTimers();
20478
+ const error51 = new DOMException(message, "TimeoutError");
20479
+ target.error(error51);
20480
+ void reader.cancel(error51).catch(() => void 0);
20481
+ }, "stop");
20482
+ return new ReadableStream({
20483
+ start: /* @__PURE__ */ __name((controller) => {
20484
+ target = controller;
20485
+ totalTimer = setTimeout(() => {
20486
+ stop("The app endpoint stream exceeded its maximum duration.");
20487
+ }, totalMs);
20488
+ }, "start"),
20489
+ pull: /* @__PURE__ */ __name(async (controller) => {
20490
+ idleTimer = setTimeout(() => {
20491
+ stop("The app endpoint stream stopped producing data.");
20492
+ }, idleMs);
20493
+ let result;
20494
+ try {
20495
+ result = await reader.read();
20496
+ } catch (error51) {
20497
+ stopped = true;
20498
+ clearTimers();
20499
+ throw error51;
20500
+ }
20501
+ clearIdleTimer();
20502
+ if (stopped) {
20503
+ return;
20504
+ }
20505
+ if (result.done) {
20506
+ stopped = true;
20507
+ clearTimers();
20508
+ controller.close();
20509
+ return;
20510
+ }
20511
+ controller.enqueue(result.value);
20512
+ }, "pull"),
20513
+ cancel: /* @__PURE__ */ __name(async (reason) => {
20514
+ stopped = true;
20515
+ clearTimers();
20516
+ await reader.cancel(reason);
20517
+ }, "cancel")
20518
+ });
20519
+ }, "streamWithDeadlines");
20520
+
20205
20521
  // src/apps/endpoint-proxy.ts
20522
+ var API_ENDPOINT_START_TIMEOUT_MS = 1e4;
20523
+ var API_ENDPOINT_STREAM_IDLE_TIMEOUT_MS = 15e4;
20524
+ var API_ENDPOINT_STREAM_MAX_DURATION_MS = 3e5;
20206
20525
  var DENIED_REQUEST_HEADERS = /* @__PURE__ */ new Set(["authorization", "cookie", "x-cf-data"]);
20207
20526
  var refuse = /* @__PURE__ */ __name((status, error51) => {
20208
20527
  return Response.json({ error: error51 }, { status });
@@ -20218,16 +20537,23 @@ var matchEndpoint = /* @__PURE__ */ __name((app2, path, method) => {
20218
20537
  if (!isHttpMethod(method)) {
20219
20538
  return void 0;
20220
20539
  }
20221
- const declared = app2.endpoints.find((endpoint) => {
20222
- return endpoint.path === path && endpoint.methods.includes(method);
20540
+ const serving = app2.endpoints.filter((endpoint) => {
20541
+ return endpoint.methods.includes(method);
20223
20542
  });
20224
- if (declared === void 0) {
20543
+ const matched = matchEndpointPath(
20544
+ serving.map((endpoint) => endpoint.path),
20545
+ path
20546
+ );
20547
+ if (matched === void 0) {
20225
20548
  return void 0;
20226
20549
  }
20550
+ const declared = serving.find((endpoint) => {
20551
+ return endpoint.path === matched.route;
20552
+ });
20227
20553
  if (declared.auth !== "public" && declared.auth !== "token" || declared.mode !== "webhook" && declared.mode !== "api") {
20228
20554
  return void 0;
20229
20555
  }
20230
- return { path: declared.path, method, auth: declared.auth, mode: declared.mode };
20556
+ return { route: declared.path, path, params: matched.params, method, auth: declared.auth, mode: declared.mode };
20231
20557
  }, "matchEndpoint");
20232
20558
  var authorizeEndpoint = /* @__PURE__ */ __name(async (c, slug, endpoint) => {
20233
20559
  if (endpoint.auth === "public") {
@@ -20259,12 +20585,17 @@ var passThroughResponse = /* @__PURE__ */ __name((upstream) => {
20259
20585
  const headers = new Headers(upstream.headers);
20260
20586
  headers.delete("set-cookie");
20261
20587
  headers.delete(APP_SPANS_HEADER);
20588
+ headers.delete(APP_OBSERVABILITY_HEADER);
20262
20589
  for (const name of [...headers.keys()]) {
20263
20590
  if (name.startsWith("access-control-")) {
20264
20591
  headers.delete(name);
20265
20592
  }
20266
20593
  }
20267
- return new Response(upstream.body, { status: upstream.status, headers });
20594
+ const body = streamWithDeadlines(upstream.body, {
20595
+ idleMs: API_ENDPOINT_STREAM_IDLE_TIMEOUT_MS,
20596
+ totalMs: API_ENDPOINT_STREAM_MAX_DURATION_MS
20597
+ });
20598
+ return new Response(body, { status: upstream.status, headers });
20268
20599
  }, "passThroughResponse");
20269
20600
  var parseJson3 = /* @__PURE__ */ __name((text) => {
20270
20601
  try {
@@ -20294,6 +20625,17 @@ var webhookAck = /* @__PURE__ */ __name(async (slug, upstream) => {
20294
20625
  }
20295
20626
  return refuse(502, "upstream");
20296
20627
  }, "webhookAck");
20628
+ var fetchApiResponse = /* @__PURE__ */ __name(async (service, url2, init) => {
20629
+ const controller = new AbortController();
20630
+ const timer = setTimeout(() => {
20631
+ controller.abort(new DOMException("The app endpoint did not start within its deadline.", "TimeoutError"));
20632
+ }, API_ENDPOINT_START_TIMEOUT_MS);
20633
+ try {
20634
+ return await service.fetch(url2, { ...init, signal: controller.signal });
20635
+ } finally {
20636
+ clearTimeout(timer);
20637
+ }
20638
+ }, "fetchApiResponse");
20297
20639
  var runForwardToApp = /* @__PURE__ */ __name(async (c, input) => {
20298
20640
  const { slug, app: app2, endpoint, installs, body, search } = input;
20299
20641
  const context = invocationContext(c.env);
@@ -20313,25 +20655,30 @@ var runForwardToApp = /* @__PURE__ */ __name(async (c, input) => {
20313
20655
  headers.set(
20314
20656
  ENDPOINT_CONTEXT_HEADER,
20315
20657
  JSON.stringify({
20658
+ route: endpoint.route,
20316
20659
  path: endpoint.path,
20660
+ params: endpoint.params,
20317
20661
  method: endpoint.method,
20318
20662
  mode: endpoint.mode,
20319
20663
  version: app2.version,
20664
+ site: { origin: context.origin },
20320
20665
  installs: installs.map((install) => ({ id: install.id, ord: install.ord, label: install.label }))
20321
20666
  })
20322
20667
  );
20323
20668
  let upstream;
20324
20669
  try {
20325
- upstream = await service.fetch(`https://app/__qinto/endpoint${endpoint.path}${search}`, {
20326
- method: endpoint.method,
20327
- headers,
20328
- body,
20329
- signal: AbortSignal.timeout(HOOK_TIMEOUT_MS)
20330
- });
20670
+ const url2 = `https://app/__qinto/endpoint${endpoint.route}${search}`;
20671
+ const init = { method: endpoint.method, headers, body };
20672
+ upstream = endpoint.mode === "api" ? await fetchApiResponse(service, url2, init) : await service.fetch(url2, { ...init, signal: AbortSignal.timeout(APP_INVOCATION_TIMEOUT_MS) });
20331
20673
  } catch (error51) {
20332
20674
  return upstreamFailed(slug, String(error51));
20333
20675
  }
20334
20676
  adoptAppSpans(upstream);
20677
+ writeAppObservability(c.env, upstream, {
20678
+ app: observabilityApp(slug, app2),
20679
+ installs,
20680
+ budget: createDataPointBudget()
20681
+ });
20335
20682
  setSpanAttributes({ "app.status": upstream.status });
20336
20683
  return endpoint.mode === "api" ? passThroughResponse(upstream) : webhookAck(slug, upstream);
20337
20684
  }, "runForwardToApp");
@@ -20344,7 +20691,7 @@ var forwardToApp = /* @__PURE__ */ __name(async (c, input) => {
20344
20691
  "app.slug": slug,
20345
20692
  "app.version": app2.version,
20346
20693
  "app.binding": app2.binding,
20347
- "app.hook": `endpoint.${endpoint.method} ${endpoint.path}`,
20694
+ "app.hook": `endpoint.${endpoint.method} ${endpoint.route}`,
20348
20695
  "app.endpoint_mode": endpoint.mode,
20349
20696
  "app.installs": installs.length
20350
20697
  }
@@ -20360,7 +20707,11 @@ var handleAppEndpoint = /* @__PURE__ */ __name(async (c, slug) => {
20360
20707
  return refuse(404, "not_found");
20361
20708
  }
20362
20709
  const url2 = new URL(c.req.url);
20363
- const endpoint = matchEndpoint(app2, url2.pathname.slice(`/app/${slug}`.length), c.req.method);
20710
+ const prefix = `/app/${slug}`;
20711
+ if (url2.pathname !== prefix && !url2.pathname.startsWith(`${prefix}/`)) {
20712
+ return refuse(404, "not_found");
20713
+ }
20714
+ const endpoint = matchEndpoint(app2, url2.pathname.slice(prefix.length) || "/", c.req.method);
20364
20715
  if (endpoint === void 0) {
20365
20716
  return refuse(404, "not_found");
20366
20717
  }
@@ -20373,7 +20724,7 @@ var handleAppEndpoint = /* @__PURE__ */ __name(async (c, slug) => {
20373
20724
  if (first === void 0) {
20374
20725
  return refuse(503, "inactive");
20375
20726
  }
20376
- const key = endpoint.mode === "api" ? `${slug}:${first.id}:endpoint` : `${slug}:${endpoint.path}:endpoint`;
20727
+ const key = endpoint.mode === "api" ? `${slug}:${first.id}:endpoint` : `${slug}:${endpoint.route}:endpoint`;
20377
20728
  if (await rateLimited(c.env, key)) {
20378
20729
  return refuse(429, "rate_limited");
20379
20730
  }
@@ -20540,10 +20891,24 @@ var verifyAppUiToken = /* @__PURE__ */ __name(async (input) => {
20540
20891
  return { ok: true, claims: claims.data, kid: signature.kid, key: signature.key };
20541
20892
  }, "verifyAppUiToken");
20542
20893
 
20894
+ // ../../libs/app-ui-token/src/schedule-catalog-ticket.ts
20895
+ var APP_SCHEDULE_CATALOG_TICKET_ISSUER = "qinto-portal";
20896
+ var claimsSchema2 = external_exports.object({
20897
+ iss: external_exports.literal(APP_SCHEDULE_CATALOG_TICKET_ISSUER),
20898
+ aud: external_exports.string().min(1),
20899
+ sub: external_exports.string().min(1),
20900
+ iat: external_exports.number().int(),
20901
+ exp: external_exports.number().int(),
20902
+ domainId: external_exports.string().min(1),
20903
+ appId: external_exports.string().min(1),
20904
+ appInstallId: external_exports.string().min(1),
20905
+ appVersionId: external_exports.string().min(1)
20906
+ }).strict();
20907
+
20543
20908
  // ../../libs/app-ui-token/src/write-ticket.ts
20544
20909
  var APP_UI_WRITE_TICKET_TTL_SECONDS = 60;
20545
20910
  var APP_UI_WRITE_TICKET_ISSUER = "qinto-site";
20546
- var claimsSchema2 = external_exports.object({
20911
+ var claimsSchema3 = external_exports.object({
20547
20912
  iss: external_exports.literal(APP_UI_WRITE_TICKET_ISSUER),
20548
20913
  aud: external_exports.string().min(1),
20549
20914
  sub: external_exports.string().min(1),
@@ -20629,16 +20994,22 @@ var runForwardToApp2 = /* @__PURE__ */ __name(async (c, input) => {
20629
20994
  method: c.req.method,
20630
20995
  headers,
20631
20996
  body,
20632
- signal: AbortSignal.timeout(HOOK_TIMEOUT_MS)
20997
+ signal: AbortSignal.timeout(APP_INVOCATION_TIMEOUT_MS)
20633
20998
  });
20634
20999
  } catch (error51) {
20635
21000
  return upstreamFailed2(origin, slug, String(error51));
20636
21001
  }
20637
21002
  adoptAppSpans(upstream);
21003
+ writeAppObservability(c.env, upstream, {
21004
+ app: observabilityApp(slug, app2),
21005
+ installs: [{ id: install.id, ord: install.ord, label: install.label }],
21006
+ budget: createDataPointBudget()
21007
+ });
20638
21008
  setSpanAttributes({ "app.status": upstream.status });
20639
21009
  const responseHeaders = new Headers(upstream.headers);
20640
21010
  responseHeaders.delete("set-cookie");
20641
21011
  responseHeaders.delete(APP_SPANS_HEADER);
21012
+ responseHeaders.delete(APP_OBSERVABILITY_HEADER);
20642
21013
  for (const name of [...responseHeaders.keys()]) {
20643
21014
  if (name.startsWith("access-control-")) {
20644
21015
  responseHeaders.delete(name);
@@ -23672,6 +24043,21 @@ var withoutQuery = /* @__PURE__ */ __name((url2) => {
23672
24043
  parsed.search = "";
23673
24044
  return parsed.toString();
23674
24045
  }, "withoutQuery");
24046
+ var browserGeoFrom = /* @__PURE__ */ __name((geo) => {
24047
+ if (geo === void 0) {
24048
+ return void 0;
24049
+ }
24050
+ return {
24051
+ country: geo.country ?? null,
24052
+ // EdgeTag's geoRegion is Cloudflare's subdivision code.
24053
+ region: geo.regionCode ?? null,
24054
+ state: geo.region ?? null,
24055
+ isEU: geo.isEUCountry === "yes"
24056
+ };
24057
+ }, "browserGeoFrom");
24058
+ var attachmentFieldBytes = /* @__PURE__ */ __name((name, value) => {
24059
+ return value === void 0 ? 0 : new TextEncoder().encode(`,"${name}":${JSON.stringify(value)}`).length;
24060
+ }, "attachmentFieldBytes");
23675
24061
  var connectionAttachment = /* @__PURE__ */ __name((request, geo) => {
23676
24062
  const userAgent = request.headers.get("user-agent");
23677
24063
  const attrs = {
@@ -23687,15 +24073,18 @@ var connectionAttachment = /* @__PURE__ */ __name((request, geo) => {
23687
24073
  locale: raw2.locale !== void 0 && raw2.locale.length <= MAX_HOST_LOCALE_LENGTH ? raw2.locale : void 0
23688
24074
  };
23689
24075
  const host = bounded.ip === void 0 && bounded.userAgent === void 0 && bounded.locale === void 0 ? void 0 : bounded;
23690
- const hostBytes = host === void 0 ? 0 : new TextEncoder().encode(`,"host":${JSON.stringify(host)}`).length;
24076
+ const hostBytes = attachmentFieldBytes("host", host);
24077
+ const browserGeo = browserGeoFrom(geo);
24078
+ const browserGeoBytes = attachmentFieldBytes("browserGeo", browserGeo);
23691
24079
  const cf = forwardedCf(request);
23692
24080
  const cfSuffix = cf === void 0 ? "" : `,"cf":${JSON.stringify(cf)}`;
23693
24081
  const cfBytes = new TextEncoder().encode(cfSuffix).length;
23694
24082
  const emptyAttrsBytes = new TextEncoder().encode(JSON.stringify({ attrs: {} })).length;
23695
- const includedCf = cfBytes <= MAX_ATTACHMENT_BYTES - emptyAttrsBytes - hostBytes ? cf : void 0;
23696
- const budgeted = budgetAttrs(attrs, MAX_ATTACHMENT_BYTES - hostBytes - (includedCf === void 0 ? 0 : cfBytes));
24083
+ const includedCf = cfBytes <= MAX_ATTACHMENT_BYTES - emptyAttrsBytes - hostBytes - browserGeoBytes ? cf : void 0;
24084
+ const budgeted = budgetAttrs(attrs, MAX_ATTACHMENT_BYTES - hostBytes - browserGeoBytes - (includedCf === void 0 ? 0 : cfBytes));
23697
24085
  return {
23698
24086
  ...budgeted,
24087
+ ...browserGeo === void 0 ? {} : { browserGeo },
23699
24088
  ...includedCf === void 0 ? {} : { cf: includedCf },
23700
24089
  ...host === void 0 ? {} : { host }
23701
24090
  };
@@ -23782,14 +24171,14 @@ var open = /* @__PURE__ */ __name(async (c) => {
23782
24171
 
23783
24172
  // browser-artifact/meta.json
23784
24173
  var meta_default = {
23785
- version: "0.1.1",
23786
- gitSha: "45acc2f65889b33f7cf2828b967a26fd879455f0",
23787
- builtAt: "2026-08-25T22:08:34.269Z",
23788
- hash: "8f1c955d16978159926d9c98b73ee2d0a3c3d1ea51d4ec8e1b017c3ea6892ea1"
24174
+ version: "0.1.3",
24175
+ gitSha: "66e1bc45acbe4891948cbb3851f3f43ce06abc84",
24176
+ builtAt: "2026-08-28T17:17:19.113Z",
24177
+ hash: "050bb7dd702ac407623ce130c324f3342f048b8a201e2975994d5ef54de7f19f"
23789
24178
  };
23790
24179
 
23791
24180
  // src/paths/q-js.ts
23792
- import qScript from "./9dcfd236748459af038da772477100c4bb0223b7-q.js.txt";
24181
+ import qScript from "./9026260a7a674b710223f296c5d2e7a01930cfed-q.js.txt";
23793
24182
 
23794
24183
  // src/apps-q.ts
23795
24184
  import appsQScript from "./apps-q.json";
@@ -23846,6 +24235,7 @@ var appsMetadata = /* @__PURE__ */ __name(() => {
23846
24235
  return Object.fromEntries(
23847
24236
  Object.entries(registry3.apps).map(([slug, app2]) => {
23848
24237
  const capture = declaredCapture(app2);
24238
+ const geo = app2.data.includes("geo") ? { geo: true } : {};
23849
24239
  const sharedContext = app2.data.includes(SHARED_CONTEXT_CAPABILITY) ? { sharedContext: true } : {};
23850
24240
  return [
23851
24241
  slug,
@@ -23853,6 +24243,7 @@ var appsMetadata = /* @__PURE__ */ __name(() => {
23853
24243
  category: app2.consent.category,
23854
24244
  eventResults: app2.eventResults,
23855
24245
  ...capture === void 0 ? {} : { capture },
24246
+ ...geo,
23856
24247
  ...sharedContext
23857
24248
  }
23858
24249
  ];
@@ -24499,7 +24890,7 @@ var runPool = /* @__PURE__ */ __name(async (limit, tasks) => {
24499
24890
  }
24500
24891
  await Promise.all(running);
24501
24892
  }, "runPool");
24502
- var fanOutToApps = /* @__PURE__ */ __name(async (env, trigger, snapshot, { deliverPush, writeAttribution: writeAttribution2, writeContext: writeContext2, trace }) => {
24893
+ var fanOutToApps = /* @__PURE__ */ __name(async (env, trigger, snapshot, { deliverPush, writeAttribution: writeAttribution2, writeContext: writeContext2, budget, trace }) => {
24503
24894
  const { registry: registry3, error: error51 } = siteRegistry();
24504
24895
  const targets = eligibleApps(registry3, trigger, snapshot.consent, consentEnforced(env));
24505
24896
  if (error51 === void 0 && targets.length === 0) {
@@ -24527,7 +24918,7 @@ var fanOutToApps = /* @__PURE__ */ __name(async (env, trigger, snapshot, { deliv
24527
24918
  await runPool(
24528
24919
  FAN_OUT_CONCURRENCY,
24529
24920
  capped.map((target) => {
24530
- return () => invokeApp(env, { target, trigger, snapshot, context, deliverPush, writeAttribution: writeAttribution2, writeContext: writeContext2 });
24921
+ return () => invokeApp(env, { target, trigger, snapshot, context, deliverPush, writeAttribution: writeAttribution2, writeContext: writeContext2, budget });
24531
24922
  })
24532
24923
  );
24533
24924
  }
@@ -24584,7 +24975,7 @@ var withReplacedPayload = /* @__PURE__ */ __name((entry, action, payload) => {
24584
24975
  }
24585
24976
  return { ...action, payload: parsed.data };
24586
24977
  }, "withReplacedPayload");
24587
- var postTransform = /* @__PURE__ */ __name(async (env, entry, post) => {
24978
+ var postTransform = /* @__PURE__ */ __name(async (env, entry, post, dataPoints) => {
24588
24979
  let response;
24589
24980
  try {
24590
24981
  response = await postHookEnvelope(env, { binding: entry.app.binding, ...post });
@@ -24594,6 +24985,11 @@ var postTransform = /* @__PURE__ */ __name(async (env, entry, post) => {
24594
24985
  if (response === void 0) {
24595
24986
  throw entryFailure(entry, `the ${entry.app.binding} service binding is missing`);
24596
24987
  }
24988
+ writeAppObservability(env, response, {
24989
+ app: observabilityApp(entry.slug, entry.app),
24990
+ installs: [entry.install],
24991
+ budget: dataPoints
24992
+ });
24597
24993
  setSpanAttributes({ "app.status": response.status });
24598
24994
  if (!response.ok) {
24599
24995
  throw entryFailure(entry, `the hook answered status ${response.status}`);
@@ -24608,14 +25004,14 @@ var postTransform = /* @__PURE__ */ __name(async (env, entry, post) => {
24608
25004
  throw entryFailure(entry, String(error51));
24609
25005
  }
24610
25006
  }, "postTransform");
24611
- var runTransformEntry = /* @__PURE__ */ __name(async (env, { entry, action, snapshot, context, timeoutMs }) => {
25007
+ var runTransformEntry = /* @__PURE__ */ __name(async (env, { entry, action, snapshot, context, timeoutMs, dataPoints }) => {
24612
25008
  const target = { slug: entry.slug, app: entry.app, installs: [entry.install] };
24613
25009
  const envelope = {
24614
25010
  ...buildEnvelope(target, { kind: "event", action }, snapshot, context),
24615
25011
  kind: "transform"
24616
25012
  };
24617
25013
  const key = await deriveAppInternalKey(context.master, context.domainId, entry.slug);
24618
- const verdict = validateEnvelopeResponse(await postTransform(env, entry, { key, envelope, timeoutMs }), 1);
25014
+ const verdict = validateEnvelopeResponse(await postTransform(env, entry, { key, envelope, timeoutMs }, dataPoints), 1);
24619
25015
  if (!verdict.ok) {
24620
25016
  throw entryFailure(entry, verdict.reason);
24621
25017
  }
@@ -24683,7 +25079,7 @@ var buildRelease = /* @__PURE__ */ __name((env, { collected, action, deliverPush
24683
25079
  }
24684
25080
  };
24685
25081
  }, "buildRelease");
24686
- var runTransformChain = /* @__PURE__ */ __name(async (env, { action, snapshot, deliverPush, writeAttribution: writeAttribution2, writeContext: writeContext2 }) => {
25082
+ var runTransformChain = /* @__PURE__ */ __name(async (env, { action, snapshot, deliverPush, writeAttribution: writeAttribution2, writeContext: writeContext2, dataPoints }) => {
24687
25083
  const { registry: registry3, error: error51 } = siteRegistry();
24688
25084
  if (error51 !== void 0) {
24689
25085
  throw new TransformChainError(`the apps registry is malformed: ${error51}`);
@@ -24703,7 +25099,14 @@ var runTransformChain = /* @__PURE__ */ __name(async (env, { action, snapshot, d
24703
25099
  for (const entry of entries) {
24704
25100
  const stepped = entryBudget(budget, entry, Date.now());
24705
25101
  budget = stepped.budget;
24706
- const verdict = await invokeTransformEntry(env, { entry, action: current, snapshot, context, timeoutMs: stepped.timeoutMs });
25102
+ const verdict = await invokeTransformEntry(env, {
25103
+ entry,
25104
+ action: current,
25105
+ snapshot,
25106
+ context,
25107
+ timeoutMs: stepped.timeoutMs,
25108
+ dataPoints
25109
+ });
24707
25110
  if (verdict.effects.accepted.length > 0) {
24708
25111
  collected.push({ slug: entry.slug, installs: [entry.install], groups: verdict.effects.accepted });
24709
25112
  }
@@ -25684,7 +26087,11 @@ var AnonDO = class extends DurableObject2 {
25684
26087
  const attachment = connectionAttachment(request, geo);
25685
26088
  server.serializeAttachment(attachment);
25686
26089
  this.deliverSharedContext(server);
25687
- this.appFanOut({ kind: "connect", ...attachment.cf === void 0 ? {} : { geo: attachment.cf }, host: attachment.host }, userId);
26090
+ this.appFanOut(
26091
+ { kind: "connect", ...attachment.cf === void 0 ? {} : { geo: attachment.cf }, host: attachment.host },
26092
+ userId,
26093
+ createDataPointBudget()
26094
+ );
25688
26095
  return new Response(null, { status: 101, webSocket: client });
25689
26096
  });
25690
26097
  }
@@ -25739,7 +26146,14 @@ var AnonDO = class extends DurableObject2 {
25739
26146
  return;
25740
26147
  }
25741
26148
  if (parsed.frame.t === "getSession") {
25742
- this.respond(ws, { t: "session", ref: parsed.ref, anonId: userId, globalId: linkedGlobalId(this.kv) });
26149
+ this.respond(ws, {
26150
+ t: "session",
26151
+ ref: parsed.ref,
26152
+ anonId: userId,
26153
+ globalId: linkedGlobalId(this.kv),
26154
+ ip: attachment?.host?.ip ?? null,
26155
+ geo: attachment?.browserGeo ?? null
26156
+ });
25743
26157
  return;
25744
26158
  }
25745
26159
  if (parsed.frame.t === "capture") {
@@ -26025,12 +26439,13 @@ var AnonDO = class extends DurableObject2 {
26025
26439
  if (isNoopAction(action)) {
26026
26440
  return;
26027
26441
  }
26028
- const transformed = await this.transformAction(action);
26442
+ const dataPoints = createDataPointBudget();
26443
+ const transformed = await this.transformAction(action, dataPoints);
26029
26444
  if (transformed === null) {
26030
26445
  return;
26031
26446
  }
26032
26447
  recorded = transformed.action;
26033
- await this.runAction(transformed.action, transformed.releaseEmissions);
26448
+ await this.runAction(transformed.action, transformed.releaseEmissions, dataPoints);
26034
26449
  } finally {
26035
26450
  this.recordActionBlocks(recorded);
26036
26451
  }
@@ -26040,9 +26455,9 @@ var AnonDO = class extends DurableObject2 {
26040
26455
  actionBlocks(action, this.appsSnapshot(action.userId), this.wiped ? [] : allIdentifiers(this.sql, this.identifiersMirror))
26041
26456
  );
26042
26457
  }
26043
- async runAction(current, releaseEmissions) {
26458
+ async runAction(current, releaseEmissions, dataPoints) {
26044
26459
  if (current.type !== "alias" && isDeliveryOnlyAction(current)) {
26045
- this.appFanOut({ kind: "event", action: current }, current.userId);
26460
+ this.appFanOut({ kind: "event", action: current }, current.userId, dataPoints);
26046
26461
  this.releaseTransformEmissions(releaseEmissions);
26047
26462
  return;
26048
26463
  }
@@ -26078,7 +26493,7 @@ var AnonDO = class extends DurableObject2 {
26078
26493
  } else {
26079
26494
  await this.ingestConsent(current);
26080
26495
  }
26081
- this.appFanOut({ kind: "event", action: current }, current.userId);
26496
+ this.appFanOut({ kind: "event", action: current }, current.userId, dataPoints);
26082
26497
  this.releaseTransformEmissions(releaseEmissions);
26083
26498
  } finally {
26084
26499
  if (novelIdentifier) {
@@ -26087,7 +26502,7 @@ var AnonDO = class extends DurableObject2 {
26087
26502
  }
26088
26503
  }
26089
26504
  // null means a transform dropped the action: the turn succeeds with nothing persisted (§5.4).
26090
- async transformAction(action) {
26505
+ async transformAction(action, dataPoints) {
26091
26506
  if (action.type !== "track" && action.type !== "identify") {
26092
26507
  return { action, releaseEmissions: null };
26093
26508
  }
@@ -26096,7 +26511,8 @@ var AnonDO = class extends DurableObject2 {
26096
26511
  snapshot: this.appsSnapshot(action.userId),
26097
26512
  deliverPush: this.pushDelivery,
26098
26513
  writeAttribution: this.attributionDelivery,
26099
- writeContext: this.contextDelivery
26514
+ writeContext: this.contextDelivery,
26515
+ dataPoints
26100
26516
  });
26101
26517
  if (outcome.dropped) {
26102
26518
  this.releaseTransformEmissions(outcome.releaseEmissions);
@@ -26132,13 +26548,14 @@ var AnonDO = class extends DurableObject2 {
26132
26548
  }
26133
26549
  this.ctx.waitUntil(release().catch((error51) => console.warn("transform emission release failed", { error: error51 })));
26134
26550
  }
26135
- appFanOut(trigger, anonId) {
26551
+ appFanOut(trigger, anonId, dataPoints) {
26136
26552
  const snapshot = this.appsSnapshot(anonId);
26137
26553
  this.ctx.waitUntil(
26138
26554
  fanOutToApps(this.env, trigger, snapshot, {
26139
26555
  deliverPush: this.pushDelivery,
26140
26556
  writeAttribution: this.attributionDelivery,
26141
26557
  writeContext: this.contextDelivery,
26558
+ budget: dataPoints,
26142
26559
  trace: currentTraceContext()
26143
26560
  }).catch((error51) => console.warn("app fan-out failed", { error: error51 }))
26144
26561
  );