@harperfast/harper-pro 5.0.22 → 5.0.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/core/DESIGN.md +2 -0
  2. package/core/bin/copyDb.ts +21 -2
  3. package/core/package-lock.json +82 -70
  4. package/core/resources/RecordEncoder.ts +21 -3
  5. package/core/resources/Table.ts +18 -6
  6. package/core/resources/blob.ts +18 -4
  7. package/core/resources/databases.ts +18 -0
  8. package/core/utility/logging/logRotator.js +3 -4
  9. package/dist/core/bin/copyDb.js +22 -1
  10. package/dist/core/bin/copyDb.js.map +1 -1
  11. package/dist/core/resources/RecordEncoder.js +14 -2
  12. package/dist/core/resources/RecordEncoder.js.map +1 -1
  13. package/dist/core/resources/Table.js +19 -6
  14. package/dist/core/resources/Table.js.map +1 -1
  15. package/dist/core/resources/blob.js +19 -4
  16. package/dist/core/resources/blob.js.map +1 -1
  17. package/dist/core/resources/databases.js +20 -0
  18. package/dist/core/resources/databases.js.map +1 -1
  19. package/dist/core/utility/logging/logRotator.js +3 -3
  20. package/dist/core/utility/logging/logRotator.js.map +1 -1
  21. package/npm-shrinkwrap.json +82 -70
  22. package/package.json +3 -3
  23. package/studio/web/assets/{index-CKW3SZJG.js → index-C5Er24-c.js} +16 -16
  24. package/studio/web/assets/index-C5Er24-c.js.map +1 -0
  25. package/studio/web/assets/index-j0M5ZXh5.css +1 -0
  26. package/studio/web/assets/{index.lazy-D9dMKjQH.js → index.lazy-C5LLrJkX.js} +2 -2
  27. package/studio/web/assets/{index.lazy-D9dMKjQH.js.map → index.lazy-C5LLrJkX.js.map} +1 -1
  28. package/studio/web/assets/{profile-DAsdweRg.js → profile-DdGCk1RA.js} +2 -2
  29. package/studio/web/assets/{profile-DAsdweRg.js.map → profile-DdGCk1RA.js.map} +1 -1
  30. package/studio/web/assets/{status-KVqwJsbk.js → status-t7ZjUhlN.js} +2 -2
  31. package/studio/web/assets/{status-KVqwJsbk.js.map → status-t7ZjUhlN.js.map} +1 -1
  32. package/studio/web/index.html +2 -2
  33. package/studio/web/assets/index-BGocx0nU.css +0 -1
  34. package/studio/web/assets/index-CKW3SZJG.js.map +0 -1
package/core/DESIGN.md CHANGED
@@ -38,6 +38,8 @@ When `migrateOnStart` opens a source LMDB primary store to read records out for
38
38
 
39
39
  Harper's normal `databases.ts` path already does this (search for `dbiInit.compression = primaryKeyAttribute.compression`); the migration path in `bin/copyDb.ts` has to match.
40
40
 
41
+ The same source-dbi open has a second non-obvious requirement: assign `sourceDbi.encoder.rootStore = sourceRootStore` for the primary store. The primary dbi decodes through a `RecordEncoder`, and decoding a record that holds a file-backed blob reference resolves that reference against `rootStore` (it locates the blob file). With `rootStore` unset, the `Blob` msgpackr extension throws `No store specified, cannot load blob from storage`; `RecordEncoder.decode` swallows the error and yields `null`, and `copyDbToRocks` then skips the `null` value — so every record with a file-backed blob is silently dropped from the migration. The runtime path gets `rootStore` from `handleLocalTimeForGets`; the migration path opens the source dbi raw and must set it explicitly (issue #857).
42
+
41
43
  ## Schema migration and `runIndexing` internals (`databases.ts`)
42
44
 
43
45
  When `table()` is called with an attribute newly marked `indexed: true` (or with any change that requires re-building the secondary index), `runIndexing` is launched asynchronously and `Table.indexingOperation` is set to its promise. While running:
@@ -376,7 +376,7 @@ export async function migrateOnStart() {
376
376
  }
377
377
  }
378
378
 
