@volter/world-core 2.0.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/LICENSE +202 -0
- package/README.md +29 -0
- package/app-route.cjs +154 -0
- package/app-route.d.cts +7 -0
- package/attach.cjs +80 -0
- package/dist/app-route.cjs +154 -0
- package/dist/app-route.d.cts +7 -0
- package/dist/attach.cjs +80 -0
- package/dist/generated/pack-facts.json +4306 -0
- package/dist/inject.cjs +1097 -0
- package/dist/network-policy.cjs +92 -0
- package/dist/network-policy.d.cts +10 -0
- package/dist/src/actions.d.ts +276 -0
- package/dist/src/actions.js +436 -0
- package/dist/src/ancestry.d.ts +22 -0
- package/dist/src/ancestry.js +238 -0
- package/dist/src/args.d.ts +3 -0
- package/dist/src/args.js +12 -0
- package/dist/src/blob-store.d.ts +55 -0
- package/dist/src/blob-store.js +186 -0
- package/dist/src/brand-tokens.d.ts +2 -0
- package/dist/src/brand-tokens.js +17 -0
- package/dist/src/changeset.d.ts +431 -0
- package/dist/src/changeset.js +0 -0
- package/dist/src/client-bundle.d.ts +1 -0
- package/dist/src/client-bundle.js +28 -0
- package/dist/src/credential.d.ts +38 -0
- package/dist/src/credential.js +114 -0
- package/dist/src/derived-core.d.ts +452 -0
- package/dist/src/derived-core.js +782 -0
- package/dist/src/derived.d.ts +84 -0
- package/dist/src/derived.js +122 -0
- package/dist/src/emit.d.ts +106 -0
- package/dist/src/emit.js +157 -0
- package/dist/src/executor.d.ts +120 -0
- package/dist/src/executor.js +387 -0
- package/dist/src/file-response.d.ts +3 -0
- package/dist/src/file-response.js +22 -0
- package/dist/src/fork.d.ts +26 -0
- package/dist/src/fork.js +68 -0
- package/dist/src/git/history.d.ts +36 -0
- package/dist/src/git/history.js +298 -0
- package/dist/src/git/index.d.ts +6 -0
- package/dist/src/git/index.js +6 -0
- package/dist/src/git/inflate.d.ts +11 -0
- package/dist/src/git/inflate.js +194 -0
- package/dist/src/git/objects.d.ts +64 -0
- package/dist/src/git/objects.js +161 -0
- package/dist/src/git/pack.d.ts +14 -0
- package/dist/src/git/pack.js +199 -0
- package/dist/src/git/refs.d.ts +19 -0
- package/dist/src/git/refs.js +35 -0
- package/dist/src/git/smart-http.d.ts +45 -0
- package/dist/src/git/smart-http.js +223 -0
- package/dist/src/hash.d.ts +38 -0
- package/dist/src/hash.js +48 -0
- package/dist/src/head.d.ts +140 -0
- package/dist/src/head.js +313 -0
- package/dist/src/history.d.ts +76 -0
- package/dist/src/history.js +322 -0
- package/dist/src/index.d.ts +73 -0
- package/dist/src/index.js +98 -0
- package/dist/src/lifecycle.d.ts +1 -0
- package/dist/src/lifecycle.js +8 -0
- package/dist/src/log.d.ts +254 -0
- package/dist/src/log.js +801 -0
- package/dist/src/mirror-shell.d.ts +2 -0
- package/dist/src/mirror-shell.js +13 -0
- package/dist/src/observe.d.ts +49 -0
- package/dist/src/observe.js +148 -0
- package/dist/src/pack-assets.d.ts +30 -0
- package/dist/src/pack-assets.js +88 -0
- package/dist/src/packRegistry.d.ts +374 -0
- package/dist/src/packRegistry.js +142 -0
- package/dist/src/placeholder-remote.d.ts +22 -0
- package/dist/src/placeholder-remote.js +86 -0
- package/dist/src/proxy.d.ts +25 -0
- package/dist/src/proxy.js +155 -0
- package/dist/src/rateBudget.d.ts +367 -0
- package/dist/src/rateBudget.js +925 -0
- package/dist/src/references.d.ts +18 -0
- package/dist/src/references.js +27 -0
- package/dist/src/remote-execute.d.ts +22 -0
- package/dist/src/remote-execute.js +1 -0
- package/dist/src/resource-blob.d.ts +10 -0
- package/dist/src/resource-blob.js +56 -0
- package/dist/src/scenario.d.ts +197 -0
- package/dist/src/scenario.js +425 -0
- package/dist/src/schemas.d.ts +78 -0
- package/dist/src/schemas.js +50 -0
- package/dist/src/serve-http.d.ts +48 -0
- package/dist/src/serve-http.js +340 -0
- package/dist/src/serve.d.ts +147 -0
- package/dist/src/serve.js +507 -0
- package/dist/src/shared-blob-index.d.ts +4 -0
- package/dist/src/shared-blob-index.js +126 -0
- package/dist/src/state-system.d.ts +70 -0
- package/dist/src/state-system.js +90 -0
- package/dist/src/storage.d.ts +101 -0
- package/dist/src/storage.js +337 -0
- package/dist/src/twin-fetch.d.ts +64 -0
- package/dist/src/twin-fetch.js +91 -0
- package/dist/src/types.d.ts +40 -0
- package/dist/src/types.js +1 -0
- package/dist/src/v1-removed.d.ts +159 -0
- package/dist/src/v1-removed.js +124 -0
- package/dist/src/volter-home.d.ts +5 -0
- package/dist/src/volter-home.js +10 -0
- package/dist/src/world-clock.d.ts +4 -0
- package/dist/src/world-clock.js +32 -0
- package/dist/src/world-env.d.ts +3 -0
- package/dist/src/world-env.js +22 -0
- package/dist/src/world-store-sql.d.ts +27 -0
- package/dist/src/world-store-sql.js +86 -0
- package/dist/src/world-store.d.ts +168 -0
- package/dist/src/world-store.js +475 -0
- package/dist/src/worldConfig.d.ts +9 -0
- package/dist/src/worldConfig.js +17 -0
- package/dist/stream-bridge.cjs +80 -0
- package/dist/vendor-hosts.cjs +200 -0
- package/generated/pack-facts.json +4306 -0
- package/inject.cjs +1097 -0
- package/network-policy.cjs +92 -0
- package/network-policy.d.cts +10 -0
- package/package.json +103 -0
- package/src/actions.ts +564 -0
- package/src/ancestry.ts +213 -0
- package/src/args.ts +14 -0
- package/src/blob-store.ts +185 -0
- package/src/brand-tokens.ts +17 -0
- package/src/changeset.ts +1032 -0
- package/src/client-bundle.ts +29 -0
- package/src/credential.ts +140 -0
- package/src/derived-core.ts +1004 -0
- package/src/derived.ts +176 -0
- package/src/emit.ts +242 -0
- package/src/executor.ts +431 -0
- package/src/file-response.ts +22 -0
- package/src/fork.ts +89 -0
- package/src/git/history.ts +177 -0
- package/src/git/index.ts +6 -0
- package/src/git/inflate.ts +125 -0
- package/src/git/objects.ts +110 -0
- package/src/git/pack.ts +105 -0
- package/src/git/refs.ts +25 -0
- package/src/git/smart-http.ts +149 -0
- package/src/hash.ts +66 -0
- package/src/head.ts +318 -0
- package/src/history.ts +246 -0
- package/src/index.ts +323 -0
- package/src/lifecycle.ts +8 -0
- package/src/log.ts +793 -0
- package/src/mirror-shell.ts +15 -0
- package/src/observe.ts +130 -0
- package/src/pack-assets.ts +81 -0
- package/src/packRegistry.ts +408 -0
- package/src/placeholder-remote.ts +81 -0
- package/src/proxy.ts +183 -0
- package/src/rateBudget.ts +1115 -0
- package/src/references.ts +46 -0
- package/src/remote-execute.ts +26 -0
- package/src/resource-blob.ts +57 -0
- package/src/scenario.ts +479 -0
- package/src/schemas.ts +56 -0
- package/src/serve-http.ts +299 -0
- package/src/serve.ts +618 -0
- package/src/shared-blob-index.ts +108 -0
- package/src/state-system.ts +115 -0
- package/src/storage.ts +407 -0
- package/src/twin-fetch.ts +147 -0
- package/src/types.ts +50 -0
- package/src/v1-removed.ts +172 -0
- package/src/volter-home.ts +11 -0
- package/src/world-clock.ts +33 -0
- package/src/world-env.ts +18 -0
- package/src/world-store-sql.ts +118 -0
- package/src/world-store.ts +572 -0
- package/src/worldConfig.ts +27 -0
- package/stream-bridge.cjs +80 -0
- package/vendor-hosts.cjs +200 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// A pack's mirror shell as a World serves it, at /<org>/<world>/<vendor>/mirror/ (the served World's
|
|
2
|
+
// mount and the hosted supervisor's): its <base> becomes the twin's place under the World, so the
|
|
3
|
+
// mirror's reads go to the World's wire; its assets stay under mirror/; and its own `#/` routes stay on
|
|
4
|
+
// the shell. A mirror links its routes as href="#/…", which the browser resolves against <base> — the
|
|
5
|
+
// twin's API path, not the shell — so a capture-phase handler moves the hash on the page instead.
|
|
6
|
+
const HASH_LINKS = `<script>document.addEventListener('click',function(e){var a=e.target&&e.target.closest&&e.target.closest('a[href^="#"]');if(!a||e.defaultPrevented||e.button!==0||e.metaKey||e.ctrlKey||e.shiftKey||e.altKey)return;e.preventDefault();location.hash=a.getAttribute('href');},true);</script>`;
|
|
7
|
+
/** The shell's HTML mounted at `base` (the twin's place, ending in '/'). */
|
|
8
|
+
export function mirrorShellUnder(html, base) {
|
|
9
|
+
const mounted = html
|
|
10
|
+
.replace(/<base href="[^"]*">/, `<base href="${base}">`)
|
|
11
|
+
.replace(/(href|src)="assets\//g, '$1="mirror/assets/');
|
|
12
|
+
return mounted.includes('</head>') ? mounted.replace('</head>', `${HASH_LINKS}</head>`) : `${HASH_LINKS}${mounted}`;
|
|
13
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type SubjectFields } from './hash.js';
|
|
2
|
+
export type ObservedResource = {
|
|
3
|
+
type: string;
|
|
4
|
+
id: string;
|
|
5
|
+
fields: SubjectFields;
|
|
6
|
+
deleted?: boolean;
|
|
7
|
+
};
|
|
8
|
+
export type ObserveReport = {
|
|
9
|
+
observed: number;
|
|
10
|
+
appended: number;
|
|
11
|
+
unchanged: number;
|
|
12
|
+
removed: number;
|
|
13
|
+
};
|
|
14
|
+
export type Observation = {
|
|
15
|
+
service: string;
|
|
16
|
+
root?: string;
|
|
17
|
+
} & ({
|
|
18
|
+
resource: ObservedResource;
|
|
19
|
+
} | {
|
|
20
|
+
resources: ObservedResource[];
|
|
21
|
+
complete?: string[];
|
|
22
|
+
});
|
|
23
|
+
/** Run `fn` with every `observeResource` inside it COLLECTED instead of folded; the caller folds the
|
|
24
|
+
* batch (`observeResources`) once `fn` returns. The kernel's refresh does this around an adapter. */
|
|
25
|
+
export declare function collectObservations<T>(fn: () => Promise<T>): Promise<{
|
|
26
|
+
value: T;
|
|
27
|
+
observations: Observation[];
|
|
28
|
+
}>;
|
|
29
|
+
/** Fold a collected batch: one `observeResources` per service, `complete` applied to each. */
|
|
30
|
+
export declare function foldObservations(observations: Observation[], opts?: {
|
|
31
|
+
root?: string;
|
|
32
|
+
at?: string;
|
|
33
|
+
complete?: string[];
|
|
34
|
+
batch?: string;
|
|
35
|
+
}): ObserveReport;
|
|
36
|
+
/** One observed resource: collected when a refresh scope is open, else folded onto the root's log now. */
|
|
37
|
+
export declare function observeResource(service: string, resource: ObservedResource, opts?: {
|
|
38
|
+
root?: string;
|
|
39
|
+
at?: string;
|
|
40
|
+
}): ObserveReport;
|
|
41
|
+
/** The fold: diff each resource against upstream state, append what changed to the root's log. `complete`
|
|
42
|
+
* names the types this batch listed in full — a subject of such a type the batch does not hold is
|
|
43
|
+
* tombstoned. Entry ids are content-addressed, so the same observation folds once. */
|
|
44
|
+
export declare function observeResources(service: string, resources: ObservedResource[], opts?: {
|
|
45
|
+
root?: string;
|
|
46
|
+
at?: string;
|
|
47
|
+
complete?: string[];
|
|
48
|
+
batch?: string;
|
|
49
|
+
}): ObserveReport;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// OBSERVE — the kernel's fold for the second difference between simulated and real: how the tree
|
|
2
|
+
// is kept current (company contract "Refresh is the kernel's fold", ruled 2026-09-06).
|
|
3
|
+
//
|
|
4
|
+
// A pack's refresh and ingest code hand the kernel RESOURCES, never rows. The kernel diffs each
|
|
5
|
+
// against the upstream view and appends to the ROOT's log (the parent log — the vendor's history as
|
|
6
|
+
// observed) only what changed: one entry per changed subject, carrying the changed fields; an
|
|
7
|
+
// unchanged resource appends nothing; a listing the adapter declares complete tombstones what it
|
|
8
|
+
// no longer holds. A refresh runs inside a scope that collects the adapter's observations and folds
|
|
9
|
+
// them as one batch; an ingest observes directly, one resource at a time.
|
|
10
|
+
import { withAncestryLock } from "./ancestry.js";
|
|
11
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
12
|
+
import { appendParentEntry, readParentTreeMap } from "./log.js";
|
|
13
|
+
import { hashFieldValue } from "./hash.js";
|
|
14
|
+
import { worldNow } from "./world-clock.js";
|
|
15
|
+
// ONE sink per process, whichever copy of the kernel a pack resolved: a pack installed beside a
|
|
16
|
+
// world carries its own `@volter/world-core`, and its observations must reach the scope the runtime
|
|
17
|
+
// opened. A module-level instance would be one per copy; a process-global symbol is one per process.
|
|
18
|
+
const SINK = Symbol.for('volter.observe.sink');
|
|
19
|
+
// created on first use, never at import: a mirror's CLIENT bundle (target browser) carries this module
|
|
20
|
+
// through the pack's shared helpers, and a browser has no AsyncLocalStorage — constructing one at load
|
|
21
|
+
// would kill the whole client ("not a constructor") for a scope the browser never opens
|
|
22
|
+
const sink = () => (globalThis[SINK] ??= new AsyncLocalStorage());
|
|
23
|
+
/** Run `fn` with every `observeResource` inside it COLLECTED instead of folded; the caller folds the
|
|
24
|
+
* batch (`observeResources`) once `fn` returns. The kernel's refresh does this around an adapter. */
|
|
25
|
+
export async function collectObservations(fn) {
|
|
26
|
+
const observations = [];
|
|
27
|
+
const value = await sink().run(observations, fn);
|
|
28
|
+
return { value, observations };
|
|
29
|
+
}
|
|
30
|
+
/** Fold a collected batch: one `observeResources` per service, `complete` applied to each. */
|
|
31
|
+
export function foldObservations(observations, opts = {}) {
|
|
32
|
+
const total = { observed: 0, appended: 0, unchanged: 0, removed: 0 };
|
|
33
|
+
const groups = new Map();
|
|
34
|
+
for (const o of observations) {
|
|
35
|
+
const root = opts.root ?? o.root;
|
|
36
|
+
const key = JSON.stringify([o.service, root]);
|
|
37
|
+
let group = groups.get(key);
|
|
38
|
+
if (!group) {
|
|
39
|
+
group = { service: o.service, root, resources: [], complete: new Set(opts.complete) };
|
|
40
|
+
groups.set(key, group);
|
|
41
|
+
}
|
|
42
|
+
if ('resource' in o)
|
|
43
|
+
group.resources.push(o.resource);
|
|
44
|
+
else
|
|
45
|
+
for (const resource of o.resources)
|
|
46
|
+
group.resources.push(resource);
|
|
47
|
+
if ('resources' in o)
|
|
48
|
+
for (const type of o.complete ?? [])
|
|
49
|
+
group.complete.add(type);
|
|
50
|
+
}
|
|
51
|
+
for (const { service, root, resources, complete } of groups.values()) {
|
|
52
|
+
const r = foldResources(service, resources, { ...opts, root, complete: [...complete] });
|
|
53
|
+
total.observed += r.observed;
|
|
54
|
+
total.appended += r.appended;
|
|
55
|
+
total.unchanged += r.unchanged;
|
|
56
|
+
total.removed += r.removed;
|
|
57
|
+
}
|
|
58
|
+
return total;
|
|
59
|
+
}
|
|
60
|
+
/** One observed resource: collected when a refresh scope is open, else folded onto the root's log now. */
|
|
61
|
+
export function observeResource(service, resource, opts = {}) {
|
|
62
|
+
const collecting = typeof AsyncLocalStorage === 'function' ? sink().getStore() : undefined;
|
|
63
|
+
if (collecting) {
|
|
64
|
+
collecting.push({ service, resource, ...(opts.root !== undefined ? { root: opts.root } : {}) });
|
|
65
|
+
return { observed: 1, appended: 0, unchanged: 0, removed: 0 };
|
|
66
|
+
}
|
|
67
|
+
return observeResources(service, [resource], opts);
|
|
68
|
+
}
|
|
69
|
+
function stable(value) {
|
|
70
|
+
if (value === null || typeof value !== 'object')
|
|
71
|
+
return JSON.stringify(value) ?? 'undefined';
|
|
72
|
+
if (Array.isArray(value))
|
|
73
|
+
return `[${value.map(stable).join(',')}]`;
|
|
74
|
+
const o = value;
|
|
75
|
+
return `{${Object.keys(o).sort().map((k) => `${JSON.stringify(k)}:${stable(o[k])}`).join(',')}}`;
|
|
76
|
+
}
|
|
77
|
+
/** The fold: diff each resource against upstream state, append what changed to the root's log. `complete`
|
|
78
|
+
* names the types this batch listed in full — a subject of such a type the batch does not hold is
|
|
79
|
+
* tombstoned. Entry ids are content-addressed, so the same observation folds once. */
|
|
80
|
+
export function observeResources(service, resources, opts = {}) {
|
|
81
|
+
const collecting = typeof AsyncLocalStorage === 'function' ? sink().getStore() : undefined;
|
|
82
|
+
if (collecting) {
|
|
83
|
+
collecting.push({ service, resources, ...(opts.root !== undefined ? { root: opts.root } : {}), ...(opts.complete ? { complete: opts.complete } : {}) });
|
|
84
|
+
return { observed: resources.length, appended: 0, unchanged: 0, removed: 0 };
|
|
85
|
+
}
|
|
86
|
+
return foldResources(service, resources, opts);
|
|
87
|
+
}
|
|
88
|
+
function foldResources(service, resources, opts) {
|
|
89
|
+
return withAncestryLock(() => {
|
|
90
|
+
const at = opts.at ?? worldNow();
|
|
91
|
+
// one look is one batch: every entry it appends carries the same id, and no position may split it
|
|
92
|
+
const batch = opts.batch ?? `obs:${service}:${at}`;
|
|
93
|
+
const tree = readParentTreeMap(service, opts.root);
|
|
94
|
+
const report = { observed: resources.length, appended: 0, unchanged: 0, removed: 0 };
|
|
95
|
+
const seen = new Set();
|
|
96
|
+
const append = (subject, fields) => {
|
|
97
|
+
const entry = {
|
|
98
|
+
id: `observed:${service}:${subject.type}:${subject.id}:${hashFieldValue({ subject, fields, at })}`,
|
|
99
|
+
service, op: 'set', operation: 'observed', subject, occurredAt: at, actor: { kind: 'system', id: 'vendor' }, fields, batch,
|
|
100
|
+
};
|
|
101
|
+
const { appended } = appendParentEntry(entry, opts.root);
|
|
102
|
+
const key = `${subject.type}:${subject.id}`;
|
|
103
|
+
const existing = tree.get(key) ?? { type: subject.type, id: subject.id, updatedAt: at, fields: {} };
|
|
104
|
+
const merged = { ...existing.fields, ...fields };
|
|
105
|
+
if (existing.fields.deleted === true && fields.deleted !== true)
|
|
106
|
+
delete merged.deleted;
|
|
107
|
+
tree.set(key, { ...existing, updatedAt: at, fields: merged });
|
|
108
|
+
return appended;
|
|
109
|
+
};
|
|
110
|
+
for (const r of resources) {
|
|
111
|
+
const key = `${r.type}:${r.id}`;
|
|
112
|
+
seen.add(key);
|
|
113
|
+
const existing = tree.get(key);
|
|
114
|
+
if (r.deleted) {
|
|
115
|
+
if (existing && existing.fields.deleted !== true) {
|
|
116
|
+
if (append({ type: r.type, id: r.id }, { deleted: true }))
|
|
117
|
+
report.removed += 1;
|
|
118
|
+
}
|
|
119
|
+
else
|
|
120
|
+
report.unchanged += 1;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
const changed = {};
|
|
124
|
+
for (const [k, v] of Object.entries(r.fields))
|
|
125
|
+
if (v !== undefined && (existing === undefined || stable(existing.fields[k]) !== stable(v)))
|
|
126
|
+
changed[k] = v;
|
|
127
|
+
if (Object.keys(changed).length === 0 && existing !== undefined && existing.fields.deleted !== true) {
|
|
128
|
+
report.unchanged += 1;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (existing?.fields.deleted === true && Object.keys(changed).length === 0)
|
|
132
|
+
Object.assign(changed, r.fields); // resurrected as it stands
|
|
133
|
+
if (append({ type: r.type, id: r.id }, changed))
|
|
134
|
+
report.appended += 1;
|
|
135
|
+
else
|
|
136
|
+
report.unchanged += 1;
|
|
137
|
+
}
|
|
138
|
+
for (const type of opts.complete ?? []) {
|
|
139
|
+
for (const s of tree.values()) {
|
|
140
|
+
if (s.type !== type || seen.has(`${s.type}:${s.id}`) || s.fields.deleted === true)
|
|
141
|
+
continue;
|
|
142
|
+
if (append({ type: s.type, id: s.id }, { deleted: true }))
|
|
143
|
+
report.removed += 1;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return report;
|
|
147
|
+
});
|
|
148
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface PackAssetStore {
|
|
2
|
+
/** The asset at `relPath` inside the pack `vendor`'s directory (e.g. `client/clerk.browser.js`),
|
|
3
|
+
* as a Response carrying the bytes and a content type — or null when the pack ships no such
|
|
4
|
+
* file. `relPath` is confined to the pack's own tree: a traversal never resolves. */
|
|
5
|
+
fetch(vendor: string, relPath: string): Promise<Response | null>;
|
|
6
|
+
}
|
|
7
|
+
export declare function assetContentType(relPath: string): string;
|
|
8
|
+
/** A relative path confined to a pack's tree: no absolute, no `..`, no empty segment. */
|
|
9
|
+
export declare function confinedAssetPath(relPath: string): string | null;
|
|
10
|
+
/** The Bun shell's store: the pack's own directory under the catalog (`packages/twin/<vendor>`).
|
|
11
|
+
* `twinDir` ends with a slash. */
|
|
12
|
+
export declare class LocalPackAssets implements PackAssetStore {
|
|
13
|
+
private readonly twinDir;
|
|
14
|
+
constructor(twinDir: string);
|
|
15
|
+
fetch(vendor: string, relPath: string): Promise<Response | null>;
|
|
16
|
+
}
|
|
17
|
+
/** The workerd shell's store: the static-assets binding, where the deploy step places each
|
|
18
|
+
* pack's declared assets under `/{vendor}/pack/<relPath>` beside the mirror shells. */
|
|
19
|
+
export declare class BindingPackAssets implements PackAssetStore {
|
|
20
|
+
private readonly assets;
|
|
21
|
+
constructor(assets: {
|
|
22
|
+
fetch(request: Request): Promise<Response>;
|
|
23
|
+
});
|
|
24
|
+
fetch(vendor: string, relPath: string): Promise<Response | null>;
|
|
25
|
+
}
|
|
26
|
+
/** The bound store — the Bun shell's local tree unless a shell bound another. */
|
|
27
|
+
export declare function getActivePackAssets(): PackAssetStore;
|
|
28
|
+
export declare function setActivePackAssets(store: PackAssetStore): PackAssetStore | undefined;
|
|
29
|
+
/** `getActivePackAssets().fetch(...)` with the pack's declared fallback: the first path that exists. */
|
|
30
|
+
export declare function packAsset(vendor: string, ...candidates: string[]): Promise<Response | null>;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// THE PACK-ASSET SEAM (runtime contract R20). A pack may ship bytes it serves byte-for-byte —
|
|
2
|
+
// clerk's vendored clerk-js dist (136 files, 14.5 MB), a mirror shell — that are neither
|
|
3
|
+
// committed text (size) nor world-state blobs (R11's seam is keyed under a world's state root
|
|
4
|
+
// and filled by requests). They are PACK assets: read-only, versioned with the package, the
|
|
5
|
+
// same on every world. This seam is how a pack reaches them without touching a host: the
|
|
6
|
+
// pack declares them on its descriptor (`assets`), reads them through `getActivePackAssets()`,
|
|
7
|
+
// and the SHELL binds the store — the Bun shell reads the pack's own tree, workerd the
|
|
8
|
+
// static-assets binding the mirror shells already deploy through. A pack that reads its tree
|
|
9
|
+
// with Bun.file or import.meta is host-bound (R12b); one that reads through here is not.
|
|
10
|
+
// No node builtins at module scope: `@volter/world-core`'s index re-exports this module and every mirror
|
|
11
|
+
// client bundle (browser target) pulls the index in — a static `node:fs`/`node:url` import here
|
|
12
|
+
// broke every mirror bundle the day the seam landed. The local store reads through Bun's file
|
|
13
|
+
// API at call time; the worker store never touches a filesystem.
|
|
14
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
15
|
+
const CONTENT_TYPES = {
|
|
16
|
+
'.js': 'application/javascript; charset=utf-8', '.mjs': 'application/javascript; charset=utf-8', '.css': 'text/css; charset=utf-8',
|
|
17
|
+
'.html': 'text/html; charset=utf-8', '.json': 'application/json; charset=utf-8', '.map': 'application/json; charset=utf-8',
|
|
18
|
+
'.svg': 'image/svg+xml', '.png': 'image/png', '.ico': 'image/x-icon', '.woff2': 'font/woff2', '.woff': 'font/woff', '.txt': 'text/plain; charset=utf-8',
|
|
19
|
+
};
|
|
20
|
+
export function assetContentType(relPath) {
|
|
21
|
+
const dot = relPath.lastIndexOf('.');
|
|
22
|
+
return (dot >= 0 ? CONTENT_TYPES[relPath.slice(dot)] : undefined) ?? 'application/octet-stream';
|
|
23
|
+
}
|
|
24
|
+
/** A relative path confined to a pack's tree: no absolute, no `..`, no empty segment. */
|
|
25
|
+
export function confinedAssetPath(relPath) {
|
|
26
|
+
if (relPath.startsWith('/') || relPath.includes('\\') || relPath.includes('\0'))
|
|
27
|
+
return null;
|
|
28
|
+
const segs = relPath.split('/');
|
|
29
|
+
if (segs.some((s) => s === '' || s === '.' || s === '..'))
|
|
30
|
+
return null;
|
|
31
|
+
return segs.join('/');
|
|
32
|
+
}
|
|
33
|
+
/** The Bun shell's store: the pack's own directory under the catalog (`packages/twin/<vendor>`).
|
|
34
|
+
* `twinDir` ends with a slash. */
|
|
35
|
+
export class LocalPackAssets {
|
|
36
|
+
twinDir;
|
|
37
|
+
constructor(twinDir) {
|
|
38
|
+
this.twinDir = twinDir;
|
|
39
|
+
}
|
|
40
|
+
async fetch(vendor, relPath) {
|
|
41
|
+
const safe = confinedAssetPath(relPath);
|
|
42
|
+
if (safe === null || !/^[a-z][a-z0-9-]*$/.test(vendor))
|
|
43
|
+
return null;
|
|
44
|
+
const file = `${this.twinDir}${vendor}/${safe}`; // confined: no `..`, no absolute, no empty segment (above)
|
|
45
|
+
if (!existsSync(file) || !statSync(file).isFile())
|
|
46
|
+
return null;
|
|
47
|
+
return new Response(readFileSync(file), { status: 200, headers: { 'content-type': assetContentType(safe) } });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/** The workerd shell's store: the static-assets binding, where the deploy step places each
|
|
51
|
+
* pack's declared assets under `/{vendor}/pack/<relPath>` beside the mirror shells. */
|
|
52
|
+
export class BindingPackAssets {
|
|
53
|
+
assets;
|
|
54
|
+
constructor(assets) {
|
|
55
|
+
this.assets = assets;
|
|
56
|
+
}
|
|
57
|
+
async fetch(vendor, relPath) {
|
|
58
|
+
const safe = confinedAssetPath(relPath);
|
|
59
|
+
if (safe === null)
|
|
60
|
+
return null;
|
|
61
|
+
const res = await this.assets.fetch(new Request(`https://pack-assets/${vendor}/pack/${safe}`));
|
|
62
|
+
if (res.status !== 200)
|
|
63
|
+
return null;
|
|
64
|
+
return new Response(res.body, { status: 200, headers: { 'content-type': assetContentType(safe) } });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
let active;
|
|
68
|
+
/** The bound store — the Bun shell's local tree unless a shell bound another. */
|
|
69
|
+
export function getActivePackAssets() {
|
|
70
|
+
if (active === undefined)
|
|
71
|
+
active = new LocalPackAssets(new URL('../../twin/', import.meta.url).pathname);
|
|
72
|
+
return active;
|
|
73
|
+
}
|
|
74
|
+
export function setActivePackAssets(store) {
|
|
75
|
+
const previous = active;
|
|
76
|
+
active = store;
|
|
77
|
+
return previous;
|
|
78
|
+
}
|
|
79
|
+
/** `getActivePackAssets().fetch(...)` with the pack's declared fallback: the first path that exists. */
|
|
80
|
+
export async function packAsset(vendor, ...candidates) {
|
|
81
|
+
const store = getActivePackAssets();
|
|
82
|
+
for (const c of candidates) {
|
|
83
|
+
const r = await store.fetch(vendor, c);
|
|
84
|
+
if (r !== null)
|
|
85
|
+
return r;
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
}
|