@delma/fylo 2.0.1 → 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 (107) hide show
  1. package/README.md +206 -261
  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/dist/core/directory.js +48 -0
  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/dist/types/fylo.d.ts +179 -0
  35. package/{src → dist}/types/node-runtime.d.ts +1 -0
  36. package/package.json +3 -6
  37. package/.env.example +0 -16
  38. package/.github/copilot-instructions.md +0 -3
  39. package/.github/prompts/release.prompt.md +0 -10
  40. package/.github/workflows/ci.yml +0 -37
  41. package/.github/workflows/publish.yml +0 -91
  42. package/.prettierrc +0 -7
  43. package/AGENTS.md +0 -3
  44. package/CLAUDE.md +0 -3
  45. package/eslint.config.js +0 -32
  46. package/src/CLI +0 -39
  47. package/src/adapters/cipher.ts +0 -180
  48. package/src/adapters/redis.ts +0 -487
  49. package/src/adapters/s3.ts +0 -61
  50. package/src/core/collection.ts +0 -5
  51. package/src/core/directory.ts +0 -387
  52. package/src/core/extensions.ts +0 -21
  53. package/src/core/format.ts +0 -457
  54. package/src/core/parser.ts +0 -901
  55. package/src/core/query.ts +0 -53
  56. package/src/core/walker.ts +0 -174
  57. package/src/core/write-queue.ts +0 -59
  58. package/src/engines/s3-files.ts +0 -1068
  59. package/src/engines/types.ts +0 -21
  60. package/src/index.ts +0 -1727
  61. package/src/migrate-cli.ts +0 -22
  62. package/src/migrate.ts +0 -74
  63. package/src/types/fylo.d.ts +0 -261
  64. package/src/types/write-queue.ts +0 -42
  65. package/src/worker.ts +0 -18
  66. package/src/workers/write-worker.ts +0 -120
  67. package/tests/collection/truncate.test.js +0 -35
  68. package/tests/data.js +0 -97
  69. package/tests/index.js +0 -14
  70. package/tests/integration/aws-s3-files.canary.test.js +0 -22
  71. package/tests/integration/create.test.js +0 -39
  72. package/tests/integration/delete.test.js +0 -95
  73. package/tests/integration/edge-cases.test.js +0 -158
  74. package/tests/integration/encryption.test.js +0 -131
  75. package/tests/integration/export.test.js +0 -46
  76. package/tests/integration/join-modes.test.js +0 -154
  77. package/tests/integration/migration.test.js +0 -38
  78. package/tests/integration/nested.test.js +0 -142
  79. package/tests/integration/operators.test.js +0 -122
  80. package/tests/integration/queue.test.js +0 -83
  81. package/tests/integration/read.test.js +0 -119
  82. package/tests/integration/rollback.test.js +0 -60
  83. package/tests/integration/s3-files.test.js +0 -192
  84. package/tests/integration/update.test.js +0 -99
  85. package/tests/mocks/cipher.js +0 -40
  86. package/tests/mocks/redis.js +0 -123
  87. package/tests/mocks/s3.js +0 -80
  88. package/tests/schemas/album.d.ts +0 -5
  89. package/tests/schemas/album.json +0 -5
  90. package/tests/schemas/comment.d.ts +0 -7
  91. package/tests/schemas/comment.json +0 -7
  92. package/tests/schemas/photo.d.ts +0 -7
  93. package/tests/schemas/photo.json +0 -7
  94. package/tests/schemas/post.d.ts +0 -6
  95. package/tests/schemas/post.json +0 -6
  96. package/tests/schemas/tip.d.ts +0 -7
  97. package/tests/schemas/tip.json +0 -7
  98. package/tests/schemas/todo.d.ts +0 -6
  99. package/tests/schemas/todo.json +0 -6
  100. package/tests/schemas/user.d.ts +0 -23
  101. package/tests/schemas/user.json +0 -23
  102. package/tsconfig.json +0 -21
  103. package/tsconfig.typecheck.json +0 -31
  104. /package/{src → dist}/types/bun-runtime.d.ts +0 -0
  105. /package/{src → dist}/types/index.d.ts +0 -0
  106. /package/{src → dist}/types/query.d.ts +0 -0
  107. /package/{src → dist}/types/vendor-modules.d.ts +0 -0
