@c9up/inker 0.1.3

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/LICENSE +21 -0
  2. package/README.md +48 -0
  3. package/dist/InkerProvider.d.ts +120 -0
  4. package/dist/InkerProvider.d.ts.map +1 -0
  5. package/dist/InkerProvider.js +448 -0
  6. package/dist/InkerProvider.js.map +1 -0
  7. package/dist/InkerRenderError.d.ts +16 -0
  8. package/dist/InkerRenderError.d.ts.map +1 -0
  9. package/dist/InkerRenderError.js +11 -0
  10. package/dist/InkerRenderError.js.map +1 -0
  11. package/dist/InkerRenderer.d.ts +28 -0
  12. package/dist/InkerRenderer.d.ts.map +1 -0
  13. package/dist/InkerRenderer.js +21 -0
  14. package/dist/InkerRenderer.js.map +1 -0
  15. package/dist/SafeString.d.ts +15 -0
  16. package/dist/SafeString.d.ts.map +1 -0
  17. package/dist/SafeString.js +24 -0
  18. package/dist/SafeString.js.map +1 -0
  19. package/dist/Templates.d.ts +16 -0
  20. package/dist/Templates.d.ts.map +1 -0
  21. package/dist/Templates.js +908 -0
  22. package/dist/Templates.js.map +1 -0
  23. package/dist/helpers.d.ts +50 -0
  24. package/dist/helpers.d.ts.map +1 -0
  25. package/dist/helpers.js +2 -0
  26. package/dist/helpers.js.map +1 -0
  27. package/dist/identifierGuards.d.ts +24 -0
  28. package/dist/identifierGuards.d.ts.map +1 -0
  29. package/dist/identifierGuards.js +49 -0
  30. package/dist/identifierGuards.js.map +1 -0
  31. package/dist/index.d.ts +5 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +4 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/loadNapi.d.ts +69 -0
  36. package/dist/loadNapi.d.ts.map +1 -0
  37. package/dist/loadNapi.js +145 -0
  38. package/dist/loadNapi.js.map +1 -0
  39. package/dist/services/main.d.ts +23 -0
  40. package/dist/services/main.d.ts.map +1 -0
  41. package/dist/services/main.js +49 -0
  42. package/dist/services/main.js.map +1 -0
  43. package/index.darwin-arm64.node +0 -0
  44. package/index.darwin-x64.node +0 -0
  45. package/index.linux-arm64-gnu.node +0 -0
  46. package/index.linux-x64-gnu.node +0 -0
  47. package/index.win32-x64-msvc.node +0 -0
  48. package/package.json +64 -0
  49. package/scripts/copy-napi.mjs +62 -0
  50. package/src/InkerProvider.ts +594 -0
  51. package/src/InkerRenderError.ts +49 -0
  52. package/src/InkerRenderer.ts +55 -0
  53. package/src/SafeString.ts +27 -0
  54. package/src/Templates.ts +1324 -0
  55. package/src/helpers.ts +57 -0
  56. package/src/identifierGuards.ts +49 -0
  57. package/src/index.ts +14 -0
  58. package/src/loadNapi.ts +270 -0
  59. package/src/services/main.ts +56 -0
