@delma/fylo 2.1.0 → 2.1.1
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 +27 -0
- package/dist/adapters/cipher.js +155 -0
- package/dist/adapters/cipher.js.map +1 -0
- package/dist/core/collection.js +6 -0
- package/dist/core/collection.js.map +1 -0
- package/{src/core/directory.ts → dist/core/directory.js} +28 -35
- package/dist/core/directory.js.map +1 -0
- package/dist/core/doc-id.js +15 -0
- package/dist/core/doc-id.js.map +1 -0
- package/dist/core/extensions.js +16 -0
- package/dist/core/extensions.js.map +1 -0
- package/dist/core/format.js +355 -0
- package/dist/core/format.js.map +1 -0
- package/dist/core/parser.js +764 -0
- package/dist/core/parser.js.map +1 -0
- package/dist/core/query.js +47 -0
- package/dist/core/query.js.map +1 -0
- package/dist/engines/s3-files/documents.js +62 -0
- package/dist/engines/s3-files/documents.js.map +1 -0
- package/dist/engines/s3-files/filesystem.js +165 -0
- package/dist/engines/s3-files/filesystem.js.map +1 -0
- package/dist/engines/s3-files/query.js +235 -0
- package/dist/engines/s3-files/query.js.map +1 -0
- package/dist/engines/s3-files/types.js +2 -0
- package/dist/engines/s3-files/types.js.map +1 -0
- package/dist/engines/s3-files.js +629 -0
- package/dist/engines/s3-files.js.map +1 -0
- package/dist/engines/types.js +2 -0
- package/dist/engines/types.js.map +1 -0
- package/dist/index.js +562 -0
- package/dist/index.js.map +1 -0
- package/dist/sync.js +18 -0
- package/dist/sync.js.map +1 -0
- package/{src → dist}/types/fylo.d.ts +14 -1
- package/package.json +2 -2
- package/.env.example +0 -16
- package/.github/copilot-instructions.md +0 -3
- package/.github/prompts/release.prompt.md +0 -10
- package/.github/workflows/ci.yml +0 -37
- package/.github/workflows/publish.yml +0 -91
- package/.prettierrc +0 -7
- package/AGENTS.md +0 -3
- package/CLAUDE.md +0 -3
- package/eslint.config.js +0 -32
- package/src/CLI +0 -39
- package/src/adapters/cipher.ts +0 -180
- package/src/core/collection.ts +0 -5
- package/src/core/extensions.ts +0 -21
- package/src/core/format.ts +0 -457
- package/src/core/parser.ts +0 -901
- package/src/core/query.ts +0 -53
- package/src/engines/s3-files/documents.ts +0 -65
- package/src/engines/s3-files/filesystem.ts +0 -172
- package/src/engines/s3-files/query.ts +0 -291
- package/src/engines/s3-files/types.ts +0 -42
- package/src/engines/s3-files.ts +0 -769
- package/src/engines/types.ts +0 -21
- package/src/index.ts +0 -632
- package/src/sync.ts +0 -58
- package/tests/collection/truncate.test.js +0 -36
- package/tests/data.js +0 -97
- package/tests/helpers/root.js +0 -7
- package/tests/integration/aws-s3-files.canary.test.js +0 -22
- package/tests/integration/create.test.js +0 -39
- package/tests/integration/delete.test.js +0 -97
- package/tests/integration/edge-cases.test.js +0 -162
- package/tests/integration/encryption.test.js +0 -148
- package/tests/integration/export.test.js +0 -46
- package/tests/integration/join-modes.test.js +0 -154
- package/tests/integration/nested.test.js +0 -144
- package/tests/integration/operators.test.js +0 -136
- package/tests/integration/read.test.js +0 -123
- package/tests/integration/rollback.test.js +0 -30
- package/tests/integration/s3-files.performance.test.js +0 -75
- package/tests/integration/s3-files.test.js +0 -205
- package/tests/integration/sync.test.js +0 -154
- package/tests/integration/update.test.js +0 -105
- package/tests/mocks/cipher.js +0 -40
- package/tests/schemas/album.d.ts +0 -5
- package/tests/schemas/album.json +0 -5
- package/tests/schemas/comment.d.ts +0 -7
- package/tests/schemas/comment.json +0 -7
- package/tests/schemas/photo.d.ts +0 -7
- package/tests/schemas/photo.json +0 -7
- package/tests/schemas/post.d.ts +0 -6
- package/tests/schemas/post.json +0 -6
- package/tests/schemas/tip.d.ts +0 -7
- package/tests/schemas/tip.json +0 -7
- package/tests/schemas/todo.d.ts +0 -6
- package/tests/schemas/todo.json +0 -6
- package/tests/schemas/user.d.ts +0 -23
- package/tests/schemas/user.json +0 -23
- package/tsconfig.json +0 -21
- package/tsconfig.typecheck.json +0 -31
- /package/{src → dist}/types/bun-runtime.d.ts +0 -0
- /package/{src → dist}/types/index.d.ts +0 -0
- /package/{src → dist}/types/node-runtime.d.ts +0 -0
- /package/{src → dist}/types/query.d.ts +0 -0
- /package/{src → dist}/types/vendor-modules.d.ts +0 -0
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
import { test, expect, describe, beforeAll, afterAll } from 'bun:test'
|
|
2
|
-
import { rm } from 'node:fs/promises'
|
|
3
|
-
import Fylo from '../../src'
|
|
4
|
-
import { albumURL, postsURL } from '../data'
|
|
5
|
-
import { createTestRoot } from '../helpers/root'
|
|
6
|
-
const ALBUMS = 'jm-album'
|
|
7
|
-
const POSTS = 'jm-post'
|
|
8
|
-
const root = await createTestRoot('fylo-join-')
|
|
9
|
-
const fylo = new Fylo({ root })
|
|
10
|
-
beforeAll(async () => {
|
|
11
|
-
await Promise.all([fylo.createCollection(ALBUMS), fylo.createCollection(POSTS)])
|
|
12
|
-
try {
|
|
13
|
-
await Promise.all([
|
|
14
|
-
fylo.importBulkData(ALBUMS, new URL(albumURL), 100),
|
|
15
|
-
fylo.importBulkData(POSTS, new URL(postsURL), 100)
|
|
16
|
-
])
|
|
17
|
-
} catch {
|
|
18
|
-
await fylo.rollback()
|
|
19
|
-
}
|
|
20
|
-
})
|
|
21
|
-
afterAll(async () => {
|
|
22
|
-
await Promise.all([fylo.dropCollection(ALBUMS), fylo.dropCollection(POSTS)])
|
|
23
|
-
await rm(root, { recursive: true, force: true })
|
|
24
|
-
})
|
|
25
|
-
describe('NO-SQL', async () => {
|
|
26
|
-
test('INNER JOIN — returns only join field values', async () => {
|
|
27
|
-
const results = await fylo.joinDocs({
|
|
28
|
-
$leftCollection: ALBUMS,
|
|
29
|
-
$rightCollection: POSTS,
|
|
30
|
-
$mode: 'inner',
|
|
31
|
-
$on: { userId: { $eq: 'userId' } }
|
|
32
|
-
})
|
|
33
|
-
const pairs = Object.values(results)
|
|
34
|
-
expect(pairs.length).toBeGreaterThan(0)
|
|
35
|
-
for (const pair of pairs) {
|
|
36
|
-
expect(pair).toHaveProperty('userId')
|
|
37
|
-
expect(typeof pair.userId).toBe('number')
|
|
38
|
-
}
|
|
39
|
-
})
|
|
40
|
-
test('LEFT JOIN — returns full left-collection document', async () => {
|
|
41
|
-
const results = await fylo.joinDocs({
|
|
42
|
-
$leftCollection: ALBUMS,
|
|
43
|
-
$rightCollection: POSTS,
|
|
44
|
-
$mode: 'left',
|
|
45
|
-
$on: { userId: { $eq: 'userId' } }
|
|
46
|
-
})
|
|
47
|
-
const docs = Object.values(results)
|
|
48
|
-
expect(docs.length).toBeGreaterThan(0)
|
|
49
|
-
for (const doc of docs) {
|
|
50
|
-
expect(doc).toHaveProperty('title')
|
|
51
|
-
expect(doc).toHaveProperty('userId')
|
|
52
|
-
}
|
|
53
|
-
})
|
|
54
|
-
test('RIGHT JOIN — returns full right-collection document', async () => {
|
|
55
|
-
const results = await fylo.joinDocs({
|
|
56
|
-
$leftCollection: ALBUMS,
|
|
57
|
-
$rightCollection: POSTS,
|
|
58
|
-
$mode: 'right',
|
|
59
|
-
$on: { userId: { $eq: 'userId' } }
|
|
60
|
-
})
|
|
61
|
-
const docs = Object.values(results)
|
|
62
|
-
expect(docs.length).toBeGreaterThan(0)
|
|
63
|
-
for (const doc of docs) {
|
|
64
|
-
expect(doc).toHaveProperty('title')
|
|
65
|
-
expect(doc).toHaveProperty('body')
|
|
66
|
-
expect(doc).toHaveProperty('userId')
|
|
67
|
-
}
|
|
68
|
-
})
|
|
69
|
-
test('OUTER JOIN — returns merged left + right document', async () => {
|
|
70
|
-
const results = await fylo.joinDocs({
|
|
71
|
-
$leftCollection: ALBUMS,
|
|
72
|
-
$rightCollection: POSTS,
|
|
73
|
-
$mode: 'outer',
|
|
74
|
-
$on: { userId: { $eq: 'userId' } }
|
|
75
|
-
})
|
|
76
|
-
const docs = Object.values(results)
|
|
77
|
-
expect(docs.length).toBeGreaterThan(0)
|
|
78
|
-
for (const doc of docs) {
|
|
79
|
-
expect(doc).toHaveProperty('userId')
|
|
80
|
-
}
|
|
81
|
-
})
|
|
82
|
-
test('JOIN with $limit — respects the result cap', async () => {
|
|
83
|
-
const results = await fylo.joinDocs({
|
|
84
|
-
$leftCollection: ALBUMS,
|
|
85
|
-
$rightCollection: POSTS,
|
|
86
|
-
$mode: 'inner',
|
|
87
|
-
$on: { userId: { $eq: 'userId' } },
|
|
88
|
-
$limit: 5
|
|
89
|
-
})
|
|
90
|
-
expect(Object.keys(results).length).toBe(5)
|
|
91
|
-
})
|
|
92
|
-
test('JOIN with $select — only requested fields are returned', async () => {
|
|
93
|
-
const results = await fylo.joinDocs({
|
|
94
|
-
$leftCollection: ALBUMS,
|
|
95
|
-
$rightCollection: POSTS,
|
|
96
|
-
$mode: 'left',
|
|
97
|
-
$on: { userId: { $eq: 'userId' } },
|
|
98
|
-
$select: ['title'],
|
|
99
|
-
$limit: 10
|
|
100
|
-
})
|
|
101
|
-
const docs = Object.values(results)
|
|
102
|
-
expect(docs.length).toBeGreaterThan(0)
|
|
103
|
-
for (const doc of docs) {
|
|
104
|
-
expect(doc).toHaveProperty('title')
|
|
105
|
-
expect(doc).not.toHaveProperty('userId')
|
|
106
|
-
}
|
|
107
|
-
})
|
|
108
|
-
test('JOIN with $groupby — groups results by field value', async () => {
|
|
109
|
-
const results = await fylo.joinDocs({
|
|
110
|
-
$leftCollection: ALBUMS,
|
|
111
|
-
$rightCollection: POSTS,
|
|
112
|
-
$mode: 'inner',
|
|
113
|
-
$on: { userId: { $eq: 'userId' } },
|
|
114
|
-
$groupby: 'userId'
|
|
115
|
-
})
|
|
116
|
-
expect(Object.keys(results).length).toBeGreaterThan(0)
|
|
117
|
-
})
|
|
118
|
-
test('JOIN with $onlyIds — returns IDs only', async () => {
|
|
119
|
-
const results = await fylo.joinDocs({
|
|
120
|
-
$leftCollection: ALBUMS,
|
|
121
|
-
$rightCollection: POSTS,
|
|
122
|
-
$mode: 'inner',
|
|
123
|
-
$on: { userId: { $eq: 'userId' } },
|
|
124
|
-
$onlyIds: true,
|
|
125
|
-
$limit: 10
|
|
126
|
-
})
|
|
127
|
-
expect(Array.isArray(results)).toBe(true)
|
|
128
|
-
expect(results.length).toBeGreaterThan(0)
|
|
129
|
-
})
|
|
130
|
-
})
|
|
131
|
-
describe('SQL', async () => {
|
|
132
|
-
test('INNER JOIN', async () => {
|
|
133
|
-
const results = await fylo.executeSQL(
|
|
134
|
-
`SELECT * FROM ${ALBUMS} INNER JOIN ${POSTS} ON userId = userId`
|
|
135
|
-
)
|
|
136
|
-
expect(Object.keys(results).length).toBeGreaterThan(0)
|
|
137
|
-
})
|
|
138
|
-
test('LEFT JOIN', async () => {
|
|
139
|
-
const results = await fylo.executeSQL(
|
|
140
|
-
`SELECT * FROM ${ALBUMS} LEFT JOIN ${POSTS} ON userId = userId`
|
|
141
|
-
)
|
|
142
|
-
const docs = Object.values(results)
|
|
143
|
-
expect(docs.length).toBeGreaterThan(0)
|
|
144
|
-
expect(docs[0]).toHaveProperty('title')
|
|
145
|
-
})
|
|
146
|
-
test('RIGHT JOIN', async () => {
|
|
147
|
-
const results = await fylo.executeSQL(
|
|
148
|
-
`SELECT * FROM ${ALBUMS} RIGHT JOIN ${POSTS} ON userId = userId`
|
|
149
|
-
)
|
|
150
|
-
const docs = Object.values(results)
|
|
151
|
-
expect(docs.length).toBeGreaterThan(0)
|
|
152
|
-
expect(docs[0]).toHaveProperty('body')
|
|
153
|
-
})
|
|
154
|
-
})
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
import { test, expect, describe, beforeAll, afterAll } from 'bun:test'
|
|
2
|
-
import { rm } from 'node:fs/promises'
|
|
3
|
-
import Fylo from '../../src'
|
|
4
|
-
import { usersURL } from '../data'
|
|
5
|
-
import { createTestRoot } from '../helpers/root'
|
|
6
|
-
const USERS = 'nst-user'
|
|
7
|
-
let insertedCount = 0
|
|
8
|
-
let sampleId
|
|
9
|
-
const root = await createTestRoot('fylo-nested-')
|
|
10
|
-
const fylo = new Fylo({ root })
|
|
11
|
-
beforeAll(async () => {
|
|
12
|
-
await fylo.createCollection(USERS)
|
|
13
|
-
try {
|
|
14
|
-
insertedCount = await fylo.importBulkData(USERS, new URL(usersURL))
|
|
15
|
-
} catch {
|
|
16
|
-
await fylo.rollback()
|
|
17
|
-
}
|
|
18
|
-
for await (const data of fylo.findDocs(USERS, { $limit: 1, $onlyIds: true }).collect()) {
|
|
19
|
-
sampleId = data
|
|
20
|
-
}
|
|
21
|
-
})
|
|
22
|
-
afterAll(async () => {
|
|
23
|
-
await fylo.dropCollection(USERS)
|
|
24
|
-
await rm(root, { recursive: true, force: true })
|
|
25
|
-
})
|
|
26
|
-
describe('NO-SQL', async () => {
|
|
27
|
-
test('SELECT ALL — nested documents are returned', async () => {
|
|
28
|
-
let results = {}
|
|
29
|
-
for await (const data of fylo.findDocs(USERS).collect()) {
|
|
30
|
-
results = { ...results, ...data }
|
|
31
|
-
}
|
|
32
|
-
expect(Object.keys(results).length).toBe(insertedCount)
|
|
33
|
-
})
|
|
34
|
-
test('GET ONE — top-level fields are reconstructed correctly', async () => {
|
|
35
|
-
const result = await fylo.getDoc(USERS, sampleId).once()
|
|
36
|
-
const user = result[sampleId]
|
|
37
|
-
expect(user).toBeDefined()
|
|
38
|
-
expect(typeof user.name).toBe('string')
|
|
39
|
-
expect(typeof user.email).toBe('string')
|
|
40
|
-
expect(typeof user.phone).toBe('string')
|
|
41
|
-
})
|
|
42
|
-
test('GET ONE — first-level nested object is reconstructed correctly', async () => {
|
|
43
|
-
const result = await fylo.getDoc(USERS, sampleId).once()
|
|
44
|
-
const user = result[sampleId]
|
|
45
|
-
expect(user.address).toBeDefined()
|
|
46
|
-
expect(typeof user.address.city).toBe('string')
|
|
47
|
-
expect(typeof user.address.street).toBe('string')
|
|
48
|
-
expect(typeof user.address.zipcode).toBe('string')
|
|
49
|
-
})
|
|
50
|
-
test('GET ONE — deeply nested object is reconstructed correctly', async () => {
|
|
51
|
-
const result = await fylo.getDoc(USERS, sampleId).once()
|
|
52
|
-
const user = result[sampleId]
|
|
53
|
-
expect(user.address.geo).toBeDefined()
|
|
54
|
-
expect(typeof user.address.geo.lat).toBe('number')
|
|
55
|
-
expect(typeof user.address.geo.lng).toBe('number')
|
|
56
|
-
})
|
|
57
|
-
test('GET ONE — second nested object is reconstructed correctly', async () => {
|
|
58
|
-
const result = await fylo.getDoc(USERS, sampleId).once()
|
|
59
|
-
const user = result[sampleId]
|
|
60
|
-
expect(user.company).toBeDefined()
|
|
61
|
-
expect(typeof user.company.name).toBe('string')
|
|
62
|
-
expect(typeof user.company.catchPhrase).toBe('string')
|
|
63
|
-
expect(typeof user.company.bs).toBe('string')
|
|
64
|
-
})
|
|
65
|
-
test('SELECT — nested values are not corrupted across documents', async () => {
|
|
66
|
-
for await (const data of fylo.findDocs(USERS).collect()) {
|
|
67
|
-
const [, user] = Object.entries(data)[0]
|
|
68
|
-
expect(user.address).toBeDefined()
|
|
69
|
-
expect(user.address.geo).toBeDefined()
|
|
70
|
-
expect(typeof user.address.geo.lat).toBe('number')
|
|
71
|
-
expect(user.company).toBeDefined()
|
|
72
|
-
}
|
|
73
|
-
})
|
|
74
|
-
test('$select — returns only requested top-level fields', async () => {
|
|
75
|
-
let results = {}
|
|
76
|
-
for await (const data of fylo.findDocs(USERS, { $select: ['name', 'email'] }).collect()) {
|
|
77
|
-
results = { ...results, ...data }
|
|
78
|
-
}
|
|
79
|
-
const users = Object.values(results)
|
|
80
|
-
const onlyNameAndEmail = users.every((u) => u.name && u.email && !u.phone && !u.address)
|
|
81
|
-
expect(onlyNameAndEmail).toBe(true)
|
|
82
|
-
})
|
|
83
|
-
test('$eq on nested string field — query by city', async () => {
|
|
84
|
-
const result = await fylo.getDoc(USERS, sampleId).once()
|
|
85
|
-
const targetCity = result[sampleId].address.city
|
|
86
|
-
let results = {}
|
|
87
|
-
for await (const data of fylo
|
|
88
|
-
.findDocs(USERS, {
|
|
89
|
-
$ops: [{ ['address/city']: { $eq: targetCity } }]
|
|
90
|
-
})
|
|
91
|
-
.collect()) {
|
|
92
|
-
results = { ...results, ...data }
|
|
93
|
-
}
|
|
94
|
-
const matchingUsers = Object.values(results)
|
|
95
|
-
const allMatch = matchingUsers.every((u) => u.address.city === targetCity)
|
|
96
|
-
expect(allMatch).toBe(true)
|
|
97
|
-
expect(matchingUsers.length).toBeGreaterThan(0)
|
|
98
|
-
})
|
|
99
|
-
})
|
|
100
|
-
describe('SQL — dot notation', async () => {
|
|
101
|
-
test('WHERE with dot notation — first-level nested field', async () => {
|
|
102
|
-
const result = await fylo.getDoc(USERS, sampleId).once()
|
|
103
|
-
const targetCity = result[sampleId].address.city
|
|
104
|
-
const results = await fylo.executeSQL(
|
|
105
|
-
`SELECT * FROM ${USERS} WHERE address.city = '${targetCity}'`
|
|
106
|
-
)
|
|
107
|
-
const users = Object.values(results)
|
|
108
|
-
const allMatch = users.every((u) => u.address.city === targetCity)
|
|
109
|
-
expect(allMatch).toBe(true)
|
|
110
|
-
expect(users.length).toBeGreaterThan(0)
|
|
111
|
-
})
|
|
112
|
-
test('WHERE with dot notation — deeply nested field', async () => {
|
|
113
|
-
const result = await fylo.getDoc(USERS, sampleId).once()
|
|
114
|
-
const targetLat = result[sampleId].address.geo.lat
|
|
115
|
-
const results = await fylo.executeSQL(
|
|
116
|
-
`SELECT * FROM ${USERS} WHERE address.geo.lat = '${targetLat}'`
|
|
117
|
-
)
|
|
118
|
-
const users = Object.values(results)
|
|
119
|
-
const allMatch = users.every((u) => u.address.geo.lat === targetLat)
|
|
120
|
-
expect(allMatch).toBe(true)
|
|
121
|
-
expect(users.length).toBeGreaterThan(0)
|
|
122
|
-
})
|
|
123
|
-
test('WHERE with dot notation — second nested object', async () => {
|
|
124
|
-
const result = await fylo.getDoc(USERS, sampleId).once()
|
|
125
|
-
const targetCompany = result[sampleId].company.name
|
|
126
|
-
const results = await fylo.executeSQL(
|
|
127
|
-
`SELECT * FROM ${USERS} WHERE company.name = '${targetCompany}'`
|
|
128
|
-
)
|
|
129
|
-
const users = Object.values(results)
|
|
130
|
-
const allMatch = users.every((u) => u.company.name === targetCompany)
|
|
131
|
-
expect(allMatch).toBe(true)
|
|
132
|
-
expect(users.length).toBeGreaterThan(0)
|
|
133
|
-
})
|
|
134
|
-
test('SELECT with dot notation in WHERE — partial field selection', async () => {
|
|
135
|
-
const result = await fylo.getDoc(USERS, sampleId).once()
|
|
136
|
-
const targetCity = result[sampleId].address.city
|
|
137
|
-
const results = await fylo.executeSQL(
|
|
138
|
-
`SELECT name, email FROM ${USERS} WHERE address.city = '${targetCity}'`
|
|
139
|
-
)
|
|
140
|
-
const users = Object.values(results)
|
|
141
|
-
expect(users.length).toBeGreaterThan(0)
|
|
142
|
-
expect(users.every((u) => u.name && u.email && !u.phone)).toBe(true)
|
|
143
|
-
})
|
|
144
|
-
})
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
import { test, expect, describe, beforeAll, afterAll } from 'bun:test'
|
|
2
|
-
import { rm } from 'node:fs/promises'
|
|
3
|
-
import Fylo from '../../src'
|
|
4
|
-
import { albumURL } from '../data'
|
|
5
|
-
import { createTestRoot } from '../helpers/root'
|
|
6
|
-
const ALBUMS = 'ops-album'
|
|
7
|
-
const root = await createTestRoot('fylo-operators-')
|
|
8
|
-
const fylo = new Fylo({ root })
|
|
9
|
-
beforeAll(async () => {
|
|
10
|
-
await fylo.createCollection(ALBUMS)
|
|
11
|
-
try {
|
|
12
|
-
await fylo.importBulkData(ALBUMS, new URL(albumURL), 100)
|
|
13
|
-
} catch {
|
|
14
|
-
await fylo.rollback()
|
|
15
|
-
}
|
|
16
|
-
})
|
|
17
|
-
afterAll(async () => {
|
|
18
|
-
await fylo.dropCollection(ALBUMS)
|
|
19
|
-
await rm(root, { recursive: true, force: true })
|
|
20
|
-
})
|
|
21
|
-
describe('NO-SQL', async () => {
|
|
22
|
-
test('$ne — excludes matching value', async () => {
|
|
23
|
-
let results = {}
|
|
24
|
-
for await (const data of fylo
|
|
25
|
-
.findDocs(ALBUMS, {
|
|
26
|
-
$ops: [{ userId: { $ne: 1 } }]
|
|
27
|
-
})
|
|
28
|
-
.collect()) {
|
|
29
|
-
results = { ...results, ...data }
|
|
30
|
-
}
|
|
31
|
-
const albums = Object.values(results)
|
|
32
|
-
const hasUserId1 = albums.some((a) => a.userId === 1)
|
|
33
|
-
expect(hasUserId1).toBe(false)
|
|
34
|
-
expect(albums.length).toBe(90)
|
|
35
|
-
})
|
|
36
|
-
test('$lt — returns documents where field is less than value', async () => {
|
|
37
|
-
let results = {}
|
|
38
|
-
for await (const data of fylo
|
|
39
|
-
.findDocs(ALBUMS, {
|
|
40
|
-
$ops: [{ userId: { $lt: 5 } }]
|
|
41
|
-
})
|
|
42
|
-
.collect()) {
|
|
43
|
-
results = { ...results, ...data }
|
|
44
|
-
}
|
|
45
|
-
const albums = Object.values(results)
|
|
46
|
-
const allLessThan5 = albums.every((a) => a.userId < 5)
|
|
47
|
-
expect(allLessThan5).toBe(true)
|
|
48
|
-
expect(albums.length).toBe(40)
|
|
49
|
-
})
|
|
50
|
-
test('$lte — returns documents where field is less than or equal to value', async () => {
|
|
51
|
-
let results = {}
|
|
52
|
-
for await (const data of fylo
|
|
53
|
-
.findDocs(ALBUMS, {
|
|
54
|
-
$ops: [{ userId: { $lte: 5 } }]
|
|
55
|
-
})
|
|
56
|
-
.collect()) {
|
|
57
|
-
results = { ...results, ...data }
|
|
58
|
-
}
|
|
59
|
-
const albums = Object.values(results)
|
|
60
|
-
const allLte5 = albums.every((a) => a.userId <= 5)
|
|
61
|
-
expect(allLte5).toBe(true)
|
|
62
|
-
expect(albums.length).toBe(50)
|
|
63
|
-
})
|
|
64
|
-
test('$gt — returns documents where field is greater than value', async () => {
|
|
65
|
-
let results = {}
|
|
66
|
-
for await (const data of fylo
|
|
67
|
-
.findDocs(ALBUMS, {
|
|
68
|
-
$ops: [{ userId: { $gt: 5 } }]
|
|
69
|
-
})
|
|
70
|
-
.collect()) {
|
|
71
|
-
results = { ...results, ...data }
|
|
72
|
-
}
|
|
73
|
-
const albums = Object.values(results)
|
|
74
|
-
const allGt5 = albums.every((a) => a.userId > 5)
|
|
75
|
-
expect(allGt5).toBe(true)
|
|
76
|
-
expect(albums.length).toBe(50)
|
|
77
|
-
})
|
|
78
|
-
test('$gte — returns documents where field is greater than or equal to value', async () => {
|
|
79
|
-
let results = {}
|
|
80
|
-
for await (const data of fylo
|
|
81
|
-
.findDocs(ALBUMS, {
|
|
82
|
-
$ops: [{ userId: { $gte: 5 } }]
|
|
83
|
-
})
|
|
84
|
-
.collect()) {
|
|
85
|
-
results = { ...results, ...data }
|
|
86
|
-
}
|
|
87
|
-
const albums = Object.values(results)
|
|
88
|
-
const allGte5 = albums.every((a) => a.userId >= 5)
|
|
89
|
-
expect(allGte5).toBe(true)
|
|
90
|
-
expect(albums.length).toBe(60)
|
|
91
|
-
})
|
|
92
|
-
test('$like — matches substring pattern', async () => {
|
|
93
|
-
let results = {}
|
|
94
|
-
for await (const data of fylo
|
|
95
|
-
.findDocs(ALBUMS, {
|
|
96
|
-
$ops: [{ title: { $like: '%quidem%' } }]
|
|
97
|
-
})
|
|
98
|
-
.collect()) {
|
|
99
|
-
results = { ...results, ...data }
|
|
100
|
-
}
|
|
101
|
-
const albums = Object.values(results)
|
|
102
|
-
const allMatch = albums.every((a) => a.title.includes('quidem'))
|
|
103
|
-
expect(allMatch).toBe(true)
|
|
104
|
-
expect(albums.length).toBeGreaterThan(0)
|
|
105
|
-
})
|
|
106
|
-
test('$like — prefix pattern', async () => {
|
|
107
|
-
let results = {}
|
|
108
|
-
for await (const data of fylo
|
|
109
|
-
.findDocs(ALBUMS, {
|
|
110
|
-
$ops: [{ title: { $like: 'omnis%' } }]
|
|
111
|
-
})
|
|
112
|
-
.collect()) {
|
|
113
|
-
results = { ...results, ...data }
|
|
114
|
-
}
|
|
115
|
-
const albums = Object.values(results)
|
|
116
|
-
const allStartWith = albums.every((a) => a.title.startsWith('omnis'))
|
|
117
|
-
expect(allStartWith).toBe(true)
|
|
118
|
-
expect(albums.length).toBeGreaterThan(0)
|
|
119
|
-
})
|
|
120
|
-
})
|
|
121
|
-
describe('SQL', async () => {
|
|
122
|
-
test('WHERE != — excludes matching value', async () => {
|
|
123
|
-
const results = await fylo.executeSQL(`SELECT * FROM ${ALBUMS} WHERE userId != 1`)
|
|
124
|
-
const albums = Object.values(results)
|
|
125
|
-
const hasUserId1 = albums.some((a) => a.userId === 1)
|
|
126
|
-
expect(hasUserId1).toBe(false)
|
|
127
|
-
expect(albums.length).toBe(90)
|
|
128
|
-
})
|
|
129
|
-
test('WHERE LIKE — matches substring pattern', async () => {
|
|
130
|
-
const results = await fylo.executeSQL(`SELECT * FROM ${ALBUMS} WHERE title LIKE '%quidem%'`)
|
|
131
|
-
const albums = Object.values(results)
|
|
132
|
-
const allMatch = albums.every((a) => a.title.includes('quidem'))
|
|
133
|
-
expect(allMatch).toBe(true)
|
|
134
|
-
expect(albums.length).toBeGreaterThan(0)
|
|
135
|
-
})
|
|
136
|
-
})
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
import { test, expect, describe, beforeAll, afterAll } from 'bun:test'
|
|
2
|
-
import { rm } from 'node:fs/promises'
|
|
3
|
-
import Fylo from '../../src'
|
|
4
|
-
import { albumURL, postsURL } from '../data'
|
|
5
|
-
import { createTestRoot } from '../helpers/root'
|
|
6
|
-
const POSTS = `post`
|
|
7
|
-
const ALBUMS = `album`
|
|
8
|
-
let count = 0
|
|
9
|
-
const root = await createTestRoot('fylo-read-')
|
|
10
|
-
const fylo = new Fylo({ root })
|
|
11
|
-
beforeAll(async () => {
|
|
12
|
-
await Promise.all([fylo.createCollection(ALBUMS), fylo.executeSQL(`CREATE TABLE ${POSTS}`)])
|
|
13
|
-
try {
|
|
14
|
-
count = await fylo.importBulkData(ALBUMS, new URL(albumURL), 100)
|
|
15
|
-
await fylo.importBulkData(POSTS, new URL(postsURL), 100)
|
|
16
|
-
} catch {
|
|
17
|
-
await fylo.rollback()
|
|
18
|
-
}
|
|
19
|
-
})
|
|
20
|
-
afterAll(async () => {
|
|
21
|
-
await Promise.all([fylo.dropCollection(ALBUMS), fylo.dropCollection(POSTS)])
|
|
22
|
-
await rm(root, { recursive: true, force: true })
|
|
23
|
-
})
|
|
24
|
-
describe('NO-SQL', async () => {
|
|
25
|
-
test('SELECT ALL', async () => {
|
|
26
|
-
let results = {}
|
|
27
|
-
for await (const data of fylo.findDocs(ALBUMS).collect()) {
|
|
28
|
-
results = { ...results, ...data }
|
|
29
|
-
}
|
|
30
|
-
expect(Object.keys(results).length).toBe(count)
|
|
31
|
-
})
|
|
32
|
-
test('SELECT PARTIAL', async () => {
|
|
33
|
-
let results = {}
|
|
34
|
-
for await (const data of fylo.findDocs(ALBUMS, { $select: ['title'] }).collect()) {
|
|
35
|
-
results = { ...results, ...data }
|
|
36
|
-
}
|
|
37
|
-
const allAlbums = Object.values(results)
|
|
38
|
-
const onlyTtitle = allAlbums.every((user) => user.title && !user.userId)
|
|
39
|
-
expect(onlyTtitle).toBe(true)
|
|
40
|
-
})
|
|
41
|
-
test('GET ONE', async () => {
|
|
42
|
-
const ids = []
|
|
43
|
-
for await (const data of fylo.findDocs(ALBUMS, { $limit: 1, $onlyIds: true }).collect()) {
|
|
44
|
-
ids.push(data)
|
|
45
|
-
}
|
|
46
|
-
const result = await fylo.getDoc(ALBUMS, ids[0]).once()
|
|
47
|
-
const _id = Object.keys(result).shift()
|
|
48
|
-
expect(ids[0]).toEqual(_id)
|
|
49
|
-
})
|
|
50
|
-
test('SELECT CLAUSE', async () => {
|
|
51
|
-
let results = {}
|
|
52
|
-
for await (const data of fylo
|
|
53
|
-
.findDocs(ALBUMS, {
|
|
54
|
-
$ops: [{ userId: { $eq: 2 } }]
|
|
55
|
-
})
|
|
56
|
-
.collect()) {
|
|
57
|
-
results = { ...results, ...data }
|
|
58
|
-
}
|
|
59
|
-
const allAlbums = Object.values(results)
|
|
60
|
-
const onlyUserId = allAlbums.every((user) => user.userId === 2)
|
|
61
|
-
expect(onlyUserId).toBe(true)
|
|
62
|
-
})
|
|
63
|
-
test('SELECT LIMIT', async () => {
|
|
64
|
-
let results = {}
|
|
65
|
-
for await (const data of fylo.findDocs(ALBUMS, { $limit: 5 }).collect()) {
|
|
66
|
-
results = { ...results, ...data }
|
|
67
|
-
}
|
|
68
|
-
expect(Object.keys(results).length).toBe(5)
|
|
69
|
-
})
|
|
70
|
-
test('SELECT GROUP BY', async () => {
|
|
71
|
-
let results = {}
|
|
72
|
-
for await (const data of fylo
|
|
73
|
-
.findDocs(ALBUMS, {
|
|
74
|
-
$groupby: 'userId',
|
|
75
|
-
$onlyIds: true
|
|
76
|
-
})
|
|
77
|
-
.collect()) {
|
|
78
|
-
results = Object.appendGroup(results, data)
|
|
79
|
-
}
|
|
80
|
-
expect(Object.keys(results).length).toBeGreaterThan(0)
|
|
81
|
-
})
|
|
82
|
-
test('SELECT JOIN', async () => {
|
|
83
|
-
const results = await fylo.joinDocs({
|
|
84
|
-
$leftCollection: ALBUMS,
|
|
85
|
-
$rightCollection: POSTS,
|
|
86
|
-
$mode: 'inner',
|
|
87
|
-
$on: { userId: { $eq: 'id' } }
|
|
88
|
-
})
|
|
89
|
-
expect(Object.keys(results).length).toBeGreaterThan(0)
|
|
90
|
-
})
|
|
91
|
-
})
|
|
92
|
-
describe('SQL', async () => {
|
|
93
|
-
test('SELECT PARTIAL', async () => {
|
|
94
|
-
const results = await fylo.executeSQL(`SELECT title FROM ${ALBUMS}`)
|
|
95
|
-
const allAlbums = Object.values(results)
|
|
96
|
-
const onlyTtitle = allAlbums.every((user) => user.title && !user.userId)
|
|
97
|
-
expect(onlyTtitle).toBe(true)
|
|
98
|
-
})
|
|
99
|
-
test('SELECT CLAUSE', async () => {
|
|
100
|
-
const results = await fylo.executeSQL(`SELECT * FROM ${ALBUMS} WHERE user_id = 2`)
|
|
101
|
-
const allAlbums = Object.values(results)
|
|
102
|
-
const onlyUserId = allAlbums.every((user) => user.userId === 2)
|
|
103
|
-
expect(onlyUserId).toBe(true)
|
|
104
|
-
})
|
|
105
|
-
test('SELECT ALL', async () => {
|
|
106
|
-
const results = await fylo.executeSQL(`SELECT * FROM ${ALBUMS}`)
|
|
107
|
-
expect(Object.keys(results).length).toBe(count)
|
|
108
|
-
})
|
|
109
|
-
test('SELECT LIMIT', async () => {
|
|
110
|
-
const results = await fylo.executeSQL(`SELECT * FROM ${ALBUMS} LIMIT 5`)
|
|
111
|
-
expect(Object.keys(results).length).toBe(5)
|
|
112
|
-
})
|
|
113
|
-
test('SELECT GROUP BY', async () => {
|
|
114
|
-
const results = await fylo.executeSQL(`SELECT * FROM ${ALBUMS} GROUP BY userId`)
|
|
115
|
-
expect(Object.keys(results).length).toBeGreaterThan(0)
|
|
116
|
-
})
|
|
117
|
-
test('SELECT JOIN', async () => {
|
|
118
|
-
const results = await fylo.executeSQL(
|
|
119
|
-
`SELECT * FROM ${ALBUMS} INNER JOIN ${POSTS} ON userId = id`
|
|
120
|
-
)
|
|
121
|
-
expect(Object.keys(results).length).toBeGreaterThan(0)
|
|
122
|
-
})
|
|
123
|
-
})
|
|
@@ -1,30 +0,0 @@
|
|
|
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'
|
|
5
|
-
import Fylo from '../../src'
|
|
6
|
-
|
|
7
|
-
const root = await mkdtemp(path.join(os.tmpdir(), 'fylo-rollback-'))
|
|
8
|
-
const POSTS = 'rb-post'
|
|
9
|
-
const fylo = new Fylo({ root })
|
|
10
|
-
|
|
11
|
-
beforeAll(async () => {
|
|
12
|
-
await fylo.createCollection(POSTS)
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
afterAll(async () => {
|
|
16
|
-
await rm(root, { recursive: true, force: true })
|
|
17
|
-
})
|
|
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'
|
|
23
|
-
})
|
|
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')
|
|
29
|
-
})
|
|
30
|
-
})
|
|
@@ -1,75 +0,0 @@
|
|
|
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
|
-
})
|