@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.
- package/README.md +34 -0
- package/dist/client-router-bootstrap.d.ts +11 -0
- package/dist/client-router-bootstrap.d.ts.map +1 -0
- package/dist/client-router-bootstrap.js +319 -0
- package/dist/config.d.ts +197 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +552 -0
- package/dist/dynamic-manifest.d.ts +78 -0
- package/dist/dynamic-manifest.d.ts.map +1 -0
- package/dist/dynamic-manifest.js +189 -0
- package/dist/dynamic-page-api.d.ts +34 -0
- package/dist/dynamic-page-api.d.ts.map +1 -0
- package/dist/dynamic-page-api.js +136 -0
- package/dist/errors.d.ts +10 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +31 -0
- package/dist/head.d.ts +3 -0
- package/dist/head.d.ts.map +1 -0
- package/dist/head.js +33 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/layouts.d.ts +14 -0
- package/dist/layouts.d.ts.map +1 -0
- package/dist/layouts.js +51 -0
- package/dist/manifest.d.ts +81 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +139 -0
- package/dist/page-api.d.ts +56 -0
- package/dist/page-api.d.ts.map +1 -0
- package/dist/page-api.js +100 -0
- package/dist/plugin.d.ts +35 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +133 -0
- package/dist/program-sandbox.d.ts +18 -0
- package/dist/program-sandbox.d.ts.map +1 -0
- package/dist/program-sandbox.js +84 -0
- package/dist/props.d.ts +2 -0
- package/dist/props.d.ts.map +1 -0
- package/dist/props.js +14 -0
- package/dist/render-policy.d.ts +8 -0
- package/dist/render-policy.d.ts.map +1 -0
- package/dist/render-policy.js +12 -0
- package/dist/resolver-fallback.d.ts +49 -0
- package/dist/resolver-fallback.d.ts.map +1 -0
- package/dist/resolver-fallback.js +99 -0
- package/dist/route-graph.d.ts +9 -0
- package/dist/route-graph.d.ts.map +1 -0
- package/dist/route-graph.js +157 -0
- package/dist/route-meta.d.ts +4 -0
- package/dist/route-meta.d.ts.map +1 -0
- package/dist/route-meta.js +48 -0
- package/dist/router.d.ts +27 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +83 -0
- package/dist/ui-adapter.d.ts +67 -0
- package/dist/ui-adapter.d.ts.map +1 -0
- package/dist/ui-adapter.js +27 -0
- package/package.json +23 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
export type RouteType = "static" | "dynamic" | "catchAll";
|
|
2
|
+
export interface RouteEntry {
|
|
3
|
+
type: RouteType;
|
|
4
|
+
html?: string;
|
|
5
|
+
clientEntry: string;
|
|
6
|
+
serverEntry?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ChunkInfo {
|
|
9
|
+
file: string;
|
|
10
|
+
imports?: string[];
|
|
11
|
+
}
|
|
12
|
+
export interface Manifest {
|
|
13
|
+
version: string;
|
|
14
|
+
basePath: string;
|
|
15
|
+
routes: Record<string, RouteEntry>;
|
|
16
|
+
chunks: Record<string, ChunkInfo>;
|
|
17
|
+
assets?: Record<string, string>;
|
|
18
|
+
}
|
|
19
|
+
export declare const manifestSchema: {
|
|
20
|
+
readonly $schema: "http://json-schema.org/draft-07/schema#";
|
|
21
|
+
readonly type: "object";
|
|
22
|
+
readonly required: readonly ["version", "basePath", "routes", "chunks"];
|
|
23
|
+
readonly additionalProperties: false;
|
|
24
|
+
readonly properties: {
|
|
25
|
+
readonly version: {
|
|
26
|
+
readonly type: "string";
|
|
27
|
+
};
|
|
28
|
+
readonly basePath: {
|
|
29
|
+
readonly type: "string";
|
|
30
|
+
};
|
|
31
|
+
readonly routes: {
|
|
32
|
+
readonly type: "object";
|
|
33
|
+
readonly additionalProperties: {
|
|
34
|
+
readonly type: "object";
|
|
35
|
+
readonly required: readonly ["type", "clientEntry"];
|
|
36
|
+
readonly additionalProperties: false;
|
|
37
|
+
readonly properties: {
|
|
38
|
+
readonly type: {
|
|
39
|
+
readonly enum: readonly ["static", "dynamic", "catchAll"];
|
|
40
|
+
};
|
|
41
|
+
readonly html: {
|
|
42
|
+
readonly type: "string";
|
|
43
|
+
};
|
|
44
|
+
readonly clientEntry: {
|
|
45
|
+
readonly type: "string";
|
|
46
|
+
};
|
|
47
|
+
readonly serverEntry: {
|
|
48
|
+
readonly type: "string";
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
readonly chunks: {
|
|
54
|
+
readonly type: "object";
|
|
55
|
+
readonly additionalProperties: {
|
|
56
|
+
readonly type: "object";
|
|
57
|
+
readonly required: readonly ["file"];
|
|
58
|
+
readonly additionalProperties: false;
|
|
59
|
+
readonly properties: {
|
|
60
|
+
readonly file: {
|
|
61
|
+
readonly type: "string";
|
|
62
|
+
};
|
|
63
|
+
readonly imports: {
|
|
64
|
+
readonly type: "array";
|
|
65
|
+
readonly items: {
|
|
66
|
+
readonly type: "string";
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
readonly assets: {
|
|
73
|
+
readonly type: "object";
|
|
74
|
+
readonly additionalProperties: {
|
|
75
|
+
readonly type: "string";
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
export declare const validateManifest: (input: unknown) => Manifest;
|
|
81
|
+
//# sourceMappingURL=manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,UAAU,CAAC;AAE1D,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACnC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuCjB,CAAC;AAsGX,eAAO,MAAM,gBAAgB,GAAI,OAAO,OAAO,KAAG,QAuDjD,CAAC"}
|
package/dist/manifest.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { HyperError } from "./errors.js";
|
|
2
|
+
export const manifestSchema = {
|
|
3
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
4
|
+
type: "object",
|
|
5
|
+
required: ["version", "basePath", "routes", "chunks"],
|
|
6
|
+
additionalProperties: false,
|
|
7
|
+
properties: {
|
|
8
|
+
version: { type: "string" },
|
|
9
|
+
basePath: { type: "string" },
|
|
10
|
+
routes: {
|
|
11
|
+
type: "object",
|
|
12
|
+
additionalProperties: {
|
|
13
|
+
type: "object",
|
|
14
|
+
required: ["type", "clientEntry"],
|
|
15
|
+
additionalProperties: false,
|
|
16
|
+
properties: {
|
|
17
|
+
type: { enum: ["static", "dynamic", "catchAll"] },
|
|
18
|
+
html: { type: "string" },
|
|
19
|
+
clientEntry: { type: "string" },
|
|
20
|
+
serverEntry: { type: "string" },
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
chunks: {
|
|
25
|
+
type: "object",
|
|
26
|
+
additionalProperties: {
|
|
27
|
+
type: "object",
|
|
28
|
+
required: ["file"],
|
|
29
|
+
additionalProperties: false,
|
|
30
|
+
properties: {
|
|
31
|
+
file: { type: "string" },
|
|
32
|
+
imports: { type: "array", items: { type: "string" } },
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
assets: {
|
|
37
|
+
type: "object",
|
|
38
|
+
additionalProperties: { type: "string" },
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
43
|
+
const assertKnownKeys = (value, allowed, path) => {
|
|
44
|
+
for (const key of Object.keys(value)) {
|
|
45
|
+
if (!allowed.has(key)) {
|
|
46
|
+
throw new HyperError("MANIFEST_UNKNOWN_KEY", `Unknown manifest key: ${path}${key}`, {
|
|
47
|
+
path: `${path}${key}`,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
const ensureString = (value, path) => {
|
|
53
|
+
if (typeof value !== "string") {
|
|
54
|
+
throw new HyperError("MANIFEST_INVALID", `Invalid manifest value at ${path}: expected string`, { path, expected: "string" });
|
|
55
|
+
}
|
|
56
|
+
return value;
|
|
57
|
+
};
|
|
58
|
+
const ensureStringArray = (value, path) => {
|
|
59
|
+
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
|
|
60
|
+
throw new HyperError("MANIFEST_INVALID", `Invalid manifest value at ${path}: expected string[]`, { path, expected: "string[]" });
|
|
61
|
+
}
|
|
62
|
+
return value;
|
|
63
|
+
};
|
|
64
|
+
const ensureRouteEntry = (value, path) => {
|
|
65
|
+
if (!isRecord(value)) {
|
|
66
|
+
throw new HyperError("MANIFEST_INVALID", `Invalid manifest value at ${path}: expected object`, { path, expected: "object" });
|
|
67
|
+
}
|
|
68
|
+
// Critical: enforce exact route entry keys so build/runtime stay in sync.
|
|
69
|
+
assertKnownKeys(value, new Set(["type", "html", "clientEntry", "serverEntry"]), `${path}.`);
|
|
70
|
+
const type = ensureString(value.type, `${path}.type`);
|
|
71
|
+
if (type !== "static" && type !== "dynamic" && type !== "catchAll") {
|
|
72
|
+
throw new HyperError("MANIFEST_INVALID", `Invalid manifest value at ${path}.type: ${type}`, { path: `${path}.type`, expected: "static | dynamic | catchAll" });
|
|
73
|
+
}
|
|
74
|
+
const entry = {
|
|
75
|
+
type,
|
|
76
|
+
clientEntry: ensureString(value.clientEntry, `${path}.clientEntry`),
|
|
77
|
+
};
|
|
78
|
+
if ("html" in value) {
|
|
79
|
+
entry.html = ensureString(value.html, `${path}.html`);
|
|
80
|
+
}
|
|
81
|
+
else if (type === "static") {
|
|
82
|
+
throw new HyperError("MANIFEST_INVALID", `Missing manifest value at ${path}.html for static route`, { path: `${path}.html` });
|
|
83
|
+
}
|
|
84
|
+
if ("serverEntry" in value) {
|
|
85
|
+
entry.serverEntry = ensureString(value.serverEntry, `${path}.serverEntry`);
|
|
86
|
+
}
|
|
87
|
+
return entry;
|
|
88
|
+
};
|
|
89
|
+
const ensureChunkInfo = (value, path) => {
|
|
90
|
+
if (!isRecord(value)) {
|
|
91
|
+
throw new HyperError("MANIFEST_INVALID", `Invalid manifest value at ${path}: expected object`, { path, expected: "object" });
|
|
92
|
+
}
|
|
93
|
+
assertKnownKeys(value, new Set(["file", "imports"]), `${path}.`);
|
|
94
|
+
const chunk = {
|
|
95
|
+
file: ensureString(value.file, `${path}.file`),
|
|
96
|
+
};
|
|
97
|
+
if ("imports" in value) {
|
|
98
|
+
chunk.imports = ensureStringArray(value.imports, `${path}.imports`);
|
|
99
|
+
}
|
|
100
|
+
return chunk;
|
|
101
|
+
};
|
|
102
|
+
export const validateManifest = (input) => {
|
|
103
|
+
if (!isRecord(input)) {
|
|
104
|
+
throw new HyperError("MANIFEST_INVALID", "Manifest must be an object.", {
|
|
105
|
+
expected: "object",
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
// Critical: fail on unknown top-level keys to avoid mismatch with schema consumers.
|
|
109
|
+
assertKnownKeys(input, new Set(["version", "basePath", "routes", "chunks", "assets"]), "");
|
|
110
|
+
const manifest = {
|
|
111
|
+
version: ensureString(input.version, "version"),
|
|
112
|
+
basePath: ensureString(input.basePath, "basePath"),
|
|
113
|
+
routes: {},
|
|
114
|
+
chunks: {},
|
|
115
|
+
};
|
|
116
|
+
if (!isRecord(input.routes)) {
|
|
117
|
+
throw new HyperError("MANIFEST_INVALID", "Invalid manifest value at routes: expected object", { path: "routes", expected: "object" });
|
|
118
|
+
}
|
|
119
|
+
for (const [route, entry] of Object.entries(input.routes)) {
|
|
120
|
+
manifest.routes[route] = ensureRouteEntry(entry, `routes.${route}`);
|
|
121
|
+
}
|
|
122
|
+
if (!isRecord(input.chunks)) {
|
|
123
|
+
throw new HyperError("MANIFEST_INVALID", "Invalid manifest value at chunks: expected object", { path: "chunks", expected: "object" });
|
|
124
|
+
}
|
|
125
|
+
for (const [key, info] of Object.entries(input.chunks)) {
|
|
126
|
+
manifest.chunks[key] = ensureChunkInfo(info, `chunks.${key}`);
|
|
127
|
+
}
|
|
128
|
+
if ("assets" in input) {
|
|
129
|
+
if (!isRecord(input.assets)) {
|
|
130
|
+
throw new HyperError("MANIFEST_INVALID", "Invalid manifest value at assets: expected object", { path: "assets", expected: "object" });
|
|
131
|
+
}
|
|
132
|
+
const assets = {};
|
|
133
|
+
for (const [key, value] of Object.entries(input.assets)) {
|
|
134
|
+
assets[key] = ensureString(value, `assets.${key}`);
|
|
135
|
+
}
|
|
136
|
+
manifest.assets = assets;
|
|
137
|
+
}
|
|
138
|
+
return manifest;
|
|
139
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export type RouteParams = Record<string, string | string[]>;
|
|
2
|
+
export interface PageContextBase {
|
|
3
|
+
routeId: string;
|
|
4
|
+
params?: RouteParams;
|
|
5
|
+
locale?: string;
|
|
6
|
+
preview?: boolean;
|
|
7
|
+
previewData?: unknown;
|
|
8
|
+
}
|
|
9
|
+
export interface StaticPropsContext extends PageContextBase {
|
|
10
|
+
}
|
|
11
|
+
export interface StaticPropsResult<Props = Record<string, unknown>> {
|
|
12
|
+
props: Props;
|
|
13
|
+
revalidate?: number | false;
|
|
14
|
+
}
|
|
15
|
+
export interface StaticPathEntry {
|
|
16
|
+
params: RouteParams;
|
|
17
|
+
}
|
|
18
|
+
export interface StaticPathsResult {
|
|
19
|
+
paths: StaticPathEntry[];
|
|
20
|
+
fallback?: boolean | "blocking";
|
|
21
|
+
}
|
|
22
|
+
export interface ServerPropsContext extends PageContextBase {
|
|
23
|
+
request?: unknown;
|
|
24
|
+
response?: unknown;
|
|
25
|
+
}
|
|
26
|
+
export interface ServerPropsResult<Props = Record<string, unknown>> {
|
|
27
|
+
props: Props;
|
|
28
|
+
headers?: Record<string, string>;
|
|
29
|
+
status?: number;
|
|
30
|
+
}
|
|
31
|
+
export interface HeadDescriptor {
|
|
32
|
+
title?: string;
|
|
33
|
+
meta?: Array<Record<string, string>>;
|
|
34
|
+
link?: Array<Record<string, string>>;
|
|
35
|
+
script?: Array<Record<string, string>>;
|
|
36
|
+
}
|
|
37
|
+
export type PageComponent<Props = Record<string, unknown>> = (props: Props) => unknown;
|
|
38
|
+
export type GetStaticProps<Props = Record<string, unknown>> = (ctx: StaticPropsContext) => Promise<StaticPropsResult<Props>> | StaticPropsResult<Props>;
|
|
39
|
+
export type GetStaticPaths = () => Promise<StaticPathsResult> | StaticPathsResult;
|
|
40
|
+
export type GetServerProps<Props = Record<string, unknown>> = (ctx: ServerPropsContext) => Promise<ServerPropsResult<Props>> | ServerPropsResult<Props>;
|
|
41
|
+
export type Head<Props = Record<string, unknown>> = (props: Props) => HeadDescriptor;
|
|
42
|
+
export interface PageModule<Props = Record<string, unknown>> {
|
|
43
|
+
default: PageComponent<Props>;
|
|
44
|
+
getStaticProps?: GetStaticProps<Props>;
|
|
45
|
+
getStaticPaths?: GetStaticPaths;
|
|
46
|
+
getServerProps?: GetServerProps<Props>;
|
|
47
|
+
head?: Head<Props>;
|
|
48
|
+
}
|
|
49
|
+
export declare const resolvePageModule: <Props = Record<string, unknown>>(module: unknown) => PageModule<Props>;
|
|
50
|
+
export declare const validateStaticPropsResult: <Props = Record<string, unknown>>(result: unknown) => StaticPropsResult<Props>;
|
|
51
|
+
export declare const validateStaticPathsResult: (result: unknown) => StaticPathsResult;
|
|
52
|
+
export declare const validateServerPropsResult: <Props = Record<string, unknown>>(result: unknown) => ServerPropsResult<Props>;
|
|
53
|
+
export declare const runGetStaticProps: <Props = Record<string, unknown>>(hook: GetStaticProps<Props>, ctx: StaticPropsContext) => Promise<StaticPropsResult<Props>>;
|
|
54
|
+
export declare const runGetStaticPaths: (hook: GetStaticPaths) => Promise<StaticPathsResult>;
|
|
55
|
+
export declare const runGetServerProps: <Props = Record<string, unknown>>(hook: GetServerProps<Props>, ctx: ServerPropsContext) => Promise<ServerPropsResult<Props>>;
|
|
56
|
+
//# sourceMappingURL=page-api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page-api.d.ts","sourceRoot":"","sources":["../src/page-api.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;AAE5D,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,kBAAmB,SAAQ,eAAe;CAAG;AAE9D,MAAM,WAAW,iBAAiB,CAAC,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAChE,KAAK,EAAE,KAAK,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;CAC7B;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,QAAQ,CAAC,EAAE,OAAO,GAAG,UAAU,CAAC;CACjC;AAED,MAAM,WAAW,kBAAmB,SAAQ,eAAe;IACzD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB,CAAC,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAChE,KAAK,EAAE,KAAK,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACrC,IAAI,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACrC,MAAM,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACxC;AAED,MAAM,MAAM,aAAa,CAAC,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC;AACvF,MAAM,MAAM,cAAc,CAAC,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAC5D,GAAG,EAAE,kBAAkB,KACpB,OAAO,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;AAClE,MAAM,MAAM,cAAc,GAAG,MAAM,OAAO,CAAC,iBAAiB,CAAC,GAAG,iBAAiB,CAAC;AAClF,MAAM,MAAM,cAAc,CAAC,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAC5D,GAAG,EAAE,kBAAkB,KACpB,OAAO,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;AAClE,MAAM,MAAM,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,KAAK,cAAc,CAAC;AAErF,MAAM,WAAW,UAAU,CAAC,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACzD,OAAO,EAAE,aAAa,CAAC,KAAK,CAAC,CAAC;IAC9B,cAAc,CAAC,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC;IACvC,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,cAAc,CAAC,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC;IACvC,IAAI,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;CACpB;AAmBD,eAAO,MAAM,iBAAiB,GAAI,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/D,QAAQ,OAAO,KACd,UAAU,CAAC,KAAK,CAuClB,CAAC;AAYF,eAAO,MAAM,yBAAyB,GAAI,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACvE,QAAQ,OAAO,KACd,iBAAiB,CAAC,KAAK,CAoBzB,CAAC;AAEF,eAAO,MAAM,yBAAyB,GAAI,QAAQ,OAAO,KAAG,iBAmC3D,CAAC;AAEF,eAAO,MAAM,yBAAyB,GAAI,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACvE,QAAQ,OAAO,KACd,iBAAiB,CAAC,KAAK,CA6BzB,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAU,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACrE,MAAM,cAAc,CAAC,KAAK,CAAC,EAC3B,KAAK,kBAAkB,KACtB,OAAO,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAIlC,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC5B,MAAM,cAAc,KACnB,OAAO,CAAC,iBAAiB,CAG3B,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAU,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACrE,MAAM,cAAc,CAAC,KAAK,CAAC,EAC3B,KAAK,kBAAkB,KACtB,OAAO,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAGlC,CAAC"}
|
package/dist/page-api.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { HyperError } from "./errors.js";
|
|
2
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3
|
+
const ensureFunction = (value, path) => {
|
|
4
|
+
if (typeof value !== "function") {
|
|
5
|
+
throw new HyperError("PAGE_API_INVALID", `Invalid page module export at ${path}: expected function`, { path, expected: "function" });
|
|
6
|
+
}
|
|
7
|
+
return value;
|
|
8
|
+
};
|
|
9
|
+
export const resolvePageModule = (module) => {
|
|
10
|
+
if (!isRecord(module)) {
|
|
11
|
+
throw new HyperError("PAGE_API_INVALID", "Page module must be an object.", {
|
|
12
|
+
expected: "object",
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
const defaultExport = ensureFunction(module.default, "default");
|
|
16
|
+
const page = { default: defaultExport };
|
|
17
|
+
if ("getStaticProps" in module) {
|
|
18
|
+
page.getStaticProps = ensureFunction(module.getStaticProps, "getStaticProps");
|
|
19
|
+
}
|
|
20
|
+
if ("getStaticPaths" in module) {
|
|
21
|
+
page.getStaticPaths = ensureFunction(module.getStaticPaths, "getStaticPaths");
|
|
22
|
+
}
|
|
23
|
+
if ("getServerProps" in module) {
|
|
24
|
+
page.getServerProps = ensureFunction(module.getServerProps, "getServerProps");
|
|
25
|
+
}
|
|
26
|
+
if (!page.getServerProps && "getServerSideProps" in module) {
|
|
27
|
+
page.getServerProps = ensureFunction(module.getServerSideProps, "getServerSideProps");
|
|
28
|
+
}
|
|
29
|
+
if ("head" in module) {
|
|
30
|
+
page.head = ensureFunction(module.head, "head");
|
|
31
|
+
}
|
|
32
|
+
return page;
|
|
33
|
+
};
|
|
34
|
+
const ensureRecord = (value, path) => {
|
|
35
|
+
if (!isRecord(value)) {
|
|
36
|
+
throw new HyperError("PAGE_API_INVALID", `Invalid page API result at ${path}`, {
|
|
37
|
+
path,
|
|
38
|
+
expected: "object",
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
return value;
|
|
42
|
+
};
|
|
43
|
+
export const validateStaticPropsResult = (result) => {
|
|
44
|
+
const record = ensureRecord(result, "getStaticProps");
|
|
45
|
+
if (!("props" in record)) {
|
|
46
|
+
throw new HyperError("PAGE_API_INVALID", "Invalid getStaticProps result: missing props", { path: "getStaticProps.props" });
|
|
47
|
+
}
|
|
48
|
+
const revalidate = record.revalidate;
|
|
49
|
+
if (revalidate !== undefined && revalidate !== false && typeof revalidate !== "number") {
|
|
50
|
+
throw new HyperError("PAGE_API_INVALID", "Invalid getStaticProps result: revalidate must be number or false", { path: "getStaticProps.revalidate" });
|
|
51
|
+
}
|
|
52
|
+
return record;
|
|
53
|
+
};
|
|
54
|
+
export const validateStaticPathsResult = (result) => {
|
|
55
|
+
const record = ensureRecord(result, "getStaticPaths");
|
|
56
|
+
if (!Array.isArray(record.paths)) {
|
|
57
|
+
throw new HyperError("PAGE_API_INVALID", "Invalid getStaticPaths result: paths must be array", { path: "getStaticPaths.paths" });
|
|
58
|
+
}
|
|
59
|
+
for (const entry of record.paths) {
|
|
60
|
+
const entryRecord = ensureRecord(entry, "getStaticPaths.paths[]");
|
|
61
|
+
if (!isRecord(entryRecord.params)) {
|
|
62
|
+
throw new HyperError("PAGE_API_INVALID", "Invalid getStaticPaths result: params must be object", { path: "getStaticPaths.paths[].params" });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const fallback = record.fallback;
|
|
66
|
+
if (fallback !== undefined &&
|
|
67
|
+
fallback !== "blocking" &&
|
|
68
|
+
typeof fallback !== "boolean") {
|
|
69
|
+
throw new HyperError("PAGE_API_INVALID", "Invalid getStaticPaths result: fallback must be boolean or 'blocking'", { path: "getStaticPaths.fallback" });
|
|
70
|
+
}
|
|
71
|
+
return record;
|
|
72
|
+
};
|
|
73
|
+
export const validateServerPropsResult = (result) => {
|
|
74
|
+
const record = ensureRecord(result, "getServerProps");
|
|
75
|
+
if (!("props" in record)) {
|
|
76
|
+
throw new HyperError("PAGE_API_INVALID", "Invalid getServerProps result: missing props", { path: "getServerProps.props" });
|
|
77
|
+
}
|
|
78
|
+
if (record.status !== undefined && typeof record.status !== "number") {
|
|
79
|
+
throw new HyperError("PAGE_API_INVALID", "Invalid getServerProps result: status must be number", { path: "getServerProps.status" });
|
|
80
|
+
}
|
|
81
|
+
if (record.headers !== undefined) {
|
|
82
|
+
if (!isRecord(record.headers)) {
|
|
83
|
+
throw new HyperError("PAGE_API_INVALID", "Invalid getServerProps result: headers must be object", { path: "getServerProps.headers" });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return record;
|
|
87
|
+
};
|
|
88
|
+
export const runGetStaticProps = async (hook, ctx) => {
|
|
89
|
+
const result = await hook(ctx);
|
|
90
|
+
// Important: validate return shape to keep build/runtime behavior consistent.
|
|
91
|
+
return validateStaticPropsResult(result);
|
|
92
|
+
};
|
|
93
|
+
export const runGetStaticPaths = async (hook) => {
|
|
94
|
+
const result = await hook();
|
|
95
|
+
return validateStaticPathsResult(result);
|
|
96
|
+
};
|
|
97
|
+
export const runGetServerProps = async (hook, ctx) => {
|
|
98
|
+
const result = await hook(ctx);
|
|
99
|
+
return validateServerPropsResult(result);
|
|
100
|
+
};
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Plugin, ResolvedConfig } from "./config.js";
|
|
2
|
+
import type { RouteGraph } from "./router.js";
|
|
3
|
+
export type MiddlewareNext = (error?: unknown) => void;
|
|
4
|
+
export type Middleware = (req: unknown, res: unknown, next: MiddlewareNext) => void | Promise<void>;
|
|
5
|
+
export interface BundleContext {
|
|
6
|
+
[key: string]: unknown;
|
|
7
|
+
}
|
|
8
|
+
export interface BundleResult {
|
|
9
|
+
[key: string]: unknown;
|
|
10
|
+
}
|
|
11
|
+
export interface RenderContext {
|
|
12
|
+
routeId?: string;
|
|
13
|
+
params?: Record<string, string | string[]>;
|
|
14
|
+
[key: string]: unknown;
|
|
15
|
+
}
|
|
16
|
+
export interface PluginHooks {
|
|
17
|
+
configResolved?: (config: ResolvedConfig) => void | ResolvedConfig | Promise<void | ResolvedConfig>;
|
|
18
|
+
routesResolved?: (routes: RouteGraph) => void | RouteGraph | Promise<void | RouteGraph>;
|
|
19
|
+
beforeBundle?: (ctx: BundleContext) => void | Promise<void>;
|
|
20
|
+
afterBundle?: (result: BundleResult) => void | Promise<void>;
|
|
21
|
+
beforeRender?: (routeCtx: RenderContext) => void | Promise<void>;
|
|
22
|
+
afterRender?: (html: string, routeCtx: RenderContext) => string | void | Promise<string | void>;
|
|
23
|
+
devMiddleware?: Middleware;
|
|
24
|
+
runtimeMiddleware?: Middleware;
|
|
25
|
+
}
|
|
26
|
+
export type HyperPlugin = Plugin & PluginHooks;
|
|
27
|
+
export declare const runConfigResolvedHooks: (plugins: readonly HyperPlugin[], config: ResolvedConfig) => Promise<ResolvedConfig>;
|
|
28
|
+
export declare const runRoutesResolvedHooks: (plugins: readonly HyperPlugin[], routeGraph: RouteGraph) => Promise<RouteGraph>;
|
|
29
|
+
export declare const runBeforeBundleHooks: (plugins: readonly HyperPlugin[], ctx: BundleContext) => Promise<void>;
|
|
30
|
+
export declare const runAfterBundleHooks: (plugins: readonly HyperPlugin[], result: BundleResult) => Promise<void>;
|
|
31
|
+
export declare const runBeforeRenderHooks: (plugins: readonly HyperPlugin[], routeCtx: RenderContext) => Promise<void>;
|
|
32
|
+
export declare const runAfterRenderHooks: (plugins: readonly HyperPlugin[], html: string, routeCtx: RenderContext) => Promise<string>;
|
|
33
|
+
export declare const collectDevMiddlewares: (plugins: readonly HyperPlugin[]) => Middleware[];
|
|
34
|
+
export declare const collectRuntimeMiddlewares: (plugins: readonly HyperPlugin[]) => Middleware[];
|
|
35
|
+
//# sourceMappingURL=plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { HyperError } from "./errors.js";
|
|
2
|
+
const pluginLabel = (plugin) => {
|
|
3
|
+
const label = typeof plugin.name === "string" ? plugin.name.trim() : "";
|
|
4
|
+
return label.length > 0 ? label : "anonymous";
|
|
5
|
+
};
|
|
6
|
+
const wrapHookError = (hook, plugin, error) => {
|
|
7
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
8
|
+
return new HyperError("PLUGIN_HOOK", `Plugin "${pluginLabel(plugin)}" failed in ${hook}: ${detail}`, { hook, plugin: pluginLabel(plugin) }, error);
|
|
9
|
+
};
|
|
10
|
+
const normalizePlugins = (plugins) => plugins ?? [];
|
|
11
|
+
export const runConfigResolvedHooks = async (plugins, config) => {
|
|
12
|
+
let nextConfig = config;
|
|
13
|
+
for (const plugin of normalizePlugins(plugins)) {
|
|
14
|
+
const hook = plugin.configResolved;
|
|
15
|
+
if (!hook) {
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const result = await hook(nextConfig);
|
|
20
|
+
if (result !== undefined) {
|
|
21
|
+
nextConfig = result;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
throw wrapHookError("configResolved", plugin, error);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return nextConfig;
|
|
29
|
+
};
|
|
30
|
+
export const runRoutesResolvedHooks = async (plugins, routeGraph) => {
|
|
31
|
+
let nextGraph = routeGraph;
|
|
32
|
+
for (const plugin of normalizePlugins(plugins)) {
|
|
33
|
+
const hook = plugin.routesResolved;
|
|
34
|
+
if (!hook) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
const result = await hook(nextGraph);
|
|
39
|
+
if (result !== undefined) {
|
|
40
|
+
nextGraph = result;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
throw wrapHookError("routesResolved", plugin, error);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return nextGraph;
|
|
48
|
+
};
|
|
49
|
+
export const runBeforeBundleHooks = async (plugins, ctx) => {
|
|
50
|
+
for (const plugin of normalizePlugins(plugins)) {
|
|
51
|
+
const hook = plugin.beforeBundle;
|
|
52
|
+
if (!hook) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
await hook(ctx);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
throw wrapHookError("beforeBundle", plugin, error);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
export const runAfterBundleHooks = async (plugins, result) => {
|
|
64
|
+
for (const plugin of normalizePlugins(plugins)) {
|
|
65
|
+
const hook = plugin.afterBundle;
|
|
66
|
+
if (!hook) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
await hook(result);
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
throw wrapHookError("afterBundle", plugin, error);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
export const runBeforeRenderHooks = async (plugins, routeCtx) => {
|
|
78
|
+
for (const plugin of normalizePlugins(plugins)) {
|
|
79
|
+
const hook = plugin.beforeRender;
|
|
80
|
+
if (!hook) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
await hook(routeCtx);
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
throw wrapHookError("beforeRender", plugin, error);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
export const runAfterRenderHooks = async (plugins, html, routeCtx) => {
|
|
92
|
+
let nextHtml = html;
|
|
93
|
+
// Important: apply hooks sequentially for deterministic HTML output.
|
|
94
|
+
for (const plugin of normalizePlugins(plugins)) {
|
|
95
|
+
const hook = plugin.afterRender;
|
|
96
|
+
if (!hook) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
const result = await hook(nextHtml, routeCtx);
|
|
101
|
+
if (typeof result === "string") {
|
|
102
|
+
nextHtml = result;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
throw wrapHookError("afterRender", plugin, error);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return nextHtml;
|
|
110
|
+
};
|
|
111
|
+
const wrapMiddleware = (hookName, plugin, middleware) => {
|
|
112
|
+
return async (req, res, next) => {
|
|
113
|
+
try {
|
|
114
|
+
await middleware(req, res, next);
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
next(wrapHookError(hookName, plugin, error));
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
const collectMiddlewares = (plugins, hookName) => {
|
|
122
|
+
const middlewares = [];
|
|
123
|
+
for (const plugin of normalizePlugins(plugins)) {
|
|
124
|
+
const hook = plugin[hookName];
|
|
125
|
+
if (!hook) {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
middlewares.push(wrapMiddleware(hookName, plugin, hook));
|
|
129
|
+
}
|
|
130
|
+
return middlewares;
|
|
131
|
+
};
|
|
132
|
+
export const collectDevMiddlewares = (plugins) => collectMiddlewares(plugins, "devMiddleware");
|
|
133
|
+
export const collectRuntimeMiddlewares = (plugins) => collectMiddlewares(plugins, "runtimeMiddleware");
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ProgramDefinition } from "./dynamic-manifest.js";
|
|
2
|
+
export interface ProgramSandboxPolicyInput {
|
|
3
|
+
integrityRequired?: boolean;
|
|
4
|
+
allow?: string[];
|
|
5
|
+
timeoutMs?: number;
|
|
6
|
+
memoryMb?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface ProgramSandboxPolicy {
|
|
9
|
+
integrityRequired: boolean;
|
|
10
|
+
allow: string[];
|
|
11
|
+
timeoutMs: number;
|
|
12
|
+
memoryMb: number;
|
|
13
|
+
}
|
|
14
|
+
export declare const DEFAULT_PROGRAM_SANDBOX_POLICY: ProgramSandboxPolicy;
|
|
15
|
+
export declare const resolveProgramSandboxPolicy: (input?: ProgramSandboxPolicyInput) => ProgramSandboxPolicy;
|
|
16
|
+
export declare const validateProgramIntegrity: (definition: ProgramDefinition, policy: ProgramSandboxPolicy) => void;
|
|
17
|
+
export declare const buildProgramSandboxContext: (policy: ProgramSandboxPolicy) => Record<string, unknown>;
|
|
18
|
+
//# sourceMappingURL=program-sandbox.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"program-sandbox.d.ts","sourceRoot":"","sources":["../src/program-sandbox.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAE/D,MAAM,WAAW,yBAAyB;IACxC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,iBAAiB,EAAE,OAAO,CAAC;IAC3B,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,eAAO,MAAM,8BAA8B,EAAE,oBAK5C,CAAC;AAmCF,eAAO,MAAM,2BAA2B,GACtC,QAAO,yBAA8B,KACpC,oBAiBF,CAAC;AAEF,eAAO,MAAM,wBAAwB,GACnC,YAAY,iBAAiB,EAC7B,QAAQ,oBAAoB,KAC3B,IAWF,CAAC;AAoBF,eAAO,MAAM,0BAA0B,GACrC,QAAQ,oBAAoB,KAC3B,MAAM,CAAC,MAAM,EAAE,OAAO,CAuBxB,CAAC"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { HyperError } from "./errors.js";
|
|
2
|
+
export const DEFAULT_PROGRAM_SANDBOX_POLICY = {
|
|
3
|
+
integrityRequired: true,
|
|
4
|
+
allow: [],
|
|
5
|
+
timeoutMs: 50,
|
|
6
|
+
memoryMb: 32,
|
|
7
|
+
};
|
|
8
|
+
const KNOWN_ALLOWLIST = new Set(["Math", "Math.random", "Date", "Intl", "JSON", "console"]);
|
|
9
|
+
const ensureNumber = (value, path) => {
|
|
10
|
+
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
11
|
+
throw new HyperError("PROGRAM_POLICY_INVALID", `Invalid program policy value at ${path}: expected number`, { path, expected: "number" });
|
|
12
|
+
}
|
|
13
|
+
return value;
|
|
14
|
+
};
|
|
15
|
+
const ensureAllowList = (value, path) => {
|
|
16
|
+
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
|
|
17
|
+
throw new HyperError("PROGRAM_POLICY_INVALID", `Invalid program policy value at ${path}: expected string[]`, { path, expected: "string[]" });
|
|
18
|
+
}
|
|
19
|
+
for (const entry of value) {
|
|
20
|
+
if (!KNOWN_ALLOWLIST.has(entry)) {
|
|
21
|
+
throw new HyperError("PROGRAM_POLICY_INVALID", `Unknown program policy allow entry: ${entry}`, { path, entry });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return value;
|
|
25
|
+
};
|
|
26
|
+
export const resolveProgramSandboxPolicy = (input = {}) => {
|
|
27
|
+
const policy = {
|
|
28
|
+
...DEFAULT_PROGRAM_SANDBOX_POLICY,
|
|
29
|
+
integrityRequired: input.integrityRequired ?? DEFAULT_PROGRAM_SANDBOX_POLICY.integrityRequired,
|
|
30
|
+
};
|
|
31
|
+
if ("allow" in input) {
|
|
32
|
+
policy.allow = ensureAllowList(input.allow, "modules.dynamicManifest.program.allow");
|
|
33
|
+
}
|
|
34
|
+
if ("timeoutMs" in input && input.timeoutMs !== undefined) {
|
|
35
|
+
policy.timeoutMs = ensureNumber(input.timeoutMs, "modules.dynamicManifest.program.timeoutMs");
|
|
36
|
+
}
|
|
37
|
+
if ("memoryMb" in input && input.memoryMb !== undefined) {
|
|
38
|
+
policy.memoryMb = ensureNumber(input.memoryMb, "modules.dynamicManifest.program.memoryMb");
|
|
39
|
+
}
|
|
40
|
+
return policy;
|
|
41
|
+
};
|
|
42
|
+
export const validateProgramIntegrity = (definition, policy) => {
|
|
43
|
+
if (!policy.integrityRequired) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (!definition.integrity?.alg || !definition.integrity.signature) {
|
|
47
|
+
throw new HyperError("PROGRAM_POLICY_INVALID", "Program integrity is required but missing.", { path: "definition.integrity" });
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
const buildSafeMath = (allowRandom) => {
|
|
51
|
+
const safeMath = {};
|
|
52
|
+
const source = Math;
|
|
53
|
+
for (const key of Object.getOwnPropertyNames(Math)) {
|
|
54
|
+
safeMath[key] = source[key];
|
|
55
|
+
}
|
|
56
|
+
if (!allowRandom) {
|
|
57
|
+
safeMath.random = () => {
|
|
58
|
+
throw new HyperError("PROGRAM_POLICY_INVALID", "Math.random is disabled by sandbox policy.", { path: "modules.dynamicManifest.program.allow", expected: "Math.random" });
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return safeMath;
|
|
62
|
+
};
|
|
63
|
+
export const buildProgramSandboxContext = (policy) => {
|
|
64
|
+
const context = Object.create(null);
|
|
65
|
+
const allow = new Set(policy.allow);
|
|
66
|
+
const allowMath = allow.has("Math") || allow.has("Math.random");
|
|
67
|
+
if (allowMath) {
|
|
68
|
+
context.Math = buildSafeMath(allow.has("Math.random"));
|
|
69
|
+
}
|
|
70
|
+
if (allow.has("Date")) {
|
|
71
|
+
context.Date = Date;
|
|
72
|
+
}
|
|
73
|
+
if (allow.has("Intl")) {
|
|
74
|
+
context.Intl = Intl;
|
|
75
|
+
}
|
|
76
|
+
if (allow.has("JSON")) {
|
|
77
|
+
context.JSON = JSON;
|
|
78
|
+
}
|
|
79
|
+
if (allow.has("console")) {
|
|
80
|
+
context.console = console;
|
|
81
|
+
}
|
|
82
|
+
context.globalThis = context;
|
|
83
|
+
return context;
|
|
84
|
+
};
|