@korajs/core 0.6.0 → 1.0.0-beta.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/LICENSE +21 -0
- package/dist/bindings/index.d.cts +6 -4
- package/dist/bindings/index.d.ts +6 -4
- package/dist/{build-scope-map-B1CcncZN.d.cts → build-scope-map-DtTr9C7A.d.cts} +1 -1
- package/dist/{build-scope-map-BmgAT82h.d.ts → build-scope-map-DvOHbYjn.d.ts} +1 -1
- package/dist/{chunk-3VIOZT7D.js → chunk-5IICSH6H.js} +150 -7
- package/dist/chunk-5IICSH6H.js.map +1 -0
- package/dist/{events-D-FWfFg8.d.cts → events-CqDkr1LN.d.cts} +114 -3
- package/dist/{events-D-FWfFg8.d.ts → events-CqDkr1LN.d.ts} +114 -3
- package/dist/index.cjs +802 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +209 -6
- package/dist/index.d.ts +209 -6
- package/dist/index.js +639 -5
- package/dist/index.js.map +1 -1
- package/dist/internal.cjs +142 -17
- package/dist/internal.cjs.map +1 -1
- package/dist/internal.d.cts +2 -2
- package/dist/internal.d.ts +2 -2
- package/dist/internal.js +1 -1
- package/dist/operation-BJtu18Fi.d.ts +164 -0
- package/dist/operation-CFoY-ZmB.d.cts +164 -0
- package/package.json +2 -1
- package/dist/chunk-3VIOZT7D.js.map +0 -1
- package/dist/operation-3-ZJJf3w.d.cts +0 -94
- package/dist/operation-CygOwIjZ.d.ts +0 -94
package/dist/index.js
CHANGED
|
@@ -2,10 +2,13 @@ import {
|
|
|
2
2
|
AppNotReadyError,
|
|
3
3
|
ClockDriftError,
|
|
4
4
|
HybridLogicalClock,
|
|
5
|
+
InvalidTimestampError,
|
|
5
6
|
KORA_ERROR_FIX_SUGGESTIONS,
|
|
6
7
|
KoraError,
|
|
8
|
+
MAX_LOGICAL,
|
|
7
9
|
MergeConflictError,
|
|
8
10
|
OperationError,
|
|
11
|
+
RemoteClockDriftError,
|
|
9
12
|
SchemaValidationError,
|
|
10
13
|
StorageError,
|
|
11
14
|
SyncError,
|
|
@@ -14,7 +17,7 @@ import {
|
|
|
14
17
|
isValidOperation,
|
|
15
18
|
topologicalSort,
|
|
16
19
|
verifyOperationIntegrity
|
|
17
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-5IICSH6H.js";
|
|
18
21
|
|
|
19
22
|
// src/types.ts
|
|
20
23
|
var MERGE_STRATEGIES = [
|
|
@@ -106,6 +109,226 @@ function formatUUID(bytes) {
|
|
|
106
109
|
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
|
|
107
110
|
}
|
|
108
111
|
|
|
112
|
+
// src/blob/blob-ref.ts
|
|
113
|
+
async function hashBlob(bytes) {
|
|
114
|
+
const view = new Uint8Array(bytes.byteLength);
|
|
115
|
+
view.set(bytes);
|
|
116
|
+
const digest = await globalThis.crypto.subtle.digest("SHA-256", view);
|
|
117
|
+
const out = new Uint8Array(digest);
|
|
118
|
+
return Array.from(out, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
119
|
+
}
|
|
120
|
+
async function createBlobRef(bytes, metadata = {}) {
|
|
121
|
+
const hash = await hashBlob(bytes);
|
|
122
|
+
const ref = { hash, size: bytes.byteLength };
|
|
123
|
+
if (metadata.mimeType !== void 0) {
|
|
124
|
+
ref.mimeType = metadata.mimeType;
|
|
125
|
+
}
|
|
126
|
+
if (metadata.filename !== void 0) {
|
|
127
|
+
ref.filename = metadata.filename;
|
|
128
|
+
}
|
|
129
|
+
return ref;
|
|
130
|
+
}
|
|
131
|
+
function isBlobRef(value) {
|
|
132
|
+
if (typeof value !== "object" || value === null) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
const ref = value;
|
|
136
|
+
if (typeof ref.hash !== "string" || !/^[0-9a-f]{64}$/.test(ref.hash)) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
if (typeof ref.size !== "number" || !Number.isInteger(ref.size) || ref.size < 0) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
if (ref.mimeType !== void 0 && typeof ref.mimeType !== "string") {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
if (ref.filename !== void 0 && typeof ref.filename !== "string") {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
if (ref.manifestHash !== void 0 && (typeof ref.manifestHash !== "string" || !/^[0-9a-f]{64}$/.test(ref.manifestHash))) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// src/secret/secret-crypto.ts
|
|
155
|
+
var PBKDF2_ITERATIONS = 21e4;
|
|
156
|
+
var SALT_BYTES = 16;
|
|
157
|
+
var IV_BYTES = 12;
|
|
158
|
+
var HASH_BYTES = 32;
|
|
159
|
+
function toHex(bytes) {
|
|
160
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
161
|
+
}
|
|
162
|
+
function fromHex(hex) {
|
|
163
|
+
if (hex.length % 2 !== 0 || /[^0-9a-f]/i.test(hex)) {
|
|
164
|
+
throw new Error("Invalid hex string");
|
|
165
|
+
}
|
|
166
|
+
const out = new Uint8Array(hex.length / 2);
|
|
167
|
+
for (let i = 0; i < out.length; i++) {
|
|
168
|
+
out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
169
|
+
}
|
|
170
|
+
return out;
|
|
171
|
+
}
|
|
172
|
+
function randomBytes(n) {
|
|
173
|
+
const out = new Uint8Array(n);
|
|
174
|
+
globalThis.crypto.getRandomValues(out);
|
|
175
|
+
return out;
|
|
176
|
+
}
|
|
177
|
+
function timingSafeEqual(a, b) {
|
|
178
|
+
if (a.length !== b.length) {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
let diff = 0;
|
|
182
|
+
for (let i = 0; i < a.length; i++) {
|
|
183
|
+
diff |= a[i] ^ b[i];
|
|
184
|
+
}
|
|
185
|
+
return diff === 0;
|
|
186
|
+
}
|
|
187
|
+
async function deriveAesKey(secretKey, salt) {
|
|
188
|
+
const baseKey = await globalThis.crypto.subtle.importKey(
|
|
189
|
+
"raw",
|
|
190
|
+
new TextEncoder().encode(secretKey),
|
|
191
|
+
"PBKDF2",
|
|
192
|
+
false,
|
|
193
|
+
["deriveKey"]
|
|
194
|
+
);
|
|
195
|
+
return globalThis.crypto.subtle.deriveKey(
|
|
196
|
+
{ name: "PBKDF2", salt, iterations: PBKDF2_ITERATIONS, hash: "SHA-256" },
|
|
197
|
+
baseKey,
|
|
198
|
+
{ name: "AES-GCM", length: 256 },
|
|
199
|
+
false,
|
|
200
|
+
["encrypt", "decrypt"]
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
async function encryptSecret(plaintext, secretKey) {
|
|
204
|
+
const salt = randomBytes(SALT_BYTES);
|
|
205
|
+
const iv = randomBytes(IV_BYTES);
|
|
206
|
+
const key = await deriveAesKey(secretKey, salt);
|
|
207
|
+
const ciphertext = await globalThis.crypto.subtle.encrypt(
|
|
208
|
+
{ name: "AES-GCM", iv },
|
|
209
|
+
key,
|
|
210
|
+
new TextEncoder().encode(plaintext)
|
|
211
|
+
);
|
|
212
|
+
return `v1.${toHex(salt)}.${toHex(iv)}.${toHex(new Uint8Array(ciphertext))}`;
|
|
213
|
+
}
|
|
214
|
+
async function decryptSecret(encrypted, secretKey) {
|
|
215
|
+
const parts = encrypted.split(".");
|
|
216
|
+
if (parts.length !== 4 || parts[0] !== "v1") {
|
|
217
|
+
throw new Error("Invalid encrypted secret format");
|
|
218
|
+
}
|
|
219
|
+
const salt = fromHex(parts[1]);
|
|
220
|
+
const iv = fromHex(parts[2]);
|
|
221
|
+
const ciphertext = fromHex(parts[3]);
|
|
222
|
+
const key = await deriveAesKey(secretKey, salt);
|
|
223
|
+
const plaintext = await globalThis.crypto.subtle.decrypt(
|
|
224
|
+
{ name: "AES-GCM", iv },
|
|
225
|
+
key,
|
|
226
|
+
ciphertext
|
|
227
|
+
);
|
|
228
|
+
return new TextDecoder().decode(plaintext);
|
|
229
|
+
}
|
|
230
|
+
async function hashSecret(plaintext) {
|
|
231
|
+
const salt = randomBytes(SALT_BYTES);
|
|
232
|
+
const hash = await pbkdf2(plaintext, salt);
|
|
233
|
+
return `v1.${toHex(salt)}.${toHex(hash)}`;
|
|
234
|
+
}
|
|
235
|
+
async function verifySecret(plaintext, stored) {
|
|
236
|
+
const parts = stored.split(".");
|
|
237
|
+
if (parts.length !== 3 || parts[0] !== "v1") {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
let salt;
|
|
241
|
+
let expected;
|
|
242
|
+
try {
|
|
243
|
+
salt = fromHex(parts[1]);
|
|
244
|
+
expected = fromHex(parts[2]);
|
|
245
|
+
} catch {
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
const actual = await pbkdf2(plaintext, salt);
|
|
249
|
+
return timingSafeEqual(actual, expected);
|
|
250
|
+
}
|
|
251
|
+
async function pbkdf2(plaintext, salt) {
|
|
252
|
+
const baseKey = await globalThis.crypto.subtle.importKey(
|
|
253
|
+
"raw",
|
|
254
|
+
new TextEncoder().encode(plaintext),
|
|
255
|
+
"PBKDF2",
|
|
256
|
+
false,
|
|
257
|
+
["deriveBits"]
|
|
258
|
+
);
|
|
259
|
+
const bits = await globalThis.crypto.subtle.deriveBits(
|
|
260
|
+
{ name: "PBKDF2", salt, iterations: PBKDF2_ITERATIONS, hash: "SHA-256" },
|
|
261
|
+
baseKey,
|
|
262
|
+
HASH_BYTES * 8
|
|
263
|
+
);
|
|
264
|
+
return new Uint8Array(bits);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// src/secret/secret-transform.ts
|
|
268
|
+
var MissingSecretKeyError = class extends Error {
|
|
269
|
+
constructor(field) {
|
|
270
|
+
super(
|
|
271
|
+
`Secret field "${field}" is encrypted but no encryption key is configured. Provide sync.encryption.key (or a key provider) to createApp.`
|
|
272
|
+
);
|
|
273
|
+
this.field = field;
|
|
274
|
+
this.name = "MissingSecretKeyError";
|
|
275
|
+
}
|
|
276
|
+
field;
|
|
277
|
+
};
|
|
278
|
+
async function resolveKey(provider) {
|
|
279
|
+
if (!provider) {
|
|
280
|
+
return void 0;
|
|
281
|
+
}
|
|
282
|
+
return provider();
|
|
283
|
+
}
|
|
284
|
+
async function transformSecretFieldsForWrite(data, collectionDef, keyProvider) {
|
|
285
|
+
const hasSecret = Object.values(collectionDef.fields).some((f) => f.kind === "secret");
|
|
286
|
+
if (!hasSecret) {
|
|
287
|
+
return data;
|
|
288
|
+
}
|
|
289
|
+
const result = { ...data };
|
|
290
|
+
let key;
|
|
291
|
+
let keyResolved = false;
|
|
292
|
+
for (const [field, value] of Object.entries(data)) {
|
|
293
|
+
const descriptor = collectionDef.fields[field];
|
|
294
|
+
if (descriptor?.kind !== "secret") {
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
if (typeof value !== "string") {
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
const mode = descriptor.secretMode ?? "encrypted";
|
|
301
|
+
if (mode === "hashed") {
|
|
302
|
+
result[field] = await hashSecret(value);
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
if (!keyResolved) {
|
|
306
|
+
key = await resolveKey(keyProvider);
|
|
307
|
+
keyResolved = true;
|
|
308
|
+
}
|
|
309
|
+
if (key === void 0) {
|
|
310
|
+
throw new MissingSecretKeyError(field);
|
|
311
|
+
}
|
|
312
|
+
result[field] = await encryptSecret(value, key);
|
|
313
|
+
}
|
|
314
|
+
return result;
|
|
315
|
+
}
|
|
316
|
+
async function revealSecret(storedValue, mode, keyProvider, field = "secret") {
|
|
317
|
+
if (mode === "hashed") {
|
|
318
|
+
throw new Error(
|
|
319
|
+
`Secret field "${field}" is hashed (one-way) and cannot be revealed. Use verifySecretValue to check a candidate.`
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
const key = await resolveKey(keyProvider);
|
|
323
|
+
if (key === void 0) {
|
|
324
|
+
throw new MissingSecretKeyError(field);
|
|
325
|
+
}
|
|
326
|
+
return decryptSecret(storedValue, key);
|
|
327
|
+
}
|
|
328
|
+
async function verifySecretValue(candidate, storedValue) {
|
|
329
|
+
return verifySecret(candidate, storedValue);
|
|
330
|
+
}
|
|
331
|
+
|
|
109
332
|
// src/operations/apply-result.ts
|
|
110
333
|
var APPLY_RESULTS = ["applied", "duplicate", "skipped", "rejected", "deferred"];
|
|
111
334
|
var APPLY_FAILURE_CODES = {
|
|
@@ -235,6 +458,106 @@ function toAtomicOp(sentinel) {
|
|
|
235
458
|
return { type: sentinel.type, value: sentinel.value };
|
|
236
459
|
}
|
|
237
460
|
|
|
461
|
+
// src/operations/op-data-binary.ts
|
|
462
|
+
function isKoraBytesValue(value) {
|
|
463
|
+
if (typeof value !== "object" || value === null) {
|
|
464
|
+
return false;
|
|
465
|
+
}
|
|
466
|
+
const record = value;
|
|
467
|
+
return Object.keys(record).length === 1 && typeof record.$koraBytes === "string";
|
|
468
|
+
}
|
|
469
|
+
function isLegacyNumericByteObject(value) {
|
|
470
|
+
if (typeof value !== "object" || value === null || ArrayBuffer.isView(value)) {
|
|
471
|
+
return false;
|
|
472
|
+
}
|
|
473
|
+
const record = value;
|
|
474
|
+
const keys = Object.keys(record);
|
|
475
|
+
if (keys.length === 0) {
|
|
476
|
+
return false;
|
|
477
|
+
}
|
|
478
|
+
for (let i = 0; i < keys.length; i++) {
|
|
479
|
+
const byte = record[String(i)];
|
|
480
|
+
if (typeof byte !== "number" || !Number.isInteger(byte) || byte < 0 || byte > 255) {
|
|
481
|
+
return false;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
return true;
|
|
485
|
+
}
|
|
486
|
+
var BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
487
|
+
var BASE64_REVERSE = new Map(
|
|
488
|
+
Array.from(BASE64_ALPHABET, (char, index) => [char, index])
|
|
489
|
+
);
|
|
490
|
+
function bytesToBase64(bytes) {
|
|
491
|
+
let out = "";
|
|
492
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
493
|
+
const b0 = bytes[i] ?? 0;
|
|
494
|
+
const b1 = bytes[i + 1] ?? 0;
|
|
495
|
+
const b2 = bytes[i + 2] ?? 0;
|
|
496
|
+
const triple = b0 << 16 | b1 << 8 | b2;
|
|
497
|
+
out += BASE64_ALPHABET[triple >> 18 & 63] ?? "";
|
|
498
|
+
out += BASE64_ALPHABET[triple >> 12 & 63] ?? "";
|
|
499
|
+
out += i + 1 < bytes.length ? BASE64_ALPHABET[triple >> 6 & 63] ?? "" : "=";
|
|
500
|
+
out += i + 2 < bytes.length ? BASE64_ALPHABET[triple & 63] ?? "" : "=";
|
|
501
|
+
}
|
|
502
|
+
return out;
|
|
503
|
+
}
|
|
504
|
+
function base64ToBytes(base64) {
|
|
505
|
+
const cleaned = base64.replace(/=+$/, "");
|
|
506
|
+
const out = new Uint8Array(Math.floor(cleaned.length * 6 / 8));
|
|
507
|
+
let buffer = 0;
|
|
508
|
+
let bits = 0;
|
|
509
|
+
let index = 0;
|
|
510
|
+
for (const char of cleaned) {
|
|
511
|
+
const value = BASE64_REVERSE.get(char);
|
|
512
|
+
if (value === void 0) {
|
|
513
|
+
throw new OperationError(`Invalid base64 character "${char}" in tagged binary value.`, {
|
|
514
|
+
char
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
buffer = buffer << 6 | value;
|
|
518
|
+
bits += 6;
|
|
519
|
+
if (bits >= 8) {
|
|
520
|
+
bits -= 8;
|
|
521
|
+
out[index] = buffer >> bits & 255;
|
|
522
|
+
index += 1;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return out;
|
|
526
|
+
}
|
|
527
|
+
function encodeBytesForOpData(value) {
|
|
528
|
+
if (typeof value === "string") {
|
|
529
|
+
return value;
|
|
530
|
+
}
|
|
531
|
+
const bytes = value instanceof Uint8Array ? value : new Uint8Array(value);
|
|
532
|
+
return { $koraBytes: bytesToBase64(bytes) };
|
|
533
|
+
}
|
|
534
|
+
function decodeBytesFromOpData(value) {
|
|
535
|
+
if (typeof value === "string") {
|
|
536
|
+
return value;
|
|
537
|
+
}
|
|
538
|
+
if (value instanceof Uint8Array) {
|
|
539
|
+
return value;
|
|
540
|
+
}
|
|
541
|
+
if (value instanceof ArrayBuffer) {
|
|
542
|
+
return new Uint8Array(value);
|
|
543
|
+
}
|
|
544
|
+
if (isKoraBytesValue(value)) {
|
|
545
|
+
return base64ToBytes(value.$koraBytes);
|
|
546
|
+
}
|
|
547
|
+
if (isLegacyNumericByteObject(value)) {
|
|
548
|
+
const keys = Object.keys(value);
|
|
549
|
+
const bytes = new Uint8Array(keys.length);
|
|
550
|
+
for (let i = 0; i < keys.length; i++) {
|
|
551
|
+
bytes[i] = value[String(i)] ?? 0;
|
|
552
|
+
}
|
|
553
|
+
return bytes;
|
|
554
|
+
}
|
|
555
|
+
throw new OperationError(
|
|
556
|
+
"Binary op-data value must be a string, Uint8Array, ArrayBuffer, tagged { $koraBytes } object, or legacy numeric-key byte object.",
|
|
557
|
+
{ receivedType: typeof value }
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
|
|
238
561
|
// src/scopes/sync-scope-bindings.ts
|
|
239
562
|
function hasSchemaSyncRules(schema) {
|
|
240
563
|
return schema.sync !== void 0 && Object.keys(schema.sync).length > 0;
|
|
@@ -664,6 +987,7 @@ function generateSQL(collectionName, collection, relations) {
|
|
|
664
987
|
columns.push("_created_at INTEGER NOT NULL");
|
|
665
988
|
columns.push("_updated_at INTEGER NOT NULL");
|
|
666
989
|
columns.push("_version TEXT NOT NULL DEFAULT ''");
|
|
990
|
+
columns.push("_field_versions TEXT NOT NULL DEFAULT '{}'");
|
|
667
991
|
columns.push("_deleted INTEGER NOT NULL DEFAULT 0");
|
|
668
992
|
statements.push(`CREATE TABLE IF NOT EXISTS ${collectionName} (
|
|
669
993
|
${columns.join(",\n ")}
|
|
@@ -676,6 +1000,10 @@ ALTER TABLE ${collectionName} ADD COLUMN ${colDef}`);
|
|
|
676
1000
|
statements.push(
|
|
677
1001
|
`--kora:safe-alter
|
|
678
1002
|
ALTER TABLE ${collectionName} ADD COLUMN _version TEXT NOT NULL DEFAULT ''`
|
|
1003
|
+
);
|
|
1004
|
+
statements.push(
|
|
1005
|
+
`--kora:safe-alter
|
|
1006
|
+
ALTER TABLE ${collectionName} ADD COLUMN _field_versions TEXT NOT NULL DEFAULT '{}'`
|
|
679
1007
|
);
|
|
680
1008
|
for (const indexField of collection.indexes) {
|
|
681
1009
|
statements.push(
|
|
@@ -703,6 +1031,9 @@ ALTER TABLE ${collectionName} ADD COLUMN _version TEXT NOT NULL DEFAULT ''`
|
|
|
703
1031
|
schema_version INTEGER NOT NULL
|
|
704
1032
|
)`
|
|
705
1033
|
);
|
|
1034
|
+
statements.push(
|
|
1035
|
+
`CREATE INDEX IF NOT EXISTS idx_kora_ops_${collectionName}_record_id ON _kora_ops_${collectionName} (record_id)`
|
|
1036
|
+
);
|
|
706
1037
|
return statements;
|
|
707
1038
|
}
|
|
708
1039
|
function generateFullDDL(schema) {
|
|
@@ -763,6 +1094,17 @@ function mapFieldType(descriptor) {
|
|
|
763
1094
|
case "array":
|
|
764
1095
|
return "TEXT";
|
|
765
1096
|
// JSON-serialized
|
|
1097
|
+
case "object":
|
|
1098
|
+
return "TEXT";
|
|
1099
|
+
// JSON-serialized structured object
|
|
1100
|
+
case "json":
|
|
1101
|
+
return "TEXT";
|
|
1102
|
+
// JSON-serialized dynamic-key value
|
|
1103
|
+
case "blob":
|
|
1104
|
+
return "TEXT";
|
|
1105
|
+
// JSON-serialized content-addressed BlobRef (bytes stored out of band)
|
|
1106
|
+
case "secret":
|
|
1107
|
+
return "TEXT";
|
|
766
1108
|
case "richtext":
|
|
767
1109
|
return "BLOB";
|
|
768
1110
|
}
|
|
@@ -996,6 +1338,160 @@ var ArrayFieldBuilder = class _ArrayFieldBuilder extends FieldBuilder {
|
|
|
996
1338
|
};
|
|
997
1339
|
}
|
|
998
1340
|
};
|
|
1341
|
+
var ObjectFieldBuilder = class _ObjectFieldBuilder extends FieldBuilder {
|
|
1342
|
+
_fields;
|
|
1343
|
+
constructor(fields, required = true, defaultValue = void 0, auto = false, mergeStrategy = null) {
|
|
1344
|
+
super("object", required, defaultValue, auto, mergeStrategy);
|
|
1345
|
+
this._fields = fields;
|
|
1346
|
+
}
|
|
1347
|
+
optional() {
|
|
1348
|
+
return new _ObjectFieldBuilder(
|
|
1349
|
+
this._fields,
|
|
1350
|
+
false,
|
|
1351
|
+
this._defaultValue,
|
|
1352
|
+
this._auto,
|
|
1353
|
+
this._mergeStrategy
|
|
1354
|
+
);
|
|
1355
|
+
}
|
|
1356
|
+
default(value) {
|
|
1357
|
+
return new _ObjectFieldBuilder(this._fields, false, value, this._auto, this._mergeStrategy);
|
|
1358
|
+
}
|
|
1359
|
+
auto() {
|
|
1360
|
+
return new _ObjectFieldBuilder(this._fields, false, void 0, true, this._mergeStrategy);
|
|
1361
|
+
}
|
|
1362
|
+
merge(strategy) {
|
|
1363
|
+
return new _ObjectFieldBuilder(
|
|
1364
|
+
this._fields,
|
|
1365
|
+
this._required,
|
|
1366
|
+
this._defaultValue,
|
|
1367
|
+
this._auto,
|
|
1368
|
+
strategy
|
|
1369
|
+
);
|
|
1370
|
+
}
|
|
1371
|
+
_build() {
|
|
1372
|
+
const nestedFields = {};
|
|
1373
|
+
for (const [key, builder] of Object.entries(this._fields)) {
|
|
1374
|
+
nestedFields[key] = builder._build();
|
|
1375
|
+
}
|
|
1376
|
+
return {
|
|
1377
|
+
kind: "object",
|
|
1378
|
+
required: this._required,
|
|
1379
|
+
defaultValue: this._defaultValue,
|
|
1380
|
+
auto: this._auto,
|
|
1381
|
+
enumValues: null,
|
|
1382
|
+
itemKind: null,
|
|
1383
|
+
mergeStrategy: this._mergeStrategy,
|
|
1384
|
+
transitions: null,
|
|
1385
|
+
nestedFields
|
|
1386
|
+
};
|
|
1387
|
+
}
|
|
1388
|
+
};
|
|
1389
|
+
var JsonFieldBuilder = class _JsonFieldBuilder extends FieldBuilder {
|
|
1390
|
+
optional() {
|
|
1391
|
+
return new _JsonFieldBuilder(
|
|
1392
|
+
"json",
|
|
1393
|
+
false,
|
|
1394
|
+
this._defaultValue,
|
|
1395
|
+
this._auto,
|
|
1396
|
+
this._mergeStrategy
|
|
1397
|
+
);
|
|
1398
|
+
}
|
|
1399
|
+
default(value) {
|
|
1400
|
+
return new _JsonFieldBuilder(
|
|
1401
|
+
"json",
|
|
1402
|
+
false,
|
|
1403
|
+
value,
|
|
1404
|
+
this._auto,
|
|
1405
|
+
this._mergeStrategy
|
|
1406
|
+
);
|
|
1407
|
+
}
|
|
1408
|
+
auto() {
|
|
1409
|
+
return new _JsonFieldBuilder("json", false, void 0, true, this._mergeStrategy);
|
|
1410
|
+
}
|
|
1411
|
+
merge(strategy) {
|
|
1412
|
+
return new _JsonFieldBuilder(
|
|
1413
|
+
"json",
|
|
1414
|
+
this._required,
|
|
1415
|
+
this._defaultValue,
|
|
1416
|
+
this._auto,
|
|
1417
|
+
strategy
|
|
1418
|
+
);
|
|
1419
|
+
}
|
|
1420
|
+
_build() {
|
|
1421
|
+
return {
|
|
1422
|
+
kind: "json",
|
|
1423
|
+
required: this._required,
|
|
1424
|
+
defaultValue: this._defaultValue,
|
|
1425
|
+
auto: this._auto,
|
|
1426
|
+
enumValues: null,
|
|
1427
|
+
itemKind: null,
|
|
1428
|
+
mergeStrategy: this._mergeStrategy,
|
|
1429
|
+
transitions: null,
|
|
1430
|
+
nestedFields: null
|
|
1431
|
+
};
|
|
1432
|
+
}
|
|
1433
|
+
};
|
|
1434
|
+
var SecretFieldBuilder = class _SecretFieldBuilder extends FieldBuilder {
|
|
1435
|
+
_secretMode;
|
|
1436
|
+
constructor(secretMode = "encrypted", required = true, defaultValue = void 0, auto = false, mergeStrategy = null) {
|
|
1437
|
+
super("secret", required, defaultValue, auto, mergeStrategy);
|
|
1438
|
+
this._secretMode = secretMode;
|
|
1439
|
+
}
|
|
1440
|
+
/** Store this secret as a one-way salted hash (passwords: verify, never read back). */
|
|
1441
|
+
hashed() {
|
|
1442
|
+
return new _SecretFieldBuilder(
|
|
1443
|
+
"hashed",
|
|
1444
|
+
this._required,
|
|
1445
|
+
this._defaultValue,
|
|
1446
|
+
this._auto,
|
|
1447
|
+
this._mergeStrategy
|
|
1448
|
+
);
|
|
1449
|
+
}
|
|
1450
|
+
/** Store this secret as reversible ciphertext (tokens/keys: decrypt to use). */
|
|
1451
|
+
encrypted() {
|
|
1452
|
+
return new _SecretFieldBuilder(
|
|
1453
|
+
"encrypted",
|
|
1454
|
+
this._required,
|
|
1455
|
+
this._defaultValue,
|
|
1456
|
+
this._auto,
|
|
1457
|
+
this._mergeStrategy
|
|
1458
|
+
);
|
|
1459
|
+
}
|
|
1460
|
+
optional() {
|
|
1461
|
+
return new _SecretFieldBuilder(
|
|
1462
|
+
this._secretMode,
|
|
1463
|
+
false,
|
|
1464
|
+
this._defaultValue,
|
|
1465
|
+
this._auto,
|
|
1466
|
+
this._mergeStrategy
|
|
1467
|
+
);
|
|
1468
|
+
}
|
|
1469
|
+
auto() {
|
|
1470
|
+
return new _SecretFieldBuilder(this._secretMode, false, void 0, true, this._mergeStrategy);
|
|
1471
|
+
}
|
|
1472
|
+
merge(strategy) {
|
|
1473
|
+
return new _SecretFieldBuilder(
|
|
1474
|
+
this._secretMode,
|
|
1475
|
+
this._required,
|
|
1476
|
+
this._defaultValue,
|
|
1477
|
+
this._auto,
|
|
1478
|
+
strategy
|
|
1479
|
+
);
|
|
1480
|
+
}
|
|
1481
|
+
_build() {
|
|
1482
|
+
return {
|
|
1483
|
+
kind: "secret",
|
|
1484
|
+
required: this._required,
|
|
1485
|
+
defaultValue: this._defaultValue,
|
|
1486
|
+
auto: this._auto,
|
|
1487
|
+
enumValues: null,
|
|
1488
|
+
itemKind: null,
|
|
1489
|
+
mergeStrategy: this._mergeStrategy,
|
|
1490
|
+
transitions: null,
|
|
1491
|
+
secretMode: this._secretMode
|
|
1492
|
+
};
|
|
1493
|
+
}
|
|
1494
|
+
};
|
|
999
1495
|
var t = {
|
|
1000
1496
|
string() {
|
|
1001
1497
|
return new FieldBuilder("string", true, void 0, false);
|
|
@@ -1017,6 +1513,18 @@ var t = {
|
|
|
1017
1513
|
},
|
|
1018
1514
|
array(itemBuilder) {
|
|
1019
1515
|
return new ArrayFieldBuilder(itemBuilder, true, void 0, false);
|
|
1516
|
+
},
|
|
1517
|
+
object(fields) {
|
|
1518
|
+
return new ObjectFieldBuilder(fields, true, void 0, false);
|
|
1519
|
+
},
|
|
1520
|
+
json() {
|
|
1521
|
+
return new JsonFieldBuilder("json", true, void 0, false);
|
|
1522
|
+
},
|
|
1523
|
+
blob() {
|
|
1524
|
+
return new FieldBuilder("blob", true, void 0, false);
|
|
1525
|
+
},
|
|
1526
|
+
secret() {
|
|
1527
|
+
return new SecretFieldBuilder("encrypted", true, void 0, false);
|
|
1020
1528
|
}
|
|
1021
1529
|
};
|
|
1022
1530
|
|
|
@@ -1168,9 +1676,9 @@ function validateFieldValue(collection, fieldName, descriptor, value) {
|
|
|
1168
1676
|
break;
|
|
1169
1677
|
}
|
|
1170
1678
|
case "richtext": {
|
|
1171
|
-
if (!(value instanceof Uint8Array) && typeof value !== "string") {
|
|
1679
|
+
if (!(value instanceof Uint8Array) && !(value instanceof ArrayBuffer) && typeof value !== "string") {
|
|
1172
1680
|
throw new SchemaValidationError(
|
|
1173
|
-
`Field "${fieldName}" in collection "${collection}" must be a Uint8Array or string for richtext, got ${typeof value}`,
|
|
1681
|
+
`Field "${fieldName}" in collection "${collection}" must be a Uint8Array, ArrayBuffer, or string for richtext, got ${typeof value}`,
|
|
1174
1682
|
{
|
|
1175
1683
|
collection,
|
|
1176
1684
|
field: fieldName,
|
|
@@ -1181,7 +1689,98 @@ function validateFieldValue(collection, fieldName, descriptor, value) {
|
|
|
1181
1689
|
}
|
|
1182
1690
|
break;
|
|
1183
1691
|
}
|
|
1692
|
+
case "object": {
|
|
1693
|
+
if (!isPlainObject(value)) {
|
|
1694
|
+
throw new SchemaValidationError(
|
|
1695
|
+
`Field "${fieldName}" in collection "${collection}" must be a plain object, got ${describeType(value)}`,
|
|
1696
|
+
{
|
|
1697
|
+
collection,
|
|
1698
|
+
field: fieldName,
|
|
1699
|
+
expectedType: "object",
|
|
1700
|
+
receivedType: describeType(value)
|
|
1701
|
+
}
|
|
1702
|
+
);
|
|
1703
|
+
}
|
|
1704
|
+
if (descriptor.nestedFields) {
|
|
1705
|
+
for (const [nestedName, nestedDescriptor] of Object.entries(descriptor.nestedFields)) {
|
|
1706
|
+
const nestedValue = value[nestedName];
|
|
1707
|
+
if (nestedValue !== void 0 && nestedValue !== null) {
|
|
1708
|
+
validateFieldValue(
|
|
1709
|
+
collection,
|
|
1710
|
+
`${fieldName}.${nestedName}`,
|
|
1711
|
+
nestedDescriptor,
|
|
1712
|
+
nestedValue
|
|
1713
|
+
);
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
break;
|
|
1718
|
+
}
|
|
1719
|
+
case "json": {
|
|
1720
|
+
if (!isJsonSerializable(value)) {
|
|
1721
|
+
throw new SchemaValidationError(
|
|
1722
|
+
`Field "${fieldName}" in collection "${collection}" must be JSON-serializable, got ${describeType(value)}`,
|
|
1723
|
+
{ collection, field: fieldName, expectedType: "json", receivedType: describeType(value) }
|
|
1724
|
+
);
|
|
1725
|
+
}
|
|
1726
|
+
break;
|
|
1727
|
+
}
|
|
1728
|
+
case "blob": {
|
|
1729
|
+
if (!isBlobRef(value)) {
|
|
1730
|
+
throw new SchemaValidationError(
|
|
1731
|
+
`Field "${fieldName}" in collection "${collection}" must be a BlobRef (from the blob store), got ${describeType(value)}`,
|
|
1732
|
+
{ collection, field: fieldName, expectedType: "blob", receivedType: describeType(value) }
|
|
1733
|
+
);
|
|
1734
|
+
}
|
|
1735
|
+
break;
|
|
1736
|
+
}
|
|
1737
|
+
case "secret": {
|
|
1738
|
+
if (typeof value !== "string") {
|
|
1739
|
+
throw new SchemaValidationError(
|
|
1740
|
+
`Field "${fieldName}" in collection "${collection}" must be a string, got ${describeType(value)}`,
|
|
1741
|
+
{
|
|
1742
|
+
collection,
|
|
1743
|
+
field: fieldName,
|
|
1744
|
+
expectedType: "secret",
|
|
1745
|
+
receivedType: describeType(value)
|
|
1746
|
+
}
|
|
1747
|
+
);
|
|
1748
|
+
}
|
|
1749
|
+
break;
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
function isPlainObject(value) {
|
|
1754
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
1755
|
+
return false;
|
|
1184
1756
|
}
|
|
1757
|
+
const proto = Object.getPrototypeOf(value);
|
|
1758
|
+
return proto === null || proto === Object.prototype;
|
|
1759
|
+
}
|
|
1760
|
+
function isJsonSerializable(value) {
|
|
1761
|
+
if (value === null) {
|
|
1762
|
+
return true;
|
|
1763
|
+
}
|
|
1764
|
+
const type = typeof value;
|
|
1765
|
+
if (type === "string" || type === "number" || type === "boolean") {
|
|
1766
|
+
return Number.isFinite(value) || type !== "number";
|
|
1767
|
+
}
|
|
1768
|
+
if (Array.isArray(value)) {
|
|
1769
|
+
return value.every(isJsonSerializable);
|
|
1770
|
+
}
|
|
1771
|
+
if (type === "object") {
|
|
1772
|
+
return Object.values(value).every(isJsonSerializable);
|
|
1773
|
+
}
|
|
1774
|
+
return false;
|
|
1775
|
+
}
|
|
1776
|
+
function describeType(value) {
|
|
1777
|
+
if (value === null) {
|
|
1778
|
+
return "null";
|
|
1779
|
+
}
|
|
1780
|
+
if (Array.isArray(value)) {
|
|
1781
|
+
return "array";
|
|
1782
|
+
}
|
|
1783
|
+
return typeof value;
|
|
1185
1784
|
}
|
|
1186
1785
|
function jsTypeForKind(kind) {
|
|
1187
1786
|
switch (kind) {
|
|
@@ -1662,6 +2261,14 @@ function mapFieldType2(descriptor) {
|
|
|
1662
2261
|
return "INTEGER";
|
|
1663
2262
|
case "array":
|
|
1664
2263
|
return "TEXT";
|
|
2264
|
+
case "object":
|
|
2265
|
+
return "TEXT";
|
|
2266
|
+
case "json":
|
|
2267
|
+
return "TEXT";
|
|
2268
|
+
case "blob":
|
|
2269
|
+
return "TEXT";
|
|
2270
|
+
case "secret":
|
|
2271
|
+
return "TEXT";
|
|
1665
2272
|
case "richtext":
|
|
1666
2273
|
return "BLOB";
|
|
1667
2274
|
}
|
|
@@ -1680,7 +2287,14 @@ var SCALAR_TYPE_MAP = {
|
|
|
1680
2287
|
number: "double",
|
|
1681
2288
|
boolean: "bool",
|
|
1682
2289
|
timestamp: "int64",
|
|
1683
|
-
richtext: "bytes"
|
|
2290
|
+
richtext: "bytes",
|
|
2291
|
+
// object/json values travel as JSON-serialized strings on the proto wire.
|
|
2292
|
+
object: "string",
|
|
2293
|
+
json: "string",
|
|
2294
|
+
// A blob field carries a JSON-serialized content-addressed reference, not bytes.
|
|
2295
|
+
blob: "string",
|
|
2296
|
+
// A secret field's stored value is a JSON string (ciphertext or hash).
|
|
2297
|
+
secret: "string"
|
|
1684
2298
|
};
|
|
1685
2299
|
var ARRAY_ITEM_TYPE_MAP = {
|
|
1686
2300
|
string: "string",
|
|
@@ -1915,33 +2529,44 @@ export {
|
|
|
1915
2529
|
EnumFieldBuilder,
|
|
1916
2530
|
FieldBuilder,
|
|
1917
2531
|
HybridLogicalClock,
|
|
2532
|
+
InvalidTimestampError,
|
|
1918
2533
|
KORA_ERROR_FIX_SUGGESTIONS,
|
|
1919
2534
|
KoraError,
|
|
2535
|
+
MAX_LOGICAL,
|
|
1920
2536
|
MERGE_STRATEGIES,
|
|
1921
2537
|
MergeConflictError,
|
|
1922
2538
|
MigrationBuilder,
|
|
1923
2539
|
MigrationRollbackError,
|
|
2540
|
+
MissingSecretKeyError,
|
|
1924
2541
|
OperationError,
|
|
2542
|
+
RemoteClockDriftError,
|
|
1925
2543
|
RollbackBuilder,
|
|
1926
2544
|
SchemaValidationError,
|
|
1927
2545
|
StorageError,
|
|
1928
2546
|
SyncError,
|
|
1929
2547
|
advanceVector,
|
|
1930
2548
|
applyOperationTransforms,
|
|
2549
|
+
base64ToBytes,
|
|
1931
2550
|
buildScopeMap,
|
|
1932
2551
|
buildStateMachineConstraints,
|
|
2552
|
+
bytesToBase64,
|
|
1933
2553
|
canAutoRollback,
|
|
1934
2554
|
collectSchemaScopeFields,
|
|
1935
2555
|
collectSchemaScopeValueKeys,
|
|
1936
2556
|
computeDelta,
|
|
2557
|
+
createBlobRef,
|
|
1937
2558
|
createOperation,
|
|
1938
2559
|
createReversibleMigration,
|
|
1939
2560
|
createVersionVector,
|
|
2561
|
+
decodeBytesFromOpData,
|
|
2562
|
+
decryptSecret,
|
|
1940
2563
|
defaultApplyFailureReason,
|
|
1941
2564
|
defaultSequenceFormat,
|
|
1942
2565
|
defineSchema,
|
|
1943
2566
|
deserializeVector,
|
|
1944
2567
|
dominates,
|
|
2568
|
+
encodeBytesForOpData,
|
|
2569
|
+
encryptSecret,
|
|
1945
2570
|
extractScopeValuesFromClaims,
|
|
1946
2571
|
extractTimestamp,
|
|
1947
2572
|
formatSequenceValue,
|
|
@@ -1954,9 +2579,14 @@ export {
|
|
|
1954
2579
|
getKoraErrorFix,
|
|
1955
2580
|
getTransitionMap,
|
|
1956
2581
|
hasSchemaSyncRules,
|
|
2582
|
+
hashBlob,
|
|
2583
|
+
hashSecret,
|
|
1957
2584
|
isApplyFailure,
|
|
1958
2585
|
isAtomicOp,
|
|
2586
|
+
isBlobRef,
|
|
1959
2587
|
isCollectionSyncScoped,
|
|
2588
|
+
isKoraBytesValue,
|
|
2589
|
+
isLegacyNumericByteObject,
|
|
1960
2590
|
isValidOperation,
|
|
1961
2591
|
isValidUUIDv7,
|
|
1962
2592
|
mergeVectors,
|
|
@@ -1964,13 +2594,17 @@ export {
|
|
|
1964
2594
|
migrationStepsToSQL,
|
|
1965
2595
|
op,
|
|
1966
2596
|
resolveAtomicOp,
|
|
2597
|
+
revealSecret,
|
|
1967
2598
|
rollbackStepsToSQL,
|
|
1968
2599
|
serializeVector,
|
|
1969
2600
|
t,
|
|
1970
2601
|
toAtomicOp,
|
|
2602
|
+
transformSecretFieldsForWrite,
|
|
1971
2603
|
validateRecord,
|
|
1972
2604
|
validateTransition,
|
|
1973
2605
|
vectorsEqual,
|
|
1974
|
-
verifyOperationIntegrity
|
|
2606
|
+
verifyOperationIntegrity,
|
|
2607
|
+
verifySecret,
|
|
2608
|
+
verifySecretValue
|
|
1975
2609
|
};
|
|
1976
2610
|
//# sourceMappingURL=index.js.map
|