@gobing-ai/ts-infra 0.5.21 → 0.5.23

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 CHANGED
@@ -621,7 +621,7 @@ bootstrap:
621
621
  logging:
622
622
  level: info
623
623
  console: true
624
- filePath: ./logs/app.jsonl
624
+ filePath: ./logs/app.jsonl # a directory (e.g. ./logs/) writes daily spur{YYYYMMDD}.log files
625
625
  telemetry:
626
626
  enabled: true
627
627
  serviceName: billing-api
@@ -775,7 +775,9 @@ never opens files: it calls `attachFileObserver(lifecycleBus, filePath, writer)`
775
775
  only when `services.fileObserverWriter` is provided; its portable default path is
776
776
  `.spur/logs/system-events.jsonl`. The Node subpath `runNodeApplication` constructs a
777
777
  Node writer and uses `createNodeFileSystem()` to resolve the path to
778
- `<projectRoot>/.spur/logs/system-events.jsonl`. Set `config.events.fileObserver: false`
778
+ `<projectRoot>/.spur/logs/system-events{YYYYMM}.jsonl` (one file per month). The Node
779
+ writer expands `{YYYYMM}` / `{YYYYMMDD}` (local date) in any `filePath` on each write, so a
780
+ long-running process rolls over without a restart. Set `config.events.fileObserver: false`
779
781
  to disable; supply a custom `filePath` (or a test-stub writer via
780
782
  `services.fileObserverWriter`) to override.
781
783
 
