@loutrejs/loutre 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/dist/adapters/aws-lambda.d.ts +52 -0
- package/dist/adapters/aws-lambda.js +114 -0
- package/dist/adapters/bun.d.ts +20 -0
- package/dist/adapters/bun.js +82 -0
- package/dist/adapters/cloudflare-workers.d.ts +18 -0
- package/dist/adapters/cloudflare-workers.js +55 -0
- package/dist/adapters/deno.d.ts +28 -0
- package/dist/adapters/deno.js +106 -0
- package/dist/adapters/electron.d.ts +27 -0
- package/dist/adapters/electron.js +52 -0
- package/dist/application/binding.d.ts +33 -0
- package/dist/application/binding.js +292 -0
- package/dist/application/host.d.ts +8 -0
- package/dist/application/host.js +14 -0
- package/dist/application/index.d.ts +62 -0
- package/dist/application/index.js +11 -0
- package/dist/application/openapi.d.ts +28 -0
- package/dist/application/openapi.js +362 -0
- package/dist/application/queue.d.ts +3 -0
- package/dist/application/queue.js +4 -0
- package/dist/application/scheduler.d.ts +2 -0
- package/dist/application/scheduler.js +45 -0
- package/dist/core/args.d.ts +16 -0
- package/dist/core/args.js +29 -0
- package/dist/core/context-key.d.ts +14 -0
- package/dist/core/context-key.js +14 -0
- package/dist/core/contract.d.ts +54 -0
- package/dist/core/contract.js +30 -0
- package/dist/core/env.d.ts +16 -0
- package/dist/core/env.js +29 -0
- package/dist/core/error.d.ts +24 -0
- package/dist/core/error.js +25 -0
- package/dist/core/execution.d.ts +78 -0
- package/dist/core/execution.js +61 -0
- package/dist/core/implementation.d.ts +34 -0
- package/dist/core/implementation.js +30 -0
- package/dist/core/index.d.ts +15 -0
- package/dist/core/index.js +15 -0
- package/dist/core/injection.d.ts +28 -0
- package/dist/core/injection.js +35 -0
- package/dist/core/layer.d.ts +109 -0
- package/dist/core/layer.js +52 -0
- package/dist/core/lifecycle.d.ts +34 -0
- package/dist/core/lifecycle.js +3 -0
- package/dist/core/module.d.ts +53 -0
- package/dist/core/module.js +31 -0
- package/dist/core/provider.d.ts +57 -0
- package/dist/core/provider.js +63 -0
- package/dist/core/runtime-input.d.ts +11 -0
- package/dist/core/runtime-input.js +1 -0
- package/dist/core/schema.d.ts +13 -0
- package/dist/core/schema.js +20 -0
- package/dist/core/token.d.ts +16 -0
- package/dist/core/token.js +13 -0
- package/dist/graph/graph.d.ts +17 -0
- package/dist/graph/graph.js +1078 -0
- package/dist/graph/index.d.ts +2 -0
- package/dist/graph/index.js +2 -0
- package/dist/graph/ir.d.ts +159 -0
- package/dist/graph/ir.js +1 -0
- package/dist/http/application.d.ts +18 -0
- package/dist/http/application.js +506 -0
- package/dist/http/basic-auth.d.ts +37 -0
- package/dist/http/basic-auth.js +67 -0
- package/dist/http/cors.d.ts +17 -0
- package/dist/http/cors.js +171 -0
- package/dist/http/definitions.d.ts +217 -0
- package/dist/http/definitions.js +65 -0
- package/dist/http/hosted-protocol.d.ts +4 -0
- package/dist/http/hosted-protocol.js +4 -0
- package/dist/http/index.d.ts +7 -0
- package/dist/http/index.js +6 -0
- package/dist/http/params.d.ts +2 -0
- package/dist/http/params.js +19 -0
- package/dist/http/path.d.ts +55 -0
- package/dist/http/path.js +88 -0
- package/dist/http/request-body.d.ts +4 -0
- package/dist/http/request-body.js +6 -0
- package/dist/http/validate.d.ts +20 -0
- package/dist/http/validate.js +6 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/message-port/environment.d.ts +2 -0
- package/dist/message-port/environment.js +4 -0
- package/dist/message-port/index.d.ts +65 -0
- package/dist/message-port/index.js +182 -0
- package/dist/runtime/application.d.ts +37 -0
- package/dist/runtime/application.js +324 -0
- package/dist/runtime/capability.d.ts +16 -0
- package/dist/runtime/capability.js +18 -0
- package/dist/runtime/di.d.ts +37 -0
- package/dist/runtime/di.js +441 -0
- package/dist/runtime/error.d.ts +10 -0
- package/dist/runtime/error.js +15 -0
- package/dist/runtime/index.d.ts +7 -0
- package/dist/runtime/index.js +7 -0
- package/dist/runtime/logger.d.ts +36 -0
- package/dist/runtime/logger.js +207 -0
- package/dist/runtime/node-capabilities.d.ts +5 -0
- package/dist/runtime/node-capabilities.js +18 -0
- package/dist/runtime/pipeline.d.ts +11 -0
- package/dist/runtime/pipeline.js +161 -0
- package/package.json +81 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const domainErrorMarker = Symbol('loutre.domain-error');
|
|
2
|
+
export class DomainError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
data;
|
|
5
|
+
[domainErrorMarker] = true;
|
|
6
|
+
constructor(code, data, options = {}) {
|
|
7
|
+
super(code, options);
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.data = data;
|
|
10
|
+
this.name = 'DomainError';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function defineError(definition) {
|
|
14
|
+
const factory = ((data, options = {}) => new DomainError(definition.code, data, options));
|
|
15
|
+
Object.defineProperties(factory, {
|
|
16
|
+
kind: { value: 'error-definition', enumerable: true },
|
|
17
|
+
code: { value: definition.code, enumerable: true },
|
|
18
|
+
data: { value: definition.data, enumerable: true },
|
|
19
|
+
is: {
|
|
20
|
+
value: (error) => error instanceof DomainError && error.code === definition.code,
|
|
21
|
+
enumerable: false,
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
return factory;
|
|
25
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { type SchemaOutput, type StandardSchemaV1 } from './schema.js';
|
|
2
|
+
import { type Token } from './token.js';
|
|
3
|
+
export type TaskRuntime<TInput, TOutput> = [TInput] extends [void] ? () => TOutput | Promise<TOutput> : (input: TInput) => TOutput | Promise<TOutput>;
|
|
4
|
+
export interface TaskDescriptor<TInput = unknown, TOutput = unknown, TName extends string = string> {
|
|
5
|
+
readonly kind: 'task';
|
|
6
|
+
readonly name: TName;
|
|
7
|
+
readonly factory: () => TaskRuntime<TInput, TOutput>;
|
|
8
|
+
}
|
|
9
|
+
export type TaskInput<T> = T extends TaskDescriptor<infer TInput, any, any> ? TInput : never;
|
|
10
|
+
export type TaskOutput<T> = T extends TaskDescriptor<any, infer TOutput, any> ? TOutput : never;
|
|
11
|
+
export type TaskArguments<T> = [TaskInput<T>] extends [void] ? readonly [] : readonly [input: TaskInput<T>];
|
|
12
|
+
export declare function task<TInput = void, TOutput = void, const TName extends string = string>(declaration: {
|
|
13
|
+
readonly name: TName;
|
|
14
|
+
readonly factory: () => TaskRuntime<TInput, TOutput>;
|
|
15
|
+
}): TaskDescriptor<TInput, TOutput, TName>;
|
|
16
|
+
type VoidTaskConstraint<TTask> = [TaskInput<TTask>] extends [void] ? unknown : never;
|
|
17
|
+
export type CronOverlap = 'allow' | 'skip';
|
|
18
|
+
export interface CronTriggerDescriptor<TTask extends TaskDescriptor<void, any> = TaskDescriptor<void, any>, TName extends string = string> {
|
|
19
|
+
readonly kind: 'trigger';
|
|
20
|
+
readonly trigger: 'cron';
|
|
21
|
+
readonly name: TName;
|
|
22
|
+
readonly expression: string;
|
|
23
|
+
readonly timezone: string;
|
|
24
|
+
readonly overlap: CronOverlap;
|
|
25
|
+
readonly task: TTask;
|
|
26
|
+
}
|
|
27
|
+
export declare function cron<const TTask extends TaskDescriptor<any, any>, const TName extends string>(declaration: {
|
|
28
|
+
readonly name: TName;
|
|
29
|
+
readonly expression: string;
|
|
30
|
+
readonly timezone: string;
|
|
31
|
+
readonly overlap?: CronOverlap;
|
|
32
|
+
readonly task: TTask;
|
|
33
|
+
} & VoidTaskConstraint<TTask>): CronTriggerDescriptor<TTask & TaskDescriptor<void, any>, TName>;
|
|
34
|
+
export interface FixedDelayTriggerDescriptor<TTask extends TaskDescriptor<void, any> = TaskDescriptor<void, any>, TName extends string = string> {
|
|
35
|
+
readonly kind: 'trigger';
|
|
36
|
+
readonly trigger: 'fixed-delay';
|
|
37
|
+
readonly name: TName;
|
|
38
|
+
readonly delay: number;
|
|
39
|
+
readonly immediate: boolean;
|
|
40
|
+
readonly task: TTask;
|
|
41
|
+
}
|
|
42
|
+
export declare function fixedDelay<const TTask extends TaskDescriptor<any, any>, const TName extends string>(declaration: {
|
|
43
|
+
readonly name: TName;
|
|
44
|
+
readonly delay: number;
|
|
45
|
+
readonly immediate?: boolean;
|
|
46
|
+
readonly task: TTask;
|
|
47
|
+
} & VoidTaskConstraint<TTask>): FixedDelayTriggerDescriptor<TTask & TaskDescriptor<void, any>, TName>;
|
|
48
|
+
declare const queueDriverToken: unique symbol;
|
|
49
|
+
export interface QueueDescriptor<TSchema extends StandardSchemaV1 = StandardSchemaV1, TName extends string = string> {
|
|
50
|
+
readonly kind: 'queue';
|
|
51
|
+
readonly name: TName;
|
|
52
|
+
readonly payload: TSchema;
|
|
53
|
+
readonly [queueDriverToken]: Token<unknown>;
|
|
54
|
+
}
|
|
55
|
+
export type QueuePayload<T> = T extends QueueDescriptor<infer TSchema, any> ? SchemaOutput<TSchema> : never;
|
|
56
|
+
export declare function queue<const TSchema extends StandardSchemaV1, const TName extends string = string>(declaration: {
|
|
57
|
+
readonly name: TName;
|
|
58
|
+
readonly payload: TSchema;
|
|
59
|
+
}): QueueDescriptor<TSchema, TName>;
|
|
60
|
+
export declare function queueRuntimeToken(descriptor: QueueDescriptor): Token<unknown>;
|
|
61
|
+
export interface QueueConsumerTriggerDescriptor<TQueue extends QueueDescriptor<any> = QueueDescriptor<any>, TTask extends TaskDescriptor<any, any> = TaskDescriptor<any, any>, TName extends string = string> {
|
|
62
|
+
readonly kind: 'trigger';
|
|
63
|
+
readonly trigger: 'queue-consumer';
|
|
64
|
+
readonly name: TName;
|
|
65
|
+
readonly queue: TQueue;
|
|
66
|
+
readonly task: TTask;
|
|
67
|
+
}
|
|
68
|
+
type ConsumerTaskConstraint<TQueue, TTask> = [TaskInput<TTask>] extends [
|
|
69
|
+
QueuePayload<TQueue>
|
|
70
|
+
] ? [QueuePayload<TQueue>] extends [TaskInput<TTask>] ? unknown : never : never;
|
|
71
|
+
export declare function consume<const TQueue extends QueueDescriptor<any>, const TTask extends TaskDescriptor<any, any>, const TName extends string>(declaration: {
|
|
72
|
+
readonly name: TName;
|
|
73
|
+
readonly queue: TQueue;
|
|
74
|
+
readonly task: TTask;
|
|
75
|
+
} & ConsumerTaskConstraint<TQueue, TTask>): QueueConsumerTriggerDescriptor<TQueue, TTask & TaskDescriptor<QueuePayload<TQueue>, any>, TName>;
|
|
76
|
+
export type TriggerDescriptor = CronTriggerDescriptor<any, any> | FixedDelayTriggerDescriptor<any, any> | QueueConsumerTriggerDescriptor<any, any, any>;
|
|
77
|
+
export type TriggerTask<TTrigger> = TTrigger extends TriggerDescriptor ? TTrigger['task'] : never;
|
|
78
|
+
export {};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { token } from './token.js';
|
|
2
|
+
export function task(declaration) {
|
|
3
|
+
return Object.freeze({
|
|
4
|
+
kind: 'task',
|
|
5
|
+
name: declaration.name,
|
|
6
|
+
factory: declaration.factory,
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
export function cron(declaration) {
|
|
10
|
+
return Object.freeze({
|
|
11
|
+
kind: 'trigger',
|
|
12
|
+
trigger: 'cron',
|
|
13
|
+
name: declaration.name,
|
|
14
|
+
expression: declaration.expression,
|
|
15
|
+
timezone: declaration.timezone,
|
|
16
|
+
overlap: declaration.overlap ?? 'skip',
|
|
17
|
+
task: declaration.task,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
export function fixedDelay(declaration) {
|
|
21
|
+
if (!Number.isFinite(declaration.delay) ||
|
|
22
|
+
!Number.isInteger(declaration.delay) ||
|
|
23
|
+
declaration.delay < 0) {
|
|
24
|
+
throw new Error('LUTRE_TRIGGER_INVALID_FIXED_DELAY: fixedDelay.delay must be a non-negative finite integer.');
|
|
25
|
+
}
|
|
26
|
+
return Object.freeze({
|
|
27
|
+
kind: 'trigger',
|
|
28
|
+
trigger: 'fixed-delay',
|
|
29
|
+
name: declaration.name,
|
|
30
|
+
delay: declaration.delay,
|
|
31
|
+
immediate: declaration.immediate ?? false,
|
|
32
|
+
task: declaration.task,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
const queueDriverToken = Symbol('loutre.queue-driver');
|
|
36
|
+
export function queue(declaration) {
|
|
37
|
+
const payload = declaration.payload;
|
|
38
|
+
if (payload === null ||
|
|
39
|
+
(typeof payload !== 'object' && typeof payload !== 'function') ||
|
|
40
|
+
!('~standard' in payload)) {
|
|
41
|
+
throw new Error('LUTRE_QUEUE_PAYLOAD_SCHEMA: queue.payload must implement Standard Schema.');
|
|
42
|
+
}
|
|
43
|
+
return Object.freeze({
|
|
44
|
+
kind: 'queue',
|
|
45
|
+
name: declaration.name,
|
|
46
|
+
payload: declaration.payload,
|
|
47
|
+
[queueDriverToken]: token(`loutre.queue-driver.${declaration.name}`),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
export function queueRuntimeToken(descriptor) {
|
|
51
|
+
return descriptor[queueDriverToken];
|
|
52
|
+
}
|
|
53
|
+
export function consume(declaration) {
|
|
54
|
+
return Object.freeze({
|
|
55
|
+
kind: 'trigger',
|
|
56
|
+
trigger: 'queue-consumer',
|
|
57
|
+
name: declaration.name,
|
|
58
|
+
queue: declaration.queue,
|
|
59
|
+
task: declaration.task,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ContractDefinition, ContractProcedures, ProtocolDescriptor, ProtocolFactory } from './contract.js';
|
|
2
|
+
type ProcedureNamesForProtocol<TContract extends ContractDefinition, TProtocol extends string> = {
|
|
3
|
+
[K in keyof ContractProcedures<TContract>]: TProtocol extends keyof ContractProcedures<TContract>[K]['protocols'] ? K : never;
|
|
4
|
+
}[keyof ContractProcedures<TContract>] & string;
|
|
5
|
+
type ImplementationRuntimeShape<TContract extends ContractDefinition, TProtocol extends string, TProcedures extends string> = {
|
|
6
|
+
[K in TProcedures]: ContractProcedures<TContract>[K]['protocols'][TProtocol & keyof ContractProcedures<TContract>[K]['protocols']] extends ProtocolDescriptor<TProtocol, infer TContext, infer TResult, any, any> ? (context: TContext) => TResult | Promise<TResult> : never;
|
|
7
|
+
};
|
|
8
|
+
export interface ImplementationDescriptor<TContract extends ContractDefinition = ContractDefinition, TProtocol extends string = string, TProcedures extends readonly string[] = readonly string[], TRuntime extends object = object, TCapabilities extends readonly string[] = readonly string[]> {
|
|
9
|
+
readonly kind: 'implementation';
|
|
10
|
+
readonly name: string;
|
|
11
|
+
readonly contract: TContract;
|
|
12
|
+
readonly protocol: TProtocol;
|
|
13
|
+
readonly capabilities: TCapabilities;
|
|
14
|
+
readonly procedures: TProcedures;
|
|
15
|
+
readonly factory: () => TRuntime;
|
|
16
|
+
}
|
|
17
|
+
type AvailableProtocolConstraint<TContract extends ContractDefinition, TProtocol extends string, TCapabilities extends readonly string[]> = [ProcedureNamesForProtocol<TContract, TProtocol>] extends [never] ? never : ProtocolFactory<TProtocol, TCapabilities>;
|
|
18
|
+
type FullImplementationDeclaration<TContract extends ContractDefinition, TProtocol extends string, TCapabilities extends readonly string[]> = {
|
|
19
|
+
readonly name: string;
|
|
20
|
+
readonly contract: TContract;
|
|
21
|
+
readonly protocol: AvailableProtocolConstraint<TContract, TProtocol, TCapabilities>;
|
|
22
|
+
readonly procedures?: never;
|
|
23
|
+
readonly factory: () => ImplementationRuntimeShape<TContract, TProtocol, ProcedureNamesForProtocol<TContract, TProtocol>>;
|
|
24
|
+
};
|
|
25
|
+
type PartialImplementationDeclaration<TContract extends ContractDefinition, TProtocol extends string, TProcedures extends readonly ProcedureNamesForProtocol<TContract, TProtocol>[], TCapabilities extends readonly string[]> = {
|
|
26
|
+
readonly name: string;
|
|
27
|
+
readonly contract: TContract;
|
|
28
|
+
readonly protocol: AvailableProtocolConstraint<TContract, TProtocol, TCapabilities>;
|
|
29
|
+
readonly procedures: TProcedures;
|
|
30
|
+
readonly factory: () => ImplementationRuntimeShape<TContract, TProtocol, TProcedures[number]>;
|
|
31
|
+
};
|
|
32
|
+
export declare function implementation<const TContract extends ContractDefinition, const TProtocol extends string, const TCapabilities extends readonly string[]>(declaration: FullImplementationDeclaration<TContract, TProtocol, TCapabilities>): ImplementationDescriptor<TContract, TProtocol, readonly ProcedureNamesForProtocol<TContract, TProtocol>[], ImplementationRuntimeShape<TContract, TProtocol, ProcedureNamesForProtocol<TContract, TProtocol>>, TCapabilities>;
|
|
33
|
+
export declare function implementation<const TContract extends ContractDefinition, const TProtocol extends string, const TProcedures extends readonly ProcedureNamesForProtocol<TContract, TProtocol>[], const TCapabilities extends readonly string[]>(declaration: PartialImplementationDeclaration<TContract, TProtocol, TProcedures, TCapabilities>): ImplementationDescriptor<TContract, TProtocol, TProcedures, ImplementationRuntimeShape<TContract, TProtocol, TProcedures[number]>, TCapabilities>;
|
|
34
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export function implementation(declaration) {
|
|
2
|
+
const protocol = declaration.protocol.protocol;
|
|
3
|
+
const available = Object.entries(declaration.contract.procedures)
|
|
4
|
+
.filter(([, procedure]) => protocol in procedure.protocols)
|
|
5
|
+
.map(([name]) => name);
|
|
6
|
+
if (available.length === 0) {
|
|
7
|
+
throw new Error(`LUTRE_IMPL_003: Contract does not declare any procedure for protocol ${protocol}.`);
|
|
8
|
+
}
|
|
9
|
+
const procedures = [...(declaration.procedures ?? available)];
|
|
10
|
+
const selected = new Set();
|
|
11
|
+
for (const procedure of procedures) {
|
|
12
|
+
if (selected.has(procedure)) {
|
|
13
|
+
throw new Error(`LUTRE_IMPL_003: Implementation ${declaration.name} selects procedure ${procedure} more than once.`);
|
|
14
|
+
}
|
|
15
|
+
selected.add(procedure);
|
|
16
|
+
const definition = declaration.contract.procedures[procedure];
|
|
17
|
+
if (!definition || !(protocol in definition.protocols)) {
|
|
18
|
+
throw new Error(`LUTRE_IMPL_003: ${procedure} is not declared for protocol ${protocol}.`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return Object.freeze({
|
|
22
|
+
kind: 'implementation',
|
|
23
|
+
name: declaration.name,
|
|
24
|
+
contract: declaration.contract,
|
|
25
|
+
protocol,
|
|
26
|
+
capabilities: declaration.protocol.capabilities ?? [],
|
|
27
|
+
procedures: Object.freeze(procedures),
|
|
28
|
+
factory: declaration.factory,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export * from './args.js';
|
|
2
|
+
export * from './contract.js';
|
|
3
|
+
export * from './context-key.js';
|
|
4
|
+
export * from './env.js';
|
|
5
|
+
export * from './error.js';
|
|
6
|
+
export * from './execution.js';
|
|
7
|
+
export * from './implementation.js';
|
|
8
|
+
export * from './injection.js';
|
|
9
|
+
export * from './layer.js';
|
|
10
|
+
export * from './lifecycle.js';
|
|
11
|
+
export * from './module.js';
|
|
12
|
+
export * from './provider.js';
|
|
13
|
+
export * from './runtime-input.js';
|
|
14
|
+
export * from './schema.js';
|
|
15
|
+
export * from './token.js';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export * from './args.js';
|
|
2
|
+
export * from './contract.js';
|
|
3
|
+
export * from './context-key.js';
|
|
4
|
+
export * from './env.js';
|
|
5
|
+
export * from './error.js';
|
|
6
|
+
export * from './execution.js';
|
|
7
|
+
export * from './implementation.js';
|
|
8
|
+
export * from './injection.js';
|
|
9
|
+
export * from './layer.js';
|
|
10
|
+
export * from './lifecycle.js';
|
|
11
|
+
export * from './module.js';
|
|
12
|
+
export * from './provider.js';
|
|
13
|
+
export * from './runtime-input.js';
|
|
14
|
+
export * from './schema.js';
|
|
15
|
+
export * from './token.js';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { TokenLike, TokenValue } from './token.js';
|
|
2
|
+
export interface LayerConsumer {
|
|
3
|
+
readonly kind: 'layer-consumer';
|
|
4
|
+
readonly id: string;
|
|
5
|
+
readonly name: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ImplementationConsumer {
|
|
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;
|
|
18
|
+
export interface InjectionContext {
|
|
19
|
+
readonly consumer: DependencyConsumer;
|
|
20
|
+
readonly resolve: <T>(token: TokenLike<T>) => T;
|
|
21
|
+
readonly record?: (consumer: DependencyConsumer, dependency: TokenLike) => void;
|
|
22
|
+
}
|
|
23
|
+
export declare function runInInjectionContext<T>(context: InjectionContext, run: () => T): T;
|
|
24
|
+
export declare class InjectionContextError extends Error {
|
|
25
|
+
readonly code = "LUTRE_DI_CONTEXT";
|
|
26
|
+
constructor(token: TokenLike);
|
|
27
|
+
}
|
|
28
|
+
export declare function inject<TToken extends TokenLike>(token: TToken): TokenValue<TToken>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const injectionContextKey = Symbol.for('loutre.injection-context');
|
|
2
|
+
function currentInjectionContext() {
|
|
3
|
+
return globalThis[injectionContextKey];
|
|
4
|
+
}
|
|
5
|
+
function setCurrentInjectionContext(context) {
|
|
6
|
+
const storage = globalThis;
|
|
7
|
+
if (context === undefined)
|
|
8
|
+
delete storage[injectionContextKey];
|
|
9
|
+
else
|
|
10
|
+
storage[injectionContextKey] = context;
|
|
11
|
+
}
|
|
12
|
+
export function runInInjectionContext(context, run) {
|
|
13
|
+
const previous = currentInjectionContext();
|
|
14
|
+
setCurrentInjectionContext(context);
|
|
15
|
+
try {
|
|
16
|
+
return run();
|
|
17
|
+
}
|
|
18
|
+
finally {
|
|
19
|
+
setCurrentInjectionContext(previous);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export class InjectionContextError extends Error {
|
|
23
|
+
code = 'LUTRE_DI_CONTEXT';
|
|
24
|
+
constructor(token) {
|
|
25
|
+
super(`inject(${typeof token === 'function' ? token.name : token.id}) was called outside a Loutre injection context.`);
|
|
26
|
+
this.name = 'InjectionContextError';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function inject(token) {
|
|
30
|
+
const context = currentInjectionContext();
|
|
31
|
+
if (!context)
|
|
32
|
+
throw new InjectionContextError(token);
|
|
33
|
+
context.record?.(context.consumer, token);
|
|
34
|
+
return context.resolve(token);
|
|
35
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { ContextKey, ContextProperties } from './context-key.js';
|
|
2
|
+
export type LayerRole = 'generic' | 'authentication' | 'guard' | 'validation' | 'framework' | 'terminal';
|
|
3
|
+
export type ValidatedInputPart = 'params' | 'query' | 'headers' | 'body';
|
|
4
|
+
declare const shortCircuitMarker: unique symbol;
|
|
5
|
+
export interface ShortCircuit<TResult = unknown> {
|
|
6
|
+
readonly kind: 'short-circuit';
|
|
7
|
+
readonly result: TResult;
|
|
8
|
+
readonly [shortCircuitMarker]: true;
|
|
9
|
+
}
|
|
10
|
+
export declare function shortCircuit<const TResult>(result: TResult): ShortCircuit<TResult>;
|
|
11
|
+
export declare function isShortCircuit(value: unknown): value is ShortCircuit;
|
|
12
|
+
declare const shortCircuitResultType: unique symbol;
|
|
13
|
+
export interface ShortCircuitDeclaration {
|
|
14
|
+
readonly protocol: string;
|
|
15
|
+
readonly variant: string;
|
|
16
|
+
readonly response?: Readonly<Record<string, unknown>>;
|
|
17
|
+
}
|
|
18
|
+
export type LayerNext<TProvides extends readonly ContextKey[]> = number extends TProvides['length'] ? (provided: ContextProperties<TProvides>) => Promise<void> : TProvides extends readonly [] ? () => Promise<void> : (provided: ContextProperties<TProvides>) => Promise<void>;
|
|
19
|
+
export type LayerRuntime<TContext extends object, TProvides extends readonly ContextKey[], TShortCircuitResult> = (context: TContext, next: LayerNext<TProvides>) => Promise<void | ShortCircuit<TShortCircuitResult>>;
|
|
20
|
+
export type LayerFactory<TContext extends object, TProvides extends readonly ContextKey[], TShortCircuitResult> = () => LayerRuntime<TContext, TProvides, TShortCircuitResult>;
|
|
21
|
+
export interface LayerDefinition<TRequires extends readonly ContextKey[] = readonly [], TProvides extends readonly ContextKey[] = readonly [], TContext extends object = {}, TShortCircuitResult = never, TShortCircuits extends readonly ShortCircuitDeclaration[] = readonly [], TName extends string = string, TRole extends Exclude<LayerRole, 'terminal' | 'validation'> = 'generic', TRequiresValidated extends readonly ValidatedInputPart[] = readonly []> {
|
|
22
|
+
readonly name: TName;
|
|
23
|
+
readonly role?: TRole;
|
|
24
|
+
readonly requires?: TRequires;
|
|
25
|
+
readonly provides?: TProvides;
|
|
26
|
+
readonly requiresValidated?: TRequiresValidated;
|
|
27
|
+
readonly shortCircuits?: TShortCircuits;
|
|
28
|
+
readonly factory: LayerFactory<TContext & ContextProperties<TRequires>, TProvides, TShortCircuitResult>;
|
|
29
|
+
}
|
|
30
|
+
export interface LayerOccurrenceDescriptor<TRequires extends readonly ContextKey[] = readonly ContextKey[], TProvides extends readonly ContextKey[] = readonly ContextKey[], TShortCircuitResult = unknown, TShortCircuits extends readonly ShortCircuitDeclaration[] = readonly ShortCircuitDeclaration[], TName extends string = string, TRole extends Exclude<LayerRole, 'terminal' | 'validation'> = Exclude<LayerRole, 'terminal' | 'validation'>, TRequiresValidated extends readonly ValidatedInputPart[] = readonly ValidatedInputPart[], TContext extends object = object, TPipeline extends readonly PipelineItem[] = readonly PipelineItem[]> {
|
|
31
|
+
readonly kind: 'layer';
|
|
32
|
+
readonly name: TName;
|
|
33
|
+
readonly role: TRole;
|
|
34
|
+
readonly requires: TRequires;
|
|
35
|
+
readonly provides: TProvides;
|
|
36
|
+
readonly requiresValidated: TRequiresValidated;
|
|
37
|
+
readonly shortCircuits: TShortCircuits;
|
|
38
|
+
readonly definition: LayerDescriptor<TRequires, TProvides, TShortCircuitResult, TShortCircuits, TName, TRole, TRequiresValidated, TContext>;
|
|
39
|
+
readonly pipeline: TPipeline;
|
|
40
|
+
readonly factory?: never;
|
|
41
|
+
readonly [shortCircuitResultType]?: TShortCircuitResult;
|
|
42
|
+
}
|
|
43
|
+
export interface LayerDescriptor<TRequires extends readonly ContextKey[] = readonly ContextKey[], TProvides extends readonly ContextKey[] = readonly ContextKey[], TShortCircuitResult = unknown, TShortCircuits extends readonly ShortCircuitDeclaration[] = readonly ShortCircuitDeclaration[], TName extends string = string, TRole extends Exclude<LayerRole, 'terminal' | 'validation'> = Exclude<LayerRole, 'terminal' | 'validation'>, TRequiresValidated extends readonly ValidatedInputPart[] = readonly ValidatedInputPart[], TContext extends object = object> {
|
|
44
|
+
<const TPipeline extends readonly PipelineItem[]>(pipeline: TPipeline): LayerOccurrenceDescriptor<TRequires, TProvides, TShortCircuitResult, TShortCircuits, TName, TRole, TRequiresValidated, TContext, TPipeline>;
|
|
45
|
+
readonly kind: 'layer';
|
|
46
|
+
readonly name: TName;
|
|
47
|
+
readonly role: TRole;
|
|
48
|
+
readonly requires: TRequires;
|
|
49
|
+
readonly provides: TProvides;
|
|
50
|
+
readonly requiresValidated: TRequiresValidated;
|
|
51
|
+
readonly shortCircuits: TShortCircuits;
|
|
52
|
+
readonly factory: LayerFactory<TContext & ContextProperties<TRequires>, TProvides, TShortCircuitResult>;
|
|
53
|
+
readonly definition?: never;
|
|
54
|
+
readonly pipeline?: never;
|
|
55
|
+
readonly [shortCircuitResultType]?: TShortCircuitResult;
|
|
56
|
+
}
|
|
57
|
+
export interface ValidationLayerDescriptor {
|
|
58
|
+
readonly kind: 'validation';
|
|
59
|
+
readonly name: `validate.${'params' | 'query' | 'headers' | 'body'}`;
|
|
60
|
+
readonly role: 'validation';
|
|
61
|
+
readonly part: 'params' | 'query' | 'headers' | 'body';
|
|
62
|
+
}
|
|
63
|
+
export interface TerminalLayerDescriptor<TProtocol extends string = string> {
|
|
64
|
+
readonly kind: 'terminal';
|
|
65
|
+
readonly name: `${TProtocol}.${string}`;
|
|
66
|
+
readonly role: 'terminal';
|
|
67
|
+
readonly protocol: TProtocol;
|
|
68
|
+
}
|
|
69
|
+
type AnyLayerDescriptor = LayerDescriptor<any, any, any, any, any, any, any, any>;
|
|
70
|
+
type AnyLayerOccurrence = LayerOccurrenceDescriptor<any, any, any, any, any, any, any, any, any>;
|
|
71
|
+
export type PipelineItem = AnyLayerDescriptor | AnyLayerOccurrence | ValidationLayerDescriptor | TerminalLayerDescriptor;
|
|
72
|
+
export declare function isLayerOccurrence(item: PipelineItem): item is AnyLayerOccurrence;
|
|
73
|
+
export declare function layerDefinitionOf(item: AnyLayerDescriptor | AnyLayerOccurrence): AnyLayerDescriptor;
|
|
74
|
+
export declare function childPipelineOf(item: AnyLayerDescriptor | AnyLayerOccurrence): readonly PipelineItem[] | undefined;
|
|
75
|
+
interface PipelineTerminalState<THasTerminal extends boolean, TValid extends boolean> {
|
|
76
|
+
readonly hasTerminal: THasTerminal;
|
|
77
|
+
readonly valid: TValid;
|
|
78
|
+
}
|
|
79
|
+
type InvalidPipelineTerminalState = PipelineTerminalState<boolean, false>;
|
|
80
|
+
type FoldPipelineTerminals<TPipeline extends readonly PipelineItem[], TProtocol extends string, TState extends PipelineTerminalState<boolean, boolean>> = TState['valid'] extends false ? TState : number extends TPipeline['length'] ? InvalidPipelineTerminalState : TPipeline extends readonly [infer THead, ...infer TTail] ? TState['hasTerminal'] extends true ? InvalidPipelineTerminalState : FoldPipelineTerminals<Extract<TTail, readonly PipelineItem[]>, TProtocol, FoldPipelineItemTerminals<THead, TProtocol, TState>> : TState;
|
|
81
|
+
type FoldPipelineItemTerminals<TItem, TProtocol extends string, TState extends PipelineTerminalState<boolean, boolean>> = TItem extends TerminalLayerDescriptor<infer TItemProtocol> ? [TItemProtocol, TProtocol] extends [TProtocol, TItemProtocol] ? PipelineTerminalState<true, true> : InvalidPipelineTerminalState : TItem extends LayerOccurrenceDescriptor<any, any, any, any, any, any, any, any, infer TPipeline> ? FoldPipelineTerminals<TPipeline, TProtocol, TState> : TState;
|
|
82
|
+
export type IsValidProtocolPipeline<TPipeline extends readonly PipelineItem[], TProtocol extends string> = FoldPipelineTerminals<TPipeline, TProtocol, PipelineTerminalState<false, true>> extends PipelineTerminalState<true, true> ? true : false;
|
|
83
|
+
export type ShortCircuitResultOf<TItem> = TItem extends LayerOccurrenceDescriptor<any, any, any, any, any, any, any, any, infer TPipeline> ? ShortCircuitResultOfPipeline<TPipeline> | ShortCircuitResultOfLayer<TItem> : ShortCircuitResultOfLayer<TItem>;
|
|
84
|
+
type ShortCircuitResultOfLayer<TItem> = TItem extends {
|
|
85
|
+
readonly [shortCircuitResultType]?: infer TResult;
|
|
86
|
+
} ? TResult : never;
|
|
87
|
+
export type ShortCircuitDeclarationsOf<TItem> = TItem extends LayerOccurrenceDescriptor<any, any, any, any, any, any, any, any, infer TPipeline> ? readonly [
|
|
88
|
+
...NormalizeDeclarations<TItem['shortCircuits']>,
|
|
89
|
+
...ShortCircuitDeclarationsOfPipeline<TPipeline>
|
|
90
|
+
] : TItem extends {
|
|
91
|
+
readonly shortCircuits: infer TShortCircuits;
|
|
92
|
+
} ? TShortCircuits : never;
|
|
93
|
+
type ShortCircuitResultOfPipeline<TPipeline extends readonly PipelineItem[]> = TPipeline[number] extends infer TItem ? ShortCircuitResultOf<TItem> : never;
|
|
94
|
+
type ShortCircuitDeclarationsOfPipeline<TPipeline extends readonly PipelineItem[]> = TPipeline extends readonly [infer THead, ...infer TTail] ? readonly [
|
|
95
|
+
...NormalizeDeclarations<ShortCircuitDeclarationsOf<THead>>,
|
|
96
|
+
...ShortCircuitDeclarationsOfPipeline<Extract<TTail, readonly PipelineItem[]>>
|
|
97
|
+
] : readonly [];
|
|
98
|
+
type NormalizeDeclarations<TDeclarations> = TDeclarations extends readonly ShortCircuitDeclaration[] ? TDeclarations : readonly [];
|
|
99
|
+
interface PipelineTypeState<TContext extends object, TValidated extends ValidatedInputPart, TDone extends boolean> {
|
|
100
|
+
readonly context: TContext;
|
|
101
|
+
readonly validated: TValidated;
|
|
102
|
+
readonly done: TDone;
|
|
103
|
+
}
|
|
104
|
+
type FoldPipeline<TPipeline extends readonly PipelineItem[], TState extends PipelineTypeState<object, ValidatedInputPart, boolean>> = TState['done'] extends true ? TState : number extends TPipeline['length'] ? TState : TPipeline extends readonly [infer THead, ...infer TTail] ? FoldPipeline<Extract<TTail, readonly PipelineItem[]>, FoldPipelineItem<THead, TState>> : TState;
|
|
105
|
+
type FoldPipelineItem<TItem, TState extends PipelineTypeState<object, ValidatedInputPart, boolean>> = TItem extends TerminalLayerDescriptor ? PipelineTypeState<TState['context'], TState['validated'], true> : TItem extends ValidationLayerDescriptor ? PipelineTypeState<TState['context'], TState['validated'] | TItem['part'], false> : TItem extends LayerOccurrenceDescriptor<any, infer TProvides, any, any, any, any, any, any, infer TPipeline> ? FoldPipeline<TPipeline, PipelineTypeState<TState['context'] & ContextProperties<TProvides>, TState['validated'], false>> : TItem extends LayerDescriptor<any, infer TProvides, any, any, any, any, any, any> ? PipelineTypeState<TState['context'] & ContextProperties<TProvides>, TState['validated'], false> : TState;
|
|
106
|
+
export type ContextProvidedBeforeTerminal<TPipeline extends readonly PipelineItem[], TContext extends object = {}> = number extends TPipeline['length'] ? TContext : FoldPipeline<TPipeline, PipelineTypeState<TContext, never, false>>['context'];
|
|
107
|
+
export type HasValidationBeforeTerminal<TPipeline extends readonly PipelineItem[], TPart extends ValidatedInputPart> = number extends TPipeline['length'] ? false : TPart extends FoldPipeline<TPipeline, PipelineTypeState<{}, never, false>>['validated'] ? true : false;
|
|
108
|
+
export declare function layer<const TRequires extends readonly ContextKey[] = readonly [], const TProvides extends readonly ContextKey[] = readonly [], TContext extends object = {}, TShortCircuitResult = never, const TShortCircuits extends readonly ShortCircuitDeclaration[] = readonly [], const TName extends string = string, const TRole extends Exclude<LayerRole, 'terminal' | 'validation'> = 'generic', const TRequiresValidated extends readonly ValidatedInputPart[] = readonly []>(definition: LayerDefinition<TRequires, TProvides, TContext, TShortCircuitResult, TShortCircuits, TName, TRole, TRequiresValidated>): LayerDescriptor<TRequires, TProvides, TShortCircuitResult, TShortCircuits, TName, TRole, TRequiresValidated, TContext>;
|
|
109
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const shortCircuitMarker = Symbol('loutre.short-circuit');
|
|
2
|
+
export function shortCircuit(result) {
|
|
3
|
+
return {
|
|
4
|
+
kind: 'short-circuit',
|
|
5
|
+
result,
|
|
6
|
+
[shortCircuitMarker]: true,
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export function isShortCircuit(value) {
|
|
10
|
+
return (typeof value === 'object' &&
|
|
11
|
+
value !== null &&
|
|
12
|
+
shortCircuitMarker in value &&
|
|
13
|
+
value[shortCircuitMarker] === true);
|
|
14
|
+
}
|
|
15
|
+
export function isLayerOccurrence(item) {
|
|
16
|
+
return item.kind === 'layer' && item.definition !== undefined;
|
|
17
|
+
}
|
|
18
|
+
export function layerDefinitionOf(item) {
|
|
19
|
+
return isLayerOccurrence(item) ? item.definition : item;
|
|
20
|
+
}
|
|
21
|
+
export function childPipelineOf(item) {
|
|
22
|
+
return isLayerOccurrence(item) ? item.pipeline : undefined;
|
|
23
|
+
}
|
|
24
|
+
export function layer(definition) {
|
|
25
|
+
let descriptor;
|
|
26
|
+
const callable = (pipeline) => Object.freeze({
|
|
27
|
+
kind: 'layer',
|
|
28
|
+
name: descriptor.name,
|
|
29
|
+
role: descriptor.role,
|
|
30
|
+
requires: descriptor.requires,
|
|
31
|
+
provides: descriptor.provides,
|
|
32
|
+
requiresValidated: descriptor.requiresValidated,
|
|
33
|
+
shortCircuits: descriptor.shortCircuits,
|
|
34
|
+
definition: descriptor,
|
|
35
|
+
pipeline,
|
|
36
|
+
});
|
|
37
|
+
Object.defineProperty(callable, 'name', {
|
|
38
|
+
value: definition.name,
|
|
39
|
+
enumerable: true,
|
|
40
|
+
configurable: true,
|
|
41
|
+
});
|
|
42
|
+
descriptor = Object.assign(callable, {
|
|
43
|
+
kind: 'layer',
|
|
44
|
+
role: definition.role ?? 'generic',
|
|
45
|
+
requires: definition.requires ?? [],
|
|
46
|
+
provides: definition.provides ?? [],
|
|
47
|
+
requiresValidated: definition.requiresValidated ?? [],
|
|
48
|
+
shortCircuits: definition.shortCircuits ?? [],
|
|
49
|
+
factory: definition.factory,
|
|
50
|
+
});
|
|
51
|
+
return Object.freeze(descriptor);
|
|
52
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { TokenLike, TokenValue } from './token.js';
|
|
2
|
+
export interface OnModuleInit {
|
|
3
|
+
onModuleInit(): void | Promise<void>;
|
|
4
|
+
}
|
|
5
|
+
export interface OnApplicationBootstrap {
|
|
6
|
+
onApplicationBootstrap(): void | Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
export interface OnModuleDestroy {
|
|
9
|
+
onModuleDestroy(): void | Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
export interface BeforeApplicationShutdown {
|
|
12
|
+
beforeApplicationShutdown(signal?: string): void | Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
export interface OnApplicationShutdown {
|
|
15
|
+
onApplicationShutdown(signal?: string): void | Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
export interface LifecycleHook<TInject extends readonly TokenLike[] = readonly TokenLike[]> {
|
|
18
|
+
readonly kind: 'lifecycle-hook';
|
|
19
|
+
readonly inject: TInject;
|
|
20
|
+
readonly run: (...dependencies: {
|
|
21
|
+
[K in keyof TInject]: TokenValue<TInject[K]>;
|
|
22
|
+
}) => void | Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
export declare function hook<const TInject extends readonly TokenLike[]>(definition: {
|
|
25
|
+
readonly inject: TInject;
|
|
26
|
+
readonly run: LifecycleHook<TInject>['run'];
|
|
27
|
+
}): LifecycleHook<TInject>;
|
|
28
|
+
export interface ModuleLifecycle {
|
|
29
|
+
readonly onModuleInit?: LifecycleHook<any>;
|
|
30
|
+
readonly onApplicationBootstrap?: LifecycleHook<any>;
|
|
31
|
+
readonly onModuleDestroy?: LifecycleHook<any>;
|
|
32
|
+
readonly beforeApplicationShutdown?: LifecycleHook<any>;
|
|
33
|
+
readonly onApplicationShutdown?: LifecycleHook<any>;
|
|
34
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { EnvClass } from './env.js';
|
|
2
|
+
import type { ImplementationDescriptor } from './implementation.js';
|
|
3
|
+
import type { ModuleLifecycle } from './lifecycle.js';
|
|
4
|
+
import { type ProviderDeclaration } from './provider.js';
|
|
5
|
+
export interface ModuleDefinition {
|
|
6
|
+
readonly name?: string;
|
|
7
|
+
readonly description?: string;
|
|
8
|
+
readonly imports?: readonly ModuleInstance[];
|
|
9
|
+
readonly environment?: readonly EnvClass[];
|
|
10
|
+
readonly providers?: readonly ProviderDeclaration[];
|
|
11
|
+
readonly implementations?: readonly ImplementationDescriptor[];
|
|
12
|
+
readonly exports?: readonly unknown[];
|
|
13
|
+
readonly lifecycle?: ModuleLifecycle;
|
|
14
|
+
readonly requires?: readonly string[];
|
|
15
|
+
}
|
|
16
|
+
export declare const moduleTypeInfo: unique symbol;
|
|
17
|
+
export interface ModuleTypeInfo<TDefinition extends ModuleDefinition = ModuleDefinition> {
|
|
18
|
+
readonly protocols: ProtocolsOfModuleDefinition<TDefinition>;
|
|
19
|
+
readonly capabilities: CapabilitiesOfModuleDefinition<TDefinition>;
|
|
20
|
+
}
|
|
21
|
+
type DirectProtocols<TDefinition extends ModuleDefinition> = TDefinition['implementations'] extends readonly ImplementationDescriptor[] ? TDefinition['implementations'][number]['protocol'] : never;
|
|
22
|
+
type ImportedProtocols<TDefinition extends ModuleDefinition> = TDefinition['imports'] extends readonly ModuleInstance[] ? ModuleProtocols<TDefinition['imports'][number]> : never;
|
|
23
|
+
export type ProtocolsOfModuleDefinition<TDefinition extends ModuleDefinition> = DirectProtocols<TDefinition> | ImportedProtocols<TDefinition>;
|
|
24
|
+
type DirectCapabilities<TDefinition extends ModuleDefinition> = TDefinition['implementations'] extends readonly ImplementationDescriptor[] ? TDefinition['implementations'][number]['capabilities'][number] : never;
|
|
25
|
+
type ImportedCapabilities<TDefinition extends ModuleDefinition> = TDefinition['imports'] extends readonly ModuleInstance[] ? ModuleCapabilities<TDefinition['imports'][number]> : never;
|
|
26
|
+
export type CapabilitiesOfModuleDefinition<TDefinition extends ModuleDefinition> = DirectCapabilities<TDefinition> | ImportedCapabilities<TDefinition>;
|
|
27
|
+
export type ModuleProtocols<TModule> = TModule extends ModuleInstance<infer TDefinition> ? ProtocolsOfModuleDefinition<TDefinition> : never;
|
|
28
|
+
export type ModuleCapabilities<TModule> = TModule extends ModuleInstance<infer TDefinition> ? CapabilitiesOfModuleDefinition<TDefinition> : never;
|
|
29
|
+
export interface ModuleInstance<TDefinition extends ModuleDefinition = ModuleDefinition> {
|
|
30
|
+
readonly kind: 'module-instance';
|
|
31
|
+
readonly template: AnyModuleTemplate;
|
|
32
|
+
readonly args: unknown;
|
|
33
|
+
readonly definition: TDefinition;
|
|
34
|
+
readonly [moduleTypeInfo]?: ModuleTypeInfo<TDefinition>;
|
|
35
|
+
}
|
|
36
|
+
export interface AnyModuleTemplate {
|
|
37
|
+
(args?: any): ModuleInstance<any>;
|
|
38
|
+
readonly kind: 'module-template';
|
|
39
|
+
readonly instantiate: (args: any) => ModuleInstance<any>;
|
|
40
|
+
readonly [moduleTypeInfo]?: ModuleTypeInfo<any>;
|
|
41
|
+
}
|
|
42
|
+
export type ModuleTemplate<Args, TDefinition extends ModuleDefinition = ModuleDefinition> = ([Args] extends [void] ? {
|
|
43
|
+
(): ModuleInstance<TDefinition>;
|
|
44
|
+
} : {
|
|
45
|
+
(args: Args): ModuleInstance<TDefinition>;
|
|
46
|
+
}) & {
|
|
47
|
+
readonly kind: 'module-template';
|
|
48
|
+
readonly instantiate: (args: Args) => ModuleInstance<TDefinition>;
|
|
49
|
+
readonly [moduleTypeInfo]?: ModuleTypeInfo<TDefinition>;
|
|
50
|
+
};
|
|
51
|
+
export declare function defineModule<Args = void, const TDefinition extends ModuleDefinition = ModuleDefinition>(factory: (args: Args) => TDefinition): ModuleTemplate<Args, TDefinition>;
|
|
52
|
+
export declare function asModuleInstance(module: ModuleInstance | ModuleTemplate<void>): ModuleInstance;
|
|
53
|
+
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { environmentProvider } from './provider.js';
|
|
2
|
+
export const moduleTypeInfo = Symbol('loutre.module-type-info');
|
|
3
|
+
export function defineModule(factory) {
|
|
4
|
+
const instantiate = (args) => {
|
|
5
|
+
const declared = factory(args);
|
|
6
|
+
const environment = [...new Set(declared.environment ?? [])];
|
|
7
|
+
const providers = [
|
|
8
|
+
...environment.map(environmentProvider),
|
|
9
|
+
...(declared.providers ?? []),
|
|
10
|
+
];
|
|
11
|
+
return {
|
|
12
|
+
kind: 'module-instance',
|
|
13
|
+
template: template,
|
|
14
|
+
args,
|
|
15
|
+
definition: {
|
|
16
|
+
...declared,
|
|
17
|
+
...(environment.length === 0 ? {} : { environment }),
|
|
18
|
+
...(providers.length === 0 ? {} : { providers }),
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
const template = ((args) => instantiate(args));
|
|
23
|
+
Object.defineProperties(template, {
|
|
24
|
+
kind: { value: 'module-template', enumerable: true },
|
|
25
|
+
instantiate: { value: instantiate, enumerable: false },
|
|
26
|
+
});
|
|
27
|
+
return template;
|
|
28
|
+
}
|
|
29
|
+
export function asModuleInstance(module) {
|
|
30
|
+
return module.kind === 'module-instance' ? module : module();
|
|
31
|
+
}
|