@delma/fylo 2.0.0 → 2.1.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 (43) hide show
  1. package/README.md +185 -267
  2. package/package.json +2 -5
  3. package/src/core/directory.ts +22 -354
  4. package/src/engines/s3-files/documents.ts +65 -0
  5. package/src/engines/s3-files/filesystem.ts +172 -0
  6. package/src/engines/s3-files/query.ts +291 -0
  7. package/src/engines/s3-files/types.ts +42 -0
  8. package/src/engines/s3-files.ts +391 -510
  9. package/src/engines/types.ts +1 -1
  10. package/src/index.ts +142 -1237
  11. package/src/sync.ts +58 -0
  12. package/src/types/fylo.d.ts +66 -161
  13. package/src/types/node-runtime.d.ts +1 -0
  14. package/tests/collection/truncate.test.js +11 -10
  15. package/tests/helpers/root.js +7 -0
  16. package/tests/integration/create.test.js +9 -9
  17. package/tests/integration/delete.test.js +16 -14
  18. package/tests/integration/edge-cases.test.js +29 -25
  19. package/tests/integration/encryption.test.js +47 -30
  20. package/tests/integration/export.test.js +11 -11
  21. package/tests/integration/join-modes.test.js +16 -16
  22. package/tests/integration/nested.test.js +26 -24
  23. package/tests/integration/operators.test.js +43 -29
  24. package/tests/integration/read.test.js +25 -21
  25. package/tests/integration/rollback.test.js +21 -51
  26. package/tests/integration/s3-files.performance.test.js +75 -0
  27. package/tests/integration/s3-files.test.js +115 -18
  28. package/tests/integration/sync.test.js +154 -0
  29. package/tests/integration/update.test.js +24 -18
  30. package/src/adapters/redis.ts +0 -487
  31. package/src/adapters/s3.ts +0 -61
  32. package/src/core/walker.ts +0 -174
  33. package/src/core/write-queue.ts +0 -59
  34. package/src/migrate-cli.ts +0 -22
  35. package/src/migrate.ts +0 -74
  36. package/src/types/write-queue.ts +0 -42
  37. package/src/worker.ts +0 -18
  38. package/src/workers/write-worker.ts +0 -120
  39. package/tests/index.js +0 -14
  40. package/tests/integration/migration.test.js +0 -38
  41. package/tests/integration/queue.test.js +0 -83
  42. package/tests/mocks/redis.js +0 -123
  43. package/tests/mocks/s3.js +0 -80
@@ -1,14 +1,13 @@
1
- import { test, expect, describe, beforeAll, afterAll, mock } from 'bun:test'
1
+ import { test, expect, describe, beforeAll, afterAll } from 'bun:test'
2
+ import { rm } from 'node:fs/promises'
2
3
  import Fylo from '../../src'
3
4
  import { albumURL } from '../data'
4
- import S3Mock from '../mocks/s3'
5
- import RedisMock from '../mocks/redis'
5
+ import { createTestRoot } from '../helpers/root'
6
6
  const ALBUMS = 'ops-album'
