@hla4ts/spacekit 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/README.md +154 -0
- package/package.json +28 -0
- package/src/app-export.test.ts +45 -0
- package/src/app.ts +1018 -0
- package/src/declarative-runtime.test.ts +271 -0
- package/src/declarative-runtime.ts +541 -0
- package/src/declarative.test.ts +49 -0
- package/src/declarative.ts +254 -0
- package/src/declare-spacefom.ts +14 -0
- package/src/decorators.test.ts +133 -0
- package/src/decorators.ts +514 -0
- package/src/entity.ts +103 -0
- package/src/env.ts +168 -0
- package/src/federate.ts +205 -0
- package/src/index.ts +51 -0
- package/src/logger.ts +45 -0
- package/src/object-model.ts +275 -0
- package/src/see-app.test.ts +62 -0
- package/src/see-app.ts +460 -0
- package/src/spacefom-bootstrap.test.ts +10 -0
- package/src/spacefom-bootstrap.ts +596 -0
- package/src/spacefom-config.ts +25 -0
- package/src/spacefom-decorators.test.ts +27 -0
- package/src/spacefom-entities.ts +546 -0
- package/src/spacefom-interactions.ts +33 -0
- package/src/time-advance.ts +46 -0
- package/src/types.ts +27 -0
package/src/env.ts
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
export type LogLevel = "debug" | "info" | "warn" | "error";
|
|
2
|
+
|
|
3
|
+
export interface SpacekitLogger {
|
|
4
|
+
debug(message: string, data?: Record<string, unknown>): void;
|
|
5
|
+
info(message: string, data?: Record<string, unknown>): void;
|
|
6
|
+
warn(message: string, data?: Record<string, unknown>): void;
|
|
7
|
+
error(message: string, data?: Record<string, unknown>): void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface SpacekitEnvDefaults {
|
|
11
|
+
rtiHost?: string;
|
|
12
|
+
rtiPort?: number;
|
|
13
|
+
rtiUseTls?: boolean;
|
|
14
|
+
federationName?: string;
|
|
15
|
+
federateName?: string;
|
|
16
|
+
federateType?: string;
|
|
17
|
+
lookaheadMicros?: bigint;
|
|
18
|
+
updatePeriodMicros?: bigint;
|
|
19
|
+
referenceFrameTimeoutMs?: number;
|
|
20
|
+
logLevel?: LogLevel;
|
|
21
|
+
logEveryN?: number;
|
|
22
|
+
debug?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface SpacekitEnvConfig {
|
|
26
|
+
rtiHost: string;
|
|
27
|
+
rtiPort: number;
|
|
28
|
+
rtiUseTls: boolean;
|
|
29
|
+
federationName: string;
|
|
30
|
+
federateName: string;
|
|
31
|
+
federateType: string;
|
|
32
|
+
lookaheadMicros: bigint;
|
|
33
|
+
updatePeriodMicros: bigint;
|
|
34
|
+
referenceFrameTimeoutMs: number;
|
|
35
|
+
logLevel: LogLevel;
|
|
36
|
+
logEveryN: number;
|
|
37
|
+
debug: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface SpacekitEnvParseOptions {
|
|
41
|
+
env?: Record<string, string | undefined>;
|
|
42
|
+
defaults?: SpacekitEnvDefaults;
|
|
43
|
+
logger?: SpacekitLogger;
|
|
44
|
+
logResolved?: boolean;
|
|
45
|
+
required?: Array<"federationName" | "federateName" | "federateType">;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const DEFAULTS: Required<SpacekitEnvDefaults> = {
|
|
49
|
+
rtiHost: "localhost",
|
|
50
|
+
rtiPort: 15164,
|
|
51
|
+
rtiUseTls: false,
|
|
52
|
+
federationName: "SpaceFederation",
|
|
53
|
+
federateName: `Federate-${Date.now()}`,
|
|
54
|
+
federateType: "Federate",
|
|
55
|
+
lookaheadMicros: 1_000_000n,
|
|
56
|
+
updatePeriodMicros: 1_000_000n,
|
|
57
|
+
referenceFrameTimeoutMs: 30_000,
|
|
58
|
+
logLevel: "info",
|
|
59
|
+
logEveryN: 1,
|
|
60
|
+
debug: false,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export function parseSpacekitEnv(options: SpacekitEnvParseOptions = {}): SpacekitEnvConfig {
|
|
64
|
+
const env = options.env ?? process.env;
|
|
65
|
+
const defaults = { ...DEFAULTS, ...(options.defaults ?? {}) };
|
|
66
|
+
|
|
67
|
+
const rtiHost = env.RTI_HOST ?? defaults.rtiHost;
|
|
68
|
+
const rtiPort = parseNumber(env.RTI_PORT, defaults.rtiPort, "RTI_PORT");
|
|
69
|
+
const rtiUseTls = parseBoolean(env.RTI_USE_TLS, defaults.rtiUseTls);
|
|
70
|
+
const federationName = env.FEDERATION_NAME ?? defaults.federationName;
|
|
71
|
+
const federateName = env.FEDERATE_NAME ?? defaults.federateName;
|
|
72
|
+
const federateType = env.FEDERATE_TYPE ?? defaults.federateType;
|
|
73
|
+
const lookaheadMicros = parseBigint(
|
|
74
|
+
env.LOOKAHEAD_MICROS,
|
|
75
|
+
defaults.lookaheadMicros,
|
|
76
|
+
"LOOKAHEAD_MICROS"
|
|
77
|
+
);
|
|
78
|
+
const updatePeriodMicros = parseBigint(
|
|
79
|
+
env.UPDATE_PERIOD_MICROS,
|
|
80
|
+
defaults.updatePeriodMicros,
|
|
81
|
+
"UPDATE_PERIOD_MICROS"
|
|
82
|
+
);
|
|
83
|
+
const referenceFrameTimeoutMs = parseNumber(
|
|
84
|
+
env.REFERENCE_FRAME_TIMEOUT_MS,
|
|
85
|
+
defaults.referenceFrameTimeoutMs,
|
|
86
|
+
"REFERENCE_FRAME_TIMEOUT_MS"
|
|
87
|
+
);
|
|
88
|
+
const debug = parseBoolean(env.DEBUG, defaults.debug);
|
|
89
|
+
const logEveryN = parseNumber(env.LOG_EVERY_N, defaults.logEveryN, "LOG_EVERY_N");
|
|
90
|
+
const logLevel = parseLogLevel(env.LOG_LEVEL, debug ? "debug" : defaults.logLevel);
|
|
91
|
+
|
|
92
|
+
const config: SpacekitEnvConfig = {
|
|
93
|
+
rtiHost,
|
|
94
|
+
rtiPort,
|
|
95
|
+
rtiUseTls,
|
|
96
|
+
federationName,
|
|
97
|
+
federateName,
|
|
98
|
+
federateType,
|
|
99
|
+
lookaheadMicros,
|
|
100
|
+
updatePeriodMicros,
|
|
101
|
+
referenceFrameTimeoutMs,
|
|
102
|
+
logLevel,
|
|
103
|
+
logEveryN,
|
|
104
|
+
debug,
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
validateRequired(config, options.required);
|
|
108
|
+
|
|
109
|
+
if (options.logResolved && options.logger) {
|
|
110
|
+
options.logger.info(
|
|
111
|
+
"Resolved Spacekit env config.",
|
|
112
|
+
redactConfig(config) as unknown as Record<string, unknown>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return config;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function validateRequired(
|
|
120
|
+
config: SpacekitEnvConfig,
|
|
121
|
+
required?: Array<"federationName" | "federateName" | "federateType">
|
|
122
|
+
): void {
|
|
123
|
+
if (!required || required.length === 0) return;
|
|
124
|
+
const missing = required.filter((key) => !config[key]);
|
|
125
|
+
if (missing.length > 0) {
|
|
126
|
+
throw new Error(`Missing required env vars: ${missing.join(", ")}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function redactConfig(config: SpacekitEnvConfig): SpacekitEnvConfig {
|
|
131
|
+
return { ...config };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function parseBoolean(value: string | undefined, fallback: boolean): boolean {
|
|
135
|
+
if (value === undefined) return fallback;
|
|
136
|
+
const normalized = value.toLowerCase();
|
|
137
|
+
return normalized === "true" || normalized === "1" || normalized === "yes";
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function parseNumber(value: string | undefined, fallback: number, label: string): number {
|
|
141
|
+
if (value === undefined || value === "") return fallback;
|
|
142
|
+
const parsed = Number(value);
|
|
143
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
144
|
+
throw new Error(`Invalid number for ${label}: ${value}`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function parseBigint(value: string | undefined, fallback: bigint, label: string): bigint {
|
|
148
|
+
if (value === undefined || value === "") return fallback;
|
|
149
|
+
try {
|
|
150
|
+
return BigInt(value);
|
|
151
|
+
} catch {
|
|
152
|
+
throw new Error(`Invalid bigint for ${label}: ${value}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function parseLogLevel(value: string | undefined, fallback: LogLevel): LogLevel {
|
|
157
|
+
if (!value) return fallback;
|
|
158
|
+
const normalized = value.toLowerCase() as LogLevel;
|
|
159
|
+
if (
|
|
160
|
+
normalized === "debug" ||
|
|
161
|
+
normalized === "info" ||
|
|
162
|
+
normalized === "warn" ||
|
|
163
|
+
normalized === "error"
|
|
164
|
+
) {
|
|
165
|
+
return normalized;
|
|
166
|
+
}
|
|
167
|
+
return fallback;
|
|
168
|
+
}
|
package/src/federate.ts
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import {
|
|
2
|
+
RTIAmbassador,
|
|
3
|
+
type RTIAmbassadorOptions,
|
|
4
|
+
type FederateAmbassador,
|
|
5
|
+
type JoinResult,
|
|
6
|
+
ResignAction,
|
|
7
|
+
} from "@hla4ts/hla-api";
|
|
8
|
+
import type { FomModuleSet } from "@hla4ts/hla-api";
|
|
9
|
+
import { renderFomXml } from "@hla4ts/fom-codegen";
|
|
10
|
+
import type { SpacekitFederateConfig, FederateHandlers, SpacekitFomConfig } from "./types.ts";
|
|
11
|
+
|
|
12
|
+
function createCompositeFederateAmbassador(
|
|
13
|
+
handlerChain: FederateHandlers[]
|
|
14
|
+
): FederateAmbassador {
|
|
15
|
+
return new Proxy(
|
|
16
|
+
{},
|
|
17
|
+
{
|
|
18
|
+
get(_target, prop) {
|
|
19
|
+
if (typeof prop !== "string") return undefined;
|
|
20
|
+
const callbacks = handlerChain
|
|
21
|
+
.map((handler) => {
|
|
22
|
+
const fn = handler[prop as keyof FederateHandlers];
|
|
23
|
+
if (typeof fn !== "function") return null;
|
|
24
|
+
return { handler, fn };
|
|
25
|
+
})
|
|
26
|
+
.filter(
|
|
27
|
+
(entry): entry is { handler: FederateHandlers; fn: (...args: unknown[]) => void } =>
|
|
28
|
+
entry !== null
|
|
29
|
+
);
|
|
30
|
+
if (callbacks.length === 0) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
return (...args: unknown[]) => {
|
|
34
|
+
for (const callback of callbacks) {
|
|
35
|
+
callback.fn.call(callback.handler, ...args);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
) as FederateAmbassador;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export class SpacekitFederate {
|
|
44
|
+
private readonly _config: SpacekitFederateConfig;
|
|
45
|
+
private readonly _handlerChain: FederateHandlers[];
|
|
46
|
+
private readonly _ambassador: FederateAmbassador;
|
|
47
|
+
private _rti: RTIAmbassador | null = null;
|
|
48
|
+
private _joinResult: JoinResult | null = null;
|
|
49
|
+
|
|
50
|
+
constructor(config: SpacekitFederateConfig, handlers: FederateHandlers = {}) {
|
|
51
|
+
this._config = config;
|
|
52
|
+
this._handlerChain = [handlers];
|
|
53
|
+
this._ambassador = createCompositeFederateAmbassador(this._handlerChain);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
get rtiAmbassador(): RTIAmbassador | null {
|
|
57
|
+
return this._rti;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
get joinResult(): JoinResult | null {
|
|
61
|
+
return this._joinResult;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async start(): Promise<JoinResult> {
|
|
65
|
+
const rti = new RTIAmbassador(this._config.rti as RTIAmbassadorOptions);
|
|
66
|
+
this._rti = rti;
|
|
67
|
+
|
|
68
|
+
await rti.connect(this._ambassador, this._config.connection);
|
|
69
|
+
|
|
70
|
+
const modules = await buildFomModules(this._config.fom);
|
|
71
|
+
const { federateName, federateType, federationName } = this._config;
|
|
72
|
+
|
|
73
|
+
let joinResult: JoinResult;
|
|
74
|
+
if (modules && modules.length > 0) {
|
|
75
|
+
if (federateName) {
|
|
76
|
+
joinResult = await rti.joinFederationExecutionWithNameAndModules(
|
|
77
|
+
federateName,
|
|
78
|
+
federateType,
|
|
79
|
+
federationName,
|
|
80
|
+
modules
|
|
81
|
+
);
|
|
82
|
+
} else {
|
|
83
|
+
joinResult = await rti.joinFederationExecutionWithModules(
|
|
84
|
+
federateType,
|
|
85
|
+
federationName,
|
|
86
|
+
modules
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
} else if (federateName) {
|
|
90
|
+
joinResult = await rti.joinFederationExecutionWithName(
|
|
91
|
+
federateName,
|
|
92
|
+
federateType,
|
|
93
|
+
federationName
|
|
94
|
+
);
|
|
95
|
+
} else {
|
|
96
|
+
joinResult = await rti.joinFederationExecution(federateType, federationName);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
this._joinResult = joinResult;
|
|
100
|
+
return joinResult;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async stop(options: {
|
|
104
|
+
resignTimeoutMs?: number;
|
|
105
|
+
disconnectTimeoutMs?: number;
|
|
106
|
+
force?: boolean;
|
|
107
|
+
onError?: (phase: "resign" | "disconnect" | "force-disconnect", error: Error) => void;
|
|
108
|
+
} = {}): Promise<void> {
|
|
109
|
+
if (!this._rti) return;
|
|
110
|
+
const resignAction = this._config.resignAction ?? ResignAction.DELETE_OBJECTS_THEN_DIVEST;
|
|
111
|
+
const resignTimeoutMs = options.resignTimeoutMs ?? 5000;
|
|
112
|
+
const disconnectTimeoutMs = options.disconnectTimeoutMs ?? 5000;
|
|
113
|
+
const force = options.force ?? true;
|
|
114
|
+
let forceDisconnect = false;
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
await withTimeout(
|
|
118
|
+
this._rti.resignFederationExecution(resignAction),
|
|
119
|
+
resignTimeoutMs,
|
|
120
|
+
"Resign timed out"
|
|
121
|
+
);
|
|
122
|
+
} catch (err) {
|
|
123
|
+
forceDisconnect = true;
|
|
124
|
+
options.onError?.(
|
|
125
|
+
"resign",
|
|
126
|
+
err instanceof Error ? err : new Error(String(err))
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
await withTimeout(
|
|
132
|
+
this._rti.disconnect(),
|
|
133
|
+
disconnectTimeoutMs,
|
|
134
|
+
"Disconnect timed out"
|
|
135
|
+
);
|
|
136
|
+
} catch (err) {
|
|
137
|
+
forceDisconnect = true;
|
|
138
|
+
options.onError?.(
|
|
139
|
+
"disconnect",
|
|
140
|
+
err instanceof Error ? err : new Error(String(err))
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (force && forceDisconnect) {
|
|
145
|
+
try {
|
|
146
|
+
await this._rti.disconnectNow();
|
|
147
|
+
} catch (err) {
|
|
148
|
+
options.onError?.(
|
|
149
|
+
"force-disconnect",
|
|
150
|
+
err instanceof Error ? err : new Error(String(err))
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
this._rti = null;
|
|
156
|
+
this._joinResult = null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
addHandlers(extra: FederateHandlers): void {
|
|
160
|
+
this._handlerChain.push(extra);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function withTimeout<T>(promise: Promise<T>, timeoutMs: number, message: string): Promise<T> {
|
|
165
|
+
if (timeoutMs <= 0) {
|
|
166
|
+
return promise;
|
|
167
|
+
}
|
|
168
|
+
let timeoutHandle: ReturnType<typeof setTimeout> | undefined;
|
|
169
|
+
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
170
|
+
timeoutHandle = setTimeout(() => reject(new Error(message)), timeoutMs);
|
|
171
|
+
});
|
|
172
|
+
try {
|
|
173
|
+
return await Promise.race([promise, timeoutPromise]);
|
|
174
|
+
} finally {
|
|
175
|
+
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function buildFomModules(config?: SpacekitFomConfig): Promise<FomModuleSet | undefined> {
|
|
180
|
+
if (!config) return undefined;
|
|
181
|
+
const modules: FomModuleSet = [];
|
|
182
|
+
if (config.baseModules) {
|
|
183
|
+
modules.push(...config.baseModules);
|
|
184
|
+
}
|
|
185
|
+
const extension = config.extension;
|
|
186
|
+
if (extension) {
|
|
187
|
+
let xml = extension.xml;
|
|
188
|
+
if (!xml && extension.registry) {
|
|
189
|
+
xml = extension.registry.toXml({
|
|
190
|
+
format: extension.format ?? "omt",
|
|
191
|
+
...extension.outputOptions,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
if (!xml && extension.model) {
|
|
195
|
+
xml = renderFomXml(extension.model, {
|
|
196
|
+
format: extension.format ?? "omt",
|
|
197
|
+
...extension.outputOptions,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
if (xml) {
|
|
201
|
+
modules.push({ type: "inline", content: xml });
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return modules.length > 0 ? modules : undefined;
|
|
205
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @hla4ts/spacekit
|
|
3
|
+
*
|
|
4
|
+
* SEE-first lifecycle + declarative helpers for HLA federates.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { SpacekitFederate } from "./federate.ts";
|
|
8
|
+
export type { SpacekitFederateConfig, SpacekitFomConfig, FederateHandlers } from "./types.ts";
|
|
9
|
+
export * from "./object-model.ts";
|
|
10
|
+
export * from "./declarative.ts";
|
|
11
|
+
export * from "./declarative-runtime.ts";
|
|
12
|
+
export * from "./decorators.ts";
|
|
13
|
+
export * from "./entity.ts";
|
|
14
|
+
export {
|
|
15
|
+
SpacekitApp as BaseSpacekitApp,
|
|
16
|
+
} from "./app.ts";
|
|
17
|
+
export type {
|
|
18
|
+
SpacekitBootstrapper,
|
|
19
|
+
SpacekitAppConfig as BaseSpacekitAppConfig,
|
|
20
|
+
RegisterObjectOptions,
|
|
21
|
+
TrackOptions,
|
|
22
|
+
TrackRecord,
|
|
23
|
+
} from "./app.ts";
|
|
24
|
+
export { SpacekitApp } from "./see-app.ts";
|
|
25
|
+
export type { MissingReferenceFrameMode, SpacekitAppOptions } from "./see-app.ts";
|
|
26
|
+
export * from "./time-advance.ts";
|
|
27
|
+
export * from "./env.ts";
|
|
28
|
+
export * from "./logger.ts";
|
|
29
|
+
export * from "./spacefom-entities.ts";
|
|
30
|
+
export * from "./spacefom-interactions.ts";
|
|
31
|
+
export * from "./spacefom-bootstrap.ts";
|
|
32
|
+
export * from "./spacefom-config.ts";
|
|
33
|
+
export * from "./declare-spacefom.ts";
|
|
34
|
+
export {
|
|
35
|
+
HLAunicodeStringCoder,
|
|
36
|
+
SpaceFomExecutionMode,
|
|
37
|
+
SpaceFomMtrMode,
|
|
38
|
+
SpaceFomSyncPointLabels,
|
|
39
|
+
loadSpaceFomModules,
|
|
40
|
+
SpaceFomBaseModules,
|
|
41
|
+
} from "@hla4ts/spacefom";
|
|
42
|
+
export type {
|
|
43
|
+
SpaceFomBaseModuleName,
|
|
44
|
+
SpaceFomModuleFormat,
|
|
45
|
+
SpaceFomCoder,
|
|
46
|
+
AttitudeQuaternion,
|
|
47
|
+
Matrix3,
|
|
48
|
+
SpaceTimeCoordinateState,
|
|
49
|
+
Vector3,
|
|
50
|
+
} from "@hla4ts/spacefom";
|
|
51
|
+
export * from "@hla4ts/spacefom";
|
package/src/logger.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { LogLevel, SpacekitLogger } from "./env.ts";
|
|
2
|
+
|
|
3
|
+
const LOG_LEVEL_ORDER: Record<LogLevel, number> = {
|
|
4
|
+
debug: 10,
|
|
5
|
+
info: 20,
|
|
6
|
+
warn: 30,
|
|
7
|
+
error: 40,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export interface CreateLoggerOptions {
|
|
11
|
+
level: LogLevel;
|
|
12
|
+
scope?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function createConsoleLogger(options: CreateLoggerOptions): SpacekitLogger {
|
|
16
|
+
const threshold = LOG_LEVEL_ORDER[options.level];
|
|
17
|
+
const scope = options.scope ?? "spacekit";
|
|
18
|
+
|
|
19
|
+
function emit(level: LogLevel, message: string, data?: Record<string, unknown>) {
|
|
20
|
+
if (LOG_LEVEL_ORDER[level] < threshold) return;
|
|
21
|
+
const prefix = `[${scope}] ${new Date().toISOString()} ${level.toUpperCase()}:`;
|
|
22
|
+
if (data) {
|
|
23
|
+
console.log(prefix, message, data);
|
|
24
|
+
} else {
|
|
25
|
+
console.log(prefix, message);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const logger: SpacekitLogger = {
|
|
30
|
+
debug: (message: string, data?: Record<string, unknown>) => emit("debug", message, data),
|
|
31
|
+
info: (message: string, data?: Record<string, unknown>) => emit("info", message, data),
|
|
32
|
+
warn: (message: string, data?: Record<string, unknown>) => emit("warn", message, data),
|
|
33
|
+
error: (message: string, data?: Record<string, unknown>) => emit("error", message, data),
|
|
34
|
+
};
|
|
35
|
+
return withDebugEnabled(logger, options.level === "debug");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function isDebugEnabled(logger: SpacekitLogger): boolean {
|
|
39
|
+
return (logger as { __debugEnabled?: boolean }).__debugEnabled === true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function withDebugEnabled(logger: SpacekitLogger, enabled: boolean): SpacekitLogger {
|
|
43
|
+
(logger as { __debugEnabled?: boolean }).__debugEnabled = enabled;
|
|
44
|
+
return logger;
|
|
45
|
+
}
|