@mosetta/router 0.0.0-stage → 0.0.9
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 +21 -0
- package/README.md +4 -2
- package/package.json +16 -3
- package/src/core/core.ts +6 -0
- package/src/core/stores.ts +36 -0
- package/src/core/universalGlue.ts +19 -0
- package/src/core/verdictTypes.ts +97 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 mosetta contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @mosetta/router
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The line instruction of mosetta: what a factory line is composed from — its modules by reference,
|
|
4
|
+
reserved module names, the control headers between the factory gate and its lines, and the
|
|
5
|
+
universal glue every line starts with.
|
package/package.json
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mosetta/router",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
3
|
+
"version": "0.0.9",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"description": "mosetta factory line protocol: the line instruction a factory line is built from, reserved module names, control headers and the universal glue.",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"files": [
|
|
8
|
+
"src"
|
|
9
|
+
],
|
|
10
|
+
"exports": {
|
|
11
|
+
"./core": "./src/core/core.ts"
|
|
12
|
+
},
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"typecheck": "tsc --noEmit"
|
|
18
|
+
}
|
|
6
19
|
}
|
package/src/core/core.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// @mosetta/router/core — the RUNNER's protocol with its isolates (ADR-0053: the router module is
|
|
2
|
+
// gone, the name stays): the isolate instruction, the reserved names and headers, the store model
|
|
3
|
+
// and the universal glue source.
|
|
4
|
+
export * from './verdictTypes';
|
|
5
|
+
export * from './stores';
|
|
6
|
+
export * from './universalGlue';
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// The application's store model: JUST base URLs — symmetric public/private pairs,
|
|
2
|
+
// one per provider plus the app's content pair (live pages + runtime files / drafts).
|
|
3
|
+
// How the URLs relate to each other (same host, four buckets, whatever) is NOBODY's knowledge:
|
|
4
|
+
// the model carries no origin identity. A provider's identity, where one is needed (an
|
|
5
|
+
// instruction's `ref.provider`), is its publicBase. Zod/di-free — the runner's store resolution. Base URLs are not secrets; keys for private stores
|
|
6
|
+
// live in the runner's registry only.
|
|
7
|
+
|
|
8
|
+
export interface IStorePair {
|
|
9
|
+
publicBase: string;
|
|
10
|
+
privateBase: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// a provider's pair — same shape, kept as a named alias for the authoring surface
|
|
14
|
+
export type IProviderStores = IStorePair;
|
|
15
|
+
|
|
16
|
+
export interface IRouterStores {
|
|
17
|
+
// [0] is the app's primary provider (the app's own distribution)
|
|
18
|
+
providers: IProviderStores[];
|
|
19
|
+
content: IStorePair;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const withSlash = (u: string): string => (u.endsWith('/') ? u : `${u}/`);
|
|
23
|
+
|
|
24
|
+
// CONFIG-LEVEL SUGAR for dev / small installs: one server serves all four stores under path
|
|
25
|
+
// prefixes — this constructor is the only place that knows that. Everything downstream sees
|
|
26
|
+
// four independent base URLs; prod registry entries are four explicit URLs and never call this.
|
|
27
|
+
export function storesFromSingleOrigin(origin: string): IRouterStores {
|
|
28
|
+
if (!origin) {
|
|
29
|
+
return { providers: [], content: { publicBase: '', privateBase: '' } };
|
|
30
|
+
}
|
|
31
|
+
const base = withSlash(origin);
|
|
32
|
+
return {
|
|
33
|
+
providers: [{ publicBase: base, privateBase: `${base}private/` }],
|
|
34
|
+
content: { publicBase: `${base}__mosetta__/`, privateBase: `${base}__mosetta__/content/` },
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// THE universal glue: embedded in the RUNNER and composed as `index.js` with every
|
|
2
|
+
// instruction's modules map — never fetched, never named in instructions. It can be this small because
|
|
3
|
+
// every module kind carries its own aspect and DEFAULT-exports fetch; the only jobs
|
|
4
|
+
// left are adapting that export to the Worker Loader entry shape and guarding the boundary:
|
|
5
|
+
// whatever escapes the monolith becomes a loud 500 error JSON.
|
|
6
|
+
export const UNIVERSAL_GLUE_SOURCE = `import module from './module.js';
|
|
7
|
+
export default {
|
|
8
|
+
async fetch(request, env) {
|
|
9
|
+
try {
|
|
10
|
+
return await module.fetch(request, env);
|
|
11
|
+
} catch (e) {
|
|
12
|
+
return new Response(
|
|
13
|
+
JSON.stringify({ error: { code: 'ISOLATE_FAILED', message: String((e && e.message) || e) } }),
|
|
14
|
+
{ status: 500, headers: { 'content-type': 'application/json; charset=utf-8' } },
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
`;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// What the RUNNER composes an isolate from (ADR-0053: there is no router — the runner decides
|
|
2
|
+
// where a request goes and builds these itself), and the words it exchanges with an isolate:
|
|
3
|
+
// the reserved module names, the recomposition and hand-over headers, the env fields a site reads.
|
|
4
|
+
|
|
5
|
+
// The four store kinds of an application. Physically four different base URLs in
|
|
6
|
+
// prod (buckets/CDN); in dev one origin derives all four bases (storesFromSingleOrigin):
|
|
7
|
+
// - provider-public: a provider's client statics (components, styles, images, public prefabs)
|
|
8
|
+
// - provider-private: a provider's server-action monoliths (+ framework isolate entries)
|
|
9
|
+
// - content-public: the app's live pages collection + the runtime files pages reference
|
|
10
|
+
// (system.js, bootstrap.js)
|
|
11
|
+
// - content-private: the app's drafts
|
|
12
|
+
export type TStoreName = 'provider-public' | 'provider-private' | 'content-public' | 'content-private';
|
|
13
|
+
|
|
14
|
+
// a store-qualified artifact reference: the RUNNER resolves it against its store registry (and
|
|
15
|
+
// attaches credentials for private stores) — instructions never carry URLs or secrets
|
|
16
|
+
export interface IArtifactRef {
|
|
17
|
+
store: TStoreName;
|
|
18
|
+
// which CODE SOURCE a provider-* ref is read from — a key of the runner's own registry;
|
|
19
|
+
// omitted → the host. A key the registry does not hold resolves to nothing.
|
|
20
|
+
provider?: string;
|
|
21
|
+
// STORE-relative path, never an absolute URL (the runner builds every ref itself)
|
|
22
|
+
path: string;
|
|
23
|
+
// the artifact's integrity as the catalog names it (`sha256-…`): the runner checks what it
|
|
24
|
+
// fetched against it — a CDN may still serve an older build under the same address
|
|
25
|
+
integrity?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// the reserved entry key: the runner's UNIVERSAL glue imports './module.js' and calls its
|
|
29
|
+
// `module.fetch` — every modules map MUST contain it. Glue itself is never named in instructions:
|
|
30
|
+
// every module kind carries its own aspect and exposes fetch, so the glue never differs.
|
|
31
|
+
export const ISOLATE_ENTRY_MODULE = 'module.js';
|
|
32
|
+
|
|
33
|
+
// "compose an isolate and hand it the request"
|
|
34
|
+
export interface ILineInstruction {
|
|
35
|
+
// isolate cache key; the runner prefixes it with the project/provider so ids never collide
|
|
36
|
+
cacheId: string;
|
|
37
|
+
// in-isolate module name → store-qualified artifact ref. Must contain ISOLATE_ENTRY_MODULE —
|
|
38
|
+
// the aspect-carrying entry. Sibling entries are importable inside the isolate by their map
|
|
39
|
+
// names: isolates cannot eval fetched code, so a multi-file composition (SSR: renderer + the
|
|
40
|
+
// page tree's server-built modules, each from its author's provider) can enter an isolate ONLY
|
|
41
|
+
// through this map. Today's strategies emit single-entry maps — monoliths are self-contained.
|
|
42
|
+
modules: Record<string, IArtifactRef>;
|
|
43
|
+
// modules the RUNNER writes itself (name → source), composed beside the fetched ones: data an
|
|
44
|
+
// isolate needs that is too large for its env (the catalog snapshot — CATALOG_MODULE)
|
|
45
|
+
sources?: Record<string, string>;
|
|
46
|
+
// becomes the target isolate's env. Created ONCE per cacheId — only isolate-level constants belong
|
|
47
|
+
// here (PROVIDER_ORIGIN, small flags); per-request variability travels in the request. A Dynamic
|
|
48
|
+
// Worker's env is capped at 1 MiB: anything that grows with the platform goes into `sources`.
|
|
49
|
+
data?: Record<string, unknown>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* A render that is NOT the page as published: it fell back to the empty shell, linked without a
|
|
54
|
+
* module it could not get, or shipped its head before its actions settled. Served to the reader
|
|
55
|
+
* (better than nothing) but never kept at the edge — the next request tries again. Set by the
|
|
56
|
+
* site isolate, read and removed by the runner.
|
|
57
|
+
*/
|
|
58
|
+
export const DEGRADED_HEADER = 'x-mosetta-degraded';
|
|
59
|
+
|
|
60
|
+
/** the catalog snapshot of a site isolate, as a module beside the code (`export default [...]`) */
|
|
61
|
+
export const CATALOG_MODULE = 'catalog.js';
|
|
62
|
+
/** the env field naming that module */
|
|
63
|
+
export const CATALOG_MODULE_ENV = 'CATALOG_MODULE';
|
|
64
|
+
|
|
65
|
+
// Recomposition: the target isolate may answer NOT the client but the runner —
|
|
66
|
+
// "recompose me with these extra modules". The response is marked by a header, the body is a
|
|
67
|
+
// tar container: RECOMPOSE_MANIFEST_ENTRY ({ modules: [{entry, name, type}] }) + module
|
|
68
|
+
// entries. The runner validates names/size, composes a NEW isolate (the static set + the
|
|
69
|
+
// reported modules) and REPLAYS the original request. hop = 1: phase 2 cannot request a third
|
|
70
|
+
// composition.
|
|
71
|
+
export const RECOMPOSE_HEADER = 'x-mosetta-recompose';
|
|
72
|
+
export const RECOMPOSE_MANIFEST_ENTRY = 'recompose.json';
|
|
73
|
+
// marker of a REPLAYED request: the phase-2 isolate must use the reported modules and fail
|
|
74
|
+
// clearly if they are missing — a second recompose request is forbidden (hop = 1)
|
|
75
|
+
export const RECOMPOSED_HEADER = 'x-mosetta-recomposed';
|
|
76
|
+
// Recomposition BY REFERENCE (ADR-0052): the site isolate answers the runner with the ids of the
|
|
77
|
+
// catalog's modules it lacks for a page (JSON `{ ids }`), not with code — the runner takes them
|
|
78
|
+
// from the public code store, composes the site anew with them and replays (hop = 1); it keeps
|
|
79
|
+
// what was learned for the site until the code changes.
|
|
80
|
+
export const RECOMPOSE_REFS_HEADER = 'x-mosetta-recompose-refs';
|
|
81
|
+
/**
|
|
82
|
+
* The site's ENGINE is not the page's (ADR-0053): the page's root names another ssr facet. The
|
|
83
|
+
* site answers the runner with this header and the facet's id; the runner replays the request
|
|
84
|
+
* in the site of that engine (hop = 1) and remembers the address for it.
|
|
85
|
+
*/
|
|
86
|
+
export const SITE_ENGINE_HEADER = 'x-mosetta-site-engine';
|
|
87
|
+
/** the env field a site isolate learns its own engine (the ssr facet it is composed with) from */
|
|
88
|
+
export const SITE_ENGINE_ENV = 'SITE_ENGINE';
|
|
89
|
+
/** the env field a site isolate learns its own map from: the module ids composed into it */
|
|
90
|
+
export const COMPOSED_ENV = 'COMPOSED';
|
|
91
|
+
/**
|
|
92
|
+
* The content's generation as the runner read it for THIS request (the marker every publish
|
|
93
|
+
* rewrites). A site isolate keeps what it read of the live site for as long as the generation
|
|
94
|
+
* stays the same, and drops it when another one arrives — the isolate itself lives on: it is
|
|
95
|
+
* named by its code and its set, not by the content (ADR-0053). Set by the runner only.
|
|
96
|
+
*/
|
|
97
|
+
export const CONTENT_GENERATION_HEADER = 'x-mosetta-content-generation';
|