@delma/fylo 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +185 -267
- package/package.json +2 -5
- package/src/core/directory.ts +22 -354
- package/src/engines/s3-files/documents.ts +65 -0
- package/src/engines/s3-files/filesystem.ts +172 -0
- package/src/engines/s3-files/query.ts +291 -0
- package/src/engines/s3-files/types.ts +42 -0
- package/src/engines/s3-files.ts +391 -510
- package/src/engines/types.ts +1 -1
- package/src/index.ts +142 -1237
- package/src/sync.ts +58 -0
- package/src/types/fylo.d.ts +66 -161
- package/src/types/node-runtime.d.ts +1 -0
- package/tests/collection/truncate.test.js +11 -10
- package/tests/helpers/root.js +7 -0
- package/tests/integration/create.test.js +9 -9
- package/tests/integration/delete.test.js +16 -14
- package/tests/integration/edge-cases.test.js +29 -25
- package/tests/integration/encryption.test.js +47 -30
- package/tests/integration/export.test.js +11 -11
- package/tests/integration/join-modes.test.js +16 -16
- package/tests/integration/nested.test.js +26 -24
- package/tests/integration/operators.test.js +43 -29
- package/tests/integration/read.test.js +25 -21
- package/tests/integration/rollback.test.js +21 -51
- package/tests/integration/s3-files.performance.test.js +75 -0
- package/tests/integration/s3-files.test.js +115 -18
- package/tests/integration/sync.test.js +154 -0
- package/tests/integration/update.test.js +24 -18
- package/src/adapters/redis.ts +0 -487
- package/src/adapters/s3.ts +0 -61
- package/src/core/walker.ts +0 -174
- package/src/core/write-queue.ts +0 -59
- package/src/migrate-cli.ts +0 -22
- package/src/migrate.ts +0 -74
- package/src/types/write-queue.ts +0 -42
- package/src/worker.ts +0 -18
- package/src/workers/write-worker.ts +0 -120
- package/tests/index.js +0 -14
- package/tests/integration/migration.test.js +0 -38
- package/tests/integration/queue.test.js +0 -83
- package/tests/mocks/redis.js +0 -123
- package/tests/mocks/s3.js +0 -80
package/README.md
CHANGED
|
@@ -1,368 +1,286 @@
|
|
|
1
|
-
#
|
|
1
|
+
# FYLO
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
FYLO is a Bun-native document store that keeps **one canonical file per document** and builds a **collection index file** to make queries fast.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The important mental model is simple:
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
7
|
+
- document files are the source of truth
|
|
8
|
+
- the index file is just an accelerator
|
|
9
|
+
- if the index ever gets out of date, FYLO can rebuild it from the documents
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
FYLO now ships with **one engine**: a filesystem-first storage model designed to work well with AWS S3 Files and other synced filesystem setups.
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
## Why this design?
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
We wanted three things:
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
- low durable storage overhead
|
|
18
|
+
- fast application queries
|
|
19
|
+
- a system that is still understandable by normal engineers
|
|
17
20
|
|
|
18
|
-
|
|
21
|
+
That is why FYLO does **not** create one tiny durable file per indexed field and does **not** depend on Redis-backed queued writes anymore.
|
|
19
22
|
|
|
20
|
-
|
|
21
|
-
bun add @delma/fylo
|
|
22
|
-
```
|
|
23
|
+
Instead, each collection looks like this:
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
})
|
|
25
|
+
```text
|
|
26
|
+
<root>/<collection>/
|
|
27
|
+
.fylo/
|
|
28
|
+
docs/
|
|
29
|
+
4U/
|
|
30
|
+
4UUB32VGUDW.json
|
|
31
|
+
indexes/
|
|
32
|
+
<collection>.idx.json
|
|
33
|
+
events/
|
|
34
|
+
<collection>.ndjson
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
## Installation
|
|
38
38
|
|
|
39
39
|
```bash
|
|
40
|
-
|
|
41
|
-
export FYLO_S3FILES_ROOT=/mnt/fylo
|
|
40
|
+
bun add @delma/fylo
|
|
42
41
|
```
|
|
43
42
|
|
|
44
|
-
##
|
|
45
|
-
|
|
46
|
-
| Variable | Purpose |
|
|
47
|
-
| ------------------------------------------------ | ------------------------------------------------------------------------------------ |
|
|
48
|
-
| `FYLO_STORAGE_ENGINE` | `legacy-s3` (default) or `s3-files` |
|
|
49
|
-
| `FYLO_S3FILES_ROOT` | Mounted S3 Files root directory used by the `s3-files` engine |
|
|
50
|
-
| `BUCKET_PREFIX` | S3 bucket name prefix |
|
|
51
|
-
| `S3_ACCESS_KEY_ID` / `AWS_ACCESS_KEY_ID` | S3 credentials |
|
|
52
|
-
| `S3_SECRET_ACCESS_KEY` / `AWS_SECRET_ACCESS_KEY` | S3 credentials |
|
|
53
|
-
| `S3_REGION` / `AWS_REGION` | S3 region |
|
|
54
|
-
| `S3_ENDPOINT` / `AWS_ENDPOINT` | S3 endpoint (for LocalStack, MinIO, etc.) |
|
|
55
|
-
| `REDIS_URL` | Redis connection URL used for pub/sub, document locks, and queued write coordination |
|
|
56
|
-
| `FYLO_WRITE_MAX_ATTEMPTS` | Maximum retry attempts before a queued job is dead-lettered |
|
|
57
|
-
| `FYLO_WRITE_RETRY_BASE_MS` | Base retry delay used for exponential backoff between recovery attempts |
|
|
58
|
-
| `FYLO_WORKER_ID` | Optional stable identifier for a write worker process |
|
|
59
|
-
| `FYLO_WORKER_BATCH_SIZE` | Number of queued jobs a worker pulls per read loop |
|
|
60
|
-
| `FYLO_WORKER_BLOCK_MS` | Redis stream block time for waiting on new jobs |
|
|
61
|
-
| `FYLO_WORKER_RECOVER_ON_START` | Whether the worker reclaims stale pending jobs on startup |
|
|
62
|
-
| `FYLO_WORKER_RECOVER_IDLE_MS` | Minimum idle time before a pending job is reclaimed |
|
|
63
|
-
| `FYLO_WORKER_STOP_WHEN_IDLE` | Exit the worker loop when no jobs are available |
|
|
64
|
-
| `LOGGING` | Enable debug logging |
|
|
65
|
-
| `STRICT` | Enable schema validation via CHEX |
|
|
66
|
-
|
|
67
|
-
### S3 Files requirements
|
|
68
|
-
|
|
69
|
-
When `FYLO_STORAGE_ENGINE=s3-files`, FYLO expects:
|
|
70
|
-
|
|
71
|
-
- an already provisioned AWS S3 Files file system
|
|
72
|
-
- the mounted root directory to be available to the Bun process
|
|
73
|
-
- bucket versioning enabled on the underlying S3 bucket
|
|
74
|
-
- Linux/AWS compute assumptions that match AWS S3 Files mounting requirements
|
|
75
|
-
|
|
76
|
-
FYLO no longer talks to the S3 API directly in this mode, but S3 remains the underlying source of truth because that is how S3 Files works.
|
|
77
|
-
|
|
78
|
-
## Usage
|
|
79
|
-
|
|
80
|
-
### CRUD — NoSQL API
|
|
81
|
-
|
|
82
|
-
```typescript
|
|
83
|
-
import Fylo from '@delma/fylo'
|
|
84
|
-
|
|
85
|
-
const fylo = new Fylo()
|
|
86
|
-
|
|
87
|
-
// Collections
|
|
88
|
-
await Fylo.createCollection('users')
|
|
89
|
-
|
|
90
|
-
// Create
|
|
91
|
-
const _id = await fylo.putData<_user>('users', { name: 'John Doe', age: 30 })
|
|
92
|
-
|
|
93
|
-
// Read one
|
|
94
|
-
const user = await Fylo.getDoc<_user>('users', _id).once()
|
|
95
|
-
|
|
96
|
-
// Read many
|
|
97
|
-
for await (const doc of Fylo.findDocs<_user>('users', { $limit: 10 }).collect()) {
|
|
98
|
-
console.log(doc)
|
|
99
|
-
}
|
|
43
|
+
## Basic usage
|
|
100
44
|
|
|
101
|
-
|
|
102
|
-
|
|
45
|
+
```ts
|
|
46
|
+
import Fylo from '@delma/fylo'
|
|
103
47
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
$where: { $ops: [{ age: { $gte: 30 } }] },
|
|
107
|
-
$set: { age: 31 }
|
|
48
|
+
const fylo = new Fylo({
|
|
49
|
+
root: '/mnt/fylo'
|
|
108
50
|
})
|
|
109
51
|
|
|
110
|
-
|
|
111
|
-
await fylo.delDoc('users', _id)
|
|
52
|
+
await fylo.createCollection('users')
|
|
112
53
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
54
|
+
const id = await fylo.putData('users', {
|
|
55
|
+
name: 'Ada',
|
|
56
|
+
role: 'admin',
|
|
57
|
+
tags: ['engineering', 'platform']
|
|
116
58
|
})
|
|
117
59
|
|
|
118
|
-
|
|
119
|
-
|
|
60
|
+
const doc = await fylo.getDoc('users', id).once()
|
|
61
|
+
console.log(doc[id])
|
|
120
62
|
```
|
|
121
63
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
`legacy-s3` only.
|
|
64
|
+
## Configuration
|
|
125
65
|
|
|
126
|
-
|
|
127
|
-
const fylo = new Fylo()
|
|
66
|
+
FYLO is filesystem-first now.
|
|
128
67
|
|
|
129
|
-
|
|
130
|
-
const _id = await fylo.putData('users', { name: 'John Doe' })
|
|
68
|
+
You can configure the root in one of two ways:
|
|
131
69
|
|
|
132
|
-
|
|
133
|
-
|
|
70
|
+
```bash
|
|
71
|
+
export FYLO_ROOT=/mnt/fylo
|
|
72
|
+
```
|
|
134
73
|
|
|
135
|
-
|
|
136
|
-
const status = await fylo.getJobStatus(queued.jobId)
|
|
74
|
+
Or:
|
|
137
75
|
|
|
138
|
-
|
|
139
|
-
|
|
76
|
+
```ts
|
|
77
|
+
const fylo = new Fylo({ root: '/mnt/fylo' })
|
|
140
78
|
```
|
|
141
79
|
|
|
142
|
-
|
|
80
|
+
If you do not configure a root, FYLO uses a project-local default:
|
|
143
81
|
|
|
144
|
-
|
|
82
|
+
```text
|
|
83
|
+
<current working directory>/.fylo-data
|
|
84
|
+
```
|
|
145
85
|
|
|
146
|
-
|
|
86
|
+
For compatibility with older `s3-files` experiments, FYLO still accepts `s3FilesRoot` and still reads `FYLO_S3FILES_ROOT` as a fallback.
|
|
147
87
|
|
|
148
|
-
|
|
149
|
-
- `getDeadLetters()` lists exhausted jobs
|
|
150
|
-
- `replayDeadLetter(streamId)` moves a dead-lettered job back into the main queue
|
|
88
|
+
### Environment variables
|
|
151
89
|
|
|
152
|
-
|
|
90
|
+
| Variable | Purpose |
|
|
91
|
+
| ------------------- | ---------------------------------------------------------------- |
|
|
92
|
+
| `FYLO_ROOT` | Preferred filesystem root for collections |
|
|
93
|
+
| `FYLO_S3FILES_ROOT` | Backward-compatible alias for `FYLO_ROOT` |
|
|
94
|
+
| `SCHEMA_DIR` | Directory containing JSON validation schemas |
|
|
95
|
+
| `STRICT` | When truthy, validate documents with `@delma/chex` before writes |
|
|
96
|
+
| `ENCRYPTION_KEY` | Required when schemas declare `$encrypted` fields |
|
|
153
97
|
|
|
154
|
-
|
|
98
|
+
## Syncing to S3-compatible storage
|
|
155
99
|
|
|
156
|
-
|
|
100
|
+
FYLO does **not** ship its own cloud sync engine.
|
|
157
101
|
|
|
158
|
-
|
|
159
|
-
bun run worker
|
|
160
|
-
```
|
|
102
|
+
That is intentional.
|
|
161
103
|
|
|
162
|
-
The
|
|
104
|
+
The package owns:
|
|
163
105
|
|
|
164
|
-
|
|
106
|
+
- document storage behavior
|
|
107
|
+
- query behavior
|
|
108
|
+
- index maintenance
|
|
165
109
|
|
|
166
|
-
|
|
110
|
+
You own:
|
|
167
111
|
|
|
168
|
-
|
|
112
|
+
- how that root directory gets synced to AWS S3 Files, S3-compatible storage, or any other file-backed replication layer you trust
|
|
169
113
|
|
|
170
|
-
|
|
171
|
-
fylo.migrate users posts
|
|
172
|
-
```
|
|
114
|
+
That means you can choose the sync tool that matches your infrastructure:
|
|
173
115
|
|
|
174
|
-
|
|
116
|
+
- AWS S3 Files
|
|
117
|
+
- `aws s3 sync`
|
|
118
|
+
- `rclone`
|
|
119
|
+
- storage vendor tooling
|
|
120
|
+
- platform-specific replication
|
|
175
121
|
|
|
176
|
-
|
|
177
|
-
import { migrateLegacyS3ToS3Files } from '@delma/fylo'
|
|
122
|
+
If you want FYLO to notify your own S3 client on document writes, you can plug in sync hooks:
|
|
178
123
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
124
|
+
```ts
|
|
125
|
+
import Fylo from '@delma/fylo'
|
|
126
|
+
|
|
127
|
+
const fylo = new Fylo({
|
|
128
|
+
root: '/mnt/fylo',
|
|
129
|
+
syncMode: 'await-sync',
|
|
130
|
+
sync: {
|
|
131
|
+
async onWrite(event) {
|
|
132
|
+
const file = Bun.file(event.path)
|
|
133
|
+
await myS3Client.putObject({
|
|
134
|
+
key: `${event.collection}/${event.docId}.json`,
|
|
135
|
+
body: await file.arrayBuffer()
|
|
136
|
+
})
|
|
137
|
+
},
|
|
138
|
+
async onDelete(event) {
|
|
139
|
+
await myS3Client.deleteObject({
|
|
140
|
+
key: `${event.collection}/${event.docId}.json`
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
}
|
|
183
144
|
})
|
|
184
145
|
```
|
|
185
146
|
|
|
186
|
-
|
|
147
|
+
There are two sync modes:
|
|
187
148
|
|
|
188
|
-
|
|
189
|
-
|
|
149
|
+
- `await-sync`: FYLO waits for your hook and throws if the remote sync fails
|
|
150
|
+
- `fire-and-forget`: FYLO commits locally first and runs your hook in the background
|
|
190
151
|
|
|
191
|
-
|
|
152
|
+
Important detail for junior engineers:
|
|
192
153
|
|
|
193
|
-
|
|
154
|
+
- the filesystem write is still the source of truth
|
|
155
|
+
- a sync hook is a replication helper, not the database itself
|
|
194
156
|
|
|
195
|
-
|
|
157
|
+
## CRUD examples
|
|
196
158
|
|
|
197
|
-
|
|
159
|
+
### Create
|
|
198
160
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
161
|
+
```ts
|
|
162
|
+
const userId = await fylo.putData('users', {
|
|
163
|
+
name: 'Jane Doe',
|
|
164
|
+
age: 29,
|
|
165
|
+
team: 'platform'
|
|
166
|
+
})
|
|
202
167
|
```
|
|
203
168
|
|
|
204
|
-
###
|
|
169
|
+
### Read one
|
|
205
170
|
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
$ops: [{ status: { $eq: 'active' } }]
|
|
210
|
-
}
|
|
171
|
+
```ts
|
|
172
|
+
const user = await fylo.getDoc('users', userId).once()
|
|
173
|
+
```
|
|
211
174
|
|
|
212
|
-
|
|
213
|
-
{
|
|
214
|
-
$ops: [{ status: { $ne: 'archived' } }]
|
|
215
|
-
}
|
|
175
|
+
### Find many
|
|
216
176
|
|
|
217
|
-
|
|
218
|
-
{
|
|
219
|
-
$ops: [{ age: { $gte: 18, $lt: 65 } }]
|
|
220
|
-
}
|
|
177
|
+
```ts
|
|
178
|
+
const results = {}
|
|
221
179
|
|
|
222
|
-
|
|
223
|
-
{
|
|
224
|
-
|
|
180
|
+
for await (const doc of fylo
|
|
181
|
+
.findDocs('users', {
|
|
182
|
+
$ops: [{ age: { $gte: 18 } }]
|
|
183
|
+
})
|
|
184
|
+
.collect()) {
|
|
185
|
+
Object.assign(results, doc)
|
|
225
186
|
}
|
|
187
|
+
```
|
|
226
188
|
|
|
227
|
-
|
|
228
|
-
{
|
|
229
|
-
$ops: [{ tags: { $contains: 'urgent' } }]
|
|
230
|
-
}
|
|
189
|
+
### Update one
|
|
231
190
|
|
|
232
|
-
|
|
233
|
-
{
|
|
234
|
-
|
|
235
|
-
|
|
191
|
+
```ts
|
|
192
|
+
const nextId = await fylo.patchDoc('users', {
|
|
193
|
+
[userId]: {
|
|
194
|
+
team: 'core-platform'
|
|
195
|
+
}
|
|
196
|
+
})
|
|
236
197
|
```
|
|
237
198
|
|
|
238
|
-
###
|
|
199
|
+
### Delete one
|
|
239
200
|
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
$leftCollection: 'posts',
|
|
243
|
-
$rightCollection: 'users',
|
|
244
|
-
$mode: 'inner', // "inner" | "left" | "right" | "outer"
|
|
245
|
-
$on: { userId: { $eq: 'id' } },
|
|
246
|
-
$select: ['title', 'name'],
|
|
247
|
-
$limit: 50
|
|
248
|
-
})
|
|
201
|
+
```ts
|
|
202
|
+
await fylo.delDoc('users', nextId)
|
|
249
203
|
```
|
|
250
204
|
|
|
251
|
-
|
|
205
|
+
## SQL support
|
|
252
206
|
|
|
253
|
-
|
|
254
|
-
// Stream new/updated documents
|
|
255
|
-
for await (const doc of Fylo.findDocs<_user>('users')) {
|
|
256
|
-
console.log(doc)
|
|
257
|
-
}
|
|
207
|
+
FYLO also supports SQL-like commands for app-facing document work:
|
|
258
208
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
209
|
+
```ts
|
|
210
|
+
await fylo.executeSQL(`
|
|
211
|
+
CREATE TABLE posts
|
|
212
|
+
`)
|
|
263
213
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
214
|
+
await fylo.executeSQL(`
|
|
215
|
+
INSERT INTO posts VALUES { "title": "Hello", "published": true }
|
|
216
|
+
`)
|
|
217
|
+
|
|
218
|
+
const posts = await fylo.executeSQL(`
|
|
219
|
+
SELECT * FROM posts WHERE published = true
|
|
220
|
+
`)
|
|
268
221
|
```
|
|
269
222
|
|
|
270
|
-
|
|
223
|
+
## Query behavior
|
|
271
224
|
|
|
272
|
-
|
|
273
|
-
const fylo = new Fylo()
|
|
225
|
+
FYLO queries use the collection index file first when they can, then hydrate only the matching documents.
|
|
274
226
|
|
|
275
|
-
|
|
276
|
-
const count = await fylo.importBulkData<_user>(
|
|
277
|
-
'users',
|
|
278
|
-
new URL('https://example.com/users.json'),
|
|
279
|
-
1000
|
|
280
|
-
)
|
|
227
|
+
That means:
|
|
281
228
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
```
|
|
229
|
+
- exact matches are fast
|
|
230
|
+
- range queries are narrowed before document reads
|
|
231
|
+
- contains-style queries can use indexed candidates
|
|
232
|
+
- final document validation still happens before returning results
|
|
287
233
|
|
|
288
|
-
|
|
234
|
+
This is why FYLO behaves more like an application document store than a data warehouse.
|
|
289
235
|
|
|
290
|
-
|
|
236
|
+
## Realtime behavior
|
|
291
237
|
|
|
292
|
-
|
|
238
|
+
FYLO keeps a filesystem event journal per collection.
|
|
293
239
|
|
|
294
|
-
|
|
295
|
-
const fylo = new Fylo()
|
|
296
|
-
await fylo.putData('users', { name: 'test' })
|
|
297
|
-
await fylo.rollback() // undoes all writes in this instance
|
|
298
|
-
```
|
|
240
|
+
That is what powers listeners such as:
|
|
299
241
|
|
|
300
|
-
|
|
242
|
+
```ts
|
|
243
|
+
for await (const doc of fylo.findDocs('users', {
|
|
244
|
+
$ops: [{ role: { $eq: 'admin' } }]
|
|
245
|
+
})) {
|
|
246
|
+
console.log(doc)
|
|
247
|
+
}
|
|
248
|
+
```
|
|
301
249
|
|
|
302
|
-
|
|
303
|
-
- `processQueuedWrites(count, true)` to recover stale pending jobs
|
|
304
|
-
- `getDeadLetters()` to inspect exhausted jobs
|
|
305
|
-
- compensating writes instead of `rollback()` after a commit
|
|
250
|
+
## What FYLO no longer does
|
|
306
251
|
|
|
307
|
-
|
|
252
|
+
FYLO no longer centers:
|
|
308
253
|
|
|
309
|
-
|
|
254
|
+
- Redis-backed queued writes
|
|
255
|
+
- worker-based write draining
|
|
256
|
+
- legacy bucket-per-collection S3 storage
|
|
257
|
+
- built-in migration commands between old and new engines
|
|
310
258
|
|
|
311
|
-
|
|
312
|
-
fylo.query "SELECT * FROM users WHERE age > 25 LIMIT 10"
|
|
313
|
-
```
|
|
259
|
+
If you see older references to those ideas in historic discussions, treat them as previous design stages, not the current product direction.
|
|
314
260
|
|
|
315
|
-
|
|
261
|
+
## Recovery story
|
|
316
262
|
|
|
317
|
-
|
|
263
|
+
This part is important:
|
|
318
264
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
```
|
|
265
|
+
- document files are the truth
|
|
266
|
+
- index files can be rebuilt
|
|
322
267
|
|
|
323
|
-
|
|
268
|
+
That means FYLO is designed so that the system can recover from index drift without treating the index as a sacred durable database.
|
|
324
269
|
|
|
325
270
|
## Development
|
|
326
271
|
|
|
327
272
|
```bash
|
|
328
|
-
bun
|
|
329
|
-
bun run build
|
|
330
|
-
bun
|
|
331
|
-
bun run lint # ESLint
|
|
273
|
+
bun run typecheck
|
|
274
|
+
bun run build
|
|
275
|
+
bun test
|
|
332
276
|
```
|
|
333
277
|
|
|
334
|
-
|
|
278
|
+
## Performance testing
|
|
279
|
+
|
|
280
|
+
FYLO includes an opt-in scale test for the filesystem engine:
|
|
335
281
|
|
|
336
282
|
```bash
|
|
337
|
-
|
|
283
|
+
FYLO_RUN_PERF_TESTS=true bun test tests/integration/s3-files.performance.test.js
|
|
338
284
|
```
|
|
339
285
|
|
|
340
|
-
This
|
|
341
|
-
|
|
342
|
-
## Security
|
|
343
|
-
|
|
344
|
-
### What Fylo does NOT provide
|
|
345
|
-
|
|
346
|
-
Fylo is a low-level storage abstraction. The following must be implemented by the integrating application:
|
|
347
|
-
|
|
348
|
-
- **Authentication** — Fylo has no concept of users or sessions. Any caller with access to the Fylo instance can read and write any collection.
|
|
349
|
-
- **Authorization** — `executeSQL` and all document operations accept any collection name with no permission check. In multi-tenant applications, a caller can access any collection unless the integrator enforces a boundary above Fylo.
|
|
350
|
-
- **Rate limiting** — There is no built-in request throttling. An attacker with access to the instance can flood S3 with requests or trigger expensive operations without restriction. Add rate limiting and document-size limits in your service layer.
|
|
351
|
-
|
|
352
|
-
### Secure configuration
|
|
353
|
-
|
|
354
|
-
| Concern | Guidance |
|
|
355
|
-
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
356
|
-
| AWS credentials | Never commit credentials to version control. Use IAM instance roles or inject via CI secrets. Rotate any credentials that have been exposed. |
|
|
357
|
-
| `ENCRYPTION_KEY` | Must be at least 32 characters. Use a high-entropy random value. |
|
|
358
|
-
| `CIPHER_SALT` | Set a unique random value per deployment to prevent cross-instance precomputation attacks. |
|
|
359
|
-
| `REDIS_URL` | Always set explicitly. Use `rediss://` (TLS) in production with authentication credentials in the URL. |
|
|
360
|
-
| Collection names | Must match `^[a-z0-9][a-z0-9\-]*[a-z0-9]$`. Names are validated before any shell or S3 operation. |
|
|
361
|
-
|
|
362
|
-
### Encrypted fields
|
|
363
|
-
|
|
364
|
-
Fields listed in `$encrypted` in a collection schema are encrypted with AES-256-CBC. By default a random IV is used per write (non-deterministic). Pass `deterministic: true` to `Cipher.encrypt()` only for fields that require `$eq`/`$ne` queries — deterministic encryption leaks value equality to observers of stored ciphertext.
|
|
365
|
-
|
|
366
|
-
## License
|
|
367
|
-
|
|
368
|
-
MIT
|
|
286
|
+
This is useful when you want to see how index size and query latency behave as collections grow.
|
package/package.json
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@delma/fylo",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"types": "./dist/types/index.d.ts",
|
|
6
6
|
"bin": {
|
|
7
|
-
"fylo.query": "./dist/cli/index.js"
|
|
8
|
-
"fylo.worker": "./dist/worker.js",
|
|
9
|
-
"fylo.migrate": "./dist/migrate-cli.js"
|
|
7
|
+
"fylo.query": "./dist/cli/index.js"
|
|
10
8
|
},
|
|
11
9
|
"scripts": {
|
|
12
10
|
"build": "tsc",
|
|
13
11
|
"test": "bun test",
|
|
14
12
|
"typecheck": "tsc -p tsconfig.typecheck.json",
|
|
15
|
-
"worker": "bun run ./src/worker.ts",
|
|
16
13
|
"lint": "prettier --check \"src/**/*.{ts,d.ts}\" \"tests/**/*.js\" README.md",
|
|
17
14
|
"format": "prettier --write src tests"
|
|
18
15
|
},
|