@loutrejs/loutre 0.9.2 → 0.10.1
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 +1 -1
- package/dist/adapters/aws-lambda.js +8 -2
- package/dist/adapters/bun.d.ts +3 -2
- package/dist/adapters/bun.js +12 -6
- package/dist/adapters/cloudflare-workers.d.ts +1 -1
- package/dist/adapters/cloudflare-workers.js +2 -1
- package/dist/adapters/electron.d.ts +1 -1
- package/dist/adapters/electron.js +8 -51
- package/dist/adapters/server-transports.js +2 -2
- package/dist/adapters/websocket-native.js +4 -1
- package/dist/application/definition.d.ts +4 -7
- package/dist/application/definition.js +3 -1
- package/dist/application/kernel.d.ts +0 -2
- package/dist/application/kernel.js +1 -3
- package/dist/core/args.d.ts +1 -0
- package/dist/core/args.js +2 -1
- package/dist/core/env.d.ts +1 -0
- package/dist/core/env.js +2 -1
- package/dist/core/extension.d.ts +23 -13
- package/dist/core/extension.js +3 -1
- package/dist/core/generic-layer.d.ts +9 -2
- package/dist/core/generic-layer.js +2 -1
- package/dist/core/identity.d.ts +18 -0
- package/dist/core/identity.js +5 -0
- package/dist/core/index.d.ts +13 -5
- package/dist/core/index.js +7 -1
- package/dist/core/injection.d.ts +2 -1
- package/dist/core/instrumentation.d.ts +7 -5
- package/dist/core/lifecycle.d.ts +4 -0
- package/dist/core/logger-identity.d.ts +4 -0
- package/dist/core/logger-identity.js +9 -0
- package/dist/core/logger-internal.d.ts +5 -0
- package/dist/core/logger-internal.js +27 -0
- package/dist/core/logger-module.d.ts +5 -0
- package/dist/core/logger-module.js +8 -0
- package/dist/core/logger.d.ts +42 -0
- package/dist/core/logger.js +221 -0
- package/dist/core/model/context.d.ts +6 -3
- package/dist/core/model/context.js +17 -17
- package/dist/core/model/identity.d.ts +19 -0
- package/dist/core/model/identity.js +8 -0
- package/dist/core/model/lifecycle.d.ts +3 -2
- package/dist/core/model/lifecycle.js +7 -3
- package/dist/core/model/logger.d.ts +3 -0
- package/dist/core/model/logger.js +59 -0
- package/dist/core/model/modules.d.ts +4 -1
- package/dist/core/model/modules.js +6 -4
- package/dist/core/model/providers.d.ts +1 -1
- package/dist/core/model/providers.js +8 -8
- package/dist/core/model/types.d.ts +25 -18
- package/dist/core/model/validation.js +3 -2
- package/dist/core/model/visibility.js +7 -2
- package/dist/core/model.js +15 -7
- package/dist/core/module.d.ts +7 -7
- package/dist/core/module.js +0 -3
- package/dist/core/runtime-input.d.ts +2 -1
- package/dist/core/source-location.d.ts +14 -2
- package/dist/core/source-location.js +8 -3
- package/dist/core/token-internal.js +3 -1
- package/dist/core/token.d.ts +4 -2
- package/dist/core/token.js +3 -0
- package/dist/core/units.d.ts +22 -0
- package/dist/core/units.js +14 -0
- package/dist/devtools/capsule.d.ts +17 -13
- package/dist/devtools/capsule.js +14 -10
- package/dist/devtools/decoder.d.ts +15 -0
- package/dist/devtools/decoder.js +179 -0
- package/dist/devtools/identity-internal.d.ts +6 -0
- package/dist/devtools/identity-internal.js +5 -0
- package/dist/devtools/identity.d.ts +21 -0
- package/dist/devtools/identity.js +7 -0
- package/dist/devtools/index.d.ts +3 -0
- package/dist/devtools/index.js +2 -0
- package/dist/devtools/inspector.d.ts +18 -16
- package/dist/devtools/observer.d.ts +32 -28
- package/dist/devtools/runtime.js +34 -30
- package/dist/devtools/values.js +3 -5
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +1 -1
- package/dist/graph/model.d.ts +25 -14
- package/dist/http/auth.d.ts +4 -2
- package/dist/http/client.d.ts +6 -3
- package/dist/http/client.js +8 -9
- package/dist/http/cors-internal.d.ts +7 -5
- package/dist/http/cors-internal.js +8 -6
- package/dist/http/extension.d.ts +60 -21
- package/dist/http/extension.js +91 -20
- package/dist/http/identity.d.ts +20 -0
- package/dist/http/identity.js +10 -0
- package/dist/http/index.d.ts +6 -2
- package/dist/http/index.js +3 -1
- package/dist/http/openapi-identity.d.ts +9 -0
- package/dist/http/openapi-identity.js +4 -0
- package/dist/http/openapi.d.ts +1 -1
- package/dist/http/openapi.js +10 -7
- package/dist/http/path.d.ts +8 -5
- package/dist/http/route-validation.js +2 -1
- package/dist/http/runtime/request.d.ts +3 -2
- package/dist/http/runtime/request.js +1 -4
- package/dist/http/websocket-upgrade.d.ts +2 -0
- package/dist/http/websocket-upgrade.js +4 -0
- package/dist/internal/brand.d.ts +8 -0
- package/dist/internal/brand.js +1 -0
- package/dist/message-port/extension.d.ts +4 -2
- package/dist/message-port/extension.js +15 -8
- package/dist/message-port/identity.d.ts +9 -0
- package/dist/message-port/identity.js +16 -0
- package/dist/message-port/index.d.ts +2 -0
- package/dist/message-port/index.js +1 -0
- package/dist/message-port/transport.d.ts +4 -2
- package/dist/message-port/transport.js +6 -1
- package/dist/runtime/async-iterator-lifecycle.d.ts +2 -1
- package/dist/runtime/async-iterator-lifecycle.js +2 -1
- package/dist/runtime/capability.d.ts +6 -2
- package/dist/runtime/capability.js +3 -1
- package/dist/runtime/di.d.ts +1 -3
- package/dist/runtime/di.js +62 -35
- package/dist/runtime/error.d.ts +3 -1
- package/dist/runtime/index.d.ts +6 -2
- package/dist/runtime/index.js +3 -1
- package/dist/runtime/kernel.d.ts +0 -2
- package/dist/runtime/kernel.js +68 -30
- package/dist/runtime/logger-source.d.ts +4 -0
- package/dist/runtime/logger-source.js +47 -0
- package/dist/runtime/logger.d.ts +2 -38
- package/dist/runtime/logger.js +1 -209
- package/dist/runtime/node-capabilities.d.ts +1 -1
- package/dist/runtime/node-capabilities.js +2 -1
- package/dist/runtime/server-port.d.ts +5 -1
- package/dist/runtime/server-port.js +2 -1
- package/dist/runtime/shutdown-hooks.d.ts +1 -1
- package/dist/runtime/shutdown-hooks.js +5 -4
- package/dist/tasks/extension.d.ts +18 -16
- package/dist/tasks/extension.js +28 -23
- package/dist/tasks/identity.d.ts +9 -0
- package/dist/tasks/identity.js +4 -0
- package/dist/tasks/index.d.ts +1 -0
- package/dist/tasks/runtime/cron.d.ts +6 -2
- package/dist/tasks/runtime/cron.js +9 -0
- package/dist/websocket/extension.d.ts +19 -9
- package/dist/websocket/extension.js +103 -30
- package/dist/websocket/identity.d.ts +9 -0
- package/dist/websocket/identity.js +4 -0
- package/dist/websocket/index.d.ts +1 -0
- package/package.json +1 -1
package/dist/runtime/logger.d.ts
CHANGED
|
@@ -1,38 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
export type
|
|
3
|
-
export interface LogRecord {
|
|
4
|
-
readonly timestamp: string;
|
|
5
|
-
readonly level: LogLevel;
|
|
6
|
-
readonly message: string;
|
|
7
|
-
readonly [key: string]: unknown;
|
|
8
|
-
}
|
|
9
|
-
export interface LoggerBackend {
|
|
10
|
-
write(record: LogRecord): void;
|
|
11
|
-
}
|
|
12
|
-
export interface ConsoleLoggerBackendOptions {
|
|
13
|
-
readonly colors?: boolean;
|
|
14
|
-
readonly prefix?: string;
|
|
15
|
-
}
|
|
16
|
-
export declare class ConsoleLoggerBackend implements LoggerBackend {
|
|
17
|
-
#private;
|
|
18
|
-
constructor(options?: ConsoleLoggerBackendOptions);
|
|
19
|
-
write(record: LogRecord): void;
|
|
20
|
-
}
|
|
21
|
-
export declare class JsonConsoleLoggerBackend implements LoggerBackend {
|
|
22
|
-
write(record: LogRecord): void;
|
|
23
|
-
}
|
|
24
|
-
export declare class Logger {
|
|
25
|
-
#private;
|
|
26
|
-
readonly backend: LoggerBackend;
|
|
27
|
-
readonly context: Readonly<Record<string, unknown>>;
|
|
28
|
-
static readonly [frameworkProvidedTokenBrand] = true;
|
|
29
|
-
constructor(backend?: LoggerBackend, context?: Readonly<Record<string, unknown>>);
|
|
30
|
-
child(context: Readonly<Record<string, unknown>>): Logger;
|
|
31
|
-
debug(message: string, context?: Readonly<Record<string, unknown>>): void;
|
|
32
|
-
info(message: string, context?: Readonly<Record<string, unknown>>): void;
|
|
33
|
-
warn(message: string, context?: Readonly<Record<string, unknown>>): void;
|
|
34
|
-
error(message: string, context?: Readonly<Record<string, unknown>>): void;
|
|
35
|
-
}
|
|
36
|
-
export declare class SilentLogger extends Logger {
|
|
37
|
-
constructor(context?: Readonly<Record<string, unknown>>);
|
|
38
|
-
}
|
|
1
|
+
export { ConsoleLoggerBackend, JsonConsoleLoggerBackend, LoggerService, LoggerToken, SilentLogger, } from '../core/logger.js';
|
|
2
|
+
export type { ConsoleLoggerBackendOptions, LoggerBackend, LogLevel, LogRecord, } from '../core/logger.js';
|
package/dist/runtime/logger.js
CHANGED
|
@@ -1,209 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export class ConsoleLoggerBackend {
|
|
3
|
-
#colors;
|
|
4
|
-
#prefix;
|
|
5
|
-
constructor(options = {}) {
|
|
6
|
-
this.#colors = options.colors ?? detectConsoleColors();
|
|
7
|
-
this.#prefix = options.prefix ?? 'Loutre';
|
|
8
|
-
}
|
|
9
|
-
write(record) {
|
|
10
|
-
writeToConsole(record.level, formatConsoleLogRecord(record, this.#prefix, this.#colors));
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
export class JsonConsoleLoggerBackend {
|
|
14
|
-
write(record) {
|
|
15
|
-
writeToConsole(record.level, serializeLogRecord(record));
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
class SilentLoggerBackend {
|
|
19
|
-
write(_record) { }
|
|
20
|
-
}
|
|
21
|
-
export class Logger {
|
|
22
|
-
backend;
|
|
23
|
-
context;
|
|
24
|
-
static [frameworkProvidedTokenBrand] = true;
|
|
25
|
-
constructor(backend = new ConsoleLoggerBackend(), context = {}) {
|
|
26
|
-
this.backend = backend;
|
|
27
|
-
this.context = context;
|
|
28
|
-
}
|
|
29
|
-
child(context) {
|
|
30
|
-
return new Logger(this.backend, { ...this.context, ...context });
|
|
31
|
-
}
|
|
32
|
-
debug(message, context = {}) {
|
|
33
|
-
this.#write('debug', message, context);
|
|
34
|
-
}
|
|
35
|
-
info(message, context = {}) {
|
|
36
|
-
this.#write('info', message, context);
|
|
37
|
-
}
|
|
38
|
-
warn(message, context = {}) {
|
|
39
|
-
this.#write('warn', message, context);
|
|
40
|
-
}
|
|
41
|
-
error(message, context = {}) {
|
|
42
|
-
this.#write('error', message, context);
|
|
43
|
-
}
|
|
44
|
-
#write(level, message, context) {
|
|
45
|
-
this.backend.write({
|
|
46
|
-
...this.context,
|
|
47
|
-
...context,
|
|
48
|
-
timestamp: new Date().toISOString(),
|
|
49
|
-
level,
|
|
50
|
-
message,
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
export class SilentLogger extends Logger {
|
|
55
|
-
constructor(context = {}) {
|
|
56
|
-
super(new SilentLoggerBackend(), context);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
function serializeLogRecord(record) {
|
|
60
|
-
const seen = new WeakSet();
|
|
61
|
-
try {
|
|
62
|
-
return JSON.stringify(record, (_key, value) => {
|
|
63
|
-
if (typeof value === 'bigint')
|
|
64
|
-
return value.toString();
|
|
65
|
-
if (value instanceof Error) {
|
|
66
|
-
return {
|
|
67
|
-
name: value.name,
|
|
68
|
-
message: value.message,
|
|
69
|
-
...(value.stack === undefined ? {} : { stack: value.stack }),
|
|
70
|
-
...(value.cause === undefined ? {} : { cause: value.cause }),
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
if (value !== null && typeof value === 'object') {
|
|
74
|
-
if (seen.has(value))
|
|
75
|
-
return '[Circular]';
|
|
76
|
-
seen.add(value);
|
|
77
|
-
}
|
|
78
|
-
return value;
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
catch (error) {
|
|
82
|
-
return JSON.stringify({
|
|
83
|
-
timestamp: record.timestamp,
|
|
84
|
-
level: 'error',
|
|
85
|
-
message: 'Log record serialization failed',
|
|
86
|
-
serializationError: error instanceof Error ? error.message : String(error),
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
function writeToConsole(level, value) {
|
|
91
|
-
const target = level === 'error'
|
|
92
|
-
? console.error
|
|
93
|
-
: level === 'warn'
|
|
94
|
-
? console.warn
|
|
95
|
-
: console.log;
|
|
96
|
-
target(value);
|
|
97
|
-
}
|
|
98
|
-
function formatConsoleLogRecord(record, prefix, colors) {
|
|
99
|
-
const { timestamp, level, message, ...recordContext } = record;
|
|
100
|
-
const context = { ...recordContext };
|
|
101
|
-
const source = typeof context.source === 'string'
|
|
102
|
-
? context.source
|
|
103
|
-
: typeof context.protocol === 'string'
|
|
104
|
-
? context.protocol
|
|
105
|
-
: undefined;
|
|
106
|
-
if (source !== undefined)
|
|
107
|
-
delete context.source;
|
|
108
|
-
const runtimeProcess = getRuntimeProcess();
|
|
109
|
-
const processLabel = runtimeProcess?.pid === undefined
|
|
110
|
-
? `[${prefix}]`
|
|
111
|
-
: `[${prefix}] ${runtimeProcess.pid}`;
|
|
112
|
-
const levelLabel = level === 'info' ? 'LOG' : level.toUpperCase();
|
|
113
|
-
const levelColor = level === 'error' ? 31 : level === 'warn' ? 33 : level === 'debug' ? 36 : 32;
|
|
114
|
-
const sourceLabel = source === undefined ? '' : `${paint(`[${source}]`, 33, colors)} `;
|
|
115
|
-
const formattedContext = formatLogContext(context);
|
|
116
|
-
const contextSuffix = formattedContext === '' ? '' : ` ${paint(formattedContext, 90, colors)}`;
|
|
117
|
-
return `${paint(processLabel, 32, colors)} - ${formatLocalTimestamp(timestamp)} ${paint(levelLabel.padStart(7), levelColor, colors)} ${sourceLabel}${paint(message, levelColor, colors)}${contextSuffix}`;
|
|
118
|
-
}
|
|
119
|
-
function formatLogContext(context) {
|
|
120
|
-
const entries = Object.entries(context);
|
|
121
|
-
if (entries.length === 0)
|
|
122
|
-
return '';
|
|
123
|
-
return `{ ${entries
|
|
124
|
-
.map(([key, value]) => `${formatLogKey(key)}=${formatLogValue(value)}`)
|
|
125
|
-
.join(', ')} }`;
|
|
126
|
-
}
|
|
127
|
-
function formatLogKey(key) {
|
|
128
|
-
return /^[A-Za-z_$][\w$]*$/u.test(key) ? key : JSON.stringify(key);
|
|
129
|
-
}
|
|
130
|
-
function formatLogValue(value, ancestors = new WeakSet()) {
|
|
131
|
-
try {
|
|
132
|
-
if (value === null)
|
|
133
|
-
return 'null';
|
|
134
|
-
if (value === undefined)
|
|
135
|
-
return 'undefined';
|
|
136
|
-
if (typeof value === 'string') {
|
|
137
|
-
return /^[\w./:@+-]+$/u.test(value) ? value : JSON.stringify(value);
|
|
138
|
-
}
|
|
139
|
-
if (typeof value === 'number' || typeof value === 'boolean')
|
|
140
|
-
return String(value);
|
|
141
|
-
if (typeof value === 'bigint')
|
|
142
|
-
return `${value}n`;
|
|
143
|
-
if (typeof value === 'symbol')
|
|
144
|
-
return String(value);
|
|
145
|
-
if (typeof value === 'function')
|
|
146
|
-
return `[Function ${value.name || 'anonymous'}]`;
|
|
147
|
-
if (ancestors.has(value))
|
|
148
|
-
return '[Circular]';
|
|
149
|
-
ancestors.add(value);
|
|
150
|
-
try {
|
|
151
|
-
if (value instanceof Date)
|
|
152
|
-
return value.toISOString();
|
|
153
|
-
if (value instanceof Error) {
|
|
154
|
-
return formatLogValue({
|
|
155
|
-
name: value.name,
|
|
156
|
-
message: value.message,
|
|
157
|
-
...(value.stack === undefined ? {} : { stack: value.stack }),
|
|
158
|
-
...(value.cause === undefined ? {} : { cause: value.cause }),
|
|
159
|
-
}, ancestors);
|
|
160
|
-
}
|
|
161
|
-
if (Array.isArray(value)) {
|
|
162
|
-
return `[${value.map((item) => formatLogValue(item, ancestors)).join(', ')}]`;
|
|
163
|
-
}
|
|
164
|
-
return `{ ${Object.entries(value)
|
|
165
|
-
.map(([key, item]) => `${formatLogKey(key)}=${formatLogValue(item, ancestors)}`)
|
|
166
|
-
.join(', ')} }`;
|
|
167
|
-
}
|
|
168
|
-
finally {
|
|
169
|
-
ancestors.delete(value);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
catch (error) {
|
|
173
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
174
|
-
return `[Unformattable: ${message}]`;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
function formatLocalTimestamp(timestamp) {
|
|
178
|
-
const date = new Date(timestamp);
|
|
179
|
-
if (Number.isNaN(date.getTime()))
|
|
180
|
-
return timestamp;
|
|
181
|
-
return ([
|
|
182
|
-
date.getFullYear(),
|
|
183
|
-
String(date.getMonth() + 1).padStart(2, '0'),
|
|
184
|
-
String(date.getDate()).padStart(2, '0'),
|
|
185
|
-
].join('/') +
|
|
186
|
-
` ${[
|
|
187
|
-
String(date.getHours()).padStart(2, '0'),
|
|
188
|
-
String(date.getMinutes()).padStart(2, '0'),
|
|
189
|
-
String(date.getSeconds()).padStart(2, '0'),
|
|
190
|
-
].join(':')}.${String(date.getMilliseconds()).padStart(3, '0')}`);
|
|
191
|
-
}
|
|
192
|
-
function paint(value, color, enabled) {
|
|
193
|
-
return enabled ? `\u001B[${color}m${value}\u001B[39m` : value;
|
|
194
|
-
}
|
|
195
|
-
function getRuntimeProcess() {
|
|
196
|
-
return globalThis.process;
|
|
197
|
-
}
|
|
198
|
-
function detectConsoleColors() {
|
|
199
|
-
const runtimeProcess = getRuntimeProcess();
|
|
200
|
-
const environment = runtimeProcess?.env;
|
|
201
|
-
if (environment?.NO_COLOR !== undefined ||
|
|
202
|
-
environment?.NODE_DISABLE_COLORS !== undefined) {
|
|
203
|
-
return false;
|
|
204
|
-
}
|
|
205
|
-
if (environment?.FORCE_COLOR !== undefined) {
|
|
206
|
-
return environment.FORCE_COLOR !== '0';
|
|
207
|
-
}
|
|
208
|
-
return runtimeProcess?.stdout?.isTTY === true;
|
|
209
|
-
}
|
|
1
|
+
export { ConsoleLoggerBackend, JsonConsoleLoggerBackend, LoggerService, LoggerToken, SilentLogger, } from '../core/logger.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** CLIが`@loutrejs/node`へ依存するとBunでもNode.js専用moduleを評価するため、Capability metadataだけをruntime-neutral側へ分離する。 */
|
|
2
2
|
export declare const nodeRuntimeSupport: {
|
|
3
3
|
readonly runtime: 'node';
|
|
4
|
-
readonly capabilities:
|
|
4
|
+
readonly capabilities: ReadonlySet<import("./capability.js").RuntimeSupportFeature>;
|
|
5
5
|
};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { runtimeSupportFeatures } from './capability.js';
|
|
1
2
|
/** CLIが`@loutrejs/node`へ依存するとBunでもNode.js専用moduleを評価するため、Capability metadataだけをruntime-neutral側へ分離する。 */
|
|
2
3
|
export const nodeRuntimeSupport = {
|
|
3
4
|
runtime: 'node',
|
|
4
|
-
capabilities:
|
|
5
|
+
capabilities: runtimeSupportFeatures([
|
|
5
6
|
'http.server',
|
|
6
7
|
'websocket.server',
|
|
7
8
|
'http.request.streaming',
|
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import type { Brand } from '../internal/brand.js';
|
|
2
|
+
export type RequestedPort = Brand<number, 'port.requested'>;
|
|
3
|
+
export type BoundPort = Brand<number, 'port.bound'>;
|
|
4
|
+
export declare const boundPort: (value: number) => BoundPort;
|
|
5
|
+
export declare function initialServerPort(requestedPort: number | undefined): RequestedPort;
|
|
2
6
|
export declare function canRetryOnNextPort(error: unknown, port: number): boolean;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
export const boundPort = (value) => value;
|
|
1
2
|
export function initialServerPort(requestedPort) {
|
|
2
|
-
return requestedPort ?? 3000;
|
|
3
|
+
return (requestedPort ?? 3000);
|
|
3
4
|
}
|
|
4
5
|
export function canRetryOnNextPort(error, port) {
|
|
5
6
|
return port < 65_535 && isAddressInUseError(error);
|
|
@@ -6,7 +6,7 @@ export interface ProcessSignalSource {
|
|
|
6
6
|
export interface ProcessShutdownHooksOptions {
|
|
7
7
|
readonly source: ProcessSignalSource;
|
|
8
8
|
readonly close: (signal: ShutdownSignal) => Promise<void>;
|
|
9
|
-
readonly onError: (error: unknown, signal: ShutdownSignal) => void
|
|
9
|
+
readonly onError: (error: unknown, signal: ShutdownSignal) => void | Promise<void>;
|
|
10
10
|
}
|
|
11
11
|
export declare class ProcessShutdownHooks {
|
|
12
12
|
#private;
|
|
@@ -43,7 +43,8 @@ export class ProcessShutdownHooks {
|
|
|
43
43
|
return;
|
|
44
44
|
}
|
|
45
45
|
catch (error) {
|
|
46
|
-
|
|
46
|
+
// ログ送信の完了待ちでshutdownの再試行を止めない。
|
|
47
|
+
void this.#reportError(error, signal);
|
|
47
48
|
if (!(error instanceof ApplicationShutdownError) ||
|
|
48
49
|
error.safeCleanupBoundaryReached ||
|
|
49
50
|
!error.retryAfter)
|
|
@@ -52,15 +53,15 @@ export class ProcessShutdownHooks {
|
|
|
52
53
|
await error.retryAfter;
|
|
53
54
|
}
|
|
54
55
|
catch (retryError) {
|
|
55
|
-
this.#reportError(retryError, signal);
|
|
56
|
+
void this.#reportError(retryError, signal);
|
|
56
57
|
return;
|
|
57
58
|
}
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
61
|
}
|
|
61
|
-
#reportError(error, signal) {
|
|
62
|
+
async #reportError(error, signal) {
|
|
62
63
|
try {
|
|
63
|
-
this.#options.onError(error, signal);
|
|
64
|
+
await this.#options.onError(error, signal);
|
|
64
65
|
}
|
|
65
66
|
catch (reportingError) {
|
|
66
67
|
console.error('Runtime shutdown error reporter failed', error, reportingError);
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { type DurationMs } from '../core/units.js';
|
|
2
|
+
import { type TaskExecutionKey, type TaskName, type TriggerName, type QueueName } from './identity.js';
|
|
1
3
|
import { type ExecutionDefinition, type ProviderDescriptor, type SchemaOutput, type StandardSchemaV1, type Token } from '../core/index.js';
|
|
2
4
|
export type TaskRuntime<TInput, TOutput> = [TInput] extends [void] ? () => TOutput | Promise<TOutput> : (input: TInput) => TOutput | Promise<TOutput>;
|
|
3
5
|
export interface TaskDefinitionData<TInput = unknown, TOutput = unknown> {
|
|
@@ -13,7 +15,7 @@ export type TaskArguments<TTask> = [TaskInput<TTask>] extends [void] ? readonly
|
|
|
13
15
|
declare const taskExecutionIdentityKey: unique symbol;
|
|
14
16
|
declare const queueDriverToken: unique symbol;
|
|
15
17
|
export interface TaskExecutionIdentity {
|
|
16
|
-
readonly [taskExecutionIdentityKey]:
|
|
18
|
+
readonly [taskExecutionIdentityKey]: TaskExecutionKey;
|
|
17
19
|
}
|
|
18
20
|
type AnyTaskDefinition = ExecutionDefinition & TaskExecutionIdentity & {
|
|
19
21
|
readonly type: 'task';
|
|
@@ -39,7 +41,7 @@ export interface FixedDelayTriggerDefinitionData<TTask extends AnyTaskDefinition
|
|
|
39
41
|
}
|
|
40
42
|
export interface QueueDescriptor<TSchema extends StandardSchemaV1 = StandardSchemaV1, TName extends string = string> {
|
|
41
43
|
readonly kind: 'queue';
|
|
42
|
-
readonly name: TName
|
|
44
|
+
readonly name: QueueName<TName>;
|
|
43
45
|
readonly payload: TSchema;
|
|
44
46
|
readonly [queueDriverToken]: Token<QueueConsumerDriver>;
|
|
45
47
|
}
|
|
@@ -62,39 +64,39 @@ export type TriggerDefinitionData = CronTriggerDefinitionData | FixedDelayTrigge
|
|
|
62
64
|
type TasksDefinitionData = (TaskDefinitionData & TaskExecutionIdentity) | TriggerDefinitionData;
|
|
63
65
|
export interface TasksCompiledTask {
|
|
64
66
|
readonly type: 'task';
|
|
65
|
-
readonly taskExecutionId:
|
|
66
|
-
readonly name:
|
|
67
|
+
readonly taskExecutionId: TaskExecutionKey;
|
|
68
|
+
readonly name: TaskName;
|
|
67
69
|
readonly factory: () => (...arguments_: any[]) => any;
|
|
68
70
|
}
|
|
69
71
|
export interface TasksCompiledCronTrigger {
|
|
70
72
|
readonly type: 'cron';
|
|
71
|
-
readonly name:
|
|
73
|
+
readonly name: TriggerName;
|
|
72
74
|
readonly expression: string;
|
|
73
75
|
readonly timezone: string;
|
|
74
76
|
readonly overlap: CronOverlap;
|
|
75
|
-
readonly taskExecutionId:
|
|
76
|
-
readonly taskName:
|
|
77
|
+
readonly taskExecutionId: TaskExecutionKey;
|
|
78
|
+
readonly taskName: TaskName;
|
|
77
79
|
}
|
|
78
80
|
export interface TasksCompiledFixedDelayTrigger {
|
|
79
81
|
readonly type: 'fixed-delay';
|
|
80
|
-
readonly name:
|
|
81
|
-
readonly delay:
|
|
82
|
+
readonly name: TriggerName;
|
|
83
|
+
readonly delay: DurationMs;
|
|
82
84
|
readonly immediate: boolean;
|
|
83
|
-
readonly taskExecutionId:
|
|
84
|
-
readonly taskName:
|
|
85
|
+
readonly taskExecutionId: TaskExecutionKey;
|
|
86
|
+
readonly taskName: TaskName;
|
|
85
87
|
}
|
|
86
88
|
export interface TasksCompiledQueueConsumerTrigger {
|
|
87
89
|
readonly type: 'queue-consumer';
|
|
88
|
-
readonly name:
|
|
89
|
-
readonly queueName:
|
|
90
|
+
readonly name: TriggerName;
|
|
91
|
+
readonly queueName: QueueName;
|
|
90
92
|
readonly queuePayload: StandardSchemaV1;
|
|
91
93
|
readonly queueDriver: Token<QueueConsumerDriver>;
|
|
92
|
-
readonly taskExecutionId:
|
|
93
|
-
readonly taskName:
|
|
94
|
+
readonly taskExecutionId: TaskExecutionKey;
|
|
95
|
+
readonly taskName: TaskName;
|
|
94
96
|
}
|
|
95
97
|
export type TasksCompiledExecution = TasksCompiledTask | TasksCompiledCronTrigger | TasksCompiledFixedDelayTrigger | TasksCompiledQueueConsumerTrigger;
|
|
96
98
|
export interface TasksExtensionRuntime {
|
|
97
|
-
run<TInput, TOutput>(taskExecutionId:
|
|
99
|
+
run<TInput, TOutput>(taskExecutionId: TaskExecutionKey, taskName: string, ...arguments_: [TInput] extends [void] ? readonly [] : readonly [input: TInput]): Promise<TOutput>;
|
|
98
100
|
startTriggers(): Promise<void>;
|
|
99
101
|
stopTriggers(): Promise<void>;
|
|
100
102
|
drain(): Promise<void>;
|
package/dist/tasks/extension.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { dependencyConsumerId } from '../core/identity.js';
|
|
2
|
+
import { milliseconds } from '../core/units.js';
|
|
3
|
+
import { taskExecutionKey, taskName as compiledTaskName, triggerName, queueName, } from './identity.js';
|
|
1
4
|
import { collectInjectedDependencies, defineExecution, defineExecutionExtension, provide, runInInjectionContext, token, validateSchema, } from '../core/index.js';
|
|
2
|
-
import { isValidCronExpression, matchesCronTrigger } from './runtime/cron.js';
|
|
5
|
+
import { isValidCronExpression, matchesCronTrigger, parseCronSchedule, } from './runtime/cron.js';
|
|
3
6
|
import { match } from 'ts-pattern';
|
|
4
7
|
const taskExecutionIdentityKey = Symbol.for('loutre.tasks.task-execution-identity');
|
|
5
8
|
const queueDriverToken = Symbol.for('loutre.tasks.queue-driver');
|
|
@@ -15,14 +18,14 @@ export const tasksExtension = defineExecutionExtension({
|
|
|
15
18
|
executionKind: 'task.invocation',
|
|
16
19
|
dependencies: collectInjectedDependencies({
|
|
17
20
|
kind: 'execution',
|
|
18
|
-
id: `task:${candidate.name}
|
|
19
|
-
name: candidate.name,
|
|
21
|
+
id: dependencyConsumerId(`task:${candidate.name}`),
|
|
22
|
+
name: compiledTaskName(candidate.name),
|
|
20
23
|
}, () => candidate.factory()),
|
|
21
24
|
capabilities: [],
|
|
22
25
|
compiled: Object.freeze({
|
|
23
26
|
type: 'task',
|
|
24
27
|
taskExecutionId: candidate[taskExecutionIdentityKey],
|
|
25
|
-
name: candidate.name,
|
|
28
|
+
name: compiledTaskName(candidate.name),
|
|
26
29
|
factory: candidate.factory,
|
|
27
30
|
}),
|
|
28
31
|
}))
|
|
@@ -34,12 +37,12 @@ export const tasksExtension = defineExecutionExtension({
|
|
|
34
37
|
capabilities: [],
|
|
35
38
|
compiled: Object.freeze({
|
|
36
39
|
type: 'cron',
|
|
37
|
-
name: candidate.name,
|
|
40
|
+
name: triggerName(candidate.name),
|
|
38
41
|
expression: candidate.expression,
|
|
39
42
|
timezone: candidate.timezone,
|
|
40
43
|
overlap: candidate.overlap,
|
|
41
44
|
taskExecutionId: candidate.task[taskExecutionIdentityKey],
|
|
42
|
-
taskName: candidate.task.name,
|
|
45
|
+
taskName: compiledTaskName(candidate.task.name),
|
|
43
46
|
}),
|
|
44
47
|
}))
|
|
45
48
|
.with({ type: 'fixed-delay' }, (candidate) => ({
|
|
@@ -50,11 +53,11 @@ export const tasksExtension = defineExecutionExtension({
|
|
|
50
53
|
capabilities: [],
|
|
51
54
|
compiled: Object.freeze({
|
|
52
55
|
type: 'fixed-delay',
|
|
53
|
-
name: candidate.name,
|
|
54
|
-
delay: candidate.delay,
|
|
56
|
+
name: triggerName(candidate.name),
|
|
57
|
+
delay: milliseconds(candidate.delay),
|
|
55
58
|
immediate: candidate.immediate,
|
|
56
59
|
taskExecutionId: candidate.task[taskExecutionIdentityKey],
|
|
57
|
-
taskName: candidate.task.name,
|
|
60
|
+
taskName: compiledTaskName(candidate.task.name),
|
|
58
61
|
}),
|
|
59
62
|
}))
|
|
60
63
|
.with({ type: 'queue-consumer' }, (candidate) => ({
|
|
@@ -65,12 +68,12 @@ export const tasksExtension = defineExecutionExtension({
|
|
|
65
68
|
capabilities: [],
|
|
66
69
|
compiled: Object.freeze({
|
|
67
70
|
type: 'queue-consumer',
|
|
68
|
-
name: candidate.name,
|
|
71
|
+
name: triggerName(candidate.name),
|
|
69
72
|
queueName: candidate.queue.name,
|
|
70
73
|
queuePayload: candidate.queue.payload,
|
|
71
74
|
queueDriver: queueRuntimeToken(candidate.queue),
|
|
72
75
|
taskExecutionId: candidate.task[taskExecutionIdentityKey],
|
|
73
|
-
taskName: candidate.task.name,
|
|
76
|
+
taskName: compiledTaskName(candidate.task.name),
|
|
74
77
|
}),
|
|
75
78
|
}))
|
|
76
79
|
.exhaustive();
|
|
@@ -142,7 +145,7 @@ export const tasksExtension = defineExecutionExtension({
|
|
|
142
145
|
.with({ type: 'fixed-delay' }, (candidate) => ({
|
|
143
146
|
type: 'fixed-delay',
|
|
144
147
|
name: candidate.name,
|
|
145
|
-
delay: candidate.delay,
|
|
148
|
+
delay: milliseconds(candidate.delay),
|
|
146
149
|
immediate: candidate.immediate,
|
|
147
150
|
task: candidate.taskName,
|
|
148
151
|
}))
|
|
@@ -168,7 +171,7 @@ export const tasksExtension = defineExecutionExtension({
|
|
|
168
171
|
},
|
|
169
172
|
});
|
|
170
173
|
export function task(definition) {
|
|
171
|
-
const taskExecutionId = Symbol(`loutre.tasks.execution:${definition.name}`);
|
|
174
|
+
const taskExecutionId = taskExecutionKey(Symbol(`loutre.tasks.execution:${definition.name}`));
|
|
172
175
|
return defineExecution(tasksExtension, {
|
|
173
176
|
type: 'task',
|
|
174
177
|
name: definition.name,
|
|
@@ -209,7 +212,7 @@ export function queue(declaration) {
|
|
|
209
212
|
}
|
|
210
213
|
return Object.freeze({
|
|
211
214
|
kind: 'queue',
|
|
212
|
-
name: declaration.name,
|
|
215
|
+
name: queueName(declaration.name),
|
|
213
216
|
payload: declaration.payload,
|
|
214
217
|
[queueDriverToken]: token(`loutre.tasks.queue-driver.${declaration.name}`),
|
|
215
218
|
});
|
|
@@ -230,7 +233,6 @@ export function consume(declaration) {
|
|
|
230
233
|
}
|
|
231
234
|
function createTasksRuntime(bindings, applicationRuntime) {
|
|
232
235
|
const runtimes = new Map();
|
|
233
|
-
const runtimeExecutionIds = new Map();
|
|
234
236
|
const triggers = [];
|
|
235
237
|
for (const { execution, resolve } of bindings) {
|
|
236
238
|
const compiled = execution.compiled;
|
|
@@ -241,13 +243,15 @@ function createTasksRuntime(bindings, applicationRuntime) {
|
|
|
241
243
|
const runtime = runInInjectionContext({
|
|
242
244
|
consumer: {
|
|
243
245
|
kind: 'execution',
|
|
244
|
-
id: `task:${compiled.name}
|
|
246
|
+
id: dependencyConsumerId(`task:${compiled.name}`),
|
|
245
247
|
name: compiled.name,
|
|
246
248
|
},
|
|
247
249
|
resolve: (dependency, consumer) => resolve(dependency, { consumer }),
|
|
248
250
|
}, () => compiled.factory());
|
|
249
|
-
runtimes.set(compiled.taskExecutionId,
|
|
250
|
-
|
|
251
|
+
runtimes.set(compiled.taskExecutionId, {
|
|
252
|
+
invoke: runtime,
|
|
253
|
+
executionId: execution.id,
|
|
254
|
+
});
|
|
251
255
|
}
|
|
252
256
|
let triggerHandles = [];
|
|
253
257
|
let triggerStartup;
|
|
@@ -264,7 +268,7 @@ function createTasksRuntime(bindings, applicationRuntime) {
|
|
|
264
268
|
return Promise.reject(new Error(`LUTRE_TASK_NOT_REGISTERED: ${taskName}`));
|
|
265
269
|
}
|
|
266
270
|
const invocation = (async () => {
|
|
267
|
-
const executionId =
|
|
271
|
+
const executionId = runtime.executionId;
|
|
268
272
|
const hasInput = arguments_.length > 0;
|
|
269
273
|
const input = hasInput ? arguments_[0] : undefined;
|
|
270
274
|
const lease = applicationRuntime.beginExecution({
|
|
@@ -279,7 +283,7 @@ function createTasksRuntime(bindings, applicationRuntime) {
|
|
|
279
283
|
let result;
|
|
280
284
|
let hasResult = false;
|
|
281
285
|
try {
|
|
282
|
-
const invoke = () => Reflect.apply(runtime, undefined, arguments_);
|
|
286
|
+
const invoke = () => Reflect.apply(runtime.invoke, undefined, arguments_);
|
|
283
287
|
result = await (lease.run ? lease.run(invoke) : invoke());
|
|
284
288
|
hasResult = true;
|
|
285
289
|
return result;
|
|
@@ -416,13 +420,14 @@ async function startTrigger(trigger, run, resolve) {
|
|
|
416
420
|
}
|
|
417
421
|
function startCronTrigger(trigger, run) {
|
|
418
422
|
let stopped = false;
|
|
423
|
+
const schedule = parseCronSchedule(trigger.expression, trigger.timezone);
|
|
419
424
|
let lastMinute;
|
|
420
425
|
const active = new Set();
|
|
421
426
|
const tick = () => {
|
|
422
427
|
if (stopped)
|
|
423
428
|
return;
|
|
424
429
|
const now = new Date();
|
|
425
|
-
if (!matchesCronTrigger(
|
|
430
|
+
if (!matchesCronTrigger(schedule, now))
|
|
426
431
|
return;
|
|
427
432
|
const minute = Math.floor(now.getTime() / 60_000);
|
|
428
433
|
if (lastMinute === minute)
|
|
@@ -506,9 +511,9 @@ async function startQueueTrigger(trigger, run, resolve) {
|
|
|
506
511
|
function taskExecutionIdentity(definition) {
|
|
507
512
|
if (taskExecutionIdentityKey in definition &&
|
|
508
513
|
typeof definition[taskExecutionIdentityKey] === 'symbol') {
|
|
509
|
-
return definition[taskExecutionIdentityKey];
|
|
514
|
+
return taskExecutionKey(definition[taskExecutionIdentityKey]);
|
|
510
515
|
}
|
|
511
|
-
return Symbol('loutre.tasks.unregistered');
|
|
516
|
+
return taskExecutionKey(Symbol('loutre.tasks.unregistered'));
|
|
512
517
|
}
|
|
513
518
|
function tasksStateError(state) {
|
|
514
519
|
return new Error(`LUTRE_TASKS_${state.toUpperCase()}`);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Brand } from '../internal/brand.js';
|
|
2
|
+
export type TaskExecutionKey = Brand<symbol, 'tasks.execution-key'>;
|
|
3
|
+
export type TaskName = Brand<string, 'tasks.task-name'>;
|
|
4
|
+
export type TriggerName = Brand<string, 'tasks.trigger-name'>;
|
|
5
|
+
export type QueueName<TName extends string = string> = Brand<TName, 'tasks.queue-name'>;
|
|
6
|
+
export declare const taskExecutionKey: (value: symbol) => TaskExecutionKey;
|
|
7
|
+
export declare const taskName: (value: string) => TaskName;
|
|
8
|
+
export declare const triggerName: (value: string) => TriggerName;
|
|
9
|
+
export declare const queueName: <const TName extends string>(value: TName) => QueueName<TName>;
|
package/dist/tasks/index.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
export { bindQueueDriver, consume, cron, fixedDelay, queue, task, tasksExtension, } from './extension.js';
|
|
2
2
|
export type { CronOverlap, CronTriggerDefinition, CronTriggerDefinitionData, FixedDelayTriggerDefinition, FixedDelayTriggerDefinitionData, QueueConsumerDriver, QueueConsumerHandle, QueueConsumerTriggerDefinition, QueueConsumerTriggerDefinitionData, QueueDescriptor, QueuePayload, TaskArguments, TaskDefinition, TaskDefinitionData, TaskExecutionIdentity, TaskInput, TaskOutput, TaskRuntime, TasksCompiledCronTrigger, TasksCompiledExecution, TasksCompiledFixedDelayTrigger, TasksCompiledQueueConsumerTrigger, TasksCompiledTask, TasksExtensionRuntime, TasksHostApi, TriggerDefinition, TriggerDefinitionData, } from './extension.js';
|
|
3
|
+
export type { TaskExecutionKey, TaskName, TriggerName, QueueName, } from './identity.js';
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import type { Brand } from '../../internal/brand.js';
|
|
2
|
+
export type CronExpression = Brand<string, 'tasks.cron-expression'>;
|
|
3
|
+
export type IanaTimeZone = Brand<string, 'tasks.iana-timezone'>;
|
|
1
4
|
export interface CronSchedule {
|
|
2
|
-
readonly expression:
|
|
3
|
-
readonly timezone:
|
|
5
|
+
readonly expression: CronExpression;
|
|
6
|
+
readonly timezone: IanaTimeZone;
|
|
4
7
|
}
|
|
5
8
|
export declare function matchesCronTrigger(trigger: CronSchedule, instant: Date): boolean;
|
|
6
9
|
export declare function isValidCronExpression(expression: string): boolean;
|
|
10
|
+
export declare function parseCronSchedule(expression: string, timezone: string): CronSchedule;
|
|
@@ -87,3 +87,12 @@ function isValidCronField(field, minimum, maximum) {
|
|
|
87
87
|
return start >= minimum && end <= maximum && start <= end;
|
|
88
88
|
});
|
|
89
89
|
}
|
|
90
|
+
export function parseCronSchedule(expression, timezone) {
|
|
91
|
+
if (!isValidCronExpression(expression))
|
|
92
|
+
throw new TypeError('portableな5-field cron expressionが必要です。');
|
|
93
|
+
new Intl.DateTimeFormat('en-US', { timeZone: timezone }).format();
|
|
94
|
+
return Object.freeze({
|
|
95
|
+
expression: expression,
|
|
96
|
+
timezone: timezone,
|
|
97
|
+
});
|
|
98
|
+
}
|