@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.
Files changed (99) hide show
  1. package/README.md +27 -0
  2. package/dist/adapters/cipher.js +155 -0
  3. package/dist/adapters/cipher.js.map +1 -0
  4. package/dist/core/collection.js +6 -0
  5. package/dist/core/collection.js.map +1 -0
  6. package/{src/core/directory.ts → dist/core/directory.js} +28 -35
  7. package/dist/core/directory.js.map +1 -0
  8. package/dist/core/doc-id.js +15 -0
  9. package/dist/core/doc-id.js.map +1 -0
  10. package/dist/core/extensions.js +16 -0
  11. package/dist/core/extensions.js.map +1 -0
  12. package/dist/core/format.js +355 -0
  13. package/dist/core/format.js.map +1 -0
  14. package/dist/core/parser.js +764 -0
  15. package/dist/core/parser.js.map +1 -0
  16. package/dist/core/query.js +47 -0
  17. package/dist/core/query.js.map +1 -0
  18. package/dist/engines/s3-files/documents.js +62 -0
  19. package/dist/engines/s3-files/documents.js.map +1 -0
  20. package/dist/engines/s3-files/filesystem.js +165 -0
  21. package/dist/engines/s3-files/filesystem.js.map +1 -0
  22. package/dist/engines/s3-files/query.js +235 -0
  23. package/dist/engines/s3-files/query.js.map +1 -0
  24. package/dist/engines/s3-files/types.js +2 -0
  25. package/dist/engines/s3-files/types.js.map +1 -0
  26. package/dist/engines/s3-files.js +629 -0
  27. package/dist/engines/s3-files.js.map +1 -0
  28. package/dist/engines/types.js +2 -0
  29. package/dist/engines/types.js.map +1 -0
  30. package/dist/index.js +562 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/sync.js +18 -0
  33. package/dist/sync.js.map +1 -0
  34. package/{src → dist}/types/fylo.d.ts +14 -1
  35. package/package.json +2 -2
  36. package/.env.example +0 -16
  37. package/.github/copilot-instructions.md +0 -3
  38. package/.github/prompts/release.prompt.md +0 -10
  39. package/.github/workflows/ci.yml +0 -37
  40. package/.github/workflows/publish.yml +0 -91
  41. package/.prettierrc +0 -7
  42. package/AGENTS.md +0 -3
  43. package/CLAUDE.md +0 -3
  44. package/eslint.config.js +0 -32
  45. package/src/CLI +0 -39
  46. package/src/adapters/cipher.ts +0 -180
  47. package/src/core/collection.ts +0 -5
  48. package/src/core/extensions.ts +0 -21
  49. package/src/core/format.ts +0 -457
  50. package/src/core/parser.ts +0 -901
  51. package/src/core/query.ts +0 -53
  52. package/src/engines/s3-files/documents.ts +0 -65
  53. package/src/engines/s3-files/filesystem.ts +0 -172
  54. package/src/engines/s3-files/query.ts +0 -291
  55. package/src/engines/s3-files/types.ts +0 -42
  56. package/src/engines/s3-files.ts +0 -769
  57. package/src/engines/types.ts +0 -21
  58. package/src/index.ts +0 -632
  59. package/src/sync.ts +0 -58
  60. package/tests/collection/truncate.test.js +0 -36
  61. package/tests/data.js +0 -97
  62. package/tests/helpers/root.js +0 -7
  63. package/tests/integration/aws-s3-files.canary.test.js +0 -22
  64. package/tests/integration/create.test.js +0 -39
  65. package/tests/integration/delete.test.js +0 -97
  66. package/tests/integration/edge-cases.test.js +0 -162
  67. package/tests/integration/encryption.test.js +0 -148
  68. package/tests/integration/export.test.js +0 -46
  69. package/tests/integration/join-modes.test.js +0 -154
  70. package/tests/integration/nested.test.js +0 -144
  71. package/tests/integration/operators.test.js +0 -136
  72. package/tests/integration/read.test.js +0 -123
  73. package/tests/integration/rollback.test.js +0 -30
  74. package/tests/integration/s3-files.performance.test.js +0 -75
  75. package/tests/integration/s3-files.test.js +0 -205
  76. package/tests/integration/sync.test.js +0 -154
  77. package/tests/integration/update.test.js +0 -105
  78. package/tests/mocks/cipher.js +0 -40
  79. package/tests/schemas/album.d.ts +0 -5
  80. package/tests/schemas/album.json +0 -5
  81. package/tests/schemas/comment.d.ts +0 -7
  82. package/tests/schemas/comment.json +0 -7
  83. package/tests/schemas/photo.d.ts +0 -7
  84. package/tests/schemas/photo.json +0 -7
  85. package/tests/schemas/post.d.ts +0 -6
  86. package/tests/schemas/post.json +0 -6
  87. package/tests/schemas/tip.d.ts +0 -7
  88. package/tests/schemas/tip.json +0 -7
  89. package/tests/schemas/todo.d.ts +0 -6
  90. package/tests/schemas/todo.json +0 -6
  91. package/tests/schemas/user.d.ts +0 -23
  92. package/tests/schemas/user.json +0 -23
  93. package/tsconfig.json +0 -21
  94. package/tsconfig.typecheck.json +0 -31
  95. /package/{src → dist}/types/bun-runtime.d.ts +0 -0
  96. /package/{src → dist}/types/index.d.ts +0 -0
  97. /package/{src → dist}/types/node-runtime.d.ts +0 -0
  98. /package/{src → dist}/types/query.d.ts +0 -0
  99. /package/{src → dist}/types/vendor-modules.d.ts +0 -0
