@delma/fylo 2.1.0 → 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 +27 -0
- 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/{src/core/directory.ts → dist/core/directory.js} +28 -35
- 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/{src → dist}/types/fylo.d.ts +14 -1
- package/package.json +2 -2
- 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/core/collection.ts +0 -5
- 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/engines/s3-files/documents.ts +0 -65
- package/src/engines/s3-files/filesystem.ts +0 -172
- package/src/engines/s3-files/query.ts +0 -291
- package/src/engines/s3-files/types.ts +0 -42
- package/src/engines/s3-files.ts +0 -769
- package/src/engines/types.ts +0 -21
- package/src/index.ts +0 -632
- package/src/sync.ts +0 -58
- package/tests/collection/truncate.test.js +0 -36
- package/tests/data.js +0 -97
- package/tests/helpers/root.js +0 -7
- 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 -97
- package/tests/integration/edge-cases.test.js +0 -162
- package/tests/integration/encryption.test.js +0 -148
- package/tests/integration/export.test.js +0 -46
- package/tests/integration/join-modes.test.js +0 -154
- package/tests/integration/nested.test.js +0 -144
- package/tests/integration/operators.test.js +0 -136
- package/tests/integration/read.test.js +0 -123
- package/tests/integration/rollback.test.js +0 -30
- package/tests/integration/s3-files.performance.test.js +0 -75
- package/tests/integration/s3-files.test.js +0 -205
- package/tests/integration/sync.test.js +0 -154
- package/tests/integration/update.test.js +0 -105
- package/tests/mocks/cipher.js +0 -40
- 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/node-runtime.d.ts +0 -0
- /package/{src → dist}/types/query.d.ts +0 -0
- /package/{src → dist}/types/vendor-modules.d.ts +0 -0
package/README.md
CHANGED
|
@@ -94,6 +94,33 @@ For compatibility with older `s3-files` experiments, FYLO still accepts `s3Files
|
|
|
94
94
|
| `SCHEMA_DIR` | Directory containing JSON validation schemas |
|
|
95
95
|
| `STRICT` | When truthy, validate documents with `@delma/chex` before writes |
|
|
96
96
|
| `ENCRYPTION_KEY` | Required when schemas declare `$encrypted` fields |
|
|
97
|
+
| `CIPHER_SALT` | Recommended unique salt for field encryption key derivation |
|
|
98
|
+
|
|
99
|
+
## Security-sensitive behavior
|
|
100
|
+
|
|
101
|
+
### Encrypted fields
|
|
102
|
+
|
|
103
|
+
Schemas can declare encrypted fields with a `$encrypted` array. When a collection schema declares encrypted fields, FYLO fails closed unless `ENCRYPTION_KEY` is set and at least 32 characters long.
|
|
104
|
+
|
|
105
|
+
Encrypted document values are stored with AES-GCM. Exact-match queries on encrypted fields use keyed HMAC blind indexes, so equality and frequency can still be inferred from index tokens, but plaintext field values are not written to document files, index files, or event journals.
|
|
106
|
+
|
|
107
|
+
If you are upgrading encrypted collections from a version before `2.1.1`, rewrite encrypted documents or otherwise rebuild affected indexes before relying on `$eq` queries for encrypted fields. Older encrypted document bodies can still be read, but old deterministic encrypted index entries do not match the new HMAC blind-index format.
|
|
108
|
+
|
|
109
|
+
### Bulk imports
|
|
110
|
+
|
|
111
|
+
`importBulkData()` is intended for trusted JSON or JSONL sources. By default, HTTP(S) imports reject localhost, private, loopback, link-local, and other private-network addresses, and responses are capped at 50 MiB.
|
|
112
|
+
|
|
113
|
+
You can tighten the import boundary with explicit options:
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
await fylo.importBulkData('users', new URL('https://data.example.com/users.json'), {
|
|
117
|
+
limit: 1000,
|
|
118
|
+
maxBytes: 5 * 1024 * 1024,
|
|
119
|
+
allowedHosts: ['data.example.com']
|
|
120
|
+
})
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Only set `allowPrivateNetwork: true` when the import URL is fully trusted by your application.
|
|
97
124
|
|
|
98
125
|
## Syncing to S3-compatible storage
|
|
99
126
|
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AES-256-GCM 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
|
+
* Exact-match queries use a separate keyed HMAC blind index. This leaks equality
|
|
11
|
+
* and frequency for indexed values, but stored document bodies use random nonces.
|
|
12
|
+
*
|
|
13
|
+
* Encrypted fields are declared per-collection in JSON schema files via the
|
|
14
|
+
* `$encrypted` array. The encryption key is sourced from `ENCRYPTION_KEY` env var.
|
|
15
|
+
* Set `CIPHER_SALT` to a unique random value to prevent cross-deployment attacks.
|
|
16
|
+
*/
|
|
17
|
+
export class Cipher {
|
|
18
|
+
static key = null;
|
|
19
|
+
static legacyCbcKey = null;
|
|
20
|
+
static hmacKey = null;
|
|
21
|
+
/** Per-collection encrypted field sets, loaded from schema `$encrypted` arrays. */
|
|
22
|
+
static collections = new Map();
|
|
23
|
+
static isConfigured() {
|
|
24
|
+
return Cipher.key !== null;
|
|
25
|
+
}
|
|
26
|
+
static hasEncryptedFields(collection) {
|
|
27
|
+
const fields = Cipher.collections.get(collection);
|
|
28
|
+
return !!fields && fields.size > 0;
|
|
29
|
+
}
|
|
30
|
+
static isEncryptedField(collection, field) {
|
|
31
|
+
const fields = Cipher.collections.get(collection);
|
|
32
|
+
if (!fields || fields.size === 0)
|
|
33
|
+
return false;
|
|
34
|
+
for (const pattern of fields) {
|
|
35
|
+
if (field === pattern)
|
|
36
|
+
return true;
|
|
37
|
+
// Support nested: encrypting "address" encrypts "address/city" etc.
|
|
38
|
+
if (field.startsWith(`${pattern}/`))
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Registers encrypted fields for a collection (from schema `$encrypted` array).
|
|
45
|
+
*/
|
|
46
|
+
static registerFields(collection, fields) {
|
|
47
|
+
if (fields.length > 0) {
|
|
48
|
+
Cipher.collections.set(collection, new Set(fields));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Derives AES + HMAC keys from a secret string. Called once at startup.
|
|
53
|
+
*/
|
|
54
|
+
static async configure(secret) {
|
|
55
|
+
const encoder = new TextEncoder();
|
|
56
|
+
const keyMaterial = await crypto.subtle.importKey('raw', encoder.encode(secret), 'PBKDF2', false, ['deriveBits']);
|
|
57
|
+
const cipherSalt = process.env.CIPHER_SALT;
|
|
58
|
+
if (!cipherSalt) {
|
|
59
|
+
console.warn('CIPHER_SALT is not set. Using default salt is insecure for multi-deployment use. Set CIPHER_SALT to a unique random value.');
|
|
60
|
+
}
|
|
61
|
+
// Derive 64 bytes: 32 for AES-GCM key + 32 for HMAC blind indexes.
|
|
62
|
+
const bits = await crypto.subtle.deriveBits({
|
|
63
|
+
name: 'PBKDF2',
|
|
64
|
+
salt: encoder.encode(cipherSalt ?? 'fylo-cipher'),
|
|
65
|
+
iterations: 100000,
|
|
66
|
+
hash: 'SHA-256'
|
|
67
|
+
}, keyMaterial, 512);
|
|
68
|
+
const derived = new Uint8Array(bits);
|
|
69
|
+
const key = await crypto.subtle.importKey('raw', derived.slice(0, 32), { name: 'AES-GCM' }, false, ['encrypt', 'decrypt']);
|
|
70
|
+
const legacyCbcKey = await crypto.subtle.importKey('raw', derived.slice(0, 32), { name: 'AES-CBC' }, false, ['decrypt']);
|
|
71
|
+
const hmacKey = await crypto.subtle.importKey('raw', derived.slice(32, 64), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
|
|
72
|
+
Cipher.key = key;
|
|
73
|
+
Cipher.legacyCbcKey = legacyCbcKey;
|
|
74
|
+
Cipher.hmacKey = hmacKey;
|
|
75
|
+
}
|
|
76
|
+
static reset() {
|
|
77
|
+
Cipher.key = null;
|
|
78
|
+
Cipher.legacyCbcKey = null;
|
|
79
|
+
Cipher.hmacKey = null;
|
|
80
|
+
Cipher.collections = new Map();
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Deterministic nonce from HMAC-SHA256 of plaintext, truncated to 12 bytes.
|
|
84
|
+
*/
|
|
85
|
+
static async deriveNonce(plaintext) {
|
|
86
|
+
const encoder = new TextEncoder();
|
|
87
|
+
const sig = await crypto.subtle.sign('HMAC', Cipher.hmacKey, encoder.encode(plaintext));
|
|
88
|
+
return new Uint8Array(sig).slice(0, 12);
|
|
89
|
+
}
|
|
90
|
+
static base64Url(bytes) {
|
|
91
|
+
let binary = '';
|
|
92
|
+
for (let i = 0; i < bytes.length; i += 0x8000) {
|
|
93
|
+
binary += String.fromCharCode(...bytes.slice(i, i + 0x8000));
|
|
94
|
+
}
|
|
95
|
+
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
96
|
+
}
|
|
97
|
+
static fromBase64Url(encoded) {
|
|
98
|
+
const b64 = encoded.replace(/-/g, '+').replace(/_/g, '/');
|
|
99
|
+
const padded = b64 + '='.repeat((4 - (b64.length % 4)) % 4);
|
|
100
|
+
return Uint8Array.from(atob(padded), (c) => c.charCodeAt(0));
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Produces a keyed lookup token for encrypted exact-match indexes.
|
|
104
|
+
*/
|
|
105
|
+
static async blindIndex(value) {
|
|
106
|
+
if (!Cipher.hmacKey)
|
|
107
|
+
throw new Error('Cipher not configured — set ENCRYPTION_KEY env var');
|
|
108
|
+
const sig = await crypto.subtle.sign('HMAC', Cipher.hmacKey, new TextEncoder().encode(value));
|
|
109
|
+
return `idx1.${Cipher.base64Url(new Uint8Array(sig))}`;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Encrypts a value. Returns a URL-safe base64 string (no slashes).
|
|
113
|
+
*
|
|
114
|
+
* @param value - The plaintext to encrypt.
|
|
115
|
+
* @param deterministic - Compatibility mode for legacy deterministic callers.
|
|
116
|
+
* Prefer `blindIndex()` for query indexes and random nonces for stored data.
|
|
117
|
+
*/
|
|
118
|
+
static async encrypt(value, deterministic = false) {
|
|
119
|
+
if (!Cipher.key)
|
|
120
|
+
throw new Error('Cipher not configured — set ENCRYPTION_KEY env var');
|
|
121
|
+
const nonce = deterministic
|
|
122
|
+
? await Cipher.deriveNonce(value)
|
|
123
|
+
: crypto.getRandomValues(new Uint8Array(12));
|
|
124
|
+
const encoder = new TextEncoder();
|
|
125
|
+
const encrypted = await crypto.subtle.encrypt({ name: 'AES-GCM', iv: nonce }, Cipher.key, encoder.encode(value));
|
|
126
|
+
const combined = new Uint8Array(nonce.length + encrypted.byteLength);
|
|
127
|
+
combined.set(nonce);
|
|
128
|
+
combined.set(new Uint8Array(encrypted), nonce.length);
|
|
129
|
+
return `v2.${Cipher.base64Url(combined)}`;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Decrypts a URL-safe base64 encoded value back to plaintext.
|
|
133
|
+
*/
|
|
134
|
+
static async decrypt(encoded) {
|
|
135
|
+
if (!Cipher.key)
|
|
136
|
+
throw new Error('Cipher not configured — set ENCRYPTION_KEY env var');
|
|
137
|
+
if (!encoded.startsWith('v2.'))
|
|
138
|
+
return await Cipher.decryptLegacyCbc(encoded);
|
|
139
|
+
const combined = Cipher.fromBase64Url(encoded.slice(3));
|
|
140
|
+
const nonce = combined.slice(0, 12);
|
|
141
|
+
const ciphertext = combined.slice(12);
|
|
142
|
+
const decrypted = await crypto.subtle.decrypt({ name: 'AES-GCM', iv: nonce }, Cipher.key, ciphertext);
|
|
143
|
+
return new TextDecoder().decode(decrypted);
|
|
144
|
+
}
|
|
145
|
+
static async decryptLegacyCbc(encoded) {
|
|
146
|
+
if (!Cipher.legacyCbcKey)
|
|
147
|
+
throw new Error('Cipher not configured — set ENCRYPTION_KEY env var');
|
|
148
|
+
const combined = Cipher.fromBase64Url(encoded);
|
|
149
|
+
const iv = combined.slice(0, 16);
|
|
150
|
+
const ciphertext = combined.slice(16);
|
|
151
|
+
const decrypted = await crypto.subtle.decrypt({ name: 'AES-CBC', iv }, Cipher.legacyCbcKey, ciphertext);
|
|
152
|
+
return new TextDecoder().decode(decrypted);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=cipher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cipher.js","sourceRoot":"","sources":["../../src/adapters/cipher.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collection.js","sourceRoot":"","sources":["../../src/core/collection.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,sBAAsB,CAAC,UAAkB;IACrD,IAAI,CAAC,+BAA+B,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAA;IAC9C,CAAC;AACL,CAAC"}
|
|
@@ -1,55 +1,48 @@
|
|
|
1
|
-
import { Cipher } from '../adapters/cipher'
|
|
2
|
-
|
|
1
|
+
import { Cipher } from '../adapters/cipher';
|
|
3
2
|
export class Dir {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
const obj = { ...data } as Record<string, any>
|
|
9
|
-
|
|
3
|
+
static SLASH_ASCII = '%2F';
|
|
4
|
+
static async extractKeys(collection, _id, data, parentField) {
|
|
5
|
+
const keys = { data: [], indexes: [] };
|
|
6
|
+
const obj = { ...data };
|
|
10
7
|
for (const field in obj) {
|
|
11
|
-
const newField = parentField ? `${parentField}/${field}` : field
|
|
12
|
-
const fieldValue = obj[field]
|
|
13
|
-
|
|
8
|
+
const newField = parentField ? `${parentField}/${field}` : field;
|
|
9
|
+
const fieldValue = obj[field];
|
|
14
10
|
if (fieldValue && typeof fieldValue === 'object' && !Array.isArray(fieldValue)) {
|
|
15
|
-
const items = await this.extractKeys(collection, _id, fieldValue, newField)
|
|
16
|
-
keys.data.push(...items.data)
|
|
17
|
-
keys.indexes.push(...items.indexes)
|
|
18
|
-
continue
|
|
11
|
+
const items = await this.extractKeys(collection, _id, fieldValue, newField);
|
|
12
|
+
keys.data.push(...items.data);
|
|
13
|
+
keys.indexes.push(...items.indexes);
|
|
14
|
+
continue;
|
|
19
15
|
}
|
|
20
|
-
|
|
21
16
|
if (Array.isArray(fieldValue)) {
|
|
22
17
|
if (fieldValue.some((item) => typeof item === 'object')) {
|
|
23
|
-
throw new Error(`Cannot have an array of objects`)
|
|
18
|
+
throw new Error(`Cannot have an array of objects`);
|
|
24
19
|
}
|
|
25
|
-
|
|
26
20
|
for (let i = 0; i < fieldValue.length; i++) {
|
|
27
|
-
let val = String(fieldValue[i]).replaceAll('/', this.SLASH_ASCII)
|
|
21
|
+
let val = String(fieldValue[i]).replaceAll('/', this.SLASH_ASCII);
|
|
28
22
|
if (Cipher.isConfigured() && Cipher.isEncryptedField(collection, newField)) {
|
|
29
|
-
val = await Cipher.
|
|
23
|
+
val = await Cipher.blindIndex(val);
|
|
30
24
|
}
|
|
31
|
-
keys.data.push(`${_id}/${newField}/${i}/${val}`)
|
|
32
|
-
keys.indexes.push(`${newField}/${i}/${val}/${_id}`)
|
|
25
|
+
keys.data.push(`${_id}/${newField}/${i}/${val}`);
|
|
26
|
+
keys.indexes.push(`${newField}/${i}/${val}/${_id}`);
|
|
33
27
|
}
|
|
34
|
-
continue
|
|
28
|
+
continue;
|
|
35
29
|
}
|
|
36
|
-
|
|
37
|
-
let val = String(fieldValue).replaceAll('/', this.SLASH_ASCII)
|
|
30
|
+
let val = String(fieldValue).replaceAll('/', this.SLASH_ASCII);
|
|
38
31
|
if (Cipher.isConfigured() && Cipher.isEncryptedField(collection, newField)) {
|
|
39
|
-
val = await Cipher.
|
|
32
|
+
val = await Cipher.blindIndex(val);
|
|
40
33
|
}
|
|
41
|
-
keys.data.push(`${_id}/${newField}/${val}`)
|
|
42
|
-
keys.indexes.push(`${newField}/${val}/${_id}`)
|
|
34
|
+
keys.data.push(`${_id}/${newField}/${val}`);
|
|
35
|
+
keys.indexes.push(`${newField}/${val}/${_id}`);
|
|
43
36
|
}
|
|
44
|
-
|
|
45
|
-
return keys
|
|
37
|
+
return keys;
|
|
46
38
|
}
|
|
47
|
-
|
|
48
|
-
static parseValue(value: string) {
|
|
39
|
+
static parseValue(value) {
|
|
49
40
|
try {
|
|
50
|
-
return JSON.parse(value)
|
|
51
|
-
}
|
|
52
|
-
|
|
41
|
+
return JSON.parse(value);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return value;
|
|
53
45
|
}
|
|
54
46
|
}
|
|
55
47
|
}
|
|
48
|
+
//# sourceMappingURL=directory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"directory.js","sourceRoot":"","sources":["../../src/core/directory.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import TTID from '@delma/ttid';
|
|
3
|
+
export function validateDocId(docId) {
|
|
4
|
+
if (!TTID.isTTID(docId))
|
|
5
|
+
throw new Error(`Invalid document ID: ${docId}`);
|
|
6
|
+
}
|
|
7
|
+
export function assertPathInside(parent, target) {
|
|
8
|
+
const resolvedParent = path.resolve(parent);
|
|
9
|
+
const resolvedTarget = path.resolve(target);
|
|
10
|
+
const relative = path.relative(resolvedParent, resolvedTarget);
|
|
11
|
+
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
12
|
+
throw new Error(`Unsafe document path: ${target}`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=doc-id.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doc-id.js","sourceRoot":"","sources":["../../src/core/doc-id.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,IAAI,MAAM,aAAa,CAAA;AAE9B,MAAM,UAAU,aAAa,CAAC,KAAa;IACvC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,KAAK,EAAE,CAAC,CAAA;AAC7E,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,MAAc,EAAE,MAAc;IAC3D,MAAM,cAAc,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;IAC3C,MAAM,cAAc,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;IAC3C,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC,CAAA;IAE9D,IAAI,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QACzD,MAAM,IAAI,KAAK,CAAC,yBAAyB,MAAM,EAAE,CAAC,CAAA;IACtD,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
|
+
Object.appendGroup = function (target, source) {
|
|
4
|
+
const result = { ...target };
|
|
5
|
+
for (const [sourceId, sourceGroup] of Object.entries(source)) {
|
|
6
|
+
if (!result[sourceId]) {
|
|
7
|
+
result[sourceId] = sourceGroup;
|
|
8
|
+
continue;
|
|
9
|
+
}
|
|
10
|
+
for (const [groupId, groupDoc] of Object.entries(sourceGroup)) {
|
|
11
|
+
result[sourceId][groupId] = groupDoc;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return result;
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=extensions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extensions.js","sourceRoot":"","sources":["../../src/core/extensions.ts"],"names":[],"mappings":";AAAA,uDAAuD;AAEvD,MAAM,CAAC,WAAW,GAAG,UACjB,MAA2B,EAC3B,MAA2B;IAE3B,MAAM,MAAM,GAAG,EAAE,GAAG,MAAM,EAAE,CAAA;IAE5B,KAAK,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3D,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;YACpB,MAAM,CAAC,QAAQ,CAAC,GAAG,WAAW,CAAA;YAC9B,SAAQ;QACZ,CAAC;QAED,KAAK,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;YAC5D,MAAM,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,GAAG,QAAQ,CAAA;QACxC,CAAC;IACL,CAAC;IAED,OAAO,MAAM,CAAA;AACjB,CAAC,CAAA"}
|