@ixo/oracle-runtime-workers 0.17.0 → 0.17.1
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ixo/oracle-runtime-workers",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.1",
|
|
4
4
|
"description": "QiForge oracle runtime for Cloudflare Workers — Durable Object per user (SQLite in WASM over DO storage), Matrix E2EE gateway Durable Object, Hono HTTP/SSE shell.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|
|
@@ -29,6 +29,8 @@ import { briefBody, record } from './document';
|
|
|
29
29
|
import { boundFindings } from './findings';
|
|
30
30
|
import { decode, INDEX_MAX_BYTES, verifyBytes } from './integrity';
|
|
31
31
|
import { LruMap } from './lru';
|
|
32
|
+
import type { Logger } from '../../plugin-api/types';
|
|
33
|
+
import { NOOP_LOGGER } from '../utils';
|
|
32
34
|
import type {
|
|
33
35
|
DocumentRequest,
|
|
34
36
|
DomainAnchor,
|
|
@@ -189,8 +191,11 @@ export class DomainContextResolver {
|
|
|
189
191
|
weigh: parsedWeight,
|
|
190
192
|
});
|
|
191
193
|
|
|
192
|
-
|
|
194
|
+
private readonly logger: Logger;
|
|
195
|
+
|
|
196
|
+
constructor(opts: { now?: () => number; logger?: Logger } = {}) {
|
|
193
197
|
this.now = opts.now ?? Date.now;
|
|
198
|
+
this.logger = opts.logger ?? NOOP_LOGGER;
|
|
194
199
|
}
|
|
195
200
|
|
|
196
201
|
/**
|
|
@@ -374,6 +379,13 @@ export class DomainContextResolver {
|
|
|
374
379
|
|
|
375
380
|
private failed(snapshot: DomainSnapshot, error: unknown): DomainSnapshot {
|
|
376
381
|
const code = errorCode(error, 'resolution-failed');
|
|
382
|
+
// A failure outside the finding vocabulary is a runtime fault (a thrown
|
|
383
|
+
// TypeError, a provider's message), not a property of the domain: the
|
|
384
|
+
// provenance only carries the fallback code, so the cause goes to the log.
|
|
385
|
+
if (errorCode(error, '') === '')
|
|
386
|
+
this.logger.warn(
|
|
387
|
+
`[domain-context] resolution of ${snapshot.did} failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
388
|
+
);
|
|
377
389
|
const failed: DomainSnapshot = {
|
|
378
390
|
...snapshot,
|
|
379
391
|
status: INVALID_PATTERN.test(code) ? 'invalid' : 'unavailable',
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
* The network edge of domain context: the IID anchor lookup on Blocksync and
|
|
3
3
|
* document reads. Reads go only to https origins on an allowlist (configured
|
|
4
4
|
* origins, the VFS, the Matrix homeserver, the IPFS gateway), never follow
|
|
5
|
-
* redirects
|
|
6
|
-
* request
|
|
5
|
+
* redirects (a 3xx answer is refused: workerd has no `redirect: 'error'`, so
|
|
6
|
+
* every request is made with `redirect: 'manual'` and the status checked),
|
|
7
|
+
* never carry URL credentials and never buffer more than the request's byte
|
|
8
|
+
* bound. Private documents are read through the user's VFS
|
|
7
9
|
* UCAN bearer or the host's `readPrivateDocument` adapter.
|
|
8
10
|
*/
|
|
9
11
|
import { CID } from 'multiformats/cid';
|
|
@@ -21,6 +23,32 @@ import type {
|
|
|
21
23
|
DomainTransport,
|
|
22
24
|
} from './types';
|
|
23
25
|
|
|
26
|
+
/** What `createDomainTransport` needs beyond the runtime context; tests inject a fetch. */
|
|
27
|
+
export interface DomainTransportDeps {
|
|
28
|
+
/** Default: the global `fetch`, looked up per call. */
|
|
29
|
+
fetch?: typeof fetch;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* A request that never follows a redirect. `redirect: 'error'` does not
|
|
34
|
+
* exist on workerd (it throws a TypeError before any network call), so the
|
|
35
|
+
* request is made with `redirect: 'manual'`, which hands back the 3xx
|
|
36
|
+
* answer itself, and any 3xx is refused with `code`, its body cancelled.
|
|
37
|
+
*/
|
|
38
|
+
export async function fetchWithoutRedirects(
|
|
39
|
+
fetcher: typeof fetch,
|
|
40
|
+
input: string | URL,
|
|
41
|
+
init: RequestInit,
|
|
42
|
+
code: string,
|
|
43
|
+
): Promise<Response> {
|
|
44
|
+
const response = await fetcher(input, { ...init, redirect: 'manual' });
|
|
45
|
+
if (response.status >= 300 && response.status < 400) {
|
|
46
|
+
await response.body?.cancel().catch(() => undefined);
|
|
47
|
+
throw new Error(code);
|
|
48
|
+
}
|
|
49
|
+
return response;
|
|
50
|
+
}
|
|
51
|
+
|
|
24
52
|
/** Bound of one document read when the caller's signal does not end it sooner. */
|
|
25
53
|
export const DOCUMENT_READ_TIMEOUT_MS = 10_000;
|
|
26
54
|
|
|
@@ -136,7 +164,12 @@ function positiveNumber(value: unknown, fallback: number): number {
|
|
|
136
164
|
export function createDomainTransport(
|
|
137
165
|
options: DomainContextOptions,
|
|
138
166
|
ctx: RuntimeContext,
|
|
167
|
+
deps: DomainTransportDeps = {},
|
|
139
168
|
): DomainTransport {
|
|
169
|
+
// Never keep the bare global: workerd rejects `fetch` invoked with a
|
|
170
|
+
// foreign `this`, and a test may stub the global after this is built.
|
|
171
|
+
const fetcher: typeof fetch =
|
|
172
|
+
deps.fetch ?? ((input, init) => globalThis.fetch(input, init));
|
|
140
173
|
const blocksync =
|
|
141
174
|
typeof ctx.config.BLOCKSYNC_GRAPHQL_URL === 'string'
|
|
142
175
|
? ctx.config.BLOCKSYNC_GRAPHQL_URL
|
|
@@ -222,13 +255,17 @@ export function createDomainTransport(
|
|
|
222
255
|
|
|
223
256
|
async resolve(did, signal) {
|
|
224
257
|
if (!blocksync) throw new Error('iid-unavailable');
|
|
225
|
-
const response = await
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
258
|
+
const response = await fetchWithoutRedirects(
|
|
259
|
+
fetcher,
|
|
260
|
+
blocksync,
|
|
261
|
+
{
|
|
262
|
+
method: 'POST',
|
|
263
|
+
signal,
|
|
264
|
+
headers: { 'content-type': 'application/json' },
|
|
265
|
+
body: JSON.stringify({ query: IID_QUERY, variables: { id: did } }),
|
|
266
|
+
},
|
|
267
|
+
'iid-unavailable',
|
|
268
|
+
);
|
|
232
269
|
const body = iidResponse.safeParse(
|
|
233
270
|
JSON.parse(
|
|
234
271
|
new TextDecoder().decode(
|
|
@@ -332,7 +369,12 @@ export function createDomainTransport(
|
|
|
332
369
|
headers.Authorization = `Bearer ${credentials.accessToken}`;
|
|
333
370
|
}
|
|
334
371
|
return boundedBytes(
|
|
335
|
-
await
|
|
372
|
+
await fetchWithoutRedirects(
|
|
373
|
+
fetcher,
|
|
374
|
+
url,
|
|
375
|
+
{ headers, signal },
|
|
376
|
+
'document-redirect-refused',
|
|
377
|
+
),
|
|
336
378
|
request.maxBytes,
|
|
337
379
|
);
|
|
338
380
|
},
|
package/src/do/user-oracle-do.ts
CHANGED
|
@@ -762,7 +762,9 @@ export function createUserOracleDO(opts: UserOracleDOOptions) {
|
|
|
762
762
|
* Domain-context anchors and documents, cached for every session of this
|
|
763
763
|
* object; `refresh_domain_context` invalidates entries here.
|
|
764
764
|
*/
|
|
765
|
-
private readonly domainResolver = new DomainContextResolver(
|
|
765
|
+
private readonly domainResolver = new DomainContextResolver({
|
|
766
|
+
logger: console,
|
|
767
|
+
});
|
|
766
768
|
/** Per-turn domain-context provenance (`domain_context_runs`). */
|
|
767
769
|
private domainContextStore: DomainContextStore | null = null;
|
|
768
770
|
private runs: RunCoordinator | null = null;
|