@delma/fylo 2.0.1 → 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.
- package/README.md +185 -267
- package/package.json +2 -5
- package/src/core/directory.ts +22 -354
- package/src/engines/s3-files/documents.ts +65 -0
- package/src/engines/s3-files/filesystem.ts +172 -0
- package/src/engines/s3-files/query.ts +291 -0
- package/src/engines/s3-files/types.ts +42 -0
- package/src/engines/s3-files.ts +391 -690
- package/src/engines/types.ts +1 -1
- package/src/index.ts +142 -1237
- package/src/sync.ts +58 -0
- package/src/types/fylo.d.ts +66 -161
- package/src/types/node-runtime.d.ts +1 -0
- package/tests/collection/truncate.test.js +11 -10
- package/tests/helpers/root.js +7 -0
- package/tests/integration/create.test.js +9 -9
- package/tests/integration/delete.test.js +16 -14
- package/tests/integration/edge-cases.test.js +29 -25
- package/tests/integration/encryption.test.js +47 -30
- package/tests/integration/export.test.js +11 -11
- package/tests/integration/join-modes.test.js +16 -16
- package/tests/integration/nested.test.js +26 -24
- package/tests/integration/operators.test.js +43 -29
- package/tests/integration/read.test.js +25 -21
- package/tests/integration/rollback.test.js +21 -51
- package/tests/integration/s3-files.performance.test.js +75 -0
- package/tests/integration/s3-files.test.js +57 -44
- package/tests/integration/sync.test.js +154 -0
- package/tests/integration/update.test.js +24 -18
- package/src/adapters/redis.ts +0 -487
- package/src/adapters/s3.ts +0 -61
- package/src/core/walker.ts +0 -174
- package/src/core/write-queue.ts +0 -59
- package/src/migrate-cli.ts +0 -22
- package/src/migrate.ts +0 -74
- package/src/types/write-queue.ts +0 -42
- package/src/worker.ts +0 -18
- package/src/workers/write-worker.ts +0 -120
- package/tests/index.js +0 -14
- package/tests/integration/migration.test.js +0 -38
- package/tests/integration/queue.test.js +0 -83
- package/tests/mocks/redis.js +0 -123
- package/tests/mocks/s3.js +0 -80
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import { test, expect, describe, beforeAll, afterAll
|
|
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
|
|
5
|
-
import RedisMock from '../mocks/redis'
|
|
5
|
+
import { createTestRoot } from '../helpers/root'
|
|
6
6
|
const ALBUMS = 'ops-album'
|
|
7
|
-
const
|
|
8
|
-
|
|
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
|
|
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
|
|
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
|
|
25
|
-
|
|
26
|
-
|
|
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
|
|
37
|
-
|
|
38
|
-
|
|
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
|
|
49
|
-
|
|
50
|
-
|
|
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
|
|
61
|
-
|
|
62
|
-
|
|
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
|
|
73
|
-
|
|
74
|
-
|
|
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
|
|
85
|
-
|
|
86
|
-
|
|
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
|
|
97
|
-
|
|
98
|
-
|
|
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
|
|
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
|
|
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
|
|
10
|
-
|
|
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([
|
|
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([
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
53
|
-
|
|
54
|
-
|
|
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
|
|
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
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
|
83
|
+
const results = await fylo.joinDocs({
|
|
80
84
|
$leftCollection: ALBUMS,
|
|
81
85
|
$rightCollection: POSTS,
|
|
82
86
|
$mode: 'inner',
|
|
@@ -1,60 +1,30 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
4
|
-
|
|
6
|
+
|
|
7
|
+
const root = await mkdtemp(path.join(os.tmpdir(), 'fylo-rollback-'))
|
|
5
8
|
const POSTS = 'rb-post'
|
|
6
|
-
const fylo = new Fylo()
|
|
7
|
-
|
|
8
|
-
mock.module('../../src/adapters/redis', () => ({ Redis: RedisMock }))
|
|
9
|
+
const fylo = new Fylo({ root })
|
|
10
|
+
|
|
9
11
|
beforeAll(async () => {
|
|
10
|
-
await
|
|
12
|
+
await fylo.createCollection(POSTS)
|
|
11
13
|
})
|
|
14
|
+
|
|
12
15
|
afterAll(async () => {
|
|
13
|
-
await
|
|
16
|
+
await rm(root, { recursive: true, force: true })
|
|
14
17
|
})
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
24
|
-
expect(
|
|
25
|
-
|
|
26
|
-
const after = await
|
|
27
|
-
expect(
|
|
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,11 +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
|
-
import { Database } from 'bun:sqlite'
|
|
6
5
|
import Fylo from '../../src'
|
|
7
6
|
const root = await mkdtemp(path.join(os.tmpdir(), 'fylo-s3files-'))
|
|
8
|
-
const fylo = new Fylo({
|
|
7
|
+
const fylo = new Fylo({ root })
|
|
9
8
|
const POSTS = 's3files-posts'
|
|
10
9
|
const USERS = 's3files-users'
|
|
11
10
|
describe('s3-files engine', () => {
|
|
@@ -60,12 +59,34 @@ describe('s3-files engine', () => {
|
|
|
60
59
|
const result = await fylo.getDoc(POSTS, id).once()
|
|
61
60
|
expect(result[id].body).toBe(longBody)
|
|
62
61
|
})
|
|
63
|
-
test('stores
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
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()
|
|
67
88
|
})
|
|
68
|
-
test('uses
|
|
89
|
+
test('uses the collection index file to support exact, range, and contains queries', async () => {
|
|
69
90
|
const queryCollection = 's3files-query'
|
|
70
91
|
await fylo.createCollection(queryCollection)
|
|
71
92
|
|
|
@@ -111,39 +132,33 @@ describe('s3-files engine', () => {
|
|
|
111
132
|
expect(Object.keys(containsResults)).toEqual([bunId])
|
|
112
133
|
expect(containsResults[nodeId]).toBeUndefined()
|
|
113
134
|
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
FROM doc_index_entries
|
|
119
|
-
WHERE doc_id = ?
|
|
120
|
-
ORDER BY field_path, raw_value`
|
|
135
|
+
const index = JSON.parse(
|
|
136
|
+
await readFile(
|
|
137
|
+
path.join(root, queryCollection, '.fylo', 'indexes', `${queryCollection}.idx.json`),
|
|
138
|
+
'utf8'
|
|
121
139
|
)
|
|
122
|
-
|
|
123
|
-
|
|
140
|
+
)
|
|
141
|
+
const rows = index.docs[bunId]
|
|
124
142
|
|
|
125
143
|
expect(rows).toEqual(
|
|
126
144
|
expect.arrayContaining([
|
|
127
145
|
expect.objectContaining({
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
numeric_value: null
|
|
146
|
+
fieldPath: 'title',
|
|
147
|
+
rawValue: 'Bun launch',
|
|
148
|
+
valueType: 'string',
|
|
149
|
+
numericValue: null
|
|
133
150
|
}),
|
|
134
151
|
expect.objectContaining({
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
numeric_value: 10
|
|
152
|
+
fieldPath: 'meta/score',
|
|
153
|
+
rawValue: '10',
|
|
154
|
+
valueType: 'number',
|
|
155
|
+
numericValue: 10
|
|
140
156
|
}),
|
|
141
157
|
expect.objectContaining({
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
numeric_value: null
|
|
158
|
+
fieldPath: 'tags/1',
|
|
159
|
+
rawValue: 'aws',
|
|
160
|
+
valueType: 'string',
|
|
161
|
+
numericValue: null
|
|
147
162
|
})
|
|
148
163
|
])
|
|
149
164
|
)
|
|
@@ -163,30 +178,28 @@ describe('s3-files engine', () => {
|
|
|
163
178
|
})
|
|
164
179
|
test('queue APIs are explicitly unsupported', async () => {
|
|
165
180
|
await expect(fylo.queuePutData(POSTS, { title: 'no queue' })).rejects.toThrow(
|
|
166
|
-
'queuePutData
|
|
167
|
-
)
|
|
168
|
-
await expect(fylo.processQueuedWrites(1)).rejects.toThrow(
|
|
169
|
-
'processQueuedWrites is not supported'
|
|
181
|
+
'queuePutData was removed'
|
|
170
182
|
)
|
|
183
|
+
await expect(fylo.processQueuedWrites(1)).rejects.toThrow('processQueuedWrites was removed')
|
|
171
184
|
})
|
|
172
185
|
test('rejects collection names that are unsafe for cross-platform filesystems', async () => {
|
|
173
186
|
await expect(fylo.createCollection('bad/name')).rejects.toThrow('Invalid collection name')
|
|
174
187
|
await expect(fylo.createCollection('bad\\name')).rejects.toThrow('Invalid collection name')
|
|
175
188
|
await expect(fylo.createCollection('bad:name')).rejects.toThrow('Invalid collection name')
|
|
176
189
|
})
|
|
177
|
-
test('static helpers can use
|
|
178
|
-
const
|
|
179
|
-
const
|
|
180
|
-
process.env.
|
|
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
|
|
181
194
|
process.env.FYLO_S3FILES_ROOT = root
|
|
182
195
|
const collection = 's3files-static'
|
|
183
196
|
await Fylo.createCollection(collection)
|
|
184
197
|
const id = await fylo.putData(collection, { title: 'Static path' })
|
|
185
198
|
const result = await Fylo.getDoc(collection, id).once()
|
|
186
199
|
expect(result[id].title).toBe('Static path')
|
|
187
|
-
if (
|
|
188
|
-
else process.env.
|
|
189
|
-
if (
|
|
190
|
-
else process.env.FYLO_S3FILES_ROOT =
|
|
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
|
|
191
204
|
})
|
|
192
205
|
})
|