@delma/fylo 1.1.2 → 2.0.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 +141 -62
- package/eslint.config.js +8 -4
- package/package.json +9 -7
- package/src/CLI +16 -14
- package/src/adapters/cipher.ts +12 -6
- package/src/adapters/redis.ts +193 -123
- package/src/adapters/s3.ts +6 -12
- package/src/core/collection.ts +5 -0
- package/src/core/directory.ts +120 -151
- package/src/core/extensions.ts +4 -2
- package/src/core/format.ts +390 -419
- package/src/core/parser.ts +167 -142
- package/src/core/query.ts +31 -26
- package/src/core/walker.ts +68 -61
- package/src/core/write-queue.ts +7 -4
- package/src/engines/s3-files.ts +888 -0
- package/src/engines/types.ts +21 -0
- package/src/index.ts +754 -378
- package/src/migrate-cli.ts +22 -0
- package/src/migrate.ts +74 -0
- package/src/types/bun-runtime.d.ts +73 -0
- package/src/types/fylo.d.ts +115 -27
- package/src/types/node-runtime.d.ts +61 -0
- package/src/types/query.d.ts +6 -2
- package/src/types/vendor-modules.d.ts +8 -7
- package/src/worker.ts +7 -1
- package/src/workers/write-worker.ts +25 -24
- package/tests/collection/truncate.test.js +35 -0
- package/tests/{data.ts → data.js} +8 -21
- package/tests/{index.ts → index.js} +4 -9
- package/tests/integration/aws-s3-files.canary.test.js +22 -0
- package/tests/integration/{create.test.ts → create.test.js} +13 -31
- package/tests/integration/delete.test.js +95 -0
- package/tests/integration/{edge-cases.test.ts → edge-cases.test.js} +50 -124
- package/tests/integration/{encryption.test.ts → encryption.test.js} +20 -65
- package/tests/integration/{export.test.ts → export.test.js} +8 -23
- package/tests/integration/{join-modes.test.ts → join-modes.test.js} +37 -104
- package/tests/integration/migration.test.js +38 -0
- package/tests/integration/nested.test.js +142 -0
- package/tests/integration/operators.test.js +122 -0
- package/tests/integration/{queue.test.ts → queue.test.js} +24 -40
- package/tests/integration/read.test.js +119 -0
- package/tests/integration/rollback.test.js +60 -0
- package/tests/integration/s3-files.test.js +108 -0
- package/tests/integration/update.test.js +99 -0
- package/tests/mocks/{cipher.ts → cipher.js} +11 -26
- package/tests/mocks/redis.js +123 -0
- package/tests/mocks/{s3.ts → s3.js} +24 -58
- package/tests/schemas/album.json +1 -1
- package/tests/schemas/comment.json +1 -1
- package/tests/schemas/photo.json +1 -1
- package/tests/schemas/post.json +1 -1
- package/tests/schemas/tip.json +1 -1
- package/tests/schemas/todo.json +1 -1
- package/tests/schemas/user.d.ts +12 -12
- package/tests/schemas/user.json +1 -1
- package/tsconfig.json +4 -2
- package/tsconfig.typecheck.json +31 -0
- package/tests/collection/truncate.test.ts +0 -56
- package/tests/integration/delete.test.ts +0 -147
- package/tests/integration/nested.test.ts +0 -212
- package/tests/integration/operators.test.ts +0 -167
- package/tests/integration/read.test.ts +0 -203
- package/tests/integration/rollback.test.ts +0 -105
- package/tests/integration/update.test.ts +0 -130
- package/tests/mocks/redis.ts +0 -169
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { test, expect, describe, afterAll, mock } from 'bun:test'
|
|
2
|
+
import Fylo from '../../src'
|
|
3
|
+
import { postsURL, albumURL } from '../data'
|
|
4
|
+
import S3Mock from '../mocks/s3'
|
|
5
|
+
import RedisMock from '../mocks/redis'
|
|
6
|
+
const POSTS = `post`
|
|
7
|
+
const ALBUMS = `album`
|
|
8
|
+
afterAll(async () => {
|
|
9
|
+
await Promise.all([Fylo.dropCollection(ALBUMS), Fylo.dropCollection(POSTS)])
|
|
10
|
+
})
|
|
11
|
+
mock.module('../../src/adapters/s3', () => ({ S3: S3Mock }))
|
|
12
|
+
mock.module('../../src/adapters/redis', () => ({ Redis: RedisMock }))
|
|
13
|
+
describe('NO-SQL', () => {
|
|
14
|
+
test('TRUNCATE', async () => {
|
|
15
|
+
const fylo = new Fylo()
|
|
16
|
+
await Fylo.createCollection(POSTS)
|
|
17
|
+
await fylo.importBulkData(POSTS, new URL(postsURL))
|
|
18
|
+
await fylo.delDocs(POSTS)
|
|
19
|
+
const ids = []
|
|
20
|
+
for await (const data of Fylo.findDocs(POSTS, { $limit: 1, $onlyIds: true }).collect()) {
|
|
21
|
+
ids.push(data)
|
|
22
|
+
}
|
|
23
|
+
expect(ids.length).toBe(0)
|
|
24
|
+
})
|
|
25
|
+
})
|
|
26
|
+
describe('SQL', () => {
|
|
27
|
+
test('TRUNCATE', async () => {
|
|
28
|
+
const fylo = new Fylo()
|
|
29
|
+
await fylo.executeSQL(`CREATE TABLE ${ALBUMS}`)
|
|
30
|
+
await fylo.importBulkData(ALBUMS, new URL(albumURL))
|
|
31
|
+
await fylo.executeSQL(`DELETE FROM ${ALBUMS}`)
|
|
32
|
+
const ids = await fylo.executeSQL(`SELECT _id FROM ${ALBUMS} LIMIT 1`)
|
|
33
|
+
expect(ids.length).toBe(0)
|
|
34
|
+
})
|
|
35
|
+
})
|
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
function makeDataUrl(data
|
|
1
|
+
function makeDataUrl(data) {
|
|
2
2
|
return `data:application/json,${encodeURIComponent(JSON.stringify(data))}`
|
|
3
3
|
}
|
|
4
|
-
|
|
5
|
-
function generateAlbums(): _album[] {
|
|
4
|
+
function generateAlbums() {
|
|
6
5
|
return Array.from({ length: 100 }, (_, index) => {
|
|
7
6
|
const id = index + 1
|
|
8
7
|
const userId = Math.ceil(id / 10)
|
|
9
8
|
const prefix = id <= 15 ? 'omnis' : id % 4 === 0 ? 'quidem' : 'album'
|
|
10
|
-
|
|
11
9
|
return {
|
|
12
10
|
id,
|
|
13
11
|
userId,
|
|
@@ -15,12 +13,10 @@ function generateAlbums(): _album[] {
|
|
|
15
13
|
}
|
|
16
14
|
})
|
|
17
15
|
}
|
|
18
|
-
|
|
19
|
-
function generatePosts(): _post[] {
|
|
16
|
+
function generatePosts() {
|
|
20
17
|
return Array.from({ length: 100 }, (_, index) => {
|
|
21
18
|
const id = index + 1
|
|
22
19
|
const userId = Math.ceil(id / 10)
|
|
23
|
-
|
|
24
20
|
return {
|
|
25
21
|
id,
|
|
26
22
|
userId,
|
|
@@ -29,11 +25,9 @@ function generatePosts(): _post[] {
|
|
|
29
25
|
}
|
|
30
26
|
})
|
|
31
27
|
}
|
|
32
|
-
|
|
33
|
-
function generateComments(): _comment[] {
|
|
28
|
+
function generateComments() {
|
|
34
29
|
return Array.from({ length: 100 }, (_, index) => {
|
|
35
30
|
const id = index + 1
|
|
36
|
-
|
|
37
31
|
return {
|
|
38
32
|
id,
|
|
39
33
|
postId: id,
|
|
@@ -43,12 +37,10 @@ function generateComments(): _comment[] {
|
|
|
43
37
|
}
|
|
44
38
|
})
|
|
45
39
|
}
|
|
46
|
-
|
|
47
|
-
function generatePhotos(): _photo[] {
|
|
40
|
+
function generatePhotos() {
|
|
48
41
|
return Array.from({ length: 100 }, (_, index) => {
|
|
49
42
|
const id = index + 1
|
|
50
43
|
const title = id % 3 === 0 ? `test photo ${id}` : `photo ${id}`
|
|
51
|
-
|
|
52
44
|
return {
|
|
53
45
|
id,
|
|
54
46
|
albumId: Math.ceil(id / 10),
|
|
@@ -58,11 +50,9 @@ function generatePhotos(): _photo[] {
|
|
|
58
50
|
}
|
|
59
51
|
})
|
|
60
52
|
}
|
|
61
|
-
|
|
62
|
-
function generateTodos(): _todo[] {
|
|
53
|
+
function generateTodos() {
|
|
63
54
|
return Array.from({ length: 100 }, (_, index) => {
|
|
64
55
|
const id = index + 1
|
|
65
|
-
|
|
66
56
|
return {
|
|
67
57
|
id,
|
|
68
58
|
userId: Math.ceil(id / 10),
|
|
@@ -71,11 +61,9 @@ function generateTodos(): _todo[] {
|
|
|
71
61
|
}
|
|
72
62
|
})
|
|
73
63
|
}
|
|
74
|
-
|
|
75
|
-
function generateUsers(): _user[] {
|
|
64
|
+
function generateUsers() {
|
|
76
65
|
return Array.from({ length: 10 }, (_, index) => {
|
|
77
66
|
const id = index + 1
|
|
78
|
-
|
|
79
67
|
return {
|
|
80
68
|
id,
|
|
81
69
|
name: `User ${id}`,
|
|
@@ -98,10 +86,9 @@ function generateUsers(): _user[] {
|
|
|
98
86
|
catchPhrase: `Catch phrase ${id}`,
|
|
99
87
|
bs: `business ${id}`
|
|
100
88
|
}
|
|
101
|
-
}
|
|
89
|
+
}
|
|
102
90
|
})
|
|
103
91
|
}
|
|
104
|
-
|
|
105
92
|
export const albumURL = makeDataUrl(generateAlbums())
|
|
106
93
|
export const postsURL = makeDataUrl(generatePosts())
|
|
107
94
|
export const commentsURL = makeDataUrl(generateComments())
|
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
import { Redis } from '../src/adapters/redis'
|
|
2
2
|
import ttid from '@delma/ttid'
|
|
3
|
-
|
|
4
3
|
const redisPub = new Redis()
|
|
5
4
|
const redisSub = new Redis()
|
|
6
|
-
|
|
7
5
|
setTimeout(async () => {
|
|
8
|
-
await redisPub.publish(
|
|
6
|
+
await redisPub.publish('bun', 'insert', ttid.generate())
|
|
9
7
|
}, 2000)
|
|
10
|
-
|
|
11
8
|
setTimeout(async () => {
|
|
12
|
-
await redisPub.publish(
|
|
9
|
+
await redisPub.publish('bun', 'insert', ttid.generate())
|
|
13
10
|
}, 3000)
|
|
14
|
-
|
|
15
11
|
await Bun.sleep(1000)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
console.log("Received:", data)
|
|
12
|
+
for await (const data of redisSub.subscribe('bun')) {
|
|
13
|
+
console.log('Received:', data)
|
|
19
14
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import Fylo from '../../src'
|
|
3
|
+
const runCanary = process.env.FYLO_RUN_S3FILES_CANARY === 'true'
|
|
4
|
+
const canaryTest = runCanary ? test : test.skip
|
|
5
|
+
describe('aws s3-files canary', () => {
|
|
6
|
+
canaryTest('mounted S3 Files root handles a real CRUD cycle', async () => {
|
|
7
|
+
const collection = `canary_${Date.now()}`
|
|
8
|
+
const fylo = new Fylo({
|
|
9
|
+
engine: 's3-files',
|
|
10
|
+
s3FilesRoot: process.env.FYLO_S3FILES_ROOT
|
|
11
|
+
})
|
|
12
|
+
await fylo.createCollection(collection)
|
|
13
|
+
const id = await fylo.putData(collection, {
|
|
14
|
+
title: 'canary',
|
|
15
|
+
tags: ['aws', 's3-files']
|
|
16
|
+
})
|
|
17
|
+
const doc = await fylo.getDoc(collection, id).once()
|
|
18
|
+
expect(doc[id].title).toBe('canary')
|
|
19
|
+
await fylo.delDoc(collection, id)
|
|
20
|
+
await fylo.dropCollection(collection)
|
|
21
|
+
})
|
|
22
|
+
})
|
|
@@ -3,55 +3,37 @@ import Fylo from '../../src'
|
|
|
3
3
|
import { albumURL, postsURL } from '../data'
|
|
4
4
|
import S3Mock from '../mocks/s3'
|
|
5
5
|
import RedisMock from '../mocks/redis'
|
|
6
|
-
|
|
7
6
|
const POSTS = `post`
|
|
8
7
|
const ALBUMS = `album`
|
|
9
|
-
|
|
10
8
|
let postsCount = 0
|
|
11
9
|
let albumsCount = 0
|
|
12
|
-
|
|
13
10
|
const fylo = new Fylo()
|
|
14
|
-
|
|
15
11
|
mock.module('../../src/adapters/s3', () => ({ S3: S3Mock }))
|
|
16
12
|
mock.module('../../src/adapters/redis', () => ({ Redis: RedisMock }))
|
|
17
|
-
|
|
18
13
|
beforeAll(async () => {
|
|
19
|
-
|
|
20
|
-
await Promise.all([Fylo.createCollection(POSTS), fylo.executeSQL<_user>(`CREATE TABLE ${ALBUMS}`)])
|
|
21
|
-
|
|
14
|
+
await Promise.all([Fylo.createCollection(POSTS), fylo.executeSQL(`CREATE TABLE ${ALBUMS}`)])
|
|
22
15
|
try {
|
|
23
|
-
albumsCount = await fylo.importBulkData
|
|
24
|
-
postsCount = await fylo.importBulkData
|
|
16
|
+
albumsCount = await fylo.importBulkData(ALBUMS, new URL(albumURL), 100)
|
|
17
|
+
postsCount = await fylo.importBulkData(POSTS, new URL(postsURL), 100)
|
|
25
18
|
} catch {
|
|
26
19
|
await fylo.rollback()
|
|
27
20
|
}
|
|
28
21
|
})
|
|
29
|
-
|
|
30
22
|
afterAll(async () => {
|
|
31
|
-
await Promise.all([Fylo.dropCollection(POSTS), fylo.executeSQL
|
|
23
|
+
await Promise.all([Fylo.dropCollection(POSTS), fylo.executeSQL(`DROP TABLE ${ALBUMS}`)])
|
|
32
24
|
})
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
let results: Record<_ttid, _post> = {}
|
|
39
|
-
|
|
40
|
-
for await (const data of Fylo.findDocs<_post>(POSTS).collect()) {
|
|
41
|
-
|
|
42
|
-
results = { ...results, ...data as Record<_ttid, _post> }
|
|
25
|
+
describe('NO-SQL', async () => {
|
|
26
|
+
test('PUT', async () => {
|
|
27
|
+
let results = {}
|
|
28
|
+
for await (const data of Fylo.findDocs(POSTS).collect()) {
|
|
29
|
+
results = { ...results, ...data }
|
|
43
30
|
}
|
|
44
|
-
|
|
45
31
|
expect(Object.keys(results).length).toEqual(postsCount)
|
|
46
32
|
})
|
|
47
33
|
})
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
test("INSERT", async () => {
|
|
52
|
-
|
|
53
|
-
const results = await fylo.executeSQL<_album>(`SELECT * FROM ${ALBUMS}`) as Record<_ttid, _album>
|
|
54
|
-
|
|
34
|
+
describe('SQL', () => {
|
|
35
|
+
test('INSERT', async () => {
|
|
36
|
+
const results = await fylo.executeSQL(`SELECT * FROM ${ALBUMS}`)
|
|
55
37
|
expect(Object.keys(results).length).toEqual(albumsCount)
|
|
56
38
|
})
|
|
57
|
-
})
|
|
39
|
+
})
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { test, expect, describe, beforeAll, afterAll, mock } from 'bun:test'
|
|
2
|
+
import Fylo from '../../src'
|
|
3
|
+
import { commentsURL, usersURL } from '../data'
|
|
4
|
+
import S3Mock from '../mocks/s3'
|
|
5
|
+
import RedisMock from '../mocks/redis'
|
|
6
|
+
const COMMENTS = `comment`
|
|
7
|
+
const USERS = `user`
|
|
8
|
+
let commentsResults = {}
|
|
9
|
+
let usersResults = {}
|
|
10
|
+
const fylo = new Fylo()
|
|
11
|
+
mock.module('../../src/adapters/s3', () => ({ S3: S3Mock }))
|
|
12
|
+
mock.module('../../src/adapters/redis', () => ({ Redis: RedisMock }))
|
|
13
|
+
beforeAll(async () => {
|
|
14
|
+
await Promise.all([Fylo.createCollection(COMMENTS), fylo.executeSQL(`CREATE TABLE ${USERS}`)])
|
|
15
|
+
try {
|
|
16
|
+
await Promise.all([
|
|
17
|
+
fylo.importBulkData(COMMENTS, new URL(commentsURL), 100),
|
|
18
|
+
fylo.importBulkData(USERS, new URL(usersURL), 100)
|
|
19
|
+
])
|
|
20
|
+
} catch {
|
|
21
|
+
await fylo.rollback()
|
|
22
|
+
}
|
|
23
|
+
for await (const data of Fylo.findDocs(COMMENTS, { $limit: 1 }).collect()) {
|
|
24
|
+
commentsResults = { ...commentsResults, ...data }
|
|
25
|
+
}
|
|
26
|
+
usersResults = await fylo.executeSQL(`SELECT * FROM ${USERS} LIMIT 1`)
|
|
27
|
+
})
|
|
28
|
+
afterAll(async () => {
|
|
29
|
+
await Promise.all([Fylo.dropCollection(COMMENTS), fylo.executeSQL(`DROP TABLE ${USERS}`)])
|
|
30
|
+
})
|
|
31
|
+
describe('NO-SQL', async () => {
|
|
32
|
+
test('DELETE ONE', async () => {
|
|
33
|
+
const id = Object.keys(commentsResults).shift()
|
|
34
|
+
try {
|
|
35
|
+
await fylo.delDoc(COMMENTS, id)
|
|
36
|
+
} catch {
|
|
37
|
+
await fylo.rollback()
|
|
38
|
+
}
|
|
39
|
+
commentsResults = {}
|
|
40
|
+
for await (const data of Fylo.findDocs(COMMENTS).collect()) {
|
|
41
|
+
commentsResults = { ...commentsResults, ...data }
|
|
42
|
+
}
|
|
43
|
+
const idx = Object.keys(commentsResults).findIndex((_id) => _id === id)
|
|
44
|
+
expect(idx).toEqual(-1)
|
|
45
|
+
})
|
|
46
|
+
test('DELETE CLAUSE', async () => {
|
|
47
|
+
try {
|
|
48
|
+
await fylo.delDocs(COMMENTS, { $ops: [{ name: { $like: '%et%' } }] })
|
|
49
|
+
} catch (e) {
|
|
50
|
+
console.error(e)
|
|
51
|
+
await fylo.rollback()
|
|
52
|
+
}
|
|
53
|
+
commentsResults = {}
|
|
54
|
+
for await (const data of Fylo.findDocs(COMMENTS, {
|
|
55
|
+
$ops: [{ name: { $like: '%et%' } }]
|
|
56
|
+
}).collect()) {
|
|
57
|
+
commentsResults = { ...commentsResults, ...data }
|
|
58
|
+
}
|
|
59
|
+
expect(Object.keys(commentsResults).length).toEqual(0)
|
|
60
|
+
})
|
|
61
|
+
test('DELETE ALL', async () => {
|
|
62
|
+
try {
|
|
63
|
+
await fylo.delDocs(COMMENTS)
|
|
64
|
+
} catch {
|
|
65
|
+
await fylo.rollback()
|
|
66
|
+
}
|
|
67
|
+
commentsResults = {}
|
|
68
|
+
for await (const data of Fylo.findDocs(COMMENTS).collect()) {
|
|
69
|
+
commentsResults = { ...commentsResults, ...data }
|
|
70
|
+
}
|
|
71
|
+
expect(Object.keys(commentsResults).length).toEqual(0)
|
|
72
|
+
})
|
|
73
|
+
})
|
|
74
|
+
describe('SQL', async () => {
|
|
75
|
+
test('DELETE CLAUSE', async () => {
|
|
76
|
+
const name = Object.values(usersResults).shift().name
|
|
77
|
+
try {
|
|
78
|
+
await fylo.executeSQL(`DELETE FROM ${USERS} WHERE name = '${name}'`)
|
|
79
|
+
} catch {
|
|
80
|
+
await fylo.rollback()
|
|
81
|
+
}
|
|
82
|
+
usersResults = await fylo.executeSQL(`SELECT * FROM ${USERS} WHERE name = '${name}'`)
|
|
83
|
+
const idx = Object.values(usersResults).findIndex((com) => com.name === name)
|
|
84
|
+
expect(idx).toBe(-1)
|
|
85
|
+
})
|
|
86
|
+
test('DELETE ALL', async () => {
|
|
87
|
+
try {
|
|
88
|
+
await fylo.executeSQL(`DELETE FROM ${USERS}`)
|
|
89
|
+
} catch {
|
|
90
|
+
await fylo.rollback()
|
|
91
|
+
}
|
|
92
|
+
usersResults = await fylo.executeSQL(`SELECT * FROM ${USERS}`)
|
|
93
|
+
expect(Object.keys(usersResults).length).toBe(0)
|
|
94
|
+
})
|
|
95
|
+
})
|
|
@@ -3,230 +3,156 @@ import Fylo from '../../src'
|
|
|
3
3
|
import TTID from '@delma/ttid'
|
|
4
4
|
import S3Mock from '../mocks/s3'
|
|
5
5
|
import RedisMock from '../mocks/redis'
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Edge case coverage:
|
|
9
|
-
* - Non-existent ID returns empty object
|
|
10
|
-
* - Values containing forward slashes survive the SLASH_ASCII (%2F) round-trip
|
|
11
|
-
* - Multiple $ops entries act as OR (union across patterns)
|
|
12
|
-
* - $rename renames fields in query output
|
|
13
|
-
* - Versioned putData (existing TTID as key) preserves the creation-time prefix
|
|
14
|
-
* - SQL UPDATE ONE — update a single document by ID via SQL
|
|
15
|
-
* - SQL DELETE ONE — delete a single document by ID via SQL
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
6
|
const COLLECTION = 'ec-test'
|
|
19
|
-
|
|
20
7
|
const fylo = new Fylo()
|
|
21
|
-
|
|
22
8
|
mock.module('../../src/adapters/s3', () => ({ S3: S3Mock }))
|
|
23
9
|
mock.module('../../src/adapters/redis', () => ({ Redis: RedisMock }))
|
|
24
|
-
|
|
25
10
|
beforeAll(async () => {
|
|
26
11
|
await Fylo.createCollection(COLLECTION)
|
|
27
12
|
})
|
|
28
|
-
|
|
29
13
|
afterAll(async () => {
|
|
30
14
|
await Fylo.dropCollection(COLLECTION)
|
|
31
15
|
})
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
test("GET ONE — non-existent ID returns empty object", async () => {
|
|
36
|
-
|
|
37
|
-
const fakeId = TTID.generate() as _ttid
|
|
38
|
-
|
|
16
|
+
describe('NO-SQL', () => {
|
|
17
|
+
test('GET ONE — non-existent ID returns empty object', async () => {
|
|
18
|
+
const fakeId = TTID.generate()
|
|
39
19
|
const result = await Fylo.getDoc(COLLECTION, fakeId).once()
|
|
40
|
-
|
|
41
20
|
expect(Object.keys(result).length).toBe(0)
|
|
42
21
|
})
|
|
43
|
-
|
|
44
|
-
test("PUT / GET — forward slashes in values round-trip correctly", async () => {
|
|
45
|
-
|
|
22
|
+
test('PUT / GET — forward slashes in values round-trip correctly', async () => {
|
|
46
23
|
const original = {
|
|
47
24
|
userId: 1,
|
|
48
25
|
id: 1,
|
|
49
26
|
title: 'Slash Test',
|
|
50
27
|
body: 'https://example.com/api/v1/resource'
|
|
51
28
|
}
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
const result = await Fylo.getDoc<_post>(COLLECTION, _id).once()
|
|
29
|
+
const _id = await fylo.putData(COLLECTION, original)
|
|
30
|
+
const result = await Fylo.getDoc(COLLECTION, _id).once()
|
|
56
31
|
const doc = result[_id]
|
|
57
|
-
|
|
58
32
|
expect(doc.body).toBe(original.body)
|
|
59
|
-
|
|
60
33
|
await fylo.delDoc(COLLECTION, _id)
|
|
61
34
|
})
|
|
62
|
-
|
|
63
|
-
test("PUT / GET — values with multiple consecutive slashes round-trip correctly", async () => {
|
|
64
|
-
|
|
35
|
+
test('PUT / GET — values with multiple consecutive slashes round-trip correctly', async () => {
|
|
65
36
|
const original = {
|
|
66
37
|
userId: 1,
|
|
67
38
|
id: 2,
|
|
68
39
|
title: 'Double Slash',
|
|
69
40
|
body: 'https://cdn.example.com//assets//image.png'
|
|
70
41
|
}
|
|
71
|
-
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
const result = await Fylo.getDoc<_post>(COLLECTION, _id).once()
|
|
75
|
-
|
|
42
|
+
const _id = await fylo.putData(COLLECTION, original)
|
|
43
|
+
const result = await Fylo.getDoc(COLLECTION, _id).once()
|
|
76
44
|
expect(result[_id].body).toBe(original.body)
|
|
77
|
-
|
|
78
45
|
await fylo.delDoc(COLLECTION, _id)
|
|
79
46
|
})
|
|
80
|
-
|
|
81
|
-
test("$ops — multiple conditions act as OR union", async () => {
|
|
82
|
-
|
|
47
|
+
test('$ops — multiple conditions act as OR union', async () => {
|
|
83
48
|
const cleanFylo = new Fylo()
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
49
|
+
const id1 = await cleanFylo.putData(COLLECTION, {
|
|
50
|
+
userId: 10,
|
|
51
|
+
id: 100,
|
|
52
|
+
title: 'Alpha',
|
|
53
|
+
body: 'first'
|
|
54
|
+
})
|
|
55
|
+
const id2 = await cleanFylo.putData(COLLECTION, {
|
|
56
|
+
userId: 20,
|
|
57
|
+
id: 200,
|
|
58
|
+
title: 'Beta',
|
|
59
|
+
body: 'second'
|
|
60
|
+
})
|
|
61
|
+
const results = {}
|
|
62
|
+
for await (const data of Fylo.findDocs(COLLECTION, {
|
|
63
|
+
$ops: [{ userId: { $eq: 10 } }, { userId: { $eq: 20 } }]
|
|
95
64
|
}).collect()) {
|
|
96
65
|
Object.assign(results, data)
|
|
97
66
|
}
|
|
98
|
-
|
|
99
67
|
expect(results[id1]).toBeDefined()
|
|
100
68
|
expect(results[id2]).toBeDefined()
|
|
101
|
-
|
|
102
69
|
await cleanFylo.delDoc(COLLECTION, id1)
|
|
103
70
|
await cleanFylo.delDoc(COLLECTION, id2)
|
|
104
71
|
})
|
|
105
|
-
|
|
106
|
-
test("$rename — renames fields in query output", async () => {
|
|
107
|
-
|
|
72
|
+
test('$rename — renames fields in query output', async () => {
|
|
108
73
|
const cleanFylo = new Fylo()
|
|
109
|
-
|
|
110
|
-
const _id = await cleanFylo.putData<_post>(COLLECTION, {
|
|
74
|
+
const _id = await cleanFylo.putData(COLLECTION, {
|
|
111
75
|
userId: 1,
|
|
112
76
|
id: 300,
|
|
113
77
|
title: 'Rename Me',
|
|
114
78
|
body: 'some body'
|
|
115
79
|
})
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
for await (const data of Fylo.findDocs<_post>(COLLECTION, {
|
|
80
|
+
let renamed = {}
|
|
81
|
+
for await (const data of Fylo.findDocs(COLLECTION, {
|
|
120
82
|
$ops: [{ id: { $eq: 300 } }],
|
|
121
|
-
$rename: { title: 'name' }
|
|
83
|
+
$rename: { title: 'name' }
|
|
122
84
|
}).collect()) {
|
|
123
|
-
renamed = Object.values(data
|
|
85
|
+
renamed = Object.values(data)[0]
|
|
124
86
|
}
|
|
125
|
-
|
|
126
87
|
expect(renamed.name).toBe('Rename Me')
|
|
127
88
|
expect(renamed.title).toBeUndefined()
|
|
128
|
-
|
|
129
89
|
await cleanFylo.delDoc(COLLECTION, _id)
|
|
130
90
|
})
|
|
131
|
-
|
|
132
|
-
test("versioned putData — preserves creation-time prefix in TTID", async () => {
|
|
133
|
-
|
|
91
|
+
test('versioned putData — preserves creation-time prefix in TTID', async () => {
|
|
134
92
|
const cleanFylo = new Fylo()
|
|
135
|
-
|
|
136
|
-
const _id1 = await cleanFylo.putData<_post>(COLLECTION, {
|
|
93
|
+
const _id1 = await cleanFylo.putData(COLLECTION, {
|
|
137
94
|
userId: 1,
|
|
138
95
|
id: 400,
|
|
139
96
|
title: 'Original',
|
|
140
97
|
body: 'v1'
|
|
141
98
|
})
|
|
142
|
-
|
|
143
|
-
const _id2 = await cleanFylo.putData<_post>(COLLECTION, {
|
|
99
|
+
const _id2 = await cleanFylo.putData(COLLECTION, {
|
|
144
100
|
[_id1]: { userId: 1, id: 400, title: 'Updated', body: 'v2' }
|
|
145
101
|
})
|
|
146
|
-
|
|
147
|
-
// The creation-time segment (before the first '-') must be identical
|
|
148
102
|
expect(_id2.split('-')[0]).toBe(_id1.split('-')[0])
|
|
149
|
-
|
|
150
|
-
// The updated doc is retrievable via its own TTID
|
|
151
|
-
const result = await Fylo.getDoc<_post>(COLLECTION, _id2).once()
|
|
103
|
+
const result = await Fylo.getDoc(COLLECTION, _id2).once()
|
|
152
104
|
const doc = result[_id2]
|
|
153
|
-
|
|
154
105
|
expect(doc).toBeDefined()
|
|
155
106
|
expect(doc.title).toBe('Updated')
|
|
156
|
-
|
|
157
|
-
// Clean up both versions
|
|
158
107
|
await cleanFylo.delDoc(COLLECTION, _id1)
|
|
159
108
|
await cleanFylo.delDoc(COLLECTION, _id2)
|
|
160
109
|
})
|
|
161
|
-
|
|
162
|
-
test("versioned putData — original version is no longer retrievable by old full TTID", async () => {
|
|
163
|
-
|
|
110
|
+
test('versioned putData — original version is no longer retrievable by old full TTID', async () => {
|
|
164
111
|
const cleanFylo = new Fylo()
|
|
165
|
-
|
|
166
|
-
const _id1 = await cleanFylo.putData<_post>(COLLECTION, {
|
|
112
|
+
const _id1 = await cleanFylo.putData(COLLECTION, {
|
|
167
113
|
userId: 1,
|
|
168
114
|
id: 500,
|
|
169
115
|
title: 'Old Version',
|
|
170
116
|
body: 'original'
|
|
171
117
|
})
|
|
172
|
-
|
|
173
|
-
const _id2 = await cleanFylo.putData<_post>(COLLECTION, {
|
|
118
|
+
const _id2 = await cleanFylo.putData(COLLECTION, {
|
|
174
119
|
[_id1]: { userId: 1, id: 500, title: 'New Version', body: 'updated' }
|
|
175
120
|
})
|
|
176
|
-
|
|
177
|
-
// Both IDs are different (update appended a segment)
|
|
178
121
|
expect(_id1).not.toBe(_id2)
|
|
179
|
-
|
|
180
122
|
await cleanFylo.delDoc(COLLECTION, _id2)
|
|
181
123
|
})
|
|
182
124
|
})
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
test("UPDATE ONE — update a single document by querying its unique field", async () => {
|
|
187
|
-
|
|
125
|
+
describe('SQL', () => {
|
|
126
|
+
test('UPDATE ONE — update a single document by querying its unique field', async () => {
|
|
188
127
|
const cleanFylo = new Fylo()
|
|
189
|
-
|
|
190
|
-
await cleanFylo.putData<_post>(COLLECTION, {
|
|
128
|
+
await cleanFylo.putData(COLLECTION, {
|
|
191
129
|
userId: 1,
|
|
192
130
|
id: 600,
|
|
193
131
|
title: 'Before SQL Update',
|
|
194
132
|
body: 'original'
|
|
195
133
|
})
|
|
196
|
-
|
|
197
|
-
const updated = await cleanFylo.executeSQL<_post>(
|
|
134
|
+
const updated = await cleanFylo.executeSQL(
|
|
198
135
|
`UPDATE ${COLLECTION} SET title = 'After SQL Update' WHERE id = 600`
|
|
199
|
-
)
|
|
200
|
-
|
|
136
|
+
)
|
|
201
137
|
expect(updated).toBe(1)
|
|
202
|
-
|
|
203
|
-
const results = await cleanFylo.executeSQL<_post>(
|
|
138
|
+
const results = await cleanFylo.executeSQL(
|
|
204
139
|
`SELECT * FROM ${COLLECTION} WHERE title = 'After SQL Update'`
|
|
205
|
-
)
|
|
206
|
-
|
|
140
|
+
)
|
|
207
141
|
expect(Object.keys(results).length).toBe(1)
|
|
208
142
|
expect(Object.values(results)[0].title).toBe('After SQL Update')
|
|
209
143
|
})
|
|
210
|
-
|
|
211
|
-
test("DELETE ONE — delete a single document by querying its unique field", async () => {
|
|
212
|
-
|
|
144
|
+
test('DELETE ONE — delete a single document by querying its unique field', async () => {
|
|
213
145
|
const cleanFylo = new Fylo()
|
|
214
|
-
|
|
215
|
-
await cleanFylo.putData<_post>(COLLECTION, {
|
|
146
|
+
await cleanFylo.putData(COLLECTION, {
|
|
216
147
|
userId: 1,
|
|
217
148
|
id: 700,
|
|
218
149
|
title: 'Delete Via SQL',
|
|
219
150
|
body: 'should be removed'
|
|
220
151
|
})
|
|
221
|
-
|
|
222
|
-
await cleanFylo.executeSQL
|
|
223
|
-
`DELETE FROM ${COLLECTION} WHERE title = 'Delete Via SQL'`
|
|
224
|
-
)
|
|
225
|
-
|
|
226
|
-
const results = await cleanFylo.executeSQL<_post>(
|
|
152
|
+
await cleanFylo.executeSQL(`DELETE FROM ${COLLECTION} WHERE title = 'Delete Via SQL'`)
|
|
153
|
+
const results = await cleanFylo.executeSQL(
|
|
227
154
|
`SELECT * FROM ${COLLECTION} WHERE title = 'Delete Via SQL'`
|
|
228
|
-
)
|
|
229
|
-
|
|
155
|
+
)
|
|
230
156
|
expect(Object.keys(results).length).toBe(0)
|
|
231
157
|
})
|
|
232
158
|
})
|