@trestleinc/replicate 1.1.1 → 1.1.2-preview.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.
Files changed (91) hide show
  1. package/README.md +395 -146
  2. package/dist/client/index.d.ts +311 -19
  3. package/dist/client/index.js +4027 -0
  4. package/dist/component/_generated/api.d.ts +13 -17
  5. package/dist/component/_generated/api.js +24 -4
  6. package/dist/component/_generated/component.d.ts +79 -77
  7. package/dist/component/_generated/component.js +1 -0
  8. package/dist/component/_generated/dataModel.d.ts +12 -15
  9. package/dist/component/_generated/dataModel.js +1 -0
  10. package/dist/component/_generated/server.d.ts +19 -22
  11. package/dist/component/_generated/server.js +65 -1
  12. package/dist/component/_virtual/rolldown_runtime.js +18 -0
  13. package/dist/component/convex.config.d.ts +6 -2
  14. package/dist/component/convex.config.js +7 -3
  15. package/dist/component/logger.d.ts +10 -6
  16. package/dist/component/logger.js +25 -28
  17. package/dist/component/public.d.ts +70 -61
  18. package/dist/component/public.js +311 -295
  19. package/dist/component/schema.d.ts +53 -45
  20. package/dist/component/schema.js +26 -32
  21. package/dist/component/shared/types.d.ts +9 -0
  22. package/dist/component/shared/types.js +15 -0
  23. package/dist/server/index.d.ts +134 -13
  24. package/dist/server/index.js +368 -0
  25. package/dist/shared/index.d.ts +27 -3
  26. package/dist/shared/index.js +1 -2
  27. package/package.json +34 -29
  28. package/src/client/collection.ts +339 -306
  29. package/src/client/errors.ts +9 -9
  30. package/src/client/index.ts +13 -32
  31. package/src/client/logger.ts +2 -2
  32. package/src/client/merge.ts +37 -34
  33. package/src/client/persistence/custom.ts +84 -0
  34. package/src/client/persistence/index.ts +9 -46
  35. package/src/client/persistence/indexeddb.ts +111 -84
  36. package/src/client/persistence/memory.ts +3 -3
  37. package/src/client/persistence/sqlite/browser.ts +168 -0
  38. package/src/client/persistence/sqlite/native.ts +29 -0
  39. package/src/client/persistence/sqlite/schema.ts +124 -0
  40. package/src/client/persistence/types.ts +32 -28
  41. package/src/client/prose-schema.ts +55 -0
  42. package/src/client/prose.ts +28 -25
  43. package/src/client/replicate.ts +5 -5
  44. package/src/client/services/cursor.ts +109 -0
  45. package/src/component/_generated/component.ts +31 -29
  46. package/src/component/convex.config.ts +2 -2
  47. package/src/component/logger.ts +7 -7
  48. package/src/component/public.ts +225 -237
  49. package/src/component/schema.ts +18 -15
  50. package/src/server/builder.ts +20 -7
  51. package/src/server/index.ts +3 -5
  52. package/src/server/schema.ts +5 -5
  53. package/src/server/storage.ts +113 -59
  54. package/src/shared/index.ts +5 -5
  55. package/src/shared/types.ts +51 -14
  56. package/dist/client/collection.d.ts +0 -96
  57. package/dist/client/errors.d.ts +0 -59
  58. package/dist/client/logger.d.ts +0 -2
  59. package/dist/client/merge.d.ts +0 -77
  60. package/dist/client/persistence/adapters/index.d.ts +0 -8
  61. package/dist/client/persistence/adapters/opsqlite.d.ts +0 -46
  62. package/dist/client/persistence/adapters/sqljs.d.ts +0 -83
  63. package/dist/client/persistence/index.d.ts +0 -49
  64. package/dist/client/persistence/indexeddb.d.ts +0 -17
  65. package/dist/client/persistence/memory.d.ts +0 -16
  66. package/dist/client/persistence/sqlite-browser.d.ts +0 -51
  67. package/dist/client/persistence/sqlite-level.d.ts +0 -63
  68. package/dist/client/persistence/sqlite-rn.d.ts +0 -36
  69. package/dist/client/persistence/sqlite.d.ts +0 -47
  70. package/dist/client/persistence/types.d.ts +0 -42
  71. package/dist/client/prose.d.ts +0 -56
  72. package/dist/client/replicate.d.ts +0 -40
  73. package/dist/client/services/checkpoint.d.ts +0 -18
  74. package/dist/client/services/reconciliation.d.ts +0 -24
  75. package/dist/index.js +0 -1618
  76. package/dist/server/builder.d.ts +0 -94
  77. package/dist/server/schema.d.ts +0 -27
  78. package/dist/server/storage.d.ts +0 -80
  79. package/dist/server.js +0 -281
  80. package/dist/shared/types.d.ts +0 -50
  81. package/dist/shared/types.js +0 -6
  82. package/dist/shared.js +0 -6
  83. package/src/client/persistence/adapters/index.ts +0 -8
  84. package/src/client/persistence/adapters/opsqlite.ts +0 -54
  85. package/src/client/persistence/adapters/sqljs.ts +0 -128
  86. package/src/client/persistence/sqlite-browser.ts +0 -107
  87. package/src/client/persistence/sqlite-level.ts +0 -407
  88. package/src/client/persistence/sqlite-rn.ts +0 -44
  89. package/src/client/persistence/sqlite.ts +0 -160
  90. package/src/client/services/checkpoint.ts +0 -86
  91. package/src/client/services/reconciliation.ts +0 -108