379
- async function copyDbToRocks(sourceRootStore, sourceDatabase: string, targetPath: string) {
379
+ export async function copyDbToRocks(sourceRootStore, sourceDatabase: string, targetPath: string) {
380
380
  console.log(`Migrating database ${sourceDatabase} to RocksDB at ${targetPath}`);
381
381
  const sourceDbisDb = sourceRootStore.dbisDb;
382
382
 
@@ -413,6 +413,11 @@ async function copyDbToRocks(sourceRootStore, sourceDatabase: string, targetPath
413
413
  const dbiInit = new OpenDBIObject(!isPrimary, isPrimary);
414
414
  dbiInit.compression = attribute.compression;
415
415
  const sourceDbi = sourceRootStore.openDB(key, dbiInit);
416
+ // The primary dbi uses a RecordEncoder, whose decode resolves file-backed blob references
417
+ // against `rootStore`. Without it, decoding any record that holds a blob throws "No store
418
+ // specified, cannot load blob from storage", the error is swallowed (record decodes to null),
419
+ // and the record is silently dropped from the migration (HarperFast/harper#857).
420
+ if (isPrimary && sourceDbi.encoder) sourceDbi.encoder.rootStore = sourceRootStore;
416
421
 
417
422
  let targetDbi;
418
423
  if (!isPrimary) {
@@ -425,9 +430,23 @@ async function copyDbToRocks(sourceRootStore, sourceDatabase: string, targetPath
425
430
  existingEncoder.isRocksDB = true;
426
431
  existingEncoder.rootStore = targetRootStore;
427
432
  const tempEncoder = new RecordEncoder({ name: key }) as any;
433
+ // msgpackr's pack closure captures `packr = this` at construction, so during
434
+ // re-encoding the structure callbacks resolve to tempEncoder's getStructures/
435
+ // saveStructures (invoked with this === tempEncoder), not existingEncoder's.
436
+ // tempEncoder must therefore carry the RocksDB wiring too, or getStructures hits
437
+ // the non-RocksDB branch where the captured super is undefined and throws.
438
+ tempEncoder.name = key;
439
+ tempEncoder.isRocksDB = true;
440
+ tempEncoder.rootStore = targetRootStore;
428
441
  existingEncoder.encode = tempEncoder.encode;
429
- existingEncoder.saveStructures = tempEncoder.saveStructures;
430
442
  existingEncoder.getStructures = tempEncoder.getStructures;
443
+ // The shared structures dictionary is copied verbatim from the source by
444
+ // copyStructures() below, so re-encoding never needs to persist new structures.
445
+ // A no-op saveStructures avoids opening a targetRootStore.transactionSync() in the
446
+ // middle of each record's encode, which otherwise discards the targetDbi record writes.
447
+ const noopSaveStructures = () => true;
448
+ existingEncoder.saveStructures = noopSaveStructures;
449
+ tempEncoder.saveStructures = noopSaveStructures;
431
450
  }
432
451
 
433
452
  copyStructures(sourceDbi, key);
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "harper",
3
- "version": "5.0.22",
3
+ "version": "5.0.24",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "harper",
9
- "version": "5.0.22",
9
+ "version": "5.0.24",
10
10
  "license": "Apache-2.0",
11
11
  "dependencies": {
12
12
  "@aws-sdk/client-s3": "^3.1012.0",
@@ -18,7 +18,7 @@
18
18
  "@fastify/cors": "^11.2.0",
19
19
  "@fastify/static": "^9.1.3",
20
20
  "@harperfast/extended-iterable": "^1.0.1",
21
- "@harperfast/rocksdb-js": "^1.2.0",
21
+ "@harperfast/rocksdb-js": "^1.4.0",
22
22
  "@turf/area": "6.5.0",
23
23
  "@turf/boolean-contains": "6.5.0",
24
24
  "@turf/boolean-disjoint": "6.5.0",
@@ -53,7 +53,7 @@
53
53
  "json-bigint-fixes": "1.1.0",
54
54
  "jsonata": "1.8.7",
55
55
  "jsonwebtoken": "9.0.3",
56
- "lmdb": "3.5.4",
56
+ "lmdb": "3.5.5",
57
57
  "lodash": "^4.17.23",
58
58
  "mathjs": "11.12.0",
59
59
  "micromatch": "^4.0.8",
@@ -2218,9 +2218,9 @@
2218
2218
  "license": "Apache-2.0"
2219
2219
  },
2220
2220
  "node_modules/@harperfast/rocksdb-js": {
2221
- "version": "1.2.0",
2222
- "resolved": "https://registry.npmjs.org/@harperfast/rocksdb-js/-/rocksdb-js-1.2.0.tgz",
2223
- "integrity": "sha512-s/JOC720wmJCTG9rBKYJMY73yfZU1gpWxAeFTJXolFSYvc6uQzPwsAfxYxayziZrjO5LcmHg6dlNR5JiZo6ZqA==",
2221
+ "version": "1.4.0",
2222
+ "resolved": "https://registry.npmjs.org/@harperfast/rocksdb-js/-/rocksdb-js-1.4.0.tgz",
2223
+ "integrity": "sha512-Qa62jWYG5GO+OI3gEHJI3WyhzgA6OdFLthbmB7TRJJMa2Zu8s3psKuAnM0t2v963ohRFF4UbyRw24zt5PX3k3A==",
2224
2224
  "license": "Apache-2.0",
2225
2225
  "dependencies": {
2226
2226
  "@harperfast/extended-iterable": "1.0.3",
@@ -2234,20 +2234,20 @@
2234
2234
  "node": ">=18"
2235
2235
  },
2236
2236
  "optionalDependencies": {
2237
- "@harperfast/rocksdb-js-darwin-arm64": "1.2.0",
2238
- "@harperfast/rocksdb-js-darwin-x64": "1.2.0",
2239
- "@harperfast/rocksdb-js-linux-arm64-glibc": "1.2.0",
2240
- "@harperfast/rocksdb-js-linux-arm64-musl": "1.2.0",
2241
- "@harperfast/rocksdb-js-linux-x64-glibc": "1.2.0",
2242
- "@harperfast/rocksdb-js-linux-x64-musl": "1.2.0",
2243
- "@harperfast/rocksdb-js-win32-arm64": "1.2.0",
2244
- "@harperfast/rocksdb-js-win32-x64": "1.2.0"
2237
+ "@harperfast/rocksdb-js-darwin-arm64": "1.4.0",
2238
+ "@harperfast/rocksdb-js-darwin-x64": "1.4.0",
2239
+ "@harperfast/rocksdb-js-linux-arm64-glibc": "1.4.0",
2240
+ "@harperfast/rocksdb-js-linux-arm64-musl": "1.4.0",
2241
+ "@harperfast/rocksdb-js-linux-x64-glibc": "1.4.0",
2242
+ "@harperfast/rocksdb-js-linux-x64-musl": "1.4.0",
2243
+ "@harperfast/rocksdb-js-win32-arm64": "1.4.0",
2244
+ "@harperfast/rocksdb-js-win32-x64": "1.4.0"
2245
2245
  }
2246
2246
  },
2247
2247
  "node_modules/@harperfast/rocksdb-js-darwin-arm64": {
2248
- "version": "1.2.0",
2249
- "resolved": "https://registry.npmjs.org/@harperfast/rocksdb-js-darwin-arm64/-/rocksdb-js-darwin-arm64-1.2.0.tgz",
2250
- "integrity": "sha512-+rCncLUYuBWFzVcfP5ZrtX0ZXPw1eVtnUoJMV2cvOIl6sOqkKkPVd7o0kQOoc9GWgqEn0+QB/AWcFSDUmPOuoQ==",
2248
+ "version": "1.4.0",
2249
+ "resolved": "https://registry.npmjs.org/@harperfast/rocksdb-js-darwin-arm64/-/rocksdb-js-darwin-arm64-1.4.0.tgz",
2250
+ "integrity": "sha512-78nT13T3P0Am0UfppPkMgUcTqS5Xx2ftXGl9IsmjWfrDawM1HOJt00n0GtPLoqOqrs80QVGbH9qgBZ22Bpty6Q==",
2251
2251
  "cpu": [
2252
2252
  "arm64"
2253
2253
  ],
@@ -2261,9 +2261,9 @@
2261
2261
  }
2262
2262
  },
2263
2263
  "node_modules/@harperfast/rocksdb-js-darwin-x64": {
2264
- "version": "1.2.0",
2265
- "resolved": "https://registry.npmjs.org/@harperfast/rocksdb-js-darwin-x64/-/rocksdb-js-darwin-x64-1.2.0.tgz",
2266
- "integrity": "sha512-kHvbdzuVP4oZjEBmI2+4bcQFYh2vkz3gCP+p6uHlZwD8cBIohIX19fw66U6tVxEU5Cpt9Q/D+rfhbYnbwqPKbA==",
2264
+ "version": "1.4.0",
2265
+ "resolved": "https://registry.npmjs.org/@harperfast/rocksdb-js-darwin-x64/-/rocksdb-js-darwin-x64-1.4.0.tgz",
2266
+ "integrity": "sha512-Ck+m4bfaiGikq1I0IAHS8AELnRL+GeKB99hR8ASR7DRAvQqYGYbPtUQQUKIQk9/8S8LviKUfto/cla1nRAcslg==",
2267
2267
  "cpu": [
2268
2268
  "x64"
2269
2269
  ],
@@ -2277,12 +2277,15 @@
2277
2277
  }
2278
2278
  },
2279
2279
  "node_modules/@harperfast/rocksdb-js-linux-arm64-glibc": {
2280
- "version": "1.2.0",
2281
- "resolved": "https://registry.npmjs.org/@harperfast/rocksdb-js-linux-arm64-glibc/-/rocksdb-js-linux-arm64-glibc-1.2.0.tgz",
2282
- "integrity": "sha512-q7mRuJTfrZ3iKxxeOx9flxDuuj3jdoOj8PvtnGzMyUR4BnBa7K/ly2KNcdT3zmOQA+sJM0XN/Uq2iusTWxhATw==",
2280
+ "version": "1.4.0",
2281
+ "resolved": "https://registry.npmjs.org/@harperfast/rocksdb-js-linux-arm64-glibc/-/rocksdb-js-linux-arm64-glibc-1.4.0.tgz",
2282
+ "integrity": "sha512-1yJkGMYR5Zh45j6joCu0cgQ+LPlsU0hjiPfv0f0cmXdjPiccXu7TemADjf3fnLz6p4p2VLP46s1fwJL7W+4lFw==",
2283
2283
  "cpu": [
2284
2284
  "arm64"
2285
2285
  ],
2286
+ "libc": [
2287
+ "glibc"
2288
+ ],
2286
2289
  "license": "Apache-2.0",
2287
2290
  "optional": true,
2288
2291
  "os": [
@@ -2293,12 +2296,15 @@
2293
2296
  }
2294
2297
  },
2295
2298
  "node_modules/@harperfast/rocksdb-js-linux-arm64-musl": {
2296
- "version": "1.2.0",
2297
- "resolved": "https://registry.npmjs.org/@harperfast/rocksdb-js-linux-arm64-musl/-/rocksdb-js-linux-arm64-musl-1.2.0.tgz",
2298
- "integrity": "sha512-f5Myta9ke0DBF901ehckMflm1DIOTNaKWMfzoJVuQIHwW8QT1+ZFUBz2dLKnoveIKWuE8r4TVW29YDW/oo6m2g==",
2299
+ "version": "1.4.0",
2300
+ "resolved": "https://registry.npmjs.org/@harperfast/rocksdb-js-linux-arm64-musl/-/rocksdb-js-linux-arm64-musl-1.4.0.tgz",
2301
+ "integrity": "sha512-wIUqCl95Fs7WL0Ecm2j6Lu9TAFMbeDageMn2MfzqTtjx6fcQRL9dPH9o8HfIOfQ6Q0T0HTPpRwBTAtryYLmcFA==",
2299
2302
  "cpu": [
2300
2303
  "arm64"
2301
2304
  ],
2305
+ "libc": [
2306
+ "musl"
2307
+ ],
2302
2308
  "license": "Apache-2.0",
2303
2309
  "optional": true,
2304
2310
  "os": [
@@ -2309,12 +2315,15 @@
2309
2315
  }
2310
2316
  },
2311
2317
  "node_modules/@harperfast/rocksdb-js-linux-x64-glibc": {
2312
- "version": "1.2.0",
2313
- "resolved": "https://registry.npmjs.org/@harperfast/rocksdb-js-linux-x64-glibc/-/rocksdb-js-linux-x64-glibc-1.2.0.tgz",
2314
- "integrity": "sha512-gMVjvANBh15mGwNntHFL1jPsfn1stW+IV8CT+QYbdR6dZ6XboCdnxFalOiyeRsY18ouNURKx3UzxnAelAMSqhA==",
2318
+ "version": "1.4.0",
2319
+ "resolved": "https://registry.npmjs.org/@harperfast/rocksdb-js-linux-x64-glibc/-/rocksdb-js-linux-x64-glibc-1.4.0.tgz",
2320
+ "integrity": "sha512-J8anAA31Dv6+h+Uu/mDlszQOoEZiTOGqrLVbWQ7/W5cET2pKZ9IC6NJjvG3rjkkFT82G+spqJ+aESY2yuEJv5Q==",
2315
2321
  "cpu": [
2316
2322
  "x64"
2317
2323
  ],
2324
+ "libc": [
2325
+ "glibc"
2326
+ ],
2318
2327
  "license": "Apache-2.0",
2319
2328
  "optional": true,
2320
2329
  "os": [
@@ -2325,12 +2334,15 @@
2325
2334
  }
2326
2335
  },
2327
2336
  "node_modules/@harperfast/rocksdb-js-linux-x64-musl": {
2328
- "version": "1.2.0",
2329
- "resolved": "https://registry.npmjs.org/@harperfast/rocksdb-js-linux-x64-musl/-/rocksdb-js-linux-x64-musl-1.2.0.tgz",
2330
- "integrity": "sha512-u0LN3maKDemGDGfA77aYr2HSpLl9ilOhQOFcLSjAtYrZlquxvNRGQR60aE0VIC/quui6Xdu7zHOVY9aLZpqW3g==",
2337
+ "version": "1.4.0",
2338
+ "resolved": "https://registry.npmjs.org/@harperfast/rocksdb-js-linux-x64-musl/-/rocksdb-js-linux-x64-musl-1.4.0.tgz",
2339
+ "integrity": "sha512-bHJvxKlx/UqeG++aLpEcm6DDzNeGvDWvBZ+LKstVmF4mNZnR/QZ8TX9dYEGHFHMB+/PVrotRzUaMA5jgjfaMFQ==",
2331
2340
  "cpu": [
2332
2341
  "x64"
2333
2342
  ],
2343
+ "libc": [
2344
+ "musl"
2345
+ ],
2334
2346
  "license": "Apache-2.0",
2335
2347
  "optional": true,
2336
2348
  "os": [
@@ -2341,9 +2353,9 @@
2341
2353
  }
2342
2354
  },
2343
2355
  "node_modules/@harperfast/rocksdb-js-win32-arm64": {
2344
- "version": "1.2.0",
2345
- "resolved": "https://registry.npmjs.org/@harperfast/rocksdb-js-win32-arm64/-/rocksdb-js-win32-arm64-1.2.0.tgz",
2346
- "integrity": "sha512-ZNF5er1udx3Ntl9dE1vtspT8jUokA503Evb1Z/qGDqVeewC2QAZvAI3R4T82qILQtirUjtbUhWOlJEJyv9K9XQ==",
2356
+ "version": "1.4.0",
2357
+ "resolved": "https://registry.npmjs.org/@harperfast/rocksdb-js-win32-arm64/-/rocksdb-js-win32-arm64-1.4.0.tgz",
2358
+ "integrity": "sha512-r6F+ZQWZmppu4mkuZwlyYDaXWXZ/AQldP2qdIRhICxXNfxcvQUxW0+CKmfnor3X3GNk46ThIaP7tM08fu7/Zag==",
2347
2359
  "cpu": [
2348
2360
  "arm64"
2349
2361
  ],
@@ -2357,9 +2369,9 @@
2357
2369
  }
2358
2370
  },
2359
2371
  "node_modules/@harperfast/rocksdb-js-win32-x64": {
2360
- "version": "1.2.0",
2361
- "resolved": "https://registry.npmjs.org/@harperfast/rocksdb-js-win32-x64/-/rocksdb-js-win32-x64-1.2.0.tgz",
2362
- "integrity": "sha512-B6up+3PzrGMpSK1vx8cf5bMnJH5z/Wb1WK3OaQKXmS8xGUhAPXzE46Jjb74VOj8tNaTF3sL3wZqPuwjEi2oqFQ==",
2372
+ "version": "1.4.0",
2373
+ "resolved": "https://registry.npmjs.org/@harperfast/rocksdb-js-win32-x64/-/rocksdb-js-win32-x64-1.4.0.tgz",
2374
+ "integrity": "sha512-msYlg3D2sm33sw7Uyiuxw04Jt8mrzcKOhjHFZbGAM9bqlUE0lar3txcO03oHwcKYVyoIEsnYgyddQFHdRLAU+A==",
2363
2375
  "cpu": [
2364
2376
  "x64"
2365
2377
  ],
@@ -2607,9 +2619,9 @@
2607
2619
  }
2608
2620
  },
