@fractary/core 0.3.2 → 0.4.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 (122) hide show
  1. package/dist/__tests__/factories.test.d.ts +5 -0
  2. package/dist/__tests__/factories.test.d.ts.map +1 -0
  3. package/dist/__tests__/factories.test.js +66 -0
  4. package/dist/__tests__/factories.test.js.map +1 -0
  5. package/dist/auth/__tests__/create-token-provider.test.d.ts +5 -0
  6. package/dist/auth/__tests__/create-token-provider.test.d.ts.map +1 -0
  7. package/dist/auth/__tests__/create-token-provider.test.js +104 -0
  8. package/dist/auth/__tests__/create-token-provider.test.js.map +1 -0
  9. package/dist/auth/__tests__/github-app-auth.test.d.ts +5 -0
  10. package/dist/auth/__tests__/github-app-auth.test.d.ts.map +1 -0
  11. package/dist/auth/__tests__/github-app-auth.test.js +293 -0
  12. package/dist/auth/__tests__/github-app-auth.test.js.map +1 -0
  13. package/dist/auth/__tests__/static-token-provider.test.d.ts +5 -0
  14. package/dist/auth/__tests__/static-token-provider.test.d.ts.map +1 -0
  15. package/dist/auth/__tests__/static-token-provider.test.js +54 -0
  16. package/dist/auth/__tests__/static-token-provider.test.js.map +1 -0
  17. package/dist/auth/github-app-auth.d.ts +109 -0
  18. package/dist/auth/github-app-auth.d.ts.map +1 -0
  19. package/dist/auth/github-app-auth.js +262 -0
  20. package/dist/auth/github-app-auth.js.map +1 -0
  21. package/dist/auth/github-app-token-provider.d.ts +59 -0
  22. package/dist/auth/github-app-token-provider.d.ts.map +1 -0
  23. package/dist/auth/github-app-token-provider.js +68 -0
  24. package/dist/auth/github-app-token-provider.js.map +1 -0
  25. package/dist/auth/index.d.ts +45 -0
  26. package/dist/auth/index.d.ts.map +1 -0
  27. package/dist/auth/index.js +74 -0
  28. package/dist/auth/index.js.map +1 -0
  29. package/dist/auth/static-token-provider.d.ts +35 -0
  30. package/dist/auth/static-token-provider.d.ts.map +1 -0
  31. package/dist/auth/static-token-provider.js +45 -0
  32. package/dist/auth/static-token-provider.js.map +1 -0
  33. package/dist/auth/types.d.ts +49 -0
  34. package/dist/auth/types.d.ts.map +1 -0
  35. package/dist/auth/types.js +8 -0
  36. package/dist/auth/types.js.map +1 -0
  37. package/dist/common/yaml-config.d.ts +10 -0
  38. package/dist/common/yaml-config.d.ts.map +1 -1
  39. package/dist/common/yaml-config.js.map +1 -1
  40. package/dist/config/__tests__/loader.test.d.ts +5 -0
  41. package/dist/config/__tests__/loader.test.d.ts.map +1 -0
  42. package/dist/config/__tests__/loader.test.js +129 -0
  43. package/dist/config/__tests__/loader.test.js.map +1 -0
  44. package/dist/config/index.d.ts +8 -0
  45. package/dist/config/index.d.ts.map +1 -0
  46. package/dist/config/index.js +27 -0
  47. package/dist/config/index.js.map +1 -0
  48. package/dist/config/loader.d.ts +126 -0
  49. package/dist/config/loader.d.ts.map +1 -0
  50. package/dist/config/loader.js +277 -0
  51. package/dist/config/loader.js.map +1 -0
  52. package/dist/docs/index.d.ts +5 -0
  53. package/dist/docs/index.d.ts.map +1 -1
  54. package/dist/docs/index.js +6 -1
  55. package/dist/docs/index.js.map +1 -1
  56. package/dist/docs/manager.d.ts +27 -0
  57. package/dist/docs/manager.d.ts.map +1 -1
  58. package/dist/docs/manager.js +168 -15
  59. package/dist/docs/manager.js.map +1 -1
  60. package/dist/docs/type-registry.d.ts +123 -0
  61. package/dist/docs/type-registry.d.ts.map +1 -0
  62. package/dist/docs/type-registry.js +393 -0
  63. package/dist/docs/type-registry.js.map +1 -0
  64. package/dist/docs/types.d.ts +93 -0
  65. package/dist/docs/types.d.ts.map +1 -1
  66. package/dist/factories.d.ts +89 -0
  67. package/dist/factories.d.ts.map +1 -0
  68. package/dist/factories.js +228 -0
  69. package/dist/factories.js.map +1 -0
  70. package/dist/file/factory.d.ts +41 -0
  71. package/dist/file/factory.d.ts.map +1 -0
  72. package/dist/file/factory.js +237 -0
  73. package/dist/file/factory.js.map +1 -0
  74. package/dist/file/gcs.d.ts +66 -0
  75. package/dist/file/gcs.d.ts.map +1 -0
  76. package/dist/file/gcs.js +226 -0
  77. package/dist/file/gcs.js.map +1 -0
  78. package/dist/file/gdrive.d.ts +78 -0
  79. package/dist/file/gdrive.d.ts.map +1 -0
  80. package/dist/file/gdrive.js +302 -0
  81. package/dist/file/gdrive.js.map +1 -0
  82. package/dist/file/index.d.ts +13 -1
  83. package/dist/file/index.d.ts.map +1 -1
  84. package/dist/file/index.js +25 -1
  85. package/dist/file/index.js.map +1 -1
  86. package/dist/file/manager.d.ts +83 -2
  87. package/dist/file/manager.d.ts.map +1 -1
  88. package/dist/file/manager.js +125 -4
  89. package/dist/file/manager.js.map +1 -1
  90. package/dist/file/r2.d.ts +56 -0
  91. package/dist/file/r2.d.ts.map +1 -0
  92. package/dist/file/r2.js +96 -0
  93. package/dist/file/r2.js.map +1 -0
  94. package/dist/file/s3.d.ts +61 -0
  95. package/dist/file/s3.d.ts.map +1 -0
  96. package/dist/file/s3.js +258 -0
  97. package/dist/file/s3.js.map +1 -0
  98. package/dist/file/types.d.ts +145 -2
  99. package/dist/file/types.d.ts.map +1 -1
  100. package/dist/index.d.ts +3 -0
  101. package/dist/index.d.ts.map +1 -1
  102. package/dist/index.js +6 -0
  103. package/dist/index.js.map +1 -1
  104. package/dist/logs/index.d.ts +1 -0
  105. package/dist/logs/index.d.ts.map +1 -1
  106. package/dist/logs/index.js +3 -1
  107. package/dist/logs/index.js.map +1 -1
  108. package/dist/logs/manager.d.ts +29 -2
  109. package/dist/logs/manager.d.ts.map +1 -1
  110. package/dist/logs/manager.js +48 -7
  111. package/dist/logs/manager.js.map +1 -1
  112. package/dist/logs/type-registry.d.ts +180 -0
  113. package/dist/logs/type-registry.d.ts.map +1 -0
  114. package/dist/logs/type-registry.js +421 -0
  115. package/dist/logs/type-registry.js.map +1 -0
  116. package/dist/logs/type-registry.test.d.ts +5 -0
  117. package/dist/logs/type-registry.test.d.ts.map +1 -0
  118. package/dist/logs/type-registry.test.js +671 -0
  119. package/dist/logs/type-registry.test.js.map +1 -0
  120. package/dist/logs/types.d.ts +2 -0
  121. package/dist/logs/types.d.ts.map +1 -1
  122. package/package.json +62 -8