@@ -1,22 +0,0 @@
1
- #!/usr/bin/env bun
2
- import { migrateLegacyS3ToS3Files } from './migrate'
3
-
4
- const [, , ...args] = process.argv
5
-
6
- const collections = args.filter((arg) => !arg.startsWith('--'))
7
- const recreateCollections = !args.includes('--keep-existing')
8
- const verify = !args.includes('--no-verify')
9
-
10
- if (collections.length === 0) {
11
- throw new Error(
12
- 'Usage: fylo.migrate <collection> [collection...] [--keep-existing] [--no-verify]'
13
- )
14
- }
15
-
16
- const summary = await migrateLegacyS3ToS3Files({
17
- collections,
18
- recreateCollections,
19
- verify
20
- })
21
-
22
- console.log(JSON.stringify(summary, null, 2))
package/src/migrate.ts DELETED
@@ -1,74 +0,0 @@
1
- import Fylo from './index'
2
- import { S3FilesEngine } from './engines/s3-files'
3
-
4
- type MigrateOptions = {
5
- collections: string[]
6
- s3FilesRoot?: string
7
- recreateCollections?: boolean
8
- verify?: boolean
9
- }
10
-
11
- function normalize<T>(value: T): T {
12
- if (Array.isArray(value)) return value.map((item) => normalize(item)).sort() as T
13
- if (value && typeof value === 'object') {
14
- return Object.keys(value as Record<string, unknown>)
15
- .sort()
16
- .reduce(
17
- (acc, key) => {
18
- acc[key] = normalize((value as Record<string, unknown>)[key])
19
- return acc
20
- },
21
- {} as Record<string, unknown>
22
- ) as T
23
- }
24
- return value
25
- }
26
-
27
- export async function migrateLegacyS3ToS3Files({
28
- collections,
29
- s3FilesRoot = process.env.FYLO_S3FILES_ROOT,
30
- recreateCollections = true,
31
- verify = true
32
- }: MigrateOptions) {
33
- if (!s3FilesRoot) throw new Error('s3FilesRoot is required')
34
-
35
- const source = new Fylo({ engine: 'legacy-s3' })
36
- const target = new S3FilesEngine(s3FilesRoot)
37
-
38
- const summary: Record<string, { migrated: number; verified: boolean }> = {}
39
-
40
- for (const collection of collections) {
41
- if (recreateCollections) {
42
- await target.dropCollection(collection)
43
- await target.createCollection(collection)
44
- } else if (!(await target.hasCollection(collection))) {
45
- await target.createCollection(collection)
46
- }
47
-
48
- const docs = (await source.executeSQL<Record<string, any>>(
49
- `SELECT * FROM ${collection}`
50
- )) as Record<_ttid, Record<string, any>>
51
-
52
- for (const [docId, doc] of Object.entries(docs) as Array<[_ttid, Record<string, any>]>) {
53
- await target.putDocument(collection, docId, doc)
54
- }
55
-
56
- let verified = false
57
-
58
- if (verify) {
59
- const targetFylo = new Fylo({ engine: 's3-files', s3FilesRoot })
60
- const migratedDocs = (await targetFylo.executeSQL<Record<string, any>>(
61
- `SELECT * FROM ${collection}`
62
- )) as Record<_ttid, Record<string, any>>
63
- verified = JSON.stringify(normalize(docs)) === JSON.stringify(normalize(migratedDocs))
64
- if (!verified) throw new Error(`Verification failed for ${collection}`)
65
- }
66
-
67
- summary[collection] = {
68
- migrated: Object.keys(docs).length,
69
- verified
70
- }
71
- }
72
-
73
- return summary
74
- }
@@ -1,261 +0,0 @@
1
- interface _getDoc {
2
- [Symbol.asyncIterator]<T>(): AsyncGenerator<_ttid | Record<_ttid, T>, void, unknown>
3
- once<T>(): Promise<Record<_ttid, T>>
4
- onDelete(): AsyncGenerator<_ttid, void, unknown>
5
- }
6
-
7
- interface _findDocs {
8
- [Symbol.asyncIterator]<T>(): AsyncGenerator<
9
- _ttid | Record<_ttid, T> | Record<string, _ttid[]> | Record<_ttid, Partial<T>> | undefined,
10
- void,
11
- unknown
12
- >
13
- once<T>(): Promise<Record<_ttid, T>>
14
- onDelete(): AsyncGenerator<_ttid, void, unknown>
15
- }
16
-
17
- interface _queuedWriteResult {
18
- jobId: string
19
- docId: _ttid
20
- status: 'queued' | 'processing' | 'committed' | 'failed' | 'dead-letter'
21
- }
22
-
23
- interface ObjectConstructor {
24
- appendGroup: (target: Record<string, any>, source: Record<string, any>) => Record<string, any>
25
- }
26
-
27
- interface Console {
28
- format: (docs: Record<string, any>) => void
29
- }
30
-
31
- type _joinDocs<T, U> =
32
- | _ttid[]
33
- | Record<string, _ttid[]>
34
- | Record<string, Record<_ttid, Partial<T | U>>>
35
- | Record<`${_ttid}, ${_ttid}`, T | U | (T & U) | (Partial<T> & Partial<U>)>
36
-
37
- type _fyloEngineKind = 'legacy-s3' | 's3-files'
38
-
39
- interface _fyloOptions {
40
- engine?: _fyloEngineKind
41
- s3FilesRoot?: string
42
- }
43
-
44
- declare module '@delma/fylo' {
45
- export default class {
46
- constructor(options?: _fyloOptions)
47
-
48
- /**
49
- * Rolls back all transcations in current instance
50
- * @deprecated Prefer queued write recovery, dead letters, or compensating writes.
51
- */
52
- rollback(): Promise<void>
53
-
54
- /**
55
- * Executes a SQL query and returns the results.
56
- * @param SQL The SQL query to execute.
57
- * @returns The results of the query.
58
- */
59
- executeSQL<T extends Record<string, any>, U extends Record<string, any> = {}>(
60
- SQL: string
61
- ): Promise<number | void | any[] | _ttid | Record<any, any>>
62
-
63
- /**
64
- * Creates a new schema for a collection.
65
- * @param collection The name of the collection.
66
- */
67
- static createCollection(collection: string): Promise<void>
68
-
69
- /**
70
- * Drops an existing schema for a collection.
71
- * @param collection The name of the collection.
72
- */
73
- static dropCollection(collection: string): Promise<void>
74
-
75
- /**
76
- * Imports data from a URL into a collection.
77
- * @param collection The name of the collection.
78
- * @param url The URL of the data to import.
79
- * @param limit The maximum number of documents to import.
80
- */
81
- importBulkData(collection: string, url: URL, limit?: number): Promise<number>
82
-
83
- /**
84
- * Exports data from a collection to a URL.
85
- * @param collection The name of the collection.
86
- * @returns The current data exported from the collection.
87
- */
88
- exportBulkData<T extends Record<string, any>>(
89
- collection: string
90
- ): AsyncGenerator<T, void, unknown>
91
-
92
- /**
93
- * Gets a document from a collection.
94
- * @param collection The name of the collection.
95
- * @param _id The ID of the document.
96
- * @param onlyId Whether to only return the ID of the document.
97
- * @returns The document or the ID of the document.
98
- */
99
- static getDoc(collection: string, _id: _ttid, onlyId: boolean): _getDoc
100
-
101
- /**
102
- * Puts multiple documents into a collection.
103
- * @param collection The name of the collection.
104
- * @param batch The documents to put.
105
- * @returns The IDs of the documents.
106
- */
107
- batchPutData<T extends Record<string, any>>(
108
- collection: string,
109
- batch: Array<T>
110
- ): Promise<_ttid[]>
111
-
112
- queuePutData<T extends Record<string, any>>(
113
- collection: string,
114
- data: Record<_ttid, T> | T
115
- ): Promise<_queuedWriteResult>
116
-
117
- queuePatchDoc<T extends Record<string, any>>(
118
- collection: string,
119
- newDoc: Record<_ttid, Partial<T>>,
120
- oldDoc?: Record<_ttid, T>
121
- ): Promise<_queuedWriteResult>
122
-
123
- queueDelDoc(collection: string, _id: _ttid): Promise<_queuedWriteResult>
124
-
125
- getJobStatus(jobId: string): Promise<Record<string, any> | null>
126
-
127
- getDocStatus(collection: string, docId: _ttid): Promise<Record<string, any> | null>
128
-
129
- getDeadLetters(count?: number): Promise<Array<Record<string, any>>>
130
-
131
- getQueueStats(): Promise<{ queued: number; pending: number; deadLetters: number }>
132
-
133
- replayDeadLetter(streamId: string): Promise<Record<string, any> | null>
134
-
135
- processQueuedWrites(count?: number, recover?: boolean): Promise<number>
136
-
137
- /**
138
- * Puts a document into a collection.
139
- * @param collection The name of the collection.
140
- * @param data The document to put.
141
- * @returns The ID of the document.
142
- */
143
- putData<T extends Record<string, any>>(collection: string, data: T): Promise<_ttid>
144
- putData<T extends Record<string, any>>(
145
- collection: string,
146
- data: Record<_ttid, T>
147
- ): Promise<_ttid>
148
- putData<T extends Record<string, any>>(
149
- collection: string,
150
- data: T,
151
- options: { wait?: true; timeoutMs?: number }
152
- ): Promise<_ttid>
153
- putData<T extends Record<string, any>>(
154
- collection: string,
155
- data: Record<_ttid, T>,
156
- options: { wait?: true; timeoutMs?: number }
157
- ): Promise<_ttid>
158
- putData<T extends Record<string, any>>(
159
- collection: string,
160
- data: T,
161
- options: { wait: false; timeoutMs?: number }
162
- ): Promise<_queuedWriteResult>
163
- putData<T extends Record<string, any>>(
164
- collection: string,
165
- data: Record<_ttid, T>,
166
- options: { wait: false; timeoutMs?: number }
167
- ): Promise<_queuedWriteResult>
168
-
169
- /**
170
- * Patches a document in a collection.
171
- * @param collection The name of the collection.
172
- * @param newDoc The new document data.
173
- * @param oldDoc The old document data.
174
- * @returns The number of documents patched.
175
- */
176
- patchDoc<T extends Record<string, any>>(
177
- collection: string,
178
- newDoc: Record<_ttid, Partial<T>>,
179
- oldDoc?: Record<_ttid, T>
180
- ): Promise<_ttid>
181
- patchDoc<T extends Record<string, any>>(
182
- collection: string,
183
- newDoc: Record<_ttid, Partial<T>>,
184
- oldDoc: Record<_ttid, T> | undefined,
185
- options: { wait?: true; timeoutMs?: number }
186
- ): Promise<_ttid>
187
- patchDoc<T extends Record<string, any>>(
188
- collection: string,
189
- newDoc: Record<_ttid, Partial<T>>,
190
- oldDoc: Record<_ttid, T> | undefined,
191
- options: { wait: false; timeoutMs?: number }
192
- ): Promise<_queuedWriteResult>
193
-
194
- /**
195
- * Patches documents in a collection.
196
- * @param collection The name of the collection.
197
- * @param updateSchema The update schema.
198
- * @returns The number of documents patched.
199
- */
200
- patchDocs<T extends Record<string, any>>(
201
- collection: string,
202
- updateSchema: _storeUpdate<T>
203
- ): Promise<number>
204
-
205
- /**
206
- * Deletes a document from a collection.
207
- * @param collection The name of the collection.
208
- * @param _id The ID of the document.
209
- * @returns The number of documents deleted.
210
- */
211
- delDoc(collection: string, _id: _ttid): Promise<void>
212
- delDoc(
213
- collection: string,
214
- _id: _ttid,
215
- options: { wait?: true; timeoutMs?: number }
216
- ): Promise<void>
217
- delDoc(
218
- collection: string,
219
- _id: _ttid,
220
- options: { wait: false; timeoutMs?: number }
221
- ): Promise<_queuedWriteResult>
222
-
223
- /**
224
- * Deletes documents from a collection.
225
- * @param collection The name of the collection.
226
- * @param deleteSchema The delete schema.
227
- * @returns The number of documents deleted.
228
- */
229
- delDocs<T extends Record<string, any>>(
230
- collection: string,
231
- deleteSchema?: _storeDelete<T>
232
- ): Promise<number>
233
-
234
- /**
235
- * Joins documents from two collections.
236
- * @param join The join schema.
237
- * @returns The joined documents.
238
- */
239
- static joinDocs<T extends Record<string, any>, U extends Record<string, any>>(
240
- join: _join<T, U>
241
- ): Promise<_joinDocs<T, U>>
242
-
243
- /**
244
- * Finds documents in a collection.
245
- * @param collection The name of the collection.
246
- * @param query The query schema.
247
- * @returns The found documents.
248
- */
249
- static findDocs<T extends Record<string, any>>(
250
- collection: string,
251
- query?: _storeQuery<T>
252
- ): _findDocs
253
- }
254
-
255
- export function migrateLegacyS3ToS3Files(options: {
256
- collections: string[]
257
- s3FilesRoot?: string
258
- recreateCollections?: boolean
259
- verify?: boolean
260
- }): Promise<Record<string, { migrated: number; verified: boolean }>>
261
- }
@@ -1,42 +0,0 @@
1
- export type WriteJobOperation = 'insert' | 'update' | 'delete'
2
-
3
- export type WriteJobStatus = 'queued' | 'processing' | 'committed' | 'failed' | 'dead-letter'
4
-
5
- export interface WriteJob<T extends Record<string, any> = Record<string, any>> {
6
- jobId: string
7
- collection: string
8
- docId: _ttid
9
- operation: WriteJobOperation
10
- payload: T
11
- status: WriteJobStatus
12
- attempts: number
13
- createdAt: number
14
- updatedAt: number
15
- nextAttemptAt?: number
16
- workerId?: string
17
- error?: string
18
- }
19
-
20
- export interface QueuedWriteResult {
21
- jobId: string
22
- docId: _ttid
23
- status: WriteJobStatus
24
- }
25
-
26
- export interface StreamJobEntry<T extends Record<string, any> = Record<string, any>> {
27
- streamId: string
28
- job: WriteJob<T>
29
- }
30
-
31
- export interface DeadLetterJob<T extends Record<string, any> = Record<string, any>> {
32
- streamId: string
33
- job: WriteJob<T>
34
- reason?: string
35
- failedAt: number
36
- }
37
-
38
- export interface QueueStats {
39
- queued: number
40
- pending: number
41
- deadLetters: number
42
- }
package/src/worker.ts DELETED
@@ -1,18 +0,0 @@
1
- #!/usr/bin/env bun
2
- import { WriteWorker } from './workers/write-worker'
3
-
4
- if (process.env.FYLO_STORAGE_ENGINE === 's3-files') {
5
- throw new Error('fylo.worker is not supported in s3-files engine')
6
- }
7
-
8
- const worker = new WriteWorker(process.env.FYLO_WORKER_ID)
9
-
10
- await worker.run({
11
- batchSize: process.env.FYLO_WORKER_BATCH_SIZE ? Number(process.env.FYLO_WORKER_BATCH_SIZE) : 1,
12
- blockMs: process.env.FYLO_WORKER_BLOCK_MS ? Number(process.env.FYLO_WORKER_BLOCK_MS) : 1000,
13
- recoverOnStart: process.env.FYLO_WORKER_RECOVER_ON_START !== 'false',
14
- recoverIdleMs: process.env.FYLO_WORKER_RECOVER_IDLE_MS
15
- ? Number(process.env.FYLO_WORKER_RECOVER_IDLE_MS)
16
- : 30_000,
17
- stopWhenIdle: process.env.FYLO_WORKER_STOP_WHEN_IDLE === 'true'
18
- })
@@ -1,120 +0,0 @@
1
- import Fylo from '../index'
2
- import { Redis } from '../adapters/redis'
3
- import type { StreamJobEntry, WriteJob } from '../types/write-queue'
4
-
5
- export class WriteWorker {
6
- private static readonly MAX_WRITE_ATTEMPTS = Number(process.env.FYLO_WRITE_MAX_ATTEMPTS ?? 3)
7
-
8
- private static readonly WRITE_RETRY_BASE_MS = Number(process.env.FYLO_WRITE_RETRY_BASE_MS ?? 10)
9
-
10
- private readonly fylo: Fylo
11
-
12
- private readonly redis: Redis
13
-
14
- readonly workerId: string
15
-
16
- constructor(workerId: string = Bun.randomUUIDv7()) {
17
- this.workerId = workerId
18
- this.fylo = new Fylo()
19
- this.redis = new Redis()
20
- }
21
-
22
- async recoverPending(minIdleMs: number = 30_000, count: number = 10) {
23
- const jobs = await this.redis.claimPendingJobs(this.workerId, minIdleMs, count)
24
- for (const job of jobs) await this.processJob(job)
25
- return jobs.length
26
- }
27
-
28
- async processNext(count: number = 1, blockMs: number = 1000) {
29
- const jobs = await this.redis.readWriteJobs(this.workerId, count, blockMs)
30
- for (const job of jobs) await this.processJob(job)
31
- return jobs.length
32
- }
33
-
34
- async processJob({ streamId, job }: StreamJobEntry) {
35
- if (job.nextAttemptAt && job.nextAttemptAt > Date.now()) return false
36
-
37
- const locked = await this.redis.acquireDocLock(job.collection, job.docId, job.jobId)
38
- if (!locked) return false
39
-
40
- try {
41
- await this.redis.setJobStatus(job.jobId, 'processing', {
42
- workerId: this.workerId,
43
- attempts: job.attempts + 1
44
- })
45
- await this.redis.setDocStatus(job.collection, job.docId, 'processing', job.jobId)
46
-
47
- await this.fylo.executeQueuedWrite(job)
48
-
49
- await this.redis.setJobStatus(job.jobId, 'committed', { workerId: this.workerId })
50
- await this.redis.setDocStatus(job.collection, job.docId, 'committed', job.jobId)
51
- await this.redis.ackWriteJob(streamId)
52
-
53
- return true
54
- } catch (err) {
55
- const attempts = job.attempts + 1
56
- const message = err instanceof Error ? err.message : String(err)
57
-
58
- if (attempts >= WriteWorker.MAX_WRITE_ATTEMPTS) {
59
- await this.redis.setJobStatus(job.jobId, 'dead-letter', {
60
- workerId: this.workerId,
61
- attempts,
62
- error: message
63
- })
64
- await this.redis.setDocStatus(job.collection, job.docId, 'dead-letter', job.jobId)
65
- await this.redis.deadLetterWriteJob(
66
- streamId,
67
- {
68
- ...job,
69
- attempts,
70
- status: 'dead-letter',
71
- workerId: this.workerId,
72
- error: message
73
- },
74
- message
75
- )
76
- return false
77
- }
78
-
79
- const nextAttemptAt =
80
- Date.now() + WriteWorker.WRITE_RETRY_BASE_MS * Math.max(1, 2 ** (attempts - 1))
81
-
82
- await this.redis.setJobStatus(job.jobId, 'failed', {
83
- workerId: this.workerId,
84
- attempts,
85
- error: message,
86
- nextAttemptAt
87
- })
88
- await this.redis.setDocStatus(job.collection, job.docId, 'failed', job.jobId)
89
-
90
- return false
91
- } finally {
92
- await this.redis.releaseDocLock(job.collection, job.docId, job.jobId)
93
- }
94
- }
95
-
96
- async processQueuedInsert(job: WriteJob) {
97
- return await this.fylo.executeQueuedWrite(job)
98
- }
99
-
100
- async run({
101
- batchSize = 1,
102
- blockMs = 1000,
103
- recoverOnStart = true,
104
- recoverIdleMs = 30_000,
105
- stopWhenIdle = false
106
- }: {
107
- batchSize?: number
108
- blockMs?: number
109
- recoverOnStart?: boolean
110
- recoverIdleMs?: number
111
- stopWhenIdle?: boolean
112
- } = {}) {
113
- if (recoverOnStart) await this.recoverPending(recoverIdleMs, batchSize)
114
-
115
- while (true) {
116
- const processed = await this.processNext(batchSize, blockMs)
117
- if (stopWhenIdle && processed === 0) break
118
- }
119
- }
120
- }
@@ -1,35 +0,0 @@
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
- })
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())
package/tests/index.js DELETED
@@ -1,14 +0,0 @@
1
- import { Redis } from '../src/adapters/redis'
2
- import ttid from '@delma/ttid'
3
- const redisPub = new Redis()
4
- const redisSub = new Redis()
5
- setTimeout(async () => {
6
- await redisPub.publish('bun', 'insert', ttid.generate())
7
- }, 2000)
8
- setTimeout(async () => {
9
- await redisPub.publish('bun', 'insert', ttid.generate())
10
- }, 3000)
11
- await Bun.sleep(1000)
12
- for await (const data of redisSub.subscribe('bun')) {
13
- console.log('Received:', data)
14
- }