@blotout/qinto-cli 0.1.10 → 0.1.12
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/dist/index.js +1378 -1341
- package/dist/index.js.map +1 -1
- package/dist/worker-artifact/app-runtime/index.js +47 -25
- package/dist/worker-artifact/main/index.js +178 -58
- package/dist/worker-artifact/version.json +6 -6
- package/dist/worker-artifact/wfp/index.js +17 -2
- package/package.json +3 -3
|
@@ -14706,7 +14706,7 @@ var scheduleSchema = external_exports.object({
|
|
|
14706
14706
|
id: external_exports.string().min(1).max(64),
|
|
14707
14707
|
ord: external_exports.number().int().min(1),
|
|
14708
14708
|
label: external_exports.string().max(100),
|
|
14709
|
-
|
|
14709
|
+
queryTicket: external_exports.string().min(1).max(2048)
|
|
14710
14710
|
}).strict()
|
|
14711
14711
|
).min(1).max(MAX_INSTALLS_PER_APP)
|
|
14712
14712
|
}).strict();
|
|
@@ -14722,7 +14722,10 @@ var toolSchema = external_exports.object({
|
|
|
14722
14722
|
// call that names no arguments normalizes to `{}` rather than reaching the handler as undefined.
|
|
14723
14723
|
arguments: external_exports.record(external_exports.string(), external_exports.unknown()).default({}),
|
|
14724
14724
|
// Stable across a collapsed retry, so an app can deduplicate its external effects on it.
|
|
14725
|
-
callId: external_exports.string().min(1).max(64)
|
|
14725
|
+
callId: external_exports.string().min(1).max(64),
|
|
14726
|
+
// The short-lived install/version-scoped credential used only by the app runtime's query
|
|
14727
|
+
// broker. It never enters handler arguments or app-authored result data.
|
|
14728
|
+
queryTicket: external_exports.string().min(1).max(2048)
|
|
14726
14729
|
}).strict().refine((tool) => {
|
|
14727
14730
|
return new TextEncoder().encode(JSON.stringify(tool.arguments)).length <= MAX_TOOL_ARGUMENTS_BYTES;
|
|
14728
14731
|
}, "Tool arguments exceed the size cap");
|
|
@@ -14932,6 +14935,9 @@ var appUiWritePath = (domainId, appSlug2) => {
|
|
|
14932
14935
|
var appUiCatalogQueryPath = (domainId, appSlug2, resourceIdentifier) => {
|
|
14933
14936
|
return `/app-ui/sites/${domainId}/apps/${appSlug2}/catalogs/${resourceIdentifier}/query`;
|
|
14934
14937
|
};
|
|
14938
|
+
var appUiAnalyticsQueryPath = (domainId, appSlug2, resourceIdentifier) => {
|
|
14939
|
+
return `/app-ui/sites/${domainId}/apps/${appSlug2}/analytics/${resourceIdentifier}/query`;
|
|
14940
|
+
};
|
|
14935
14941
|
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();
|
|
14936
14942
|
var uiContextSchema = external_exports.object({
|
|
14937
14943
|
version: external_exports.string().min(1).max(32),
|
|
@@ -16195,43 +16201,58 @@ var failureMessage = (error51) => {
|
|
|
16195
16201
|
return message.slice(0, MAX_ERROR_MESSAGE_LENGTH);
|
|
16196
16202
|
};
|
|
16197
16203
|
|
|
16198
|
-
// src/
|
|
16199
|
-
var
|
|
16200
|
-
var
|
|
16201
|
-
const raw = await readBody(response,
|
|
16204
|
+
// src/query.ts
|
|
16205
|
+
var MAX_QUERY_RESPONSE_BYTES = 5 * 1024 * 1024;
|
|
16206
|
+
var parseQueryResponse = async (kind, response) => {
|
|
16207
|
+
const raw = await readBody(response, MAX_QUERY_RESPONSE_BYTES);
|
|
16202
16208
|
if (raw === null) {
|
|
16203
|
-
throw new Error(
|
|
16209
|
+
throw new Error(`The ${kind} query response exceeded the app runtime limit.`);
|
|
16204
16210
|
}
|
|
16205
16211
|
let body;
|
|
16206
16212
|
try {
|
|
16207
16213
|
body = JSON.parse(raw);
|
|
16208
16214
|
} catch {
|
|
16209
|
-
throw new Error(`The
|
|
16215
|
+
throw new Error(`The ${kind} query returned an invalid response (${response.status}).`);
|
|
16210
16216
|
}
|
|
16211
16217
|
if (!response.ok) {
|
|
16212
16218
|
const error51 = body;
|
|
16213
16219
|
const reason = typeof error51.message === "string" ? error51.message : typeof error51.error === "string" ? error51.error : `request failed with status ${response.status}`;
|
|
16214
|
-
throw new Error(
|
|
16220
|
+
throw new Error(`${kind === "catalog" ? "Catalog" : "Analytics"} query failed: ${reason}`);
|
|
16215
16221
|
}
|
|
16216
16222
|
return body;
|
|
16217
16223
|
};
|
|
16218
|
-
var
|
|
16224
|
+
var buildQuery = ({ env, ticket, installId }) => {
|
|
16225
|
+
const platform = env;
|
|
16226
|
+
const context = JSON.parse(platform.APP_CONTEXT);
|
|
16227
|
+
const post = async (kind, path, resourceIdentifier, sql) => {
|
|
16228
|
+
return traced(
|
|
16229
|
+
`qinto.${kind}.query`,
|
|
16230
|
+
{ attrs: { "app.install_id": installId, [`app.${kind}.resource`]: resourceIdentifier } },
|
|
16231
|
+
async () => {
|
|
16232
|
+
const response = await fetch(new URL(path, platform.QINTO_PLATFORM_API), {
|
|
16233
|
+
method: "POST",
|
|
16234
|
+
headers: { authorization: `Bearer ${ticket}`, "content-type": "application/json" },
|
|
16235
|
+
body: JSON.stringify({ sql })
|
|
16236
|
+
});
|
|
16237
|
+
return parseQueryResponse(kind, response);
|
|
16238
|
+
}
|
|
16239
|
+
);
|
|
16240
|
+
};
|
|
16219
16241
|
return {
|
|
16220
16242
|
catalog: async (resourceIdentifier, sql) => {
|
|
16221
|
-
|
|
16222
|
-
|
|
16223
|
-
|
|
16224
|
-
|
|
16225
|
-
|
|
16226
|
-
|
|
16227
|
-
|
|
16228
|
-
|
|
16229
|
-
|
|
16230
|
-
|
|
16231
|
-
|
|
16232
|
-
|
|
16233
|
-
|
|
16234
|
-
}
|
|
16243
|
+
return post(
|
|
16244
|
+
"catalog",
|
|
16245
|
+
appUiCatalogQueryPath(context.domainId, context.slug, resourceIdentifier),
|
|
16246
|
+
resourceIdentifier,
|
|
16247
|
+
sql
|
|
16248
|
+
);
|
|
16249
|
+
},
|
|
16250
|
+
analytics: async (resourceIdentifier, sql) => {
|
|
16251
|
+
return post(
|
|
16252
|
+
"analytics",
|
|
16253
|
+
appUiAnalyticsQueryPath(context.domainId, context.slug, resourceIdentifier),
|
|
16254
|
+
resourceIdentifier,
|
|
16255
|
+
sql
|
|
16235
16256
|
);
|
|
16236
16257
|
}
|
|
16237
16258
|
};
|
|
@@ -16273,7 +16294,7 @@ var dispatchSchedule = async (envelope, bundle, env) => {
|
|
|
16273
16294
|
appSecrets: appScope.appSecrets,
|
|
16274
16295
|
observability: buildObservability(install.ord),
|
|
16275
16296
|
apps: buildAppsClient(env, { depth: 0 }),
|
|
16276
|
-
query:
|
|
16297
|
+
query: buildQuery({ env, ticket: install.queryTicket, installId: install.id }),
|
|
16277
16298
|
push: async (userId, message) => {
|
|
16278
16299
|
await brokerPush(env, {
|
|
16279
16300
|
userId,
|
|
@@ -16393,6 +16414,7 @@ var buildContext2 = (envelope, env) => {
|
|
|
16393
16414
|
appSecrets: appScope.appSecrets,
|
|
16394
16415
|
observability: buildObservability(install.ord),
|
|
16395
16416
|
apps: buildAppsClient(env, { depth: 0 }),
|
|
16417
|
+
query: buildQuery({ env, ticket: envelope.tool.queryTicket, installId: install.id }),
|
|
16396
16418
|
fail: (message) => {
|
|
16397
16419
|
return { kind: "tool_error", message: truncate(message) };
|
|
16398
16420
|
}
|
|
@@ -570,6 +570,92 @@ var appendSpan = /* @__PURE__ */ __name((span) => {
|
|
|
570
570
|
scope.buffer.spans.push(span);
|
|
571
571
|
}, "appendSpan");
|
|
572
572
|
|
|
573
|
+
// ../../node_modules/.pnpm/hono@4.12.28/node_modules/hono/dist/http-exception.js
|
|
574
|
+
var HTTPException = class extends Error {
|
|
575
|
+
static {
|
|
576
|
+
__name(this, "HTTPException");
|
|
577
|
+
}
|
|
578
|
+
res;
|
|
579
|
+
status;
|
|
580
|
+
/**
|
|
581
|
+
* Creates an instance of `HTTPException`.
|
|
582
|
+
* @param status - HTTP status code for the exception. Defaults to 500.
|
|
583
|
+
* @param options - Additional options for the exception.
|
|
584
|
+
*/
|
|
585
|
+
constructor(status = 500, options) {
|
|
586
|
+
super(options?.message, { cause: options?.cause });
|
|
587
|
+
this.res = options?.res;
|
|
588
|
+
this.status = status;
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Returns the response object associated with the exception.
|
|
592
|
+
* If a response object is not provided, a new response is created with the error message and status code.
|
|
593
|
+
* @returns The response object.
|
|
594
|
+
*/
|
|
595
|
+
getResponse() {
|
|
596
|
+
if (this.res) {
|
|
597
|
+
const newResponse = new Response(this.res.body, {
|
|
598
|
+
status: this.status,
|
|
599
|
+
headers: this.res.headers
|
|
600
|
+
});
|
|
601
|
+
return newResponse;
|
|
602
|
+
}
|
|
603
|
+
return new Response(this.message, {
|
|
604
|
+
status: this.status
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
// ../../node_modules/.pnpm/hono@4.12.28/node_modules/hono/dist/middleware/body-limit/index.js
|
|
610
|
+
var ERROR_MESSAGE = "Payload Too Large";
|
|
611
|
+
var bodyLimit = /* @__PURE__ */ __name((options) => {
|
|
612
|
+
const onError = options.onError || (() => {
|
|
613
|
+
const res = new Response(ERROR_MESSAGE, {
|
|
614
|
+
status: 413
|
|
615
|
+
});
|
|
616
|
+
throw new HTTPException(413, { res });
|
|
617
|
+
});
|
|
618
|
+
const maxSize = options.maxSize;
|
|
619
|
+
return /* @__PURE__ */ __name(async function bodyLimit2(c, next) {
|
|
620
|
+
if (!c.req.raw.body) {
|
|
621
|
+
return next();
|
|
622
|
+
}
|
|
623
|
+
const hasTransferEncoding = c.req.raw.headers.has("transfer-encoding");
|
|
624
|
+
const hasContentLength = c.req.raw.headers.has("content-length");
|
|
625
|
+
if (hasContentLength && !hasTransferEncoding) {
|
|
626
|
+
const contentLength = parseInt(c.req.raw.headers.get("content-length") || "0", 10);
|
|
627
|
+
return contentLength > maxSize ? onError(c) : next();
|
|
628
|
+
}
|
|
629
|
+
let size = 0;
|
|
630
|
+
const chunks = [];
|
|
631
|
+
const rawReader = c.req.raw.body.getReader();
|
|
632
|
+
for (; ; ) {
|
|
633
|
+
const { done, value } = await rawReader.read();
|
|
634
|
+
if (done) {
|
|
635
|
+
break;
|
|
636
|
+
}
|
|
637
|
+
size += value.length;
|
|
638
|
+
if (size > maxSize) {
|
|
639
|
+
return onError(c);
|
|
640
|
+
}
|
|
641
|
+
chunks.push(value);
|
|
642
|
+
}
|
|
643
|
+
const requestInit = {
|
|
644
|
+
body: new ReadableStream({
|
|
645
|
+
start(controller) {
|
|
646
|
+
for (const chunk of chunks) {
|
|
647
|
+
controller.enqueue(chunk);
|
|
648
|
+
}
|
|
649
|
+
controller.close();
|
|
650
|
+
}
|
|
651
|
+
}),
|
|
652
|
+
duplex: "half"
|
|
653
|
+
};
|
|
654
|
+
c.req.raw = new Request(c.req.raw, requestInit);
|
|
655
|
+
return next();
|
|
656
|
+
}, "bodyLimit2");
|
|
657
|
+
}, "bodyLimit");
|
|
658
|
+
|
|
573
659
|
// ../../node_modules/.pnpm/hono@4.12.28/node_modules/hono/dist/middleware/cors/index.js
|
|
574
660
|
var cors = /* @__PURE__ */ __name((options) => {
|
|
575
661
|
const opts = {
|
|
@@ -653,42 +739,6 @@ var cors = /* @__PURE__ */ __name((options) => {
|
|
|
653
739
|
}, "cors2");
|
|
654
740
|
}, "cors");
|
|
655
741
|
|
|
656
|
-
// ../../node_modules/.pnpm/hono@4.12.28/node_modules/hono/dist/http-exception.js
|
|
657
|
-
var HTTPException = class extends Error {
|
|
658
|
-
static {
|
|
659
|
-
__name(this, "HTTPException");
|
|
660
|
-
}
|
|
661
|
-
res;
|
|
662
|
-
status;
|
|
663
|
-
/**
|
|
664
|
-
* Creates an instance of `HTTPException`.
|
|
665
|
-
* @param status - HTTP status code for the exception. Defaults to 500.
|
|
666
|
-
* @param options - Additional options for the exception.
|
|
667
|
-
*/
|
|
668
|
-
constructor(status = 500, options) {
|
|
669
|
-
super(options?.message, { cause: options?.cause });
|
|
670
|
-
this.res = options?.res;
|
|
671
|
-
this.status = status;
|
|
672
|
-
}
|
|
673
|
-
/**
|
|
674
|
-
* Returns the response object associated with the exception.
|
|
675
|
-
* If a response object is not provided, a new response is created with the error message and status code.
|
|
676
|
-
* @returns The response object.
|
|
677
|
-
*/
|
|
678
|
-
getResponse() {
|
|
679
|
-
if (this.res) {
|
|
680
|
-
const newResponse = new Response(this.res.body, {
|
|
681
|
-
status: this.status,
|
|
682
|
-
headers: this.res.headers
|
|
683
|
-
});
|
|
684
|
-
return newResponse;
|
|
685
|
-
}
|
|
686
|
-
return new Response(this.message, {
|
|
687
|
-
status: this.status
|
|
688
|
-
});
|
|
689
|
-
}
|
|
690
|
-
};
|
|
691
|
-
|
|
692
742
|
// src/apps/broker.ts
|
|
693
743
|
import { timingSafeEqual } from "node:crypto";
|
|
694
744
|
import { WorkerEntrypoint } from "cloudflare:workers";
|
|
@@ -15932,7 +15982,7 @@ var scheduleSchema = external_exports.object({
|
|
|
15932
15982
|
id: external_exports.string().min(1).max(64),
|
|
15933
15983
|
ord: external_exports.number().int().min(1),
|
|
15934
15984
|
label: external_exports.string().max(100),
|
|
15935
|
-
|
|
15985
|
+
queryTicket: external_exports.string().min(1).max(2048)
|
|
15936
15986
|
}).strict()
|
|
15937
15987
|
).min(1).max(MAX_INSTALLS_PER_APP)
|
|
15938
15988
|
}).strict();
|
|
@@ -15948,7 +15998,10 @@ var toolSchema = external_exports.object({
|
|
|
15948
15998
|
// call that names no arguments normalizes to `{}` rather than reaching the handler as undefined.
|
|
15949
15999
|
arguments: external_exports.record(external_exports.string(), external_exports.unknown()).default({}),
|
|
15950
16000
|
// Stable across a collapsed retry, so an app can deduplicate its external effects on it.
|
|
15951
|
-
callId: external_exports.string().min(1).max(64)
|
|
16001
|
+
callId: external_exports.string().min(1).max(64),
|
|
16002
|
+
// The short-lived install/version-scoped credential used only by the app runtime's query
|
|
16003
|
+
// broker. It never enters handler arguments or app-authored result data.
|
|
16004
|
+
queryTicket: external_exports.string().min(1).max(2048)
|
|
15952
16005
|
}).strict().refine((tool) => {
|
|
15953
16006
|
return new TextEncoder().encode(JSON.stringify(tool.arguments)).length <= MAX_TOOL_ARGUMENTS_BYTES;
|
|
15954
16007
|
}, "Tool arguments exceed the size cap");
|
|
@@ -20993,10 +21046,10 @@ var verifyAppUiToken = /* @__PURE__ */ __name(async (input) => {
|
|
|
20993
21046
|
return { ok: true, claims: claims.data, kid: signature.kid, key: signature.key };
|
|
20994
21047
|
}, "verifyAppUiToken");
|
|
20995
21048
|
|
|
20996
|
-
// ../../libs/app-ui-token/src/
|
|
20997
|
-
var
|
|
21049
|
+
// ../../libs/app-ui-token/src/runtime-query-ticket.ts
|
|
21050
|
+
var APP_RUNTIME_QUERY_TICKET_ISSUER = "qinto-portal";
|
|
20998
21051
|
var claimsSchema2 = external_exports.object({
|
|
20999
|
-
iss: external_exports.literal(
|
|
21052
|
+
iss: external_exports.literal(APP_RUNTIME_QUERY_TICKET_ISSUER),
|
|
21000
21053
|
aud: external_exports.string().min(1),
|
|
21001
21054
|
sub: external_exports.string().min(1),
|
|
21002
21055
|
iat: external_exports.number().int(),
|
|
@@ -25968,9 +26021,29 @@ CREATE UNIQUE INDEX IF NOT EXISTS idx_facts_conversion ON facts (key) WHERE kind
|
|
|
25968
26021
|
"ALTER TABLE facts ADD COLUMN expires_at INTEGER;"
|
|
25969
26022
|
];
|
|
25970
26023
|
|
|
26024
|
+
// src/id-graph/anon/token-bucket.ts
|
|
26025
|
+
var createTokenBucket = /* @__PURE__ */ __name((capacity, refillPerSecond) => {
|
|
26026
|
+
let tokens = capacity;
|
|
26027
|
+
let updatedAt = null;
|
|
26028
|
+
return {
|
|
26029
|
+
take: /* @__PURE__ */ __name((now) => {
|
|
26030
|
+
if (updatedAt !== null) {
|
|
26031
|
+
tokens = Math.min(capacity, tokens + Math.max(0, now - updatedAt) * (refillPerSecond / 1e3));
|
|
26032
|
+
}
|
|
26033
|
+
updatedAt = now;
|
|
26034
|
+
if (tokens < 1) {
|
|
26035
|
+
return false;
|
|
26036
|
+
}
|
|
26037
|
+
tokens -= 1;
|
|
26038
|
+
return true;
|
|
26039
|
+
}, "take")
|
|
26040
|
+
};
|
|
26041
|
+
}, "createTokenBucket");
|
|
26042
|
+
|
|
25971
26043
|
// src/id-graph/anon/websocket.ts
|
|
25972
26044
|
var MAX_BROWSER_RESULT_BYTES = 1024;
|
|
25973
26045
|
var MAX_BROWSER_RESULTS_BYTES = 32 * 1024;
|
|
26046
|
+
var MAX_FRAME_BYTES = 64 * 1024;
|
|
25974
26047
|
var browserContextFrame = external_exports.object({
|
|
25975
26048
|
occurredAt: external_exports.number().int().nonnegative().safe(),
|
|
25976
26049
|
page: external_exports.object({ url: external_exports.string().min(1).max(4096), referrer: external_exports.string().min(1).max(4096).optional() }).strict(),
|
|
@@ -26043,6 +26116,10 @@ var pluckRef = /* @__PURE__ */ __name((parsed) => {
|
|
|
26043
26116
|
return typeof ref === "string" && ref.length > 0 ? ref : void 0;
|
|
26044
26117
|
}, "pluckRef");
|
|
26045
26118
|
var parseFrame = /* @__PURE__ */ __name((message) => {
|
|
26119
|
+
const size = typeof message === "string" ? message.length : message.byteLength;
|
|
26120
|
+
if (size > MAX_FRAME_BYTES) {
|
|
26121
|
+
return { ok: false, ref: void 0, error: new Error(`frame exceeds ${MAX_FRAME_BYTES} bytes`) };
|
|
26122
|
+
}
|
|
26046
26123
|
const text = typeof message === "string" ? message : new TextDecoder().decode(message);
|
|
26047
26124
|
let parsed;
|
|
26048
26125
|
try {
|
|
@@ -26141,12 +26218,19 @@ var siteAudience = /* @__PURE__ */ __name(({ registry: registry3, app: app2, top
|
|
|
26141
26218
|
const allowed = app2.events.publishes.includes(topic) && app2.data.includes(SHARED_CONTEXT_CAPABILITY);
|
|
26142
26219
|
return allowed ? siteEventReceivers(registry3, consent2, enforced) : null;
|
|
26143
26220
|
}, "siteAudience");
|
|
26221
|
+
var FRAME_BURST = 60;
|
|
26222
|
+
var FRAMES_PER_SECOND = 10;
|
|
26223
|
+
var EMIT_BURST = 120;
|
|
26224
|
+
var EMITS_PER_SECOND = 5;
|
|
26144
26225
|
var AnonDO = class extends DurableObject2 {
|
|
26145
26226
|
static {
|
|
26146
26227
|
__name(this, "AnonDO");
|
|
26147
26228
|
}
|
|
26148
26229
|
// Serialize link decisions so concurrent first identifiers cannot split the graph.
|
|
26149
26230
|
ingestQueue = Promise.resolve();
|
|
26231
|
+
// In memory on purpose: a hibernated socket sent nothing, so a fresh bucket on wake is the right one.
|
|
26232
|
+
frameBuckets = /* @__PURE__ */ new WeakMap();
|
|
26233
|
+
emitBudget = createTokenBucket(EMIT_BURST, EMITS_PER_SECOND);
|
|
26150
26234
|
// deleteAll removes the live schema; only a later write recreates it.
|
|
26151
26235
|
wiped = false;
|
|
26152
26236
|
// Warm write-through mirrors reset together after expiry.
|
|
@@ -26205,9 +26289,50 @@ var AnonDO = class extends DurableObject2 {
|
|
|
26205
26289
|
...action.type === "track" ? { "action.event": action.payload.event } : {}
|
|
26206
26290
|
};
|
|
26207
26291
|
return runInvocation({ name: "do.anon.ingest", trace, attrs }, () => {
|
|
26292
|
+
if (action.origin !== void 0 && !this.emitBudget.take(Date.now())) {
|
|
26293
|
+
addSpanEvent("emit rejected: the visitor\u2019s emit budget is spent", {
|
|
26294
|
+
level: "warn",
|
|
26295
|
+
attrs: { app: action.origin.app, depth: action.origin.depth }
|
|
26296
|
+
});
|
|
26297
|
+
return Promise.resolve();
|
|
26298
|
+
}
|
|
26208
26299
|
return this.ingestLocal(action);
|
|
26209
26300
|
});
|
|
26210
26301
|
}
|
|
26302
|
+
// The frames that ask instead of act are answered in place; an action frame comes back to be queued.
|
|
26303
|
+
async answerQuery(ws, parsed, userId, attachment) {
|
|
26304
|
+
const { frame, ref } = parsed;
|
|
26305
|
+
if (frame.t === "getSession") {
|
|
26306
|
+
this.respond(ws, {
|
|
26307
|
+
t: "session",
|
|
26308
|
+
ref,
|
|
26309
|
+
anonId: userId,
|
|
26310
|
+
globalId: linkedGlobalId(this.kv),
|
|
26311
|
+
ip: attachment?.host?.ip ?? null,
|
|
26312
|
+
geo: attachment?.browserGeo ?? null
|
|
26313
|
+
});
|
|
26314
|
+
return null;
|
|
26315
|
+
}
|
|
26316
|
+
if (frame.t === "capture") {
|
|
26317
|
+
const { accepted } = await this.capture({ apps: frame.apps });
|
|
26318
|
+
this.respond(ws, { t: "captureAck", ref, anonId: userId, accepted });
|
|
26319
|
+
return null;
|
|
26320
|
+
}
|
|
26321
|
+
return frame;
|
|
26322
|
+
}
|
|
26323
|
+
admitFrame(ws) {
|
|
26324
|
+
let bucket = this.frameBuckets.get(ws);
|
|
26325
|
+
if (bucket === void 0) {
|
|
26326
|
+
bucket = createTokenBucket(FRAME_BURST, FRAMES_PER_SECOND);
|
|
26327
|
+
this.frameBuckets.set(ws, bucket);
|
|
26328
|
+
}
|
|
26329
|
+
if (bucket.take(Date.now())) {
|
|
26330
|
+
return true;
|
|
26331
|
+
}
|
|
26332
|
+
addSpanEvent("websocket frame rate limited", { level: "warn" });
|
|
26333
|
+
this.respond(ws, { t: "error", ref: void 0, message: "rate limited" });
|
|
26334
|
+
return false;
|
|
26335
|
+
}
|
|
26211
26336
|
ingestLocal(action) {
|
|
26212
26337
|
return this.enqueue(() => this.processAction(action));
|
|
26213
26338
|
}
|
|
@@ -26230,6 +26355,9 @@ var AnonDO = class extends DurableObject2 {
|
|
|
26230
26355
|
async webSocketMessage(ws, message) {
|
|
26231
26356
|
const attachment = ws.deserializeAttachment() ?? void 0;
|
|
26232
26357
|
return runInvocation({ name: "ws.message", attrs: attachment?.attrs }, async () => {
|
|
26358
|
+
if (!this.admitFrame(ws)) {
|
|
26359
|
+
return;
|
|
26360
|
+
}
|
|
26233
26361
|
const parsed = parseFrame(message);
|
|
26234
26362
|
if (!parsed.ok) {
|
|
26235
26363
|
addSpanEvent("invalid websocket frame", { level: "warn", attrs: { error: String(parsed.error) } });
|
|
@@ -26247,23 +26375,11 @@ var AnonDO = class extends DurableObject2 {
|
|
|
26247
26375
|
this.respond(ws, { t: "error", ref: parsed.ref, message: "session not initialized" });
|
|
26248
26376
|
return;
|
|
26249
26377
|
}
|
|
26250
|
-
|
|
26251
|
-
|
|
26252
|
-
t: "session",
|
|
26253
|
-
ref: parsed.ref,
|
|
26254
|
-
anonId: userId,
|
|
26255
|
-
globalId: linkedGlobalId(this.kv),
|
|
26256
|
-
ip: attachment?.host?.ip ?? null,
|
|
26257
|
-
geo: attachment?.browserGeo ?? null
|
|
26258
|
-
});
|
|
26259
|
-
return;
|
|
26260
|
-
}
|
|
26261
|
-
if (parsed.frame.t === "capture") {
|
|
26262
|
-
const { accepted } = await this.capture({ apps: parsed.frame.apps });
|
|
26263
|
-
this.respond(ws, { t: "captureAck", ref: parsed.ref, anonId: userId, accepted });
|
|
26378
|
+
const frame = await this.answerQuery(ws, { frame: parsed.frame, ref: parsed.ref }, userId, attachment);
|
|
26379
|
+
if (frame === null) {
|
|
26264
26380
|
return;
|
|
26265
26381
|
}
|
|
26266
|
-
const action = actionFromFrame(
|
|
26382
|
+
const action = actionFromFrame(frame, userId, attachment?.cf, attachment?.host);
|
|
26267
26383
|
const ref = parsed.ref ?? action.actionId;
|
|
26268
26384
|
setSpanAttributes({ "action.id": action.actionId });
|
|
26269
26385
|
try {
|
|
@@ -27189,6 +27305,7 @@ CREATE UNIQUE INDEX IF NOT EXISTS idx_facts_key ON facts (kind, scope, key);
|
|
|
27189
27305
|
];
|
|
27190
27306
|
|
|
27191
27307
|
// src/id-graph/global/do.ts
|
|
27308
|
+
var SIBLING_FAN_OUT_CONCURRENCY = 6;
|
|
27192
27309
|
var GlobalDO = class extends DurableObject3 {
|
|
27193
27310
|
static {
|
|
27194
27311
|
__name(this, "GlobalDO");
|
|
@@ -27290,8 +27407,9 @@ var GlobalDO = class extends DurableObject3 {
|
|
|
27290
27407
|
}
|
|
27291
27408
|
await runInvocation({ name: "do.global.fan_out", trace, attrs: { "global.id": globalId } }, async () => {
|
|
27292
27409
|
const snapshot = this.snapshot(globalId);
|
|
27293
|
-
await
|
|
27294
|
-
|
|
27410
|
+
await runPool(
|
|
27411
|
+
SIBLING_FAN_OUT_CONCURRENCY,
|
|
27412
|
+
recipients.map((anonId) => async () => {
|
|
27295
27413
|
try {
|
|
27296
27414
|
await traced("rpc anon.sync_from_global", { attrs: { "anon.id": anonId } }, () => {
|
|
27297
27415
|
return anonStub(this.env, anonId).syncFromGlobal(snapshot, currentTraceContext());
|
|
@@ -27314,8 +27432,10 @@ app.use("/q.js", cors());
|
|
|
27314
27432
|
app.get("/q.js", qJs);
|
|
27315
27433
|
app.use("/health", cors());
|
|
27316
27434
|
app.route("/", health);
|
|
27435
|
+
var MAX_V1_BODY_BYTES = 1024 * 1024;
|
|
27317
27436
|
var allowCors = cors({ origin: /* @__PURE__ */ __name((origin) => origin, "origin"), credentials: true });
|
|
27318
27437
|
app.use("/v1/*", allowCors);
|
|
27438
|
+
app.use("/v1/*", bodyLimit({ maxSize: MAX_V1_BODY_BYTES, onError: /* @__PURE__ */ __name((c) => c.json({ error: "too_large" }, 413), "onError") }));
|
|
27319
27439
|
app.route("/", alias);
|
|
27320
27440
|
app.route("/", identify);
|
|
27321
27441
|
app.route("/", track);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"main": {
|
|
3
3
|
"version": "0.1.0",
|
|
4
|
-
"gitSha": "
|
|
5
|
-
"builtAt": "2026-08-
|
|
4
|
+
"gitSha": "539e97d61a67ca9c5c6580b94350354e917fcf99",
|
|
5
|
+
"builtAt": "2026-08-31T10:11:02.141Z",
|
|
6
6
|
"compatibilityDate": "2026-06-23",
|
|
7
7
|
"compatibilityFlags": [
|
|
8
8
|
"nodejs_compat"
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
},
|
|
24
24
|
"wfp": {
|
|
25
25
|
"version": "0.1.0",
|
|
26
|
-
"gitSha": "
|
|
27
|
-
"builtAt": "2026-08-
|
|
26
|
+
"gitSha": "6064f0fb074965ec6add05255c92ece93bb31321",
|
|
27
|
+
"builtAt": "2026-08-31T12:30:46.702Z",
|
|
28
28
|
"compatibilityDate": "2026-06-23",
|
|
29
29
|
"compatibilityFlags": [
|
|
30
30
|
"nodejs_compat"
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
},
|
|
41
41
|
"app-runtime": {
|
|
42
42
|
"version": "0.1.0",
|
|
43
|
-
"gitSha": "
|
|
44
|
-
"builtAt": "2026-08-
|
|
43
|
+
"gitSha": "83bcac7072f2feb866505c4a7b99f7281b08d972",
|
|
44
|
+
"builtAt": "2026-08-29T21:13:19.154Z",
|
|
45
45
|
"compatibilityDate": "2026-06-23",
|
|
46
46
|
"compatibilityFlags": [
|
|
47
47
|
"nodejs_compat"
|
|
@@ -2158,10 +2158,9 @@ var EDGETAG_PREFIXES = [
|
|
|
2158
2158
|
|
|
2159
2159
|
// src/allowed-paths.ts
|
|
2160
2160
|
var QINTO_PREFIXES = ["/open", "/q.js", "/health", "/v1/", "/openapi.json", "/apps/", "/app/"];
|
|
2161
|
-
var QINTO_EXACT = /* @__PURE__ */ new Set(["/__qinto/control"]);
|
|
2162
2161
|
var ALLOWED_PREFIXES = [...QINTO_PREFIXES, ...EDGETAG_PREFIXES];
|
|
2163
2162
|
var allowedPaths = /* @__PURE__ */ __name((path) => {
|
|
2164
|
-
return path === "/" ||
|
|
2163
|
+
return path === "/" || ALLOWED_PREFIXES.some((prefix) => path.startsWith(prefix));
|
|
2165
2164
|
}, "allowedPaths");
|
|
2166
2165
|
|
|
2167
2166
|
// ../../libs/wfp-control-token/src/control-token.ts
|
|
@@ -2244,6 +2243,21 @@ var requireControlToken = /* @__PURE__ */ __name(async (c, next) => {
|
|
|
2244
2243
|
return next();
|
|
2245
2244
|
}, "requireControlToken");
|
|
2246
2245
|
|
|
2246
|
+
// src/control/app.ts
|
|
2247
|
+
var forwardAppControl = /* @__PURE__ */ __name(async (c) => {
|
|
2248
|
+
const workerName = c.req.query("workerName");
|
|
2249
|
+
if (workerName === void 0 || workerName === "") {
|
|
2250
|
+
return c.text("workerName required", 400);
|
|
2251
|
+
}
|
|
2252
|
+
const url = new URL(c.req.url);
|
|
2253
|
+
url.pathname = "/__qinto/control";
|
|
2254
|
+
url.search = "";
|
|
2255
|
+
const headers = new Headers(c.req.raw.headers);
|
|
2256
|
+
headers.delete("authorization");
|
|
2257
|
+
const body = await c.req.raw.text();
|
|
2258
|
+
return c.env.DISPATCHER.get(workerName).fetch(new Request(url, { method: "POST", headers, body }));
|
|
2259
|
+
}, "forwardAppControl");
|
|
2260
|
+
|
|
2247
2261
|
// src/control/id-graph.ts
|
|
2248
2262
|
var INSPECT_AUTHORIZATION_HEADER = "x-qinto-inspect-authorization";
|
|
2249
2263
|
var ACTIONS = /* @__PURE__ */ new Set(["resolve", "inspect", "rows"]);
|
|
@@ -2683,6 +2697,7 @@ var Tail = class extends DurableObject {
|
|
|
2683
2697
|
// src/index.ts
|
|
2684
2698
|
var app = new Hono2();
|
|
2685
2699
|
app.get("/tail", requireControlToken, openTailSocket);
|
|
2700
|
+
app.post("/control/app", requireControlToken, forwardAppControl);
|
|
2686
2701
|
app.post("/control/id-graph/:action", requireControlToken, forwardIdGraph);
|
|
2687
2702
|
app.all("*", (c) => {
|
|
2688
2703
|
if (!allowedPaths(c.req.path)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blotout/qinto-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
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": {
|
|
@@ -40,9 +40,9 @@
|
|
|
40
40
|
"typescript": "6.0.3",
|
|
41
41
|
"vite": "^8.0.0",
|
|
42
42
|
"vitest": "4.1.10",
|
|
43
|
-
"@qinto/app-build": "0.0.0",
|
|
44
|
-
"@qinto/app-dev": "0.0.0",
|
|
45
43
|
"@qinto/app-manifest": "0.0.0",
|
|
44
|
+
"@qinto/app-dev": "0.0.0",
|
|
45
|
+
"@qinto/app-build": "0.0.0",
|
|
46
46
|
"@qinto/oauth-grammar": "0.0.0"
|
|
47
47
|
},
|
|
48
48
|
"scripts": {
|