@@ -0,0 +1,671 @@
1
+ "use strict";
2
+ /**
3
+ * Tests for LogTypeRegistry
4
+ */
5
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ var desc = Object.getOwnPropertyDescriptor(m, k);
8
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
+ desc = { enumerable: true, get: function() { return m[k]; } };
10
+ }
11
+ Object.defineProperty(o, k2, desc);
12
+ }) : (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ o[k2] = m[k];
15
+ }));
16
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
18
+ }) : function(o, v) {
19
+ o["default"] = v;
20
+ });
21
+ var __importStar = (this && this.__importStar) || (function () {
22
+ var ownKeys = function(o) {
23
+ ownKeys = Object.getOwnPropertyNames || function (o) {
24
+ var ar = [];
25
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
26
+ return ar;
27
+ };
28
+ return ownKeys(o);
29
+ };
30
+ return function (mod) {
31
+ if (mod && mod.__esModule) return mod;
32
+ var result = {};
33
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
34
+ __setModuleDefault(result, mod);
35
+ return result;
36
+ };
37
+ })();
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ const fs = __importStar(require("fs"));
40
+ const path = __importStar(require("path"));
41
+ const os = __importStar(require("os"));
42
+ const type_registry_1 = require("./type-registry");
43
+ describe('LogTypeRegistry', () => {
44
+ let tempDir;
45
+ let originalCwd;
46
+ beforeEach(() => {
47
+ // Create temp directory for tests
48
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'fractary-log-registry-test-'));
49
+ originalCwd = process.cwd();
50
+ });
51
+ afterEach(() => {
52
+ // Restore original state
53
+ process.chdir(originalCwd);
54
+ // Clean up temp directory
55
+ if (fs.existsSync(tempDir)) {
56
+ fs.rmSync(tempDir, { recursive: true, force: true });
57
+ }
58
+ });
59
+ /**
60
+ * Helper to create a minimal log type directory
61
+ */
62
+ function createLogTypeDirectory(basePath, id, overrides) {
63
+ const typeDir = path.join(basePath, id);
64
+ fs.mkdirSync(typeDir, { recursive: true });
65
+ const displayName = overrides?.displayName || `${id.charAt(0).toUpperCase()}${id.slice(1)} Log`;
66
+ const description = overrides?.description || `Description for ${id}`;
67
+ // Write type.yaml
68
+ const typeYaml = `
69
+ id: ${id}
70
+ display_name: "${displayName}"
71
+ description: "${description}"
72
+ output_path: .fractary/logs/${id}
73
+ file_naming:
74
+ pattern: "{date}-{id}.md"
75
+ date_format: "YYYYMMDD"
76
+ slug_source: id
77
+ slug_max_length: 50
78
+ frontmatter:
79
+ required_fields:
80
+ - log_type
81
+ - title
82
+ - date
83
+ - status
84
+ optional_fields:
85
+ - tags
86
+ defaults:
87
+ log_type: ${id}
88
+ status: active
89
+ structure:
90
+ required_sections:
91
+ - Content
92
+ optional_sections:
93
+ - Notes
94
+ status:
95
+ allowed_values:
96
+ - active
97
+ - completed
98
+ - archived
99
+ default: active
100
+ retention:
101
+ default_local_days: 30
102
+ default_cloud_days: 90
103
+ auto_archive: true
104
+ cleanup_after_archive: false
105
+ `;
106
+ fs.writeFileSync(path.join(typeDir, 'type.yaml'), typeYaml);
107
+ // Write template.md
108
+ const template = overrides?.template || `---
109
+ log_type: {{log_type}}
110
+ title: {{title}}
111
+ date: {{date}}
112
+ status: {{status}}
113
+ ---
114
+
115
+ # {{title}}
116
+
117
+ ## Content
118
+
119
+ {{content}}
120
+ `;
121
+ fs.writeFileSync(path.join(typeDir, 'template.md'), template);
122
+ // Write standards.md
123
+ const standards = overrides?.standards || `# ${displayName} Standards
124
+
125
+ Best practices for ${id} logs.
126
+ `;
127
+ fs.writeFileSync(path.join(typeDir, 'standards.md'), standards);
128
+ }
129
+ /**
130
+ * Helper to create a manifest file
131
+ */
132
+ function createManifest(basePath, types) {
133
+ const manifest = `
134
+ version: "1.0"
135
+ base_url: https://example.com/logs
136
+ log_types:
137
+ ${types.map((t) => ` - id: ${t.id}
138
+ display_name: "${t.displayName}"
139
+ description: "${t.description}"
140
+ path: ./${t.id}`).join('\n')}
141
+ `;
142
+ fs.writeFileSync(path.join(basePath, 'manifest.yaml'), manifest);
143
+ }
144
+ describe('constructor', () => {
145
+ it('should warn if specified coreTypesPath has no manifest', () => {
146
+ // Test that a non-existent coreTypesPath logs a warning
147
+ // (it doesn't throw because loadCoreTypes catches errors)
148
+ const nonExistentPath = path.join(tempDir, 'non-existent-types');
149
+ const consoleSpy = jest.spyOn(console, 'warn').mockImplementation();
150
+ const registry = new type_registry_1.LogTypeRegistry({ coreTypesPath: nonExistentPath });
151
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('manifest not found'));
152
+ expect(registry.getCoreTypes()).toEqual([]);
153
+ consoleSpy.mockRestore();
154
+ });
155
+ it('should not throw when skipCoreTypes is true', () => {
156
+ process.chdir(tempDir);
157
+ const registry = new type_registry_1.LogTypeRegistry({ skipCoreTypes: true });
158
+ expect(registry.getAllTypes()).toEqual([]);
159
+ });
160
+ it('should load from explicit coreTypesPath', () => {
161
+ // Create test types directory
162
+ const typesDir = path.join(tempDir, 'types');
163
+ fs.mkdirSync(typesDir, { recursive: true });
164
+ createLogTypeDirectory(typesDir, 'test-type');
165
+ createManifest(typesDir, [
166
+ { id: 'test-type', displayName: 'Test Type', description: 'A test type' },
167
+ ]);
168
+ const registry = new type_registry_1.LogTypeRegistry({ coreTypesPath: typesDir });
169
+ expect(registry.hasType('test-type')).toBe(true);
170
+ expect(registry.getAllTypes().length).toBe(1);
171
+ });
172
+ it('should use provided baseDir for custom types', () => {
173
+ const typesDir = path.join(tempDir, 'core-types');
174
+ const customDir = path.join(tempDir, 'custom-types');
175
+ fs.mkdirSync(typesDir, { recursive: true });
176
+ fs.mkdirSync(customDir, { recursive: true });
177
+ // Create core type
178
+ createLogTypeDirectory(typesDir, 'core');
179
+ createManifest(typesDir, [{ id: 'core', displayName: 'Core', description: 'Core type' }]);
180
+ // Create custom type in custom directory
181
+ createLogTypeDirectory(customDir, 'custom');
182
+ const registry = new type_registry_1.LogTypeRegistry({
183
+ coreTypesPath: typesDir,
184
+ baseDir: tempDir,
185
+ customTypes: [{ id: 'custom', path: 'custom-types/custom' }],
186
+ });
187
+ expect(registry.hasType('core')).toBe(true);
188
+ expect(registry.hasType('custom')).toBe(true);
189
+ });
190
+ });
191
+ describe('loadCoreTypes', () => {
192
+ it('should load all types from manifest', () => {
193
+ const typesDir = path.join(tempDir, 'types');
194
+ fs.mkdirSync(typesDir, { recursive: true });
195
+ createLogTypeDirectory(typesDir, 'type-a');
196
+ createLogTypeDirectory(typesDir, 'type-b');
197
+ createLogTypeDirectory(typesDir, 'type-c');
198
+ createManifest(typesDir, [
199
+ { id: 'type-a', displayName: 'Type A', description: 'First type' },
200
+ { id: 'type-b', displayName: 'Type B', description: 'Second type' },
201
+ { id: 'type-c', displayName: 'Type C', description: 'Third type' },
202
+ ]);
203
+ const registry = new type_registry_1.LogTypeRegistry({ coreTypesPath: typesDir });
204
+ expect(registry.getCoreTypes().length).toBe(3);
205
+ expect(registry.hasType('type-a')).toBe(true);
206
+ expect(registry.hasType('type-b')).toBe(true);
207
+ expect(registry.hasType('type-c')).toBe(true);
208
+ });
209
+ it('should skip types with missing directories', () => {
210
+ const typesDir = path.join(tempDir, 'types');
211
+ fs.mkdirSync(typesDir, { recursive: true });
212
+ createLogTypeDirectory(typesDir, 'existing');
213
+ // Note: 'missing' is in manifest but has no directory
214
+ createManifest(typesDir, [
215
+ { id: 'existing', displayName: 'Existing', description: 'Exists' },
216
+ { id: 'missing', displayName: 'Missing', description: 'Does not exist' },
217
+ ]);
218
+ const consoleSpy = jest.spyOn(console, 'warn').mockImplementation();
219
+ const registry = new type_registry_1.LogTypeRegistry({ coreTypesPath: typesDir });
220
+ expect(registry.hasType('existing')).toBe(true);
221
+ expect(registry.hasType('missing')).toBe(false);
222
+ expect(consoleSpy).toHaveBeenCalled();
223
+ consoleSpy.mockRestore();
224
+ });
225
+ });
226
+ describe('getType', () => {
227
+ it('should return type definition by id', () => {
228
+ const typesDir = path.join(tempDir, 'types');
229
+ fs.mkdirSync(typesDir, { recursive: true });
230
+ createLogTypeDirectory(typesDir, 'session', {
231
+ displayName: 'Session Log',
232
+ description: 'Claude Code sessions',
233
+ });
234
+ createManifest(typesDir, [
235
+ { id: 'session', displayName: 'Session Log', description: 'Claude Code sessions' },
236
+ ]);
237
+ const registry = new type_registry_1.LogTypeRegistry({ coreTypesPath: typesDir });
238
+ const logType = registry.getType('session');
239
+ expect(logType).not.toBeNull();
240
+ expect(logType?.id).toBe('session');
241
+ expect(logType?.displayName).toBe('Session Log');
242
+ expect(logType?.description).toBe('Claude Code sessions');
243
+ });
244
+ it('should return null for unknown type', () => {
245
+ const typesDir = path.join(tempDir, 'types');
246
+ fs.mkdirSync(typesDir, { recursive: true });
247
+ createLogTypeDirectory(typesDir, 'known');
248
+ createManifest(typesDir, [{ id: 'known', displayName: 'Known', description: 'Known type' }]);
249
+ const registry = new type_registry_1.LogTypeRegistry({ coreTypesPath: typesDir });
250
+ expect(registry.getType('unknown')).toBeNull();
251
+ });
252
+ it('should prefer custom type over core type with same id', () => {
253
+ const typesDir = path.join(tempDir, 'core-types');
254
+ const customDir = path.join(tempDir, 'custom-types');
255
+ fs.mkdirSync(typesDir, { recursive: true });
256
+ fs.mkdirSync(customDir, { recursive: true });
257
+ // Create core type
258
+ createLogTypeDirectory(typesDir, 'override', {
259
+ displayName: 'Core Override',
260
+ description: 'Core version',
261
+ });
262
+ createManifest(typesDir, [
263
+ { id: 'override', displayName: 'Core Override', description: 'Core version' },
264
+ ]);
265
+ // Create custom type with same id
266
+ createLogTypeDirectory(customDir, 'override', {
267
+ displayName: 'Custom Override',
268
+ description: 'Custom version',
269
+ });
270
+ const registry = new type_registry_1.LogTypeRegistry({
271
+ coreTypesPath: typesDir,
272
+ baseDir: tempDir,
273
+ customTypes: [{ id: 'override', path: 'custom-types/override' }],
274
+ });
275
+ const logType = registry.getType('override');
276
+ expect(logType?.displayName).toBe('Custom Override');
277
+ expect(logType?.description).toBe('Custom version');
278
+ });
279
+ });
280
+ describe('hasType', () => {
281
+ it('should return true for existing core type', () => {
282
+ const typesDir = path.join(tempDir, 'types');
283
+ fs.mkdirSync(typesDir, { recursive: true });
284
+ createLogTypeDirectory(typesDir, 'session');
285
+ createManifest(typesDir, [
286
+ { id: 'session', displayName: 'Session', description: 'Session type' },
287
+ ]);
288
+ const registry = new type_registry_1.LogTypeRegistry({ coreTypesPath: typesDir });
289
+ expect(registry.hasType('session')).toBe(true);
290
+ });
291
+ it('should return true for existing custom type', () => {
292
+ const typesDir = path.join(tempDir, 'core-types');
293
+ const customDir = path.join(tempDir, 'custom-types');
294
+ fs.mkdirSync(typesDir, { recursive: true });
295
+ fs.mkdirSync(customDir, { recursive: true });
296
+ createLogTypeDirectory(typesDir, 'core');
297
+ createManifest(typesDir, [{ id: 'core', displayName: 'Core', description: 'Core type' }]);
298
+ createLogTypeDirectory(customDir, 'custom');
299
+ const registry = new type_registry_1.LogTypeRegistry({
300
+ coreTypesPath: typesDir,
301
+ baseDir: tempDir,
302
+ customTypes: [{ id: 'custom', path: 'custom-types/custom' }],
303
+ });
304
+ expect(registry.hasType('custom')).toBe(true);
305
+ });
306
+ it('should return false for non-existent type', () => {
307
+ const registry = new type_registry_1.LogTypeRegistry({ skipCoreTypes: true });
308
+ expect(registry.hasType('nonexistent')).toBe(false);
309
+ });
310
+ });
311
+ describe('getAllTypes', () => {
312
+ it('should return combined core and custom types sorted by id', () => {
313
+ const typesDir = path.join(tempDir, 'core-types');
314
+ const customDir = path.join(tempDir, 'custom-types');
315
+ fs.mkdirSync(typesDir, { recursive: true });
316
+ fs.mkdirSync(customDir, { recursive: true });
317
+ createLogTypeDirectory(typesDir, 'beta');
318
+ createLogTypeDirectory(typesDir, 'alpha');
319
+ createManifest(typesDir, [
320
+ { id: 'beta', displayName: 'Beta', description: 'Beta type' },
321
+ { id: 'alpha', displayName: 'Alpha', description: 'Alpha type' },
322
+ ]);
323
+ createLogTypeDirectory(customDir, 'gamma');
324
+ const registry = new type_registry_1.LogTypeRegistry({
325
+ coreTypesPath: typesDir,
326
+ baseDir: tempDir,
327
+ customTypes: [{ id: 'gamma', path: 'custom-types/gamma' }],
328
+ });
329
+ const types = registry.getAllTypes();
330
+ expect(types.length).toBe(3);
331
+ expect(types[0].id).toBe('alpha');
332
+ expect(types[1].id).toBe('beta');
333
+ expect(types[2].id).toBe('gamma');
334
+ });
335
+ it('should override core type with custom type of same id', () => {
336
+ const typesDir = path.join(tempDir, 'core-types');
337
+ const customDir = path.join(tempDir, 'custom-types');
338
+ fs.mkdirSync(typesDir, { recursive: true });
339
+ fs.mkdirSync(customDir, { recursive: true });
340
+ createLogTypeDirectory(typesDir, 'shared', { description: 'Core description' });
341
+ createManifest(typesDir, [
342
+ { id: 'shared', displayName: 'Shared', description: 'Core description' },
343
+ ]);
344
+ createLogTypeDirectory(customDir, 'shared', { description: 'Custom description' });
345
+ const registry = new type_registry_1.LogTypeRegistry({
346
+ coreTypesPath: typesDir,
347
+ baseDir: tempDir,
348
+ customTypes: [{ id: 'shared', path: 'custom-types/shared' }],
349
+ });
350
+ const types = registry.getAllTypes();
351
+ expect(types.length).toBe(1);
352
+ expect(types[0].description).toBe('Custom description');
353
+ });
354
+ });
355
+ describe('getCoreTypes', () => {
356
+ it('should return only core types sorted by id', () => {
357
+ const typesDir = path.join(tempDir, 'core-types');
358
+ const customDir = path.join(tempDir, 'custom-types');
359
+ fs.mkdirSync(typesDir, { recursive: true });
360
+ fs.mkdirSync(customDir, { recursive: true });
361
+ createLogTypeDirectory(typesDir, 'core-b');
362
+ createLogTypeDirectory(typesDir, 'core-a');
363
+ createManifest(typesDir, [
364
+ { id: 'core-b', displayName: 'Core B', description: 'Core B' },
365
+ { id: 'core-a', displayName: 'Core A', description: 'Core A' },
366
+ ]);
367
+ createLogTypeDirectory(customDir, 'custom');
368
+ const registry = new type_registry_1.LogTypeRegistry({
369
+ coreTypesPath: typesDir,
370
+ baseDir: tempDir,
371
+ customTypes: [{ id: 'custom', path: 'custom-types/custom' }],
372
+ });
373
+ const coreTypes = registry.getCoreTypes();
374
+ expect(coreTypes.length).toBe(2);
375
+ expect(coreTypes[0].id).toBe('core-a');
376
+ expect(coreTypes[1].id).toBe('core-b');
377
+ });
378
+ });
379
+ describe('getCustomTypes', () => {
380
+ it('should return only custom types sorted by id', () => {
381
+ const typesDir = path.join(tempDir, 'core-types');
382
+ const customDir = path.join(tempDir, 'custom-types');
383
+ fs.mkdirSync(typesDir, { recursive: true });
384
+ fs.mkdirSync(customDir, { recursive: true });
385
+ createLogTypeDirectory(typesDir, 'core');
386
+ createManifest(typesDir, [{ id: 'core', displayName: 'Core', description: 'Core' }]);
387
+ createLogTypeDirectory(customDir, 'custom-b');
388
+ createLogTypeDirectory(customDir, 'custom-a');
389
+ const registry = new type_registry_1.LogTypeRegistry({
390
+ coreTypesPath: typesDir,
391
+ baseDir: tempDir,
392
+ customTypes: [
393
+ { id: 'custom-b', path: 'custom-types/custom-b' },
394
+ { id: 'custom-a', path: 'custom-types/custom-a' },
395
+ ],
396
+ });
397
+ const customTypes = registry.getCustomTypes();
398
+ expect(customTypes.length).toBe(2);
399
+ expect(customTypes[0].id).toBe('custom-a');
400
+ expect(customTypes[1].id).toBe('custom-b');
401
+ });
402
+ });
403
+ describe('getTypeIds', () => {
404
+ it('should return unique sorted type ids', () => {
405
+ const typesDir = path.join(tempDir, 'core-types');
406
+ const customDir = path.join(tempDir, 'custom-types');
407
+ fs.mkdirSync(typesDir, { recursive: true });
408
+ fs.mkdirSync(customDir, { recursive: true });
409
+ createLogTypeDirectory(typesDir, 'gamma');
410
+ createLogTypeDirectory(typesDir, 'alpha');
411
+ createManifest(typesDir, [
412
+ { id: 'gamma', displayName: 'Gamma', description: 'Gamma' },
413
+ { id: 'alpha', displayName: 'Alpha', description: 'Alpha' },
414
+ ]);
415
+ createLogTypeDirectory(customDir, 'beta');
416
+ createLogTypeDirectory(customDir, 'alpha'); // Duplicate id
417
+ const registry = new type_registry_1.LogTypeRegistry({
418
+ coreTypesPath: typesDir,
419
+ baseDir: tempDir,
420
+ customTypes: [
421
+ { id: 'beta', path: 'custom-types/beta' },
422
+ { id: 'alpha', path: 'custom-types/alpha' },
423
+ ],
424
+ });
425
+ const ids = registry.getTypeIds();
426
+ expect(ids).toEqual(['alpha', 'beta', 'gamma']);
427
+ });
428
+ });
429
+ describe('registerType', () => {
430
+ it('should register a custom type programmatically', () => {
431
+ const registry = new type_registry_1.LogTypeRegistry({ skipCoreTypes: true });
432
+ const logType = {
433
+ id: 'programmatic',
434
+ displayName: 'Programmatic Log',
435
+ description: 'Added via registerType',
436
+ template: '# {{title}}',
437
+ outputPath: '.fractary/logs/programmatic',
438
+ fileNaming: {
439
+ pattern: '{date}-{id}.md',
440
+ dateFormat: 'YYYYMMDD',
441
+ },
442
+ frontmatter: {
443
+ requiredFields: ['log_type', 'title'],
444
+ },
445
+ };
446
+ registry.registerType(logType);
447
+ expect(registry.hasType('programmatic')).toBe(true);
448
+ expect(registry.getType('programmatic')?.displayName).toBe('Programmatic Log');
449
+ });
450
+ it('should override existing type with same id', () => {
451
+ const registry = new type_registry_1.LogTypeRegistry({ skipCoreTypes: true });
452
+ const original = {
453
+ id: 'test',
454
+ displayName: 'Original',
455
+ description: 'Original description',
456
+ template: '',
457
+ outputPath: '',
458
+ fileNaming: { pattern: '' },
459
+ frontmatter: { requiredFields: [] },
460
+ };
461
+ const updated = {
462
+ id: 'test',
463
+ displayName: 'Updated',
464
+ description: 'Updated description',
465
+ template: '',
466
+ outputPath: '',
467
+ fileNaming: { pattern: '' },
468
+ frontmatter: { requiredFields: [] },
469
+ };
470
+ registry.registerType(original);
471
+ registry.registerType(updated);
472
+ expect(registry.getType('test')?.displayName).toBe('Updated');
473
+ });
474
+ });
475
+ describe('unregisterType', () => {
476
+ it('should remove a custom type', () => {
477
+ const registry = new type_registry_1.LogTypeRegistry({ skipCoreTypes: true });
478
+ const logType = {
479
+ id: 'removable',
480
+ displayName: 'Removable',
481
+ description: '',
482
+ template: '',
483
+ outputPath: '',
484
+ fileNaming: { pattern: '' },
485
+ frontmatter: { requiredFields: [] },
486
+ };
487
+ registry.registerType(logType);
488
+ expect(registry.hasType('removable')).toBe(true);
489
+ const result = registry.unregisterType('removable');
490
+ expect(result).toBe(true);
491
+ expect(registry.hasType('removable')).toBe(false);
492
+ });
493
+ it('should return false for non-existent type', () => {
494
+ const registry = new type_registry_1.LogTypeRegistry({ skipCoreTypes: true });
495
+ const result = registry.unregisterType('nonexistent');
496
+ expect(result).toBe(false);
497
+ });
498
+ it('should not affect core types', () => {
499
+ const typesDir = path.join(tempDir, 'types');
500
+ fs.mkdirSync(typesDir, { recursive: true });
501
+ createLogTypeDirectory(typesDir, 'core-type');
502
+ createManifest(typesDir, [
503
+ { id: 'core-type', displayName: 'Core Type', description: 'A core type' },
504
+ ]);
505
+ const registry = new type_registry_1.LogTypeRegistry({ coreTypesPath: typesDir });
506
+ expect(registry.hasType('core-type')).toBe(true);
507
+ // unregisterType only affects custom types
508
+ const result = registry.unregisterType('core-type');
509
+ expect(result).toBe(false);
510
+ expect(registry.hasType('core-type')).toBe(true); // Still exists in core
511
+ });
512
+ });
513
+ describe('loadCustomTypesFromManifest', () => {
514
+ it('should load custom types from manifest file', () => {
515
+ const typesDir = path.join(tempDir, 'core-types');
516
+ const customDir = path.join(tempDir, 'custom-types');
517
+ fs.mkdirSync(typesDir, { recursive: true });
518
+ fs.mkdirSync(customDir, { recursive: true });
519
+ // Create empty core types
520
+ createManifest(typesDir, []);
521
+ // Create custom manifest with types
522
+ createLogTypeDirectory(customDir, 'custom-a');
523
+ createLogTypeDirectory(customDir, 'custom-b');
524
+ createManifest(customDir, [
525
+ { id: 'custom-a', displayName: 'Custom A', description: 'First custom' },
526
+ { id: 'custom-b', displayName: 'Custom B', description: 'Second custom' },
527
+ ]);
528
+ const registry = new type_registry_1.LogTypeRegistry({
529
+ coreTypesPath: typesDir,
530
+ baseDir: tempDir,
531
+ customManifestPath: 'custom-types/manifest.yaml',
532
+ });
533
+ expect(registry.hasType('custom-a')).toBe(true);
534
+ expect(registry.hasType('custom-b')).toBe(true);
535
+ expect(registry.getCustomTypes().length).toBe(2);
536
+ });
537
+ it('should not error when custom manifest does not exist', () => {
538
+ const typesDir = path.join(tempDir, 'types');
539
+ fs.mkdirSync(typesDir, { recursive: true });
540
+ createManifest(typesDir, []);
541
+ // Should not throw even if custom manifest doesn't exist
542
+ expect(() => {
543
+ new type_registry_1.LogTypeRegistry({
544
+ coreTypesPath: typesDir,
545
+ baseDir: tempDir,
546
+ customManifestPath: 'nonexistent/manifest.yaml',
547
+ });
548
+ }).not.toThrow();
549
+ });
550
+ });
551
+ describe('type definition structure', () => {
552
+ it('should load all expected fields from type.yaml', () => {
553
+ const typesDir = path.join(tempDir, 'types');
554
+ fs.mkdirSync(typesDir, { recursive: true });
555
+ createLogTypeDirectory(typesDir, 'full-type');
556
+ createManifest(typesDir, [
557
+ { id: 'full-type', displayName: 'Full Type', description: 'Full type definition' },
558
+ ]);
559
+ const registry = new type_registry_1.LogTypeRegistry({ coreTypesPath: typesDir });
560
+ const logType = registry.getType('full-type');
561
+ expect(logType).not.toBeNull();
562
+ expect(logType?.fileNaming).toBeDefined();
563
+ expect(logType?.fileNaming.pattern).toBeDefined();
564
+ expect(logType?.frontmatter).toBeDefined();
565
+ expect(logType?.frontmatter.requiredFields).toBeDefined();
566
+ expect(logType?.structure).toBeDefined();
567
+ expect(logType?.status).toBeDefined();
568
+ expect(logType?.retention).toBeDefined();
569
+ });
570
+ it('should include template content', () => {
571
+ const typesDir = path.join(tempDir, 'types');
572
+ fs.mkdirSync(typesDir, { recursive: true });
573
+ const customTemplate = '# Custom Template\n\n{{content}}';
574
+ createLogTypeDirectory(typesDir, 'with-template', { template: customTemplate });
575
+ createManifest(typesDir, [
576
+ { id: 'with-template', displayName: 'With Template', description: 'Has template' },
577
+ ]);
578
+ const registry = new type_registry_1.LogTypeRegistry({ coreTypesPath: typesDir });
579
+ const logType = registry.getType('with-template');
580
+ expect(logType?.template).toBe(customTemplate);
581
+ });
582
+ it('should include standards content', () => {
583
+ const typesDir = path.join(tempDir, 'types');
584
+ fs.mkdirSync(typesDir, { recursive: true });
585
+ const customStandards = '# Standards\n\nFollow these rules.';
586
+ createLogTypeDirectory(typesDir, 'with-standards', { standards: customStandards });
587
+ createManifest(typesDir, [
588
+ { id: 'with-standards', displayName: 'With Standards', description: 'Has standards' },
589
+ ]);
590
+ const registry = new type_registry_1.LogTypeRegistry({ coreTypesPath: typesDir });
591
+ const logType = registry.getType('with-standards');
592
+ expect(logType?.standards).toBe(customStandards);
593
+ });
594
+ });
595
+ describe('static methods', () => {
596
+ it('should return core manifest URL', () => {
597
+ const url = type_registry_1.LogTypeRegistry.getCoreManifestUrl();
598
+ expect(url).toContain('githubusercontent.com/fractary/core');
599
+ expect(url).toContain('manifest.yaml');
600
+ });
601
+ it('should return core type URL for given id', () => {
602
+ const url = type_registry_1.LogTypeRegistry.getCoreTypeUrl('session');
603
+ expect(url).toContain('githubusercontent.com/fractary/core');
604
+ expect(url).toContain('/session');
605
+ });
606
+ });
607
+ describe('edge cases', () => {
608
+ it('should handle empty manifest', () => {
609
+ const typesDir = path.join(tempDir, 'types');
610
+ fs.mkdirSync(typesDir, { recursive: true });
611
+ createManifest(typesDir, []);
612
+ const registry = new type_registry_1.LogTypeRegistry({ coreTypesPath: typesDir });
613
+ expect(registry.getAllTypes()).toEqual([]);
614
+ });
615
+ it('should handle missing template and standards files', () => {
616
+ const typesDir = path.join(tempDir, 'types');
617
+ const typeDir = path.join(typesDir, 'minimal');
618
+ fs.mkdirSync(typeDir, { recursive: true });
619
+ // Only create type.yaml, no template.md or standards.md
620
+ const typeYaml = `
621
+ id: minimal
622
+ display_name: "Minimal"
623
+ description: "Minimal type"
624
+ output_path: .fractary/logs/minimal
625
+ file_naming:
626
+ pattern: "{date}.md"
627
+ frontmatter:
628
+ required_fields:
629
+ - log_type
630
+ `;
631
+ fs.writeFileSync(path.join(typeDir, 'type.yaml'), typeYaml);
632
+ createManifest(typesDir, [
633
+ { id: 'minimal', displayName: 'Minimal', description: 'Minimal' },
634
+ ]);
635
+ const registry = new type_registry_1.LogTypeRegistry({ coreTypesPath: typesDir });
636
+ const logType = registry.getType('minimal');
637
+ expect(logType).not.toBeNull();
638
+ expect(logType?.template).toBe('');
639
+ expect(logType?.standards).toBe('');
640
+ });
641
+ it('should warn and correct mismatched type id', () => {
642
+ const typesDir = path.join(tempDir, 'types');
643
+ const typeDir = path.join(typesDir, 'expected-id');
644
+ fs.mkdirSync(typeDir, { recursive: true });
645
+ // Type YAML has different id than directory name
646
+ const typeYaml = `
647
+ id: different-id
648
+ display_name: "Mismatched"
649
+ description: "Id in yaml differs from manifest"
650
+ output_path: .fractary/logs/mismatched
651
+ file_naming:
652
+ pattern: "{date}.md"
653
+ frontmatter:
654
+ required_fields:
655
+ - log_type
656
+ `;
657
+ fs.writeFileSync(path.join(typeDir, 'type.yaml'), typeYaml);
658
+ fs.writeFileSync(path.join(typeDir, 'template.md'), '');
659
+ fs.writeFileSync(path.join(typeDir, 'standards.md'), '');
660
+ createManifest(typesDir, [
661
+ { id: 'expected-id', displayName: 'Expected', description: 'Expected id' },
662
+ ]);
663
+ const consoleSpy = jest.spyOn(console, 'warn').mockImplementation();
664
+ const registry = new type_registry_1.LogTypeRegistry({ coreTypesPath: typesDir });
665
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('ID mismatch'));
666
+ expect(registry.hasType('expected-id')).toBe(true);
667
+ consoleSpy.mockRestore();
668
+ });
669
+ });
670
+ });
671
+ //# sourceMappingURL=type-registry.test.js.map