@pulse-compute/runtime 0.0.0 → 1.0.0-beta.2

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.
@@ -0,0 +1,130 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Package-local JavaScript realization of the sealed Pulse path grammar.
5
+ * It intentionally mirrors @pulse-compute/wasm-contracts without importing
6
+ * compiler/package-contract code into the public runtime package.
7
+ */
8
+
9
+ function ensureLeadingSlash(value) {
10
+ return value.startsWith('/') ? value : `/${value}`;
11
+ }
12
+
13
+ function trimTrailingSlash(value) {
14
+ let out = value;
15
+ while (out.length > 1 && out.endsWith('/')) out = out.slice(0, -1);
16
+ return out;
17
+ }
18
+
19
+ function normalizeRoutePath(path) {
20
+ const text = String(path ?? '').trim();
21
+ return trimTrailingSlash(ensureLeadingSlash(text || '/'));
22
+ }
23
+
24
+ function stripScopedWildcard(path) {
25
+ let normalized = normalizeRoutePath(path);
26
+ if (normalized === '/*') return '/';
27
+ if (normalized.endsWith('/*')) normalized = normalized.slice(0, -2);
28
+ if (normalized.endsWith('*')) normalized = normalized.slice(0, -1);
29
+ return trimTrailingSlash(normalized || '/');
30
+ }
31
+
32
+ function normalizeScopedPath(path) {
33
+ const stripped = stripScopedWildcard(path || '/');
34
+ return stripped === '/' ? '/*' : `${stripped}/*`;
35
+ }
36
+
37
+ function rawSegments(normalizedPath) {
38
+ if (normalizedPath === '/') return [];
39
+ const body = normalizedPath.slice(1);
40
+ const segments = body.split('/');
41
+ if (segments.some((segment) => segment.length === 0)) {
42
+ throw new Error(`Duplicate or empty path segment is unsupported: ${normalizedPath}`);
43
+ }
44
+ return segments;
45
+ }
46
+
47
+ function compileRoutePath(path, options = {}) {
48
+ const scoped = Boolean(options.scoped);
49
+ const normalized = scoped ? normalizeScopedPath(path) : normalizeRoutePath(path);
50
+ const segments = [];
51
+ const params = [];
52
+ const seen = new Set();
53
+ let wildcard = false;
54
+
55
+ rawSegments(normalized).forEach((segment, index, all) => {
56
+ if (segment === '*') {
57
+ if (options.allowWildcard === false || index !== all.length - 1) {
58
+ throw new Error(`Wildcard must be the final supported path segment: ${normalized}`);
59
+ }
60
+ wildcard = true;
61
+ segments.push(Object.freeze({ kind: 'wildcard' }));
62
+ return;
63
+ }
64
+ if (segment.startsWith('*') || segment.includes('*')) {
65
+ throw new Error(`Only one unnamed trailing * wildcard segment is supported: ${normalized}`);
66
+ }
67
+ if (segment.startsWith(':')) {
68
+ const name = segment.slice(1);
69
+ if (!/^[A-Za-z_$][\w$]*$/.test(name)) {
70
+ throw new Error(`Unsupported path parameter name "${name}" in ${normalized}`);
71
+ }
72
+ if (seen.has(name)) throw new Error(`Duplicate path parameter ":${name}" is unsupported: ${normalized}`);
73
+ seen.add(name);
74
+ params.push(name);
75
+ segments.push(Object.freeze({ kind: 'param', name }));
76
+ return;
77
+ }
78
+ if (!/^[A-Za-z0-9._~!$&'()+,;=@%-]+$/.test(segment)) {
79
+ throw new Error(`Unsupported literal path segment "${segment}" in ${normalized}`);
80
+ }
81
+ segments.push(Object.freeze({ kind: 'literal', value: segment }));
82
+ });
83
+
84
+ return Object.freeze({
85
+ original: String(path ?? ''),
86
+ normalized,
87
+ scoped,
88
+ wildcard,
89
+ segments: Object.freeze(segments),
90
+ params: Object.freeze(params)
91
+ });
92
+ }
93
+
94
+ function matchRoutePath(compiledPattern, path) {
95
+ const pattern = compiledPattern && compiledPattern.segments
96
+ ? compiledPattern
97
+ : compileRoutePath(compiledPattern, { allowWildcard: true });
98
+ const pathSegments = rawSegments(normalizeRoutePath(path));
99
+ const params = {};
100
+ let pathIndex = 0;
101
+
102
+ for (const segment of pattern.segments) {
103
+ if (segment.kind === 'wildcard') {
104
+ const rest = pathSegments.slice(pathIndex);
105
+ return Object.freeze({
106
+ params: Object.freeze({ ...params }),
107
+ rest: rest.length === 0 ? '/' : `/${rest.join('/')}`
108
+ });
109
+ }
110
+ const value = pathSegments[pathIndex];
111
+ if (value == null) return null;
112
+ if (segment.kind === 'literal') {
113
+ if (segment.value !== value) return null;
114
+ } else {
115
+ params[segment.name] = value;
116
+ }
117
+ pathIndex += 1;
118
+ }
119
+
120
+ if (pathIndex !== pathSegments.length) return null;
121
+ return Object.freeze({ params: Object.freeze({ ...params }), rest: '/' });
122
+ }
123
+
124
+ module.exports = Object.freeze({
125
+ compileRoutePath,
126
+ matchRoutePath,
127
+ normalizeRoutePath,
128
+ normalizeScopedPath,
129
+ stripScopedWildcard
130
+ });
@@ -0,0 +1,231 @@
1
+ 'use strict';
2
+
3
+ const { PulseRuntimeContractError, PulseUnhandledError } = require('./errors.js');
4
+
5
+ const REDACTED_VALUE = '<redacted>';
6
+ const CIRCULAR_VALUE = '<circular>';
7
+ const BINARY_VALUE = '<binary>';
8
+ const BOUNDED_VALUE = '<bounded>';
9
+ const MAX_REDACTION_DEPTH = 32;
10
+ const MAX_REDACTION_ENTRIES = 1024;
11
+
12
+ const SENSITIVE_KEYS = new Set([
13
+ 'authorization',
14
+ 'proxy-authorization',
15
+ 'cookie',
16
+ 'set-cookie',
17
+ 'x-api-key',
18
+ 'api-key',
19
+ 'apikey',
20
+ 'token',
21
+ 'secret',
22
+ 'password',
23
+ 'credential',
24
+ 'credentials'
25
+ ]);
26
+
27
+ function isSensitiveKey(value) {
28
+ const key = String(value || '').toLowerCase();
29
+ if (SENSITIVE_KEYS.has(key)) return true;
30
+ const compact = key.replace(/[^a-z0-9]/g, '');
31
+ return compact.endsWith('token')
32
+ || compact.endsWith('secret')
33
+ || compact.endsWith('password')
34
+ || compact.endsWith('credential')
35
+ || compact.endsWith('credentials')
36
+ || compact.endsWith('apikey');
37
+ }
38
+
39
+ function ownDataValue(object, key) {
40
+ if (!object || (typeof object !== 'object' && typeof object !== 'function')) return undefined;
41
+ const descriptor = Object.getOwnPropertyDescriptor(object, key);
42
+ return descriptor && Object.prototype.hasOwnProperty.call(descriptor, 'value')
43
+ ? descriptor.value
44
+ : undefined;
45
+ }
46
+
47
+ function normalizeInitialValues(input) {
48
+ if (input === undefined || input === null) return [];
49
+ if (typeof input === 'string') return input.length > 0 ? [input] : [];
50
+ if (!input || typeof input[Symbol.iterator] !== 'function') return [];
51
+ const output = [];
52
+ for (const value of input) {
53
+ if (typeof value === 'string' && value.length > 0) output.push(value);
54
+ }
55
+ return output;
56
+ }
57
+
58
+ function createRedactionState(initialValues = []) {
59
+ const registered = new Set(normalizeInitialValues(initialValues));
60
+
61
+ function tokens() {
62
+ return [...registered].sort((left, right) => right.length - left.length);
63
+ }
64
+
65
+ function redactString(value) {
66
+ let output = String(value);
67
+ for (const token of tokens()) output = output.split(token).join(REDACTED_VALUE);
68
+ return output;
69
+ }
70
+
71
+ function redactValue(value, keyName = '', seen = new WeakSet(), depth = 0) {
72
+ if (isSensitiveKey(keyName)) return REDACTED_VALUE;
73
+ if (typeof value === 'string') return redactString(value);
74
+ if (value === null || value === undefined || typeof value !== 'object') return value;
75
+ if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) return BINARY_VALUE;
76
+ if (value instanceof Uint8Array || value instanceof ArrayBuffer) return BINARY_VALUE;
77
+ if (depth >= MAX_REDACTION_DEPTH) return BOUNDED_VALUE;
78
+ if (value instanceof Error) return redactError(value, seen, depth);
79
+ if (seen.has(value)) return CIRCULAR_VALUE;
80
+ seen.add(value);
81
+
82
+ if (Array.isArray(value)) {
83
+ const output = [];
84
+ const length = Math.min(value.length, MAX_REDACTION_ENTRIES);
85
+ for (let index = 0; index < length; index += 1) {
86
+ const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
87
+ if (!descriptor || !Object.prototype.hasOwnProperty.call(descriptor, 'value')) {
88
+ output.push('<accessor>');
89
+ continue;
90
+ }
91
+ const entry = descriptor.value;
92
+ if (Array.isArray(entry) && entry.length >= 2) {
93
+ const keyDescriptor = Object.getOwnPropertyDescriptor(entry, '0');
94
+ const key = keyDescriptor && Object.prototype.hasOwnProperty.call(keyDescriptor, 'value')
95
+ ? keyDescriptor.value
96
+ : undefined;
97
+ if (isSensitiveKey(key)) {
98
+ const pair = [redactString(key), REDACTED_VALUE];
99
+ for (let pairIndex = 2; pairIndex < Math.min(entry.length, MAX_REDACTION_ENTRIES); pairIndex += 1) {
100
+ const pairDescriptor = Object.getOwnPropertyDescriptor(entry, String(pairIndex));
101
+ pair.push(pairDescriptor && Object.prototype.hasOwnProperty.call(pairDescriptor, 'value')
102
+ ? redactValue(pairDescriptor.value, '', seen, depth + 1)
103
+ : '<accessor>');
104
+ }
105
+ output.push(Object.freeze(pair));
106
+ continue;
107
+ }
108
+ }
109
+ output.push(redactValue(entry, '', seen, depth + 1));
110
+ }
111
+ return Object.freeze(output);
112
+ }
113
+
114
+ const output = {};
115
+ for (const key of Reflect.ownKeys(value).slice(0, MAX_REDACTION_ENTRIES)) {
116
+ if (typeof key !== 'string') continue;
117
+ const outputKey = redactString(key);
118
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
119
+ const outputValue = !descriptor || !Object.prototype.hasOwnProperty.call(descriptor, 'value')
120
+ ? (isSensitiveKey(key) ? REDACTED_VALUE : '<accessor>')
121
+ : redactValue(descriptor.value, key, seen, depth + 1);
122
+ Object.defineProperty(output, outputKey, {
123
+ enumerable: true,
124
+ configurable: false,
125
+ writable: false,
126
+ value: outputValue
127
+ });
128
+ }
129
+ return Object.freeze(output);
130
+ }
131
+
132
+ function redactError(error, seen = new WeakSet(), depth = 0) {
133
+ if (!(error instanceof Error)) return redactValue(error, '', seen, depth);
134
+ if (depth >= MAX_REDACTION_DEPTH) {
135
+ const bounded = new Error(BOUNDED_VALUE);
136
+ bounded.name = 'PulseRedactedError';
137
+ return bounded;
138
+ }
139
+ if (seen.has(error)) return new Error(CIRCULAR_VALUE);
140
+ seen.add(error);
141
+
142
+ const rawMessage = ownDataValue(error, 'message');
143
+ const rawName = ownDataValue(error, 'name');
144
+ const rawCode = ownDataValue(error, 'code');
145
+ const message = redactString(typeof rawMessage === 'string' ? rawMessage : 'Pulse runtime failure.');
146
+ const causeDescriptor = Object.getOwnPropertyDescriptor(error, 'cause');
147
+ const cause = causeDescriptor && Object.prototype.hasOwnProperty.call(causeDescriptor, 'value')
148
+ ? redactValue(causeDescriptor.value, 'cause', seen, depth + 1)
149
+ : undefined;
150
+ const detailDescriptor = Object.getOwnPropertyDescriptor(error, 'detail');
151
+ const detail = detailDescriptor && Object.prototype.hasOwnProperty.call(detailDescriptor, 'value')
152
+ ? redactValue(detailDescriptor.value, 'detail', seen, depth + 1)
153
+ : undefined;
154
+
155
+ let safe;
156
+ if (error instanceof PulseRuntimeContractError) {
157
+ safe = new PulseRuntimeContractError(
158
+ typeof rawCode === 'string' ? redactString(rawCode) : 'PULSE_RUNTIME_CONTRACT_ERROR',
159
+ message,
160
+ { cause, detail }
161
+ );
162
+ } else if (error instanceof PulseUnhandledError) {
163
+ safe = new PulseUnhandledError(cause);
164
+ } else {
165
+ safe = new Error(message, cause === undefined ? undefined : { cause });
166
+ safe.name = typeof rawName === 'string' ? redactString(rawName) : 'Error';
167
+ }
168
+
169
+ const reserved = new Set(['name', 'message', 'stack', 'cause', 'detail']);
170
+ for (const key of Reflect.ownKeys(error).slice(0, MAX_REDACTION_ENTRIES)) {
171
+ if (typeof key !== 'string' || reserved.has(key)) continue;
172
+ const descriptor = Object.getOwnPropertyDescriptor(error, key);
173
+ if (!descriptor || !Object.prototype.hasOwnProperty.call(descriptor, 'value')) continue;
174
+ const outputKey = redactString(key);
175
+ const value = redactValue(descriptor.value, key, seen, depth + 1);
176
+ try {
177
+ Object.defineProperty(safe, outputKey, {
178
+ enumerable: descriptor.enumerable,
179
+ configurable: true,
180
+ writable: false,
181
+ value
182
+ });
183
+ } catch (_) {
184
+ // A hostile error object must not prevent containment or redaction.
185
+ }
186
+ }
187
+ if (detail !== undefined && !(safe instanceof PulseRuntimeContractError)) {
188
+ Object.defineProperty(safe, 'detail', { enumerable: false, configurable: true, value: detail });
189
+ }
190
+ const rawStack = ownDataValue(error, 'stack');
191
+ const stack = typeof rawStack === 'string' ? redactString(rawStack) : undefined;
192
+ if (stack !== undefined) {
193
+ try { Object.defineProperty(safe, 'stack', { configurable: true, writable: true, value: stack }); }
194
+ catch (_) { /* best effort */ }
195
+ }
196
+ return safe;
197
+ }
198
+
199
+ return Object.freeze({
200
+ add(value) {
201
+ if (typeof value === 'string' && value.length > 0) registered.add(value);
202
+ return value;
203
+ },
204
+ count() { return registered.size; },
205
+ redactString,
206
+ redactValue(value) { return redactValue(value); },
207
+ redactError(error) { return redactError(error); },
208
+ values() { return Object.freeze([...registered].map(() => REDACTED_VALUE)); },
209
+ summary() { return Object.freeze({ registeredSecretCount: registered.size }); }
210
+ });
211
+ }
212
+
213
+ // Compatibility aliases for the earlier internal helper name.
214
+ function createRedactionRegistry(initialValues) {
215
+ const state = createRedactionState(initialValues);
216
+ return Object.freeze({
217
+ add: state.add,
218
+ redact: state.redactValue,
219
+ redactString: state.redactString,
220
+ summary: state.summary
221
+ });
222
+ }
223
+
224
+ module.exports = Object.freeze({
225
+ REDACTED_VALUE,
226
+ BOUNDED_VALUE,
227
+ SENSITIVE_KEYS,
228
+ createRedactionRegistry,
229
+ createRedactionState,
230
+ isSensitiveKey
231
+ });