@blotout/qinto-cli 0.1.21 → 0.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -11
- package/dist/index.js +4686 -4295
- package/dist/index.js.map +1 -1
- package/dist/worker-artifact/app-runtime/index.js +259 -98
- package/dist/worker-artifact/main/944c722e73fe144b1067da970f87716a835c697e-q.js.txt +1 -0
- package/dist/worker-artifact/main/index.js +480 -66
- package/dist/worker-artifact/version.json +12 -11
- package/dist/worker-artifact/wfp/index.js +1 -1
- package/package.json +1 -1
- package/dist/worker-artifact/main/7bc13565a6fa9d520d0ebed788d11591e8db5b44-q.js.txt +0 -1
|
@@ -790,6 +790,10 @@ var deriveVisitorSignature = /* @__PURE__ */ __name(async (masterBase64, domainI
|
|
|
790
790
|
return hmacHex(masterBase64, `${domainId}#visitor#${anonId}`);
|
|
791
791
|
}, "deriveVisitorSignature");
|
|
792
792
|
|
|
793
|
+
// ../../libs/app-manifest/src/app-roles.ts
|
|
794
|
+
var DECLARABLE_APP_ROLES = ["owner", "admin", "manager", "editor", "member", "viewer"];
|
|
795
|
+
var APP_ROLES = ["full", ...DECLARABLE_APP_ROLES];
|
|
796
|
+
|
|
793
797
|
// ../../libs/app-manifest/src/cron.ts
|
|
794
798
|
var MAX_CRON_LENGTH = 128;
|
|
795
799
|
var MINUTE_LITERAL = /^([0-9]|[1-5][0-9])$/;
|
|
@@ -20348,6 +20352,10 @@ var VARIABLE_NAME = /^[A-Z][A-Z0-9_]*$/;
|
|
|
20348
20352
|
var APP_SLUG_PATTERN = /^[a-z][a-z0-9-]{2,29}$/;
|
|
20349
20353
|
var FUNCTION_NAME = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
20350
20354
|
var identifierField = external_exports.string().min(1).max(64).regex(IDENTIFIER);
|
|
20355
|
+
var RENDERABLE_TEXT = /^[^\u0000-\u0009\u000B-\u001F\u007F\u200B-\u200F\u202A-\u202E\u2066-\u2069\uFEFF]*$/;
|
|
20356
|
+
var renderableText = /* @__PURE__ */ __name((min, max) => {
|
|
20357
|
+
return external_exports.string().min(min).max(max).regex(RENDERABLE_TEXT, "must not carry control or bidirectional-override characters");
|
|
20358
|
+
}, "renderableText");
|
|
20351
20359
|
var httpsUrl = /* @__PURE__ */ __name((field) => {
|
|
20352
20360
|
return external_exports.url().max(2048).refine((value) => value.startsWith("https://"), `${field} must be an HTTPS URL`);
|
|
20353
20361
|
}, "httpsUrl");
|
|
@@ -20483,6 +20491,35 @@ var validateOauth = /* @__PURE__ */ __name((input2, ctx) => {
|
|
|
20483
20491
|
}
|
|
20484
20492
|
}, "validateOauth");
|
|
20485
20493
|
|
|
20494
|
+
// ../../libs/app-manifest/src/roles.ts
|
|
20495
|
+
var MAX_APP_ROLE_DESCRIPTION = 200;
|
|
20496
|
+
var roleSchema = external_exports.object({
|
|
20497
|
+
key: external_exports.enum(DECLARABLE_APP_ROLES, {
|
|
20498
|
+
error: /* @__PURE__ */ __name((issue2) => {
|
|
20499
|
+
return issue2.input === "full" ? "full access is always available and is not declared" : void 0;
|
|
20500
|
+
}, "error")
|
|
20501
|
+
}),
|
|
20502
|
+
description: renderableText(1, MAX_APP_ROLE_DESCRIPTION).optional()
|
|
20503
|
+
}).strict();
|
|
20504
|
+
var rolesSchema = external_exports.array(roleSchema).max(DECLARABLE_APP_ROLES.length).superRefine((roles, ctx) => {
|
|
20505
|
+
uniqueOrIssue(
|
|
20506
|
+
ctx,
|
|
20507
|
+
roles.map((role) => role.key),
|
|
20508
|
+
[],
|
|
20509
|
+
"role"
|
|
20510
|
+
);
|
|
20511
|
+
}).transform((roles) => {
|
|
20512
|
+
return [...roles].sort((a, b) => APP_ROLES.indexOf(a.key) - APP_ROLES.indexOf(b.key));
|
|
20513
|
+
});
|
|
20514
|
+
var validateRoles = /* @__PURE__ */ __name((manifest, ctx) => {
|
|
20515
|
+
const declared = /* @__PURE__ */ new Set(["full", ...(manifest.roles ?? []).map((role) => role.key)]);
|
|
20516
|
+
for (const [index, tool] of manifest.tools.entries()) {
|
|
20517
|
+
if (tool.role !== void 0 && !declared.has(tool.role)) {
|
|
20518
|
+
ctx.addIssue({ code: "custom", path: ["tools", index, "role"], message: `Tool role ${tool.role} is not declared in roles` });
|
|
20519
|
+
}
|
|
20520
|
+
}
|
|
20521
|
+
}, "validateRoles");
|
|
20522
|
+
|
|
20486
20523
|
// ../../libs/app-manifest/src/route.ts
|
|
20487
20524
|
var ENDPOINT_PATH_PATTERN = /^\/(?:(?::[a-zA-Z][a-zA-Z0-9_]*|[a-zA-Z0-9_-]+)(?:\/(?::[a-zA-Z][a-zA-Z0-9_]*|[a-zA-Z0-9_-]+))*)?$/;
|
|
20488
20525
|
var isParam = /* @__PURE__ */ __name((segment) => {
|
|
@@ -20579,10 +20616,6 @@ var MAX_MANIFEST_TOOLS_BYTES = 150 * 1024;
|
|
|
20579
20616
|
var TOOL_PROPERTY_NAME = /^[a-zA-Z][a-zA-Z0-9_]*$/;
|
|
20580
20617
|
var RESERVED_TOOL_ARGUMENTS = ["teamId", "siteId", "installId"];
|
|
20581
20618
|
var TOOL_STRING_FORMATS = ["uuid", "date", "date-time", "email", "uri"];
|
|
20582
|
-
var RENDERABLE_TEXT = /^[^\u0000-\u0009\u000B-\u001F\u007F\u200B-\u200F\u202A-\u202E\u2066-\u2069\uFEFF]*$/;
|
|
20583
|
-
var renderableText = /* @__PURE__ */ __name((min, max) => {
|
|
20584
|
-
return external_exports.string().min(min).max(max).regex(RENDERABLE_TEXT, "must not carry control or bidirectional-override characters");
|
|
20585
|
-
}, "renderableText");
|
|
20586
20619
|
var propertyName = external_exports.string().min(1).max(48).regex(TOOL_PROPERTY_NAME);
|
|
20587
20620
|
var FORMAT_SCHEMAS = {
|
|
20588
20621
|
uuid: external_exports.uuid(),
|
|
@@ -20678,7 +20711,8 @@ var toolSchema = external_exports.object({
|
|
|
20678
20711
|
title: renderableText(1, MAX_APP_TOOL_TITLE_LENGTH),
|
|
20679
20712
|
description: renderableText(MIN_APP_TOOL_DESCRIPTION, MAX_APP_TOOL_DESCRIPTION),
|
|
20680
20713
|
inputSchema: toolInputSchema,
|
|
20681
|
-
annotations: toolAnnotationsSchema
|
|
20714
|
+
annotations: toolAnnotationsSchema,
|
|
20715
|
+
role: external_exports.enum(APP_ROLES).optional()
|
|
20682
20716
|
}).strict();
|
|
20683
20717
|
var nodeChildren = /* @__PURE__ */ __name((value) => {
|
|
20684
20718
|
const properties2 = value["properties"];
|
|
@@ -21082,6 +21116,9 @@ var baseSchema = external_exports.object({
|
|
|
21082
21116
|
description: "The push topics this app broadcasts to every app on the site. Requires the `sharedContext` data capability; a topic left out reaches only this app\u2019s own browser code."
|
|
21083
21117
|
}),
|
|
21084
21118
|
endpoints: external_exports.array(endpointSchema).max(16).default([]).meta({ description: "The HTTP routes served at https://<site>/app/<slug><path>." }),
|
|
21119
|
+
roles: rolesSchema.optional().meta({
|
|
21120
|
+
description: "The app roles this app uses, from the fixed list owner, admin, manager, editor, member, viewer. Full access is always available and is not declared. A team member is given one app role per app."
|
|
21121
|
+
}),
|
|
21085
21122
|
tools: toolsSchema.meta({
|
|
21086
21123
|
description: "The MCP tools this app serves to AI assistants, published as `<slug>_<name>`. Each one a site manager accepts becomes callable on their behalf by an assistant."
|
|
21087
21124
|
}),
|
|
@@ -21438,6 +21475,7 @@ var appManifestSchema = baseSchema.superRefine((manifest, ctx) => {
|
|
|
21438
21475
|
validateOauth(manifest, ctx);
|
|
21439
21476
|
validateServer(manifest, ctx);
|
|
21440
21477
|
validateTools(manifest.tools, ctx);
|
|
21478
|
+
validateRoles(manifest, ctx);
|
|
21441
21479
|
});
|
|
21442
21480
|
|
|
21443
21481
|
// ../../libs/app-manifest/src/oauth-flow.ts
|
|
@@ -21625,6 +21663,7 @@ var MAX_TOOL_DATA_NODES = 5e3;
|
|
|
21625
21663
|
var CONTROL_DEADLINES_MS = {
|
|
21626
21664
|
noop: { platform: 15e3, site: 1e4 },
|
|
21627
21665
|
lifecycle: { platform: 15e3, site: 1e4 },
|
|
21666
|
+
deleteRequest: { platform: 15e3, site: 1e4 },
|
|
21628
21667
|
migration: { platform: 15e3, site: 1e4 },
|
|
21629
21668
|
schedule: { platform: 65e3, site: 6e4 },
|
|
21630
21669
|
tool: { platform: 55e3, site: 5e4 }
|
|
@@ -21666,6 +21705,14 @@ var lifecycleEventSchema = external_exports.discriminatedUnion("type", [
|
|
|
21666
21705
|
}).strict(),
|
|
21667
21706
|
external_exports.object({ ...lifecycleBase, type: external_exports.literal("uninstalled"), scope: external_exports.enum(["app", "install"]) }).strict()
|
|
21668
21707
|
]);
|
|
21708
|
+
var deleteRequestSchema = external_exports.object({
|
|
21709
|
+
eventId: external_exports.string().min(1).max(64),
|
|
21710
|
+
occurredAt: external_exports.number().int().nonnegative().safe(),
|
|
21711
|
+
install: installSchema2,
|
|
21712
|
+
personId: external_exports.string().min(1).nullable(),
|
|
21713
|
+
anonIds: external_exports.array(external_exports.string().min(1)),
|
|
21714
|
+
identifiers: external_exports.array(external_exports.object({ type: external_exports.enum(["email", "phone", "custom"]), value: external_exports.string().min(1) }).strict()).optional()
|
|
21715
|
+
}).strict();
|
|
21669
21716
|
var migrationSchema = external_exports.discriminatedUnion("phase", [
|
|
21670
21717
|
external_exports.object({
|
|
21671
21718
|
resource: external_exports.string().min(1).max(64),
|
|
@@ -21708,7 +21755,12 @@ var toolSchema2 = external_exports.object({
|
|
|
21708
21755
|
name: external_exports.string().min(1).max(MAX_APP_TOOL_NAME_LENGTH).regex(APP_TOOL_NAME_PATTERN),
|
|
21709
21756
|
// Who asked, and what they may do on this site. Derived from the bearer by the portal, never
|
|
21710
21757
|
// from the model's arguments.
|
|
21711
|
-
viewer: external_exports.object({
|
|
21758
|
+
viewer: external_exports.object({
|
|
21759
|
+
userId: external_exports.string().min(1).max(64),
|
|
21760
|
+
access: external_exports.enum(["read", "write"]),
|
|
21761
|
+
// Optional only while a site worker or app runtime that predates it can still receive this envelope; absent means `full`.
|
|
21762
|
+
appRole: external_exports.enum(APP_ROLES).optional()
|
|
21763
|
+
}).strict(),
|
|
21712
21764
|
// Already validated against the tool's declared inputSchema, with the reserved routing names
|
|
21713
21765
|
// stripped. Every declared input schema has an object root, so this is an object too — and a
|
|
21714
21766
|
// call that names no arguments normalizes to `{}` rather than reaching the handler as undefined.
|
|
@@ -21723,10 +21775,11 @@ var toolSchema2 = external_exports.object({
|
|
|
21723
21775
|
}, "Tool arguments exceed the size cap");
|
|
21724
21776
|
var controlEnvelopeSchema = external_exports.object({
|
|
21725
21777
|
v: external_exports.literal(CONTROL_ENVELOPE_VERSION),
|
|
21726
|
-
kind: external_exports.enum(["noop", "lifecycle", "migration", "schedule", "tool"]),
|
|
21778
|
+
kind: external_exports.enum(["noop", "lifecycle", "deleteRequest", "migration", "schedule", "tool"]),
|
|
21727
21779
|
app: external_exports.object({ slug: external_exports.string().min(1).max(30), version: external_exports.string().min(1).max(32) }).strict(),
|
|
21728
21780
|
install: external_exports.object({ id: external_exports.string().min(1).max(64), ord: external_exports.number().int().min(1), label: external_exports.string().max(100) }).strict().optional(),
|
|
21729
21781
|
lifecycle: lifecycleEventSchema.optional(),
|
|
21782
|
+
deleteRequest: deleteRequestSchema.optional(),
|
|
21730
21783
|
migration: migrationSchema.optional(),
|
|
21731
21784
|
schedule: scheduleSchema2.optional(),
|
|
21732
21785
|
tool: toolSchema2.optional()
|
|
@@ -21734,6 +21787,9 @@ var controlEnvelopeSchema = external_exports.object({
|
|
|
21734
21787
|
if (envelope.kind === "lifecycle" !== (envelope.lifecycle !== void 0)) {
|
|
21735
21788
|
ctx.addIssue({ code: "custom", path: ["lifecycle"], message: "Lifecycle payload must match the control kind" });
|
|
21736
21789
|
}
|
|
21790
|
+
if (envelope.kind === "deleteRequest" !== (envelope.deleteRequest !== void 0)) {
|
|
21791
|
+
ctx.addIssue({ code: "custom", path: ["deleteRequest"], message: "Delete request payload must match the control kind" });
|
|
21792
|
+
}
|
|
21737
21793
|
if (envelope.kind === "migration" !== (envelope.migration !== void 0)) {
|
|
21738
21794
|
ctx.addIssue({ code: "custom", path: ["migration"], message: "Migration payload must match the control kind" });
|
|
21739
21795
|
}
|
|
@@ -21743,8 +21799,8 @@ var controlEnvelopeSchema = external_exports.object({
|
|
|
21743
21799
|
if (envelope.kind === "tool" !== (envelope.tool !== void 0)) {
|
|
21744
21800
|
ctx.addIssue({ code: "custom", path: ["tool"], message: "Tool payload must match the control kind" });
|
|
21745
21801
|
}
|
|
21746
|
-
if (envelope.kind === "lifecycle" && envelope.install === void 0) {
|
|
21747
|
-
ctx.addIssue({ code: "custom", path: ["install"], message:
|
|
21802
|
+
if ((envelope.kind === "lifecycle" || envelope.kind === "deleteRequest") && envelope.install === void 0) {
|
|
21803
|
+
ctx.addIssue({ code: "custom", path: ["install"], message: `${envelope.kind} control needs an install` });
|
|
21748
21804
|
}
|
|
21749
21805
|
if (envelope.kind === "tool" && envelope.install === void 0) {
|
|
21750
21806
|
ctx.addIssue({ code: "custom", path: ["install"], message: "Tool control needs an install" });
|
|
@@ -21783,6 +21839,8 @@ var toolResultSchema = external_exports.object({
|
|
|
21783
21839
|
var controlResponseSchema = external_exports.object({
|
|
21784
21840
|
v: external_exports.literal(CONTROL_ENVELOPE_VERSION),
|
|
21785
21841
|
ok: external_exports.boolean(),
|
|
21842
|
+
// The bundle has no delete request handler, so a retry cannot succeed.
|
|
21843
|
+
skipped: external_exports.literal(true).optional(),
|
|
21786
21844
|
tool: toolResultSchema.optional(),
|
|
21787
21845
|
migration: external_exports.union([
|
|
21788
21846
|
external_exports.object({ done: external_exports.literal(true) }).strict(),
|
|
@@ -21914,7 +21972,12 @@ var MAX_UI_BODY_BYTES = 1024 * 1024;
|
|
|
21914
21972
|
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();
|
|
21915
21973
|
var uiContextSchema = external_exports.object({
|
|
21916
21974
|
version: external_exports.string().min(1).max(32),
|
|
21917
|
-
viewer: external_exports.object({
|
|
21975
|
+
viewer: external_exports.object({
|
|
21976
|
+
userId: external_exports.string().min(1).max(64),
|
|
21977
|
+
access: external_exports.enum(["read", "write"]),
|
|
21978
|
+
// Optional only while a site worker that predates it can still send this header; absent means `full`.
|
|
21979
|
+
appRole: external_exports.enum(APP_ROLES).optional()
|
|
21980
|
+
}).strict(),
|
|
21918
21981
|
install: uiInstallSchema,
|
|
21919
21982
|
installs: external_exports.array(uiInstallSchema).min(1).max(MAX_INSTALLS_PER_APP),
|
|
21920
21983
|
writeTicket: external_exports.string().min(1).max(2048).optional()
|
|
@@ -26275,6 +26338,16 @@ var identifierField2 = external_exports.string().trim().min(1).refine((value) =>
|
|
|
26275
26338
|
description: "Who the request applies to: a canonical Qinto id (`anon_\u2026`, or a `tag_user_id` adopted from EdgeTag), or any identifier from your own systems (email, phone, custom id). Internal `gid_\u2026` ids are rejected.",
|
|
26276
26339
|
example: "crm-user-8412"
|
|
26277
26340
|
});
|
|
26341
|
+
var readIdentifierField = identifierField2.optional().openapi({
|
|
26342
|
+
param: { name: "identifier", in: "query" },
|
|
26343
|
+
description: "User to read: any identifier from your own systems (email, phone, custom id), or a canonical Qinto id (`anon_\u2026`, or a `tag_user_id` adopted from EdgeTag). Omit it to read the user from the first-party cookie instead. Internal `gid_\u2026` ids are rejected.",
|
|
26344
|
+
example: "ada@example.com"
|
|
26345
|
+
});
|
|
26346
|
+
var canonicalIdentifierField = external_exports.string().min(1).optional().openapi({
|
|
26347
|
+
param: { name: "identifier", in: "query" },
|
|
26348
|
+
description: "Canonical Qinto id (`anon_\u2026`) of the user to read. Omit it to resolve the user from the first-party cookie instead. Reading by a custom identifier arrives with authenticated API access.",
|
|
26349
|
+
example: "anon_7b8d1a52-6c1e-4b6f-9a30-2f8a54d1c3e9"
|
|
26350
|
+
});
|
|
26278
26351
|
var consentCategoriesField = external_exports.object({
|
|
26279
26352
|
// Necessary consent cannot be opted out.
|
|
26280
26353
|
necessary: external_exports.literal(true).optional().openapi({
|
|
@@ -27102,6 +27175,9 @@ var linkedAnonsPage = /* @__PURE__ */ __name((sql, afterRowId, limit) => {
|
|
|
27102
27175
|
var linkedAnonsExcept = /* @__PURE__ */ __name((sql, originAnonId) => {
|
|
27103
27176
|
return sql.exec("SELECT key FROM facts WHERE kind = 'anon' AND key != ?", originAnonId).toArray().map((row) => row.key);
|
|
27104
27177
|
}, "linkedAnonsExcept");
|
|
27178
|
+
var allLinkedAnons = /* @__PURE__ */ __name((sql) => {
|
|
27179
|
+
return sql.exec("SELECT key FROM facts WHERE kind = 'anon' ORDER BY rowid").toArray().map((row) => row.key);
|
|
27180
|
+
}, "allLinkedAnons");
|
|
27105
27181
|
|
|
27106
27182
|
// src/id-graph/person.ts
|
|
27107
27183
|
var IDENTITY_CAPABILITY = "identity";
|
|
@@ -27179,6 +27255,124 @@ var identityFields = {
|
|
|
27179
27255
|
key: external_exports.string().min(1)
|
|
27180
27256
|
};
|
|
27181
27257
|
|
|
27258
|
+
// src/apps/currency-rates.ts
|
|
27259
|
+
var CURRENCY_FEED_URL = "https://cdn.shopify.com/s/javascripts/currencies.js";
|
|
27260
|
+
var RATES_TTL_MS = 60 * 60 * 1e3;
|
|
27261
|
+
var RETRY_AFTER_FAILURE_MS = 60 * 1e3;
|
|
27262
|
+
var FEED_TIMEOUT_MS = 5e3;
|
|
27263
|
+
var FEED_EDGE_CACHE_TTL_S = 8 * 60 * 60;
|
|
27264
|
+
var MIN_CURRENCY_CODES = 100;
|
|
27265
|
+
var currencyMemo;
|
|
27266
|
+
var parseRatesLiteral = /* @__PURE__ */ __name((feed) => {
|
|
27267
|
+
const opening = "rates:{";
|
|
27268
|
+
const start = feed.indexOf(opening);
|
|
27269
|
+
if (start === -1) {
|
|
27270
|
+
return null;
|
|
27271
|
+
}
|
|
27272
|
+
const entry = /([A-Z]{3}):(-?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?)([,}])/y;
|
|
27273
|
+
entry.lastIndex = start + opening.length;
|
|
27274
|
+
const rates = {};
|
|
27275
|
+
for (; ; ) {
|
|
27276
|
+
const match2 = entry.exec(feed);
|
|
27277
|
+
if (match2 === null) {
|
|
27278
|
+
return null;
|
|
27279
|
+
}
|
|
27280
|
+
const [, code, value, terminator] = match2;
|
|
27281
|
+
if (Object.hasOwn(rates, code)) {
|
|
27282
|
+
return null;
|
|
27283
|
+
}
|
|
27284
|
+
rates[code] = Number(value);
|
|
27285
|
+
if (terminator === "}") {
|
|
27286
|
+
return rates;
|
|
27287
|
+
}
|
|
27288
|
+
}
|
|
27289
|
+
}, "parseRatesLiteral");
|
|
27290
|
+
var parseCurrencyFeed = /* @__PURE__ */ __name((feed) => {
|
|
27291
|
+
const rates = parseRatesLiteral(feed);
|
|
27292
|
+
if (rates === null) {
|
|
27293
|
+
return { ok: false, reason: "the rates literal does not parse" };
|
|
27294
|
+
}
|
|
27295
|
+
if (rates["USD"] !== 1) {
|
|
27296
|
+
return { ok: false, reason: "USD is not 1" };
|
|
27297
|
+
}
|
|
27298
|
+
const invalid = Object.entries(rates).find(([, rate]) => !Number.isFinite(rate) || rate <= 0);
|
|
27299
|
+
if (invalid !== void 0) {
|
|
27300
|
+
return { ok: false, reason: `${invalid[0]} is not a positive finite rate` };
|
|
27301
|
+
}
|
|
27302
|
+
const count = Object.keys(rates).length;
|
|
27303
|
+
if (count < MIN_CURRENCY_CODES) {
|
|
27304
|
+
return { ok: false, reason: `the feed has ${count} codes` };
|
|
27305
|
+
}
|
|
27306
|
+
return { ok: true, rates };
|
|
27307
|
+
}, "parseCurrencyFeed");
|
|
27308
|
+
var fetchCurrencyRates = /* @__PURE__ */ __name(async () => {
|
|
27309
|
+
return traced("currency.fetch", { attrs: { "http.url": CURRENCY_FEED_URL } }, async () => {
|
|
27310
|
+
let feed;
|
|
27311
|
+
try {
|
|
27312
|
+
const response = await fetch(CURRENCY_FEED_URL, {
|
|
27313
|
+
signal: AbortSignal.timeout(FEED_TIMEOUT_MS),
|
|
27314
|
+
cf: { cacheTtlByStatus: { "200-299": FEED_EDGE_CACHE_TTL_S, "300-599": 0 } }
|
|
27315
|
+
});
|
|
27316
|
+
if (!response.ok) {
|
|
27317
|
+
addSpanEvent("currency fetch failed", { level: "error", attrs: { status: response.status } });
|
|
27318
|
+
return null;
|
|
27319
|
+
}
|
|
27320
|
+
feed = await response.text();
|
|
27321
|
+
} catch (error61) {
|
|
27322
|
+
addSpanEvent("currency fetch failed", { level: "error", attrs: { error: String(error61) } });
|
|
27323
|
+
return null;
|
|
27324
|
+
}
|
|
27325
|
+
const verdict = parseCurrencyFeed(feed);
|
|
27326
|
+
if (!verdict.ok) {
|
|
27327
|
+
addSpanEvent("currency feed rejected", { level: "error", attrs: { reason: verdict.reason } });
|
|
27328
|
+
return null;
|
|
27329
|
+
}
|
|
27330
|
+
return verdict.rates;
|
|
27331
|
+
});
|
|
27332
|
+
}, "fetchCurrencyRates");
|
|
27333
|
+
var refreshCurrencyRates = /* @__PURE__ */ __name(async () => {
|
|
27334
|
+
const rates = await fetchCurrencyRates();
|
|
27335
|
+
const now = Date.now();
|
|
27336
|
+
if (rates === null) {
|
|
27337
|
+
const current2 = currencyMemo?.current ?? null;
|
|
27338
|
+
currencyMemo = { current: current2, nextAttemptAt: now + RETRY_AFTER_FAILURE_MS };
|
|
27339
|
+
return current2;
|
|
27340
|
+
}
|
|
27341
|
+
const current = { rates, fetchedAt: now };
|
|
27342
|
+
currencyMemo = { current, nextAttemptAt: now + RATES_TTL_MS };
|
|
27343
|
+
return current;
|
|
27344
|
+
}, "refreshCurrencyRates");
|
|
27345
|
+
var currentCurrencyRates = /* @__PURE__ */ __name(async (ctx) => {
|
|
27346
|
+
const now = Date.now();
|
|
27347
|
+
if (currencyMemo !== void 0 && now < currencyMemo.nextAttemptAt) {
|
|
27348
|
+
return currencyMemo.current;
|
|
27349
|
+
}
|
|
27350
|
+
const stale = currencyMemo?.current ?? null;
|
|
27351
|
+
if (stale === null) {
|
|
27352
|
+
return refreshCurrencyRates();
|
|
27353
|
+
}
|
|
27354
|
+
currencyMemo = { current: stale, nextAttemptAt: now + RETRY_AFTER_FAILURE_MS };
|
|
27355
|
+
ctx.waitUntil(
|
|
27356
|
+
runInvocation({ name: "currency.refresh", trace: currentTraceContext() }, () => {
|
|
27357
|
+
return refreshCurrencyRates();
|
|
27358
|
+
})
|
|
27359
|
+
);
|
|
27360
|
+
return stale;
|
|
27361
|
+
}, "currentCurrencyRates");
|
|
27362
|
+
|
|
27363
|
+
// src/apps/broker-currency.ts
|
|
27364
|
+
var currencyRatesBody = external_exports.object({
|
|
27365
|
+
...identityFields,
|
|
27366
|
+
kind: external_exports.literal("currencyRates")
|
|
27367
|
+
}).strict();
|
|
27368
|
+
var handleCurrencyRates = /* @__PURE__ */ __name(async (ctx) => {
|
|
27369
|
+
const current = await currentCurrencyRates(ctx);
|
|
27370
|
+
if (current === null) {
|
|
27371
|
+
return Response.json({ error: "currency_rates_unavailable" }, { status: 503 });
|
|
27372
|
+
}
|
|
27373
|
+
return Response.json(current);
|
|
27374
|
+
}, "handleCurrencyRates");
|
|
27375
|
+
|
|
27182
27376
|
// src/apps/emit-payload.ts
|
|
27183
27377
|
var emitPayloadSchema = external_exports.object({
|
|
27184
27378
|
event: external_exports.string().min(1),
|
|
@@ -27981,6 +28175,7 @@ var brokerBody = external_exports.discriminatedUnion("kind", [
|
|
|
27981
28175
|
contextWriteBody,
|
|
27982
28176
|
factsReadBody,
|
|
27983
28177
|
identityReadBody,
|
|
28178
|
+
currencyRatesBody,
|
|
27984
28179
|
trackBody
|
|
27985
28180
|
]);
|
|
27986
28181
|
var BODY_ENVELOPE_ALLOWANCE_BYTES = 2 * 1024;
|
|
@@ -28316,7 +28511,7 @@ var handleActive = /* @__PURE__ */ __name(async (env, body) => {
|
|
|
28316
28511
|
}
|
|
28317
28512
|
return Response.json({ active: true });
|
|
28318
28513
|
}, "handleActive");
|
|
28319
|
-
var handleBroker = /* @__PURE__ */ __name(async (request, env) => {
|
|
28514
|
+
var handleBroker = /* @__PURE__ */ __name(async (request, env, ctx) => {
|
|
28320
28515
|
const bytes = await readBoundedBody(request, MAX_BODY_BYTES);
|
|
28321
28516
|
if (bytes === null) {
|
|
28322
28517
|
return Response.json({ error: "too_large" }, { status: 413 });
|
|
@@ -28368,6 +28563,9 @@ var handleBroker = /* @__PURE__ */ __name(async (request, env) => {
|
|
|
28368
28563
|
case "identityRead": {
|
|
28369
28564
|
return handleIdentityRead(env, body);
|
|
28370
28565
|
}
|
|
28566
|
+
case "currencyRates": {
|
|
28567
|
+
return handleCurrencyRates(ctx);
|
|
28568
|
+
}
|
|
28371
28569
|
case "track": {
|
|
28372
28570
|
return handleTrack(env, body);
|
|
28373
28571
|
}
|
|
@@ -28378,7 +28576,7 @@ var dispatchGated = /* @__PURE__ */ __name((c) => {
|
|
|
28378
28576
|
if (c.env.DEPLOY_TARGET !== "dispatch") {
|
|
28379
28577
|
return c.notFound();
|
|
28380
28578
|
}
|
|
28381
|
-
return handleBroker(c.req.raw, c.env);
|
|
28579
|
+
return handleBroker(c.req.raw, c.env, c.executionCtx);
|
|
28382
28580
|
}, "dispatchGated");
|
|
28383
28581
|
for (const path of BROKER_PATHS) {
|
|
28384
28582
|
brokerRoutes.post(path, dispatchGated);
|
|
@@ -28390,7 +28588,7 @@ var Broker = class extends WorkerEntrypoint {
|
|
|
28390
28588
|
fetch(request) {
|
|
28391
28589
|
const trace = parseTraceContext(request.headers.get(TRACE_HEADER));
|
|
28392
28590
|
return runInvocation({ name: "broker", trace }, () => {
|
|
28393
|
-
return handleBroker(request, this.env);
|
|
28591
|
+
return handleBroker(request, this.env, this.ctx);
|
|
28394
28592
|
});
|
|
28395
28593
|
}
|
|
28396
28594
|
};
|
|
@@ -29391,20 +29589,17 @@ var matchEndpoint = /* @__PURE__ */ __name((app2, path, method) => {
|
|
|
29391
29589
|
if (!isHttpMethod(method)) {
|
|
29392
29590
|
return void 0;
|
|
29393
29591
|
}
|
|
29394
|
-
const serving = app2.endpoints.filter((endpoint) => {
|
|
29395
|
-
return endpoint.methods.includes(method);
|
|
29396
|
-
});
|
|
29397
29592
|
const matched = matchEndpointPath(
|
|
29398
|
-
|
|
29593
|
+
app2.endpoints.map((endpoint) => endpoint.path),
|
|
29399
29594
|
path
|
|
29400
29595
|
);
|
|
29401
29596
|
if (matched === void 0) {
|
|
29402
29597
|
return void 0;
|
|
29403
29598
|
}
|
|
29404
|
-
const declared =
|
|
29599
|
+
const declared = app2.endpoints.find((endpoint) => {
|
|
29405
29600
|
return endpoint.path === matched.route;
|
|
29406
29601
|
});
|
|
29407
|
-
if (!["public", "token", "visitor"].includes(declared.auth) || declared.mode !== "webhook" && declared.mode !== "api") {
|
|
29602
|
+
if (!declared.methods.includes(method) || !["public", "token", "visitor"].includes(declared.auth) || declared.mode !== "webhook" && declared.mode !== "api") {
|
|
29408
29603
|
return void 0;
|
|
29409
29604
|
}
|
|
29410
29605
|
return {
|
|
@@ -29738,7 +29933,9 @@ var claimsSchema = external_exports.object({
|
|
|
29738
29933
|
appId: external_exports.string().min(1),
|
|
29739
29934
|
appInstallId: external_exports.string().min(1),
|
|
29740
29935
|
appVersionId: external_exports.string().min(1),
|
|
29741
|
-
access: external_exports.enum(["read", "write"])
|
|
29936
|
+
access: external_exports.enum(["read", "write"]),
|
|
29937
|
+
// Absent means `full`; see the mint below.
|
|
29938
|
+
appRole: external_exports.enum(APP_ROLES).optional()
|
|
29742
29939
|
}).strict();
|
|
29743
29940
|
var appUiAudience = /* @__PURE__ */ __name((slug) => {
|
|
29744
29941
|
return `app-ui:${slug}`;
|
|
@@ -29907,7 +30104,8 @@ var forwardToApp2 = /* @__PURE__ */ __name(async (c, input2) => {
|
|
|
29907
30104
|
"app.install_ord": install.ord,
|
|
29908
30105
|
"app.install_label": install.label,
|
|
29909
30106
|
"app.viewer_id": viewer.userId,
|
|
29910
|
-
"app.viewer_access": viewer.access
|
|
30107
|
+
"app.viewer_access": viewer.access,
|
|
30108
|
+
"app.viewer_app_role": viewer.appRole
|
|
29911
30109
|
}
|
|
29912
30110
|
},
|
|
29913
30111
|
() => {
|
|
@@ -29953,7 +30151,13 @@ var authorizeUi = /* @__PURE__ */ __name(async (c, slug, origin) => {
|
|
|
29953
30151
|
appId: verdict.claims.appId,
|
|
29954
30152
|
appVersionId: verdict.claims.appVersionId
|
|
29955
30153
|
}) : void 0;
|
|
29956
|
-
return {
|
|
30154
|
+
return {
|
|
30155
|
+
app: app2,
|
|
30156
|
+
install,
|
|
30157
|
+
// The portal mints `full` as no claim, so a site worker that predates the claim still accepts the token.
|
|
30158
|
+
viewer: { userId: verdict.claims.userId, access: verdict.claims.access, appRole: verdict.claims.appRole ?? "full" },
|
|
30159
|
+
writeTicket
|
|
30160
|
+
};
|
|
29957
30161
|
}, "authorizeUi");
|
|
29958
30162
|
var handleAppUi = /* @__PURE__ */ __name(async (c, slug) => {
|
|
29959
30163
|
const host = c.env.APP_UI_HOST;
|
|
@@ -30001,7 +30205,7 @@ appUiRoutes.all("/apps/:slug/ui/*", (c) => {
|
|
|
30001
30205
|
var apiInfo = {
|
|
30002
30206
|
title: "Qinto Edge API",
|
|
30003
30207
|
version: "0.1.0",
|
|
30004
|
-
description:
|
|
30208
|
+
description: 'HTTP API for recording user activity on the Qinto platform: track events, attach identity traits, link custom identifiers, and record or read a user\u2019s consent and properties.\n\nEach request applies to one user. Writes address that user by any identifier of your own (email, phone, custom id) or a canonical Qinto id (`anon_\u2026`); the same identifier always reaches the same user. Reads take a canonical id or the first-party cookie; `GET /v1/session` also accepts your own.\n\nEvery `/v1` operation takes a worker access token as `Authorization: Bearer <token>`, scoped to the endpoints it may call; generate one from the Qinto platform\u2019s worker access tokens page. Errors use one JSON envelope: `error` is a machine-readable slug to switch on, and `details` optionally carries field-level validation errors. The health check is unauthenticated and answers `{ "status": "success" }`.'
|
|
30005
30209
|
};
|
|
30006
30210
|
var apiTags = [
|
|
30007
30211
|
{
|
|
@@ -30049,7 +30253,7 @@ var ERRORS = {
|
|
|
30049
30253
|
},
|
|
30050
30254
|
internal: {
|
|
30051
30255
|
status: 500,
|
|
30052
|
-
description: "Unexpected server error. Nothing is guaranteed to have been persisted; reads are safe to retry, and conversions dedupe on their order id, but
|
|
30256
|
+
description: "Unexpected server error. Nothing is guaranteed to have been persisted; reads are safe to retry, and conversions dedupe on their order id, but retrying an ingestion request can deliver the event to the site\u2019s apps again."
|
|
30053
30257
|
}
|
|
30054
30258
|
};
|
|
30055
30259
|
var DETAILS_SLUGS = /* @__PURE__ */ new Set(["invalid_params", "invalid_body"]);
|
|
@@ -30306,6 +30510,140 @@ var health = createApiRouter().openapi(definition, (c) => {
|
|
|
30306
30510
|
return respond(c, healthResponse, { status: "success" });
|
|
30307
30511
|
});
|
|
30308
30512
|
|
|
30513
|
+
// src/id-graph/delete-request.ts
|
|
30514
|
+
var readAlias = /* @__PURE__ */ __name((env, key) => {
|
|
30515
|
+
return traced("rpc alias.get", {}, () => {
|
|
30516
|
+
return aliasStub(env, key).get(currentTraceContext());
|
|
30517
|
+
});
|
|
30518
|
+
}, "readAlias");
|
|
30519
|
+
var readAnon = /* @__PURE__ */ __name((env, anonId) => {
|
|
30520
|
+
return traced("rpc anon.erasable", { attrs: { "anon.id": anonId } }, () => {
|
|
30521
|
+
return anonStub(env, anonId).erasable(currentTraceContext());
|
|
30522
|
+
});
|
|
30523
|
+
}, "readAnon");
|
|
30524
|
+
var readPerson = /* @__PURE__ */ __name((env, globalId) => {
|
|
30525
|
+
return traced("rpc global.erasable", { attrs: { "global.id": globalId } }, () => {
|
|
30526
|
+
return globalStub(env, globalId).erasable(currentTraceContext());
|
|
30527
|
+
});
|
|
30528
|
+
}, "readPerson");
|
|
30529
|
+
var typedOf = /* @__PURE__ */ __name(async (env, value) => {
|
|
30530
|
+
if (isCanonicalId(value)) {
|
|
30531
|
+
return { inputType: "anon", kind: "anon", name: value, alias: null, personId: null, anonId: value };
|
|
30532
|
+
}
|
|
30533
|
+
if (isGlobalId(value)) {
|
|
30534
|
+
return { inputType: "person", kind: "global", name: value, alias: null, personId: value, anonId: null };
|
|
30535
|
+
}
|
|
30536
|
+
const identifier = classifyIdentifier(value);
|
|
30537
|
+
const key = await aliasKey(identifier);
|
|
30538
|
+
const assignment = await readAlias(env, key);
|
|
30539
|
+
return {
|
|
30540
|
+
inputType: identifier.type,
|
|
30541
|
+
kind: "alias",
|
|
30542
|
+
name: key,
|
|
30543
|
+
alias: assignment === null ? null : identifier,
|
|
30544
|
+
personId: assignment?.globalId ?? null,
|
|
30545
|
+
anonId: assignment?.anonId ?? null
|
|
30546
|
+
};
|
|
30547
|
+
}, "typedOf");
|
|
30548
|
+
var uniqueIdentifiers = /* @__PURE__ */ __name((identifiers) => {
|
|
30549
|
+
const byKey = new Map(identifiers.map((identifier) => [`${identifier.type}:${identifier.value}`, identifier]));
|
|
30550
|
+
return [...byKey.values()];
|
|
30551
|
+
}, "uniqueIdentifiers");
|
|
30552
|
+
var reachFrom = /* @__PURE__ */ __name(async (env, typed) => {
|
|
30553
|
+
const anons = /* @__PURE__ */ new Map();
|
|
30554
|
+
let personId = typed.personId;
|
|
30555
|
+
if (personId === null && typed.anonId !== null) {
|
|
30556
|
+
const anon = await readAnon(env, typed.anonId);
|
|
30557
|
+
if (anon !== null) {
|
|
30558
|
+
anons.set(typed.anonId, anon);
|
|
30559
|
+
personId = anon.globalId;
|
|
30560
|
+
}
|
|
30561
|
+
}
|
|
30562
|
+
const person2 = personId === null ? null : await readPerson(env, personId);
|
|
30563
|
+
for (const anonId of person2?.anonIds ?? []) {
|
|
30564
|
+
const anon = anons.has(anonId) ? null : await readAnon(env, anonId);
|
|
30565
|
+
if (anon !== null) {
|
|
30566
|
+
anons.set(anonId, anon);
|
|
30567
|
+
}
|
|
30568
|
+
}
|
|
30569
|
+
return { personId, person: person2, anons };
|
|
30570
|
+
}, "reachFrom");
|
|
30571
|
+
var ownedAliases = /* @__PURE__ */ __name(async (env, typed, reached) => {
|
|
30572
|
+
const aliasKeys = [];
|
|
30573
|
+
const identifiers = [];
|
|
30574
|
+
const held = [...reached.person?.identifiers ?? [], ...[...reached.anons.values()].flatMap((anon) => anon.identifiers)];
|
|
30575
|
+
for (const identifier of uniqueIdentifiers(held)) {
|
|
30576
|
+
const key = await aliasKey(identifier);
|
|
30577
|
+
const assignment = key === typed.name ? null : await readAlias(env, key);
|
|
30578
|
+
const owner = assignment?.globalId ?? null;
|
|
30579
|
+
const owned = assignment === null || (owner === null ? [...reached.anons.keys()].some((anonId) => anonId === assignment.anonId) : owner === reached.personId);
|
|
30580
|
+
if (key !== typed.name && owned) {
|
|
30581
|
+
identifiers.push(identifier);
|
|
30582
|
+
if (assignment !== null) {
|
|
30583
|
+
aliasKeys.push(key);
|
|
30584
|
+
}
|
|
30585
|
+
}
|
|
30586
|
+
}
|
|
30587
|
+
if (typed.alias !== null) {
|
|
30588
|
+
aliasKeys.push(typed.name);
|
|
30589
|
+
identifiers.push(typed.alias);
|
|
30590
|
+
}
|
|
30591
|
+
return { aliasKeys, identifiers };
|
|
30592
|
+
}, "ownedAliases");
|
|
30593
|
+
var planOf = /* @__PURE__ */ __name(async (env, typed) => {
|
|
30594
|
+
const reached = await reachFrom(env, typed);
|
|
30595
|
+
const { aliasKeys, identifiers } = await ownedAliases(env, typed, reached);
|
|
30596
|
+
return {
|
|
30597
|
+
inputType: typed.inputType,
|
|
30598
|
+
personId: reached.person === null ? null : reached.personId,
|
|
30599
|
+
anonIds: [...reached.anons.keys()],
|
|
30600
|
+
aliasKeys,
|
|
30601
|
+
identifiers
|
|
30602
|
+
};
|
|
30603
|
+
}, "planOf");
|
|
30604
|
+
var planDeleteRequest = /* @__PURE__ */ __name(async (env, value) => {
|
|
30605
|
+
return planOf(env, await typedOf(env, value));
|
|
30606
|
+
}, "planDeleteRequest");
|
|
30607
|
+
var ERASERS = Object.freeze({
|
|
30608
|
+
alias: /* @__PURE__ */ __name((env, name) => {
|
|
30609
|
+
return traced("rpc alias.erase", {}, () => {
|
|
30610
|
+
return aliasStub(env, name).erase(currentTraceContext());
|
|
30611
|
+
});
|
|
30612
|
+
}, "alias"),
|
|
30613
|
+
anon: /* @__PURE__ */ __name((env, name) => {
|
|
30614
|
+
return traced("rpc anon.erase", { attrs: { "anon.id": name } }, () => {
|
|
30615
|
+
return anonStub(env, name).erase(currentTraceContext());
|
|
30616
|
+
});
|
|
30617
|
+
}, "anon"),
|
|
30618
|
+
global: /* @__PURE__ */ __name((env, name) => {
|
|
30619
|
+
return traced("rpc global.erase", { attrs: { "global.id": name } }, () => {
|
|
30620
|
+
return globalStub(env, name).erase(currentTraceContext());
|
|
30621
|
+
});
|
|
30622
|
+
}, "global")
|
|
30623
|
+
});
|
|
30624
|
+
var eraseOrder = /* @__PURE__ */ __name((plan, typed) => {
|
|
30625
|
+
const steps = [
|
|
30626
|
+
...plan.aliasKeys.map((name) => ({ kind: "alias", name })),
|
|
30627
|
+
...plan.anonIds.map((name) => ({ kind: "anon", name })),
|
|
30628
|
+
...plan.personId === null ? [] : [{ kind: "global", name: plan.personId }]
|
|
30629
|
+
];
|
|
30630
|
+
const isTyped = /* @__PURE__ */ __name((step) => {
|
|
30631
|
+
return step.kind === typed.kind && step.name === typed.name;
|
|
30632
|
+
}, "isTyped");
|
|
30633
|
+
return [...steps.filter((step) => !isTyped(step)), ...steps.filter(isTyped)];
|
|
30634
|
+
}, "eraseOrder");
|
|
30635
|
+
var runDeleteRequest = /* @__PURE__ */ __name(async (env, value) => {
|
|
30636
|
+
const typed = await typedOf(env, value);
|
|
30637
|
+
const plan = await planOf(env, typed);
|
|
30638
|
+
const erased = { alias: 0, anon: 0, global: 0 };
|
|
30639
|
+
for (const step of eraseOrder(plan, typed)) {
|
|
30640
|
+
if (await ERASERS[step.kind](env, step.name)) {
|
|
30641
|
+
erased[step.kind] += 1;
|
|
30642
|
+
}
|
|
30643
|
+
}
|
|
30644
|
+
return { inputType: plan.inputType, anons: erased.anon, aliases: erased.alias, persons: erased.global };
|
|
30645
|
+
}, "runDeleteRequest");
|
|
30646
|
+
|
|
30309
30647
|
// src/id-graph/inspect.ts
|
|
30310
30648
|
var MAX_TABLE_PAGE = 100;
|
|
30311
30649
|
var INTERNAL_TABLE = /^(sqlite_|_cf_)/;
|
|
@@ -30430,6 +30768,7 @@ var rowsBody = inspectBody.extend({
|
|
|
30430
30768
|
offset: external_exports.number().int().min(0).default(0),
|
|
30431
30769
|
limit: external_exports.number().int().min(1).max(MAX_TABLE_PAGE).default(MAX_TABLE_PAGE)
|
|
30432
30770
|
});
|
|
30771
|
+
var valueBody = external_exports.object({ value: external_exports.string().min(1) });
|
|
30433
30772
|
var readBody = /* @__PURE__ */ __name(async (c, schema) => {
|
|
30434
30773
|
const parsed = schema.safeParse(await c.req.json().catch(() => null));
|
|
30435
30774
|
return parsed.success ? parsed.data : null;
|
|
@@ -30457,6 +30796,20 @@ idGraphInspect.post("/internal/id-graph/rows", async (c) => {
|
|
|
30457
30796
|
}
|
|
30458
30797
|
return c.json({ page: await inspectObjectTable(c.env, body) });
|
|
30459
30798
|
});
|
|
30799
|
+
idGraphInspect.post("/internal/id-graph/plan", async (c) => {
|
|
30800
|
+
const body = await readBody(c, valueBody);
|
|
30801
|
+
if (body === null) {
|
|
30802
|
+
return c.json({ error: "invalid_body" }, 400);
|
|
30803
|
+
}
|
|
30804
|
+
return c.json(await planDeleteRequest(c.env, body.value));
|
|
30805
|
+
});
|
|
30806
|
+
idGraphInspect.post("/internal/id-graph/delete", async (c) => {
|
|
30807
|
+
const body = await readBody(c, valueBody);
|
|
30808
|
+
if (body === null) {
|
|
30809
|
+
return c.json({ error: "invalid_body" }, 400);
|
|
30810
|
+
}
|
|
30811
|
+
return c.json(await runDeleteRequest(c.env, body.value));
|
|
30812
|
+
});
|
|
30460
30813
|
|
|
30461
30814
|
// src/paths/open.ts
|
|
30462
30815
|
var withSetCookie = /* @__PURE__ */ __name((res, cookies) => {
|
|
@@ -30544,14 +30897,14 @@ var open2 = /* @__PURE__ */ __name(async (c) => {
|
|
|
30544
30897
|
|
|
30545
30898
|
// browser-artifact/meta.json
|
|
30546
30899
|
var meta_default = {
|
|
30547
|
-
version: "0.1.
|
|
30548
|
-
gitSha: "
|
|
30549
|
-
builtAt: "2026-09-
|
|
30550
|
-
hash: "
|
|
30900
|
+
version: "0.1.11",
|
|
30901
|
+
gitSha: "bb87bca6d3b96627f24bc0c29675f2f19680ce60",
|
|
30902
|
+
builtAt: "2026-09-28T04:35:01.606Z",
|
|
30903
|
+
hash: "e32dfe041826d6daaa3682d1f5cac137ffcc6a24f55bfef1a936f7e3ef4cde12"
|
|
30551
30904
|
};
|
|
30552
30905
|
|
|
30553
30906
|
// src/paths/q-js.ts
|
|
30554
|
-
import qScript from "./
|
|
30907
|
+
import qScript from "./944c722e73fe144b1067da970f87716a835c697e-q.js.txt";
|
|
30555
30908
|
|
|
30556
30909
|
// src/apps-q.ts
|
|
30557
30910
|
import appsQScript from "./apps-q.json";
|
|
@@ -30797,11 +31150,7 @@ var consent = createApiRouter().openapi(definition4, async (c) => {
|
|
|
30797
31150
|
|
|
30798
31151
|
// src/paths/v1/consent-state.ts
|
|
30799
31152
|
var consentStateQuery = external_exports.object({
|
|
30800
|
-
identifier:
|
|
30801
|
-
param: { name: "identifier", in: "query" },
|
|
30802
|
-
description: "Canonical Qinto id (`anon_\u2026`) of the user to read. Omit it to resolve the user from the first-party cookie instead. Reading by a custom identifier arrives with authenticated API access.",
|
|
30803
|
-
example: "anon_7b8d1a52-6c1e-4b6f-9a30-2f8a54d1c3e9"
|
|
30804
|
-
})
|
|
31153
|
+
identifier: canonicalIdentifierField
|
|
30805
31154
|
});
|
|
30806
31155
|
var consentStateResponse = external_exports.object({
|
|
30807
31156
|
categories: external_exports.object({
|
|
@@ -30828,7 +31177,7 @@ var definition5 = createRoute({
|
|
|
30828
31177
|
summary: "Get user consent",
|
|
30829
31178
|
description: operationDescription({
|
|
30830
31179
|
intro: "Returns the consent state currently stored for the user.",
|
|
30831
|
-
howItWorks: "A request that carries neither
|
|
31180
|
+
howItWorks: "A request that carries neither an `identifier` nor the cookie \u2014 or an `identifier` that is not canonical \u2014 fails with `invalid_params`. A choice saved on another session reaches this one separately, so a read taken right after a write elsewhere can return the previous value, and a session with no further activity can stay behind indefinitely. Read back from the session that made the change when you need the value you just wrote."
|
|
30832
31181
|
}),
|
|
30833
31182
|
request: { query: consentStateQuery },
|
|
30834
31183
|
responses: {
|
|
@@ -30899,11 +31248,7 @@ var identify = createApiRouter().openapi(definition6, async (c) => {
|
|
|
30899
31248
|
|
|
30900
31249
|
// src/paths/v1/person.ts
|
|
30901
31250
|
var personQuery = external_exports.object({
|
|
30902
|
-
identifier:
|
|
30903
|
-
param: { name: "identifier", in: "query" },
|
|
30904
|
-
description: "User to read: any identifier from your own systems (email, phone, custom id), or a canonical Qinto id (`anon_\u2026`, or a `tag_user_id` adopted from EdgeTag). Omit it to read the user from the first-party cookie instead. Internal `gid_\u2026` ids are rejected.",
|
|
30905
|
-
example: "ada@example.com"
|
|
30906
|
-
}),
|
|
31251
|
+
identifier: readIdentifierField,
|
|
30907
31252
|
cursor: external_exports.string().regex(/^\d+$/).optional().openapi({
|
|
30908
31253
|
param: { name: "cursor", in: "query" },
|
|
30909
31254
|
description: "The `nextCursor` of the previous page. Omit it for the first page.",
|
|
@@ -30956,11 +31301,7 @@ var person = createApiRouter().openapi(definition7, async (c) => {
|
|
|
30956
31301
|
|
|
30957
31302
|
// src/paths/v1/properties.ts
|
|
30958
31303
|
var propertiesQuery = external_exports.object({
|
|
30959
|
-
identifier:
|
|
30960
|
-
param: { name: "identifier", in: "query" },
|
|
30961
|
-
description: "Canonical Qinto id (`anon_\u2026`) of the user to read. Omit it to resolve the user from the first-party cookie instead. Reading by a custom identifier arrives with authenticated API access.",
|
|
30962
|
-
example: "anon_7b8d1a52-6c1e-4b6f-9a30-2f8a54d1c3e9"
|
|
30963
|
-
})
|
|
31304
|
+
identifier: canonicalIdentifierField
|
|
30964
31305
|
});
|
|
30965
31306
|
var propertiesResponse = external_exports.record(external_exports.string(), external_exports.unknown()).openapi("UserProperties", { description: "Every property currently stored for the user, keyed by property name." });
|
|
30966
31307
|
var definition8 = createRoute({
|
|
@@ -30971,10 +31312,7 @@ var definition8 = createRoute({
|
|
|
30971
31312
|
security: [{ QintoWorkerAccessToken: [] }],
|
|
30972
31313
|
middleware: [requireScope(SCOPE.propertiesRead)],
|
|
30973
31314
|
summary: "Get user properties",
|
|
30974
|
-
description: operationDescription({
|
|
30975
|
-
intro: "Returns every property currently stored for the user.",
|
|
30976
|
-
howItWorks: "A request that carries neither a `identifier` nor the cookie \u2014 or a `identifier` that is not canonical \u2014 fails with `invalid_params`."
|
|
30977
|
-
}),
|
|
31315
|
+
description: operationDescription({ intro: "Returns every property currently stored for the user." }),
|
|
30978
31316
|
request: { query: propertiesQuery },
|
|
30979
31317
|
responses: {
|
|
30980
31318
|
200: {
|
|
@@ -30997,11 +31335,7 @@ var properties = createApiRouter().openapi(definition8, async (c) => {
|
|
|
30997
31335
|
|
|
30998
31336
|
// src/paths/v1/session.ts
|
|
30999
31337
|
var sessionQuery = external_exports.object({
|
|
31000
|
-
identifier:
|
|
31001
|
-
param: { name: "identifier", in: "query" },
|
|
31002
|
-
description: "User to read: any identifier from your own systems (email, phone, custom id), or a canonical Qinto id (`anon_\u2026`). Omit it to read the user from the first-party cookie instead. Internal `gid_\u2026` ids are rejected.",
|
|
31003
|
-
example: "ada@example.com"
|
|
31004
|
-
})
|
|
31338
|
+
identifier: readIdentifierField
|
|
31005
31339
|
});
|
|
31006
31340
|
var sessionResponse = external_exports.object({
|
|
31007
31341
|
anonId: external_exports.string().nullable().openapi({
|
|
@@ -31478,6 +31812,16 @@ var AliasDO = class extends DurableObject {
|
|
|
31478
31812
|
return { exists: true, objectId: this.ctx.id.toString(), assignment, databaseSize: this.ctx.storage.sql.databaseSize };
|
|
31479
31813
|
});
|
|
31480
31814
|
}
|
|
31815
|
+
async erase(trace) {
|
|
31816
|
+
return runInvocation({ name: "do.alias.erase", trace }, async () => {
|
|
31817
|
+
if (await this.record() === void 0) {
|
|
31818
|
+
return false;
|
|
31819
|
+
}
|
|
31820
|
+
await this.ctx.storage.deleteAll();
|
|
31821
|
+
this.assignment = void 0;
|
|
31822
|
+
return true;
|
|
31823
|
+
});
|
|
31824
|
+
}
|
|
31481
31825
|
async record() {
|
|
31482
31826
|
if (!this.loaded) {
|
|
31483
31827
|
this.assignment = await this.ctx.storage.get(ASSIGNMENT_KEY);
|
|
@@ -32254,6 +32598,19 @@ var relink = /* @__PURE__ */ __name(async (storage, env, anonId, globalId) => {
|
|
|
32254
32598
|
await storage.setAlarm(cleanupTarget(storage, Date.now(), lastActivityAt(storage.kv) ?? Date.now()));
|
|
32255
32599
|
}
|
|
32256
32600
|
}, "relink");
|
|
32601
|
+
var unlinkFromPerson = /* @__PURE__ */ __name((env, anonId, globalId) => {
|
|
32602
|
+
return traced("rpc global.unlink_anon", { attrs: { "global.id": globalId } }, () => {
|
|
32603
|
+
return globalStub(env, globalId).unlinkAnon(anonId, currentTraceContext());
|
|
32604
|
+
});
|
|
32605
|
+
}, "unlinkFromPerson");
|
|
32606
|
+
var eraseAnon = /* @__PURE__ */ __name(async (storage, env) => {
|
|
32607
|
+
const anonId = storedAnonId(storage.kv);
|
|
32608
|
+
const globalId = linkedGlobalId(storage.kv);
|
|
32609
|
+
if (anonId !== null && globalId !== null) {
|
|
32610
|
+
await unlinkFromPerson(env, anonId, globalId);
|
|
32611
|
+
}
|
|
32612
|
+
await storage.deleteAll();
|
|
32613
|
+
}, "eraseAnon");
|
|
32257
32614
|
var expireAnon = /* @__PURE__ */ __name(async (storage, env, staleActivity) => {
|
|
32258
32615
|
if (await standsDown(storage, staleActivity)) {
|
|
32259
32616
|
return false;
|
|
@@ -32262,9 +32619,7 @@ var expireAnon = /* @__PURE__ */ __name(async (storage, env, staleActivity) => {
|
|
|
32262
32619
|
const globalId = linkedGlobalId(storage.kv);
|
|
32263
32620
|
if (anonId !== null && globalId !== null) {
|
|
32264
32621
|
try {
|
|
32265
|
-
await
|
|
32266
|
-
return globalStub(env, globalId).unlinkAnon(anonId, currentTraceContext());
|
|
32267
|
-
});
|
|
32622
|
+
await unlinkFromPerson(env, anonId, globalId);
|
|
32268
32623
|
} catch (error61) {
|
|
32269
32624
|
addSpanEvent("expiry unlink failed", { level: "warn", attrs: { "anon.id": anonId, "global.id": globalId, error: String(error61) } });
|
|
32270
32625
|
await storage.setAlarm(Date.now() + CLEANUP_INTERVAL_MS);
|
|
@@ -32770,7 +33125,7 @@ var AnonDO = class extends DurableObject2 {
|
|
|
32770
33125
|
wiped = false;
|
|
32771
33126
|
// True while processAction holds ingestQueue, including the transform await.
|
|
32772
33127
|
ingestTurn = false;
|
|
32773
|
-
// Warm write-through mirrors reset together after expiry.
|
|
33128
|
+
// Warm write-through mirrors reset together after an expiry or an erase.
|
|
32774
33129
|
kvCache;
|
|
32775
33130
|
cachedStorage;
|
|
32776
33131
|
identifiersMirror = identifiersCache();
|
|
@@ -33409,6 +33764,17 @@ var AnonDO = class extends DurableObject2 {
|
|
|
33409
33764
|
});
|
|
33410
33765
|
});
|
|
33411
33766
|
}
|
|
33767
|
+
erasable(trace) {
|
|
33768
|
+
return runInvocation({ name: "do.anon.erasable", trace }, () => {
|
|
33769
|
+
return this.enqueue(async () => {
|
|
33770
|
+
if (await this.discardIfUnused()) {
|
|
33771
|
+
return null;
|
|
33772
|
+
}
|
|
33773
|
+
const identifiers = allIdentifiers(this.sql, this.identifiersMirror).map(({ type, value }) => ({ type, value }));
|
|
33774
|
+
return { globalId: linkedGlobalId(this.kv), identifiers };
|
|
33775
|
+
});
|
|
33776
|
+
});
|
|
33777
|
+
}
|
|
33412
33778
|
inspect(trace) {
|
|
33413
33779
|
return runInvocation({ name: "do.anon.inspect", trace }, () => {
|
|
33414
33780
|
return this.enqueue(async () => {
|
|
@@ -33429,6 +33795,25 @@ var AnonDO = class extends DurableObject2 {
|
|
|
33429
33795
|
});
|
|
33430
33796
|
});
|
|
33431
33797
|
}
|
|
33798
|
+
erase(trace) {
|
|
33799
|
+
return runInvocation({ name: "do.anon.erase", trace }, () => {
|
|
33800
|
+
return this.enqueue(async () => {
|
|
33801
|
+
if (await this.discardIfUnused()) {
|
|
33802
|
+
return false;
|
|
33803
|
+
}
|
|
33804
|
+
try {
|
|
33805
|
+
await eraseAnon(this.ctx.storage, this.graph);
|
|
33806
|
+
} finally {
|
|
33807
|
+
this.resetMirrors();
|
|
33808
|
+
}
|
|
33809
|
+
this.wiped = true;
|
|
33810
|
+
for (const socket of this.ctx.getWebSockets()) {
|
|
33811
|
+
socket.close(1e3, "erased");
|
|
33812
|
+
}
|
|
33813
|
+
return true;
|
|
33814
|
+
});
|
|
33815
|
+
});
|
|
33816
|
+
}
|
|
33432
33817
|
// Addressing an object runs the constructor's schema exec, so a call that never writes — a read, an
|
|
33433
33818
|
// inspect, a push or a snapshot for an id that was never used — would otherwise leave an empty object
|
|
33434
33819
|
// behind that no cleanup alarm ever collects. Every write path seeds meta before it stores anything,
|
|
@@ -33668,16 +34053,19 @@ var AnonDO = class extends DurableObject2 {
|
|
|
33668
34053
|
try {
|
|
33669
34054
|
this.wiped = await expireAnon(this.ctx.storage, this.graph, staleActivity);
|
|
33670
34055
|
} finally {
|
|
33671
|
-
this.
|
|
33672
|
-
this.cleanupAlarm = void 0;
|
|
33673
|
-
this.identifiersMirror.reset();
|
|
33674
|
-
this.conversionsMirror.reset();
|
|
33675
|
-
this.customPropsMirror.reset();
|
|
33676
|
-
this.captureMirror.reset();
|
|
33677
|
-
this.contextMirror.reset();
|
|
34056
|
+
this.resetMirrors();
|
|
33678
34057
|
}
|
|
33679
34058
|
});
|
|
33680
34059
|
}
|
|
34060
|
+
resetMirrors() {
|
|
34061
|
+
this.kvCache.clear();
|
|
34062
|
+
this.cleanupAlarm = void 0;
|
|
34063
|
+
this.identifiersMirror.reset();
|
|
34064
|
+
this.conversionsMirror.reset();
|
|
34065
|
+
this.customPropsMirror.reset();
|
|
34066
|
+
this.captureMirror.reset();
|
|
34067
|
+
this.contextMirror.reset();
|
|
34068
|
+
}
|
|
33681
34069
|
revive() {
|
|
33682
34070
|
if (this.wiped) {
|
|
33683
34071
|
applyMigrations(this.ctx.storage, ANON_MIGRATIONS);
|
|
@@ -33919,7 +34307,9 @@ var GlobalDO = class extends DurableObject3 {
|
|
|
33919
34307
|
}
|
|
33920
34308
|
async unlinkAnon(anonId, trace) {
|
|
33921
34309
|
return runInvocation({ name: "do.global.unlink_anon", trace, attrs: { "anon.id": anonId } }, async () => {
|
|
33922
|
-
this.
|
|
34310
|
+
if (await this.discardIfUnused()) {
|
|
34311
|
+
return;
|
|
34312
|
+
}
|
|
33923
34313
|
unlinkAnon(this.sql, anonId);
|
|
33924
34314
|
});
|
|
33925
34315
|
}
|
|
@@ -33944,6 +34334,30 @@ var GlobalDO = class extends DurableObject3 {
|
|
|
33944
34334
|
return linkedAnonsPage(this.sql, afterRowId, limit);
|
|
33945
34335
|
});
|
|
33946
34336
|
}
|
|
34337
|
+
async erasable(trace) {
|
|
34338
|
+
return runInvocation({ name: "do.global.erasable", trace }, async () => {
|
|
34339
|
+
if (await this.discardIfUnused()) {
|
|
34340
|
+
return null;
|
|
34341
|
+
}
|
|
34342
|
+
const identifiers = allIdentifiers2(this.sql, this.identifiersMirror).map(({ type, value }) => ({ type, value }));
|
|
34343
|
+
return { anonIds: allLinkedAnons(this.sql), identifiers };
|
|
34344
|
+
});
|
|
34345
|
+
}
|
|
34346
|
+
async erase(trace) {
|
|
34347
|
+
return runInvocation({ name: "do.global.erase", trace }, async () => {
|
|
34348
|
+
if (await this.discardIfUnused()) {
|
|
34349
|
+
return false;
|
|
34350
|
+
}
|
|
34351
|
+
try {
|
|
34352
|
+
await this.ctx.storage.deleteAll();
|
|
34353
|
+
} finally {
|
|
34354
|
+
this.kvCache.clear();
|
|
34355
|
+
this.identifiersMirror = identifiersCache2();
|
|
34356
|
+
}
|
|
34357
|
+
this.wiped = true;
|
|
34358
|
+
return true;
|
|
34359
|
+
});
|
|
34360
|
+
}
|
|
33947
34361
|
async inspect(trace) {
|
|
33948
34362
|
return runInvocation({ name: "do.global.inspect", trace }, async () => {
|
|
33949
34363
|
if (await this.discardIfUnused()) {
|