@spfn/core 0.3.0-beta.3 → 0.3.0-beta.5
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 +84 -0
- package/dist/authz/index.js +1 -381
- package/dist/authz/index.js.map +1 -1
- package/dist/contract/index.d.ts +4 -3
- package/dist/env/loader.js +24 -1
- package/dist/env/loader.js.map +1 -1
- package/dist/errors/index.js +1 -381
- package/dist/errors/index.js.map +1 -1
- package/dist/logger/index.js +0 -12
- package/dist/logger/index.js.map +1 -1
- package/dist/middleware/index.js +6 -387
- package/dist/middleware/index.js.map +1 -1
- package/dist/ops/index.d.ts +64 -8
- package/dist/ops/index.js +330 -30
- package/dist/ops/index.js.map +1 -1
- package/dist/route/index.d.ts +4 -2
- package/dist/{router-Cy7rAfmj.d.ts → route-builder-2ani2jEI.d.ts} +2 -125
- package/dist/router-DJdpwuB6.d.ts +127 -0
- package/dist/server/index.d.ts +56 -2
- package/dist/server/index.js +414 -3
- package/dist/server/index.js.map +1 -1
- package/dist/{types-CfYVhIQ9.d.ts → types-ClQVomgV.d.ts} +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -359,6 +359,39 @@ probe depends on, and a probe reaches it unauthenticated. An app with `src/serve
|
|
|
359
359
|
|
|
360
360
|
---
|
|
361
361
|
|
|
362
|
+
## How does a client synchronize with the server clock?
|
|
363
|
+
|
|
364
|
+
Call `GET /_core/time` before minting a timestamped proof. It is a built-in,
|
|
365
|
+
unauthenticated and session-free operation registered before application middleware and
|
|
366
|
+
routes:
|
|
367
|
+
|
|
368
|
+
```json
|
|
369
|
+
{ "serverTimeMillis": 1750000000123 }
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
The response is a closed contract: `serverTimeMillis` is an integer Unix epoch in
|
|
373
|
+
milliseconds and no additional fields are declared. It always carries
|
|
374
|
+
`Cache-Control: no-store`; a cached clock reading is not a synchronization point.
|
|
375
|
+
|
|
376
|
+
The value is trustworthy only when the transport is trustworthy. Production clients must
|
|
377
|
+
call the endpoint over HTTPS and validate the server certificate. The endpoint does not
|
|
378
|
+
sign its response, compensate for network latency, choose an authentication skew margin,
|
|
379
|
+
or define client retry and persistence policy — those belong to the consuming protocol.
|
|
380
|
+
|
|
381
|
+
Tests can inject an exact clock without replacing global time:
|
|
382
|
+
|
|
383
|
+
```typescript
|
|
384
|
+
const app = await createServer(defineServerConfig()
|
|
385
|
+
.serverTime({ clock: { now: () => 1750000000123 } })
|
|
386
|
+
.build());
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
`CORE_TIME_ROUTE`, `CORE_TIME_PATH`, `ServerTimeResponseSchema` and the
|
|
390
|
+
`ServerTimeResponse` type are exported from `@spfn/core/server` for separately deployed
|
|
391
|
+
client-contract exporters to consume from the same wire definition.
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
362
395
|
## Can I deploy this to Vercel?
|
|
363
396
|
|
|
364
397
|
Yes, and it is a first-class target rather than a workaround. From your app:
|
|
@@ -483,6 +516,57 @@ spfn ops call listSignups --query limit=50 # invoke one
|
|
|
483
516
|
spfn ops call listSignups --describe # print its usage (--json for raw schemas)
|
|
484
517
|
```
|
|
485
518
|
|
|
519
|
+
### Can a package ship ops commands?
|
|
520
|
+
|
|
521
|
+
It can describe them. Whether they are reachable is your application's decision, made in
|
|
522
|
+
the `createOpsRouter` call — installing a package never adds anything to your ops surface.
|
|
523
|
+
Available from **0.3.0-beta.5**.
|
|
524
|
+
|
|
525
|
+
```typescript
|
|
526
|
+
// in the package
|
|
527
|
+
export const ledgerOpsModule = defineOpsModule({
|
|
528
|
+
id: 'ledger',
|
|
529
|
+
source: '@acme/ledger',
|
|
530
|
+
contractVersion: '1.0.0',
|
|
531
|
+
summary: 'Ledger diagnostics',
|
|
532
|
+
commands: {
|
|
533
|
+
verify: {
|
|
534
|
+
summary: 'Verify ledger invariants',
|
|
535
|
+
effect: 'read', // read | write | destructive
|
|
536
|
+
scopes: ['ledger:read'],
|
|
537
|
+
route: opsRoute.get('/ledger/verify').handler(verifyLedger),
|
|
538
|
+
},
|
|
539
|
+
},
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
// in the application — nothing is mounted until this line names it
|
|
543
|
+
export const opsRouter = createOpsRouter({ listSignups }, {
|
|
544
|
+
auth: opsTokenAuth,
|
|
545
|
+
authorize: requireOpsScope,
|
|
546
|
+
modules: [ledgerOpsModule],
|
|
547
|
+
});
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
A module command is named `<module>.<command>` (`ledger.verify`) and its route must live
|
|
551
|
+
under `/_ops/<module>/`. The scopes it declares become a server-side guard, run after
|
|
552
|
+
authentication — `authorize` is required as soon as any module is mounted, and it is passed
|
|
553
|
+
in rather than imported so core stays independent of `@spfn/auth`.
|
|
554
|
+
|
|
555
|
+
What is refused at definition time: a path that could decode its way out of the module's
|
|
556
|
+
namespace, two commands in one module that could answer the same URL, and an app route that
|
|
557
|
+
overlaps a mounted module's command. That last one matters because the alternative is a
|
|
558
|
+
surface where which command answers depends on route registration order.
|
|
559
|
+
|
|
560
|
+
The manifest gains a `modules` array and per-command `module`, `summary`, `effect` and
|
|
561
|
+
`scopes`. All of it is additive — an app that mounts no modules serves exactly the v1
|
|
562
|
+
manifest it served before.
|
|
563
|
+
|
|
564
|
+
```bash
|
|
565
|
+
spfn ops modules # what is mounted, and from where
|
|
566
|
+
spfn ops list --module ledger # just that module's commands
|
|
567
|
+
spfn ops call ledger.compact --yes # effect=destructive needs this
|
|
568
|
+
```
|
|
569
|
+
|
|
486
570
|
Authentication is an ops token from [`@spfn/auth`](../auth/README.md#ops-tokens-spfn-ops):
|
|
487
571
|
scoped, revocable, hash-stored, issued with `spfn ops token issue` against the running app
|
|
488
572
|
— the CLI signs in as an administrator, so issuance needs no database access. On macOS the
|
package/dist/authz/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { logger } from '@spfn/core/logger';
|
|
2
2
|
|
|
3
3
|
// src/errors/error-registry.ts
|
|
4
4
|
var ErrorRegistry = class _ErrorRegistry {
|
|
@@ -90,386 +90,6 @@ var ErrorRegistry = class _ErrorRegistry {
|
|
|
90
90
|
return Array.from(this.errors.keys());
|
|
91
91
|
}
|
|
92
92
|
};
|
|
93
|
-
|
|
94
|
-
// src/logger/types.ts
|
|
95
|
-
var LOG_LEVEL_PRIORITY = {
|
|
96
|
-
debug: 0,
|
|
97
|
-
info: 1,
|
|
98
|
-
warn: 2,
|
|
99
|
-
error: 3,
|
|
100
|
-
fatal: 4
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
// src/logger/formatters.ts
|
|
104
|
-
var SENSITIVE_KEYS = [
|
|
105
|
-
"password",
|
|
106
|
-
"passwd",
|
|
107
|
-
"pwd",
|
|
108
|
-
"secret",
|
|
109
|
-
"token",
|
|
110
|
-
"apikey",
|
|
111
|
-
"api_key",
|
|
112
|
-
"accesstoken",
|
|
113
|
-
"access_token",
|
|
114
|
-
"refreshtoken",
|
|
115
|
-
"refresh_token",
|
|
116
|
-
"authorization",
|
|
117
|
-
"auth",
|
|
118
|
-
"cookie",
|
|
119
|
-
"session",
|
|
120
|
-
"sessionid",
|
|
121
|
-
"session_id",
|
|
122
|
-
"privatekey",
|
|
123
|
-
"private_key",
|
|
124
|
-
"creditcard",
|
|
125
|
-
"credit_card",
|
|
126
|
-
"cardnumber",
|
|
127
|
-
"card_number",
|
|
128
|
-
"cvv",
|
|
129
|
-
"ssn",
|
|
130
|
-
"pin"
|
|
131
|
-
];
|
|
132
|
-
var MASKED_VALUE = "***MASKED***";
|
|
133
|
-
function isSensitiveKey(key) {
|
|
134
|
-
const lowerKey = key.toLowerCase();
|
|
135
|
-
return SENSITIVE_KEYS.some((sensitive) => lowerKey.includes(sensitive));
|
|
136
|
-
}
|
|
137
|
-
function maskSensitiveData(data, seen = /* @__PURE__ */ new WeakSet()) {
|
|
138
|
-
if (data === null || data === void 0) {
|
|
139
|
-
return data;
|
|
140
|
-
}
|
|
141
|
-
if (typeof data !== "object") {
|
|
142
|
-
return data;
|
|
143
|
-
}
|
|
144
|
-
if (seen.has(data)) {
|
|
145
|
-
return "[Circular]";
|
|
146
|
-
}
|
|
147
|
-
seen.add(data);
|
|
148
|
-
if (Array.isArray(data)) {
|
|
149
|
-
return data.map((item) => maskSensitiveData(item, seen));
|
|
150
|
-
}
|
|
151
|
-
const masked = {};
|
|
152
|
-
for (const [key, value] of Object.entries(data)) {
|
|
153
|
-
if (isSensitiveKey(key)) {
|
|
154
|
-
masked[key] = MASKED_VALUE;
|
|
155
|
-
} else if (typeof value === "object" && value !== null) {
|
|
156
|
-
masked[key] = maskSensitiveData(value, seen);
|
|
157
|
-
} else {
|
|
158
|
-
masked[key] = value;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
return masked;
|
|
162
|
-
}
|
|
163
|
-
var COLORS = {
|
|
164
|
-
reset: "\x1B[0m",
|
|
165
|
-
bright: "\x1B[1m",
|
|
166
|
-
dim: "\x1B[2m",
|
|
167
|
-
// 로그 레벨 컬러
|
|
168
|
-
debug: "\x1B[36m",
|
|
169
|
-
// cyan
|
|
170
|
-
info: "\x1B[32m",
|
|
171
|
-
// green
|
|
172
|
-
warn: "\x1B[33m",
|
|
173
|
-
// yellow
|
|
174
|
-
error: "\x1B[31m",
|
|
175
|
-
// red
|
|
176
|
-
fatal: "\x1B[35m",
|
|
177
|
-
// magenta
|
|
178
|
-
// 추가 컬러
|
|
179
|
-
gray: "\x1B[90m"
|
|
180
|
-
};
|
|
181
|
-
function formatTimestampHuman(date) {
|
|
182
|
-
const year = date.getFullYear();
|
|
183
|
-
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
184
|
-
const day = String(date.getDate()).padStart(2, "0");
|
|
185
|
-
const hours = String(date.getHours()).padStart(2, "0");
|
|
186
|
-
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
187
|
-
const seconds = String(date.getSeconds()).padStart(2, "0");
|
|
188
|
-
const ms = String(date.getMilliseconds()).padStart(3, "0");
|
|
189
|
-
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${ms}`;
|
|
190
|
-
}
|
|
191
|
-
function formatError(error) {
|
|
192
|
-
const lines = [];
|
|
193
|
-
lines.push(`${error.name}: ${error.message}`);
|
|
194
|
-
if (error.stack) {
|
|
195
|
-
const stackLines = error.stack.split("\n").slice(1);
|
|
196
|
-
lines.push(...stackLines);
|
|
197
|
-
}
|
|
198
|
-
if (error.cause instanceof Error) {
|
|
199
|
-
lines.push(`Caused by: ${formatError(error.cause)}`);
|
|
200
|
-
} else if (error.cause !== void 0) {
|
|
201
|
-
lines.push(`Caused by: ${String(error.cause)}`);
|
|
202
|
-
}
|
|
203
|
-
return lines.join("\n");
|
|
204
|
-
}
|
|
205
|
-
function formatConsole(metadata, colorize = true) {
|
|
206
|
-
const parts = [];
|
|
207
|
-
const timestamp = formatTimestampHuman(metadata.timestamp);
|
|
208
|
-
if (colorize) {
|
|
209
|
-
parts.push(`${COLORS.gray}[${timestamp}]${COLORS.reset}`);
|
|
210
|
-
} else {
|
|
211
|
-
parts.push(`[${timestamp}]`);
|
|
212
|
-
}
|
|
213
|
-
const pid = process.pid;
|
|
214
|
-
if (colorize) {
|
|
215
|
-
parts.push(`${COLORS.dim}[pid=${pid}]${COLORS.reset}`);
|
|
216
|
-
} else {
|
|
217
|
-
parts.push(`[pid=${pid}]`);
|
|
218
|
-
}
|
|
219
|
-
if (metadata.module) {
|
|
220
|
-
if (colorize) {
|
|
221
|
-
parts.push(`${COLORS.dim}[module=${metadata.module}]${COLORS.reset}`);
|
|
222
|
-
} else {
|
|
223
|
-
parts.push(`[module=${metadata.module}]`);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
if (metadata.context && Object.keys(metadata.context).length > 0) {
|
|
227
|
-
Object.entries(metadata.context).forEach(([key, value]) => {
|
|
228
|
-
let valueStr;
|
|
229
|
-
if (typeof value === "string") {
|
|
230
|
-
valueStr = value;
|
|
231
|
-
} else if (typeof value === "object" && value !== null) {
|
|
232
|
-
try {
|
|
233
|
-
valueStr = JSON.stringify(value);
|
|
234
|
-
} catch (error) {
|
|
235
|
-
valueStr = "[circular]";
|
|
236
|
-
}
|
|
237
|
-
} else {
|
|
238
|
-
valueStr = String(value);
|
|
239
|
-
}
|
|
240
|
-
if (colorize) {
|
|
241
|
-
parts.push(`${COLORS.dim}[${key}=${valueStr}]${COLORS.reset}`);
|
|
242
|
-
} else {
|
|
243
|
-
parts.push(`[${key}=${valueStr}]`);
|
|
244
|
-
}
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
const levelStr = metadata.level.toUpperCase();
|
|
248
|
-
if (colorize) {
|
|
249
|
-
const color = COLORS[metadata.level];
|
|
250
|
-
parts.push(`${color}(${levelStr})${COLORS.reset}:`);
|
|
251
|
-
} else {
|
|
252
|
-
parts.push(`(${levelStr}):`);
|
|
253
|
-
}
|
|
254
|
-
if (colorize) {
|
|
255
|
-
parts.push(`${COLORS.bright}${metadata.message}${COLORS.reset}`);
|
|
256
|
-
} else {
|
|
257
|
-
parts.push(metadata.message);
|
|
258
|
-
}
|
|
259
|
-
let output = parts.join(" ");
|
|
260
|
-
if (metadata.error) {
|
|
261
|
-
output += "\n" + formatError(metadata.error);
|
|
262
|
-
}
|
|
263
|
-
return output;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// src/logger/logger.ts
|
|
267
|
-
var FORMAT_PATTERN = /%[sdifjoOc%]/;
|
|
268
|
-
var Logger = class _Logger {
|
|
269
|
-
config;
|
|
270
|
-
module;
|
|
271
|
-
constructor(config) {
|
|
272
|
-
this.config = config;
|
|
273
|
-
this.module = config.module;
|
|
274
|
-
}
|
|
275
|
-
/**
|
|
276
|
-
* Convert unknown error to Error object
|
|
277
|
-
*/
|
|
278
|
-
toError(error) {
|
|
279
|
-
if (error instanceof Error) return error;
|
|
280
|
-
if (typeof error === "string") return new Error(error);
|
|
281
|
-
if (typeof error === "object" && error !== null) {
|
|
282
|
-
return new Error(JSON.stringify(error));
|
|
283
|
-
}
|
|
284
|
-
return new Error(String(error));
|
|
285
|
-
}
|
|
286
|
-
/**
|
|
287
|
-
* Check if value is a context object (not an error)
|
|
288
|
-
*/
|
|
289
|
-
isContext(value) {
|
|
290
|
-
if (typeof value !== "object" || value === null) return false;
|
|
291
|
-
if (value instanceof Error) return false;
|
|
292
|
-
const hasStack = "stack" in value && typeof value.stack === "string";
|
|
293
|
-
if (hasStack) {
|
|
294
|
-
return false;
|
|
295
|
-
}
|
|
296
|
-
return true;
|
|
297
|
-
}
|
|
298
|
-
/**
|
|
299
|
-
* Get current log level
|
|
300
|
-
*/
|
|
301
|
-
get level() {
|
|
302
|
-
return this.config.level;
|
|
303
|
-
}
|
|
304
|
-
/**
|
|
305
|
-
* Create child logger (per module)
|
|
306
|
-
*/
|
|
307
|
-
child(module) {
|
|
308
|
-
return new _Logger({
|
|
309
|
-
...this.config,
|
|
310
|
-
module
|
|
311
|
-
});
|
|
312
|
-
}
|
|
313
|
-
/**
|
|
314
|
-
* Common log method with error/context detection
|
|
315
|
-
*/
|
|
316
|
-
logWithLevel(level, message, errorOrContext, context) {
|
|
317
|
-
if (errorOrContext !== void 0 && FORMAT_PATTERN.test(message)) {
|
|
318
|
-
this.log(level, format(message, errorOrContext), void 0, context);
|
|
319
|
-
return;
|
|
320
|
-
}
|
|
321
|
-
if (errorOrContext instanceof Error) {
|
|
322
|
-
this.log(level, message, errorOrContext, context);
|
|
323
|
-
} else if (errorOrContext !== void 0 && typeof errorOrContext === "object" && !this.isContext(errorOrContext)) {
|
|
324
|
-
this.log(level, message, this.toError(errorOrContext), context);
|
|
325
|
-
} else if (typeof errorOrContext === "string" || typeof errorOrContext === "number" || typeof errorOrContext === "boolean") {
|
|
326
|
-
this.log(level, message, this.toError(errorOrContext), context);
|
|
327
|
-
} else {
|
|
328
|
-
this.log(level, message, void 0, errorOrContext);
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
debug(message, errorOrContext, context) {
|
|
332
|
-
this.logWithLevel("debug", message, errorOrContext, context);
|
|
333
|
-
}
|
|
334
|
-
info(message, errorOrContext, context) {
|
|
335
|
-
this.logWithLevel("info", message, errorOrContext, context);
|
|
336
|
-
}
|
|
337
|
-
warn(message, errorOrContext, context) {
|
|
338
|
-
this.logWithLevel("warn", message, errorOrContext, context);
|
|
339
|
-
}
|
|
340
|
-
error(message, errorOrContext, context) {
|
|
341
|
-
this.logWithLevel("error", message, errorOrContext, context);
|
|
342
|
-
}
|
|
343
|
-
fatal(message, errorOrContext, context) {
|
|
344
|
-
this.logWithLevel("fatal", message, errorOrContext, context);
|
|
345
|
-
}
|
|
346
|
-
/**
|
|
347
|
-
* Log processing (internal)
|
|
348
|
-
*/
|
|
349
|
-
log(level, message, error, context) {
|
|
350
|
-
if (LOG_LEVEL_PRIORITY[level] < LOG_LEVEL_PRIORITY[this.config.level]) {
|
|
351
|
-
return;
|
|
352
|
-
}
|
|
353
|
-
const metadata = {
|
|
354
|
-
timestamp: /* @__PURE__ */ new Date(),
|
|
355
|
-
level,
|
|
356
|
-
message,
|
|
357
|
-
module: this.module,
|
|
358
|
-
error,
|
|
359
|
-
// Mask sensitive information in context to prevent credential leaks
|
|
360
|
-
context: context ? maskSensitiveData(context) : void 0
|
|
361
|
-
};
|
|
362
|
-
this.processTransports(metadata);
|
|
363
|
-
}
|
|
364
|
-
/**
|
|
365
|
-
* Process Transports
|
|
366
|
-
*/
|
|
367
|
-
processTransports(metadata) {
|
|
368
|
-
const promises = this.config.transports.filter((transport) => transport.enabled).map((transport) => this.safeTransportLog(transport, metadata));
|
|
369
|
-
Promise.all(promises).catch((error) => {
|
|
370
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
371
|
-
process.stderr.write(`[Logger] Transport error: ${errorMessage}
|
|
372
|
-
`);
|
|
373
|
-
});
|
|
374
|
-
}
|
|
375
|
-
/**
|
|
376
|
-
* Transport log (error-safe)
|
|
377
|
-
*/
|
|
378
|
-
async safeTransportLog(transport, metadata) {
|
|
379
|
-
try {
|
|
380
|
-
await transport.log(metadata);
|
|
381
|
-
} catch (error) {
|
|
382
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
383
|
-
process.stderr.write(`[Logger] Transport "${transport.name}" failed: ${errorMessage}
|
|
384
|
-
`);
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
/**
|
|
388
|
-
* Close all Transports
|
|
389
|
-
*/
|
|
390
|
-
async close() {
|
|
391
|
-
const closePromises = this.config.transports.filter((transport) => transport.close).map((transport) => transport.close());
|
|
392
|
-
await Promise.all(closePromises);
|
|
393
|
-
}
|
|
394
|
-
};
|
|
395
|
-
|
|
396
|
-
// src/logger/transports/console.ts
|
|
397
|
-
var ConsoleTransport = class {
|
|
398
|
-
name = "console";
|
|
399
|
-
level;
|
|
400
|
-
enabled;
|
|
401
|
-
colorize;
|
|
402
|
-
constructor(config) {
|
|
403
|
-
this.level = config.level;
|
|
404
|
-
this.enabled = config.enabled;
|
|
405
|
-
this.colorize = config.colorize ?? true;
|
|
406
|
-
}
|
|
407
|
-
async log(metadata) {
|
|
408
|
-
if (!this.enabled) {
|
|
409
|
-
return;
|
|
410
|
-
}
|
|
411
|
-
if (LOG_LEVEL_PRIORITY[metadata.level] < LOG_LEVEL_PRIORITY[this.level]) {
|
|
412
|
-
return;
|
|
413
|
-
}
|
|
414
|
-
const message = formatConsole(metadata, this.colorize);
|
|
415
|
-
if (metadata.level === "warn" || metadata.level === "error" || metadata.level === "fatal") {
|
|
416
|
-
console.error(message);
|
|
417
|
-
} else {
|
|
418
|
-
console.log(message);
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
};
|
|
422
|
-
|
|
423
|
-
// src/logger/config.ts
|
|
424
|
-
function getConsoleConfig() {
|
|
425
|
-
const isProduction = process.env.NODE_ENV === "production";
|
|
426
|
-
return {
|
|
427
|
-
level: "debug",
|
|
428
|
-
enabled: true,
|
|
429
|
-
colorize: !isProduction
|
|
430
|
-
// Dev: colored output, Production: plain text
|
|
431
|
-
};
|
|
432
|
-
}
|
|
433
|
-
function validateEnvironment() {
|
|
434
|
-
const nodeEnv = process.env.NODE_ENV;
|
|
435
|
-
if (!nodeEnv) {
|
|
436
|
-
process.stderr.write(
|
|
437
|
-
"[Logger] Warning: NODE_ENV is not set. Defaulting to test environment.\n"
|
|
438
|
-
);
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
function validateConfig() {
|
|
442
|
-
validateEnvironment();
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
// src/logger/factory.ts
|
|
446
|
-
function initializeTransports() {
|
|
447
|
-
const transports = [];
|
|
448
|
-
const consoleConfig = getConsoleConfig();
|
|
449
|
-
transports.push(new ConsoleTransport(consoleConfig));
|
|
450
|
-
return transports;
|
|
451
|
-
}
|
|
452
|
-
function getLogLevel() {
|
|
453
|
-
const envLevel = process.env.SPFN_LOG_LEVEL || process.env.NEXT_PUBLIC_SPFN_LOG_LEVEL || "info";
|
|
454
|
-
if (envLevel in LOG_LEVEL_PRIORITY) {
|
|
455
|
-
return envLevel;
|
|
456
|
-
}
|
|
457
|
-
process.stderr.write(
|
|
458
|
-
`[Logger] Invalid log level "${envLevel}", defaulting to "info"
|
|
459
|
-
`
|
|
460
|
-
);
|
|
461
|
-
return "info";
|
|
462
|
-
}
|
|
463
|
-
function initializeLogger() {
|
|
464
|
-
validateConfig();
|
|
465
|
-
return new Logger({
|
|
466
|
-
level: getLogLevel(),
|
|
467
|
-
transports: initializeTransports()
|
|
468
|
-
});
|
|
469
|
-
}
|
|
470
|
-
var logger = initializeLogger();
|
|
471
|
-
|
|
472
|
-
// src/errors/serializable-error.ts
|
|
473
93
|
var RESERVED_RESPONSE_KEYS = /* @__PURE__ */ new Set(["__type", "message", "error"]);
|
|
474
94
|
var AUTHORING_ENVIRONMENTS = /* @__PURE__ */ new Set(["local", "development", "test"]);
|
|
475
95
|
function refuseReservedKey(className, key) {
|