@korajs/core 1.0.0-beta.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/dist/bindings/index.d.cts +2 -2
- package/dist/bindings/index.d.ts +2 -2
- package/dist/{build-scope-map-BIeawJzC.d.cts → build-scope-map-DtTr9C7A.d.cts} +1 -1
- package/dist/{build-scope-map-DOf4JLTh.d.ts → build-scope-map-DvOHbYjn.d.ts} +1 -1
- package/dist/{events-BynBOsO3.d.cts → events-CqDkr1LN.d.cts} +101 -2
- package/dist/{events-BynBOsO3.d.ts → events-CqDkr1LN.d.ts} +101 -2
- package/dist/index.cjs +528 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +141 -6
- package/dist/index.d.ts +141 -6
- package/dist/index.js +516 -2
- package/dist/index.js.map +1 -1
- package/dist/internal.d.cts +2 -2
- package/dist/internal.d.ts +2 -2
- package/dist/{operation-D5WOZYvy.d.ts → operation-BJtu18Fi.d.ts} +1 -1
- package/dist/{operation-BpZlYSpe.d.cts → operation-CFoY-ZmB.d.cts} +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -109,6 +109,226 @@ function formatUUID(bytes) {
|
|
|
109
109
|
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
|
|
110
110
|
}
|
|
111
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
|
+
|
|
112
332
|
// src/operations/apply-result.ts
|
|
113
333
|
var APPLY_RESULTS = ["applied", "duplicate", "skipped", "rejected", "deferred"];
|
|
114
334
|
var APPLY_FAILURE_CODES = {
|
|
@@ -874,6 +1094,17 @@ function mapFieldType(descriptor) {
|
|
|
874
1094
|
case "array":
|
|
875
1095
|
return "TEXT";
|
|
876
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";
|
|
877
1108
|
case "richtext":
|
|
878
1109
|
return "BLOB";
|
|
879
1110
|
}
|
|
@@ -1107,6 +1338,160 @@ var ArrayFieldBuilder = class _ArrayFieldBuilder extends FieldBuilder {
|
|
|
1107
1338
|
};
|
|
1108
1339
|
}
|
|
1109
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
|
+
};
|
|
1110
1495
|
var t = {
|
|
1111
1496
|
string() {
|
|
1112
1497
|
return new FieldBuilder("string", true, void 0, false);
|
|
@@ -1128,6 +1513,18 @@ var t = {
|
|
|
1128
1513
|
},
|
|
1129
1514
|
array(itemBuilder) {
|
|
1130
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);
|
|
1131
1528
|
}
|
|
1132
1529
|
};
|
|
1133
1530
|
|
|
@@ -1292,7 +1689,98 @@ function validateFieldValue(collection, fieldName, descriptor, value) {
|
|
|
1292
1689
|
}
|
|
1293
1690
|
break;
|
|
1294
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;
|
|
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";
|
|
1295
1779
|
}
|
|
1780
|
+
if (Array.isArray(value)) {
|
|
1781
|
+
return "array";
|
|
1782
|
+
}
|
|
1783
|
+
return typeof value;
|
|
1296
1784
|
}
|
|
1297
1785
|
function jsTypeForKind(kind) {
|
|
1298
1786
|
switch (kind) {
|
|
@@ -1773,6 +2261,14 @@ function mapFieldType2(descriptor) {
|
|
|
1773
2261
|
return "INTEGER";
|
|
1774
2262
|
case "array":
|
|
1775
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";
|
|
1776
2272
|
case "richtext":
|
|
1777
2273
|
return "BLOB";
|
|
1778
2274
|
}
|
|
@@ -1791,7 +2287,14 @@ var SCALAR_TYPE_MAP = {
|
|
|
1791
2287
|
number: "double",
|
|
1792
2288
|
boolean: "bool",
|
|
1793
2289
|
timestamp: "int64",
|
|
1794
|
-
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"
|
|
1795
2298
|
};
|
|
1796
2299
|
var ARRAY_ITEM_TYPE_MAP = {
|
|
1797
2300
|
string: "string",
|
|
@@ -2034,6 +2537,7 @@ export {
|
|
|
2034
2537
|
MergeConflictError,
|
|
2035
2538
|
MigrationBuilder,
|
|
2036
2539
|
MigrationRollbackError,
|
|
2540
|
+
MissingSecretKeyError,
|
|
2037
2541
|
OperationError,
|
|
2038
2542
|
RemoteClockDriftError,
|
|
2039
2543
|
RollbackBuilder,
|
|
@@ -2050,16 +2554,19 @@ export {
|
|
|
2050
2554
|
collectSchemaScopeFields,
|
|
2051
2555
|
collectSchemaScopeValueKeys,
|
|
2052
2556
|
computeDelta,
|
|
2557
|
+
createBlobRef,
|
|
2053
2558
|
createOperation,
|
|
2054
2559
|
createReversibleMigration,
|
|
2055
2560
|
createVersionVector,
|
|
2056
2561
|
decodeBytesFromOpData,
|
|
2562
|
+
decryptSecret,
|
|
2057
2563
|
defaultApplyFailureReason,
|
|
2058
2564
|
defaultSequenceFormat,
|
|
2059
2565
|
defineSchema,
|
|
2060
2566
|
deserializeVector,
|
|
2061
2567
|
dominates,
|
|
2062
2568
|
encodeBytesForOpData,
|
|
2569
|
+
encryptSecret,
|
|
2063
2570
|
extractScopeValuesFromClaims,
|
|
2064
2571
|
extractTimestamp,
|
|
2065
2572
|
formatSequenceValue,
|
|
@@ -2072,8 +2579,11 @@ export {
|
|
|
2072
2579
|
getKoraErrorFix,
|
|
2073
2580
|
getTransitionMap,
|
|
2074
2581
|
hasSchemaSyncRules,
|
|
2582
|
+
hashBlob,
|
|
2583
|
+
hashSecret,
|
|
2075
2584
|
isApplyFailure,
|
|
2076
2585
|
isAtomicOp,
|
|
2586
|
+
isBlobRef,
|
|
2077
2587
|
isCollectionSyncScoped,
|
|
2078
2588
|
isKoraBytesValue,
|
|
2079
2589
|
isLegacyNumericByteObject,
|
|
@@ -2084,13 +2594,17 @@ export {
|
|
|
2084
2594
|
migrationStepsToSQL,
|
|
2085
2595
|
op,
|
|
2086
2596
|
resolveAtomicOp,
|
|
2597
|
+
revealSecret,
|
|
2087
2598
|
rollbackStepsToSQL,
|
|
2088
2599
|
serializeVector,
|
|
2089
2600
|
t,
|
|
2090
2601
|
toAtomicOp,
|
|
2602
|
+
transformSecretFieldsForWrite,
|
|
2091
2603
|
validateRecord,
|
|
2092
2604
|
validateTransition,
|
|
2093
2605
|
vectorsEqual,
|
|
2094
|
-
verifyOperationIntegrity
|
|
2606
|
+
verifyOperationIntegrity,
|
|
2607
|
+
verifySecret,
|
|
2608
|
+
verifySecretValue
|
|
2095
2609
|
};
|
|
2096
2610
|
//# sourceMappingURL=index.js.map
|