di-bag 0.1.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 +21 -0
- package/README.md +280 -0
- package/dist/acquisition-context.d.ts +42 -0
- package/dist/acquisition-context.js +19 -0
- package/dist/acquisition-family.d.ts +32 -0
- package/dist/acquisition-family.js +135 -0
- package/dist/acquisition-mode.d.ts +34 -0
- package/dist/acquisition-mode.js +35 -0
- package/dist/acquisition.d.ts +44 -0
- package/dist/acquisition.js +395 -0
- package/dist/alias-types.d.ts +22 -0
- package/dist/alias-types.js +2 -0
- package/dist/aliases.d.ts +4 -0
- package/dist/aliases.js +24 -0
- package/dist/composition.d.ts +41 -0
- package/dist/composition.js +45 -0
- package/dist/contribution-types.d.ts +57 -0
- package/dist/contribution-types.js +2 -0
- package/dist/contributions.d.ts +3 -0
- package/dist/contributions.js +11 -0
- package/dist/dependency-references.d.ts +52 -0
- package/dist/dependency-references.js +53 -0
- package/dist/di-bag.d.ts +247 -0
- package/dist/di-bag.js +211 -0
- package/dist/errors.d.ts +66 -0
- package/dist/errors.js +89 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +10 -0
- package/dist/inspection.d.ts +36 -0
- package/dist/inspection.js +2 -0
- package/dist/lifetime-types.d.ts +214 -0
- package/dist/lifetime-types.js +2 -0
- package/dist/lifetime.d.ts +42 -0
- package/dist/lifetime.js +31 -0
- package/dist/module-types.d.ts +182 -0
- package/dist/module-types.js +2 -0
- package/dist/module.d.ts +45 -0
- package/dist/module.js +118 -0
- package/dist/node.d.ts +4 -0
- package/dist/node.js +22 -0
- package/dist/observers.d.ts +71 -0
- package/dist/observers.js +58 -0
- package/dist/persistent-map.d.ts +29 -0
- package/dist/persistent-map.js +146 -0
- package/dist/persistent-sequence.d.ts +9 -0
- package/dist/persistent-sequence.js +20 -0
- package/dist/plugins.d.ts +32 -0
- package/dist/plugins.js +82 -0
- package/dist/provider-execution.d.ts +59 -0
- package/dist/provider-execution.js +271 -0
- package/dist/provider-operations.d.ts +59 -0
- package/dist/provider-operations.js +38 -0
- package/dist/provider.d.ts +199 -0
- package/dist/provider.js +158 -0
- package/dist/registration.d.ts +32 -0
- package/dist/registration.js +45 -0
- package/dist/replacement-types.d.ts +30 -0
- package/dist/replacement-types.js +2 -0
- package/dist/runtime.d.ts +90 -0
- package/dist/runtime.js +428 -0
- package/dist/scope-selection.d.ts +6 -0
- package/dist/scope-selection.js +62 -0
- package/dist/scope-types.d.ts +58 -0
- package/dist/scope-types.js +2 -0
- package/dist/startup.d.ts +14 -0
- package/dist/startup.js +141 -0
- package/dist/token-types.d.ts +67 -0
- package/dist/token-types.js +2 -0
- package/dist/tokens.d.ts +47 -0
- package/dist/tokens.js +69 -0
- package/dist/types.d.ts +174 -0
- package/dist/types.js +2 -0
- package/package.json +64 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PersistentMap = void 0;
|
|
4
|
+
// Registered symbols cannot be weak keys. Hash their registry strings; local and
|
|
5
|
+
// well-known symbols use collectible identities where the host supports them.
|
|
6
|
+
const symbolHashes = new WeakMap();
|
|
7
|
+
let nextSymbolHash = 0;
|
|
8
|
+
const weakSymbols = (() => {
|
|
9
|
+
try {
|
|
10
|
+
symbolHashes.set(Symbol(), 0);
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
})();
|
|
17
|
+
function stringHash(key) {
|
|
18
|
+
let hash = 2166136261;
|
|
19
|
+
for (let i = 0; i < key.length; i++)
|
|
20
|
+
hash = Math.imul(hash ^ key.charCodeAt(i), 16777619);
|
|
21
|
+
return hash >>> 0;
|
|
22
|
+
}
|
|
23
|
+
function hashKey(key) {
|
|
24
|
+
if (typeof key === 'string')
|
|
25
|
+
return stringHash(key);
|
|
26
|
+
const registered = Symbol.keyFor(key);
|
|
27
|
+
if (registered !== undefined)
|
|
28
|
+
return stringHash(registered);
|
|
29
|
+
if (!weakSymbols)
|
|
30
|
+
return stringHash(String(key));
|
|
31
|
+
const weakKey = key;
|
|
32
|
+
let hash = symbolHashes.get(weakKey);
|
|
33
|
+
if (hash === undefined) {
|
|
34
|
+
hash = nextSymbolHash = (nextSymbolHash + 1) >>> 0;
|
|
35
|
+
symbolHashes.set(weakKey, hash);
|
|
36
|
+
}
|
|
37
|
+
return hash;
|
|
38
|
+
}
|
|
39
|
+
function index(bitmap, bit) {
|
|
40
|
+
let bits = bitmap & (bit - 1);
|
|
41
|
+
bits -= (bits >>> 1) & 0x5555;
|
|
42
|
+
bits = (bits & 0x3333) + ((bits >>> 2) & 0x3333);
|
|
43
|
+
return (((bits + (bits >>> 4)) & 0x0f0f) * 0x0101 >>> 8) & 0xff;
|
|
44
|
+
}
|
|
45
|
+
function join(left, right, shift) {
|
|
46
|
+
if (left.hash === right.hash)
|
|
47
|
+
return { kind: 'collision', hash: right.hash, entries: left.kind === 'leaf' ? [left, right] : [...left.entries, right] };
|
|
48
|
+
const a = (left.hash >>> shift) & 15, b = (right.hash >>> shift) & 15;
|
|
49
|
+
return a === b
|
|
50
|
+
? { kind: 'branch', bitmap: 1 << a, children: [join(left, right, shift + 4)] }
|
|
51
|
+
: { kind: 'branch', bitmap: (1 << a) | (1 << b), children: a < b ? [left, right] : [right, left] };
|
|
52
|
+
}
|
|
53
|
+
function put(node, leaf, shift) {
|
|
54
|
+
if (!node)
|
|
55
|
+
return leaf;
|
|
56
|
+
if (node.kind === 'leaf')
|
|
57
|
+
return node.key === leaf.key ? leaf : join(node, leaf, shift);
|
|
58
|
+
if (node.kind === 'collision') {
|
|
59
|
+
if (node.hash !== leaf.hash)
|
|
60
|
+
return join(node, leaf, shift);
|
|
61
|
+
const at = node.entries.findIndex(entry => entry.key === leaf.key);
|
|
62
|
+
if (at < 0)
|
|
63
|
+
return { ...node, entries: [...node.entries, leaf] };
|
|
64
|
+
const entries = node.entries.slice();
|
|
65
|
+
entries[at] = leaf;
|
|
66
|
+
return { ...node, entries };
|
|
67
|
+
}
|
|
68
|
+
const bit = 1 << ((leaf.hash >>> shift) & 15), at = index(node.bitmap, bit);
|
|
69
|
+
const children = node.children.slice();
|
|
70
|
+
if (node.bitmap & bit)
|
|
71
|
+
children[at] = put(children[at], leaf, shift + 4);
|
|
72
|
+
else
|
|
73
|
+
children.splice(at, 0, leaf);
|
|
74
|
+
return { kind: 'branch', bitmap: node.bitmap | bit, children };
|
|
75
|
+
}
|
|
76
|
+
function remove(node, key, hash, shift) {
|
|
77
|
+
if (!node)
|
|
78
|
+
return undefined;
|
|
79
|
+
if (node.kind === 'leaf')
|
|
80
|
+
return node.key === key ? undefined : node;
|
|
81
|
+
if (node.kind === 'collision') {
|
|
82
|
+
const at = node.entries.findIndex(entry => entry.key === key);
|
|
83
|
+
if (at < 0)
|
|
84
|
+
return node;
|
|
85
|
+
const entries = node.entries.slice();
|
|
86
|
+
entries.splice(at, 1);
|
|
87
|
+
return entries.length === 1 ? entries[0] : { ...node, entries };
|
|
88
|
+
}
|
|
89
|
+
const bit = 1 << ((hash >>> shift) & 15);
|
|
90
|
+
if (!(node.bitmap & bit))
|
|
91
|
+
return node;
|
|
92
|
+
const at = index(node.bitmap, bit), previous = node.children[at];
|
|
93
|
+
const child = remove(previous, key, hash, shift + 4);
|
|
94
|
+
if (child === previous)
|
|
95
|
+
return node;
|
|
96
|
+
const children = node.children.slice();
|
|
97
|
+
if (child)
|
|
98
|
+
children[at] = child;
|
|
99
|
+
else
|
|
100
|
+
children.splice(at, 1);
|
|
101
|
+
if (children.length === 0)
|
|
102
|
+
return undefined;
|
|
103
|
+
// A branch cannot move up: its bitmap describes a particular hash nibble.
|
|
104
|
+
if (children.length === 1 && children[0].kind !== 'branch')
|
|
105
|
+
return children[0];
|
|
106
|
+
return { kind: 'branch', bitmap: child ? node.bitmap : node.bitmap & ~bit, children };
|
|
107
|
+
}
|
|
108
|
+
class PersistentMap {
|
|
109
|
+
root;
|
|
110
|
+
constructor(root) {
|
|
111
|
+
this.root = root;
|
|
112
|
+
}
|
|
113
|
+
get(key) {
|
|
114
|
+
const hash = hashKey(key);
|
|
115
|
+
let node = this.root, shift = 0;
|
|
116
|
+
while (node?.kind === 'branch') {
|
|
117
|
+
const bit = 1 << ((hash >>> shift) & 15);
|
|
118
|
+
if (!(node.bitmap & bit))
|
|
119
|
+
return undefined;
|
|
120
|
+
node = node.children[index(node.bitmap, bit)];
|
|
121
|
+
shift += 4;
|
|
122
|
+
}
|
|
123
|
+
if (node?.kind === 'leaf')
|
|
124
|
+
return node.key === key ? node.value : undefined;
|
|
125
|
+
return node?.entries.find(entry => entry.key === key)?.value;
|
|
126
|
+
}
|
|
127
|
+
has(key) { return this.get(key) !== undefined; }
|
|
128
|
+
set(key, value) {
|
|
129
|
+
return new PersistentMap(put(this.root, { kind: 'leaf', key, hash: hashKey(key), value }, 0));
|
|
130
|
+
}
|
|
131
|
+
delete(key) { return new PersistentMap(remove(this.root, key, hashKey(key), 0)); }
|
|
132
|
+
*[Symbol.iterator]() {
|
|
133
|
+
const stack = this.root ? [this.root] : [];
|
|
134
|
+
while (stack.length) {
|
|
135
|
+
const node = stack.pop();
|
|
136
|
+
if (node.kind === 'branch')
|
|
137
|
+
stack.push(...node.children);
|
|
138
|
+
else if (node.kind === 'collision')
|
|
139
|
+
for (const entry of node.entries)
|
|
140
|
+
yield [entry.key, entry.value];
|
|
141
|
+
else
|
|
142
|
+
yield [node.key, node.value];
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
exports.PersistentMap = PersistentMap;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Append/concat nodes contain no version-specific materialization cache. */
|
|
2
|
+
export type Sequence<T> = {
|
|
3
|
+
readonly values: readonly T[];
|
|
4
|
+
} | {
|
|
5
|
+
readonly left: Sequence<T>;
|
|
6
|
+
readonly right: Sequence<T>;
|
|
7
|
+
};
|
|
8
|
+
export declare function append<T>(previous: Sequence<T> | undefined, next: Sequence<T>): Sequence<T>;
|
|
9
|
+
export declare function materialize<T>(sequence: Sequence<T>): readonly T[];
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.append = append;
|
|
4
|
+
exports.materialize = materialize;
|
|
5
|
+
function append(previous, next) {
|
|
6
|
+
return previous ? { left: previous, right: next } : next;
|
|
7
|
+
}
|
|
8
|
+
function materialize(sequence) {
|
|
9
|
+
const values = [], stack = [sequence];
|
|
10
|
+
while (stack.length) {
|
|
11
|
+
const node = stack.pop();
|
|
12
|
+
if ('values' in node)
|
|
13
|
+
for (const value of node.values)
|
|
14
|
+
values.push(value);
|
|
15
|
+
else {
|
|
16
|
+
stack.push(node.right, node.left);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return Object.freeze(values);
|
|
20
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { DependencyReference } from './dependency-references';
|
|
2
|
+
import type { Provider } from './provider';
|
|
3
|
+
import type { DependencyTupleAdmission, ReferenceGraph } from './token-types';
|
|
4
|
+
/** The explicit output boundary used for an application-selected plugin. */
|
|
5
|
+
export type PluginAcquisitionMode = 'raw' | 'nativePromise';
|
|
6
|
+
/** A synchronous predicate that admits an unknown plugin output as a service type. */
|
|
7
|
+
export type PluginOutputValidator<V> = (this: void, value: unknown) => value is V;
|
|
8
|
+
/** Validation and acquisition choices for {@link DiBagApi.fromPlugin}. */
|
|
9
|
+
export interface PluginOptions<M extends PluginAcquisitionMode, V> {
|
|
10
|
+
/** Preserve the exact result with `raw`, or await a genuine native Promise with `nativePromise`. */
|
|
11
|
+
readonly acquisitionMode: M;
|
|
12
|
+
/** Must synchronously return exactly `true` for acceptable output values. */
|
|
13
|
+
readonly validate: PluginOutputValidator<V>;
|
|
14
|
+
}
|
|
15
|
+
/** The provider contract produced by {@link DiBagApi.fromPlugin}. */
|
|
16
|
+
export type PluginProvider<T extends readonly DependencyReference[], V, M extends PluginAcquisitionMode> = Provider<() => M extends 'raw' ? V : Promise<Awaited<V>>, Readonly<{}>, readonly [], ReferenceGraph<T>, M extends 'raw' ? V : Awaited<V>>;
|
|
17
|
+
/**
|
|
18
|
+
* Callable checked plugin adapter exposed by DiBag.fromPlugin.
|
|
19
|
+
* The descriptor must have own `apiVersion: 1` and callable `create`, with an optional
|
|
20
|
+
* callable `dispose`. A disposer owns the original acquired value before validation.
|
|
21
|
+
* The named call signature keeps extracted methods nameable in consumer declarations.
|
|
22
|
+
* @param dependencies - Host values supplied to the plugin in positional order.
|
|
23
|
+
* @param plugin - The application-selected unknown descriptor.
|
|
24
|
+
* @param options - Required raw/nativePromise acquisition and a synchronous output predicate.
|
|
25
|
+
* @returns A lazy provider that validates its output when acquired.
|
|
26
|
+
* @throws {@link DiBagPluginValidationError} for an invalid descriptor or rejected output.
|
|
27
|
+
* @typeParam T - The exact positional dependency-reference tuple.
|
|
28
|
+
* @typeParam V - The service admitted by the synchronous output validator.
|
|
29
|
+
* @typeParam M - The raw or nativePromise plugin acquisition policy.
|
|
30
|
+
*/
|
|
31
|
+
export type PluginProviderFactory = <const T extends readonly DependencyReference[], V, M extends PluginAcquisitionMode>(dependencies: T & DependencyTupleAdmission<T>, plugin: unknown, options: PluginOptions<M, V>) => PluginProvider<T, V, M>;
|
|
32
|
+
export declare const fromPlugin: PluginProviderFactory;
|
package/dist/plugins.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fromPlugin = void 0;
|
|
4
|
+
const errors_1 = require("./errors");
|
|
5
|
+
const dependency_references_1 = require("./dependency-references");
|
|
6
|
+
const errors_2 = require("./errors");
|
|
7
|
+
const provider_1 = require("./provider");
|
|
8
|
+
const provider_operations_1 = require("./provider-operations");
|
|
9
|
+
function invalidDescriptor(reason) {
|
|
10
|
+
return new errors_2.DiBagPluginValidationError('descriptor', reason);
|
|
11
|
+
}
|
|
12
|
+
function validateOptions(value) {
|
|
13
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
14
|
+
throw (0, errors_1.libraryError)('DI_BAG_INVALID_PLUGIN_OPTIONS', 'fromPlugin requires acquisitionMode and validate options', { operation: 'fromPlugin' });
|
|
15
|
+
}
|
|
16
|
+
if (!Object.hasOwn(value, 'acquisitionMode'))
|
|
17
|
+
throw (0, errors_1.libraryError)('DI_BAG_INVALID_PLUGIN_OPTIONS', 'fromPlugin requires acquisitionMode', { operation: 'fromPlugin' });
|
|
18
|
+
const acquisitionMode = Reflect.get(value, 'acquisitionMode');
|
|
19
|
+
if (acquisitionMode !== 'raw' && acquisitionMode !== 'nativePromise')
|
|
20
|
+
throw (0, errors_1.libraryError)('DI_BAG_INVALID_PLUGIN_OPTIONS', 'fromPlugin acquisitionMode must be raw or nativePromise', { operation: 'fromPlugin' });
|
|
21
|
+
if (!Object.hasOwn(value, 'validate'))
|
|
22
|
+
throw (0, errors_1.libraryError)('DI_BAG_INVALID_PLUGIN_OPTIONS', 'fromPlugin requires a validation predicate', { operation: 'fromPlugin' });
|
|
23
|
+
const validate = Reflect.get(value, 'validate');
|
|
24
|
+
if (typeof validate !== 'function')
|
|
25
|
+
throw (0, errors_1.libraryError)('DI_BAG_INVALID_PLUGIN_OPTIONS', 'fromPlugin validate must be a function', { operation: 'fromPlugin' });
|
|
26
|
+
return Object.freeze({ acquisitionMode, validate: validate });
|
|
27
|
+
}
|
|
28
|
+
function validateDescriptor(value) {
|
|
29
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
30
|
+
throw invalidDescriptor('plugin descriptor must be a non-array object');
|
|
31
|
+
}
|
|
32
|
+
if (!Object.hasOwn(value, 'apiVersion'))
|
|
33
|
+
throw invalidDescriptor('plugin descriptor requires own apiVersion');
|
|
34
|
+
if (Reflect.get(value, 'apiVersion') !== 1)
|
|
35
|
+
throw invalidDescriptor('plugin apiVersion must be 1');
|
|
36
|
+
if (!Object.hasOwn(value, 'create'))
|
|
37
|
+
throw invalidDescriptor('plugin descriptor requires own create');
|
|
38
|
+
const create = Reflect.get(value, 'create');
|
|
39
|
+
if (typeof create !== 'function')
|
|
40
|
+
throw invalidDescriptor('plugin create must be a function');
|
|
41
|
+
if (!Object.hasOwn(value, 'dispose'))
|
|
42
|
+
return Object.freeze({ apiVersion: 1, create });
|
|
43
|
+
const dispose = Reflect.get(value, 'dispose');
|
|
44
|
+
if (typeof dispose !== 'function')
|
|
45
|
+
throw invalidDescriptor('plugin dispose must be a function when present');
|
|
46
|
+
return Object.freeze({ apiVersion: 1, create, dispose });
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Validate an unknown plugin descriptor and its output at a declared dependency boundary.
|
|
50
|
+
* The descriptor must have own `apiVersion: 1` and callable `create`, with an optional
|
|
51
|
+
* callable `dispose`. A disposer owns the original acquired value before validation.
|
|
52
|
+
* @param dependencies - Host values supplied to the plugin in positional order.
|
|
53
|
+
* @param plugin - The application-selected unknown descriptor.
|
|
54
|
+
* @param options - Required raw/nativePromise acquisition and a synchronous output predicate.
|
|
55
|
+
* @returns A lazy provider that validates its output when acquired.
|
|
56
|
+
* @throws {@link DiBagPluginValidationError} for an invalid descriptor or rejected output.
|
|
57
|
+
*/
|
|
58
|
+
function createPluginProvider(dependencies, plugin, options) {
|
|
59
|
+
const references = (0, dependency_references_1.snapshotReferences)(dependencies);
|
|
60
|
+
const selected = validateOptions(options);
|
|
61
|
+
const descriptor = validateDescriptor(plugin);
|
|
62
|
+
const create = (deps) => Reflect.apply(descriptor.create, undefined, references.map(reference => Reflect.get(deps, reference.slot)));
|
|
63
|
+
const dispose = descriptor.dispose === undefined ? undefined : (value) => Reflect.apply(descriptor.dispose, undefined, [value]);
|
|
64
|
+
const project = (value) => {
|
|
65
|
+
if (Reflect.apply(selected.validate, undefined, [value]) !== true) {
|
|
66
|
+
throw new errors_2.DiBagPluginValidationError('output', 'plugin output failed validation');
|
|
67
|
+
}
|
|
68
|
+
return value;
|
|
69
|
+
};
|
|
70
|
+
if (selected.acquisitionMode === 'raw') {
|
|
71
|
+
const source = (0, provider_1.createProvider)();
|
|
72
|
+
(0, provider_operations_1.retainDescription)(source, (0, provider_operations_1.sourceDescription)(create, dispose, references.map(reference => reference.key), 'raw', false, references));
|
|
73
|
+
return (0, provider_1.transformService)(source, { mode: 'direct', transform: project, acquisitionMode: 'raw' });
|
|
74
|
+
}
|
|
75
|
+
const source = (0, provider_1.createProvider)();
|
|
76
|
+
(0, provider_operations_1.retainDescription)(source, (0, provider_operations_1.sourceDescription)(create, dispose, references.map(reference => reference.key), 'nativePromise', false, references));
|
|
77
|
+
return (0, provider_1.transformService)(source, { mode: 'awaited', transform: project });
|
|
78
|
+
}
|
|
79
|
+
// Both signatures retain the same tuple admission and invariant provider result.
|
|
80
|
+
// Generic assignability otherwise re-infers T as T & DependencyTupleAdmission<T>,
|
|
81
|
+
// applying admission twice. Name the public callable without widening either contract.
|
|
82
|
+
exports.fromPlugin = createPluginProvider;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { normalize } from './provider-operations';
|
|
2
|
+
import type { AcquisitionMetadataPresence } from './inspection';
|
|
3
|
+
import type { RuntimeContext } from './acquisition-mode';
|
|
4
|
+
import type { AcquisitionContext } from './acquisition-context';
|
|
5
|
+
type RegistrationDescription = ReturnType<typeof normalize>;
|
|
6
|
+
interface ExecutionEvents {
|
|
7
|
+
accepted(): void;
|
|
8
|
+
settled(): void;
|
|
9
|
+
drained(): void;
|
|
10
|
+
invoking(): number;
|
|
11
|
+
cleanupStarted?(): void;
|
|
12
|
+
cleanupCompleted?(outcome: 'success' | 'failure'): void;
|
|
13
|
+
cleanupFailed(sequence: number, error: unknown): void;
|
|
14
|
+
}
|
|
15
|
+
/** Fully drained borrowed attempts keep frames, but no execution closures or payloads. */
|
|
16
|
+
export declare class CompletedExecution {
|
|
17
|
+
private frames;
|
|
18
|
+
readonly state = "ready";
|
|
19
|
+
readonly sourceInFlight = false;
|
|
20
|
+
readonly hasOwnership = false;
|
|
21
|
+
readonly error: undefined;
|
|
22
|
+
readonly work: readonly Promise<void>[];
|
|
23
|
+
constructor(frames: AcquisitionMetadataPresence<readonly unknown[]>);
|
|
24
|
+
inspectFrames(): AcquisitionMetadataPresence<readonly unknown[]>;
|
|
25
|
+
ready(): Promise<void>;
|
|
26
|
+
dispose(): Promise<void>;
|
|
27
|
+
release(): void;
|
|
28
|
+
}
|
|
29
|
+
/** One source invocation and its ordered projections/ownership, local to an attempt. */
|
|
30
|
+
export declare class ProviderExecution {
|
|
31
|
+
private readonly events;
|
|
32
|
+
private readonly context;
|
|
33
|
+
private readonly frames;
|
|
34
|
+
private readonly stages;
|
|
35
|
+
private readonly pending;
|
|
36
|
+
private result;
|
|
37
|
+
private cleaning;
|
|
38
|
+
sourceInFlight: boolean;
|
|
39
|
+
constructor(events: ExecutionEvents, description: RegistrationDescription, context: RuntimeContext);
|
|
40
|
+
inspectFrames(): AcquisitionMetadataPresence<readonly unknown[]>;
|
|
41
|
+
get state(): 'pending' | 'ready' | 'failed';
|
|
42
|
+
get error(): unknown;
|
|
43
|
+
get hasOwnership(): boolean;
|
|
44
|
+
get work(): readonly Promise<void>[];
|
|
45
|
+
/** Observe the selected stage, retaining its failure even after retirement. */
|
|
46
|
+
ready(): Promise<void>;
|
|
47
|
+
compact(): ProviderExecution | CompletedExecution;
|
|
48
|
+
/** Classify/own only after the direct operation-free source call has returned. */
|
|
49
|
+
publishSource(value: unknown, description: RegistrationDescription): void;
|
|
50
|
+
evaluate(description: RegistrationDescription, deps: unknown, acquisitionContext: () => AcquisitionContext): unknown;
|
|
51
|
+
private consume;
|
|
52
|
+
private capture;
|
|
53
|
+
private own;
|
|
54
|
+
private accept;
|
|
55
|
+
dispose(): Promise<void>;
|
|
56
|
+
private disposeStages;
|
|
57
|
+
release(): void;
|
|
58
|
+
}
|
|
59
|
+
export {};
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ProviderExecution = exports.CompletedExecution = void 0;
|
|
4
|
+
const errors_1 = require("./errors");
|
|
5
|
+
const observePromise = (Promise.prototype.then);
|
|
6
|
+
const emptyFrames = Object.freeze([]);
|
|
7
|
+
const emptyWork = Object.freeze([]);
|
|
8
|
+
/** Fully drained borrowed attempts keep frames, but no execution closures or payloads. */
|
|
9
|
+
class CompletedExecution {
|
|
10
|
+
frames;
|
|
11
|
+
state = 'ready';
|
|
12
|
+
sourceInFlight = false;
|
|
13
|
+
hasOwnership = false;
|
|
14
|
+
error = undefined;
|
|
15
|
+
work = emptyWork;
|
|
16
|
+
constructor(frames) {
|
|
17
|
+
this.frames = frames;
|
|
18
|
+
}
|
|
19
|
+
inspectFrames() { return Object.freeze([...this.frames]); }
|
|
20
|
+
async ready() { }
|
|
21
|
+
async dispose() { }
|
|
22
|
+
release() {
|
|
23
|
+
// Frame-bearing records belong to one attempt; the shared empty record is
|
|
24
|
+
// never mutated. Inspection arrays already returned to callers stay intact.
|
|
25
|
+
if (this.frames.length)
|
|
26
|
+
this.frames = emptyFrames;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
exports.CompletedExecution = CompletedExecution;
|
|
30
|
+
const completedWithoutFrames = new CompletedExecution(emptyFrames);
|
|
31
|
+
/** One source invocation and its ordered projections/ownership, local to an attempt. */
|
|
32
|
+
class ProviderExecution {
|
|
33
|
+
events;
|
|
34
|
+
context;
|
|
35
|
+
frames;
|
|
36
|
+
stages = [];
|
|
37
|
+
pending = new Set();
|
|
38
|
+
result;
|
|
39
|
+
cleaning;
|
|
40
|
+
// Source permission is independent of the exposed projection's cache state.
|
|
41
|
+
sourceInFlight = true;
|
|
42
|
+
constructor(events, description, context) {
|
|
43
|
+
this.events = events;
|
|
44
|
+
this.context = context;
|
|
45
|
+
// Reserve every metadata frame before the source can reenter inspection.
|
|
46
|
+
this.frames = description.operations.filter(operation => operation.kind === 'frame-sync' || operation.kind === 'frame-async')
|
|
47
|
+
.map(() => Object.freeze({ present: false }));
|
|
48
|
+
}
|
|
49
|
+
inspectFrames() { return Object.freeze([...this.frames]); }
|
|
50
|
+
get state() { return this.result?.state ?? 'failed'; }
|
|
51
|
+
get error() { return this.result?.error; }
|
|
52
|
+
get hasOwnership() { return this.stages.length > 0; }
|
|
53
|
+
get work() { return [...this.pending]; }
|
|
54
|
+
/** Observe the selected stage, retaining its failure even after retirement. */
|
|
55
|
+
async ready() {
|
|
56
|
+
const result = this.result;
|
|
57
|
+
if (!result)
|
|
58
|
+
throw (0, errors_1.libraryError)('DI_BAG_INTERNAL_STATE', 'acquisition has no result', {});
|
|
59
|
+
if (result.state === 'pending')
|
|
60
|
+
await result.settled;
|
|
61
|
+
if (result.state === 'failed')
|
|
62
|
+
throw result.error;
|
|
63
|
+
}
|
|
64
|
+
compact() {
|
|
65
|
+
if (this.state !== 'ready' || this.pending.size || this.hasOwnership)
|
|
66
|
+
return this;
|
|
67
|
+
return this.frames.length ? new CompletedExecution(this.inspectFrames()) : completedWithoutFrames;
|
|
68
|
+
}
|
|
69
|
+
/** Classify/own only after the direct operation-free source call has returned. */
|
|
70
|
+
publishSource(value, description) {
|
|
71
|
+
if (description.acquisitionMode === 'raw') {
|
|
72
|
+
this.sourceInFlight = false;
|
|
73
|
+
this.result = { exposed: undefined, consumed: true, state: 'ready', value: undefined, error: undefined, owners: [] };
|
|
74
|
+
if (description.dispose)
|
|
75
|
+
this.accept(0, value, description.dispose);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const stage = this.capture(() => value, true, description.acquisitionMode);
|
|
79
|
+
if (description.dispose)
|
|
80
|
+
this.own(stage, 0, description.dispose);
|
|
81
|
+
this.result = stage;
|
|
82
|
+
this.consume(stage);
|
|
83
|
+
if (stage.state === 'failed')
|
|
84
|
+
throw stage.error;
|
|
85
|
+
}
|
|
86
|
+
evaluate(description, deps, acquisitionContext) {
|
|
87
|
+
const { create, dispose } = description;
|
|
88
|
+
let current = this.capture(() => description.contextual
|
|
89
|
+
? Reflect.apply(create, undefined, [deps, acquisitionContext()])
|
|
90
|
+
: create(deps), true, description.acquisitionMode);
|
|
91
|
+
let nextFrame = 0;
|
|
92
|
+
if (dispose)
|
|
93
|
+
this.own(current, 0, dispose);
|
|
94
|
+
description.operations.forEach((operation, offset) => {
|
|
95
|
+
const inputStage = current;
|
|
96
|
+
const index = offset + 1;
|
|
97
|
+
if (operation.kind === 'owned') {
|
|
98
|
+
this.own(current, index, operation.dispose);
|
|
99
|
+
}
|
|
100
|
+
else if (operation.kind === 'map-sync') {
|
|
101
|
+
if (current.state !== 'failed') {
|
|
102
|
+
const input = current.exposed;
|
|
103
|
+
const { project } = operation;
|
|
104
|
+
current = this.capture(() => project(input), false, operation.acquisitionMode);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
else if (operation.kind === 'map-async') {
|
|
108
|
+
const input = current;
|
|
109
|
+
const { project } = operation;
|
|
110
|
+
current = this.capture(async () => {
|
|
111
|
+
if (input.state === 'failed')
|
|
112
|
+
throw input.error;
|
|
113
|
+
return project(await input.exposed);
|
|
114
|
+
}, false, 'nativePromise');
|
|
115
|
+
}
|
|
116
|
+
else if (operation.kind === 'frame-sync' || operation.kind === 'frame-async') {
|
|
117
|
+
const frameIndex = nextFrame++;
|
|
118
|
+
const input = current;
|
|
119
|
+
const { project } = operation;
|
|
120
|
+
const apply = (value) => {
|
|
121
|
+
const projected = project(value);
|
|
122
|
+
this.frames[frameIndex] = Object.freeze({ present: true, value: projected.frame });
|
|
123
|
+
return projected.value;
|
|
124
|
+
};
|
|
125
|
+
if (operation.kind === 'frame-async') {
|
|
126
|
+
current = this.capture(async () => {
|
|
127
|
+
if (input.state === 'failed')
|
|
128
|
+
throw input.error;
|
|
129
|
+
return apply(await input.exposed);
|
|
130
|
+
}, false, 'nativePromise');
|
|
131
|
+
}
|
|
132
|
+
else if (input.state !== 'failed') {
|
|
133
|
+
current = this.capture(() => apply(input.exposed), false, operation.acquisitionMode);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (current !== inputStage)
|
|
137
|
+
this.consume(inputStage);
|
|
138
|
+
});
|
|
139
|
+
this.result = current;
|
|
140
|
+
if (current.state === 'failed')
|
|
141
|
+
throw current.error;
|
|
142
|
+
const exposed = current.exposed;
|
|
143
|
+
this.consume(current);
|
|
144
|
+
return exposed;
|
|
145
|
+
}
|
|
146
|
+
consume(stage) {
|
|
147
|
+
// Projections have captured their input and ownership has its own value.
|
|
148
|
+
// Pending callbacks still accept ownership, but must not retain fulfillment.
|
|
149
|
+
stage.consumed = true;
|
|
150
|
+
stage.exposed = undefined;
|
|
151
|
+
stage.value = undefined;
|
|
152
|
+
}
|
|
153
|
+
capture(create, source, mode) {
|
|
154
|
+
try {
|
|
155
|
+
const exposed = create();
|
|
156
|
+
const stage = { exposed, consumed: false, state: 'ready', value: exposed, error: undefined, owners: [] };
|
|
157
|
+
let native = mode === 'nativePromise';
|
|
158
|
+
if (mode === 'auto') {
|
|
159
|
+
const { isNativePromise } = this.context;
|
|
160
|
+
// Whole-graph preflight establishes capability before invoking this factory.
|
|
161
|
+
const classified = isNativePromise(exposed);
|
|
162
|
+
if (typeof classified !== 'boolean')
|
|
163
|
+
throw (0, errors_1.libraryError)('DI_BAG_INVALID_CLASSIFIER_RESULT', 'isNativePromise must return a boolean', { option: 'isNativePromise', provided: classified });
|
|
164
|
+
native = classified;
|
|
165
|
+
}
|
|
166
|
+
if (mode !== 'raw' && exposed !== null && (typeof exposed === 'object' || typeof exposed === 'function') && 'then' in exposed) {
|
|
167
|
+
// Preserve original getter failures, but never use callability as native branding.
|
|
168
|
+
const then = Reflect.get(exposed, 'then');
|
|
169
|
+
if (!native && typeof then === 'function')
|
|
170
|
+
throw (0, errors_1.libraryTypeError)('DI_BAG_STRUCTURAL_THENABLE', 'Structural thenables require explicit native Promise conversion or raw acquisitionMode', { acquisitionMode: mode });
|
|
171
|
+
}
|
|
172
|
+
if (native) {
|
|
173
|
+
let settled;
|
|
174
|
+
const barrier = new Promise(resolve => { settled = resolve; });
|
|
175
|
+
// The species result is untrusted; only our independently made barrier
|
|
176
|
+
// participates in draining. No structural assimilation or error fallback.
|
|
177
|
+
observePromise.call(exposed, value => {
|
|
178
|
+
stage.state = 'ready';
|
|
179
|
+
if (!stage.consumed)
|
|
180
|
+
stage.value = value;
|
|
181
|
+
for (const owner of stage.owners)
|
|
182
|
+
this.accept(owner.index, value, owner.dispose);
|
|
183
|
+
stage.owners.length = 0;
|
|
184
|
+
finish();
|
|
185
|
+
}, error => {
|
|
186
|
+
stage.state = 'failed';
|
|
187
|
+
stage.error = error;
|
|
188
|
+
stage.owners.length = 0;
|
|
189
|
+
finish();
|
|
190
|
+
});
|
|
191
|
+
stage.state = 'pending';
|
|
192
|
+
stage.value = undefined;
|
|
193
|
+
stage.settled = barrier;
|
|
194
|
+
this.pending.add(barrier);
|
|
195
|
+
const finish = () => {
|
|
196
|
+
if (source)
|
|
197
|
+
this.sourceInFlight = false;
|
|
198
|
+
this.pending.delete(barrier);
|
|
199
|
+
settled();
|
|
200
|
+
if (this.result === stage)
|
|
201
|
+
this.events.settled();
|
|
202
|
+
if (!this.pending.size)
|
|
203
|
+
this.events.drained();
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
else if (source) {
|
|
207
|
+
this.sourceInFlight = false;
|
|
208
|
+
}
|
|
209
|
+
return stage;
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
if (source)
|
|
213
|
+
this.sourceInFlight = false;
|
|
214
|
+
return { exposed: undefined, consumed: true, state: 'failed', value: undefined, error, owners: [] };
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
own(stage, index, dispose) {
|
|
218
|
+
if (stage.state === 'ready')
|
|
219
|
+
this.accept(index, stage.value, dispose);
|
|
220
|
+
else if (stage.state === 'pending')
|
|
221
|
+
stage.owners.push({ index, dispose });
|
|
222
|
+
}
|
|
223
|
+
accept(index, value, dispose) {
|
|
224
|
+
this.stages.push({ index, value, dispose, state: 'accepted' });
|
|
225
|
+
this.events.accepted();
|
|
226
|
+
}
|
|
227
|
+
dispose() {
|
|
228
|
+
if (this.cleaning)
|
|
229
|
+
return this.cleaning;
|
|
230
|
+
let complete;
|
|
231
|
+
this.cleaning = new Promise(resolve => { complete = resolve; });
|
|
232
|
+
// Publish before invoking user code, including synchronous finalizers.
|
|
233
|
+
void this.disposeStages().then(complete);
|
|
234
|
+
return this.cleaning;
|
|
235
|
+
}
|
|
236
|
+
async disposeStages() {
|
|
237
|
+
// All later acceptances must be known before reversing stable stage indices.
|
|
238
|
+
while (this.pending.size)
|
|
239
|
+
await Promise.all(this.pending);
|
|
240
|
+
const owned = this.stages.length > 0;
|
|
241
|
+
let failed = false;
|
|
242
|
+
if (owned)
|
|
243
|
+
this.events.cleanupStarted?.();
|
|
244
|
+
for (const stage of this.stages.sort((a, b) => b.index - a.index)) {
|
|
245
|
+
stage.state = 'disposing';
|
|
246
|
+
const sequence = this.events.invoking();
|
|
247
|
+
const { dispose, value } = stage;
|
|
248
|
+
try {
|
|
249
|
+
await dispose(value);
|
|
250
|
+
}
|
|
251
|
+
catch (error) {
|
|
252
|
+
failed = true;
|
|
253
|
+
this.events.cleanupFailed(sequence, error);
|
|
254
|
+
}
|
|
255
|
+
finally {
|
|
256
|
+
stage.state = 'disposed';
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (owned)
|
|
260
|
+
this.events.cleanupCompleted?.(failed ? 'failure' : 'success');
|
|
261
|
+
this.stages.length = 0;
|
|
262
|
+
this.result = undefined;
|
|
263
|
+
}
|
|
264
|
+
release() {
|
|
265
|
+
this.frames.length = 0;
|
|
266
|
+
this.stages.length = 0;
|
|
267
|
+
this.pending.clear();
|
|
268
|
+
this.result = undefined;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
exports.ProviderExecution = ProviderExecution;
|