7
- const fylo = new Fylo()
8
- mock.module('../../src/adapters/s3', () => ({ S3: S3Mock }))
9
- mock.module('../../src/adapters/redis', () => ({ Redis: RedisMock }))
7
+ const root = await createTestRoot('fylo-operators-')
8
+ const fylo = new Fylo({ root })
10
9
  beforeAll(async () => {
11
- await Fylo.createCollection(ALBUMS)
10
+ await fylo.createCollection(ALBUMS)
12
11
  try {
13
12
  await fylo.importBulkData(ALBUMS, new URL(albumURL), 100)
14
13
  } catch {
@@ -16,14 +15,17 @@ beforeAll(async () => {
16
15
  }
17
16
  })
18
17
  afterAll(async () => {
19
- await Fylo.dropCollection(ALBUMS)
18
+ await fylo.dropCollection(ALBUMS)
19
+ await rm(root, { recursive: true, force: true })
20
20
  })
21
21
  describe('NO-SQL', async () => {
22
22
  test('$ne — excludes matching value', async () => {
23
23
  let results = {}
24
- for await (const data of Fylo.findDocs(ALBUMS, {
25
- $ops: [{ userId: { $ne: 1 } }]
26
- }).collect()) {
24
+ for await (const data of fylo
25
+ .findDocs(ALBUMS, {
26
+ $ops: [{ userId: { $ne: 1 } }]
27
+ })
28
+ .collect()) {
27
29
  results = { ...results, ...data }
28
30
  }
29
31
  const albums = Object.values(results)
@@ -33,9 +35,11 @@ describe('NO-SQL', async () => {
33
35
  })
34
36
  test('$lt — returns documents where field is less than value', async () => {
35
37
  let results = {}
36
- for await (const data of Fylo.findDocs(ALBUMS, {
37
- $ops: [{ userId: { $lt: 5 } }]
38
- }).collect()) {
38
+ for await (const data of fylo
39
+ .findDocs(ALBUMS, {
40
+ $ops: [{ userId: { $lt: 5 } }]
41
+ })
42
+ .collect()) {
39
43
  results = { ...results, ...data }
40
44
  }
41
45
  const albums = Object.values(results)
@@ -45,9 +49,11 @@ describe('NO-SQL', async () => {
45
49
  })
46
50
  test('$lte — returns documents where field is less than or equal to value', async () => {
47
51
  let results = {}
48
- for await (const data of Fylo.findDocs(ALBUMS, {
49
- $ops: [{ userId: { $lte: 5 } }]
50
- }).collect()) {
52
+ for await (const data of fylo
53
+ .findDocs(ALBUMS, {
54
+ $ops: [{ userId: { $lte: 5 } }]
55
+ })
56
+ .collect()) {
51
57
  results = { ...results, ...data }
52
58
  }
53
59
  const albums = Object.values(results)
@@ -57,9 +63,11 @@ describe('NO-SQL', async () => {
57
63
  })
58
64
  test('$gt — returns documents where field is greater than value', async () => {
59
65
  let results = {}
60
- for await (const data of Fylo.findDocs(ALBUMS, {
61
- $ops: [{ userId: { $gt: 5 } }]
62
- }).collect()) {
66
+ for await (const data of fylo
67
+ .findDocs(ALBUMS, {
68
+ $ops: [{ userId: { $gt: 5 } }]
69
+ })
70
+ .collect()) {
63
71
  results = { ...results, ...data }
64
72
  }
65
73
  const albums = Object.values(results)
@@ -69,9 +77,11 @@ describe('NO-SQL', async () => {
69
77
  })
70
78
  test('$gte — returns documents where field is greater than or equal to value', async () => {
71
79
  let results = {}
72
- for await (const data of Fylo.findDocs(ALBUMS, {
73
- $ops: [{ userId: { $gte: 5 } }]
74
- }).collect()) {
80
+ for await (const data of fylo
81
+ .findDocs(ALBUMS, {
82
+ $ops: [{ userId: { $gte: 5 } }]
83
+ })
84
+ .collect()) {
75
85
  results = { ...results, ...data }
76
86
  }
77
87
  const albums = Object.values(results)
@@ -81,9 +91,11 @@ describe('NO-SQL', async () => {
81
91
  })
82
92
  test('$like — matches substring pattern', async () => {
83
93
  let results = {}
84
- for await (const data of Fylo.findDocs(ALBUMS, {
85
- $ops: [{ title: { $like: '%quidem%' } }]
86
- }).collect()) {
94
+ for await (const data of fylo
95
+ .findDocs(ALBUMS, {
96
+ $ops: [{ title: { $like: '%quidem%' } }]
97
+ })
98
+ .collect()) {
87
99
  results = { ...results, ...data }
88
100
  }
89
101
  const albums = Object.values(results)
@@ -93,9 +105,11 @@ describe('NO-SQL', async () => {
93
105
  })
94
106
  test('$like — prefix pattern', async () => {
95
107
  let results = {}
96
- for await (const data of Fylo.findDocs(ALBUMS, {
97
- $ops: [{ title: { $like: 'omnis%' } }]
98
- }).collect()) {
108
+ for await (const data of fylo
109
+ .findDocs(ALBUMS, {
110
+ $ops: [{ title: { $like: 'omnis%' } }]
111
+ })
112
+ .collect()) {
99
113
  results = { ...results, ...data }
100
114
  }
101
115
  const albums = Object.values(results)
@@ -1,16 +1,15 @@
1
- import { test, expect, describe, beforeAll, afterAll, mock } from 'bun:test'
1
+ import { test, expect, describe, beforeAll, afterAll } from 'bun:test'
2
+ import { rm } from 'node:fs/promises'
2
3
  import Fylo from '../../src'
3
4
  import { albumURL, postsURL } from '../data'
4
- import S3Mock from '../mocks/s3'
5
- import RedisMock from '../mocks/redis'
5
+ import { createTestRoot } from '../helpers/root'
6
6
  const POSTS = `post`
7
7
  const ALBUMS = `album`
8
8
  let count = 0
9
- const fylo = new Fylo()
10
- mock.module('../../src/adapters/s3', () => ({ S3: S3Mock }))
11
- mock.module('../../src/adapters/redis', () => ({ Redis: RedisMock }))
9
+ const root = await createTestRoot('fylo-read-')
10
+ const fylo = new Fylo({ root })
12
11
  beforeAll(async () => {
13
- await Promise.all([Fylo.createCollection(ALBUMS), fylo.executeSQL(`CREATE TABLE ${POSTS}`)])
12
+ await Promise.all([fylo.createCollection(ALBUMS), fylo.executeSQL(`CREATE TABLE ${POSTS}`)])
14
13
  try {
15
14
  count = await fylo.importBulkData(ALBUMS, new URL(albumURL), 100)
16
15
  await fylo.importBulkData(POSTS, new URL(postsURL), 100)
@@ -19,19 +18,20 @@ beforeAll(async () => {
19
18
  }
20
19
  })
21
20
  afterAll(async () => {
22
- await Promise.all([Fylo.dropCollection(ALBUMS), Fylo.dropCollection(POSTS)])
21
+ await Promise.all([fylo.dropCollection(ALBUMS), fylo.dropCollection(POSTS)])
22
+ await rm(root, { recursive: true, force: true })
23
23
  })
24
24
  describe('NO-SQL', async () => {
25
25
  test('SELECT ALL', async () => {
26
26
  let results = {}
27
- for await (const data of Fylo.findDocs(ALBUMS).collect()) {
27
+ for await (const data of fylo.findDocs(ALBUMS).collect()) {
28
28
  results = { ...results, ...data }
29
29
  }
30
30
  expect(Object.keys(results).length).toBe(count)
31
31
  })
32
32
  test('SELECT PARTIAL', async () => {
33
33
  let results = {}
34
- for await (const data of Fylo.findDocs(ALBUMS, { $select: ['title'] }).collect()) {
34
+ for await (const data of fylo.findDocs(ALBUMS, { $select: ['title'] }).collect()) {
35
35
  results = { ...results, ...data }
36
36
  }
37
37
  const allAlbums = Object.values(results)
@@ -40,18 +40,20 @@ describe('NO-SQL', async () => {
40
40
  })
41
41
  test('GET ONE', async () => {
42
42
  const ids = []
43
- for await (const data of Fylo.findDocs(ALBUMS, { $limit: 1, $onlyIds: true }).collect()) {
43
+ for await (const data of fylo.findDocs(ALBUMS, { $limit: 1, $onlyIds: true }).collect()) {
44
44
  ids.push(data)
45
45
  }
46
- const result = await Fylo.getDoc(ALBUMS, ids[0]).once()
46
+ const result = await fylo.getDoc(ALBUMS, ids[0]).once()
47
47
  const _id = Object.keys(result).shift()
48
48
  expect(ids[0]).toEqual(_id)
49
49
  })
50
50
  test('SELECT CLAUSE', async () => {
51
51
  let results = {}
52
- for await (const data of Fylo.findDocs(ALBUMS, {
53
- $ops: [{ userId: { $eq: 2 } }]
54
- }).collect()) {
52
+ for await (const data of fylo
53
+ .findDocs(ALBUMS, {
54
+ $ops: [{ userId: { $eq: 2 } }]
55
+ })
56
+ .collect()) {
55
57
  results = { ...results, ...data }
56
58
  }
57
59
  const allAlbums = Object.values(results)
@@ -60,23 +62,25 @@ describe('NO-SQL', async () => {
60
62
  })
61
63
  test('SELECT LIMIT', async () => {
62
64
  let results = {}
63
- for await (const data of Fylo.findDocs(ALBUMS, { $limit: 5 }).collect()) {
65
+ for await (const data of fylo.findDocs(ALBUMS, { $limit: 5 }).collect()) {
64
66
  results = { ...results, ...data }
65
67
  }
66
68
  expect(Object.keys(results).length).toBe(5)
67
69
  })
68
70
  test('SELECT GROUP BY', async () => {
69
71
  let results = {}
70
- for await (const data of Fylo.findDocs(ALBUMS, {
71
- $groupby: 'userId',
72
- $onlyIds: true
73
- }).collect()) {
72
+ for await (const data of fylo
73
+ .findDocs(ALBUMS, {
74
+ $groupby: 'userId',
75
+ $onlyIds: true
76
+ })
77
+ .collect()) {
74
78
  results = Object.appendGroup(results, data)
75
79
  }
76
80
  expect(Object.keys(results).length).toBeGreaterThan(0)
77
81
  })
78
82
  test('SELECT JOIN', async () => {
79
- const results = await Fylo.joinDocs({
83
+ const results = await fylo.joinDocs({
80
84
  $leftCollection: ALBUMS,
81
85
  $rightCollection: POSTS,
82
86
  $mode: 'inner',
@@ -1,60 +1,30 @@
1
- import { test, expect, describe, beforeAll, afterAll, mock } from 'bun:test'
1
+ import { afterAll, beforeAll, describe, expect, test } from 'bun:test'
2
+ import { mkdtemp, rm } from 'node:fs/promises'
3
+ import os from 'node:os'
4
+ import path from 'node:path'
2
5
  import Fylo from '../../src'
3
- import S3Mock from '../mocks/s3'
4
- import RedisMock from '../mocks/redis'
6
+
7
+ const root = await mkdtemp(path.join(os.tmpdir(), 'fylo-rollback-'))
5
8
  const POSTS = 'rb-post'
6
- const fylo = new Fylo()
7
- mock.module('../../src/adapters/s3', () => ({ S3: S3Mock }))
8
- mock.module('../../src/adapters/redis', () => ({ Redis: RedisMock }))
9
+ const fylo = new Fylo({ root })
10
+
9
11
  beforeAll(async () => {
10
- await Fylo.createCollection(POSTS)
12
+ await fylo.createCollection(POSTS)
11
13
  })
14
+
12
15
  afterAll(async () => {
13
- await Fylo.dropCollection(POSTS)
16
+ await rm(root, { recursive: true, force: true })
14
17
  })
15
- describe('NO-SQL', () => {
16
- test('INSERT then rollback — document is not retrievable', async () => {
17
- const _id = await fylo.putData(POSTS, {
18
- userId: 99,
19
- id: 9001,
20
- title: 'Rollback Me',
21
- body: 'This document should disappear after rollback'
18
+
19
+ describe('rollback compatibility', () => {
20
+ test('rollback is a safe no-op in filesystem-first FYLO', async () => {
21
+ const id = await fylo.putData(POSTS, {
22
+ title: 'Still here'
22
23
  })
23
- const before = await Fylo.getDoc(POSTS, _id).once()
24
- expect(Object.keys(before).length).toBe(1)
25
- await fylo.rollback()
26
- const after = await Fylo.getDoc(POSTS, _id).once()
27
- expect(Object.keys(after).length).toBe(0)
28
- })
29
- test('DELETE then rollback — document is restored', async () => {
30
- const freshFylo = new Fylo()
31
- const _id = await freshFylo.putData(POSTS, {
32
- userId: 99,
33
- id: 9002,
34
- title: 'Restore Me',
35
- body: 'This document should reappear after delete rollback'
36
- })
37
- const deleteInstance = new Fylo()
38
- await deleteInstance.delDoc(POSTS, _id)
39
- const after = await Fylo.getDoc(POSTS, _id).once()
40
- expect(Object.keys(after).length).toBe(0)
41
- await deleteInstance.rollback()
42
- const restored = await Fylo.getDoc(POSTS, _id).once()
43
- expect(Object.keys(restored).length).toBe(1)
44
- expect(restored[_id].title).toBe('Restore Me')
45
- })
46
- test('batch INSERT then rollback — all documents are removed', async () => {
47
- const batchFylo = new Fylo()
48
- const batch = [
49
- { userId: 98, id: 9003, title: 'Batch A', body: 'body a' },
50
- { userId: 98, id: 9004, title: 'Batch B', body: 'body b' },
51
- { userId: 98, id: 9005, title: 'Batch C', body: 'body c' }
52
- ]
53
- const ids = await batchFylo.batchPutData(POSTS, batch)
54
- const beforeResults = await Promise.all(ids.map((id) => Fylo.getDoc(POSTS, id).once()))
55
- expect(beforeResults.every((r) => Object.keys(r).length === 1)).toBe(true)
56
- await batchFylo.rollback()
57
- const afterResults = await Promise.all(ids.map((id) => Fylo.getDoc(POSTS, id).once()))
58
- expect(afterResults.every((r) => Object.keys(r).length === 0)).toBe(true)
24
+
25
+ await expect(fylo.rollback()).resolves.toBeUndefined()
26
+
27
+ const after = await fylo.getDoc(POSTS, id).once()
28
+ expect(after[id].title).toBe('Still here')
59
29
  })
60
30
  })
@@ -0,0 +1,75 @@
1
+ import { afterAll, beforeAll, describe, expect, test } from 'bun:test'
2
+ import { mkdtemp, rm, stat } from 'node:fs/promises'
3
+ import { performance } from 'node:perf_hooks'
4
+ import os from 'node:os'
5
+ import path from 'node:path'
6
+ import Fylo from '../../src'
7
+
8
+ const runPerf = process.env.FYLO_RUN_PERF_TESTS === 'true'
9
+
10
+ describe.skipIf(!runPerf)('s3-files engine performance', () => {
11
+ let root = path.join(os.tmpdir(), `fylo-s3files-perf-${Date.now()}`)
12
+ const collection = 's3files-perf'
13
+ let fylo = new Fylo({ root })
14
+
15
+ beforeAll(async () => {
16
+ root = await mkdtemp(root)
17
+ fylo = new Fylo({ root })
18
+ await fylo.createCollection(collection)
19
+ })
20
+
21
+ afterAll(async () => {
22
+ await rm(root, { recursive: true, force: true })
23
+ })
24
+
25
+ test('keeps a single durable index file while querying a large dataset', async () => {
26
+ const totalDocs = 2000
27
+ const insertStart = performance.now()
28
+
29
+ for (let index = 0; index < totalDocs; index++) {
30
+ await fylo.putData(collection, {
31
+ title: `doc-${index}`,
32
+ group: index % 10,
33
+ tags: [`tag-${index % 5}`, `batch-${Math.floor(index / 100)}`],
34
+ meta: { score: index }
35
+ })
36
+ }
37
+
38
+ const insertMs = performance.now() - insertStart
39
+
40
+ const exactStart = performance.now()
41
+ let exactResults = {}
42
+ for await (const data of fylo
43
+ .findDocs(collection, {
44
+ $ops: [{ title: { $eq: 'doc-1555' } }]
45
+ })
46
+ .collect()) {
47
+ exactResults = { ...exactResults, ...data }
48
+ }
49
+ const exactMs = performance.now() - exactStart
50
+
51
+ const rangeStart = performance.now()
52
+ let rangeCount = 0
53
+ for await (const data of fylo
54
+ .findDocs(collection, {
55
+ $ops: [{ ['meta.score']: { $gte: 1900 } }]
56
+ })
57
+ .collect()) {
58
+ rangeCount += Object.keys(data).length
59
+ }
60
+ const rangeMs = performance.now() - rangeStart
61
+
62
+ const indexFile = path.join(root, collection, '.fylo', 'indexes', `${collection}.idx.json`)
63
+ const indexStats = await stat(indexFile)
64
+
65
+ expect(Object.keys(exactResults)).toHaveLength(1)
66
+ expect(rangeCount).toBe(100)
67
+ expect(indexStats.isFile()).toBe(true)
68
+
69
+ console.log(
70
+ `[FYLO perf] docs=${totalDocs} insertMs=${insertMs.toFixed(1)} exactMs=${exactMs.toFixed(
71
+ 1
72
+ )} rangeMs=${rangeMs.toFixed(1)} indexBytes=${indexStats.size}`
73
+ )
74
+ }, 120_000)
75
+ })
@@ -1,10 +1,10 @@
1
1
  import { afterAll, beforeAll, describe, expect, test } from 'bun:test'
2
- import { mkdtemp, rm, stat } from 'node:fs/promises'
2
+ import { mkdtemp, readFile, rm, stat } from 'node:fs/promises'
3
3
  import os from 'node:os'
4
4
  import path from 'node:path'
5
5
  import Fylo from '../../src'
6
6
  const root = await mkdtemp(path.join(os.tmpdir(), 'fylo-s3files-'))
7
- const fylo = new Fylo({ engine: 's3-files', s3FilesRoot: root })
7
+ const fylo = new Fylo({ root })
8
8
  const POSTS = 's3files-posts'
9
9
  const USERS = 's3files-users'
10
10
  describe('s3-files engine', () => {
@@ -59,10 +59,109 @@ describe('s3-files engine', () => {
59
59
  const result = await fylo.getDoc(POSTS, id).once()
60
60
  expect(result[id].body).toBe(longBody)
61
61
  })
62
- test('stores indexes in a single SQLite database instead of per-entry files', async () => {
63
- const dbStat = await stat(path.join(root, POSTS, '.fylo', 'index.db'))
64
- expect(dbStat.isFile()).toBe(true)
65
- await expect(stat(path.join(root, POSTS, '.fylo', 'indexes'))).rejects.toThrow()
62
+ test('stores only user document data in the file body', async () => {
63
+ const id = await fylo.putData(POSTS, {
64
+ title: 'Lean doc',
65
+ body: 'payload only'
66
+ })
67
+ const raw = JSON.parse(
68
+ await readFile(
69
+ path.join(root, POSTS, '.fylo', 'docs', id.slice(0, 2), `${id}.json`),
70
+ 'utf8'
71
+ )
72
+ )
73
+
74
+ expect(raw).toEqual({
75
+ title: 'Lean doc',
76
+ body: 'payload only'
77
+ })
78
+ expect(raw.id).toBeUndefined()
79
+ expect(raw.createdAt).toBeUndefined()
80
+ expect(raw.updatedAt).toBeUndefined()
81
+ })
82
+ test('stores indexes in a single collection file instead of SQLite or per-entry files', async () => {
83
+ const indexStat = await stat(
84
+ path.join(root, POSTS, '.fylo', 'indexes', `${POSTS}.idx.json`)
85
+ )
86
+ expect(indexStat.isFile()).toBe(true)
87
+ await expect(stat(path.join(root, POSTS, '.fylo', 'index.db'))).rejects.toThrow()
88
+ })
89
+ test('uses the collection index file to support exact, range, and contains queries', async () => {
90
+ const queryCollection = 's3files-query'
91
+ await fylo.createCollection(queryCollection)
92
+
93
+ const bunId = await fylo.putData(queryCollection, {
94
+ title: 'Bun launch',
95
+ tags: ['bun', 'aws'],
96
+ meta: { score: 10 }
97
+ })
98
+ const nodeId = await fylo.putData(queryCollection, {
99
+ title: 'Node launch',
100
+ tags: ['node'],
101
+ meta: { score: 2 }
102
+ })
103
+
104
+ let eqResults = {}
105
+ for await (const data of fylo
106
+ .findDocs(queryCollection, {
107
+ $ops: [{ title: { $eq: 'Bun launch' } }]
108
+ })
109
+ .collect()) {
110
+ eqResults = { ...eqResults, ...data }
111
+ }
112
+ expect(Object.keys(eqResults)).toEqual([bunId])
113
+
114
+ let rangeResults = {}
115
+ for await (const data of fylo
116
+ .findDocs(queryCollection, {
117
+ $ops: [{ ['meta.score']: { $gte: 5 } }]
118
+ })
119
+ .collect()) {
120
+ rangeResults = { ...rangeResults, ...data }
121
+ }
122
+ expect(Object.keys(rangeResults)).toEqual([bunId])
123
+
124
+ let containsResults = {}
125
+ for await (const data of fylo
126
+ .findDocs(queryCollection, {
127
+ $ops: [{ tags: { $contains: 'aws' } }]
128
+ })
129
+ .collect()) {
130
+ containsResults = { ...containsResults, ...data }
131
+ }
132
+ expect(Object.keys(containsResults)).toEqual([bunId])
133
+ expect(containsResults[nodeId]).toBeUndefined()
134
+
135
+ const index = JSON.parse(
136
+ await readFile(
137
+ path.join(root, queryCollection, '.fylo', 'indexes', `${queryCollection}.idx.json`),
138
+ 'utf8'
139
+ )
140
+ )
141
+ const rows = index.docs[bunId]
142
+
143
+ expect(rows).toEqual(
144
+ expect.arrayContaining([
145
+ expect.objectContaining({
146
+ fieldPath: 'title',
147
+ rawValue: 'Bun launch',
148
+ valueType: 'string',
149
+ numericValue: null
150
+ }),
151
+ expect.objectContaining({
152
+ fieldPath: 'meta/score',
153
+ rawValue: '10',
154
+ valueType: 'number',
155
+ numericValue: 10
156
+ }),
157
+ expect.objectContaining({
158
+ fieldPath: 'tags/1',
159
+ rawValue: 'aws',
160
+ valueType: 'string',
161
+ numericValue: null
162
+ })
163
+ ])
164
+ )
66
165
  })
67
166
  test('joins work in s3-files mode', async () => {
68
167
  const userId = await fylo.putData(USERS, { id: 42, name: 'Ada' })
@@ -79,30 +178,28 @@ describe('s3-files engine', () => {
79
178
  })
80
179
  test('queue APIs are explicitly unsupported', async () => {
81
180
  await expect(fylo.queuePutData(POSTS, { title: 'no queue' })).rejects.toThrow(
82
- 'queuePutData is not supported'
83
- )
84
- await expect(fylo.processQueuedWrites(1)).rejects.toThrow(
85
- 'processQueuedWrites is not supported'
181
+ 'queuePutData was removed'
86
182
  )
183
+ await expect(fylo.processQueuedWrites(1)).rejects.toThrow('processQueuedWrites was removed')
87
184
  })
88
185
  test('rejects collection names that are unsafe for cross-platform filesystems', async () => {
89
186
  await expect(fylo.createCollection('bad/name')).rejects.toThrow('Invalid collection name')
90
187
  await expect(fylo.createCollection('bad\\name')).rejects.toThrow('Invalid collection name')
91
188
  await expect(fylo.createCollection('bad:name')).rejects.toThrow('Invalid collection name')
92
189
  })
93
- test('static helpers can use s3-files through env defaults', async () => {
94
- const prevEngine = process.env.FYLO_STORAGE_ENGINE
95
- const prevRoot = process.env.FYLO_S3FILES_ROOT
96
- process.env.FYLO_STORAGE_ENGINE = 's3-files'
190
+ test('static helpers can use filesystem root env defaults', async () => {
191
+ const prevFyloRoot = process.env.FYLO_ROOT
192
+ const prevS3FilesRoot = process.env.FYLO_S3FILES_ROOT
193
+ process.env.FYLO_ROOT = root
97
194
  process.env.FYLO_S3FILES_ROOT = root
98
195
  const collection = 's3files-static'
99
196
  await Fylo.createCollection(collection)
100
197
  const id = await fylo.putData(collection, { title: 'Static path' })
101
198
  const result = await Fylo.getDoc(collection, id).once()
102
199
  expect(result[id].title).toBe('Static path')
103
- if (prevEngine === undefined) delete process.env.FYLO_STORAGE_ENGINE
104
- else process.env.FYLO_STORAGE_ENGINE = prevEngine
105
- if (prevRoot === undefined) delete process.env.FYLO_S3FILES_ROOT
106
- else process.env.FYLO_S3FILES_ROOT = prevRoot
200
+ if (prevFyloRoot === undefined) delete process.env.FYLO_ROOT
201
+ else process.env.FYLO_ROOT = prevFyloRoot
202
+ if (prevS3FilesRoot === undefined) delete process.env.FYLO_S3FILES_ROOT
203
+ else process.env.FYLO_S3FILES_ROOT = prevS3FilesRoot
107
204
  })
108
205
  })