@blotout/qinto-cli 0.1.20 → 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.
@@ -20026,9 +20026,10 @@ var effectGroupSchema = external_exports.object({
20026
20026
  context: external_exports.array(contextWriteSchema).optional()
20027
20027
  }).strict();
20028
20028
  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();
20029
+ var pageSchema = external_exports.object({ url: external_exports.string().min(1).max(4096), referrer: external_exports.string().min(1).max(4096).optional() }).strict();
20029
20030
  var actionContextFields = {
20030
20031
  occurredAt: external_exports.number().int().nonnegative().safe(),
20031
- page: external_exports.object({ url: external_exports.string().min(1).max(4096), referrer: external_exports.string().min(1).max(4096).optional() }).strict().optional(),
20032
+ page: pageSchema.optional(),
20032
20033
  network: external_exports.object({
20033
20034
  ip: external_exports.string().min(1).max(64).optional(),
20034
20035
  userAgent: external_exports.string().min(1).max(512).optional(),
@@ -20056,6 +20057,8 @@ var envelopeRequestSchema = external_exports.object({
20056
20057
  }).strict().optional(),
20057
20058
  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(),
20058
20059
  geo: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
20060
+ // Connect envelopes only: the page the visitor's socket opened from.
20061
+ page: pageSchema.optional(),
20059
20062
  // Only what this app captured or wrote for this visitor; `updatedAt` is when the current value
20060
20063
  // was first seen, which is the click time a CAPI `fbc` is built from.
20061
20064
  captures: external_exports.record(external_exports.string().max(64), captureValueSchema).optional(),
@@ -22889,7 +22892,7 @@ var dispatch = async (envelope, versions, env) => {
22889
22892
  if (handler === void 0) {
22890
22893
  return jsonError(200, "missing handler: onConnect");
22891
22894
  }
22892
- return runInstalls(envelope, env, (ctx) => handler(ctx));
22895
+ return runInstalls(envelope, env, (ctx) => handler({ ...ctx, ...envelope.page === void 0 ? {} : { page: envelope.page } }));
22893
22896
  }
22894
22897
  case "transform": {
22895
22898
  return dispatchTransform(envelope, bundle, env);
@@ -21500,9 +21500,10 @@ var effectGroupSchema = external_exports.object({
21500
21500
  context: external_exports.array(contextWriteSchema).optional()
21501
21501
  }).strict();
21502
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();
21503
21504
  var actionContextFields = {
21504
21505
  occurredAt: external_exports.number().int().nonnegative().safe(),
21505
- 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(),
21506
21507
  network: external_exports.object({
21507
21508
  ip: external_exports.string().min(1).max(64).optional(),
21508
21509
  userAgent: external_exports.string().min(1).max(512).optional(),
@@ -21530,6 +21531,8 @@ var envelopeRequestSchema = external_exports.object({
21530
21531
  }).strict().optional(),
21531
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(),
21532
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(),
21533
21536
  // Only what this app captured or wrote for this visitor; `updatedAt` is when the current value
21534
21537
  // was first seen, which is the click time a CAPI `fbc` is built from.
21535
21538
  captures: external_exports.record(external_exports.string().max(64), captureValueSchema).optional(),
@@ -26305,14 +26308,15 @@ var captureAppsField = external_exports.record(external_exports.string().max(30)
26305
26308
  description: "Values to record, keyed by app slug and then by capture name. A name the app does not declare is dropped.",
26306
26309
  example: { facebook: { fbclid: "IwAR0abc" }, "google-ads": { gclid: "Cj0KCQ" } }
26307
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();
26308
26315
  var serverActionContextField = external_exports.object({
26309
26316
  occurredAt: external_exports.number().int().nonnegative().safe().optional().openapi({
26310
26317
  description: "When the visitor action occurred, as Unix milliseconds; defaults to Qinto receipt time."
26311
26318
  }),
26312
- page: external_exports.object({
26313
- url: external_exports.string().min(1).max(4096),
26314
- referrer: external_exports.string().min(1).max(4096).optional()
26315
- }).strict().optional(),
26319
+ page: pageContextField.optional(),
26316
26320
  network: external_exports.object({
26317
26321
  ip: external_exports.string().min(1).max(64).optional(),
26318
26322
  userAgent: external_exports.string().min(1).max(512).optional(),
@@ -27718,6 +27722,7 @@ var buildEnvelope = /* @__PURE__ */ __name((target, trigger, snapshot, context)
27718
27722
  },
27719
27723
  ...buckets.includes("consentState") ? { consent: { enabled: context.consentEnforced, categories: snapshot.consent.categories, apps: snapshot.consent.apps } } : {},
27720
27724
  ...geoAllowed && currentGeo !== void 0 ? { geo: currentGeo } : {},
27725
+ ...trigger.kind === "connect" && trigger.page !== void 0 && buckets.includes("eventContext") ? { page: trigger.page } : {},
27721
27726
  // Not gated on a capabilities.data bucket: an app only ever sees what it captured or wrote.
27722
27727
  ...captures === void 0 ? {} : { captures },
27723
27728
  ...visitorContext === void 0 ? {} : { context: visitorContext },
@@ -30087,9 +30092,141 @@ var defaultHook = /* @__PURE__ */ __name((result, c) => {
30087
30092
  }, "defaultHook");
30088
30093
  var createApiRouter = /* @__PURE__ */ __name(() => new OpenAPIHono({ defaultHook }), "createApiRouter");
30089
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
+
30090
30227
  // src/helpers/request-span.ts
30091
30228
  var OMITTED_HEADERS = /* @__PURE__ */ new Set(["authorization", DISPATCH_HEADER, CF_DATA_HEADER]);
30092
- var requestAttrs = /* @__PURE__ */ __name((request) => {
30229
+ var requestAttrs = /* @__PURE__ */ __name((request, pathname) => {
30093
30230
  const headers = {};
30094
30231
  for (const [name, value] of request.headers) {
30095
30232
  if (!OMITTED_HEADERS.has(name.toLowerCase())) {
@@ -30099,7 +30236,7 @@ var requestAttrs = /* @__PURE__ */ __name((request) => {
30099
30236
  const cf = request.cf;
30100
30237
  return {
30101
30238
  "http.request.method": request.method,
30102
- "url.full": request.url,
30239
+ "url.full": pathname === "/open" ? withoutPageParams(request.url) : request.url,
30103
30240
  "http.request.headers": JSON.stringify(headers),
30104
30241
  ...cf === void 0 ? {} : { "qinto.cf": JSON.stringify(cf) }
30105
30242
  };
@@ -30114,7 +30251,7 @@ var requestSpan = /* @__PURE__ */ __name(async (c, next) => {
30114
30251
  const pathname = new URL(c.req.url).pathname;
30115
30252
  const dispatch = parseDispatchTrace(c.req.raw.headers.get(DISPATCH_HEADER));
30116
30253
  const trace = dispatch === void 0 ? inboundTrace(c.req.raw, pathname) : { traceId: dispatch.traceId, parent: dispatch.spanId };
30117
- 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 () => {
30118
30255
  try {
30119
30256
  await next();
30120
30257
  setSpanAttributes({ "http.response.status_code": c.res.status });
@@ -30321,120 +30458,6 @@ idGraphInspect.post("/internal/id-graph/rows", async (c) => {
30321
30458
  return c.json({ page: await inspectObjectTable(c.env, body) });
30322
30459
  });
30323
30460
 
30324
- // src/id-graph/anon/connection.ts
30325
- var CONNECTION_CF_HEADER = "x-connection-cf";
30326
- var SESSION_HEADER = "x-session";
30327
- var MAX_HOST_USER_AGENT_LENGTH = 512;
30328
- var MAX_HOST_LOCALE_LENGTH = 64;
30329
- var INTERNAL_HEADERS = /* @__PURE__ */ new Set([
30330
- "x-user-id",
30331
- "x-new-user",
30332
- "x-geo",
30333
- SITE_ID_HEADER,
30334
- CONNECTION_CF_HEADER,
30335
- SESSION_HEADER,
30336
- TRACE_HEADER,
30337
- CF_DATA_HEADER
30338
- ]);
30339
- var REDACTED_HEADERS = /* @__PURE__ */ new Set(["authorization"]);
30340
- var encodeConnectionCf = /* @__PURE__ */ __name((cf) => {
30341
- if (cf === void 0) {
30342
- return void 0;
30343
- }
30344
- const encoded = headerJson(cf);
30345
- return encoded.length > MAX_ATTACHMENT_BYTES ? void 0 : encoded;
30346
- }, "encodeConnectionCf");
30347
- var forwardedCf = /* @__PURE__ */ __name((request) => {
30348
- const raw2 = request.headers.get(CONNECTION_CF_HEADER);
30349
- if (raw2 === null) {
30350
- return void 0;
30351
- }
30352
- return JSON.parse(raw2);
30353
- }, "forwardedCf");
30354
- var forwardedSession = /* @__PURE__ */ __name((request) => {
30355
- const raw2 = request.headers.get(SESSION_HEADER);
30356
- if (raw2 === null) {
30357
- return void 0;
30358
- }
30359
- return JSON.parse(raw2);
30360
- }, "forwardedSession");
30361
- var connectAttrs = /* @__PURE__ */ __name((request, geo) => {
30362
- const headers = {};
30363
- for (const [name, value] of request.headers) {
30364
- const lower = name.toLowerCase();
30365
- if (!INTERNAL_HEADERS.has(lower) && !REDACTED_HEADERS.has(lower)) {
30366
- headers[name] = value;
30367
- }
30368
- }
30369
- const cf = forwardedCf(request);
30370
- return {
30371
- "url.full": request.url,
30372
- "http.request.headers": JSON.stringify(headers),
30373
- ...cf === void 0 ? {} : { "qinto.cf": JSON.stringify(cf) },
30374
- ...geo === void 0 ? {} : { "qinto.geo": JSON.stringify(loggableGeo(geo)) }
30375
- };
30376
- }, "connectAttrs");
30377
- var withoutQuery = /* @__PURE__ */ __name((url2) => {
30378
- const parsed = new URL(url2);
30379
- parsed.search = "";
30380
- return parsed.toString();
30381
- }, "withoutQuery");
30382
- var browserGeoFrom = /* @__PURE__ */ __name((geo) => {
30383
- if (geo === void 0) {
30384
- return void 0;
30385
- }
30386
- return {
30387
- country: geo.country ?? null,
30388
- // EdgeTag's geoRegion is Cloudflare's subdivision code.
30389
- region: geo.regionCode ?? null,
30390
- state: geo.region ?? null,
30391
- isEU: geo.isEUCountry === "yes"
30392
- };
30393
- }, "browserGeoFrom");
30394
- var attachmentFieldBytes = /* @__PURE__ */ __name((name, value) => {
30395
- return value === void 0 ? 0 : new TextEncoder().encode(`,"${name}":${JSON.stringify(value)}`).length;
30396
- }, "attachmentFieldBytes");
30397
- var boundedHost = /* @__PURE__ */ __name((request) => {
30398
- const raw2 = hostFromRequest(request);
30399
- const bounded = {
30400
- ip: raw2.ip,
30401
- userAgent: raw2.userAgent !== void 0 && raw2.userAgent.length <= MAX_HOST_USER_AGENT_LENGTH ? raw2.userAgent : void 0,
30402
- locale: raw2.locale !== void 0 && raw2.locale.length <= MAX_HOST_LOCALE_LENGTH ? raw2.locale : void 0
30403
- };
30404
- return bounded.ip === void 0 && bounded.userAgent === void 0 && bounded.locale === void 0 ? void 0 : bounded;
30405
- }, "boundedHost");
30406
- var connectionAttachment = /* @__PURE__ */ __name((request, geo, siteId) => {
30407
- const userAgent = request.headers.get("user-agent");
30408
- const attrs = {
30409
- "url.full": withoutQuery(request.url),
30410
- ...geo?.country === void 0 ? {} : { "qinto.country": geo.country },
30411
- ...geo?.city === void 0 ? {} : { "qinto.city": geo.city },
30412
- ...userAgent === null ? {} : { user_agent: userAgent }
30413
- };
30414
- const host = boundedHost(request);
30415
- const hostBytes = attachmentFieldBytes("host", host);
30416
- const browserGeo = browserGeoFrom(geo);
30417
- const browserGeoBytes = attachmentFieldBytes("browserGeo", browserGeo);
30418
- const session2 = forwardedSession(request);
30419
- const sessionBytes = attachmentFieldBytes("session", session2);
30420
- const siteBytes = attachmentFieldBytes("siteId", siteId);
30421
- const cf = forwardedCf(request);
30422
- const cfSuffix = cf === void 0 ? "" : `,"cf":${JSON.stringify(cf)}`;
30423
- const cfBytes = new TextEncoder().encode(cfSuffix).length;
30424
- const emptyAttrsBytes = new TextEncoder().encode(JSON.stringify({ attrs: {} })).length;
30425
- const fixedBytes = siteBytes + sessionBytes + hostBytes + browserGeoBytes;
30426
- const includedCf = cfBytes <= MAX_ATTACHMENT_BYTES - emptyAttrsBytes - fixedBytes ? cf : void 0;
30427
- const budgeted = budgetAttrs(attrs, MAX_ATTACHMENT_BYTES - fixedBytes - (includedCf === void 0 ? 0 : cfBytes));
30428
- return {
30429
- ...budgeted,
30430
- siteId,
30431
- ...session2 === void 0 ? {} : { session: session2 },
30432
- ...browserGeo === void 0 ? {} : { browserGeo },
30433
- ...includedCf === void 0 ? {} : { cf: includedCf },
30434
- ...host === void 0 ? {} : { host }
30435
- };
30436
- }, "connectionAttachment");
30437
-
30438
30461
  // src/paths/open.ts
30439
30462
  var withSetCookie = /* @__PURE__ */ __name((res, cookies) => {
30440
30463
  if (res.webSocket) {
@@ -31638,6 +31661,10 @@ var acceptedCaptures = /* @__PURE__ */ __name((registry3, consent2, apps, enforc
31638
31661
  }
31639
31662
  return accepted;
31640
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");
31641
31668
 
31642
31669
  // src/id-graph/kv-cache.ts
31643
31670
  var cachedKv = /* @__PURE__ */ __name((kv) => {
@@ -32562,7 +32589,7 @@ var MAX_BROWSER_RESULTS_BYTES = 32 * 1024;
32562
32589
  var MAX_FRAME_BYTES = 64 * 1024;
32563
32590
  var browserContextFrame = external_exports.object({
32564
32591
  occurredAt: external_exports.number().int().nonnegative().safe(),
32565
- 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
32566
32593
  }).strict();
32567
32594
  var browserResult = external_exports.unknown().refine((value) => {
32568
32595
  return new TextEncoder().encode(JSON.stringify(value)).length <= MAX_BROWSER_RESULT_BYTES;
@@ -32794,8 +32821,14 @@ var AnonDO = class extends DurableObject2 {
32794
32821
  const attachment = connectionAttachment(request, geo, siteId);
32795
32822
  server.serializeAttachment(attachment);
32796
32823
  this.sendSharedContext([server], context);
32824
+ const page = connectPage(request);
32797
32825
  this.appFanOut(
32798
- { 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
+ },
32799
32832
  userId,
32800
32833
  context
32801
32834
  );
@@ -32996,7 +33029,11 @@ var AnonDO = class extends DurableObject2 {
32996
33029
  if (!appWriteAllowed(context.registry, consentState2(this.kv), input2.slug, context.consentEnforced)) {
32997
33030
  return;
32998
33031
  }
32999
- 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
+ );
33000
33037
  });
33001
33038
  });
33002
33039
  }
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "main": {
3
- "version": "0.1.4",
4
- "gitSha": "84a03b1ccdff814840f560c7efd0f72837454227",
5
- "builtAt": "2026-09-24T14:23:50.391Z",
3
+ "version": "0.1.5",
4
+ "gitSha": "7c31ca4765a91cfd5098f098208c2409638b6e24",
5
+ "builtAt": "2026-09-25T12:23:20.370Z",
6
6
  "compatibilityDate": "2026-06-23",
7
7
  "compatibilityFlags": [
8
8
  "nodejs_compat"
@@ -39,9 +39,9 @@
39
39
  ]
40
40
  },
41
41
  "app-runtime": {
42
- "version": "0.1.2",
43
- "gitSha": "5c24e6442ad668125bba59aaec084adb43b70d1d",
44
- "builtAt": "2026-09-24T14:23:53.656Z",
42
+ "version": "0.1.3",
43
+ "gitSha": "5991b0352059c2c9701ccc4e3414dc93fbba3371",
44
+ "builtAt": "2026-09-25T12:23:22.651Z",
45
45
  "compatibilityDate": "2026-06-23",
46
46
  "compatibilityFlags": [
47
47
  "nodejs_compat"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blotout/qinto-cli",
3
- "version": "0.1.20",
3
+ "version": "0.1.21",
4
4
  "type": "module",
5
5
  "description": "Qinto CLI: authenticate against the Qinto platform and operate it from the terminal or from an agent.",
6
6
  "repository": {
@@ -16,8 +16,7 @@
16
16
  },
17
17
  "files": [
18
18
  "bin",
19
- "dist",
20
- "templates"
19
+ "dist"
21
20
  ],
22
21
  "publishConfig": {
23
22
  "access": "public"
@@ -35,6 +34,7 @@
35
34
  "@qinto/app-build": "0.0.0",
36
35
  "@qinto/app-dev": "0.0.0",
37
36
  "@qinto/app-manifest": "0.0.0",
37
+ "@qinto/app-templates": "0.0.0",
38
38
  "@qinto/oauth-grammar": "0.0.0",
39
39
  "@types/node": "25.9.5",
40
40
  "@types/proper-lockfile": "4.1.4",
@@ -1,129 +0,0 @@
1
- {
2
- "$schema": "./node_modules/@blotout/qinto-sdk-app/app.schema.json",
3
- // The immutable identifier of the app on the Qinto platform.
4
- "slug": "__APP_SLUG__",
5
- "name": "__APP_NAME__",
6
- "description": "Rewrites events on their way through, records purchases, forwards them onward, and ships its own panel.",
7
- // This app both rewrites events before destinations see them and receives them afterwards.
8
- "flows": ["transform", "destination"],
9
- // Events reach the app only when the visitor granted this consent category.
10
- "consent": { "category": "marketing" },
11
- // Replace these with your own hosted documents before publishing.
12
- "legal": {
13
- "termsUrl": "https://example.com/terms",
14
- "privacyUrl": "https://example.com/privacy"
15
- },
16
- "server": {
17
- "entry": "src/server.ts",
18
- // Must match the __hooks the server entry exports.
19
- "hooks": {
20
- "transform": ["track"],
21
- "onEvent": ["track", "identify"],
22
- "onConnect": false,
23
- "onLifecycle": ["installed", "configurationChanged", "uninstalled"]
24
- },
25
- // Time-based runs, with no visitor behind them — a nightly rollup, a vendor reconcile, a
26
- // backfill kick-off. Each entry is attempted once per active install per matching window, best
27
- // effort: a missed run is not caught up, so keep handlers repeatable. The minute field must be a
28
- // literal, which is what holds a schedule to at most one run an hour; the rest is standard
29
- // five-field cron in UTC, with day-of-week 0 = Sunday. Ids key the handlers the bundle
30
- // registers under "schedules", and the publish refuses a mismatch either way.
31
- "schedules": [{ "id": "daily-rollup", "cron": "0 2 * * *", "description": "Nightly per-install rollup" }]
32
- },
33
- // The browser bundle ships to the visitor's page alongside the tag. "entry" is optional: an app
34
- // that only wants "capture" declares this block without shipping any browser code.
35
- //
36
- // "capture" names the query params and cookies the platform records against each visitor for
37
- // this app, which only this app reads back. Each name belongs to one list. Uncomment to use it —
38
- // a site manager sees the list and must accept any name a later version adds.
39
- //
40
- // "type" says what a value is: "attribution" for a click or campaign id, "identity" (the default)
41
- // for a vendor id that names the visitor across visits. "expiresInSeconds" is optional and runs
42
- // from the first sighting of that value; seeing the same value again never extends it. Omit it to
43
- // keep the value for the life of the visitor profile.
44
- // "capture": { "queryParams": [{ "name": "fbclid", "type": "attribution", "expiresInSeconds": 604800 }], "cookies": [{ "name": "_fbp" }] },
45
- // eventResults lets the matching server install receive this browser handler's return value on
46
- // the same action as action.browser. It is untrusted page input and delivery stays fail-open.
47
- "browser": { "entry": "src/browser.ts", "eventResults": ["track"] },
48
- "capabilities": {
49
- "api": [],
50
- // "pii" is what puts the visitor's email in ctx.user.properties.
51
- "data": ["pii", "customProperties", "eventContext"]
52
- },
53
- // The platform creates this database, binds it as env.PURCHASES, and applies these immutable,
54
- // ordered entries. Expand entries are additive and run before rollout. Use backfill handlers for
55
- // resumable data work and contract entries for destructive SQL after old bundles are gone.
56
- "resources": [
57
- {
58
- "type": "d1",
59
- "identifier": "purchases",
60
- "binding": "PURCHASES",
61
- "migrations": [
62
- {
63
- "id": "001-create-purchases",
64
- "phase": "expand",
65
- "statements": [
66
- "CREATE TABLE IF NOT EXISTS purchases (order_id TEXT PRIMARY KEY, value REAL NOT NULL, email_sha256 TEXT, recorded_at INTEGER NOT NULL)"
67
- ]
68
- },
69
- {
70
- "id": "002-index-purchases",
71
- "phase": "expand",
72
- "statements": ["CREATE INDEX IF NOT EXISTS purchases_recorded_at ON purchases (recorded_at)"]
73
- }
74
- ]
75
- }
76
- ],
77
- // What a site manager fills in per install; a secret never reaches the browser.
78
- "variables": [
79
- {
80
- "name": "ENDPOINT_URL",
81
- "kind": "plain",
82
- "type": "text",
83
- "label": "Delivery endpoint",
84
- "required": true,
85
- "helpText": "The HTTPS endpoint each event is forwarded to. Until it is set the install stays inactive and sends nothing."
86
- },
87
- {
88
- "name": "API_KEY",
89
- "kind": "secret",
90
- "type": "password",
91
- "label": "API key",
92
- "required": true,
93
- "helpText": "The key this app authenticates to that endpoint with."
94
- },
95
- {
96
- "name": "SITE_TAG",
97
- "kind": "plain",
98
- "type": "text",
99
- "label": "Site tag",
100
- "browser": true,
101
- "helpText": "Sent with every event, and readable from the browser bundle."
102
- },
103
- {
104
- "name": "LAST_SYNCED_AT",
105
- "kind": "plain",
106
- "type": "text",
107
- "label": "Last synced",
108
- "helpText": "Written by the panel's own /refresh handler; nobody fills this in by hand."
109
- }
110
- ],
111
- // A site installs this app once per destination, so the panel switches between them.
112
- "install": { "multiple": true, "labelHint": "e.g. EU endpoint" },
113
- // Values you set once for the whole app with `qinto app var set` / `qinto app secret set`; every
114
- // site that installs it gets the same one, and a required one blocks the publish until it is set.
115
- // They are never written here: this file is snapshotted into the published version and readable.
116
- "appVariables": [
117
- {
118
- "name": "VENDOR_APP_SECRET",
119
- "kind": "secret",
120
- "type": "password",
121
- "label": "Vendor app secret",
122
- "required": true,
123
- "helpText": "Your own credential with the vendor, the same for every site. Set it with `qinto app secret set`."
124
- }
125
- ],
126
- // The panel the platform renders in an iframe, and the command that builds it. "writesSettings"
127
- // lets its server handler change this site's settings for the app; a site manager accepts it once.
128
- "ui": { "dir": "ui/dist", "build": "npm run build", "writesSettings": true }
129
- }
@@ -1,15 +0,0 @@
1
- import { defineBrowserApp } from '@blotout/qinto-sdk-app/browser'
2
-
3
- export default defineBrowserApp((app) => {
4
- // The factory runs once per active install, with that install's
5
- // browser-exposed plain variables in app.variables.
6
- app.onEvent((event) => {
7
- console.info('event on the page', event.event, app.variables.SITE_TAG)
8
-
9
- return { observedAt: Date.now(), siteTag: app.variables.SITE_TAG }
10
- })
11
-
12
- app.onPush((message) => {
13
- console.info('server push', message.from, message.topic, message.data)
14
- })
15
- })