@@ -0,0 +1,55 @@
1
+ import { z } from "zod";
2
+ import type { ProseValue } from "$/shared/types";
3
+
4
+ const PROSE_MARKER = Symbol.for("replicate:prose");
5
+
6
+ function createProseSchema(): z.ZodType<ProseValue> {
7
+ const schema = z.custom<ProseValue>(
8
+ (val) => {
9
+ if (val == null) return true;
10
+ if (typeof val !== "object") return false;
11
+ return (val as { type?: string }).type === "doc";
12
+ },
13
+ { message: "Expected prose document with type \"doc\"" },
14
+ );
15
+
16
+ Object.defineProperty(schema, PROSE_MARKER, { value: true, writable: false });
17
+
18
+ return schema;
19
+ }
20
+
21
+ function emptyProse(): ProseValue {
22
+ return { type: "doc", content: [] } as unknown as ProseValue;
23
+ }
24
+
25
+ export function prose(): z.ZodType<ProseValue> {
26
+ return createProseSchema();
27
+ }
28
+
29
+ prose.empty = emptyProse;
30
+
31
+ export function isProseSchema(schema: unknown): boolean {
32
+ return (
33
+ schema != null
34
+ && typeof schema === "object"
35
+ && PROSE_MARKER in schema
36
+ && (schema as Record<symbol, unknown>)[PROSE_MARKER] === true
37
+ );
38
+ }
39
+
40
+ export function extractProseFields(schema: z.ZodObject<z.ZodRawShape>): string[] {
41
+ const fields: string[] = [];
42
+
43
+ for (const [key, fieldSchema] of Object.entries(schema.shape)) {
44
+ let unwrapped = fieldSchema;
45
+ while (unwrapped instanceof z.ZodOptional || unwrapped instanceof z.ZodNullable) {
46
+ unwrapped = unwrapped.unwrap();
47
+ }
48
+
49
+ if (isProseSchema(unwrapped)) {
50
+ fields.push(key);
51
+ }
52
+ }
53
+
54
+ return fields;
55
+ }
@@ -5,15 +5,15 @@
5
5
  * Uses document-level tracking to prevent race conditions.
6
6
  */
7
7
 
8
- import * as Y from 'yjs';
9
- import type { Collection } from '@tanstack/db';
10
- import { getLogger } from '$/client/logger.js';
11
- import { serializeYMapValue } from '$/client/merge.js';
8
+ import * as Y from "yjs";
9
+ import type { Collection } from "@tanstack/db";
10
+ import { getLogger } from "$/client/logger";
11
+ import { serializeYMapValue } from "$/client/merge";
12
12
 
13
13
  /** Server origin - changes from server should not trigger local sync */
14
- const SERVER_ORIGIN = 'server';
14
+ const SERVER_ORIGIN = "server";
15
15
 
16
- const logger = getLogger(['replicate', 'prose']);
16
+ const logger = getLogger(["replicate", "prose"]);
17
17
 
18
18
  // Default debounce time for prose sync
19
19
  const DEFAULT_DEBOUNCE_MS = 1000;
@@ -62,12 +62,13 @@ export function isApplyingFromServer(collection: string, documentId: string): bo
62
62
  export function setApplyingFromServer(
63
63
  collection: string,
64
64
  documentId: string,
65
- value: boolean
65
+ value: boolean,
66
66
  ): void {
67
67
  const key = `${collection}:${documentId}`;
68
68
  if (value) {
69
69
  applyingFromServer.set(key, true);
70
- } else {
70
+ }
71
+ else {
71
72
  applyingFromServer.delete(key);
72
73
  }
73
74
  }
