@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
|
@@ -0,0 +1,179 @@
|
|
|
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
|
+
collect<T>(): AsyncGenerator<
|
|
14
|
+
_ttid | Record<_ttid, T> | Record<string, _ttid[]> | Record<_ttid, Partial<T>> | undefined,
|
|
15
|
+
void,
|
|
16
|
+
unknown
|
|
17
|
+
>
|
|
18
|
+
onDelete(): AsyncGenerator<_ttid, void, unknown>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface ObjectConstructor {
|
|
22
|
+
appendGroup: (target: Record<string, any>, source: Record<string, any>) => Record<string, any>
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface Console {
|
|
26
|
+
format: (docs: Record<string, any>) => void
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type _joinDocs<T, U> =
|
|
30
|
+
| _ttid[]
|
|
31
|
+
| Record<string, _ttid[]>
|
|
32
|
+
| Record<string, Record<_ttid, Partial<T | U>>>
|
|
33
|
+
| Record<`${_ttid}, ${_ttid}`, T | U | (T & U) | (Partial<T> & Partial<U>)>
|
|
34
|
+
|
|
35
|
+
type _fyloSyncMode = 'await-sync' | 'fire-and-forget'
|
|
36
|
+
|
|
37
|
+
interface _fyloWriteSyncEvent<T extends Record<string, any> = Record<string, any>> {
|
|
38
|
+
operation: 'put' | 'patch'
|
|
39
|
+
collection: string
|
|
40
|
+
docId: _ttid
|
|
41
|
+
previousDocId?: _ttid
|
|
42
|
+
path: string
|
|
43
|
+
data: T
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface _fyloDeleteSyncEvent {
|
|
47
|
+
operation: 'delete' | 'patch'
|
|
48
|
+
collection: string
|
|
49
|
+
docId: _ttid
|
|
50
|
+
path: string
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface _fyloSyncHooks<T extends Record<string, any> = Record<string, any>> {
|
|
54
|
+
onWrite?: (event: _fyloWriteSyncEvent<T>) => Promise<void> | void
|
|
55
|
+
onDelete?: (event: _fyloDeleteSyncEvent) => Promise<void> | void
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
interface _fyloOptions {
|
|
59
|
+
root?: string
|
|
60
|
+
s3FilesRoot?: string
|
|
61
|
+
sync?: _fyloSyncHooks
|
|
62
|
+
syncMode?: _fyloSyncMode
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
interface _importBulkDataOptions {
|
|
66
|
+
limit?: number
|
|
67
|
+
maxBytes?: number
|
|
68
|
+
allowedProtocols?: string[]
|
|
69
|
+
allowedHosts?: string[]
|
|
70
|
+
allowPrivateNetwork?: boolean
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
declare module '@delma/fylo' {
|
|
74
|
+
export class FyloSyncError extends Error {
|
|
75
|
+
readonly collection: string
|
|
76
|
+
readonly docId: _ttid
|
|
77
|
+
readonly path: string
|
|
78
|
+
readonly operation: string
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export type FyloSyncMode = _fyloSyncMode
|
|
82
|
+
export type FyloWriteSyncEvent<T extends Record<string, any> = Record<string, any>> =
|
|
83
|
+
_fyloWriteSyncEvent<T>
|
|
84
|
+
export type FyloDeleteSyncEvent = _fyloDeleteSyncEvent
|
|
85
|
+
export type FyloSyncHooks<T extends Record<string, any> = Record<string, any>> =
|
|
86
|
+
_fyloSyncHooks<T>
|
|
87
|
+
export type FyloOptions = _fyloOptions
|
|
88
|
+
export type ImportBulkDataOptions = _importBulkDataOptions
|
|
89
|
+
|
|
90
|
+
export default class {
|
|
91
|
+
constructor(options?: _fyloOptions)
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Compatibility helper. FYLO now writes synchronously to the filesystem,
|
|
95
|
+
* so rollback is a no-op.
|
|
96
|
+
*/
|
|
97
|
+
rollback(): Promise<void>
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Executes a SQL query and returns the results.
|
|
101
|
+
* @param SQL The SQL query to execute.
|
|
102
|
+
* @returns The results of the query.
|
|
103
|
+
*/
|
|
104
|
+
executeSQL<T extends Record<string, any>, U extends Record<string, any> = {}>(
|
|
105
|
+
SQL: string
|
|
106
|
+
): Promise<number | void | any[] | _ttid | Record<any, any>>
|
|
107
|
+
|
|
108
|
+
static createCollection(collection: string): Promise<void>
|
|
109
|
+
static dropCollection(collection: string): Promise<void>
|
|
110
|
+
|
|
111
|
+
createCollection(collection: string): Promise<void>
|
|
112
|
+
dropCollection(collection: string): Promise<void>
|
|
113
|
+
|
|
114
|
+
importBulkData(
|
|
115
|
+
collection: string,
|
|
116
|
+
url: URL,
|
|
117
|
+
limitOrOptions?: number | _importBulkDataOptions
|
|
118
|
+
): Promise<number>
|
|
119
|
+
|
|
120
|
+
exportBulkData<T extends Record<string, any>>(
|
|
121
|
+
collection: string
|
|
122
|
+
): AsyncGenerator<T, void, unknown>
|
|
123
|
+
|
|
124
|
+
static exportBulkData<T extends Record<string, any>>(
|
|
125
|
+
collection: string
|
|
126
|
+
): AsyncGenerator<T, void, unknown>
|
|
127
|
+
|
|
128
|
+
static getDoc(collection: string, _id: _ttid, onlyId?: boolean): _getDoc
|
|
129
|
+
|
|
130
|
+
getDoc(collection: string, _id: _ttid, onlyId?: boolean): _getDoc
|
|
131
|
+
|
|
132
|
+
batchPutData<T extends Record<string, any>>(
|
|
133
|
+
collection: string,
|
|
134
|
+
batch: Array<T>
|
|
135
|
+
): Promise<_ttid[]>
|
|
136
|
+
|
|
137
|
+
putData<T extends Record<string, any>>(collection: string, data: T): Promise<_ttid>
|
|
138
|
+
putData<T extends Record<string, any>>(
|
|
139
|
+
collection: string,
|
|
140
|
+
data: Record<_ttid, T>
|
|
141
|
+
): Promise<_ttid>
|
|
142
|
+
|
|
143
|
+
patchDoc<T extends Record<string, any>>(
|
|
144
|
+
collection: string,
|
|
145
|
+
newDoc: Record<_ttid, Partial<T>>,
|
|
146
|
+
oldDoc?: Record<_ttid, T>
|
|
147
|
+
): Promise<_ttid>
|
|
148
|
+
|
|
149
|
+
patchDocs<T extends Record<string, any>>(
|
|
150
|
+
collection: string,
|
|
151
|
+
updateSchema: _storeUpdate<T>
|
|
152
|
+
): Promise<number>
|
|
153
|
+
|
|
154
|
+
delDoc(collection: string, _id: _ttid): Promise<void>
|
|
155
|
+
|
|
156
|
+
delDocs<T extends Record<string, any>>(
|
|
157
|
+
collection: string,
|
|
158
|
+
deleteSchema?: _storeDelete<T>
|
|
159
|
+
): Promise<number>
|
|
160
|
+
|
|
161
|
+
static joinDocs<T extends Record<string, any>, U extends Record<string, any>>(
|
|
162
|
+
join: _join<T, U>
|
|
163
|
+
): Promise<_joinDocs<T, U>>
|
|
164
|
+
|
|
165
|
+
joinDocs<T extends Record<string, any>, U extends Record<string, any>>(
|
|
166
|
+
join: _join<T, U>
|
|
167
|
+
): Promise<_joinDocs<T, U>>
|
|
168
|
+
|
|
169
|
+
static findDocs<T extends Record<string, any>>(
|
|
170
|
+
collection: string,
|
|
171
|
+
query?: _storeQuery<T>
|
|
172
|
+
): _findDocs
|
|
173
|
+
|
|
174
|
+
findDocs<T extends Record<string, any>>(
|
|
175
|
+
collection: string,
|
|
176
|
+
query?: _storeQuery<T>
|
|
177
|
+
): _findDocs
|
|
178
|
+
}
|
|
179
|
+
}
|
package/package.json
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@delma/fylo",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
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
|
-
"build": "tsc",
|
|
10
|
+
"build": "rm -rf dist && tsc && mkdir -p dist/types && cp src/types/*.d.ts dist/types/",
|
|
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
|
},
|
package/.env.example
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
LOGGING=
|
|
2
|
-
STRICT=
|
|
3
|
-
S3_REGION=ca-central-1
|
|
4
|
-
S3_ACCESS_KEY_ID=HELLO
|
|
5
|
-
S3_SECRET_ACCESS_KEY=WORLD
|
|
6
|
-
S3_ENDPOINT=https//example.com
|
|
7
|
-
BUCKET_PREFIX="byos-test"
|
|
8
|
-
SCHEMA_DIR=/path/to/schema/dir
|
|
9
|
-
REDIS_URL=redis://localhost:6379
|
|
10
|
-
REDIS_CONN_TIMEOUT=5000
|
|
11
|
-
REDIS_IDLE_TIMEOUT=30000
|
|
12
|
-
REDIS_AUTO_RECONNECT=
|
|
13
|
-
REDIS_MAX_RETRIES=10
|
|
14
|
-
REDIS_ENABLE_OFFLINE_QUEUE=
|
|
15
|
-
REDIS_ENABLE_AUTO_PIPELINING=
|
|
16
|
-
REDIS_TLS=
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: "Follow the shared workspace release process"
|
|
3
|
-
agent: "agent"
|
|
4
|
-
tools: [runInTerminal]
|
|
5
|
-
---
|
|
6
|
-
Follow the shared workspace release process in [../../../RELEASE.md](../../../RELEASE.md).
|
|
7
|
-
|
|
8
|
-
Use the repo's actual default branch. Do not assume it is `main`.
|
|
9
|
-
|
|
10
|
-
If repo-local workflows impose stricter checks or branch requirements, follow the repo-local workflow and then update the shared release guide later if that rule becomes the new standard.
|
package/.github/workflows/ci.yml
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
name: CI
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [release/*]
|
|
6
|
-
pull_request:
|
|
7
|
-
branches: [main]
|
|
8
|
-
|
|
9
|
-
jobs:
|
|
10
|
-
test:
|
|
11
|
-
name: Test (Bun ${{ matrix.bun-version }})
|
|
12
|
-
runs-on: ubuntu-latest
|
|
13
|
-
|
|
14
|
-
strategy:
|
|
15
|
-
matrix:
|
|
16
|
-
bun-version: [latest, 1.2.x]
|
|
17
|
-
|
|
18
|
-
steps:
|
|
19
|
-
- name: Checkout
|
|
20
|
-
uses: actions/checkout@v4
|
|
21
|
-
|
|
22
|
-
- name: Setup Bun
|
|
23
|
-
uses: oven-sh/setup-bun@v2
|
|
24
|
-
with:
|
|
25
|
-
bun-version: ${{ matrix.bun-version }}
|
|
26
|
-
|
|
27
|
-
- name: Install dependencies
|
|
28
|
-
run: bun install --frozen-lockfile
|
|
29
|
-
|
|
30
|
-
- name: Type check
|
|
31
|
-
run: bun run typecheck
|
|
32
|
-
|
|
33
|
-
- name: Lint
|
|
34
|
-
run: bun run lint
|
|
35
|
-
|
|
36
|
-
- name: Run tests
|
|
37
|
-
run: bun test
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
name: Publish
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [release/*]
|
|
6
|
-
|
|
7
|
-
jobs:
|
|
8
|
-
test:
|
|
9
|
-
name: Test before publish
|
|
10
|
-
runs-on: ubuntu-latest
|
|
11
|
-
|
|
12
|
-
steps:
|
|
13
|
-
- name: Checkout
|
|
14
|
-
uses: actions/checkout@v4
|
|
15
|
-
|
|
16
|
-
- name: Setup Bun
|
|
17
|
-
uses: oven-sh/setup-bun@v2
|
|
18
|
-
with:
|
|
19
|
-
bun-version: latest
|
|
20
|
-
|
|
21
|
-
- name: Install dependencies
|
|
22
|
-
run: bun install --frozen-lockfile
|
|
23
|
-
|
|
24
|
-
- name: Type check
|
|
25
|
-
run: bun run typecheck
|
|
26
|
-
|
|
27
|
-
- name: Lint
|
|
28
|
-
run: bun run lint
|
|
29
|
-
|
|
30
|
-
- name: Run tests
|
|
31
|
-
run: bun test
|
|
32
|
-
|
|
33
|
-
publish:
|
|
34
|
-
name: Publish to npm
|
|
35
|
-
runs-on: ubuntu-latest
|
|
36
|
-
needs: test
|
|
37
|
-
permissions:
|
|
38
|
-
contents: write # create git tags
|
|
39
|
-
id-token: write # npm provenance
|
|
40
|
-
|
|
41
|
-
steps:
|
|
42
|
-
- name: Checkout
|
|
43
|
-
uses: actions/checkout@v4
|
|
44
|
-
|
|
45
|
-
- name: Setup Bun
|
|
46
|
-
uses: oven-sh/setup-bun@v2
|
|
47
|
-
with:
|
|
48
|
-
bun-version: latest
|
|
49
|
-
|
|
50
|
-
- name: Install dependencies
|
|
51
|
-
run: bun install --frozen-lockfile
|
|
52
|
-
|
|
53
|
-
- name: Setup Node and upgrade npm
|
|
54
|
-
uses: actions/setup-node@v4
|
|
55
|
-
with:
|
|
56
|
-
node-version: '20'
|
|
57
|
-
|
|
58
|
-
- name: Upgrade npm
|
|
59
|
-
run: npm install -g npm@latest
|
|
60
|
-
|
|
61
|
-
- name: Resolve version from branch
|
|
62
|
-
id: version
|
|
63
|
-
run: |
|
|
64
|
-
BRANCH="${GITHUB_REF#refs/heads/}"
|
|
65
|
-
VERSION="${BRANCH#release/}"
|
|
66
|
-
PKG_VERSION=$(bun -e "console.log(require('./package.json').version)")
|
|
67
|
-
if [ "$VERSION" != "$PKG_VERSION" ]; then
|
|
68
|
-
echo "Branch version ($VERSION) does not match package.json ($PKG_VERSION). Skipping publish."
|
|
69
|
-
exit 1
|
|
70
|
-
fi
|
|
71
|
-
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
72
|
-
|
|
73
|
-
- name: Publish to npm (Trusted Publishing via OIDC)
|
|
74
|
-
run: |
|
|
75
|
-
echo "registry=https://registry.npmjs.org/" >> ~/.npmrc
|
|
76
|
-
npm publish --access public --provenance
|
|
77
|
-
|
|
78
|
-
- name: Create and push version tag
|
|
79
|
-
run: |
|
|
80
|
-
git config user.name "github-actions[bot]"
|
|
81
|
-
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
82
|
-
git tag -f -a "v${{ steps.version.outputs.version }}" -m "v${{ steps.version.outputs.version }}"
|
|
83
|
-
git push origin "v${{ steps.version.outputs.version }}" --force
|
|
84
|
-
|
|
85
|
-
- name: Create GitHub release
|
|
86
|
-
run: |
|
|
87
|
-
gh release create "v${{ steps.version.outputs.version }}" \
|
|
88
|
-
--title "v${{ steps.version.outputs.version }}" \
|
|
89
|
-
--generate-notes
|
|
90
|
-
env:
|
|
91
|
-
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
package/.prettierrc
DELETED
package/AGENTS.md
DELETED
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
Follow the shared workspace instructions in [../INSTRUCTIONS.md](../INSTRUCTIONS.md), shared context in [../MEMORY.md](../MEMORY.md), and shared release process in [../RELEASE.md](../RELEASE.md).
|
|
2
|
-
|
|
3
|
-
Repo-local rules may add to or override the shared files when explicitly stated.
|
package/CLAUDE.md
DELETED
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
Follow the shared workspace instructions in [../INSTRUCTIONS.md](../INSTRUCTIONS.md), shared context in [../MEMORY.md](../MEMORY.md), and shared release process in [../RELEASE.md](../RELEASE.md).
|
|
2
|
-
|
|
3
|
-
Repo-local rules may add to or override the shared files when explicitly stated.
|
package/eslint.config.js
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import tsPlugin from '@typescript-eslint/eslint-plugin'
|
|
2
|
-
import tsParser from '@typescript-eslint/parser'
|
|
3
|
-
import prettierConfig from 'eslint-config-prettier'
|
|
4
|
-
|
|
5
|
-
export default [
|
|
6
|
-
{
|
|
7
|
-
files: ['src/**/*.ts'],
|
|
8
|
-
languageOptions: {
|
|
9
|
-
parser: tsParser,
|
|
10
|
-
parserOptions: {}
|
|
11
|
-
},
|
|
12
|
-
plugins: {
|
|
13
|
-
'@typescript-eslint': tsPlugin
|
|
14
|
-
},
|
|
15
|
-
rules: {
|
|
16
|
-
...tsPlugin.configs['recommended'].rules,
|
|
17
|
-
'@typescript-eslint/no-explicit-any': 'warn',
|
|
18
|
-
'@typescript-eslint/explicit-function-return-type': 'warn',
|
|
19
|
-
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }]
|
|
20
|
-
}
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
files: ['tests/**/*.js'],
|
|
24
|
-
rules: {
|
|
25
|
-
'no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }]
|
|
26
|
-
}
|
|
27
|
-
},
|
|
28
|
-
prettierConfig,
|
|
29
|
-
{
|
|
30
|
-
ignores: ['bin/**', 'node_modules/**', '**/*.d.ts']
|
|
31
|
-
}
|
|
32
|
-
]
|
package/src/CLI
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
/// <reference path="./types/index.d.ts" />
|
|
3
|
-
import Silo from '.'
|
|
4
|
-
|
|
5
|
-
const SQL = process.argv[process.argv.length - 1]
|
|
6
|
-
|
|
7
|
-
const op = SQL.match(
|
|
8
|
-
/^((?:SELECT|select)|(?:INSERT|insert)|(?:UPDATE|update)|(?:DELETE|delete)|(?:CREATE|create)|(?:DROP|drop))/i
|
|
9
|
-
)
|
|
10
|
-
|
|
11
|
-
if (!op) throw new Error('Missing SQL Operation')
|
|
12
|
-
|
|
13
|
-
const res = await new Silo().executeSQL(SQL)
|
|
14
|
-
|
|
15
|
-
const cmnd = op.shift()!
|
|
16
|
-
|
|
17
|
-
switch (cmnd.toUpperCase()) {
|
|
18
|
-
case 'CREATE':
|
|
19
|
-
console.log('Successfully created schema')
|
|
20
|
-
break
|
|
21
|
-
case 'DROP':
|
|
22
|
-
console.log('Successfully dropped schema')
|
|
23
|
-
break
|
|
24
|
-
case 'SELECT':
|
|
25
|
-
if (typeof res === 'object' && !Array.isArray(res)) console.format(res)
|
|
26
|
-
else console.log(res)
|
|
27
|
-
break
|
|
28
|
-
case 'INSERT':
|
|
29
|
-
console.log(res)
|
|
30
|
-
break
|
|
31
|
-
case 'UPDATE':
|
|
32
|
-
console.log(`Successfully updated ${res} document(s)`)
|
|
33
|
-
break
|
|
34
|
-
case 'DELETE':
|
|
35
|
-
console.log(`Successfully deleted ${res} document(s)`)
|
|
36
|
-
break
|
|
37
|
-
default:
|
|
38
|
-
throw new Error('Invalid Operation: ' + cmnd)
|
|
39
|
-
}
|
package/src/adapters/cipher.ts
DELETED
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AES-256-CBC encryption adapter for field-level value encryption.
|
|
3
|
-
*
|
|
4
|
-
* Two modes are supported via the `deterministic` flag on `encrypt()`:
|
|
5
|
-
*
|
|
6
|
-
* - **Random IV (default)**: A cryptographically random IV is generated per
|
|
7
|
-
* encryption operation. Identical plaintexts produce different ciphertexts.
|
|
8
|
-
* Use this for fields that do not need exact-match ($eq/$ne) queries.
|
|
9
|
-
*
|
|
10
|
-
* - **Deterministic IV (opt-in)**: The IV is derived from HMAC-SHA256 of the
|
|
11
|
-
* plaintext, so identical values always produce identical ciphertext. This
|
|
12
|
-
* enables exact-match queries on encrypted fields but leaks equality — an
|
|
13
|
-
* observer can determine which records share field values without decrypting.
|
|
14
|
-
* Use only when $eq/$ne queries on encrypted fields are required.
|
|
15
|
-
*
|
|
16
|
-
* Encrypted fields are declared per-collection in JSON schema files via the
|
|
17
|
-
* `$encrypted` array. The encryption key is sourced from `ENCRYPTION_KEY` env var.
|
|
18
|
-
* Set `CIPHER_SALT` to a unique random value to prevent cross-deployment attacks.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
export class Cipher {
|
|
22
|
-
private static key: CryptoKey | null = null
|
|
23
|
-
private static hmacKey: CryptoKey | null = null
|
|
24
|
-
|
|
25
|
-
/** Per-collection encrypted field sets, loaded from schema `$encrypted` arrays. */
|
|
26
|
-
private static collections: Map<string, Set<string>> = new Map()
|
|
27
|
-
|
|
28
|
-
static isConfigured(): boolean {
|
|
29
|
-
return Cipher.key !== null
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
static hasEncryptedFields(collection: string): boolean {
|
|
33
|
-
const fields = Cipher.collections.get(collection)
|
|
34
|
-
return !!fields && fields.size > 0
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
static isEncryptedField(collection: string, field: string): boolean {
|
|
38
|
-
const fields = Cipher.collections.get(collection)
|
|
39
|
-
if (!fields || fields.size === 0) return false
|
|
40
|
-
|
|
41
|
-
for (const pattern of fields) {
|
|
42
|
-
if (field === pattern) return true
|
|
43
|
-
// Support nested: encrypting "address" encrypts "address/city" etc.
|
|
44
|
-
if (field.startsWith(`${pattern}/`)) return true
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return false
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Registers encrypted fields for a collection (from schema `$encrypted` array).
|
|
52
|
-
*/
|
|
53
|
-
static registerFields(collection: string, fields: string[]): void {
|
|
54
|
-
if (fields.length > 0) {
|
|
55
|
-
Cipher.collections.set(collection, new Set(fields))
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Derives AES + HMAC keys from a secret string. Called once at startup.
|
|
61
|
-
*/
|
|
62
|
-
static async configure(secret: string): Promise<void> {
|
|
63
|
-
const encoder = new TextEncoder()
|
|
64
|
-
const keyMaterial = await crypto.subtle.importKey(
|
|
65
|
-
'raw',
|
|
66
|
-
encoder.encode(secret),
|
|
67
|
-
'PBKDF2',
|
|
68
|
-
false,
|
|
69
|
-
['deriveBits']
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
const cipherSalt = process.env.CIPHER_SALT
|
|
73
|
-
if (!cipherSalt) {
|
|
74
|
-
console.warn(
|
|
75
|
-
'CIPHER_SALT is not set. Using default salt is insecure for multi-deployment use. Set CIPHER_SALT to a unique random value.'
|
|
76
|
-
)
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Derive 48 bytes: 32 for AES key + 16 for HMAC key
|
|
80
|
-
const bits = await crypto.subtle.deriveBits(
|
|
81
|
-
{
|
|
82
|
-
name: 'PBKDF2',
|
|
83
|
-
salt: encoder.encode(cipherSalt ?? 'fylo-cipher'),
|
|
84
|
-
iterations: 100000,
|
|
85
|
-
hash: 'SHA-256'
|
|
86
|
-
},
|
|
87
|
-
keyMaterial,
|
|
88
|
-
384
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
const derived = new Uint8Array(bits)
|
|
92
|
-
|
|
93
|
-
Cipher.key = await crypto.subtle.importKey(
|
|
94
|
-
'raw',
|
|
95
|
-
derived.slice(0, 32),
|
|
96
|
-
{ name: 'AES-CBC' },
|
|
97
|
-
false,
|
|
98
|
-
['encrypt', 'decrypt']
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
Cipher.hmacKey = await crypto.subtle.importKey(
|
|
102
|
-
'raw',
|
|
103
|
-
derived.slice(32),
|
|
104
|
-
{ name: 'HMAC', hash: 'SHA-256' },
|
|
105
|
-
false,
|
|
106
|
-
['sign']
|
|
107
|
-
)
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
static reset(): void {
|
|
111
|
-
Cipher.key = null
|
|
112
|
-
Cipher.hmacKey = null
|
|
113
|
-
Cipher.collections = new Map()
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Deterministic IV from HMAC-SHA256 of plaintext, truncated to 16 bytes.
|
|
118
|
-
*/
|
|
119
|
-
private static async deriveIV(plaintext: string): Promise<Uint8Array> {
|
|
120
|
-
const encoder = new TextEncoder()
|
|
121
|
-
const sig = await crypto.subtle.sign('HMAC', Cipher.hmacKey!, encoder.encode(plaintext))
|
|
122
|
-
return new Uint8Array(sig).slice(0, 16)
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Encrypts a value. Returns a URL-safe base64 string (no slashes).
|
|
127
|
-
*
|
|
128
|
-
* @param value - The plaintext to encrypt.
|
|
129
|
-
* @param deterministic - When true, derives IV from HMAC of plaintext (same
|
|
130
|
-
* input always produces same ciphertext). Required for $eq/$ne queries on
|
|
131
|
-
* encrypted fields. Defaults to false (random IV per operation).
|
|
132
|
-
*/
|
|
133
|
-
static async encrypt(value: string, deterministic = false): Promise<string> {
|
|
134
|
-
if (!Cipher.key) throw new Error('Cipher not configured — set ENCRYPTION_KEY env var')
|
|
135
|
-
|
|
136
|
-
const iv = deterministic
|
|
137
|
-
? await Cipher.deriveIV(value)
|
|
138
|
-
: crypto.getRandomValues(new Uint8Array(16))
|
|
139
|
-
const encoder = new TextEncoder()
|
|
140
|
-
|
|
141
|
-
const encrypted = await crypto.subtle.encrypt(
|
|
142
|
-
{ name: 'AES-CBC', iv: iv as any },
|
|
143
|
-
Cipher.key,
|
|
144
|
-
encoder.encode(value)
|
|
145
|
-
)
|
|
146
|
-
|
|
147
|
-
// Concatenate IV + ciphertext and encode as URL-safe base64
|
|
148
|
-
const combined = new Uint8Array(iv.length + encrypted.byteLength)
|
|
149
|
-
combined.set(iv)
|
|
150
|
-
combined.set(new Uint8Array(encrypted), iv.length)
|
|
151
|
-
|
|
152
|
-
return btoa(String.fromCharCode(...combined))
|
|
153
|
-
.replace(/\+/g, '-')
|
|
154
|
-
.replace(/\//g, '_')
|
|
155
|
-
.replace(/=+$/, '')
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Decrypts a URL-safe base64 encoded value back to plaintext.
|
|
160
|
-
*/
|
|
161
|
-
static async decrypt(encoded: string): Promise<string> {
|
|
162
|
-
if (!Cipher.key) throw new Error('Cipher not configured — set ENCRYPTION_KEY env var')
|
|
163
|
-
|
|
164
|
-
// Restore standard base64
|
|
165
|
-
const b64 = encoded.replace(/-/g, '+').replace(/_/g, '/')
|
|
166
|
-
const padded = b64 + '='.repeat((4 - (b64.length % 4)) % 4)
|
|
167
|
-
|
|
168
|
-
const combined = Uint8Array.from(atob(padded), (c) => c.charCodeAt(0))
|
|
169
|
-
const iv = combined.slice(0, 16)
|
|
170
|
-
const ciphertext = combined.slice(16)
|
|
171
|
-
|
|
172
|
-
const decrypted = await crypto.subtle.decrypt(
|
|
173
|
-
{ name: 'AES-CBC', iv },
|
|
174
|
-
Cipher.key,
|
|
175
|
-
ciphertext
|
|
176
|
-
)
|
|
177
|
-
|
|
178
|
-
return new TextDecoder().decode(decrypted)
|
|
179
|
-
}
|
|
180
|
-
}
|