2609
2621
  "node_modules/@lmdb/lmdb-darwin-arm64": {
2610
- "version": "3.5.4",
2611
- "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-3.5.4.tgz",
2612
- "integrity": "sha512-Kk4Kz3iyu1QiLsLZBS9Af1eSKUC8VR2T+/jyE2iAyuGw2VwK08pp5iTbZnXn6sWu0LogO/RFktMxOjiDA2sS3w==",
2622
+ "version": "3.5.5",
2623
+ "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-3.5.5.tgz",
2624
+ "integrity": "sha512-hcSIUgSblRtwEuPzV/88cTbEPxaWfxVSrTaMIVUreyAA02ysE3S9tsiQdmT7KNmUaXlwgRGs+x+GaVDCuk7Prg==",
2613
2625
  "cpu": [
2614
2626
  "arm64"
2615
2627
  ],
@@ -2620,9 +2632,9 @@
2620
2632
  ]
2621
2633
  },
2622
2634
  "node_modules/@lmdb/lmdb-darwin-x64": {
2623
- "version": "3.5.4",
2624
- "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-3.5.4.tgz",
2625
- "integrity": "sha512-BEe5Rp3trn26oxoXOVL5HVDoiYmjUDwr8NRPkBOdUdCSBEorKI+7JrZLRKAdxO+G6cGQLgseXk0gR7qIQa7aGw==",
2635
+ "version": "3.5.5",
2636
+ "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-3.5.5.tgz",
2637
+ "integrity": "sha512-cLlp9ypk3W9Hr4fuTYwg1kL2vPfz887v9NXiHQE1raSKtJ8/hnghekqUu62wUQdQPgThLV8Om+Ran08wWuM/3w==",
2626
2638
  "cpu": [
2627
2639
  "x64"
2628
2640
  ],
@@ -2633,9 +2645,9 @@
2633
2645
  ]
