target-supabase-sdk 0.2.12 → 0.2.14

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.
Files changed (197) hide show
  1. package/dist/browser.d.ts +1 -1
  2. package/dist/browser.d.ts.map +1 -1
  3. package/dist/browser.js +1 -1
  4. package/dist/chunks/enable-C7lFooTG.js +874 -0
  5. package/dist/chunks/enable-C7lFooTG.js.map +1 -0
  6. package/dist/chunks/list.api-BzbTJBsM.js.map +1 -1
  7. package/dist/chunks/{task-queue-H4mpSW8R.js → task-queue-qSnsfiIg.js} +111 -4
  8. package/dist/chunks/task-queue-qSnsfiIg.js.map +1 -0
  9. package/dist/node/env/index.d.ts +1 -1
  10. package/dist/node/env/index.d.ts.map +1 -1
  11. package/dist/node/node-runtime.base.d.ts +14 -1
  12. package/dist/node/node-runtime.base.d.ts.map +1 -1
  13. package/dist/node/node.api.d.ts.map +1 -1
  14. package/dist/node/node.constant.d.ts +3 -0
  15. package/dist/node/node.constant.d.ts.map +1 -0
  16. package/dist/node/node.utils.d.ts +0 -2
  17. package/dist/node/node.utils.d.ts.map +1 -1
  18. package/dist/node/preload/run-service-preload.d.ts.map +1 -1
  19. package/dist/node/preload/service-preload.interface.d.ts +3 -1
  20. package/dist/node/preload/service-preload.interface.d.ts.map +1 -1
  21. package/dist/node/process/managed-child-processes.d.ts.map +1 -1
  22. package/dist/node/readiness/index.d.ts +2 -0
  23. package/dist/node/readiness/index.d.ts.map +1 -1
  24. package/dist/node/{service-guard → readiness}/run-readiness-gate.d.ts +1 -1
  25. package/dist/node/readiness/run-readiness-gate.d.ts.map +1 -0
  26. package/dist/node/readiness/service-ready-gate.d.ts.map +1 -1
  27. package/dist/node/runtime-state/create-service-runtime-state-store.d.ts.map +1 -1
  28. package/dist/node/runtime-state/service-runtime-state.types.d.ts +16 -0
  29. package/dist/node/runtime-state/service-runtime-state.types.d.ts.map +1 -1
  30. package/dist/node/service-guard/index.d.ts +4 -4
  31. package/dist/node/service-guard/index.d.ts.map +1 -1
  32. package/dist/node/service-guard/register-service-guard-runner.d.ts +1 -2
  33. package/dist/node/service-guard/register-service-guard-runner.d.ts.map +1 -1
  34. package/dist/node/service-guard/registry-slot-guard-step.d.ts.map +1 -0
  35. package/dist/node/service-guard/run-service-guard-tick.d.ts.map +1 -1
  36. package/dist/node/service-guard/service-guard-node.d.ts +25 -2
  37. package/dist/node/service-guard/service-guard-node.d.ts.map +1 -1
  38. package/dist/node/service-guard/service-guard.interface.d.ts +25 -10
  39. package/dist/node/service-guard/service-guard.interface.d.ts.map +1 -1
  40. package/dist/node/service-host/claim-registry-slot.d.ts +15 -0
  41. package/dist/node/service-host/claim-registry-slot.d.ts.map +1 -0
  42. package/dist/node/service-host/create-l3-child-launcher.d.ts +38 -0
  43. package/dist/node/service-host/create-l3-child-launcher.d.ts.map +1 -0
  44. package/dist/node/service-host/index.d.ts +2 -4
  45. package/dist/node/service-host/index.d.ts.map +1 -1
  46. package/dist/node/service-host/service-host.d.ts +8 -4
  47. package/dist/node/service-host/service-host.d.ts.map +1 -1
  48. package/dist/node/service-host/single-process-service.d.ts +3 -3
  49. package/dist/node/service-host/single-process-service.d.ts.map +1 -1
  50. package/dist/node.d.ts +17 -10
  51. package/dist/node.d.ts.map +1 -1
  52. package/dist/node.js +927 -119
  53. package/dist/node.js.map +1 -1
  54. package/dist/preload.js +9 -6
  55. package/dist/preload.js.map +1 -1
  56. package/dist/service/index.d.ts +2 -2
  57. package/dist/service/index.d.ts.map +1 -1
  58. package/dist/service/registry.service.d.ts +19 -0
  59. package/dist/service/registry.service.d.ts.map +1 -1
  60. package/dist/shared/log/core/create-logger.d.ts.map +1 -0
  61. package/dist/shared/log/{log-batch.d.ts → core/log-batch.d.ts} +4 -4
  62. package/dist/shared/log/core/log-batch.d.ts.map +1 -0
  63. package/dist/shared/log/core/log-manager.d.ts.map +1 -0
  64. package/dist/shared/log/core/log-min-level.d.ts.map +1 -0
  65. package/dist/shared/log/core/log-scope.d.ts.map +1 -0
  66. package/dist/shared/log/index.d.ts +13 -13
  67. package/dist/shared/log/index.d.ts.map +1 -1
  68. package/dist/shared/log/spool/collector.d.ts +21 -0
  69. package/dist/shared/log/spool/collector.d.ts.map +1 -0
  70. package/dist/shared/log/spool/config.d.ts +7 -0
  71. package/dist/shared/log/spool/config.d.ts.map +1 -0
  72. package/dist/shared/log/spool/coordinator.d.ts +18 -0
  73. package/dist/shared/log/spool/coordinator.d.ts.map +1 -0
  74. package/dist/shared/log/spool/enable.d.ts +23 -0
  75. package/dist/shared/log/spool/enable.d.ts.map +1 -0
  76. package/dist/shared/log/spool/file.d.ts +21 -0
  77. package/dist/shared/log/spool/file.d.ts.map +1 -0
  78. package/dist/shared/log/spool/interface.d.ts +55 -0
  79. package/dist/shared/log/spool/interface.d.ts.map +1 -0
  80. package/dist/shared/log/spool/paths.d.ts +6 -0
  81. package/dist/shared/log/spool/paths.d.ts.map +1 -0
  82. package/dist/shared/log/spool/process-roles.d.ts +17 -0
  83. package/dist/shared/log/spool/process-roles.d.ts.map +1 -0
  84. package/dist/shared/log/spool/register-collect-log-runner.d.ts +15 -0
  85. package/dist/shared/log/spool/register-collect-log-runner.d.ts.map +1 -0
  86. package/dist/shared/log/spool/service-lifecycle.d.ts +13 -0
  87. package/dist/shared/log/spool/service-lifecycle.d.ts.map +1 -0
  88. package/dist/shared/log/spool/writer.d.ts +32 -0
  89. package/dist/shared/log/spool/writer.d.ts.map +1 -0
  90. package/dist/shared/log/{log-persist-batch-id.d.ts → upload/batch-id.d.ts} +3 -3
  91. package/dist/shared/log/upload/batch-id.d.ts.map +1 -0
  92. package/dist/shared/log/upload/env.d.ts +7 -0
  93. package/dist/shared/log/upload/env.d.ts.map +1 -0
  94. package/dist/shared/log/{log-persist-flush.d.ts → upload/flush.d.ts} +3 -3
  95. package/dist/shared/log/upload/flush.d.ts.map +1 -0
  96. package/dist/shared/log/{log-persist-hook.d.ts → upload/hook.d.ts} +2 -2
  97. package/dist/shared/log/upload/hook.d.ts.map +1 -0
  98. package/dist/shared/log/upload/interface.d.ts +33 -0
  99. package/dist/shared/log/upload/interface.d.ts.map +1 -0
  100. package/dist/shared/log/upload/logger.d.ts +22 -0
  101. package/dist/shared/log/upload/logger.d.ts.map +1 -0
  102. package/package.json +1 -1
  103. package/src/browser.ts +1 -0
  104. package/src/node/env/index.ts +0 -4
  105. package/src/node/node-runtime.base.ts +33 -5
  106. package/src/node/node.api.ts +1 -1
  107. package/src/node/node.constant.ts +2 -0
  108. package/src/node/node.utils.ts +2 -2
  109. package/src/node/preload/run-service-preload.ts +11 -6
  110. package/src/node/preload/service-preload.interface.ts +3 -1
  111. package/src/node/process/create-managed-child-processes.ts +1 -1
  112. package/src/node/process/managed-child-processes.ts +8 -1
  113. package/src/node/readiness/index.ts +2 -0
  114. package/src/node/{service-guard → readiness}/run-readiness-gate.ts +2 -2
  115. package/src/node/readiness/service-ready-gate.ts +27 -23
  116. package/src/node/runtime-state/create-service-runtime-state-store.ts +11 -0
  117. package/src/node/runtime-state/service-runtime-state.types.ts +12 -0
  118. package/src/node/service-guard/index.ts +7 -8
  119. package/src/node/service-guard/register-service-guard-runner.ts +0 -2
  120. package/src/node/service-guard/run-service-guard-tick.ts +5 -2
  121. package/src/node/service-guard/service-guard-node.ts +234 -6
  122. package/src/node/service-guard/service-guard.interface.ts +35 -10
  123. package/src/node/service-host/claim-registry-slot.ts +33 -0
  124. package/src/node/service-host/create-l3-child-launcher.ts +157 -0
  125. package/src/node/service-host/index.ts +5 -20
  126. package/src/node/service-host/service-host.ts +33 -21
  127. package/src/node/service-host/single-process-service.ts +10 -20
  128. package/src/node.ts +45 -35
  129. package/src/service/index.ts +3 -0
  130. package/src/service/registry.service.ts +142 -0
  131. package/src/shared/log/README.md +206 -0
  132. package/src/shared/log/{log-batch.ts → core/log-batch.ts} +4 -4
  133. package/src/shared/log/{log-manager.ts → core/log-manager.ts} +3 -3
  134. package/src/shared/log/{log-min-level.ts → core/log-min-level.ts} +1 -1
  135. package/src/shared/log/{log-scope.ts → core/log-scope.ts} +1 -1
  136. package/src/shared/log/index.ts +13 -18
  137. package/src/shared/log/spool/collector.ts +330 -0
  138. package/src/shared/log/spool/config.ts +56 -0
  139. package/src/shared/log/spool/coordinator.ts +55 -0
  140. package/src/shared/log/spool/enable.ts +136 -0
  141. package/src/shared/log/spool/file.ts +75 -0
  142. package/src/shared/log/spool/interface.ts +62 -0
  143. package/src/shared/log/spool/paths.ts +30 -0
  144. package/src/shared/log/spool/process-roles.ts +75 -0
  145. package/src/shared/log/spool/register-collect-log-runner.ts +63 -0
  146. package/src/shared/log/spool/service-lifecycle.ts +35 -0
  147. package/src/shared/log/spool/writer.ts +273 -0
  148. package/src/shared/log/{log-persist-batch-id.ts → upload/batch-id.ts} +2 -2
  149. package/src/shared/log/upload/env.ts +26 -0
  150. package/src/shared/log/{log-persist-flush.ts → upload/flush.ts} +8 -7
  151. package/src/shared/log/{log-persist-hook.ts → upload/hook.ts} +1 -1
  152. package/src/shared/log/upload/interface.ts +38 -0
  153. package/src/shared/log/upload/logger.ts +37 -0
  154. package/dist/chunks/enable-log-persist-Dl55_mv2.js +0 -1674
  155. package/dist/chunks/enable-log-persist-Dl55_mv2.js.map +0 -1
  156. package/dist/chunks/task-queue-H4mpSW8R.js.map +0 -1
  157. package/dist/node/service-guard/run-readiness-gate.d.ts.map +0 -1
  158. package/dist/node/service-host/registry-slot-guard-step.d.ts.map +0 -1
  159. package/dist/shared/log/create-logger.d.ts.map +0 -1
  160. package/dist/shared/log/enable-log-persist.d.ts +0 -28
  161. package/dist/shared/log/enable-log-persist.d.ts.map +0 -1
  162. package/dist/shared/log/log-batch.d.ts.map +0 -1
  163. package/dist/shared/log/log-manager.d.ts.map +0 -1
  164. package/dist/shared/log/log-min-level.d.ts.map +0 -1
  165. package/dist/shared/log/log-persist-batch-id.d.ts.map +0 -1
  166. package/dist/shared/log/log-persist-coordinator.d.ts +0 -37
  167. package/dist/shared/log/log-persist-coordinator.d.ts.map +0 -1
  168. package/dist/shared/log/log-persist-errors.d.ts +0 -4
  169. package/dist/shared/log/log-persist-errors.d.ts.map +0 -1
  170. package/dist/shared/log/log-persist-flush.d.ts.map +0 -1
  171. package/dist/shared/log/log-persist-hook.d.ts.map +0 -1
  172. package/dist/shared/log/log-persist-logger.d.ts +0 -16
  173. package/dist/shared/log/log-persist-logger.d.ts.map +0 -1
  174. package/dist/shared/log/log-persist-registry.d.ts +0 -18
  175. package/dist/shared/log/log-persist-registry.d.ts.map +0 -1
  176. package/dist/shared/log/log-persist.config.d.ts +0 -9
  177. package/dist/shared/log/log-persist.config.d.ts.map +0 -1
  178. package/dist/shared/log/log-persist.d.ts +0 -62
  179. package/dist/shared/log/log-persist.d.ts.map +0 -1
  180. package/dist/shared/log/log-persist.interface.d.ts +0 -132
  181. package/dist/shared/log/log-persist.interface.d.ts.map +0 -1
  182. package/dist/shared/log/log-scope.d.ts.map +0 -1
  183. package/src/shared/log/enable-log-persist.ts +0 -225
  184. package/src/shared/log/log-persist-coordinator.ts +0 -107
  185. package/src/shared/log/log-persist-errors.ts +0 -16
  186. package/src/shared/log/log-persist-logger.ts +0 -61
  187. package/src/shared/log/log-persist-registry.ts +0 -252
  188. package/src/shared/log/log-persist.config.ts +0 -115
  189. package/src/shared/log/log-persist.interface.ts +0 -150
  190. package/src/shared/log/log-persist.ts +0 -822
  191. /package/dist/node/{service-host → service-guard}/registry-slot-guard-step.d.ts +0 -0
  192. /package/dist/shared/log/{create-logger.d.ts → core/create-logger.d.ts} +0 -0
  193. /package/dist/shared/log/{log-manager.d.ts → core/log-manager.d.ts} +0 -0
  194. /package/dist/shared/log/{log-min-level.d.ts → core/log-min-level.d.ts} +0 -0
  195. /package/dist/shared/log/{log-scope.d.ts → core/log-scope.d.ts} +0 -0
  196. /package/src/node/{service-host → service-guard}/registry-slot-guard-step.ts +0 -0
  197. /package/src/shared/log/{create-logger.ts → core/create-logger.ts} +0 -0