@@ -89,8 +90,9 @@ function setPendingInternal(key: string, value: boolean): void {
89
90
  for (const cb of listeners) {
90
91
  try {
91
92
  cb(value);
92
- } catch (err) {
93
- logger.error('Pending listener error', { key, error: String(err) });
93
+ }
94
+ catch (err) {
95
+ logger.error("Pending listener error", { key, error: String(err) });
94
96
  }
95
97
  }
96
98
  }
@@ -110,7 +112,7 @@ export function isPending(collection: string, documentId: string): boolean {
110
112
  export function subscribePending(
111
113
  collection: string,
112
114
  documentId: string,
113
- callback: (pending: boolean) => void
115
+ callback: (pending: boolean) => void,
114
116
  ): () => void {
115
117
  const key = `${collection}:${documentId}`;
116
118
 
@@ -145,7 +147,7 @@ export function cancelPending(collection: string, documentId: string): void {
145
147
  clearTimeout(timer);
146
148
  debounceTimers.delete(key);
147
149
  setPendingInternal(key, false);
148
- logger.debug('Cancelled pending sync due to remote update', { collection, documentId });
150
+ logger.debug("Cancelled pending sync due to remote update", { collection, documentId });
149
151
  }
150
152
  }
151
153
 
@@ -162,7 +164,7 @@ export function cancelAllPending(collection: string): void {
162
164
  setPendingInternal(key, false);
163
165
  }
164
166
  }
165
- logger.debug('Cancelled all pending syncs', { collection });
167
+ logger.debug("Cancelled all pending syncs", { collection });
166
168
  }
167
169
 
168
170
  // ============================================================================
