@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,205 +0,0 @@
|
|
|
1
|
-
import { afterAll, beforeAll, describe, expect, test } from 'bun:test'
|
|
2
|
-
import { mkdtemp, readFile, rm, stat } from 'node:fs/promises'
|
|
3
|
-
import os from 'node:os'
|
|
4
|
-
import path from 'node:path'
|
|
5
|
-
import Fylo from '../../src'
|
|
6
|
-
const root = await mkdtemp(path.join(os.tmpdir(), 'fylo-s3files-'))
|
|
7
|
-
const fylo = new Fylo({ root })
|
|
8
|
-
const POSTS = 's3files-posts'
|
|
9
|
-
const USERS = 's3files-users'
|
|
10
|
-
describe('s3-files engine', () => {
|
|
11
|
-
beforeAll(async () => {
|
|
12
|
-
await fylo.createCollection(POSTS)
|
|
13
|
-
await fylo.createCollection(USERS)
|
|
14
|
-
})
|
|
15
|
-
afterAll(async () => {
|
|
16
|
-
await rm(root, { recursive: true, force: true })
|
|
17
|
-
})
|
|
18
|
-
test('put/get/patch/delete works without Redis or S3 adapters', async () => {
|
|
19
|
-
const id = await fylo.putData(POSTS, {
|
|
20
|
-
title: 'Hello',
|
|
21
|
-
tags: ['bun', 'aws'],
|
|
22
|
-
meta: { score: 1 }
|
|
23
|
-
})
|
|
24
|
-
const created = await fylo.getDoc(POSTS, id).once()
|
|
25
|
-
expect(created[id].title).toBe('Hello')
|
|
26
|
-
expect(created[id].tags).toEqual(['bun', 'aws'])
|
|
27
|
-
const nextId = await fylo.patchDoc(POSTS, {
|
|
28
|
-
[id]: {
|
|
29
|
-
title: 'Hello 2',
|
|
30
|
-
meta: { score: 2 }
|
|
31
|
-
}
|
|
32
|
-
})
|
|
33
|
-
const updated = await fylo.getDoc(POSTS, nextId).once()
|
|
34
|
-
expect(updated[nextId].title).toBe('Hello 2')
|
|
35
|
-
expect(updated[nextId].meta.score).toBe(2)
|
|
36
|
-
expect(await fylo.getDoc(POSTS, id).once()).toEqual({})
|
|
37
|
-
await fylo.delDoc(POSTS, nextId)
|
|
38
|
-
expect(await fylo.getDoc(POSTS, nextId).once()).toEqual({})
|
|
39
|
-
})
|
|
40
|
-
test('findDocs listener is backed by the filesystem event journal', async () => {
|
|
41
|
-
const iter = fylo
|
|
42
|
-
.findDocs(POSTS, {
|
|
43
|
-
$ops: [{ title: { $eq: 'Live event' } }]
|
|
44
|
-
})
|
|
45
|
-
[Symbol.asyncIterator]()
|
|
46
|
-
const pending = iter.next()
|
|
47
|
-
await Bun.sleep(100)
|
|
48
|
-
const id = await fylo.putData(POSTS, { title: 'Live event' })
|
|
49
|
-
const { value } = await pending
|
|
50
|
-
expect(value).toEqual({ [id]: { title: 'Live event' } })
|
|
51
|
-
await iter.return?.()
|
|
52
|
-
})
|
|
53
|
-
test('supports long values without path-length issues', async () => {
|
|
54
|
-
const longBody = 'x'.repeat(5000)
|
|
55
|
-
const id = await fylo.putData(POSTS, {
|
|
56
|
-
title: 'Long payload',
|
|
57
|
-
body: longBody
|
|
58
|
-
})
|
|
59
|
-
const result = await fylo.getDoc(POSTS, id).once()
|
|
60
|
-
expect(result[id].body).toBe(longBody)
|
|
61
|
-
})
|
|
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
|
-
)
|
|
165
|
-
})
|
|
166
|
-
test('joins work in s3-files mode', async () => {
|
|
167
|
-
const userId = await fylo.putData(USERS, { id: 42, name: 'Ada' })
|
|
168
|
-
const postId = await fylo.putData(POSTS, { id: 42, title: 'Shared', content: 'join me' })
|
|
169
|
-
const joined = await fylo.joinDocs({
|
|
170
|
-
$leftCollection: USERS,
|
|
171
|
-
$rightCollection: POSTS,
|
|
172
|
-
$mode: 'inner',
|
|
173
|
-
$on: {
|
|
174
|
-
id: { $eq: 'id' }
|
|
175
|
-
}
|
|
176
|
-
})
|
|
177
|
-
expect(joined[`${userId}, ${postId}`]).toBeDefined()
|
|
178
|
-
})
|
|
179
|
-
test('queue APIs are explicitly unsupported', async () => {
|
|
180
|
-
await expect(fylo.queuePutData(POSTS, { title: 'no queue' })).rejects.toThrow(
|
|
181
|
-
'queuePutData was removed'
|
|
182
|
-
)
|
|
183
|
-
await expect(fylo.processQueuedWrites(1)).rejects.toThrow('processQueuedWrites was removed')
|
|
184
|
-
})
|
|
185
|
-
test('rejects collection names that are unsafe for cross-platform filesystems', async () => {
|
|
186
|
-
await expect(fylo.createCollection('bad/name')).rejects.toThrow('Invalid collection name')
|
|
187
|
-
await expect(fylo.createCollection('bad\\name')).rejects.toThrow('Invalid collection name')
|
|
188
|
-
await expect(fylo.createCollection('bad:name')).rejects.toThrow('Invalid collection name')
|
|
189
|
-
})
|
|
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
|
|
194
|
-
process.env.FYLO_S3FILES_ROOT = root
|
|
195
|
-
const collection = 's3files-static'
|
|
196
|
-
await Fylo.createCollection(collection)
|
|
197
|
-
const id = await fylo.putData(collection, { title: 'Static path' })
|
|
198
|
-
const result = await Fylo.getDoc(collection, id).once()
|
|
199
|
-
expect(result[id].title).toBe('Static path')
|
|
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
|
|
204
|
-
})
|
|
205
|
-
})
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
import { afterAll, 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, { FyloSyncError } from '../../src'
|
|
6
|
-
|
|
7
|
-
const roots = []
|
|
8
|
-
|
|
9
|
-
async function createRoot(prefix) {
|
|
10
|
-
const root = await mkdtemp(path.join(os.tmpdir(), prefix))
|
|
11
|
-
roots.push(root)
|
|
12
|
-
return root
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
afterAll(async () => {
|
|
16
|
-
await Promise.all(roots.map((root) => rm(root, { recursive: true, force: true })))
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
describe('sync hooks', () => {
|
|
20
|
-
test('await-sync emits write, patch, and delete events with filesystem paths', async () => {
|
|
21
|
-
const root = await createRoot('fylo-sync-await-')
|
|
22
|
-
const calls = []
|
|
23
|
-
const fylo = new Fylo({
|
|
24
|
-
root,
|
|
25
|
-
sync: {
|
|
26
|
-
onWrite: async (event) => {
|
|
27
|
-
calls.push({ hook: 'write', ...event })
|
|
28
|
-
},
|
|
29
|
-
onDelete: async (event) => {
|
|
30
|
-
calls.push({ hook: 'delete', ...event })
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
const collection = 'sync-posts'
|
|
36
|
-
await fylo.createCollection(collection)
|
|
37
|
-
|
|
38
|
-
const id = await fylo.putData(collection, { title: 'Hello sync' })
|
|
39
|
-
const nextId = await fylo.patchDoc(collection, {
|
|
40
|
-
[id]: { title: 'Hello sync 2' }
|
|
41
|
-
})
|
|
42
|
-
await fylo.delDoc(collection, nextId)
|
|
43
|
-
|
|
44
|
-
expect(calls).toEqual([
|
|
45
|
-
{
|
|
46
|
-
hook: 'write',
|
|
47
|
-
operation: 'put',
|
|
48
|
-
collection,
|
|
49
|
-
docId: id,
|
|
50
|
-
path: path.join(root, collection, '.fylo', 'docs', id.slice(0, 2), `${id}.json`),
|
|
51
|
-
data: { title: 'Hello sync' }
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
hook: 'delete',
|
|
55
|
-
operation: 'patch',
|
|
56
|
-
collection,
|
|
57
|
-
docId: id,
|
|
58
|
-
path: path.join(root, collection, '.fylo', 'docs', id.slice(0, 2), `${id}.json`)
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
hook: 'write',
|
|
62
|
-
operation: 'patch',
|
|
63
|
-
collection,
|
|
64
|
-
docId: nextId,
|
|
65
|
-
previousDocId: id,
|
|
66
|
-
path: path.join(
|
|
67
|
-
root,
|
|
68
|
-
collection,
|
|
69
|
-
'.fylo',
|
|
70
|
-
'docs',
|
|
71
|
-
nextId.slice(0, 2),
|
|
72
|
-
`${nextId}.json`
|
|
73
|
-
),
|
|
74
|
-
data: { title: 'Hello sync 2' }
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
hook: 'delete',
|
|
78
|
-
operation: 'delete',
|
|
79
|
-
collection,
|
|
80
|
-
docId: nextId,
|
|
81
|
-
path: path.join(
|
|
82
|
-
root,
|
|
83
|
-
collection,
|
|
84
|
-
'.fylo',
|
|
85
|
-
'docs',
|
|
86
|
-
nextId.slice(0, 2),
|
|
87
|
-
`${nextId}.json`
|
|
88
|
-
)
|
|
89
|
-
}
|
|
90
|
-
])
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
test('fire-and-forget does not block the local write', async () => {
|
|
94
|
-
const root = await createRoot('fylo-sync-fire-')
|
|
95
|
-
let releaseHook
|
|
96
|
-
let writeStarted = false
|
|
97
|
-
const started = Promise.withResolvers()
|
|
98
|
-
const hookBlocker = new Promise((resolve) => {
|
|
99
|
-
releaseHook = resolve
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
const fylo = new Fylo({
|
|
103
|
-
root,
|
|
104
|
-
syncMode: 'fire-and-forget',
|
|
105
|
-
sync: {
|
|
106
|
-
onWrite: async () => {
|
|
107
|
-
writeStarted = true
|
|
108
|
-
started.resolve()
|
|
109
|
-
await hookBlocker
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
await fylo.createCollection('fire-posts')
|
|
115
|
-
|
|
116
|
-
const putPromise = fylo.putData('fire-posts', { title: 'Fast local write' })
|
|
117
|
-
await started.promise
|
|
118
|
-
|
|
119
|
-
const state = await Promise.race([
|
|
120
|
-
putPromise.then(() => 'resolved'),
|
|
121
|
-
Bun.sleep(25).then(() => 'pending')
|
|
122
|
-
])
|
|
123
|
-
|
|
124
|
-
expect(writeStarted).toBe(true)
|
|
125
|
-
expect(state).toBe('resolved')
|
|
126
|
-
|
|
127
|
-
releaseHook()
|
|
128
|
-
await putPromise
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
test('await-sync surfaces sync failures as FyloSyncError after the local write', async () => {
|
|
132
|
-
const root = await createRoot('fylo-sync-error-')
|
|
133
|
-
let failedDocId
|
|
134
|
-
const fylo = new Fylo({
|
|
135
|
-
root,
|
|
136
|
-
sync: {
|
|
137
|
-
onWrite: async (event) => {
|
|
138
|
-
failedDocId = event.docId
|
|
139
|
-
throw new Error('remote unavailable')
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
await fylo.createCollection('error-posts')
|
|
145
|
-
|
|
146
|
-
await expect(
|
|
147
|
-
fylo.putData('error-posts', { title: 'Still written locally' })
|
|
148
|
-
).rejects.toBeInstanceOf(FyloSyncError)
|
|
149
|
-
|
|
150
|
-
expect(failedDocId).toBeDefined()
|
|
151
|
-
const stored = await fylo.getDoc('error-posts', failedDocId).once()
|
|
152
|
-
expect(stored[failedDocId]).toEqual({ title: 'Still written locally' })
|
|
153
|
-
})
|
|
154
|
-
})
|
|
@@ -1,105 +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 { photosURL, todosURL } from '../data'
|
|
5
|
-
import { createTestRoot } from '../helpers/root'
|
|
6
|
-
const PHOTOS = `photo`
|
|
7
|
-
const TODOS = `todo`
|
|
8
|
-
const root = await createTestRoot('fylo-update-')
|
|
9
|
-
const fylo = new Fylo({ root })
|
|
10
|
-
beforeAll(async () => {
|
|
11
|
-
await Promise.all([fylo.createCollection(PHOTOS), fylo.executeSQL(`CREATE TABLE ${TODOS}`)])
|
|
12
|
-
try {
|
|
13
|
-
await fylo.importBulkData(PHOTOS, new URL(photosURL), 100)
|
|
14
|
-
await fylo.importBulkData(TODOS, new URL(todosURL), 100)
|
|
15
|
-
} catch {
|
|
16
|
-
await fylo.rollback()
|
|
17
|
-
}
|
|
18
|
-
})
|
|
19
|
-
afterAll(async () => {
|
|
20
|
-
await Promise.all([fylo.dropCollection(PHOTOS), fylo.executeSQL(`DROP TABLE ${TODOS}`)])
|
|
21
|
-
await rm(root, { recursive: true, force: true })
|
|
22
|
-
})
|
|
23
|
-
describe('NO-SQL', async () => {
|
|
24
|
-
test('UPDATE ONE', async () => {
|
|
25
|
-
const ids = []
|
|
26
|
-
for await (const data of fylo.findDocs(PHOTOS, { $limit: 1, $onlyIds: true }).collect()) {
|
|
27
|
-
ids.push(data)
|
|
28
|
-
}
|
|
29
|
-
try {
|
|
30
|
-
await fylo.patchDoc(PHOTOS, { [ids.shift()]: { title: 'All Mighty' } })
|
|
31
|
-
} catch {
|
|
32
|
-
await fylo.rollback()
|
|
33
|
-
}
|
|
34
|
-
let results = {}
|
|
35
|
-
for await (const data of fylo
|
|
36
|
-
.findDocs(PHOTOS, {
|
|
37
|
-
$ops: [{ title: { $eq: 'All Mighty' } }]
|
|
38
|
-
})
|
|
39
|
-
.collect()) {
|
|
40
|
-
results = { ...results, ...data }
|
|
41
|
-
}
|
|
42
|
-
expect(Object.keys(results).length).toBe(1)
|
|
43
|
-
})
|
|
44
|
-
test('UPDATE CLAUSE', async () => {
|
|
45
|
-
let count = -1
|
|
46
|
-
try {
|
|
47
|
-
count = await fylo.patchDocs(PHOTOS, {
|
|
48
|
-
$set: { title: 'All Mighti' },
|
|
49
|
-
$where: { $ops: [{ title: { $like: '%est%' } }] }
|
|
50
|
-
})
|
|
51
|
-
} catch {
|
|
52
|
-
await fylo.rollback()
|
|
53
|
-
}
|
|
54
|
-
let results = {}
|
|
55
|
-
for await (const data of fylo
|
|
56
|
-
.findDocs(PHOTOS, {
|
|
57
|
-
$ops: [{ title: { $eq: 'All Mighti' } }]
|
|
58
|
-
})
|
|
59
|
-
.collect()) {
|
|
60
|
-
results = { ...results, ...data }
|
|
61
|
-
}
|
|
62
|
-
expect(Object.keys(results).length).toBe(count)
|
|
63
|
-
})
|
|
64
|
-
test('UPDATE ALL', async () => {
|
|
65
|
-
let count = -1
|
|
66
|
-
try {
|
|
67
|
-
count = await fylo.patchDocs(PHOTOS, { $set: { title: 'All Mighter' } })
|
|
68
|
-
} catch {
|
|
69
|
-
await fylo.rollback()
|
|
70
|
-
}
|
|
71
|
-
let results = {}
|
|
72
|
-
for await (const data of fylo
|
|
73
|
-
.findDocs(PHOTOS, {
|
|
74
|
-
$ops: [{ title: { $eq: 'All Mighter' } }]
|
|
75
|
-
})
|
|
76
|
-
.collect()) {
|
|
77
|
-
results = { ...results, ...data }
|
|
78
|
-
}
|
|
79
|
-
expect(Object.keys(results).length).toBe(count)
|
|
80
|
-
}, 20000)
|
|
81
|
-
})
|
|
82
|
-
describe('SQL', async () => {
|
|
83
|
-
test('UPDATE CLAUSE', async () => {
|
|
84
|
-
let count = -1
|
|
85
|
-
try {
|
|
86
|
-
count = await fylo.executeSQL(
|
|
87
|
-
`UPDATE ${TODOS} SET title = 'All Mighty' WHERE title LIKE '%est%'`
|
|
88
|
-
)
|
|
89
|
-
} catch {
|
|
90
|
-
await fylo.rollback()
|
|
91
|
-
}
|
|
92
|
-
const results = await fylo.executeSQL(`SELECT * FROM ${TODOS} WHERE title = 'All Mighty'`)
|
|
93
|
-
expect(Object.keys(results).length).toBe(count)
|
|
94
|
-
})
|
|
95
|
-
test('UPDATE ALL', async () => {
|
|
96
|
-
let count = -1
|
|
97
|
-
try {
|
|
98
|
-
count = await fylo.executeSQL(`UPDATE ${TODOS} SET title = 'All Mightier'`)
|
|
99
|
-
} catch {
|
|
100
|
-
await fylo.rollback()
|
|
101
|
-
}
|
|
102
|
-
const results = await fylo.executeSQL(`SELECT * FROM ${TODOS} WHERE title = 'All Mightier'`)
|
|
103
|
-
expect(Object.keys(results).length).toBe(count)
|
|
104
|
-
}, 20000)
|
|
105
|
-
})
|
package/tests/mocks/cipher.js
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
export class CipherMock {
|
|
2
|
-
static _configured = false
|
|
3
|
-
static collections = new Map()
|
|
4
|
-
static isConfigured() {
|
|
5
|
-
return CipherMock._configured
|
|
6
|
-
}
|
|
7
|
-
static hasEncryptedFields(collection) {
|
|
8
|
-
const fields = CipherMock.collections.get(collection)
|
|
9
|
-
return !!fields && fields.size > 0
|
|
10
|
-
}
|
|
11
|
-
static isEncryptedField(collection, field) {
|
|
12
|
-
const fields = CipherMock.collections.get(collection)
|
|
13
|
-
if (!fields || fields.size === 0) return false
|
|
14
|
-
for (const pattern of fields) {
|
|
15
|
-
if (field === pattern) return true
|
|
16
|
-
if (field.startsWith(`${pattern}/`)) return true
|
|
17
|
-
}
|
|
18
|
-
return false
|
|
19
|
-
}
|
|
20
|
-
static registerFields(collection, fields) {
|
|
21
|
-
if (fields.length > 0) {
|
|
22
|
-
CipherMock.collections.set(collection, new Set(fields))
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
static async configure(_secret) {
|
|
26
|
-
CipherMock._configured = true
|
|
27
|
-
}
|
|
28
|
-
static reset() {
|
|
29
|
-
CipherMock._configured = false
|
|
30
|
-
CipherMock.collections = new Map()
|
|
31
|
-
}
|
|
32
|
-
static async encrypt(value) {
|
|
33
|
-
return btoa(value).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
|
|
34
|
-
}
|
|
35
|
-
static async decrypt(encoded) {
|
|
36
|
-
const b64 = encoded.replace(/-/g, '+').replace(/_/g, '/')
|
|
37
|
-
const padded = b64 + '='.repeat((4 - (b64.length % 4)) % 4)
|
|
38
|
-
return atob(padded)
|
|
39
|
-
}
|
|
40
|
-
}
|
package/tests/schemas/album.d.ts
DELETED
package/tests/schemas/album.json
DELETED
package/tests/schemas/photo.d.ts
DELETED
package/tests/schemas/photo.json
DELETED
package/tests/schemas/post.d.ts
DELETED
package/tests/schemas/post.json
DELETED
package/tests/schemas/tip.d.ts
DELETED
package/tests/schemas/tip.json
DELETED
package/tests/schemas/todo.d.ts
DELETED
package/tests/schemas/todo.json
DELETED
package/tests/schemas/user.d.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
interface _user {
|
|
2
|
-
id: number
|
|
3
|
-
name: string
|
|
4
|
-
username: string
|
|
5
|
-
email: string
|
|
6
|
-
address: {
|
|
7
|
-
street: string
|
|
8
|
-
suite: string
|
|
9
|
-
city: string
|
|
10
|
-
zipcode: string
|
|
11
|
-
geo: {
|
|
12
|
-
lat: string
|
|
13
|
-
lng: string
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
phone: string
|
|
17
|
-
website: string
|
|
18
|
-
company: {
|
|
19
|
-
name: string
|
|
20
|
-
catchPhrase: string
|
|
21
|
-
bs: string
|
|
22
|
-
}
|
|
23
|
-
}
|
package/tests/schemas/user.json
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"id": -0,
|
|
3
|
-
"name": "",
|
|
4
|
-
"username": "",
|
|
5
|
-
"^email$": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$",
|
|
6
|
-
"address": {
|
|
7
|
-
"street": "",
|
|
8
|
-
"suite": "",
|
|
9
|
-
"city": "",
|
|
10
|
-
"zipcode": "",
|
|
11
|
-
"geo": {
|
|
12
|
-
"lat": "",
|
|
13
|
-
"lng": ""
|
|
14
|
-
}
|
|
15
|
-
},
|
|
16
|
-
"phone": "",
|
|
17
|
-
"website": "",
|
|
18
|
-
"company": {
|
|
19
|
-
"name": "",
|
|
20
|
-
"catchPhrase": "",
|
|
21
|
-
"bs": ""
|
|
22
|
-
}
|
|
23
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"strict": true,
|
|
4
|
-
"outDir": "dist",
|
|
5
|
-
"target": "ESNext",
|
|
6
|
-
"lib": ["ESNext"],
|
|
7
|
-
"module": "ES2022",
|
|
8
|
-
"moduleResolution": "bundler",
|
|
9
|
-
"sourceMap": true,
|
|
10
|
-
"experimentalDecorators": true,
|
|
11
|
-
"pretty": true,
|
|
12
|
-
"noFallthroughCasesInSwitch": true,
|
|
13
|
-
"noImplicitReturns": true,
|
|
14
|
-
"forceConsistentCasingInFileNames": true,
|
|
15
|
-
"types": ["bun-types", "node"],
|
|
16
|
-
"isolatedModules": true,
|
|
17
|
-
"skipLibCheck": true
|
|
18
|
-
},
|
|
19
|
-
"include": ["src/**/*"],
|
|
20
|
-
"exclude": ["dist", "tests", "node_modules"]
|
|
21
|
-
}
|