@@ -0,0 +1,62 @@
1
+ import type { LogEntry } from "../core/log-manager";
2
+ import type { LogSpoolCoreProcessRole, LogSpoolProcessRole } from "./process-roles";
3
+
4
+ /** On-disk batch before guard upload — producer writes `.tmp`, guard renames to `.json` after post. */
5
+ export interface LogSpoolBatchFile {
6
+ entries: LogEntry[];
7
+ meta?: Record<string, unknown>;
8
+ }
9
+
10
+ export interface LogSpoolWriterConfig {
11
+ maxEntries: number;
12
+ maxBytes: number;
13
+ flushDebounceMs: number;
14
+ /** Flush buffered entries after this age even when below count/byte thresholds. */
15
+ maxAgeMs: number;
16
+ postTimeoutMs: number;
17
+ }
18
+
19
+ export interface LogSpoolGcConfig {
20
+ /** How often guard runs tmp/json GC (upload collect still runs every collect tick). */
21
+ intervalMs: number;
22
+ syncedRetentionMs: number;
23
+ tmpRetentionMs: number;
24
+ }
25
+
26
+ export interface LogSpoolConfig {
27
+ writer: LogSpoolWriterConfig;
28
+ gc: LogSpoolGcConfig;
29
+ collectIntervalMs: number;
30
+ }
31
+
32
+ export interface EnableLogSpoolOptions {
33
+ /** Service instance id (`Service.id` from registry claim). */
34
+ serviceId: string;
35
+ /** Spool directory name — core or {@link LOG_SPOOL_EXTRA_PROCESS_ROLES_ENV} extra. */
36
+ processRole: LogSpoolProcessRole;
37
+ /** Logical service value for List meta (`LOG_PERSIST_SERVICE`, e.g. `watch-service`). */
38
+ serviceValue: string;
39
+ spoolRoot?: string;
40
+ config?: Partial<LogSpoolConfig>;
41
+ }
42
+
43
+ export interface EnsureLogSpoolFromEnvOptions {
44
+ serviceId?: string;
45
+ processRole?: LogSpoolProcessRole;
46
+ serviceValue?: string;
47
+ spoolRoot?: string;
48
+ config?: Partial<LogSpoolConfig>;
49
+ }
50
+
51
+ export interface LogSpoolWriterStats {
52
+ enabled: boolean;
53
+ bufferedEntries: number;
54
+ bufferedBytes: number;
55
+ flushInProgress: boolean;
56
+ debouncePending: boolean;
57
+ ageFlushPending: boolean;
58
+ serviceId: string | null;
59
+ processRole: LogSpoolProcessRole | null;
60
+ }
61
+
62
+ export type { LogSpoolCoreProcessRole, LogSpoolProcessRole };
@@ -0,0 +1,30 @@
1
+ import { join } from "node:path";
2
+ import { runtimeDataDirFromEnv } from "../upload/env";
3
+ import type { LogSpoolProcessRole } from "./interface";
4
+
5
+ export const LOG_SPOOL_DIR_NAME = "log-spool";
6
+
7
+ export function resolveLogSpoolRoot(explicit?: string): string {
8
+ if (explicit != null && explicit.trim() !== "") {
9
+ return explicit;
10
+ }
11
+ const configured = process.env.LOG_SPOOL_DIR?.trim();
12
+ if (configured != null && configured !== "") {
13
+ return configured;
14
+ }
15
+ const runtimeDir = runtimeDataDirFromEnv();
16
+ if (runtimeDir == null) {
17
+ throw new Error(
18
+ "[LogSpool] spool root required: set LOG_SPOOL_DIR or LOG_PERSIST_REGISTRY_DIR / RUNTIME_DATA_DIR",
19
+ );
20
+ }
21
+ return join(runtimeDir, LOG_SPOOL_DIR_NAME);
22
+ }
23
+
24
+ export function logSpoolServiceDir(spoolRoot: string, serviceId: string): string {
25
+ return join(spoolRoot, serviceId);
26
+ }
27
+
28
+ export function logSpoolProcessDir(spoolRoot: string, serviceId: string, processRole: LogSpoolProcessRole): string {
29
+ return join(spoolRoot, serviceId, processRole);
30
+ }
@@ -0,0 +1,75 @@
1
+ /** Four-process L3 blueprint roles — always scanned by collect-log. */
2
+ export const LOG_SPOOL_CORE_PROCESS_ROLES = ["main", "guard", "scheduler", "worker"] as const;
3
+
4
+ export type LogSpoolCoreProcessRole = (typeof LOG_SPOOL_CORE_PROCESS_ROLES)[number];
5
+
6
+ /** Spool directory / `LOG_PERSIST_PROCESS` name — core or service-specific extra. */
7
+ export type LogSpoolProcessRole = LogSpoolCoreProcessRole | string;
8
+
9
+ export const LOG_SPOOL_EXTRA_PROCESS_ROLES_ENV = "LOG_SPOOL_EXTRA_PROCESS_ROLES";
10
+
11
+ const EXTRA_ROLE_PATTERN = /^[a-z][a-z0-9-]*$/;
12
+
13
+ function parseRoleList(raw: string | undefined): string[] {
14
+ if (raw == null || raw.trim() === "") {
15
+ return [];
16
+ }
17
+ return raw
18
+ .split(",")
19
+ .map((part) => part.trim())
20
+ .filter((part) => part !== "");
21
+ }
22
+
23
+ /** Parse comma-separated extra roles from env (e.g. `chrome-sidecar`). */
24
+ export function logSpoolExtraProcessRolesFromEnv(env: NodeJS.ProcessEnv = process.env): string[] {
25
+ return parseRoleList(env[LOG_SPOOL_EXTRA_PROCESS_ROLES_ENV]);
26
+ }
27
+
28
+ /** Core + extras, deduped, stable order (core first). */
29
+ export function resolveAllLogSpoolProcessRoles(env: NodeJS.ProcessEnv = process.env): LogSpoolProcessRole[] {
30
+ const extras = logSpoolExtraProcessRolesFromEnv(env);
31
+ const seen = new Set<string>(LOG_SPOOL_CORE_PROCESS_ROLES);
32
+ const all: LogSpoolProcessRole[] = [...LOG_SPOOL_CORE_PROCESS_ROLES];
33
+ for (const role of extras) {
34
+ if (!seen.has(role)) {
35
+ seen.add(role);
36
+ all.push(role);
37
+ }
38
+ }
39
+ return all;
40
+ }
41
+
42
+ export function isLogSpoolCoreProcessRole(value: string): value is LogSpoolCoreProcessRole {
43
+ return (LOG_SPOOL_CORE_PROCESS_ROLES as readonly string[]).includes(value);
44
+ }
45
+
46
+ /** Whether `value` is a known spool role (core or configured extra). */
47
+ export function isKnownLogSpoolProcessRole(value: string, env: NodeJS.ProcessEnv = process.env): boolean {
48
+ if (isLogSpoolCoreProcessRole(value)) {
49
+ return true;
50
+ }
51
+ return logSpoolExtraProcessRolesFromEnv(env).includes(value);
52
+ }
53
+
54
+ /** Validate extra role token — lowercase alphanumeric + hyphen, not a duplicate core name. */
55
+ export function assertValidLogSpoolExtraProcessRole(role: string): void {
56
+ if (role === "" || !EXTRA_ROLE_PATTERN.test(role)) {
57
+ throw new Error(
58
+ `[LogSpool] invalid extra process role "${role}" — use lowercase letters, digits, hyphen (e.g. chrome-sidecar)`,
59
+ );
60
+ }
61
+ if (isLogSpoolCoreProcessRole(role)) {
62
+ throw new Error(`[LogSpool] extra process role "${role}" duplicates a core role`);
63
+ }
64
+ }
65
+
66
+ export function mergeLogSpoolExtraProcessRolesEnv(env: NodeJS.ProcessEnv, extraRoles: readonly string[]): void {
67
+ if (extraRoles.length === 0) {
68
+ return;
69
+ }
70
+ for (const role of extraRoles) {
71
+ assertValidLogSpoolExtraProcessRole(role);
72
+ }
73
+ const merged = new Set([...logSpoolExtraProcessRolesFromEnv(env), ...extraRoles]);
74
+ env[LOG_SPOOL_EXTRA_PROCESS_ROLES_ENV] = [...merged].join(",");
75
+ }
@@ -0,0 +1,63 @@
1
+ import { LOG_TOPIC_TRIGGER } from "../../../trigger/trigger.constant";
2
+ import { TriggerManager } from "../../../trigger/trigger-manager";
3
+ import { logSpoolEnabledFromEnv } from "../upload/env";
4
+ import { spoolLogger } from "../upload/logger";
5
+ import { runCollectLogTick } from "./collector";
6
+ import { logSpoolConfigFromEnv } from "./config";
7
+
8
+ export const COLLECT_LOG_RUNNER_KEY = "collect-log";
9
+
10
+ export interface RegisterCollectLogRunnerOptions {
11
+ serviceValue: string;
12
+ getServiceId: () => Promise<string | null>;
13
+ runnerKey?: string;
14
+ intervalMs?: number;
15
+ initialDelayMs?: number;
16
+ spoolRoot?: string;
17
+ }
18
+
19
+ /**
20
+ * Guard process only — uploads `.tmp` spool batches and runs GC.
21
+ * Register before {@link TriggerNode.start} (e.g. in {@link ServiceGuardNode.create}).
22
+ */
23
+ export function registerCollectLogRunner(options: RegisterCollectLogRunnerOptions): void {
24
+ if (!logSpoolEnabledFromEnv()) {
25
+ return;
26
+ }
27
+
28
+ const config = logSpoolConfigFromEnv();
29
+ const intervalMs = options.intervalMs ?? config.collectIntervalMs;
30
+
31
+ TriggerManager.registerRunner({
32
+ key: options.runnerKey ?? COLLECT_LOG_RUNNER_KEY,
33
+ intervalMs,
34
+ initialDelayMs: options.initialDelayMs ?? 0,
35
+ retryCount: 1,
36
+ fn: async (ctx) => {
37
+ const serviceId = await options.getServiceId();
38
+ if (serviceId == null) {
39
+ ctx.logger.warn("collect-log skipped — serviceId unavailable", { topic: LOG_TOPIC_TRIGGER });
40
+ return;
41
+ }
42
+
43
+ const result = await runCollectLogTick({
44
+ serviceId,
45
+ serviceValue: options.serviceValue,
46
+ spoolRoot: options.spoolRoot,
47
+ config,
48
+ });
49
+
50
+ if (result.uploaded > 0 || result.failed > 0) {
51
+ ctx.logger.info("collect-log runner finished", {
52
+ topic: LOG_TOPIC_TRIGGER,
53
+ data: result,
54
+ });
55
+ }
56
+ },
57
+ });
58
+
59
+ spoolLogger.info("collect-log runner registered", {
60
+ intervalMs,
61
+ serviceValue: options.serviceValue,
62
+ });
63
+ }
@@ -0,0 +1,35 @@
1
+ import { logSpoolEnabledFromEnv } from "../upload/env";
2
+ import { ensureLogSpoolFromEnv, getLogSpoolStats, shutdownLogSpoolFromEnv } from "./enable";
3
+ import type { LogSpoolWriterStats } from "./interface";
4
+
5
+ /** Whether file log spool is enabled (`LOG_PERSIST_ENABLED=true`). */
6
+ export function isLogSpoolEnabled(env: NodeJS.ProcessEnv = process.env): boolean {
7
+ return logSpoolEnabledFromEnv(env);
8
+ }
9
+
10
+ /**
11
+ * Guard / scheduler / worker (and extra roles) — enable writer from spawn env.
12
+ * Main is enabled by `createServiceHost` after registry claim.
13
+ */
14
+ export async function enableLogSpoolFromEnvInChild(): Promise<void> {
15
+ if (!logSpoolEnabledFromEnv()) {
16
+ return;
17
+ }
18
+ await ensureLogSpoolFromEnv();
19
+ }
20
+
21
+ /** Writer buffer stats when spool enabled; `null` when disabled. Typical use: main observability. */
22
+ export function getMainLogSpoolWriterStats(): LogSpoolWriterStats | null {
23
+ if (!logSpoolEnabledFromEnv()) {
24
+ return null;
25
+ }
26
+ return getLogSpoolStats();
27
+ }
28
+
29
+ /** Flush memory buffer and disable writer when spool enabled; no-op otherwise. */
30
+ export async function shutdownLogSpool(): Promise<void> {
31
+ if (!logSpoolEnabledFromEnv()) {
32
+ return;
33
+ }
34
+ await shutdownLogSpoolFromEnv();
35
+ }
@@ -0,0 +1,273 @@
1
+ import { mkdir, rename, writeFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import type { LogEntry } from "../core/log-manager";
4
+ import { estimateEntriesBytes, takeEntriesWithinBytes } from "../upload/flush";
5
+ import { registerLogPersistOffer } from "../upload/hook";
6
+ import { isLogPersistInternalTopic, spoolLogger } from "../upload/logger";
7
+ import { DEFAULT_LOG_SPOOL_CONFIG, resolveLogSpoolConfig } from "./config";
8
+ import { buildLogSpoolBatchBaseName, computeLogSpoolShortHash } from "./file";
9
+ import type { EnableLogSpoolOptions, LogSpoolBatchFile, LogSpoolProcessRole, LogSpoolWriterStats } from "./interface";
10
+ import { logSpoolProcessDir, resolveLogSpoolRoot } from "./paths";
11
+
12
+ class LogSpoolWriter {
13
+ private static instance: LogSpoolWriter | null = null;
14
+
15
+ private enabled = false;
16
+ private buffer: LogEntry[] = [];
17
+ private flushInProgress = false;
18
+ private debounceTimer: ReturnType<typeof setTimeout> | null = null;
19
+ private ageTimer: ReturnType<typeof setTimeout> | null = null;
20
+ private serviceId: string | null = null;
21
+ private processRole: LogSpoolProcessRole | null = null;
22
+ private serviceValue: string | null = null;
23
+ private spoolRoot: string | null = null;
24
+ private config = DEFAULT_LOG_SPOOL_CONFIG.writer;
25
+
26
+ static getInstance(): LogSpoolWriter {
27
+ LogSpoolWriter.instance ??= new LogSpoolWriter();
28
+ return LogSpoolWriter.instance;
29
+ }
30
+
31
+ async enable(options: EnableLogSpoolOptions): Promise<void> {
32
+ if (this.enabled) {
33
+ spoolLogger.warn("log spool writer already enabled", {
34
+ serviceId: this.serviceId,
35
+ processRole: this.processRole,
36
+ });
37
+ return;
38
+ }
39
+
40
+ this.serviceId = options.serviceId;
41
+ this.processRole = options.processRole;
42
+ this.serviceValue = options.serviceValue;
43
+ this.spoolRoot = options.spoolRoot ?? resolveLogSpoolRoot();
44
+ this.config = resolveLogSpoolConfig(options.config).writer;
45
+ this.enabled = true;
46
+
47
+ registerLogPersistOffer((entry) => this.offer(entry));
48
+
49
+ spoolLogger.info("log spool writer enabled", {
50
+ serviceId: options.serviceId,
51
+ processRole: options.processRole,
52
+ serviceValue: options.serviceValue,
53
+ spoolRoot: this.spoolRoot,
54
+ config: this.config,
55
+ });
56
+ }
57
+
58
+ async shutdown(): Promise<void> {
59
+ this.cancelDebounce();
60
+ this.cancelAgeFlush();
61
+ registerLogPersistOffer(null);
62
+ this.enabled = false;
63
+
64
+ while (this.buffer.length > 0) {
65
+ await this.flushChunk({ reason: "shutdown" });
66
+ }
67
+
68
+ spoolLogger.info("log spool writer shutdown complete", {
69
+ serviceId: this.serviceId,
70
+ processRole: this.processRole,
71
+ });
72
+ }
73
+
74
+ getStats(): LogSpoolWriterStats {
75
+ return {
76
+ enabled: this.enabled,
77
+ bufferedEntries: this.buffer.length,
78
+ bufferedBytes: estimateEntriesBytes(this.buffer),
79
+ flushInProgress: this.flushInProgress,
80
+ debouncePending: this.debounceTimer != null,
81
+ ageFlushPending: this.ageTimer != null,
82
+ serviceId: this.serviceId,
83
+ processRole: this.processRole,
84
+ };
85
+ }
86
+
87
+ offer(entry: LogEntry): void {
88
+ if (isLogPersistInternalTopic(entry.topic)) {
89
+ return;
90
+ }
91
+
92
+ if (!this.enabled) {
93
+ return;
94
+ }
95
+
96
+ if (this.buffer.length === 0) {
97
+ this.scheduleAgeFlush();
98
+ }
99
+
100
+ this.buffer.push(entry);
101
+
102
+ if (this.shouldScheduleDebouncedFlush()) {
103
+ this.scheduleDebouncedFlush();
104
+ }
105
+ }
106
+
107
+ private shouldScheduleDebouncedFlush(): boolean {
108
+ return (
109
+ this.buffer.length >= this.config.maxEntries || estimateEntriesBytes(this.buffer) >= this.config.maxBytes
110
+ );
111
+ }
112
+
113
+ private scheduleDebouncedFlush(): void {
114
+ if (this.debounceTimer != null) {
115
+ return;
116
+ }
117
+
118
+ spoolLogger.debug("log spool flush debounce scheduled", {
119
+ debounceMs: this.config.flushDebounceMs,
120
+ bufferedEntries: this.buffer.length,
121
+ });
122
+
123
+ this.debounceTimer = setTimeout(() => {
124
+ this.debounceTimer = null;
125
+ void this.flushChunk({ reason: "debounce" });
126
+ }, this.config.flushDebounceMs);
127
+ this.debounceTimer.unref?.();
128
+ }
129
+
130
+ private scheduleAgeFlush(): void {
131
+ if (this.ageTimer != null) {
132
+ return;
133
+ }
134
+
135
+ spoolLogger.debug("log spool age flush scheduled", {
136
+ maxAgeMs: this.config.maxAgeMs,
137
+ });
138
+
139
+ this.ageTimer = setTimeout(() => {
140
+ this.ageTimer = null;
141
+ void this.flushChunk({ reason: "maxAge" });
142
+ }, this.config.maxAgeMs);
143
+ this.ageTimer.unref?.();
144
+ }
145
+
146
+ private cancelDebounce(): void {
147
+ if (this.debounceTimer != null) {
148
+ clearTimeout(this.debounceTimer);
149
+ this.debounceTimer = null;
150
+ }
151
+ }
152
+
153
+ private cancelAgeFlush(): void {
154
+ if (this.ageTimer != null) {
155
+ clearTimeout(this.ageTimer);
156
+ this.ageTimer = null;
157
+ }
158
+ }
159
+
160
+ private async flushChunk(input: { reason: string }): Promise<void> {
161
+ if (this.flushInProgress || this.buffer.length === 0) {
162
+ return;
163
+ }
164
+ if (this.serviceId == null || this.processRole == null || this.serviceValue == null || this.spoolRoot == null) {
165
+ return;
166
+ }
167
+
168
+ this.cancelDebounce();
169
+ this.cancelAgeFlush();
170
+ this.flushInProgress = true;
171
+ try {
172
+ const byCount = this.buffer.slice(0, this.config.maxEntries);
173
+ const entries = takeEntriesWithinBytes(byCount, this.config.maxBytes);
174
+ this.buffer = this.buffer.slice(entries.length);
175
+
176
+ if (entries.length === 0) {
177
+ if (this.buffer.length > 0) {
178
+ this.scheduleAgeFlush();
179
+ } else {
180
+ this.cancelAgeFlush();
181
+ }
182
+ return;
183
+ }
184
+
185
+ const batchFile = await this.writeBatchTmp({
186
+ entries,
187
+ serviceId: this.serviceId,
188
+ processRole: this.processRole,
189
+ serviceValue: this.serviceValue,
190
+ spoolRoot: this.spoolRoot,
191
+ });
192
+
193
+ spoolLogger.info("log spool flushed to tmp", {
194
+ reason: input.reason,
195
+ fileName: batchFile.fileName,
196
+ entryCount: entries.length,
197
+ remainingBuffered: this.buffer.length,
198
+ });
199
+
200
+ if (this.buffer.length > 0) {
201
+ this.scheduleAgeFlush();
202
+ if (this.shouldScheduleDebouncedFlush()) {
203
+ this.scheduleDebouncedFlush();
204
+ }
205
+ } else {
206
+ this.cancelAgeFlush();
207
+ }
208
+ } catch (error) {
209
+ const message = error instanceof Error ? error.message : String(error);
210
+ spoolLogger.error("log spool flush failed", { reason: input.reason, error: message });
211
+ if (this.buffer.length > 0) {
212
+ this.scheduleAgeFlush();
213
+ }
214
+ } finally {
215
+ this.flushInProgress = false;
216
+ }
217
+ }
218
+
219
+ private async writeBatchTmp(input: {
220
+ entries: LogEntry[];
221
+ serviceId: string;
222
+ processRole: LogSpoolProcessRole;
223
+ serviceValue: string;
224
+ spoolRoot: string;
225
+ }): Promise<{ fileName: string; tmpPath: string }> {
226
+ const first = input.entries[0];
227
+ const shortHash = computeLogSpoolShortHash({
228
+ serviceValue: input.serviceValue,
229
+ serviceId: input.serviceId,
230
+ processRole: input.processRole,
231
+ entries: input.entries,
232
+ });
233
+ const baseName = buildLogSpoolBatchBaseName({
234
+ processRole: input.processRole,
235
+ firstEntryTimestamp: first.timestamp,
236
+ shortHash,
237
+ });
238
+ const fileName = `${baseName}.tmp`;
239
+ const processDir = logSpoolProcessDir(input.spoolRoot, input.serviceId, input.processRole);
240
+ await mkdir(processDir, { recursive: true });
241
+
242
+ const tmpPath = join(processDir, fileName);
243
+ const partPath = `${tmpPath}.part`;
244
+ const payload: LogSpoolBatchFile = {
245
+ entries: input.entries,
246
+ meta: {
247
+ serviceId: input.serviceId,
248
+ processRole: input.processRole,
249
+ serviceValue: input.serviceValue,
250
+ },
251
+ };
252
+ const content = `${JSON.stringify(payload)}\n`;
253
+
254
+ await writeFile(partPath, content, "utf8");
255
+ await rename(partPath, tmpPath);
256
+
257
+ return { fileName, tmpPath };
258
+ }
259
+ }
260
+
261
+ export { LogSpoolWriter };
262
+
263
+ export async function enableLogSpoolWriter(options: EnableLogSpoolOptions): Promise<void> {
264
+ await LogSpoolWriter.getInstance().enable(options);
265
+ }
266
+
267
+ export async function shutdownLogSpoolWriter(): Promise<void> {
268
+ await LogSpoolWriter.getInstance().shutdown();
269
+ }
270
+
271
+ export function getLogSpoolWriterStats(): LogSpoolWriterStats {
272
+ return LogSpoolWriter.getInstance().getStats();
273
+ }
@@ -1,6 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
- import type { LogEntry } from "./log-manager";
3
- import type { LogPersistLane } from "./log-persist.interface";
2
+ import type { LogEntry } from "../core/log-manager";
3
+ import type { LogPersistLane } from "./interface";
4
4
 
5
5
  const BATCH_ID_VERSION = 1;
6
6
 
@@ -0,0 +1,26 @@
1
+ import { envBool } from "../../../node/env/env-parsers";
2
+
3
+ /** Master switch — env name kept for compatibility (`LOG_PERSIST_ENABLED`). */
4
+ export function logSpoolEnabledFromEnv(env: NodeJS.ProcessEnv = process.env): boolean {
5
+ return envBool("LOG_PERSIST_ENABLED", false, env);
6
+ }
7
+
8
+ export function logPersistServiceFromEnv(env: NodeJS.ProcessEnv = process.env): string | undefined {
9
+ const raw = env.LOG_PERSIST_SERVICE?.trim();
10
+ return raw != null && raw !== "" ? raw : undefined;
11
+ }
12
+
13
+ export function logPersistProcessFromEnv(env: NodeJS.ProcessEnv = process.env): string | undefined {
14
+ const raw = env.LOG_PERSIST_PROCESS?.trim();
15
+ return raw != null && raw !== "" ? raw : undefined;
16
+ }
17
+
18
+ /** `LOG_PERSIST_REGISTRY_DIR` or `RUNTIME_DATA_DIR`. */
19
+ export function runtimeDataDirFromEnv(env: NodeJS.ProcessEnv = process.env): string | undefined {
20
+ const configured = env.LOG_PERSIST_REGISTRY_DIR?.trim();
21
+ if (configured != null && configured !== "") {
22
+ return configured;
23
+ }
24
+ const runtime = env.RUNTIME_DATA_DIR?.trim();
25
+ return runtime != null && runtime !== "" ? runtime : undefined;
26
+ }
@@ -1,10 +1,10 @@
1
- import { isCreateTargetAlreadyExistsError } from "../../core.api";
2
- import { postLogListCreate } from "../../list/list.api";
3
- import { CategoryList } from "../../list/list.interface";
4
- import type { LogEntry } from "./log-manager";
5
- import type { BuildLogListDraftInput, LogListDraft } from "./log-persist.interface";
6
- import { LOG_PERSIST_LOADER_KEY } from "./log-persist.interface";
7
- import { persistLogger } from "./log-persist-logger";
1
+ import { isCreateTargetAlreadyExistsError } from "../../../core.api";
2
+ import { postLogListCreate } from "../../../list/list.api";
3
+ import { CategoryList } from "../../../list/list.interface";
4
+ import type { LogEntry } from "../core/log-manager";
5
+ import type { BuildLogListDraftInput, LogListDraft } from "./interface";
6
+ import { LOG_PERSIST_LOADER_KEY } from "./interface";
7
+ import { persistLogger } from "./logger";
8
8
 
9
9
  export function estimateEntriesBytes(entries: LogEntry[]): number {
10
10
  if (entries.length === 0) {
@@ -19,6 +19,7 @@ export function estimateEntriesBytes(entries: LogEntry[]): number {
19
19
 
20
20
  export function buildLogListDraft(input: BuildLogListDraftInput): LogListDraft {
21
21
  const { service, process, lane, idempotencyKey, entries } = input;
22
+ // TODO: `lane` is legacy transport metadata — scheduled for removal; callers still pass a fixed value for decode compatibility.
22
23
  const first = entries[0];
23
24
  const last = entries[entries.length - 1];
24
25
 
@@ -1,4 +1,4 @@
1
- import type { LogEntry } from "./log-manager";
1
+ import type { LogEntry } from "../core/log-manager";
2
2
 
3
3
  type LogPersistOfferFn = (entry: LogEntry) => void;
4
4
 
@@ -0,0 +1,38 @@
1
+ import type { TargetDraft } from "../../../core.interface";
2
+ import type { List } from "../../../list/list.interface";
3
+ import type { LogEntry } from "../core/log-manager";
4
+
5
+ /**
6
+ * `ListDetails.loaderKey` stamped on every persisted log batch (write + read side).
7
+ * Business type: **LogBatch** (transport flush). Pairs with log-service **LogTrace**
8
+ * (`LogTrace.0`) after rekeying by `traceId`.
9
+ */
10
+ export const LOG_PERSIST_LOADER_KEY = "LogBatch.0";
11
+
12
+ /** Alias for {@link LOG_PERSIST_LOADER_KEY} — preferred when contrasting with LogTrace. */
13
+ export const LOG_BATCH_LOADER_KEY = LOG_PERSIST_LOADER_KEY;
14
+
15
+ /** Legacy transport metadata — fixed `"slow"` for spool uploads; required for downstream decode today. */
16
+ export type LogPersistLane = "fast" | "medium" | "slow";
17
+
18
+ export interface LogBatchMeta {
19
+ service: string;
20
+ process: string;
21
+ pid: number;
22
+ lane: LogPersistLane;
23
+ count: number;
24
+ from: number | null;
25
+ to: number | null;
26
+ flushedAt: number;
27
+ idempotencyKey: string;
28
+ }
29
+
30
+ export interface BuildLogListDraftInput {
31
+ service: string;
32
+ process: string;
33
+ lane: LogPersistLane;
34
+ idempotencyKey: string;
35
+ entries: LogEntry[];
36
+ }
37
+
38
+ export type LogListDraft = TargetDraft<List>;
@@ -0,0 +1,37 @@
1
+ import { createLogger } from "../core/create-logger";
2
+
3
+ /** User-facing / lifecycle logs — may be persisted like normal app logs. */
4
+ export const LOG_PERSIST_TOPIC = "log-persist";
5
+
6
+ /** Operational logs from the persist pipeline — never re-offered to avoid feedback loops. */
7
+ export const LOG_PERSIST_INTERNAL_TOPIC = "log-persist-internal";
8
+
9
+ type PipelineLogData = Record<string, unknown>;
10
+
11
+ function createPipelineLogger(module: string) {
12
+ const logger = createLogger({ module });
13
+
14
+ function write(level: "debug" | "info" | "warn" | "error", message: string, data?: PipelineLogData): void {
15
+ logger[level](message, {
16
+ topic: LOG_PERSIST_INTERNAL_TOPIC,
17
+ ...(data != null ? { data } : {}),
18
+ });
19
+ }
20
+
21
+ return {
22
+ debug: (message: string, data?: PipelineLogData) => write("debug", message, data),
23
+ info: (message: string, data?: PipelineLogData) => write("info", message, data),
24
+ warn: (message: string, data?: PipelineLogData) => write("warn", message, data),
25
+ error: (message: string, data?: PipelineLogData) => write("error", message, data),
26
+ };
27
+ }
28
+
29
+ /** Supabase upload path (`postLogBatch`). */
30
+ export const persistLogger = createPipelineLogger("log-upload");
31
+
32
+ /** File spool writer + collector. */
33
+ export const spoolLogger = createPipelineLogger("log-spool");
34
+
35
+ export function isLogPersistInternalTopic(topic: string | undefined): boolean {
36
+ return topic === LOG_PERSIST_INTERNAL_TOPIC;
37
+ }