@@ -201,7 +203,7 @@ export function observeFragment(config: ProseObserverConfig): () => void {
201
203
  // Skip if already observing this document
202
204
  const existingCleanup = fragmentObservers.get(key);
203
205
  if (existingCleanup) {
204
- logger.debug('Fragment already being observed', { collection, documentId, field });
206
+ logger.debug("Fragment already being observed", { collection, documentId, field });
205
207
  return existingCleanup;
206
208
  }
207
209
 
@@ -224,7 +226,7 @@ export function observeFragment(config: ProseObserverConfig): () => void {
224
226
 
225
227
  const itemYMap = ymap.get(documentId) as Y.Map<unknown> | undefined;
226
228
  if (!itemYMap) {
227
- logger.error('Document not found', { collection, documentId });
229
+ logger.error("Document not found", { collection, documentId });
228
230
  setPendingInternal(key, false);
229
231
  return;
230
232
  }
@@ -237,7 +239,7 @@ export function observeFragment(config: ProseObserverConfig): () => void {
237
239
  : Y.encodeStateAsUpdateV2(ydoc);
238
240
 
239
241
  if (delta.length <= 2) {
240
- logger.debug('No changes to sync', { collection, documentId });
242
+ logger.debug("No changes to sync", { collection, documentId });
241
243
  setPendingInternal(key, false);
242
244
  return;
243
245
  }
@@ -245,7 +247,7 @@ export function observeFragment(config: ProseObserverConfig): () => void {
245
247
  const crdtBytes = delta.buffer as ArrayBuffer;
246
248
  const currentVector = Y.encodeStateVector(ydoc);
247
249
 
248
- logger.debug('Syncing prose delta', {
250
+ logger.debug("Syncing prose delta", {
249
251
  collection,
250
252
  documentId,
251
253
  deltaSize: delta.byteLength,
@@ -259,7 +261,7 @@ export function observeFragment(config: ProseObserverConfig): () => void {
259
261
  { metadata: { contentSync: { crdtBytes, materializedDoc } } },
260
262
  (draft: any) => {
261
263
  draft.updatedAt = Date.now();
262
- }
264
+ },
263
265
  );
264
266
  await result.isPersisted.promise;
265
267
 
@@ -267,9 +269,10 @@ export function observeFragment(config: ProseObserverConfig): () => void {
267
269
  lastSyncedVectors.set(key, currentVector);
268
270
  failedSyncQueue.delete(key);
269
271
  setPendingInternal(key, false);
270
- logger.debug('Prose sync completed', { collection, documentId });
271
- } catch (err) {
272
- logger.error('Prose sync failed, queued for retry', {
272
+ logger.debug("Prose sync completed", { collection, documentId });
273
+ }
274
+ catch (err) {
275
+ logger.error("Prose sync failed, queued for retry", {
273
276
  collection,
274
277
  documentId,
275
278
  error: String(err),
@@ -284,7 +287,7 @@ export function observeFragment(config: ProseObserverConfig): () => void {
284
287
  // Also retry any failed syncs for this document
285
288
  if (failedSyncQueue.has(key)) {
286
289
  failedSyncQueue.delete(key);
287
- logger.debug('Retrying failed sync', { collection, documentId });
290
+ logger.debug("Retrying failed sync", { collection, documentId });
288
291
  }
289
292
  };
290
293
 
@@ -296,11 +299,11 @@ export function observeFragment(config: ProseObserverConfig): () => void {
296
299
  cancelPending(collection, documentId);
297
300
  fragmentObservers.delete(key);
298
301
  lastSyncedVectors.delete(key);
299
- logger.debug('Fragment observer cleaned up', { collection, documentId, field });
302
+ logger.debug("Fragment observer cleaned up", { collection, documentId, field });
300
303
  };
301
304
 
302
305
  fragmentObservers.set(key, cleanup);
303
- logger.debug('Fragment observer registered', { collection, documentId, field });
306
+ logger.debug("Fragment observer registered", { collection, documentId, field });
304
307
 
305
308
  return cleanup;
306
309
  }
@@ -365,5 +368,5 @@ export function cleanup(collection: string): void {
365
368
  }
366
369
  }
367
370
 
368
- logger.debug('Prose cleanup complete', { collection });
371
+ logger.debug("Prose cleanup complete", { collection });
369
372
  }
@@ -7,7 +7,7 @@
7
7
 
8
8
  export interface ReplicateParams {
9
9
  readonly begin: () => void;
10
- readonly write: (message: { type: 'insert' | 'update' | 'delete'; value: unknown }) => void;
10
+ readonly write: (message: { type: "insert" | "update" | "delete"; value: unknown }) => void;
11
11
  readonly commit: () => void;
12
12
  readonly truncate: () => void;
13
13
  }
@@ -41,7 +41,7 @@ export function createReplicateOps<T>(params: ReplicateParams): BoundReplicateOp
41
41
  insert(items: T[]): void {
42
42
  params.begin();
43
43
  for (const item of items) {
44
- params.write({ type: 'insert', value: item });
44
+ params.write({ type: "insert", value: item });
45
45
  }
46
46
  params.commit();
47
47
  },
@@ -49,7 +49,7 @@ export function createReplicateOps<T>(params: ReplicateParams): BoundReplicateOp
49
49
  delete(items: T[]): void {
50
50
  params.begin();
51
51
  for (const item of items) {
52
- params.write({ type: 'delete', value: item });
52
+ params.write({ type: "delete", value: item });
53
53
  }
54
54
  params.commit();
55
55
  },
@@ -58,7 +58,7 @@ export function createReplicateOps<T>(params: ReplicateParams): BoundReplicateOp
58
58
  upsert(items: T[]): void {
59
59
  params.begin();
60
60
  for (const item of items) {
61
- params.write({ type: 'update', value: item });
61
+ params.write({ type: "update", value: item });
62
62
  }
63
63
  params.commit();
64
64
  },
@@ -67,7 +67,7 @@ export function createReplicateOps<T>(params: ReplicateParams): BoundReplicateOp
67
67
  params.begin();
68
68
  params.truncate();
69
69
  for (const item of items) {
70
- params.write({ type: 'insert', value: item });
70
+ params.write({ type: "insert", value: item });
71
71
  }
72
72
  params.commit();
73
73
  },
@@ -0,0 +1,109 @@
1
+ import { Effect, Context, Layer } from "effect";
2
+ import { IDBError, IDBWriteError } from "$/client/errors";
3
+ import type { KeyValueStore } from "$/client/persistence/types";
4
+
5
+ export type Cursor = number;
6
+
7
+ export class CursorService extends Context.Tag("CursorService")<
8
+ CursorService,
9
+ {
10
+ readonly loadCursor: (collection: string) => Effect.Effect<Cursor, IDBError>;
11
+ readonly saveCursor: (collection: string, cursor: Cursor) => Effect.Effect<void, IDBWriteError>;
12
+ readonly clearCursor: (collection: string) => Effect.Effect<void, IDBError>;
13
+ readonly loadPeerId: (collection: string) => Effect.Effect<string, IDBError | IDBWriteError>;
14
+ }
15
+ >() {}
16
+
17
+ function generatePeerId(): string {
18
+ return crypto.randomUUID();
19
+ }
20
+
21
+ export function createCursorLayer(kv: KeyValueStore) {
22
+ return Layer.succeed(
23
+ CursorService,
24
+ CursorService.of({
25
+ loadCursor: collection =>
26
+ Effect.gen(function* (_) {
27
+ const key = `cursor:${collection}`;
28
+ const stored = yield* _(
29
+ Effect.tryPromise({
30
+ try: () => kv.get<Cursor>(key),
31
+ catch: cause => new IDBError({ operation: "get", key, cause }),
32
+ }),
33
+ );
34
+
35
+ if (stored !== undefined) {
36
+ yield* _(
37
+ Effect.logDebug("Loaded cursor from storage", {
38
+ collection,
39
+ cursor: stored,
40
+ }),
41
+ );
42
+ return stored;
43
+ }
44
+
45
+ yield* _(
46
+ Effect.logDebug("No stored cursor, using default", {
47
+ collection,
48
+ }),
49
+ );
50
+ return 0;
51
+ }),
52
+
53
+ saveCursor: (collection, cursor) =>
54
+ Effect.gen(function* (_) {
55
+ const key = `cursor:${collection}`;
56
+ yield* _(
57
+ Effect.tryPromise({
58
+ try: () => kv.set(key, cursor),
59
+ catch: cause => new IDBWriteError({ key, value: cursor, cause }),
60
+ }),
61
+ );
62
+ yield* _(
63
+ Effect.logDebug("Cursor saved", {
64
+ collection,
65
+ cursor,
66
+ }),
67
+ );
68
+ }),
69
+
70
+ clearCursor: collection =>
71
+ Effect.gen(function* (_) {
72
+ const key = `cursor:${collection}`;
73
+ yield* _(
74
+ Effect.tryPromise({
75
+ try: () => kv.del(key),
76
+ catch: cause => new IDBError({ operation: "delete", key, cause }),
77
+ }),
78
+ );
79
+ yield* _(Effect.logDebug("Cursor cleared", { collection }));
80
+ }),
81
+
82
+ loadPeerId: collection =>
83
+ Effect.gen(function* (_) {
84
+ const key = `peerId:${collection}`;
85
+ const stored = yield* _(
86
+ Effect.tryPromise({
87
+ try: () => kv.get<string>(key),
88
+ catch: cause => new IDBError({ operation: "get", key, cause }),
89
+ }),
90
+ );
91
+
92
+ if (stored) {
93
+ yield* _(Effect.logDebug("Loaded peerId from storage", { collection, peerId: stored }));
94
+ return stored;
95
+ }
96
+
97
+ const newPeerId = generatePeerId();
98
+ yield* _(
99
+ Effect.tryPromise({
100
+ try: () => kv.set(key, newPeerId),
101
+ catch: cause => new IDBWriteError({ key, value: newPeerId, cause }),
102
+ }),
103
+ );
104
+ yield* _(Effect.logDebug("Generated new peerId", { collection, peerId: newPeerId }));
105
+ return newPeerId;
106
+ }),
107
+ }),
108
+ );
109
+ }
@@ -24,64 +24,72 @@ import type { FunctionReference } from "convex/server";
24
24
  export type ComponentApi<Name extends string | undefined = string | undefined> =
25
25
  {
26
26
  public: {
27
- deleteDocument: FunctionReference<
27
+ compact: FunctionReference<
28
28
  "mutation",
29
29
  "internal",
30
30
  {
31
31
  collection: string;
32
- crdtBytes: ArrayBuffer;
33
32
  documentId: string;
34
- threshold?: number;
35
- version: number;
33
+ peerTimeout?: number;
34
+ snapshotBytes: ArrayBuffer;
35
+ stateVector: ArrayBuffer;
36
36
  },
37
- { compacted?: boolean; success: boolean },
37
+ { removed: number; retained: number; success: boolean },
38
+ Name
39
+ >;
40
+ deleteDocument: FunctionReference<
41
+ "mutation",
42
+ "internal",
43
+ { collection: string; crdtBytes: ArrayBuffer; documentId: string },
44
+ { seq: number; success: boolean },
38
45
  Name
39
46
  >;
40
47
  getInitialState: FunctionReference<
41
48
  "query",
42
49
  "internal",
43
50
  { collection: string },
44
- { checkpoint: { lastModified: number }; crdtBytes: ArrayBuffer } | null,
51
+ { crdtBytes: ArrayBuffer; cursor: number } | null,
45
52
  Name
46
53
  >;
47
54
  insertDocument: FunctionReference<
48
55
  "mutation",
49
56
  "internal",
50
- {
51
- collection: string;
52
- crdtBytes: ArrayBuffer;
53
- documentId: string;
54
- threshold?: number;
55
- version: number;
56
- },
57
- { compacted?: boolean; success: boolean },
57
+ { collection: string; crdtBytes: ArrayBuffer; documentId: string },
58
+ { seq: number; success: boolean },
59
+ Name
60
+ >;
61
+ mark: FunctionReference<
62
+ "mutation",
63
+ "internal",
64
+ { collection: string; peerId: string; syncedSeq: number },
65
+ null,
58
66
  Name
59
67
  >;
60
68
  recovery: FunctionReference<
61
69
  "query",
62
70
  "internal",
63
71
  { clientStateVector: ArrayBuffer; collection: string },
64
- { diff?: ArrayBuffer; serverStateVector: ArrayBuffer },
72
+ { cursor: number; diff?: ArrayBuffer; serverStateVector: ArrayBuffer },
65
73
  Name
66
74
  >;
67
75
  stream: FunctionReference<
68
76
  "query",
69
77
  "internal",
70
78
  {
71
- checkpoint: { lastModified: number };
72
79
  collection: string;
80
+ cursor: number;
73
81
  limit?: number;
74
- vector?: ArrayBuffer;
82
+ sizeThreshold?: number;
75
83
  },
76
84
  {
77
85
  changes: Array<{
78
86
  crdtBytes: ArrayBuffer;
79
- documentId?: string;
87
+ documentId: string;
80
88
  operationType: string;
81
- timestamp: number;
82
- version: number;
89
+ seq: number;
83
90
  }>;
84
- checkpoint: { lastModified: number };
91
+ compact?: string;
92
+ cursor: number;
85
93
  hasMore: boolean;
86
94
  },
87
95
  Name
@@ -89,14 +97,8 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
89
97
  updateDocument: FunctionReference<
90
98
  "mutation",
91
99
  "internal",
92
- {
93
- collection: string;
94
- crdtBytes: ArrayBuffer;
95
- documentId: string;
96
- threshold?: number;
97
- version: number;
98
- },
99
- { compacted?: boolean; success: boolean },
100
+ { collection: string; crdtBytes: ArrayBuffer; documentId: string },
101
+ { seq: number; success: boolean },
100
102
  Name
101
103
  >;
102
104
  };
@@ -1,5 +1,5 @@
1
- import { defineComponent } from 'convex/server';
1
+ import { defineComponent } from "convex/server";
2
2
 
3
- const component = defineComponent('replicate');
3
+ const component = defineComponent("replicate");
4
4
 
5
5
  export default component;
@@ -9,28 +9,28 @@ class ComponentLogger implements Logger {
9
9
  constructor(private category: string[]) {}
10
10
 
11
11
  private format(level: string, message: string, context?: Record<string, any>): string {
12
- const prefix = `[${this.category.join(':')}]`;
13
- const contextStr = context ? ` ${JSON.stringify(context)}` : '';
12
+ const prefix = `[${this.category.join(":")}]`;
13
+ const contextStr = context ? ` ${JSON.stringify(context)}` : "";
14
14
  return `${prefix} ${level}: ${message}${contextStr}`;
15
15
  }
16
16
 
17
17
  debug(message: string, context?: Record<string, any>): void {
18
- console.log(this.format('DEBUG', message, context));
18
+ console.log(this.format("DEBUG", message, context));
19
19
  }
20
20
 
21
21
  info(message: string, context?: Record<string, any>): void {
22
- console.log(this.format('INFO', message, context));
22
+ console.log(this.format("INFO", message, context));
23
23
  }
24
24
 
25
25
  warn(message: string, context?: Record<string, any>): void {
26
- console.warn(this.format('WARN', message, context));
26
+ console.warn(this.format("WARN", message, context));
27
27
  }
28
28
 
29
29
  error(message: string, context?: Record<string, any>): void {
30
- console.error(this.format('ERROR', message, context));
30
+ console.error(this.format("ERROR", message, context));
31
31
  }
32
32
  }
33
33
 
34
34
  export function getLogger(category: string[]): Logger {
35
- return new ComponentLogger(['component', ...category]);
35
+ return new ComponentLogger(["component", ...category]);
36
36
  }