apcore-js 0.19.0 → 0.20.0
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 +110 -9
- package/dist/acl.d.ts +18 -0
- package/dist/acl.d.ts.map +1 -1
- package/dist/acl.js +54 -17
- package/dist/acl.js.map +1 -1
- package/dist/async-task.d.ts +70 -16
- package/dist/async-task.d.ts.map +1 -1
- package/dist/async-task.js +212 -72
- package/dist/async-task.js.map +1 -1
- package/dist/builtin-steps.d.ts +16 -5
- package/dist/builtin-steps.d.ts.map +1 -1
- package/dist/builtin-steps.js +45 -28
- package/dist/builtin-steps.js.map +1 -1
- package/dist/config.d.ts +38 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +163 -33
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +12 -1
- package/dist/context.js.map +1 -1
- package/dist/errors.d.ts +32 -10
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +55 -16
- package/dist/errors.js.map +1 -1
- package/dist/events/circuit-breaker.d.ts +45 -0
- package/dist/events/circuit-breaker.d.ts.map +1 -0
- package/dist/events/circuit-breaker.js +115 -0
- package/dist/events/circuit-breaker.js.map +1 -0
- package/dist/events/emitter.d.ts +22 -1
- package/dist/events/emitter.d.ts.map +1 -1
- package/dist/events/emitter.js +66 -2
- package/dist/events/emitter.js.map +1 -1
- package/dist/events/index.d.ts +4 -2
- package/dist/events/index.d.ts.map +1 -1
- package/dist/events/index.js +3 -2
- package/dist/events/index.js.map +1 -1
- package/dist/events/subscribers.d.ts +33 -1
- package/dist/events/subscribers.d.ts.map +1 -1
- package/dist/events/subscribers.js +124 -1
- package/dist/events/subscribers.js.map +1 -1
- package/dist/executor.d.ts +10 -2
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +134 -51
- package/dist/executor.js.map +1 -1
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +1 -1
- package/dist/index.d.ts +35 -25
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23 -17
- package/dist/index.js.map +1 -1
- package/dist/middleware/base.d.ts +25 -3
- package/dist/middleware/base.d.ts.map +1 -1
- package/dist/middleware/base.js +24 -0
- package/dist/middleware/base.js.map +1 -1
- package/dist/middleware/circuit-breaker.d.ts +54 -0
- package/dist/middleware/circuit-breaker.d.ts.map +1 -0
- package/dist/middleware/circuit-breaker.js +168 -0
- package/dist/middleware/circuit-breaker.js.map +1 -0
- package/dist/middleware/context-namespace.d.ts +30 -0
- package/dist/middleware/context-namespace.d.ts.map +1 -0
- package/dist/middleware/context-namespace.js +38 -0
- package/dist/middleware/context-namespace.js.map +1 -0
- package/dist/middleware/index.d.ts +7 -1
- package/dist/middleware/index.d.ts.map +1 -1
- package/dist/middleware/index.js +4 -1
- package/dist/middleware/index.js.map +1 -1
- package/dist/middleware/manager.d.ts +11 -4
- package/dist/middleware/manager.d.ts.map +1 -1
- package/dist/middleware/manager.js +25 -9
- package/dist/middleware/manager.js.map +1 -1
- package/dist/middleware/platform-notify.d.ts +8 -4
- package/dist/middleware/platform-notify.d.ts.map +1 -1
- package/dist/middleware/platform-notify.js +11 -7
- package/dist/middleware/platform-notify.js.map +1 -1
- package/dist/middleware/tracing.d.ts +50 -0
- package/dist/middleware/tracing.d.ts.map +1 -0
- package/dist/middleware/tracing.js +89 -0
- package/dist/middleware/tracing.js.map +1 -0
- package/dist/observability/batch-span-processor.d.ts +48 -0
- package/dist/observability/batch-span-processor.d.ts.map +1 -0
- package/dist/observability/batch-span-processor.js +89 -0
- package/dist/observability/batch-span-processor.js.map +1 -0
- package/dist/observability/context-logger.d.ts +54 -1
- package/dist/observability/context-logger.d.ts.map +1 -1
- package/dist/observability/context-logger.js +270 -6
- package/dist/observability/context-logger.js.map +1 -1
- package/dist/observability/error-history.d.ts +36 -7
- package/dist/observability/error-history.d.ts.map +1 -1
- package/dist/observability/error-history.js +169 -50
- package/dist/observability/error-history.js.map +1 -1
- package/dist/observability/index.d.ts +16 -5
- package/dist/observability/index.d.ts.map +1 -1
- package/dist/observability/index.js +8 -3
- package/dist/observability/index.js.map +1 -1
- package/dist/observability/metrics.d.ts +14 -1
- package/dist/observability/metrics.d.ts.map +1 -1
- package/dist/observability/metrics.js +23 -2
- package/dist/observability/metrics.js.map +1 -1
- package/dist/observability/prometheus-exporter.d.ts +37 -0
- package/dist/observability/prometheus-exporter.d.ts.map +1 -0
- package/dist/observability/prometheus-exporter.js +135 -0
- package/dist/observability/prometheus-exporter.js.map +1 -0
- package/dist/observability/storage.d.ts +43 -0
- package/dist/observability/storage.d.ts.map +1 -0
- package/dist/observability/storage.js +58 -0
- package/dist/observability/storage.js.map +1 -0
- package/dist/observability/store.d.ts +29 -0
- package/dist/observability/store.d.ts.map +1 -0
- package/dist/observability/store.js +36 -0
- package/dist/observability/store.js.map +1 -0
- package/dist/observability/usage-exporter.d.ts +58 -0
- package/dist/observability/usage-exporter.d.ts.map +1 -0
- package/dist/observability/usage-exporter.js +86 -0
- package/dist/observability/usage-exporter.js.map +1 -0
- package/dist/observability/usage.d.ts +18 -1
- package/dist/observability/usage.d.ts.map +1 -1
- package/dist/observability/usage.js +25 -3
- package/dist/observability/usage.js.map +1 -1
- package/dist/pipeline-config.d.ts +11 -0
- package/dist/pipeline-config.d.ts.map +1 -1
- package/dist/pipeline-config.js +36 -10
- package/dist/pipeline-config.js.map +1 -1
- package/dist/pipeline.d.ts +123 -2
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/pipeline.js +249 -50
- package/dist/pipeline.js.map +1 -1
- package/dist/registry/index.d.ts +2 -0
- package/dist/registry/index.d.ts.map +1 -1
- package/dist/registry/index.js +1 -0
- package/dist/registry/index.js.map +1 -1
- package/dist/registry/multi-class.d.ts +57 -0
- package/dist/registry/multi-class.d.ts.map +1 -0
- package/dist/registry/multi-class.js +120 -0
- package/dist/registry/multi-class.js.map +1 -0
- package/dist/registry/registry.d.ts +91 -3
- package/dist/registry/registry.d.ts.map +1 -1
- package/dist/registry/registry.js +181 -11
- package/dist/registry/registry.js.map +1 -1
- package/dist/schema/constants.d.ts +9 -0
- package/dist/schema/constants.d.ts.map +1 -0
- package/dist/schema/constants.js +9 -0
- package/dist/schema/constants.js.map +1 -0
- package/dist/schema/index.d.ts +1 -1
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +1 -1
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/loader.d.ts +27 -3
- package/dist/schema/loader.d.ts.map +1 -1
- package/dist/schema/loader.js +137 -32
- package/dist/schema/loader.js.map +1 -1
- package/dist/schema/types.d.ts +4 -0
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/schema/types.js.map +1 -1
- package/dist/schema/validator.d.ts +9 -0
- package/dist/schema/validator.d.ts.map +1 -1
- package/dist/schema/validator.js +153 -4
- package/dist/schema/validator.js.map +1 -1
- package/dist/sys-modules/audit.d.ts +50 -0
- package/dist/sys-modules/audit.d.ts.map +1 -0
- package/dist/sys-modules/audit.js +89 -0
- package/dist/sys-modules/audit.js.map +1 -0
- package/dist/sys-modules/control.d.ts +32 -4
- package/dist/sys-modules/control.d.ts.map +1 -1
- package/dist/sys-modules/control.js +196 -25
- package/dist/sys-modules/control.js.map +1 -1
- package/dist/sys-modules/index.d.ts +7 -2
- package/dist/sys-modules/index.d.ts.map +1 -1
- package/dist/sys-modules/index.js +3 -1
- package/dist/sys-modules/index.js.map +1 -1
- package/dist/sys-modules/overrides.d.ts +58 -0
- package/dist/sys-modules/overrides.d.ts.map +1 -0
- package/dist/sys-modules/overrides.js +106 -0
- package/dist/sys-modules/overrides.js.map +1 -0
- package/dist/sys-modules/registration.d.ts +17 -12
- package/dist/sys-modules/registration.d.ts.map +1 -1
- package/dist/sys-modules/registration.js +115 -23
- package/dist/sys-modules/registration.js.map +1 -1
- package/dist/sys-modules/toggle.d.ts +7 -2
- package/dist/sys-modules/toggle.d.ts.map +1 -1
- package/dist/sys-modules/toggle.js +61 -5
- package/dist/sys-modules/toggle.js.map +1 -1
- package/dist/trace-context.d.ts +47 -9
- package/dist/trace-context.d.ts.map +1 -1
- package/dist/trace-context.js +139 -16
- package/dist/trace-context.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-class module discovery: opt-in scanner, snake_case ID derivation, conflict detection.
|
|
3
|
+
*
|
|
4
|
+
* Implements PROTOCOL_SPEC §2.1.1 (Multi-Module Discovery).
|
|
5
|
+
*/
|
|
6
|
+
import { IdTooLongError, InvalidSegmentError, ModuleIdConflictError } from '../errors.js';
|
|
7
|
+
const SEGMENT_RE = /^[a-z][a-z0-9_]*$/;
|
|
8
|
+
const CANONICAL_ID_RE = /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)*$/;
|
|
9
|
+
const MAX_MODULE_ID_LEN = 192;
|
|
10
|
+
/**
|
|
11
|
+
* Convert a class name to a snake_case segment per PROTOCOL_SPEC §2.1.1.
|
|
12
|
+
*
|
|
13
|
+
* Algorithm:
|
|
14
|
+
* 1. Insert boundary at ALLCAPS→CamelCase transitions (HTTPSender → HTTP_Sender).
|
|
15
|
+
* 2. Insert boundary at lowercase/digit→uppercase transitions (MathOps → Math_Ops).
|
|
16
|
+
* 3. Replace every non-alphanumeric character with `_`.
|
|
17
|
+
* 4. Lowercase.
|
|
18
|
+
* 5. Collapse consecutive `_` to a single `_`.
|
|
19
|
+
* 6. Strip leading and trailing `_`.
|
|
20
|
+
*/
|
|
21
|
+
export function classNameToSegment(className) {
|
|
22
|
+
let s = className.replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2');
|
|
23
|
+
s = s.replace(/([a-z\d])([A-Z])/g, '$1_$2');
|
|
24
|
+
s = s.replace(/[^a-zA-Z0-9]/g, '_');
|
|
25
|
+
s = s.toLowerCase();
|
|
26
|
+
s = s.replace(/_+/g, '_');
|
|
27
|
+
return s.replace(/^_+|_+$/g, '');
|
|
28
|
+
}
|
|
29
|
+
function computeBaseId(filePath, extensionsRoot) {
|
|
30
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
31
|
+
const parts = normalized.split('/');
|
|
32
|
+
const rootIdx = parts.findIndex(p => p === extensionsRoot);
|
|
33
|
+
const relParts = rootIdx === -1 ? [parts[parts.length - 1]] : parts.slice(rootIdx + 1);
|
|
34
|
+
// Strip file extension from the last segment
|
|
35
|
+
relParts[relParts.length - 1] = relParts[relParts.length - 1].replace(/\.[^.]+$/, '');
|
|
36
|
+
return relParts.join('.');
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Discover module IDs for classes in a single file under multi-class mode.
|
|
40
|
+
*
|
|
41
|
+
* Implements the Registry.discover_multi_class contract from PROTOCOL_SPEC §2.1.1.
|
|
42
|
+
*
|
|
43
|
+
* When multiClassEnabled is false (the default — multi-class mode is opt-in),
|
|
44
|
+
* only the first qualifying class is used and the bare base_id is returned.
|
|
45
|
+
*
|
|
46
|
+
* When multiClassEnabled is true and exactly one class qualifies:
|
|
47
|
+
* - If the class segment matches the file's last path segment (class named after
|
|
48
|
+
* the file), the bare base_id is returned to preserve existing module IDs.
|
|
49
|
+
* - Otherwise the class segment is appended: base_id.class_segment.
|
|
50
|
+
*
|
|
51
|
+
* @internal Prefer `Registry.discoverMultiClass` (D-15) for the canonical
|
|
52
|
+
* cross-language API surface; this free function is retained for backwards
|
|
53
|
+
* compatibility and direct use by the scanner.
|
|
54
|
+
*
|
|
55
|
+
* @throws ModuleIdConflictError — two classes produce the same class_segment
|
|
56
|
+
* @throws InvalidSegmentError — a segment does not match ^[a-z][a-z0-9_]*$
|
|
57
|
+
* @throws IdTooLongError — a derived module_id exceeds 192 characters
|
|
58
|
+
*/
|
|
59
|
+
export function discoverMultiClass(filePath, classes, extensionsRoot = 'extensions', multiClassEnabled = false) {
|
|
60
|
+
const qualifying = classes.filter(c => c.implementsModule);
|
|
61
|
+
if (qualifying.length === 0)
|
|
62
|
+
return [];
|
|
63
|
+
const baseId = computeBaseId(filePath, extensionsRoot);
|
|
64
|
+
if (!multiClassEnabled) {
|
|
65
|
+
return [{ moduleId: baseId, className: qualifying[0].name }];
|
|
66
|
+
}
|
|
67
|
+
if (qualifying.length === 1) {
|
|
68
|
+
const segment = classNameToSegment(qualifying[0].name);
|
|
69
|
+
const lastBaseSegment = baseId.split('.').pop();
|
|
70
|
+
// Identity guarantee: when the class segment matches the file's last segment
|
|
71
|
+
// (class named after the file), preserve the bare base_id so existing IDs
|
|
72
|
+
// are not broken when multi-class mode is enabled.
|
|
73
|
+
if (segment === lastBaseSegment) {
|
|
74
|
+
return [{ moduleId: baseId, className: qualifying[0].name }];
|
|
75
|
+
}
|
|
76
|
+
// Class has a distinct name from the file — append the class segment.
|
|
77
|
+
if (!SEGMENT_RE.test(segment))
|
|
78
|
+
throw new InvalidSegmentError(filePath, qualifying[0].name, segment);
|
|
79
|
+
const moduleId = `${baseId}.${segment}`;
|
|
80
|
+
if (!CANONICAL_ID_RE.test(moduleId))
|
|
81
|
+
throw new InvalidSegmentError(filePath, qualifying[0].name, segment);
|
|
82
|
+
if (moduleId.length > MAX_MODULE_ID_LEN)
|
|
83
|
+
throw new IdTooLongError(filePath, moduleId);
|
|
84
|
+
return [{ moduleId, className: qualifying[0].name }];
|
|
85
|
+
}
|
|
86
|
+
// Multi-class path: derive IDs, detect conflicts, validate
|
|
87
|
+
const seenSegments = new Map(); // segment → className
|
|
88
|
+
const results = [];
|
|
89
|
+
for (const cls of qualifying) {
|
|
90
|
+
const segment = classNameToSegment(cls.name);
|
|
91
|
+
if (!SEGMENT_RE.test(segment)) {
|
|
92
|
+
throw new InvalidSegmentError(filePath, cls.name, segment);
|
|
93
|
+
}
|
|
94
|
+
if (seenSegments.has(segment)) {
|
|
95
|
+
console.warn(`[apcore:multi-class] MODULE_ID_CONFLICT in '${filePath}': ` +
|
|
96
|
+
`classes '${seenSegments.get(segment)}' and '${cls.name}' both produce segment '${segment}'`);
|
|
97
|
+
throw new ModuleIdConflictError(filePath, [seenSegments.get(segment), cls.name], segment);
|
|
98
|
+
}
|
|
99
|
+
seenSegments.set(segment, cls.name);
|
|
100
|
+
const moduleId = `${baseId}.${segment}`;
|
|
101
|
+
if (!CANONICAL_ID_RE.test(moduleId)) {
|
|
102
|
+
throw new InvalidSegmentError(filePath, cls.name, segment);
|
|
103
|
+
}
|
|
104
|
+
if (moduleId.length > MAX_MODULE_ID_LEN) {
|
|
105
|
+
throw new IdTooLongError(filePath, moduleId);
|
|
106
|
+
}
|
|
107
|
+
results.push({ moduleId, className: cls.name });
|
|
108
|
+
}
|
|
109
|
+
return results;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Internal alias of {@link discoverMultiClass}. Use {@link Registry.discoverMultiClass}
|
|
113
|
+
* instead — this name is preserved so the scanner and other internal callers
|
|
114
|
+
* have a stable reference that signals the surface is not part of the public
|
|
115
|
+
* API.
|
|
116
|
+
*
|
|
117
|
+
* @internal
|
|
118
|
+
*/
|
|
119
|
+
export const _discoverMultiClass = discoverMultiClass;
|
|
120
|
+
//# sourceMappingURL=multi-class.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multi-class.js","sourceRoot":"","sources":["../../src/registry/multi-class.ts"],"names":[],"mappings":"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"}
|
|
@@ -77,12 +77,48 @@ export declare class Registry {
|
|
|
77
77
|
setValidator(validator: ModuleValidator): void;
|
|
78
78
|
discover(): Promise<number>;
|
|
79
79
|
private _discoverCustom;
|
|
80
|
+
/**
|
|
81
|
+
* Default discovery pipeline (D-32 — 8 canonical stages, mirroring
|
|
82
|
+
* apcore-rust `default_discoverer.rs`):
|
|
83
|
+
*
|
|
84
|
+
* 1. _ensureIdMap — lazy-load the optional id_map.json
|
|
85
|
+
* 2. _scanRoots — walk extension roots
|
|
86
|
+
* 3. _applyIdMapOverrides — rewrite canonical IDs from the map
|
|
87
|
+
* 4. _loadAllMetadata — load each module's `module.yaml`
|
|
88
|
+
* 5. _resolveAllEntryPoints — import the JS/TS entry point
|
|
89
|
+
* 6. _validateAll — run module/custom validators
|
|
90
|
+
* 7. _filterIdConflicts — batch-drop conflicting / invalid IDs
|
|
91
|
+
* 8. _resolveLoadOrder + _registerInOrder
|
|
92
|
+
* — topological sort then register.
|
|
93
|
+
*/
|
|
80
94
|
private _discoverDefault;
|
|
81
95
|
private _scanRoots;
|
|
82
96
|
private _applyIdMapOverrides;
|
|
83
97
|
private _loadAllMetadata;
|
|
84
98
|
private _resolveAllEntryPoints;
|
|
85
99
|
private _validateAll;
|
|
100
|
+
/**
|
|
101
|
+
* Stage 7 (D-32) — batch-drop modules with invalid or conflicting IDs.
|
|
102
|
+
*
|
|
103
|
+
* Mirrors apcore-python `_filter_id_conflicts` and apcore-rust
|
|
104
|
+
* `default_discoverer::filter_id_conflicts`. Two failure modes drop a
|
|
105
|
+
* module here (warn + skip) rather than aborting the whole batch:
|
|
106
|
+
* - PROTOCOL_SPEC §2.7 ID validation (empty / pattern / length /
|
|
107
|
+
* reserved-word first segment), and
|
|
108
|
+
* - Algorithm A03 conflict detection (duplicate against an existing
|
|
109
|
+
* registration, lowercase collision, reserved-word collision).
|
|
110
|
+
*
|
|
111
|
+
* Soft-severity conflicts (e.g. case-insensitive match against an
|
|
112
|
+
* already-registered ID at `warn` level) are NOT dropped here — the
|
|
113
|
+
* warning is logged and the module flows through to registration so
|
|
114
|
+
* existing behaviour is preserved. Only `error`-severity conflicts and
|
|
115
|
+
* invalid IDs are filtered out.
|
|
116
|
+
*
|
|
117
|
+
* `rawMetadata` is accepted for cross-language signature parity with
|
|
118
|
+
* the Rust/Python helpers (which may inspect metadata for additional
|
|
119
|
+
* checks); the TS implementation reads only the module ID.
|
|
120
|
+
*/
|
|
121
|
+
private _filterIdConflicts;
|
|
86
122
|
private _resolveLoadOrder;
|
|
87
123
|
private _registerInOrder;
|
|
88
124
|
/**
|
|
@@ -91,12 +127,31 @@ export declare class Registry {
|
|
|
91
127
|
* Validation order (PROTOCOL_SPEC §2.7, aligned with apcore-python and
|
|
92
128
|
* apcore-rust): empty → pattern → length → reserved (per-segment) →
|
|
93
129
|
* duplicate.
|
|
130
|
+
*
|
|
131
|
+
* The optional `version` and `metadata` parameters mirror apcore-python's
|
|
132
|
+
* `Registry.register(module_id, module, version=None, metadata=None)` for
|
|
133
|
+
* cross-language signature parity (sync finding A-001). Multi-version
|
|
134
|
+
* coexistence is not yet implemented in this SDK — when supplied, both
|
|
135
|
+
* fields are merged into the module's metadata so callers can read them
|
|
136
|
+
* back via `getDefinition()` and `list({tags})`. See PROTOCOL_SPEC §5.4.
|
|
94
137
|
*/
|
|
95
|
-
register(moduleId: string, module: unknown): void;
|
|
138
|
+
register(moduleId: string, module: unknown, version?: string | null, metadata?: Record<string, unknown> | null): void;
|
|
96
139
|
/** Inner registration — no validator, no ID validation. Used by discover() paths that run their own checks. */
|
|
97
140
|
private _registerImpl;
|
|
98
141
|
unregister(moduleId: string): boolean;
|
|
99
|
-
|
|
142
|
+
/**
|
|
143
|
+
* Look up a registered module by ID.
|
|
144
|
+
*
|
|
145
|
+
* @param moduleId - Module identifier (must be non-empty).
|
|
146
|
+
* @param _versionHint - Optional semver range for multi-version coexistence
|
|
147
|
+
* (PROTOCOL_SPEC §5.4). This SDK currently exposes a single-version
|
|
148
|
+
* registry, so the hint is accepted for cross-language API parity with
|
|
149
|
+
* apcore-python (sync finding A-002) but does NOT participate in
|
|
150
|
+
* resolution: the latest registered module for `moduleId` is returned
|
|
151
|
+
* regardless of the hint. When multi-version registration lands, this
|
|
152
|
+
* parameter will gate semver-range matching.
|
|
153
|
+
*/
|
|
154
|
+
get(moduleId: string, _versionHint?: string | null): unknown | null;
|
|
100
155
|
has(moduleId: string): boolean;
|
|
101
156
|
list(options?: {
|
|
102
157
|
tags?: string[];
|
|
@@ -105,12 +160,35 @@ export declare class Registry {
|
|
|
105
160
|
iter(): IterableIterator<[string, unknown]>;
|
|
106
161
|
get count(): number;
|
|
107
162
|
get moduleIds(): string[];
|
|
108
|
-
getDefinition(moduleId: string): ModuleDescriptor | null;
|
|
163
|
+
getDefinition(moduleId: string, _versionHint?: string | null): ModuleDescriptor | null;
|
|
109
164
|
describe(moduleId: string): string;
|
|
110
165
|
on(event: string, callback: EventCallback): void;
|
|
111
166
|
off(event: string, callback: EventCallback): boolean;
|
|
112
167
|
reload(): Promise<number>;
|
|
113
168
|
private _triggerEvent;
|
|
169
|
+
/**
|
|
170
|
+
* Watch the configured extension roots for filesystem changes and
|
|
171
|
+
* unregister any module whose source file is modified or deleted.
|
|
172
|
+
*
|
|
173
|
+
* **Cross-language divergence (sync finding A-D-104):** unlike apcore-python
|
|
174
|
+
* (which re-imports the file via `importlib.reload`) and apcore-rust (which
|
|
175
|
+
* triggers full rediscovery), the TypeScript SDK is **event-only**. On a
|
|
176
|
+
* file change the registry:
|
|
177
|
+
* 1. unregisters the previously-loaded module (calling its `onUnload`),
|
|
178
|
+
* 2. emits a `'file_changed'` event with `{ filePath }` payload.
|
|
179
|
+
*
|
|
180
|
+
* Consumers are expected to subscribe and re-register the module
|
|
181
|
+
* themselves (e.g. by calling `registry.discover()` or registering a fresh
|
|
182
|
+
* import). ES module specifiers are immutable in Node — there is no
|
|
183
|
+
* portable "reload from disk" primitive — so a transparent dynamic
|
|
184
|
+
* `import()` would silently return the cached old module on every
|
|
185
|
+
* invocation. A workaround using a cache-busting query (`?v=Date.now()`)
|
|
186
|
+
* leaks the old module each reload and breaks browser bundlers, so it is
|
|
187
|
+
* intentionally **not** offered here.
|
|
188
|
+
*
|
|
189
|
+
* If your application needs Python-style hot-reload semantics, listen for
|
|
190
|
+
* `'file_changed'` and re-discover or re-import explicitly.
|
|
191
|
+
*/
|
|
114
192
|
watch(): Promise<void>;
|
|
115
193
|
unwatch(): void;
|
|
116
194
|
private _handleFileChange;
|
|
@@ -137,6 +215,16 @@ export declare class Registry {
|
|
|
137
215
|
*/
|
|
138
216
|
exportSchema(moduleId: string, strict?: boolean): Record<string, unknown> | null;
|
|
139
217
|
clearCache(): void;
|
|
218
|
+
/**
|
|
219
|
+
* Discover module IDs for the classes in a single file under multi-class
|
|
220
|
+
* mode (PROTOCOL_SPEC §2.1.1).
|
|
221
|
+
*
|
|
222
|
+
* D-15: cross-language alignment with Python `Registry.discover_multi_class`
|
|
223
|
+
* and the Rust trait method. Internally delegates to the free function
|
|
224
|
+
* {@link discoverMultiClass} (re-exported as `_discoverMultiClass` for
|
|
225
|
+
* scanner internals), so behaviour is identical.
|
|
226
|
+
*/
|
|
227
|
+
discoverMultiClass(filePath: string, classes: readonly import('./multi-class.js').ClassDescriptor[], extensionsRoot?: string, multiClassEnabled?: boolean): import('./multi-class.js').MultiClassEntry[];
|
|
140
228
|
/**
|
|
141
229
|
* Number of in-flight executions per module.
|
|
142
230
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/registry/registry.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/registry/registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,6 +7,7 @@ import { detectIdConflicts } from './conflicts.js';
|
|
|
7
7
|
import { resolveDependencies } from './dependencies.js';
|
|
8
8
|
import { resolveEntryPoint } from './entry-point.js';
|
|
9
9
|
import { mergeModuleMetadata, parseDependencies } from './metadata.js';
|
|
10
|
+
import { _discoverMultiClass } from './multi-class.js';
|
|
10
11
|
import { getSchema } from './schema-export.js';
|
|
11
12
|
import { toStrictSchema } from '../schema/strict.js';
|
|
12
13
|
import { deepCopy } from '../utils/index.js';
|
|
@@ -197,6 +198,17 @@ export class Registry {
|
|
|
197
198
|
continue;
|
|
198
199
|
}
|
|
199
200
|
}
|
|
201
|
+
// PROTOCOL_SPEC §2.7 ID validation — sync finding A-D-102.
|
|
202
|
+
// Mirrors apcore-python `Registry._discover_custom` which calls
|
|
203
|
+
// `_validate_module_id` before registration. Invalid IDs are skipped
|
|
204
|
+
// with a warning rather than aborting the whole discover run.
|
|
205
|
+
try {
|
|
206
|
+
validateModuleId(moduleId, false);
|
|
207
|
+
}
|
|
208
|
+
catch (e) {
|
|
209
|
+
console.warn(`[apcore:registry] Skipping custom-discovered module with invalid ID '${moduleId}': ${e.message}`);
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
200
212
|
try {
|
|
201
213
|
this._registerImpl(moduleId, mod);
|
|
202
214
|
count++;
|
|
@@ -207,6 +219,20 @@ export class Registry {
|
|
|
207
219
|
}
|
|
208
220
|
return count;
|
|
209
221
|
}
|
|
222
|
+
/**
|
|
223
|
+
* Default discovery pipeline (D-32 — 8 canonical stages, mirroring
|
|
224
|
+
* apcore-rust `default_discoverer.rs`):
|
|
225
|
+
*
|
|
226
|
+
* 1. _ensureIdMap — lazy-load the optional id_map.json
|
|
227
|
+
* 2. _scanRoots — walk extension roots
|
|
228
|
+
* 3. _applyIdMapOverrides — rewrite canonical IDs from the map
|
|
229
|
+
* 4. _loadAllMetadata — load each module's `module.yaml`
|
|
230
|
+
* 5. _resolveAllEntryPoints — import the JS/TS entry point
|
|
231
|
+
* 6. _validateAll — run module/custom validators
|
|
232
|
+
* 7. _filterIdConflicts — batch-drop conflicting / invalid IDs
|
|
233
|
+
* 8. _resolveLoadOrder + _registerInOrder
|
|
234
|
+
* — topological sort then register.
|
|
235
|
+
*/
|
|
210
236
|
async _discoverDefault() {
|
|
211
237
|
await this._ensureIdMap();
|
|
212
238
|
const discovered = await this._scanRoots();
|
|
@@ -214,8 +240,9 @@ export class Registry {
|
|
|
214
240
|
const rawMetadata = await this._loadAllMetadata(discovered);
|
|
215
241
|
const resolvedModules = await this._resolveAllEntryPoints(discovered, rawMetadata);
|
|
216
242
|
const validModules = await this._validateAll(resolvedModules);
|
|
217
|
-
const
|
|
218
|
-
|
|
243
|
+
const filteredModules = this._filterIdConflicts(validModules, rawMetadata);
|
|
244
|
+
const loadOrder = this._resolveLoadOrder(filteredModules, rawMetadata);
|
|
245
|
+
return this._registerInOrder(loadOrder, filteredModules, rawMetadata);
|
|
219
246
|
}
|
|
220
247
|
async _scanRoots() {
|
|
221
248
|
let maxDepth = 8;
|
|
@@ -295,6 +322,56 @@ export class Registry {
|
|
|
295
322
|
}
|
|
296
323
|
return validModules;
|
|
297
324
|
}
|
|
325
|
+
/**
|
|
326
|
+
* Stage 7 (D-32) — batch-drop modules with invalid or conflicting IDs.
|
|
327
|
+
*
|
|
328
|
+
* Mirrors apcore-python `_filter_id_conflicts` and apcore-rust
|
|
329
|
+
* `default_discoverer::filter_id_conflicts`. Two failure modes drop a
|
|
330
|
+
* module here (warn + skip) rather than aborting the whole batch:
|
|
331
|
+
* - PROTOCOL_SPEC §2.7 ID validation (empty / pattern / length /
|
|
332
|
+
* reserved-word first segment), and
|
|
333
|
+
* - Algorithm A03 conflict detection (duplicate against an existing
|
|
334
|
+
* registration, lowercase collision, reserved-word collision).
|
|
335
|
+
*
|
|
336
|
+
* Soft-severity conflicts (e.g. case-insensitive match against an
|
|
337
|
+
* already-registered ID at `warn` level) are NOT dropped here — the
|
|
338
|
+
* warning is logged and the module flows through to registration so
|
|
339
|
+
* existing behaviour is preserved. Only `error`-severity conflicts and
|
|
340
|
+
* invalid IDs are filtered out.
|
|
341
|
+
*
|
|
342
|
+
* `rawMetadata` is accepted for cross-language signature parity with
|
|
343
|
+
* the Rust/Python helpers (which may inspect metadata for additional
|
|
344
|
+
* checks); the TS implementation reads only the module ID.
|
|
345
|
+
*/
|
|
346
|
+
_filterIdConflicts(validModules, _rawMetadata) {
|
|
347
|
+
const filtered = new Map();
|
|
348
|
+
// Track within-batch IDs (case-insensitive) so two newly-discovered
|
|
349
|
+
// modules whose IDs collide on lowercase don't both slip through.
|
|
350
|
+
const batchLowercase = new Map(this._lowercaseMap);
|
|
351
|
+
const batchIds = new Set(this._modules.keys());
|
|
352
|
+
for (const [modId, mod] of validModules.entries()) {
|
|
353
|
+
try {
|
|
354
|
+
validateModuleId(modId, false);
|
|
355
|
+
}
|
|
356
|
+
catch (e) {
|
|
357
|
+
console.warn(`[apcore:registry] Skipping discovered module with invalid ID '${modId}': ${e.message}`);
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
const conflict = detectIdConflicts(modId, batchIds, RESERVED_WORDS, batchLowercase);
|
|
361
|
+
if (conflict !== null) {
|
|
362
|
+
if (conflict.severity === 'error') {
|
|
363
|
+
console.warn(`[apcore:registry] Skipping discovered module '${modId}' due to ID conflict: ${conflict.message}`);
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
// Soft severity — log but keep the module.
|
|
367
|
+
console.warn(`[apcore:registry] ID conflict: ${conflict.message}`);
|
|
368
|
+
}
|
|
369
|
+
filtered.set(modId, mod);
|
|
370
|
+
batchIds.add(modId);
|
|
371
|
+
batchLowercase.set(modId.toLowerCase(), modId);
|
|
372
|
+
}
|
|
373
|
+
return filtered;
|
|
374
|
+
}
|
|
298
375
|
_resolveLoadOrder(validModules, rawMetadata) {
|
|
299
376
|
const modulesWithDeps = [];
|
|
300
377
|
const moduleVersions = new Map();
|
|
@@ -326,9 +403,13 @@ export class Registry {
|
|
|
326
403
|
return resolveDependencies(modulesWithDeps, knownIds, moduleVersions);
|
|
327
404
|
}
|
|
328
405
|
_registerInOrder(loadOrder, validModules, rawMetadata) {
|
|
406
|
+
// Stage 8 (D-32). Conflict detection / ID validation already happened in
|
|
407
|
+
// `_filterIdConflicts` (stage 7), so this loop is purely a register pass.
|
|
329
408
|
let count = 0;
|
|
330
409
|
for (const modId of loadOrder) {
|
|
331
410
|
const mod = validModules.get(modId);
|
|
411
|
+
if (mod === undefined)
|
|
412
|
+
continue;
|
|
332
413
|
const modObj = mod;
|
|
333
414
|
const mergedMeta = mergeModuleMetadata(modObj, rawMetadata.get(modId) ?? {});
|
|
334
415
|
this._modules.set(modId, mod);
|
|
@@ -357,8 +438,15 @@ export class Registry {
|
|
|
357
438
|
* Validation order (PROTOCOL_SPEC §2.7, aligned with apcore-python and
|
|
358
439
|
* apcore-rust): empty → pattern → length → reserved (per-segment) →
|
|
359
440
|
* duplicate.
|
|
441
|
+
*
|
|
442
|
+
* The optional `version` and `metadata` parameters mirror apcore-python's
|
|
443
|
+
* `Registry.register(module_id, module, version=None, metadata=None)` for
|
|
444
|
+
* cross-language signature parity (sync finding A-001). Multi-version
|
|
445
|
+
* coexistence is not yet implemented in this SDK — when supplied, both
|
|
446
|
+
* fields are merged into the module's metadata so callers can read them
|
|
447
|
+
* back via `getDefinition()` and `list({tags})`. See PROTOCOL_SPEC §5.4.
|
|
360
448
|
*/
|
|
361
|
-
register(moduleId, module) {
|
|
449
|
+
register(moduleId, module, version, metadata) {
|
|
362
450
|
validateModuleId(moduleId, false);
|
|
363
451
|
if (this._customValidator !== null) {
|
|
364
452
|
const result = this._customValidator.validate(module);
|
|
@@ -369,10 +457,14 @@ export class Registry {
|
|
|
369
457
|
throw new InvalidInputError(`Custom validator rejected module '${moduleId}': ${result.join('; ')}`);
|
|
370
458
|
}
|
|
371
459
|
}
|
|
372
|
-
|
|
460
|
+
const overrides = { ...(metadata ?? {}) };
|
|
461
|
+
if (version !== undefined && version !== null) {
|
|
462
|
+
overrides['version'] = version;
|
|
463
|
+
}
|
|
464
|
+
this._registerImpl(moduleId, module, overrides);
|
|
373
465
|
}
|
|
374
466
|
/** Inner registration — no validator, no ID validation. Used by discover() paths that run their own checks. */
|
|
375
|
-
_registerImpl(moduleId, module) {
|
|
467
|
+
_registerImpl(moduleId, module, metadataOverrides = {}) {
|
|
376
468
|
// Algorithm A03: detect ID conflicts (exact duplicate, reserved word, case collision)
|
|
377
469
|
const conflict = detectIdConflicts(moduleId, new Set(this._modules.keys()), RESERVED_WORDS, this._lowercaseMap);
|
|
378
470
|
if (conflict !== null) {
|
|
@@ -385,9 +477,10 @@ export class Registry {
|
|
|
385
477
|
}
|
|
386
478
|
this._modules.set(moduleId, module);
|
|
387
479
|
this._lowercaseMap.set(moduleId.toLowerCase(), moduleId);
|
|
388
|
-
// Populate metadata from the module object
|
|
480
|
+
// Populate metadata from the module object, layering any explicit overrides
|
|
481
|
+
// (e.g. the `version` / `metadata` args passed to `register()`) on top.
|
|
389
482
|
const modObj = module;
|
|
390
|
-
this._moduleMeta.set(moduleId, mergeModuleMetadata(modObj,
|
|
483
|
+
this._moduleMeta.set(moduleId, mergeModuleMetadata(modObj, metadataOverrides));
|
|
391
484
|
// Call onLoad if available
|
|
392
485
|
if (typeof modObj['onLoad'] === 'function') {
|
|
393
486
|
try {
|
|
@@ -423,7 +516,19 @@ export class Registry {
|
|
|
423
516
|
this._triggerEvent(REGISTRY_EVENTS.UNREGISTER, moduleId, module);
|
|
424
517
|
return true;
|
|
425
518
|
}
|
|
426
|
-
|
|
519
|
+
/**
|
|
520
|
+
* Look up a registered module by ID.
|
|
521
|
+
*
|
|
522
|
+
* @param moduleId - Module identifier (must be non-empty).
|
|
523
|
+
* @param _versionHint - Optional semver range for multi-version coexistence
|
|
524
|
+
* (PROTOCOL_SPEC §5.4). This SDK currently exposes a single-version
|
|
525
|
+
* registry, so the hint is accepted for cross-language API parity with
|
|
526
|
+
* apcore-python (sync finding A-002) but does NOT participate in
|
|
527
|
+
* resolution: the latest registered module for `moduleId` is returned
|
|
528
|
+
* regardless of the hint. When multi-version registration lands, this
|
|
529
|
+
* parameter will gate semver-range matching.
|
|
530
|
+
*/
|
|
531
|
+
get(moduleId, _versionHint) {
|
|
427
532
|
if (moduleId === '') {
|
|
428
533
|
throw new ModuleNotFoundError('');
|
|
429
534
|
}
|
|
@@ -465,7 +570,18 @@ export class Registry {
|
|
|
465
570
|
get moduleIds() {
|
|
466
571
|
return [...this._modules.keys()].sort();
|
|
467
572
|
}
|
|
468
|
-
getDefinition(moduleId) {
|
|
573
|
+
getDefinition(moduleId, _versionHint) {
|
|
574
|
+
// `_versionHint` accepted for cross-language API parity with apcore-python
|
|
575
|
+
// (sync finding A-002 / §5.4). Ignored under the single-version registry;
|
|
576
|
+
// see `get()` for the rationale.
|
|
577
|
+
//
|
|
578
|
+
// D10-011: spec registry-system.md:382 says any error that `get(module_id)`
|
|
579
|
+
// raises is propagated. The empty-string guard mirrors `get()` (line 669)
|
|
580
|
+
// so callers using getDefinition see the same ModuleNotFoundError as
|
|
581
|
+
// get(), matching apcore-python where getDefinition routes through get().
|
|
582
|
+
if (moduleId === '') {
|
|
583
|
+
throw new ModuleNotFoundError('');
|
|
584
|
+
}
|
|
469
585
|
const module = this._modules.get(moduleId);
|
|
470
586
|
if (module == null)
|
|
471
587
|
return null;
|
|
@@ -564,6 +680,29 @@ export class Registry {
|
|
|
564
680
|
}
|
|
565
681
|
}
|
|
566
682
|
}
|
|
683
|
+
/**
|
|
684
|
+
* Watch the configured extension roots for filesystem changes and
|
|
685
|
+
* unregister any module whose source file is modified or deleted.
|
|
686
|
+
*
|
|
687
|
+
* **Cross-language divergence (sync finding A-D-104):** unlike apcore-python
|
|
688
|
+
* (which re-imports the file via `importlib.reload`) and apcore-rust (which
|
|
689
|
+
* triggers full rediscovery), the TypeScript SDK is **event-only**. On a
|
|
690
|
+
* file change the registry:
|
|
691
|
+
* 1. unregisters the previously-loaded module (calling its `onUnload`),
|
|
692
|
+
* 2. emits a `'file_changed'` event with `{ filePath }` payload.
|
|
693
|
+
*
|
|
694
|
+
* Consumers are expected to subscribe and re-register the module
|
|
695
|
+
* themselves (e.g. by calling `registry.discover()` or registering a fresh
|
|
696
|
+
* import). ES module specifiers are immutable in Node — there is no
|
|
697
|
+
* portable "reload from disk" primitive — so a transparent dynamic
|
|
698
|
+
* `import()` would silently return the cached old module on every
|
|
699
|
+
* invocation. A workaround using a cache-busting query (`?v=Date.now()`)
|
|
700
|
+
* leaks the old module each reload and breaks browser bundlers, so it is
|
|
701
|
+
* intentionally **not** offered here.
|
|
702
|
+
*
|
|
703
|
+
* If your application needs Python-style hot-reload semantics, listen for
|
|
704
|
+
* `'file_changed'` and re-discover or re-import explicitly.
|
|
705
|
+
*/
|
|
567
706
|
async watch() {
|
|
568
707
|
if (this._watchers && this._watchers.length > 0) {
|
|
569
708
|
return; // Already watching
|
|
@@ -685,12 +824,30 @@ export class Registry {
|
|
|
685
824
|
*/
|
|
686
825
|
registerInternal(moduleId, module) {
|
|
687
826
|
validateModuleId(moduleId, true);
|
|
688
|
-
|
|
689
|
-
|
|
827
|
+
// D11-007: route duplicate detection through detectIdConflicts (with an
|
|
828
|
+
// empty reserved-words set so the bypass for system.* prefixes is
|
|
829
|
+
// preserved). This restores the case-collision branch present in
|
|
830
|
+
// apcore-python (registry.py:1674) and apcore-rust (registry.rs:727).
|
|
831
|
+
// The lowercase-only EBNF in validateModuleId makes case collisions
|
|
832
|
+
// unreachable today, but the contract surface stays aligned across SDKs.
|
|
833
|
+
const conflict = detectIdConflicts(moduleId, new Set(this._modules.keys()), new Set(), this._lowercaseMap);
|
|
834
|
+
if (conflict !== null) {
|
|
835
|
+
if (conflict.severity === 'error') {
|
|
836
|
+
throw new InvalidInputError(conflict.message);
|
|
837
|
+
}
|
|
838
|
+
else {
|
|
839
|
+
console.warn(`[apcore:registry] ID conflict: ${conflict.message}`);
|
|
840
|
+
}
|
|
690
841
|
}
|
|
691
842
|
this._modules.set(moduleId, module);
|
|
692
843
|
const modObj = module;
|
|
693
844
|
this._moduleMeta.set(moduleId, mergeModuleMetadata(modObj, {}));
|
|
845
|
+
// Mirror apcore-python register_internal and apcore-rust register_core:
|
|
846
|
+
// every registration site (including sys/internal) populates the lowercase
|
|
847
|
+
// index. The lowercase-only EBNF pattern enforced by validateModuleId makes
|
|
848
|
+
// case collisions unreachable today, but keeping _lowercaseMap consistent
|
|
849
|
+
// with _modules preserves the invariant for downstream conflict detection.
|
|
850
|
+
this._lowercaseMap.set(moduleId.toLowerCase(), moduleId);
|
|
694
851
|
if (typeof modObj['onLoad'] === 'function') {
|
|
695
852
|
try {
|
|
696
853
|
modObj['onLoad']();
|
|
@@ -698,6 +855,7 @@ export class Registry {
|
|
|
698
855
|
catch (e) {
|
|
699
856
|
this._modules.delete(moduleId);
|
|
700
857
|
this._moduleMeta.delete(moduleId);
|
|
858
|
+
this._lowercaseMap.delete(moduleId.toLowerCase());
|
|
701
859
|
throw e;
|
|
702
860
|
}
|
|
703
861
|
}
|
|
@@ -728,6 +886,18 @@ export class Registry {
|
|
|
728
886
|
this._schemaCache.clear();
|
|
729
887
|
}
|
|
730
888
|
// ── Safe Hot-Reload (F09 / Algorithm A21) ───────────────────────
|
|
889
|
+
/**
|
|
890
|
+
* Discover module IDs for the classes in a single file under multi-class
|
|
891
|
+
* mode (PROTOCOL_SPEC §2.1.1).
|
|
892
|
+
*
|
|
893
|
+
* D-15: cross-language alignment with Python `Registry.discover_multi_class`
|
|
894
|
+
* and the Rust trait method. Internally delegates to the free function
|
|
895
|
+
* {@link discoverMultiClass} (re-exported as `_discoverMultiClass` for
|
|
896
|
+
* scanner internals), so behaviour is identical.
|
|
897
|
+
*/
|
|
898
|
+
discoverMultiClass(filePath, classes, extensionsRoot = 'extensions', multiClassEnabled = false) {
|
|
899
|
+
return _discoverMultiClass(filePath, classes, extensionsRoot, multiClassEnabled);
|
|
900
|
+
}
|
|
731
901
|
/**
|
|
732
902
|
* Number of in-flight executions per module.
|
|
733
903
|
*/
|