@harborclient/sdk 0.4.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 (106) hide show
  1. package/README.md +41 -0
  2. package/dist/client.d.ts +2 -0
  3. package/dist/clipboard.d.ts +27 -0
  4. package/dist/clipboard.d.ts.map +1 -0
  5. package/dist/clipboard.js +17 -0
  6. package/dist/http/index.d.ts +3 -0
  7. package/dist/http/index.d.ts.map +1 -0
  8. package/dist/http/index.js +2 -0
  9. package/dist/http/resolveRequest.d.ts +66 -0
  10. package/dist/http/resolveRequest.d.ts.map +1 -0
  11. package/dist/http/resolveRequest.js +191 -0
  12. package/dist/http/resolveRequest.test.d.ts +2 -0
  13. package/dist/http/resolveRequest.test.d.ts.map +1 -0
  14. package/dist/http/resolveRequest.test.js +40 -0
  15. package/dist/http/substitute.d.ts +29 -0
  16. package/dist/http/substitute.d.ts.map +1 -0
  17. package/dist/http/substitute.js +43 -0
  18. package/dist/http/substitute.test.d.ts +2 -0
  19. package/dist/http/substitute.test.d.ts.map +1 -0
  20. package/dist/http/substitute.test.js +85 -0
  21. package/dist/index.d.ts +2 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +1 -0
  24. package/dist/main.d.ts +2 -0
  25. package/dist/main.d.ts.map +1 -0
  26. package/dist/main.js +1 -0
  27. package/dist/runtime/index.d.ts +20 -0
  28. package/dist/runtime/index.js +43 -0
  29. package/dist/runtime/jsx-dev-runtime.d.ts +12 -0
  30. package/dist/runtime/jsx-dev-runtime.js +15 -0
  31. package/dist/runtime/jsx-runtime.d.ts +35 -0
  32. package/dist/runtime/jsx-runtime.js +30 -0
  33. package/dist/runtime/react.d.ts +1 -0
  34. package/dist/runtime/react.js +46 -0
  35. package/dist/runtime/reactHost.js +26 -0
  36. package/dist/runtime/store.d.ts +19 -0
  37. package/dist/runtime/store.d.ts.map +1 -0
  38. package/dist/runtime/store.js +38 -0
  39. package/dist/runtime/store.ts +45 -0
  40. package/dist/runtime-utils.d.ts +36 -0
  41. package/dist/runtime-utils.d.ts.map +1 -0
  42. package/dist/runtime-utils.js +101 -0
  43. package/dist/runtime-utils.test.d.ts +2 -0
  44. package/dist/runtime-utils.test.d.ts.map +1 -0
  45. package/dist/runtime-utils.test.js +104 -0
  46. package/dist/signing/canonical.d.ts +27 -0
  47. package/dist/signing/canonical.d.ts.map +1 -0
  48. package/dist/signing/canonical.js +92 -0
  49. package/dist/signing/cli-sign.d.ts +3 -0
  50. package/dist/signing/cli-sign.d.ts.map +1 -0
  51. package/dist/signing/cli-sign.js +4 -0
  52. package/dist/signing/cli-verify.d.ts +3 -0
  53. package/dist/signing/cli-verify.d.ts.map +1 -0
  54. package/dist/signing/cli-verify.js +4 -0
  55. package/dist/signing/cli.d.ts +13 -0
  56. package/dist/signing/cli.d.ts.map +1 -0
  57. package/dist/signing/cli.js +148 -0
  58. package/dist/signing/index.d.ts +10 -0
  59. package/dist/signing/index.d.ts.map +1 -0
  60. package/dist/signing/index.js +7 -0
  61. package/dist/signing/inventory.d.ts +21 -0
  62. package/dist/signing/inventory.d.ts.map +1 -0
  63. package/dist/signing/inventory.js +80 -0
  64. package/dist/signing/manifest.d.ts +16 -0
  65. package/dist/signing/manifest.d.ts.map +1 -0
  66. package/dist/signing/manifest.js +33 -0
  67. package/dist/signing/sign.d.ts +10 -0
  68. package/dist/signing/sign.d.ts.map +1 -0
  69. package/dist/signing/sign.js +48 -0
  70. package/dist/signing/signing.test.d.ts +2 -0
  71. package/dist/signing/signing.test.d.ts.map +1 -0
  72. package/dist/signing/signing.test.js +100 -0
  73. package/dist/signing/testFixtures.d.ts +21 -0
  74. package/dist/signing/testFixtures.d.ts.map +1 -0
  75. package/dist/signing/testFixtures.js +41 -0
  76. package/dist/signing/types.d.ts +87 -0
  77. package/dist/signing/types.d.ts.map +1 -0
  78. package/dist/signing/types.js +12 -0
  79. package/dist/signing/verify.d.ts +17 -0
  80. package/dist/signing/verify.d.ts.map +1 -0
  81. package/dist/signing/verify.js +129 -0
  82. package/dist/storage/cappedList.d.ts +55 -0
  83. package/dist/storage/cappedList.d.ts.map +1 -0
  84. package/dist/storage/cappedList.js +102 -0
  85. package/dist/storage/cappedList.test.d.ts +2 -0
  86. package/dist/storage/cappedList.test.d.ts.map +1 -0
  87. package/dist/storage/cappedList.test.js +11 -0
  88. package/dist/storage/index.d.ts +2 -0
  89. package/dist/storage/index.d.ts.map +1 -0
  90. package/dist/storage/index.js +1 -0
  91. package/dist/types.d.ts +1282 -0
  92. package/dist/types.d.ts.map +1 -0
  93. package/dist/types.js +1 -0
  94. package/dist/ui/format.d.ts +23 -0
  95. package/dist/ui/format.d.ts.map +1 -0
  96. package/dist/ui/format.js +45 -0
  97. package/dist/ui/index.d.ts +3 -0
  98. package/dist/ui/index.d.ts.map +1 -0
  99. package/dist/ui/index.js +2 -0
  100. package/dist/ui/tokens.d.ts +34 -0
  101. package/dist/ui/tokens.d.ts.map +1 -0
  102. package/dist/ui/tokens.js +56 -0
  103. package/dist/utilities.test.d.ts +2 -0
  104. package/dist/utilities.test.d.ts.map +1 -0
  105. package/dist/utilities.test.js +16 -0
  106. package/package.json +130 -0
