@tyndall/core 0.0.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.
Files changed (59) hide show
  1. package/README.md +34 -0
  2. package/dist/client-router-bootstrap.d.ts +11 -0
  3. package/dist/client-router-bootstrap.d.ts.map +1 -0
  4. package/dist/client-router-bootstrap.js +319 -0
  5. package/dist/config.d.ts +197 -0
  6. package/dist/config.d.ts.map +1 -0
  7. package/dist/config.js +552 -0
  8. package/dist/dynamic-manifest.d.ts +78 -0
  9. package/dist/dynamic-manifest.d.ts.map +1 -0
  10. package/dist/dynamic-manifest.js +189 -0
  11. package/dist/dynamic-page-api.d.ts +34 -0
  12. package/dist/dynamic-page-api.d.ts.map +1 -0
  13. package/dist/dynamic-page-api.js +136 -0
  14. package/dist/errors.d.ts +10 -0
  15. package/dist/errors.d.ts.map +1 -0
  16. package/dist/errors.js +31 -0
  17. package/dist/head.d.ts +3 -0
  18. package/dist/head.d.ts.map +1 -0
  19. package/dist/head.js +33 -0
  20. package/dist/index.d.ts +33 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +18 -0
  23. package/dist/layouts.d.ts +14 -0
  24. package/dist/layouts.d.ts.map +1 -0
  25. package/dist/layouts.js +51 -0
  26. package/dist/manifest.d.ts +81 -0
  27. package/dist/manifest.d.ts.map +1 -0
  28. package/dist/manifest.js +139 -0
  29. package/dist/page-api.d.ts +56 -0
  30. package/dist/page-api.d.ts.map +1 -0
  31. package/dist/page-api.js +100 -0
  32. package/dist/plugin.d.ts +35 -0
  33. package/dist/plugin.d.ts.map +1 -0
  34. package/dist/plugin.js +133 -0
  35. package/dist/program-sandbox.d.ts +18 -0
  36. package/dist/program-sandbox.d.ts.map +1 -0
  37. package/dist/program-sandbox.js +84 -0
  38. package/dist/props.d.ts +2 -0
  39. package/dist/props.d.ts.map +1 -0
  40. package/dist/props.js +14 -0
  41. package/dist/render-policy.d.ts +8 -0
  42. package/dist/render-policy.d.ts.map +1 -0
  43. package/dist/render-policy.js +12 -0
  44. package/dist/resolver-fallback.d.ts +49 -0
  45. package/dist/resolver-fallback.d.ts.map +1 -0
  46. package/dist/resolver-fallback.js +99 -0
  47. package/dist/route-graph.d.ts +9 -0
  48. package/dist/route-graph.d.ts.map +1 -0
  49. package/dist/route-graph.js +157 -0
  50. package/dist/route-meta.d.ts +4 -0
  51. package/dist/route-meta.d.ts.map +1 -0
  52. package/dist/route-meta.js +48 -0
  53. package/dist/router.d.ts +27 -0
  54. package/dist/router.d.ts.map +1 -0
  55. package/dist/router.js +83 -0
  56. package/dist/ui-adapter.d.ts +67 -0
  57. package/dist/ui-adapter.d.ts.map +1 -0
  58. package/dist/ui-adapter.js +27 -0
  59. package/package.json +23 -0