2634
2646
  },
2635
2647
  "node_modules/@lmdb/lmdb-linux-arm": {
2636
- "version": "3.5.4",
2637
- "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-3.5.4.tgz",
2638
- "integrity": "sha512-SGbFR7816uBcTHc2ZY4S6WyOkl9bICnzqTQd2Mv4V/j24cfds88xx2nC6cm/y8zGQL7Ds31YF/5NGxjgcdM5Hw==",
2648
+ "version": "3.5.5",
2649
+ "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-3.5.5.tgz",
2650
+ "integrity": "sha512-eDeK9vZww0dIXlRCZa7pvbEXSUpUNIJKUs13FxDzazL8CRLDLggbeirm5m0hZvJcRgZzTYGESE8UUEBqHcdZ9Q==",
2639
2651
  "cpu": [
2640
2652
  "arm"
2641
2653
  ],
@@ -2646,9 +2658,9 @@
2646
2658
  ]
2647
2659
  },
2648
2660
  "node_modules/@lmdb/lmdb-linux-arm64": {
2649
- "version": "3.5.4",
2650
- "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-3.5.4.tgz",
2651
- "integrity": "sha512-cUXEengO8o60v1SWerJTH4/RH4U3+9jC0/4njp2Z9NdmvaGzhKsbRM2wpXuRYrN8tytsoJCg0SvWEWwHAwLbCA==",
2661
+ "version": "3.5.5",
2662
+ "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-3.5.5.tgz",
2663
+ "integrity": "sha512-tn8KSYdvX5sietpuGyJJSXocvQQtWlenzquap/Mzr+wWx5jrjK5I3nEAIMFxvTtuMMrpPTGHGNuE/Hanh3o0EQ==",
2652
2664
  "cpu": [
2653
2665
  "arm64"
2654
2666
  ],
@@ -2659,9 +2671,9 @@
2659
2671
  ]
