@luzzle/core 0.0.62 → 0.0.64

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 (81) hide show
  1. package/README.md +38 -8
  2. package/dist/src/index.d.ts +2 -2
  3. package/dist/src/index.js +2 -2
  4. package/dist/src/index.js.map +1 -1
  5. package/dist/src/pieces/Piece.js +2 -2
  6. package/dist/src/pieces/Piece.js.map +1 -1
  7. package/dist/src/pieces/index.d.ts +1 -0
  8. package/dist/src/pieces/index.js +1 -0
  9. package/dist/src/pieces/index.js.map +1 -1
  10. package/dist/src/pieces/utils/piece.d.ts +6 -2
  11. package/dist/src/pieces/utils/piece.js +43 -14
  12. package/dist/src/pieces/utils/piece.js.map +1 -1
  13. package/dist/tsconfig.build.tsbuildinfo +1 -0
  14. package/package.json +3 -3
  15. package/dist/src/database/NodeSqliteDialect.test.d.ts +0 -1
  16. package/dist/src/database/NodeSqliteDialect.test.js +0 -104
  17. package/dist/src/database/NodeSqliteDialect.test.js.map +0 -1
  18. package/dist/src/database/client.test.d.ts +0 -1
  19. package/dist/src/database/client.test.js +0 -36
  20. package/dist/src/database/client.test.js.map +0 -1
  21. package/dist/src/database/migrations.test.d.ts +0 -1
  22. package/dist/src/database/migrations.test.js +0 -19
  23. package/dist/src/database/migrations.test.js.map +0 -1
  24. package/dist/src/database/utils.test.d.ts +0 -1
  25. package/dist/src/database/utils.test.js +0 -9
  26. package/dist/src/database/utils.test.js.map +0 -1
  27. package/dist/src/index.test.d.ts +0 -1
  28. package/dist/src/index.test.js +0 -9
  29. package/dist/src/index.test.js.map +0 -1
  30. package/dist/src/lib/ajv.test.d.ts +0 -1
  31. package/dist/src/lib/ajv.test.js +0 -49
  32. package/dist/src/lib/ajv.test.js.map +0 -1
  33. package/dist/src/lib/frontmatter.test.d.ts +0 -1
  34. package/dist/src/lib/frontmatter.test.js +0 -37
  35. package/dist/src/lib/frontmatter.test.js.map +0 -1
  36. package/dist/src/lib/markdown.test.d.ts +0 -1
  37. package/dist/src/lib/markdown.test.js +0 -34
  38. package/dist/src/lib/markdown.test.js.map +0 -1
  39. package/dist/src/llm/google.test.d.ts +0 -1
  40. package/dist/src/llm/google.test.js +0 -253
  41. package/dist/src/llm/google.test.js.map +0 -1
  42. package/dist/src/pieces/Piece.test.d.ts +0 -1
  43. package/dist/src/pieces/Piece.test.js +0 -628
  44. package/dist/src/pieces/Piece.test.js.map +0 -1
  45. package/dist/src/pieces/Pieces.test.d.ts +0 -1
  46. package/dist/src/pieces/Pieces.test.js +0 -325
  47. package/dist/src/pieces/Pieces.test.js.map +0 -1
  48. package/dist/src/pieces/cache.test.d.ts +0 -1
  49. package/dist/src/pieces/cache.test.js +0 -72
  50. package/dist/src/pieces/cache.test.js.map +0 -1
  51. package/dist/src/pieces/item.test.d.ts +0 -1
  52. package/dist/src/pieces/item.test.js +0 -243
  53. package/dist/src/pieces/item.test.js.map +0 -1
  54. package/dist/src/pieces/items.test.d.ts +0 -1
  55. package/dist/src/pieces/items.test.js +0 -103
  56. package/dist/src/pieces/items.test.js.map +0 -1
  57. package/dist/src/pieces/json.schema.test.d.ts +0 -1
  58. package/dist/src/pieces/json.schema.test.js +0 -27
  59. package/dist/src/pieces/json.schema.test.js.map +0 -1
  60. package/dist/src/pieces/manager.test.d.ts +0 -1
  61. package/dist/src/pieces/manager.test.js +0 -55
  62. package/dist/src/pieces/manager.test.js.map +0 -1
  63. package/dist/src/pieces/utils/frontmatter.path.test.d.ts +0 -1
  64. package/dist/src/pieces/utils/frontmatter.path.test.js +0 -319
  65. package/dist/src/pieces/utils/frontmatter.path.test.js.map +0 -1
  66. package/dist/src/pieces/utils/frontmatter.test.d.ts +0 -1
  67. package/dist/src/pieces/utils/frontmatter.test.js +0 -335
  68. package/dist/src/pieces/utils/frontmatter.test.js.map +0 -1
  69. package/dist/src/pieces/utils/markdown.test.d.ts +0 -1
  70. package/dist/src/pieces/utils/markdown.test.js +0 -38
  71. package/dist/src/pieces/utils/markdown.test.js.map +0 -1
  72. package/dist/src/pieces/utils/piece.test.d.ts +0 -1
  73. package/dist/src/pieces/utils/piece.test.js +0 -348
  74. package/dist/src/pieces/utils/piece.test.js.map +0 -1
  75. package/dist/src/storage/fs.test.d.ts +0 -1
  76. package/dist/src/storage/fs.test.js +0 -212
  77. package/dist/src/storage/fs.test.js.map +0 -1
  78. package/dist/test/db.d.ts +0 -3
  79. package/dist/test/db.js +0 -19
  80. package/dist/test/db.js.map +0 -1
  81. package/dist/tsconfig.tsbuildinfo +0 -1
