@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 CHANGED
@@ -60,6 +60,7 @@ __export(index_exports, {
60
60
  SecurePluginContext: () => SecurePluginContext,
61
61
  SemanticVersionManager: () => SemanticVersionManager,
62
62
  ServiceLifecycle: () => ServiceLifecycle,
63
+ bucketKeyToCalendarRange: () => bucketKeyToCalendarRange,
63
64
  buildPermissionsFromGrants: () => buildPermissionsFromGrants,
64
65
  bulkWrite: () => bulkWrite,
65
66
  calendarPartsInTz: () => calendarPartsInTz,
@@ -105,7 +106,8 @@ __export(index_exports, {
105
106
  verifyPluginArtifact: () => verifyPluginArtifact,
106
107
  verifyPublisherSignature: () => verifyPublisherSignature,
107
108
  wireAuthoredTranslationSync: () => wireAuthoredTranslationSync,
108
- withTransientRetry: () => withTransientRetry
109
+ withTransientRetry: () => withTransientRetry,
110
+ zonedDateStartToUtcMs: () => zonedDateStartToUtcMs
109
111
  });
110
112
  module.exports = __toCommonJS(index_exports);
111
113
 
@@ -343,8 +345,34 @@ var LEVEL_COLORS = {
343
345
  silent: ""
344
346
  };
345
347
  var RESET = "\x1B[0m";
348
+ function colorEnabled(stream) {
349
+ if (typeof process !== "undefined") {
350
+ const noColor = process.env?.NO_COLOR;
351
+ if (noColor !== void 0 && noColor !== "") return false;
352
+ }
353
+ return Boolean(stream?.isTTY);
354
+ }
355
+ function loadNodeBuiltin(id) {
356
+ if (typeof process === "undefined") return void 0;
357
+ const getBuiltinModule = process.getBuiltinModule;
358
+ if (typeof getBuiltinModule === "function") {
359
+ try {
360
+ return getBuiltinModule.call(process, `node:${id}`);
361
+ } catch {
362
+ return void 0;
363
+ }
364
+ }
365
+ try {
366
+ return require(id);
367
+ } catch {
368
+ return void 0;
369
+ }
370
+ }
346
371
  var ObjectLogger = class _ObjectLogger {
347
372
  constructor(config = {}, bindings = {}) {
373
+ /** Only the logger that opened the stream may close it — children share it. */
374
+ this.ownsFileStream = false;
375
+ this.fileLoggingDisabled = false;
348
376
  this.config = {
349
377
  name: config.name,
350
378
  level: config.level ?? "info",
@@ -360,12 +388,41 @@ var ObjectLogger = class _ObjectLogger {
360
388
  }
361
389
  }
362
390
  openFileStream(path) {
391
+ const fs = loadNodeBuiltin("fs");
392
+ const nodePath2 = loadNodeBuiltin("path");
393
+ if (!fs || !nodePath2) {
394
+ this.disableFileLogging(path, "no filesystem access in this runtime");
395
+ return;
396
+ }
363
397
  try {
364
- const fs = require("fs");
365
- const dir = require("path").dirname(path);
366
- fs.mkdirSync(dir, { recursive: true });
367
- this.fileStream = fs.createWriteStream(path, { flags: "a" });
368
- } catch {
398
+ fs.mkdirSync(nodePath2.dirname(path), { recursive: true });
399
+ const stream = fs.createWriteStream(path, { flags: "a" });
400
+ stream.on("error", (err) => this.disableFileLogging(path, err.message));
401
+ this.fileStream = stream;
402
+ this.ownsFileStream = true;
403
+ } catch (err) {
404
+ this.disableFileLogging(path, err.message);
405
+ }
406
+ }
407
+ /**
408
+ * Report — once — that an explicitly configured `file` destination is not
409
+ * being written, and stop trying.
410
+ *
411
+ * Deliberately not routed through `write()`: this says the logger cannot
412
+ * honour its own config, so `level` must not filter it. The bare `catch {}`
413
+ * this replaces is exactly how #3110 stayed hidden.
414
+ */
415
+ disableFileLogging(path, reason) {
416
+ this.fileStream = void 0;
417
+ this.ownsFileStream = false;
418
+ if (this.fileLoggingDisabled) return;
419
+ this.fileLoggingDisabled = true;
420
+ const label = this.config.name ? `[${this.config.name}] ` : "";
421
+ const notice = `${label}logger: file logging disabled \u2014 cannot write to ${path}: ${reason}`;
422
+ if (typeof process !== "undefined" && process.stderr) {
423
+ process.stderr.write(notice + "\n");
424
+ } else if (typeof console !== "undefined") {
425
+ console.warn(notice);
369
426
  }
370
427
  }
371
428
  isEnabled(level) {
@@ -393,9 +450,13 @@ var ObjectLogger = class _ObjectLogger {
393
450
  });
394
451
  const hasContext = Object.keys(context).length > 0;
395
452
  const ts = (/* @__PURE__ */ new Date()).toISOString();
453
+ const isErrorLevel = level === "error" || level === "fatal";
454
+ const proc = typeof process !== "undefined" ? process : void 0;
455
+ const stream = proc ? isErrorLevel ? proc.stderr : proc.stdout : void 0;
396
456
  let line;
457
+ let plainLine;
397
458
  if (this.config.format === "json") {
398
- line = JSON.stringify({
459
+ line = plainLine = JSON.stringify({
399
460
  time: ts,
400
461
  level,
401
462
  ...this.config.name ? { name: this.config.name } : {},
@@ -405,26 +466,24 @@ var ObjectLogger = class _ObjectLogger {
405
466
  } else if (this.config.format === "text") {
406
467
  const parts = [ts, level.toUpperCase(), message];
407
468
  if (hasContext) parts.push(JSON.stringify(context));
408
- line = parts.join(" | ");
469
+ line = plainLine = parts.join(" | ");
409
470
  } else {
410
- const color = LEVEL_COLORS[level] || "";
411
471
  const label = this.config.name ? `[${this.config.name}] ` : "";
412
- line = `${color}${ts} ${level.toUpperCase()}${RESET} ${label}${message}`;
413
- if (hasContext) line += ` ${JSON.stringify(context)}`;
472
+ const head = `${ts} ${level.toUpperCase()}`;
473
+ let tail = ` ${label}${message}`;
474
+ if (hasContext) tail += ` ${JSON.stringify(context)}`;
475
+ plainLine = head + tail;
476
+ const color = LEVEL_COLORS[level] || "";
477
+ line = color && colorEnabled(stream) ? `${color}${head}${RESET}${tail}` : plainLine;
414
478
  }
415
- const out = line + "\n";
416
- if (typeof process !== "undefined" && process.stderr) {
417
- if (level === "error" || level === "fatal") {
418
- process.stderr.write(out);
419
- } else {
420
- process.stdout?.write(out);
421
- }
479
+ if (stream) {
480
+ stream.write(line + "\n");
422
481
  } else if (typeof console !== "undefined") {
423
482
  const fn = level === "error" || level === "fatal" ? console.error : level === "warn" ? console.warn : level === "debug" ? console.debug : console.log;
424
483
  fn(line);
425
484
  }
426
485
  if (this.fileStream) {
427
- this.fileStream.write(out);
486
+ this.fileStream.write(plainLine + "\n");
428
487
  }
429
488
  }
430
489
  debug(message, meta) {
@@ -454,7 +513,8 @@ var ObjectLogger = class _ObjectLogger {
454
513
  this.info(message, args.length > 0 ? { args } : void 0);
455
514
  }
456
515
  child(context) {
457
- const child = new _ObjectLogger(this.config, { ...this.bindings, ...context });
516
+ const child = new _ObjectLogger({ ...this.config, file: void 0 }, { ...this.bindings, ...context });
517
+ child.config.file = this.config.file;
458
518
  child.fileStream = this.fileStream;
459
519
  return child;
460
520
  }
@@ -462,10 +522,11 @@ var ObjectLogger = class _ObjectLogger {
462
522
  return this.child({ traceId, spanId });
463
523
  }
464
524
  async destroy() {
465
- if (this.fileStream) {
466
- await new Promise((resolve) => this.fileStream.end(resolve));
467
- this.fileStream = void 0;
468
- }
525
+ const stream = this.fileStream;
526
+ this.fileStream = void 0;
527
+ if (!stream || !this.ownsFileStream) return;
528
+ this.ownsFileStream = false;
529
+ await new Promise((resolve) => stream.end(resolve));
469
530
  }
470
531
  };
471
532
  function createLogger(config) {
@@ -1713,6 +1774,19 @@ var ObjectKernel = class {
1713
1774
  getPluginMetrics() {
1714
1775
  return new Map(this.pluginStartTimes);
1715
1776
  }
1777
+ /**
1778
+ * Whether a plugin with the given name has been registered on this kernel.
1779
+ *
1780
+ * Registration happens synchronously in `use()` before any plugin's
1781
+ * `start()` runs, so a plugin may use this during its own start() to make
1782
+ * composition-dependent decisions deterministically — e.g. the dispatcher
1783
+ * bridge cedes `${prefix}/discovery` to `com.objectstack.rest.api` when
1784
+ * both are mounted (ADR-0076 D11: single owner per route, not
1785
+ * first-registration-wins).
1786
+ */
1787
+ hasPlugin(name) {
1788
+ return this.plugins.has(name);
1789
+ }
1716
1790
  /**
1717
1791
  * Get a service (sync helper)
1718
1792
  */
@@ -4591,6 +4665,105 @@ function calendarPartsInTzOrUtc(d, tz) {
4591
4665
  day: d.getUTCDate()
4592
4666
  };
4593
4667
  }
4668
+ function zonedDateStartToUtcMs(ymd, tz) {
4669
+ const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(ymd);
4670
+ const wallAsUtc = m ? Date.UTC(Number(m[1]), Number(m[2]) - 1, Number(m[3])) : NaN;
4671
+ if (!tz || tz === "UTC" || Number.isNaN(wallAsUtc)) return wallAsUtc;
4672
+ try {
4673
+ const offsetAt = (t) => {
4674
+ const p = new Intl.DateTimeFormat("en-US", {
4675
+ timeZone: tz,
4676
+ hourCycle: "h23",
4677
+ year: "numeric",
4678
+ month: "2-digit",
4679
+ day: "2-digit",
4680
+ hour: "2-digit",
4681
+ minute: "2-digit",
4682
+ second: "2-digit"
4683
+ }).formatToParts(new Date(t));
4684
+ const g = (k) => Number(p.find((x) => x.type === k)?.value);
4685
+ return Date.UTC(g("year"), g("month") - 1, g("day"), g("hour"), g("minute"), g("second")) - t;
4686
+ };
4687
+ const off1 = offsetAt(wallAsUtc - offsetAt(wallAsUtc));
4688
+ return wallAsUtc - off1;
4689
+ } catch {
4690
+ return wallAsUtc;
4691
+ }
4692
+ }
4693
+ function isoWeekLabelUtc(d) {
4694
+ const target = new Date(d.getTime());
4695
+ const dayNum = (target.getUTCDay() + 6) % 7;
4696
+ target.setUTCDate(target.getUTCDate() - dayNum + 3);
4697
+ const firstThursday = new Date(Date.UTC(target.getUTCFullYear(), 0, 4));
4698
+ const weekNo = 1 + Math.round(
4699
+ ((target.getTime() - firstThursday.getTime()) / 864e5 - 3 + (firstThursday.getUTCDay() + 6) % 7) / 7
4700
+ );
4701
+ return `${target.getUTCFullYear()}-W${String(weekNo).padStart(2, "0")}`;
4702
+ }
4703
+ function bucketKeyToCalendarRange(key, granularity) {
4704
+ if (typeof key !== "string" || key.length === 0) return null;
4705
+ const fmt = (dt) => `${String(dt.getUTCFullYear()).padStart(4, "0")}-${String(dt.getUTCMonth() + 1).padStart(
4706
+ 2,
4707
+ "0"
4708
+ )}-${String(dt.getUTCDate()).padStart(2, "0")}`;
4709
+ switch (granularity) {
4710
+ case "year": {
4711
+ const m = /^(\d{4})$/.exec(key);
4712
+ if (!m) return null;
4713
+ const y = Number(m[1]);
4714
+ return { start: fmt(new Date(Date.UTC(y, 0, 1))), end: fmt(new Date(Date.UTC(y + 1, 0, 1))) };
4715
+ }
4716
+ case "quarter": {
4717
+ const m = /^(\d{4})-Q([1-4])$/.exec(key);
4718
+ if (!m) return null;
4719
+ const y = Number(m[1]);
4720
+ const startMonth = (Number(m[2]) - 1) * 3;
4721
+ return {
4722
+ start: fmt(new Date(Date.UTC(y, startMonth, 1))),
4723
+ end: fmt(new Date(Date.UTC(y, startMonth + 3, 1)))
4724
+ // Date.UTC rolls Q4 into next year
4725
+ };
4726
+ }
4727
+ case "month": {
4728
+ const m = /^(\d{4})-(\d{2})$/.exec(key);
4729
+ if (!m) return null;
4730
+ const mo = Number(m[2]);
4731
+ if (mo < 1 || mo > 12) return null;
4732
+ const y = Number(m[1]);
4733
+ return {
4734
+ start: fmt(new Date(Date.UTC(y, mo - 1, 1))),
4735
+ end: fmt(new Date(Date.UTC(y, mo, 1)))
4736
+ };
4737
+ }
4738
+ case "day": {
4739
+ const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(key);
4740
+ if (!m) return null;
4741
+ const y = Number(m[1]);
4742
+ const mo = Number(m[2]);
4743
+ const d = Number(m[3]);
4744
+ const start = new Date(Date.UTC(y, mo - 1, d));
4745
+ if (fmt(start) !== key) return null;
4746
+ return { start: key, end: fmt(new Date(Date.UTC(y, mo - 1, d + 1))) };
4747
+ }
4748
+ case "week": {
4749
+ const m = /^(\d{4})-W(\d{2})$/.exec(key);
4750
+ if (!m) return null;
4751
+ const isoYear = Number(m[1]);
4752
+ const week = Number(m[2]);
4753
+ if (week < 1 || week > 53) return null;
4754
+ const jan4 = new Date(Date.UTC(isoYear, 0, 4));
4755
+ const jan4Dow = (jan4.getUTCDay() + 6) % 7;
4756
+ const start = new Date(jan4.getTime());
4757
+ start.setUTCDate(jan4.getUTCDate() - jan4Dow + (week - 1) * 7);
4758
+ if (isoWeekLabelUtc(start) !== key) return null;
4759
+ const end = new Date(start.getTime());
4760
+ end.setUTCDate(start.getUTCDate() + 7);
4761
+ return { start: fmt(start), end: fmt(end) };
4762
+ }
4763
+ default:
4764
+ return null;
4765
+ }
4766
+ }
4594
4767
 
4595
4768
  // src/utils/bulk-write.ts
4596
4769
  var DEFAULT_BATCH_SIZE = 200;
@@ -4608,11 +4781,23 @@ var TRANSIENT_PATTERNS = [
4608
4781
  /too many connections/i
4609
4782
  ];
4610
4783
  var TRANSIENT_CODES = /^(ECONNRESET|ECONNREFUSED|ECONNABORTED|EPIPE|EAI_AGAIN|ETIMEDOUT|EHOSTUNREACH|ENETUNREACH|ENOTFOUND)$/i;
4784
+ var NON_TRANSIENT_PATTERNS = [
4785
+ /validation/i,
4786
+ /constraint/i,
4787
+ /\brequired\b/i,
4788
+ /\bunique\b/i,
4789
+ /duplicate/i,
4790
+ /not[\s_-]*null/i,
4791
+ /invalid/i,
4792
+ /not allowed/i,
4793
+ /out of range/i
4794
+ ];
4611
4795
  function defaultIsTransientError(err) {
4612
- const code = err?.code;
4613
- if (typeof code === "string" && TRANSIENT_CODES.test(code)) return true;
4614
4796
  const message = err?.message;
4615
4797
  const text = typeof message === "string" ? message : String(err ?? "");
4798
+ if (NON_TRANSIENT_PATTERNS.some((re) => re.test(text))) return false;
4799
+ const code = err?.code;
4800
+ if (typeof code === "string" && TRANSIENT_CODES.test(code)) return true;
4616
4801
  return TRANSIENT_PATTERNS.some((re) => re.test(text));
4617
4802
  }
4618
4803
  var defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
@@ -4620,7 +4805,7 @@ async function withRetry(fn, opts) {
4620
4805
  let lastError;
4621
4806
  for (let attempt = 1; attempt <= opts.maxRetries; attempt++) {
4622
4807
  try {
4623
- return await fn();
4808
+ return await fn(attempt);
4624
4809
  } catch (err) {
4625
4810
  lastError = err;
4626
4811
  if (attempt >= opts.maxRetries || !opts.isTransientError(err)) throw err;
@@ -4650,7 +4835,31 @@ async function bulkWrite(rows, opts) {
4650
4835
  for (let start = 0; start < rows.length; start += batchSize) {
4651
4836
  const batch = rows.slice(start, start + batchSize);
4652
4837
  try {
4653
- const records = await withRetry(() => opts.writeBatch(batch), retryOpts);
4838
+ if (opts.writeBatchPartial) {
4839
+ const outcomes = await withRetry((attempt) => opts.writeBatchPartial(batch, { attempt }), retryOpts);
4840
+ if (!Array.isArray(outcomes) || outcomes.length !== batch.length) {
4841
+ throw Object.assign(
4842
+ new Error(
4843
+ `bulkWrite: writeBatchPartial returned ${Array.isArray(outcomes) ? `${outcomes.length} outcome(s)` : String(typeof outcomes)} for a ${batch.length}-row batch \u2014 treating batch as failed`
4844
+ ),
4845
+ { code: "ERR_BULK_RESULT_MISMATCH" }
4846
+ );
4847
+ }
4848
+ for (let i = 0; i < batch.length; i++) {
4849
+ const o = outcomes[i];
4850
+ results[start + i] = o.ok ? { index: start + i, ok: true, record: o.record } : { index: start + i, ok: false, error: o.error };
4851
+ }
4852
+ continue;
4853
+ }
4854
+ const records = await withRetry((attempt) => opts.writeBatch(batch, { attempt }), retryOpts);
4855
+ if (!Array.isArray(records) || records.length !== batch.length) {
4856
+ throw Object.assign(
4857
+ new Error(
4858
+ `bulkWrite: writeBatch returned ${Array.isArray(records) ? `${records.length} record(s)` : String(typeof records)} for a ${batch.length}-row batch \u2014 treating batch as failed`
4859
+ ),
4860
+ { code: "ERR_BULK_RESULT_MISMATCH" }
4861
+ );
4862
+ }
4654
4863
  for (let i = 0; i < batch.length; i++) {
4655
4864
  results[start + i] = { index: start + i, ok: true, record: records[i] };
4656
4865
  }
@@ -4662,7 +4871,7 @@ async function bulkWrite(rows, opts) {
4662
4871
  for (let i = 0; i < batch.length; i++) {
4663
4872
  const idx = start + i;
4664
4873
  try {
4665
- const record = await withRetry(() => opts.writeOne(batch[i]), retryOpts);
4874
+ const record = await withRetry((attempt) => opts.writeOne(batch[i], { attempt }), retryOpts);
4666
4875
  results[idx] = { index: idx, ok: true, record };
4667
4876
  } catch (err) {
4668
4877
  results[idx] = { index: idx, ok: false, error: err };
@@ -5618,6 +5827,7 @@ var NamespaceResolver = class {
5618
5827
  SecurePluginContext,
5619
5828
  SemanticVersionManager,
5620
5829
  ServiceLifecycle,
5830
+ bucketKeyToCalendarRange,
5621
5831
  buildPermissionsFromGrants,
5622
5832
  bulkWrite,
5623
5833
  calendarPartsInTz,
@@ -5663,6 +5873,7 @@ var NamespaceResolver = class {
5663
5873
  verifyPluginArtifact,
5664
5874
  verifyPublisherSignature,
5665
5875
  wireAuthoredTranslationSync,
5666
- withTransientRetry
5876
+ withTransientRetry,
5877
+ zonedDateStartToUtcMs
5667
5878
  });
5668
5879
  //# sourceMappingURL=index.cjs.map