2660
2672
  },
2661
2673
  "node_modules/@lmdb/lmdb-linux-x64": {
2662
- "version": "3.5.4",
2663
- "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-3.5.4.tgz",
2664
- "integrity": "sha512-Gxq8jpgOWXwd0PUR+c9R2Ik1/uBnGd5GMIIzRRDqABCkvmjtC3KWcyhesV9jSPCz759isl0NlbsstZ2oyvk8lA==",
2674
+ "version": "3.5.5",
2675
+ "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-3.5.5.tgz",
2676
+ "integrity": "sha512-o6VRcJjZ6Zc67MnymLEa3AtbYSreFzm6Um6LMHh4lkynlE5Qk/XUHXU2beTZBdB3QuZBiMoXO2gjPSgP0tYUKg==",
2665
2677
  "cpu": [
2666
2678
  "x64"
2667
2679
  ],
@@ -2672,9 +2684,9 @@
2672
2684
  ]
2673
2685
  },
2674
2686
  "node_modules/@lmdb/lmdb-win32-arm64": {
2675
- "version": "3.5.4",
2676
- "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-arm64/-/lmdb-win32-arm64-3.5.4.tgz",
2677
- "integrity": "sha512-pKv1DJ1bPZAaHkdFsSz5IDfUG8x9vntgquXF9/Dm2xuupcIe/EkLzylpoBxppFVK5vzbV561Dq26jNY2fIMA7g==",
2687
+ "version": "3.5.5",
2688
+ "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-arm64/-/lmdb-win32-arm64-3.5.5.tgz",
2689
+ "integrity": "sha512-k0k/CwywhymTAJT7Uj5PdehH31Vem2ov/Jp0rn5McuJEHgzj7vbE0cmnByhjrQv+VeHLALI8wUZNicqwocPB4Q==",
2678
2690
  "cpu": [
2679
2691
  "arm64"
2680
2692
  ],
@@ -2685,9 +2697,9 @@
2685
2697
  ]