@@ -1,628 +0,0 @@
1
- import { describe, expect, test, vi, afterEach, beforeEach } from 'vitest';
2
- import { makeMarkdownSample, makePieceItemSelectable, makePieceMock, makeSchema, makeStorage, } from './Piece.fixtures.js';
3
- import { setupDatabase, teardownDatabase } from '../../test/db.js';
4
- import * as cache from './cache.js';
5
- import * as item from './item.js';
6
- import * as items from './items.js';
7
- import * as pieceUtils from './utils/piece.js';
8
- import slugify from '@sindresorhus/slugify';
9
- import { PassThrough } from 'stream';
10
- import { cpus } from 'os';
11
- import { makeCache } from './cache.fixtures.js';
12
- vi.mock('./cache.js');
13
- vi.mock('./item.js');
14
- vi.mock('./items.js');
15
- vi.mock('./utils/piece.js');
16
- vi.mock('os');
17
- vi.mock('@sindresorhus/slugify');
18
- const mocks = {
19
- cache: vi.mocked(cache),
20
- item: vi.mocked(item),
21
- items: vi.mocked(items),
22
- pieceUtils: vi.mocked(pieceUtils),
23
- slugify: vi.mocked(slugify),
24
- cpus: vi.mocked(cpus),
25
- };
26
- let db;
27
- beforeEach(async () => {
28
- db = await setupDatabase();
29
- });
30
- describe('pieces/Piece.ts', () => {
31
- afterEach(async () => {
32
- await teardownDatabase(db);
33
- vi.clearAllMocks();
34
- });
35
- test('constructor throws on name mismatch', () => {
36
- const PieceType = makePieceMock();
37
- const schema = makeSchema('table');
38
- const storage = makeStorage();
39
- expect(() => new PieceType('mismatch', storage, schema)).toThrow('does not match the schema title');
40
- });
41
- test('create generates a new markdown piece', async () => {
42
- const PieceType = makePieceMock();
43
- const storage = makeStorage();
44
- const piece = new PieceType('table', storage);
45
- mocks.slugify.mockReturnValue('my-title');
46
- vi.spyOn(storage, 'exists').mockResolvedValue(false);
47
- const result = await piece.create('dir', 'My Title');
48
- expect(result.piece).toBe('table');
49
- expect(result.frontmatter.title).toBe('title');
50
- });
51
- test('create throws if file already exists', async () => {
52
- const PieceType = makePieceMock();
53
- const storage = makeStorage();
54
- const piece = new PieceType('table', storage);
55
- mocks.slugify.mockReturnValue('my-title');
56
- vi.spyOn(storage, 'exists').mockResolvedValue(true);
57
- await expect(piece.create('dir', 'My Title')).rejects.toThrow('file already exists');
58
- });
59
- test('delete removes file if it exists', async () => {
60
- const storage = makeStorage();
61
- const PieceType = makePieceMock();
62
- const piece = new PieceType('table', storage);
63
- vi.spyOn(storage, 'exists').mockResolvedValue(true);
64
- vi.spyOn(storage, 'delete').mockResolvedValue(undefined);
65
- await piece.delete('file.md');
66
- expect(storage.delete).toHaveBeenCalledWith('file.md');
67
- });
68
- test('delete throws if file missing', async () => {
69
- const storage = makeStorage();
70
- const PieceType = makePieceMock();
71
- const piece = new PieceType('table', storage);
72
- vi.spyOn(storage, 'exists').mockResolvedValue(false);
73
- await expect(piece.delete('file.md')).rejects.toThrow('does not exist');
74
- });
75
- test('get schema', () => {
76
- const PieceType = makePieceMock();
77
- const type = 'table';
78
- const schema = makeSchema(type);
79
- const storage = makeStorage('root');
80
- const piece = new PieceType(type, storage, schema);
81
- expect(piece.schema).toEqual(schema);
82
- });
83
- test('isOutdated returns true if file is newer than cache', async () => {
84
- const PieceType = makePieceMock();
85
- const storage = makeStorage();
86
- const piece = new PieceType('table', storage);
87
- mocks.cache.getCache.mockResolvedValue(makeCache({
88
- date_updated: 1000,
89
- date_added: 1000,
90
- }));
91
- vi.spyOn(storage, 'stat').mockResolvedValue({ last_modified: new Date(2000) });
92
- expect(await piece.isOutdated('file.md', db)).toBe(true);
93
- });
94
- test('isOutdated by date_added', async () => {
95
- const filename = '/path/to/slug.books.md';
96
- const PieceType = makePieceMock();
97
- const cacheDate = new Date('11-11-2000').getTime();
98
- const fileDate = new Date('11-11-2001');
99
- const storage = makeStorage();
100
- const pieceTest = new PieceType('table', storage);
101
- mocks.cache.getCache.mockResolvedValue(makeCache({
102
- date_added: cacheDate,
103
- date_updated: null,
104
- id: '1',
105
- file_path: filename,
106
- content_hash: 'h',
107
- }));
108
- vi.spyOn(storage, 'stat').mockResolvedValue({ last_modified: fileDate });
109
- const isOutdated = await pieceTest.isOutdated(filename, db);
110
- expect(isOutdated).toEqual(true);
111
- });
112
- test('isOutdated returns false if cache is current', async () => {
113
- const PieceType = makePieceMock();
114
- const storage = makeStorage();
115
- const piece = new PieceType('table', storage);
116
- mocks.cache.getCache.mockResolvedValue(makeCache({
117
- date_updated: 3000,
118
- date_added: 3000,
119
- }));
120
- vi.spyOn(storage, 'stat').mockResolvedValue({ last_modified: new Date(2000) });
121
- expect(await piece.isOutdated('file.md', db)).toBe(false);
122
- });
123
- test('isOutdated throws', async () => {
124
- const filename = 'file.md';
125
- const PieceType = makePieceMock();
126
- const storage = makeStorage();
127
- const pieceTest = new PieceType('table', storage);
128
- vi.spyOn(storage, 'stat').mockRejectedValue(new Error('oof'));
129
- await expect(pieceTest.isOutdated(filename, db)).rejects.toThrow();
130
- });
131
- test('validate calls item.validatePieceItem', () => {
132
- const PieceType = makePieceMock();
133
- const piece = new PieceType();
134
- const markdown = makeMarkdownSample();
135
- mocks.item.validatePieceItem.mockReturnValue(true);
136
- const result = piece.validate(markdown);
137
- expect(result.isValid).toBe(true);
138
- });
139
- test('validate returns errors on failure', () => {
140
- const PieceType = makePieceMock();
141
- const piece = new PieceType();
142
- const markdown = makeMarkdownSample();
143
- mocks.item.validatePieceItem.mockReturnValue(false);
144
- mocks.item.getValidatePieceItemErrors.mockReturnValue(['error']);
145
- const result = piece.validate(markdown);
146
- expect(result.isValid).toBe(false);
147
- if (!result.isValid) {
148
- expect(result.errors).toEqual(['error']);
149
- }
150
- });
151
- test('get reads and extracts markdown', async () => {
152
- const PieceType = makePieceMock();
153
- const storage = makeStorage();
154
- const piece = new PieceType('table', storage);
155
- const fm = { title: 'sample' };
156
- vi.spyOn(storage, 'exists').mockResolvedValue(true);
157
- vi.spyOn(storage, 'readFile').mockResolvedValue('---\ntitle: sample\n---\nbody');
158
- const result = await piece.get('file.md');
159
- expect(result.frontmatter.title).toBe(fm.title);
160
- expect(result.note).toBe('body');
161
- });
162
- test('get throws if file missing', async () => {
163
- const PieceType = makePieceMock();
164
- const storage = makeStorage();
165
- const piece = new PieceType('table', storage);
166
- vi.spyOn(storage, 'exists').mockResolvedValue(false);
167
- await expect(piece.get('file.md')).rejects.toThrow('does not exist');
168
- });
169
- test('write saves markdown if valid', async () => {
170
- const PieceType = makePieceMock();
171
- const storage = makeStorage();
172
- const piece = new PieceType('table', storage);
173
- const markdown = makeMarkdownSample();
174
- mocks.item.validatePieceItem.mockReturnValue(true);
175
- vi.spyOn(storage, 'writeFile').mockResolvedValue(undefined);
176
- await piece.write(markdown);
177
- expect(storage.writeFile).toHaveBeenCalled();
178
- });
179
- test('write throws if invalid', async () => {
180
- const piece = new (makePieceMock())();
181
- const markdown = makeMarkdownSample();
182
- mocks.item.validatePieceItem.mockReturnValue(false);
183
- mocks.item.getValidatePieceItemErrors.mockReturnValue(['bad']);
184
- await expect(piece.write(markdown)).rejects.toThrow('Could not write');
185
- });
186
- test('prune deletes missing pieces from DB', async () => {
187
- const PieceType = makePieceMock();
188
- const piece = new PieceType('table');
189
- mocks.cpus.mockReturnValue([{}]);
190
- mocks.items.selectItems.mockResolvedValue([
191
- makePieceItemSelectable({ file_path: 'missing.md' }),
192
- ]);
193
- mocks.items.deleteItem.mockResolvedValue(undefined);
194
- const stream = await piece.prune(db, ['exists.md']);
195
- for await (const result of stream) {
196
- if (!result.error) {
197
- expect(result.action).toBe('pruned');
198
- }
199
- }
200
- expect(mocks.items.deleteItem).toHaveBeenCalledWith(db, 'missing.md');
201
- });
202
- test('prune handles dryRun', async () => {
203
- const PieceType = makePieceMock();
204
- const piece = new PieceType('table');
205
- mocks.cpus.mockReturnValue([{}]);
206
- mocks.items.selectItems.mockResolvedValue([
207
- makePieceItemSelectable({ file_path: 'missing.md' }),
208
- ]);
209
- const stream = await piece.prune(db, [], { dryRun: true });
210
- for await (const result of stream) {
211
- if (!result.error) {
212
- expect(result.action).toBe('pruned');
213
- }
214
- }
215
- expect(mocks.items.deleteItem).not.toHaveBeenCalled();
216
- });
217
- test('prune handles error', async () => {
218
- const PieceType = makePieceMock();
219
- const piece = new PieceType('table');
220
- mocks.cpus.mockReturnValue([{}]);
221
- mocks.items.selectItems.mockResolvedValue([makePieceItemSelectable({ file_path: 'm.md' })]);
222
- mocks.items.deleteItem.mockRejectedValue(new Error('oof'));
223
- const stream = await piece.prune(db, []);
224
- for await (const result of stream) {
225
- expect(result.error).toBe(true);
226
- }
227
- });
228
- test('syncMarkdownAdd inserts piece and adds cache', async () => {
229
- const PieceType = makePieceMock();
230
- const storage = makeStorage();
231
- const piece = new PieceType('table', storage);
232
- const markdown = makeMarkdownSample();
233
- mocks.item.makePieceItemInsertable.mockReturnValue({});
234
- mocks.pieceUtils.calculateHashFromFile.mockResolvedValue('hash');
235
- vi.spyOn(storage, 'createReadStream').mockReturnValue({});
236
- await piece.syncMarkdownAdd(db, markdown);
237
- expect(mocks.items.insertItem).toHaveBeenCalled();
238
- expect(mocks.cache.addCache).toHaveBeenCalledWith(db, markdown.filePath, 'hash');
239
- });
240
- test('syncMarkdown handles update or add', async () => {
241
- const PieceType = makePieceMock();
242
- const piece = new PieceType('table');
243
- const markdown = makeMarkdownSample();
244
- const syncAddSpy = vi.spyOn(piece, 'syncMarkdownAdd').mockResolvedValue(undefined);
245
- const syncUpdateSpy = vi.spyOn(piece, 'syncMarkdownUpdate').mockResolvedValue(undefined);
246
- mocks.items.selectItem.mockResolvedValueOnce(undefined);
247
- await piece.syncMarkdown(db, markdown);
248
- expect(syncAddSpy).toHaveBeenCalled();
249
- mocks.items.selectItem.mockResolvedValueOnce(makePieceItemSelectable({ id: '1' }));
250
- await piece.syncMarkdown(db, markdown);
251
- expect(syncUpdateSpy).toHaveBeenCalled();
252
- });
253
- test('syncMarkdownUpdate updates item and cache', async () => {
254
- const PieceType = makePieceMock();
255
- const storage = makeStorage();
256
- const piece = new PieceType('table', storage);
257
- const markdown = makeMarkdownSample();
258
- const data = makePieceItemSelectable();
259
- mocks.item.makePieceItemUpdatable.mockReturnValue({});
260
- mocks.pieceUtils.calculateHashFromFile.mockResolvedValue('new-hash');
261
- vi.spyOn(storage, 'createReadStream').mockReturnValue({});
262
- await piece.syncMarkdownUpdate(db, markdown, data);
263
- expect(mocks.items.updateItem).toHaveBeenCalled();
264
- expect(mocks.cache.updateCache).toHaveBeenCalledWith(db, data.file_path, 'new-hash');
265
- });
266
- test('sync adds new pieces to DB', async () => {
267
- const PieceType = makePieceMock();
268
- const storage = makeStorage();
269
- const piece = new PieceType('table', storage);
270
- const markdown = makeMarkdownSample({ filePath: 'new.md' });
271
- mocks.cpus.mockReturnValue([{}]);
272
- vi.spyOn(piece, 'get').mockResolvedValue(markdown);
273
- mocks.items.selectItem.mockResolvedValue(undefined);
274
- const syncAddSpy = vi.spyOn(piece, 'syncMarkdownAdd').mockResolvedValue(undefined);
275
- const stream = await piece.sync(db, ['new.md']);
276
- for await (const result of stream) {
277
- if (!result.error) {
278
- expect(result.action).toBe('added');
279
- }
280
- }
281
- expect(syncAddSpy).toHaveBeenCalled();
282
- });
283
- test('sync updates existing pieces if hash changed', async () => {
284
- const PieceType = makePieceMock();
285
- const storage = makeStorage();
286
- const piece = new PieceType('table', storage);
287
- const markdown = makeMarkdownSample({ filePath: 'u.md' });
288
- mocks.cpus.mockReturnValue([{}]);
289
- vi.spyOn(piece, 'get').mockResolvedValue(markdown);
290
- mocks.items.selectItem.mockResolvedValue(makePieceItemSelectable({ id: '1' }));
291
- mocks.pieceUtils.calculateHashFromFile.mockResolvedValue('new-hash');
292
- mocks.cache.getCache.mockResolvedValue(makeCache({ content_hash: 'old-hash' }));
293
- vi.spyOn(storage, 'createReadStream').mockReturnValue({});
294
- const syncUpdateSpy = vi.spyOn(piece, 'syncMarkdownUpdate').mockResolvedValue(undefined);
295
- const stream = await piece.sync(db, ['u.md']);
296
- for await (const result of stream) {
297
- if (!result.error) {
298
- expect(result.action).toBe('updated');
299
- }
300
- }
301
- expect(syncUpdateSpy).toHaveBeenCalled();
302
- });
303
- test('sync skips unchanged pieces', async () => {
304
- const PieceType = makePieceMock();
305
- const storage = makeStorage();
306
- const piece = new PieceType('table', storage);
307
- const markdown = makeMarkdownSample({ filePath: 's.md' });
308
- mocks.cpus.mockReturnValue([{}]);
309
- vi.spyOn(piece, 'get').mockResolvedValue(markdown);
310
- mocks.items.selectItem.mockResolvedValue(makePieceItemSelectable({ id: '1' }));
311
- mocks.pieceUtils.calculateHashFromFile.mockResolvedValue('same-hash');
312
- mocks.cache.getCache.mockResolvedValue(makeCache({ content_hash: 'same-hash' }));
313
- vi.spyOn(storage, 'createReadStream').mockReturnValue({});
314
- const stream = await piece.sync(db, ['s.md']);
315
- for await (const result of stream) {
316
- if (!result.error) {
317
- expect(result.action).toBe('skipped');
318
- }
319
- }
320
- expect(mocks.cache.updateCache).toHaveBeenCalledWith(db, 's.md', 'same-hash');
321
- });
322
- test('sync handles dryRun', async () => {
323
- const PieceType = makePieceMock();
324
- const storage = makeStorage();
325
- const piece = new PieceType('table', storage);
326
- const markdown = makeMarkdownSample({ filePath: 'new.md' });
327
- vi.spyOn(piece, 'get').mockResolvedValue(markdown);
328
- mocks.items.selectItem.mockResolvedValue(undefined);
329
- const syncAddSpy = vi.spyOn(piece, 'syncMarkdownAdd').mockResolvedValue(undefined);
330
- const stream = await piece.sync(db, ['new.md'], { dryRun: true });
331
- for await (const result of stream) {
332
- if (!result.error) {
333
- expect(result.action).toBe('added');
334
- }
335
- }
336
- expect(syncAddSpy).not.toHaveBeenCalled();
337
- });
338
- test('sync handles dryRun update', async () => {
339
- const PieceType = makePieceMock();
340
- const storage = makeStorage();
341
- const piece = new PieceType('table', storage);
342
- const markdown = makeMarkdownSample({ filePath: 'u.md' });
343
- vi.spyOn(piece, 'get').mockResolvedValue(markdown);
344
- mocks.items.selectItem.mockResolvedValue(makePieceItemSelectable({ id: '1' }));
345
- mocks.pieceUtils.calculateHashFromFile.mockResolvedValue('new-hash');
346
- mocks.cache.getCache.mockResolvedValue(makeCache({ content_hash: 'old-hash' }));
347
- vi.spyOn(storage, 'createReadStream').mockReturnValue({});
348
- const syncUpdateSpy = vi.spyOn(piece, 'syncMarkdownUpdate').mockResolvedValue(undefined);
349
- const stream = await piece.sync(db, ['u.md'], { dryRun: true });
350
- for await (const result of stream) {
351
- if (!result.error) {
352
- expect(result.action).toBe('updated');
353
- }
354
- }
355
- expect(syncUpdateSpy).not.toHaveBeenCalled();
356
- });
357
- test('sync handles dryRun skip', async () => {
358
- const PieceType = makePieceMock();
359
- const storage = makeStorage();
360
- const piece = new PieceType('table', storage);
361
- const markdown = makeMarkdownSample({ filePath: 's.md' });
362
- vi.spyOn(piece, 'get').mockResolvedValue(markdown);
363
- mocks.items.selectItem.mockResolvedValue(makePieceItemSelectable({ id: '1' }));
364
- mocks.pieceUtils.calculateHashFromFile.mockResolvedValue('same-hash');
365
- mocks.cache.getCache.mockResolvedValue(makeCache({ content_hash: 'same-hash' }));
366
- vi.spyOn(storage, 'createReadStream').mockReturnValue({});
367
- const stream = await piece.sync(db, ['s.md'], { dryRun: true });
368
- for await (const result of stream) {
369
- if (!result.error) {
370
- expect(result.action).toBe('skipped');
371
- }
372
- }
373
- expect(mocks.cache.updateCache).not.toHaveBeenCalled();
374
- });
375
- test('sync handles force update even if hash same', async () => {
376
- const PieceType = makePieceMock();
377
- const storage = makeStorage();
378
- const piece = new PieceType('table', storage);
379
- const markdown = makeMarkdownSample({ filePath: 'u.md' });
380
- vi.spyOn(piece, 'get').mockResolvedValue(markdown);
381
- mocks.items.selectItem.mockResolvedValue(makePieceItemSelectable({ id: '1' }));
382
- mocks.pieceUtils.calculateHashFromFile.mockResolvedValue('same-hash');
383
- mocks.cache.getCache.mockResolvedValue(makeCache({ content_hash: 'same-hash' }));
384
- vi.spyOn(storage, 'createReadStream').mockReturnValue({});
385
- const syncUpdateSpy = vi.spyOn(piece, 'syncMarkdownUpdate').mockResolvedValue(undefined);
386
- const stream = await piece.sync(db, ['u.md'], { force: true });
387
- for await (const result of stream) {
388
- if (!result.error) {
389
- expect(result.action).toBe('updated');
390
- }
391
- }
392
- expect(syncUpdateSpy).toHaveBeenCalled();
393
- });
394
- test('sync handles errors', async () => {
395
- const PieceType = makePieceMock();
396
- const piece = new PieceType('table');
397
- mocks.cpus.mockReturnValue([{}]);
398
- vi.spyOn(piece, 'get').mockRejectedValue(new Error('oof'));
399
- const stream = await piece.sync(db, ['e.md']);
400
- for await (const result of stream) {
401
- expect(result.error).toBe(true);
402
- }
403
- });
404
- test('toMarkdown restores frontmatter from DB JSON', () => {
405
- const PieceType = makePieceMock();
406
- const piece = new PieceType('table');
407
- const dbPiece = makePieceItemSelectable({
408
- frontmatter_json: JSON.stringify({ title: 'db-title', keywords: ['a', 'b'] }),
409
- });
410
- const result = piece.toMarkdown(dbPiece);
411
- expect(result.frontmatter.title).toBe('db-title');
412
- expect(result.frontmatter.keywords).toEqual(['a', 'b']);
413
- });
414
- test('getField', async () => {
415
- const PieceType = makePieceMock();
416
- const schema = makeSchema({
417
- title: { type: 'string' },
418
- subtitle: { type: 'string', nullable: true },
419
- });
420
- const piece = new PieceType('table', makeStorage(), schema);
421
- const markdown = makeMarkdownSample({ frontmatter: { title: 'old', subtitle: 'old' } });
422
- mocks.pieceUtils.makePieceValue.mockImplementation(async (_, v) => v);
423
- const field = piece.getField(markdown, 'subtitle');
424
- expect(field).toBe('old');
425
- });
426
- test('setFields updates multiple fields', async () => {
427
- const PieceType = makePieceMock();
428
- const schema = makeSchema({
429
- title: { type: 'string' },
430
- subtitle: { type: 'string', nullable: true },
431
- });
432
- const piece = new PieceType('table', makeStorage(), schema);
433
- const markdown = makeMarkdownSample({ frontmatter: { title: 'old', subtitle: 'old' } });
434
- mocks.pieceUtils.makePieceValue.mockImplementation(async (_, v) => v);
435
- const updated = await piece.setFields(markdown, { title: 'new', subtitle: 'new' });
436
- expect(updated.frontmatter.title).toBe('new');
437
- expect(updated.frontmatter.subtitle).toBe('new');
438
- });
439
- test('setField with nested path', async () => {
440
- const PieceType = makePieceMock();
441
- const schema = makeSchema({
442
- meta: {
443
- type: 'object',
444
- properties: { author: { type: 'string' } },
445
- },
446
- });
447
- const piece = new PieceType('table', makeStorage(), schema);
448
- const markdown = makeMarkdownSample({ frontmatter: { title: 't' } });
449
- mocks.pieceUtils.makePieceValue.mockImplementation(async (_, v) => v);
450
- const updated = await piece.setField(markdown, 'meta.author', 'Bob');
451
- const fm = updated.frontmatter;
452
- expect(fm.meta.author).toBe('Bob');
453
- });
454
- test('setField appends to nested array', async () => {
455
- const PieceType = makePieceMock();
456
- const schema = makeSchema({
457
- meta: {
458
- type: 'object',
459
- properties: { tags: { type: 'array', items: { type: 'string' } } },
460
- },
461
- });
462
- const piece = new PieceType('table', makeStorage(), schema);
463
- const markdown = makeMarkdownSample({ frontmatter: { title: 'title', meta: { tags: ['a'] } } });
464
- mocks.pieceUtils.makePieceValue.mockImplementation(async (_, v) => v);
465
- const updated = await piece.setField(markdown, 'meta.tags', 'b');
466
- const fm = updated.frontmatter;
467
- expect(fm.meta.tags).toEqual(['a', 'b']);
468
- });
469
- test('setField initializes missing array field', async () => {
470
- const PieceType = makePieceMock();
471
- const schema = makeSchema({ tags: { type: 'array', items: { type: 'string' } } });
472
- const piece = new PieceType('table', makeStorage(), schema);
473
- const markdown = makeMarkdownSample({ frontmatter: { title: 't' } });
474
- mocks.pieceUtils.makePieceValue.mockImplementation(async (_, v) => v);
475
- const updated = await piece.setField(markdown, 'tags', 'tag1');
476
- const fm = updated.frontmatter;
477
- expect(fm.tags).toEqual(['tag1']);
478
- });
479
- test('setField handles an array of values', async () => {
480
- const PieceType = makePieceMock();
481
- const schema = makeSchema({
482
- tags: { type: 'array', items: { type: 'string' } },
483
- });
484
- const piece = new PieceType('table', makeStorage(), schema);
485
- const markdown = makeMarkdownSample({ frontmatter: { title: 't', tags: ['a'] } });
486
- mocks.pieceUtils.makePieceValue.mockImplementation(async (_, v) => v);
487
- const updated = await piece.setField(markdown, 'tags', ['b', 'c']);
488
- const fm = updated.frontmatter;
489
- expect(fm.tags).toEqual(['a', 'b', 'c']);
490
- });
491
- test('setField attaches assets even in nested paths', async () => {
492
- const PieceType = makePieceMock();
493
- const schema = makeSchema({
494
- meta: {
495
- type: 'object',
496
- properties: { cover: { type: 'string', format: 'asset' } },
497
- },
498
- });
499
- const storage = makeStorage();
500
- const piece = new PieceType('table', storage, schema);
501
- const markdown = makeMarkdownSample();
502
- mocks.pieceUtils.makePieceValue.mockResolvedValue({
503
- stream: new PassThrough(),
504
- });
505
- mocks.pieceUtils.isAttachableStream.mockReturnValueOnce(true);
506
- mocks.pieceUtils.makePieceAttachment.mockResolvedValue('assets/cover.jpg');
507
- const updated = await piece.setField(markdown, 'meta.cover', 'upload-me');
508
- const fm = updated.frontmatter;
509
- expect(fm.meta.cover).toBe('assets/cover.jpg');
510
- expect(mocks.pieceUtils.makePieceAttachment).toHaveBeenCalled();
511
- });
512
- test('setField handles set error', async () => {
513
- const PieceType = makePieceMock();
514
- const piece = new PieceType('table');
515
- const markdown = makeMarkdownSample();
516
- mocks.pieceUtils.makePieceValue.mockRejectedValue(new Error('bad'));
517
- const result = await piece.setField(markdown, 'title', 'new');
518
- expect(result).toBe(markdown);
519
- });
520
- test('setField throws on bad field', async () => {
521
- const PieceType = makePieceMock();
522
- const piece = new PieceType('table');
523
- const markdown = makeMarkdownSample();
524
- const setting = piece.setField(markdown, 'title2', 'new');
525
- await expect(setting).rejects.toThrow();
526
- });
527
- test('removeField unsets a nested value', async () => {
528
- const PieceType = makePieceMock();
529
- const schema = makeSchema({
530
- meta: {
531
- type: 'object',
532
- nullable: true,
533
- properties: { author: { type: 'string', nullable: true } },
534
- },
535
- });
536
- const piece = new PieceType('table', makeStorage(), schema);
537
- const markdown = makeMarkdownSample({ frontmatter: { title: 't', meta: { author: 'Alice' } } });
538
- const updated = await piece.removeField(markdown, 'meta.author');
539
- const fm = updated.frontmatter;
540
- expect(fm.meta).toBeUndefined();
541
- });
542
- test('removeField removes specific array index', async () => {
543
- const PieceType = makePieceMock();
544
- const schema = makeSchema({
545
- tags: { type: 'array', nullable: true, items: { type: 'string', nullable: true } },
546
- });
547
- const piece = new PieceType('table', makeStorage(), schema);
548
- const markdown = makeMarkdownSample({ frontmatter: { title: 't', tags: ['a', 'b', 'c'] } });
549
- const updated = await piece.removeField(markdown, 'tags.1');
550
- const fm = updated.frontmatter;
551
- expect(fm.tags).toEqual(['a', 'c']);
552
- });
553
- test('removeField removes by value from array', async () => {
554
- const PieceType = makePieceMock();
555
- const schema = makeSchema({
556
- tags: { type: 'array', nullable: true, items: { type: 'string', nullable: true } },
557
- });
558
- const piece = new PieceType('table', makeStorage(), schema);
559
- const markdown = makeMarkdownSample({ frontmatter: { title: 't', tags: ['a', 'b'] } });
560
- mocks.pieceUtils.makePieceValue.mockImplementation(async (_, v) => v);
561
- const updated = await piece.removeField(markdown, 'tags', 'a');
562
- const fm = updated.frontmatter;
563
- expect(fm.tags).toEqual(['b']);
564
- });
565
- test('removeField returns markdown if array value missing', async () => {
566
- const PieceType = makePieceMock();
567
- const schema = makeSchema({
568
- tags: { type: 'array', nullable: true, items: { type: 'string', nullable: true } },
569
- });
570
- const piece = new PieceType('table', makeStorage(), schema);
571
- const markdown = makeMarkdownSample({ frontmatter: { title: 't', tags: ['a'] } });
572
- mocks.pieceUtils.makePieceValue.mockResolvedValue('b');
573
- const result = await piece.removeField(markdown, 'tags', 'b');
574
- expect(result).toStrictEqual(markdown);
575
- });
576
- test('removeField handles scalars by value', async () => {
577
- const PieceType = makePieceMock();
578
- const schema = makeSchema({ subtitle: { type: 'string', nullable: true } });
579
- const piece = new PieceType('table', makeStorage(), schema);
580
- const markdown = makeMarkdownSample({ frontmatter: { title: 't', subtitle: 's' } });
581
- mocks.pieceUtils.makePieceValue.mockResolvedValue('s');
582
- const updated = await piece.removeField(markdown, 'subtitle', 's');
583
- expect(updated.frontmatter.subtitle).toBeUndefined();
584
- });
585
- test('removeField skips removal if scalar value mismatch', async () => {
586
- const PieceType = makePieceMock();
587
- const schema = makeSchema({ subtitle: { type: 'string', nullable: true } });
588
- const piece = new PieceType('table', makeStorage(), schema);
589
- const markdown = makeMarkdownSample({ frontmatter: { title: 't', subtitle: 's' } });
590
- // Setup markdown so current value is 's'
591
- // Our mock return value will be 'mismatch'
592
- mocks.pieceUtils.makePieceValue.mockResolvedValue('mismatch');
593
- const result = await piece.removeField(markdown, 'subtitle', 'mismatch');
594
- expect(result).toStrictEqual(markdown);
595
- });
596
- test('removeField throws on required field', async () => {
597
- const PieceType = makePieceMock();
598
- const schema = makeSchema({
599
- title: { type: 'string', nullable: false },
600
- });
601
- const piece = new PieceType('table', makeStorage(), schema);
602
- const markdown = makeMarkdownSample({ frontmatter: { title: 't' } });
603
- await expect(piece.removeField(markdown, 'title')).rejects.toThrow('is a required field');
604
- });
605
- test('removeField throws on bad field', async () => {
606
- const PieceType = makePieceMock();
607
- const schema = makeSchema({
608
- title: { type: 'string', nullable: false },
609
- });
610
- const piece = new PieceType('table', makeStorage(), schema);
611
- const markdown = makeMarkdownSample({ frontmatter: { title: 't' } });
612
- await expect(piece.removeField(markdown, 'title2')).rejects.toThrow();
613
- });
614
- test('removeFields updates multiple fields', async () => {
615
- const PieceType = makePieceMock();
616
- const schema = makeSchema({
617
- s1: { type: 'string', nullable: true },
618
- s2: { type: 'string', nullable: true },
619
- });
620
- const piece = new PieceType('table', makeStorage(), schema);
621
- const markdown = makeMarkdownSample({ frontmatter: { title: 't', s1: 'v', s2: 'v' } });
622
- const updated = await piece.removeFields(markdown, ['s1', 's2']);
623
- const fm = updated.frontmatter;
624
- expect(fm.s1).toBeUndefined();
625
- expect(fm.s2).toBeUndefined();
626
- });
627
- });
628
- //# sourceMappingURL=Piece.test.js.map