@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.
- package/README.md +206 -261
- 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/dist/core/directory.js +48 -0
- 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/dist/types/fylo.d.ts +179 -0
- package/{src → dist}/types/node-runtime.d.ts +1 -0
- package/package.json +3 -6
- 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/adapters/redis.ts +0 -487
- package/src/adapters/s3.ts +0 -61
- package/src/core/collection.ts +0 -5
- package/src/core/directory.ts +0 -387
- 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/core/walker.ts +0 -174
- package/src/core/write-queue.ts +0 -59
- package/src/engines/s3-files.ts +0 -1068
- package/src/engines/types.ts +0 -21
- package/src/index.ts +0 -1727
- package/src/migrate-cli.ts +0 -22
- package/src/migrate.ts +0 -74
- package/src/types/fylo.d.ts +0 -261
- package/src/types/write-queue.ts +0 -42
- package/src/worker.ts +0 -18
- package/src/workers/write-worker.ts +0 -120
- package/tests/collection/truncate.test.js +0 -35
- package/tests/data.js +0 -97
- package/tests/index.js +0 -14
- 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 -95
- package/tests/integration/edge-cases.test.js +0 -158
- package/tests/integration/encryption.test.js +0 -131
- package/tests/integration/export.test.js +0 -46
- package/tests/integration/join-modes.test.js +0 -154
- package/tests/integration/migration.test.js +0 -38
- package/tests/integration/nested.test.js +0 -142
- package/tests/integration/operators.test.js +0 -122
- package/tests/integration/queue.test.js +0 -83
- package/tests/integration/read.test.js +0 -119
- package/tests/integration/rollback.test.js +0 -60
- package/tests/integration/s3-files.test.js +0 -192
- package/tests/integration/update.test.js +0 -99
- package/tests/mocks/cipher.js +0 -40
- package/tests/mocks/redis.js +0 -123
- package/tests/mocks/s3.js +0 -80
- 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/query.d.ts +0 -0
- /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
|
-
}
|
package/src/core/walker.ts
DELETED
|
@@ -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
|
-
}
|
package/src/core/write-queue.ts
DELETED
|
@@ -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
|
-
}
|