@databehave/core 0.0.0-stage → 0.2.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/CHANGELOG.md +29 -0
- package/LICENSE +21 -0
- package/README.md +76 -2
- package/dist/capability.d.ts +5 -0
- package/dist/capability.d.ts.map +1 -0
- package/dist/capability.js +16 -0
- package/dist/capability.js.map +1 -0
- package/dist/contracts.d.ts +52 -0
- package/dist/contracts.d.ts.map +1 -0
- package/dist/contracts.js +2 -0
- package/dist/contracts.js.map +1 -0
- package/dist/host.d.ts +6 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +147 -0
- package/dist/host.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/registration.d.ts +17 -0
- package/dist/registration.d.ts.map +1 -0
- package/dist/registration.js +141 -0
- package/dist/registration.js.map +1 -0
- package/package.json +42 -3
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [Unreleased]
|
|
4
|
+
|
|
5
|
+
Upcoming package version: **0.2.0**. This section describes planned release contents, not a published release.
|
|
6
|
+
|
|
7
|
+
### Breaking changes
|
|
8
|
+
|
|
9
|
+
- Require Node.js 24.22.1+ within 24.x or 26.12.1+ within 26.x to enforce the updated security patch floors.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Refresh the shared build, test and release tooling dependencies.
|
|
14
|
+
|
|
15
|
+
See [migration](../../MIGRATION.md) and [release procedures](../../docs/releasing.md).
|
|
16
|
+
|
|
17
|
+
## [0.1.0]
|
|
18
|
+
|
|
19
|
+
### Runtime requirements
|
|
20
|
+
|
|
21
|
+
- Require Node.js 24 or 26; support and test majors 24 and 26. Node.js 18, 20 and 22 are unsupported.
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- Typed versioned capabilities, explicit dependency validation and instance-local plugin registration.
|
|
26
|
+
- Synchronous setup/sealing and separate prepare, activate and open barriers.
|
|
27
|
+
- Idempotent startup/close, cancellation, ingress drain and reverse resource disposal with cleanup failure reporting.
|
|
28
|
+
|
|
29
|
+
See [migration](../../MIGRATION.md) and [release procedures](../../docs/releasing.md).
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ochairo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,77 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @databehave/core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Explicit, typed plugin composition with no product dependencies or global registry.
|
|
4
|
+
|
|
5
|
+
Requires Node.js 24.22.1+ (24.x) or 26.12.1+ (26.x); supported majors are 24 and 26. See the
|
|
6
|
+
[plugin author guide](../../docs/plugins.md) and [runnable plugin example](../../examples/plugin.mjs).
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import { capability, createHost, type Plugin } from '@databehave/core'
|
|
10
|
+
|
|
11
|
+
const greeting = capability<{ text: string }>('example/greeting', 1)
|
|
12
|
+
const provider: Plugin = {
|
|
13
|
+
id: 'greeting',
|
|
14
|
+
apiVersion: 1,
|
|
15
|
+
provides: [greeting],
|
|
16
|
+
setup(ctx) { ctx.provide(greeting, { text: 'Hello' }) },
|
|
17
|
+
}
|
|
18
|
+
const consumer: Plugin = {
|
|
19
|
+
id: 'printer',
|
|
20
|
+
apiVersion: 1,
|
|
21
|
+
requires: [greeting],
|
|
22
|
+
setup() {},
|
|
23
|
+
activate(ctx) { console.log(ctx.use(greeting).text) },
|
|
24
|
+
}
|
|
25
|
+
const host = createHost({ plugins: [consumer, provider] })
|
|
26
|
+
await host.start()
|
|
27
|
+
await host.close()
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Capability identity is its nonempty, trimmed name and positive integer version. Separate
|
|
31
|
+
copies of a contract match by those values. Generic tokens connect the capability's
|
|
32
|
+
value type to `provide`, `use`, and `get`; JavaScript values are trusted to honor that
|
|
33
|
+
contract. Names should be package-qualified. Duplicate providers of the same name/version,
|
|
34
|
+
missing requirements, duplicate plugin IDs, invalid API versions and cycles fail before
|
|
35
|
+
setup. Different capability versions may coexist when explicitly provided and required.
|
|
36
|
+
Plugins may only provide declared capabilities and consume declared requirements.
|
|
37
|
+
`host.get(token)` is for the application's composition boundary; it does not extend the
|
|
38
|
+
service's lifetime past host shutdown. Omitted plugins contribute no capabilities.
|
|
39
|
+
|
|
40
|
+
`createHost` validates the complete graph, runs synchronous setup in dependency order,
|
|
41
|
+
then invokes seal callbacks. Setup must not perform I/O or acquire resources. Async setup
|
|
42
|
+
and seal callbacks are rejected; trusted plugin code is not sandboxed and cannot be
|
|
43
|
+
forcibly undone. Setup contexts expire when their setup returns. Seal callbacks run after
|
|
44
|
+
all setup contributions and should seal feature-owned registries. Register contributions
|
|
45
|
+
through those feature contracts only while registration remains open.
|
|
46
|
+
|
|
47
|
+
Registration snapshots plugin identities and callback references. Object literals and class implementations
|
|
48
|
+
are supported; callbacks retain their original receiver, including private fields. Reassigning a handler
|
|
49
|
+
after construction does not replace the registered handler. Receiver and closure state remain plugin-owned.
|
|
50
|
+
|
|
51
|
+
Startup runs separate `prepare`, `activate`, then `open` barriers. Each phase is sequential:
|
|
52
|
+
providers precede consumers, and available independent plugins follow configuration order.
|
|
53
|
+
Context `order` always gives the original configuration index, allowing feature-owned
|
|
54
|
+
middleware ordering independent of dependency ordering. All preparation finishes before
|
|
55
|
+
any activation, and all activation finishes before any external ingress opens.
|
|
56
|
+
|
|
57
|
+
Call `ctx.defer(dispose)` immediately when acquiring each resource. Only `open` may call
|
|
58
|
+
`ctx.ownIngress({ stopAccepting, drainAndClose })`; do not also register the same ingress
|
|
59
|
+
as an ordinary disposer. Close stops all registered ingress synchronously, aborts the
|
|
60
|
+
lifetime signal, waits for the current startup hook, drains ingress in reverse acquisition
|
|
61
|
+
order, and disposes other resources in reverse acquisition order. A hook completing after
|
|
62
|
+
cancellation may still register its acquisitions; late ingress stops immediately. Its
|
|
63
|
+
context expires on completion, so detached background acquisition is unsupported. Hooks
|
|
64
|
+
must cooperate with cancellation, and ingress implementations own bounded draining.
|
|
65
|
+
|
|
66
|
+
Concurrent `start()` calls share one promise, as do all `close()` calls. Close during
|
|
67
|
+
startup prevents subsequent hooks/phases and rejects startup after cleanup. Repeated
|
|
68
|
+
start after a completed start returns that original promise and never restarts the host.
|
|
69
|
+
Closing before start rejects a later start. Failure is terminal: create a new host to retry.
|
|
70
|
+
Cleanup continues after errors and rejects with `AggregateError`; startup cleanup errors
|
|
71
|
+
also retain the original startup error as the first error and `cause`. If cleanup succeeds,
|
|
72
|
+
startup rejects with the original failure and close succeeds. States are `ready`, `starting`,
|
|
73
|
+
`running`, `closing`, `closed`, and `failed`.
|
|
74
|
+
|
|
75
|
+
Run `pnpm typecheck`, `pnpm test`, and `pnpm build` in the project development container.
|
|
76
|
+
The root `api:check` command verifies the package API report in `etc/core.api.md`.
|
|
77
|
+
Use `api:update` when intentionally changing the public contract.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Capability, CapabilityKey } from './contracts.js';
|
|
2
|
+
/** Create a token; matching depends on name/version, never object or symbol identity. */
|
|
3
|
+
export declare function capability<T>(name: string, version?: number): Capability<T>;
|
|
4
|
+
export declare function capabilityId(token: CapabilityKey): string;
|
|
5
|
+
//# sourceMappingURL=capability.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capability.d.ts","sourceRoot":"","sources":["../src/capability.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAE/D,yFAAyF;AACzF,wBAAgB,UAAU,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,SAAI,GAAG,UAAU,CAAC,CAAC,CAAC,CAItE;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAQzD"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Create a token; matching depends on name/version, never object or symbol identity. */
|
|
2
|
+
export function capability(name, version = 1) {
|
|
3
|
+
const token = { name, version };
|
|
4
|
+
capabilityId(token);
|
|
5
|
+
return Object.freeze(token);
|
|
6
|
+
}
|
|
7
|
+
export function capabilityId(token) {
|
|
8
|
+
if (!token || typeof token.name !== 'string' || token.name.trim() !== token.name || !token.name) {
|
|
9
|
+
throw new Error('Capability name must be a nonempty trimmed string');
|
|
10
|
+
}
|
|
11
|
+
if (!Number.isSafeInteger(token.version) || token.version < 1) {
|
|
12
|
+
throw new Error(`Invalid capability version for ${token.name}`);
|
|
13
|
+
}
|
|
14
|
+
return JSON.stringify([token.name, token.version]);
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=capability.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capability.js","sourceRoot":"","sources":["../src/capability.ts"],"names":[],"mappings":"AAEA,yFAAyF;AACzF,MAAM,UAAU,UAAU,CAAI,IAAY,EAAE,OAAO,GAAG,CAAC;IACrD,MAAM,KAAK,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,CAAA;IAC/B,YAAY,CAAC,KAAK,CAAC,CAAA;IACnB,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AAC7B,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,KAAoB;IAC/C,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;QAChG,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAA;IACtE,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,OAAO,GAAG,CAAC,EAAE,CAAC;QAC9D,MAAM,IAAI,KAAK,CAAC,kCAAkC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAA;IACjE,CAAC;IACD,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,CAAA;AACpD,CAAC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/** Stable identity shared by separately installed copies of a capability contract. */
|
|
2
|
+
export interface CapabilityKey {
|
|
3
|
+
readonly name: string;
|
|
4
|
+
readonly version: number;
|
|
5
|
+
}
|
|
6
|
+
/** Typed capability identity. The optional phantom member carries no runtime state. */
|
|
7
|
+
export interface Capability<T> extends CapabilityKey {
|
|
8
|
+
readonly __valueType?: (value: T) => T;
|
|
9
|
+
}
|
|
10
|
+
/** Synchronous, effect-free registration, available only during the plugin's setup. */
|
|
11
|
+
export interface SetupContext {
|
|
12
|
+
/** Original configuration index, independent of dependency ordering. */
|
|
13
|
+
readonly order: number;
|
|
14
|
+
use<T>(token: Capability<T>): T;
|
|
15
|
+
provide<T>(token: Capability<T>, value: T): void;
|
|
16
|
+
onSeal(fn: () => void): void;
|
|
17
|
+
}
|
|
18
|
+
/** External ingress stops synchronously before lifetime cancellation and draining. */
|
|
19
|
+
export interface IngressHandle {
|
|
20
|
+
stopAccepting(): void;
|
|
21
|
+
drainAndClose(): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
/** Resource ownership for one running hook; register cleanup immediately on acquisition. */
|
|
24
|
+
export interface LifecycleContext {
|
|
25
|
+
/** Original configuration index, independent of dependency ordering. */
|
|
26
|
+
readonly order: number;
|
|
27
|
+
use<T>(token: Capability<T>): T;
|
|
28
|
+
readonly signal: AbortSignal;
|
|
29
|
+
defer(dispose: () => void | Promise<void>): void;
|
|
30
|
+
ownIngress(handle: IngressHandle): void;
|
|
31
|
+
}
|
|
32
|
+
/** An explicit capability provider/consumer using host contract version 1. */
|
|
33
|
+
export interface Plugin {
|
|
34
|
+
readonly id: string;
|
|
35
|
+
readonly apiVersion: 1;
|
|
36
|
+
readonly provides?: readonly CapabilityKey[];
|
|
37
|
+
readonly requires?: readonly CapabilityKey[];
|
|
38
|
+
setup(ctx: SetupContext): void;
|
|
39
|
+
prepare?(ctx: LifecycleContext): void | Promise<void>;
|
|
40
|
+
activate?(ctx: LifecycleContext): void | Promise<void>;
|
|
41
|
+
open?(ctx: LifecycleContext): void | Promise<void>;
|
|
42
|
+
}
|
|
43
|
+
/** A host is terminal after close or failure; create another host to restart. */
|
|
44
|
+
export type HostState = 'ready' | 'starting' | 'running' | 'closing' | 'closed' | 'failed';
|
|
45
|
+
/** Independently owned application lifetime and its sealed capabilities. */
|
|
46
|
+
export interface Host {
|
|
47
|
+
readonly state: HostState;
|
|
48
|
+
start(): Promise<void>;
|
|
49
|
+
close(): Promise<void>;
|
|
50
|
+
get<T>(token: Capability<T>): T;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=contracts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contracts.d.ts","sourceRoot":"","sources":["../src/contracts.ts"],"names":[],"mappings":"AAAA,sFAAsF;AACtF,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACzB;AAED,uFAAuF;AACvF,MAAM,WAAW,UAAU,CAAC,CAAC,CAAE,SAAQ,aAAa;IAClD,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,CAAA;CACvC;AAED,uFAAuF;AACvF,MAAM,WAAW,YAAY;IAC3B,wEAAwE;IACxE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;IAC/B,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI,CAAA;IAChD,MAAM,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI,CAAA;CAC7B;AAED,sFAAsF;AACtF,MAAM,WAAW,aAAa;IAC5B,aAAa,IAAI,IAAI,CAAA;IACrB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CAC/B;AAED,4FAA4F;AAC5F,MAAM,WAAW,gBAAgB;IAC/B,wEAAwE;IACxE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;IAC/B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;IAC5B,KAAK,CAAC,OAAO,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAChD,UAAU,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAAA;CACxC;AAED,8EAA8E;AAC9E,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAA;IACtB,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,aAAa,EAAE,CAAA;IAC5C,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,aAAa,EAAE,CAAA;IAC5C,KAAK,CAAC,GAAG,EAAE,YAAY,GAAG,IAAI,CAAA;IAC9B,OAAO,CAAC,CAAC,GAAG,EAAE,gBAAgB,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACrD,QAAQ,CAAC,CAAC,GAAG,EAAE,gBAAgB,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACtD,IAAI,CAAC,CAAC,GAAG,EAAE,gBAAgB,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACnD;AAED,iFAAiF;AACjF,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,UAAU,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAA;AAE1F,4EAA4E;AAC5E,MAAM,WAAW,IAAI;IACnB,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;IACzB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACtB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACtB,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;CAChC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contracts.js","sourceRoot":"","sources":["../src/contracts.ts"],"names":[],"mappings":""}
|
package/dist/host.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Host, Plugin } from './contracts.js';
|
|
2
|
+
/** Validate, register and seal plugins synchronously, without starting their effects. */
|
|
3
|
+
export declare function createHost(options: {
|
|
4
|
+
readonly plugins: readonly Plugin[];
|
|
5
|
+
}): Host;
|
|
6
|
+
//# sourceMappingURL=host.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAA8C,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAG9F,yFAAyF;AACzF,wBAAgB,UAAU,CAAC,OAAO,EAAE;IAAE,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GAAG,IAAI,CAsHjF"}
|
package/dist/host.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { register } from './registration.js';
|
|
2
|
+
/** Validate, register and seal plugins synchronously, without starting their effects. */
|
|
3
|
+
export function createHost(options) {
|
|
4
|
+
const registration = register(options.plugins);
|
|
5
|
+
const lifetime = new AbortController();
|
|
6
|
+
const disposers = [];
|
|
7
|
+
const ingress = [];
|
|
8
|
+
const stopped = new Set();
|
|
9
|
+
const cleanupErrors = [];
|
|
10
|
+
let state = 'ready';
|
|
11
|
+
let startPromise;
|
|
12
|
+
let execution;
|
|
13
|
+
let closePromise;
|
|
14
|
+
let failed = false;
|
|
15
|
+
let startupError;
|
|
16
|
+
let closing = false;
|
|
17
|
+
function stopAccepting(handle) {
|
|
18
|
+
if (stopped.has(handle))
|
|
19
|
+
return;
|
|
20
|
+
stopped.add(handle);
|
|
21
|
+
try {
|
|
22
|
+
handle.stopAccepting();
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
cleanupErrors.push(error);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function beginClose() {
|
|
29
|
+
closing = true;
|
|
30
|
+
state = 'closing';
|
|
31
|
+
for (const handle of ingress)
|
|
32
|
+
stopAccepting(handle);
|
|
33
|
+
lifetime.abort();
|
|
34
|
+
}
|
|
35
|
+
async function cleanup() {
|
|
36
|
+
// A completing hook still owns its context, and can register a newly acquired resource.
|
|
37
|
+
try {
|
|
38
|
+
await execution;
|
|
39
|
+
}
|
|
40
|
+
catch { /* Startup failure is retained separately. */ }
|
|
41
|
+
for (const handle of [...ingress].reverse()) {
|
|
42
|
+
try {
|
|
43
|
+
await handle.drainAndClose();
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
cleanupErrors.push(error);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
for (const dispose of disposers.reverse()) {
|
|
50
|
+
try {
|
|
51
|
+
await dispose();
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
cleanupErrors.push(error);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
state = failed || cleanupErrors.length ? 'failed' : 'closed';
|
|
58
|
+
if (cleanupErrors.length) {
|
|
59
|
+
throw new AggregateError(failed ? [startupError, ...cleanupErrors] : cleanupErrors, 'Host cleanup failed', failed ? { cause: startupError } : undefined);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function close() {
|
|
63
|
+
if (!closePromise) {
|
|
64
|
+
// Schedule cleanup after installing the shared promise, before invoking plugin callbacks.
|
|
65
|
+
closePromise = Promise.resolve().then(cleanup);
|
|
66
|
+
beginClose();
|
|
67
|
+
}
|
|
68
|
+
return closePromise;
|
|
69
|
+
}
|
|
70
|
+
async function run() {
|
|
71
|
+
for (const phase of ['prepare', 'activate', 'open']) {
|
|
72
|
+
for (const owner of registration.ordered) {
|
|
73
|
+
if (closing)
|
|
74
|
+
return;
|
|
75
|
+
const hook = owner.plugin[phase];
|
|
76
|
+
if (!hook)
|
|
77
|
+
continue;
|
|
78
|
+
let active = true;
|
|
79
|
+
function assertActive() {
|
|
80
|
+
if (!active)
|
|
81
|
+
throw new Error(`Lifecycle context expired for ${owner.plugin.id}:${phase}`);
|
|
82
|
+
}
|
|
83
|
+
const context = {
|
|
84
|
+
order: owner.order,
|
|
85
|
+
signal: lifetime.signal,
|
|
86
|
+
use(token) {
|
|
87
|
+
assertActive();
|
|
88
|
+
return registration.use(owner, token);
|
|
89
|
+
},
|
|
90
|
+
defer(dispose) {
|
|
91
|
+
assertActive();
|
|
92
|
+
if (typeof dispose !== 'function')
|
|
93
|
+
throw new Error('Disposer must be a function');
|
|
94
|
+
disposers.push(dispose);
|
|
95
|
+
},
|
|
96
|
+
ownIngress(handle) {
|
|
97
|
+
assertActive();
|
|
98
|
+
if (phase !== 'open')
|
|
99
|
+
throw new Error('Ingress may only be acquired during open');
|
|
100
|
+
if (!handle || typeof handle.stopAccepting !== 'function' || typeof handle.drainAndClose !== 'function') {
|
|
101
|
+
throw new Error('Invalid ingress handle');
|
|
102
|
+
}
|
|
103
|
+
if (ingress.includes(handle))
|
|
104
|
+
throw new Error('Ingress handle already owned');
|
|
105
|
+
ingress.push(handle);
|
|
106
|
+
if (closing)
|
|
107
|
+
stopAccepting(handle);
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
try {
|
|
111
|
+
await hook.call(owner.plugin, context);
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
active = false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function start() {
|
|
120
|
+
if (!startPromise) {
|
|
121
|
+
if (closing) {
|
|
122
|
+
startPromise = Promise.reject(new Error('Cannot start a closed host'));
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
state = 'starting';
|
|
126
|
+
execution = Promise.resolve().then(run).catch((error) => {
|
|
127
|
+
failed = true;
|
|
128
|
+
startupError = error;
|
|
129
|
+
throw error;
|
|
130
|
+
});
|
|
131
|
+
startPromise = execution.then(async () => {
|
|
132
|
+
if (closing) {
|
|
133
|
+
await close();
|
|
134
|
+
throw new Error('Host closed during startup');
|
|
135
|
+
}
|
|
136
|
+
state = 'running';
|
|
137
|
+
}, async (error) => {
|
|
138
|
+
await close();
|
|
139
|
+
throw error;
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return startPromise;
|
|
144
|
+
}
|
|
145
|
+
return { start, close, get: registration.get, get state() { return state; } };
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=host.js.map
|
package/dist/host.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host.js","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,UAAU,EACV,aAAa,EACb,IAAI,EACJ,SAAS,EACT,aAAa,EACb,gBAAgB,EAChB,MAAM,EACN,YAAY,GACb,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Capability, Plugin } from './contracts.js';
|
|
2
|
+
/** Validated snapshot of one plugin and its declared capability identities. */
|
|
3
|
+
export interface RegisteredPlugin {
|
|
4
|
+
readonly order: number;
|
|
5
|
+
readonly plugin: Plugin;
|
|
6
|
+
readonly provides: ReadonlySet<string>;
|
|
7
|
+
readonly requires: ReadonlySet<string>;
|
|
8
|
+
}
|
|
9
|
+
/** The sealed, instance-local service registry and deterministic dependency order. */
|
|
10
|
+
export interface Registration {
|
|
11
|
+
readonly ordered: readonly RegisteredPlugin[];
|
|
12
|
+
get<T>(token: Capability<T>): T;
|
|
13
|
+
use<T>(owner: RegisteredPlugin, token: Capability<T>): T;
|
|
14
|
+
}
|
|
15
|
+
export declare function assertSynchronous(result: unknown, label: string): void;
|
|
16
|
+
export declare function register(plugins: readonly Plugin[]): Registration;
|
|
17
|
+
//# sourceMappingURL=registration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registration.d.ts","sourceRoot":"","sources":["../src/registration.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAiB,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAEvE,+EAA+E;AAC/E,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IACtC,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;CACvC;AAED,sFAAsF;AACtF,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE,SAAS,gBAAgB,EAAE,CAAA;IAC7C,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;IAC/B,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;CACzD;AAaD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAOtE;AAED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,YAAY,CAsGjE"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { capabilityId } from './capability.js';
|
|
2
|
+
function keys(tokens, owner) {
|
|
3
|
+
if (tokens !== undefined && !Array.isArray(tokens))
|
|
4
|
+
throw new Error(`Invalid capabilities in ${owner}`);
|
|
5
|
+
const result = new Set();
|
|
6
|
+
for (const token of tokens ?? []) {
|
|
7
|
+
const key = capabilityId(token);
|
|
8
|
+
if (result.has(key))
|
|
9
|
+
throw new Error(`Duplicate capability declaration ${key} in ${owner}`);
|
|
10
|
+
result.add(key);
|
|
11
|
+
}
|
|
12
|
+
return result;
|
|
13
|
+
}
|
|
14
|
+
export function assertSynchronous(result, label) {
|
|
15
|
+
if (result !== null && (typeof result === 'object' || typeof result === 'function') &&
|
|
16
|
+
'then' in result && typeof result.then === 'function') {
|
|
17
|
+
// A rejected async hook must not become an unhandled rejection after construction fails.
|
|
18
|
+
void Promise.resolve(result).catch(() => { });
|
|
19
|
+
throw new Error(`${label} must be synchronous`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export function register(plugins) {
|
|
23
|
+
if (!Array.isArray(plugins))
|
|
24
|
+
throw new Error('plugins must be an array');
|
|
25
|
+
const ids = new Set();
|
|
26
|
+
const providers = new Map();
|
|
27
|
+
const entries = plugins.map((input, order) => {
|
|
28
|
+
if (!input || typeof input.id !== 'string' || !input.id || input.id.trim() !== input.id) {
|
|
29
|
+
throw new Error('Plugin id must be a nonempty trimmed string');
|
|
30
|
+
}
|
|
31
|
+
if (ids.has(input.id))
|
|
32
|
+
throw new Error(`Duplicate plugin id ${input.id}`);
|
|
33
|
+
ids.add(input.id);
|
|
34
|
+
if (input.apiVersion !== 1)
|
|
35
|
+
throw new Error(`Unsupported host API version in ${input.id}`);
|
|
36
|
+
if (typeof input.setup !== 'function')
|
|
37
|
+
throw new Error(`Missing setup in ${input.id}`);
|
|
38
|
+
for (const phase of ['prepare', 'activate', 'open']) {
|
|
39
|
+
if (input[phase] !== undefined && typeof input[phase] !== 'function') {
|
|
40
|
+
throw new Error(`Invalid ${phase} hook in ${input.id}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const entry = {
|
|
44
|
+
order,
|
|
45
|
+
// Capture public callbacks while retaining the extension's receiver (including private fields).
|
|
46
|
+
plugin: {
|
|
47
|
+
id: input.id,
|
|
48
|
+
apiVersion: input.apiVersion,
|
|
49
|
+
setup: input.setup.bind(input),
|
|
50
|
+
...(input.prepare === undefined ? {} : { prepare: input.prepare.bind(input) }),
|
|
51
|
+
...(input.activate === undefined ? {} : { activate: input.activate.bind(input) }),
|
|
52
|
+
...(input.open === undefined ? {} : { open: input.open.bind(input) }),
|
|
53
|
+
},
|
|
54
|
+
provides: keys(input.provides, input.id),
|
|
55
|
+
requires: keys(input.requires, input.id),
|
|
56
|
+
};
|
|
57
|
+
for (const key of entry.provides) {
|
|
58
|
+
const previous = providers.get(key);
|
|
59
|
+
if (previous)
|
|
60
|
+
throw new Error(`Duplicate provider for ${key}: ${previous.plugin.id}, ${input.id}`);
|
|
61
|
+
providers.set(key, entry);
|
|
62
|
+
}
|
|
63
|
+
return entry;
|
|
64
|
+
});
|
|
65
|
+
for (const entry of entries) {
|
|
66
|
+
for (const key of entry.requires) {
|
|
67
|
+
if (!providers.has(key))
|
|
68
|
+
throw new Error(`Missing capability ${key} required by ${entry.plugin.id}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const ordered = [];
|
|
72
|
+
const remaining = new Set(entries);
|
|
73
|
+
const resolved = new Set();
|
|
74
|
+
while (remaining.size) {
|
|
75
|
+
const next = entries.find(entry => remaining.has(entry) && [...entry.requires].every(key => resolved.has(key)));
|
|
76
|
+
if (!next)
|
|
77
|
+
throw new Error(`Capability dependency cycle: ${[...remaining].map(e => e.plugin.id).join(', ')}`);
|
|
78
|
+
remaining.delete(next);
|
|
79
|
+
ordered.push(next);
|
|
80
|
+
for (const key of next.provides)
|
|
81
|
+
resolved.add(key);
|
|
82
|
+
}
|
|
83
|
+
const services = new Map();
|
|
84
|
+
const seals = [];
|
|
85
|
+
function get(token) {
|
|
86
|
+
const key = capabilityId(token);
|
|
87
|
+
if (!services.has(key))
|
|
88
|
+
throw new Error(`Capability unavailable: ${key}`);
|
|
89
|
+
// provide() pairs each typed token with its value; name/version is the public identity boundary.
|
|
90
|
+
return services.get(key);
|
|
91
|
+
}
|
|
92
|
+
function use(owner, token) {
|
|
93
|
+
const key = capabilityId(token);
|
|
94
|
+
if (!owner.requires.has(key))
|
|
95
|
+
throw new Error(`Undeclared requirement ${key} in ${owner.plugin.id}`);
|
|
96
|
+
return get(token);
|
|
97
|
+
}
|
|
98
|
+
for (const owner of ordered) {
|
|
99
|
+
let registering = true;
|
|
100
|
+
function assertRegistering() {
|
|
101
|
+
if (!registering)
|
|
102
|
+
throw new Error(`Registration sealed for ${owner.plugin.id}`);
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
const result = owner.plugin.setup({
|
|
106
|
+
order: owner.order,
|
|
107
|
+
use(token) {
|
|
108
|
+
assertRegistering();
|
|
109
|
+
return use(owner, token);
|
|
110
|
+
},
|
|
111
|
+
provide(token, value) {
|
|
112
|
+
assertRegistering();
|
|
113
|
+
const key = capabilityId(token);
|
|
114
|
+
if (!owner.provides.has(key))
|
|
115
|
+
throw new Error(`Undeclared provider ${key} in ${owner.plugin.id}`);
|
|
116
|
+
if (services.has(key))
|
|
117
|
+
throw new Error(`Capability already provided: ${key}`);
|
|
118
|
+
services.set(key, value);
|
|
119
|
+
},
|
|
120
|
+
onSeal(fn) {
|
|
121
|
+
assertRegistering();
|
|
122
|
+
if (typeof fn !== 'function')
|
|
123
|
+
throw new Error('Seal callback must be a function');
|
|
124
|
+
seals.push(fn);
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
assertSynchronous(result, `Setup for ${owner.plugin.id}`);
|
|
128
|
+
for (const key of owner.provides) {
|
|
129
|
+
if (!services.has(key))
|
|
130
|
+
throw new Error(`Declared capability ${key} not provided by ${owner.plugin.id}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
finally {
|
|
134
|
+
registering = false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
for (const seal of seals)
|
|
138
|
+
assertSynchronous(seal(), 'Seal callback');
|
|
139
|
+
return { ordered, get, use };
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=registration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registration.js","sourceRoot":"","sources":["../src/registration.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,45 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@databehave/core",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Instance-local typed plugin composition and application lifecycle.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/ochairo/databehave.git",
|
|
10
|
+
"directory": "packages/core"
|
|
11
|
+
},
|
|
12
|
+
"main": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"sideEffects": false,
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"import": "./dist/index.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"README.md",
|
|
24
|
+
"CHANGELOG.md"
|
|
25
|
+
],
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": "^24.22.1 || ^26.12.1"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"typescript": "6.0.3",
|
|
31
|
+
"@types/node": "24.19.2"
|
|
32
|
+
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public",
|
|
35
|
+
"provenance": true
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsc -p tsconfig.build.json",
|
|
39
|
+
"build:test": "tsc -p tsconfig.test.json",
|
|
40
|
+
"typecheck": "tsc --noEmit",
|
|
41
|
+
"pretest": "pnpm build:test",
|
|
42
|
+
"test": "node --test dist-test/test/*.test.js",
|
|
43
|
+
"clean": "rm -rf dist dist-test"
|
|
44
|
+
}
|
|
6
45
|
}
|