apcore-js 0.17.1 → 0.19.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/LICENSE +190 -0
- package/README.md +14 -10
- package/dist/acl-handlers.d.ts +14 -0
- package/dist/acl-handlers.d.ts.map +1 -1
- package/dist/acl-handlers.js +37 -4
- package/dist/acl-handlers.js.map +1 -1
- package/dist/acl.d.ts +4 -3
- package/dist/acl.d.ts.map +1 -1
- package/dist/acl.js +44 -40
- package/dist/acl.js.map +1 -1
- package/dist/async-task.d.ts +6 -5
- package/dist/async-task.d.ts.map +1 -1
- package/dist/async-task.js +10 -6
- package/dist/async-task.js.map +1 -1
- package/dist/bindings.d.ts.map +1 -1
- package/dist/bindings.js +113 -11
- package/dist/bindings.js.map +1 -1
- package/dist/builtin-steps.d.ts +19 -5
- package/dist/builtin-steps.d.ts.map +1 -1
- package/dist/builtin-steps.js +83 -27
- package/dist/builtin-steps.js.map +1 -1
- package/dist/client.d.ts +2 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +8 -6
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +8 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +44 -6
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +34 -7
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +97 -40
- package/dist/context.js.map +1 -1
- package/dist/decorator.d.ts +3 -0
- package/dist/decorator.d.ts.map +1 -1
- package/dist/decorator.js +17 -1
- package/dist/decorator.js.map +1 -1
- package/dist/errors.d.ts +65 -16
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +191 -82
- package/dist/errors.js.map +1 -1
- package/dist/events/emitter.d.ts +4 -1
- package/dist/events/emitter.d.ts.map +1 -1
- package/dist/events/emitter.js +26 -16
- package/dist/events/emitter.js.map +1 -1
- package/dist/executor.d.ts +9 -9
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +62 -38
- 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 +21 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -7
- package/dist/index.js.map +1 -1
- package/dist/middleware/index.d.ts +1 -1
- package/dist/middleware/index.d.ts.map +1 -1
- package/dist/middleware/index.js +1 -1
- package/dist/middleware/index.js.map +1 -1
- package/dist/middleware/logging.d.ts +6 -0
- package/dist/middleware/logging.d.ts.map +1 -1
- package/dist/middleware/logging.js +13 -3
- package/dist/middleware/logging.js.map +1 -1
- package/dist/middleware/manager.d.ts.map +1 -1
- package/dist/middleware/manager.js +1 -0
- package/dist/middleware/manager.js.map +1 -1
- package/dist/middleware/platform-notify.d.ts +1 -1
- package/dist/middleware/platform-notify.d.ts.map +1 -1
- package/dist/middleware/platform-notify.js +5 -3
- package/dist/middleware/platform-notify.js.map +1 -1
- package/dist/middleware/retry.d.ts +16 -7
- package/dist/middleware/retry.d.ts.map +1 -1
- package/dist/middleware/retry.js +21 -15
- package/dist/middleware/retry.js.map +1 -1
- package/dist/module.d.ts +8 -2
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +10 -3
- package/dist/module.js.map +1 -1
- package/dist/observability/context-logger.d.ts.map +1 -1
- package/dist/observability/context-logger.js +18 -5
- package/dist/observability/context-logger.js.map +1 -1
- package/dist/observability/metrics-utils.d.ts.map +1 -1
- package/dist/observability/metrics-utils.js +3 -5
- package/dist/observability/metrics-utils.js.map +1 -1
- package/dist/observability/metrics.d.ts +1 -0
- package/dist/observability/metrics.d.ts.map +1 -1
- package/dist/observability/metrics.js +14 -1
- package/dist/observability/metrics.js.map +1 -1
- package/dist/observability/tracing.d.ts +2 -0
- package/dist/observability/tracing.d.ts.map +1 -1
- package/dist/observability/tracing.js +12 -2
- package/dist/observability/tracing.js.map +1 -1
- package/dist/observability/usage.d.ts.map +1 -1
- package/dist/observability/usage.js +10 -1
- package/dist/observability/usage.js.map +1 -1
- package/dist/pipeline-config.d.ts +13 -9
- package/dist/pipeline-config.d.ts.map +1 -1
- package/dist/pipeline-config.js +77 -13
- package/dist/pipeline-config.js.map +1 -1
- package/dist/registry/conflicts.d.ts +29 -0
- package/dist/registry/conflicts.d.ts.map +1 -0
- package/dist/registry/conflicts.js +61 -0
- package/dist/registry/conflicts.js.map +1 -0
- package/dist/registry/dependencies.d.ts +1 -1
- package/dist/registry/dependencies.d.ts.map +1 -1
- package/dist/registry/dependencies.js +69 -20
- package/dist/registry/dependencies.js.map +1 -1
- package/dist/registry/entry-point.d.ts.map +1 -1
- package/dist/registry/entry-point.js.map +1 -1
- package/dist/registry/index.d.ts +5 -2
- package/dist/registry/index.d.ts.map +1 -1
- package/dist/registry/index.js +4 -2
- package/dist/registry/index.js.map +1 -1
- package/dist/registry/metadata.d.ts.map +1 -1
- package/dist/registry/metadata.js +24 -3
- package/dist/registry/metadata.js.map +1 -1
- package/dist/registry/registry.d.ts +40 -4
- package/dist/registry/registry.d.ts.map +1 -1
- package/dist/registry/registry.js +222 -53
- package/dist/registry/registry.js.map +1 -1
- package/dist/registry/scanner.d.ts.map +1 -1
- package/dist/registry/scanner.js +6 -0
- package/dist/registry/scanner.js.map +1 -1
- package/dist/registry/version.d.ts +50 -0
- package/dist/registry/version.d.ts.map +1 -0
- package/dist/registry/version.js +198 -0
- package/dist/registry/version.js.map +1 -0
- package/dist/schema/exporter.js +2 -2
- package/dist/schema/extractor.d.ts +69 -0
- package/dist/schema/extractor.d.ts.map +1 -0
- package/dist/schema/extractor.js +142 -0
- package/dist/schema/extractor.js.map +1 -0
- package/dist/schema/index.d.ts +2 -0
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +1 -0
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/loader.js +7 -7
- package/dist/schema/loader.js.map +1 -1
- package/dist/schema/ref-resolver.d.ts.map +1 -1
- package/dist/schema/ref-resolver.js +10 -1
- package/dist/schema/ref-resolver.js.map +1 -1
- package/dist/schema/types.d.ts +6 -6
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/schema/types.js +6 -6
- package/dist/schema/types.js.map +1 -1
- package/dist/sys-modules/control.d.ts +79 -1
- package/dist/sys-modules/control.d.ts.map +1 -1
- package/dist/sys-modules/control.js +44 -11
- package/dist/sys-modules/control.js.map +1 -1
- package/dist/sys-modules/health.d.ts +89 -1
- package/dist/sys-modules/health.d.ts.map +1 -1
- package/dist/sys-modules/health.js +41 -1
- package/dist/sys-modules/health.js.map +1 -1
- package/dist/sys-modules/index.d.ts +5 -5
- package/dist/sys-modules/index.d.ts.map +1 -1
- package/dist/sys-modules/index.js +5 -5
- package/dist/sys-modules/index.js.map +1 -1
- package/dist/sys-modules/manifest.d.ts +98 -1
- package/dist/sys-modules/manifest.d.ts.map +1 -1
- package/dist/sys-modules/manifest.js +43 -1
- package/dist/sys-modules/manifest.js.map +1 -1
- package/dist/sys-modules/registration.d.ts +12 -0
- package/dist/sys-modules/registration.d.ts.map +1 -1
- package/dist/sys-modules/registration.js +20 -8
- package/dist/sys-modules/registration.js.map +1 -1
- package/dist/sys-modules/toggle.d.ts +39 -2
- package/dist/sys-modules/toggle.d.ts.map +1 -1
- package/dist/sys-modules/toggle.js +23 -6
- package/dist/sys-modules/toggle.js.map +1 -1
- package/dist/sys-modules/usage.d.ts +92 -1
- package/dist/sys-modules/usage.d.ts.map +1 -1
- package/dist/sys-modules/usage.js +42 -1
- package/dist/sys-modules/usage.js.map +1 -1
- package/dist/trace-context.d.ts +3 -4
- package/dist/trace-context.d.ts.map +1 -1
- package/dist/trace-context.js +4 -5
- package/dist/trace-context.js.map +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -1
- package/dist/utils/index.js.map +1 -1
- package/package.json +9 -5
|
@@ -17,10 +17,15 @@ export declare const REGISTRY_EVENTS: Readonly<{
|
|
|
17
17
|
export declare const MODULE_ID_PATTERN: RegExp;
|
|
18
18
|
/**
|
|
19
19
|
* Maximum allowed length for a module ID.
|
|
20
|
+
*
|
|
21
|
+
* Per PROTOCOL_SPEC §2.7 EBNF constraint #1. 192 is filesystem-safe
|
|
22
|
+
* (192 + ".binding.yaml".length = 205 < 255-byte filename limit on
|
|
23
|
+
* ext4/xfs/NTFS/APFS/btrfs) and accommodates Java/.NET deep-namespace
|
|
24
|
+
* FQN-derived IDs. Bumped from 128 in spec 1.6.0-draft (2026-04-08).
|
|
20
25
|
*/
|
|
21
|
-
export declare const MAX_MODULE_ID_LENGTH =
|
|
26
|
+
export declare const MAX_MODULE_ID_LENGTH = 192;
|
|
22
27
|
/**
|
|
23
|
-
* Reserved words that cannot appear as
|
|
28
|
+
* Reserved words that cannot appear as the first segment of a module ID.
|
|
24
29
|
*/
|
|
25
30
|
export declare const RESERVED_WORDS: Set<string>;
|
|
26
31
|
/**
|
|
@@ -48,6 +53,7 @@ export declare class Registry {
|
|
|
48
53
|
private _moduleMeta;
|
|
49
54
|
private _callbacks;
|
|
50
55
|
private _idMap;
|
|
56
|
+
private _lowercaseMap;
|
|
51
57
|
private _schemaCache;
|
|
52
58
|
private _config;
|
|
53
59
|
private _watchers?;
|
|
@@ -79,7 +85,16 @@ export declare class Registry {
|
|
|
79
85
|
private _validateAll;
|
|
80
86
|
private _resolveLoadOrder;
|
|
81
87
|
private _registerInOrder;
|
|
88
|
+
/**
|
|
89
|
+
* Register a module.
|
|
90
|
+
*
|
|
91
|
+
* Validation order (PROTOCOL_SPEC §2.7, aligned with apcore-python and
|
|
92
|
+
* apcore-rust): empty → pattern → length → reserved (per-segment) →
|
|
93
|
+
* duplicate.
|
|
94
|
+
*/
|
|
82
95
|
register(moduleId: string, module: unknown): void;
|
|
96
|
+
/** Inner registration — no validator, no ID validation. Used by discover() paths that run their own checks. */
|
|
97
|
+
private _registerImpl;
|
|
83
98
|
unregister(moduleId: string): boolean;
|
|
84
99
|
get(moduleId: string): unknown | null;
|
|
85
100
|
has(moduleId: string): boolean;
|
|
@@ -93,6 +108,8 @@ export declare class Registry {
|
|
|
93
108
|
getDefinition(moduleId: string): ModuleDescriptor | null;
|
|
94
109
|
describe(moduleId: string): string;
|
|
95
110
|
on(event: string, callback: EventCallback): void;
|
|
111
|
+
off(event: string, callback: EventCallback): boolean;
|
|
112
|
+
reload(): Promise<number>;
|
|
96
113
|
private _triggerEvent;
|
|
97
114
|
watch(): Promise<void>;
|
|
98
115
|
unwatch(): void;
|
|
@@ -100,10 +117,25 @@ export declare class Registry {
|
|
|
100
117
|
private _handleFileDeletion;
|
|
101
118
|
private _pathToModuleId;
|
|
102
119
|
/**
|
|
103
|
-
* Register a module
|
|
104
|
-
*
|
|
120
|
+
* Register a sys/internal module that bypasses **only** the reserved word
|
|
121
|
+
* check. All other PROTOCOL_SPEC §2.7 validations (empty, EBNF pattern,
|
|
122
|
+
* length, duplicate) still apply.
|
|
123
|
+
*
|
|
124
|
+
* Used exclusively by the sys-modules subsystem for `system.*` IDs.
|
|
125
|
+
* Aligned with apcore-python `Registry.register_internal` and apcore-rust
|
|
126
|
+
* `Registry::register_internal`.
|
|
105
127
|
*/
|
|
106
128
|
registerInternal(moduleId: string, module: unknown): void;
|
|
129
|
+
/**
|
|
130
|
+
* Export the JSON Schema for a registered module.
|
|
131
|
+
*
|
|
132
|
+
* Returns the schema as a plain object (`Record<string, unknown> | null`),
|
|
133
|
+
* matching Python's `dict | None` and Rust's `Option<Value>` return types.
|
|
134
|
+
* Returns `null` if the module is not registered.
|
|
135
|
+
* Use the standalone `exportSchema` function from `schema-export.ts` for
|
|
136
|
+
* serialized (JSON/YAML) output.
|
|
137
|
+
*/
|
|
138
|
+
exportSchema(moduleId: string, strict?: boolean): Record<string, unknown> | null;
|
|
107
139
|
clearCache(): void;
|
|
108
140
|
/**
|
|
109
141
|
* Number of in-flight executions per module.
|
|
@@ -133,6 +165,10 @@ export declare class Registry {
|
|
|
133
165
|
beginDrain(moduleId: string): void;
|
|
134
166
|
/**
|
|
135
167
|
* Remove the draining mark and clean up drain state.
|
|
168
|
+
*
|
|
169
|
+
* If any waitDrained waiters are still pending (e.g., refCount briefly
|
|
170
|
+
* hit zero and then a new acquire bumped it back up), resolve them first
|
|
171
|
+
* so they do not wait for their individual timeouts before returning.
|
|
136
172
|
*/
|
|
137
173
|
endDrain(moduleId: string): void;
|
|
138
174
|
/**
|
|
@@ -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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"}
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Central module registry for discovering, registering, and querying modules.
|
|
3
3
|
*/
|
|
4
|
+
import { getDefault } from '../config.js';
|
|
4
5
|
import { InvalidInputError, ModuleNotFoundError } from '../errors.js';
|
|
6
|
+
import { detectIdConflicts } from './conflicts.js';
|
|
5
7
|
import { resolveDependencies } from './dependencies.js';
|
|
6
8
|
import { resolveEntryPoint } from './entry-point.js';
|
|
7
9
|
import { mergeModuleMetadata, parseDependencies } from './metadata.js';
|
|
10
|
+
import { getSchema } from './schema-export.js';
|
|
11
|
+
import { toStrictSchema } from '../schema/strict.js';
|
|
12
|
+
import { deepCopy } from '../utils/index.js';
|
|
8
13
|
import { validateModule } from './validation.js';
|
|
9
14
|
// ── Lazy-loaded Node.js modules ────────────────────────────────────
|
|
10
15
|
// These are loaded on first use so that importing Registry in a browser
|
|
@@ -52,12 +57,56 @@ export const REGISTRY_EVENTS = Object.freeze({
|
|
|
52
57
|
export const MODULE_ID_PATTERN = /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)*$/;
|
|
53
58
|
/**
|
|
54
59
|
* Maximum allowed length for a module ID.
|
|
60
|
+
*
|
|
61
|
+
* Per PROTOCOL_SPEC §2.7 EBNF constraint #1. 192 is filesystem-safe
|
|
62
|
+
* (192 + ".binding.yaml".length = 205 < 255-byte filename limit on
|
|
63
|
+
* ext4/xfs/NTFS/APFS/btrfs) and accommodates Java/.NET deep-namespace
|
|
64
|
+
* FQN-derived IDs. Bumped from 128 in spec 1.6.0-draft (2026-04-08).
|
|
55
65
|
*/
|
|
56
|
-
export const MAX_MODULE_ID_LENGTH =
|
|
66
|
+
export const MAX_MODULE_ID_LENGTH = 192;
|
|
57
67
|
/**
|
|
58
|
-
* Reserved words that cannot appear as
|
|
68
|
+
* Reserved words that cannot appear as the first segment of a module ID.
|
|
59
69
|
*/
|
|
60
70
|
export const RESERVED_WORDS = new Set(['system', 'internal', 'core', 'apcore', 'plugin', 'schema', 'acl']);
|
|
71
|
+
/**
|
|
72
|
+
* Validate a module ID against PROTOCOL_SPEC §2.7 in canonical order.
|
|
73
|
+
*
|
|
74
|
+
* Order: empty → pattern → length → reserved (first-segment).
|
|
75
|
+
* Duplicate detection is the caller's responsibility (it requires registry
|
|
76
|
+
* state).
|
|
77
|
+
*
|
|
78
|
+
* When `allowReserved` is true the first-segment reserved word check is
|
|
79
|
+
* skipped — used by `Registry.registerInternal` so sys modules can use the
|
|
80
|
+
* `system.*` prefix. All other validations (empty, pattern, length) still
|
|
81
|
+
* apply.
|
|
82
|
+
*
|
|
83
|
+
* Aligned with `apcore-python._validate_module_id` and
|
|
84
|
+
* `apcore::registry::registry::validate_module_id`.
|
|
85
|
+
*
|
|
86
|
+
* @internal
|
|
87
|
+
*/
|
|
88
|
+
function validateModuleId(moduleId, allowReserved) {
|
|
89
|
+
// 1. empty check (message byte-aligned with apcore-python and apcore-rust)
|
|
90
|
+
if (!moduleId || typeof moduleId !== 'string') {
|
|
91
|
+
throw new InvalidInputError('module_id must be a non-empty string');
|
|
92
|
+
}
|
|
93
|
+
// 2. EBNF pattern check (message byte-aligned with apcore-python and apcore-rust:
|
|
94
|
+
// single quotes around the offending ID; bare regex source without /…/ delimiters)
|
|
95
|
+
if (!MODULE_ID_PATTERN.test(moduleId)) {
|
|
96
|
+
throw new InvalidInputError(`Invalid module ID: '${moduleId}'. Must match pattern: ${MODULE_ID_PATTERN.source} (lowercase, digits, underscores, dots only; no hyphens)`);
|
|
97
|
+
}
|
|
98
|
+
// 3. length check
|
|
99
|
+
if (moduleId.length > MAX_MODULE_ID_LENGTH) {
|
|
100
|
+
throw new InvalidInputError(`Module ID exceeds maximum length of ${MAX_MODULE_ID_LENGTH}: ${moduleId.length}`);
|
|
101
|
+
}
|
|
102
|
+
// 4. reserved word first-segment check (skipped for registerInternal)
|
|
103
|
+
if (!allowReserved) {
|
|
104
|
+
const firstSegment = moduleId.split('.')[0];
|
|
105
|
+
if (RESERVED_WORDS.has(firstSegment)) {
|
|
106
|
+
throw new InvalidInputError(`Module ID contains reserved word: '${firstSegment}'`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
61
110
|
export class Registry {
|
|
62
111
|
_extensionRoots;
|
|
63
112
|
_modules = new Map();
|
|
@@ -67,6 +116,7 @@ export class Registry {
|
|
|
67
116
|
[REGISTRY_EVENTS.UNREGISTER, []],
|
|
68
117
|
]);
|
|
69
118
|
_idMap = {};
|
|
119
|
+
_lowercaseMap = new Map();
|
|
70
120
|
_schemaCache = new Map();
|
|
71
121
|
_config;
|
|
72
122
|
_watchers;
|
|
@@ -94,10 +144,10 @@ export class Registry {
|
|
|
94
144
|
}
|
|
95
145
|
else if (config !== null) {
|
|
96
146
|
const extRoot = config.get('extensions.root');
|
|
97
|
-
this._extensionRoots = [{ root: extRoot ?? '
|
|
147
|
+
this._extensionRoots = [{ root: extRoot ?? getDefault('extensions.root') }];
|
|
98
148
|
}
|
|
99
149
|
else {
|
|
100
|
-
this._extensionRoots = [{ root: '
|
|
150
|
+
this._extensionRoots = [{ root: getDefault('extensions.root') }];
|
|
101
151
|
}
|
|
102
152
|
this._config = config;
|
|
103
153
|
this._idMapPath = options?.idMapPath ?? null;
|
|
@@ -124,9 +174,21 @@ export class Registry {
|
|
|
124
174
|
async _discoverCustom() {
|
|
125
175
|
const rootPaths = this._extensionRoots.map((r) => r['root']);
|
|
126
176
|
const customModules = await this._customDiscoverer.discover(rootPaths);
|
|
177
|
+
if (!Array.isArray(customModules)) {
|
|
178
|
+
console.warn(`[apcore:registry] Custom discoverer returned non-array (${typeof customModules}); expected Array<{moduleId, module}>. Ignoring.`);
|
|
179
|
+
return 0;
|
|
180
|
+
}
|
|
127
181
|
let count = 0;
|
|
128
182
|
for (const entry of customModules) {
|
|
183
|
+
if (entry === null || typeof entry !== 'object') {
|
|
184
|
+
console.warn(`[apcore:registry] Malformed entry from custom discoverer (expected object, got ${entry === null ? 'null' : typeof entry}); skipping.`);
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
129
187
|
const { moduleId, module: mod } = entry;
|
|
188
|
+
if (typeof moduleId !== 'string' || mod === undefined) {
|
|
189
|
+
console.warn(`[apcore:registry] Malformed entry from custom discoverer (missing 'moduleId' string or 'module'); skipping.`);
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
130
192
|
// Apply custom validator if set
|
|
131
193
|
if (this._customValidator !== null) {
|
|
132
194
|
const errors = await this._customValidator.validate(mod);
|
|
@@ -136,7 +198,7 @@ export class Registry {
|
|
|
136
198
|
}
|
|
137
199
|
}
|
|
138
200
|
try {
|
|
139
|
-
this.
|
|
201
|
+
this._registerImpl(moduleId, mod);
|
|
140
202
|
count++;
|
|
141
203
|
}
|
|
142
204
|
catch (e) {
|
|
@@ -180,7 +242,13 @@ export class Registry {
|
|
|
180
242
|
? dm.filePath.slice(root.length + 1)
|
|
181
243
|
: null;
|
|
182
244
|
if (relPath && relPath in this._idMap) {
|
|
183
|
-
|
|
245
|
+
const rawId = this._idMap[relPath]['id'];
|
|
246
|
+
if (typeof rawId === 'string' && rawId.length > 0) {
|
|
247
|
+
dm.canonicalId = rawId;
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
console.warn(`[apcore:registry] ID map entry for '${relPath}' has invalid 'id' field (got ${typeof rawId}), skipping override`);
|
|
251
|
+
}
|
|
184
252
|
break;
|
|
185
253
|
}
|
|
186
254
|
}
|
|
@@ -229,13 +297,33 @@ export class Registry {
|
|
|
229
297
|
}
|
|
230
298
|
_resolveLoadOrder(validModules, rawMetadata) {
|
|
231
299
|
const modulesWithDeps = [];
|
|
232
|
-
|
|
300
|
+
const moduleVersions = new Map();
|
|
301
|
+
for (const [modId, cls] of validModules.entries()) {
|
|
233
302
|
const meta = rawMetadata.get(modId) ?? {};
|
|
234
303
|
const depsRaw = meta['dependencies'] ?? [];
|
|
235
304
|
modulesWithDeps.push([modId, depsRaw.length > 0 ? parseDependencies(depsRaw) : []]);
|
|
305
|
+
const yamlVersion = meta['version'];
|
|
306
|
+
const codeVersion = cls?.version;
|
|
307
|
+
const resolvedVersion = (typeof yamlVersion === 'string' && yamlVersion) ||
|
|
308
|
+
(typeof codeVersion === 'string' && codeVersion) ||
|
|
309
|
+
'1.0.0';
|
|
310
|
+
moduleVersions.set(modId, resolvedVersion);
|
|
311
|
+
}
|
|
312
|
+
// Include already-registered modules so inter-batch version constraints
|
|
313
|
+
// resolve against the live registry too.
|
|
314
|
+
for (const [existingId, existingMod] of this._modules.entries()) {
|
|
315
|
+
if (!moduleVersions.has(existingId)) {
|
|
316
|
+
const existingVersion = existingMod?.version;
|
|
317
|
+
if (typeof existingVersion === 'string') {
|
|
318
|
+
moduleVersions.set(existingId, existingVersion);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
236
321
|
}
|
|
237
|
-
const knownIds = new Set(
|
|
238
|
-
|
|
322
|
+
const knownIds = new Set([
|
|
323
|
+
...modulesWithDeps.map(([id]) => id),
|
|
324
|
+
...this._modules.keys(),
|
|
325
|
+
]);
|
|
326
|
+
return resolveDependencies(modulesWithDeps, knownIds, moduleVersions);
|
|
239
327
|
}
|
|
240
328
|
_registerInOrder(loadOrder, validModules, rawMetadata) {
|
|
241
329
|
let count = 0;
|
|
@@ -245,6 +333,7 @@ export class Registry {
|
|
|
245
333
|
const mergedMeta = mergeModuleMetadata(modObj, rawMetadata.get(modId) ?? {});
|
|
246
334
|
this._modules.set(modId, mod);
|
|
247
335
|
this._moduleMeta.set(modId, mergedMeta);
|
|
336
|
+
this._lowercaseMap.set(modId.toLowerCase(), modId);
|
|
248
337
|
if (typeof modObj['onLoad'] === 'function') {
|
|
249
338
|
try {
|
|
250
339
|
modObj['onLoad']();
|
|
@@ -253,6 +342,7 @@ export class Registry {
|
|
|
253
342
|
console.warn(`[apcore:registry] onLoad failed for ${modId}, skipping:`, e);
|
|
254
343
|
this._modules.delete(modId);
|
|
255
344
|
this._moduleMeta.delete(modId);
|
|
345
|
+
this._lowercaseMap.delete(modId.toLowerCase());
|
|
256
346
|
continue;
|
|
257
347
|
}
|
|
258
348
|
}
|
|
@@ -261,26 +351,40 @@ export class Registry {
|
|
|
261
351
|
}
|
|
262
352
|
return count;
|
|
263
353
|
}
|
|
354
|
+
/**
|
|
355
|
+
* Register a module.
|
|
356
|
+
*
|
|
357
|
+
* Validation order (PROTOCOL_SPEC §2.7, aligned with apcore-python and
|
|
358
|
+
* apcore-rust): empty → pattern → length → reserved (per-segment) →
|
|
359
|
+
* duplicate.
|
|
360
|
+
*/
|
|
264
361
|
register(moduleId, module) {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
if (RESERVED_WORDS.has(part)) {
|
|
274
|
-
throw new InvalidInputError(`Module ID contains reserved word: '${part}'`);
|
|
362
|
+
validateModuleId(moduleId, false);
|
|
363
|
+
if (this._customValidator !== null) {
|
|
364
|
+
const result = this._customValidator.validate(module);
|
|
365
|
+
if (result instanceof Promise) {
|
|
366
|
+
throw new InvalidInputError(`Custom validator for '${moduleId}' is async — use discover() which awaits the validator, or register after awaiting validation manually.`);
|
|
367
|
+
}
|
|
368
|
+
if (result.length > 0) {
|
|
369
|
+
throw new InvalidInputError(`Custom validator rejected module '${moduleId}': ${result.join('; ')}`);
|
|
275
370
|
}
|
|
276
371
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
372
|
+
this._registerImpl(moduleId, module);
|
|
373
|
+
}
|
|
374
|
+
/** Inner registration — no validator, no ID validation. Used by discover() paths that run their own checks. */
|
|
375
|
+
_registerImpl(moduleId, module) {
|
|
376
|
+
// Algorithm A03: detect ID conflicts (exact duplicate, reserved word, case collision)
|
|
377
|
+
const conflict = detectIdConflicts(moduleId, new Set(this._modules.keys()), RESERVED_WORDS, this._lowercaseMap);
|
|
378
|
+
if (conflict !== null) {
|
|
379
|
+
if (conflict.severity === 'error') {
|
|
380
|
+
throw new InvalidInputError(conflict.message);
|
|
381
|
+
}
|
|
382
|
+
else {
|
|
383
|
+
console.warn(`[apcore:registry] ID conflict: ${conflict.message}`);
|
|
384
|
+
}
|
|
282
385
|
}
|
|
283
386
|
this._modules.set(moduleId, module);
|
|
387
|
+
this._lowercaseMap.set(moduleId.toLowerCase(), moduleId);
|
|
284
388
|
// Populate metadata from the module object
|
|
285
389
|
const modObj = module;
|
|
286
390
|
this._moduleMeta.set(moduleId, mergeModuleMetadata(modObj, {}));
|
|
@@ -292,6 +396,7 @@ export class Registry {
|
|
|
292
396
|
catch (e) {
|
|
293
397
|
this._modules.delete(moduleId);
|
|
294
398
|
this._moduleMeta.delete(moduleId);
|
|
399
|
+
this._lowercaseMap.delete(moduleId.toLowerCase());
|
|
295
400
|
throw e;
|
|
296
401
|
}
|
|
297
402
|
}
|
|
@@ -304,6 +409,7 @@ export class Registry {
|
|
|
304
409
|
this._modules.delete(moduleId);
|
|
305
410
|
this._moduleMeta.delete(moduleId);
|
|
306
411
|
this._schemaCache.delete(moduleId);
|
|
412
|
+
this._lowercaseMap.delete(moduleId.toLowerCase());
|
|
307
413
|
// Call onUnload if available
|
|
308
414
|
const modObj = module;
|
|
309
415
|
if (typeof modObj['onUnload'] === 'function') {
|
|
@@ -363,21 +469,27 @@ export class Registry {
|
|
|
363
469
|
const module = this._modules.get(moduleId);
|
|
364
470
|
if (module == null)
|
|
365
471
|
return null;
|
|
472
|
+
// INVARIANT: every registration site (`register`, `registerInternal`,
|
|
473
|
+
// `_discoverDefault`) populates `_moduleMeta` via `mergeModuleMetadata`,
|
|
474
|
+
// so `meta` always contains the full set of canonical keys including
|
|
475
|
+
// an `annotations` slot. Read fields directly from it. The schemas
|
|
476
|
+
// come straight from the module instance because they are not part
|
|
477
|
+
// of the merged metadata payload.
|
|
366
478
|
const meta = this._moduleMeta.get(moduleId) ?? {};
|
|
367
479
|
const mod = module;
|
|
368
480
|
return {
|
|
369
481
|
moduleId,
|
|
370
|
-
name:
|
|
371
|
-
description:
|
|
372
|
-
documentation:
|
|
482
|
+
name: meta['name'] ?? null,
|
|
483
|
+
description: meta['description'] ?? '',
|
|
484
|
+
documentation: meta['documentation'] ?? null,
|
|
373
485
|
inputSchema: mod['inputSchema'] ?? {},
|
|
374
486
|
outputSchema: mod['outputSchema'] ?? {},
|
|
375
|
-
version:
|
|
376
|
-
tags: meta['tags'] ??
|
|
377
|
-
annotations:
|
|
378
|
-
examples:
|
|
487
|
+
version: meta['version'] ?? '1.0.0',
|
|
488
|
+
tags: meta['tags'] ?? [],
|
|
489
|
+
annotations: meta['annotations'] ?? null,
|
|
490
|
+
examples: meta['examples'] ?? [],
|
|
379
491
|
metadata: meta['metadata'] ?? {},
|
|
380
|
-
sunsetDate:
|
|
492
|
+
sunsetDate: meta['sunsetDate'] ?? null,
|
|
381
493
|
};
|
|
382
494
|
}
|
|
383
495
|
describe(moduleId) {
|
|
@@ -426,6 +538,21 @@ export class Registry {
|
|
|
426
538
|
}
|
|
427
539
|
this._callbacks.get(event).push(callback);
|
|
428
540
|
}
|
|
541
|
+
off(event, callback) {
|
|
542
|
+
const validEvents = Object.values(REGISTRY_EVENTS);
|
|
543
|
+
if (!validEvents.includes(event)) {
|
|
544
|
+
throw new InvalidInputError(`Invalid event: ${event}. Must be one of: ${validEvents.map((e) => `'${e}'`).join(', ')}`);
|
|
545
|
+
}
|
|
546
|
+
const callbacks = this._callbacks.get(event);
|
|
547
|
+
const idx = callbacks.indexOf(callback);
|
|
548
|
+
if (idx === -1)
|
|
549
|
+
return false;
|
|
550
|
+
callbacks.splice(idx, 1);
|
|
551
|
+
return true;
|
|
552
|
+
}
|
|
553
|
+
async reload() {
|
|
554
|
+
return this.discover();
|
|
555
|
+
}
|
|
429
556
|
_triggerEvent(event, moduleId, module) {
|
|
430
557
|
const callbacks = this._callbacks.get(event) ?? [];
|
|
431
558
|
for (const cb of callbacks) {
|
|
@@ -461,24 +588,32 @@ export class Registry {
|
|
|
461
588
|
if (now - last < 300)
|
|
462
589
|
return;
|
|
463
590
|
this._debounceTimers?.set(fullPath, now);
|
|
591
|
+
const handle = (p) => {
|
|
592
|
+
p.catch((e) => {
|
|
593
|
+
console.warn(`[apcore:registry] Watch handler failed for ${fullPath}:`, e);
|
|
594
|
+
});
|
|
595
|
+
};
|
|
464
596
|
if (eventType === "rename") {
|
|
465
597
|
// Could be create or delete
|
|
466
598
|
try {
|
|
467
599
|
fs.accessSync(fullPath);
|
|
468
|
-
this._handleFileChange(fullPath);
|
|
600
|
+
handle(this._handleFileChange(fullPath));
|
|
469
601
|
}
|
|
470
602
|
catch {
|
|
471
|
-
this._handleFileDeletion(fullPath);
|
|
603
|
+
handle(this._handleFileDeletion(fullPath));
|
|
472
604
|
}
|
|
473
605
|
}
|
|
474
606
|
else {
|
|
475
|
-
this._handleFileChange(fullPath);
|
|
607
|
+
handle(this._handleFileChange(fullPath));
|
|
476
608
|
}
|
|
477
609
|
});
|
|
478
610
|
this._watchers.push(watcher);
|
|
479
611
|
}
|
|
480
|
-
catch {
|
|
481
|
-
//
|
|
612
|
+
catch (e) {
|
|
613
|
+
// Surface real failures (EMFILE, EACCES, Linux kernels < 4.7 without
|
|
614
|
+
// recursive support, etc.). A silently non-functional watch misleads
|
|
615
|
+
// users who expect hot-reload to be active.
|
|
616
|
+
console.warn(`[apcore:registry] fs.watch failed for '${rootPath}' — hot-reload disabled for this root:`, e);
|
|
482
617
|
}
|
|
483
618
|
}
|
|
484
619
|
}
|
|
@@ -501,12 +636,17 @@ export class Registry {
|
|
|
501
636
|
try {
|
|
502
637
|
oldModule.onUnload();
|
|
503
638
|
}
|
|
504
|
-
catch {
|
|
639
|
+
catch (e) {
|
|
640
|
+
console.warn(`[apcore:registry] onUnload failed for '${moduleId}':`, e);
|
|
641
|
+
}
|
|
505
642
|
}
|
|
506
643
|
this.unregister(moduleId);
|
|
507
644
|
}
|
|
508
|
-
// Re-import is complex in ES modules
|
|
509
|
-
|
|
645
|
+
// Re-import is complex in ES modules — tell consumers that a watched file
|
|
646
|
+
// changed so they can re-import and re-register. The earlier design
|
|
647
|
+
// emitted a 'register' event with a null module, which crashed any
|
|
648
|
+
// consumer that accessed fields on the module argument.
|
|
649
|
+
this._triggerEvent("file_changed", moduleId ?? basename(filePath, extname(filePath)), { filePath });
|
|
510
650
|
}
|
|
511
651
|
async _handleFileDeletion(path) {
|
|
512
652
|
const moduleId = this._pathToModuleId(path);
|
|
@@ -516,7 +656,9 @@ export class Registry {
|
|
|
516
656
|
try {
|
|
517
657
|
module.onUnload();
|
|
518
658
|
}
|
|
519
|
-
catch {
|
|
659
|
+
catch (e) {
|
|
660
|
+
console.warn(`[apcore:registry] onUnload failed for '${moduleId}':`, e);
|
|
661
|
+
}
|
|
520
662
|
}
|
|
521
663
|
this.unregister(moduleId);
|
|
522
664
|
}
|
|
@@ -533,21 +675,18 @@ export class Registry {
|
|
|
533
675
|
return null;
|
|
534
676
|
}
|
|
535
677
|
/**
|
|
536
|
-
* Register a module
|
|
537
|
-
*
|
|
678
|
+
* Register a sys/internal module that bypasses **only** the reserved word
|
|
679
|
+
* check. All other PROTOCOL_SPEC §2.7 validations (empty, EBNF pattern,
|
|
680
|
+
* length, duplicate) still apply.
|
|
681
|
+
*
|
|
682
|
+
* Used exclusively by the sys-modules subsystem for `system.*` IDs.
|
|
683
|
+
* Aligned with apcore-python `Registry.register_internal` and apcore-rust
|
|
684
|
+
* `Registry::register_internal`.
|
|
538
685
|
*/
|
|
539
686
|
registerInternal(moduleId, module) {
|
|
540
|
-
|
|
541
|
-
throw new InvalidInputError("Module ID must be a non-empty string");
|
|
542
|
-
}
|
|
543
|
-
if (!MODULE_ID_PATTERN.test(moduleId)) {
|
|
544
|
-
throw new InvalidInputError(`Invalid module ID: "${moduleId}". Must match pattern: ${MODULE_ID_PATTERN}`);
|
|
545
|
-
}
|
|
546
|
-
if (moduleId.length > MAX_MODULE_ID_LENGTH) {
|
|
547
|
-
throw new InvalidInputError(`Module ID exceeds maximum length of ${MAX_MODULE_ID_LENGTH}: ${moduleId.length}`);
|
|
548
|
-
}
|
|
687
|
+
validateModuleId(moduleId, true);
|
|
549
688
|
if (this._modules.has(moduleId)) {
|
|
550
|
-
throw new InvalidInputError(`Module
|
|
689
|
+
throw new InvalidInputError(`Module ID '${moduleId}' is already registered`);
|
|
551
690
|
}
|
|
552
691
|
this._modules.set(moduleId, module);
|
|
553
692
|
const modObj = module;
|
|
@@ -564,6 +703,27 @@ export class Registry {
|
|
|
564
703
|
}
|
|
565
704
|
this._triggerEvent(REGISTRY_EVENTS.REGISTER, moduleId, module);
|
|
566
705
|
}
|
|
706
|
+
/**
|
|
707
|
+
* Export the JSON Schema for a registered module.
|
|
708
|
+
*
|
|
709
|
+
* Returns the schema as a plain object (`Record<string, unknown> | null`),
|
|
710
|
+
* matching Python's `dict | None` and Rust's `Option<Value>` return types.
|
|
711
|
+
* Returns `null` if the module is not registered.
|
|
712
|
+
* Use the standalone `exportSchema` function from `schema-export.ts` for
|
|
713
|
+
* serialized (JSON/YAML) output.
|
|
714
|
+
*/
|
|
715
|
+
exportSchema(moduleId, strict = false) {
|
|
716
|
+
const schema = getSchema(this, moduleId);
|
|
717
|
+
if (schema === null)
|
|
718
|
+
return null;
|
|
719
|
+
if (strict) {
|
|
720
|
+
const result = deepCopy(schema);
|
|
721
|
+
result['input_schema'] = toStrictSchema(result['input_schema']);
|
|
722
|
+
result['output_schema'] = toStrictSchema(result['output_schema']);
|
|
723
|
+
return result;
|
|
724
|
+
}
|
|
725
|
+
return schema;
|
|
726
|
+
}
|
|
567
727
|
clearCache() {
|
|
568
728
|
this._schemaCache.clear();
|
|
569
729
|
}
|
|
@@ -629,9 +789,18 @@ export class Registry {
|
|
|
629
789
|
}
|
|
630
790
|
/**
|
|
631
791
|
* Remove the draining mark and clean up drain state.
|
|
792
|
+
*
|
|
793
|
+
* If any waitDrained waiters are still pending (e.g., refCount briefly
|
|
794
|
+
* hit zero and then a new acquire bumped it back up), resolve them first
|
|
795
|
+
* so they do not wait for their individual timeouts before returning.
|
|
632
796
|
*/
|
|
633
797
|
endDrain(moduleId) {
|
|
634
798
|
this._draining.delete(moduleId);
|
|
799
|
+
const resolvers = this._drainResolvers.get(moduleId);
|
|
800
|
+
if (resolvers) {
|
|
801
|
+
for (const resolve of resolvers)
|
|
802
|
+
resolve();
|
|
803
|
+
}
|
|
635
804
|
this._drainResolvers.delete(moduleId);
|
|
636
805
|
this._refCounts.delete(moduleId);
|
|
637
806
|
}
|