@zackbart/connecta 0.28.1 → 0.29.0
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/CHANGELOG.md +788 -5
- package/README.md +62 -42
- package/bin/connecta.mjs +110 -139
- package/bin/meta-tool-names.d.mts +2 -0
- package/bin/meta-tool-names.mjs +9 -0
- package/bin/version.mjs +3 -0
- package/dist/access-tokens.js +53 -63
- package/dist/activity-request.js +8 -0
- package/dist/activity.d.ts +76 -42
- package/dist/activity.js +110 -42
- package/dist/auth/clerk-sdk/client.d.ts +65 -0
- package/dist/auth/clerk-sdk/client.js +5 -0
- package/dist/auth/clerk-sdk/generated.js +1845 -0
- package/dist/auth/clerk-transport.js +19 -0
- package/dist/auth/clerk.d.ts +17 -7
- package/dist/auth/clerk.js +189 -107
- package/dist/auth/cloudflare-access.js +25 -26
- package/dist/auth/downstream-client-metadata.d.ts +29 -0
- package/dist/auth/downstream-client-metadata.js +133 -0
- package/dist/auth/downstream-oauth.js +1584 -1580
- package/dist/auth/downstream-revocation.js +106 -0
- package/dist/auth/oauth-token-response.js +231 -0
- package/dist/auth/oauth-v2-migration.js +116 -0
- package/dist/auth/static-oauth.js +93 -71
- package/dist/auth-elicitation.js +283 -0
- package/dist/branding.d.ts +8 -16
- package/dist/branding.js +26 -59
- package/dist/byte-read-response.js +39 -0
- package/dist/call-admission.js +12 -16
- package/dist/catalog-cache.js +739 -0
- package/dist/catalog-drift.d.ts +50 -15
- package/dist/catalog-drift.js +365 -128
- package/dist/catalog-fingerprint.js +2 -4
- package/dist/catalog-freshness.js +13 -0
- package/dist/catalog-limits.js +2 -2
- package/dist/catalog-service.js +368 -224
- package/dist/catalog.js +43 -84
- package/dist/concurrency.js +3 -4
- package/dist/config-defaults.d.ts +39 -0
- package/dist/config-defaults.js +46 -0
- package/dist/config-schema.d.ts +181 -0
- package/dist/config-schema.js +435 -0
- package/dist/config-value-sources.js +102 -0
- package/dist/config.d.ts +631 -0
- package/dist/config.js +503 -0
- package/dist/connector-access.d.ts +1 -0
- package/dist/connector-access.js +13 -15
- package/dist/connector-caller.d.ts +8 -0
- package/dist/connector-caller.js +15 -6
- package/dist/connector-scope.js +1 -1
- package/dist/connectors/api-connector.d.ts +59 -7
- package/dist/connectors/api-connector.js +106 -19
- package/dist/connectors/api.js +4 -1
- package/dist/connectors/guarded-fetch.d.ts +144 -0
- package/dist/connectors/guarded-fetch.js +84 -35
- package/dist/connectors/negotiation-cache.js +46 -0
- package/dist/connectors/option-shapes.js +27 -0
- package/dist/connectors/remote-mcp.d.ts +30 -4
- package/dist/connectors/remote-mcp.js +1774 -320
- package/dist/connectors/resource-uri.js +335 -0
- package/dist/credential-contract.d.ts +10 -0
- package/dist/credential-rules.js +1 -3
- package/dist/credential-url.js +108 -0
- package/dist/credentials.d.ts +6 -1
- package/dist/credentials.js +27 -18
- package/dist/d1.d.ts +130 -0
- package/dist/d1.js +290 -0
- package/dist/describe-config.d.ts +165 -0
- package/dist/describe-config.js +315 -0
- package/dist/described.js +106 -0
- package/dist/downstream-input-context.js +119 -0
- package/dist/downstream-input.js +323 -0
- package/dist/errors.d.ts +97 -20
- package/dist/errors.js +158 -80
- package/dist/execute.js +453 -305
- package/dist/executor-admission.d.ts +1 -0
- package/dist/executor-admission.js +11 -15
- package/dist/executor-contract.d.ts +20 -0
- package/dist/executor-contract.js +48 -0
- package/dist/executor-result.js +24 -9
- package/dist/executors/quickjs-child.js +3 -4
- package/dist/executors/quickjs-runtime.js +147 -59
- package/dist/executors/quickjs.js +29 -35
- package/dist/favicon.js +1 -2
- package/dist/fix-prompt.js +1 -3
- package/dist/guest-runtime.js +188 -0
- package/dist/guest-types.d.ts +176 -0
- package/dist/guest-types.js +1 -0
- package/dist/html-security.js +24 -0
- package/dist/identity.js +1 -3
- package/dist/inbound-credential.js +52 -0
- package/dist/index.d.ts +24 -256
- package/dist/index.js +65 -478
- package/dist/invocation-auth.js +69 -0
- package/dist/invocation.js +230 -73
- package/dist/json-schema.d.ts +1 -1
- package/dist/mcp-client-context.js +35 -0
- package/dist/mcp-result.js +11 -7
- package/dist/meta-output.js +76 -0
- package/dist/meta-tool-names.d.ts +2 -0
- package/dist/meta-tool-names.js +3 -0
- package/dist/meta-tools.js +285 -209
- package/dist/module-contracts.d.ts +8 -23
- package/dist/node.d.ts +0 -2
- package/dist/node.js +2 -2
- package/dist/oauth-callback-outcome.js +1 -3
- package/dist/oauth-handoff.js +117 -0
- package/dist/oauth-sealing.js +20 -0
- package/dist/operator-record.d.ts +137 -0
- package/dist/operator-record.js +420 -0
- package/dist/operator-ui/assets.js +31 -0
- package/dist/operator-ui/contract.d.ts +72 -0
- package/dist/operator-ui/contract.js +1 -0
- package/dist/operator-ui/fix-prompts.d.ts +18 -0
- package/dist/operator-ui/generated.js +5 -4
- package/dist/operator-ui/model.d.ts +12 -87
- package/dist/operator-ui/model.js +2 -9
- package/dist/operator-ui/setup-commands.js +1 -3
- package/dist/operator-ui/view.d.ts +323 -0
- package/dist/operator-ui/view.js +62 -189
- package/dist/page-styles.js +2 -3
- package/dist/program-source.js +14 -2
- package/dist/{exempt-writes.js → program-writes.js} +55 -18
- package/dist/provider.d.ts +142 -0
- package/dist/provider.js +187 -0
- package/dist/providers/_shared/google/result-size.js +57 -0
- package/dist/providers/_shared/google/service-account.d.ts +58 -0
- package/dist/providers/_shared/google/service-account.js +500 -0
- package/dist/providers/_shared/google/workspace.d.ts +276 -0
- package/dist/providers/_shared/google/workspace.js +518 -0
- package/dist/providers/{basecamp.d.ts → basecamp/index.d.ts} +6 -28
- package/dist/providers/basecamp/index.js +997 -0
- package/dist/providers/basecamp/skill.generated.js +9 -0
- package/dist/providers/{breeze.d.ts → breeze/index.d.ts} +2 -2
- package/dist/providers/{breeze.js → breeze/index.js} +184 -140
- package/dist/providers/breeze/skill.generated.js +9 -0
- package/dist/providers/{ccb.d.ts → ccb/index.d.ts} +2 -2
- package/dist/providers/{ccb.js → ccb/index.js} +271 -116
- package/dist/providers/ccb/skill.generated.js +9 -0
- package/dist/providers/{cloudflare.d.ts → cloudflare/index.d.ts} +8 -7
- package/dist/providers/{cloudflare.js → cloudflare/index.js} +171 -738
- package/dist/providers/cloudflare/skill.generated.js +10 -0
- package/dist/providers/docs/index.d.ts +54 -0
- package/dist/providers/docs/index.js +1237 -0
- package/dist/providers/docs/skill.generated.js +9 -0
- package/dist/providers/drive/index.d.ts +53 -0
- package/dist/providers/drive/index.js +1523 -0
- package/dist/providers/drive/skill.generated.js +9 -0
- package/dist/providers/forms/index.d.ts +54 -0
- package/dist/providers/forms/index.js +1217 -0
- package/dist/providers/forms/skill.generated.js +11 -0
- package/dist/providers/github/auth.d.ts +32 -0
- package/dist/providers/github/auth.js +361 -0
- package/dist/providers/github/hosted.js +218 -0
- package/dist/providers/github/index.d.ts +13 -0
- package/dist/providers/github/index.js +140 -0
- package/dist/providers/github/key.js +100 -0
- package/dist/providers/github/rest.js +321 -0
- package/dist/providers/github/scope.d.ts +29 -0
- package/dist/providers/github/scope.js +124 -0
- package/dist/providers/github/skill.generated.js +8 -0
- package/dist/providers/gmail/index.d.ts +51 -0
- package/dist/providers/gmail/index.js +1436 -0
- package/dist/providers/gmail/skill.generated.js +9 -0
- package/dist/providers/{linear.d.ts → linear/index.d.ts} +11 -14
- package/dist/providers/linear/index.js +352 -0
- package/dist/providers/linear/skill.generated.js +8 -0
- package/dist/providers/{mixpanel.d.ts → mixpanel/index.d.ts} +5 -10
- package/dist/providers/mixpanel/index.js +416 -0
- package/dist/providers/mixpanel/skill.generated.js +9 -0
- package/dist/providers/{notion.d.ts → notion/index.d.ts} +8 -8
- package/dist/providers/{notion.js → notion/index.js} +226 -239
- package/dist/providers/notion/skill.generated.js +10 -0
- package/dist/providers/{overflow.d.ts → overflow/index.d.ts} +2 -2
- package/dist/providers/{overflow.js → overflow/index.js} +74 -111
- package/dist/providers/overflow/skill.generated.js +8 -0
- package/dist/providers/{planning-center.d.ts → planning-center/index.d.ts} +2 -2
- package/dist/providers/{planning-center.js → planning-center/index.js} +264 -104
- package/dist/providers/planning-center/skill.generated.js +10 -0
- package/dist/providers/{revenuecat.d.ts → revenuecat/index.d.ts} +5 -10
- package/dist/providers/revenuecat/index.js +557 -0
- package/dist/providers/revenuecat/skill.generated.js +11 -0
- package/dist/providers/sheets/index.d.ts +54 -0
- package/dist/providers/sheets/index.js +1237 -0
- package/dist/providers/sheets/skill.generated.js +10 -0
- package/dist/providers/slides/index.d.ts +53 -0
- package/dist/providers/slides/index.js +2645 -0
- package/dist/providers/slides/skill.generated.js +9 -0
- package/dist/providers/{stripe.d.ts → stripe/index.d.ts} +5 -12
- package/dist/providers/stripe/index.js +266 -0
- package/dist/providers/stripe/skill.generated.js +11 -0
- package/dist/providers/{tithely.d.ts → tithely/index.d.ts} +2 -2
- package/dist/providers/{tithely.js → tithely/index.js} +60 -91
- package/dist/providers/tithely/skill.generated.js +8 -0
- package/dist/providers/{vercel.d.ts → vercel/index.d.ts} +8 -8
- package/dist/providers/vercel/index.js +1038 -0
- package/dist/providers/vercel/mcp-ownership.js +212 -0
- package/dist/providers/vercel/skill.generated.js +11 -0
- package/dist/registry.d.ts +108 -171
- package/dist/registry.js +513 -925
- package/dist/request-state.js +21 -0
- package/dist/resource-read.js +100 -0
- package/dist/result-shapes.js +17 -49
- package/dist/retired-toolkits.js +1 -1
- package/dist/routes/access-tokens.js +3 -6
- package/dist/routes/activity-context.js +21 -0
- package/dist/routes/activity-disclosure.js +40 -0
- package/dist/routes/activity.js +59 -21
- package/dist/routes/actor-labels.js +3 -50
- package/dist/routes/connect.js +221 -0
- package/dist/routes/credentials.js +15 -27
- package/dist/routes/mcp.js +356 -130
- package/dist/routes/oauth-client-metadata.js +15 -0
- package/dist/routes/oauth-management.js +45 -107
- package/dist/routes/oauth.js +110 -57
- package/dist/routes/operator.js +3 -3
- package/dist/routes/shared.d.ts +25 -64
- package/dist/routes/shared.js +160 -80
- package/dist/routes/skills.js +30 -0
- package/dist/routes/ui-config.js +225 -0
- package/dist/routes/ui-data.js +40 -35
- package/dist/routes/ui.js +66 -56
- package/dist/runtime/admission.js +4 -2
- package/dist/runtime/call-admission.js +3 -1
- package/dist/runtime/connector-scope.js +1 -4
- package/dist/runtime/run.js +10 -39
- package/dist/runtime/services.js +12 -117
- package/dist/runtime/shared-read.js +1 -3
- package/dist/runtime/storage.js +8 -1
- package/dist/sent-secrets.d.ts +52 -0
- package/dist/sent-secrets.js +474 -0
- package/dist/server.js +28 -71
- package/dist/skills.js +406 -174
- package/dist/sqlite.d.ts +48 -0
- package/dist/sqlite.js +166 -0
- package/dist/storage/keys.js +428 -0
- package/dist/storage/memory.js +7 -0
- package/dist/storage/sql.d.ts +66 -0
- package/dist/storage/sql.js +534 -0
- package/dist/timeout.js +4 -2
- package/dist/tool-name.js +17 -0
- package/dist/tool-safety.d.ts +9 -35
- package/dist/tool-safety.js +20 -40
- package/dist/types.d.ts +320 -54
- package/dist/types.js +0 -1
- package/dist/ui.d.ts +7 -19
- package/dist/ui.js +76 -90
- package/dist/url-safety.js +3 -6
- package/dist/usage-guide.js +137 -0
- package/dist/validate.d.ts +7 -0
- package/dist/validate.js +309 -47
- package/dist/version.d.ts +1 -7
- package/dist/version.js +3 -7
- package/dist/worker.d.ts +11 -0
- package/dist/worker.js +434 -0
- package/documentation/architecture.md +664 -231
- package/documentation/auth.md +936 -546
- package/documentation/code-mode.md +501 -312
- package/documentation/meta-tools.md +449 -200
- package/documentation/operator-tests.md +93 -0
- package/documentation/provider-migration-0.29.md +60 -0
- package/examples/worker/AGENTS.md +34 -12
- package/examples/worker/README.md +286 -255
- package/examples/worker/kv-to-d1.wrangler.jsonc +26 -0
- package/examples/worker/scripts/copy-kv-to-d1.mjs +162 -0
- package/examples/worker/src/connecta.config.ts +151 -0
- package/examples/worker/src/index.ts +14 -197
- package/examples/worker/wrangler.jsonc +15 -35
- package/package.json +120 -54
- package/templates/node/.dockerignore +1 -2
- package/templates/node/.env.example +20 -21
- package/templates/node/AGENTS.md +21 -17
- package/templates/node/Dockerfile +3 -5
- package/templates/node/README.md +133 -58
- package/templates/node/docker-compose.yml +11 -12
- package/templates/node/package.json +4 -2
- package/templates/node/src/connecta.config.ts +119 -0
- package/templates/node/src/index.ts +9 -154
- package/templates/node/src/provision-token.ts +17 -0
- package/AGENTS.md +0 -170
- package/dist/artifacts/connector.d.ts +0 -58
- package/dist/artifacts/connector.js +0 -730
- package/dist/artifacts/document.js +0 -86
- package/dist/artifacts/guide.js +0 -140
- package/dist/artifacts/html-scan.js +0 -329
- package/dist/artifacts/json.js +0 -78
- package/dist/artifacts/kv-store.d.ts +0 -20
- package/dist/artifacts/kv-store.js +0 -140
- package/dist/artifacts/markdown.js +0 -478
- package/dist/artifacts/module.d.ts +0 -37
- package/dist/artifacts/module.js +0 -66
- package/dist/artifacts/operations.d.ts +0 -197
- package/dist/artifacts/operations.js +0 -1002
- package/dist/artifacts/refresh.d.ts +0 -55
- package/dist/artifacts/refresh.js +0 -236
- package/dist/artifacts/routes.js +0 -206
- package/dist/artifacts/types.d.ts +0 -214
- package/dist/artifacts/types.js +0 -22
- package/dist/artifacts/validate.d.ts +0 -62
- package/dist/artifacts/validate.js +0 -651
- package/dist/artifacts.d.ts +0 -6
- package/dist/artifacts.js +0 -9
- package/dist/auth/bearer.d.ts +0 -11
- package/dist/auth/bearer.js +0 -50
- package/dist/providers/basecamp.js +0 -445
- package/dist/providers/linear.js +0 -217
- package/dist/providers/mixpanel.js +0 -247
- package/dist/providers/revenuecat.js +0 -317
- package/dist/providers/stripe.js +0 -253
- package/dist/providers/vercel.js +0 -1253
- package/dist/storage/file.d.ts +0 -15
- package/dist/storage/file.js +0 -455
- package/ethos.md +0 -93
- package/examples/worker/src/cloudflare-kv.ts +0 -43
- package/examples/worker/src/d1-activity-row.ts +0 -155
- package/examples/worker/src/d1-activity.ts +0 -147
- package/examples/worker/src/d1-storage.ts +0 -138
- package/examples/worker/src/r2-artifact-blobs.ts +0 -29
- package/templates/node/src/file-activity.ts +0 -182
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
// The combinators behind ConnectaConfig's one schema (src/config.ts).
|
|
2
|
+
//
|
|
3
|
+
// A schema here is a value that does three jobs at once, so they cannot drift
|
|
4
|
+
// apart: its type parameters produce the TypeScript type a deployment writes
|
|
5
|
+
// (`ConfigInput`) and the resolved shape core reads (`ConfigOutput`); its
|
|
6
|
+
// object shapes drive unknown-key rejection; and each field's `parse` and
|
|
7
|
+
// `absent` carry its validation and its default. There is one validation
|
|
8
|
+
// policy: a value that is present and wrong throws, with its path, before
|
|
9
|
+
// createConnecta does any work (INV-11). Nothing warns and falls back.
|
|
10
|
+
//
|
|
11
|
+
// Deliberately small. Opaque fields (connectors, modules, functions) are
|
|
12
|
+
// checked by identity or shape and passed through untouched, never cloned, so
|
|
13
|
+
// a private-field receiver or a frozen object reaches core as configured.
|
|
14
|
+
/** A structural configuration mistake, thrown before construction does work. */
|
|
15
|
+
export class ConfigError extends TypeError {
|
|
16
|
+
name = "ConfigError";
|
|
17
|
+
}
|
|
18
|
+
function fail(message) {
|
|
19
|
+
throw new ConfigError(message);
|
|
20
|
+
}
|
|
21
|
+
function isPlainObject(value) {
|
|
22
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
23
|
+
}
|
|
24
|
+
function ownValue(value, key) {
|
|
25
|
+
return Object.prototype.hasOwnProperty.call(value, key) ? value[key] : undefined;
|
|
26
|
+
}
|
|
27
|
+
export function whole(options) {
|
|
28
|
+
const kind = options.min === 1 ? "positive" : "non-negative";
|
|
29
|
+
return {
|
|
30
|
+
optional: true,
|
|
31
|
+
parse: (value, path) => {
|
|
32
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < options.min) {
|
|
33
|
+
fail(`${path} must be a ${kind} whole number.`);
|
|
34
|
+
}
|
|
35
|
+
if (options.max !== undefined && value > options.max) {
|
|
36
|
+
fail(`${path} must be at most ${options.max.toLocaleString("en-US")}` + (options.unit ? ` ${options.unit}.` : "."));
|
|
37
|
+
}
|
|
38
|
+
return value;
|
|
39
|
+
},
|
|
40
|
+
absent: () => options.default,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/** A non-negative finite number, fractions allowed. */
|
|
44
|
+
export function seconds(options) {
|
|
45
|
+
return {
|
|
46
|
+
optional: true,
|
|
47
|
+
parse: (value, path) => {
|
|
48
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
49
|
+
fail(`${path} must be a non-negative number of seconds.`);
|
|
50
|
+
}
|
|
51
|
+
return value;
|
|
52
|
+
},
|
|
53
|
+
absent: () => options.default,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export function text() {
|
|
57
|
+
return {
|
|
58
|
+
optional: true,
|
|
59
|
+
parse: (value, path) => {
|
|
60
|
+
if (typeof value !== "string")
|
|
61
|
+
fail(`${path} must be a string.`);
|
|
62
|
+
return value;
|
|
63
|
+
},
|
|
64
|
+
absent: () => undefined,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* A value core checks by shape and passes through untouched: a connector, a
|
|
69
|
+
* module, an executor. `check` throws its own message for a wrong value;
|
|
70
|
+
* `resolve` maps the accepted value (or `undefined`, when omitted) to what
|
|
71
|
+
* core reads.
|
|
72
|
+
*/
|
|
73
|
+
export function opaque(options = {}) {
|
|
74
|
+
const resolve = options.resolve ?? ((value) => value);
|
|
75
|
+
return {
|
|
76
|
+
optional: true,
|
|
77
|
+
parse: (value, path) => {
|
|
78
|
+
options.check?.(value, path);
|
|
79
|
+
return resolve(value);
|
|
80
|
+
},
|
|
81
|
+
absent: () => resolve(undefined),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/** A function, never called, read, or serialized by configuration. */
|
|
85
|
+
export function fn() {
|
|
86
|
+
return opaque({
|
|
87
|
+
check: (value, path) => {
|
|
88
|
+
if (typeof value !== "function")
|
|
89
|
+
fail(`${path} must be a function.`);
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
/** The same field, but a deployment must supply it. */
|
|
94
|
+
export function required(field, message) {
|
|
95
|
+
return {
|
|
96
|
+
...field,
|
|
97
|
+
optional: false,
|
|
98
|
+
parse: field.parse,
|
|
99
|
+
absent: (path) => fail(message ? message(path) : `${path} is required.`),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* A closed object. Its keys are the shape's keys; anything else is refused
|
|
104
|
+
* by the unknown-key walk before any value is read. An omitted object
|
|
105
|
+
* resolves to its children's defaults.
|
|
106
|
+
*/
|
|
107
|
+
export function object(shape) {
|
|
108
|
+
const resolve = (value, path) => {
|
|
109
|
+
const out = {};
|
|
110
|
+
for (const [key, field] of Object.entries(shape)) {
|
|
111
|
+
const child = value === undefined ? undefined : ownValue(value, key);
|
|
112
|
+
const resolved = child === undefined ? field.absent(`${path}.${key}`) : field.parse(child, `${path}.${key}`);
|
|
113
|
+
// An unset field without a default stays absent, as it was configured.
|
|
114
|
+
if (resolved !== undefined)
|
|
115
|
+
out[key] = resolved;
|
|
116
|
+
}
|
|
117
|
+
return Object.freeze(out);
|
|
118
|
+
};
|
|
119
|
+
return {
|
|
120
|
+
optional: true,
|
|
121
|
+
shape,
|
|
122
|
+
parse: (value, path) => {
|
|
123
|
+
if (!isPlainObject(value))
|
|
124
|
+
fail(`${path} must be an object.`);
|
|
125
|
+
return resolve(value, path);
|
|
126
|
+
},
|
|
127
|
+
absent: (path) => resolve(undefined, path),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/** A list whose items each satisfy `item`. */
|
|
131
|
+
export function array(item) {
|
|
132
|
+
return {
|
|
133
|
+
optional: true,
|
|
134
|
+
element: item,
|
|
135
|
+
parse: (value, path) => {
|
|
136
|
+
if (!Array.isArray(value))
|
|
137
|
+
fail(`${path} must be an array.`);
|
|
138
|
+
return Object.freeze(value.map((entry, index) => item.parse(entry, `${path}[${index}]`)));
|
|
139
|
+
},
|
|
140
|
+
absent: () => undefined,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
/** An object keyed by caller-chosen names, each value satisfying `value`. */
|
|
144
|
+
export function record(value) {
|
|
145
|
+
return {
|
|
146
|
+
optional: true,
|
|
147
|
+
element: value,
|
|
148
|
+
keyed: true,
|
|
149
|
+
parse: (input, path) => {
|
|
150
|
+
if (!isPlainObject(input))
|
|
151
|
+
fail(`${path} must be an object.`);
|
|
152
|
+
// Keys are caller-chosen, so `__proto__`, `constructor`, and the like
|
|
153
|
+
// must stay ordinary entries: no prototype to assign through or inherit.
|
|
154
|
+
const out = Object.create(null);
|
|
155
|
+
for (const key of Object.keys(input)) {
|
|
156
|
+
// An explicitly undefined entry is an omitted one, as for any field.
|
|
157
|
+
if (input[key] === undefined)
|
|
158
|
+
continue;
|
|
159
|
+
out[key] = value.parse(input[key], `${path}.${key}`);
|
|
160
|
+
}
|
|
161
|
+
return Object.freeze(out);
|
|
162
|
+
},
|
|
163
|
+
absent: () => undefined,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* A map of caller-chosen names to strings, such as static headers or extra
|
|
168
|
+
* OAuth parameters. The walk copies it through property descriptors, so an
|
|
169
|
+
* accessor is refused by path unrun and a non-string value by path unechoed.
|
|
170
|
+
*/
|
|
171
|
+
export function strings() {
|
|
172
|
+
return { ...record(text()), strings: true };
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* A closed object that carries behaviour, such as an `api()` tool. The walk
|
|
176
|
+
* checks it like any closed object (own keys declared, no own accessor, and
|
|
177
|
+
* no inherited accessor at a declared key) but returns the original rather
|
|
178
|
+
* than a copy, so a prototype method keeps its receiver and a handler that
|
|
179
|
+
* reads `#private` fields still finds them. It may be a class instance.
|
|
180
|
+
*/
|
|
181
|
+
export function instance(field) {
|
|
182
|
+
return { ...field, instance: true };
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* A closed object whose allowed keys depend on a discriminant, such as
|
|
186
|
+
* `remoteMcp({ auth: { type: "headers", headers } })`. An omitted
|
|
187
|
+
* discriminant selects `otherwise` when given; an omitted one without it, an
|
|
188
|
+
* unrecognized one, or a value that is not an object throws with its path and
|
|
189
|
+
* the valid values, so no union falls through to "no case". Only the walk
|
|
190
|
+
* reads variants; the factory validates the remaining values itself.
|
|
191
|
+
*/
|
|
192
|
+
export function variants(key, cases, otherwise) {
|
|
193
|
+
return {
|
|
194
|
+
...opaque(),
|
|
195
|
+
variants: { key, cases, ...(otherwise !== undefined ? { otherwise } : {}) },
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* A closed object for the unknown-key walk only: each named key may hold
|
|
200
|
+
* anything, which its factory validates. For the options of a built-in
|
|
201
|
+
* factory, where `object()` would resolve values the factory already owns.
|
|
202
|
+
*/
|
|
203
|
+
export function keys(...names) {
|
|
204
|
+
const field = opaque();
|
|
205
|
+
return Object.fromEntries(names.map((name) => [name, field]));
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* The closed options of a built-in factory, checked against its options type:
|
|
209
|
+
* a key the type declares but the shape omits, or the reverse, fails to
|
|
210
|
+
* compile, so the runtime walk cannot drift from the published type.
|
|
211
|
+
*/
|
|
212
|
+
export function optionsOf() {
|
|
213
|
+
return (shape) => ({ ...opaque(), shape });
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Refuse a factory's unknown options and accessors before it reads any value,
|
|
217
|
+
* naming each path: `api("crm").maxResultByte`. Returns the options as a
|
|
218
|
+
* plain copy read through property descriptors, which the factory reads in
|
|
219
|
+
* place of what it was given, so no getter or Proxy trap of the caller's runs
|
|
220
|
+
* later either. Custom implementations a factory accepts (stores, handlers,
|
|
221
|
+
* executors) are `opaque()` in its shape and pass through untouched; an
|
|
222
|
+
* object that carries behaviour (an `api()` tool) is `instance()`, checked in
|
|
223
|
+
* place and passed through as given.
|
|
224
|
+
*/
|
|
225
|
+
export function assertKnownOptions(value, path, field) {
|
|
226
|
+
const read = readPlain(value, path, field);
|
|
227
|
+
if (read.unknown.length === 0)
|
|
228
|
+
return read.value;
|
|
229
|
+
return fail(`Unknown option${read.unknown.length === 1 ? "" : "s"}: ${read.unknown.join(", ")}. ` +
|
|
230
|
+
"Check the spelling against the factory's options type.");
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Own property descriptors, read without invoking any accessor. A config
|
|
234
|
+
* object that cannot be inspected (a Proxy whose trap throws, or a revoked
|
|
235
|
+
* one) is refused by path; the trap's own message never surfaces.
|
|
236
|
+
*/
|
|
237
|
+
function inspect(value, path) {
|
|
238
|
+
try {
|
|
239
|
+
const proto = Reflect.getPrototypeOf(value);
|
|
240
|
+
return {
|
|
241
|
+
array: Array.isArray(value),
|
|
242
|
+
plain: proto === null || Reflect.getPrototypeOf(proto) === null,
|
|
243
|
+
proto,
|
|
244
|
+
own: Reflect.ownKeys(value).flatMap((key) => {
|
|
245
|
+
const descriptor = Reflect.getOwnPropertyDescriptor(value, key);
|
|
246
|
+
return descriptor ? [[key, descriptor]] : [];
|
|
247
|
+
}),
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
return fail(`${path} could not be read as plain configuration.`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
/** Whether `Array.isArray` holds, refusing by path a value that cannot say. */
|
|
255
|
+
function isArrayAt(value, path) {
|
|
256
|
+
try {
|
|
257
|
+
return Array.isArray(value);
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
return fail(`${path} could not be read as plain configuration.`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
const isAccessor = (descriptor) => "get" in descriptor || "set" in descriptor;
|
|
264
|
+
/** A data property's value, or `undefined` for an accessor, recorded by path and never run. */
|
|
265
|
+
function dataValue(descriptor, path, state) {
|
|
266
|
+
if (!isAccessor(descriptor))
|
|
267
|
+
return descriptor.value;
|
|
268
|
+
state.refused.push(`${path} must be a plain value, not a getter or setter.`);
|
|
269
|
+
return undefined;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* The descriptor a declared key inherits, read along the prototype chain
|
|
273
|
+
* without invoking an accessor. A chain that cannot be inspected is refused
|
|
274
|
+
* by path like the object itself.
|
|
275
|
+
*/
|
|
276
|
+
function inherited(proto, key, path) {
|
|
277
|
+
const seen = new Set();
|
|
278
|
+
try {
|
|
279
|
+
for (let at = proto; at !== null && !seen.has(at); at = Reflect.getPrototypeOf(at)) {
|
|
280
|
+
seen.add(at);
|
|
281
|
+
const descriptor = Reflect.getOwnPropertyDescriptor(at, key);
|
|
282
|
+
if (descriptor)
|
|
283
|
+
return descriptor;
|
|
284
|
+
}
|
|
285
|
+
return undefined;
|
|
286
|
+
}
|
|
287
|
+
catch {
|
|
288
|
+
return fail(`${path} could not be read as plain configuration.`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Inspect a value a closed object or record slot holds. An array, or a
|
|
293
|
+
* class instance where only plain data belongs, is refused by path before
|
|
294
|
+
* any of its keys is read: `remoteMcp([...])` must not pass for options.
|
|
295
|
+
*/
|
|
296
|
+
function inspectRecord(value, path, field) {
|
|
297
|
+
const inspected = inspect(value, path);
|
|
298
|
+
if (inspected.array)
|
|
299
|
+
fail(`${path} must be an object.`);
|
|
300
|
+
if (!inspected.plain && !field.instance)
|
|
301
|
+
fail(`${path} must be a plain object.`);
|
|
302
|
+
return inspected;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* The case a discriminant selects. A missing one selects `otherwise` when the
|
|
306
|
+
* union has a default; any other missing, unrecognized, or computed
|
|
307
|
+
* discriminant throws with the valid values, so a misspelled
|
|
308
|
+
* `type: "header"` cannot construct as if no case applied. The value itself
|
|
309
|
+
* is never echoed.
|
|
310
|
+
*/
|
|
311
|
+
function selectShape(inspected, path, variants) {
|
|
312
|
+
const at = `${path}.${variants.key}`;
|
|
313
|
+
const valid = Object.keys(variants.cases)
|
|
314
|
+
.map((name) => JSON.stringify(name))
|
|
315
|
+
.join(", ");
|
|
316
|
+
const descriptor = inspected.own.find(([key]) => key === variants.key)?.[1];
|
|
317
|
+
if (descriptor && isAccessor(descriptor))
|
|
318
|
+
fail(`${at} must be a plain value, not a getter or setter.`);
|
|
319
|
+
const discriminant = descriptor?.value;
|
|
320
|
+
if (discriminant === undefined) {
|
|
321
|
+
if (variants.otherwise !== undefined)
|
|
322
|
+
return variants.cases[variants.otherwise];
|
|
323
|
+
return fail(`${at} is required: one of ${valid}.`);
|
|
324
|
+
}
|
|
325
|
+
if (typeof discriminant !== "string" || !Object.hasOwn(variants.cases, discriminant)) {
|
|
326
|
+
return fail(`${at} must be one of ${valid}.`);
|
|
327
|
+
}
|
|
328
|
+
return variants.cases[discriminant];
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Read a configuration value the way the schema sees it: every own key it
|
|
332
|
+
* does not declare, as paths, and the value as a plain copy built only from
|
|
333
|
+
* the property descriptors this walk inspected. Callers resolve that copy,
|
|
334
|
+
* never the original, so no property is read through `[[Get]]` twice or at
|
|
335
|
+
* all: no getter runs, a Proxy's `get` trap never fires, and no value is
|
|
336
|
+
* echoed.
|
|
337
|
+
*
|
|
338
|
+
* Unknown keys anywhere are reported first. With none, a declared key holding
|
|
339
|
+
* an accessor is refused by path, since a plain configuration object has no
|
|
340
|
+
* reason to compute a value, and a getter that throws or changes between
|
|
341
|
+
* reads would break the one-read guarantee. Only own string keys count, so a
|
|
342
|
+
* prototype contributes nothing. A closed object or record slot takes a
|
|
343
|
+
* plain object only; an array or class instance there is refused by path.
|
|
344
|
+
* Arrays are walked by the same rules, the items of an `opaque()` array
|
|
345
|
+
* included (`allowedOrigins`, `auth`); an `instance()` object (an `api()`
|
|
346
|
+
* tool) is checked the same way but returned as given; any other `opaque()`
|
|
347
|
+
* value (a connector, module, executor, storage, or logger) is never entered
|
|
348
|
+
* and passes through as configured. Configuration is operator-authored and
|
|
349
|
+
* trusted: the walk refuses mistakes by path without echoing a value; it is
|
|
350
|
+
* not a sandbox for hostile objects.
|
|
351
|
+
*/
|
|
352
|
+
export function readPlain(value, path, field) {
|
|
353
|
+
const state = { unknown: [], refused: [] };
|
|
354
|
+
const plain = walk(value, path, field, state);
|
|
355
|
+
if (state.unknown.length === 0 && state.refused.length > 0)
|
|
356
|
+
fail(state.refused[0]);
|
|
357
|
+
return { unknown: state.unknown, value: plain };
|
|
358
|
+
}
|
|
359
|
+
function walk(value, path, field, state) {
|
|
360
|
+
if (field.variants &&
|
|
361
|
+
value !== undefined &&
|
|
362
|
+
(typeof value !== "object" || value === null || isArrayAt(value, path))) {
|
|
363
|
+
return fail(`${path} must be an object.`);
|
|
364
|
+
}
|
|
365
|
+
if (typeof value !== "object" || value === null)
|
|
366
|
+
return value;
|
|
367
|
+
if (field.shape || field.variants) {
|
|
368
|
+
const inspected = inspectRecord(value, path, field);
|
|
369
|
+
const shape = field.shape ?? selectShape(inspected, path, field.variants);
|
|
370
|
+
const unknown = inspected.own
|
|
371
|
+
.filter(([key]) => typeof key !== "string" || !Object.hasOwn(shape, key))
|
|
372
|
+
.map(([key]) => `${path}.${String(key)}`)
|
|
373
|
+
.sort();
|
|
374
|
+
if (unknown.length > 0) {
|
|
375
|
+
state.unknown.push(...unknown);
|
|
376
|
+
return undefined;
|
|
377
|
+
}
|
|
378
|
+
const out = {};
|
|
379
|
+
for (const [key, descriptor] of inspected.own) {
|
|
380
|
+
const child = `${path}.${String(key)}`;
|
|
381
|
+
out[key] = walk(dataValue(descriptor, child, state), child, shape[key], state);
|
|
382
|
+
}
|
|
383
|
+
if (!field.instance)
|
|
384
|
+
return out;
|
|
385
|
+
// A declared key the object inherits, such as a class's `handler()`, is
|
|
386
|
+
// read from its prototype when called; it must not be a computed one.
|
|
387
|
+
for (const key of Object.keys(shape)) {
|
|
388
|
+
if (Object.hasOwn(out, key))
|
|
389
|
+
continue;
|
|
390
|
+
const descriptor = inherited(inspected.proto, key, `${path}.${key}`);
|
|
391
|
+
if (descriptor)
|
|
392
|
+
dataValue(descriptor, `${path}.${key}`, state);
|
|
393
|
+
}
|
|
394
|
+
return value;
|
|
395
|
+
}
|
|
396
|
+
if (field.keyed) {
|
|
397
|
+
const inspected = inspectRecord(value, path, field);
|
|
398
|
+
// Keys are caller-chosen, so the copy has no prototype for `__proto__` to reach.
|
|
399
|
+
const out = Object.create(null);
|
|
400
|
+
for (const [key, descriptor] of inspected.own) {
|
|
401
|
+
if (typeof key !== "string" || !descriptor.enumerable)
|
|
402
|
+
continue;
|
|
403
|
+
const child = `${path}.${key}`;
|
|
404
|
+
const entry = dataValue(descriptor, child, state);
|
|
405
|
+
if (field.strings && !isAccessor(descriptor) && typeof entry !== "string") {
|
|
406
|
+
state.refused.push(`${child} must be a string.`);
|
|
407
|
+
}
|
|
408
|
+
out[key] = walk(entry, child, field.element, state);
|
|
409
|
+
}
|
|
410
|
+
return out;
|
|
411
|
+
}
|
|
412
|
+
if (!field.element && !isArrayAt(value, path))
|
|
413
|
+
return value;
|
|
414
|
+
const inspected = inspect(value, path);
|
|
415
|
+
if (!inspected.array)
|
|
416
|
+
return value;
|
|
417
|
+
const element = field.element ?? OPAQUE;
|
|
418
|
+
const out = [];
|
|
419
|
+
for (const [key, descriptor] of inspected.own) {
|
|
420
|
+
if (key === "length") {
|
|
421
|
+
const length = descriptor.value;
|
|
422
|
+
if (Number.isInteger(length) && length >= 0 && length <= 0xffff_ffff) {
|
|
423
|
+
out.length = length;
|
|
424
|
+
}
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
// Only indices are items; any other key on an array is never read.
|
|
428
|
+
if (typeof key !== "string" || String(Number(key) >>> 0) !== key)
|
|
429
|
+
continue;
|
|
430
|
+
const child = `${path}[${key}]`;
|
|
431
|
+
out[Number(key)] = walk(dataValue(descriptor, child, state), child, element, state);
|
|
432
|
+
}
|
|
433
|
+
return out;
|
|
434
|
+
}
|
|
435
|
+
const OPAQUE = opaque();
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/** Walk only serialized keys. Stable connector/pool keys survive viewer filtering. */
|
|
2
|
+
export function configValuePaths(value, path = "config") {
|
|
3
|
+
if (value === null || typeof value !== "object")
|
|
4
|
+
return [path];
|
|
5
|
+
const entries = Object.entries(value);
|
|
6
|
+
if (!entries.length)
|
|
7
|
+
return [path];
|
|
8
|
+
if ("value" in value && "source" in value && entries.length === 2)
|
|
9
|
+
return [path];
|
|
10
|
+
return entries.flatMap(([key, item]) => {
|
|
11
|
+
const stableKey = Array.isArray(value) &&
|
|
12
|
+
(path === "config.connectors" || path === "config.pools") &&
|
|
13
|
+
item &&
|
|
14
|
+
typeof item === "object"
|
|
15
|
+
? String(path === "config.connectors" ? item.id : item.name)
|
|
16
|
+
: key;
|
|
17
|
+
return configValuePaths(item, `${path}.${stableKey}`);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
/** Read only provenance literals; custom describe hooks may throw or return unknown data. */
|
|
21
|
+
function factorySources(factory) {
|
|
22
|
+
try {
|
|
23
|
+
const description = factory?.describe?.();
|
|
24
|
+
if (!description || typeof description !== "object" || !("optionSources" in description))
|
|
25
|
+
return {};
|
|
26
|
+
const sources = description.optionSources;
|
|
27
|
+
if (!sources || typeof sources !== "object")
|
|
28
|
+
return {};
|
|
29
|
+
return Object.fromEntries(Object.entries(sources).filter((entry) => entry[1] === "default" || entry[1] === "config"));
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return {};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function factorySource(sources, parts) {
|
|
36
|
+
for (let length = parts.length; length > 0; length--) {
|
|
37
|
+
const source = sources[parts.slice(0, length).join(".")];
|
|
38
|
+
if (source === "default" || source === "config")
|
|
39
|
+
return source;
|
|
40
|
+
}
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
/** Provenance is presence in code, not equality to a default value. No raw value leaves this function. */
|
|
44
|
+
export function describeConfigSources(snapshot, raw) {
|
|
45
|
+
const sources = {};
|
|
46
|
+
const connectorSources = new Map(raw.connectors.map((connector) => [connector.id, factorySources(connector)]));
|
|
47
|
+
const moduleSources = { accessTokens: factorySources(raw.accessTokens) };
|
|
48
|
+
const provided = (value) => (value === undefined ? "default" : "config");
|
|
49
|
+
for (const path of configValuePaths(snapshot)) {
|
|
50
|
+
const parts = path.split(".").slice(1);
|
|
51
|
+
const [group, key, field] = parts;
|
|
52
|
+
let source = "config";
|
|
53
|
+
if (group === "schemaVersion" || group === "connectaVersion")
|
|
54
|
+
source = "default";
|
|
55
|
+
else if (group === "server")
|
|
56
|
+
source = provided(raw.serverInfo?.[key]);
|
|
57
|
+
else if (group === "urls")
|
|
58
|
+
source = key === "mcpPath" ? "default" : provided(raw[key]);
|
|
59
|
+
else if (group === "trust")
|
|
60
|
+
source = provided(raw.trust);
|
|
61
|
+
else if (group === "identity")
|
|
62
|
+
source = provided(raw.identity?.[key]);
|
|
63
|
+
else if (group === "storage")
|
|
64
|
+
source = provided(raw.storage);
|
|
65
|
+
else if (group === "deploymentInfo")
|
|
66
|
+
source = provided(raw.deploymentInfo);
|
|
67
|
+
else if (group === "classification")
|
|
68
|
+
source = provided(raw.classification);
|
|
69
|
+
else if (group === "pools")
|
|
70
|
+
source = field === "trust" ? provided(raw.pools?.[key]?.trust) : provided(raw.pools);
|
|
71
|
+
else if (group === "modules") {
|
|
72
|
+
const module = key === "ui" ? raw.ui : key === "vault" ? raw.vault : key === "activity" ? raw.activity : raw.accessTokens;
|
|
73
|
+
const factory = moduleSources[key];
|
|
74
|
+
source = (factory ? factorySource(factory, parts.slice(2)) : undefined) ?? provided(module);
|
|
75
|
+
}
|
|
76
|
+
else if (group === "branding") {
|
|
77
|
+
const brand = raw.ui?.branding;
|
|
78
|
+
const brandKey = key === "faviconHref" ? "favicon" : key;
|
|
79
|
+
source =
|
|
80
|
+
key === "theme"
|
|
81
|
+
? provided(brand?.theme?.[field])
|
|
82
|
+
: provided(brand?.[brandKey]);
|
|
83
|
+
}
|
|
84
|
+
else if (group === "connectors") {
|
|
85
|
+
const connector = raw.connectors.find((c) => c.id === key);
|
|
86
|
+
source = factorySource(connectorSources.get(key) ?? {}, parts.slice(2)) ?? source;
|
|
87
|
+
if (field === "authScope")
|
|
88
|
+
source = provided(connector?.authScope);
|
|
89
|
+
else if (field === "maxResultBytes")
|
|
90
|
+
source = provided(connector?.maxResultBytes ?? raw.calls?.maxResultBytes);
|
|
91
|
+
}
|
|
92
|
+
else if (group === "limits" || (group === "executor" && key === "admission")) {
|
|
93
|
+
let limit = snapshot;
|
|
94
|
+
for (const part of parts)
|
|
95
|
+
limit = limit && typeof limit === "object" ? limit[part] : undefined;
|
|
96
|
+
source =
|
|
97
|
+
limit && typeof limit === "object" && "source" in limit && limit.source === "default" ? "default" : "config";
|
|
98
|
+
}
|
|
99
|
+
sources[path] = source;
|
|
100
|
+
}
|
|
101
|
+
return sources;
|
|
102
|
+
}
|