@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.
- package/README.md +126 -2
- package/docs/API.md +572 -0
- package/docs/guides/routing.md +202 -0
- package/docs/preview-scope.md +142 -0
- package/package.json +38 -5
- package/src/host.d.ts +420 -0
- package/src/host.js +115 -0
- package/src/index.d.ts +215 -0
- package/src/index.js +11 -0
- package/src/internal/bindings.js +281 -0
- package/src/internal/body.js +254 -0
- package/src/internal/conditional-kv.js +199 -0
- package/src/internal/context.js +275 -0
- package/src/internal/effect-adapter.js +877 -0
- package/src/internal/errors.js +27 -0
- package/src/internal/event-emission.js +182 -0
- package/src/internal/event-execution.js +496 -0
- package/src/internal/fetch.js +221 -0
- package/src/internal/index.js +24 -0
- package/src/internal/logging.js +65 -0
- package/src/internal/package-runtime.js +604 -0
- package/src/internal/path.js +130 -0
- package/src/internal/redaction.js +231 -0
- package/src/internal/response.js +415 -0
- package/src/internal/router.js +487 -0
- package/src/internal/schema.js +340 -0
- package/src/package.d.ts +127 -0
- package/src/package.js +19 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
class PulseRuntimeContractError extends Error {
|
|
4
|
+
constructor(code, message, options = {}) {
|
|
5
|
+
super(message, options.cause === undefined ? undefined : { cause: options.cause });
|
|
6
|
+
this.name = 'PulseRuntimeContractError';
|
|
7
|
+
this.code = code;
|
|
8
|
+
if (options.detail !== undefined) {
|
|
9
|
+
Object.defineProperty(this, 'detail', {
|
|
10
|
+
enumerable: false,
|
|
11
|
+
configurable: false,
|
|
12
|
+
writable: false,
|
|
13
|
+
value: options.detail
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
class PulseUnhandledError extends Error {
|
|
20
|
+
constructor(cause) {
|
|
21
|
+
super('Pulse contained an unexpected handler or runtime failure.', { cause });
|
|
22
|
+
this.name = 'PulseUnhandledError';
|
|
23
|
+
this.code = 'PULSE_RUNTIME_UNHANDLED_ERROR';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = Object.freeze({ PulseRuntimeContractError, PulseUnhandledError });
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { PulseRuntimeContractError } = require('./errors.js');
|
|
4
|
+
const { validateSchemaValue } = require('./schema.js');
|
|
5
|
+
|
|
6
|
+
const EVENT_ADAPTER_VERSION = 'pulse.event-adapter.v1';
|
|
7
|
+
const EVENT_ADAPTER_SEMANTICS = Object.freeze({
|
|
8
|
+
acceptance: 'host-accepted',
|
|
9
|
+
deliveryGuarantee: 'none',
|
|
10
|
+
autoLoopback: false,
|
|
11
|
+
sameStackReentry: false,
|
|
12
|
+
queueDiscipline: 'fifo-per-declared-adapter',
|
|
13
|
+
executionIsolation: 'one-event-frame-per-execution'
|
|
14
|
+
});
|
|
15
|
+
const DEFAULT_MAX_QUEUE_DEPTH = 1024;
|
|
16
|
+
const MAX_QUEUE_DEPTH = 65_536;
|
|
17
|
+
|
|
18
|
+
const EVENT_EMIT_CODES = Object.freeze({
|
|
19
|
+
INPUT_INVALID: 'PULSE_RUNTIME_EVENT_EMIT_INPUT_INVALID',
|
|
20
|
+
SCHEMA_INVALID: 'PULSE_RUNTIME_EVENT_EMIT_SCHEMA_INVALID',
|
|
21
|
+
ACCEPTANCE_INVALID: 'PULSE_RUNTIME_EVENT_EMIT_ACCEPTANCE_INVALID',
|
|
22
|
+
QUEUE_FULL: 'PULSE_RUNTIME_EVENT_EMIT_QUEUE_FULL',
|
|
23
|
+
ADAPTER_INVALID: 'PULSE_RUNTIME_EVENT_ADAPTER_INVALID'
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
function ownDataValue(value, key) {
|
|
27
|
+
if (!value || (typeof value !== 'object' && typeof value !== 'function')) return undefined;
|
|
28
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
29
|
+
return descriptor && Object.prototype.hasOwnProperty.call(descriptor, 'value')
|
|
30
|
+
? descriptor.value
|
|
31
|
+
: undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function requireDataObject(value, label, code = EVENT_EMIT_CODES.INPUT_INVALID) {
|
|
35
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
36
|
+
throw new PulseRuntimeContractError(code, `Pulse ${label} must be a plain data object.`);
|
|
37
|
+
}
|
|
38
|
+
const prototype = Object.getPrototypeOf(value);
|
|
39
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
40
|
+
throw new PulseRuntimeContractError(code, `Pulse ${label} must be a plain data object.`);
|
|
41
|
+
}
|
|
42
|
+
if (Object.getOwnPropertySymbols(value).length > 0) {
|
|
43
|
+
throw new PulseRuntimeContractError(code, `Pulse ${label} must not contain symbol keys.`);
|
|
44
|
+
}
|
|
45
|
+
for (const key of Object.keys(value)) {
|
|
46
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
47
|
+
if (!descriptor || !Object.prototype.hasOwnProperty.call(descriptor, 'value')) {
|
|
48
|
+
throw new PulseRuntimeContractError(code, `Pulse ${label}.${key} must be an own data property.`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function errorCode(error) {
|
|
55
|
+
const value = ownDataValue(error, 'code');
|
|
56
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function eventFrameNormalizer() {
|
|
60
|
+
// event-execution owns the canonical frame normalizer and imports context.
|
|
61
|
+
// Resolve it only after module initialization to avoid a CommonJS cycle.
|
|
62
|
+
return require('./event-execution.js').normalizeEventFrame;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function normalizeEventEmission(type, input, options = {}) {
|
|
66
|
+
requireDataObject(input, 'event emission');
|
|
67
|
+
const keys = Object.keys(input);
|
|
68
|
+
const unknown = keys.filter((key) => key !== 'schema' && key !== 'payload').sort();
|
|
69
|
+
if (unknown.length > 0 || !Object.prototype.hasOwnProperty.call(input, 'schema')) {
|
|
70
|
+
throw new PulseRuntimeContractError(
|
|
71
|
+
EVENT_EMIT_CODES.INPUT_INVALID,
|
|
72
|
+
'Pulse event emissions require exactly schema and the schema-dependent payload field.',
|
|
73
|
+
{ detail: Object.freeze({ unknown }) }
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
const schemaId = ownDataValue(input, 'schema');
|
|
77
|
+
const hasPayload = Object.prototype.hasOwnProperty.call(input, 'payload');
|
|
78
|
+
let frame = eventFrameNormalizer()({
|
|
79
|
+
version: 'pulse.event-frame.v1',
|
|
80
|
+
type,
|
|
81
|
+
schemaId,
|
|
82
|
+
...(hasPayload ? { payload: ownDataValue(input, 'payload') } : {})
|
|
83
|
+
}, options.eventLimits);
|
|
84
|
+
if (frame.schemaId === null) return frame;
|
|
85
|
+
|
|
86
|
+
let payload;
|
|
87
|
+
try {
|
|
88
|
+
payload = validateSchemaValue(frame.schemaId, frame.payload, {
|
|
89
|
+
schemaCodecs: options.schemaCodecs,
|
|
90
|
+
strict: true,
|
|
91
|
+
target: options.target,
|
|
92
|
+
provider: options.provider
|
|
93
|
+
}, {
|
|
94
|
+
source: 'event-emit',
|
|
95
|
+
operationId: `event.emit:${frame.type}`
|
|
96
|
+
});
|
|
97
|
+
} catch (error) {
|
|
98
|
+
throw new PulseRuntimeContractError(
|
|
99
|
+
EVENT_EMIT_CODES.SCHEMA_INVALID,
|
|
100
|
+
'Pulse outbound event payload failed its declared schema.',
|
|
101
|
+
{ detail: Object.freeze({
|
|
102
|
+
type: frame.type,
|
|
103
|
+
schemaId: frame.schemaId,
|
|
104
|
+
causeCode: errorCode(error) || null
|
|
105
|
+
}) }
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
frame = eventFrameNormalizer()({
|
|
109
|
+
version: frame.version,
|
|
110
|
+
type: frame.type,
|
|
111
|
+
schemaId: frame.schemaId,
|
|
112
|
+
payload
|
|
113
|
+
}, options.eventLimits);
|
|
114
|
+
return frame;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function normalizeQueueDepth(value) {
|
|
118
|
+
const depth = value === undefined ? DEFAULT_MAX_QUEUE_DEPTH : value;
|
|
119
|
+
if (typeof depth !== 'number' || !Number.isSafeInteger(depth) || depth <= 0 || depth > MAX_QUEUE_DEPTH) {
|
|
120
|
+
throw new PulseRuntimeContractError(
|
|
121
|
+
EVENT_EMIT_CODES.ADAPTER_INVALID,
|
|
122
|
+
`Pulse event recording adapter maxQueueDepth must be an integer from 1 through ${MAX_QUEUE_DEPTH}.`
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
return depth;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function createEventRecordingAdapter(options = {}) {
|
|
129
|
+
requireDataObject(options, 'event recording adapter options', EVENT_EMIT_CODES.ADAPTER_INVALID);
|
|
130
|
+
const unknown = Object.keys(options).filter((key) => key !== 'id' && key !== 'maxQueueDepth').sort();
|
|
131
|
+
if (unknown.length > 0) {
|
|
132
|
+
throw new PulseRuntimeContractError(
|
|
133
|
+
EVENT_EMIT_CODES.ADAPTER_INVALID,
|
|
134
|
+
`Pulse event recording adapter options contain unsupported fields: ${unknown.join(', ')}.`
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
const id = options.id === undefined ? 'pulse.event-adapter.recording' : options.id;
|
|
138
|
+
if (typeof id !== 'string' || !id || new TextEncoder().encode(id).byteLength > 128) {
|
|
139
|
+
throw new PulseRuntimeContractError(EVENT_EMIT_CODES.ADAPTER_INVALID, 'Pulse event recording adapter id must be a bounded non-empty string.');
|
|
140
|
+
}
|
|
141
|
+
const maxQueueDepth = normalizeQueueDepth(options.maxQueueDepth);
|
|
142
|
+
const accepted = [];
|
|
143
|
+
const eventAdapter = Object.freeze({
|
|
144
|
+
version: EVENT_ADAPTER_VERSION,
|
|
145
|
+
id,
|
|
146
|
+
maxQueueDepth,
|
|
147
|
+
semantics: EVENT_ADAPTER_SEMANTICS
|
|
148
|
+
});
|
|
149
|
+
return Object.freeze({
|
|
150
|
+
version: 'pulse.javascript-effect-adapter.v1',
|
|
151
|
+
id,
|
|
152
|
+
eventAdapter,
|
|
153
|
+
dispatch(effect) {
|
|
154
|
+
if (!effect || effect.kind !== 'event.emit' || effect.capability !== 'event.emit') {
|
|
155
|
+
throw new PulseRuntimeContractError(
|
|
156
|
+
'PULSE_RUNTIME_CAPABILITY_UNAVAILABLE',
|
|
157
|
+
`Pulse capability ${String(effect && effect.capability || '<unknown>')} is unavailable in the event recording adapter.`
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
if (accepted.length >= maxQueueDepth) {
|
|
161
|
+
throw new PulseRuntimeContractError(
|
|
162
|
+
EVENT_EMIT_CODES.QUEUE_FULL,
|
|
163
|
+
`Pulse event recording adapter ${JSON.stringify(id)} reached its bounded acceptance capacity.`
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
const frame = eventFrameNormalizer()(effect.frame);
|
|
167
|
+
accepted.push(frame);
|
|
168
|
+
return undefined;
|
|
169
|
+
},
|
|
170
|
+
acceptedFrames() {
|
|
171
|
+
return Object.freeze([...accepted]);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
module.exports = Object.freeze({
|
|
177
|
+
EVENT_ADAPTER_VERSION,
|
|
178
|
+
EVENT_ADAPTER_SEMANTICS,
|
|
179
|
+
EVENT_EMIT_CODES,
|
|
180
|
+
normalizeEventEmission,
|
|
181
|
+
createEventRecordingAdapter
|
|
182
|
+
});
|