@@ -1 +1 @@
1
- {"version":3,"file":"application-node.d.ts","sourceRoot":"","sources":["../src/application-node.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAWH,OAAO,KAAK,EACR,2BAA2B,EAC3B,uBAAuB,EAEvB,kBAAkB,EAClB,qBAAqB,EAGrB,QAAQ,EACR,aAAa,EACb,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EACnB,MAAM,qBAAqB,CAAC;AAS7B,kGAAkG;AAClG,qBAAa,qBAAsB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI9B;AAwOD;;;;GAIG;AACH,MAAM,WAAW,6BAA8B,SAAQ,gBAAgB;IACnE,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAED,uEAAuE;AACvE,MAAM,WAAW,sBAAsB,CAAC,UAAU,GAAG,OAAO,EAAE,OAAO,SAAS,QAAQ,GAAG,WAAW;IAChG,gEAAgE;IAChE,QAAQ,CAAC,YAAY,CAAC,EAAE,uBAAuB,CAAC,UAAU,CAAC,CAAC;IAC5D,8DAA8D;IAC9D,QAAQ,CAAC,MAAM,CAAC,EAAE;QACd,OAAO,CAAC,EAAE,cAAc,CAAC;QACzB,MAAM,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;QAChC,SAAS,CAAC,EAAE,6BAA6B,CAAC;QAC1C,SAAS,CAAC,EAAE,gBAAgB,CAAC;KAChC,CAAC;IACF,oCAAoC;IACpC,QAAQ,CAAC,QAAQ,CAAC,EAAE,2BAA2B,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,UAAU,CAAC,CAAC;IACjF,wCAAwC;IACxC,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,EAAE,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACvF,wDAAwD;IACxD,QAAQ,CAAC,IAAI,CAAC,EAAE,CACZ,GAAG,EAAE,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,EAC5C,MAAM,EAAE,qBAAqB,KAC5B,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC7B;AAID;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAsB,kBAAkB,CAAC,UAAU,GAAG,OAAO,EAAE,OAAO,SAAS,QAAQ,GAAG,WAAW,EACjG,OAAO,EAAE,sBAAsB,CAAC,UAAU,EAAE,OAAO,CAAC,GACrD,OAAO,CAAC,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAyJlD"}
1
+ {"version":3,"file":"application-node.d.ts","sourceRoot":"","sources":["../src/application-node.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAWH,OAAO,KAAK,EACR,2BAA2B,EAC3B,uBAAuB,EAEvB,kBAAkB,EAClB,qBAAqB,EAGrB,QAAQ,EACR,aAAa,EACb,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EACnB,MAAM,qBAAqB,CAAC;AAS7B,kGAAkG;AAClG,qBAAa,qBAAsB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI9B;AA8PD;;;;GAIG;AACH,MAAM,WAAW,6BAA8B,SAAQ,gBAAgB;IACnE,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAED,uEAAuE;AACvE,MAAM,WAAW,sBAAsB,CAAC,UAAU,GAAG,OAAO,EAAE,OAAO,SAAS,QAAQ,GAAG,WAAW;IAChG,gEAAgE;IAChE,QAAQ,CAAC,YAAY,CAAC,EAAE,uBAAuB,CAAC,UAAU,CAAC,CAAC;IAC5D,8DAA8D;IAC9D,QAAQ,CAAC,MAAM,CAAC,EAAE;QACd,OAAO,CAAC,EAAE,cAAc,CAAC;QACzB,MAAM,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;QAChC,SAAS,CAAC,EAAE,6BAA6B,CAAC;QAC1C,SAAS,CAAC,EAAE,gBAAgB,CAAC;KAChC,CAAC;IACF,oCAAoC;IACpC,QAAQ,CAAC,QAAQ,CAAC,EAAE,2BAA2B,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,UAAU,CAAC,CAAC;IACjF,wCAAwC;IACxC,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,EAAE,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACvF,wDAAwD;IACxD,QAAQ,CAAC,IAAI,CAAC,EAAE,CACZ,GAAG,EAAE,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,EAC5C,MAAM,EAAE,qBAAqB,KAC5B,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC7B;AAID;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAsB,kBAAkB,CAAC,UAAU,GAAG,OAAO,EAAE,OAAO,SAAS,QAAQ,GAAG,WAAW,EACjG,OAAO,EAAE,sBAAsB,CAAC,UAAU,EAAE,OAAO,CAAC,GACrD,OAAO,CAAC,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CA6JlD"}
@@ -14,8 +14,8 @@
14
14
  *
15
15
  * @module application-node
16
16
  */
17
- import { appendFileSync, mkdirSync, readFileSync } from 'node:fs';
18
- import { dirname } from 'node:path';
17
+ import { appendFileSync, mkdirSync, readFileSync, statSync } from 'node:fs';
18
+ import { dirname, join } from 'node:path';
19
19
  import { createDbAdapter } from '@gobing-ai/ts-db';
20
20
  import { createNodeFileSystem, interpolateTree, parseYamlObject } from '@gobing-ai/ts-runtime';
21
21
  import { runApplication } from './application/index.js';
@@ -159,11 +159,33 @@ function normalizeSchedulerJobs(raw) {
159
159
  return jobs;
160
160
  }
161
161
  // ── File sink helper ──────────────────────────────────────────────────────
162
+ /**
163
+ * Expand `{YYYYMMDD}` / `{YYYYMM}` (local date) in a file path. Resolved per write,
164
+ * so a long-running process rolls over to the next file without a restart.
165
+ */
166
+ function expandDateTokens(path, now = new Date()) {
167
+ const yyyymm = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}`;
168
+ const dd = String(now.getDate()).padStart(2, '0');
169
+ return path.replaceAll('{YYYYMMDD}', `${yyyymm}${dd}`).replaceAll('{YYYYMM}', yyyymm);
170
+ }
171
+ function isDirectoryPath(path) {
172
+ if (path.endsWith('/') || path.endsWith('\\'))
173
+ return true;
174
+ try {
175
+ return statSync(path).isDirectory();
176
+ }
177
+ catch {
178
+ return false;
179
+ }
180
+ }
181
+ /** A directory `filePath` gets the daily-rotated `spur{YYYYMMDD}.log` inside it; a file path is used as-is. */
162
182
  function createFileSink(filePath) {
183
+ const pattern = isDirectoryPath(filePath) ? join(filePath, 'spur{YYYYMMDD}.log') : filePath;
163
184
  return (line) => {
164
185
  try {
165
- mkdirSync(dirname(filePath), { recursive: true });
166
- appendFileSync(filePath, line);
186
+ const target = expandDateTokens(pattern);
187
+ mkdirSync(dirname(target), { recursive: true });
188
+ appendFileSync(target, line);
167
189
  }
168
190
  catch {
169
191
  // Best-effort — don't crash on log write failure
@@ -347,7 +369,7 @@ export async function runNodeApplication(options) {
347
369
  const fileObserverWriter = options.services?.fileObserverWriter ?? {
348
370
  ensureDir: (dir) => {
349
371
  try {
350
- mkdirSync(dir, { recursive: true });
372
+ mkdirSync(expandDateTokens(dir), { recursive: true });
351
373
  }
352
374
  catch (error) {
353
375
  // ensureDir contract: an existing directory is success. Node
@@ -359,10 +381,13 @@ export async function runNodeApplication(options) {
359
381
  }
360
382
  },
361
383
  appendFile: (path, content) => {
362
- appendFileSync(path, content);
384
+ const target = expandDateTokens(path);
385
+ mkdirSync(dirname(target), { recursive: true });
386
+ appendFileSync(target, content);
363
387
  },
364
388
  };
365
- const eventsFilePath = eventsOpts.filePath ?? injectedFileSystem.resolve('.spur', 'logs', 'system-events.jsonl');
389
+ // Monthly file by default so the raw bus trace never grows unbounded in one file.
390
+ const eventsFilePath = eventsOpts.filePath ?? injectedFileSystem.resolve('.spur', 'logs', 'system-events{YYYYMM}.jsonl');
366
391
  const eventsConfig = {
367
392
  ...eventsOpts,
368
393
  filePath: eventsFilePath,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobing-ai/ts-infra",
3
- "version": "0.5.21",
3
+ "version": "0.5.23",
4
4
  "description": "@gobing-ai/ts-infra — Infrastructure backbone: event bus, job queue, scheduler, telemetry, API client, and logging.",
5
5
  "keywords": [
6
6
  "typescript",
@@ -78,12 +78,12 @@
78
78
  "release": "echo 'Manual publish is disabled. Releases go through GitHub Actions via Trusted Publishing — push a tag: git tag @gobing-ai/ts-infra-v<version> && git push --tags' && exit 1"
79
79
  },
80
80
  "dependencies": {
81
- "@gobing-ai/ts-utils": "^0.5.21",
81
+ "@gobing-ai/ts-utils": "^0.5.23",
82
82
  "@logtape/logtape": "^2.0.0"
83
83
  },
84
84
  "peerDependencies": {
85
- "@gobing-ai/ts-db": "^0.5.21",
86
- "@gobing-ai/ts-runtime": "^0.5.21",
85
+ "@gobing-ai/ts-db": "^0.5.23",
86
+ "@gobing-ai/ts-runtime": "^0.5.23",
87
87
  "@opentelemetry/api": "^1.9.0",
88
88
  "@opentelemetry/sdk-trace-node": "^2.0.0",
89
89
  "@opentelemetry/sdk-metrics": "^2.0.0",
@@ -116,8 +116,8 @@
116
116
  }
117
117
  },
118
118
  "devDependencies": {
119
- "@gobing-ai/ts-db": "^0.5.21",
120
- "@gobing-ai/ts-runtime": "^0.5.21",
119
+ "@gobing-ai/ts-db": "^0.5.23",
120
+ "@gobing-ai/ts-runtime": "^0.5.23",
121
121
  "@types/bun": "1.3.14",
122
122
  "@opentelemetry/api": "^1.9.0",
123
123
  "@opentelemetry/sdk-trace-node": "^2.0.0",
@@ -15,8 +15,8 @@
15
15
  * @module application-node
16
16
  */
17
17
 
18
- import { appendFileSync, mkdirSync, readFileSync } from 'node:fs';
19
- import { dirname } from 'node:path';
18
+ import { appendFileSync, mkdirSync, readFileSync, statSync } from 'node:fs';
19
+ import { dirname, join } from 'node:path';
20
20
 
21
21
  import { createDbAdapter, type DbAdapter } from '@gobing-ai/ts-db';
22
22
  import { createNodeFileSystem, interpolateTree, parseYamlObject } from '@gobing-ai/ts-runtime';
@@ -217,11 +217,33 @@ function normalizeSchedulerJobs(raw: unknown): readonly SchedulerJobConfig[] {
217
217
 
218
218
  // ── File sink helper ──────────────────────────────────────────────────────
219
219
 
220
+ /**
221
+ * Expand `{YYYYMMDD}` / `{YYYYMM}` (local date) in a file path. Resolved per write,
222
+ * so a long-running process rolls over to the next file without a restart.
223
+ */
224
+ function expandDateTokens(path: string, now = new Date()): string {
225
+ const yyyymm = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}`;
226
+ const dd = String(now.getDate()).padStart(2, '0');
227
+ return path.replaceAll('{YYYYMMDD}', `${yyyymm}${dd}`).replaceAll('{YYYYMM}', yyyymm);
228
+ }
229
+
230
+ function isDirectoryPath(path: string): boolean {
231
+ if (path.endsWith('/') || path.endsWith('\\')) return true;
232
+ try {
233
+ return statSync(path).isDirectory();
234
+ } catch {
235
+ return false;
236
+ }
237
+ }
238
+
239
+ /** A directory `filePath` gets the daily-rotated `spur{YYYYMMDD}.log` inside it; a file path is used as-is. */
220
240
  function createFileSink(filePath: string): (line: string) => void {
241
+ const pattern = isDirectoryPath(filePath) ? join(filePath, 'spur{YYYYMMDD}.log') : filePath;
221
242
  return (line: string) => {
222
243
  try {
223
- mkdirSync(dirname(filePath), { recursive: true });
224
- appendFileSync(filePath, line);
244
+ const target = expandDateTokens(pattern);
245
+ mkdirSync(dirname(target), { recursive: true });
246
+ appendFileSync(target, line);
225
247
  } catch {
226
248
  // Best-effort — don't crash on log write failure
227
249
  }
@@ -470,7 +492,7 @@ export async function runNodeApplication<TAppConfig = unknown, TEvents extends E
470
492
  const fileObserverWriter = options.services?.fileObserverWriter ?? {
471
493
  ensureDir: (dir: string) => {
472
494
  try {
473
- mkdirSync(dir, { recursive: true });
495
+ mkdirSync(expandDateTokens(dir), { recursive: true });
474
496
  } catch (error) {
475
497
  // ensureDir contract: an existing directory is success. Node
476
498
  // semantics already suppress EEXIST with `recursive: true`, but
@@ -480,10 +502,14 @@ export async function runNodeApplication<TAppConfig = unknown, TEvents extends E
480
502
  }
481
503
  },
482
504
  appendFile: (path: string, content: string) => {
483
- appendFileSync(path, content);
505
+ const target = expandDateTokens(path);
506
+ mkdirSync(dirname(target), { recursive: true });
507
+ appendFileSync(target, content);
484
508
  },
485
509
  };
486
- const eventsFilePath = eventsOpts.filePath ?? injectedFileSystem.resolve('.spur', 'logs', 'system-events.jsonl');
510
+ // Monthly file by default so the raw bus trace never grows unbounded in one file.
511
+ const eventsFilePath =
512
+ eventsOpts.filePath ?? injectedFileSystem.resolve('.spur', 'logs', 'system-events{YYYYMM}.jsonl');
487
513
  const eventsConfig: EventsOptions<TEvents> = {
488
514
  ...eventsOpts,
489
515
  filePath: eventsFilePath,