2686
2698
  },
2687
2699
  "node_modules/@lmdb/lmdb-win32-x64": {
2688
- "version": "3.5.4",
2689
- "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-3.5.4.tgz",
2690
- "integrity": "sha512-JF1BmLCm9kGEVZgYmJq43zeQVdHVgAJnTi/NURWEsy6L1ZrrlSmdltS+D17QN4LODwf+1LMXAA9auIZVXtWwzw==",
2700
+ "version": "3.5.5",
2701
+ "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-3.5.5.tgz",
2702
+ "integrity": "sha512-dkV3r04ro8MfhBaPj4ZsARy0LIP3sK+rm2LAuVe2QLCepBIpSzRx8p8RLWfRfkKV5YNyJ3c0NUJxGNMqtTB6Fg==",
2691
2703
  "cpu": [
2692
2704
  "x64"
2693
2705
  ],
@@ -7535,9 +7547,9 @@
7535
7547
  "license": "MIT"
7536
7548
  },
7537
7549
  "node_modules/lmdb": {
7538
- "version": "3.5.4",
7539
- "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-3.5.4.tgz",
7540
- "integrity": "sha512-9FKQA6G1MMtqNxfxvSBNXD/axeG2QRjYbNh0/ykRL5xYcRbCm2vXq7B9bhc7nSuKdHzr8/BHIwfPuYYH1UsXXw==",
7550
+ "version": "3.5.5",
7551
+ "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-3.5.5.tgz",
7552
+ "integrity": "sha512-e2q1RtHdJoDaQQNMJ55wJnD5BqpvzoJIEL3NBxfZXNBjYxY0QjCh3RSL/sqmi+y9l97cERdO4WwGFfYYVft1fA==",
7541
7553
  "hasInstallScript": true,
7542
7554
  "license": "MIT",
7543
7555
  "dependencies": {
@@ -7552,13 +7564,13 @@
7552
7564
  "download-lmdb-prebuilds": "bin/download-prebuilds.js"
7553
7565
  },
7554
7566
  "optionalDependencies": {
7555
- "@lmdb/lmdb-darwin-arm64": "3.5.4",
7556
- "@lmdb/lmdb-darwin-x64": "3.5.4",
7557
- "@lmdb/lmdb-linux-arm": "3.5.4",
7558
- "@lmdb/lmdb-linux-arm64": "3.5.4",
7559
- "@lmdb/lmdb-linux-x64": "3.5.4",
7560
- "@lmdb/lmdb-win32-arm64": "3.5.4",
7561
- "@lmdb/lmdb-win32-x64": "3.5.4"
7567
+ "@lmdb/lmdb-darwin-arm64": "3.5.5",
7568
+ "@lmdb/lmdb-darwin-x64": "3.5.5",
7569
+ "@lmdb/lmdb-linux-arm": "3.5.5",
7570
+ "@lmdb/lmdb-linux-arm64": "3.5.5",
7571
+ "@lmdb/lmdb-linux-x64": "3.5.5",
7572
+ "@lmdb/lmdb-win32-arm64": "3.5.5",
7573
+ "@lmdb/lmdb-win32-x64": "3.5.5"
7562
7574
  }
7563
7575
  },