@@ -0,0 +1,189 @@
1
+ import { HyperError } from "./errors.js";
2
+ export const dynamicManifestSchema = {
3
+ $schema: "http://json-schema.org/draft-07/schema#",
4
+ type: "object",
5
+ required: ["pageId", "version", "mode", "definition"],
6
+ additionalProperties: false,
7
+ properties: {
8
+ pageId: { type: "string" },
9
+ version: { type: ["string", "number"] },
10
+ mode: { enum: ["meta", "program"] },
11
+ cache: { type: "object" },
12
+ policy: { type: "object" },
13
+ pageProps: { type: "object" },
14
+ definition: { type: "object" },
15
+ },
16
+ };
17
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
18
+ const assertKnownKeys = (value, allowed, path) => {
19
+ for (const key of Object.keys(value)) {
20
+ if (!allowed.has(key)) {
21
+ throw new HyperError("DYNAMIC_MANIFEST_UNKNOWN_KEY", `Unknown dynamic manifest key: ${path}${key}`, { path: `${path}${key}` });
22
+ }
23
+ }
24
+ };
25
+ const ensureString = (value, path) => {
26
+ if (typeof value !== "string") {
27
+ throw new HyperError("DYNAMIC_MANIFEST_INVALID", `Invalid dynamic manifest value at ${path}: expected string`, { path, expected: "string" });
28
+ }
29
+ return value;
30
+ };
31
+ const ensureNumber = (value, path) => {
32
+ if (typeof value !== "number" || Number.isNaN(value)) {
33
+ throw new HyperError("DYNAMIC_MANIFEST_INVALID", `Invalid dynamic manifest value at ${path}: expected number`, { path, expected: "number" });
34
+ }
35
+ return value;
36
+ };
37
+ const ensureStringOrNumber = (value, path) => {
38
+ if (typeof value !== "string" && typeof value !== "number") {
39
+ throw new HyperError("DYNAMIC_MANIFEST_INVALID", `Invalid dynamic manifest value at ${path}: expected string | number`, { path, expected: "string | number" });
40
+ }
41
+ return value;
42
+ };
43
+ const ensureRecord = (value, path) => {
44
+ if (!isRecord(value)) {
45
+ throw new HyperError("DYNAMIC_MANIFEST_INVALID", `Invalid dynamic manifest value at ${path}: expected object`, { path, expected: "object" });
46
+ }
47
+ return value;
48
+ };
49
+ const ensureCache = (value, path) => {
50
+ const record = ensureRecord(value, path);
51
+ assertKnownKeys(record, new Set(["ttlSeconds", "etag", "swrSeconds"]), `${path}.`);
52
+ const cache = {};
53
+ if ("ttlSeconds" in record) {
54
+ cache.ttlSeconds = ensureNumber(record.ttlSeconds, `${path}.ttlSeconds`);
55
+ }
56
+ if ("etag" in record) {
57
+ cache.etag = ensureString(record.etag, `${path}.etag`);
58
+ }
59
+ if ("swrSeconds" in record) {
60
+ cache.swrSeconds = ensureNumber(record.swrSeconds, `${path}.swrSeconds`);
61
+ }
62
+ return cache;
63
+ };
64
+ const ensurePolicy = (value, path) => {
65
+ const record = ensureRecord(value, path);
66
+ assertKnownKeys(record, new Set(["render", "auth", "seo", "headers", "redirect"]), `${path}.`);
67
+ const policy = {};
68
+ if ("render" in record) {
69
+ const render = ensureString(record.render, `${path}.render`);
70
+ if (!["csr", "ssr", "redirect", "auto"].includes(render)) {
71
+ throw new HyperError("DYNAMIC_MANIFEST_INVALID", `Invalid dynamic manifest value at ${path}.render: ${render}`, { path: `${path}.render`, expected: "csr | ssr | redirect | auto" });
72
+ }
73
+ policy.render = render;
74
+ }
75
+ if ("auth" in record) {
76
+ policy.auth = ensureString(record.auth, `${path}.auth`);
77
+ }
78
+ if ("seo" in record) {
79
+ policy.seo = ensureString(record.seo, `${path}.seo`);
80
+ }
81
+ if ("redirect" in record) {
82
+ policy.redirect = ensureString(record.redirect, `${path}.redirect`);
83
+ }
84
+ if ("headers" in record) {
85
+ const headers = ensureRecord(record.headers, `${path}.headers`);
86
+ const normalized = {};
87
+ for (const [key, value] of Object.entries(headers)) {
88
+ normalized[key] = ensureString(value, `${path}.headers.${key}`);
89
+ }
90
+ policy.headers = normalized;
91
+ }
92
+ return policy;
93
+ };
94
+ const ensureMetaNode = (value, path) => {
95
+ const record = ensureRecord(value, path);
96
+ assertKnownKeys(record, new Set(["type", "props", "children"]), `${path}.`);
97
+ const node = {
98
+ type: ensureString(record.type, `${path}.type`),
99
+ };
100
+ if ("props" in record) {
101
+ node.props = ensureRecord(record.props, `${path}.props`);
102
+ }
103
+ if ("children" in record) {
104
+ if (!Array.isArray(record.children)) {
105
+ throw new HyperError("DYNAMIC_MANIFEST_INVALID", `Invalid dynamic manifest value at ${path}.children: expected array`, { path: `${path}.children`, expected: "array" });
106
+ }
107
+ node.children = record.children.map((child, index) => {
108
+ if (typeof child === "string") {
109
+ return child;
110
+ }
111
+ return ensureMetaNode(child, `${path}.children[${index}]`);
112
+ });
113
+ }
114
+ return node;
115
+ };
116
+ const ensureDefinition = (value, path, mode) => {
117
+ const record = ensureRecord(value, path);
118
+ const kind = ensureString(record.kind, `${path}.kind`);
119
+ if (kind === "meta") {
120
+ assertKnownKeys(record, new Set(["kind", "root"]), `${path}.`);
121
+ // Important: meta definitions must align with the declared mode.
122
+ if (mode !== "meta") {
123
+ throw new HyperError("DYNAMIC_MANIFEST_INVALID", `Dynamic manifest mode mismatch: expected ${mode} definition`, { path, expected: mode });
124
+ }
125
+ return { kind: "meta", root: ensureMetaNode(record.root, `${path}.root`) };
126
+ }
127
+ if (kind === "program") {
128
+ assertKnownKeys(record, new Set(["kind", "entry", "integrity", "sandbox"]), `${path}.`);
129
+ if (mode !== "program") {
130
+ throw new HyperError("DYNAMIC_MANIFEST_INVALID", `Dynamic manifest mode mismatch: expected ${mode} definition`, { path, expected: mode });
131
+ }
132
+ const entry = ensureRecord(record.entry, `${path}.entry`);
133
+ assertKnownKeys(entry, new Set(["code"]), `${path}.entry.`);
134
+ const program = {
135
+ kind: "program",
136
+ entry: {
137
+ code: ensureString(entry.code, `${path}.entry.code`),
138
+ },
139
+ };
140
+ if ("integrity" in record) {
141
+ const integrity = ensureRecord(record.integrity, `${path}.integrity`);
142
+ assertKnownKeys(integrity, new Set(["alg", "signature"]), `${path}.integrity.`);
143
+ program.integrity = {};
144
+ if ("alg" in integrity) {
145
+ program.integrity.alg = ensureString(integrity.alg, `${path}.integrity.alg`);
146
+ }
147
+ if ("signature" in integrity) {
148
+ program.integrity.signature = ensureString(integrity.signature, `${path}.integrity.signature`);
149
+ }
150
+ }
151
+ if ("sandbox" in record) {
152
+ const sandbox = ensureRecord(record.sandbox, `${path}.sandbox`);
153
+ assertKnownKeys(sandbox, new Set(["timeoutMs", "memoryMb"]), `${path}.sandbox.`);
154
+ program.sandbox = {};
155
+ if ("timeoutMs" in sandbox) {
156
+ program.sandbox.timeoutMs = ensureNumber(sandbox.timeoutMs, `${path}.sandbox.timeoutMs`);
157
+ }
158
+ if ("memoryMb" in sandbox) {
159
+ program.sandbox.memoryMb = ensureNumber(sandbox.memoryMb, `${path}.sandbox.memoryMb`);
160
+ }
161
+ }
162
+ return program;
163
+ }
164
+ throw new HyperError("DYNAMIC_MANIFEST_INVALID", `Invalid dynamic manifest value at ${path}.kind: ${kind}`, { path: `${path}.kind`, expected: "meta | program" });
165
+ };
166
+ export const validateDynamicManifest = (input) => {
167
+ const record = ensureRecord(input, "manifest");
168
+ assertKnownKeys(record, new Set(["pageId", "version", "mode", "cache", "policy", "pageProps", "definition"]), "");
169
+ const mode = ensureString(record.mode, "mode");
170
+ if (mode !== "meta" && mode !== "program") {
171
+ throw new HyperError("DYNAMIC_MANIFEST_INVALID", `Invalid dynamic manifest value at mode: ${mode}`, { path: "mode", expected: "meta | program" });
172
+ }
173
+ const envelope = {
174
+ pageId: ensureString(record.pageId, "pageId"),
175
+ version: ensureStringOrNumber(record.version, "version"),
176
+ mode,
177
+ definition: ensureDefinition(record.definition, "definition", mode),
178
+ };
179
+ if ("cache" in record) {
180
+ envelope.cache = ensureCache(record.cache, "cache");
181
+ }
182
+ if ("policy" in record) {
183
+ envelope.policy = ensurePolicy(record.policy, "policy");
184
+ }
185
+ if ("pageProps" in record) {
186
+ envelope.pageProps = ensureRecord(record.pageProps, "pageProps");
187
+ }
188
+ return envelope;
189
+ };
@@ -0,0 +1,34 @@
1
+ import type { DynamicPageEnvelope } from "./dynamic-manifest.js";
2
+ export interface DynamicPageResolveInput {
3
+ path: string;
4
+ locale?: string;
5
+ variant?: string;
6
+ device?: string;
7
+ ctxHash?: string;
8
+ }
9
+ export interface DynamicPageComponentDescriptor {
10
+ type: string;
11
+ version?: string;
12
+ schema?: unknown;
13
+ }
14
+ export interface DynamicPageComponentRegistry {
15
+ types: DynamicPageComponentDescriptor[];
16
+ }
17
+ export interface DynamicPageApiProvider {
18
+ resolveByPath: (input: DynamicPageResolveInput) => Promise<DynamicPageEnvelope | null>;
19
+ resolveById: (pageId: string) => Promise<DynamicPageEnvelope | null>;
20
+ listComponents?: (filters: Record<string, string>) => Promise<DynamicPageComponentRegistry | null>;
21
+ }
22
+ export interface DynamicPageApiRequest {
23
+ method: string;
24
+ pathname: string;
25
+ query: URLSearchParams;
26
+ headers: Record<string, string | string[] | undefined>;
27
+ }
28
+ export interface DynamicPageApiResult {
29
+ status: number;
30
+ headers: Record<string, string>;
31
+ body?: string;
32
+ }
33
+ export declare const handleDynamicPageApiRequest: (request: DynamicPageApiRequest, provider: DynamicPageApiProvider) => Promise<DynamicPageApiResult | null>;
34
+ //# sourceMappingURL=dynamic-page-api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dynamic-page-api.d.ts","sourceRoot":"","sources":["../src/dynamic-page-api.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAEjE,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,8BAA8B;IAC7C,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,4BAA4B;IAC3C,KAAK,EAAE,8BAA8B,EAAE,CAAC;CACzC;AAED,MAAM,WAAW,sBAAsB;IACrC,aAAa,EAAE,CAAC,KAAK,EAAE,uBAAuB,KAAK,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAAC;IACvF,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAAC;IACrE,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,OAAO,CAAC,4BAA4B,GAAG,IAAI,CAAC,CAAC;CACpG;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,eAAe,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;CACxD;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAyGD,eAAO,MAAM,2BAA2B,GACtC,SAAS,qBAAqB,EAC9B,UAAU,sBAAsB,KAC/B,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAoDrC,CAAC"}
@@ -0,0 +1,136 @@
1
+ import { hash, stableStringify } from "@tyndall/shared";
2
+ const DEFAULT_TTL_SECONDS = 60;
3
+ const REGISTRY_TTL_SECONDS = 3600;
4
+ const getHeaderValue = (headers, name) => {
5
+ const value = headers[name.toLowerCase()] ?? headers[name];
6
+ if (Array.isArray(value)) {
7
+ return value.join(", ");
8
+ }
9
+ return value;
10
+ };
11
+ const formatEtag = (etag) => {
12
+ if (!etag) {
13
+ return etag;
14
+ }
15
+ if (etag.startsWith("W/") || etag.startsWith("\"")) {
16
+ return etag;
17
+ }
18
+ return `W/\"${etag}\"`;
19
+ };
20
+ const matchesEtag = (candidate, etag) => {
21
+ if (!candidate) {
22
+ return false;
23
+ }
24
+ if (candidate.trim() === "*") {
25
+ return true;
26
+ }
27
+ return candidate
28
+ .split(",")
29
+ .map((value) => value.trim())
30
+ .filter(Boolean)
31
+ .includes(etag);
32
+ };
33
+ const jsonResponse = (status, body) => ({
34
+ status,
35
+ headers: { "content-type": "application/json; charset=utf-8" },
36
+ body: JSON.stringify(body),
37
+ });
38
+ const buildEnvelopeResponse = (envelope, headers, ttlSeconds = DEFAULT_TTL_SECONDS) => {
39
+ const rawEtag = envelope.cache?.etag ?? hash(stableStringify(envelope));
40
+ const etag = formatEtag(rawEtag);
41
+ const cacheControl = `public, max-age=${ttlSeconds}`;
42
+ const ifNoneMatch = getHeaderValue(headers, "if-none-match");
43
+ // Honor cache validation to avoid sending full payloads when unchanged.
44
+ if (matchesEtag(ifNoneMatch, etag)) {
45
+ return {
46
+ status: 304,
47
+ headers: {
48
+ etag,
49
+ "cache-control": cacheControl,
50
+ },
51
+ };
52
+ }
53
+ return {
54
+ status: 200,
55
+ headers: {
56
+ "content-type": "application/json; charset=utf-8",
57
+ etag,
58
+ "cache-control": cacheControl,
59
+ },
60
+ body: JSON.stringify(envelope),
61
+ };
62
+ };
63
+ const buildRegistryResponse = (registry, headers) => {
64
+ const rawEtag = hash(stableStringify(registry));
65
+ const etag = formatEtag(rawEtag);
66
+ const cacheControl = `public, max-age=${REGISTRY_TTL_SECONDS}`;
67
+ const ifNoneMatch = getHeaderValue(headers, "if-none-match");
68
+ // Honor cache validation to avoid sending full payloads when unchanged.
69
+ if (matchesEtag(ifNoneMatch, etag)) {
70
+ return {
71
+ status: 304,
72
+ headers: {
73
+ etag,
74
+ "cache-control": cacheControl,
75
+ },
76
+ };
77
+ }
78
+ return {
79
+ status: 200,
80
+ headers: {
81
+ "content-type": "application/json; charset=utf-8",
82
+ etag,
83
+ "cache-control": cacheControl,
84
+ },
85
+ body: JSON.stringify(registry),
86
+ };
87
+ };
88
+ export const handleDynamicPageApiRequest = async (request, provider) => {
89
+ const method = request.method.toUpperCase();
90
+ if (method !== "GET") {
91
+ return jsonResponse(405, { error: "Method Not Allowed" });
92
+ }
93
+ if (request.pathname === "/api/pages/resolve") {
94
+ const path = request.query.get("path");
95
+ if (!path) {
96
+ return jsonResponse(400, { error: "Missing path" });
97
+ }
98
+ const envelope = await provider.resolveByPath({
99
+ path,
100
+ locale: request.query.get("locale") ?? undefined,
101
+ variant: request.query.get("variant") ?? undefined,
102
+ device: request.query.get("device") ?? undefined,
103
+ ctxHash: request.query.get("ctxHash") ?? undefined,
104
+ });
105
+ if (!envelope) {
106
+ return jsonResponse(404, { error: "Not Found" });
107
+ }
108
+ return buildEnvelopeResponse(envelope, request.headers, envelope.cache?.ttlSeconds);
109
+ }
110
+ if (request.pathname.startsWith("/api/pages/")) {
111
+ const pageId = request.pathname.replace("/api/pages/", "");
112
+ if (!pageId) {
113
+ return jsonResponse(400, { error: "Missing pageId" });
114
+ }
115
+ const envelope = await provider.resolveById(decodeURIComponent(pageId));
116
+ if (!envelope) {
117
+ return jsonResponse(404, { error: "Not Found" });
118
+ }
119
+ return buildEnvelopeResponse(envelope, request.headers, envelope.cache?.ttlSeconds);
120
+ }
121
+ if (request.pathname === "/api/components/registry") {
122
+ if (!provider.listComponents) {
123
+ return jsonResponse(404, { error: "Not Found" });
124
+ }
125
+ const filters = {};
126
+ for (const [key, value] of request.query.entries()) {
127
+ filters[key] = value;
128
+ }
129
+ const registry = await provider.listComponents(filters);
130
+ if (!registry) {
131
+ return jsonResponse(404, { error: "Not Found" });
132
+ }
133
+ return buildRegistryResponse(registry, request.headers);
134
+ }
135
+ return null;
136
+ };
@@ -0,0 +1,10 @@
1
+ export type HyperErrorCode = "CONFIG_INVALID" | "CONFIG_UNKNOWN_KEY" | "MANIFEST_INVALID" | "MANIFEST_UNKNOWN_KEY" | "DYNAMIC_MANIFEST_INVALID" | "DYNAMIC_MANIFEST_UNKNOWN_KEY" | "PROGRAM_POLICY_INVALID" | "PAGE_API_INVALID" | "ROUTE_INVALID" | "ROUTE_DUPLICATE" | "ROUTE_META_INVALID" | "ROUTE_META_LOAD_FAILED" | "PLUGIN_HOOK";
2
+ export type HyperErrorDetails = Record<string, unknown>;
3
+ export declare class HyperError extends Error {
4
+ readonly code: HyperErrorCode;
5
+ readonly details?: HyperErrorDetails;
6
+ constructor(code: HyperErrorCode, message: string, details?: HyperErrorDetails, cause?: unknown);
7
+ }
8
+ export declare const isHyperError: (error: unknown) => error is HyperError;
9
+ export declare const formatHyperError: (error: HyperError) => string;
10
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,cAAc,GACtB,gBAAgB,GAChB,oBAAoB,GACpB,kBAAkB,GAClB,sBAAsB,GACtB,0BAA0B,GAC1B,8BAA8B,GAC9B,wBAAwB,GACxB,kBAAkB,GAClB,eAAe,GACf,iBAAiB,GACjB,oBAAoB,GACpB,wBAAwB,GACxB,aAAa,CAAC;AAElB,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAExD,qBAAa,UAAW,SAAQ,KAAK;IACnC,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAC9B,QAAQ,CAAC,OAAO,CAAC,EAAE,iBAAiB,CAAC;gBAEzB,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,iBAAiB,EAAE,KAAK,CAAC,EAAE,OAAO;CAQhG;AAED,eAAO,MAAM,YAAY,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,UAAyC,CAAC;AAajG,eAAO,MAAM,gBAAgB,GAAI,OAAO,UAAU,KAAG,MAQpD,CAAC"}
package/dist/errors.js ADDED
@@ -0,0 +1,31 @@
1
+ export class HyperError extends Error {
2
+ constructor(code, message, details, cause) {
3
+ super(message);
4
+ this.code = code;
5
+ this.details = details;
6
+ if (cause !== undefined) {
7
+ this.cause = cause;
8
+ }
9
+ }
10
+ }
11
+ export const isHyperError = (error) => error instanceof HyperError;
12
+ const formatDetailValue = (value) => {
13
+ if (typeof value === "string") {
14
+ return value;
15
+ }
16
+ try {
17
+ return JSON.stringify(value);
18
+ }
19
+ catch {
20
+ return String(value);
21
+ }
22
+ };
23
+ export const formatHyperError = (error) => {
24
+ const lines = [`[hyper] ${error.code}: ${error.message}`];
25
+ if (error.details) {
26
+ for (const [key, value] of Object.entries(error.details)) {
27
+ lines.push(`- ${key}: ${formatDetailValue(value)}`);
28
+ }
29
+ }
30
+ return lines.join("\n");
31
+ };
package/dist/head.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import type { HeadDescriptor } from "./page-api.js";
2
+ export declare const mergeHeadDescriptors: (...descriptors: HeadDescriptor[]) => HeadDescriptor;
3
+ //# sourceMappingURL=head.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"head.d.ts","sourceRoot":"","sources":["../src/head.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD,eAAO,MAAM,oBAAoB,GAAI,GAAG,aAAa,cAAc,EAAE,KAAG,cAmCvE,CAAC"}
package/dist/head.js ADDED
@@ -0,0 +1,33 @@
1
+ export const mergeHeadDescriptors = (...descriptors) => {
2
+ const merged = {};
3
+ const meta = [];
4
+ const link = [];
5
+ const script = [];
6
+ for (const descriptor of descriptors) {
7
+ if (!descriptor) {
8
+ continue;
9
+ }
10
+ if (descriptor.title) {
11
+ merged.title = descriptor.title;
12
+ }
13
+ if (descriptor.meta) {
14
+ meta.push(...descriptor.meta);
15
+ }
16
+ if (descriptor.link) {
17
+ link.push(...descriptor.link);
18
+ }
19
+ if (descriptor.script) {
20
+ script.push(...descriptor.script);
21
+ }
22
+ }
23
+ if (meta.length) {
24
+ merged.meta = meta;
25
+ }
26
+ if (link.length) {
27
+ merged.link = link;
28
+ }
29
+ if (script.length) {
30
+ merged.script = script;
31
+ }
32
+ return merged;
33
+ };
@@ -0,0 +1,33 @@
1
+ export { loadConfig } from "./config.js";
2
+ export type { HyperConfig, ResolvedConfig, RenderMode, ProjectMode, JsxRuntime, Engine, Bundler, TranspileStrategy, LintTool, FormatTool, RuntimeAdapter, LegacyPolyfills, LegacyChunking, NavigationMode, ClientRenderMode, LoggingLevel, Plugin, } from "./config.js";
3
+ export { resolveUIAdapter } from "./ui-adapter.js";
4
+ export type { HmrIntegration, HmrOverlayHandler, HmrRefreshBoundary, UIAdapter, EntrySpec, UIAdapterEntryContext, UIAdapterFactory, UIAdapterRegistry, UIAdapterRenderContext, UIAdapterRenderResult, UIAdapterRenderStreamResult, UIAdapterStream, UIAdapterStreamTarget, } from "./ui-adapter.js";
5
+ export { resolveHmrIntegration } from "./ui-adapter.js";
6
+ export { manifestSchema, validateManifest } from "./manifest.js";
7
+ export type { Manifest, RouteEntry, RouteType, ChunkInfo } from "./manifest.js";
8
+ export { HyperError, formatHyperError, isHyperError } from "./errors.js";
9
+ export type { HyperErrorCode, HyperErrorDetails } from "./errors.js";
10
+ export { mergeHeadDescriptors } from "./head.js";
11
+ export { evaluateRenderPolicy } from "./render-policy.js";
12
+ export { resolveRouteWithFallback, resolveRouteWithPolicyFallback, shouldForceDynamicFallback, } from "./resolver-fallback.js";
13
+ export type { DynamicFallbackPolicy, DynamicManifestResolver, ResolverFallbackPolicy, RouteFallbackResult, RoutePolicyFallbackResult, } from "./resolver-fallback.js";
14
+ export { handleDynamicPageApiRequest } from "./dynamic-page-api.js";
15
+ export type { DynamicPageApiProvider, DynamicPageApiRequest, DynamicPageApiResult, DynamicPageComponentDescriptor, DynamicPageComponentRegistry, DynamicPageResolveInput, } from "./dynamic-page-api.js";
16
+ export { dynamicManifestSchema, validateDynamicManifest } from "./dynamic-manifest.js";
17
+ export type { DynamicPageCache, DynamicPageDefinition, DynamicPageEnvelope, DynamicPageMode, DynamicPagePolicy, MetaDefinition, MetaNode, ProgramDefinition, } from "./dynamic-manifest.js";
18
+ export { DEFAULT_PROGRAM_SANDBOX_POLICY, buildProgramSandboxContext, resolveProgramSandboxPolicy, validateProgramIntegrity, } from "./program-sandbox.js";
19
+ export type { ProgramSandboxPolicy, ProgramSandboxPolicyInput } from "./program-sandbox.js";
20
+ export { serializeProps } from "./props.js";
21
+ export { CLIENT_ROUTER_BOOTSTRAP_PATH, renderClientRouterBootstrap, } from "./client-router-bootstrap.js";
22
+ export type { ClientRouterBootstrapOptions, LinkInterceptionMode, } from "./client-router-bootstrap.js";
23
+ export { resolvePageModule, validateStaticPropsResult, validateStaticPathsResult, validateServerPropsResult, runGetStaticProps, runGetStaticPaths, runGetServerProps, } from "./page-api.js";
24
+ export type { RouteParams, PageContextBase, StaticPropsContext, StaticPropsResult, StaticPathEntry, StaticPathsResult, ServerPropsContext, ServerPropsResult, HeadDescriptor, PageComponent, GetStaticProps, GetStaticPaths, GetServerProps, Head, PageModule, } from "./page-api.js";
25
+ export { matchRoute } from "./router.js";
26
+ export type { RouteGraph, RouteRecord, RouteSegment, MatchResult, RoutePolicy } from "./router.js";
27
+ export { collectRouteLayouts, composeLayouts } from "./layouts.js";
28
+ export type { LayoutNode, LayoutResolution } from "./layouts.js";
29
+ export { createRouteGraph } from "./route-graph.js";
30
+ export type { RouteGraphOptions } from "./route-graph.js";
31
+ export { runConfigResolvedHooks, runRoutesResolvedHooks, runBeforeBundleHooks, runAfterBundleHooks, runBeforeRenderHooks, runAfterRenderHooks, collectDevMiddlewares, collectRuntimeMiddlewares, } from "./plugin.js";
32
+ export type { BundleContext, BundleResult, RenderContext, PluginHooks, HyperPlugin, Middleware, MiddlewareNext, } from "./plugin.js";
33
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,18 @@
1
+ export { loadConfig } from "./config.js";
2
+ export { resolveUIAdapter } from "./ui-adapter.js";
3
+ export { resolveHmrIntegration } from "./ui-adapter.js";
4
+ export { manifestSchema, validateManifest } from "./manifest.js";
5
+ export { HyperError, formatHyperError, isHyperError } from "./errors.js";
6
+ export { mergeHeadDescriptors } from "./head.js";
7
+ export { evaluateRenderPolicy } from "./render-policy.js";
8
+ export { resolveRouteWithFallback, resolveRouteWithPolicyFallback, shouldForceDynamicFallback, } from "./resolver-fallback.js";
9
+ export { handleDynamicPageApiRequest } from "./dynamic-page-api.js";
10
+ export { dynamicManifestSchema, validateDynamicManifest } from "./dynamic-manifest.js";
11
+ export { DEFAULT_PROGRAM_SANDBOX_POLICY, buildProgramSandboxContext, resolveProgramSandboxPolicy, validateProgramIntegrity, } from "./program-sandbox.js";
12
+ export { serializeProps } from "./props.js";
13
+ export { CLIENT_ROUTER_BOOTSTRAP_PATH, renderClientRouterBootstrap, } from "./client-router-bootstrap.js";
14
+ export { resolvePageModule, validateStaticPropsResult, validateStaticPathsResult, validateServerPropsResult, runGetStaticProps, runGetStaticPaths, runGetServerProps, } from "./page-api.js";
15
+ export { matchRoute } from "./router.js";
16
+ export { collectRouteLayouts, composeLayouts } from "./layouts.js";
17
+ export { createRouteGraph } from "./route-graph.js";
18
+ export { runConfigResolvedHooks, runRoutesResolvedHooks, runBeforeBundleHooks, runAfterBundleHooks, runBeforeRenderHooks, runAfterRenderHooks, collectDevMiddlewares, collectRuntimeMiddlewares, } from "./plugin.js";
@@ -0,0 +1,14 @@
1
+ export type LayoutNode = {
2
+ type: "layout";
3
+ filePath: string;
4
+ child: LayoutNode;
5
+ } | {
6
+ type: "page";
7
+ filePath: string;
8
+ };
9
+ export interface LayoutResolution {
10
+ layoutFiles: string[];
11
+ }
12
+ export declare const collectRouteLayouts: (routeFilePath: string, routesDir: string, extensions?: string[]) => LayoutResolution;
13
+ export declare const composeLayouts: (layoutFiles: string[], pageFilePath: string) => LayoutNode;
14
+ //# sourceMappingURL=layouts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layouts.d.ts","sourceRoot":"","sources":["../src/layouts.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,UAAU,CAAA;CAAE,GACvD;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvC,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAmBD,eAAO,MAAM,mBAAmB,GAC9B,eAAe,MAAM,EACrB,WAAW,MAAM,EACjB,aAAY,MAAM,EAAsB,KACvC,gBA4BF,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,aAAa,MAAM,EAAE,EAAE,cAAc,MAAM,KAAG,UAS5E,CAAC"}
@@ -0,0 +1,51 @@
1
+ import { statSync } from "node:fs";
2
+ import { join, relative, sep } from "node:path";
3
+ import { HyperError } from "./errors.js";
4
+ const defaultExtensions = [".tsx", ".ts", ".jsx", ".js"];
5
+ const findLayoutFile = (dir, extensions) => {
6
+ for (const ext of extensions) {
7
+ const candidate = join(dir, `_layout${ext}`);
8
+ try {
9
+ const stats = statSync(candidate);
10
+ if (stats.isFile()) {
11
+ return candidate;
12
+ }
13
+ }
14
+ catch {
15
+ continue;
16
+ }
17
+ }
18
+ return null;
19
+ };
20
+ export const collectRouteLayouts = (routeFilePath, routesDir, extensions = defaultExtensions) => {
21
+ const relativePath = relative(routesDir, routeFilePath);
22
+ if (!relativePath || relativePath.startsWith("..") || relativePath.includes(`..${sep}`)) {
23
+ throw new HyperError("ROUTE_INVALID", "Route file is outside of routesDir.", {
24
+ routeFilePath,
25
+ routesDir,
26
+ });
27
+ }
28
+ const segments = relativePath.split(sep);
29
+ segments.pop();
30
+ const layoutFiles = [];
31
+ const rootLayout = findLayoutFile(routesDir, extensions);
32
+ if (rootLayout) {
33
+ layoutFiles.push(rootLayout);
34
+ }
35
+ let currentDir = routesDir;
36
+ for (const segment of segments) {
37
+ currentDir = join(currentDir, segment);
38
+ const layoutFile = findLayoutFile(currentDir, extensions);
39
+ if (layoutFile) {
40
+ layoutFiles.push(layoutFile);
41
+ }
42
+ }
43
+ return { layoutFiles };
44
+ };
45
+ export const composeLayouts = (layoutFiles, pageFilePath) => {
46
+ // Important: compose outer→inner so root layout wraps leaf layout wraps page.
47
+ return layoutFiles
48
+ .slice()
49
+ .reverse()
50
+ .reduce((child, layoutFile) => ({ type: "layout", filePath: layoutFile, child }), { type: "page", filePath: pageFilePath });
51
+ };