@objectstack/core 15.1.0 → 16.0.0-rc.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/index.cjs +241 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +99 -6
- package/dist/index.d.ts +99 -6
- package/dist/index.js +238 -29
- package/dist/index.js.map +1 -1
- package/dist/logger.cjs +82 -23
- package/dist/logger.cjs.map +1 -1
- package/dist/logger.d.cts +12 -0
- package/dist/logger.d.ts +12 -0
- package/dist/logger.js +82 -23
- package/dist/logger.js.map +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -244,8 +244,34 @@ var LEVEL_COLORS = {
|
|
|
244
244
|
silent: ""
|
|
245
245
|
};
|
|
246
246
|
var RESET = "\x1B[0m";
|
|
247
|
+
function colorEnabled(stream) {
|
|
248
|
+
if (typeof process !== "undefined") {
|
|
249
|
+
const noColor = process.env?.NO_COLOR;
|
|
250
|
+
if (noColor !== void 0 && noColor !== "") return false;
|
|
251
|
+
}
|
|
252
|
+
return Boolean(stream?.isTTY);
|
|
253
|
+
}
|
|
254
|
+
function loadNodeBuiltin(id) {
|
|
255
|
+
if (typeof process === "undefined") return void 0;
|
|
256
|
+
const getBuiltinModule = process.getBuiltinModule;
|
|
257
|
+
if (typeof getBuiltinModule === "function") {
|
|
258
|
+
try {
|
|
259
|
+
return getBuiltinModule.call(process, `node:${id}`);
|
|
260
|
+
} catch {
|
|
261
|
+
return void 0;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
try {
|
|
265
|
+
return __require(id);
|
|
266
|
+
} catch {
|
|
267
|
+
return void 0;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
247
270
|
var ObjectLogger = class _ObjectLogger {
|
|
248
271
|
constructor(config = {}, bindings = {}) {
|
|
272
|
+
/** Only the logger that opened the stream may close it — children share it. */
|
|
273
|
+
this.ownsFileStream = false;
|
|
274
|
+
this.fileLoggingDisabled = false;
|
|
249
275
|
this.config = {
|
|
250
276
|
name: config.name,
|
|
251
277
|
level: config.level ?? "info",
|
|
@@ -261,12 +287,41 @@ var ObjectLogger = class _ObjectLogger {
|
|
|
261
287
|
}
|
|
262
288
|
}
|
|
263
289
|
openFileStream(path) {
|
|
290
|
+
const fs = loadNodeBuiltin("fs");
|
|
291
|
+
const nodePath2 = loadNodeBuiltin("path");
|
|
292
|
+
if (!fs || !nodePath2) {
|
|
293
|
+
this.disableFileLogging(path, "no filesystem access in this runtime");
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
264
296
|
try {
|
|
265
|
-
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
this.fileStream =
|
|
269
|
-
|
|
297
|
+
fs.mkdirSync(nodePath2.dirname(path), { recursive: true });
|
|
298
|
+
const stream = fs.createWriteStream(path, { flags: "a" });
|
|
299
|
+
stream.on("error", (err) => this.disableFileLogging(path, err.message));
|
|
300
|
+
this.fileStream = stream;
|
|
301
|
+
this.ownsFileStream = true;
|
|
302
|
+
} catch (err) {
|
|
303
|
+
this.disableFileLogging(path, err.message);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Report — once — that an explicitly configured `file` destination is not
|
|
308
|
+
* being written, and stop trying.
|
|
309
|
+
*
|
|
310
|
+
* Deliberately not routed through `write()`: this says the logger cannot
|
|
311
|
+
* honour its own config, so `level` must not filter it. The bare `catch {}`
|
|
312
|
+
* this replaces is exactly how #3110 stayed hidden.
|
|
313
|
+
*/
|
|
314
|
+
disableFileLogging(path, reason) {
|
|
315
|
+
this.fileStream = void 0;
|
|
316
|
+
this.ownsFileStream = false;
|
|
317
|
+
if (this.fileLoggingDisabled) return;
|
|
318
|
+
this.fileLoggingDisabled = true;
|
|
319
|
+
const label = this.config.name ? `[${this.config.name}] ` : "";
|
|
320
|
+
const notice = `${label}logger: file logging disabled \u2014 cannot write to ${path}: ${reason}`;
|
|
321
|
+
if (typeof process !== "undefined" && process.stderr) {
|
|
322
|
+
process.stderr.write(notice + "\n");
|
|
323
|
+
} else if (typeof console !== "undefined") {
|
|
324
|
+
console.warn(notice);
|
|
270
325
|
}
|
|
271
326
|
}
|
|
272
327
|
isEnabled(level) {
|
|
@@ -294,9 +349,13 @@ var ObjectLogger = class _ObjectLogger {
|
|
|
294
349
|
});
|
|
295
350
|
const hasContext = Object.keys(context).length > 0;
|
|
296
351
|
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
352
|
+
const isErrorLevel = level === "error" || level === "fatal";
|
|
353
|
+
const proc = typeof process !== "undefined" ? process : void 0;
|
|
354
|
+
const stream = proc ? isErrorLevel ? proc.stderr : proc.stdout : void 0;
|
|
297
355
|
let line;
|
|
356
|
+
let plainLine;
|
|
298
357
|
if (this.config.format === "json") {
|
|
299
|
-
line = JSON.stringify({
|
|
358
|
+
line = plainLine = JSON.stringify({
|
|
300
359
|
time: ts,
|
|
301
360
|
level,
|
|
302
361
|
...this.config.name ? { name: this.config.name } : {},
|
|
@@ -306,26 +365,24 @@ var ObjectLogger = class _ObjectLogger {
|
|
|
306
365
|
} else if (this.config.format === "text") {
|
|
307
366
|
const parts = [ts, level.toUpperCase(), message];
|
|
308
367
|
if (hasContext) parts.push(JSON.stringify(context));
|
|
309
|
-
line = parts.join(" | ");
|
|
368
|
+
line = plainLine = parts.join(" | ");
|
|
310
369
|
} else {
|
|
311
|
-
const color = LEVEL_COLORS[level] || "";
|
|
312
370
|
const label = this.config.name ? `[${this.config.name}] ` : "";
|
|
313
|
-
|
|
314
|
-
|
|
371
|
+
const head = `${ts} ${level.toUpperCase()}`;
|
|
372
|
+
let tail = ` ${label}${message}`;
|
|
373
|
+
if (hasContext) tail += ` ${JSON.stringify(context)}`;
|
|
374
|
+
plainLine = head + tail;
|
|
375
|
+
const color = LEVEL_COLORS[level] || "";
|
|
376
|
+
line = color && colorEnabled(stream) ? `${color}${head}${RESET}${tail}` : plainLine;
|
|
315
377
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
if (level === "error" || level === "fatal") {
|
|
319
|
-
process.stderr.write(out);
|
|
320
|
-
} else {
|
|
321
|
-
process.stdout?.write(out);
|
|
322
|
-
}
|
|
378
|
+
if (stream) {
|
|
379
|
+
stream.write(line + "\n");
|
|
323
380
|
} else if (typeof console !== "undefined") {
|
|
324
381
|
const fn = level === "error" || level === "fatal" ? console.error : level === "warn" ? console.warn : level === "debug" ? console.debug : console.log;
|
|
325
382
|
fn(line);
|
|
326
383
|
}
|
|
327
384
|
if (this.fileStream) {
|
|
328
|
-
this.fileStream.write(
|
|
385
|
+
this.fileStream.write(plainLine + "\n");
|
|
329
386
|
}
|
|
330
387
|
}
|
|
331
388
|
debug(message, meta) {
|
|
@@ -355,7 +412,8 @@ var ObjectLogger = class _ObjectLogger {
|
|
|
355
412
|
this.info(message, args.length > 0 ? { args } : void 0);
|
|
356
413
|
}
|
|
357
414
|
child(context) {
|
|
358
|
-
const child = new _ObjectLogger(this.config, { ...this.bindings, ...context });
|
|
415
|
+
const child = new _ObjectLogger({ ...this.config, file: void 0 }, { ...this.bindings, ...context });
|
|
416
|
+
child.config.file = this.config.file;
|
|
359
417
|
child.fileStream = this.fileStream;
|
|
360
418
|
return child;
|
|
361
419
|
}
|
|
@@ -363,10 +421,11 @@ var ObjectLogger = class _ObjectLogger {
|
|
|
363
421
|
return this.child({ traceId, spanId });
|
|
364
422
|
}
|
|
365
423
|
async destroy() {
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
424
|
+
const stream = this.fileStream;
|
|
425
|
+
this.fileStream = void 0;
|
|
426
|
+
if (!stream || !this.ownsFileStream) return;
|
|
427
|
+
this.ownsFileStream = false;
|
|
428
|
+
await new Promise((resolve) => stream.end(resolve));
|
|
370
429
|
}
|
|
371
430
|
};
|
|
372
431
|
function createLogger(config) {
|
|
@@ -1620,6 +1679,19 @@ var ObjectKernel = class {
|
|
|
1620
1679
|
getPluginMetrics() {
|
|
1621
1680
|
return new Map(this.pluginStartTimes);
|
|
1622
1681
|
}
|
|
1682
|
+
/**
|
|
1683
|
+
* Whether a plugin with the given name has been registered on this kernel.
|
|
1684
|
+
*
|
|
1685
|
+
* Registration happens synchronously in `use()` before any plugin's
|
|
1686
|
+
* `start()` runs, so a plugin may use this during its own start() to make
|
|
1687
|
+
* composition-dependent decisions deterministically — e.g. the dispatcher
|
|
1688
|
+
* bridge cedes `${prefix}/discovery` to `com.objectstack.rest.api` when
|
|
1689
|
+
* both are mounted (ADR-0076 D11: single owner per route, not
|
|
1690
|
+
* first-registration-wins).
|
|
1691
|
+
*/
|
|
1692
|
+
hasPlugin(name) {
|
|
1693
|
+
return this.plugins.has(name);
|
|
1694
|
+
}
|
|
1623
1695
|
/**
|
|
1624
1696
|
* Get a service (sync helper)
|
|
1625
1697
|
*/
|
|
@@ -4503,6 +4575,105 @@ function calendarPartsInTzOrUtc(d, tz) {
|
|
|
4503
4575
|
day: d.getUTCDate()
|
|
4504
4576
|
};
|
|
4505
4577
|
}
|
|
4578
|
+
function zonedDateStartToUtcMs(ymd, tz) {
|
|
4579
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(ymd);
|
|
4580
|
+
const wallAsUtc = m ? Date.UTC(Number(m[1]), Number(m[2]) - 1, Number(m[3])) : NaN;
|
|
4581
|
+
if (!tz || tz === "UTC" || Number.isNaN(wallAsUtc)) return wallAsUtc;
|
|
4582
|
+
try {
|
|
4583
|
+
const offsetAt = (t) => {
|
|
4584
|
+
const p = new Intl.DateTimeFormat("en-US", {
|
|
4585
|
+
timeZone: tz,
|
|
4586
|
+
hourCycle: "h23",
|
|
4587
|
+
year: "numeric",
|
|
4588
|
+
month: "2-digit",
|
|
4589
|
+
day: "2-digit",
|
|
4590
|
+
hour: "2-digit",
|
|
4591
|
+
minute: "2-digit",
|
|
4592
|
+
second: "2-digit"
|
|
4593
|
+
}).formatToParts(new Date(t));
|
|
4594
|
+
const g = (k) => Number(p.find((x) => x.type === k)?.value);
|
|
4595
|
+
return Date.UTC(g("year"), g("month") - 1, g("day"), g("hour"), g("minute"), g("second")) - t;
|
|
4596
|
+
};
|
|
4597
|
+
const off1 = offsetAt(wallAsUtc - offsetAt(wallAsUtc));
|
|
4598
|
+
return wallAsUtc - off1;
|
|
4599
|
+
} catch {
|
|
4600
|
+
return wallAsUtc;
|
|
4601
|
+
}
|
|
4602
|
+
}
|
|
4603
|
+
function isoWeekLabelUtc(d) {
|
|
4604
|
+
const target = new Date(d.getTime());
|
|
4605
|
+
const dayNum = (target.getUTCDay() + 6) % 7;
|
|
4606
|
+
target.setUTCDate(target.getUTCDate() - dayNum + 3);
|
|
4607
|
+
const firstThursday = new Date(Date.UTC(target.getUTCFullYear(), 0, 4));
|
|
4608
|
+
const weekNo = 1 + Math.round(
|
|
4609
|
+
((target.getTime() - firstThursday.getTime()) / 864e5 - 3 + (firstThursday.getUTCDay() + 6) % 7) / 7
|
|
4610
|
+
);
|
|
4611
|
+
return `${target.getUTCFullYear()}-W${String(weekNo).padStart(2, "0")}`;
|
|
4612
|
+
}
|
|
4613
|
+
function bucketKeyToCalendarRange(key, granularity) {
|
|
4614
|
+
if (typeof key !== "string" || key.length === 0) return null;
|
|
4615
|
+
const fmt = (dt) => `${String(dt.getUTCFullYear()).padStart(4, "0")}-${String(dt.getUTCMonth() + 1).padStart(
|
|
4616
|
+
2,
|
|
4617
|
+
"0"
|
|
4618
|
+
)}-${String(dt.getUTCDate()).padStart(2, "0")}`;
|
|
4619
|
+
switch (granularity) {
|
|
4620
|
+
case "year": {
|
|
4621
|
+
const m = /^(\d{4})$/.exec(key);
|
|
4622
|
+
if (!m) return null;
|
|
4623
|
+
const y = Number(m[1]);
|
|
4624
|
+
return { start: fmt(new Date(Date.UTC(y, 0, 1))), end: fmt(new Date(Date.UTC(y + 1, 0, 1))) };
|
|
4625
|
+
}
|
|
4626
|
+
case "quarter": {
|
|
4627
|
+
const m = /^(\d{4})-Q([1-4])$/.exec(key);
|
|
4628
|
+
if (!m) return null;
|
|
4629
|
+
const y = Number(m[1]);
|
|
4630
|
+
const startMonth = (Number(m[2]) - 1) * 3;
|
|
4631
|
+
return {
|
|
4632
|
+
start: fmt(new Date(Date.UTC(y, startMonth, 1))),
|
|
4633
|
+
end: fmt(new Date(Date.UTC(y, startMonth + 3, 1)))
|
|
4634
|
+
// Date.UTC rolls Q4 into next year
|
|
4635
|
+
};
|
|
4636
|
+
}
|
|
4637
|
+
case "month": {
|
|
4638
|
+
const m = /^(\d{4})-(\d{2})$/.exec(key);
|
|
4639
|
+
if (!m) return null;
|
|
4640
|
+
const mo = Number(m[2]);
|
|
4641
|
+
if (mo < 1 || mo > 12) return null;
|
|
4642
|
+
const y = Number(m[1]);
|
|
4643
|
+
return {
|
|
4644
|
+
start: fmt(new Date(Date.UTC(y, mo - 1, 1))),
|
|
4645
|
+
end: fmt(new Date(Date.UTC(y, mo, 1)))
|
|
4646
|
+
};
|
|
4647
|
+
}
|
|
4648
|
+
case "day": {
|
|
4649
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(key);
|
|
4650
|
+
if (!m) return null;
|
|
4651
|
+
const y = Number(m[1]);
|
|
4652
|
+
const mo = Number(m[2]);
|
|
4653
|
+
const d = Number(m[3]);
|
|
4654
|
+
const start = new Date(Date.UTC(y, mo - 1, d));
|
|
4655
|
+
if (fmt(start) !== key) return null;
|
|
4656
|
+
return { start: key, end: fmt(new Date(Date.UTC(y, mo - 1, d + 1))) };
|
|
4657
|
+
}
|
|
4658
|
+
case "week": {
|
|
4659
|
+
const m = /^(\d{4})-W(\d{2})$/.exec(key);
|
|
4660
|
+
if (!m) return null;
|
|
4661
|
+
const isoYear = Number(m[1]);
|
|
4662
|
+
const week = Number(m[2]);
|
|
4663
|
+
if (week < 1 || week > 53) return null;
|
|
4664
|
+
const jan4 = new Date(Date.UTC(isoYear, 0, 4));
|
|
4665
|
+
const jan4Dow = (jan4.getUTCDay() + 6) % 7;
|
|
4666
|
+
const start = new Date(jan4.getTime());
|
|
4667
|
+
start.setUTCDate(jan4.getUTCDate() - jan4Dow + (week - 1) * 7);
|
|
4668
|
+
if (isoWeekLabelUtc(start) !== key) return null;
|
|
4669
|
+
const end = new Date(start.getTime());
|
|
4670
|
+
end.setUTCDate(start.getUTCDate() + 7);
|
|
4671
|
+
return { start: fmt(start), end: fmt(end) };
|
|
4672
|
+
}
|
|
4673
|
+
default:
|
|
4674
|
+
return null;
|
|
4675
|
+
}
|
|
4676
|
+
}
|
|
4506
4677
|
|
|
4507
4678
|
// src/utils/bulk-write.ts
|
|
4508
4679
|
var DEFAULT_BATCH_SIZE = 200;
|
|
@@ -4520,11 +4691,23 @@ var TRANSIENT_PATTERNS = [
|
|
|
4520
4691
|
/too many connections/i
|
|
4521
4692
|
];
|
|
4522
4693
|
var TRANSIENT_CODES = /^(ECONNRESET|ECONNREFUSED|ECONNABORTED|EPIPE|EAI_AGAIN|ETIMEDOUT|EHOSTUNREACH|ENETUNREACH|ENOTFOUND)$/i;
|
|
4694
|
+
var NON_TRANSIENT_PATTERNS = [
|
|
4695
|
+
/validation/i,
|
|
4696
|
+
/constraint/i,
|
|
4697
|
+
/\brequired\b/i,
|
|
4698
|
+
/\bunique\b/i,
|
|
4699
|
+
/duplicate/i,
|
|
4700
|
+
/not[\s_-]*null/i,
|
|
4701
|
+
/invalid/i,
|
|
4702
|
+
/not allowed/i,
|
|
4703
|
+
/out of range/i
|
|
4704
|
+
];
|
|
4523
4705
|
function defaultIsTransientError(err) {
|
|
4524
|
-
const code = err?.code;
|
|
4525
|
-
if (typeof code === "string" && TRANSIENT_CODES.test(code)) return true;
|
|
4526
4706
|
const message = err?.message;
|
|
4527
4707
|
const text = typeof message === "string" ? message : String(err ?? "");
|
|
4708
|
+
if (NON_TRANSIENT_PATTERNS.some((re) => re.test(text))) return false;
|
|
4709
|
+
const code = err?.code;
|
|
4710
|
+
if (typeof code === "string" && TRANSIENT_CODES.test(code)) return true;
|
|
4528
4711
|
return TRANSIENT_PATTERNS.some((re) => re.test(text));
|
|
4529
4712
|
}
|
|
4530
4713
|
var defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -4532,7 +4715,7 @@ async function withRetry(fn, opts) {
|
|
|
4532
4715
|
let lastError;
|
|
4533
4716
|
for (let attempt = 1; attempt <= opts.maxRetries; attempt++) {
|
|
4534
4717
|
try {
|
|
4535
|
-
return await fn();
|
|
4718
|
+
return await fn(attempt);
|
|
4536
4719
|
} catch (err) {
|
|
4537
4720
|
lastError = err;
|
|
4538
4721
|
if (attempt >= opts.maxRetries || !opts.isTransientError(err)) throw err;
|
|
@@ -4562,7 +4745,31 @@ async function bulkWrite(rows, opts) {
|
|
|
4562
4745
|
for (let start = 0; start < rows.length; start += batchSize) {
|
|
4563
4746
|
const batch = rows.slice(start, start + batchSize);
|
|
4564
4747
|
try {
|
|
4565
|
-
|
|
4748
|
+
if (opts.writeBatchPartial) {
|
|
4749
|
+
const outcomes = await withRetry((attempt) => opts.writeBatchPartial(batch, { attempt }), retryOpts);
|
|
4750
|
+
if (!Array.isArray(outcomes) || outcomes.length !== batch.length) {
|
|
4751
|
+
throw Object.assign(
|
|
4752
|
+
new Error(
|
|
4753
|
+
`bulkWrite: writeBatchPartial returned ${Array.isArray(outcomes) ? `${outcomes.length} outcome(s)` : String(typeof outcomes)} for a ${batch.length}-row batch \u2014 treating batch as failed`
|
|
4754
|
+
),
|
|
4755
|
+
{ code: "ERR_BULK_RESULT_MISMATCH" }
|
|
4756
|
+
);
|
|
4757
|
+
}
|
|
4758
|
+
for (let i = 0; i < batch.length; i++) {
|
|
4759
|
+
const o = outcomes[i];
|
|
4760
|
+
results[start + i] = o.ok ? { index: start + i, ok: true, record: o.record } : { index: start + i, ok: false, error: o.error };
|
|
4761
|
+
}
|
|
4762
|
+
continue;
|
|
4763
|
+
}
|
|
4764
|
+
const records = await withRetry((attempt) => opts.writeBatch(batch, { attempt }), retryOpts);
|
|
4765
|
+
if (!Array.isArray(records) || records.length !== batch.length) {
|
|
4766
|
+
throw Object.assign(
|
|
4767
|
+
new Error(
|
|
4768
|
+
`bulkWrite: writeBatch returned ${Array.isArray(records) ? `${records.length} record(s)` : String(typeof records)} for a ${batch.length}-row batch \u2014 treating batch as failed`
|
|
4769
|
+
),
|
|
4770
|
+
{ code: "ERR_BULK_RESULT_MISMATCH" }
|
|
4771
|
+
);
|
|
4772
|
+
}
|
|
4566
4773
|
for (let i = 0; i < batch.length; i++) {
|
|
4567
4774
|
results[start + i] = { index: start + i, ok: true, record: records[i] };
|
|
4568
4775
|
}
|
|
@@ -4574,7 +4781,7 @@ async function bulkWrite(rows, opts) {
|
|
|
4574
4781
|
for (let i = 0; i < batch.length; i++) {
|
|
4575
4782
|
const idx = start + i;
|
|
4576
4783
|
try {
|
|
4577
|
-
const record = await withRetry(() => opts.writeOne(batch[i]), retryOpts);
|
|
4784
|
+
const record = await withRetry((attempt) => opts.writeOne(batch[i], { attempt }), retryOpts);
|
|
4578
4785
|
results[idx] = { index: idx, ok: true, record };
|
|
4579
4786
|
} catch (err) {
|
|
4580
4787
|
results[idx] = { index: idx, ok: false, error: err };
|
|
@@ -5529,6 +5736,7 @@ export {
|
|
|
5529
5736
|
SecurePluginContext,
|
|
5530
5737
|
SemanticVersionManager,
|
|
5531
5738
|
ServiceLifecycle,
|
|
5739
|
+
bucketKeyToCalendarRange,
|
|
5532
5740
|
buildPermissionsFromGrants,
|
|
5533
5741
|
bulkWrite,
|
|
5534
5742
|
calendarPartsInTz,
|
|
@@ -5574,6 +5782,7 @@ export {
|
|
|
5574
5782
|
verifyPluginArtifact,
|
|
5575
5783
|
verifyPublisherSignature,
|
|
5576
5784
|
wireAuthoredTranslationSync,
|
|
5577
|
-
withTransientRetry
|
|
5785
|
+
withTransientRetry,
|
|
5786
|
+
zonedDateStartToUtcMs
|
|
5578
5787
|
};
|
|
5579
5788
|
//# sourceMappingURL=index.js.map
|