@c9up/inker 0.1.6 → 0.1.7
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 +1 -1
- package/dist/InkerProvider.d.ts +5 -1
- package/dist/InkerProvider.d.ts.map +1 -1
- package/dist/InkerProvider.js +33 -17
- package/dist/InkerProvider.js.map +1 -1
- package/dist/InkerRenderError.d.ts +1 -1
- package/dist/InkerRenderError.d.ts.map +1 -1
- package/dist/InkerRenderError.js.map +1 -1
- package/dist/InkerRenderer.d.ts +9 -0
- package/dist/InkerRenderer.d.ts.map +1 -1
- package/dist/InkerRenderer.js +13 -0
- package/dist/InkerRenderer.js.map +1 -1
- package/dist/Templates.d.ts +44 -0
- package/dist/Templates.d.ts.map +1 -1
- package/dist/Templates.js +271 -147
- package/dist/Templates.js.map +1 -1
- package/dist/globals.d.ts +10 -0
- package/dist/globals.d.ts.map +1 -0
- package/dist/globals.js +235 -0
- package/dist/globals.js.map +1 -0
- package/dist/identifierGuards.d.ts +2 -2
- package/dist/identifierGuards.js +2 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/loadNapi.d.ts +6 -4
- package/dist/loadNapi.d.ts.map +1 -1
- package/dist/loadNapi.js +2 -2
- package/dist/loadNapi.js.map +1 -1
- package/dist/renderNode.d.ts +184 -0
- package/dist/renderNode.d.ts.map +1 -0
- package/dist/renderNode.js +479 -0
- package/dist/renderNode.js.map +1 -0
- package/index.darwin-arm64.node +0 -0
- package/index.darwin-x64.node +0 -0
- package/index.linux-arm64-gnu.node +0 -0
- package/index.linux-x64-gnu.node +0 -0
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +3 -4
- package/scripts/copy-napi.mjs +0 -62
- package/src/InkerProvider.ts +0 -588
- package/src/InkerRenderError.ts +0 -49
- package/src/InkerRenderer.ts +0 -55
- package/src/SafeString.ts +0 -27
- package/src/Templates.ts +0 -1332
- package/src/helpers.ts +0 -76
- package/src/identifierGuards.ts +0 -49
- package/src/index.ts +0 -16
- package/src/loadNapi.ts +0 -270
- package/src/services/main.ts +0 -56
package/src/helpers.ts
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import type { SafeString } from "./SafeString.js";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* A caller-registered template helper. Receives the in-scope-evaluated
|
|
5
|
-
* argument values and returns a string (HTML-escaped by the renderer) or a
|
|
6
|
-
* `SafeString` (emitted verbatim). Re-homed here from the deleted `render.ts`
|
|
7
|
-
* when the lex/parse/render hot path moved to the Rust engine (Story 55.1).
|
|
8
|
-
*
|
|
9
|
-
* NOTE (55.1): argument values are evaluated in the Rust engine and cross the
|
|
10
|
-
* NAPI boundary as JSON, so they are JSON-coerced before reaching the helper:
|
|
11
|
-
* a `Date` arrives as a string, a `bigint` as a (possibly lossy) number, and
|
|
12
|
-
* `NaN` / `±Infinity` as `null`. Pass pre-stringified values for any type that
|
|
13
|
-
* does not survive JSON if the helper needs the original form.
|
|
14
|
-
*/
|
|
15
|
-
export type HelperFn = (...args: readonly unknown[]) => string | SafeString;
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Signature for the canonical `t(key, params?)` helper.
|
|
19
|
-
*
|
|
20
|
-
* Inker ships ZERO body for this helper — implementations come from
|
|
21
|
-
* the caller via `TemplatesOptions.helpers`. The Ream provider (Story
|
|
22
|
-
* 53.5) wires this signature to `@c9up/rosetta`'s `Rosetta#t(key,
|
|
23
|
-
* params?, options?)`.
|
|
24
|
-
*/
|
|
25
|
-
export type THelper = (key: string, params?: Record<string, unknown>) => string;
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Signature for the canonical `csrfField()` helper.
|
|
29
|
-
*
|
|
30
|
-
* Returns a `SafeString` containing the `<input type="hidden"
|
|
31
|
-
* name="_csrf" value="…">` element. Returning a `SafeString` is
|
|
32
|
-
* mandatory — a plain string would be HTML-escaped by the renderer
|
|
33
|
-
* and the resulting `<input …>` would not be a usable form
|
|
34
|
-
* field. Implementation is provided by the Ream provider (Story 53.5)
|
|
35
|
-
* wiring the session's CSRF token.
|
|
36
|
-
*/
|
|
37
|
-
export type CsrfFieldHelper = () => SafeString;
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Signature for the canonical `csrfMeta()` helper.
|
|
41
|
-
*
|
|
42
|
-
* Returns a `SafeString` containing the `<meta name="csrf-token" …>` element
|
|
43
|
-
* (for JS clients to read the token). Like `csrfField`, returning a `SafeString`
|
|
44
|
-
* is mandatory so the renderer doesn't escape the markup. Provided by the Ream
|
|
45
|
-
* provider wiring the session's CSRF token.
|
|
46
|
-
*/
|
|
47
|
-
export type CsrfMetaHelper = () => SafeString;
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Signature for the canonical `cspNonce()` helper.
|
|
51
|
-
*
|
|
52
|
-
* Returns the per-request Content-Security-Policy nonce (a plain string) so
|
|
53
|
-
* inline `<script nonce="…">` tags match the response CSP header, or `""` when
|
|
54
|
-
* no nonce is set. Provided by the Ream provider.
|
|
55
|
-
*/
|
|
56
|
-
export type CspNonceHelper = () => string;
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Signature for the canonical `url(name, params?)` helper.
|
|
60
|
-
*
|
|
61
|
-
* Resolves a named route + interpolates path params. Implementation
|
|
62
|
-
* is provided by the Ream provider (Story 53.5) wiring the router.
|
|
63
|
-
*/
|
|
64
|
-
export type UrlHelper = (
|
|
65
|
-
name: string,
|
|
66
|
-
params?: Record<string, unknown>,
|
|
67
|
-
) => string;
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Signature for the canonical `asset(name)` helper.
|
|
71
|
-
*
|
|
72
|
-
* Resolves an asset manifest entry to its hashed public URL.
|
|
73
|
-
* Implementation is provided by the Ream provider (Story 53.5) wiring
|
|
74
|
-
* the asset manifest.
|
|
75
|
-
*/
|
|
76
|
-
export type AssetHelper = (name: string) => string;
|
package/src/identifierGuards.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Identifier denylists shared between `parseBlockTag.ts` (each-binding +
|
|
3
|
-
* component-arg-key validation) and `parseExpression.ts` (object-literal
|
|
4
|
-
* key validation).
|
|
5
|
-
*
|
|
6
|
-
* `PROTOTYPE_POLLUTION_KEYS` blocks the three keys whose own-property
|
|
7
|
-
* assignment can shadow `Object.prototype` methods or invoke
|
|
8
|
-
* `Object.create` semantics in surprising ways. The gate is
|
|
9
|
-
* defence-in-depth: JavaScript's `obj[key] = value` semantics make
|
|
10
|
-
* `__proto__` non-exploitable when used via square brackets on a
|
|
11
|
-
* non-frozen object built via `Object.create(null)`, but template
|
|
12
|
-
* authors cannot reason about the renderer's storage shape and the
|
|
13
|
-
* cost of the guard is one Set membership check per object key.
|
|
14
|
-
*
|
|
15
|
-
* `RESERVED_BINDING_NAMES` blocks identifiers that would collide with
|
|
16
|
-
* Inker grammar keywords (e.g. `as`) or JavaScript reserved words when
|
|
17
|
-
* used as `{% each items as <name> %}` bindings or `{% each items as
|
|
18
|
-
* [<a>, <b>] %}` destructured names. The block is per-position rather
|
|
19
|
-
* than universal — Inker permits these names inside path expressions
|
|
20
|
-
* since paths cannot collide with the grammar.
|
|
21
|
-
*/
|
|
22
|
-
export const PROTOTYPE_POLLUTION_KEYS: ReadonlySet<string> = new Set([
|
|
23
|
-
"__proto__",
|
|
24
|
-
"constructor",
|
|
25
|
-
"prototype",
|
|
26
|
-
]);
|
|
27
|
-
|
|
28
|
-
export const RESERVED_BINDING_NAMES: ReadonlySet<string> = new Set([
|
|
29
|
-
"as",
|
|
30
|
-
"if",
|
|
31
|
-
"else",
|
|
32
|
-
"each",
|
|
33
|
-
"do",
|
|
34
|
-
"for",
|
|
35
|
-
"while",
|
|
36
|
-
"let",
|
|
37
|
-
"const",
|
|
38
|
-
"var",
|
|
39
|
-
"return",
|
|
40
|
-
"function",
|
|
41
|
-
"class",
|
|
42
|
-
"new",
|
|
43
|
-
"this",
|
|
44
|
-
"super",
|
|
45
|
-
"null",
|
|
46
|
-
"undefined",
|
|
47
|
-
"true",
|
|
48
|
-
"false",
|
|
49
|
-
]);
|
package/src/index.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
export type {
|
|
2
|
-
AssetHelper,
|
|
3
|
-
CspNonceHelper,
|
|
4
|
-
CsrfFieldHelper,
|
|
5
|
-
CsrfMetaHelper,
|
|
6
|
-
HelperFn,
|
|
7
|
-
THelper,
|
|
8
|
-
UrlHelper,
|
|
9
|
-
} from "./helpers.js";
|
|
10
|
-
export {
|
|
11
|
-
type InkerErrorCode,
|
|
12
|
-
type InkerErrorContext,
|
|
13
|
-
InkerRenderError,
|
|
14
|
-
} from "./InkerRenderError.js";
|
|
15
|
-
export { SafeString } from "./SafeString.js";
|
|
16
|
-
export { Templates, type TemplatesOptions } from "./Templates.js";
|
package/src/loadNapi.ts
DELETED
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
// PATTERN: copy-and-rename for 55.2/55.3/55.4 — Rust hot-path packages.
|
|
2
|
-
//
|
|
3
|
-
// Loads the native `inker-engine-napi` binary built by `scripts/copy-napi.mjs`
|
|
4
|
-
// and re-throws load failures as `E_INKER_NAPI_REQUIRED` per cerebrum
|
|
5
|
-
// NAPI-loader pattern (2026-04-27) — actionable hint points at
|
|
6
|
-
// `pnpm --filter @c9up/inker build:napi`.
|
|
7
|
-
//
|
|
8
|
-
// Per cerebrum 2026-04-15 there is NO JS fallback. If the binary fails to
|
|
9
|
-
// load, consumers get a typed error. Zero `as` / `any` per cerebrum 2026-05-04.
|
|
10
|
-
|
|
11
|
-
import { createRequire } from "node:module";
|
|
12
|
-
import { arch, platform } from "node:process";
|
|
13
|
-
import { fileURLToPath } from "node:url";
|
|
14
|
-
import { type InkerErrorCode, InkerRenderError } from "./InkerRenderError.js";
|
|
15
|
-
|
|
16
|
-
const SUFFIX_MAP: Readonly<Record<string, string>> = {
|
|
17
|
-
"linux-x64": "linux-x64-gnu",
|
|
18
|
-
"linux-arm64": "linux-arm64-gnu",
|
|
19
|
-
"darwin-x64": "darwin-x64",
|
|
20
|
-
"darwin-arm64": "darwin-arm64",
|
|
21
|
-
"win32-x64": "win32-x64-msvc",
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
function platformSuffix(): string {
|
|
25
|
-
const key = `${platform}-${arch}`;
|
|
26
|
-
const suffix = SUFFIX_MAP[key];
|
|
27
|
-
if (typeof suffix !== "string") {
|
|
28
|
-
throw new InkerRenderError(
|
|
29
|
-
"E_INKER_NAPI_REQUIRED",
|
|
30
|
-
`Unsupported platform/arch '${key}' for @c9up/inker native binary. Supported: ${Object.keys(SUFFIX_MAP).join(", ")}.`,
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
return suffix;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/** A `{% include %}` / `{% component %}` reference with source position. */
|
|
37
|
-
export interface NapiNodeRef {
|
|
38
|
-
readonly name: string;
|
|
39
|
-
readonly line: number;
|
|
40
|
-
readonly column: number;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/** A `{{> name }}` slot reference. */
|
|
44
|
-
export interface NapiSlotRef {
|
|
45
|
-
readonly name: string;
|
|
46
|
-
readonly line: number;
|
|
47
|
-
readonly column: number;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** First disk-requiring node (`renderString` E_INKER_DISK_REQUIRED guard). */
|
|
51
|
-
export interface NapiDiskNodeRef {
|
|
52
|
-
readonly kind: string;
|
|
53
|
-
readonly name: string;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/** All composition metadata for one parsed AST (one NAPI call). */
|
|
57
|
-
export interface NapiComposeInfo {
|
|
58
|
-
readonly hasLayout: boolean;
|
|
59
|
-
readonly layoutName: string | null;
|
|
60
|
-
readonly layoutLine: number | null;
|
|
61
|
-
readonly layoutColumn: number | null;
|
|
62
|
-
readonly slots: readonly NapiSlotRef[];
|
|
63
|
-
readonly partials: readonly NapiNodeRef[];
|
|
64
|
-
readonly components: readonly NapiNodeRef[];
|
|
65
|
-
readonly hasContent: boolean;
|
|
66
|
-
readonly firstDiskNode: NapiDiskNodeRef | null;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/** Opaque handle to a parsed Rust AST. */
|
|
70
|
-
export interface NapiInkerAst {
|
|
71
|
-
readonly composeInfo: NapiComposeInfo;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/** One in-scope-evaluated helper invocation request (collect pass). */
|
|
75
|
-
export interface NapiInvocation {
|
|
76
|
-
readonly id: number;
|
|
77
|
-
readonly name: string;
|
|
78
|
-
/** JSON array of the evaluated argument values. */
|
|
79
|
-
readonly args: readonly unknown[];
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/** One pre-resolved helper result (consumed in tape order by the renderer). */
|
|
83
|
-
export interface NapiHelperResult {
|
|
84
|
-
readonly value: string;
|
|
85
|
-
readonly isSafe: boolean;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export interface NapiRenderContext {
|
|
89
|
-
readonly partials: Record<string, NapiInkerAst>;
|
|
90
|
-
readonly components: Record<string, NapiInkerAst>;
|
|
91
|
-
readonly bodyHtml: string | undefined;
|
|
92
|
-
readonly templateName: string | undefined;
|
|
93
|
-
readonly templatePath: string | undefined;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
interface NativeExports {
|
|
97
|
-
readonly engineVersion: () => string;
|
|
98
|
-
readonly parseTemplate: (
|
|
99
|
-
source: string,
|
|
100
|
-
helpers: readonly string[],
|
|
101
|
-
) => NapiInkerAst;
|
|
102
|
-
readonly collectInvocations: (
|
|
103
|
-
ast: NapiInkerAst,
|
|
104
|
-
data: unknown,
|
|
105
|
-
ctx: NapiRenderContext,
|
|
106
|
-
) => NapiInvocation[];
|
|
107
|
-
readonly renderAst: (
|
|
108
|
-
ast: NapiInkerAst,
|
|
109
|
-
data: unknown,
|
|
110
|
-
resolved: readonly NapiHelperResult[],
|
|
111
|
-
ctx: NapiRenderContext,
|
|
112
|
-
) => string;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function isNativeExports(value: unknown): value is NativeExports {
|
|
116
|
-
if (value === null || typeof value !== "object") return false;
|
|
117
|
-
return (
|
|
118
|
-
typeof Reflect.get(value, "engineVersion") === "function" &&
|
|
119
|
-
typeof Reflect.get(value, "parseTemplate") === "function" &&
|
|
120
|
-
typeof Reflect.get(value, "collectInvocations") === "function" &&
|
|
121
|
-
typeof Reflect.get(value, "renderAst") === "function"
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
let cachedNative: NativeExports | undefined;
|
|
126
|
-
|
|
127
|
-
export function getNative(): NativeExports {
|
|
128
|
-
if (cachedNative !== undefined) return cachedNative;
|
|
129
|
-
|
|
130
|
-
const require = createRequire(import.meta.url);
|
|
131
|
-
const here = fileURLToPath(import.meta.url);
|
|
132
|
-
// `here` is `…/packages/inker/{src,dist}/loadNapi.ts|js`. The `.node` lives
|
|
133
|
-
// one level up at `…/packages/inker/index.<suffix>.node`. `..` traversal
|
|
134
|
-
// resolved via require (handles both dev `src/` and publish `dist/`).
|
|
135
|
-
const suffix = platformSuffix();
|
|
136
|
-
const candidates: readonly string[] = [`../index.${suffix}.node`];
|
|
137
|
-
let loaded: unknown;
|
|
138
|
-
let lastErr: unknown;
|
|
139
|
-
for (const candidate of candidates) {
|
|
140
|
-
try {
|
|
141
|
-
loaded = require(candidate);
|
|
142
|
-
break;
|
|
143
|
-
} catch (err) {
|
|
144
|
-
lastErr = err;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
if (loaded === undefined) {
|
|
148
|
-
const causeMessage =
|
|
149
|
-
lastErr instanceof Error ? lastErr.message : String(lastErr);
|
|
150
|
-
// The prebuilt linux binaries target glibc (`-gnu`). On musl hosts (Alpine
|
|
151
|
-
// containers) the `-gnu` binary fails to dlopen with a libc symbol error —
|
|
152
|
-
// surface that explicitly rather than only pointing at the build step.
|
|
153
|
-
const muslHint = suffix.endsWith("-gnu")
|
|
154
|
-
? " If you are on Alpine/musl, note the prebuilt binaries target glibc (musl is not a supported target)."
|
|
155
|
-
: "";
|
|
156
|
-
throw new InkerRenderError(
|
|
157
|
-
"E_INKER_NAPI_REQUIRED",
|
|
158
|
-
`@c9up/inker native binary 'index.${suffix}.node' not found or failed to load near ${here} — run 'pnpm --filter @c9up/inker build:napi' to build it.${muslHint} Cause: ${causeMessage}`,
|
|
159
|
-
undefined,
|
|
160
|
-
{ cause: lastErr },
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
if (!isNativeExports(loaded)) {
|
|
164
|
-
throw new InkerRenderError(
|
|
165
|
-
"E_INKER_NAPI_REQUIRED",
|
|
166
|
-
`@c9up/inker native binary loaded but missing expected exports (engineVersion / parseTemplate / renderAst). Rebuild with 'pnpm --filter @c9up/inker build:napi'.`,
|
|
167
|
-
);
|
|
168
|
-
}
|
|
169
|
-
cachedNative = loaded;
|
|
170
|
-
return cachedNative;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Shape of the JSON payload Rust packs into `napi::Error::from_reason`. Rust
|
|
175
|
-
* guarantees `code` / `message` present; positional fields optional.
|
|
176
|
-
*/
|
|
177
|
-
interface NapiErrorPayload {
|
|
178
|
-
readonly code: string;
|
|
179
|
-
readonly message: string;
|
|
180
|
-
readonly line?: number;
|
|
181
|
-
readonly column?: number;
|
|
182
|
-
readonly templateName?: string;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
function readString(target: unknown, key: string): string | undefined {
|
|
186
|
-
const v = Reflect.get(Object(target), key);
|
|
187
|
-
return typeof v === "string" ? v : undefined;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function readNumber(target: unknown, key: string): number | undefined {
|
|
191
|
-
const v = Reflect.get(Object(target), key);
|
|
192
|
-
return typeof v === "number" ? v : undefined;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function isNapiErrorPayload(value: unknown): value is NapiErrorPayload {
|
|
196
|
-
if (value === null || typeof value !== "object") return false;
|
|
197
|
-
return (
|
|
198
|
-
typeof Reflect.get(value, "code") === "string" &&
|
|
199
|
-
typeof Reflect.get(value, "message") === "string"
|
|
200
|
-
);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
const CODE_MAP: Readonly<Record<string, InkerErrorCode>> = {
|
|
204
|
-
E_INKER_TEMPLATE_NOT_FOUND: "E_INKER_TEMPLATE_NOT_FOUND",
|
|
205
|
-
E_INKER_PARSE_ERROR: "E_INKER_PARSE_ERROR",
|
|
206
|
-
E_INKER_UNKNOWN_IDENTIFIER: "E_INKER_UNKNOWN_IDENTIFIER",
|
|
207
|
-
E_INKER_INVALID_PATH: "E_INKER_INVALID_PATH",
|
|
208
|
-
E_INKER_UNCLOSED_INTERPOLATION: "E_INKER_UNCLOSED_INTERPOLATION",
|
|
209
|
-
E_INKER_UNCLOSED_BLOCK_TAG: "E_INKER_UNCLOSED_BLOCK_TAG",
|
|
210
|
-
E_INKER_UNKNOWN_DIRECTIVE: "E_INKER_UNKNOWN_DIRECTIVE",
|
|
211
|
-
E_INKER_INVALID_LAYOUT_POSITION: "E_INKER_INVALID_LAYOUT_POSITION",
|
|
212
|
-
E_INKER_DUPLICATE_LAYOUT: "E_INKER_DUPLICATE_LAYOUT",
|
|
213
|
-
E_INKER_NESTED_LAYOUT_UNSUPPORTED: "E_INKER_NESTED_LAYOUT_UNSUPPORTED",
|
|
214
|
-
E_INKER_LAYOUT_IN_PARTIAL: "E_INKER_LAYOUT_IN_PARTIAL",
|
|
215
|
-
E_INKER_CIRCULAR_INCLUDE: "E_INKER_CIRCULAR_INCLUDE",
|
|
216
|
-
E_INKER_MISSING_SLOT: "E_INKER_MISSING_SLOT",
|
|
217
|
-
E_INKER_UNKNOWN_SLOT: "E_INKER_UNKNOWN_SLOT",
|
|
218
|
-
E_INKER_DISK_REQUIRED: "E_INKER_DISK_REQUIRED",
|
|
219
|
-
E_INKER_UNCLOSED_BLOCK: "E_INKER_UNCLOSED_BLOCK",
|
|
220
|
-
E_INKER_UNMATCHED_BLOCK_END: "E_INKER_UNMATCHED_BLOCK_END",
|
|
221
|
-
E_INKER_MISMATCHED_BLOCK_END: "E_INKER_MISMATCHED_BLOCK_END",
|
|
222
|
-
E_INKER_INVALID_EXPRESSION: "E_INKER_INVALID_EXPRESSION",
|
|
223
|
-
E_INKER_INVALID_ITERABLE: "E_INKER_INVALID_ITERABLE",
|
|
224
|
-
E_INKER_UNKNOWN_HELPER: "E_INKER_UNKNOWN_HELPER",
|
|
225
|
-
E_INKER_HELPER_THROW: "E_INKER_HELPER_THROW",
|
|
226
|
-
E_INKER_NAPI_REQUIRED: "E_INKER_NAPI_REQUIRED",
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Translate a thrown value from a NAPI call into an `InkerRenderError`.
|
|
231
|
-
* If it's already an `InkerRenderError` (helper threw and propagated), pass through.
|
|
232
|
-
* If it's a `napi::Error` carrying our JSON envelope, reconstruct typed.
|
|
233
|
-
* Otherwise wrap as parse error.
|
|
234
|
-
*/
|
|
235
|
-
export function napiThrowToInker(err: unknown): InkerRenderError {
|
|
236
|
-
if (err instanceof InkerRenderError) return err;
|
|
237
|
-
if (err instanceof Error) {
|
|
238
|
-
let parsed: unknown;
|
|
239
|
-
try {
|
|
240
|
-
parsed = JSON.parse(err.message);
|
|
241
|
-
} catch {
|
|
242
|
-
parsed = undefined;
|
|
243
|
-
}
|
|
244
|
-
if (isNapiErrorPayload(parsed)) {
|
|
245
|
-
const code = CODE_MAP[parsed.code];
|
|
246
|
-
if (code !== undefined) {
|
|
247
|
-
return new InkerRenderError(
|
|
248
|
-
code,
|
|
249
|
-
parsed.message,
|
|
250
|
-
{
|
|
251
|
-
line: readNumber(parsed, "line"),
|
|
252
|
-
column: readNumber(parsed, "column"),
|
|
253
|
-
templateName: readString(parsed, "templateName"),
|
|
254
|
-
},
|
|
255
|
-
{ cause: err },
|
|
256
|
-
);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
return new InkerRenderError(
|
|
260
|
-
"E_INKER_PARSE_ERROR",
|
|
261
|
-
`Native call failed: ${err.message}`,
|
|
262
|
-
undefined,
|
|
263
|
-
{ cause: err },
|
|
264
|
-
);
|
|
265
|
-
}
|
|
266
|
-
return new InkerRenderError(
|
|
267
|
-
"E_INKER_PARSE_ERROR",
|
|
268
|
-
`Native call failed with non-Error: ${String(err)}`,
|
|
269
|
-
);
|
|
270
|
-
}
|
package/src/services/main.ts
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Default `InkerRenderer` singleton — Adonis-style:
|
|
3
|
-
*
|
|
4
|
-
* import inker from '@c9up/inker/provider/services/main'
|
|
5
|
-
*
|
|
6
|
-
* await inker.render(ctx, 'invoice', { user })
|
|
7
|
-
*
|
|
8
|
-
* Populated by `InkerProvider.start()` (registered via reamrc.ts) or by the
|
|
9
|
-
* app itself via `setInker(myRenderer)`.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import type { InkerRenderer } from "../InkerRenderer.js";
|
|
13
|
-
|
|
14
|
-
let instance: InkerRenderer | undefined;
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* @internal Bind (or clear) the singleton. Called by InkerProvider.start() to
|
|
18
|
-
* wire the renderer, or by tests passing `undefined` to reset between cases
|
|
19
|
-
* (the type field already permits `undefined`, so the signature mirrors it
|
|
20
|
-
* honestly rather than requiring a `bypassTypeCheck` cast at every call site).
|
|
21
|
-
*/
|
|
22
|
-
export function setInker(renderer: InkerRenderer | undefined): void {
|
|
23
|
-
instance = renderer;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/** @internal Read the singleton (or `undefined` pre-boot). */
|
|
27
|
-
export function getInker(): InkerRenderer | undefined {
|
|
28
|
-
return instance;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Sanctioned `as InkerRenderer` site — typed-Proxy idiom shared by
|
|
32
|
-
// station/rosetta/aurora/inker `services/main.ts`. Every property access is
|
|
33
|
-
// guarded by the `instance` check below, so the cast is structural and
|
|
34
|
-
// bounded. DNR `feedback_no_any_types` documents this as one of two
|
|
35
|
-
// permitted `as` patterns alongside `loadBearingCast` in `InkerProvider.ts`.
|
|
36
|
-
const inker: InkerRenderer = new Proxy({} as InkerRenderer, {
|
|
37
|
-
get(_target, prop) {
|
|
38
|
-
// Short-circuit the thenable probe: an accidental `await mod.default`
|
|
39
|
-
// (or `Promise.resolve(mod.default)`) would otherwise trigger our
|
|
40
|
-
// pre-boot throw inside the await machinery and surface a confusing
|
|
41
|
-
// rejected Promise. Returning `undefined` makes the value plainly
|
|
42
|
-
// non-thenable, so the caller's await resolves immediately to the
|
|
43
|
-
// Proxy itself — subsequent real property access still throws.
|
|
44
|
-
if (prop === "then") return undefined;
|
|
45
|
-
if (!instance) {
|
|
46
|
-
throw new Error(
|
|
47
|
-
"[inker] InkerRenderer singleton accessed before InkerProvider.start() ran " +
|
|
48
|
-
"or `setInker(myRenderer)` was called. Wire one of them first.",
|
|
49
|
-
);
|
|
50
|
-
}
|
|
51
|
-
const value = Reflect.get(instance, prop, instance);
|
|
52
|
-
return typeof value === "function" ? value.bind(instance) : value;
|
|
53
|
-
},
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
export default inker;
|