@zackbart/connecta 0.28.0 → 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 +961 -5
- package/README.md +62 -39
- 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 +1632 -1509
- 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 +526 -0
- 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 +220 -0
- package/dist/connectors/api-connector.js +214 -0
- package/dist/connectors/api.d.ts +8 -71
- package/dist/connectors/api.js +13 -99
- package/dist/connectors/guarded-fetch.d.ts +144 -0
- package/dist/connectors/guarded-fetch.js +108 -25
- 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 +1780 -351
- 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 +25 -257
- 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-partition.js +17 -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/index.d.ts +43 -0
- package/dist/providers/basecamp/index.js +997 -0
- package/dist/providers/basecamp/skill.generated.js +9 -0
- package/dist/providers/breeze/index.d.ts +34 -0
- package/dist/providers/breeze/index.js +1446 -0
- package/dist/providers/breeze/skill.generated.js +9 -0
- package/dist/providers/ccb/index.d.ts +103 -0
- package/dist/providers/ccb/index.js +1725 -0
- 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/index.d.ts +60 -0
- package/dist/providers/overflow/index.js +1535 -0
- package/dist/providers/overflow/skill.generated.js +8 -0
- package/dist/providers/planning-center/index.d.ts +59 -0
- package/dist/providers/planning-center/index.js +2199 -0
- 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/index.d.ts +49 -0
- package/dist/providers/tithely/index.js +1003 -0
- 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 +136 -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 +665 -230
- package/documentation/auth.md +962 -458
- 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 +125 -35
- 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/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
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
// The reference ArtifactStore over KVStorage.
|
|
2
|
-
//
|
|
3
|
-
// Layout, under one prefix (default `artifact:`):
|
|
4
|
-
//
|
|
5
|
-
// head:<id> the one mutable record per artifact
|
|
6
|
-
// ver:<id>:<stream>:<n, 10 digits> every version but each stream's latest
|
|
7
|
-
// blob:<sha256> bodies, unless a blob store holds them
|
|
8
|
-
// run:<id>:<startedAt ms>:<runId> refresh run history, newest 50
|
|
9
|
-
//
|
|
10
|
-
// The head is the only key ever compared-and-set, and a write commits by
|
|
11
|
-
// swapping it. Everything else is written before that swap and is either
|
|
12
|
-
// content-addressed (bodies) or immutable once the head that describes it
|
|
13
|
-
// exists (versions), so a plain `set` of the same key twice writes the same
|
|
14
|
-
// bytes twice.
|
|
15
|
-
import { ARTIFACT_RUNS_RETAINED, } from "./types.js";
|
|
16
|
-
const VERSION_DIGITS = 10;
|
|
17
|
-
const pad = (n, digits) => String(n).padStart(digits, "0");
|
|
18
|
-
/**
|
|
19
|
-
* An artifact store over `KVStorage`. The storage must provide `compareAndSet`
|
|
20
|
-
* — a write commits by swapping one head record, and a read followed by a
|
|
21
|
-
* write cannot stand in for that — and `list`, which history and the library
|
|
22
|
-
* page through. Cloudflare Workers KV has neither guarantee and is refused
|
|
23
|
-
* here, at construction, rather than losing a write later.
|
|
24
|
-
*/
|
|
25
|
-
export function kvArtifactStore(kv, options = {}) {
|
|
26
|
-
const { compareAndSet, list } = kv;
|
|
27
|
-
if (typeof compareAndSet !== "function" || typeof list !== "function") {
|
|
28
|
-
throw new TypeError("kvArtifactStore needs storage with compareAndSet and list: every " +
|
|
29
|
-
"artifact write commits by compare-and-set, and Cloudflare Workers KV " +
|
|
30
|
-
"cannot offer one. Use the Worker example's D1 store (d1Storage), " +
|
|
31
|
-
"fileStorage from @zackbart/connecta/node, or memoryStorage in tests.");
|
|
32
|
-
}
|
|
33
|
-
const cas = compareAndSet.bind(kv);
|
|
34
|
-
const keys = list.bind(kv);
|
|
35
|
-
const prefix = options.prefix ?? "artifact:";
|
|
36
|
-
if (typeof prefix !== "string") {
|
|
37
|
-
throw new TypeError("kvArtifactStore: prefix must be a string");
|
|
38
|
-
}
|
|
39
|
-
const blobs = options.blobs;
|
|
40
|
-
const headKey = (id) => `${prefix}head:${id}`;
|
|
41
|
-
const versionPrefix = (id, stream) => `${prefix}ver:${id}:${stream}:`;
|
|
42
|
-
const runPrefix = (id) => `${prefix}run:${id}:`;
|
|
43
|
-
const blobKey = (key) => `${prefix}blob:${key}`;
|
|
44
|
-
const scanKey = `${prefix}refresh:scan-cursor`;
|
|
45
|
-
const readJson = async (key) => {
|
|
46
|
-
const text = await kv.get(key);
|
|
47
|
-
return text === null ? null : JSON.parse(text);
|
|
48
|
-
};
|
|
49
|
-
return {
|
|
50
|
-
async head(id) {
|
|
51
|
-
const token = await kv.get(headKey(id));
|
|
52
|
-
if (token === null)
|
|
53
|
-
return null;
|
|
54
|
-
return { head: JSON.parse(token), token };
|
|
55
|
-
},
|
|
56
|
-
async swapHead(id, expected, next) {
|
|
57
|
-
return cas(headKey(id), expected, JSON.stringify(next));
|
|
58
|
-
},
|
|
59
|
-
async heads({ after, limit }) {
|
|
60
|
-
const base = `${prefix}head:`;
|
|
61
|
-
const ids = (await keys(base))
|
|
62
|
-
.map((key) => key.slice(base.length))
|
|
63
|
-
.filter((id) => after === undefined || id > after);
|
|
64
|
-
const page = ids.slice(0, Math.max(0, limit));
|
|
65
|
-
const heads = [];
|
|
66
|
-
for (const [index, head] of (await Promise.all(page.map((id) => readJson(headKey(id))))).entries()) {
|
|
67
|
-
const id = page[index];
|
|
68
|
-
// A head listed and then gone is not an error: nothing deletes one,
|
|
69
|
-
// but an adapter's listing may lag its reads.
|
|
70
|
-
if (id !== undefined && head !== null)
|
|
71
|
-
heads.push({ id, head });
|
|
72
|
-
}
|
|
73
|
-
const last = page.at(-1);
|
|
74
|
-
return ids.length > page.length && last !== undefined
|
|
75
|
-
? { heads, next: last }
|
|
76
|
-
: { heads };
|
|
77
|
-
},
|
|
78
|
-
async putBody(key, body) {
|
|
79
|
-
if (blobs)
|
|
80
|
-
await blobs.put(key, body);
|
|
81
|
-
else
|
|
82
|
-
await kv.set(blobKey(key), body);
|
|
83
|
-
},
|
|
84
|
-
async body(key) {
|
|
85
|
-
return blobs ? blobs.get(key) : kv.get(blobKey(key));
|
|
86
|
-
},
|
|
87
|
-
async putVersion(id, stream, version, record) {
|
|
88
|
-
await kv.set(`${versionPrefix(id, stream)}${pad(version, VERSION_DIGITS)}`, JSON.stringify(record));
|
|
89
|
-
},
|
|
90
|
-
async versions(id, stream, { below, limit }) {
|
|
91
|
-
const base = versionPrefix(id, stream);
|
|
92
|
-
if (limit <= 0)
|
|
93
|
-
return [];
|
|
94
|
-
let wanted;
|
|
95
|
-
if (below === undefined) {
|
|
96
|
-
// Versions below each stream's latest are dense, but the latest
|
|
97
|
-
// itself lives only in the head, so an unbounded read lists.
|
|
98
|
-
wanted = (await keys(base))
|
|
99
|
-
.map((key) => Number(key.slice(base.length)))
|
|
100
|
-
.filter(Number.isInteger)
|
|
101
|
-
.sort((a, b) => b - a)
|
|
102
|
-
.slice(0, limit);
|
|
103
|
-
}
|
|
104
|
-
else {
|
|
105
|
-
wanted = [];
|
|
106
|
-
for (let n = below - 1; n >= 1 && wanted.length < limit; n--) {
|
|
107
|
-
wanted.push(n);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
const records = await Promise.all(wanted.map((n) => readJson(`${base}${pad(n, VERSION_DIGITS)}`)));
|
|
111
|
-
return records.filter((record) => record !== null);
|
|
112
|
-
},
|
|
113
|
-
async putRun(id, run) {
|
|
114
|
-
const started = Date.parse(run.startedAt);
|
|
115
|
-
const key = `${runPrefix(id)}${pad(Number.isFinite(started) ? started : 0, 15)}:${run.runId}`;
|
|
116
|
-
await kv.set(key, JSON.stringify(run));
|
|
117
|
-
const all = await keys(runPrefix(id));
|
|
118
|
-
const excess = all.length - ARTIFACT_RUNS_RETAINED;
|
|
119
|
-
if (excess > 0) {
|
|
120
|
-
await Promise.all(all.slice(0, excess).map((old) => kv.delete(old)));
|
|
121
|
-
}
|
|
122
|
-
},
|
|
123
|
-
async runs(id, limit) {
|
|
124
|
-
if (limit <= 0)
|
|
125
|
-
return [];
|
|
126
|
-
const newest = (await keys(runPrefix(id))).slice(-limit).reverse();
|
|
127
|
-
const runs = await Promise.all(newest.map((key) => readJson(key)));
|
|
128
|
-
return runs.filter((run) => run !== null);
|
|
129
|
-
},
|
|
130
|
-
async refreshScanCursor() {
|
|
131
|
-
return (await kv.get(scanKey)) ?? undefined;
|
|
132
|
-
},
|
|
133
|
-
async setRefreshScanCursor(after) {
|
|
134
|
-
if (after === undefined)
|
|
135
|
-
await kv.delete(scanKey);
|
|
136
|
-
else
|
|
137
|
-
await kv.set(scanKey, after);
|
|
138
|
-
},
|
|
139
|
-
};
|
|
140
|
-
}
|
|
@@ -1,478 +0,0 @@
|
|
|
1
|
-
// A CommonMark subset for Markdown artifacts: ATX headings, paragraphs,
|
|
2
|
-
// nested lists, fenced code, block quotes, thematic breaks, GFM tables,
|
|
3
|
-
// emphasis, strong, strikethrough, code spans, links, autolinks, images, and
|
|
4
|
-
// hard breaks. Raw HTML is text: a Markdown page carries no markup of its own,
|
|
5
|
-
// so everything it renders is escaped here, and the only URLs that survive are
|
|
6
|
-
// ones a sandboxed page can use.
|
|
7
|
-
//
|
|
8
|
-
// Deliberately not a dependency. The subset is what documents and reports
|
|
9
|
-
// use; the viewer's CSP is the security boundary either way, and a full
|
|
10
|
-
// CommonMark implementation would outweigh the rest of this module.
|
|
11
|
-
//
|
|
12
|
-
// Every scan is bounded: brackets and parentheses are matched once per span
|
|
13
|
-
// of text, a delimiter that found no closer is never searched for again, and
|
|
14
|
-
// nesting stops at a fixed depth. A megabyte of `[[[[` or `**` renders in
|
|
15
|
-
// linear time, because this runs on every page view.
|
|
16
|
-
import { resolveTheme, schemeAttribute, themeCss, } from "../branding.js";
|
|
17
|
-
import { TOKENS_CSS } from "../page-styles.js";
|
|
18
|
-
const escapeHtml = (text) => text.replace(/[&<>"']/g, (c) => c === "&" ? "&" : c === "<" ? "<" : c === ">" ? ">" : c === '"' ? """ : "'");
|
|
19
|
-
const PUNCTUATION = /^[!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~]$/;
|
|
20
|
-
const MAX_INLINE_DEPTH = 8;
|
|
21
|
-
const MAX_BLOCK_DEPTH = 64;
|
|
22
|
-
export class MarkdownNestingError extends Error {
|
|
23
|
-
constructor() {
|
|
24
|
-
super(`Markdown blocks are nested more than ${MAX_BLOCK_DEPTH} levels deep. Flatten the block quotes or lists.`);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
/** A link target a sandboxed page can follow; anything else renders as text. */
|
|
28
|
-
function safeHref(url) {
|
|
29
|
-
const trimmed = url.trim();
|
|
30
|
-
if (trimmed.startsWith("#"))
|
|
31
|
-
return trimmed;
|
|
32
|
-
return /^(?:https:\/\/|mailto:)/i.test(trimmed) ? trimmed : undefined;
|
|
33
|
-
}
|
|
34
|
-
/** Only embedded raster or SVG images load: pages have no network. */
|
|
35
|
-
function safeImage(url) {
|
|
36
|
-
const trimmed = url.trim();
|
|
37
|
-
return /^data:image\/(?:png|gif|jpeg|webp|svg\+xml);base64,[A-Za-z0-9+/=]*$/i.test(trimmed)
|
|
38
|
-
? trimmed
|
|
39
|
-
: undefined;
|
|
40
|
-
}
|
|
41
|
-
/** Matching close for every open bracket, computed in one pass. */
|
|
42
|
-
function matchPairs(text, open, close) {
|
|
43
|
-
const pairs = new Map();
|
|
44
|
-
const stack = [];
|
|
45
|
-
for (let i = 0; i < text.length; i++) {
|
|
46
|
-
const c = text[i];
|
|
47
|
-
if (c === "\\")
|
|
48
|
-
i++;
|
|
49
|
-
else if (c === open)
|
|
50
|
-
stack.push(i);
|
|
51
|
-
else if (c === close) {
|
|
52
|
-
const start = stack.pop();
|
|
53
|
-
if (start !== undefined)
|
|
54
|
-
pairs.set(start, i);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
return pairs;
|
|
58
|
-
}
|
|
59
|
-
function inline(text, depth = 0) {
|
|
60
|
-
return renderSpan({
|
|
61
|
-
text,
|
|
62
|
-
brackets: matchPairs(text, "[", "]"),
|
|
63
|
-
parens: matchPairs(text, "(", ")"),
|
|
64
|
-
unclosed: new Map(),
|
|
65
|
-
depth,
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
function renderSpan(span) {
|
|
69
|
-
const { text } = span;
|
|
70
|
-
let out = "";
|
|
71
|
-
let i = 0;
|
|
72
|
-
const n = text.length;
|
|
73
|
-
while (i < n) {
|
|
74
|
-
const c = text[i] ?? "";
|
|
75
|
-
if (c === "\\" && i + 1 < n) {
|
|
76
|
-
const next = text[i + 1] ?? "";
|
|
77
|
-
if (next === "\n") {
|
|
78
|
-
out += "<br>\n";
|
|
79
|
-
i += 2;
|
|
80
|
-
continue;
|
|
81
|
-
}
|
|
82
|
-
if (PUNCTUATION.test(next)) {
|
|
83
|
-
out += escapeHtml(next);
|
|
84
|
-
i += 2;
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
if (c === "`") {
|
|
89
|
-
const code = codeSpan(span, i);
|
|
90
|
-
if (code) {
|
|
91
|
-
out += code.html;
|
|
92
|
-
i = code.end;
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
let run = 0;
|
|
96
|
-
while (text[i + run] === "`")
|
|
97
|
-
run++;
|
|
98
|
-
out += "`".repeat(run);
|
|
99
|
-
i += run;
|
|
100
|
-
continue;
|
|
101
|
-
}
|
|
102
|
-
if (c === "<") {
|
|
103
|
-
const auto = /^<((?:https|mailto):[^\s<>]*)>/i.exec(text.slice(i, i + 2048));
|
|
104
|
-
if (auto?.[1]) {
|
|
105
|
-
const href = safeHref(auto[1]);
|
|
106
|
-
out += href
|
|
107
|
-
? `<a href="${escapeHtml(href)}">${escapeHtml(auto[1])}</a>`
|
|
108
|
-
: escapeHtml(auto[0]);
|
|
109
|
-
i += auto[0].length;
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
if (c === "!" && text[i + 1] === "[") {
|
|
114
|
-
const link = parseLink(span, i + 1);
|
|
115
|
-
if (link) {
|
|
116
|
-
const src = safeImage(link.url);
|
|
117
|
-
out += src
|
|
118
|
-
? `<img src="${escapeHtml(src)}" alt="${escapeHtml(link.label)}"${link.title ? ` title="${escapeHtml(link.title)}"` : ""}>`
|
|
119
|
-
: escapeHtml(link.label);
|
|
120
|
-
i = link.end;
|
|
121
|
-
continue;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
if (c === "[") {
|
|
125
|
-
const link = parseLink(span, i);
|
|
126
|
-
if (link) {
|
|
127
|
-
const href = safeHref(link.url);
|
|
128
|
-
const label = span.depth < MAX_INLINE_DEPTH ? inline(link.label, span.depth + 1) : escapeHtml(link.label);
|
|
129
|
-
out += href
|
|
130
|
-
? `<a href="${escapeHtml(href)}"${link.title ? ` title="${escapeHtml(link.title)}"` : ""}>${label}</a>`
|
|
131
|
-
: label;
|
|
132
|
-
i = link.end;
|
|
133
|
-
continue;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
if ((c === "*" || c === "_" || c === "~") && span.depth < MAX_INLINE_DEPTH) {
|
|
137
|
-
const emphasis = parseEmphasis(span, i);
|
|
138
|
-
if (emphasis) {
|
|
139
|
-
out += emphasis.html;
|
|
140
|
-
i = emphasis.end;
|
|
141
|
-
continue;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
if (c === "\n") {
|
|
145
|
-
// Two trailing spaces make a hard break.
|
|
146
|
-
if (out.endsWith(" "))
|
|
147
|
-
out = `${out.replace(/ +$/, "")}<br>`;
|
|
148
|
-
out += "\n";
|
|
149
|
-
i++;
|
|
150
|
-
continue;
|
|
151
|
-
}
|
|
152
|
-
out += escapeHtml(c);
|
|
153
|
-
i++;
|
|
154
|
-
}
|
|
155
|
-
return out;
|
|
156
|
-
}
|
|
157
|
-
function codeSpan(span, start) {
|
|
158
|
-
const { text } = span;
|
|
159
|
-
let run = 0;
|
|
160
|
-
while (text[start + run] === "`")
|
|
161
|
-
run++;
|
|
162
|
-
const key = `\`${run}`;
|
|
163
|
-
const known = span.unclosed.get(key);
|
|
164
|
-
if (known !== undefined && known <= start)
|
|
165
|
-
return undefined;
|
|
166
|
-
// The first run of exactly `run` backticks after this one closes it.
|
|
167
|
-
let at = start + run;
|
|
168
|
-
for (;;) {
|
|
169
|
-
const found = text.indexOf("`", at);
|
|
170
|
-
if (found < 0) {
|
|
171
|
-
span.unclosed.set(key, start);
|
|
172
|
-
return undefined;
|
|
173
|
-
}
|
|
174
|
-
let length = 0;
|
|
175
|
-
while (text[found + length] === "`")
|
|
176
|
-
length++;
|
|
177
|
-
if (length === run) {
|
|
178
|
-
let code = text.slice(start + run, found).replace(/\n/g, " ");
|
|
179
|
-
if (code.length > 2 && code.startsWith(" ") && code.endsWith(" ") && code.trim()) {
|
|
180
|
-
code = code.slice(1, -1);
|
|
181
|
-
}
|
|
182
|
-
return { html: `<code>${escapeHtml(code)}</code>`, end: found + run };
|
|
183
|
-
}
|
|
184
|
-
at = found + length;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
/** `[label](url "title")` whose `[` is at `start`. */
|
|
188
|
-
function parseLink(span, start) {
|
|
189
|
-
const { text } = span;
|
|
190
|
-
const close = span.brackets.get(start);
|
|
191
|
-
if (close === undefined || text[close + 1] !== "(")
|
|
192
|
-
return undefined;
|
|
193
|
-
const end = span.parens.get(close + 1);
|
|
194
|
-
if (end === undefined)
|
|
195
|
-
return undefined;
|
|
196
|
-
const inner = text.slice(close + 2, end).trim();
|
|
197
|
-
const titled = /^(\S+)\s+(?:"([^"]*)"|'([^']*)')$/.exec(inner);
|
|
198
|
-
const rawUrl = titled?.[1] ?? inner;
|
|
199
|
-
const url = rawUrl.startsWith("<") && rawUrl.endsWith(">") ? rawUrl.slice(1, -1) : rawUrl;
|
|
200
|
-
if (/\s/.test(url))
|
|
201
|
-
return undefined;
|
|
202
|
-
const label = text.slice(start + 1, close);
|
|
203
|
-
const title = titled?.[2] ?? titled?.[3];
|
|
204
|
-
return title === undefined
|
|
205
|
-
? { label, url, end: end + 1 }
|
|
206
|
-
: { label, url, title, end: end + 1 };
|
|
207
|
-
}
|
|
208
|
-
/** `**strong**`, `__strong__`, `*em*`, `_em_`, `~~del~~` opening at `start`. */
|
|
209
|
-
function parseEmphasis(span, start) {
|
|
210
|
-
const { text } = span;
|
|
211
|
-
const c = text[start] ?? "";
|
|
212
|
-
let run = 0;
|
|
213
|
-
while (text[start + run] === c)
|
|
214
|
-
run++;
|
|
215
|
-
if (c === "~" && run !== 2)
|
|
216
|
-
return undefined;
|
|
217
|
-
const width = c === "~" ? 2 : Math.min(run, 2);
|
|
218
|
-
const delimiter = c.repeat(width);
|
|
219
|
-
const after = text[start + width];
|
|
220
|
-
if (after === undefined || /\s/.test(after))
|
|
221
|
-
return undefined;
|
|
222
|
-
// `_` never opens inside a word.
|
|
223
|
-
if (c === "_" && start > 0 && /[A-Za-z0-9]/.test(text[start - 1] ?? ""))
|
|
224
|
-
return undefined;
|
|
225
|
-
// Whether a position closes depends only on that position, so an opener
|
|
226
|
-
// that found no closer proves every later opener finds none either.
|
|
227
|
-
const known = span.unclosed.get(delimiter);
|
|
228
|
-
if (known !== undefined && known <= start)
|
|
229
|
-
return undefined;
|
|
230
|
-
let search = start + width + 1;
|
|
231
|
-
for (;;) {
|
|
232
|
-
const close = text.indexOf(delimiter, search);
|
|
233
|
-
if (close < 0) {
|
|
234
|
-
span.unclosed.set(delimiter, start);
|
|
235
|
-
return undefined;
|
|
236
|
-
}
|
|
237
|
-
const before = text[close - 1] ?? "";
|
|
238
|
-
const next = text[close + width] ?? "";
|
|
239
|
-
const closes = !/\s/.test(before) &&
|
|
240
|
-
!(c === "_" && /[A-Za-z0-9]/.test(next)) &&
|
|
241
|
-
// A single delimiter must not be half of a double.
|
|
242
|
-
(width === 2 || next !== c);
|
|
243
|
-
if (closes) {
|
|
244
|
-
const inner = inline(text.slice(start + width, close), span.depth + 1);
|
|
245
|
-
const tag = c === "~" ? "del" : width === 2 ? "strong" : "em";
|
|
246
|
-
return { html: `<${tag}>${inner}</${tag}>`, end: close + width };
|
|
247
|
-
}
|
|
248
|
-
search = close + 1;
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
const FENCE = /^( {0,3})(`{3,}|~{3,})[ \t]*([^`\s]*)[^`]*$/;
|
|
252
|
-
const HEADING = /^ {0,3}(#{1,6})(?:[ \t]+(.*?))?(?:[ \t]+#+)?[ \t]*$/;
|
|
253
|
-
const RULE = /^ {0,3}([-*_])(?:[ \t]*\1){2,}[ \t]*$/;
|
|
254
|
-
const QUOTE = /^ {0,3}> ?/;
|
|
255
|
-
const ITEM = /^( {0,3})([-*+]|\d{1,9}[.)])(?:[ \t]+(.*))?$/;
|
|
256
|
-
const TABLE_DELIMITER = /^ {0,3}\|?[ \t]*:?-+:?[ \t]*(?:\|[ \t]*:?-+:?[ \t]*)*\|?[ \t]*$/;
|
|
257
|
-
function splitRow(line) {
|
|
258
|
-
let row = line.trim();
|
|
259
|
-
if (row.startsWith("|"))
|
|
260
|
-
row = row.slice(1);
|
|
261
|
-
if (row.endsWith("|") && !row.endsWith("\\|"))
|
|
262
|
-
row = row.slice(0, -1);
|
|
263
|
-
const cells = [];
|
|
264
|
-
let cell = "";
|
|
265
|
-
for (let i = 0; i < row.length; i++) {
|
|
266
|
-
if (row[i] === "\\" && row[i + 1] === "|") {
|
|
267
|
-
cell += "|";
|
|
268
|
-
i++;
|
|
269
|
-
}
|
|
270
|
-
else if (row[i] === "|") {
|
|
271
|
-
cells.push(cell.trim());
|
|
272
|
-
cell = "";
|
|
273
|
-
}
|
|
274
|
-
else
|
|
275
|
-
cell += row[i];
|
|
276
|
-
}
|
|
277
|
-
cells.push(cell.trim());
|
|
278
|
-
return cells;
|
|
279
|
-
}
|
|
280
|
-
const startsBlock = (line) => FENCE.test(line) ||
|
|
281
|
-
HEADING.test(line) ||
|
|
282
|
-
RULE.test(line) ||
|
|
283
|
-
QUOTE.test(line) ||
|
|
284
|
-
/^ {0,3}(?:[-*+]|1[.)])[ \t]+\S/.test(line);
|
|
285
|
-
const leadingSpaces = (line) => /^ */.exec(line)?.[0].length ?? 0;
|
|
286
|
-
const dedent = (line, width) => line.slice(Math.min(width, leadingSpaces(line)));
|
|
287
|
-
/** Render block-level Markdown. `tight` renders paragraphs bare, as in a tight list. */
|
|
288
|
-
function blocks(lines, tight = false, depth = 0) {
|
|
289
|
-
if (depth > MAX_BLOCK_DEPTH)
|
|
290
|
-
throw new MarkdownNestingError();
|
|
291
|
-
const out = [];
|
|
292
|
-
let i = 0;
|
|
293
|
-
while (i < lines.length) {
|
|
294
|
-
const line = lines[i] ?? "";
|
|
295
|
-
if (!line.trim()) {
|
|
296
|
-
i++;
|
|
297
|
-
continue;
|
|
298
|
-
}
|
|
299
|
-
const fence = FENCE.exec(line);
|
|
300
|
-
if (fence) {
|
|
301
|
-
const indent = fence[1]?.length ?? 0;
|
|
302
|
-
const marker = fence[2] ?? "```";
|
|
303
|
-
const lang = fence[3] ?? "";
|
|
304
|
-
const body = [];
|
|
305
|
-
i++;
|
|
306
|
-
while (i < lines.length) {
|
|
307
|
-
const candidate = lines[i] ?? "";
|
|
308
|
-
const closing = /^ {0,3}(`{3,}|~{3,})[ \t]*$/.exec(candidate)?.[1];
|
|
309
|
-
i++;
|
|
310
|
-
if (closing && closing[0] === marker[0] && closing.length >= marker.length)
|
|
311
|
-
break;
|
|
312
|
-
body.push(dedent(candidate, indent));
|
|
313
|
-
}
|
|
314
|
-
const cls = lang ? ` class="language-${escapeHtml(lang)}"` : "";
|
|
315
|
-
out.push(`<pre><code${cls}>${escapeHtml(body.join("\n"))}${body.length ? "\n" : ""}</code></pre>`);
|
|
316
|
-
continue;
|
|
317
|
-
}
|
|
318
|
-
const heading = HEADING.exec(line);
|
|
319
|
-
if (heading) {
|
|
320
|
-
const level = heading[1]?.length ?? 1;
|
|
321
|
-
out.push(`<h${level}>${inline((heading[2] ?? "").trim())}</h${level}>`);
|
|
322
|
-
i++;
|
|
323
|
-
continue;
|
|
324
|
-
}
|
|
325
|
-
if (RULE.test(line)) {
|
|
326
|
-
out.push("<hr>");
|
|
327
|
-
i++;
|
|
328
|
-
continue;
|
|
329
|
-
}
|
|
330
|
-
if (QUOTE.test(line)) {
|
|
331
|
-
const inner = [];
|
|
332
|
-
while (i < lines.length && QUOTE.test(lines[i] ?? "")) {
|
|
333
|
-
inner.push((lines[i] ?? "").replace(QUOTE, ""));
|
|
334
|
-
i++;
|
|
335
|
-
}
|
|
336
|
-
out.push(`<blockquote>\n${blocks(inner, false, depth + 1)}\n</blockquote>`);
|
|
337
|
-
continue;
|
|
338
|
-
}
|
|
339
|
-
const next = lines[i + 1] ?? "";
|
|
340
|
-
if (line.includes("|") && next.includes("-") && TABLE_DELIMITER.test(next)) {
|
|
341
|
-
const header = splitRow(line);
|
|
342
|
-
const aligns = splitRow(next).map((cell) => cell.startsWith(":") && cell.endsWith(":")
|
|
343
|
-
? "center"
|
|
344
|
-
: cell.endsWith(":")
|
|
345
|
-
? "right"
|
|
346
|
-
: cell.startsWith(":")
|
|
347
|
-
? "left"
|
|
348
|
-
: "");
|
|
349
|
-
const cell = (tag, text, index) => {
|
|
350
|
-
const align = aligns[index];
|
|
351
|
-
return `<${tag}${align ? ` style="text-align:${align}"` : ""}>${inline(text)}</${tag}>`;
|
|
352
|
-
};
|
|
353
|
-
const rows = [];
|
|
354
|
-
i += 2;
|
|
355
|
-
while (i < lines.length && (lines[i] ?? "").trim() && (lines[i] ?? "").includes("|")) {
|
|
356
|
-
const cells = splitRow(lines[i] ?? "");
|
|
357
|
-
rows.push(`<tr>${header.map((_, index) => cell("td", cells[index] ?? "", index)).join("")}</tr>`);
|
|
358
|
-
i++;
|
|
359
|
-
}
|
|
360
|
-
out.push(`<table>\n<thead><tr>${header.map((text, index) => cell("th", text, index)).join("")}</tr></thead>` +
|
|
361
|
-
(rows.length ? `\n<tbody>\n${rows.join("\n")}\n</tbody>` : "") +
|
|
362
|
-
"\n</table>");
|
|
363
|
-
continue;
|
|
364
|
-
}
|
|
365
|
-
const item = ITEM.exec(line);
|
|
366
|
-
if (item) {
|
|
367
|
-
const ordered = /\d/.test(item[2] ?? "");
|
|
368
|
-
const bullet = (item[2] ?? "").slice(-1);
|
|
369
|
-
const items = [];
|
|
370
|
-
let loose = false;
|
|
371
|
-
let sawBlank = false;
|
|
372
|
-
while (i < lines.length) {
|
|
373
|
-
const match = ITEM.exec(lines[i] ?? "");
|
|
374
|
-
if (!match ||
|
|
375
|
-
/\d/.test(match[2] ?? "") !== ordered ||
|
|
376
|
-
(match[2] ?? "").slice(-1) !== bullet)
|
|
377
|
-
break;
|
|
378
|
-
if (sawBlank)
|
|
379
|
-
loose = true;
|
|
380
|
-
const contentIndent = (match[1]?.length ?? 0) + (match[2]?.length ?? 0) + 1;
|
|
381
|
-
const body = [match[3] ?? ""];
|
|
382
|
-
i++;
|
|
383
|
-
sawBlank = false;
|
|
384
|
-
while (i < lines.length) {
|
|
385
|
-
const continuation = lines[i] ?? "";
|
|
386
|
-
if (!continuation.trim()) {
|
|
387
|
-
sawBlank = true;
|
|
388
|
-
body.push("");
|
|
389
|
-
i++;
|
|
390
|
-
continue;
|
|
391
|
-
}
|
|
392
|
-
const nested = leadingSpaces(continuation) >= Math.min(contentIndent, 4);
|
|
393
|
-
const lazy = !sawBlank && !startsBlock(continuation) && !ITEM.test(continuation);
|
|
394
|
-
if (!nested && !lazy)
|
|
395
|
-
break;
|
|
396
|
-
if (sawBlank)
|
|
397
|
-
loose = true;
|
|
398
|
-
body.push(dedent(continuation, contentIndent));
|
|
399
|
-
sawBlank = false;
|
|
400
|
-
i++;
|
|
401
|
-
}
|
|
402
|
-
while (body.length && !(body.at(-1) ?? "").trim())
|
|
403
|
-
body.pop();
|
|
404
|
-
items.push(body);
|
|
405
|
-
}
|
|
406
|
-
const start = ordered ? Number.parseInt(item[2] ?? "1", 10) : 1;
|
|
407
|
-
const tag = ordered ? "ol" : "ul";
|
|
408
|
-
out.push(`<${tag}${ordered && start !== 1 ? ` start="${start}"` : ""}>\n` +
|
|
409
|
-
items.map((body) => `<li>${blocks(body, !loose, depth + 1)}</li>`).join("\n") +
|
|
410
|
-
`\n</${tag}>`);
|
|
411
|
-
continue;
|
|
412
|
-
}
|
|
413
|
-
const paragraph = [line.trim()];
|
|
414
|
-
i++;
|
|
415
|
-
while (i < lines.length) {
|
|
416
|
-
const candidate = lines[i] ?? "";
|
|
417
|
-
if (!candidate.trim() || startsBlock(candidate))
|
|
418
|
-
break;
|
|
419
|
-
paragraph.push(candidate.replace(/^ +/, ""));
|
|
420
|
-
i++;
|
|
421
|
-
}
|
|
422
|
-
const html = inline(paragraph.join("\n"));
|
|
423
|
-
out.push(tight ? html : `<p>${html}</p>`);
|
|
424
|
-
}
|
|
425
|
-
return out.join("\n");
|
|
426
|
-
}
|
|
427
|
-
/** Render Markdown source to an HTML fragment. */
|
|
428
|
-
export function renderMarkdown(source) {
|
|
429
|
-
const lines = source
|
|
430
|
-
.replace(/\r\n?/g, "\n")
|
|
431
|
-
.split("\n")
|
|
432
|
-
.map((line) => line.replace(/^\t+/, (tabs) => " ".repeat(tabs.length)));
|
|
433
|
-
return blocks(lines);
|
|
434
|
-
}
|
|
435
|
-
/**
|
|
436
|
-
* Every color, font, and radius comes from the shared tokens, so a Markdown
|
|
437
|
-
* page reads as part of the viewer around it: the deployment's accent and
|
|
438
|
-
* fonts, and the palette of the scheme the page was given rather than whatever
|
|
439
|
-
* the OS prefers. The background is the frame's `--surface`, so the page sits
|
|
440
|
-
* flush in its card instead of painting a second one.
|
|
441
|
-
*/
|
|
442
|
-
const MARKDOWN_STYLE = "body{margin:0;font:16px/1.6 var(--sans);color:var(--text);background:var(--surface)}" +
|
|
443
|
-
"main{max-width:820px;margin:0 auto;padding:32px 24px}main>:first-child{margin-top:0}" +
|
|
444
|
-
"h1,h2,h3,h4{line-height:1.25;margin:1.6em 0 .6em}h1{font-size:2em}h2{font-size:1.5em}" +
|
|
445
|
-
"a{color:var(--link)}code{font:.9em var(--mono);background:var(--surface-2);padding:.1em .3em;border-radius:4px}" +
|
|
446
|
-
"pre{overflow:auto;padding:16px;background:var(--surface-2);border-radius:calc(var(--radius) - 2px)}pre code{background:none;padding:0}" +
|
|
447
|
-
"blockquote{margin:0;padding:0 1em;color:var(--muted);border-left:4px solid var(--border-strong)}" +
|
|
448
|
-
"table{border-collapse:collapse;display:block;overflow:auto}th,td{padding:6px 12px;border:1px solid var(--border-strong)}" +
|
|
449
|
-
"th{background:var(--surface-2)}img{max-width:100%}hr{border:0;border-top:1px solid var(--border)}";
|
|
450
|
-
const collapse = (text) => text.replace(/\s+/g, " ").trim().toLowerCase();
|
|
451
|
-
/**
|
|
452
|
-
* The source without a leading `# Heading` that only repeats the title. The
|
|
453
|
-
* viewer shows the title as the page's heading directly above the frame, so a
|
|
454
|
-
* page opening with the same words would say them twice. Any other opening
|
|
455
|
-
* heading is the author's and stays.
|
|
456
|
-
*/
|
|
457
|
-
function withoutRepeatedTitle(source, title) {
|
|
458
|
-
const lines = source.replace(/\r\n?/g, "\n").split("\n");
|
|
459
|
-
const first = lines.findIndex((line) => line.trim() !== "");
|
|
460
|
-
const heading = first < 0 ? undefined : HEADING.exec(lines[first] ?? "");
|
|
461
|
-
if (heading?.[1] !== "#" || !collapse(title) || collapse(heading[2] ?? "") !== collapse(title)) {
|
|
462
|
-
return source;
|
|
463
|
-
}
|
|
464
|
-
return lines.slice(first + 1).join("\n");
|
|
465
|
-
}
|
|
466
|
-
/**
|
|
467
|
-
* A complete page for a Markdown artifact: the rendered body in
|
|
468
|
-
* `<main id="artifact-root">`. `theme` is the deployment's resolved theme; the
|
|
469
|
-
* viewer passes it so a pinned scheme and the operator's tokens reach the
|
|
470
|
-
* frame. Without one the page follows the OS scheme with default tokens.
|
|
471
|
-
*/
|
|
472
|
-
export function markdownPage(source, title, theme) {
|
|
473
|
-
const resolved = theme ?? resolveTheme();
|
|
474
|
-
return (`<!doctype html><html lang="en"${schemeAttribute(resolved)}><head><meta charset="utf-8">` +
|
|
475
|
-
`<meta name="viewport" content="width=device-width,initial-scale=1">` +
|
|
476
|
-
`<title>${escapeHtml(title)}</title><style>${TOKENS_CSS}${themeCss(resolved)}${MARKDOWN_STYLE}</style></head>` +
|
|
477
|
-
`<body><main id="artifact-root">\n${renderMarkdown(withoutRepeatedTitle(source, title))}\n</main></body></html>`);
|
|
478
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import type { ArtifactsModule } from "../module-contracts.js";
|
|
2
|
-
import { type ArtifactRenderCheck } from "./connector.js";
|
|
3
|
-
import { ArtifactRefreshService, type ArtifactRefreshRuntime, type RefreshOutcome } from "./refresh.js";
|
|
4
|
-
import type { ArtifactAllowlist, ArtifactLimits, ArtifactStore } from "./types.js";
|
|
5
|
-
export interface ArtifactsOptions {
|
|
6
|
-
/**
|
|
7
|
-
* Where artifacts live: `kvArtifactStore(storage)` over storage with
|
|
8
|
-
* `compareAndSet` — the Worker example's D1 store (optionally with R2 for
|
|
9
|
-
* bodies), `fileStorage` on Node, `memoryStorage` in tests.
|
|
10
|
-
*/
|
|
11
|
-
store: ArtifactStore;
|
|
12
|
-
/**
|
|
13
|
-
* Origins pages may load scripts, stylesheets, and fonts from, each an exact
|
|
14
|
-
* `https:` origin. Every list defaults to empty; a deployment must opt in
|
|
15
|
-
* to each origin it trusts with page data. The viewer's CSP names only these.
|
|
16
|
-
*/
|
|
17
|
-
allowlist?: Partial<ArtifactAllowlist>;
|
|
18
|
-
/** Tighten any limit below its default; none can be raised. */
|
|
19
|
-
limits?: Partial<ArtifactLimits>;
|
|
20
|
-
/**
|
|
21
|
-
* A real render check, such as a headless browser, run on
|
|
22
|
-
* `validate_artifact` and every write that changes what a page renders.
|
|
23
|
-
* Without one, static validation is the floor.
|
|
24
|
-
*/
|
|
25
|
-
renderCheck?: ArtifactRenderCheck;
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Team pages over stored data, reached by agents through the built-in
|
|
29
|
-
* `artifacts` connector. Pass the result as `createConnecta({ artifacts })`.
|
|
30
|
-
* Structural mistakes throw here, before a deployment boots.
|
|
31
|
-
*/
|
|
32
|
-
export interface RefreshableArtifacts extends ArtifactsModule {
|
|
33
|
-
bindRefresh(runtime: ArtifactRefreshRuntime): void;
|
|
34
|
-
refresh(id: string, by: import("./types.js").ArtifactActor): Promise<RefreshOutcome>;
|
|
35
|
-
runDue(): ReturnType<ArtifactRefreshService["runDue"]>;
|
|
36
|
-
}
|
|
37
|
-
export declare function artifacts(options: ArtifactsOptions): RefreshableArtifacts;
|