@opendatalabs/personal-server-ts-core 0.0.1-canary.01ca694 → 0.0.1-canary.052d47e
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/api/index.d.ts +282 -9
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +867 -163
- package/dist/api/index.js.map +1 -1
- package/dist/auth/request.d.ts.map +1 -1
- package/dist/auth/request.js.map +1 -1
- package/dist/client.d.ts +8 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +21 -3
- package/dist/client.js.map +1 -1
- package/dist/contracts/binary.d.ts +12 -0
- package/dist/contracts/binary.d.ts.map +1 -1
- package/dist/contracts/binary.js +31 -0
- package/dist/contracts/binary.js.map +1 -1
- package/dist/contracts/data.d.ts +51 -5
- package/dist/contracts/data.d.ts.map +1 -1
- package/dist/contracts/data.js +166 -30
- package/dist/contracts/data.js.map +1 -1
- package/dist/contracts/grants.d.ts +7 -5
- package/dist/contracts/grants.d.ts.map +1 -1
- package/dist/contracts/grants.js +60 -37
- package/dist/contracts/grants.js.map +1 -1
- package/dist/contracts/index.d.ts +1 -1
- package/dist/contracts/index.d.ts.map +1 -1
- package/dist/contracts/index.js +1 -1
- package/dist/contracts/index.js.map +1 -1
- package/dist/derivatives/api.d.ts +48 -0
- package/dist/derivatives/api.d.ts.map +1 -0
- package/dist/derivatives/api.js +366 -0
- package/dist/derivatives/api.js.map +1 -0
- package/dist/derivatives/compute.d.ts +106 -0
- package/dist/derivatives/compute.d.ts.map +1 -0
- package/dist/derivatives/compute.js +414 -0
- package/dist/derivatives/compute.js.map +1 -0
- package/dist/derivatives/e2ee/aad.d.ts +22 -0
- package/dist/derivatives/e2ee/aad.d.ts.map +1 -0
- package/dist/derivatives/e2ee/aad.js +32 -0
- package/dist/derivatives/e2ee/aad.js.map +1 -0
- package/dist/derivatives/e2ee/attestation.d.ts +134 -0
- package/dist/derivatives/e2ee/attestation.d.ts.map +1 -0
- package/dist/derivatives/e2ee/attestation.js +248 -0
- package/dist/derivatives/e2ee/attestation.js.map +1 -0
- package/dist/derivatives/e2ee/index.d.ts +6 -0
- package/dist/derivatives/e2ee/index.d.ts.map +1 -0
- package/dist/derivatives/e2ee/index.js +6 -0
- package/dist/derivatives/e2ee/index.js.map +1 -0
- package/dist/derivatives/e2ee/jcs.d.ts +17 -0
- package/dist/derivatives/e2ee/jcs.d.ts.map +1 -0
- package/dist/derivatives/e2ee/jcs.js +55 -0
- package/dist/derivatives/e2ee/jcs.js.map +1 -0
- package/dist/derivatives/e2ee/phala.d.ts +62 -0
- package/dist/derivatives/e2ee/phala.d.ts.map +1 -0
- package/dist/derivatives/e2ee/phala.js +168 -0
- package/dist/derivatives/e2ee/phala.js.map +1 -0
- package/dist/derivatives/e2ee/suite.d.ts +56 -0
- package/dist/derivatives/e2ee/suite.d.ts.map +1 -0
- package/dist/derivatives/e2ee/suite.js +164 -0
- package/dist/derivatives/e2ee/suite.js.map +1 -0
- package/dist/derivatives/index.d.ts +10 -0
- package/dist/derivatives/index.d.ts.map +1 -0
- package/dist/derivatives/index.js +10 -0
- package/dist/derivatives/index.js.map +1 -0
- package/dist/derivatives/inference.d.ts +151 -0
- package/dist/derivatives/inference.d.ts.map +1 -0
- package/dist/derivatives/inference.js +262 -0
- package/dist/derivatives/inference.js.map +1 -0
- package/dist/derivatives/prompt.d.ts +64 -0
- package/dist/derivatives/prompt.d.ts.map +1 -0
- package/dist/derivatives/prompt.js +193 -0
- package/dist/derivatives/prompt.js.map +1 -0
- package/dist/derivatives/registration.d.ts +54 -0
- package/dist/derivatives/registration.d.ts.map +1 -0
- package/dist/derivatives/registration.js +180 -0
- package/dist/derivatives/registration.js.map +1 -0
- package/dist/derivatives/scheduler.d.ts +84 -0
- package/dist/derivatives/scheduler.d.ts.map +1 -0
- package/dist/derivatives/scheduler.js +257 -0
- package/dist/derivatives/scheduler.js.map +1 -0
- package/dist/derivatives/store.d.ts +14 -0
- package/dist/derivatives/store.d.ts.map +1 -0
- package/dist/derivatives/store.js +77 -0
- package/dist/derivatives/store.js.map +1 -0
- package/dist/derivatives/types.d.ts +92 -0
- package/dist/derivatives/types.d.ts.map +1 -0
- package/dist/derivatives/types.js +27 -0
- package/dist/derivatives/types.js.map +1 -0
- package/dist/errors/catalog.d.ts +75 -0
- package/dist/errors/catalog.d.ts.map +1 -1
- package/dist/errors/catalog.js +104 -0
- package/dist/errors/catalog.js.map +1 -1
- package/dist/grants/index.d.ts +2 -1
- package/dist/grants/index.d.ts.map +1 -1
- package/dist/grants/index.js +1 -1
- package/dist/grants/index.js.map +1 -1
- package/dist/keys/index.d.ts +1 -0
- package/dist/keys/index.d.ts.map +1 -1
- package/dist/keys/index.js +1 -1
- package/dist/keys/index.js.map +1 -1
- package/dist/keys/public-only-account.d.ts +8 -0
- package/dist/keys/public-only-account.d.ts.map +1 -0
- package/dist/keys/public-only-account.js +21 -0
- package/dist/keys/public-only-account.js.map +1 -0
- package/dist/keys/server-account.d.ts +2 -2
- package/dist/keys/server-account.d.ts.map +1 -1
- package/dist/lineage/attestation.d.ts +42 -0
- package/dist/lineage/attestation.d.ts.map +1 -0
- package/dist/lineage/attestation.js +25 -0
- package/dist/lineage/attestation.js.map +1 -0
- package/dist/lineage/gateway.d.ts +105 -0
- package/dist/lineage/gateway.d.ts.map +1 -0
- package/dist/lineage/gateway.js +242 -0
- package/dist/lineage/gateway.js.map +1 -0
- package/dist/lineage/index.d.ts +5 -0
- package/dist/lineage/index.d.ts.map +1 -0
- package/dist/lineage/index.js +5 -0
- package/dist/lineage/index.js.map +1 -0
- package/dist/lineage/lineage.d.ts +141 -0
- package/dist/lineage/lineage.d.ts.map +1 -0
- package/dist/lineage/lineage.js +255 -0
- package/dist/lineage/lineage.js.map +1 -0
- package/dist/logging/access-log.d.ts +10 -1
- package/dist/logging/access-log.d.ts.map +1 -1
- package/dist/mcp/activity.d.ts.map +1 -1
- package/dist/mcp/activity.js.map +1 -1
- package/dist/mcp/connection-api.d.ts.map +1 -1
- package/dist/mcp/connection-api.js +0 -1
- package/dist/mcp/connection-api.js.map +1 -1
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +1 -0
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/read-client.d.ts +61 -1
- package/dist/mcp/read-client.d.ts.map +1 -1
- package/dist/mcp/read-client.js +397 -42
- package/dist/mcp/read-client.js.map +1 -1
- package/dist/mcp/resources.d.ts +37 -0
- package/dist/mcp/resources.d.ts.map +1 -0
- package/dist/mcp/resources.js +114 -0
- package/dist/mcp/resources.js.map +1 -0
- package/dist/mcp/search/index.d.ts.map +1 -1
- package/dist/mcp/search/index.js +108 -21
- package/dist/mcp/search/index.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +27 -9
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/session.d.ts +160 -0
- package/dist/mcp/session.d.ts.map +1 -0
- package/dist/mcp/session.js +243 -0
- package/dist/mcp/session.js.map +1 -0
- package/dist/mcp/tools.d.ts +29 -3
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +515 -48
- package/dist/mcp/tools.js.map +1 -1
- package/dist/mcp/types.d.ts.map +1 -1
- package/dist/payment/index.d.ts +2 -0
- package/dist/payment/index.d.ts.map +1 -0
- package/dist/payment/index.js +2 -0
- package/dist/payment/index.js.map +1 -0
- package/dist/payment/x402.d.ts +155 -0
- package/dist/payment/x402.d.ts.map +1 -0
- package/dist/payment/x402.js +402 -0
- package/dist/payment/x402.js.map +1 -0
- package/dist/policy/data-read.d.ts +18 -2
- package/dist/policy/data-read.d.ts.map +1 -1
- package/dist/policy/data-read.js +80 -49
- package/dist/policy/data-read.js.map +1 -1
- package/dist/policy/data-write.d.ts +69 -0
- package/dist/policy/data-write.d.ts.map +1 -0
- package/dist/policy/data-write.js +131 -0
- package/dist/policy/data-write.js.map +1 -0
- package/dist/policy/index.d.ts +3 -1
- package/dist/policy/index.d.ts.map +1 -1
- package/dist/policy/index.js +3 -1
- package/dist/policy/index.js.map +1 -1
- package/dist/policy/signed-artifacts.d.ts +17 -0
- package/dist/policy/signed-artifacts.d.ts.map +1 -0
- package/dist/policy/signed-artifacts.js +110 -0
- package/dist/policy/signed-artifacts.js.map +1 -0
- package/dist/ports/index.d.ts +159 -32
- package/dist/ports/index.d.ts.map +1 -1
- package/dist/ports/index.js +6 -5
- package/dist/ports/index.js.map +1 -1
- package/dist/schemas/server-config.d.ts +49 -4
- package/dist/schemas/server-config.d.ts.map +1 -1
- package/dist/schemas/server-config.js +95 -13
- package/dist/schemas/server-config.js.map +1 -1
- package/dist/signing/index.d.ts +2 -2
- package/dist/signing/index.d.ts.map +1 -1
- package/dist/signing/index.js +1 -1
- package/dist/signing/index.js.map +1 -1
- package/dist/signing/request-signer.d.ts +2 -0
- package/dist/signing/request-signer.d.ts.map +1 -1
- package/dist/signing/request-signer.js +1 -0
- package/dist/signing/request-signer.js.map +1 -1
- package/dist/signing/signer.d.ts +27 -25
- package/dist/signing/signer.d.ts.map +1 -1
- package/dist/signing/signer.js +27 -51
- package/dist/signing/signer.js.map +1 -1
- package/dist/storage/adapters/index.d.ts +1 -1
- package/dist/storage/adapters/index.d.ts.map +1 -1
- package/dist/storage/adapters/index.js +1 -1
- package/dist/storage/adapters/index.js.map +1 -1
- package/dist/storage/adapters/interface.d.ts +10 -0
- package/dist/storage/adapters/interface.d.ts.map +1 -1
- package/dist/storage/adapters/sdk.d.ts +11 -1
- package/dist/storage/adapters/sdk.d.ts.map +1 -1
- package/dist/storage/adapters/sdk.js +38 -3
- package/dist/storage/adapters/sdk.js.map +1 -1
- package/dist/storage/adapters/vana.d.ts +3 -0
- package/dist/storage/adapters/vana.d.ts.map +1 -1
- package/dist/storage/adapters/vana.js +95 -2
- package/dist/storage/adapters/vana.js.map +1 -1
- package/dist/storage/blocks/errors.d.ts.map +1 -1
- package/dist/storage/blocks/errors.js.map +1 -1
- package/dist/storage/blocks/index.d.ts +2 -0
- package/dist/storage/blocks/index.d.ts.map +1 -1
- package/dist/storage/blocks/index.js +2 -0
- package/dist/storage/blocks/index.js.map +1 -1
- package/dist/storage/blocks/select.d.ts +29 -0
- package/dist/storage/blocks/select.d.ts.map +1 -0
- package/dist/storage/blocks/select.js +120 -0
- package/dist/storage/blocks/select.js.map +1 -0
- package/dist/storage/blocks/types.d.ts +7 -0
- package/dist/storage/blocks/types.d.ts.map +1 -1
- package/dist/storage/index/manager.d.ts +30 -2
- package/dist/storage/index/manager.d.ts.map +1 -1
- package/dist/storage/index/schema.d.ts +1 -1
- package/dist/storage/index/schema.js +1 -1
- package/dist/storage/index/types.d.ts +6 -1
- package/dist/storage/index/types.d.ts.map +1 -1
- package/dist/sync/data-point-feed.d.ts +35 -0
- package/dist/sync/data-point-feed.d.ts.map +1 -0
- package/dist/sync/data-point-feed.js +122 -0
- package/dist/sync/data-point-feed.js.map +1 -0
- package/dist/sync/data-point-id.d.ts +7 -0
- package/dist/sync/data-point-id.d.ts.map +1 -0
- package/dist/sync/data-point-id.js +16 -0
- package/dist/sync/data-point-id.js.map +1 -0
- package/dist/sync/delete-data-port.d.ts +50 -0
- package/dist/sync/delete-data-port.d.ts.map +1 -0
- package/dist/sync/delete-data-port.js +244 -0
- package/dist/sync/delete-data-port.js.map +1 -0
- package/dist/sync/engine/sync-manager.d.ts +16 -4
- package/dist/sync/engine/sync-manager.d.ts.map +1 -1
- package/dist/sync/engine/sync-manager.js +93 -32
- package/dist/sync/engine/sync-manager.js.map +1 -1
- package/dist/sync/index.d.ts +7 -0
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +7 -0
- package/dist/sync/index.js.map +1 -1
- package/dist/sync/issues.d.ts.map +1 -1
- package/dist/sync/issues.js +21 -3
- package/dist/sync/issues.js.map +1 -1
- package/dist/sync/pending-blob-deletions.d.ts +29 -0
- package/dist/sync/pending-blob-deletions.d.ts.map +1 -0
- package/dist/sync/pending-blob-deletions.js +116 -0
- package/dist/sync/pending-blob-deletions.js.map +1 -0
- package/dist/sync/retry-memory.d.ts +33 -0
- package/dist/sync/retry-memory.d.ts.map +1 -0
- package/dist/sync/retry-memory.js +68 -0
- package/dist/sync/retry-memory.js.map +1 -0
- package/dist/sync/scope-deletions.d.ts +155 -0
- package/dist/sync/scope-deletions.d.ts.map +1 -0
- package/dist/sync/scope-deletions.js +225 -0
- package/dist/sync/scope-deletions.js.map +1 -0
- package/dist/sync/tombstone.d.ts +27 -0
- package/dist/sync/tombstone.d.ts.map +1 -0
- package/dist/sync/tombstone.js +28 -0
- package/dist/sync/tombstone.js.map +1 -0
- package/dist/sync/workers/delete.d.ts +152 -31
- package/dist/sync/workers/delete.d.ts.map +1 -1
- package/dist/sync/workers/delete.js +457 -55
- package/dist/sync/workers/delete.js.map +1 -1
- package/dist/sync/workers/download.d.ts +108 -27
- package/dist/sync/workers/download.d.ts.map +1 -1
- package/dist/sync/workers/download.js +321 -126
- package/dist/sync/workers/download.js.map +1 -1
- package/dist/sync/workers/upload.d.ts +65 -10
- package/dist/sync/workers/upload.d.ts.map +1 -1
- package/dist/sync/workers/upload.js +371 -42
- package/dist/sync/workers/upload.js.map +1 -1
- package/dist/test-utils/e2ee-fixtures.d.ts +94 -0
- package/dist/test-utils/e2ee-fixtures.d.ts.map +1 -0
- package/dist/test-utils/e2ee-fixtures.js +116 -0
- package/dist/test-utils/e2ee-fixtures.js.map +1 -0
- package/dist/test-utils/e2ee-gateway.d.ts +61 -0
- package/dist/test-utils/e2ee-gateway.d.ts.map +1 -0
- package/dist/test-utils/e2ee-gateway.js +248 -0
- package/dist/test-utils/e2ee-gateway.js.map +1 -0
- package/dist/test-utils/index.d.ts +3 -0
- package/dist/test-utils/index.d.ts.map +1 -1
- package/dist/test-utils/index.js +3 -0
- package/dist/test-utils/index.js.map +1 -1
- package/dist/test-utils/memory-storage.d.ts +12 -0
- package/dist/test-utils/memory-storage.d.ts.map +1 -0
- package/dist/test-utils/memory-storage.js +146 -0
- package/dist/test-utils/memory-storage.js.map +1 -0
- package/dist/test-utils/wallet.d.ts +6 -0
- package/dist/test-utils/wallet.d.ts.map +1 -1
- package/dist/test-utils/wallet.js +46 -0
- package/dist/test-utils/wallet.js.map +1 -1
- package/dist/write/attribution.d.ts +185 -0
- package/dist/write/attribution.d.ts.map +1 -0
- package/dist/write/attribution.js +450 -0
- package/dist/write/attribution.js.map +1 -0
- package/dist/write/authorize.d.ts +77 -0
- package/dist/write/authorize.d.ts.map +1 -0
- package/dist/write/authorize.js +109 -0
- package/dist/write/authorize.js.map +1 -0
- package/dist/write/index.d.ts +5 -0
- package/dist/write/index.d.ts.map +1 -0
- package/dist/write/index.js +5 -0
- package/dist/write/index.js.map +1 -0
- package/dist/write/route.d.ts +61 -0
- package/dist/write/route.d.ts.map +1 -0
- package/dist/write/route.js +132 -0
- package/dist/write/route.js.map +1 -0
- package/dist/write/session.d.ts +96 -0
- package/dist/write/session.d.ts.map +1 -0
- package/dist/write/session.js +136 -0
- package/dist/write/session.js.map +1 -0
- package/package.json +19 -3
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Question registration: input validation, the naming rule (shared with the
|
|
3
|
+
* lineage write path) and the cycle guard that keeps recompute-on-refresh
|
|
4
|
+
* bounded.
|
|
5
|
+
*/
|
|
6
|
+
import { DerivativeCycleError, DerivativeQuestionInvalidError, } from "../errors/catalog.js";
|
|
7
|
+
import { parseDataScopeContract } from "../contracts/data.js";
|
|
8
|
+
import { assertDerivedScopeNaming } from "../lineage/lineage.js";
|
|
9
|
+
import { isWriteScopeEntry } from "../policy/data-write.js";
|
|
10
|
+
import { scopeCoveredByGrant } from "@opendatalabs/vana-sdk/browser";
|
|
11
|
+
export const MAX_QUESTION_SOURCE_SCOPES = 16;
|
|
12
|
+
export const MAX_QUESTION_CHARS = 8_000;
|
|
13
|
+
export const MAX_MODEL_CHARS = 128;
|
|
14
|
+
/** Unvalidated scope strings echoed in 400 details are cut to this. */
|
|
15
|
+
export const MAX_ECHOED_SCOPE_CHARS = 128;
|
|
16
|
+
/** Model ids as providers spell them (`z-ai/glm-5.2`, `gpt-4o-mini`, ...). */
|
|
17
|
+
const MODEL_ID = /^[A-Za-z0-9][A-Za-z0-9._:/-]*$/;
|
|
18
|
+
function isRecord(value) {
|
|
19
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
20
|
+
}
|
|
21
|
+
function parseScope(value, field) {
|
|
22
|
+
if (typeof value !== "string") {
|
|
23
|
+
throw new DerivativeQuestionInvalidError(`${field} must be a scope string`, {
|
|
24
|
+
field,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
const parsed = parseDataScopeContract(value);
|
|
28
|
+
if (!parsed.ok) {
|
|
29
|
+
throw new DerivativeQuestionInvalidError(`${field} is not a valid scope: ${parsed.body.message}`, { field, scope: value.slice(0, MAX_ECHOED_SCOPE_CHARS) });
|
|
30
|
+
}
|
|
31
|
+
return parsed.scope;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Validate the POST body of a question registration. Throws 400
|
|
35
|
+
* DERIVATIVE_QUESTION_INVALID on shape errors and 400
|
|
36
|
+
* LINEAGE_SCOPE_UNDER_SOURCE_PREFIX when the derived scope shares its
|
|
37
|
+
* first segment with a source (the lineage naming rule, applied up front
|
|
38
|
+
* so a registration that could never be written is refused here).
|
|
39
|
+
*/
|
|
40
|
+
export function parseQuestionInput(body) {
|
|
41
|
+
if (!isRecord(body)) {
|
|
42
|
+
throw new DerivativeQuestionInvalidError("Body must be a JSON object");
|
|
43
|
+
}
|
|
44
|
+
const derivedScope = parseScope(body.derivedScope, "derivedScope");
|
|
45
|
+
if (!Array.isArray(body.sourceScopes) || body.sourceScopes.length === 0) {
|
|
46
|
+
throw new DerivativeQuestionInvalidError("sourceScopes must be a non-empty array of scopes", { field: "sourceScopes" });
|
|
47
|
+
}
|
|
48
|
+
if (body.sourceScopes.length > MAX_QUESTION_SOURCE_SCOPES) {
|
|
49
|
+
throw new DerivativeQuestionInvalidError(`sourceScopes lists ${body.sourceScopes.length} scopes; the maximum is ${MAX_QUESTION_SOURCE_SCOPES}`, { field: "sourceScopes", max: MAX_QUESTION_SOURCE_SCOPES });
|
|
50
|
+
}
|
|
51
|
+
const sourceScopes = [];
|
|
52
|
+
for (const entry of body.sourceScopes) {
|
|
53
|
+
const scope = parseScope(entry, "sourceScopes[]");
|
|
54
|
+
if (sourceScopes.includes(scope)) {
|
|
55
|
+
throw new DerivativeQuestionInvalidError("sourceScopes lists the same scope twice", { field: "sourceScopes", duplicate: scope });
|
|
56
|
+
}
|
|
57
|
+
if (scope === derivedScope) {
|
|
58
|
+
throw new DerivativeQuestionInvalidError("derivedScope cannot be one of its own sources", { field: "sourceScopes", scope });
|
|
59
|
+
}
|
|
60
|
+
sourceScopes.push(scope);
|
|
61
|
+
}
|
|
62
|
+
if (typeof body.question !== "string" || body.question.trim() === "") {
|
|
63
|
+
throw new DerivativeQuestionInvalidError("question must be a non-empty string", { field: "question" });
|
|
64
|
+
}
|
|
65
|
+
if (body.question.length > MAX_QUESTION_CHARS) {
|
|
66
|
+
throw new DerivativeQuestionInvalidError(`question is ${body.question.length} characters; the maximum is ${MAX_QUESTION_CHARS}`, { field: "question", max: MAX_QUESTION_CHARS });
|
|
67
|
+
}
|
|
68
|
+
let model = null;
|
|
69
|
+
if (body.model !== undefined && body.model !== null) {
|
|
70
|
+
if (typeof body.model !== "string" ||
|
|
71
|
+
body.model.length > MAX_MODEL_CHARS ||
|
|
72
|
+
!MODEL_ID.test(body.model)) {
|
|
73
|
+
throw new DerivativeQuestionInvalidError("model must be a provider model id", { field: "model" });
|
|
74
|
+
}
|
|
75
|
+
model = body.model;
|
|
76
|
+
}
|
|
77
|
+
// Absent (or null, like model) keeps the original follow-every-change
|
|
78
|
+
// behavior, so existing builder clients register exactly as before.
|
|
79
|
+
let recompute = "on-change";
|
|
80
|
+
if (body.recompute !== undefined && body.recompute !== null) {
|
|
81
|
+
if (body.recompute !== "snapshot" && body.recompute !== "on-change") {
|
|
82
|
+
throw new DerivativeQuestionInvalidError('recompute must be "snapshot" or "on-change"', { field: "recompute" });
|
|
83
|
+
}
|
|
84
|
+
recompute = body.recompute;
|
|
85
|
+
}
|
|
86
|
+
assertDerivedScopeNaming(derivedScope, sourceScopes);
|
|
87
|
+
return {
|
|
88
|
+
derivedScope,
|
|
89
|
+
sourceScopes,
|
|
90
|
+
question: body.question,
|
|
91
|
+
model,
|
|
92
|
+
recompute,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Recompute-on-refresh follows edges source -> derived. A registration whose
|
|
97
|
+
* derived scope can reach one of its own sources through other
|
|
98
|
+
* registrations would recompute forever (A from B, B from A). Refuse it.
|
|
99
|
+
* Returns the offending path (derived scope back to itself) or null.
|
|
100
|
+
*/
|
|
101
|
+
export function findDerivationCycle(candidate, existing) {
|
|
102
|
+
// derived scope -> the scopes it is computed from
|
|
103
|
+
const sourcesOf = new Map();
|
|
104
|
+
const add = (derived, sources) => {
|
|
105
|
+
const set = sourcesOf.get(derived) ?? new Set();
|
|
106
|
+
for (const source of sources)
|
|
107
|
+
set.add(source);
|
|
108
|
+
sourcesOf.set(derived, set);
|
|
109
|
+
};
|
|
110
|
+
for (const registration of existing) {
|
|
111
|
+
add(registration.derivedScope, registration.sourceScopes);
|
|
112
|
+
}
|
|
113
|
+
add(candidate.derivedScope, candidate.sourceScopes);
|
|
114
|
+
// Depth-first from the candidate's derived scope through the sources it
|
|
115
|
+
// depends on; reaching the derived scope again is a cycle. Bounded by the
|
|
116
|
+
// number of distinct scopes (visited set), so no unbounded walk.
|
|
117
|
+
const target = candidate.derivedScope;
|
|
118
|
+
const visited = new Set();
|
|
119
|
+
const stack = [
|
|
120
|
+
{ scope: target, path: [target] },
|
|
121
|
+
];
|
|
122
|
+
while (stack.length > 0) {
|
|
123
|
+
const { scope, path } = stack.pop();
|
|
124
|
+
for (const source of sourcesOf.get(scope) ?? []) {
|
|
125
|
+
if (source === target)
|
|
126
|
+
return [...path, source];
|
|
127
|
+
if (visited.has(source))
|
|
128
|
+
continue;
|
|
129
|
+
visited.add(source);
|
|
130
|
+
stack.push({ scope: source, path: [...path, source] });
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Validate, guard against cycles and persist a registration in `pending`.
|
|
137
|
+
* The caller has already authorized the write on `derivedScope`.
|
|
138
|
+
*/
|
|
139
|
+
export async function createQuestionRegistration(input) {
|
|
140
|
+
const parsed = parseQuestionInput(input.body);
|
|
141
|
+
const cycle = findDerivationCycle(parsed, await input.store.list());
|
|
142
|
+
if (cycle) {
|
|
143
|
+
throw new DerivativeCycleError({
|
|
144
|
+
derivedScope: parsed.derivedScope,
|
|
145
|
+
path: cycle,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
const at = input.now().toISOString();
|
|
149
|
+
const registration = {
|
|
150
|
+
questionId: input.questionId,
|
|
151
|
+
derivedScope: parsed.derivedScope,
|
|
152
|
+
sourceScopes: parsed.sourceScopes,
|
|
153
|
+
question: parsed.question,
|
|
154
|
+
model: parsed.model,
|
|
155
|
+
recompute: parsed.recompute,
|
|
156
|
+
registeredBy: input.registeredBy,
|
|
157
|
+
status: "pending",
|
|
158
|
+
error: null,
|
|
159
|
+
errorCode: null,
|
|
160
|
+
createdAt: at,
|
|
161
|
+
updatedAt: at,
|
|
162
|
+
lastComputedAt: null,
|
|
163
|
+
derivedVersion: null,
|
|
164
|
+
derivedCollectedAt: null,
|
|
165
|
+
};
|
|
166
|
+
await input.store.insert(registration);
|
|
167
|
+
return registration;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Consent check for a builder question: the sources feed a prompt whose
|
|
171
|
+
* answer the builder reads, so every source scope must be covered by a READ
|
|
172
|
+
* entry of the builder's grant (bare entries; `write:` entries confer
|
|
173
|
+
* nothing). Returns the uncovered scopes; empty = all covered. The system
|
|
174
|
+
* prompt is not a security boundary, this is.
|
|
175
|
+
*/
|
|
176
|
+
export function uncoveredSourceScopes(sourceScopes, grantScopes) {
|
|
177
|
+
const readEntries = (grantScopes ?? []).filter((entry) => !isWriteScopeEntry(entry));
|
|
178
|
+
return sourceScopes.filter((scope) => !scopeCoveredByGrant(scope, readEntries));
|
|
179
|
+
}
|
|
180
|
+
//# sourceMappingURL=registration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registration.js","sourceRoot":"","sources":["../../src/derivatives/registration.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recompute scheduler: marks registrations stale when one of their source
|
|
3
|
+
* scopes changes and runs the compute after a quiet period, one in flight
|
|
4
|
+
* per question. Timers and the clock are injectable so tests drive it
|
|
5
|
+
* deterministically.
|
|
6
|
+
*/
|
|
7
|
+
import type { ComputeOutcome } from "./compute.js";
|
|
8
|
+
import type { QuestionStore } from "./types.js";
|
|
9
|
+
export interface SchedulerTimers {
|
|
10
|
+
setTimeout(callback: () => void, ms: number): unknown;
|
|
11
|
+
clearTimeout(handle: unknown): void;
|
|
12
|
+
}
|
|
13
|
+
export interface RecomputeSchedulerOptions {
|
|
14
|
+
store: QuestionStore;
|
|
15
|
+
/**
|
|
16
|
+
* Runs one compute; must not throw for compute failures. The outcome is
|
|
17
|
+
* what the retry logic classifies — a wrapper that swallows it (returns
|
|
18
|
+
* void) silently disables automatic retries, so forward it.
|
|
19
|
+
*/
|
|
20
|
+
compute(questionId: string): Promise<ComputeOutcome | void>;
|
|
21
|
+
/** Quiet period after a source change (default 5s). */
|
|
22
|
+
debounceMs?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Backoff steps for automatic retry of a compute that failed with the
|
|
25
|
+
* transient class (`errorCode: "inference_unavailable"`). Default
|
|
26
|
+
* 1m, 5m, 30m; empty disables retry. Non-transient failures never
|
|
27
|
+
* retry — they wait for a source change or an explicit recompute.
|
|
28
|
+
*/
|
|
29
|
+
retryDelaysMs?: readonly number[];
|
|
30
|
+
/**
|
|
31
|
+
* Lets `markSourceChanged` recognise a new version that was computed FROM
|
|
32
|
+
* a question's own derived scope (its `$lineage` names the derived data
|
|
33
|
+
* point id) and skip that question: a cross-replica cycle then settles
|
|
34
|
+
* instead of ping-ponging through sync. Without it every change counts.
|
|
35
|
+
*/
|
|
36
|
+
serverOwner?: `0x${string}`;
|
|
37
|
+
timers?: SchedulerTimers;
|
|
38
|
+
now?: () => Date;
|
|
39
|
+
logger?: {
|
|
40
|
+
warn?(payload: Record<string, unknown>, message: string): void;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export interface RecomputeScheduler {
|
|
44
|
+
/**
|
|
45
|
+
* A scope got a new local version (ingest or sync). Every registration
|
|
46
|
+
* that lists it as a source is marked `stale` (if it had a result) and
|
|
47
|
+
* scheduled. Fire and forget; failures are logged.
|
|
48
|
+
*/
|
|
49
|
+
markSourceChanged(scope: string, options?: {
|
|
50
|
+
/** `$lineage.sources` of the new version, when it is a derivative. */
|
|
51
|
+
lineageSources?: readonly string[];
|
|
52
|
+
}): void;
|
|
53
|
+
/** Schedule one question; `immediate` skips the debounce (owner recompute). */
|
|
54
|
+
requestRecompute(questionId: string, options?: {
|
|
55
|
+
immediate?: boolean;
|
|
56
|
+
}): void;
|
|
57
|
+
/**
|
|
58
|
+
* Next scheduled automatic retry for a failed question (ISO time), or
|
|
59
|
+
* null when none is pending. In-memory, like the rest of the scheduler:
|
|
60
|
+
* a restart drops the chain and the question waits for a source change
|
|
61
|
+
* or an explicit recompute.
|
|
62
|
+
*/
|
|
63
|
+
nextRetryAt(questionId: string): string | null;
|
|
64
|
+
/**
|
|
65
|
+
* True while a retry compute is actually RUNNING (its timer fired, the
|
|
66
|
+
* compute has not settled). `nextRetryAt` is null in that window; without
|
|
67
|
+
* this signal a status reader would see the terminal
|
|
68
|
+
* failed-with-no-retry signature during every in-flight retry.
|
|
69
|
+
*/
|
|
70
|
+
retryInFlight(questionId: string): boolean;
|
|
71
|
+
/** Resolves once no compute or store lookup is pending or timed. */
|
|
72
|
+
whenIdle(): Promise<void>;
|
|
73
|
+
/** Cancel pending timers. In-flight computes finish on their own. */
|
|
74
|
+
stop(): void;
|
|
75
|
+
/**
|
|
76
|
+
* Schedule every question left `pending` or `stale` (immediately). The
|
|
77
|
+
* first call is the boot reschedule: a restarted process only holds what
|
|
78
|
+
* the store holds, and those questions have no other way back onto a
|
|
79
|
+
* timer. Later calls resume after `stop()`; idempotent while running.
|
|
80
|
+
*/
|
|
81
|
+
start(): void;
|
|
82
|
+
}
|
|
83
|
+
export declare function createRecomputeScheduler(options: RecomputeSchedulerOptions): RecomputeScheduler;
|
|
84
|
+
//# sourceMappingURL=scheduler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../../src/derivatives/scheduler.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,WAAW,eAAe;IAC9B,UAAU,CAAC,QAAQ,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;IACtD,YAAY,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC;CACrC;AAED,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,aAAa,CAAC;IACrB;;;;OAIG;IACH,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAC5D,uDAAuD;IACvD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,KAAK,MAAM,EAAE,CAAC;IAC5B,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;IACjB,MAAM,CAAC,EAAE;QACP,IAAI,CAAC,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;KAChE,CAAC;CACH;AAED,MAAM,WAAW,kBAAkB;IACjC;;;;OAIG;IACH,iBAAiB,CACf,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE;QACR,sEAAsE;QACtE,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;KACpC,GACA,IAAI,CAAC;IACR,+EAA+E;IAC/E,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IAC9E;;;;;OAKG;IACH,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC/C;;;;;OAKG;IACH,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3C,oEAAoE;IACpE,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,qEAAqE;IACrE,IAAI,IAAI,IAAI,CAAC;IACb;;;;;OAKG;IACH,KAAK,IAAI,IAAI,CAAC;CACf;AAoDD,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,yBAAyB,GACjC,kBAAkB,CAsQpB"}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recompute scheduler: marks registrations stale when one of their source
|
|
3
|
+
* scopes changes and runs the compute after a quiet period, one in flight
|
|
4
|
+
* per question. Timers and the clock are injectable so tests drive it
|
|
5
|
+
* deterministically.
|
|
6
|
+
*/
|
|
7
|
+
import { computeDataPointId } from "../sync/data-point-id.js";
|
|
8
|
+
const DEFAULT_RETRY_DELAYS_MS = [60_000, 300_000, 1_800_000];
|
|
9
|
+
/**
|
|
10
|
+
* Structural check on the compute callback's resolved value: retry is only
|
|
11
|
+
* for a failed outcome whose stored class is the one transient kind. A
|
|
12
|
+
* callback that resolves anything else (older wiring, tests) never retries.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* A compute skipped because the runtime is unavailable (PS-Lite with the
|
|
16
|
+
* hosting tab inactive). During a retry chain this must not end the chain:
|
|
17
|
+
* nothing else would ever reschedule a failed question.
|
|
18
|
+
*/
|
|
19
|
+
function isRuntimeSkipOutcome(outcome) {
|
|
20
|
+
if (typeof outcome !== "object" || outcome === null)
|
|
21
|
+
return false;
|
|
22
|
+
const shaped = outcome;
|
|
23
|
+
return shaped.status === "skipped" && shaped.reason === "runtime-unavailable";
|
|
24
|
+
}
|
|
25
|
+
function isTransientFailureOutcome(outcome) {
|
|
26
|
+
if (typeof outcome !== "object" || outcome === null)
|
|
27
|
+
return false;
|
|
28
|
+
const shaped = outcome;
|
|
29
|
+
return (shaped.status === "failed" &&
|
|
30
|
+
shaped.registration?.errorCode === "inference_unavailable");
|
|
31
|
+
}
|
|
32
|
+
const defaultTimers = {
|
|
33
|
+
setTimeout: (callback, ms) => setTimeout(callback, ms),
|
|
34
|
+
clearTimeout: (handle) => clearTimeout(handle),
|
|
35
|
+
};
|
|
36
|
+
export function createRecomputeScheduler(options) {
|
|
37
|
+
const debounceMs = options.debounceMs ?? 5_000;
|
|
38
|
+
const retryDelaysMs = options.retryDelaysMs ?? DEFAULT_RETRY_DELAYS_MS;
|
|
39
|
+
const timers = options.timers ?? defaultTimers;
|
|
40
|
+
const now = options.now ?? (() => new Date());
|
|
41
|
+
const states = new Map();
|
|
42
|
+
const pending = new Set();
|
|
43
|
+
let stopped = false;
|
|
44
|
+
// Distinguishes the first start() (which must reschedule) from a
|
|
45
|
+
// redundant one while already running (which must not).
|
|
46
|
+
let started = false;
|
|
47
|
+
function track(promise) {
|
|
48
|
+
pending.add(promise);
|
|
49
|
+
const done = () => pending.delete(promise);
|
|
50
|
+
promise.then(done, done);
|
|
51
|
+
return promise;
|
|
52
|
+
}
|
|
53
|
+
function warn(payload, message) {
|
|
54
|
+
options.logger?.warn?.(payload, message);
|
|
55
|
+
}
|
|
56
|
+
function stateFor(questionId) {
|
|
57
|
+
let state = states.get(questionId);
|
|
58
|
+
if (!state) {
|
|
59
|
+
state = {
|
|
60
|
+
timer: null,
|
|
61
|
+
running: null,
|
|
62
|
+
rerun: false,
|
|
63
|
+
retryAttempts: 0,
|
|
64
|
+
retryAtMs: null,
|
|
65
|
+
retryScheduled: false,
|
|
66
|
+
retryRunning: false,
|
|
67
|
+
};
|
|
68
|
+
states.set(questionId, state);
|
|
69
|
+
}
|
|
70
|
+
return state;
|
|
71
|
+
}
|
|
72
|
+
function run(questionId) {
|
|
73
|
+
if (stopped)
|
|
74
|
+
return;
|
|
75
|
+
const state = stateFor(questionId);
|
|
76
|
+
if (state.running) {
|
|
77
|
+
// One in flight per question; a change during the run means the
|
|
78
|
+
// result is already stale, so run once more when it finishes.
|
|
79
|
+
state.rerun = true;
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
state.running = track(Promise.resolve()
|
|
83
|
+
.then(() => options.compute(questionId))
|
|
84
|
+
.then((outcome) => outcome, (err) => {
|
|
85
|
+
warn({
|
|
86
|
+
questionId,
|
|
87
|
+
error: err instanceof Error ? err.name : String(err),
|
|
88
|
+
}, "Derivative compute threw");
|
|
89
|
+
return undefined;
|
|
90
|
+
})
|
|
91
|
+
.then(async (outcome) => {
|
|
92
|
+
state.running = null;
|
|
93
|
+
state.retryAtMs = null;
|
|
94
|
+
state.retryRunning = false;
|
|
95
|
+
const retryable = isTransientFailureOutcome(outcome) ||
|
|
96
|
+
// A runtime-unavailable skip DURING a chain consumes an attempt
|
|
97
|
+
// instead of ending the chain; on a fresh compute it keeps the
|
|
98
|
+
// pre-retry behavior (pending/stale, reswept by start()).
|
|
99
|
+
(isRuntimeSkipOutcome(outcome) && state.retryAttempts > 0);
|
|
100
|
+
if (state.rerun) {
|
|
101
|
+
// A source changed during the run: that supersedes any retry.
|
|
102
|
+
state.rerun = false;
|
|
103
|
+
state.retryAttempts = 0;
|
|
104
|
+
await markStale(questionId);
|
|
105
|
+
schedule(questionId, 0);
|
|
106
|
+
}
|
|
107
|
+
else if (retryable &&
|
|
108
|
+
// A pending timer means a source change or explicit recompute
|
|
109
|
+
// arrived during the run and already scheduled a fresher run;
|
|
110
|
+
// replacing its 5s debounce with a 60s retry would delay the
|
|
111
|
+
// recompute of data this compute never saw.
|
|
112
|
+
state.timer === null &&
|
|
113
|
+
// After stop() nothing may be scheduled; without this a compute
|
|
114
|
+
// that settles late would record a retryAtMs no timer backs.
|
|
115
|
+
!stopped &&
|
|
116
|
+
state.retryAttempts < retryDelaysMs.length) {
|
|
117
|
+
const delayMs = retryDelaysMs[state.retryAttempts];
|
|
118
|
+
state.retryAttempts += 1;
|
|
119
|
+
state.retryAtMs = now().getTime() + delayMs;
|
|
120
|
+
schedule(questionId, delayMs);
|
|
121
|
+
state.retryScheduled = true;
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
state.retryAttempts = 0;
|
|
125
|
+
if (!states.get(questionId)?.timer) {
|
|
126
|
+
states.delete(questionId);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}));
|
|
130
|
+
}
|
|
131
|
+
function schedule(questionId, delayMs) {
|
|
132
|
+
if (stopped)
|
|
133
|
+
return;
|
|
134
|
+
const state = stateFor(questionId);
|
|
135
|
+
if (state.timer !== null)
|
|
136
|
+
timers.clearTimeout(state.timer);
|
|
137
|
+
state.timer = timers.setTimeout(() => {
|
|
138
|
+
state.timer = null;
|
|
139
|
+
// Whatever this timer was (debounce or retry), any promised retry is
|
|
140
|
+
// now being consumed: a past retryAtMs would pin the status route's
|
|
141
|
+
// retryAfterSeconds at 0 for the whole in-flight compute. A firing
|
|
142
|
+
// RETRY timer hands over to retryRunning so the in-flight window is
|
|
143
|
+
// still distinguishable from a terminal failure.
|
|
144
|
+
state.retryAtMs = null;
|
|
145
|
+
state.retryRunning = state.retryScheduled;
|
|
146
|
+
state.retryScheduled = false;
|
|
147
|
+
run(questionId);
|
|
148
|
+
}, delayMs);
|
|
149
|
+
}
|
|
150
|
+
async function markStale(questionId) {
|
|
151
|
+
const registration = await options.store.get(questionId);
|
|
152
|
+
if (!registration)
|
|
153
|
+
return;
|
|
154
|
+
if (registration.status === "ready" || registration.status === "failed") {
|
|
155
|
+
await options.store.update(questionId, {
|
|
156
|
+
status: "stale",
|
|
157
|
+
// The reader contract says errorCode is null unless failed; a stale
|
|
158
|
+
// question is a recompute in progress, not a terminal failure.
|
|
159
|
+
errorCode: null,
|
|
160
|
+
updatedAt: now().toISOString(),
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function resetRetry(questionId) {
|
|
165
|
+
const state = states.get(questionId);
|
|
166
|
+
if (!state)
|
|
167
|
+
return;
|
|
168
|
+
state.retryAttempts = 0;
|
|
169
|
+
state.retryAtMs = null;
|
|
170
|
+
state.retryScheduled = false;
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
nextRetryAt(questionId) {
|
|
174
|
+
const retryAtMs = states.get(questionId)?.retryAtMs;
|
|
175
|
+
return retryAtMs == null ? null : new Date(retryAtMs).toISOString();
|
|
176
|
+
},
|
|
177
|
+
retryInFlight(questionId) {
|
|
178
|
+
return states.get(questionId)?.retryRunning ?? false;
|
|
179
|
+
},
|
|
180
|
+
markSourceChanged(scope, opts) {
|
|
181
|
+
if (stopped)
|
|
182
|
+
return;
|
|
183
|
+
const lineage = new Set((opts?.lineageSources ?? []).map((id) => id.toLowerCase()));
|
|
184
|
+
void track((async () => {
|
|
185
|
+
const affected = await options.store.list({ sourceScope: scope });
|
|
186
|
+
for (const registration of affected) {
|
|
187
|
+
if (registration.recompute === "snapshot") {
|
|
188
|
+
// A snapshot question computes at registration and on an
|
|
189
|
+
// explicit recompute only; source changes never touch it.
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
if (options.serverOwner &&
|
|
193
|
+
lineage.has(computeDataPointId(options.serverOwner, registration.derivedScope))) {
|
|
194
|
+
// The new version descends from this question's own output.
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
await markStale(registration.questionId);
|
|
198
|
+
resetRetry(registration.questionId);
|
|
199
|
+
schedule(registration.questionId, debounceMs);
|
|
200
|
+
}
|
|
201
|
+
})().catch((err) => warn({ scope, error: err instanceof Error ? err.name : String(err) }, "Could not mark derivative questions stale")));
|
|
202
|
+
},
|
|
203
|
+
requestRecompute(questionId, opts) {
|
|
204
|
+
if (stopped)
|
|
205
|
+
return;
|
|
206
|
+
void track(markStale(questionId)
|
|
207
|
+
.catch((err) => warn({
|
|
208
|
+
questionId,
|
|
209
|
+
error: err instanceof Error ? err.name : String(err),
|
|
210
|
+
}, "Could not mark derivative question stale"))
|
|
211
|
+
.then(() => {
|
|
212
|
+
resetRetry(questionId);
|
|
213
|
+
schedule(questionId, opts?.immediate ? 0 : debounceMs);
|
|
214
|
+
}));
|
|
215
|
+
},
|
|
216
|
+
async whenIdle() {
|
|
217
|
+
// Waits for store lookups and in-flight computes. Real timers are
|
|
218
|
+
// polled too; injected timers are the caller's to fire.
|
|
219
|
+
const waitForTimers = !options.timers;
|
|
220
|
+
for (;;) {
|
|
221
|
+
while (pending.size > 0) {
|
|
222
|
+
await Promise.allSettled([...pending]);
|
|
223
|
+
}
|
|
224
|
+
const busy = [...states.values()].some((state) => state.running !== null || (waitForTimers && state.timer !== null));
|
|
225
|
+
if (!busy)
|
|
226
|
+
return;
|
|
227
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
stop() {
|
|
231
|
+
stopped = true;
|
|
232
|
+
for (const state of states.values()) {
|
|
233
|
+
if (state.timer !== null)
|
|
234
|
+
timers.clearTimeout(state.timer);
|
|
235
|
+
state.timer = null;
|
|
236
|
+
state.retryAttempts = 0;
|
|
237
|
+
state.retryAtMs = null;
|
|
238
|
+
state.retryScheduled = false;
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
start() {
|
|
242
|
+
if (started && !stopped)
|
|
243
|
+
return;
|
|
244
|
+
started = true;
|
|
245
|
+
stopped = false;
|
|
246
|
+
void track((async () => {
|
|
247
|
+
for (const registration of await options.store.list()) {
|
|
248
|
+
if (registration.status === "pending" ||
|
|
249
|
+
registration.status === "stale") {
|
|
250
|
+
schedule(registration.questionId, 0);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
})().catch((err) => warn({ error: err instanceof Error ? err.name : String(err) }, "Could not reschedule derivative questions")));
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
//# sourceMappingURL=scheduler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../../src/derivatives/scheduler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { QuestionRegistration, QuestionStore, QuestionStoreListFilter } from "./types.js";
|
|
2
|
+
export declare function matchesQuestionFilter(registration: QuestionRegistration, filter: QuestionStoreListFilter | undefined): boolean;
|
|
3
|
+
/** Oldest first, the order every store lists in. */
|
|
4
|
+
export declare function sortQuestions(registrations: QuestionRegistration[]): QuestionRegistration[];
|
|
5
|
+
/**
|
|
6
|
+
* Map-backed store. The PS-Lite store wraps it with persistence; tests use
|
|
7
|
+
* it as is.
|
|
8
|
+
*/
|
|
9
|
+
export declare function createInMemoryQuestionStore(options?: {
|
|
10
|
+
/** Called after every mutation with the full current list. */
|
|
11
|
+
onChange?: (registrations: QuestionRegistration[]) => Promise<void>;
|
|
12
|
+
initial?: QuestionRegistration[];
|
|
13
|
+
}): QuestionStore;
|
|
14
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/derivatives/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,oBAAoB,EAEpB,aAAa,EACb,uBAAuB,EACxB,MAAM,YAAY,CAAC;AAYpB,wBAAgB,qBAAqB,CACnC,YAAY,EAAE,oBAAoB,EAClC,MAAM,EAAE,uBAAuB,GAAG,SAAS,GAC1C,OAAO,CAkBT;AAED,oDAAoD;AACpD,wBAAgB,aAAa,CAC3B,aAAa,EAAE,oBAAoB,EAAE,GACpC,oBAAoB,EAAE,CAMxB;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,GAAE;IACP,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,CAAC,aAAa,EAAE,oBAAoB,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE,OAAO,CAAC,EAAE,oBAAoB,EAAE,CAAC;CAC7B,GACL,aAAa,CA4Cf"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
function clone(registration) {
|
|
2
|
+
return {
|
|
3
|
+
...registration,
|
|
4
|
+
// Registrations persisted before the field existed load as null.
|
|
5
|
+
errorCode: registration.errorCode ?? null,
|
|
6
|
+
sourceScopes: [...registration.sourceScopes],
|
|
7
|
+
registeredBy: { ...registration.registeredBy },
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export function matchesQuestionFilter(registration, filter) {
|
|
11
|
+
if (!filter)
|
|
12
|
+
return true;
|
|
13
|
+
if (filter.derivedScope && registration.derivedScope !== filter.derivedScope)
|
|
14
|
+
return false;
|
|
15
|
+
if (filter.sourceScope &&
|
|
16
|
+
!registration.sourceScopes.includes(filter.sourceScope))
|
|
17
|
+
return false;
|
|
18
|
+
if (filter.builder) {
|
|
19
|
+
const by = registration.registeredBy;
|
|
20
|
+
if (by.kind !== "builder" ||
|
|
21
|
+
by.builder.toLowerCase() !== filter.builder.toLowerCase())
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
/** Oldest first, the order every store lists in. */
|
|
27
|
+
export function sortQuestions(registrations) {
|
|
28
|
+
return [...registrations].sort((a, b) => a.createdAt.localeCompare(b.createdAt) ||
|
|
29
|
+
a.questionId.localeCompare(b.questionId));
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Map-backed store. The PS-Lite store wraps it with persistence; tests use
|
|
33
|
+
* it as is.
|
|
34
|
+
*/
|
|
35
|
+
export function createInMemoryQuestionStore(options = {}) {
|
|
36
|
+
const byId = new Map();
|
|
37
|
+
for (const registration of options.initial ?? []) {
|
|
38
|
+
byId.set(registration.questionId, clone(registration));
|
|
39
|
+
}
|
|
40
|
+
async function changed() {
|
|
41
|
+
if (!options.onChange)
|
|
42
|
+
return;
|
|
43
|
+
await options.onChange(sortQuestions([...byId.values()]).map(clone));
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
async list(filter) {
|
|
47
|
+
return sortQuestions([...byId.values()].filter((registration) => matchesQuestionFilter(registration, filter))).map(clone);
|
|
48
|
+
},
|
|
49
|
+
async get(questionId) {
|
|
50
|
+
const registration = byId.get(questionId);
|
|
51
|
+
return registration ? clone(registration) : null;
|
|
52
|
+
},
|
|
53
|
+
async insert(registration) {
|
|
54
|
+
if (byId.has(registration.questionId)) {
|
|
55
|
+
throw new Error(`Question ${registration.questionId} is already registered`);
|
|
56
|
+
}
|
|
57
|
+
byId.set(registration.questionId, clone(registration));
|
|
58
|
+
await changed();
|
|
59
|
+
},
|
|
60
|
+
async update(questionId, patch) {
|
|
61
|
+
const current = byId.get(questionId);
|
|
62
|
+
if (!current)
|
|
63
|
+
return null;
|
|
64
|
+
const next = { ...current, ...patch };
|
|
65
|
+
byId.set(questionId, next);
|
|
66
|
+
await changed();
|
|
67
|
+
return clone(next);
|
|
68
|
+
},
|
|
69
|
+
async delete(questionId) {
|
|
70
|
+
const existed = byId.delete(questionId);
|
|
71
|
+
if (existed)
|
|
72
|
+
await changed();
|
|
73
|
+
return existed;
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/derivatives/store.ts"],"names":[],"mappings":"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"}
|