@loutrejs/loutre 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -32
- package/dist/adapters/aws-lambda.d.ts +5 -3
- package/dist/adapters/aws-lambda.js +53 -23
- package/dist/adapters/bun.d.ts +7 -4
- package/dist/adapters/bun.js +52 -42
- package/dist/adapters/cloudflare-workers.d.ts +5 -3
- package/dist/adapters/cloudflare-workers.js +23 -28
- package/dist/adapters/deno.d.ts +7 -4
- package/dist/adapters/deno.js +94 -57
- package/dist/adapters/electron.d.ts +13 -5
- package/dist/adapters/electron.js +76 -28
- package/dist/application/definition.d.ts +31 -0
- package/dist/application/definition.js +16 -0
- package/dist/application/index.d.ts +4 -63
- package/dist/application/index.js +2 -11
- package/dist/application/kernel-internal.d.ts +2 -0
- package/dist/application/kernel-internal.js +10 -0
- package/dist/application/kernel.d.ts +21 -0
- package/dist/application/kernel.js +65 -0
- package/dist/core/diagnostic.d.ts +9 -0
- package/dist/core/diagnostic.js +9 -0
- package/dist/core/extension.d.ts +107 -0
- package/dist/core/extension.js +73 -0
- package/dist/core/generic-layer.d.ts +26 -0
- package/dist/core/generic-layer.js +116 -0
- package/dist/core/index.d.ts +31 -15
- package/dist/core/index.js +15 -15
- package/dist/core/injection.d.ts +4 -13
- package/dist/core/injection.js +63 -0
- package/dist/core/model/context.d.ts +18 -0
- package/dist/core/model/context.js +34 -0
- package/dist/core/model/lifecycle.d.ts +5 -0
- package/dist/core/model/lifecycle.js +44 -0
- package/dist/core/model/modules.d.ts +2 -0
- package/dist/core/model/modules.js +101 -0
- package/dist/core/model/providers.d.ts +4 -0
- package/dist/core/model/providers.js +74 -0
- package/dist/core/model/snapshot.d.ts +6 -0
- package/dist/core/model/snapshot.js +31 -0
- package/dist/core/model/types.d.ts +78 -0
- package/dist/core/model/validation.d.ts +4 -0
- package/dist/core/model/validation.js +40 -0
- package/dist/core/model/visibility.d.ts +4 -0
- package/dist/core/model/visibility.js +20 -0
- package/dist/core/model.d.ts +10 -0
- package/dist/core/model.js +346 -0
- package/dist/core/module.d.ts +8 -13
- package/dist/core/source-location.d.ts +8 -0
- package/dist/core/source-location.js +31 -0
- package/dist/core/token-internal.d.ts +3 -0
- package/dist/core/token-internal.js +4 -0
- package/dist/core/type.d.ts +7 -0
- package/dist/core/type.js +4 -0
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +1 -1
- package/dist/graph/index.d.ts +2 -3
- package/dist/graph/index.js +1 -3
- package/dist/graph/model.d.ts +34 -0
- package/dist/graph/model.js +127 -0
- package/dist/http/auth.d.ts +46 -0
- package/dist/http/auth.js +96 -0
- package/dist/http/client.d.ts +28 -31
- package/dist/http/client.js +58 -44
- package/dist/http/cors-internal.d.ts +29 -0
- package/dist/http/cors-internal.js +160 -0
- package/dist/http/cors.d.ts +5 -8
- package/dist/http/cors.js +7 -165
- package/dist/http/extension.d.ts +348 -0
- package/dist/http/extension.js +947 -0
- package/dist/http/index.d.ts +9 -7
- package/dist/http/index.js +5 -7
- package/dist/{application → http}/openapi.d.ts +12 -3
- package/dist/{application → http}/openapi.js +164 -102
- package/dist/http/path.d.ts +1 -42
- package/dist/http/path.js +1 -2
- package/dist/http/runtime/request.d.ts +17 -0
- package/dist/http/runtime/request.js +109 -0
- package/dist/http/runtime/response.d.ts +18 -0
- package/dist/http/runtime/response.js +108 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +2 -0
- package/dist/message-port/extension.d.ts +66 -0
- package/dist/message-port/extension.js +395 -0
- package/dist/message-port/index.d.ts +4 -73
- package/dist/message-port/index.js +2 -189
- package/dist/message-port/transport.d.ts +11 -0
- package/dist/message-port/transport.js +45 -0
- package/dist/runtime/async-iterator-lifecycle.d.ts +12 -0
- package/dist/runtime/async-iterator-lifecycle.js +95 -0
- package/dist/runtime/capability.d.ts +11 -11
- package/dist/runtime/capability.js +3 -3
- package/dist/runtime/di.d.ts +2 -22
- package/dist/runtime/di.js +8 -285
- package/dist/runtime/execution-tracker.d.ts +8 -0
- package/dist/runtime/execution-tracker.js +54 -0
- package/dist/runtime/index.d.ts +15 -9
- package/dist/runtime/index.js +9 -9
- package/dist/runtime/ingress-gate.d.ts +7 -0
- package/dist/runtime/ingress-gate.js +33 -0
- package/dist/runtime/kernel.d.ts +23 -0
- package/dist/runtime/kernel.js +354 -0
- package/dist/runtime/logger.d.ts +2 -0
- package/dist/runtime/logger.js +2 -0
- package/dist/runtime/node-capabilities.d.ts +1 -1
- package/dist/runtime/node-capabilities.js +1 -1
- package/dist/runtime/server-port.d.ts +2 -0
- package/dist/runtime/server-port.js +11 -0
- package/dist/tasks/extension.d.ts +144 -0
- package/dist/tasks/extension.js +499 -0
- package/dist/tasks/index.d.ts +2 -0
- package/dist/tasks/index.js +1 -0
- package/dist/tasks/runtime/cron.d.ts +6 -0
- package/dist/tasks/runtime/cron.js +89 -0
- package/dist/websocket/errors.d.ts +9 -0
- package/dist/websocket/errors.js +18 -0
- package/dist/websocket/extension.d.ts +154 -0
- package/dist/websocket/extension.js +524 -0
- package/dist/websocket/index.d.ts +3 -0
- package/dist/websocket/index.js +2 -0
- package/package.json +12 -16
- package/dist/application/binding.d.ts +0 -33
- package/dist/application/binding.js +0 -295
- package/dist/application/host.d.ts +0 -8
- package/dist/application/host.js +0 -14
- package/dist/application/queue.d.ts +0 -3
- package/dist/application/queue.js +0 -4
- package/dist/application/scheduler.d.ts +0 -2
- package/dist/application/scheduler.js +0 -45
- package/dist/core/context-key.d.ts +0 -16
- package/dist/core/context-key.js +0 -17
- package/dist/core/contract-internal.d.ts +0 -22
- package/dist/core/contract-internal.js +0 -61
- package/dist/core/contract.d.ts +0 -96
- package/dist/core/contract.js +0 -88
- package/dist/core/execution.d.ts +0 -78
- package/dist/core/execution.js +0 -61
- package/dist/core/implementation.d.ts +0 -44
- package/dist/core/implementation.js +0 -35
- package/dist/core/layer.d.ts +0 -137
- package/dist/core/layer.js +0 -81
- package/dist/graph/compiler.d.ts +0 -4
- package/dist/graph/compiler.js +0 -228
- package/dist/graph/graph.d.ts +0 -18
- package/dist/graph/graph.js +0 -1118
- package/dist/graph/ir.d.ts +0 -175
- package/dist/http/application.d.ts +0 -18
- package/dist/http/application.js +0 -515
- package/dist/http/basic-auth.d.ts +0 -37
- package/dist/http/basic-auth.js +0 -67
- package/dist/http/definitions.d.ts +0 -363
- package/dist/http/definitions.js +0 -310
- package/dist/http/hosted-protocol.d.ts +0 -4
- package/dist/http/hosted-protocol.js +0 -4
- package/dist/http/internal.d.ts +0 -8
- package/dist/http/internal.js +0 -1
- package/dist/http/params.d.ts +0 -2
- package/dist/http/params.js +0 -19
- package/dist/http/request-body.d.ts +0 -4
- package/dist/http/request-body.js +0 -6
- package/dist/http/validate.d.ts +0 -20
- package/dist/http/validate.js +0 -6
- package/dist/message-port/environment.d.ts +0 -2
- package/dist/message-port/environment.js +0 -4
- package/dist/runtime/application.d.ts +0 -38
- package/dist/runtime/application.js +0 -339
- package/dist/runtime/pipeline.d.ts +0 -11
- package/dist/runtime/pipeline.js +0 -161
- /package/dist/{graph/ir.js → core/model/types.js} +0 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { Diagnostic } from './diagnostic.js';
|
|
2
|
+
import type { TokenLike } from './token.js';
|
|
3
|
+
declare const runtimeCapabilityValue: unique symbol;
|
|
4
|
+
declare const executionExtensionTypeInfo: unique symbol;
|
|
5
|
+
export interface RuntimeCapability<TValue = unknown> {
|
|
6
|
+
readonly kind: 'runtime-capability';
|
|
7
|
+
readonly id: string;
|
|
8
|
+
readonly identity: symbol;
|
|
9
|
+
readonly [runtimeCapabilityValue]?: TValue;
|
|
10
|
+
}
|
|
11
|
+
export type RuntimeCapabilityValue<TCapability> = TCapability extends RuntimeCapability<infer TValue> ? TValue : never;
|
|
12
|
+
export declare function runtimeCapability<TValue>(id: string): RuntimeCapability<TValue>;
|
|
13
|
+
export interface RuntimeCapabilityBindings {
|
|
14
|
+
has(capability: RuntimeCapability<any>): boolean;
|
|
15
|
+
get<TValue>(capability: RuntimeCapability<TValue>): TValue;
|
|
16
|
+
}
|
|
17
|
+
export interface RuntimeCapabilityBinding<TValue = unknown> {
|
|
18
|
+
readonly capability: RuntimeCapability<TValue>;
|
|
19
|
+
readonly value: TValue;
|
|
20
|
+
}
|
|
21
|
+
export declare function bindRuntimeCapability<TValue>(capability: RuntimeCapability<TValue>, value: TValue): RuntimeCapabilityBinding<TValue>;
|
|
22
|
+
export declare class RuntimeCapabilityRegistry implements RuntimeCapabilityBindings {
|
|
23
|
+
#private;
|
|
24
|
+
constructor(bindings?: Iterable<RuntimeCapabilityBinding>);
|
|
25
|
+
has(capability: RuntimeCapability<any>): boolean;
|
|
26
|
+
get<TValue>(capability: RuntimeCapability<TValue>): TValue;
|
|
27
|
+
}
|
|
28
|
+
export interface ExecutionLease {
|
|
29
|
+
readonly signal: AbortSignal;
|
|
30
|
+
abort(reason?: unknown): void;
|
|
31
|
+
complete(): void;
|
|
32
|
+
}
|
|
33
|
+
export interface ExecutionKernelRuntime {
|
|
34
|
+
beginExecution(): ExecutionLease;
|
|
35
|
+
resolve<TValue>(token: TokenLike<TValue>, source?: string): TValue;
|
|
36
|
+
}
|
|
37
|
+
export interface ExecutionExtensionDrainContext {
|
|
38
|
+
readonly timeoutMs: number;
|
|
39
|
+
}
|
|
40
|
+
export interface ExecutionExtensionRuntime {
|
|
41
|
+
drain?(context: ExecutionExtensionDrainContext): void | Promise<void>;
|
|
42
|
+
close?(): void | Promise<void>;
|
|
43
|
+
}
|
|
44
|
+
export interface ExecutionCompileContext {
|
|
45
|
+
readonly moduleId: string;
|
|
46
|
+
readonly definitionIndex: number;
|
|
47
|
+
}
|
|
48
|
+
export interface ExecutionContribution<TCompiled = unknown> {
|
|
49
|
+
readonly kind: 'execution';
|
|
50
|
+
readonly id: string;
|
|
51
|
+
readonly executionKind: string;
|
|
52
|
+
readonly dependencies: readonly TokenLike[];
|
|
53
|
+
readonly capabilities: readonly RuntimeCapability[];
|
|
54
|
+
readonly compiled: TCompiled;
|
|
55
|
+
}
|
|
56
|
+
export interface ExecutionExtensionValidationContext<TCompiled = unknown> {
|
|
57
|
+
readonly executions: readonly ExecutionContribution<TCompiled>[];
|
|
58
|
+
}
|
|
59
|
+
export interface ExecutionProjectionContext<TCompiled = unknown> {
|
|
60
|
+
readonly execution: ExecutionContribution<TCompiled>;
|
|
61
|
+
}
|
|
62
|
+
export interface ExecutionExtensionRuntimeContext<TCompiled = unknown> {
|
|
63
|
+
readonly executions: readonly ExecutionContribution<TCompiled>[];
|
|
64
|
+
readonly capabilities: RuntimeCapabilityBindings;
|
|
65
|
+
readonly applicationRuntime: ExecutionKernelRuntime;
|
|
66
|
+
}
|
|
67
|
+
export interface HostExtensionContext<TCompiled = unknown, TRuntime extends ExecutionExtensionRuntime = ExecutionExtensionRuntime> {
|
|
68
|
+
readonly executions: readonly ExecutionContribution<TCompiled>[];
|
|
69
|
+
readonly runtime: TRuntime;
|
|
70
|
+
readonly applicationRuntime: ExecutionKernelRuntime;
|
|
71
|
+
}
|
|
72
|
+
export interface HostExtension<TNamespace extends string = string, TApi extends object = object, TCompiled = unknown, TRuntime extends ExecutionExtensionRuntime = ExecutionExtensionRuntime> {
|
|
73
|
+
readonly namespace: TNamespace;
|
|
74
|
+
create(context: HostExtensionContext<TCompiled, TRuntime>): TApi;
|
|
75
|
+
}
|
|
76
|
+
export interface ExecutionExtension<TDefinition extends ExecutionDefinition<any> = ExecutionDefinition<any>, TCompiled = unknown, TNamespace extends string = string, THostApi extends object = object, TRuntime extends ExecutionExtensionRuntime = ExecutionExtensionRuntime> {
|
|
77
|
+
readonly kind: 'execution-extension';
|
|
78
|
+
readonly name: string;
|
|
79
|
+
readonly abiVersion: string;
|
|
80
|
+
readonly identity: symbol;
|
|
81
|
+
readonly [executionExtensionTypeInfo]?: readonly [TNamespace, THostApi];
|
|
82
|
+
compile(definition: TDefinition, context: ExecutionCompileContext): ExecutionContribution<TCompiled>;
|
|
83
|
+
references?(definition: TDefinition): readonly ExecutionDefinition[];
|
|
84
|
+
validate?(context: ExecutionExtensionValidationContext<TCompiled>): readonly Diagnostic[];
|
|
85
|
+
createRuntime(context: ExecutionExtensionRuntimeContext<TCompiled>): TRuntime | Promise<TRuntime>;
|
|
86
|
+
projectGraph?(context: ExecutionProjectionContext<TCompiled>): unknown;
|
|
87
|
+
readonly host?: HostExtension<TNamespace, THostApi, TCompiled, TRuntime>;
|
|
88
|
+
}
|
|
89
|
+
export declare function defineExecutionExtension<const TDefinition extends ExecutionDefinition, TCompiled, const TNamespace extends string = never, THostApi extends object = {}, TRuntime extends ExecutionExtensionRuntime = ExecutionExtensionRuntime>(extension: Omit<ExecutionExtension<TDefinition, TCompiled, TNamespace, THostApi, TRuntime>, 'identity'>): ExecutionExtension<TDefinition, TCompiled, TNamespace, THostApi, TRuntime>;
|
|
90
|
+
export declare const executionDefinitionBrand: unique symbol;
|
|
91
|
+
export interface ExecutionDefinition<TExtension extends AnyExecutionExtension = AnyExecutionExtension> {
|
|
92
|
+
readonly kind: 'execution-definition';
|
|
93
|
+
readonly extension: TExtension;
|
|
94
|
+
readonly [executionDefinitionBrand]: true;
|
|
95
|
+
}
|
|
96
|
+
export declare function defineExecution<const TExtension extends ExecutionExtension, const TDefinition extends object>(extension: TExtension, definition: TDefinition): Readonly<TDefinition & ExecutionDefinition<TExtension>>;
|
|
97
|
+
export declare function isExecutionDefinition(value: unknown): value is ExecutionDefinition;
|
|
98
|
+
export type ExtensionOfDefinition<TDefinition> = TDefinition extends {
|
|
99
|
+
readonly extension: infer TExtension extends AnyExecutionExtension;
|
|
100
|
+
} ? TExtension : never;
|
|
101
|
+
export type AnyExecutionExtension = ExecutionExtension<any, any, any, any, any>;
|
|
102
|
+
export type HostApiOfExtension<TExtension> = TExtension extends {
|
|
103
|
+
readonly host?: infer THost;
|
|
104
|
+
} ? NonNullable<THost> extends HostExtension<infer TNamespace, infer THostApi, any, any> ? [TNamespace] extends [never] ? {} : {
|
|
105
|
+
readonly [K in TNamespace]: THostApi;
|
|
106
|
+
} : {} : {};
|
|
107
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const executionExtensionIdentityNamespace = 'loutre.execution-extension:';
|
|
2
|
+
const runtimeCapabilityIdentityNamespace = 'loutre.runtime-capability:';
|
|
3
|
+
export function runtimeCapability(id) {
|
|
4
|
+
if (id.trim().length === 0) {
|
|
5
|
+
throw new Error('LUTRE_CAPABILITY_ID: Capability id must not be empty.');
|
|
6
|
+
}
|
|
7
|
+
return Object.freeze({
|
|
8
|
+
kind: 'runtime-capability',
|
|
9
|
+
id,
|
|
10
|
+
identity: Symbol.for(`${runtimeCapabilityIdentityNamespace}${id}`),
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
export function bindRuntimeCapability(capability, value) {
|
|
14
|
+
return Object.freeze({ capability, value });
|
|
15
|
+
}
|
|
16
|
+
export class RuntimeCapabilityRegistry {
|
|
17
|
+
#values = new Map();
|
|
18
|
+
#capabilities = new Map();
|
|
19
|
+
constructor(bindings = []) {
|
|
20
|
+
for (const binding of bindings) {
|
|
21
|
+
const identity = binding.capability.identity;
|
|
22
|
+
const existing = this.#capabilities.get(identity);
|
|
23
|
+
if (existing && existing.id !== binding.capability.id) {
|
|
24
|
+
throw new Error(`LUTRE_CAPABILITY_ID_COLLISION: ${binding.capability.id}`);
|
|
25
|
+
}
|
|
26
|
+
if (this.#values.has(identity)) {
|
|
27
|
+
throw new Error(`LUTRE_CAPABILITY_DUPLICATE_BINDING: ${binding.capability.id}`);
|
|
28
|
+
}
|
|
29
|
+
this.#capabilities.set(identity, binding.capability);
|
|
30
|
+
this.#values.set(identity, binding.value);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
has(capability) {
|
|
34
|
+
return this.#values.has(capability.identity);
|
|
35
|
+
}
|
|
36
|
+
get(capability) {
|
|
37
|
+
if (!this.#values.has(capability.identity)) {
|
|
38
|
+
throw new Error(`LUTRE_CAPABILITY_MISSING: ${capability.id}`);
|
|
39
|
+
}
|
|
40
|
+
return this.#values.get(capability.identity);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function executionExtensionIdentity(name, abiVersion) {
|
|
44
|
+
return Symbol.for(`${executionExtensionIdentityNamespace}${JSON.stringify([name, abiVersion])}`);
|
|
45
|
+
}
|
|
46
|
+
export function defineExecutionExtension(extension) {
|
|
47
|
+
if (extension.abiVersion.trim().length === 0) {
|
|
48
|
+
throw new Error('LUTRE_EXTENSION_ABI_VERSION: Extension abiVersion must not be empty.');
|
|
49
|
+
}
|
|
50
|
+
return Object.freeze({
|
|
51
|
+
...extension,
|
|
52
|
+
...(extension.host === undefined
|
|
53
|
+
? {}
|
|
54
|
+
: { host: Object.freeze({ ...extension.host }) }),
|
|
55
|
+
identity: executionExtensionIdentity(extension.name, extension.abiVersion),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
export const executionDefinitionBrand = Symbol.for('loutre.execution-definition');
|
|
59
|
+
export function defineExecution(extension, definition) {
|
|
60
|
+
return Object.freeze({
|
|
61
|
+
...definition,
|
|
62
|
+
kind: 'execution-definition',
|
|
63
|
+
extension,
|
|
64
|
+
[executionDefinitionBrand]: true,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
export function isExecutionDefinition(value) {
|
|
68
|
+
return (typeof value === 'object' &&
|
|
69
|
+
value !== null &&
|
|
70
|
+
value.kind === 'execution-definition' &&
|
|
71
|
+
executionDefinitionBrand in value &&
|
|
72
|
+
value[executionDefinitionBrand] === true);
|
|
73
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { RuntimeCapability } from './extension.js';
|
|
2
|
+
import type { TokenLike } from './token.js';
|
|
3
|
+
import type { Type } from './type.js';
|
|
4
|
+
export interface GenericLayerContext<TState extends object = {}> {
|
|
5
|
+
readonly state: Readonly<TState>;
|
|
6
|
+
}
|
|
7
|
+
export type GenericLayerNext<TContribution extends object> = keyof TContribution extends never ? () => Promise<void> : (contribution: TContribution) => Promise<void>;
|
|
8
|
+
export interface GenericLayer<TContext extends object = object, TContribution extends object = {}, TOutcome = unknown> {
|
|
9
|
+
readonly kind: 'generic-layer';
|
|
10
|
+
readonly state?: Type<TContribution>;
|
|
11
|
+
readonly name: string;
|
|
12
|
+
readonly capabilities: readonly RuntimeCapability[];
|
|
13
|
+
readonly factory: () => (context: TContext & GenericLayerContext, next: GenericLayerNext<TContribution>) => Promise<void | TOutcome>;
|
|
14
|
+
}
|
|
15
|
+
export declare function defineLayer<TContribution extends object = {}, TContext extends object = object, TOutcome = never>(declaration: {
|
|
16
|
+
readonly name: string;
|
|
17
|
+
readonly state?: Type<TContribution>;
|
|
18
|
+
readonly capabilities?: readonly RuntimeCapability[];
|
|
19
|
+
readonly factory: GenericLayer<TContext, TContribution, TOutcome>['factory'];
|
|
20
|
+
}): GenericLayer<TContext, TContribution, TOutcome>;
|
|
21
|
+
export declare function composeLayers<TContext extends object, TOutcome>(options: {
|
|
22
|
+
readonly context: TContext;
|
|
23
|
+
readonly layers: readonly GenericLayer<any, any, TOutcome>[];
|
|
24
|
+
readonly resolve: <TValue>(token: TokenLike<TValue>, source?: string) => TValue;
|
|
25
|
+
readonly terminal: (context: TContext & GenericLayerContext<Record<string, unknown>>) => Promise<TOutcome>;
|
|
26
|
+
}): Promise<TOutcome>;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { runInInjectionContext } from './injection.js';
|
|
2
|
+
export function defineLayer(declaration) {
|
|
3
|
+
return Object.freeze({
|
|
4
|
+
kind: 'generic-layer',
|
|
5
|
+
...(declaration.state === undefined ? {} : { state: declaration.state }),
|
|
6
|
+
name: declaration.name,
|
|
7
|
+
capabilities: declaration.capabilities ?? [],
|
|
8
|
+
factory: declaration.factory,
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
export function composeLayers(options) {
|
|
12
|
+
const runtimes = options.layers.map((layer, index) => runInInjectionContext({
|
|
13
|
+
consumer: {
|
|
14
|
+
kind: 'layer',
|
|
15
|
+
id: `layer:${index}:${layer.name}`,
|
|
16
|
+
name: layer.name,
|
|
17
|
+
},
|
|
18
|
+
resolve: (token) => options.resolve(token, layer.name),
|
|
19
|
+
}, () => layer.factory()));
|
|
20
|
+
const dispatch = async (index, state) => {
|
|
21
|
+
const context = Object.assign(Object.create(options.context), {
|
|
22
|
+
state: Object.freeze({ ...state }),
|
|
23
|
+
});
|
|
24
|
+
const runtime = runtimes[index];
|
|
25
|
+
if (!runtime)
|
|
26
|
+
return options.terminal(context);
|
|
27
|
+
let called = false;
|
|
28
|
+
let continuationCompleted = false;
|
|
29
|
+
let continuationResult;
|
|
30
|
+
let continuationObserved = false;
|
|
31
|
+
let continuation;
|
|
32
|
+
const runtimeResult = await runtime(context, ((contribution = {}) => {
|
|
33
|
+
if (called) {
|
|
34
|
+
return Promise.reject(new Error('LUTRE_LAYER_NEXT_MULTIPLE: next() can be called once.'));
|
|
35
|
+
}
|
|
36
|
+
called = true;
|
|
37
|
+
continuation = dispatch(index + 1, mergeStateContribution(options.layers[index].name, state, contribution)).then((result) => {
|
|
38
|
+
continuationResult = result;
|
|
39
|
+
continuationCompleted = true;
|
|
40
|
+
});
|
|
41
|
+
return observePromise(continuation, () => {
|
|
42
|
+
continuationObserved = true;
|
|
43
|
+
});
|
|
44
|
+
}));
|
|
45
|
+
if (called && (!continuationObserved || !continuationCompleted)) {
|
|
46
|
+
const error = new Error(`LUTRE_LAYER_NEXT_NOT_AWAITED: Layer ${options.layers[index].name} must await or return next().`);
|
|
47
|
+
try {
|
|
48
|
+
await continuation;
|
|
49
|
+
}
|
|
50
|
+
catch (downstreamError) {
|
|
51
|
+
throw new AggregateError([error, downstreamError], error.message, {
|
|
52
|
+
cause: downstreamError,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
if (called && continuationCompleted) {
|
|
58
|
+
return continuationResult;
|
|
59
|
+
}
|
|
60
|
+
return runtimeResult;
|
|
61
|
+
};
|
|
62
|
+
return dispatch(0, {});
|
|
63
|
+
}
|
|
64
|
+
function observePromise(promise, observe) {
|
|
65
|
+
const observed = {
|
|
66
|
+
// `await next()`の観測にはnative Promiseではなくthenable境界が必要になる。
|
|
67
|
+
// eslint-disable-next-line unicorn/no-thenable
|
|
68
|
+
then(onfulfilled, onrejected) {
|
|
69
|
+
observe();
|
|
70
|
+
return promise.then(onfulfilled, onrejected);
|
|
71
|
+
},
|
|
72
|
+
catch(onrejected) {
|
|
73
|
+
observe();
|
|
74
|
+
return promise.catch(onrejected);
|
|
75
|
+
},
|
|
76
|
+
finally(onfinally) {
|
|
77
|
+
observe();
|
|
78
|
+
return promise.finally(onfinally ?? undefined);
|
|
79
|
+
},
|
|
80
|
+
[Symbol.toStringTag]: 'Promise',
|
|
81
|
+
};
|
|
82
|
+
return observed;
|
|
83
|
+
}
|
|
84
|
+
function mergeStateContribution(layerName, state, contribution) {
|
|
85
|
+
if (!isPlainObject(contribution)) {
|
|
86
|
+
throw new Error(`LUTRE_LAYER_STATE_INVALID: Layer ${layerName} must pass a plain State contribution to next().`);
|
|
87
|
+
}
|
|
88
|
+
const nextState = { ...state };
|
|
89
|
+
for (const [namespace, payload] of Object.entries(contribution)) {
|
|
90
|
+
if (namespace === '__proto__') {
|
|
91
|
+
throw new Error(`LUTRE_LAYER_STATE_RESERVED: Layer ${layerName} cannot contribute reserved State namespace ${namespace}.`);
|
|
92
|
+
}
|
|
93
|
+
if (!Object.hasOwn(nextState, namespace)) {
|
|
94
|
+
nextState[namespace] = payload;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const current = nextState[namespace];
|
|
98
|
+
if (!isPlainObject(current) || !isPlainObject(payload)) {
|
|
99
|
+
throw new Error(`LUTRE_LAYER_STATE_OVERWRITE: Layer ${layerName} cannot overwrite existing State namespace ${namespace}.`);
|
|
100
|
+
}
|
|
101
|
+
for (const key of Object.keys(payload)) {
|
|
102
|
+
if (Object.hasOwn(current, key)) {
|
|
103
|
+
throw new Error(`LUTRE_LAYER_STATE_OVERWRITE: Layer ${layerName} cannot overwrite existing State property ${namespace}.${key}.`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
nextState[namespace] = { ...current, ...payload };
|
|
107
|
+
}
|
|
108
|
+
return nextState;
|
|
109
|
+
}
|
|
110
|
+
function isPlainObject(value) {
|
|
111
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
const prototype = Object.getPrototypeOf(value);
|
|
115
|
+
return prototype === Object.prototype || prototype === null;
|
|
116
|
+
}
|
package/dist/core/index.d.ts
CHANGED
|
@@ -1,15 +1,31 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
3
|
-
export
|
|
4
|
-
export
|
|
5
|
-
export
|
|
6
|
-
export
|
|
7
|
-
export
|
|
8
|
-
export
|
|
9
|
-
export
|
|
10
|
-
export
|
|
11
|
-
export
|
|
12
|
-
export
|
|
13
|
-
export
|
|
14
|
-
export
|
|
15
|
-
export
|
|
1
|
+
export { defineArgs, isArgsClass, loadArgs } from './args.js';
|
|
2
|
+
export type { ArgsClass, ArgsKey, ArgsSchema } from './args.js';
|
|
3
|
+
export { diagnostic, hasErrorDiagnostics, isErrorDiagnostic, } from './diagnostic.js';
|
|
4
|
+
export type { Diagnostic } from './diagnostic.js';
|
|
5
|
+
export { defineEnv, isEnvClass, loadEnv } from './env.js';
|
|
6
|
+
export type { EnvClass, EnvKey, EnvSchema } from './env.js';
|
|
7
|
+
export { defineError, DomainError } from './error.js';
|
|
8
|
+
export type { ErrorDefinition } from './error.js';
|
|
9
|
+
export { bindRuntimeCapability, defineExecution, defineExecutionExtension, executionDefinitionBrand, isExecutionDefinition, runtimeCapability, RuntimeCapabilityRegistry, } from './extension.js';
|
|
10
|
+
export type { AnyExecutionExtension, ExecutionCompileContext, ExecutionContribution, ExecutionDefinition, ExecutionExtension, ExecutionExtensionDrainContext, ExecutionExtensionRuntime, ExecutionExtensionRuntimeContext, ExecutionExtensionValidationContext, ExecutionKernelRuntime, ExecutionLease, ExecutionProjectionContext, ExtensionOfDefinition, HostApiOfExtension, HostExtension, HostExtensionContext, RuntimeCapability, RuntimeCapabilityBinding, RuntimeCapabilityBindings, RuntimeCapabilityValue, } from './extension.js';
|
|
11
|
+
export { composeLayers, defineLayer } from './generic-layer.js';
|
|
12
|
+
export type { GenericLayer, GenericLayerContext, GenericLayerNext, } from './generic-layer.js';
|
|
13
|
+
export { collectInjectedDependencies, inject, InjectionContextError, runInInjectionContext, } from './injection.js';
|
|
14
|
+
export type { DependencyConsumer, DependencyConsumerDescriptor, InjectionContext, } from './injection.js';
|
|
15
|
+
export { hook } from './lifecycle.js';
|
|
16
|
+
export type { BeforeApplicationShutdown, LifecycleHook, ModuleLifecycle, OnApplicationBootstrap, OnApplicationShutdown, OnModuleDestroy, OnModuleInit, } from './lifecycle.js';
|
|
17
|
+
export { ApplicationModelError, assertValidApplicationModel, buildApplicationModel, } from './model.js';
|
|
18
|
+
export type { ApplicationModel, ApplicationModelBuildOptions, ApplicationModelEdge, ApplicationModelExtension, ApplicationModelExtensions, ApplicationModelNode, CompiledOf, ExecutionModelNode, FrameworkModelNode, LifecycleModelNode, ModuleModelNode, ProviderModelNode, } from './model.js';
|
|
19
|
+
export { asModuleInstance, defineModule, moduleTypeInfo } from './module.js';
|
|
20
|
+
export type { AnyModuleTemplate, ExtensionsOfModuleDefinition, ModuleDefinition, ModuleExtensions, ModuleHostApis, ModuleInstance, ModuleTemplate, ModuleTypeInfo, } from './module.js';
|
|
21
|
+
export { argumentsProvider, environmentProvider, normalizeProvider, provide, } from './provider.js';
|
|
22
|
+
export type { ArgumentsProvider, ClassProvider, ConditionalProvider, EnvironmentProvider, FactoryDefinition, FactoryProvider, ProviderDeclaration, ProviderDescriptor, ProviderScopeOptions, Scope, ValueProvider, } from './provider.js';
|
|
23
|
+
export type { RuntimeInputContract, RuntimeInputKey } from './runtime-input.js';
|
|
24
|
+
export { getSourceLocation, inheritSourceLocation, registerSourceLocation, } from './source-location.js';
|
|
25
|
+
export type { SourceLocation } from './source-location.js';
|
|
26
|
+
export { SchemaValidationError, supportsJsonSchema, validateSchema, } from './schema.js';
|
|
27
|
+
export type { JsonSchemaInput, JsonSchemaOutput, SchemaInput, SchemaOutput, StandardJSONSchemaV1, StandardSchemaResult, StandardSchemaV1, } from './schema.js';
|
|
28
|
+
export { token, tokenName } from './token.js';
|
|
29
|
+
export type { AbstractClass, Class, Token, TokenLike, TokenOptions, TokenValue, } from './token.js';
|
|
30
|
+
export { type } from './type.js';
|
|
31
|
+
export type { Type, TypeOf } from './type.js';
|
package/dist/core/index.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
3
|
-
export
|
|
4
|
-
export
|
|
5
|
-
export
|
|
6
|
-
export
|
|
7
|
-
export
|
|
8
|
-
export
|
|
9
|
-
export
|
|
10
|
-
export
|
|
11
|
-
export
|
|
12
|
-
export
|
|
13
|
-
export
|
|
14
|
-
export
|
|
15
|
-
export
|
|
1
|
+
export { defineArgs, isArgsClass, loadArgs } from './args.js';
|
|
2
|
+
export { diagnostic, hasErrorDiagnostics, isErrorDiagnostic, } from './diagnostic.js';
|
|
3
|
+
export { defineEnv, isEnvClass, loadEnv } from './env.js';
|
|
4
|
+
export { defineError, DomainError } from './error.js';
|
|
5
|
+
export { bindRuntimeCapability, defineExecution, defineExecutionExtension, executionDefinitionBrand, isExecutionDefinition, runtimeCapability, RuntimeCapabilityRegistry, } from './extension.js';
|
|
6
|
+
export { composeLayers, defineLayer } from './generic-layer.js';
|
|
7
|
+
export { collectInjectedDependencies, inject, InjectionContextError, runInInjectionContext, } from './injection.js';
|
|
8
|
+
export { hook } from './lifecycle.js';
|
|
9
|
+
export { ApplicationModelError, assertValidApplicationModel, buildApplicationModel, } from './model.js';
|
|
10
|
+
export { asModuleInstance, defineModule, moduleTypeInfo } from './module.js';
|
|
11
|
+
export { argumentsProvider, environmentProvider, normalizeProvider, provide, } from './provider.js';
|
|
12
|
+
export { getSourceLocation, inheritSourceLocation, registerSourceLocation, } from './source-location.js';
|
|
13
|
+
export { SchemaValidationError, supportsJsonSchema, validateSchema, } from './schema.js';
|
|
14
|
+
export { token, tokenName } from './token.js';
|
|
15
|
+
export { type } from './type.js';
|
package/dist/core/injection.d.ts
CHANGED
|
@@ -1,20 +1,10 @@
|
|
|
1
1
|
import type { TokenLike, TokenValue } from './token.js';
|
|
2
|
-
export interface
|
|
3
|
-
readonly kind: 'layer-consumer';
|
|
2
|
+
export interface DependencyConsumerDescriptor {
|
|
4
3
|
readonly id: string;
|
|
5
4
|
readonly name: string;
|
|
5
|
+
readonly kind?: string;
|
|
6
6
|
}
|
|
7
|
-
export
|
|
8
|
-
readonly kind: 'implementation-consumer';
|
|
9
|
-
readonly id: string;
|
|
10
|
-
readonly name: string;
|
|
11
|
-
}
|
|
12
|
-
export interface TaskConsumer {
|
|
13
|
-
readonly kind: 'task-consumer';
|
|
14
|
-
readonly id: string;
|
|
15
|
-
readonly name: string;
|
|
16
|
-
}
|
|
17
|
-
export type DependencyConsumer = TokenLike | LayerConsumer | ImplementationConsumer | TaskConsumer;
|
|
7
|
+
export type DependencyConsumer = TokenLike | DependencyConsumerDescriptor;
|
|
18
8
|
export interface InjectionContext {
|
|
19
9
|
readonly consumer: DependencyConsumer;
|
|
20
10
|
readonly resolve: <T>(token: TokenLike<T>) => T;
|
|
@@ -26,3 +16,4 @@ export declare class InjectionContextError extends Error {
|
|
|
26
16
|
constructor(token: TokenLike);
|
|
27
17
|
}
|
|
28
18
|
export declare function inject<TToken extends TokenLike>(token: TToken): TokenValue<TToken>;
|
|
19
|
+
export declare function collectInjectedDependencies(consumer: DependencyConsumer, construct: () => unknown): readonly TokenLike[];
|
package/dist/core/injection.js
CHANGED
|
@@ -33,3 +33,66 @@ export function inject(token) {
|
|
|
33
33
|
context.record?.(context.consumer, token);
|
|
34
34
|
return context.resolve(token);
|
|
35
35
|
}
|
|
36
|
+
function dependencyProbeTarget() { }
|
|
37
|
+
class DependencyProbeBoundary extends Error {
|
|
38
|
+
source;
|
|
39
|
+
constructor(source) {
|
|
40
|
+
super(`Dependency probe reached runtime-dependent value: ${source}`);
|
|
41
|
+
this.source = source;
|
|
42
|
+
this.name = 'DependencyProbeBoundary';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export function collectInjectedDependencies(consumer, construct) {
|
|
46
|
+
const dependencies = new Set();
|
|
47
|
+
try {
|
|
48
|
+
runInInjectionContext({
|
|
49
|
+
consumer,
|
|
50
|
+
resolve: (token) => createDependencyProbeValue(token),
|
|
51
|
+
record: (_consumer, dependency) => dependencies.add(dependency),
|
|
52
|
+
}, construct);
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
if (!(error instanceof DependencyProbeBoundary))
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
return [...dependencies];
|
|
59
|
+
}
|
|
60
|
+
function createDependencyProbeValue(token) {
|
|
61
|
+
const source = typeof token === 'function' ? token.name : token.id;
|
|
62
|
+
const boundary = (operation) => new DependencyProbeBoundary(`${source}.${String(operation)}`);
|
|
63
|
+
return new Proxy(dependencyProbeTarget, {
|
|
64
|
+
apply() {
|
|
65
|
+
throw boundary('[[Call]]');
|
|
66
|
+
},
|
|
67
|
+
construct() {
|
|
68
|
+
throw boundary('[[Construct]]');
|
|
69
|
+
},
|
|
70
|
+
get(_target, key) {
|
|
71
|
+
throw boundary(key);
|
|
72
|
+
},
|
|
73
|
+
set(_target, key) {
|
|
74
|
+
throw boundary(key);
|
|
75
|
+
},
|
|
76
|
+
has(_target, key) {
|
|
77
|
+
throw boundary(key);
|
|
78
|
+
},
|
|
79
|
+
ownKeys() {
|
|
80
|
+
throw boundary('*');
|
|
81
|
+
},
|
|
82
|
+
getOwnPropertyDescriptor(_target, key) {
|
|
83
|
+
throw boundary(key);
|
|
84
|
+
},
|
|
85
|
+
defineProperty(_target, key) {
|
|
86
|
+
throw boundary(key);
|
|
87
|
+
},
|
|
88
|
+
deleteProperty(_target, key) {
|
|
89
|
+
throw boundary(key);
|
|
90
|
+
},
|
|
91
|
+
getPrototypeOf() {
|
|
92
|
+
throw boundary('[[Prototype]]');
|
|
93
|
+
},
|
|
94
|
+
setPrototypeOf() {
|
|
95
|
+
throw boundary('[[Prototype]]');
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Diagnostic } from '../diagnostic.js';
|
|
2
|
+
import type { ModuleInstance } from '../module.js';
|
|
3
|
+
import type { ProviderDescriptor } from '../provider.js';
|
|
4
|
+
import type { TokenLike } from '../token.js';
|
|
5
|
+
import type { ApplicationModelBuildOptions, ApplicationModelEdge, ApplicationModelNode, ExecutionModelNode, ProviderModelNode } from './types.js';
|
|
6
|
+
export interface ModelBuildContext {
|
|
7
|
+
readonly options: ApplicationModelBuildOptions;
|
|
8
|
+
readonly modules: readonly ModuleInstance[];
|
|
9
|
+
readonly moduleIds: ReadonlyMap<ModuleInstance, string>;
|
|
10
|
+
readonly nodes: ApplicationModelNode[];
|
|
11
|
+
readonly edges: ApplicationModelEdge[];
|
|
12
|
+
readonly diagnostics: Diagnostic[];
|
|
13
|
+
readonly providers: ProviderDescriptor[];
|
|
14
|
+
readonly executions: ExecutionModelNode[];
|
|
15
|
+
readonly providerNodes: Map<TokenLike, ProviderModelNode>;
|
|
16
|
+
nextProviderId(): string;
|
|
17
|
+
}
|
|
18
|
+
export declare function createModelBuildContext(options: ApplicationModelBuildOptions): ModelBuildContext;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { asModuleInstance } from '../module.js';
|
|
2
|
+
function collectModules(roots) {
|
|
3
|
+
const result = [];
|
|
4
|
+
const seen = new Set();
|
|
5
|
+
const visit = (candidate) => {
|
|
6
|
+
const module = asModuleInstance(candidate);
|
|
7
|
+
if (seen.has(module))
|
|
8
|
+
return;
|
|
9
|
+
seen.add(module);
|
|
10
|
+
for (const imported of module.definition.imports ?? [])
|
|
11
|
+
visit(imported);
|
|
12
|
+
result.push(module);
|
|
13
|
+
};
|
|
14
|
+
for (const root of roots)
|
|
15
|
+
visit(root);
|
|
16
|
+
return result;
|
|
17
|
+
}
|
|
18
|
+
export function createModelBuildContext(options) {
|
|
19
|
+
const modules = collectModules(options.modules);
|
|
20
|
+
const moduleIds = new Map(modules.map((module, index) => [module, `module:${index + 1}`]));
|
|
21
|
+
let providerOrdinal = 0;
|
|
22
|
+
return {
|
|
23
|
+
options,
|
|
24
|
+
modules,
|
|
25
|
+
moduleIds,
|
|
26
|
+
nodes: [],
|
|
27
|
+
edges: [],
|
|
28
|
+
diagnostics: [],
|
|
29
|
+
providers: [],
|
|
30
|
+
executions: [],
|
|
31
|
+
providerNodes: new Map(),
|
|
32
|
+
nextProviderId: () => `provider:${++providerOrdinal}`,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ModuleInstance } from '../module.js';
|
|
2
|
+
import type { ApplicationModelEdge, ApplicationModelNode } from './types.js';
|
|
3
|
+
import type { ModelBuildContext } from './context.js';
|
|
4
|
+
export declare function appendLifecycleNodes(module: ModuleInstance, moduleId: string, nodes: ApplicationModelNode[], edges: ApplicationModelEdge[]): void;
|
|
5
|
+
export declare function resolveLifecycleDependencies(context: ModelBuildContext): void;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { diagnostic } from '../diagnostic.js';
|
|
2
|
+
import { isFrameworkProvidedToken } from '../token-internal.js';
|
|
3
|
+
import { tokenName } from '../token.js';
|
|
4
|
+
import { snapshotLifecycleHook } from './snapshot.js';
|
|
5
|
+
import { isTokenVisible, moduleDeclaresToken } from './visibility.js';
|
|
6
|
+
export function appendLifecycleNodes(module, moduleId, nodes, edges) {
|
|
7
|
+
const lifecycle = module.definition.lifecycle;
|
|
8
|
+
if (!lifecycle)
|
|
9
|
+
return;
|
|
10
|
+
for (const [phase, hook] of Object.entries(lifecycle)) {
|
|
11
|
+
const id = `lifecycle:${moduleId}:${phase}`;
|
|
12
|
+
nodes.push({
|
|
13
|
+
kind: 'lifecycle',
|
|
14
|
+
id,
|
|
15
|
+
moduleId,
|
|
16
|
+
phase,
|
|
17
|
+
hook: snapshotLifecycleHook(hook),
|
|
18
|
+
});
|
|
19
|
+
edges.push({ from: moduleId, to: id, kind: 'owns' });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export function resolveLifecycleDependencies(context) {
|
|
23
|
+
const { nodes, providerNodes, modules, moduleIds, edges, diagnostics } = context;
|
|
24
|
+
for (const lifecycle of nodes.filter((node) => node.kind === 'lifecycle')) {
|
|
25
|
+
for (const dependency of lifecycle.hook.inject) {
|
|
26
|
+
const provider = providerNodes.get(dependency);
|
|
27
|
+
if (provider) {
|
|
28
|
+
edges.push({ from: lifecycle.id, to: provider.id, kind: 'injects' });
|
|
29
|
+
const sourceModule = modules.find((candidate) => moduleIds.get(candidate) === lifecycle.moduleId);
|
|
30
|
+
const providerModule = modules.find((candidate) => moduleIds.get(candidate) === provider.moduleId);
|
|
31
|
+
if (sourceModule &&
|
|
32
|
+
providerModule &&
|
|
33
|
+
provider.moduleId !== lifecycle.moduleId &&
|
|
34
|
+
!moduleDeclaresToken(sourceModule, dependency) &&
|
|
35
|
+
!isTokenVisible(sourceModule, providerModule, dependency)) {
|
|
36
|
+
diagnostics.push(diagnostic('LUTRE_MODULE_VISIBILITY', `Lifecycle ${lifecycle.phase} depends on private ${tokenName(dependency)} from another Module.`, lifecycle.id));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
else if (!isFrameworkProvidedToken(dependency)) {
|
|
40
|
+
diagnostics.push(diagnostic('LUTRE_LIFECYCLE_DEPENDENCY_MISSING', `Lifecycle ${lifecycle.phase} requires ${tokenName(dependency)}, but no provider is declared.`, lifecycle.id));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|