@@ -1,36 +0,0 @@
1
- import { test, expect, describe, afterAll } from 'bun:test'
2
- import { rm } from 'node:fs/promises'
3
- import Fylo from '../../src'
4
- import { postsURL, albumURL } from '../data'
5
- import { createTestRoot } from '../helpers/root'
6
- const POSTS = `post`
7
- const ALBUMS = `album`
8
- const root = await createTestRoot('fylo-truncate-')
9
- afterAll(async () => {
10
- const fylo = new Fylo({ root })
11
- await Promise.all([fylo.dropCollection(ALBUMS), fylo.dropCollection(POSTS)])
12
- await rm(root, { recursive: true, force: true })
13
- })
14
- describe('NO-SQL', () => {
15
- test('TRUNCATE', async () => {
16
- const fylo = new Fylo({ root })
17
- await fylo.createCollection(POSTS)
18
- await fylo.importBulkData(POSTS, new URL(postsURL))
19
- await fylo.delDocs(POSTS)
20
- const ids = []
21
- for await (const data of fylo.findDocs(POSTS, { $limit: 1, $onlyIds: true }).collect()) {
22
- ids.push(data)
23
- }
24
- expect(ids.length).toBe(0)
25
- })
26
- })
27
- describe('SQL', () => {
28
- test('TRUNCATE', async () => {
29
- const fylo = new Fylo({ root })
30
- await fylo.executeSQL(`CREATE TABLE ${ALBUMS}`)
31
- await fylo.importBulkData(ALBUMS, new URL(albumURL))
32
- await fylo.executeSQL(`DELETE FROM ${ALBUMS}`)
33
- const ids = await fylo.executeSQL(`SELECT _id FROM ${ALBUMS} LIMIT 1`)
34
- expect(ids.length).toBe(0)
35
- })
36
- })
package/tests/data.js DELETED
@@ -1,97 +0,0 @@
1
- function makeDataUrl(data) {
2
- return `data:application/json,${encodeURIComponent(JSON.stringify(data))}`
3
- }
4
- function generateAlbums() {
5
- return Array.from({ length: 100 }, (_, index) => {
6
- const id = index + 1
7
- const userId = Math.ceil(id / 10)
8
- const prefix = id <= 15 ? 'omnis' : id % 4 === 0 ? 'quidem' : 'album'
9
- return {
10
- id,
11
- userId,
12
- title: `${prefix} album ${id}`
13
- }
14
- })
15
- }
16
- function generatePosts() {
17
- return Array.from({ length: 100 }, (_, index) => {
18
- const id = index + 1
19
- const userId = Math.ceil(id / 10)
20
- return {
21
- id,
22
- userId,
23
- title: `post title ${id}`,
24
- body: `post body ${id} for user ${userId}`
25
- }
26
- })
27
- }
28
- function generateComments() {
29
- return Array.from({ length: 100 }, (_, index) => {
30
- const id = index + 1
31
- return {
32
- id,
33
- postId: id,
34
- name: `comment ${id}`,
35
- email: `comment${id}@example.com`,
36
- body: `comment body ${id}`
37
- }
38
- })
39
- }
40
- function generatePhotos() {
41
- return Array.from({ length: 100 }, (_, index) => {
42
- const id = index + 1
43
- const title = id % 3 === 0 ? `test photo ${id}` : `photo ${id}`
44
- return {
45
- id,
46
- albumId: Math.ceil(id / 10),
47
- title,
48
- url: `https://example.com/photos/${id}.jpg`,
49
- thumbnailUrl: `https://example.com/photos/${id}-thumb.jpg`
50
- }
51
- })
52
- }
53
- function generateTodos() {
54
- return Array.from({ length: 100 }, (_, index) => {
55
- const id = index + 1
56
- return {
57
- id,
58
- userId: Math.ceil(id / 10),
59
- title: id % 4 === 0 ? `test todo ${id}` : `todo ${id}`,
60
- completed: id % 2 === 0
61
- }
62
- })
63
- }
64
- function generateUsers() {
65
- return Array.from({ length: 10 }, (_, index) => {
66
- const id = index + 1
67
- return {
68
- id,
69
- name: `User ${id}`,
70
- username: `user${id}`,
71
- email: `user${id}@example.com`,
72
- address: {
73
- street: `Main Street ${id}`,
74
- suite: `Suite ${id}`,
75
- city: id <= 5 ? 'South Christy' : 'North Christy',
76
- zipcode: `0000${id}`,
77
- geo: {
78
- lat: 10 + id,
79
- lng: -20 - id
80
- }
81
- },
82
- phone: `555-000-${String(id).padStart(4, '0')}`,
83
- website: `user${id}.example.com`,
84
- company: {
85
- name: id <= 5 ? 'Acme Labs' : 'Northwind Labs',
86
- catchPhrase: `Catch phrase ${id}`,
87
- bs: `business ${id}`
88
- }
89
- }
90
- })
91
- }
92
- export const albumURL = makeDataUrl(generateAlbums())
93
- export const postsURL = makeDataUrl(generatePosts())
94
- export const commentsURL = makeDataUrl(generateComments())
95
- export const photosURL = makeDataUrl(generatePhotos())
96
- export const todosURL = makeDataUrl(generateTodos())
97
- export const usersURL = makeDataUrl(generateUsers())
@@ -1,7 +0,0 @@
1
- import { mkdtemp } from 'node:fs/promises'
2
- import os from 'node:os'
3
- import path from 'node:path'
4
-
5
- export async function createTestRoot(prefix = 'fylo-test-') {
6
- return await mkdtemp(path.join(os.tmpdir(), prefix))
7
- }
@@ -1,22 +0,0 @@
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
- })
@@ -1,39 +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 postsCount = 0
9
- let albumsCount = 0
10
- const root = await createTestRoot('fylo-create-')
11
- const fylo = new Fylo({ root })
12
- beforeAll(async () => {
13
- await Promise.all([fylo.createCollection(POSTS), fylo.executeSQL(`CREATE TABLE ${ALBUMS}`)])
14
- try {
15
- albumsCount = await fylo.importBulkData(ALBUMS, new URL(albumURL), 100)
16
- postsCount = await fylo.importBulkData(POSTS, new URL(postsURL), 100)
17
- } catch {
18
- await fylo.rollback()
19
- }
20
- })
21
- afterAll(async () => {
22
- await Promise.all([fylo.dropCollection(POSTS), fylo.executeSQL(`DROP TABLE ${ALBUMS}`)])
23
- await rm(root, { recursive: true, force: true })
24
- })
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 }
30
- }
31
- expect(Object.keys(results).length).toEqual(postsCount)
32
- })
33
- })
34
- describe('SQL', () => {
35
- test('INSERT', async () => {
36
- const results = await fylo.executeSQL(`SELECT * FROM ${ALBUMS}`)
37
- expect(Object.keys(results).length).toEqual(albumsCount)
38
- })
39
- })
@@ -1,97 +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 { commentsURL, usersURL } from '../data'
5
- import { createTestRoot } from '../helpers/root'
6
- const COMMENTS = `comment`
7
- const USERS = `user`
8
- let commentsResults = {}
9
- let usersResults = {}
10
- const root = await createTestRoot('fylo-delete-')
11
- const fylo = new Fylo({ root })
12
- beforeAll(async () => {
13
- await Promise.all([fylo.createCollection(COMMENTS), fylo.executeSQL(`CREATE TABLE ${USERS}`)])
14
- try {
15
- await Promise.all([
16
- fylo.importBulkData(COMMENTS, new URL(commentsURL), 100),
17
- fylo.importBulkData(USERS, new URL(usersURL), 100)
18
- ])
19
- } catch {
20
- await fylo.rollback()
21
- }
22
- for await (const data of fylo.findDocs(COMMENTS, { $limit: 1 }).collect()) {
23
- commentsResults = { ...commentsResults, ...data }
24
- }
25
- usersResults = await fylo.executeSQL(`SELECT * FROM ${USERS} LIMIT 1`)
26
- })
27
- afterAll(async () => {
28
- await Promise.all([fylo.dropCollection(COMMENTS), fylo.executeSQL(`DROP TABLE ${USERS}`)])
29
- await rm(root, { recursive: true, force: true })
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
55
- .findDocs(COMMENTS, {
56
- $ops: [{ name: { $like: '%et%' } }]
57
- })
58
- .collect()) {
59
- commentsResults = { ...commentsResults, ...data }
60
- }
61
- expect(Object.keys(commentsResults).length).toEqual(0)
62
- })
63
- test('DELETE ALL', async () => {
64
- try {
65
- await fylo.delDocs(COMMENTS)
66
- } catch {
67
- await fylo.rollback()
68
- }
69
- commentsResults = {}
70
- for await (const data of fylo.findDocs(COMMENTS).collect()) {
71
- commentsResults = { ...commentsResults, ...data }
72
- }
73
- expect(Object.keys(commentsResults).length).toEqual(0)
74
- })
75
- })
76
- describe('SQL', async () => {
77
- test('DELETE CLAUSE', async () => {
78
- const name = Object.values(usersResults).shift().name
79
- try {
80
- await fylo.executeSQL(`DELETE FROM ${USERS} WHERE name = '${name}'`)
81
- } catch {
82
- await fylo.rollback()
83
- }
84
- usersResults = await fylo.executeSQL(`SELECT * FROM ${USERS} WHERE name = '${name}'`)
85
- const idx = Object.values(usersResults).findIndex((com) => com.name === name)
86
- expect(idx).toBe(-1)
87
- })
88
- test('DELETE ALL', async () => {
89
- try {
90
- await fylo.executeSQL(`DELETE FROM ${USERS}`)
91
- } catch {
92
- await fylo.rollback()
93
- }
94
- usersResults = await fylo.executeSQL(`SELECT * FROM ${USERS}`)
95
- expect(Object.keys(usersResults).length).toBe(0)
96
- })
97
- })
@@ -1,162 +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 TTID from '@delma/ttid'
5
- import { createTestRoot } from '../helpers/root'
6
- const COLLECTION = 'ec-test'
7
- const root = await createTestRoot('fylo-edge-')
8
- const fylo = new Fylo({ root })
9
- beforeAll(async () => {
10
- await fylo.createCollection(COLLECTION)
11
- })
12
- afterAll(async () => {
13
- await fylo.dropCollection(COLLECTION)
14
- await rm(root, { recursive: true, force: true })
15
- })
16
- describe('NO-SQL', () => {
17
- test('GET ONE — non-existent ID returns empty object', async () => {
18
- const fakeId = TTID.generate()
19
- const result = await fylo.getDoc(COLLECTION, fakeId).once()
20
- expect(Object.keys(result).length).toBe(0)
21
- })
22
- test('PUT / GET — forward slashes in values round-trip correctly', async () => {
23
- const original = {
24
- userId: 1,
25
- id: 1,
26
- title: 'Slash Test',
27
- body: 'https://example.com/api/v1/resource'
28
- }
29
- const _id = await fylo.putData(COLLECTION, original)
30
- const result = await fylo.getDoc(COLLECTION, _id).once()
31
- const doc = result[_id]
32
- expect(doc.body).toBe(original.body)
33
- await fylo.delDoc(COLLECTION, _id)
34
- })
35
- test('PUT / GET — values with multiple consecutive slashes round-trip correctly', async () => {
36
- const original = {
37
- userId: 1,
38
- id: 2,
39
- title: 'Double Slash',
40
- body: 'https://cdn.example.com//assets//image.png'
41
- }
42
- const _id = await fylo.putData(COLLECTION, original)
43
- const result = await fylo.getDoc(COLLECTION, _id).once()
44
- expect(result[_id].body).toBe(original.body)
45
- await fylo.delDoc(COLLECTION, _id)
46
- })
47
- test('$ops — multiple conditions act as OR union', async () => {
48
- const cleanFylo = new Fylo({ root })
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
63
- .findDocs(COLLECTION, {
64
- $ops: [{ userId: { $eq: 10 } }, { userId: { $eq: 20 } }]
65
- })
66
- .collect()) {
67
- Object.assign(results, data)
68
- }
69
- expect(results[id1]).toBeDefined()
70
- expect(results[id2]).toBeDefined()
71
- await cleanFylo.delDoc(COLLECTION, id1)
72
- await cleanFylo.delDoc(COLLECTION, id2)
73
- })
74
- test('$rename — renames fields in query output', async () => {
75
- const cleanFylo = new Fylo({ root })
76
- const _id = await cleanFylo.putData(COLLECTION, {
77
- userId: 1,
78
- id: 300,
79
- title: 'Rename Me',
80
- body: 'some body'
81
- })
82
- let renamed = {}
83
- for await (const data of fylo
84
- .findDocs(COLLECTION, {
85
- $ops: [{ id: { $eq: 300 } }],
86
- $rename: { title: 'name' }
87
- })
88
- .collect()) {
89
- renamed = Object.values(data)[0]
90
- }
91
- expect(renamed.name).toBe('Rename Me')
92
- expect(renamed.title).toBeUndefined()
93
- await cleanFylo.delDoc(COLLECTION, _id)
94
- })
95
- test('versioned putData — preserves creation-time prefix in TTID', async () => {
96
- const cleanFylo = new Fylo({ root })
97
- const _id1 = await cleanFylo.putData(COLLECTION, {
98
- userId: 1,
99
- id: 400,
100
- title: 'Original',
101
- body: 'v1'
102
- })
103
- const _id2 = await cleanFylo.putData(COLLECTION, {
104
- [_id1]: { userId: 1, id: 400, title: 'Updated', body: 'v2' }
105
- })
106
- expect(_id2.split('-')[0]).toBe(_id1.split('-')[0])
107
- const result = await fylo.getDoc(COLLECTION, _id2).once()
108
- const doc = result[_id2]
109
- expect(doc).toBeDefined()
110
- expect(doc.title).toBe('Updated')
111
- await cleanFylo.delDoc(COLLECTION, _id1)
112
- await cleanFylo.delDoc(COLLECTION, _id2)
113
- })
114
- test('versioned putData — original version is no longer retrievable by old full TTID', async () => {
115
- const cleanFylo = new Fylo({ root })
116
- const _id1 = await cleanFylo.putData(COLLECTION, {
117
- userId: 1,
118
- id: 500,
119
- title: 'Old Version',
120
- body: 'original'
121
- })
122
- const _id2 = await cleanFylo.putData(COLLECTION, {
123
- [_id1]: { userId: 1, id: 500, title: 'New Version', body: 'updated' }
124
- })
125
- expect(_id1).not.toBe(_id2)
126
- await cleanFylo.delDoc(COLLECTION, _id2)
127
- })
128
- })
129
- describe('SQL', () => {
130
- test('UPDATE ONE — update a single document by querying its unique field', async () => {
131
- const cleanFylo = new Fylo({ root })
132
- await cleanFylo.putData(COLLECTION, {
133
- userId: 1,
134
- id: 600,
135
- title: 'Before SQL Update',
136
- body: 'original'
137
- })
138
- const updated = await cleanFylo.executeSQL(
139
- `UPDATE ${COLLECTION} SET title = 'After SQL Update' WHERE id = 600`
140
- )
141
- expect(updated).toBe(1)
142
- const results = await cleanFylo.executeSQL(
143
- `SELECT * FROM ${COLLECTION} WHERE title = 'After SQL Update'`
144
- )
145
- expect(Object.keys(results).length).toBe(1)
146
- expect(Object.values(results)[0].title).toBe('After SQL Update')
147
- })
148
- test('DELETE ONE — delete a single document by querying its unique field', async () => {
149
- const cleanFylo = new Fylo({ root })
150
- await cleanFylo.putData(COLLECTION, {
151
- userId: 1,
152
- id: 700,
153
- title: 'Delete Via SQL',
154
- body: 'should be removed'
155
- })
156
- await cleanFylo.executeSQL(`DELETE FROM ${COLLECTION} WHERE title = 'Delete Via SQL'`)
157
- const results = await cleanFylo.executeSQL(
158
- `SELECT * FROM ${COLLECTION} WHERE title = 'Delete Via SQL'`
159
- )
160
- expect(Object.keys(results).length).toBe(0)
161
- })
162
- })
@@ -1,148 +0,0 @@
1
- import { test, expect, describe, beforeAll, afterAll, mock } from 'bun:test'
2
- import { readFile, rm } from 'node:fs/promises'
3
- import path from 'node:path'
4
- import Fylo from '../../src'
5
- import { createTestRoot } from '../helpers/root'
6
- import { CipherMock } from '../mocks/cipher'
7
- const COLLECTION = 'encrypted-test'
8
- const root = await createTestRoot('fylo-encryption-')
9
- const fylo = new Fylo({ root })
10
- mock.module('../../src/adapters/cipher', () => ({ Cipher: CipherMock }))
11
- beforeAll(async () => {
12
- await fylo.createCollection(COLLECTION)
13
- await CipherMock.configure('test-secret-key')
14
- CipherMock.registerFields(COLLECTION, ['email', 'ssn', 'address'])
15
- })
16
- afterAll(async () => {
17
- CipherMock.reset()
18
- await fylo.dropCollection(COLLECTION)
19
- await rm(root, { recursive: true, force: true })
20
- })
21
- describe('Encryption', () => {
22
- let docId
23
- test('PUT encrypted document', async () => {
24
- docId = await fylo.putData(COLLECTION, {
25
- name: 'Alice',
26
- email: 'alice@example.com',
27
- ssn: '123-45-6789',
28
- age: 30
29
- })
30
- expect(docId).toBeDefined()
31
- })
32
- test('GET decrypts fields transparently', async () => {
33
- const result = await fylo.getDoc(COLLECTION, docId).once()
34
- const doc = Object.values(result)[0]
35
- expect(doc.name).toBe('Alice')
36
- expect(doc.email).toBe('alice@example.com')
37
- expect(doc.ssn).toBe('123-45-6789')
38
- expect(doc.age).toBe(30)
39
- })
40
- test('encrypted values stored in the doc file are not plaintext', async () => {
41
- const raw = await readFile(
42
- path.join(root, COLLECTION, '.fylo', 'docs', docId.slice(0, 2), `${docId}.json`),
43
- 'utf8'
44
- )
45
- expect(raw).not.toContain('alice@example.com')
46
- expect(raw).not.toContain('123-45-6789')
47
- })
48
- test('$eq query works on encrypted field', async () => {
49
- let found = false
50
- for await (const data of fylo
51
- .findDocs(COLLECTION, {
52
- $ops: [{ email: { $eq: 'alice@example.com' } }]
53
- })
54
- .collect()) {
55
- if (typeof data === 'object') {
56
- const doc = Object.values(data)[0]
57
- expect(doc.email).toBe('alice@example.com')
58
- found = true
59
- }
60
- }
61
- expect(found).toBe(true)
62
- })
63
- test('$ne throws on encrypted field', async () => {
64
- try {
65
- const iter = fylo
66
- .findDocs(COLLECTION, {
67
- $ops: [{ email: { $ne: 'bob@example.com' } }]
68
- })
69
- .collect()
70
- await iter.next()
71
- expect(true).toBe(false)
72
- } catch (e) {
73
- expect(e.message).toContain('not supported on encrypted field')
74
- }
75
- })
76
- test('$gt throws on encrypted field', async () => {
77
- try {
78
- const iter = fylo
79
- .findDocs(COLLECTION, {
80
- $ops: [{ ssn: { $gt: 0 } }]
81
- })
82
- .collect()
83
- await iter.next()
84
- expect(true).toBe(false)
85
- } catch (e) {
86
- expect(e.message).toContain('not supported on encrypted field')
87
- }
88
- })
89
- test('$like throws on encrypted field', async () => {
90
- try {
91
- const iter = fylo
92
- .findDocs(COLLECTION, {
93
- $ops: [{ email: { $like: '%@example.com' } }]
94
- })
95
- .collect()
96
- await iter.next()
97
- expect(true).toBe(false)
98
- } catch (e) {
99
- expect(e.message).toContain('not supported on encrypted field')
100
- }
101
- })
102
- test('non-encrypted fields remain queryable with all operators', async () => {
103
- let found = false
104
- for await (const data of fylo
105
- .findDocs(COLLECTION, {
106
- $ops: [{ name: { $eq: 'Alice' } }]
107
- })
108
- .collect()) {
109
- if (typeof data === 'object') {
110
- const doc = Object.values(data)[0]
111
- expect(doc.name).toBe('Alice')
112
- found = true
113
- }
114
- }
115
- expect(found).toBe(true)
116
- })
117
- test('nested encrypted field (address.city)', async () => {
118
- const id = await fylo.putData(COLLECTION, {
119
- name: 'Bob',
120
- email: 'bob@example.com',
121
- ssn: '987-65-4321',
122
- age: 25,
123
- address: { city: 'Toronto', zip: 'M5V 2T6' }
124
- })
125
- const result = await fylo.getDoc(COLLECTION, id).once()
126
- const doc = Object.values(result)[0]
127
- expect(doc.address.city).toBe('Toronto')
128
- expect(doc.address.zip).toBe('M5V 2T6')
129
- })
130
- test('UPDATE preserves encryption', async () => {
131
- await fylo.patchDoc(COLLECTION, {
132
- [docId]: { email: 'alice-new@example.com' }
133
- })
134
- let found = false
135
- for await (const data of fylo
136
- .findDocs(COLLECTION, {
137
- $ops: [{ email: { $eq: 'alice-new@example.com' } }]
138
- })
139
- .collect()) {
140
- if (typeof data === 'object') {
141
- const doc = Object.values(data)[0]
142
- expect(doc.email).toBe('alice-new@example.com')
143
- found = true
144
- }
145
- }
146
- expect(found).toBe(true)
147
- })
148
- })
@@ -1,46 +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 { postsURL } from '../data'
5
- import { createTestRoot } from '../helpers/root'
6
- const POSTS = 'exp-post'
7
- const IMPORT_LIMIT = 20
8
- let importedCount = 0
9
- const root = await createTestRoot('fylo-export-')
10
- const fylo = new Fylo({ root })
11
- beforeAll(async () => {
12
- await fylo.createCollection(POSTS)
13
- try {
14
- importedCount = await fylo.importBulkData(POSTS, new URL(postsURL), IMPORT_LIMIT)
15
- } catch {
16
- await fylo.rollback()
17
- }
18
- })
19
- afterAll(async () => {
20
- await fylo.dropCollection(POSTS)
21
- await rm(root, { recursive: true, force: true })
22
- })
23
- describe('NO-SQL', () => {
24
- test('EXPORT count matches import', async () => {
25
- let exported = 0
26
- for await (const _doc of fylo.exportBulkData(POSTS)) {
27
- exported++
28
- }
29
- expect(exported).toBe(importedCount)
30
- })
31
- test('EXPORT document shape', async () => {
32
- for await (const doc of fylo.exportBulkData(POSTS)) {
33
- expect(doc).toHaveProperty('title')
34
- expect(doc).toHaveProperty('userId')
35
- expect(doc).toHaveProperty('body')
36
- break
37
- }
38
- })
39
- test('EXPORT all documents are valid posts', async () => {
40
- for await (const doc of fylo.exportBulkData(POSTS)) {
41
- expect(typeof doc.title).toBe('string')
42
- expect(typeof doc.userId).toBe('number')
43
- expect(doc.userId).toBeGreaterThan(0)
44
- }
45
- })
46
- })