@pulse-compute/wasm-contracts 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 +35 -2
- package/package.json +124 -6
- package/src/assets/contracts.js +296 -0
- package/src/config/provider-resolution.js +54 -0
- package/src/config/runtime-provider-kv.js +73 -0
- package/src/config/symbolic-refs.js +192 -0
- package/src/crypto/contracts.js +647 -0
- package/src/definitions/config-schema.js +30 -0
- package/src/definitions/handler-roles.js +66 -0
- package/src/definitions/path-grammar.js +11 -0
- package/src/definitions/router-api.js +149 -0
- package/src/diagnostics/codes.js +42 -0
- package/src/diagnostics/reporter.js +20 -0
- package/src/diagnostics.js +139 -0
- package/src/dispatch-table.js +23 -0
- package/src/entities/catalog.d.ts +7 -0
- package/src/entities/catalog.js +87 -0
- package/src/entities/contracts.d.ts +95 -0
- package/src/entities/contracts.js +122 -0
- package/src/entities/json-rpc.d.ts +27 -0
- package/src/entities/json-rpc.js +69 -0
- package/src/entities/runtime.d.ts +67 -0
- package/src/entities/runtime.js +288 -0
- package/src/events/contracts.js +456 -0
- package/src/execution-plan.js +42 -0
- package/src/grip/contracts.js +106 -0
- package/src/handler/backend-json-request-body.js +58 -0
- package/src/handler/beta-lifecycle-golden.js +97 -0
- package/src/handler/canonical-native-plan.js +143 -0
- package/src/handler/canonical-native-runtime.js +168 -0
- package/src/handler/canonical-runtime.js +13 -0
- package/src/handler/context.js +131 -0
- package/src/handler/ctx-contract.js +71 -0
- package/src/handler/doctor-readiness-aggregator.js +67 -0
- package/src/handler/effects.js +446 -0
- package/src/handler/io-lifecycle.js +134 -0
- package/src/handler/lifecycle-authoring-alignment.js +56 -0
- package/src/handler/lifecycle-readiness-aggregate.js +93 -0
- package/src/handler/reference-node-lifecycle-golden.js +73 -0
- package/src/handler/request-json-body.js +147 -0
- package/src/handler/runtime-api.js +471 -0
- package/src/handler/schema-decode-result.js +135 -0
- package/src/handler/schema-response-codec.js +141 -0
- package/src/handler/surface-contract.js +601 -0
- package/src/handler/types-contract.js +51 -0
- package/src/handler-table.js +28 -0
- package/src/host/backend-capabilities.js +24 -0
- package/src/host/capabilities.js +273 -0
- package/src/host/channel-broadcaster.js +60 -0
- package/src/host/json-body.js +80 -0
- package/src/host/request-result-headers.js +38 -0
- package/src/host/streaming-passthrough.js +90 -0
- package/src/host/wasm-host-abi.js +154 -0
- package/src/index.js +75 -0
- package/src/jwt/contracts.js +427 -0
- package/src/library/api-surface.js +99 -0
- package/src/library/contracts.js +273 -0
- package/src/library/manifest.js +317 -0
- package/src/library/second-package-owned-lowering.js +39 -0
- package/src/library/sidecars.js +147 -0
- package/src/logging.js +89 -0
- package/src/package/package-contract.js +975 -0
- package/src/package/package-contract.schema.json +303 -0
- package/src/package/runtime-pulse-live.js +100 -0
- package/src/path-table.js +12 -0
- package/src/path.js +275 -0
- package/src/project/config-factory.js +48 -0
- package/src/project/config-plan.js +409 -0
- package/src/project/config-runtime.js +46 -0
- package/src/project/javascript-application.js +212 -0
- package/src/project/javascript-core-capabilities.js +44 -0
- package/src/project/native-optimization.js +9 -0
- package/src/project/package-ownership.js +242 -0
- package/src/project/package-product-projection.js +171 -0
- package/src/project/reachable-graph-identity.js +219 -0
- package/src/project/reachable-graph-projections.js +279 -0
- package/src/project/reachable-graph-resolver.js +303 -0
- package/src/project/reachable-graph-v2.js +430 -0
- package/src/project/reachable-graph-v2.schema.json +285 -0
- package/src/project/reachable-graph.js +199 -0
- package/src/project/reachable-graph.schema.json +222 -0
- package/src/project/target-support-evidence.js +559 -0
- package/src/provider/canonical-provider.js +239 -0
- package/src/provider/final-wasm-policy.js +87 -0
- package/src/provider/toolchain.js +771 -0
- package/src/route-plan.js +43 -0
- package/src/s3/contracts.js +12 -0
- package/src/schema-json/compile.js +63 -0
- package/src/schema-json/contracts.js +10 -0
- package/src/schema-json/generic-parser.js +62 -0
- package/src/schema-json/registry.js +359 -0
- package/src/schema-json/semantic-trace.js +241 -0
- package/src/schema-json/v1.js +65 -0
- package/src/schema-json/v2.js +110 -0
- package/src/sha256.js +104 -0
- package/src/spine.js +381 -0
- package/src/stable-id.js +142 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const HANDLER_ROLE_VERSION = 'pulsewasm.handler-role-definitions.v1';
|
|
4
|
+
|
|
5
|
+
const HANDLER_ROLE_DEFINITIONS = Object.freeze([
|
|
6
|
+
Object.freeze({ role: 'route', params: 2, display: '(ctx, next) => PulseResult | void', asyncAllowed: false, generatorAllowed: false, promiseReturnAllowed: false }),
|
|
7
|
+
Object.freeze({ role: 'middleware', params: 2, display: '(ctx, next) => PulseResult | void', asyncAllowed: false, generatorAllowed: false, promiseReturnAllowed: false }),
|
|
8
|
+
Object.freeze({ role: 'lifecycle', params: 2, display: '(ctx, next) => void', asyncAllowed: false, generatorAllowed: false, promiseReturnAllowed: false }),
|
|
9
|
+
Object.freeze({ role: 'error', params: 3, display: '(err, ctx, next) => PulseResult | void', asyncAllowed: false, generatorAllowed: false, promiseReturnAllowed: false }),
|
|
10
|
+
Object.freeze({ role: 'channel', params: 1, display: '(ctx) => string | string[]', asyncAllowed: false, generatorAllowed: false, promiseReturnAllowed: false })
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
function buildHandlerRoleRegistry(definitions = HANDLER_ROLE_DEFINITIONS) {
|
|
14
|
+
const list = Array.from(definitions);
|
|
15
|
+
const byRole = new Map();
|
|
16
|
+
for (const definition of list) {
|
|
17
|
+
if (!definition || !definition.role) throw new Error('Handler role definition is missing a role.');
|
|
18
|
+
if (byRole.has(definition.role)) throw new Error(`Duplicate handler role definition: ${definition.role}`);
|
|
19
|
+
byRole.set(definition.role, definition);
|
|
20
|
+
}
|
|
21
|
+
return Object.freeze({
|
|
22
|
+
version: HANDLER_ROLE_VERSION,
|
|
23
|
+
definitions: Object.freeze(list),
|
|
24
|
+
byRole,
|
|
25
|
+
get(role) {
|
|
26
|
+
return byRole.get(role);
|
|
27
|
+
},
|
|
28
|
+
has(role) {
|
|
29
|
+
return byRole.has(role);
|
|
30
|
+
},
|
|
31
|
+
roleNames() {
|
|
32
|
+
return Array.from(byRole.keys());
|
|
33
|
+
},
|
|
34
|
+
asSignatureMap() {
|
|
35
|
+
const out = {};
|
|
36
|
+
for (const [role, definition] of byRole.entries()) {
|
|
37
|
+
out[role] = { params: definition.params, display: definition.display };
|
|
38
|
+
}
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const DEFAULT_HANDLER_ROLE_REGISTRY = buildHandlerRoleRegistry();
|
|
45
|
+
const ROLE_SIGNATURES = DEFAULT_HANDLER_ROLE_REGISTRY.asSignatureMap();
|
|
46
|
+
|
|
47
|
+
function roleForOperation(op) {
|
|
48
|
+
if (!op) return undefined;
|
|
49
|
+
if (op.route) return 'route';
|
|
50
|
+
if (op.kind === 'use') return 'middleware';
|
|
51
|
+
if (op.kind === 'get' || op.kind === 'post') return 'route';
|
|
52
|
+
if (op.kind === 'lifecycle') return 'lifecycle';
|
|
53
|
+
if (op.kind === 'error') return 'error';
|
|
54
|
+
if (op.kind === 'channel') return 'channel';
|
|
55
|
+
if (op.role) return op.role;
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = {
|
|
60
|
+
HANDLER_ROLE_VERSION,
|
|
61
|
+
HANDLER_ROLE_DEFINITIONS,
|
|
62
|
+
DEFAULT_HANDLER_ROLE_REGISTRY,
|
|
63
|
+
ROLE_SIGNATURES,
|
|
64
|
+
buildHandlerRoleRegistry,
|
|
65
|
+
roleForOperation
|
|
66
|
+
};
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const ROUTER_API_VERSION = 'pulsewasm.router-api-definitions.v1';
|
|
4
|
+
|
|
5
|
+
function createHttpRouteDefinition(name, method) {
|
|
6
|
+
if (!name || !method) throw new Error('createHttpRouteDefinition requires name and method.');
|
|
7
|
+
return Object.freeze({
|
|
8
|
+
name,
|
|
9
|
+
kind: 'route',
|
|
10
|
+
opKind: name,
|
|
11
|
+
method: String(method).toUpperCase(),
|
|
12
|
+
allowedArities: [2],
|
|
13
|
+
signatures: Object.freeze([
|
|
14
|
+
Object.freeze({ arity: 2, pathArg: 0, handlerArg: 1 })
|
|
15
|
+
]),
|
|
16
|
+
pathPolicy: 'required-static',
|
|
17
|
+
role: 'route'
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const ROUTER_API_DEFINITIONS = Object.freeze([
|
|
22
|
+
Object.freeze({
|
|
23
|
+
name: 'use',
|
|
24
|
+
kind: 'middleware',
|
|
25
|
+
opKind: 'use',
|
|
26
|
+
allowedArities: Object.freeze([1, 2]),
|
|
27
|
+
signatures: Object.freeze([
|
|
28
|
+
Object.freeze({ arity: 1, handlerArg: 0 }),
|
|
29
|
+
Object.freeze({ arity: 2, pathArg: 0, handlerArg: 1 })
|
|
30
|
+
]),
|
|
31
|
+
pathPolicy: 'optional-static',
|
|
32
|
+
role: 'middleware'
|
|
33
|
+
}),
|
|
34
|
+
createHttpRouteDefinition('get', 'GET'),
|
|
35
|
+
createHttpRouteDefinition('head', 'HEAD'),
|
|
36
|
+
createHttpRouteDefinition('post', 'POST'),
|
|
37
|
+
createHttpRouteDefinition('put', 'PUT'),
|
|
38
|
+
createHttpRouteDefinition('patch', 'PATCH'),
|
|
39
|
+
createHttpRouteDefinition('delete', 'DELETE'),
|
|
40
|
+
Object.freeze({
|
|
41
|
+
name: 'mount',
|
|
42
|
+
kind: 'mount',
|
|
43
|
+
opKind: 'mount',
|
|
44
|
+
allowedArities: Object.freeze([2]),
|
|
45
|
+
signatures: Object.freeze([
|
|
46
|
+
Object.freeze({ arity: 2, pathArg: 0, routerArg: 1 })
|
|
47
|
+
]),
|
|
48
|
+
pathPolicy: 'mount-static'
|
|
49
|
+
}),
|
|
50
|
+
Object.freeze({
|
|
51
|
+
name: 'channel',
|
|
52
|
+
kind: 'channel',
|
|
53
|
+
opKind: 'channel',
|
|
54
|
+
allowedArities: Object.freeze([1]),
|
|
55
|
+
signatures: Object.freeze([
|
|
56
|
+
Object.freeze({ arity: 1, valueArg: 0 })
|
|
57
|
+
]),
|
|
58
|
+
pathPolicy: 'none',
|
|
59
|
+
role: 'channel'
|
|
60
|
+
}),
|
|
61
|
+
Object.freeze({
|
|
62
|
+
name: 'on',
|
|
63
|
+
kind: 'lifecycle',
|
|
64
|
+
opKind: 'lifecycle',
|
|
65
|
+
allowedArities: Object.freeze([2]),
|
|
66
|
+
signatures: Object.freeze([
|
|
67
|
+
Object.freeze({ arity: 2, eventArg: 0, handlerArg: 1 })
|
|
68
|
+
]),
|
|
69
|
+
allowedEvents: Object.freeze(['connect', 'disconnect']),
|
|
70
|
+
pathPolicy: 'none',
|
|
71
|
+
role: 'lifecycle'
|
|
72
|
+
}),
|
|
73
|
+
Object.freeze({
|
|
74
|
+
name: 'error',
|
|
75
|
+
kind: 'error',
|
|
76
|
+
opKind: 'error',
|
|
77
|
+
allowedArities: Object.freeze([1]),
|
|
78
|
+
signatures: Object.freeze([
|
|
79
|
+
Object.freeze({ arity: 1, handlerArg: 0 })
|
|
80
|
+
]),
|
|
81
|
+
pathPolicy: 'none',
|
|
82
|
+
role: 'error'
|
|
83
|
+
}),
|
|
84
|
+
Object.freeze({
|
|
85
|
+
name: 'timeout',
|
|
86
|
+
kind: 'timeout',
|
|
87
|
+
opKind: 'timeout',
|
|
88
|
+
allowedArities: Object.freeze([1]),
|
|
89
|
+
signatures: Object.freeze([
|
|
90
|
+
Object.freeze({ arity: 1, valueArg: 0 })
|
|
91
|
+
]),
|
|
92
|
+
pathPolicy: 'none'
|
|
93
|
+
})
|
|
94
|
+
]);
|
|
95
|
+
|
|
96
|
+
function buildRouterApiRegistry(definitions = ROUTER_API_DEFINITIONS) {
|
|
97
|
+
const list = Array.from(definitions);
|
|
98
|
+
const byName = new Map();
|
|
99
|
+
for (const definition of list) {
|
|
100
|
+
if (!definition || !definition.name) throw new Error('Router API definition is missing a name.');
|
|
101
|
+
if (byName.has(definition.name)) throw new Error(`Duplicate router API definition: ${definition.name}`);
|
|
102
|
+
byName.set(definition.name, definition);
|
|
103
|
+
}
|
|
104
|
+
return Object.freeze({
|
|
105
|
+
version: ROUTER_API_VERSION,
|
|
106
|
+
definitions: Object.freeze(list),
|
|
107
|
+
byName,
|
|
108
|
+
supportedMethods: new Set(byName.keys()),
|
|
109
|
+
routeMethods: new Set(list.filter((definition) => definition.kind === 'route').map((definition) => definition.name)),
|
|
110
|
+
get(name) {
|
|
111
|
+
return byName.get(name);
|
|
112
|
+
},
|
|
113
|
+
has(name) {
|
|
114
|
+
return byName.has(name);
|
|
115
|
+
},
|
|
116
|
+
supportedMethodNames() {
|
|
117
|
+
return Array.from(byName.keys());
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const DEFAULT_ROUTER_API_REGISTRY = buildRouterApiRegistry();
|
|
123
|
+
const SUPPORTED_METHODS = DEFAULT_ROUTER_API_REGISTRY.supportedMethods;
|
|
124
|
+
const ROUTE_METHODS = DEFAULT_ROUTER_API_REGISTRY.routeMethods;
|
|
125
|
+
|
|
126
|
+
function signatureForArity(definition, arity) {
|
|
127
|
+
return (definition.signatures || []).find((signature) => signature.arity === arity);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function arityDisplay(definition) {
|
|
131
|
+
const allowed = Array.from(definition.allowedArities || []);
|
|
132
|
+
if (allowed.length === 0) return '0';
|
|
133
|
+
if (allowed.length === 1) return String(allowed[0]);
|
|
134
|
+
const min = Math.min(...allowed);
|
|
135
|
+
const max = Math.max(...allowed);
|
|
136
|
+
return allowed.length === max - min + 1 ? `${min}-${max}` : allowed.join(' or ');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
module.exports = {
|
|
140
|
+
ROUTER_API_VERSION,
|
|
141
|
+
ROUTER_API_DEFINITIONS,
|
|
142
|
+
DEFAULT_ROUTER_API_REGISTRY,
|
|
143
|
+
SUPPORTED_METHODS,
|
|
144
|
+
ROUTE_METHODS,
|
|
145
|
+
arityDisplay,
|
|
146
|
+
buildRouterApiRegistry,
|
|
147
|
+
createHttpRouteDefinition,
|
|
148
|
+
signatureForArity
|
|
149
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const DIAGNOSTIC_CODE_VERSION = 'pulsewasm.diagnostic-codes.v1';
|
|
4
|
+
|
|
5
|
+
const DIAGNOSTIC_CODES = Object.freeze({
|
|
6
|
+
UNSUPPORTED_ROUTER_METHOD: 'PULSEWASM_UNSUPPORTED_ROUTER_METHOD',
|
|
7
|
+
ARITY: 'PULSEWASM_ARITY',
|
|
8
|
+
DYNAMIC_PATH: 'PULSEWASM_DYNAMIC_PATH',
|
|
9
|
+
INVALID_PATH: 'PULSEWASM_INVALID_PATH',
|
|
10
|
+
UNSUPPORTED_HANDLER: 'PULSEWASM_UNSUPPORTED_HANDLER',
|
|
11
|
+
UNSUPPORTED_CHANNEL: 'PULSEWASM_UNSUPPORTED_CHANNEL',
|
|
12
|
+
UNSUPPORTED_LIFECYCLE: 'PULSEWASM_UNSUPPORTED_LIFECYCLE',
|
|
13
|
+
UNSUPPORTED_MOUNT_TARGET: 'PULSEWASM_UNSUPPORTED_MOUNT_TARGET',
|
|
14
|
+
COMPUTED_METHOD: 'PULSEWASM_COMPUTED_METHOD',
|
|
15
|
+
DYNAMIC_ROUTE_REGISTRATION: 'PULSEWASM_DYNAMIC_ROUTE_REGISTRATION',
|
|
16
|
+
ROUTER_REASSIGNMENT: 'PULSEWASM_ROUTER_REASSIGNMENT',
|
|
17
|
+
HANDLER_BINDING_UNKNOWN_HANDLER: 'PULSEWASM_HANDLER_BINDING_UNKNOWN_HANDLER',
|
|
18
|
+
HANDLER_BINDING_INLINE_UNSUPPORTED: 'PULSEWASM_HANDLER_BINDING_INLINE_UNSUPPORTED',
|
|
19
|
+
HANDLER_BINDING_NOT_EXPORTED: 'PULSEWASM_HANDLER_BINDING_NOT_EXPORTED',
|
|
20
|
+
HANDLER_BINDING_EXPORT_NAME_MISSING: 'PULSEWASM_HANDLER_BINDING_EXPORT_NAME_MISSING',
|
|
21
|
+
UNSUPPORTED_ROUTER_CONSTRUCTION: 'PULSEWASM_UNSUPPORTED_ROUTER_CONSTRUCTION',
|
|
22
|
+
GUEST_UNIT_INVALID: 'PULSE_GUEST_UNIT_INVALID',
|
|
23
|
+
GUEST_UNIT_HASH_MISMATCH: 'PULSE_GUEST_UNIT_HASH_MISMATCH',
|
|
24
|
+
GUEST_UNIT_OWNER_MISMATCH: 'PULSE_GUEST_UNIT_OWNER_MISMATCH',
|
|
25
|
+
GUEST_UNIT_ABI_MISMATCH: 'PULSE_GUEST_UNIT_ABI_MISMATCH',
|
|
26
|
+
GUEST_UNIT_IMPORT_MISMATCH: 'PULSE_GUEST_UNIT_IMPORT_MISMATCH',
|
|
27
|
+
GUEST_UNIT_EXPORT_MISMATCH: 'PULSE_GUEST_UNIT_EXPORT_MISMATCH',
|
|
28
|
+
GUEST_UNIT_MEMORY_MISMATCH: 'PULSE_GUEST_UNIT_MEMORY_MISMATCH',
|
|
29
|
+
GUEST_UNIT_START_MISMATCH: 'PULSE_GUEST_UNIT_START_MISMATCH',
|
|
30
|
+
GUEST_UNIT_FEATURE_MISMATCH: 'PULSE_GUEST_UNIT_FEATURE_MISMATCH',
|
|
31
|
+
GUEST_UNIT_MATERIALIZATION_FAILED: 'PULSE_GUEST_UNIT_MATERIALIZATION_FAILED',
|
|
32
|
+
GUEST_UNIT_CARDINALITY_UNSUPPORTED: 'PULSE_GUEST_UNIT_CARDINALITY_UNSUPPORTED',
|
|
33
|
+
GUEST_TARGET_POLICY_REQUIRED: 'PULSE_GUEST_TARGET_POLICY_REQUIRED',
|
|
34
|
+
GUEST_LINK_FAILED: 'PULSE_GUEST_LINK_FAILED',
|
|
35
|
+
GUEST_OPTIMIZATION_FAILED: 'PULSE_GUEST_OPTIMIZATION_FAILED',
|
|
36
|
+
GUEST_FINAL_AUDIT_FAILED: 'PULSE_GUEST_FINAL_AUDIT_FAILED'
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
module.exports = {
|
|
40
|
+
DIAGNOSTIC_CODE_VERSION,
|
|
41
|
+
DIAGNOSTIC_CODES
|
|
42
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const core = require('../diagnostics.js');
|
|
4
|
+
|
|
5
|
+
function createDiagnosticReporter(defaults = {}) {
|
|
6
|
+
return {
|
|
7
|
+
report(sourceFile, node, code, message, hint, options = {}) {
|
|
8
|
+
return core.diagnostic(sourceFile, node, code, message, hint, { ...defaults, ...options });
|
|
9
|
+
},
|
|
10
|
+
push(diagnostics, sourceFile, node, code, message, hint, options = {}) {
|
|
11
|
+
const diag = this.report(sourceFile, node, code, message, hint, options);
|
|
12
|
+
diagnostics.push(diag);
|
|
13
|
+
return diag;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = {
|
|
19
|
+
createDiagnosticReporter
|
|
20
|
+
};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
|
|
5
|
+
const PACKAGE_VERSION = 'pulsewasm-route-extractor@1.0.0-beta.2';
|
|
6
|
+
const DIAGNOSTICS_VERSION = 'pulsewasm.diagnostics.v1';
|
|
7
|
+
|
|
8
|
+
function stableFileName(fileName, cwd) {
|
|
9
|
+
if (!fileName) return fileName;
|
|
10
|
+
if (fileName.startsWith('<') && fileName.endsWith('>')) return fileName;
|
|
11
|
+
const root = cwd || process.cwd();
|
|
12
|
+
const rel = path.relative(root, fileName);
|
|
13
|
+
return rel && !rel.startsWith('..') && !path.isAbsolute(rel) ? rel : fileName;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function normalizeArtifact(value, cwd) {
|
|
17
|
+
if (Array.isArray(value)) return value.map((item) => normalizeArtifact(item, cwd));
|
|
18
|
+
if (!value || typeof value !== 'object') return value;
|
|
19
|
+
const out = {};
|
|
20
|
+
for (const [key, child] of Object.entries(value)) {
|
|
21
|
+
if ((key === 'file' || key === 'source') && typeof child === 'string') {
|
|
22
|
+
out[key] = stableFileName(child, cwd);
|
|
23
|
+
} else {
|
|
24
|
+
out[key] = normalizeArtifact(child, cwd);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
class ExtractionError extends Error {
|
|
31
|
+
constructor(diagnostics, options = {}) {
|
|
32
|
+
const list = Array.isArray(diagnostics) ? diagnostics : [diagnostics];
|
|
33
|
+
const normalized = normalizeDiagnostics(list);
|
|
34
|
+
super(formatDiagnostics(normalized));
|
|
35
|
+
this.name = 'ExtractionError';
|
|
36
|
+
this.diagnostics = normalized;
|
|
37
|
+
this.status = options.status || 'error';
|
|
38
|
+
this.passes = options.passes || [];
|
|
39
|
+
this.summary = options.summary;
|
|
40
|
+
this.source = options.source;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function sourceLoc(sourceFile, node) {
|
|
45
|
+
const start = typeof node.getStart === 'function' ? node.getStart(sourceFile) : node.pos ?? 0;
|
|
46
|
+
const end = node.end ?? start;
|
|
47
|
+
const startLC = sourceFile.getLineAndCharacterOfPosition(start);
|
|
48
|
+
const endLC = sourceFile.getLineAndCharacterOfPosition(end);
|
|
49
|
+
return {
|
|
50
|
+
file: sourceFile.fileName,
|
|
51
|
+
start: { line: startLC.line + 1, column: startLC.character + 1, offset: start },
|
|
52
|
+
end: { line: endLC.line + 1, column: endLC.character + 1, offset: end }
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function diagnostic(sourceFile, node, code, message, hint, options = {}) {
|
|
57
|
+
return normalizeDiagnostic({
|
|
58
|
+
code,
|
|
59
|
+
message,
|
|
60
|
+
hint,
|
|
61
|
+
severity: options.severity || 'error',
|
|
62
|
+
pass: options.pass || options.phase,
|
|
63
|
+
phase: options.phase,
|
|
64
|
+
loc: node ? sourceLoc(sourceFile, node) : { file: sourceFile.fileName }
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function normalizeDiagnostic(diag, index) {
|
|
69
|
+
const loc = diag.loc || { file: '<unknown>' };
|
|
70
|
+
const out = {
|
|
71
|
+
code: diag.code || 'PULSEWASM_DIAGNOSTIC',
|
|
72
|
+
severity: diag.severity || 'error',
|
|
73
|
+
message: diag.message || '',
|
|
74
|
+
loc
|
|
75
|
+
};
|
|
76
|
+
const pass = diag.pass || diag.phase;
|
|
77
|
+
if (pass) out.pass = pass;
|
|
78
|
+
if (diag.hint) out.hint = diag.hint;
|
|
79
|
+
if (diag.details !== undefined) out.details = diag.details;
|
|
80
|
+
if (typeof index === 'number') out.index = index;
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function normalizeDiagnostics(diagnostics) {
|
|
85
|
+
const list = Array.isArray(diagnostics) ? diagnostics : diagnostics ? [diagnostics] : [];
|
|
86
|
+
return list.map((diag, index) => normalizeDiagnostic(diag, index));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function countDiagnostics(diagnostics) {
|
|
90
|
+
const counts = { total: diagnostics.length, error: 0, warning: 0, info: 0 };
|
|
91
|
+
for (const diag of diagnostics) {
|
|
92
|
+
const severity = diag.severity || 'error';
|
|
93
|
+
if (counts[severity] === undefined) counts[severity] = 0;
|
|
94
|
+
counts[severity] += 1;
|
|
95
|
+
}
|
|
96
|
+
return counts;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function createDiagnosticsEnvelope(options = {}) {
|
|
100
|
+
const diagnostics = normalizeDiagnostics(options.diagnostics || []);
|
|
101
|
+
return normalizeArtifact({
|
|
102
|
+
version: DIAGNOSTICS_VERSION,
|
|
103
|
+
generatedBy: options.generatedBy || PACKAGE_VERSION,
|
|
104
|
+
status: options.status || (diagnostics.some((diag) => diag.severity === 'error') ? 'error' : 'ok'),
|
|
105
|
+
source: options.source,
|
|
106
|
+
entryRouter: options.entryRouter,
|
|
107
|
+
passes: options.passes || [],
|
|
108
|
+
summary: options.summary,
|
|
109
|
+
counts: countDiagnostics(diagnostics),
|
|
110
|
+
diagnostics
|
|
111
|
+
}, options.cwd || process.cwd());
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function formatDiagnostics(diagnostics) {
|
|
115
|
+
return normalizeDiagnostics(diagnostics)
|
|
116
|
+
.map((diag) => {
|
|
117
|
+
const loc = diag.loc || {};
|
|
118
|
+
const start = loc.start ? `${loc.file}:${loc.start.line}:${loc.start.column}` : loc.file || '<unknown>';
|
|
119
|
+
const pass = diag.pass ? ` [${diag.pass}]` : '';
|
|
120
|
+
const hint = diag.hint ? `\n hint: ${diag.hint}` : '';
|
|
121
|
+
return `${start}${pass} ${diag.code}: ${diag.message}${hint}`;
|
|
122
|
+
})
|
|
123
|
+
.join('\n');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
module.exports = {
|
|
127
|
+
PACKAGE_VERSION,
|
|
128
|
+
DIAGNOSTICS_VERSION,
|
|
129
|
+
ExtractionError,
|
|
130
|
+
countDiagnostics,
|
|
131
|
+
createDiagnosticsEnvelope,
|
|
132
|
+
diagnostic,
|
|
133
|
+
formatDiagnostics,
|
|
134
|
+
normalizeArtifact,
|
|
135
|
+
normalizeDiagnostic,
|
|
136
|
+
normalizeDiagnostics,
|
|
137
|
+
sourceLoc,
|
|
138
|
+
stableFileName
|
|
139
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const DISPATCH_TABLE_VERSION = 'pulsewasm.dispatch-table.v1';
|
|
4
|
+
const DISPATCH_POLICY = Object.freeze({
|
|
5
|
+
dispatchVersion: DISPATCH_TABLE_VERSION,
|
|
6
|
+
routeIdField: 'runtimeId',
|
|
7
|
+
routeIdShape: 'dense integer IDs assigned by flattened route order',
|
|
8
|
+
handlerSlotField: 'slot',
|
|
9
|
+
handlerSlotShape: 'dense integer slots assigned by deterministic handler table order',
|
|
10
|
+
routeLookup: 'method bucket -> compiled path pattern -> runtimeId',
|
|
11
|
+
executionModel: Object.freeze({
|
|
12
|
+
normal: 'ordered middleware slots followed by terminal route handler slot',
|
|
13
|
+
errors: 'ordered route error handler slots carried separately for runtime error unwinding',
|
|
14
|
+
lifecycle: 'connect/disconnect slots carried separately for websocket lifecycle dispatch',
|
|
15
|
+
channel: 'static channel literal or handler slot reference'
|
|
16
|
+
}),
|
|
17
|
+
codegen: 'metadata-only; Phase 6 does not emit AssemblyScript or Wasm code'
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
module.exports = {
|
|
21
|
+
DISPATCH_TABLE_VERSION,
|
|
22
|
+
DISPATCH_POLICY
|
|
23
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
declare const catalog: {
|
|
2
|
+
readonly ENTITIES_CATALOG_TARGETS: readonly ['fastly-javascript', 'fastly-native', 'node-javascript', 'node-native'];
|
|
3
|
+
normalizeEligibility(input?: Readonly<Record<string, boolean>>): Readonly<Record<string, boolean>>;
|
|
4
|
+
normalizeEntityCatalog(input: unknown): Readonly<Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export = catalog;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { sha256Hex, stableStringify } = require('../stable-id.js');
|
|
4
|
+
const {
|
|
5
|
+
ENTITIES_CATALOG_VERSION,
|
|
6
|
+
ENTITIES_CONTRACT_ID,
|
|
7
|
+
ENTITIES_DEFAULT_LIMITS,
|
|
8
|
+
ENTITIES_DIAGNOSTIC_CODES,
|
|
9
|
+
entitiesContractError,
|
|
10
|
+
normalizeDiscriminator,
|
|
11
|
+
normalizeSchemaId,
|
|
12
|
+
normalizeStaticMetadata,
|
|
13
|
+
utf8ByteLength
|
|
14
|
+
} = require('./contracts.js');
|
|
15
|
+
|
|
16
|
+
const ENTITIES_CATALOG_TARGETS = Object.freeze([
|
|
17
|
+
'fastly-javascript',
|
|
18
|
+
'fastly-native',
|
|
19
|
+
'node-javascript',
|
|
20
|
+
'node-native'
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
function plainObject(value) {
|
|
24
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
|
|
25
|
+
const prototype = Object.getPrototypeOf(value);
|
|
26
|
+
return prototype === Object.prototype || prototype === null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function compareText(left, right) {
|
|
30
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function normalizeEligibility(input = {}) {
|
|
34
|
+
if (!plainObject(input)) throw entitiesContractError(ENTITIES_DIAGNOSTIC_CODES.DECLARATION_INVALID, 'Catalog eligibility must be an object.');
|
|
35
|
+
const unknown = Object.keys(input).filter((key) => !ENTITIES_CATALOG_TARGETS.includes(key)).sort();
|
|
36
|
+
if (unknown.length > 0) throw entitiesContractError(ENTITIES_DIAGNOSTIC_CODES.DECLARATION_INVALID, `Catalog eligibility contains unknown targets: ${unknown.join(', ')}.`);
|
|
37
|
+
return Object.freeze(Object.fromEntries(ENTITIES_CATALOG_TARGETS.map((target) => {
|
|
38
|
+
const value = input[target];
|
|
39
|
+
if (value !== undefined && typeof value !== 'boolean') throw entitiesContractError(ENTITIES_DIAGNOSTIC_CODES.DECLARATION_INVALID, `Eligibility for ${target} must be boolean.`);
|
|
40
|
+
return [target, value === true];
|
|
41
|
+
})));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function normalizeEntityCatalog(input) {
|
|
45
|
+
if (!plainObject(input) || input.version !== ENTITIES_CATALOG_VERSION || input.contractId !== ENTITIES_CONTRACT_ID || !Array.isArray(input.routers)) {
|
|
46
|
+
throw entitiesContractError(ENTITIES_DIAGNOSTIC_CODES.DECLARATION_INVALID, `Entity catalog requires ${ENTITIES_CATALOG_VERSION}.`);
|
|
47
|
+
}
|
|
48
|
+
const routerIds = new Set();
|
|
49
|
+
const routers = input.routers.map((router) => {
|
|
50
|
+
if (!plainObject(router)) throw entitiesContractError(ENTITIES_DIAGNOSTIC_CODES.DECLARATION_INVALID, 'Catalog router must be an object.');
|
|
51
|
+
const id = String(router.id || '');
|
|
52
|
+
if (!id || routerIds.has(id) || router.adapter !== 'json-rpc' || router.binding !== 'request' || !Array.isArray(router.entities)) {
|
|
53
|
+
throw entitiesContractError(ENTITIES_DIAGNOSTIC_CODES.DECLARATION_INVALID, `Catalog router ${id || '<missing>'} is invalid.`);
|
|
54
|
+
}
|
|
55
|
+
routerIds.add(id);
|
|
56
|
+
if (router.entities.length > ENTITIES_DEFAULT_LIMITS.maxEntities) {
|
|
57
|
+
throw entitiesContractError(ENTITIES_DIAGNOSTIC_CODES.LIMIT_EXCEEDED, `Catalog router ${id} exceeds its entity limit.`);
|
|
58
|
+
}
|
|
59
|
+
const names = new Set();
|
|
60
|
+
const entities = router.entities.map((entity) => {
|
|
61
|
+
if (!plainObject(entity)) throw entitiesContractError(ENTITIES_DIAGNOSTIC_CODES.DECLARATION_INVALID, 'Catalog entity must be an object.');
|
|
62
|
+
const name = normalizeDiscriminator(entity.name);
|
|
63
|
+
if (names.has(name)) throw entitiesContractError(ENTITIES_DIAGNOSTIC_CODES.DISCRIMINATOR_DUPLICATE, `Catalog repeats entity ${name}.`);
|
|
64
|
+
names.add(name);
|
|
65
|
+
return Object.freeze({
|
|
66
|
+
name,
|
|
67
|
+
inputSchema: normalizeSchemaId(entity.inputSchema, 'catalog.inputSchema'),
|
|
68
|
+
outputSchema: normalizeSchemaId(entity.outputSchema, 'catalog.outputSchema'),
|
|
69
|
+
...(entity.metadata === undefined ? {} : { metadata: normalizeStaticMetadata(entity.metadata) }),
|
|
70
|
+
eligibility: normalizeEligibility(entity.eligibility)
|
|
71
|
+
});
|
|
72
|
+
}).sort((left, right) => compareText(left.name, right.name));
|
|
73
|
+
const metadataBytes = entities.reduce((sum, entity) => sum + (entity.metadata ? utf8ByteLength(stableStringify(entity.metadata)) : 0), 0);
|
|
74
|
+
if (metadataBytes > ENTITIES_DEFAULT_LIMITS.maxMetadataBytesPerRouter) {
|
|
75
|
+
throw entitiesContractError(ENTITIES_DIAGNOSTIC_CODES.LIMIT_EXCEEDED, `Catalog router ${id} exceeds its metadata limit.`);
|
|
76
|
+
}
|
|
77
|
+
return Object.freeze({ id, adapter: 'json-rpc', binding: 'request', entities: Object.freeze(entities) });
|
|
78
|
+
}).sort((left, right) => compareText(left.id, right.id));
|
|
79
|
+
const normalized = Object.freeze({ version: ENTITIES_CATALOG_VERSION, contractId: ENTITIES_CONTRACT_ID, routers: Object.freeze(routers) });
|
|
80
|
+
return Object.freeze({ ...normalized, catalogHash: sha256Hex(stableStringify(normalized)) });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
module.exports = Object.freeze({
|
|
84
|
+
ENTITIES_CATALOG_TARGETS,
|
|
85
|
+
normalizeEligibility,
|
|
86
|
+
normalizeEntityCatalog
|
|
87
|
+
});
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
declare interface EntitiesLimits {
|
|
2
|
+
readonly maxEnvelopeBytes: number;
|
|
3
|
+
readonly maxPayloadBytes: number;
|
|
4
|
+
readonly maxMethodBytes: number;
|
|
5
|
+
readonly maxEntities: number;
|
|
6
|
+
readonly maxMetadataBytesPerEntity: number;
|
|
7
|
+
readonly maxMetadataBytesPerRouter: number;
|
|
8
|
+
readonly maxMetadataDepth: number;
|
|
9
|
+
readonly maxMetadataEntries: number;
|
|
10
|
+
readonly maxJsonDepth: number;
|
|
11
|
+
readonly maxOutputBytes: number;
|
|
12
|
+
readonly maxErrorMessageBytes: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
declare interface NormalizedEntityDeclaration {
|
|
16
|
+
readonly input: string | null;
|
|
17
|
+
readonly output: string | null;
|
|
18
|
+
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
declare interface NormalizedEntityAdapter {
|
|
22
|
+
readonly version: string;
|
|
23
|
+
readonly id: 'json-rpc';
|
|
24
|
+
readonly adapterVersion: string;
|
|
25
|
+
readonly options: Readonly<Record<string, unknown>>;
|
|
26
|
+
readonly limits: EntitiesLimits;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
declare interface EntitiesContractError extends TypeError {
|
|
30
|
+
readonly code: string;
|
|
31
|
+
readonly detail: Readonly<Record<string, unknown>>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
declare const contracts: {
|
|
35
|
+
readonly ENTITIES_PACKAGE_VERSION: 'pulse.entities-package.v1';
|
|
36
|
+
readonly ENTITIES_CONTRACT_VERSION: 'pulse.entities-contract.v1';
|
|
37
|
+
readonly ENTITIES_ADAPTER_VERSION: 'pulse.entities-adapter.v1';
|
|
38
|
+
readonly ENTITIES_PLAN_VERSION: 'pulse.entities-lowering-plan.v1';
|
|
39
|
+
readonly ENTITIES_CATALOG_VERSION: 'pulse.entities-catalog.v1';
|
|
40
|
+
readonly ENTITIES_FAILURE_VERSION: 'pulse.entities-failure.v1';
|
|
41
|
+
readonly ENTITIES_DIAGNOSTIC_VERSION: 'pulse.entities-diagnostics.v1';
|
|
42
|
+
readonly ENTITIES_HANDLER_REFERENCE_VERSION: 'pulse.entities-handler-reference.v1';
|
|
43
|
+
readonly ENTITIES_REGISTRY_VERSION: 'pulse.entities-registry.v1';
|
|
44
|
+
readonly ENTITIES_JSON_RPC_ADAPTER_VERSION: 'pulse.entities-json-rpc-adapter.v1';
|
|
45
|
+
readonly ENTITIES_CONTRACT_ID: 'pulse.entities';
|
|
46
|
+
readonly ENTITIES_PACKAGE_NAME: '@pulse-compute/entities';
|
|
47
|
+
readonly ENTITIES_LOWERER_ID: 'pulse.entities.compiler-builder.v1';
|
|
48
|
+
readonly ENTITIES_LOWERER_EXPORT: 'createEntitiesPackageCompilerBuilder';
|
|
49
|
+
readonly ENTITIES_LOWERER_MANIFEST: './pulsewasm.manifest.cjs';
|
|
50
|
+
readonly ENTITIES_LOWERABLE_SUBPATH: '@pulse-compute/entities';
|
|
51
|
+
readonly ENTITIES_PACKAGE_INTRINSIC_VERSION: 'pulse.package-intrinsic.v1';
|
|
52
|
+
readonly ENTITIES_PUBLIC_SYMBOLS: readonly ['EntityRouter', 'jsonRpc'];
|
|
53
|
+
readonly ENTITIES_PACKAGE_INTRINSICS: Readonly<{
|
|
54
|
+
handle: Readonly<{
|
|
55
|
+
name: 'pulse.entities.handle.v1';
|
|
56
|
+
compilerName: '__pulse_entities_handle';
|
|
57
|
+
valueKind: 'response';
|
|
58
|
+
argumentIndexes: readonly [0];
|
|
59
|
+
}>;
|
|
60
|
+
}>;
|
|
61
|
+
readonly ENTITIES_JSON_RPC_OPTIONS: Readonly<{ namedParamsOnly: true; acceptEmptyObjectForNoInput: false }>;
|
|
62
|
+
readonly ENTITIES_DEFAULT_LIMITS: EntitiesLimits;
|
|
63
|
+
readonly ENTITIES_LIMIT_KEYS: readonly string[];
|
|
64
|
+
readonly ENTITIES_FAILURE_KINDS: readonly string[];
|
|
65
|
+
readonly ENTITIES_FAILURE_CODES: Readonly<Record<string, string>>;
|
|
66
|
+
readonly ENTITIES_DIAGNOSTIC_CODES: Readonly<Record<string, string>>;
|
|
67
|
+
entitiesContractError(code: string, message: string, detail?: Readonly<Record<string, unknown>>): EntitiesContractError;
|
|
68
|
+
normalizeEntityLimits(input?: Readonly<Partial<EntitiesLimits>>): EntitiesLimits;
|
|
69
|
+
normalizeDiscriminator(value: unknown, limits?: EntitiesLimits): string;
|
|
70
|
+
normalizeSchemaId(value: unknown, field?: string): string | null;
|
|
71
|
+
normalizeStaticMetadata(value: unknown, limits?: EntitiesLimits): Readonly<Record<string, unknown>> | undefined;
|
|
72
|
+
normalizeEntityDeclaration(input: unknown, limits?: EntitiesLimits): NormalizedEntityDeclaration;
|
|
73
|
+
normalizeHandler<T extends Function>(handler: T): T;
|
|
74
|
+
normalizeHandlerReference(input: unknown): Readonly<Record<string, string>>;
|
|
75
|
+
normalizeJsonRpcAdapterOptions(input?: Readonly<{ namedParamsOnly?: true; acceptEmptyObjectForNoInput?: boolean }>): Readonly<{
|
|
76
|
+
namedParamsOnly: true;
|
|
77
|
+
acceptEmptyObjectForNoInput: boolean;
|
|
78
|
+
}>;
|
|
79
|
+
normalizeAdapter(input: unknown, limits?: EntitiesLimits): NormalizedEntityAdapter;
|
|
80
|
+
normalizeEntityRegistration(input: unknown, limits?: EntitiesLimits): Readonly<{
|
|
81
|
+
discriminator: string;
|
|
82
|
+
declaration: NormalizedEntityDeclaration;
|
|
83
|
+
handler: Function;
|
|
84
|
+
}>;
|
|
85
|
+
normalizeEntityRegistry(input: readonly unknown[], limits?: EntitiesLimits): Readonly<{
|
|
86
|
+
version: string;
|
|
87
|
+
entries: readonly unknown[];
|
|
88
|
+
registryHash: string;
|
|
89
|
+
limits: EntitiesLimits;
|
|
90
|
+
}>;
|
|
91
|
+
normalizeEntityFailure(input: unknown, limits?: EntitiesLimits): Readonly<Record<string, unknown>>;
|
|
92
|
+
normalizeEntityPlan(input: unknown): Readonly<Record<string, unknown>>;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export = contracts;
|