@blotout/qinto-cli 0.1.25 → 0.1.26

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.
@@ -20818,6 +20818,7 @@ var APP_FLOWS = ["transform", "destination"];
20818
20818
  var TRANSFORM_ACTION_TYPES = ["track", "identify"];
20819
20819
  var EVENT_ACTION_TYPES = ["track", "identify", "consent"];
20820
20820
  var BROWSER_RESULT_ACTION_TYPES = ["track", "identify"];
20821
+ var BROWSER_HOOKS = ["transform"];
20821
20822
  var APP_LIFECYCLE_TYPES = ["installed", "configurationChanged", "enabled", "disabled", "versionChanged", "uninstalled"];
20822
20823
  var API_CAPABILITIES = ["track", "identify", "alias", "consentWrite", "consentRead", "propertiesRead", "sessionRead"];
20823
20824
  var DATA_CAPABILITIES = [
@@ -20828,7 +20829,8 @@ var DATA_CAPABILITIES = [
20828
20829
  "eventContext",
20829
20830
  "sharedContext",
20830
20831
  "facts",
20831
- "identity"
20832
+ "identity",
20833
+ "siteInstalls"
20832
20834
  ];
20833
20835
  var MAX_APP_VARIABLES = 32;
20834
20836
  var MAX_APP_SCOPE_VARIABLES = 16;
@@ -21106,7 +21108,8 @@ var baseSchema = external_exports.object({
21106
21108
  browser: external_exports.object({
21107
21109
  entry: external_exports.string().min(1).max(255).optional(),
21108
21110
  capture: captureSchema.optional(),
21109
- eventResults: external_exports.array(external_exports.enum(BROWSER_RESULT_ACTION_TYPES)).max(BROWSER_RESULT_ACTION_TYPES.length).optional()
21111
+ eventResults: external_exports.array(external_exports.enum(BROWSER_RESULT_ACTION_TYPES)).max(BROWSER_RESULT_ACTION_TYPES.length).optional(),
21112
+ hooks: external_exports.array(external_exports.enum(BROWSER_HOOKS)).max(BROWSER_HOOKS.length).optional()
21110
21113
  }).strict().optional().meta({
21111
21114
  description: "The app's browser surface: an optional entry for the bundle that ships inside the site's q.js, and the query params and cookies it declares for capture."
21112
21115
  }),
@@ -21410,6 +21413,15 @@ var validateCapture = /* @__PURE__ */ __name((manifest, ctx) => {
21410
21413
  }
21411
21414
  }
21412
21415
  }, "validateCapture");
21416
+ var validateBrowserHooks = /* @__PURE__ */ __name((manifest, ctx) => {
21417
+ const hooks = manifest.browser?.hooks ?? [];
21418
+ if (hooks.length > 0 && manifest.browser?.entry === void 0) {
21419
+ ctx.addIssue({ code: "custom", path: ["browser", "hooks"], message: "hooks needs a browser entry" });
21420
+ }
21421
+ if (hooks.includes("transform") && !manifest.flows.includes("transform")) {
21422
+ ctx.addIssue({ code: "custom", path: ["browser", "hooks"], message: "The browser transform hook needs the transform flow" });
21423
+ }
21424
+ }, "validateBrowserHooks");
21413
21425
  var validateRpc = /* @__PURE__ */ __name((manifest, ctx) => {
21414
21426
  const exposes = manifest.rpc?.exposes ?? [];
21415
21427
  uniqueOrIssue(
@@ -21470,6 +21482,7 @@ var appManifestSchema = baseSchema.superRefine((manifest, ctx) => {
21470
21482
  validatePipelines(manifest, ctx);
21471
21483
  validateEndpoints(manifest, ctx);
21472
21484
  validateCapture(manifest, ctx);
21485
+ validateBrowserHooks(manifest, ctx);
21473
21486
  validateRpc(manifest, ctx);
21474
21487
  validateEvents(manifest, ctx);
21475
21488
  validateOauth(manifest, ctx);
@@ -21502,6 +21515,7 @@ var MAX_APPS_Q_BYTES = 4 * 1024 * 1024;
21502
21515
  var ENVELOPE_VERSION = 3;
21503
21516
  var ENVELOPE_ERROR_CODES = ["invalid_arguments"];
21504
21517
  var MAX_INSTALLS_PER_APP = 15;
21518
+ var MAX_APPS_PER_DOMAIN = 50;
21505
21519
  var MAX_EMITS_PER_INSTALL = 10;
21506
21520
  var MAX_PUSHES_PER_INSTALL = 10;
21507
21521
  var MAX_CAPTURE_WRITES_PER_INSTALL = 10;
@@ -21517,6 +21531,10 @@ var MAX_RESPONSE_BYTES = 1024 * 1024;
21517
21531
  var MAX_RPC_BYTES = 128 * 1024;
21518
21532
  var installSchema = 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();
21519
21533
  var emitSchema = external_exports.object({ type: external_exports.literal("track"), occurredAt: external_exports.number().int().nonnegative().safe(), payload: external_exports.record(external_exports.string(), external_exports.unknown()) }).strict();
21534
+ var installRuleSchema = external_exports.object({
21535
+ mode: external_exports.enum(["except", "only"]),
21536
+ installIds: external_exports.array(external_exports.string().min(1).max(64)).max(MAX_APPS_PER_DOMAIN * MAX_INSTALLS_PER_APP)
21537
+ }).strict();
21520
21538
  var pushSchema = external_exports.object({ topic: external_exports.string().min(1).max(64), data: external_exports.unknown(), broadcast: external_exports.enum(PUSH_BROADCASTS).optional() }).strict().refine((push) => {
21521
21539
  return new TextEncoder().encode(JSON.stringify(push.data ?? null)).length <= MAX_PUSH_BYTES;
21522
21540
  }, "Push data exceeds the per-message size cap");
@@ -21603,7 +21621,11 @@ var envelopeRequestSchema = external_exports.object({
21603
21621
  var envelopeResponseSchema = external_exports.object({
21604
21622
  v: external_exports.literal(ENVELOPE_VERSION),
21605
21623
  // Transform invocations only: pass the action through, drop it, or replace its payload (§5.4).
21606
- result: external_exports.union([external_exports.literal("pass"), external_exports.literal("drop"), external_exports.object({ payload: external_exports.record(external_exports.string(), external_exports.unknown()) }).strict()]).optional(),
21624
+ result: external_exports.union([
21625
+ external_exports.literal("pass"),
21626
+ external_exports.literal("drop"),
21627
+ external_exports.object({ payload: external_exports.record(external_exports.string(), external_exports.unknown()).optional(), installs: installRuleSchema.optional() }).strict()
21628
+ ]).optional(),
21607
21629
  effects: external_exports.array(effectGroupSchema).optional(),
21608
21630
  rpc: external_exports.object({ result: external_exports.unknown() }).strict().refine((rpc) => {
21609
21631
  return new TextEncoder().encode(JSON.stringify(rpc.result ?? null)).length <= MAX_RPC_BYTES;
@@ -21901,6 +21923,16 @@ var aggregateEndpointOutcomes = /* @__PURE__ */ __name((outcomes) => {
21901
21923
  return { status: failed ? 502 : 200 };
21902
21924
  }, "aggregateEndpointOutcomes");
21903
21925
 
21926
+ // ../../libs/app-envelope/src/install-rule.ts
21927
+ var intersectInstallRules = /* @__PURE__ */ __name((a, b) => {
21928
+ if (a.mode === "except" && b.mode === "except") {
21929
+ return { mode: "except", installIds: [.../* @__PURE__ */ new Set([...a.installIds, ...b.installIds])] };
21930
+ }
21931
+ const [only, other] = a.mode === "only" ? [a, b] : [b, a];
21932
+ const listed = new Set(other.installIds);
21933
+ return { mode: "only", installIds: only.installIds.filter((id) => listed.has(id) === (other.mode === "only")) };
21934
+ }, "intersectInstallRules");
21935
+
21904
21936
  // ../../libs/app-envelope/src/observability.ts
21905
21937
  var APP_OBSERVABILITY_HEADER = "x-qinto-app-observability";
21906
21938
  var APP_OBSERVABILITY_VERSION = 1;
@@ -21970,6 +22002,12 @@ var parseAppObservability = /* @__PURE__ */ __name((header) => {
21970
22002
  var UI_CONTEXT_HEADER = "x-qinto-ui-context";
21971
22003
  var MAX_UI_BODY_BYTES = 1024 * 1024;
21972
22004
  var uiInstallSchema = external_exports.object({ id: external_exports.string().min(1).max(64), ord: external_exports.number().int().min(1), label: external_exports.string().max(100), enabled: external_exports.boolean() }).strict();
22005
+ var siteInstallSchema = external_exports.object({
22006
+ id: external_exports.string().min(1).max(64),
22007
+ appSlug: external_exports.string().min(1).max(30),
22008
+ appName: external_exports.string().min(1).max(100),
22009
+ label: external_exports.string().max(100)
22010
+ }).strict();
21973
22011
  var uiContextSchema = external_exports.object({
21974
22012
  version: external_exports.string().min(1).max(32),
21975
22013
  viewer: external_exports.object({
@@ -21980,8 +22018,35 @@ var uiContextSchema = external_exports.object({
21980
22018
  }).strict(),
21981
22019
  install: uiInstallSchema,
21982
22020
  installs: external_exports.array(uiInstallSchema).min(1).max(MAX_INSTALLS_PER_APP),
22021
+ siteInstalls: external_exports.array(siteInstallSchema).max(MAX_APPS_PER_DOMAIN * MAX_INSTALLS_PER_APP).optional(),
21983
22022
  writeTicket: external_exports.string().min(1).max(2048).optional()
21984
22023
  }).strict();
22024
+ var MAX_UI_SITE_INSTALLS_BYTES = 64 * 1024;
22025
+ var asciiJson = /* @__PURE__ */ __name((value) => {
22026
+ return JSON.stringify(value).replace(/[\u007f-\uffff]/g, (char) => `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}`);
22027
+ }, "asciiJson");
22028
+ var encodeUiContext = /* @__PURE__ */ __name((context) => {
22029
+ return asciiJson(context);
22030
+ }, "encodeUiContext");
22031
+ var bySlugThenOrd = /* @__PURE__ */ __name((a, b) => {
22032
+ if (a.appSlug !== b.appSlug) {
22033
+ return a.appSlug < b.appSlug ? -1 : 1;
22034
+ }
22035
+ return a.ord - b.ord;
22036
+ }, "bySlugThenOrd");
22037
+ var boundSiteInstalls = /* @__PURE__ */ __name((installs) => {
22038
+ const listed = [];
22039
+ let bytes = 2;
22040
+ for (const { id, appSlug, appName, label } of [...installs].sort(bySlugThenOrd)) {
22041
+ const install = { id, appSlug, appName, label };
22042
+ bytes += asciiJson(install).length + 1;
22043
+ if (bytes > MAX_UI_SITE_INSTALLS_BYTES) {
22044
+ break;
22045
+ }
22046
+ listed.push(install);
22047
+ }
22048
+ return listed;
22049
+ }, "boundSiteInstalls");
21985
22050
 
21986
22051
  // ../../node_modules/.pnpm/hono@4.13.5/node_modules/hono/dist/compose.js
21987
22052
  var compose = /* @__PURE__ */ __name((middleware, onError, onNotFound) => {
@@ -26322,6 +26387,10 @@ var phoneIdentifier = /* @__PURE__ */ __name((raw2) => {
26322
26387
  const identifier = { type: "phone", value: normalizePhone(raw2) };
26323
26388
  return isValidIdentifier(identifier) ? identifier : void 0;
26324
26389
  }, "phoneIdentifier");
26390
+ var customIdentifier = /* @__PURE__ */ __name((raw2) => {
26391
+ const identifier = { type: "custom", value: raw2.trim() };
26392
+ return isValidIdentifier(identifier) ? identifier : void 0;
26393
+ }, "customIdentifier");
26325
26394
  var classifyIdentifier = /* @__PURE__ */ __name((raw2) => {
26326
26395
  const identifier = emailIdentifier(raw2) ?? (raw2.trim().startsWith("+") ? phoneIdentifier(raw2) : void 0);
26327
26396
  return identifier ?? { type: "custom", value: raw2.trim() };
@@ -27375,22 +27444,80 @@ var handleCurrencyRates = /* @__PURE__ */ __name(async (ctx) => {
27375
27444
  return Response.json(current);
27376
27445
  }, "handleCurrencyRates");
27377
27446
 
27378
- // src/apps/emit-payload.ts
27379
- var emitPayloadSchema = external_exports.object({
27380
- event: external_exports.string().min(1),
27381
- eventId: external_exports.string().min(1).optional(),
27382
- properties: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
27383
- }).strict();
27384
- var emittedEventId = /* @__PURE__ */ __name((eventId, properties2) => {
27385
- if (eventId !== void 0) {
27386
- return eventId;
27447
+ // src/id-graph/upsert.ts
27448
+ var identifierValue = external_exports.string().min(1).max(256).optional();
27449
+ var traitValue = external_exports.string().max(256).optional();
27450
+ var personUpsertSchema = external_exports.object({
27451
+ email: identifierValue,
27452
+ phone: identifierValue,
27453
+ customId: identifierValue,
27454
+ firstName: traitValue,
27455
+ lastName: traitValue,
27456
+ city: traitValue,
27457
+ state: traitValue,
27458
+ zip: traitValue,
27459
+ country: traitValue,
27460
+ observedAt: external_exports.number().int().nonnegative().safe().optional()
27461
+ }).strict().refine((person2) => person2.email !== void 0 || person2.phone !== void 0 || person2.customId !== void 0);
27462
+ var IDENTIFIER_PARSERS = { email: emailIdentifier, phone: phoneIdentifier, customId: customIdentifier };
27463
+ var TRAIT_KEYS = {
27464
+ email: "email",
27465
+ phone: "phone",
27466
+ firstName: "first_name",
27467
+ lastName: "last_name",
27468
+ city: "city",
27469
+ state: "state",
27470
+ zip: "postal_code",
27471
+ country: "country"
27472
+ };
27473
+ var upsertIdentifiers = /* @__PURE__ */ __name((upsert) => {
27474
+ return Object.entries(IDENTIFIER_PARSERS).flatMap(([field, parse4]) => {
27475
+ const raw2 = upsert[field];
27476
+ if (raw2 === void 0) {
27477
+ return [];
27478
+ }
27479
+ const identifier = parse4(raw2);
27480
+ if (identifier === void 0) {
27481
+ addSpanEvent("invalid identifier ignored", { level: "warn", attrs: { field } });
27482
+ return [];
27483
+ }
27484
+ return [identifier];
27485
+ });
27486
+ }, "upsertIdentifiers");
27487
+ var upsertTraits = /* @__PURE__ */ __name((upsert) => {
27488
+ const traits = {};
27489
+ for (const [field, key] of Object.entries(TRAIT_KEYS)) {
27490
+ const value = upsert[field];
27491
+ if (value !== void 0) {
27492
+ traits[key] = value;
27493
+ }
27387
27494
  }
27388
- const orderId = properties2?.["orderId"];
27389
- if (typeof orderId === "string" && orderId !== "") {
27390
- return orderId;
27495
+ return traits;
27496
+ }, "upsertTraits");
27497
+ var upsertPerson = /* @__PURE__ */ __name(async (env, upsert) => {
27498
+ const [routing, ...others] = upsertIdentifiers(upsert);
27499
+ if (routing === void 0) {
27500
+ return null;
27391
27501
  }
27392
- return `evt_${ulid3()}`;
27393
- }, "emittedEventId");
27502
+ const candidate = generateUserId();
27503
+ const { anonId } = await claimAnon(env, routing, candidate);
27504
+ const receivedAt = Date.now();
27505
+ const observedAt = upsert.observedAt ?? receivedAt;
27506
+ const action = buildAction({
27507
+ type: "identify",
27508
+ userId: anonId,
27509
+ source: "app",
27510
+ identifier: routing,
27511
+ identifiers: others,
27512
+ graphOnly: true,
27513
+ observedAt,
27514
+ receivedAt,
27515
+ payload: { traits: upsertTraits(upsert) },
27516
+ context: { source: "app", occurredAt: observedAt, offline: false, replay: false }
27517
+ });
27518
+ await ingest(env, action);
27519
+ return { anonId, created: anonId === candidate };
27520
+ }, "upsertPerson");
27394
27521
 
27395
27522
  // src/apps/rate-limit.ts
27396
27523
  var limiterAbsenceLogged = false;
@@ -27407,6 +27534,49 @@ var rateLimited = /* @__PURE__ */ __name(async (env, key, binding = "BROKER_RATE
27407
27534
  return !outcome.success;
27408
27535
  }, "rateLimited");
27409
27536
 
27537
+ // src/apps/broker-identity.ts
27538
+ var identityUpsertBody = external_exports.object({
27539
+ ...identityFields,
27540
+ kind: external_exports.literal("identityUpsert"),
27541
+ person: personUpsertSchema
27542
+ }).strict();
27543
+ var identityGranted = /* @__PURE__ */ __name((slug) => {
27544
+ return siteRegistry().registry.apps[slug]?.data.includes(IDENTITY_CAPABILITY) === true;
27545
+ }, "identityGranted");
27546
+ var identityPermissionDenied = /* @__PURE__ */ __name(() => {
27547
+ return Response.json({ error: "identity_permission_denied" }, { status: 403 });
27548
+ }, "identityPermissionDenied");
27549
+ var handleIdentityUpsert = /* @__PURE__ */ __name(async (env, body) => {
27550
+ if (!identityGranted(body.slug)) {
27551
+ return identityPermissionDenied();
27552
+ }
27553
+ if (await rateLimited(env, `${body.slug}::identity-upsert`)) {
27554
+ return Response.json({ error: "rate_limited" }, { status: 429 });
27555
+ }
27556
+ const upserted = await upsertPerson(env, body.person);
27557
+ if (upserted === null) {
27558
+ return Response.json({ error: "invalid_identifier" }, { status: 400 });
27559
+ }
27560
+ return Response.json(upserted);
27561
+ }, "handleIdentityUpsert");
27562
+
27563
+ // src/apps/emit-payload.ts
27564
+ var emitPayloadSchema = external_exports.object({
27565
+ event: external_exports.string().min(1),
27566
+ eventId: external_exports.string().min(1).optional(),
27567
+ properties: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
27568
+ }).strict();
27569
+ var emittedEventId = /* @__PURE__ */ __name((eventId, properties2) => {
27570
+ if (eventId !== void 0) {
27571
+ return eventId;
27572
+ }
27573
+ const orderId = properties2?.["orderId"];
27574
+ if (typeof orderId === "string" && orderId !== "") {
27575
+ return orderId;
27576
+ }
27577
+ return `evt_${ulid3()}`;
27578
+ }, "emittedEventId");
27579
+
27410
27580
  // src/apps/broker-track.ts
27411
27581
  var trackBody = external_exports.object({
27412
27582
  ...identityFields,
@@ -27414,6 +27584,7 @@ var trackBody = external_exports.object({
27414
27584
  source: external_exports.enum(APP_HANDLER_SOURCES),
27415
27585
  userId: external_exports.string().min(1).max(64),
27416
27586
  installId: external_exports.string().min(1).max(64),
27587
+ installs: installRuleSchema.optional(),
27417
27588
  event: emitSchema
27418
27589
  }).strict();
27419
27590
  var handleTrack = /* @__PURE__ */ __name(async (env, body) => {
@@ -27444,7 +27615,8 @@ var handleTrack = /* @__PURE__ */ __name(async (env, body) => {
27444
27615
  userId: body.userId,
27445
27616
  source: body.source,
27446
27617
  payload: { event, eventId: emittedEventId(eventId, properties2), properties: properties2 ?? {} },
27447
- context: { source: "app", occurredAt: body.event.occurredAt, offline: false, replay: false }
27618
+ context: { source: "app", occurredAt: body.event.occurredAt, offline: false, replay: false },
27619
+ ...body.installs === void 0 ? {} : { installs: body.installs }
27448
27620
  });
27449
27621
  await ingest(env, action, site);
27450
27622
  return Response.json({ accepted: true }, { status: 202 });
@@ -28653,6 +28825,7 @@ var brokerBody = external_exports.discriminatedUnion("kind", [
28653
28825
  contextWriteBody,
28654
28826
  factsReadBody,
28655
28827
  identityReadBody,
28828
+ identityUpsertBody,
28656
28829
  currencyRatesBody,
28657
28830
  trackBody
28658
28831
  ]);
@@ -28721,9 +28894,6 @@ var forbidden = /* @__PURE__ */ __name(() => {
28721
28894
  var factsPermissionDenied = /* @__PURE__ */ __name(() => {
28722
28895
  return Response.json({ error: "facts_permission_denied" }, { status: 403 });
28723
28896
  }, "factsPermissionDenied");
28724
- var identityPermissionDenied = /* @__PURE__ */ __name(() => {
28725
- return Response.json({ error: "identity_permission_denied" }, { status: 403 });
28726
- }, "identityPermissionDenied");
28727
28897
  var handlePush = /* @__PURE__ */ __name(async (env, body) => {
28728
28898
  if (new TextEncoder().encode(JSON.stringify(body.data ?? null)).length > MAX_PUSH_BYTES) {
28729
28899
  return invalidBody();
@@ -28963,8 +29133,7 @@ var handleFactsRead = /* @__PURE__ */ __name(async (env, body) => {
28963
29133
  return Response.json({ items: page.items, nextCursor });
28964
29134
  }, "handleFactsRead");
28965
29135
  var handleIdentityRead = /* @__PURE__ */ __name(async (env, body) => {
28966
- const app2 = siteRegistry().registry.apps[body.slug];
28967
- if (app2 === void 0 || !app2.data.includes(IDENTITY_CAPABILITY)) {
29136
+ if (!identityGranted(body.slug)) {
28968
29137
  return identityPermissionDenied();
28969
29138
  }
28970
29139
  if (await rateLimited(env, `${body.slug}::identity`)) {
@@ -29041,6 +29210,9 @@ var handleBroker = /* @__PURE__ */ __name(async (request, env, ctx) => {
29041
29210
  case "identityRead": {
29042
29211
  return handleIdentityRead(env, body);
29043
29212
  }
29213
+ case "identityUpsert": {
29214
+ return handleIdentityUpsert(env, body);
29215
+ }
29044
29216
  case "currencyRates": {
29045
29217
  return handleCurrencyRates(ctx);
29046
29218
  }
@@ -30171,6 +30343,19 @@ var preflight = /* @__PURE__ */ __name((origin) => {
30171
30343
  var uiInstall = /* @__PURE__ */ __name((install) => {
30172
30344
  return { id: install.id, ord: install.ord, label: install.label, enabled: install.enabled ?? install.active };
30173
30345
  }, "uiInstall");
30346
+ var otherAppInstalls = /* @__PURE__ */ __name((ownSlug) => {
30347
+ const installs = Object.entries(siteRegistry().registry.apps).flatMap(([appSlug, entry]) => {
30348
+ if (appSlug === ownSlug) {
30349
+ return [];
30350
+ }
30351
+ return entry.installs.filter((install) => install.enabled ?? install.active).map((install) => ({ id: install.id, appSlug, appName: entry.name, label: install.label, ord: install.ord }));
30352
+ });
30353
+ const listed = boundSiteInstalls(installs);
30354
+ if (listed.length < installs.length) {
30355
+ addSpanEvent("app ui site installs trimmed", { level: "warn", attrs: { app: ownSlug, listed: listed.length, total: installs.length } });
30356
+ }
30357
+ return listed;
30358
+ }, "otherAppInstalls");
30174
30359
  var runForwardToApp2 = /* @__PURE__ */ __name(async (c, input2) => {
30175
30360
  const { slug, origin, app: app2, install, viewer, writeTicket, body } = input2;
30176
30361
  const context = invocationContext(c.env);
@@ -30194,9 +30379,10 @@ var runForwardToApp2 = /* @__PURE__ */ __name(async (c, input2) => {
30194
30379
  viewer,
30195
30380
  install: uiInstall(install),
30196
30381
  installs: app2.installs.map(uiInstall),
30382
+ ...app2.data.includes("siteInstalls") ? { siteInstalls: otherAppInstalls(slug) } : {},
30197
30383
  ...writeTicket === void 0 ? {} : { writeTicket }
30198
30384
  };
30199
- headers.set(UI_CONTEXT_HEADER, JSON.stringify(uiContext));
30385
+ headers.set(UI_CONTEXT_HEADER, encodeUiContext(uiContext));
30200
30386
  const trace = currentTraceContext();
30201
30387
  if (trace !== void 0) {
30202
30388
  headers.set(TRACE_HEADER, serializeTraceContext(trace));
@@ -31038,14 +31224,14 @@ var open2 = /* @__PURE__ */ __name(async (c) => {
31038
31224
 
31039
31225
  // browser-artifact/meta.json
31040
31226
  var meta_default = {
31041
- version: "0.1.11",
31042
- gitSha: "bb87bca6d3b96627f24bc0c29675f2f19680ce60",
31043
- builtAt: "2026-09-28T04:35:01.606Z",
31044
- hash: "e32dfe041826d6daaa3682d1f5cac137ffcc6a24f55bfef1a936f7e3ef4cde12"
31227
+ version: "0.1.13",
31228
+ gitSha: "21a2eff7e038bc98c8c087656c9793c8f3a82434",
31229
+ builtAt: "2026-10-01T08:32:14.748Z",
31230
+ hash: "3d5d7fe29d7e160413d4cb0e08de724613f89eb314feed6d5d1ec2e7473f7afd"
31045
31231
  };
31046
31232
 
31047
31233
  // src/paths/q-js.ts
31048
- import qScript from "./944c722e73fe144b1067da970f87716a835c697e-q.js.txt";
31234
+ import qScript from "./61afaba4cf160e30ef4b71a7ce30a398f71f13e8-q.js.txt";
31049
31235
 
31050
31236
  // src/apps-q.ts
31051
31237
  import appsQScript from "./apps-q.json";
@@ -31532,7 +31718,11 @@ var trackBody2 = external_exports.object({
31532
31718
  description: "Free-form properties describing the event; omit it when the event name alone says everything.",
31533
31719
  example: { orderId: "9241", value: 129.9, currency: "USD" }
31534
31720
  }),
31535
- context: serverActionContextField
31721
+ context: serverActionContextField,
31722
+ installs: installRuleSchema.optional().openapi({
31723
+ description: "Which of the site\u2019s app installs receive the event: `except` delivers to every install but the listed ids, `only` to the listed ids alone. An unknown id is ignored, and an empty `only` list reaches no install while the event is still stored when it qualifies. Omit it to deliver to every install.",
31724
+ example: { mode: "except", installIds: ["3f2a9c1e-8b4d-4e2a-9f6c-1d7b5e0a4c83"] }
31725
+ })
31536
31726
  }).strict().openapi("TrackRequest", { description: "An event the user performed, with optional free-form properties." });
31537
31727
  var definition10 = createRoute({
31538
31728
  method: "post",
@@ -31562,12 +31752,18 @@ var definition10 = createRoute({
31562
31752
  }
31563
31753
  });
31564
31754
  var track = createApiRouter().openapi(definition10, async (c) => {
31565
- const { identifier, event, eventId, properties: properties2, context } = c.req.valid("json");
31755
+ const { identifier, event, eventId, properties: properties2, context, installs } = c.req.valid("json");
31566
31756
  try {
31567
31757
  const id = await submitAction(
31568
31758
  c.env,
31569
31759
  identifier,
31570
- { type: "track", source: c.get("actionSource"), payload: { event, eventId, properties: properties2 ?? {} }, context },
31760
+ {
31761
+ type: "track",
31762
+ source: c.get("actionSource"),
31763
+ payload: { event, eventId, properties: properties2 ?? {} },
31764
+ context,
31765
+ ...installs === void 0 ? {} : { installs }
31766
+ },
31571
31767
  c.req.raw
31572
31768
  );
31573
31769
  return respond(c, acceptedResponse, { ok: true, id });
@@ -31606,6 +31802,9 @@ var gatePasses = /* @__PURE__ */ __name((entry, trigger, consent2, enforced) =>
31606
31802
  }, "gatePasses");
31607
31803
  var eligibleApps = /* @__PURE__ */ __name((registry3, trigger, consent2, enforced) => {
31608
31804
  const location = visitorLocation(trigger);
31805
+ const rule = trigger.kind === "event" && trigger.action.type === "track" ? trigger.action.installs : void 0;
31806
+ const listed = new Set(rule?.installIds);
31807
+ const keepListed = rule?.mode === "only";
31609
31808
  const targets = [];
31610
31809
  const geoSkipped = [];
31611
31810
  for (const slug of registry3.order) {
@@ -31615,7 +31814,7 @@ var eligibleApps = /* @__PURE__ */ __name((registry3, trigger, consent2, enforce
31615
31814
  }
31616
31815
  const { runs, skipped } = geoRunnableInstalls(slug, app2, location);
31617
31816
  geoSkipped.push(...skipped);
31618
- const installs = runs.map(({ id, ord, label }) => ({ id, ord, label }));
31817
+ const installs = runs.filter((install) => listed.has(install.id) === keepListed).map(({ id, ord, label }) => ({ id, ord, label }));
31619
31818
  if (installs.length === 0) {
31620
31819
  continue;
31621
31820
  }
@@ -31625,7 +31824,6 @@ var eligibleApps = /* @__PURE__ */ __name((registry3, trigger, consent2, enforce
31625
31824
  }, "eligibleApps");
31626
31825
 
31627
31826
  // src/apps/fan-out.ts
31628
- var MAX_APPS_PER_DOMAIN = 50;
31629
31827
  var fanOutToApps = /* @__PURE__ */ __name(async (env, { trigger, snapshot, trace }, { deliverEffects }) => {
31630
31828
  const { registry: registry3, error: error61 } = siteRegistry();
31631
31829
  const { targets, geoSkipped } = eligibleApps(registry3, trigger, snapshot.consent, consentEnforced(env));
@@ -31750,6 +31948,16 @@ var withReplacedPayload = /* @__PURE__ */ __name((entry, action, payload) => {
31750
31948
  }
31751
31949
  return { ...action, payload: parsed.data };
31752
31950
  }, "withReplacedPayload");
31951
+ var applyTransformResult = /* @__PURE__ */ __name((entry, action, { payload, installs }) => {
31952
+ const replaced = payload === void 0 ? action : withReplacedPayload(entry, action, payload);
31953
+ if (installs === void 0) {
31954
+ return replaced;
31955
+ }
31956
+ if (replaced.type !== "track") {
31957
+ throw entryFailure(entry, "an identify transform cannot set an install rule");
31958
+ }
31959
+ return { ...replaced, installs: replaced.installs === void 0 ? installs : intersectInstallRules(replaced.installs, installs) };
31960
+ }, "applyTransformResult");
31753
31961
  var postTransform = /* @__PURE__ */ __name(async (env, entry, post, dataPoints) => {
31754
31962
  let response;
31755
31963
  try {
@@ -31896,7 +32104,7 @@ var runTransformChain = /* @__PURE__ */ __name(async (env, { action, snapshot, d
31896
32104
  return { dropped: true, effects: collected };
31897
32105
  }
31898
32106
  if (verdict.result !== "pass") {
31899
- current = withReplacedPayload(entry, current, verdict.result.payload);
32107
+ current = applyTransformResult(entry, current, verdict.result);
31900
32108
  }
31901
32109
  }
31902
32110
  return { dropped: false, action: current, effects: collected };
@@ -33145,7 +33353,8 @@ var trackFrame = external_exports.object({
33145
33353
  eventId: external_exports.string().min(1),
33146
33354
  properties: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
33147
33355
  context: browserContextFrame,
33148
- appResults: browserResults.optional()
33356
+ appResults: browserResults.optional(),
33357
+ installs: installRuleSchema.optional()
33149
33358
  }).strict();
33150
33359
  var aliasFrame = external_exports.object({
33151
33360
  t: external_exports.literal("alias"),
@@ -33270,7 +33479,8 @@ var actionFromFrame = /* @__PURE__ */ __name((frame, userId, connection = {}) =>
33270
33479
  context: browserActionContext(frame.context, host, session2),
33271
33480
  browserResults: frame.appResults,
33272
33481
  geo,
33273
- host
33482
+ host,
33483
+ ...frame.installs === void 0 ? {} : { installs: frame.installs }
33274
33484
  });
33275
33485
  }
33276
33486
  case "alias": {
@@ -33843,16 +34053,9 @@ var AnonDO = class extends DurableObject2 {
33843
34053
  );
33844
34054
  seedMeta(this.kv, current.userId, current.receivedAt);
33845
34055
  touchActivity(this.kv, current.receivedAt);
33846
- let novelIdentifier = false;
33847
- if (current.identifier) {
33848
- novelIdentifier = upsertIdentifier(
33849
- this.sql,
33850
- { ...current.identifier, source: current.source, firstSeenAt: current.receivedAt },
33851
- this.identifiersMirror
33852
- );
33853
- if (novelIdentifier && linkedGlobalId(this.kv) !== null) {
33854
- recordSyncDebt(this.kv, "link");
33855
- }
34056
+ const novelIdentifier = this.recordIdentifiers(current);
34057
+ if (novelIdentifier && linkedGlobalId(this.kv) !== null) {
34058
+ recordSyncDebt(this.kv, "link");
33856
34059
  }
33857
34060
  try {
33858
34061
  if (current.type === "alias") {
@@ -33866,7 +34069,9 @@ var AnonDO = class extends DurableObject2 {
33866
34069
  } else {
33867
34070
  await this.ingestConsent(current);
33868
34071
  }
33869
- this.appFanOut({ kind: "event", action: current }, current.userId, context);
34072
+ if (current.type !== "identify" || current.graphOnly !== true) {
34073
+ this.appFanOut({ kind: "event", action: current }, current.userId, context);
34074
+ }
33870
34075
  this.releaseTransformEmissions(releaseEmissions);
33871
34076
  } finally {
33872
34077
  if (novelIdentifier) {
@@ -33874,6 +34079,17 @@ var AnonDO = class extends DurableObject2 {
33874
34079
  }
33875
34080
  }
33876
34081
  }
34082
+ recordIdentifiers(action) {
34083
+ const identifiers = [
34084
+ ...action.identifier ? [action.identifier] : [],
34085
+ ...action.type === "identify" ? action.identifiers ?? [] : []
34086
+ ];
34087
+ let novel = false;
34088
+ for (const identifier of identifiers) {
34089
+ novel = upsertIdentifier(this.sql, { ...identifier, source: action.source, firstSeenAt: action.receivedAt }, this.identifiersMirror) || novel;
34090
+ }
34091
+ return novel;
34092
+ }
33877
34093
  // null means a transform dropped the action: the turn succeeds with nothing persisted (§5.4).
33878
34094
  // The chain runs on the originating site's worker, against its apps and key master; only the
33879
34095
  // collected effects come back, and they are released here, past the turn outcome, so a delivery
@@ -34061,7 +34277,7 @@ var AnonDO = class extends DurableObject2 {
34061
34277
  }
34062
34278
  async ingestIdentify(action) {
34063
34279
  const classified = await traced("pii.classify", {}, () => {
34064
- const result = classifyTraits(action.payload.traits, action.receivedAt);
34280
+ const result = classifyTraits(action.payload.traits, action.observedAt ?? action.receivedAt);
34065
34281
  for (const key of result.dropped) {
34066
34282
  addSpanEvent("invalid pii trait ignored", { level: "warn", attrs: { "action.id": action.actionId, key } });
34067
34283
  }
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "main": {
3
- "version": "0.1.8",
4
- "gitSha": "980d4090b9558f84f1a60a302f7d069e4f33065d",
5
- "builtAt": "2026-09-30T13:03:42.976Z",
3
+ "version": "0.1.9",
4
+ "gitSha": "ffbdbe7148d4aba3072a6c3c857e69f0557b0ec5",
5
+ "builtAt": "2026-10-01T08:37:00.893Z",
6
6
  "compatibilityDate": "2026-06-23",
7
7
  "compatibilityFlags": [
8
8
  "nodejs_compat"
@@ -10,8 +10,8 @@
10
10
  "mainModule": "index.js",
11
11
  "modules": [
12
12
  {
13
- "name": "944c722e73fe144b1067da970f87716a835c697e-q.js.txt",
14
- "path": "main/944c722e73fe144b1067da970f87716a835c697e-q.js.txt",
13
+ "name": "61afaba4cf160e30ef4b71a7ce30a398f71f13e8-q.js.txt",
14
+ "path": "main/61afaba4cf160e30ef4b71a7ce30a398f71f13e8-q.js.txt",
15
15
  "contentType": "text/plain"
16
16
  },
17
17
  {
@@ -39,9 +39,9 @@
39
39
  ]
40
40
  },
41
41
  "app-runtime": {
42
- "version": "0.1.6",
43
- "gitSha": "9259277de3d7879928d4e829094916cedb0acb58",
44
- "builtAt": "2026-09-30T13:03:45.923Z",
42
+ "version": "0.1.7",
43
+ "gitSha": "403986012db0fea5975162b971aca874682f3184",
44
+ "builtAt": "2026-10-01T08:37:02.873Z",
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.25",
3
+ "version": "0.1.26",
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": {
@@ -1 +0,0 @@
1
- (function(){var e=()=>{},t=t=>{t.config?.autoCapture?.pageLoad===!0&&t.q.push([`track`,[`PageView`],e,e])},n=class extends Error{constructor(e){super(e),this.name=`QintoError`}},r=class extends n{constructor(e){super(e),this.name=`QintoTimeoutError`}},i=(e=Date.now())=>`evt_${globalThis.crypto.randomUUID()}_${e}`,a=(e,t)=>{if(e!==void 0)return e;let n=t?.orderId;return typeof n==`string`&&n!==``?n:i()},o=[`necessary`,`functional`,`analytics`,`marketing`,`salesOfData`],s=[`then`,`ready`,`config`],c=()=>{},l=(e,t)=>(...n)=>{let r=new Promise((r,i)=>{e.q.push([t,n,r,i])});return r.catch(c),r},u=e=>{let t=e;return t._q=1,t.q=[],new Proxy(t,{get:(e,n)=>typeof n!=`string`||s.includes(n)||n in t?Reflect.get(t,n):(t[n]=l(t,n),t[n])})},d=()=>{let e=document.currentScript?.dataset.qintoGlobal;return typeof e==`string`&&e!==``?e:`qinto`},f=e=>typeof e==`object`&&!!e&&e._q===1,p=(e=`qinto`)=>{let t=globalThis;if(e in t){let n=t[e];if(!f(n))return null;let r=Array.isArray(n.q)?n:u(n);return r!==n&&Object.defineProperty(t,e,{value:r,writable:!0,enumerable:!0,configurable:!0}),r}let n=u({});return Object.defineProperty(t,e,{value:n,writable:!0,enumerable:!0,configurable:!0}),n},m=(e,t)=>async(r,i)=>{let a=Reflect.get(e,r);if(typeof a!=`function`)throw new n(`[Qinto] ${t}.${r} is not a method of this tag.`);return a.apply(e,Array.from(i))},h=e=>()=>Promise.reject(e),g=(e,t)=>{let[n,r,i,a]=t;e(n,r).then(i,a)},ee=e=>{let t=[];return t.push=(...t)=>{for(let n of t)g(e,n);return 0},t},_=(e,t,n=`window.qinto`)=>{let r=e.ready;if(r!==void 0)return r;let i=e,a=e=>{let t=i.q;i.q=ee(e);for(let n of t)g(e,n)},o=t().then(e=>{a(m(e,n))},e=>{throw a(h(e)),e});return o.catch(c),i.ready=o,o},v=(e,t,n)=>n===void 0||!o.some(e=>e===n)?!1:n===`necessary`||e!==null&&e.categories[n]===!0&&e.apps[t]!==!1,y=()=>{},te=100,ne=1024,re=32768,b=(e,t,n)=>e.target.config?.consentConfiguration?.enabled!==!0||v(n,t,e.target.config?.apps?.[t]?.category),x=e=>Object.values(e.config?.apps??{}).some(e=>e.category!==`necessary`||e.capture!==void 0),S=(e,t)=>{let{app:n,install:r,topic:i,to:a}=e;return typeof n!=`string`||typeof i!=`string`||r!==void 0&&typeof r!=`string`||t&&!(Array.isArray(a)&&a.every(e=>typeof e==`string`))},ie=(e,t,n,r)=>r===null?e.slug===t&&(n===void 0||n===e.installId):r.has(e.slug),C=(e,t)=>{console.error(`[Qinto] A handler of app "${e.slug}" (install "${e.installId}") failed.`,t)},w=(e,t,n)=>{for(let r of t)try{let t=r(n);t instanceof Promise&&t.catch(t=>{C(e,t)})}catch(t){C(e,t)}},ae=(e,t,n,r)=>{e.activated.add(r.id);let i={slug:t,installId:r.id,eventHandlers:[],consentHandlers:[],pushHandlers:[],sharedContextHandlers:[]};e.instances.push(i);let a={variables:r.variables,install:{id:r.id,label:r.label},getSession:async()=>{let n=await e.client.getSession();return e.target.config?.apps?.[t]?.geo===!0?n:{...n,ip:null,geo:null}},track:(t,n)=>{e.client.track(t,n).catch(y)},onEvent:e=>{i.eventHandlers.push(e)},onConsent:e=>{i.consentHandlers.push(e)},onPush:e=>{i.pushHandlers.push(e)},fetch:async(n,i)=>{let a=new URL(`${e.client.destination}/app/${t}${n}`);if(!n.startsWith(`/`)||!a.pathname.startsWith(`/app/${t}/`))throw TypeError(`[Qinto] App "${t}" fetched "${n}"; the path must be absolute and stay inside the app.`);let o=new Headers(i?.headers);return o.set(`x-qinto-install`,r.id),fetch(a,{...i,headers:o,credentials:`include`})},onSharedContext:n=>{if(e.target.config?.apps?.[t]?.sharedContext!==!0){console.error(`[Qinto] App "${t}" reads shared context without declaring the sharedContext capability.`);return}i.sharedContextHandlers.push(n),e.sharedContext!==null&&oe(e,t)&&w(i,[n],e.sharedContext)}};try{n(a)}catch(e){console.error(`[Qinto] App "${t}" failed to activate install "${r.id}".`,e)}},T=(e,t,n)=>{if(!b(e,t,e.consent))return;let r=((`_appInstalls`in e.target?e.target._appInstalls:void 0)?.[t]??[]).filter(t=>!e.activated.has(t.id));if(r.length===0)return;let i=n.factory;if(i===void 0){try{i=n.loader()}catch(e){i=null,console.error(`[Qinto] App "${t}" failed to load.`,e)}e.apps.set(t,{...n,factory:i})}if(i!==null)for(let n of r)ae(e,t,i,n)},oe=(e,t)=>b(e,t,e.consent)?e.sharedContextConsent===void 0||b(e,t,e.sharedContextConsent):!1,se=e=>{let{categories:t,apps:n}=e;if(typeof t==`object`&&t&&typeof n==`object`&&n)return{categories:t,apps:n}},ce=(e,t)=>{let n=e;n.consent=t,n.sharedContextConsent=void 0;for(let n of e.instances)w(n,n.consentHandlers,t);e.onConsentSettled?.(t);for(let[t,n]of e.apps)T(e,t,n)},E=async e=>{try{ce(e,await e.client.getConsent())}catch(e){console.error(`[Qinto] The apps consent read failed.`,e)}},D=e=>{let t;try{t=JSON.stringify(e)}catch{return}if(!(t===void 0||new TextEncoder().encode(t).length>ne))return JSON.parse(t)},O=e=>{let t={};for(let n of e){if(n.settled!==n.total||n.values.length!==1)continue;let e=D(n.values[0]);if(e===void 0)continue;let r={...t[n.instance.slug]??{},[n.instance.installId]:e},i={...t,[n.instance.slug]:r};new TextEncoder().encode(JSON.stringify(i)).length<=re&&(t[n.instance.slug]=r)}return Object.keys(t).length===0?void 0:t},k=async(e,t)=>{e.onPageEvent?.(e.consent);let n=[],r=[];for(let i of e.instances){if(!b(e,i.slug,e.consent))continue;if(e.target.config?.apps?.[i.slug]?.eventResults?.includes(t.type)!==!0){w(i,i.eventHandlers,t);continue}let a={instance:i,total:i.eventHandlers.length,settled:0,values:[]};n.push(a);for(let e of i.eventHandlers){let n;try{n=e(t)}catch(e){a.settled+=1,C(i,e);continue}r.push(Promise.resolve(n).then(e=>{a.settled+=1,e!==void 0&&a.values.push(e)},e=>{a.settled+=1,C(i,e)}))}}if(r.length>0){let e=y,t=new Promise(t=>{e=t}),n=setTimeout(e,te);await Promise.race([Promise.all(r),t]),clearTimeout(n)}return O(n)},A=e=>{let{client:t}=e;return{track:async(n,r,i)=>{let o=a(i?.eventId,r),s=t.actionContext(),c=await k(e,{type:`track`,event:n,eventId:o,properties:r??{}});return await t.submitTrack({event:n,eventId:o,properties:r,context:s,appResults:c}),{eventId:o}},identify:async(n,r)=>{let i=t.actionContext(),a=await k(e,{type:`identify`,event:`identify`,properties:n??{}});return t.submitIdentify({traits:n,id:r,context:i,appResults:a})},alias:t.alias,consent:async n=>{if(await t.submitConsent(n,t.actionContext()),e.onConsentMutation?.(),x(e.target)){let t=e;t.consent=null,await E(e)}},getAnonId:t.getAnonId,getConsent:t.getConsent,getSession:t.getSession,close:t.close}},j=(e,t)=>{let n=new Map,r=null,i=null,a=e=>{let[t,i]=e;if(n.has(t))return;let a={loader:i};n.set(t,a),r?.(t,a)},o=e,s=`_apps`in o?o._apps:void 0,c=Array.isArray(s)?s:[];c.push=(...e)=>{for(let t of e)a(t);return 0},o._apps=c;for(let e of c.splice(0))a(e);return{sharedContext:e=>{let{context:t}=e;if(typeof t!=`object`||!t||Array.isArray(t)){console.error(`[Qinto] The server sent a malformed shared context.`,e);return}let n=i;if(n===null)return;n.sharedContext=t;let r=se(e),a=r??n.consent;n.sharedContextConsent=r;for(let e of n.instances)b(n,e.slug,a)&&w(e,e.sharedContextHandlers,n.sharedContext)},push:e=>{let{app:t,install:n,topic:r,scope:a,to:o}=e,s=a===`site`;if(S(e,s)){console.error(`[Qinto] The server sent a malformed app push.`,e);return}let c=i;if(c===null)return;let l=s?new Set(o):null,u={topic:r,data:e.data,from:t};for(let e of c.instances)ie(e,t,n,l)&&b(c,e.slug,c.consent)&&w(e,e.pushHandlers,u)},connect:async a=>{let o={target:e,client:a,apps:n,instances:[],activated:new Set,consent:null,sharedContext:i?.sharedContext??null,sharedContextConsent:i?.sharedContextConsent,onConsentSettled:t?.onConsentSettled,onConsentMutation:t?.onConsentMutation,onPageEvent:t?.onPageEvent};i=o,r=(e,t)=>{T(o,e,t),o.consent===null&&o.target.config?.consentConfiguration?.enabled===!0&&o.target.config?.apps?.[e]?.category!==`necessary`&&E(o)};for(let[e,t]of n)T(o,e,t);return e.config?.consentConfiguration?.enabled===!0&&x(e)?await E(o):o.onConsentSettled?.(o.consent),A(o)}}},M=512,N=864e5,P=e=>Object.fromEntries(Object.entries(e).map(([e,t])=>[e,Object.fromEntries(Object.entries(t).map(([e,t])=>[e,t.value]))])),F=(e,t)=>`qinto:cap:${encodeURIComponent(e)}:${encodeURIComponent(t)}`,I=e=>{try{let t=window.sessionStorage.getItem(e);if(t===null)return null;let n=JSON.parse(t);return typeof n.at!=`number`||typeof n.anonId!=`string`||typeof n.apps!=`object`||n.apps===null||Date.now()-n.at>N?null:{at:n.at,anonId:n.anonId,apps:n.apps}}catch{return null}},L=(e,t)=>{try{window.sessionStorage.setItem(e,JSON.stringify(t))}catch{}},R=e=>{try{window.sessionStorage.removeItem(e)}catch{}},z=e=>{try{return decodeURIComponent(e)}catch{return e}},B=()=>{let e={};for(let t of document.cookie.split(`;`)){let n=t.indexOf(`=`);n>0&&(e[t.slice(0,n).trim()]=z(t.slice(n+1)))}return e},V=(e,t)=>{let n={};for(let r of e.queryParams){let e=t.params.get(r);e!==null&&e.length<=M&&(n[r]={value:e,source:`query`})}for(let r of e.cookies){let e=t.cookies[r];e!==void 0&&e.length<=M&&(n[r]={value:e,source:`cookie`})}return n},H=(e,t)=>{let n={params:new URLSearchParams(window.location.search),cookies:B()},r={};for(let[i,a]of Object.entries(e.config?.apps??{})){if(a.capture===void 0||!v(t,i,a.category))continue;let e=V(a.capture,n);Object.keys(e).length>0&&(r[i]=e)}return r},U=(e,t)=>{let n={};for(let[r,i]of Object.entries(e)){let e=t[r]??{},a={};for(let[t,n]of Object.entries(i))(e[t]?.value!==n.value||e[t]?.source!==n.source)&&(a[t]=n);Object.keys(a).length>0&&(n[r]=a)}return n},W=(e,t,n,r)=>{let i=F(r,n),a=I(i),o=a?.apps??{},s=a?.anonId??null,c=a===null,l={},u=0,d=(e,t)=>{s!==null&&s!==e.anonId&&(o={}),s=e.anonId;let n=new Set(Object.entries(e.accepted).flatMap(([e,t])=>t.map(t=>`${e}\u0000${t}`)));for(let[e,r]of Object.entries(t))for(let[t,i]of Object.entries(r))n.has(`${e}\u0000${t}`)&&(o[e]={...o[e],[t]:i});L(i,{at:Date.now(),anonId:s,apps:o})},f=null,p=()=>{u+=1,o={},l={},R(i)},m=n=>{if(f=n,!c)return;let r=U(H(e,n),{...o,...l});if(Object.keys(r).length===0)return;let i=u;l={...l,...r},t(P(r)).then(e=>{i===u&&d(e,r)}).catch(()=>{}).finally(()=>{l={}})};return{settled:e=>{m(e)},needsAnon:()=>!c,observedAnon:e=>{(e===null||s!==null&&s!==e)&&p(),s=e,c=!0,m(f)},clear:p}},G=Symbol.for(`qinto.connectGate`),K=()=>{},le=e=>{let t=globalThis,n=(t[G]??Promise.resolve()).then(e);return t[G]=n.then(K,K),n},q=class extends n{constructor(e){super(e),this.name=`QintoProtocolError`}},ue=e=>{if(typeof e!=`string`)return null;let t;try{t=JSON.parse(e)}catch{return null}if(typeof t!=`object`||!t)return null;let n=t;return n.t===`ack`||n.t===`error`||n.t===`consentState`||n.t===`session`||n.t===`captureAck`||n.t===`appPush`||n.t===`sharedContext`?t:null},de=e=>{if(e.t===`consentState`)return{categories:e.categories,apps:e.apps};if(e.t===`session`)return{anonId:e.anonId,globalId:e.globalId,ip:e.ip,geo:e.geo};if(e.t===`captureAck`)return{anonId:e.anonId,accepted:e.accepted}},fe=e=>{let t=new Map,n=e=>{let n=t.get(e);if(n!==void 0)return clearTimeout(n.timer),t.delete(e),n};return{submit:n=>new Promise((i,a)=>{let o={frame:n,sent:e.send(n),resolve:i,reject:a,timer:setTimeout(()=>{t.delete(n.ref),a(new r(`The Qinto request timed out.`))},e.requestTimeoutMs)};t.set(n.ref,o)}),flush:()=>{for(let n of t.values())n.sent||=e.send(n.frame)},deliver:e=>{let t=e.ref===void 0?void 0:n(e.ref);if(t!==void 0){if(e.t===`error`){t.reject(new q(e.message));return}t.resolve(de(e))}},rejectAll:e=>{let n=[...t.values()];t.clear();for(let t of n)clearTimeout(t.timer),t.reject(e)}}},J=()=>{let e=document.referrer;return{url:window.location.href,...e===``?{}:{referrer:e}}},pe=e=>new WebSocket(e),me=500,he=3e4,Y=4096,ge=8192,_e=(e,t)=>{if(t.url.length>Y)return e;let n=new URLSearchParams({url:t.url});t.referrer!==void 0&&t.referrer.length<=Y&&n.set(`referrer`,t.referrer);let r=`${e}?${n}`;return r.length<=ge?r:e},ve=`session not initialized`,ye=(e,t)=>{let n=Math.min(me*2**e,he);return Math.round(n/2+n/2*t)},be=(e,t)=>e.t===`appPush`?(t.onAppPush?.(e),!0):e.t===`sharedContext`&&(t.onSharedContext?.(e),!0),xe=e=>{let t=`${e.destination.replace(/\/+$/,``)}/open`,n=null,r=null,i=0,a=null,o=fe({send:e=>n===null||n.readyState!==1?!1:(n.send(JSON.stringify(e)),!0),requestTimeoutMs:e.requestTimeoutMs}),s=t=>{e.onError?.(t)},c=()=>{if(r!==null&&(clearTimeout(r),r=null),n!==null){let e=n;n=null,e.close()}o.rejectAll(new q(`connection closed`))},l=()=>{let u=pe(_e(t,J()));n=u,u.onopen=()=>{i=0;let e=a;a=null,o.flush(),e?.resolve()},u.onmessage=t=>{let n=ue(t.data);if(n===null){s(new q(`invalid message`));return}be(n,e)||(`anonId`in n&&typeof n.anonId==`string`&&e.onAnonId?.(n.anonId),o.deliver(n),n.t===`error`&&n.message===ve&&u.close())},u.onerror=()=>{s(new q(`connection closed`))},u.onclose=()=>{if(n!==u)return;n=null;let e=a;if(e!==null){a=null,c(),e.reject(new q(`connection closed`));return}o.rejectAll(new q(`connection closed`)),r=setTimeout(()=>{r=null,l()},ye(i,Math.random())),i+=1}};return{connect:()=>new Promise((e,t)=>{a={resolve:e,reject:t},l()}),submit:e=>o.submit(e),close:()=>{c()}}},Se=1e4,Ce=()=>{let e=0;return()=>(e+=1,`q${e}`)},X=(e,t,n)=>{let r=null,i=()=>({occurredAt:Date.now(),page:J()}),o=async()=>{let n=await e.submit({t:`getSession`,ref:t()});return r=n.anonId,n};return{destination:n.replace(/\/+$/,``),actionContext:i,submitIdentify:async({traits:n,id:r,context:i,appResults:a})=>{await e.submit({t:`identify`,ref:t(),traits:n,id:r,context:i,appResults:a})},submitTrack:async({event:n,eventId:r,properties:i,context:a,appResults:o})=>{await e.submit({t:`track`,ref:t(),event:n,eventId:r,properties:i,context:a,appResults:o})},submitConsent:async(n,r)=>{await e.submit({t:`consent`,ref:t(),categories:n.categories,apps:n.apps,context:r})},track:async(n,r,o)=>{let s=a(o?.eventId,r);return await e.submit({t:`track`,ref:t(),event:n,eventId:s,properties:r,context:i()}),{eventId:s}},alias:async n=>{await e.submit({t:`alias`,ref:t(),alias:n})},getConsent:async()=>await e.submit({t:`getConsent`,ref:t()}),getSession:o,getAnonId:async()=>(r===null&&await o(),r),close:async()=>{e.close()}}},we=async e=>{let t=xe({destination:e.destination,requestTimeoutMs:e.requestTimeoutMs??Se,onError:e.onError,onAppPush:e.onAppPush,onSharedContext:e.onSharedContext,onAnonId:e.onAnonId});await le(()=>t.connect());let n=Ce();return{client:X(t,n,e.destination),submitCapture:async e=>await t.submit({t:`capture`,ref:n(),apps:e})}},Te=(e,t)=>{let n=null,r=j(e,{onConsentSettled:e=>{n?.settled(e)},onConsentMutation:()=>{n?.clear()},onPageEvent:e=>{n?.settled(e)}});return{connect:async i=>{let a=await we({...i,onAppPush:r.push,onSharedContext:r.sharedContext,onAnonId:e=>{n?.observedAnon(e)}});n=W(e,a.submitCapture,i.destination,t);let o=await r.connect(a.client);return n.needsAnon()&&n.observedAnon((await a.client.getSession()).anonId),o}}},Z=d(),Q=p(Z),$=`window.${Z}`;if(Q!==null&&Q.ready===void 0){let e=Te(Q,Z);t(Q),_(Q,async()=>{let t=Q.config?.destination;if(typeof t!=`string`||t===``)throw Error(`[Qinto] ${$}.config.destination is missing.`);return e.connect({destination:t})},$)}})();