7564
7576
  "node_modules/lmdb/node_modules/node-addon-api": {
@@ -17,7 +17,14 @@ import {
17
17
  } from './auditStore.ts';
18
18
  import * as harperLogger from '../utility/logging/harper_logger.js';
19
19
  import './blob.ts';
20
- import { blobsWereEncoded, decodeFromDatabase, deleteBlobsInObject, encodeBlobsWithFilePath } from './blob.ts';
20
+ import {
21
+ blobsWereEncoded,
22
+ decodeFromDatabase,
23
+ deleteBlobsInObject,
24
+ encodeBlobsWithFilePath,
25
+ findBlobsInObject,
26
+ getFileId,
27
+ } from './blob.ts';
21
28
  import { getThisNodeId } from './nodeIdMapping.ts';
22
29
  import { recordAction } from './analytics/write.ts';
23
30
  import { RocksDatabase } from '@harperfast/rocksdb-js';
@@ -606,8 +613,19 @@ export function recordUpdater(store, tableId, auditStore) {
606
613
  // we use resolveRecord outside of transaction, so must explicitly make it conditional
607
614
  if (resolveRecord) putOptions.ifVersion = ifVersion = existingEntry?.version ?? null;
608
615
  if (existingEntry && existingEntry.value && type !== 'message' && existingEntry.metadataFlags & HAS_BLOBS) {
609
- // delete the old blobs
610
- deleteBlobsInObject(existingEntry.value);
616
+ // Delete the prior row's blob files — except any the new record still references.
617
+ // Without the retention check, updating an unrelated attribute on a row that
618
+ // carries a file-backed blob unlinks the blob ~deletionDelay ms later, leaving
619
+ // the new (otherwise valid) row pointing at a missing file. See HarperFast/harper#641
620
+ // (deployment tracking) for the production repro.
621
+ let retainedFileIds: Set<string> | undefined;
622
+ if (record) {
623
+ findBlobsInObject(record, (blob) => {
624
+ const fileId = getFileId(blob);
625
+ if (fileId) (retainedFileIds ??= new Set()).add(fileId);
626
+ });
627
+ }
628
+ deleteBlobsInObject(existingEntry.value, retainedFileIds);
611
629
  }
612
630
  let result: Promise<void>;
613
631
  if (record !== undefined) {
@@ -1441,6 +1441,7 @@ export function makeTable(options) {
1441
1441
  const lmdbTransaction = txnForContext({ transaction: new DatabaseTransaction() });
1442
1442
  let transaction = lmdbTransaction.getReadTxn();
1443
1443
  let options = { transaction };
1444
+ let committed = false;
1444
1445
  try {
1445
1446
  if (hasSourceGet || audit) {
1446
1447
  if (!existingRecord) return;
@@ -1460,19 +1461,30 @@ export function makeTable(options) {
1460
1461
  primaryStore.ifVersion?.(id, existingVersion, () => {
1461
1462
  updateIndices(id, existingRecord, null);
1462
1463
  });
1463
- return removeEntry(primaryStore, entry ?? primaryStore.getEntry(id), existingVersion);
1464
+ removeEntry(primaryStore, entry ?? primaryStore.getEntry(id), existingVersion);
1464
1465
  } else {
1465
1466
  updateIndices(id, existingRecord, null, options);
1466
- return removeEntry(primaryStore, entry ?? primaryStore.getEntry(id), options);
1467
+ removeEntry(primaryStore, entry ?? primaryStore.getEntry(id), options);
1467
1468
  }
1468
- } finally {
1469
+ committed = true;
1469
1470
  if (primaryStore.ifVersion) {
1470
1471
  // LMDB: committing the wrapper calls doneReadTxn(), removing it from trackedTxns
1471
1472
  return lmdbTransaction.commit();
1472
1473
  }
1473
1474
  // RocksDB: eviction writes went directly into the raw transaction via options;
1474
- // commit it directly, as DatabaseTransaction.commit() would abort it (no tracked writes)
1475
- return transaction?.commit();
1475
+ // commit it directly, as DatabaseTransaction.commit() would abort it (no tracked writes).
1476
+ // Wrap in Promise.resolve so callers can rely on a thenable return regardless of engine.
1477
+ return Promise.resolve((transaction as any).commit());
1478
+ } finally {
1479
+ if (!committed) {
1480
+ // Skip path or thrown error: abort instead of committing so we don't apply
1481
+ // partial work and the txn handle is released.
1482
+ if (primaryStore.ifVersion) {
1483
+ (lmdbTransaction as any).abort?.();
1484
+ } else {
1485
+ (transaction as any)?.abort?.();
1486
+ }
1487
+ }
1476
1488
  }
1477
1489
  }
1478
1490
  /**
@@ -4459,7 +4471,7 @@ export function makeTable(options) {
4459
4471
  const { key, value: record, version, expiresAt, metadataFlags } = entry;
4460
4472
  // if there is no auditing cleanup and we are tracking deletion, need to do cleanup of
4461
4473
  // these deletion entries (LMDB audit cleanup has its own scheduled job for this)
4462
- let resolution: Promise<void>;
4474
+ let resolution: Promise<void> | undefined;
4463
4475
  if (record === null && removeDeletedRecords && version + auditRetention < Date.now()) {
4464
4476
  // make sure it is still deleted when we do the removal
4465
4477
  resolution = removeEntry(primaryStore, entry, version);
@@ -970,12 +970,26 @@ export function decodeFromDatabase<T>(callback: () => T, rootStore: LMDBStore) {
970
970
  }
971
971
 
972
972
  /**
973
- * Delete blobs in an object, recursively searching for blobs
973
+ * Delete blobs in an object, recursively searching for blobs. When `retainedFileIds`
974
+ * is supplied, blobs whose fileId is in that set are skipped — used by the update path
975
+ * to avoid deleting a blob the new record still references.
976
+ *
977
+ * Background: `RecordEncoder` calls this with the *prior* row's value on every update.
978
+ * If a caller updates an unrelated attribute on a row that still carries a file-backed
979
+ * blob, the unfiltered delete unlinks the blob file ~`deletionDelay` ms later, even
980
+ * though the new row still references the same fileId. The retainedFileIds set
981
+ * prevents that data loss.
982
+ *
974
983
  * @param object
984
+ * @param retainedFileIds optional set of fileIds the caller wants to keep on disk
975
985
  */
976
- export function deleteBlobsInObject(object) {
977
- findBlobsInObject(object, (object) => {
978
- deleteBlob(object);
986
+ export function deleteBlobsInObject(object: any, retainedFileIds?: Set<string>): void {
987
+ findBlobsInObject(object, (blob) => {
988
+ if (retainedFileIds?.size) {
989
+ const fileId = getFileId(blob);
990
+ if (fileId && retainedFileIds.has(fileId)) return;
991
+ }
992
+ deleteBlob(blob);
979
993
  });
980
994
  }
981
995
 
@@ -1317,6 +1317,24 @@ async function runIndexing(Table, attributes, indicesToRemove) {
1317
1317
  }
1318
1318
  } catch (error) {
1319
1319
  logger.error('Error in indexing', error);
1320
+ // Persist indexingFailed so the next restart re-triggers the rebuild from an
1321
+ // explicitly failed state rather than silently looping. Without this,
1322
+ // indexingPID (written before runIndexing was called) stays in the descriptor
1323
+ // but indexingFailed is never set, leaving isIndexing stuck with no recovery
1324
+ // signal. Mirrors the hadIndexingErrors path. harper#843
1325
+ try {
1326
+ const puts: Promise<unknown>[] = [];
1327
+ for (const attribute of attributes) {
1328
+ attribute.indexingFailed = true;
1329
+ puts.push(Table.dbisDB.put(attribute.key, attribute));
1330
+ attribute.dbi.isIndexing = true;
1331
+ const activeDbi = Table.indices[attribute.name];
1332
+ if (activeDbi) activeDbi.isIndexing = true;
1333
+ }
1334
+ await Promise.all(puts);
1335
+ } catch (persistError) {
1336
+ logger.warn('Failed to persist indexing failure state', persistError);
1337
+ }
1320
1338
  }
1321
1339
  }
1322
1340
 
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { promises: fsProm, createReadStream, createWriteStream } = require('fs');
3
+ const { promises: fsProm, createReadStream, createWriteStream, mkdirSync } = require('fs');
4
4
  const { createGzip } = require('zlib');
5
5
  const { promisify } = require('util');
6
6
  const { pipeline } = require('stream');
@@ -17,8 +17,6 @@ const { onStorageReclamation } = require('../../server/storageReclamation.ts');
17
17
  const LOG_AUDIT_INTERVAL = 60000;
18
18
  const INT_SIZE_UNDEFINED_MSG =
19
19
  "'interval' and 'maxSize' are both undefined, to enable logging rotation at least one of these values must be defined in harperdb-config.yaml";
20
- const PATH_UNDEFINED_MSG =
21
- "'logging.rotation.path' is undefined, to enable logging rotation set this value in harperdb-config.yaml";
22
20
 
23
21
  let lastRotationTime;
24
22
  let setIntervalId;
@@ -47,8 +45,9 @@ function logRotator({ logger, maxSize, interval, retention, enabled, path: rotat
47
45
  }
48
46
 
49
47
  if (!rotatedLogDir) {
50
- throw new Error(PATH_UNDEFINED_MSG);
48
+ rotatedLogDir = path.join(path.dirname(logger.path), 'rotated');
51
49
  }
50
+ mkdirSync(rotatedLogDir, { recursive: true });
52
51
 
53
52
  // Convert maxSize param to bytes.
54
53
  let maxBytes;
@@ -39,6 +39,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.compactOnStart = compactOnStart;
40
40
  exports.copyDb = copyDb;
41
41
  exports.migrateOnStart = migrateOnStart;
42
+ exports.copyDbToRocks = copyDbToRocks;
42
43
  const databases_ts_1 = require("../resources/databases.js");
43
44
  const lmdb_1 = require("lmdb");
44
45
  const path_1 = require("path");
@@ -423,6 +424,12 @@ async function copyDbToRocks(sourceRootStore, sourceDatabase, targetPath) {
423
424
  const dbiInit = new OpenDBIObject_js_1.OpenDBIObject(!isPrimary, isPrimary);
424
425
  dbiInit.compression = attribute.compression;
425
426
  const sourceDbi = sourceRootStore.openDB(key, dbiInit);
427
+ // The primary dbi uses a RecordEncoder, whose decode resolves file-backed blob references
428
+ // against `rootStore`. Without it, decoding any record that holds a blob throws "No store
429
+ // specified, cannot load blob from storage", the error is swallowed (record decodes to null),
430
+ // and the record is silently dropped from the migration (HarperFast/harper#857).
431
+ if (isPrimary && sourceDbi.encoder)
432
+ sourceDbi.encoder.rootStore = sourceRootStore;
426
433
  let targetDbi;
427
434
  if (!isPrimary) {
428
435
  targetDbi = openRocksDb(targetPath, { dupSort: true, name: key });
@@ -435,9 +442,23 @@ async function copyDbToRocks(sourceRootStore, sourceDatabase, targetPath) {
435
442
  existingEncoder.isRocksDB = true;
436
443
  existingEncoder.rootStore = targetRootStore;
437
444
  const tempEncoder = new RecordEncoder_ts_1.RecordEncoder({ name: key });
445
+ // msgpackr's pack closure captures `packr = this` at construction, so during
446
+ // re-encoding the structure callbacks resolve to tempEncoder's getStructures/
447
+ // saveStructures (invoked with this === tempEncoder), not existingEncoder's.
448
+ // tempEncoder must therefore carry the RocksDB wiring too, or getStructures hits
449
+ // the non-RocksDB branch where the captured super is undefined and throws.
450
+ tempEncoder.name = key;
451
+ tempEncoder.isRocksDB = true;
452
+ tempEncoder.rootStore = targetRootStore;
438
453
  existingEncoder.encode = tempEncoder.encode;
439
- existingEncoder.saveStructures = tempEncoder.saveStructures;
440
454
  existingEncoder.getStructures = tempEncoder.getStructures;
455
+ // The shared structures dictionary is copied verbatim from the source by
456
+ // copyStructures() below, so re-encoding never needs to persist new structures.
457
+ // A no-op saveStructures avoids opening a targetRootStore.transactionSync() in the
458
+ // middle of each record's encode, which otherwise discards the targetDbi record writes.
459
+ const noopSaveStructures = () => true;
460
+ existingEncoder.saveStructures = noopSaveStructures;
461
+ tempEncoder.saveStructures = noopSaveStructures;
441
462
  }
442
463
  copyStructures(sourceDbi, key);
443
464
  console.log('migrating', key, 'from', sourceDatabase, 'to RocksDB');