@@ -0,0 +1,49 @@
1
+ export type InkerErrorCode =
2
+ | "E_INKER_TEMPLATE_NOT_FOUND"
3
+ | "E_INKER_PARSE_ERROR"
4
+ | "E_INKER_UNKNOWN_IDENTIFIER"
5
+ | "E_INKER_INVALID_PATH"
6
+ | "E_INKER_UNCLOSED_INTERPOLATION"
7
+ | "E_INKER_UNCLOSED_BLOCK_TAG"
8
+ | "E_INKER_UNKNOWN_DIRECTIVE"
9
+ | "E_INKER_INVALID_LAYOUT_POSITION"
10
+ | "E_INKER_DUPLICATE_LAYOUT"
11
+ | "E_INKER_NESTED_LAYOUT_UNSUPPORTED"
12
+ | "E_INKER_LAYOUT_IN_PARTIAL"
13
+ | "E_INKER_CIRCULAR_INCLUDE"
14
+ | "E_INKER_MISSING_SLOT"
15
+ | "E_INKER_UNKNOWN_SLOT"
16
+ | "E_INKER_DISK_REQUIRED"
17
+ | "E_INKER_UNCLOSED_BLOCK"
18
+ | "E_INKER_UNMATCHED_BLOCK_END"
19
+ | "E_INKER_MISMATCHED_BLOCK_END"
20
+ | "E_INKER_INVALID_EXPRESSION"
21
+ | "E_INKER_INVALID_ITERABLE"
22
+ | "E_INKER_UNKNOWN_HELPER"
23
+ | "E_INKER_HELPER_THROW"
24
+ | "E_INKER_NAPI_REQUIRED";
25
+
26
+ export interface InkerErrorContext {
27
+ readonly templatePath?: string;
28
+ readonly templateName?: string;
29
+ readonly line?: number;
30
+ readonly column?: number;
31
+ readonly expression?: string;
32
+ }
33
+
34
+ export class InkerRenderError extends Error {
35
+ readonly code: InkerErrorCode;
36
+ readonly context: Readonly<InkerErrorContext>;
37
+
38
+ constructor(
39
+ code: InkerErrorCode,
40
+ message: string,
41
+ context?: InkerErrorContext,
42
+ options?: { cause?: unknown },
43
+ ) {
44
+ super(message, options);
45
+ this.name = "InkerRenderError";
46
+ this.code = code;
47
+ this.context = Object.freeze({ ...(context ?? {}) });
48
+ }
49
+ }
@@ -0,0 +1,55 @@
1
+ import type { AsyncLocalStorage } from "node:async_hooks";
2
+ import type { Templates } from "./Templates.js";
3
+
4
+ /**
5
+ * Duck-typed contract for the per-request context Inker reads inside its
6
+ * canonical helper bodies. Keeping the interface local (instead of importing
7
+ * `@c9up/ream`'s HttpContext) preserves Inker's leaf-invariant: the
8
+ * InkerRenderer file lives in the LEAF half of the package and must compile
9
+ * without `@c9up/ream` installed. Ream's HttpContext structurally satisfies
10
+ * this shape — verified by the integration test's compile step.
11
+ */
12
+ export interface InkerHttpContext {
13
+ readonly request: object;
14
+ readonly response: {
15
+ type(value: string): unknown;
16
+ send(body: string): unknown;
17
+ };
18
+ readonly store: Map<string, unknown>;
19
+ readonly locale: string;
20
+ }
21
+
22
+ export class InkerRenderer {
23
+ readonly #templates: Templates;
24
+ readonly #als: AsyncLocalStorage<InkerHttpContext>;
25
+
26
+ constructor(templates: Templates, als: AsyncLocalStorage<InkerHttpContext>) {
27
+ this.#templates = templates;
28
+ this.#als = als;
29
+ }
30
+
31
+ async render(
32
+ ctx: InkerHttpContext,
33
+ name: string,
34
+ data: Readonly<Record<string, unknown>>,
35
+ ): Promise<void> {
36
+ const html = await this.#als.run(ctx, () =>
37
+ this.#templates.render(name, data),
38
+ );
39
+ ctx.response.type("text/html; charset=utf-8");
40
+ ctx.response.send(html);
41
+ }
42
+
43
+ async renderToString(
44
+ ctx: InkerHttpContext,
45
+ name: string,
46
+ data: Readonly<Record<string, unknown>>,
47
+ ): Promise<string> {
48
+ return this.#als.run(ctx, () => this.#templates.render(name, data));
49
+ }
50
+
51
+ /** @internal Test seam — access the underlying Templates for cache control. */
52
+ get _templates(): Templates {
53
+ return this.#templates;
54
+ }
55
+ }
@@ -0,0 +1,27 @@
1
+ import { InkerRenderError } from "./InkerRenderError.js";
2
+
3
+ /**
4
+ * Marker class for already-escaped strings. Helpers that return
5
+ * trusted HTML (e.g. `csrfField()` returning `<input type="hidden" …>`)
6
+ * should return `new SafeString(html)` so the renderer emits the value
7
+ * raw instead of double-escaping.
8
+ *
9
+ * Returning a `SafeString` constructed from user input is a cross-site
10
+ * scripting bug. Restrict `SafeString` to author-controlled HTML
11
+ * (constants, library output where escaping is already handled).
12
+ */
13
+ export class SafeString {
14
+ readonly value: string;
15
+ constructor(value: string) {
16
+ if (typeof value !== "string") {
17
+ // Throws `InkerRenderError` (not native `TypeError`) so callers
18
+ // handling the typed error contract uniformly catch SafeString
19
+ // construction failures alongside every other Inker failure mode.
20
+ throw new InkerRenderError(
21
+ "E_INKER_INVALID_EXPRESSION",
22
+ `SafeString requires a string; got ${typeof value}`,
23
+ );
24
+ }
25
+ this.value = value;
26
+ }
27
+ }