@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,192 +0,0 @@
1
- import { afterAll, beforeAll, describe, expect, test } from 'bun:test'
2
- import { mkdtemp, rm, stat } from 'node:fs/promises'
3
- import os from 'node:os'
4
- import path from 'node:path'
5
- import { Database } from 'bun:sqlite'
6
- import Fylo from '../../src'
7
- const root = await mkdtemp(path.join(os.tmpdir(), 'fylo-s3files-'))
8
- const fylo = new Fylo({ engine: 's3-files', s3FilesRoot: root })
9
- const POSTS = 's3files-posts'
10
- const USERS = 's3files-users'
11
- describe('s3-files engine', () => {
12
- beforeAll(async () => {
13
- await fylo.createCollection(POSTS)
14
- await fylo.createCollection(USERS)
15
- })
16
- afterAll(async () => {
17
- await rm(root, { recursive: true, force: true })
18
- })
19
- test('put/get/patch/delete works without Redis or S3 adapters', async () => {
20
- const id = await fylo.putData(POSTS, {
21
- title: 'Hello',
22
- tags: ['bun', 'aws'],
23
- meta: { score: 1 }
24
- })
25
- const created = await fylo.getDoc(POSTS, id).once()
26
- expect(created[id].title).toBe('Hello')
27
- expect(created[id].tags).toEqual(['bun', 'aws'])
28
- const nextId = await fylo.patchDoc(POSTS, {
29
- [id]: {
30
- title: 'Hello 2',
31
- meta: { score: 2 }
32
- }
33
- })
34
- const updated = await fylo.getDoc(POSTS, nextId).once()
35
- expect(updated[nextId].title).toBe('Hello 2')
36
- expect(updated[nextId].meta.score).toBe(2)
37
- expect(await fylo.getDoc(POSTS, id).once()).toEqual({})
38
- await fylo.delDoc(POSTS, nextId)
39
- expect(await fylo.getDoc(POSTS, nextId).once()).toEqual({})
40
- })
41
- test('findDocs listener is backed by the filesystem event journal', async () => {
42
- const iter = fylo
43
- .findDocs(POSTS, {
44
- $ops: [{ title: { $eq: 'Live event' } }]
45
- })
46
- [Symbol.asyncIterator]()
47
- const pending = iter.next()
48
- await Bun.sleep(100)
49
- const id = await fylo.putData(POSTS, { title: 'Live event' })
50
- const { value } = await pending
51
- expect(value).toEqual({ [id]: { title: 'Live event' } })
52
- await iter.return?.()
53
- })
54
- test('supports long values without path-length issues', async () => {
55
- const longBody = 'x'.repeat(5000)
56
- const id = await fylo.putData(POSTS, {
57
- title: 'Long payload',
58
- body: longBody
59
- })
60
- const result = await fylo.getDoc(POSTS, id).once()
61
- expect(result[id].body).toBe(longBody)
62
- })
63
- test('stores indexes in a single SQLite database instead of per-entry files', async () => {
64
- const dbStat = await stat(path.join(root, POSTS, '.fylo', 'index.db'))
65
- expect(dbStat.isFile()).toBe(true)
66
- await expect(stat(path.join(root, POSTS, '.fylo', 'indexes'))).rejects.toThrow()
67
- })
68
- test('uses SQLite index rows to support exact, range, and contains queries', async () => {
69
- const queryCollection = 's3files-query'
70
- await fylo.createCollection(queryCollection)
71
-
72
- const bunId = await fylo.putData(queryCollection, {
73
- title: 'Bun launch',
74
- tags: ['bun', 'aws'],
75
- meta: { score: 10 }
76
- })
77
- const nodeId = await fylo.putData(queryCollection, {
78
- title: 'Node launch',
79
- tags: ['node'],
80
- meta: { score: 2 }
81
- })
82
-
83
- let eqResults = {}
84
- for await (const data of fylo
85
- .findDocs(queryCollection, {
86
- $ops: [{ title: { $eq: 'Bun launch' } }]
87
- })
88
- .collect()) {
89
- eqResults = { ...eqResults, ...data }
90
- }
91
- expect(Object.keys(eqResults)).toEqual([bunId])
92
-
93
- let rangeResults = {}
94
- for await (const data of fylo
95
- .findDocs(queryCollection, {
96
- $ops: [{ ['meta.score']: { $gte: 5 } }]
97
- })
98
- .collect()) {
99
- rangeResults = { ...rangeResults, ...data }
100
- }
101
- expect(Object.keys(rangeResults)).toEqual([bunId])
102
-
103
- let containsResults = {}
104
- for await (const data of fylo
105
- .findDocs(queryCollection, {
106
- $ops: [{ tags: { $contains: 'aws' } }]
107
- })
108
- .collect()) {
109
- containsResults = { ...containsResults, ...data }
110
- }
111
- expect(Object.keys(containsResults)).toEqual([bunId])
112
- expect(containsResults[nodeId]).toBeUndefined()
113
-
114
- const db = new Database(path.join(root, queryCollection, '.fylo', 'index.db'))
115
- const rows = db
116
- .query(
117
- `SELECT doc_id, field_path, raw_value, value_type, numeric_value
118
- FROM doc_index_entries
119
- WHERE doc_id = ?
120
- ORDER BY field_path, raw_value`
121
- )
122
- .all(bunId)
123
- db.close()
124
-
125
- expect(rows).toEqual(
126
- expect.arrayContaining([
127
- expect.objectContaining({
128
- doc_id: bunId,
129
- field_path: 'title',
130
- raw_value: 'Bun launch',
131
- value_type: 'string',
132
- numeric_value: null
133
- }),
134
- expect.objectContaining({
135
- doc_id: bunId,
136
- field_path: 'meta/score',
137
- raw_value: '10',
138
- value_type: 'number',
139
- numeric_value: 10
140
- }),
141
- expect.objectContaining({
142
- doc_id: bunId,
143
- field_path: 'tags/1',
144
- raw_value: 'aws',
145
- value_type: 'string',
146
- numeric_value: null
147
- })
148
- ])
149
- )
150
- })
151
- test('joins work in s3-files mode', async () => {
152
- const userId = await fylo.putData(USERS, { id: 42, name: 'Ada' })
153
- const postId = await fylo.putData(POSTS, { id: 42, title: 'Shared', content: 'join me' })
154
- const joined = await fylo.joinDocs({
155
- $leftCollection: USERS,
156
- $rightCollection: POSTS,
157
- $mode: 'inner',
158
- $on: {
159
- id: { $eq: 'id' }
160
- }
161
- })
162
- expect(joined[`${userId}, ${postId}`]).toBeDefined()
163
- })
164
- test('queue APIs are explicitly unsupported', async () => {
165
- await expect(fylo.queuePutData(POSTS, { title: 'no queue' })).rejects.toThrow(
166
- 'queuePutData is not supported'
167
- )
168
- await expect(fylo.processQueuedWrites(1)).rejects.toThrow(
169
- 'processQueuedWrites is not supported'
170
- )
171
- })
172
- test('rejects collection names that are unsafe for cross-platform filesystems', async () => {
173
- await expect(fylo.createCollection('bad/name')).rejects.toThrow('Invalid collection name')
174
- await expect(fylo.createCollection('bad\\name')).rejects.toThrow('Invalid collection name')
175
- await expect(fylo.createCollection('bad:name')).rejects.toThrow('Invalid collection name')
176
- })
177
- test('static helpers can use s3-files through env defaults', async () => {
178
- const prevEngine = process.env.FYLO_STORAGE_ENGINE
179
- const prevRoot = process.env.FYLO_S3FILES_ROOT
180
- process.env.FYLO_STORAGE_ENGINE = 's3-files'
181
- process.env.FYLO_S3FILES_ROOT = root
182
- const collection = 's3files-static'
183
- await Fylo.createCollection(collection)
184
- const id = await fylo.putData(collection, { title: 'Static path' })
185
- const result = await Fylo.getDoc(collection, id).once()
186
- expect(result[id].title).toBe('Static path')
187
- if (prevEngine === undefined) delete process.env.FYLO_STORAGE_ENGINE
188
- else process.env.FYLO_STORAGE_ENGINE = prevEngine
189
- if (prevRoot === undefined) delete process.env.FYLO_S3FILES_ROOT
190
- else process.env.FYLO_S3FILES_ROOT = prevRoot
191
- })
192
- })
@@ -1,99 +0,0 @@
1
- import { test, expect, describe, beforeAll, afterAll, mock } from 'bun:test'
2
- import Fylo from '../../src'
3
- import { photosURL, todosURL } from '../data'
4
- import S3Mock from '../mocks/s3'
5
- import RedisMock from '../mocks/redis'
6
- const PHOTOS = `photo`
7
- const TODOS = `todo`
8
- const fylo = new Fylo()
9
- mock.module('../../src/adapters/s3', () => ({ S3: S3Mock }))
10
- mock.module('../../src/adapters/redis', () => ({ Redis: RedisMock }))
11
- beforeAll(async () => {
12
- await Promise.all([Fylo.createCollection(PHOTOS), fylo.executeSQL(`CREATE TABLE ${TODOS}`)])
13
- try {
14
- await fylo.importBulkData(PHOTOS, new URL(photosURL), 100)
15
- await fylo.importBulkData(TODOS, new URL(todosURL), 100)
16
- } catch {
17
- await fylo.rollback()
18
- }
19
- })
20
- afterAll(async () => {
21
- await Promise.all([Fylo.dropCollection(PHOTOS), fylo.executeSQL(`DROP TABLE ${TODOS}`)])
22
- })
23
- describe('NO-SQL', async () => {
24
- test('UPDATE ONE', async () => {
25
- const ids = []
26
- for await (const data of Fylo.findDocs(PHOTOS, { $limit: 1, $onlyIds: true }).collect()) {
27
- ids.push(data)
28
- }
29
- try {
30
- await fylo.patchDoc(PHOTOS, { [ids.shift()]: { title: 'All Mighty' } })
31
- } catch {
32
- await fylo.rollback()
33
- }
34
- let results = {}
35
- for await (const data of Fylo.findDocs(PHOTOS, {
36
- $ops: [{ title: { $eq: 'All Mighty' } }]
37
- }).collect()) {
38
- results = { ...results, ...data }
39
- }
40
- expect(Object.keys(results).length).toBe(1)
41
- })
42
- test('UPDATE CLAUSE', async () => {
43
- let count = -1
44
- try {
45
- count = await fylo.patchDocs(PHOTOS, {
46
- $set: { title: 'All Mighti' },
47
- $where: { $ops: [{ title: { $like: '%est%' } }] }
48
- })
49
- } catch {
50
- await fylo.rollback()
51
- }
52
- let results = {}
53
- for await (const data of Fylo.findDocs(PHOTOS, {
54
- $ops: [{ title: { $eq: 'All Mighti' } }]
55
- }).collect()) {
56
- results = { ...results, ...data }
57
- }
58
- expect(Object.keys(results).length).toBe(count)
59
- })
60
- test('UPDATE ALL', async () => {
61
- let count = -1
62
- try {
63
- count = await fylo.patchDocs(PHOTOS, { $set: { title: 'All Mighter' } })
64
- } catch {
65
- await fylo.rollback()
66
- }
67
- let results = {}
68
- for await (const data of Fylo.findDocs(PHOTOS, {
69
- $ops: [{ title: { $eq: 'All Mighter' } }]
70
- }).collect()) {
71
- results = { ...results, ...data }
72
- }
73
- expect(Object.keys(results).length).toBe(count)
74
- }, 20000)
75
- })
76
- describe('SQL', async () => {
77
- test('UPDATE CLAUSE', async () => {
78
- let count = -1
79
- try {
80
- count = await fylo.executeSQL(
81
- `UPDATE ${TODOS} SET title = 'All Mighty' WHERE title LIKE '%est%'`
82
- )
83
- } catch {
84
- await fylo.rollback()
85
- }
86
- const results = await fylo.executeSQL(`SELECT * FROM ${TODOS} WHERE title = 'All Mighty'`)
87
- expect(Object.keys(results).length).toBe(count)
88
- })
89
- test('UPDATE ALL', async () => {
90
- let count = -1
91
- try {
92
- count = await fylo.executeSQL(`UPDATE ${TODOS} SET title = 'All Mightier'`)
93
- } catch {
94
- await fylo.rollback()
95
- }
96
- const results = await fylo.executeSQL(`SELECT * FROM ${TODOS} WHERE title = 'All Mightier'`)
97
- expect(Object.keys(results).length).toBe(count)
98
- }, 20000)
99
- })
@@ -1,40 +0,0 @@
1
- export class CipherMock {
2
- static _configured = false
3
- static collections = new Map()
4
- static isConfigured() {
5
- return CipherMock._configured
6
- }
7
- static hasEncryptedFields(collection) {
8
- const fields = CipherMock.collections.get(collection)
9
- return !!fields && fields.size > 0
10
- }
11
- static isEncryptedField(collection, field) {
12
- const fields = CipherMock.collections.get(collection)
13
- if (!fields || fields.size === 0) return false
14
- for (const pattern of fields) {
15
- if (field === pattern) return true
16
- if (field.startsWith(`${pattern}/`)) return true
17
- }
18
- return false
19
- }
20
- static registerFields(collection, fields) {
21
- if (fields.length > 0) {
22
- CipherMock.collections.set(collection, new Set(fields))
23
- }
24
- }
25
- static async configure(_secret) {
26
- CipherMock._configured = true
27
- }
28
- static reset() {
29
- CipherMock._configured = false
30
- CipherMock.collections = new Map()
31
- }
32
- static async encrypt(value) {
33
- return btoa(value).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
34
- }
35
- static async decrypt(encoded) {
36
- const b64 = encoded.replace(/-/g, '+').replace(/_/g, '/')
37
- const padded = b64 + '='.repeat((4 - (b64.length % 4)) % 4)
38
- return atob(padded)
39
- }
40
- }
@@ -1,123 +0,0 @@
1
- export default class RedisMock {
2
- static stream = []
3
- static jobs = new Map()
4
- static docs = new Map()
5
- static locks = new Map()
6
- static deadLetters = []
7
- static nextId = 0
8
- async publish(_collection, _action, _keyId) {}
9
- async claimTTID(_id, _ttlSeconds = 10) {
10
- return true
11
- }
12
- async enqueueWrite(job) {
13
- RedisMock.jobs.set(job.jobId, { ...job, nextAttemptAt: job.nextAttemptAt ?? Date.now() })
14
- RedisMock.docs.set(`fylo:doc:${job.collection}:${job.docId}`, {
15
- status: 'queued',
16
- lastJobId: job.jobId,
17
- updatedAt: String(Date.now())
18
- })
19
- const streamId = String(++RedisMock.nextId)
20
- RedisMock.stream.push({
21
- streamId,
22
- jobId: job.jobId,
23
- collection: job.collection,
24
- docId: job.docId,
25
- operation: job.operation
26
- })
27
- return streamId
28
- }
29
- async readWriteJobs(workerId, count = 1) {
30
- const available = RedisMock.stream.filter((entry) => !entry.claimedBy).slice(0, count)
31
- for (const entry of available) entry.claimedBy = workerId
32
- return available.map((entry) => ({
33
- streamId: entry.streamId,
34
- job: { ...RedisMock.jobs.get(entry.jobId) }
35
- }))
36
- }
37
- async ackWriteJob(streamId) {
38
- RedisMock.stream = RedisMock.stream.filter((item) => item.streamId !== streamId)
39
- }
40
- async deadLetterWriteJob(streamId, job, reason) {
41
- RedisMock.deadLetters.push({
42
- streamId: String(RedisMock.deadLetters.length + 1),
43
- jobId: job.jobId,
44
- reason,
45
- failedAt: Date.now()
46
- })
47
- await this.ackWriteJob(streamId)
48
- }
49
- async claimPendingJobs(workerId, _minIdleMs = 30000, count = 10) {
50
- const pending = RedisMock.stream.filter((entry) => entry.claimedBy).slice(0, count)
51
- for (const entry of pending) entry.claimedBy = workerId
52
- return pending.map((entry) => ({
53
- streamId: entry.streamId,
54
- job: { ...RedisMock.jobs.get(entry.jobId) }
55
- }))
56
- }
57
- async setJobStatus(jobId, status, extra = {}) {
58
- const job = RedisMock.jobs.get(jobId)
59
- if (job) Object.assign(job, extra, { status, updatedAt: Date.now() })
60
- }
61
- async setDocStatus(collection, docId, status, jobId) {
62
- const key = `fylo:doc:${collection}:${docId}`
63
- const curr = RedisMock.docs.get(key) ?? {}
64
- RedisMock.docs.set(key, {
65
- ...curr,
66
- status,
67
- updatedAt: String(Date.now()),
68
- ...(jobId ? { lastJobId: jobId } : {})
69
- })
70
- }
71
- async getJob(jobId) {
72
- const job = RedisMock.jobs.get(jobId)
73
- return job ? { ...job } : null
74
- }
75
- async getDocStatus(collection, docId) {
76
- return RedisMock.docs.get(`fylo:doc:${collection}:${docId}`) ?? null
77
- }
78
- async readDeadLetters(count = 10) {
79
- return RedisMock.deadLetters.slice(0, count).map((item) => ({
80
- streamId: item.streamId,
81
- job: { ...RedisMock.jobs.get(item.jobId) },
82
- reason: item.reason,
83
- failedAt: item.failedAt
84
- }))
85
- }
86
- async replayDeadLetter(streamId) {
87
- const item = RedisMock.deadLetters.find((entry) => entry.streamId === streamId)
88
- if (!item) return null
89
- const job = RedisMock.jobs.get(item.jobId)
90
- if (!job) return null
91
- const replayed = {
92
- ...job,
93
- status: 'queued',
94
- error: undefined,
95
- workerId: undefined,
96
- attempts: 0,
97
- updatedAt: Date.now(),
98
- nextAttemptAt: Date.now()
99
- }
100
- RedisMock.jobs.set(item.jobId, replayed)
101
- await this.enqueueWrite(replayed)
102
- RedisMock.deadLetters = RedisMock.deadLetters.filter((entry) => entry.streamId !== streamId)
103
- return { ...replayed }
104
- }
105
- async getQueueStats() {
106
- return {
107
- queued: RedisMock.stream.length,
108
- pending: RedisMock.stream.filter((entry) => entry.claimedBy).length,
109
- deadLetters: RedisMock.deadLetters.length
110
- }
111
- }
112
- async acquireDocLock(collection, docId, jobId) {
113
- const key = `fylo:lock:${collection}:${docId}`
114
- if (RedisMock.locks.has(key)) return false
115
- RedisMock.locks.set(key, jobId)
116
- return true
117
- }
118
- async releaseDocLock(collection, docId, jobId) {
119
- const key = `fylo:lock:${collection}:${docId}`
120
- if (RedisMock.locks.get(key) === jobId) RedisMock.locks.delete(key)
121
- }
122
- async *subscribe(_collection) {}
123
- }
package/tests/mocks/s3.js DELETED
@@ -1,80 +0,0 @@
1
- const store = new Map()
2
- function getBucket(name) {
3
- if (!store.has(name)) store.set(name, new Map())
4
- return store.get(name)
5
- }
6
- export default class S3Mock {
7
- static BUCKET_ENV = process.env.BUCKET_PREFIX
8
- static CREDS = {
9
- accessKeyId: 'mock',
10
- secretAccessKey: 'mock',
11
- region: 'mock',
12
- endpoint: undefined
13
- }
14
- static getBucketFormat(collection) {
15
- return S3Mock.BUCKET_ENV ? `${S3Mock.BUCKET_ENV}-${collection}` : collection
16
- }
17
- static file(collection, path) {
18
- const bucket = getBucket(S3Mock.getBucketFormat(collection))
19
- return {
20
- get size() {
21
- const val = bucket.get(path)
22
- return val !== undefined ? val.length : 0
23
- },
24
- async text() {
25
- return bucket.get(path) ?? ''
26
- }
27
- }
28
- }
29
- static async list(collection, options = {}) {
30
- const bucket = getBucket(S3Mock.getBucketFormat(collection))
31
- const prefix = options.prefix ?? ''
32
- const delimiter = options.delimiter
33
- const maxKeys = options.maxKeys ?? 1000
34
- const token = options.continuationToken
35
- const allKeys = Array.from(bucket.keys())
36
- .filter((k) => k.startsWith(prefix))
37
- .sort()
38
- if (delimiter) {
39
- const prefixSet = new Set()
40
- const contents = []
41
- for (const key of allKeys) {
42
- const rest = key.slice(prefix.length)
43
- const idx = rest.indexOf(delimiter)
44
- if (idx >= 0) {
45
- prefixSet.add(prefix + rest.slice(0, idx + 1))
46
- } else {
47
- contents.push({ key })
48
- }
49
- }
50
- const allPrefixes = Array.from(prefixSet).map((p) => ({ prefix: p }))
51
- const limitedPrefixes = allPrefixes.slice(0, maxKeys)
52
- return {
53
- contents: contents.length ? contents : undefined,
54
- commonPrefixes: limitedPrefixes.length ? limitedPrefixes : undefined,
55
- isTruncated: allPrefixes.length > maxKeys,
56
- nextContinuationToken: undefined
57
- }
58
- }
59
- const startIdx = token ? parseInt(token) : 0
60
- const page = allKeys.slice(startIdx, startIdx + maxKeys)
61
- const nextToken =
62
- startIdx + maxKeys < allKeys.length ? String(startIdx + maxKeys) : undefined
63
- return {
64
- contents: page.length ? page.map((k) => ({ key: k })) : undefined,
65
- isTruncated: !!nextToken,
66
- nextContinuationToken: nextToken,
67
- commonPrefixes: undefined
68
- }
69
- }
70
- static async put(collection, path, data) {
71
- getBucket(S3Mock.getBucketFormat(collection)).set(path, data)
72
- }
73
- static async delete(collection, path) {
74
- getBucket(S3Mock.getBucketFormat(collection)).delete(path)
75
- }
76
- static async createBucket(_collection) {}
77
- static async deleteBucket(collection) {
78
- store.delete(S3Mock.getBucketFormat(collection))
79
- }
80
- }
@@ -1,5 +0,0 @@
1
- interface _album {
2
- id: number
3
- userId: number
4
- title: string
5
- }
@@ -1,5 +0,0 @@
1
- {
2
- "id": -0,
3
- "userId": -0,
4
- "title": ""
5
- }
@@ -1,7 +0,0 @@
1
- interface _comment {
2
- id: number
3
- name: string
4
- email: string
5
- body: string
6
- postId: number
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "id": -0,
3
- "name": "",
4
- "email": "",
5
- "body": "",
6
- "postId": -0
7
- }
@@ -1,7 +0,0 @@
1
- interface _photo {
2
- id: number
3
- albumId: number
4
- title: string
5
- url: string
6
- thumbnailUrl: string
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "id": -0,
3
- "albumId": -0,
4
- "title": "",
5
- "url": "",
6
- "thumbnailUrl": ""
7
- }
@@ -1,6 +0,0 @@
1
- interface _post {
2
- id: number
3
- userId: number
4
- title: string
5
- body: string
6
- }
@@ -1,6 +0,0 @@
1
- {
2
- "id": -0,
3
- "userId": -0,
4
- "title": "",
5
- "body": ""
6
- }
@@ -1,7 +0,0 @@
1
- interface _tip {
2
- user_id: string
3
- business_id: string
4
- text: string
5
- date: string
6
- compliment_count: number
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "user_id": "",
3
- "business_id": "",
4
- "text": "",
5
- "date": "",
6
- "compliment_count": -0
7
- }
@@ -1,6 +0,0 @@
1
- interface _todo {
2
- id: number
3
- userId: number
4
- title: string
5
- completed: boolean
6
- }
@@ -1,6 +0,0 @@
1
- {
2
- "id": -0,
3
- "userId": -0,
4
- "title": "",
5
- "completed": false
6
- }
@@ -1,23 +0,0 @@
1
- interface _user {
2
- id: number
3
- name: string
4
- username: string
5
- email: string
6
- address: {
7
- street: string
8
- suite: string
9
- city: string
10
- zipcode: string
11
- geo: {
12
- lat: string
13
- lng: string
14
- }
15
- }
16
- phone: string
17
- website: string
18
- company: {
19
- name: string
20
- catchPhrase: string
21
- bs: string
22
- }
23
- }
@@ -1,23 +0,0 @@
1
- {
2
- "id": -0,
3
- "name": "",
4
- "username": "",
5
- "^email$": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$",
6
- "address": {
7
- "street": "",
8
- "suite": "",
9
- "city": "",
10
- "zipcode": "",
11
- "geo": {
12
- "lat": "",
13
- "lng": ""
14
- }
15
- },
16
- "phone": "",
17
- "website": "",
18
- "company": {
19
- "name": "",
20
- "catchPhrase": "",
21
- "bs": ""
22
- }
23
- }