@@ -0,0 +1,43 @@
1
+ import { setHostReact, requireHostReact } from './reactHost.js';
2
+
3
+ /**
4
+ * Installs the HarborClient renderer React instance for plugin JSX and hooks.
5
+ *
6
+ * Call once at the start of `activate(hc)` before registering UI contributions.
7
+ *
8
+ * @param {typeof import('react')} react - React namespace from `hc.react`.
9
+ */
10
+ export function installReact(react) {
11
+ setHostReact(react);
12
+ }
13
+
14
+ /**
15
+ * Creates a React component from a factory that receives the host React namespace.
16
+ *
17
+ * Useful when you need hooks or createElement in the same module as activate()
18
+ * without importing React directly.
19
+ *
20
+ * @template {Record<string, unknown>} P
21
+ * @param {(react: typeof import('react')) => import('react').ComponentType<P>} factory
22
+ * @returns {import('react').ComponentType<P>}
23
+ */
24
+ export function createPluginComponent(factory) {
25
+ /** @type {import('react').ComponentType<P> | null} */
26
+ let Component = null;
27
+
28
+ /**
29
+ * Lazily builds the component on first render after installReact().
30
+ *
31
+ * @param {P} props - Component props.
32
+ * @returns {import('react').ReactElement | null}
33
+ */
34
+ function PluginComponent(props) {
35
+ if (Component == null) {
36
+ Component = factory(requireHostReact());
37
+ }
38
+ const react = requireHostReact();
39
+ return react.createElement(Component, props);
40
+ }
41
+
42
+ return PluginComponent;
43
+ }
@@ -0,0 +1,12 @@
1
+ import type * as React from 'react';
2
+
3
+ export { Fragment } from './jsx-runtime';
4
+
5
+ /**
6
+ * Development JSX runtime entry; delegates to the production jsx helper.
7
+ */
8
+ export function jsxDEV(
9
+ type: React.ElementType,
10
+ props: Record<string, unknown> | null | undefined,
11
+ key?: string | number
12
+ ): React.ReactElement;
@@ -0,0 +1,15 @@
1
+ import { Fragment, jsx } from './jsx-runtime.js';
2
+
3
+ export { Fragment };
4
+
5
+ /**
6
+ * Development JSX transform entry; delegates to the production jsx helper.
7
+ *
8
+ * @param {import('react').ElementType} type - Element type or Fragment sentinel.
9
+ * @param {Record<string, unknown> | null | undefined} props - Element props.
10
+ * @param {string | number | undefined} key - React key when provided by the compiler.
11
+ * @returns {import('react').ReactElement}
12
+ */
13
+ export function jsxDEV(type, props, key) {
14
+ return jsx(type, props, key);
15
+ }
@@ -0,0 +1,35 @@
1
+ import type * as React from 'react';
2
+
3
+ /**
4
+ * Sentinel value compiled from JSX fragments; resolved to host React.Fragment at runtime.
5
+ */
6
+ export declare const Fragment: unique symbol;
7
+
8
+ /**
9
+ * Automatic JSX runtime entry used when `jsxImportSource` is `@harborclient/sdk`.
10
+ */
11
+ export function jsx(
12
+ type: React.ElementType,
13
+ props: Record<string, unknown> | null | undefined,
14
+ key?: string | number
15
+ ): React.ReactElement;
16
+
17
+ /**
18
+ * Automatic JSX runtime entry for elements with multiple children.
19
+ */
20
+ export function jsxs(
21
+ type: React.ElementType,
22
+ props: Record<string, unknown> | null | undefined,
23
+ key?: string | number
24
+ ): React.ReactElement;
25
+
26
+ export namespace JSX {
27
+ interface Element extends React.JSX.Element { }
28
+ interface ElementClass extends React.JSX.ElementClass { }
29
+ interface ElementAttributesProperty extends React.JSX.ElementAttributesProperty { }
30
+ interface ElementChildrenAttribute extends React.JSX.ElementChildrenAttribute { }
31
+ type LibraryManagedAttributes<C, P> = React.JSX.LibraryManagedAttributes<C, P>;
32
+ interface IntrinsicAttributes extends React.JSX.IntrinsicAttributes { }
33
+ interface IntrinsicClassAttributes<T> extends React.JSX.IntrinsicClassAttributes<T> { }
34
+ interface IntrinsicElements extends React.JSX.IntrinsicElements { }
35
+ }
@@ -0,0 +1,30 @@
1
+ import { requireHostReact } from './reactHost.js';
2
+
3
+ /**
4
+ * Sentinel value compiled from JSX fragments; resolved to host React.Fragment at runtime.
5
+ */
6
+ export const Fragment = Symbol.for('@harborclient/sdk.Fragment');
7
+
8
+ /**
9
+ * Builds a React element using the installed host React instance.
10
+ *
11
+ * @param {import('react').ElementType} type - Element type or Fragment sentinel.
12
+ * @param {Record<string, unknown> | null | undefined} props - Element props.
13
+ * @param {string | number | undefined} key - React key when provided by the compiler.
14
+ * @returns {import('react').ReactElement}
15
+ */
16
+ function build(type, props, key) {
17
+ const react = requireHostReact();
18
+ const elementType = type === Fragment ? react.Fragment : type;
19
+ const { children, ...rest } = props ?? {};
20
+ if (key !== undefined) {
21
+ rest.key = key;
22
+ }
23
+ return react.createElement(elementType, rest, children);
24
+ }
25
+
26
+ /** @type {typeof import('react/jsx-runtime').jsx} */
27
+ export const jsx = build;
28
+
29
+ /** @type {typeof import('react/jsx-runtime').jsxs} */
30
+ export const jsxs = build;
@@ -0,0 +1 @@
1
+ export { useState, useEffect, useCallback, useMemo, useRef, useSyncExternalStore } from 'react';
@@ -0,0 +1,46 @@
1
+ import { requireHostReact } from './reactHost.js';
2
+
3
+ /**
4
+ * Returns a hook from the installed host React instance.
5
+ *
6
+ * @param {keyof typeof import('react')} name - Hook name on the React namespace.
7
+ * @returns {unknown} Hook function from host React.
8
+ */
9
+ function hook(name) {
10
+ const react = requireHostReact();
11
+ const fn = react[name];
12
+ if (typeof fn !== 'function') {
13
+ throw new Error(`React hook "${String(name)}" is not available on hc.react.`);
14
+ }
15
+ return fn;
16
+ }
17
+
18
+ /** @type {typeof import('react').useState} */
19
+ export function useState(initialState) {
20
+ return hook('useState')(initialState);
21
+ }
22
+
23
+ /** @type {typeof import('react').useEffect} */
24
+ export function useEffect(effect, deps) {
25
+ return hook('useEffect')(effect, deps);
26
+ }
27
+
28
+ /** @type {typeof import('react').useCallback} */
29
+ export function useCallback(callback, deps) {
30
+ return hook('useCallback')(callback, deps);
31
+ }
32
+
33
+ /** @type {typeof import('react').useMemo} */
34
+ export function useMemo(factory, deps) {
35
+ return hook('useMemo')(factory, deps);
36
+ }
37
+
38
+ /** @type {typeof import('react').useRef} */
39
+ export function useRef(initialValue) {
40
+ return hook('useRef')(initialValue);
41
+ }
42
+
43
+ /** @type {typeof import('react').useSyncExternalStore} */
44
+ export function useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot) {
45
+ return hook('useSyncExternalStore')(subscribe, getSnapshot, getServerSnapshot);
46
+ }
@@ -0,0 +1,26 @@
1
+ /** @type {typeof import('react') | null} */
2
+ let hostReact = null;
3
+
4
+ /**
5
+ * Installs the HarborClient renderer React instance for plugin JSX and hooks.
6
+ *
7
+ * @param {typeof import('react')} react - React namespace from `hc.react`.
8
+ */
9
+ export function setHostReact(react) {
10
+ hostReact = react;
11
+ }
12
+
13
+ /**
14
+ * Returns the installed host React instance.
15
+ *
16
+ * @returns {typeof import('react')} Host React namespace.
17
+ * @throws {Error} When {@link setHostReact} has not been called yet.
18
+ */
19
+ export function requireHostReact() {
20
+ if (hostReact == null) {
21
+ throw new Error(
22
+ 'Plugin React host is not installed. Call installReact(hc.react) at the start of activate().'
23
+ );
24
+ }
25
+ return hostReact;
26
+ }
@@ -0,0 +1,19 @@
1
+ import type { Disposable } from '../types.js';
2
+ /**
3
+ * Creates a module-level external store compatible with React `useSyncExternalStore`.
4
+ *
5
+ * @param initial - Initial snapshot value.
6
+ */
7
+ export declare function createExternalStore<T>(initial: T): {
8
+ subscribe: (listener: () => void) => () => void;
9
+ getSnapshot: () => T;
10
+ setState: (next: T) => void;
11
+ };
12
+ /**
13
+ * Starts an interval and returns a disposable that clears it on deactivation.
14
+ *
15
+ * @param callback - Function invoked on each tick.
16
+ * @param intervalMs - Interval in milliseconds.
17
+ */
18
+ export declare function setIntervalDisposable(callback: () => void, intervalMs: number): Disposable;
19
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/runtime/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,GAAG;IAClD,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAC;IAChD,WAAW,EAAE,MAAM,CAAC,CAAC;IACrB,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC;CAC7B,CAkBA;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,IAAI,EAAE,UAAU,EAAE,MAAM,GAAG,UAAU,CAO1F"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Creates a module-level external store compatible with React `useSyncExternalStore`.
3
+ *
4
+ * @param initial - Initial snapshot value.
5
+ */
6
+ export function createExternalStore(initial) {
7
+ let state = initial;
8
+ const listeners = new Set();
9
+ return {
10
+ subscribe: (listener) => {
11
+ listeners.add(listener);
12
+ return () => {
13
+ listeners.delete(listener);
14
+ };
15
+ },
16
+ getSnapshot: () => state,
17
+ setState: (next) => {
18
+ state = next;
19
+ for (const listener of listeners) {
20
+ listener();
21
+ }
22
+ }
23
+ };
24
+ }
25
+ /**
26
+ * Starts an interval and returns a disposable that clears it on deactivation.
27
+ *
28
+ * @param callback - Function invoked on each tick.
29
+ * @param intervalMs - Interval in milliseconds.
30
+ */
31
+ export function setIntervalDisposable(callback, intervalMs) {
32
+ const timer = setInterval(callback, intervalMs);
33
+ return {
34
+ dispose: () => {
35
+ clearInterval(timer);
36
+ }
37
+ };
38
+ }
@@ -0,0 +1,45 @@
1
+ import type { Disposable } from '../types.js';
2
+
3
+ /**
4
+ * Creates a module-level external store compatible with React `useSyncExternalStore`.
5
+ *
6
+ * @param initial - Initial snapshot value.
7
+ */
8
+ export function createExternalStore<T>(initial: T): {
9
+ subscribe: (listener: () => void) => () => void;
10
+ getSnapshot: () => T;
11
+ setState: (next: T) => void;
12
+ } {
13
+ let state = initial;
14
+ const listeners = new Set<() => void>();
15
+ return {
16
+ subscribe: (listener) => {
17
+ listeners.add(listener);
18
+ return () => {
19
+ listeners.delete(listener);
20
+ };
21
+ },
22
+ getSnapshot: () => state,
23
+ setState: (next) => {
24
+ state = next;
25
+ for (const listener of listeners) {
26
+ listener();
27
+ }
28
+ }
29
+ };
30
+ }
31
+
32
+ /**
33
+ * Starts an interval and returns a disposable that clears it on deactivation.
34
+ *
35
+ * @param callback - Function invoked on each tick.
36
+ * @param intervalMs - Interval in milliseconds.
37
+ */
38
+ export function setIntervalDisposable(callback: () => void, intervalMs: number): Disposable {
39
+ const timer = setInterval(callback, intervalMs);
40
+ return {
41
+ dispose: () => {
42
+ clearInterval(timer);
43
+ }
44
+ };
45
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Creates a new unique id string.
3
+ *
4
+ * Uses `crypto.randomUUID` when available (renderer); falls back for the SES main runtime.
5
+ *
6
+ * @param prefix - Optional prefix when falling back from randomUUID.
7
+ */
8
+ export declare function randomId(prefix?: string): string;
9
+ /**
10
+ * Returns the UTF-8 byte length of a string without relying on TextEncoder.
11
+ *
12
+ * Safe in the SES plugin main runtime where only `hc`, `console`, `Date`, and `Math`
13
+ * globals are available.
14
+ *
15
+ * @param value - String to measure.
16
+ */
17
+ export declare function byteLength(value: string): number;
18
+ /**
19
+ * Truncates a string to a maximum UTF-8 byte length without TextEncoder.
20
+ *
21
+ * @param value - String to truncate.
22
+ * @param maxBytes - Maximum UTF-8 bytes to retain.
23
+ */
24
+ export declare function truncateToBytes(value: string, maxBytes: number): string;
25
+ /**
26
+ * Truncates a body string to the configured byte limit with an optional suffix.
27
+ *
28
+ * @param body - Raw body text.
29
+ * @param maxBytes - Maximum UTF-8 bytes to retain.
30
+ * @param suffix - Optional suffix appended when truncated.
31
+ */
32
+ export declare function truncateBody(body: string, maxBytes: number, suffix?: string): {
33
+ body: string;
34
+ truncated: boolean;
35
+ };
36
+ //# sourceMappingURL=runtime-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-utils.d.ts","sourceRoot":"","sources":["../src/runtime-utils.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,MAAM,SAAO,GAAG,MAAM,CAK9C;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAmBhD;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CA+BvE;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,MAAM,SAAsD,GAC3D;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAQtC"}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Creates a new unique id string.
3
+ *
4
+ * Uses `crypto.randomUUID` when available (renderer); falls back for the SES main runtime.
5
+ *
6
+ * @param prefix - Optional prefix when falling back from randomUUID.
7
+ */
8
+ export function randomId(prefix = 'id') {
9
+ if (typeof globalThis.crypto?.randomUUID === 'function') {
10
+ return globalThis.crypto.randomUUID();
11
+ }
12
+ return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
13
+ }
14
+ /**
15
+ * Returns the UTF-8 byte length of a string without relying on TextEncoder.
16
+ *
17
+ * Safe in the SES plugin main runtime where only `hc`, `console`, `Date`, and `Math`
18
+ * globals are available.
19
+ *
20
+ * @param value - String to measure.
21
+ */
22
+ export function byteLength(value) {
23
+ if (typeof TextEncoder !== 'undefined') {
24
+ return new TextEncoder().encode(value).length;
25
+ }
26
+ let bytes = 0;
27
+ for (let index = 0; index < value.length; index += 1) {
28
+ const code = value.charCodeAt(index);
29
+ if (code <= 0x7f) {
30
+ bytes += 1;
31
+ }
32
+ else if (code <= 0x7ff) {
33
+ bytes += 2;
34
+ }
35
+ else if (code >= 0xd800 && code <= 0xdbff) {
36
+ bytes += 4;
37
+ index += 1;
38
+ }
39
+ else {
40
+ bytes += 3;
41
+ }
42
+ }
43
+ return bytes;
44
+ }
45
+ /**
46
+ * Truncates a string to a maximum UTF-8 byte length without TextEncoder.
47
+ *
48
+ * @param value - String to truncate.
49
+ * @param maxBytes - Maximum UTF-8 bytes to retain.
50
+ */
51
+ export function truncateToBytes(value, maxBytes) {
52
+ if (typeof TextEncoder !== 'undefined') {
53
+ const encoded = new TextEncoder().encode(value);
54
+ if (encoded.length <= maxBytes) {
55
+ return value;
56
+ }
57
+ return new TextDecoder().decode(encoded.slice(0, maxBytes));
58
+ }
59
+ let bytes = 0;
60
+ let index = 0;
61
+ for (; index < value.length; index += 1) {
62
+ const code = value.charCodeAt(index);
63
+ let charBytes;
64
+ if (code <= 0x7f) {
65
+ charBytes = 1;
66
+ }
67
+ else if (code <= 0x7ff) {
68
+ charBytes = 2;
69
+ }
70
+ else if (code >= 0xd800 && code <= 0xdbff) {
71
+ charBytes = 4;
72
+ }
73
+ else {
74
+ charBytes = 3;
75
+ }
76
+ if (bytes + charBytes > maxBytes) {
77
+ break;
78
+ }
79
+ bytes += charBytes;
80
+ if (charBytes === 4) {
81
+ index += 1;
82
+ }
83
+ }
84
+ return value.slice(0, index);
85
+ }
86
+ /**
87
+ * Truncates a body string to the configured byte limit with an optional suffix.
88
+ *
89
+ * @param body - Raw body text.
90
+ * @param maxBytes - Maximum UTF-8 bytes to retain.
91
+ * @param suffix - Optional suffix appended when truncated.
92
+ */
93
+ export function truncateBody(body, maxBytes, suffix = `\n\n[truncated — body exceeded ${maxBytes} bytes]`) {
94
+ if (byteLength(body) <= maxBytes) {
95
+ return { body, truncated: false };
96
+ }
97
+ return {
98
+ body: `${truncateToBytes(body, maxBytes)}${suffix}`,
99
+ truncated: true
100
+ };
101
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=runtime-utils.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-utils.test.d.ts","sourceRoot":"","sources":["../src/runtime-utils.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,104 @@
1
+ import { afterEach, describe, expect, it, jest } from '@jest/globals';
2
+ import { byteLength, randomId, truncateBody, truncateToBytes } from './runtime-utils.js';
3
+ const originalTextEncoder = globalThis.TextEncoder;
4
+ const originalCrypto = globalThis.crypto;
5
+ afterEach(() => {
6
+ globalThis.TextEncoder = originalTextEncoder;
7
+ globalThis.crypto = originalCrypto;
8
+ jest.restoreAllMocks();
9
+ });
10
+ function withoutTextEncoder(run) {
11
+ // @ts-expect-error — exercise manual UTF-8 fallback used in SES main runtime
12
+ globalThis.TextEncoder = undefined;
13
+ return run();
14
+ }
15
+ describe('randomId', () => {
16
+ it('returns a UUID when crypto.randomUUID is available', () => {
17
+ const spy = jest
18
+ .spyOn(globalThis.crypto, 'randomUUID')
19
+ .mockReturnValue('00000000-0000-4000-8000-000000000000');
20
+ expect(randomId()).toBe('00000000-0000-4000-8000-000000000000');
21
+ spy.mockRestore();
22
+ });
23
+ it('falls back to a prefixed id when randomUUID is unavailable', () => {
24
+ // @ts-expect-error — exercise SES main-runtime fallback
25
+ globalThis.crypto = undefined;
26
+ expect(randomId('req')).toMatch(/^req-\d+-[a-z0-9]+$/);
27
+ });
28
+ it('uses the default prefix in the fallback path', () => {
29
+ // @ts-expect-error — exercise SES main-runtime fallback
30
+ globalThis.crypto = undefined;
31
+ expect(randomId()).toMatch(/^id-\d+-[a-z0-9]+$/);
32
+ });
33
+ });
34
+ describe('byteLength', () => {
35
+ it('counts ASCII as one byte per character', () => {
36
+ expect(byteLength('abc')).toBe(3);
37
+ expect(byteLength('')).toBe(0);
38
+ });
39
+ it('counts two-byte UTF-8 characters', () => {
40
+ expect(byteLength('é')).toBe(2);
41
+ });
42
+ it('counts three-byte UTF-8 characters', () => {
43
+ expect(byteLength('中')).toBe(3);
44
+ });
45
+ it('counts surrogate pairs as four bytes', () => {
46
+ expect(byteLength('😀')).toBe(4);
47
+ });
48
+ it('matches TextEncoder when the manual fallback is used', () => {
49
+ const samples = ['plain', 'é', '中', '😀', 'mix é 中 😀'];
50
+ for (const sample of samples) {
51
+ const encodedLength = new originalTextEncoder().encode(sample).length;
52
+ withoutTextEncoder(() => {
53
+ expect(byteLength(sample)).toBe(encodedLength);
54
+ });
55
+ }
56
+ });
57
+ });
58
+ describe('truncateToBytes', () => {
59
+ it('returns the original string when it fits', () => {
60
+ expect(truncateToBytes('hello', 10)).toBe('hello');
61
+ expect(truncateToBytes('é', 2)).toBe('é');
62
+ });
63
+ it('truncates multibyte text without splitting code units', () => {
64
+ expect(truncateToBytes('éé', 2)).toBe('é');
65
+ expect(truncateToBytes('abc', 2)).toBe('ab');
66
+ });
67
+ it('returns an empty string when maxBytes is zero', () => {
68
+ expect(truncateToBytes('hello', 0)).toBe('');
69
+ });
70
+ it('excludes an incomplete surrogate pair in the manual fallback', () => {
71
+ withoutTextEncoder(() => {
72
+ expect(truncateToBytes('a😀b', 2)).toBe('a');
73
+ });
74
+ });
75
+ it('does not split multibyte characters in the manual fallback', () => {
76
+ withoutTextEncoder(() => {
77
+ expect(truncateToBytes('mix é end', 5)).toBe('mix ');
78
+ expect(truncateToBytes('中文字', 3)).toBe('中');
79
+ });
80
+ });
81
+ });
82
+ describe('truncateBody', () => {
83
+ it('returns the body unchanged when within the byte limit', () => {
84
+ expect(truncateBody('{"ok":true}', 100)).toEqual({
85
+ body: '{"ok":true}',
86
+ truncated: false
87
+ });
88
+ });
89
+ it('truncates and appends the default suffix when over the limit', () => {
90
+ const body = 'x'.repeat(20);
91
+ const maxBytes = 10;
92
+ const result = truncateBody(body, maxBytes);
93
+ expect(result.truncated).toBe(true);
94
+ expect(result.body.endsWith(`[truncated — body exceeded ${maxBytes} bytes]`)).toBe(true);
95
+ expect(byteLength(result.body.replace(/\n\n\[truncated — body exceeded \d+ bytes\]$/, ''))).toBeLessThanOrEqual(maxBytes);
96
+ });
97
+ it('uses a custom suffix when provided', () => {
98
+ const result = truncateBody('0123456789', 5, '…');
99
+ expect(result).toEqual({
100
+ body: '01234…',
101
+ truncated: true
102
+ });
103
+ });
104
+ });
@@ -0,0 +1,27 @@
1
+ import type { PluginFileHash, PluginSignaturePayload } from './types.js';
2
+ /**
3
+ * Builds the canonical signing payload object from manifest identity and file hashes.
4
+ *
5
+ * @param pluginId - Plugin manifest id.
6
+ * @param pluginVersion - Plugin manifest version.
7
+ * @param files - Sorted plugin file inventory.
8
+ * @param keyId - Optional signing key label.
9
+ */
10
+ export declare function buildSignaturePayload(pluginId: string, pluginVersion: string, files: PluginFileHash[], keyId?: string): PluginSignaturePayload;
11
+ /**
12
+ * Serializes a signing payload to canonical JSON bytes for Ed25519 signing.
13
+ *
14
+ * @param payload - Unsigned signature payload.
15
+ */
16
+ export declare function canonicalizeSignaturePayload(payload: PluginSignaturePayload): Buffer;
17
+ /**
18
+ * Parses and validates a signature.json object read from disk.
19
+ *
20
+ * @param raw - Parsed JSON value.
21
+ * @returns Validated signature file contents.
22
+ * @throws When the payload shape is invalid.
23
+ */
24
+ export declare function parsePluginSignatureFile(raw: unknown): PluginSignaturePayload & {
25
+ signature: string;
26
+ };
27
+ //# sourceMappingURL=canonical.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canonical.d.ts","sourceRoot":"","sources":["../../src/signing/canonical.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAGzE;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,cAAc,EAAE,EACvB,KAAK,CAAC,EAAE,MAAM,GACb,sBAAsB,CAcxB;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,sBAAsB,GAAG,MAAM,CAEpF;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,OAAO,GACX,sBAAsB,GAAG;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,CA2DhD"}