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

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,597 @@
1
+ 'use strict';
2
+
3
+ const { PulseRuntimeContractError } = require('./errors.js');
4
+ const {
5
+ decodeSchemaText,
6
+ encodeSchemaValue,
7
+ requireExplicitSchemaId
8
+ } = require('./schema.js');
9
+
10
+ const PACKAGE_RUNTIME_BRIDGE_VERSION = 'pulse.package-runtime-bridge.v1';
11
+ const PACKAGE_SCHEMA_CODEC_BRIDGE_VERSION = 'pulse.first-party-embedded-schema-codec-bridge.v1';
12
+ const DEFAULT_MAX_PAYLOAD_BYTES = 256 * 1024;
13
+ const DEFAULT_MAX_PAYLOAD_DEPTH = 32;
14
+ const DEFAULT_MAX_PAYLOAD_ENTRIES = 4096;
15
+
16
+ const PACKAGE_CONTEXTS = new WeakMap();
17
+
18
+ const TRUSTED_PACKAGE_EFFECT_CATALOG = Object.freeze({
19
+ '@pulse-compute/assets': Object.freeze({
20
+ contractId: 'pulse.assets',
21
+ providerKind: 'assets',
22
+ operations: Object.freeze({
23
+ lookup: Object.freeze({
24
+ kind: 'assets.lookup',
25
+ capability: 'assets.lookup',
26
+ result: 'asset'
27
+ })
28
+ })
29
+ }),
30
+ '@pulse-compute/grip': Object.freeze({
31
+ contractId: 'pulse.grip',
32
+ providerKind: 'grip',
33
+ operations: Object.freeze({
34
+ broadcast: Object.freeze({
35
+ kind: 'grip.broadcast',
36
+ capability: 'grip.broadcast',
37
+ result: 'ack'
38
+ })
39
+ })
40
+ }),
41
+ '@pulse-compute/jwt': Object.freeze({
42
+ contractId: 'pulse.jwt',
43
+ providerKind: 'jwt',
44
+ operations: Object.freeze({
45
+ verify: Object.freeze({
46
+ kind: 'jwt.verify',
47
+ capability: 'jwt.verify',
48
+ result: 'jwt-verification'
49
+ })
50
+ })
51
+ })
52
+ });
53
+
54
+ const trustedPackageSchemaOwners = Object.freeze({
55
+ '@pulse-compute/entities': Object.freeze({
56
+ contractId: 'pulse.entities'
57
+ })
58
+ });
59
+
60
+ function contractError(code, message, detail) {
61
+ return new PulseRuntimeContractError(code, message, detail === undefined ? {} : { detail });
62
+ }
63
+
64
+ function requireObject(label, value) {
65
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
66
+ throw contractError(
67
+ 'PULSE_RUNTIME_PACKAGE_EFFECT_DEFINITION_INVALID',
68
+ `Pulse ${label} must be an ordinary object.`
69
+ );
70
+ }
71
+ const prototype = Object.getPrototypeOf(value);
72
+ if (prototype !== Object.prototype && prototype !== null) {
73
+ throw contractError(
74
+ 'PULSE_RUNTIME_PACKAGE_EFFECT_DEFINITION_INVALID',
75
+ `Pulse ${label} must use an ordinary object prototype.`
76
+ );
77
+ }
78
+ return value;
79
+ }
80
+
81
+ function enumerableDataEntries(value, label) {
82
+ requireObject(label, value);
83
+ const entries = [];
84
+ for (const key of Reflect.ownKeys(value)) {
85
+ if (typeof key !== 'string') {
86
+ throw contractError(
87
+ 'PULSE_RUNTIME_PACKAGE_EFFECT_DEFINITION_INVALID',
88
+ `Pulse ${label} may not contain symbol keys.`
89
+ );
90
+ }
91
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
92
+ if (!descriptor || !descriptor.enumerable) continue;
93
+ if (!Object.prototype.hasOwnProperty.call(descriptor, 'value')) {
94
+ throw contractError(
95
+ 'PULSE_RUNTIME_PACKAGE_EFFECT_DEFINITION_INVALID',
96
+ `Pulse ${label} may only contain enumerable data properties.`
97
+ );
98
+ }
99
+ entries.push([key, descriptor.value]);
100
+ }
101
+ return entries;
102
+ }
103
+
104
+ function definePayloadProperty(target, key, value) {
105
+ Object.defineProperty(target, key, {
106
+ enumerable: true,
107
+ configurable: false,
108
+ writable: false,
109
+ value
110
+ });
111
+ }
112
+
113
+ function clonePayloadValue(value, state, path, depth) {
114
+ if (depth > state.maxDepth) {
115
+ throw contractError(
116
+ 'PULSE_RUNTIME_PACKAGE_EFFECT_PAYLOAD_INVALID',
117
+ `Pulse package effect payload exceeds the maximum depth of ${state.maxDepth}.`,
118
+ Object.freeze({ path })
119
+ );
120
+ }
121
+ if (value === null || typeof value === 'string' || typeof value === 'boolean') return value;
122
+ if (typeof value === 'number') {
123
+ if (!Number.isFinite(value)) {
124
+ throw contractError(
125
+ 'PULSE_RUNTIME_PACKAGE_EFFECT_PAYLOAD_INVALID',
126
+ 'Pulse package effect payload numbers must be finite.',
127
+ Object.freeze({ path })
128
+ );
129
+ }
130
+ return Object.is(value, -0) ? 0 : value;
131
+ }
132
+ if (value === undefined || typeof value === 'function' || typeof value === 'symbol' || typeof value === 'bigint') {
133
+ throw contractError(
134
+ 'PULSE_RUNTIME_PACKAGE_EFFECT_PAYLOAD_INVALID',
135
+ `Pulse package effect payload contains unsupported ${typeof value} data.`,
136
+ Object.freeze({ path })
137
+ );
138
+ }
139
+ if (!value || typeof value !== 'object') {
140
+ throw contractError(
141
+ 'PULSE_RUNTIME_PACKAGE_EFFECT_PAYLOAD_INVALID',
142
+ 'Pulse package effect payload contains unsupported data.',
143
+ Object.freeze({ path })
144
+ );
145
+ }
146
+ if (state.seen.has(value)) {
147
+ throw contractError(
148
+ 'PULSE_RUNTIME_PACKAGE_EFFECT_PAYLOAD_INVALID',
149
+ 'Pulse package effect payload may not contain cycles or repeated object references.',
150
+ Object.freeze({ path })
151
+ );
152
+ }
153
+ state.seen.add(value);
154
+
155
+ if (Array.isArray(value)) {
156
+ const allowed = new Set(['length']);
157
+ for (let index = 0; index < value.length; index += 1) allowed.add(String(index));
158
+ for (const key of Reflect.ownKeys(value)) {
159
+ if (typeof key !== 'string' || !allowed.has(key)) {
160
+ throw contractError(
161
+ 'PULSE_RUNTIME_PACKAGE_EFFECT_PAYLOAD_INVALID',
162
+ 'Pulse package effect payload arrays may only contain indexed data properties.',
163
+ Object.freeze({ path, key: String(key) })
164
+ );
165
+ }
166
+ }
167
+ const output = [];
168
+ for (let index = 0; index < value.length; index += 1) {
169
+ const key = String(index);
170
+ if (!Object.prototype.hasOwnProperty.call(value, key)) {
171
+ throw contractError(
172
+ 'PULSE_RUNTIME_PACKAGE_EFFECT_PAYLOAD_INVALID',
173
+ 'Pulse package effect payload arrays may not be sparse.',
174
+ Object.freeze({ path: `${path}[${index}]` })
175
+ );
176
+ }
177
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
178
+ if (!descriptor || !descriptor.enumerable || !Object.prototype.hasOwnProperty.call(descriptor, 'value')) {
179
+ throw contractError(
180
+ 'PULSE_RUNTIME_PACKAGE_EFFECT_PAYLOAD_INVALID',
181
+ 'Pulse package effect payload arrays may only contain enumerable data properties.',
182
+ Object.freeze({ path: `${path}[${index}]` })
183
+ );
184
+ }
185
+ state.entries += 1;
186
+ if (state.entries > state.maxEntries) {
187
+ throw contractError(
188
+ 'PULSE_RUNTIME_PACKAGE_EFFECT_PAYLOAD_INVALID',
189
+ `Pulse package effect payload exceeds the maximum of ${state.maxEntries} entries.`
190
+ );
191
+ }
192
+ definePayloadProperty(
193
+ output,
194
+ key,
195
+ clonePayloadValue(descriptor.value, state, `${path}[${index}]`, depth + 1)
196
+ );
197
+ }
198
+ Object.defineProperty(output, 'length', { writable: false });
199
+ return Object.freeze(output);
200
+ }
201
+
202
+ const prototype = Object.getPrototypeOf(value);
203
+ if (prototype !== Object.prototype && prototype !== null) {
204
+ throw contractError(
205
+ 'PULSE_RUNTIME_PACKAGE_EFFECT_PAYLOAD_INVALID',
206
+ 'Pulse package effect payload objects must use an ordinary object prototype.',
207
+ Object.freeze({ path })
208
+ );
209
+ }
210
+ const output = {};
211
+ for (const key of Reflect.ownKeys(value)) {
212
+ if (typeof key !== 'string') {
213
+ throw contractError(
214
+ 'PULSE_RUNTIME_PACKAGE_EFFECT_PAYLOAD_INVALID',
215
+ 'Pulse package effect payload may not contain symbol keys.',
216
+ Object.freeze({ path })
217
+ );
218
+ }
219
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
220
+ if (!descriptor || !descriptor.enumerable || !Object.prototype.hasOwnProperty.call(descriptor, 'value')) {
221
+ throw contractError(
222
+ 'PULSE_RUNTIME_PACKAGE_EFFECT_PAYLOAD_INVALID',
223
+ 'Pulse package effect payload may only contain enumerable data properties.',
224
+ Object.freeze({ path: `${path}.${key}` })
225
+ );
226
+ }
227
+ state.entries += 1;
228
+ if (state.entries > state.maxEntries) {
229
+ throw contractError(
230
+ 'PULSE_RUNTIME_PACKAGE_EFFECT_PAYLOAD_INVALID',
231
+ `Pulse package effect payload exceeds the maximum of ${state.maxEntries} entries.`
232
+ );
233
+ }
234
+ definePayloadProperty(
235
+ output,
236
+ key,
237
+ clonePayloadValue(descriptor.value, state, `${path}.${key}`, depth + 1)
238
+ );
239
+ }
240
+ return Object.freeze(output);
241
+ }
242
+
243
+ function clonePackageEffectPayload(payload, limits = {}) {
244
+ if (payload === undefined) return Object.freeze({});
245
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
246
+ throw contractError(
247
+ 'PULSE_RUNTIME_PACKAGE_EFFECT_PAYLOAD_INVALID',
248
+ 'Pulse package effect payload must be an ordinary object.'
249
+ );
250
+ }
251
+ const state = {
252
+ seen: new Set(),
253
+ entries: 0,
254
+ maxDepth: limits.maxDepth === undefined ? DEFAULT_MAX_PAYLOAD_DEPTH : Number(limits.maxDepth),
255
+ maxEntries: limits.maxEntries === undefined ? DEFAULT_MAX_PAYLOAD_ENTRIES : Number(limits.maxEntries)
256
+ };
257
+ if (!Number.isSafeInteger(state.maxDepth) || state.maxDepth <= 0) throw new TypeError('Pulse package payload maxDepth must be a positive safe integer.');
258
+ if (!Number.isSafeInteger(state.maxEntries) || state.maxEntries <= 0) throw new TypeError('Pulse package payload maxEntries must be a positive safe integer.');
259
+ const cloned = clonePayloadValue(payload, state, '$', 0);
260
+ const maxBytes = limits.maxBytes === undefined ? DEFAULT_MAX_PAYLOAD_BYTES : Number(limits.maxBytes);
261
+ if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) throw new TypeError('Pulse package payload maxBytes must be a positive safe integer.');
262
+ const bytes = new TextEncoder().encode(JSON.stringify(cloned)).byteLength;
263
+ if (bytes > maxBytes) {
264
+ throw contractError(
265
+ 'PULSE_RUNTIME_PACKAGE_EFFECT_PAYLOAD_INVALID',
266
+ `Pulse package effect payload exceeds the maximum of ${maxBytes} UTF-8 bytes.`,
267
+ Object.freeze({ bytes, maxBytes })
268
+ );
269
+ }
270
+ return cloned;
271
+ }
272
+
273
+ function packageRequestBodyUnavailable() {
274
+ return Promise.reject(contractError(
275
+ 'PULSE_RUNTIME_PACKAGE_REQUEST_BODY_UNAVAILABLE',
276
+ 'Trusted Pulse package request views do not expose the application request body; use ctx.req projections through the owning application contract.'
277
+ ));
278
+ }
279
+
280
+ function createPackageRequestView(request, signal) {
281
+ const lifecycleSignal = signal || request.signal;
282
+ const view = new Request(request.url, {
283
+ method: request.method,
284
+ headers: request.headers,
285
+ signal: lifecycleSignal
286
+ });
287
+ for (const method of ['arrayBuffer', 'blob', 'bytes', 'formData', 'json', 'text']) {
288
+ Object.defineProperty(view, method, {
289
+ enumerable: false,
290
+ configurable: false,
291
+ writable: false,
292
+ value: packageRequestBodyUnavailable
293
+ });
294
+ }
295
+ Object.defineProperty(view, 'clone', {
296
+ enumerable: false,
297
+ configurable: false,
298
+ writable: false,
299
+ value: () => createPackageRequestView(request, lifecycleSignal)
300
+ });
301
+ return view;
302
+ }
303
+
304
+ function bindPackageRuntimeContext(context, frame) {
305
+ if (!context || (typeof context !== 'object' && typeof context !== 'function')) {
306
+ throw new TypeError('Pulse package runtime context binding requires a context object.');
307
+ }
308
+ if (!frame || typeof frame !== 'object' || !frame.effectExecution || !(frame.request instanceof Request)) {
309
+ throw new TypeError('Pulse package runtime context binding requires the request frame and effect execution.');
310
+ }
311
+ const pathname = new URL(frame.request.url).pathname || '/';
312
+ const relativePath = frame.packageRelativePath == null
313
+ ? (frame.relativePath == null ? pathname : frame.relativePath)
314
+ : frame.packageRelativePath;
315
+ const lifecycleSignal = frame.signal || frame.request.signal;
316
+ const publicView = Object.freeze({
317
+ request: createPackageRequestView(frame.request, lifecycleSignal),
318
+ params: frame.params || Object.freeze({}),
319
+ path: Object.freeze({
320
+ absolute: pathname,
321
+ relative: String(relativePath)
322
+ }),
323
+ signal: lifecycleSignal
324
+ });
325
+ const executionOptions = frame.executionOptions || {};
326
+ PACKAGE_CONTEXTS.set(context, Object.freeze({
327
+ effectExecution: frame.effectExecution,
328
+ publicView,
329
+ requestHeaders: frame.req && Array.isArray(frame.req.headers) ? frame.req.headers : Object.freeze([]),
330
+ schemaCodecs: executionOptions.schemaCodecs,
331
+ target: executionOptions.target,
332
+ provider: executionOptions.provider,
333
+ onJsonTrace: executionOptions.onJsonTrace,
334
+ redactJsonTraceValue: executionOptions.redactJsonTraceValue
335
+ }));
336
+ return context;
337
+ }
338
+
339
+ function packageContextData(context) {
340
+ if (!context || (typeof context !== 'object' && typeof context !== 'function')) {
341
+ throw contractError(
342
+ 'PULSE_RUNTIME_PACKAGE_CONTEXT_REQUIRED',
343
+ 'Trusted Pulse package operations require the current request context.'
344
+ );
345
+ }
346
+ const data = PACKAGE_CONTEXTS.get(context);
347
+ if (!data) {
348
+ throw contractError(
349
+ 'PULSE_RUNTIME_PACKAGE_CONTEXT_REQUIRED',
350
+ 'The supplied context is not owned by an active managed Pulse request.'
351
+ );
352
+ }
353
+ return data;
354
+ }
355
+
356
+ function assertPackageContextActive(data) {
357
+ const summary = data.effectExecution.summary();
358
+ if (summary.closed) {
359
+ throw contractError(
360
+ 'PULSE_RUNTIME_EFFECT_EXECUTION_CLOSED',
361
+ 'Pulse package schema codecs are unavailable after their managed request has closed.'
362
+ );
363
+ }
364
+ }
365
+
366
+ function exactDataObject(label, value, allowedKeys) {
367
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
368
+ throw contractError(
369
+ 'PULSE_RUNTIME_PACKAGE_SCHEMA_BRIDGE_INVALID',
370
+ `Pulse ${label} must be an ordinary object.`
371
+ );
372
+ }
373
+ const prototype = Object.getPrototypeOf(value);
374
+ if (prototype !== Object.prototype && prototype !== null) {
375
+ throw contractError(
376
+ 'PULSE_RUNTIME_PACKAGE_SCHEMA_BRIDGE_INVALID',
377
+ `Pulse ${label} must use an ordinary object prototype.`
378
+ );
379
+ }
380
+ const entries = [];
381
+ for (const key of Reflect.ownKeys(value)) {
382
+ if (typeof key !== 'string') {
383
+ throw contractError(
384
+ 'PULSE_RUNTIME_PACKAGE_SCHEMA_BRIDGE_INVALID',
385
+ `Pulse ${label} must not contain symbol keys.`
386
+ );
387
+ }
388
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
389
+ if (!descriptor || !descriptor.enumerable) continue;
390
+ if (!Object.prototype.hasOwnProperty.call(descriptor, 'value')) {
391
+ throw contractError(
392
+ 'PULSE_RUNTIME_PACKAGE_SCHEMA_BRIDGE_INVALID',
393
+ `Pulse ${label} may only contain enumerable data properties.`
394
+ );
395
+ }
396
+ entries.push([key, descriptor.value]);
397
+ }
398
+ const unknown = entries.map(([key]) => key).filter((key) => !allowedKeys.includes(key)).sort();
399
+ if (unknown.length > 0) {
400
+ throw contractError(
401
+ 'PULSE_RUNTIME_PACKAGE_SCHEMA_BRIDGE_INVALID',
402
+ `Pulse ${label} contains unsupported fields: ${unknown.join(', ')}.`,
403
+ Object.freeze({ unknown })
404
+ );
405
+ }
406
+ return Object.fromEntries(entries);
407
+ }
408
+
409
+ function normalizePackageSchemaRuntimeDefinition(input) {
410
+ const definition = exactDataObject('package schema runtime definition', input, ['package', 'contractId']);
411
+ const packageName = typeof definition.package === 'string' ? definition.package : '';
412
+ const contractId = typeof definition.contractId === 'string' ? definition.contractId : '';
413
+ const trusted = trustedPackageSchemaOwners[packageName];
414
+ if (!trusted || trusted.contractId !== contractId) {
415
+ throw contractError(
416
+ 'PULSE_RUNTIME_PACKAGE_SCHEMA_BRIDGE_UNTRUSTED',
417
+ `Pulse package schema runtime ${JSON.stringify(packageName || contractId || '<unknown>')} is not a fixed first-party schema owner.`,
418
+ Object.freeze({ package: packageName, contractId })
419
+ );
420
+ }
421
+ return Object.freeze({ packageName, contractId });
422
+ }
423
+
424
+ function normalizePackageSchemaDeclaration(input) {
425
+ const declaration = exactDataObject('package schema declaration', input, ['input', 'output']);
426
+ const normalize = (role) => {
427
+ const value = declaration[role];
428
+ if (value === null) return null;
429
+ if (typeof value !== 'string' || value.length === 0) {
430
+ throw contractError(
431
+ 'PULSE_RUNTIME_PACKAGE_SCHEMA_BRIDGE_INVALID',
432
+ `Pulse package ${role} schema must be a non-empty declared schema ID or null.`,
433
+ Object.freeze({ role, schemaIdType: typeof value })
434
+ );
435
+ }
436
+ return value;
437
+ };
438
+ return Object.freeze({ input: normalize('input'), output: normalize('output') });
439
+ }
440
+
441
+ function privateSchemaOptions(data) {
442
+ return Object.freeze({
443
+ schemaCodecs: data.schemaCodecs,
444
+ strict: true,
445
+ target: typeof data.target === 'string' ? data.target : undefined,
446
+ provider: typeof data.provider === 'string' ? data.provider : undefined,
447
+ onJsonTrace: data.onJsonTrace,
448
+ redactJsonTraceValue: data.redactJsonTraceValue
449
+ });
450
+ }
451
+
452
+ function assertDeclaredSchema(role, declaredSchemaId, schemaId) {
453
+ if (typeof schemaId !== 'string' || declaredSchemaId === null || schemaId !== declaredSchemaId) {
454
+ throw contractError(
455
+ 'PULSE_RUNTIME_PACKAGE_SCHEMA_NOT_DECLARED',
456
+ `Pulse package schema bridge may use only its declared ${role} schema.`,
457
+ Object.freeze({ role, schemaId: typeof schemaId === 'string' ? schemaId : null })
458
+ );
459
+ }
460
+ return schemaId;
461
+ }
462
+
463
+ function createPackageSchemaCodecRuntime(input) {
464
+ const definition = normalizePackageSchemaRuntimeDefinition(input);
465
+ return Object.freeze({
466
+ version: PACKAGE_SCHEMA_CODEC_BRIDGE_VERSION,
467
+ package: definition.packageName,
468
+ contractId: definition.contractId,
469
+ bind(context, declarationInput) {
470
+ const data = packageContextData(context);
471
+ assertPackageContextActive(data);
472
+ const declaration = normalizePackageSchemaDeclaration(declarationInput);
473
+ const schemaOptions = privateSchemaOptions(data);
474
+ if (declaration.input !== null) requireExplicitSchemaId(declaration.input, schemaOptions, 'request');
475
+ if (declaration.output !== null) requireExplicitSchemaId(declaration.output, schemaOptions, 'application-response');
476
+ return Object.freeze({
477
+ decodeEmbeddedJson(schemaId, packageOwnedText) {
478
+ assertPackageContextActive(data);
479
+ const id = assertDeclaredSchema('input', declaration.input, schemaId);
480
+ if (typeof packageOwnedText !== 'string') {
481
+ throw contractError(
482
+ 'PULSE_RUNTIME_PACKAGE_SCHEMA_TEXT_REQUIRED',
483
+ 'Pulse package schema decode requires a package-owned JSON text slice.'
484
+ );
485
+ }
486
+ return decodeSchemaText(id, packageOwnedText, schemaOptions, {
487
+ source: 'request',
488
+ headers: data.requestHeaders,
489
+ operationId: `entities.decode:${id}`
490
+ });
491
+ },
492
+ encodeEmbeddedJson(schemaId, packageOwnedValue) {
493
+ assertPackageContextActive(data);
494
+ const id = assertDeclaredSchema('output', declaration.output, schemaId);
495
+ const encoded = encodeSchemaValue(id, packageOwnedValue, schemaOptions, {
496
+ source: 'application-response',
497
+ operationId: `entities.encode:${id}`
498
+ });
499
+ if (typeof encoded.text !== 'string') {
500
+ throw contractError(
501
+ 'PULSE_RUNTIME_PACKAGE_SCHEMA_TEXT_REQUIRED',
502
+ 'Pulse package schema encode must produce package-owned JSON text.'
503
+ );
504
+ }
505
+ return encoded.text;
506
+ }
507
+ });
508
+ }
509
+ });
510
+ }
511
+
512
+ function normalizePackageDefinition(input) {
513
+ requireObject('package runtime definition', input);
514
+ const packageName = String(input.package || '');
515
+ const contractId = String(input.contractId || '');
516
+ const providerKind = String(input.providerKind || '');
517
+ const catalog = TRUSTED_PACKAGE_EFFECT_CATALOG[packageName];
518
+ if (!catalog || catalog.contractId !== contractId || catalog.providerKind !== providerKind) {
519
+ throw contractError(
520
+ 'PULSE_RUNTIME_PACKAGE_EFFECT_UNTRUSTED',
521
+ `Pulse package runtime ${JSON.stringify(packageName || contractId || '<unknown>')} is not in the fixed first-party package effect catalog.`,
522
+ Object.freeze({ package: packageName, contractId, providerKind })
523
+ );
524
+ }
525
+ const operations = {};
526
+ for (const [operation, declared] of enumerableDataEntries(input.operations, 'package effect operations')) {
527
+ const expected = catalog.operations[operation];
528
+ requireObject(`package effect operation ${JSON.stringify(operation)}`, declared);
529
+ if (!expected
530
+ || String(declared.kind || '') !== expected.kind
531
+ || String(declared.capability || '') !== expected.capability
532
+ || String(declared.result || '') !== expected.result) {
533
+ throw contractError(
534
+ 'PULSE_RUNTIME_PACKAGE_EFFECT_DEFINITION_INVALID',
535
+ `Pulse package operation ${JSON.stringify(operation)} does not match the fixed first-party effect catalog.`,
536
+ Object.freeze({ package: packageName, operation })
537
+ );
538
+ }
539
+ operations[operation] = expected;
540
+ }
541
+ if (Object.keys(operations).length === 0) {
542
+ throw contractError(
543
+ 'PULSE_RUNTIME_PACKAGE_EFFECT_DEFINITION_INVALID',
544
+ 'Pulse package runtime definitions must declare at least one trusted operation.'
545
+ );
546
+ }
547
+ return Object.freeze({ packageName, contractId, providerKind, operations: Object.freeze(operations) });
548
+ }
549
+
550
+ function createPackageRuntime(input) {
551
+ const definition = normalizePackageDefinition(input);
552
+ return Object.freeze({
553
+ version: PACKAGE_RUNTIME_BRIDGE_VERSION,
554
+ package: definition.packageName,
555
+ contractId: definition.contractId,
556
+ providerKind: definition.providerKind,
557
+ context(context) {
558
+ return packageContextData(context).publicView;
559
+ },
560
+ effect(context, operationInput, payload, projector) {
561
+ const operation = String(operationInput || '');
562
+ const declared = definition.operations[operation];
563
+ if (!declared) {
564
+ throw contractError(
565
+ 'PULSE_RUNTIME_PACKAGE_EFFECT_OPERATION_UNSUPPORTED',
566
+ `Pulse package ${definition.packageName} does not declare effect operation ${JSON.stringify(operation)}.`,
567
+ Object.freeze({ package: definition.packageName, operation })
568
+ );
569
+ }
570
+ if (projector !== undefined && typeof projector !== 'function') {
571
+ throw new TypeError('Pulse package effect projector must be a function when supplied.');
572
+ }
573
+ const data = packageContextData(context);
574
+ return data.effectExecution.dispatch({
575
+ package: definition.packageName,
576
+ contractId: definition.contractId,
577
+ kind: declared.kind,
578
+ providerKind: definition.providerKind,
579
+ operation,
580
+ capability: declared.capability,
581
+ result: declared.result,
582
+ payload: clonePackageEffectPayload(payload)
583
+ }, projector);
584
+ }
585
+ });
586
+ }
587
+
588
+ module.exports = Object.freeze({
589
+ PACKAGE_RUNTIME_BRIDGE_VERSION,
590
+ PACKAGE_SCHEMA_CODEC_BRIDGE_VERSION,
591
+ TRUSTED_PACKAGE_EFFECT_CATALOG,
592
+ bindPackageRuntimeContext,
593
+ clonePackageEffectPayload,
594
+ createPackageRequestView,
595
+ createPackageRuntime,
596
+ createPackageSchemaCodecRuntime
597
+ });