@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
package/src/core/query.ts DELETED
@@ -1,53 +0,0 @@
1
- import { Cipher } from '../adapters/cipher'
2
-
3
- const ENCRYPTED_FIELD_OPS = ['$ne', '$gt', '$gte', '$lt', '$lte', '$like', '$contains'] as const
4
-
5
- export class Query {
6
- static async getExprs<T extends Record<string, any>>(
7
- collection: string,
8
- query: _storeQuery<T>
9
- ) {
10
- let exprs = new Set<string>()
11
-
12
- if (query.$ops) {
13
- for (const op of query.$ops) {
14
- for (const column in op) {
15
- const col = op[column as keyof T]!
16
-
17
- const fieldPath = String(column).replaceAll('.', '/')
18
- const encrypted =
19
- Cipher.isConfigured() && Cipher.isEncryptedField(collection, fieldPath)
20
-
21
- if (encrypted) {
22
- for (const opKey of ENCRYPTED_FIELD_OPS) {
23
- if (col[opKey] !== undefined) {
24
- throw new Error(
25
- `Operator ${opKey} is not supported on encrypted field "${String(column)}"`
26
- )
27
- }
28
- }
29
- }
30
-
31
- if (col.$eq) {
32
- const val = encrypted
33
- ? await Cipher.encrypt(String(col.$eq).replaceAll('/', '%2F'))
34
- : col.$eq
35
- exprs.add(`${column}/${val}/**/*`)
36
- }
37
- if (col.$ne) exprs.add(`${column}/**/*`)
38
- if (col.$gt) exprs.add(`${column}/**/*`)
39
- if (col.$gte) exprs.add(`${column}/**/*`)
40
- if (col.$lt) exprs.add(`${column}/**/*`)
41
- if (col.$lte) exprs.add(`${column}/**/*`)
42
- if (col.$like) exprs.add(`${column}/${col.$like.replaceAll('%', '*')}/**/*`)
43
- if (col.$contains !== undefined)
44
- exprs.add(
45
- `${column}/*/${String(col.$contains).split('/').join('%2F')}/**/*`
46
- )
47
- }
48
- }
49
- } else exprs = new Set([`**/*`])
50
-
51
- return Array.from(exprs)
52
- }
53
- }
@@ -1,174 +0,0 @@
1
- import { S3 } from '../adapters/s3'
2
- import TTID from '@delma/ttid'
3
- import { Redis } from '../adapters/redis'
4
-
5
- export class Walker {
6
- private static readonly MAX_KEYS = 1000
7
-
8
- private static _redis: Redis | null = null
9
-
10
- private static get redis(): Redis {
11
- if (!Walker._redis) Walker._redis = new Redis()
12
- return Walker._redis
13
- }
14
-
15
- private static async *searchS3(
16
- collection: string,
17
- prefix: string,
18
- pattern?: string
19
- ): AsyncGenerator<
20
- { _id: _ttid; data: string[] } | void,
21
- void,
22
- { count: number; limit?: number }
23
- > {
24
- const uniqueIds = new Set<string>()
25
-
26
- let token: string | undefined
27
-
28
- let filter = yield
29
-
30
- let limit = filter ? filter.limit : this.MAX_KEYS
31
-
32
- do {
33
- const res = await S3.list(collection, {
34
- prefix,
35
- maxKeys: pattern ? limit : undefined,
36
- continuationToken: token
37
- })
38
-
39
- if (res.contents === undefined) break
40
-
41
- const keys = res.contents.map((item) => item.key!)
42
-
43
- if (pattern) {
44
- for (const key of keys) {
45
- const segements = key.split('/')
46
-
47
- const _id = segements.pop()! as _ttid
48
-
49
- if (
50
- TTID.isTTID(_id) &&
51
- !uniqueIds.has(_id) &&
52
- pattern.length <= 1024 &&
53
- new Bun.Glob(pattern).match(key)
54
- ) {
55
- filter = yield { _id, data: await this.getDocData(collection, _id) }
56
-
57
- limit = filter.limit ? filter.limit : this.MAX_KEYS
58
-
59
- uniqueIds.add(_id)
60
-
61
- if (filter.count === limit) break
62
- }
63
- }
64
- } else {
65
- const _id = prefix.split('/').pop()! as _ttid
66
-
67
- yield { _id, data: keys }
68
-
69
- break
70
- }
71
-
72
- token = res.nextContinuationToken
73
- } while (token !== undefined)
74
- }
75
-
76
- static async *search(
77
- collection: string,
78
- pattern: string,
79
- { listen = false, skip = false }: { listen: boolean; skip: boolean },
80
- action: 'insert' | 'delete' = 'insert'
81
- ): AsyncGenerator<
82
- { _id: _ttid; data: string[] } | void,
83
- void,
84
- { count: number; limit?: number }
85
- > {
86
- if (!skip) {
87
- const segments = pattern.split('/')
88
- const idx = segments.findIndex((seg) => seg.includes('*'))
89
- const prefix = segments.slice(0, idx).join('/')
90
-
91
- yield* this.searchS3(collection, prefix, pattern)
92
- }
93
-
94
- const eventIds = new Set<string>()
95
-
96
- if (listen)
97
- for await (const event of this.listen(collection, pattern)) {
98
- if (event.action !== action && eventIds.has(event.id)) {
99
- eventIds.delete(event.id)
100
- } else if (event.action === action && !eventIds.has(event.id)) {
101
- eventIds.add(event.id)
102
- yield { _id: event.id, data: event.data }
103
- }
104
- }
105
- }
106
-
107
- static async getDocData(collection: string, _id: _ttid) {
108
- const prefix = _id.split('-')[0]
109
-
110
- const data: string[] = []
111
-
112
- let finished = false
113
-
114
- const iter = this.searchS3(collection, prefix)
115
-
116
- do {
117
- const { value, done } = await iter.next()
118
-
119
- if (done) {
120
- finished = true
121
- break
122
- }
123
-
124
- if (value) {
125
- for (const key of value.data) {
126
- if (key.startsWith(_id + '/')) data.push(key)
127
- }
128
- finished = true
129
- break
130
- }
131
- } while (!finished)
132
-
133
- return data
134
- }
135
-
136
- private static async *processPattern(collection: string, pattern: string) {
137
- const stackIds = new Set<string>()
138
-
139
- for await (const { action, keyId } of Walker.redis.subscribe(collection)) {
140
- if (
141
- action === 'insert' &&
142
- !TTID.isTTID(keyId) &&
143
- pattern.length <= 1024 &&
144
- new Bun.Glob(pattern).match(keyId)
145
- ) {
146
- const _id = keyId.split('/').pop()! as _ttid
147
-
148
- if (!stackIds.has(_id)) {
149
- stackIds.add(_id)
150
-
151
- yield {
152
- id: _id,
153
- action: 'insert',
154
- data: await this.getDocData(collection, _id)
155
- }
156
- }
157
- } else if (action === 'delete' && TTID.isTTID(keyId)) {
158
- yield { id: keyId as _ttid, action: 'delete', data: [] }
159
- } else if (TTID.isTTID(keyId) && stackIds.has(keyId)) {
160
- stackIds.delete(keyId)
161
- }
162
- }
163
- }
164
-
165
- static async *listen(collection: string, pattern: string | string[]) {
166
- if (Array.isArray(pattern)) {
167
- for (const p of pattern) {
168
- for await (const event of this.processPattern(collection, p)) yield event
169
- }
170
- } else {
171
- for await (const event of this.processPattern(collection, pattern)) yield event
172
- }
173
- }
174
- }
@@ -1,59 +0,0 @@
1
- import type { WriteJob } from '../types/write-queue'
2
-
3
- export class WriteQueue {
4
- static createInsertJob<T extends Record<string, any>>(
5
- collection: string,
6
- docId: _ttid,
7
- payload: T
8
- ): WriteJob<T> {
9
- const now = Date.now()
10
-
11
- return {
12
- jobId: Bun.randomUUIDv7(),
13
- collection,
14
- docId,
15
- operation: 'insert',
16
- payload,
17
- status: 'queued',
18
- attempts: 0,
19
- createdAt: now,
20
- updatedAt: now
21
- }
22
- }
23
-
24
- static createUpdateJob<T extends Record<string, any>>(
25
- collection: string,
26
- docId: _ttid,
27
- payload: { newDoc: Record<_ttid, Partial<T>>; oldDoc?: Record<_ttid, T> }
28
- ): WriteJob<{ newDoc: Record<_ttid, Partial<T>>; oldDoc?: Record<_ttid, T> }> {
29
- const now = Date.now()
30
-
31
- return {
32
- jobId: Bun.randomUUIDv7(),
33
- collection,
34
- docId,
35
- operation: 'update',
36
- payload,
37
- status: 'queued',
38
- attempts: 0,
39
- createdAt: now,
40
- updatedAt: now
41
- }
42
- }
43
-
44
- static createDeleteJob(collection: string, docId: _ttid): WriteJob<{ _id: _ttid }> {
45
- const now = Date.now()
46
-
47
- return {
48
- jobId: Bun.randomUUIDv7(),
49
- collection,
50
- docId,
51
- operation: 'delete',
52
- payload: { _id: docId },
53
- status: 'queued',
54
- attempts: 0,
55
- createdAt: now,
56
- updatedAt: now
57
- }
58
- }
59
- }