@blotout/qinto-cli 0.1.19 → 0.1.21

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.
@@ -20992,10 +20992,13 @@ var variableSchema = external_exports.object({
20992
20992
  label: external_exports.string().min(1).max(100),
20993
20993
  required: external_exports.boolean().optional(),
20994
20994
  browser: external_exports.boolean().optional(),
20995
+ owner: external_exports.enum(["site", "developer"]).optional(),
20995
20996
  helpText: external_exports.string().min(1).max(MAX_VARIABLE_HELP_TEXT).optional(),
20996
20997
  helpUrl: httpsUrl("helpUrl").optional()
20997
20998
  }).strict().refine((variable) => !(variable.kind === "secret" && variable.browser === true), {
20998
20999
  message: "A secret variable can never be browser-exposed"
21000
+ }).refine((variable) => !(variable.required === true && variable.owner === "developer"), {
21001
+ message: "A developer-owned variable cannot be required"
20999
21002
  });
21000
21003
  var appVariableSchema = external_exports.object({
21001
21004
  name: external_exports.string().min(1).max(64).regex(VARIABLE_NAME),
@@ -21467,6 +21470,8 @@ var MAX_CAPTURE_WRITES_PER_INSTALL = 10;
21467
21470
  var MAX_CONTEXT_WRITES_PER_INSTALL = 2;
21468
21471
  var CONTEXT_LEVELS = ["private", "shared"];
21469
21472
  var PUSH_BROADCASTS = ["install", "app", "site"];
21473
+ var ACTION_SOURCES = ["website", "api", "app", "scheduled", "webhook", "tool", "workflow"];
21474
+ var APP_HANDLER_SOURCES = ["scheduled", "webhook", "tool", "workflow"];
21470
21475
  var MAX_PRIVATE_CONTEXT_ENTRY_BYTES = 32 * 1024;
21471
21476
  var MAX_SHARED_CONTEXT_ENTRY_BYTES = 4 * 1024;
21472
21477
  var MAX_PUSH_BYTES = 16 * 1024;
@@ -21495,9 +21500,10 @@ var effectGroupSchema = external_exports.object({
21495
21500
  context: external_exports.array(contextWriteSchema).optional()
21496
21501
  }).strict();
21497
21502
  var captureValueSchema = external_exports.object({ value: external_exports.string().max(512), createdAt: external_exports.number().int(), updatedAt: external_exports.number().int(), type: external_exports.enum(CAPTURE_TYPES) }).strict();
21503
+ var pageSchema = external_exports.object({ url: external_exports.string().min(1).max(4096), referrer: external_exports.string().min(1).max(4096).optional() }).strict();
21498
21504
  var actionContextFields = {
21499
21505
  occurredAt: external_exports.number().int().nonnegative().safe(),
21500
- page: external_exports.object({ url: external_exports.string().min(1).max(4096), referrer: external_exports.string().min(1).max(4096).optional() }).strict().optional(),
21506
+ page: pageSchema.optional(),
21501
21507
  network: external_exports.object({
21502
21508
  ip: external_exports.string().min(1).max(64).optional(),
21503
21509
  userAgent: external_exports.string().min(1).max(512).optional(),
@@ -21525,6 +21531,8 @@ var envelopeRequestSchema = external_exports.object({
21525
21531
  }).strict().optional(),
21526
21532
  consent: external_exports.object({ enabled: external_exports.boolean(), categories: external_exports.record(external_exports.string(), external_exports.boolean()), apps: external_exports.record(external_exports.string(), external_exports.boolean()) }).strict().optional(),
21527
21533
  geo: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
21534
+ // Connect envelopes only: the page the visitor's socket opened from.
21535
+ page: pageSchema.optional(),
21528
21536
  // Only what this app captured or wrote for this visitor; `updatedAt` is when the current value
21529
21537
  // was first seen, which is the click time a CAPI `fbc` is built from.
21530
21538
  captures: external_exports.record(external_exports.string().max(64), captureValueSchema).optional(),
@@ -21542,6 +21550,9 @@ var envelopeRequestSchema = external_exports.object({
21542
21550
  userId: external_exports.string().min(1).max(64),
21543
21551
  payload: external_exports.record(external_exports.string(), external_exports.unknown()),
21544
21552
  receivedAt: external_exports.number().int(),
21553
+ // Optional on the wire: an app worker redeploys before its site worker, so this reader must
21554
+ // accept an envelope a site worker predating the field produces.
21555
+ source: external_exports.enum(ACTION_SOURCES).optional(),
21545
21556
  context: actionContextSchema.optional(),
21546
21557
  browser: external_exports.record(external_exports.string().min(1).max(64), external_exports.unknown()).optional(),
21547
21558
  origin: external_exports.object({ app: external_exports.string().min(1).max(30), install: external_exports.number().int().min(1), depth: external_exports.number().int().min(0) }).strict().optional()
@@ -26297,14 +26308,15 @@ var captureAppsField = external_exports.record(external_exports.string().max(30)
26297
26308
  description: "Values to record, keyed by app slug and then by capture name. A name the app does not declare is dropped.",
26298
26309
  example: { facebook: { fbclid: "IwAR0abc" }, "google-ads": { gclid: "Cj0KCQ" } }
26299
26310
  });
26311
+ var pageContextField = external_exports.object({
26312
+ url: external_exports.string().min(1).max(4096),
26313
+ referrer: external_exports.string().min(1).max(4096).optional()
26314
+ }).strict();
26300
26315
  var serverActionContextField = external_exports.object({
26301
26316
  occurredAt: external_exports.number().int().nonnegative().safe().optional().openapi({
26302
26317
  description: "When the visitor action occurred, as Unix milliseconds; defaults to Qinto receipt time."
26303
26318
  }),
26304
- page: external_exports.object({
26305
- url: external_exports.string().min(1).max(4096),
26306
- referrer: external_exports.string().min(1).max(4096).optional()
26307
- }).strict().optional(),
26319
+ page: pageContextField.optional(),
26308
26320
  network: external_exports.object({
26309
26321
  ip: external_exports.string().min(1).max(64).optional(),
26310
26322
  userAgent: external_exports.string().min(1).max(512).optional(),
@@ -26868,7 +26880,6 @@ var submitAction = /* @__PURE__ */ __name(async (env, userId, args, request) =>
26868
26880
  ...args,
26869
26881
  userId: resolved.id,
26870
26882
  identifier: resolved.identifier,
26871
- source: "http",
26872
26883
  receivedAt,
26873
26884
  context: serverActionContext(args.context, receivedAt),
26874
26885
  geo: request?.cf,
@@ -27159,6 +27170,90 @@ var lookupPersonForApp = /* @__PURE__ */ __name(async (env, slug, id, afterRowId
27159
27170
  return page === null ? null : personOf(globalId, page);
27160
27171
  }, "lookupPersonForApp");
27161
27172
 
27173
+ // src/apps/broker-fields.ts
27174
+ var identityFields = {
27175
+ // Derived, never a literal: the client stamps ENVELOPE_VERSION on every body, so a hardcoded
27176
+ // number here silently 400s the whole channel the next time the envelope version moves.
27177
+ v: external_exports.literal(ENVELOPE_VERSION),
27178
+ slug: external_exports.string().min(1).max(30),
27179
+ key: external_exports.string().min(1)
27180
+ };
27181
+
27182
+ // src/apps/emit-payload.ts
27183
+ var emitPayloadSchema = external_exports.object({
27184
+ event: external_exports.string().min(1),
27185
+ eventId: external_exports.string().min(1).optional(),
27186
+ properties: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
27187
+ }).strict();
27188
+ var emittedEventId = /* @__PURE__ */ __name((eventId, properties2) => {
27189
+ if (eventId !== void 0) {
27190
+ return eventId;
27191
+ }
27192
+ const orderId = properties2?.["orderId"];
27193
+ if (typeof orderId === "string" && orderId !== "") {
27194
+ return orderId;
27195
+ }
27196
+ return `evt_${ulid3()}`;
27197
+ }, "emittedEventId");
27198
+
27199
+ // src/apps/rate-limit.ts
27200
+ var limiterAbsenceLogged = false;
27201
+ var rateLimited = /* @__PURE__ */ __name(async (env, key, binding = "BROKER_RATE_LIMITER") => {
27202
+ const limiter = env[binding];
27203
+ if (limiter === void 0) {
27204
+ if (!limiterAbsenceLogged) {
27205
+ limiterAbsenceLogged = true;
27206
+ addSpanEvent("rate limiter binding is absent; limits are off", { level: "warn", attrs: { binding } });
27207
+ }
27208
+ return false;
27209
+ }
27210
+ const outcome = await limiter.limit({ key });
27211
+ return !outcome.success;
27212
+ }, "rateLimited");
27213
+
27214
+ // src/apps/broker-track.ts
27215
+ var trackBody = external_exports.object({
27216
+ ...identityFields,
27217
+ kind: external_exports.literal("track"),
27218
+ source: external_exports.enum(APP_HANDLER_SOURCES),
27219
+ userId: external_exports.string().min(1).max(64),
27220
+ installId: external_exports.string().min(1).max(64),
27221
+ event: emitSchema
27222
+ }).strict();
27223
+ var handleTrack = /* @__PURE__ */ __name(async (env, body) => {
27224
+ const app2 = siteRegistry().registry.apps[body.slug];
27225
+ if (app2 === void 0) {
27226
+ return Response.json({ error: "forbidden" }, { status: 403 });
27227
+ }
27228
+ if (!app2.installs.some((install) => install.active && install.id === body.installId)) {
27229
+ return Response.json({ error: "invalid_install" }, { status: 400 });
27230
+ }
27231
+ const parsed = emitPayloadSchema.safeParse(body.event.payload);
27232
+ if (!parsed.success) {
27233
+ return Response.json({ error: "invalid_event" }, { status: 400 });
27234
+ }
27235
+ if (!isCanonicalId(body.userId)) {
27236
+ return Response.json({ error: "invalid_user" }, { status: 400 });
27237
+ }
27238
+ if (await rateLimited(env, `${body.slug}:${body.installId}:track`)) {
27239
+ return Response.json({ error: "rate_limited" }, { status: 429 });
27240
+ }
27241
+ const site = ownSite(env);
27242
+ if (!await anonStub(env, body.userId).appAllowed(body.slug, site, currentTraceContext())) {
27243
+ return Response.json({ error: "forbidden" }, { status: 403 });
27244
+ }
27245
+ const { event, eventId, properties: properties2 } = parsed.data;
27246
+ const action = buildAction({
27247
+ type: "track",
27248
+ userId: body.userId,
27249
+ source: body.source,
27250
+ payload: { event, eventId: emittedEventId(eventId, properties2), properties: properties2 ?? {} },
27251
+ context: { source: "app", occurredAt: body.event.occurredAt, offline: false, replay: false }
27252
+ });
27253
+ await ingest(env, action, site);
27254
+ return Response.json({ accepted: true }, { status: 202 });
27255
+ }, "handleTrack");
27256
+
27162
27257
  // src/id-graph/anon/context.ts
27163
27258
  var LEVEL_KINDS = {
27164
27259
  private: { kv: "context_private_kv", text: "context_private_text" },
@@ -27596,6 +27691,7 @@ var eventAction = /* @__PURE__ */ __name((target, trigger, snapshot) => {
27596
27691
  userId: snapshot.anonId,
27597
27692
  payload: trigger.action.payload,
27598
27693
  receivedAt: trigger.action.receivedAt,
27694
+ source: trigger.action.source,
27599
27695
  ...buckets.includes("eventContext") && trigger.action.context !== void 0 ? { context: trigger.action.context } : {},
27600
27696
  ...trigger.action.browserResults?.[target.slug] === void 0 ? {} : { browser: trigger.action.browserResults[target.slug] },
27601
27697
  ...trigger.action.origin !== void 0 ? { origin: trigger.action.origin } : {}
@@ -27626,6 +27722,7 @@ var buildEnvelope = /* @__PURE__ */ __name((target, trigger, snapshot, context)
27626
27722
  },
27627
27723
  ...buckets.includes("consentState") ? { consent: { enabled: context.consentEnforced, categories: snapshot.consent.categories, apps: snapshot.consent.apps } } : {},
27628
27724
  ...geoAllowed && currentGeo !== void 0 ? { geo: currentGeo } : {},
27725
+ ...trigger.kind === "connect" && trigger.page !== void 0 && buckets.includes("eventContext") ? { page: trigger.page } : {},
27629
27726
  // Not gated on a capabilities.data bucket: an app only ever sees what it captured or wrote.
27630
27727
  ...captures === void 0 ? {} : { captures },
27631
27728
  ...visitorContext === void 0 ? {} : { context: visitorContext },
@@ -27633,21 +27730,6 @@ var buildEnvelope = /* @__PURE__ */ __name((target, trigger, snapshot, context)
27633
27730
  ...trigger.kind === "event" ? { action: eventAction(target, trigger, snapshot) } : {}
27634
27731
  };
27635
27732
  }, "buildEnvelope");
27636
- var emitPayloadSchema = external_exports.object({
27637
- event: external_exports.string().min(1),
27638
- eventId: external_exports.string().min(1).optional(),
27639
- properties: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
27640
- }).strict();
27641
- var emittedEventId = /* @__PURE__ */ __name((eventId, properties2) => {
27642
- if (eventId !== void 0) {
27643
- return eventId;
27644
- }
27645
- const orderId = properties2?.["orderId"];
27646
- if (typeof orderId === "string" && orderId !== "") {
27647
- return orderId;
27648
- }
27649
- return `evt_${ulid3()}`;
27650
- }, "emittedEventId");
27651
27733
  var ingestEmit = /* @__PURE__ */ __name(async (ingest2, { slug, ord, depth, emit, anonId, geo, host, context }) => {
27652
27734
  const parsed = emitPayloadSchema.safeParse(emit.payload);
27653
27735
  if (!parsed.success) {
@@ -27826,31 +27908,9 @@ var invokeApp = /* @__PURE__ */ __name(async (env, input2) => {
27826
27908
  );
27827
27909
  }, "invokeApp");
27828
27910
 
27829
- // src/apps/rate-limit.ts
27830
- var limiterAbsenceLogged = false;
27831
- var rateLimited = /* @__PURE__ */ __name(async (env, key, binding = "BROKER_RATE_LIMITER") => {
27832
- const limiter = env[binding];
27833
- if (limiter === void 0) {
27834
- if (!limiterAbsenceLogged) {
27835
- limiterAbsenceLogged = true;
27836
- addSpanEvent("rate limiter binding is absent; limits are off", { level: "warn", attrs: { binding } });
27837
- }
27838
- return false;
27839
- }
27840
- const outcome = await limiter.limit({ key });
27841
- return !outcome.success;
27842
- }, "rateLimited");
27843
-
27844
27911
  // src/apps/broker.ts
27845
27912
  var MAX_RPC_DEPTH = 3;
27846
27913
  var BROKER_PATHS = ["/__qinto/push", "/__qinto/rpc"];
27847
- var identityFields = {
27848
- // Derived, never a literal: the client stamps ENVELOPE_VERSION on every body, so a hardcoded
27849
- // number here silently 400s the whole channel the next time the envelope version moves.
27850
- v: external_exports.literal(ENVELOPE_VERSION),
27851
- slug: external_exports.string().min(1).max(30),
27852
- key: external_exports.string().min(1)
27853
- };
27854
27914
  var pushBody = external_exports.object({
27855
27915
  ...identityFields,
27856
27916
  kind: external_exports.literal("push"),
@@ -27920,7 +27980,8 @@ var brokerBody = external_exports.discriminatedUnion("kind", [
27920
27980
  contextReadBody,
27921
27981
  contextWriteBody,
27922
27982
  factsReadBody,
27923
- identityReadBody
27983
+ identityReadBody,
27984
+ trackBody
27924
27985
  ]);
27925
27986
  var BODY_ENVELOPE_ALLOWANCE_BYTES = 2 * 1024;
27926
27987
  var MAX_BODY_BYTES = MAX_RPC_BYTES + BODY_ENVELOPE_ALLOWANCE_BYTES;
@@ -28307,6 +28368,9 @@ var handleBroker = /* @__PURE__ */ __name(async (request, env) => {
28307
28368
  case "identityRead": {
28308
28369
  return handleIdentityRead(env, body);
28309
28370
  }
28371
+ case "track": {
28372
+ return handleTrack(env, body);
28373
+ }
28310
28374
  }
28311
28375
  }, "handleBroker");
28312
28376
  var brokerRoutes = new Hono2();
@@ -28749,6 +28813,7 @@ var requireScope = /* @__PURE__ */ __name((bit) => {
28749
28813
  if ((entry.mask & bit) === 0) {
28750
28814
  return c.json({ error: "forbidden" }, 403);
28751
28815
  }
28816
+ c.set("actionSource", entry.app === null ? "api" : "app");
28752
28817
  return next();
28753
28818
  };
28754
28819
  }, "requireScope");
@@ -30027,9 +30092,141 @@ var defaultHook = /* @__PURE__ */ __name((result, c) => {
30027
30092
  }, "defaultHook");
30028
30093
  var createApiRouter = /* @__PURE__ */ __name(() => new OpenAPIHono({ defaultHook }), "createApiRouter");
30029
30094
 
30095
+ // src/id-graph/anon/connection.ts
30096
+ var CONNECTION_CF_HEADER = "x-connection-cf";
30097
+ var SESSION_HEADER = "x-session";
30098
+ var OPEN_PAGE_PARAMS = ["url", "referrer"];
30099
+ var MAX_HOST_USER_AGENT_LENGTH = 512;
30100
+ var MAX_HOST_LOCALE_LENGTH = 64;
30101
+ var INTERNAL_HEADERS = /* @__PURE__ */ new Set([
30102
+ "x-user-id",
30103
+ "x-new-user",
30104
+ "x-geo",
30105
+ SITE_ID_HEADER,
30106
+ CONNECTION_CF_HEADER,
30107
+ SESSION_HEADER,
30108
+ TRACE_HEADER,
30109
+ CF_DATA_HEADER
30110
+ ]);
30111
+ var REDACTED_HEADERS = /* @__PURE__ */ new Set(["authorization"]);
30112
+ var encodeConnectionCf = /* @__PURE__ */ __name((cf) => {
30113
+ if (cf === void 0) {
30114
+ return void 0;
30115
+ }
30116
+ const encoded = headerJson(cf);
30117
+ return encoded.length > MAX_ATTACHMENT_BYTES ? void 0 : encoded;
30118
+ }, "encodeConnectionCf");
30119
+ var forwardedCf = /* @__PURE__ */ __name((request) => {
30120
+ const raw2 = request.headers.get(CONNECTION_CF_HEADER);
30121
+ if (raw2 === null) {
30122
+ return void 0;
30123
+ }
30124
+ return JSON.parse(raw2);
30125
+ }, "forwardedCf");
30126
+ var forwardedSession = /* @__PURE__ */ __name((request) => {
30127
+ const raw2 = request.headers.get(SESSION_HEADER);
30128
+ if (raw2 === null) {
30129
+ return void 0;
30130
+ }
30131
+ return JSON.parse(raw2);
30132
+ }, "forwardedSession");
30133
+ var withoutPageParams = /* @__PURE__ */ __name((url2) => {
30134
+ const parsed = new URL(url2);
30135
+ for (const param of OPEN_PAGE_PARAMS) {
30136
+ parsed.searchParams.delete(param);
30137
+ }
30138
+ return parsed.toString();
30139
+ }, "withoutPageParams");
30140
+ var connectPage = /* @__PURE__ */ __name((request) => {
30141
+ const params = new URL(request.url).searchParams;
30142
+ const url2 = params.get("url");
30143
+ if (url2 === null) {
30144
+ return void 0;
30145
+ }
30146
+ const referrer = params.get("referrer");
30147
+ const parsed = pageContextField.safeParse({ url: url2, ...referrer === null ? {} : { referrer } });
30148
+ return parsed.success ? parsed.data : void 0;
30149
+ }, "connectPage");
30150
+ var connectAttrs = /* @__PURE__ */ __name((request, geo) => {
30151
+ const headers = {};
30152
+ for (const [name, value] of request.headers) {
30153
+ const lower = name.toLowerCase();
30154
+ if (!INTERNAL_HEADERS.has(lower) && !REDACTED_HEADERS.has(lower)) {
30155
+ headers[name] = value;
30156
+ }
30157
+ }
30158
+ const cf = forwardedCf(request);
30159
+ return {
30160
+ "url.full": withoutPageParams(request.url),
30161
+ "http.request.headers": JSON.stringify(headers),
30162
+ ...cf === void 0 ? {} : { "qinto.cf": JSON.stringify(cf) },
30163
+ ...geo === void 0 ? {} : { "qinto.geo": JSON.stringify(loggableGeo(geo)) }
30164
+ };
30165
+ }, "connectAttrs");
30166
+ var withoutQuery = /* @__PURE__ */ __name((url2) => {
30167
+ const parsed = new URL(url2);
30168
+ parsed.search = "";
30169
+ return parsed.toString();
30170
+ }, "withoutQuery");
30171
+ var browserGeoFrom = /* @__PURE__ */ __name((geo) => {
30172
+ if (geo === void 0) {
30173
+ return void 0;
30174
+ }
30175
+ return {
30176
+ country: geo.country ?? null,
30177
+ // EdgeTag's geoRegion is Cloudflare's subdivision code.
30178
+ region: geo.regionCode ?? null,
30179
+ state: geo.region ?? null,
30180
+ isEU: geo.isEUCountry === "yes"
30181
+ };
30182
+ }, "browserGeoFrom");
30183
+ var attachmentFieldBytes = /* @__PURE__ */ __name((name, value) => {
30184
+ return value === void 0 ? 0 : new TextEncoder().encode(`,"${name}":${JSON.stringify(value)}`).length;
30185
+ }, "attachmentFieldBytes");
30186
+ var boundedHost = /* @__PURE__ */ __name((request) => {
30187
+ const raw2 = hostFromRequest(request);
30188
+ const bounded = {
30189
+ ip: raw2.ip,
30190
+ userAgent: raw2.userAgent !== void 0 && raw2.userAgent.length <= MAX_HOST_USER_AGENT_LENGTH ? raw2.userAgent : void 0,
30191
+ locale: raw2.locale !== void 0 && raw2.locale.length <= MAX_HOST_LOCALE_LENGTH ? raw2.locale : void 0
30192
+ };
30193
+ return bounded.ip === void 0 && bounded.userAgent === void 0 && bounded.locale === void 0 ? void 0 : bounded;
30194
+ }, "boundedHost");
30195
+ var connectionAttachment = /* @__PURE__ */ __name((request, geo, siteId) => {
30196
+ const userAgent = request.headers.get("user-agent");
30197
+ const attrs = {
30198
+ "url.full": withoutQuery(request.url),
30199
+ ...geo?.country === void 0 ? {} : { "qinto.country": geo.country },
30200
+ ...geo?.city === void 0 ? {} : { "qinto.city": geo.city },
30201
+ ...userAgent === null ? {} : { user_agent: userAgent }
30202
+ };
30203
+ const host = boundedHost(request);
30204
+ const hostBytes = attachmentFieldBytes("host", host);
30205
+ const browserGeo = browserGeoFrom(geo);
30206
+ const browserGeoBytes = attachmentFieldBytes("browserGeo", browserGeo);
30207
+ const session2 = forwardedSession(request);
30208
+ const sessionBytes = attachmentFieldBytes("session", session2);
30209
+ const siteBytes = attachmentFieldBytes("siteId", siteId);
30210
+ const cf = forwardedCf(request);
30211
+ const cfSuffix = cf === void 0 ? "" : `,"cf":${JSON.stringify(cf)}`;
30212
+ const cfBytes = new TextEncoder().encode(cfSuffix).length;
30213
+ const emptyAttrsBytes = new TextEncoder().encode(JSON.stringify({ attrs: {} })).length;
30214
+ const fixedBytes = siteBytes + sessionBytes + hostBytes + browserGeoBytes;
30215
+ const includedCf = cfBytes <= MAX_ATTACHMENT_BYTES - emptyAttrsBytes - fixedBytes ? cf : void 0;
30216
+ const budgeted = budgetAttrs(attrs, MAX_ATTACHMENT_BYTES - fixedBytes - (includedCf === void 0 ? 0 : cfBytes));
30217
+ return {
30218
+ ...budgeted,
30219
+ siteId,
30220
+ ...session2 === void 0 ? {} : { session: session2 },
30221
+ ...browserGeo === void 0 ? {} : { browserGeo },
30222
+ ...includedCf === void 0 ? {} : { cf: includedCf },
30223
+ ...host === void 0 ? {} : { host }
30224
+ };
30225
+ }, "connectionAttachment");
30226
+
30030
30227
  // src/helpers/request-span.ts
30031
30228
  var OMITTED_HEADERS = /* @__PURE__ */ new Set(["authorization", DISPATCH_HEADER, CF_DATA_HEADER]);
30032
- var requestAttrs = /* @__PURE__ */ __name((request) => {
30229
+ var requestAttrs = /* @__PURE__ */ __name((request, pathname) => {
30033
30230
  const headers = {};
30034
30231
  for (const [name, value] of request.headers) {
30035
30232
  if (!OMITTED_HEADERS.has(name.toLowerCase())) {
@@ -30039,7 +30236,7 @@ var requestAttrs = /* @__PURE__ */ __name((request) => {
30039
30236
  const cf = request.cf;
30040
30237
  return {
30041
30238
  "http.request.method": request.method,
30042
- "url.full": request.url,
30239
+ "url.full": pathname === "/open" ? withoutPageParams(request.url) : request.url,
30043
30240
  "http.request.headers": JSON.stringify(headers),
30044
30241
  ...cf === void 0 ? {} : { "qinto.cf": JSON.stringify(cf) }
30045
30242
  };
@@ -30054,7 +30251,7 @@ var requestSpan = /* @__PURE__ */ __name(async (c, next) => {
30054
30251
  const pathname = new URL(c.req.url).pathname;
30055
30252
  const dispatch = parseDispatchTrace(c.req.raw.headers.get(DISPATCH_HEADER));
30056
30253
  const trace = dispatch === void 0 ? inboundTrace(c.req.raw, pathname) : { traceId: dispatch.traceId, parent: dispatch.spanId };
30057
- await runInvocation({ name: `${c.req.method} ${pathname}`, trace, attrs: requestAttrs(c.req.raw) }, async () => {
30254
+ await runInvocation({ name: `${c.req.method} ${pathname}`, trace, attrs: requestAttrs(c.req.raw, pathname) }, async () => {
30058
30255
  try {
30059
30256
  await next();
30060
30257
  setSpanAttributes({ "http.response.status_code": c.res.status });
@@ -30261,120 +30458,6 @@ idGraphInspect.post("/internal/id-graph/rows", async (c) => {
30261
30458
  return c.json({ page: await inspectObjectTable(c.env, body) });
30262
30459
  });
30263
30460
 
30264
- // src/id-graph/anon/connection.ts
30265
- var CONNECTION_CF_HEADER = "x-connection-cf";
30266
- var SESSION_HEADER = "x-session";
30267
- var MAX_HOST_USER_AGENT_LENGTH = 512;
30268
- var MAX_HOST_LOCALE_LENGTH = 64;
30269
- var INTERNAL_HEADERS = /* @__PURE__ */ new Set([
30270
- "x-user-id",
30271
- "x-new-user",
30272
- "x-geo",
30273
- SITE_ID_HEADER,
30274
- CONNECTION_CF_HEADER,
30275
- SESSION_HEADER,
30276
- TRACE_HEADER,
30277
- CF_DATA_HEADER
30278
- ]);
30279
- var REDACTED_HEADERS = /* @__PURE__ */ new Set(["authorization"]);
30280
- var encodeConnectionCf = /* @__PURE__ */ __name((cf) => {
30281
- if (cf === void 0) {
30282
- return void 0;
30283
- }
30284
- const encoded = headerJson(cf);
30285
- return encoded.length > MAX_ATTACHMENT_BYTES ? void 0 : encoded;
30286
- }, "encodeConnectionCf");
30287
- var forwardedCf = /* @__PURE__ */ __name((request) => {
30288
- const raw2 = request.headers.get(CONNECTION_CF_HEADER);
30289
- if (raw2 === null) {
30290
- return void 0;
30291
- }
30292
- return JSON.parse(raw2);
30293
- }, "forwardedCf");
30294
- var forwardedSession = /* @__PURE__ */ __name((request) => {
30295
- const raw2 = request.headers.get(SESSION_HEADER);
30296
- if (raw2 === null) {
30297
- return void 0;
30298
- }
30299
- return JSON.parse(raw2);
30300
- }, "forwardedSession");
30301
- var connectAttrs = /* @__PURE__ */ __name((request, geo) => {
30302
- const headers = {};
30303
- for (const [name, value] of request.headers) {
30304
- const lower = name.toLowerCase();
30305
- if (!INTERNAL_HEADERS.has(lower) && !REDACTED_HEADERS.has(lower)) {
30306
- headers[name] = value;
30307
- }
30308
- }
30309
- const cf = forwardedCf(request);
30310
- return {
30311
- "url.full": request.url,
30312
- "http.request.headers": JSON.stringify(headers),
30313
- ...cf === void 0 ? {} : { "qinto.cf": JSON.stringify(cf) },
30314
- ...geo === void 0 ? {} : { "qinto.geo": JSON.stringify(loggableGeo(geo)) }
30315
- };
30316
- }, "connectAttrs");
30317
- var withoutQuery = /* @__PURE__ */ __name((url2) => {
30318
- const parsed = new URL(url2);
30319
- parsed.search = "";
30320
- return parsed.toString();
30321
- }, "withoutQuery");
30322
- var browserGeoFrom = /* @__PURE__ */ __name((geo) => {
30323
- if (geo === void 0) {
30324
- return void 0;
30325
- }
30326
- return {
30327
- country: geo.country ?? null,
30328
- // EdgeTag's geoRegion is Cloudflare's subdivision code.
30329
- region: geo.regionCode ?? null,
30330
- state: geo.region ?? null,
30331
- isEU: geo.isEUCountry === "yes"
30332
- };
30333
- }, "browserGeoFrom");
30334
- var attachmentFieldBytes = /* @__PURE__ */ __name((name, value) => {
30335
- return value === void 0 ? 0 : new TextEncoder().encode(`,"${name}":${JSON.stringify(value)}`).length;
30336
- }, "attachmentFieldBytes");
30337
- var boundedHost = /* @__PURE__ */ __name((request) => {
30338
- const raw2 = hostFromRequest(request);
30339
- const bounded = {
30340
- ip: raw2.ip,
30341
- userAgent: raw2.userAgent !== void 0 && raw2.userAgent.length <= MAX_HOST_USER_AGENT_LENGTH ? raw2.userAgent : void 0,
30342
- locale: raw2.locale !== void 0 && raw2.locale.length <= MAX_HOST_LOCALE_LENGTH ? raw2.locale : void 0
30343
- };
30344
- return bounded.ip === void 0 && bounded.userAgent === void 0 && bounded.locale === void 0 ? void 0 : bounded;
30345
- }, "boundedHost");
30346
- var connectionAttachment = /* @__PURE__ */ __name((request, geo, siteId) => {
30347
- const userAgent = request.headers.get("user-agent");
30348
- const attrs = {
30349
- "url.full": withoutQuery(request.url),
30350
- ...geo?.country === void 0 ? {} : { "qinto.country": geo.country },
30351
- ...geo?.city === void 0 ? {} : { "qinto.city": geo.city },
30352
- ...userAgent === null ? {} : { user_agent: userAgent }
30353
- };
30354
- const host = boundedHost(request);
30355
- const hostBytes = attachmentFieldBytes("host", host);
30356
- const browserGeo = browserGeoFrom(geo);
30357
- const browserGeoBytes = attachmentFieldBytes("browserGeo", browserGeo);
30358
- const session2 = forwardedSession(request);
30359
- const sessionBytes = attachmentFieldBytes("session", session2);
30360
- const siteBytes = attachmentFieldBytes("siteId", siteId);
30361
- const cf = forwardedCf(request);
30362
- const cfSuffix = cf === void 0 ? "" : `,"cf":${JSON.stringify(cf)}`;
30363
- const cfBytes = new TextEncoder().encode(cfSuffix).length;
30364
- const emptyAttrsBytes = new TextEncoder().encode(JSON.stringify({ attrs: {} })).length;
30365
- const fixedBytes = siteBytes + sessionBytes + hostBytes + browserGeoBytes;
30366
- const includedCf = cfBytes <= MAX_ATTACHMENT_BYTES - emptyAttrsBytes - fixedBytes ? cf : void 0;
30367
- const budgeted = budgetAttrs(attrs, MAX_ATTACHMENT_BYTES - fixedBytes - (includedCf === void 0 ? 0 : cfBytes));
30368
- return {
30369
- ...budgeted,
30370
- siteId,
30371
- ...session2 === void 0 ? {} : { session: session2 },
30372
- ...browserGeo === void 0 ? {} : { browserGeo },
30373
- ...includedCf === void 0 ? {} : { cf: includedCf },
30374
- ...host === void 0 ? {} : { host }
30375
- };
30376
- }, "connectionAttachment");
30377
-
30378
30461
  // src/paths/open.ts
30379
30462
  var withSetCookie = /* @__PURE__ */ __name((res, cookies) => {
30380
30463
  if (res.webSocket) {
@@ -30599,7 +30682,12 @@ var definition2 = createRoute({
30599
30682
  });
30600
30683
  var alias = createApiRouter().openapi(definition2, async (c) => {
30601
30684
  const body = c.req.valid("json");
30602
- const id = await submitAction(c.env, body.identifier, { type: "alias", payload: { alias: body.alias } }, c.req.raw);
30685
+ const id = await submitAction(
30686
+ c.env,
30687
+ body.identifier,
30688
+ { type: "alias", source: c.get("actionSource"), payload: { alias: body.alias } },
30689
+ c.req.raw
30690
+ );
30603
30691
  return respond(c, acceptedResponse, { ok: true, id });
30604
30692
  });
30605
30693
 
@@ -30701,7 +30789,7 @@ var consent = createApiRouter().openapi(definition4, async (c) => {
30701
30789
  const id = await submitAction(
30702
30790
  c.env,
30703
30791
  identifier,
30704
- { type: "consent", payload: { categories: categories ?? {}, apps: apps ?? {} }, context },
30792
+ { type: "consent", source: c.get("actionSource"), payload: { categories: categories ?? {}, apps: apps ?? {} }, context },
30705
30793
  c.req.raw
30706
30794
  );
30707
30795
  return respond(c, acceptedResponse, { ok: true, id });
@@ -30800,7 +30888,12 @@ var definition6 = createRoute({
30800
30888
  });
30801
30889
  var identify = createApiRouter().openapi(definition6, async (c) => {
30802
30890
  const { identifier, traits, context } = c.req.valid("json");
30803
- const id = await submitAction(c.env, identifier, { type: "identify", payload: { traits: traits ?? {} }, context }, c.req.raw);
30891
+ const id = await submitAction(
30892
+ c.env,
30893
+ identifier,
30894
+ { type: "identify", source: c.get("actionSource"), payload: { traits: traits ?? {} }, context },
30895
+ c.req.raw
30896
+ );
30804
30897
  return respond(c, acceptedResponse, { ok: true, id });
30805
30898
  });
30806
30899
 
@@ -30950,7 +31043,7 @@ var session = createApiRouter().openapi(definition9, async (c) => {
30950
31043
  });
30951
31044
 
30952
31045
  // src/paths/v1/track.ts
30953
- var trackBody = external_exports.object({
31046
+ var trackBody2 = external_exports.object({
30954
31047
  identifier: identifierField2,
30955
31048
  event: external_exports.string().min(1).openapi({
30956
31049
  description: "Name of the event the user performed.",
@@ -30982,7 +31075,7 @@ var definition10 = createRoute({
30982
31075
  body: {
30983
31076
  required: true,
30984
31077
  description: "The event to record.",
30985
- content: { "application/json": { schema: trackBody } }
31078
+ content: { "application/json": { schema: trackBody2 } }
30986
31079
  }
30987
31080
  },
30988
31081
  responses: {
@@ -30999,7 +31092,7 @@ var track = createApiRouter().openapi(definition10, async (c) => {
30999
31092
  const id = await submitAction(
31000
31093
  c.env,
31001
31094
  identifier,
31002
- { type: "track", payload: { event, eventId, properties: properties2 ?? {} }, context },
31095
+ { type: "track", source: c.get("actionSource"), payload: { event, eventId, properties: properties2 ?? {} }, context },
31003
31096
  c.req.raw
31004
31097
  );
31005
31098
  return respond(c, acceptedResponse, { ok: true, id });
@@ -31111,6 +31204,13 @@ var FanOut = class extends WorkerEntrypoint2 {
31111
31204
  // src/apps/storage-link-site.ts
31112
31205
  import { WorkerEntrypoint as WorkerEntrypoint3 } from "cloudflare:workers";
31113
31206
 
31207
+ // src/apps/action-source.ts
31208
+ var LEGACY_SOURCES = { ws: "website", http: "api" };
31209
+ var withCurrentSource = /* @__PURE__ */ __name((action) => {
31210
+ const current = LEGACY_SOURCES[action.source];
31211
+ return current === void 0 ? action : { ...action, source: current };
31212
+ }, "withCurrentSource");
31213
+
31114
31214
  // src/apps/transform.ts
31115
31215
  var TRANSFORM_TIMEOUT_MS = 500;
31116
31216
  var TRANSFORM_CHAIN_BUDGET_MS = 1e3;
@@ -31312,10 +31412,11 @@ var StorageLinkSite = class extends WorkerEntrypoint3 {
31312
31412
  __name(this, "StorageLinkSite");
31313
31413
  }
31314
31414
  fanOut(job) {
31315
- runFanOut(this.env, this.ctx, job);
31415
+ const trigger = job.trigger.kind === "event" ? { ...job.trigger, action: withCurrentSource(job.trigger.action) } : job.trigger;
31416
+ runFanOut(this.env, this.ctx, { ...job, trigger });
31316
31417
  }
31317
31418
  transform(input2) {
31318
- return runTransformChain(this.env, { ...input2, dataPoints: createDataPointBudget() });
31419
+ return runTransformChain(this.env, { ...input2, action: withCurrentSource(input2.action), dataPoints: createDataPointBudget() });
31319
31420
  }
31320
31421
  describe() {
31321
31422
  return describeSite(this.env);
@@ -31560,6 +31661,10 @@ var acceptedCaptures = /* @__PURE__ */ __name((registry3, consent2, apps, enforc
31560
31661
  }
31561
31662
  return accepted;
31562
31663
  }, "acceptedCaptures");
31664
+ var declaredTtlMs = /* @__PURE__ */ __name((registry3, appId, key) => {
31665
+ const app2 = registry3.apps[appId];
31666
+ return app2 === void 0 ? null : declaration(app2, key)?.ttlMs ?? null;
31667
+ }, "declaredTtlMs");
31563
31668
 
31564
31669
  // src/id-graph/kv-cache.ts
31565
31670
  var cachedKv = /* @__PURE__ */ __name((kv) => {
@@ -32199,7 +32304,7 @@ var updateGeo = /* @__PURE__ */ __name((storage, geo, observedAt) => {
32199
32304
  record2[key] = { value, observedAt };
32200
32305
  mutated = true;
32201
32306
  if (GEO_SERIES_KEY_SET.has(key)) {
32202
- insertFact(storage.sql, { ...projectionFact(entry.key), value: entry.value, source: "ws", observedAt: entry.observedAt });
32307
+ insertFact(storage.sql, { ...projectionFact(entry.key), value: entry.value, source: "website", observedAt: entry.observedAt });
32203
32308
  appended.push(entry);
32204
32309
  }
32205
32310
  }
@@ -32484,7 +32589,7 @@ var MAX_BROWSER_RESULTS_BYTES = 32 * 1024;
32484
32589
  var MAX_FRAME_BYTES = 64 * 1024;
32485
32590
  var browserContextFrame = external_exports.object({
32486
32591
  occurredAt: external_exports.number().int().nonnegative().safe(),
32487
- page: external_exports.object({ url: external_exports.string().min(1).max(4096), referrer: external_exports.string().min(1).max(4096).optional() }).strict()
32592
+ page: pageContextField
32488
32593
  }).strict();
32489
32594
  var browserResult = external_exports.unknown().refine((value) => {
32490
32595
  return new TextEncoder().encode(JSON.stringify(value)).length <= MAX_BROWSER_RESULT_BYTES;
@@ -32610,7 +32715,7 @@ var actionFromFrame = /* @__PURE__ */ __name((frame, userId, connection = {}) =>
32610
32715
  type: "identify",
32611
32716
  userId,
32612
32717
  identifier,
32613
- source: "ws",
32718
+ source: "website",
32614
32719
  payload: { traits: frame.traits ?? {} },
32615
32720
  context: browserActionContext(frame.context, host, session2),
32616
32721
  browserResults: frame.appResults,
@@ -32622,7 +32727,7 @@ var actionFromFrame = /* @__PURE__ */ __name((frame, userId, connection = {}) =>
32622
32727
  return buildAction({
32623
32728
  type: "track",
32624
32729
  userId,
32625
- source: "ws",
32730
+ source: "website",
32626
32731
  payload: { event: frame.event, eventId: frame.eventId, properties: frame.properties ?? {} },
32627
32732
  context: browserActionContext(frame.context, host, session2),
32628
32733
  browserResults: frame.appResults,
@@ -32631,13 +32736,13 @@ var actionFromFrame = /* @__PURE__ */ __name((frame, userId, connection = {}) =>
32631
32736
  });
32632
32737
  }
32633
32738
  case "alias": {
32634
- return buildAction({ type: "alias", userId, source: "ws", payload: { alias: frame.alias }, geo, host });
32739
+ return buildAction({ type: "alias", userId, source: "website", payload: { alias: frame.alias }, geo, host });
32635
32740
  }
32636
32741
  case "consent": {
32637
32742
  return buildAction({
32638
32743
  type: "consent",
32639
32744
  userId,
32640
- source: "ws",
32745
+ source: "website",
32641
32746
  payload: { categories: frame.categories ?? {}, apps: frame.apps ?? {} },
32642
32747
  context: browserActionContext(frame.context, host, session2),
32643
32748
  geo,
@@ -32716,8 +32821,14 @@ var AnonDO = class extends DurableObject2 {
32716
32821
  const attachment = connectionAttachment(request, geo, siteId);
32717
32822
  server.serializeAttachment(attachment);
32718
32823
  this.sendSharedContext([server], context);
32824
+ const page = connectPage(request);
32719
32825
  this.appFanOut(
32720
- { kind: "connect", ...attachment.cf === void 0 ? {} : { geo: attachment.cf }, host: attachment.host },
32826
+ {
32827
+ kind: "connect",
32828
+ ...attachment.cf === void 0 ? {} : { geo: attachment.cf },
32829
+ host: attachment.host,
32830
+ ...page === void 0 ? {} : { page }
32831
+ },
32721
32832
  userId,
32722
32833
  context
32723
32834
  );
@@ -32918,7 +33029,11 @@ var AnonDO = class extends DurableObject2 {
32918
33029
  if (!appWriteAllowed(context.registry, consentState2(this.kv), input2.slug, context.consentEnforced)) {
32919
33030
  return;
32920
33031
  }
32921
- this.storeCaptures(input2.writes.map((write) => ({ ...write, appId: input2.slug, source: "app", ttlMs: null })));
33032
+ this.storeCaptures(
33033
+ input2.writes.map((write) => {
33034
+ return { ...write, appId: input2.slug, source: "app", ttlMs: declaredTtlMs(context.registry, input2.slug, write.key) };
33035
+ })
33036
+ );
32922
33037
  });
32923
33038
  });
32924
33039
  }