@harperfast/harper-pro 5.1.0-beta.3 → 5.1.0
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/core/DESIGN.md +24 -0
- package/core/bin/copyDb.ts +16 -3
- package/core/package-lock.json +44 -54
- package/core/resources/Table.ts +41 -4
- package/core/resources/databases.ts +134 -42
- package/core/resources/graphql.ts +3 -0
- package/core/resources/replayLogs.ts +114 -42
- package/core/resources/replayLogsGuards.ts +58 -9
- package/core/server/nodeName.ts +9 -3
- package/core/server/operationsServer.ts +17 -0
- package/core/utility/install/installer.ts +12 -5
- package/dist/core/bin/copyDb.js +16 -3
- package/dist/core/bin/copyDb.js.map +1 -1
- package/dist/core/resources/Table.js +43 -4
- package/dist/core/resources/Table.js.map +1 -1
- package/dist/core/resources/databases.js +135 -41
- package/dist/core/resources/databases.js.map +1 -1
- package/dist/core/resources/graphql.js +3 -0
- package/dist/core/resources/graphql.js.map +1 -1
- package/dist/core/resources/replayLogs.js +100 -40
- package/dist/core/resources/replayLogs.js.map +1 -1
- package/dist/core/resources/replayLogsGuards.js +50 -10
- package/dist/core/resources/replayLogsGuards.js.map +1 -1
- package/dist/core/server/nodeName.js +9 -3
- package/dist/core/server/nodeName.js.map +1 -1
- package/dist/core/server/operationsServer.js +20 -0
- package/dist/core/server/operationsServer.js.map +1 -1
- package/dist/core/utility/install/installer.js +12 -6
- package/dist/core/utility/install/installer.js.map +1 -1
- package/dist/replication/knownNodes.js +106 -17
- package/dist/replication/knownNodes.js.map +1 -1
- package/dist/replication/replicator.js +6 -3
- package/dist/replication/replicator.js.map +1 -1
- package/npm-shrinkwrap.json +43 -52
- package/package.json +3 -3
- package/replication/knownNodes.ts +104 -14
- package/replication/replicator.ts +7 -3
- package/studio/web/assets/{Chat-BdFickL8.js → Chat-DMBW4pI2.js} +2 -2
- package/studio/web/assets/{Chat-BdFickL8.js.map → Chat-DMBW4pI2.js.map} +1 -1
- package/studio/web/assets/{FloatingChat-CaAoco8I.js → FloatingChat-DTmhPsrm.js} +4 -4
- package/studio/web/assets/{FloatingChat-CaAoco8I.js.map → FloatingChat-DTmhPsrm.js.map} +1 -1
- package/studio/web/assets/{applications-C3y3xwyG.js → applications-CigxJarn.js} +2 -2
- package/studio/web/assets/{applications-C3y3xwyG.js.map → applications-CigxJarn.js.map} +1 -1
- package/studio/web/assets/{index-Cd3Zh3nK.js → index-oRZw5GW3.js} +6 -6
- package/studio/web/assets/index-oRZw5GW3.js.map +1 -0
- package/studio/web/assets/{index.lazy-0hLbh5Fo.js → index.lazy-DY3VcR86.js} +4 -4
- package/studio/web/assets/{index.lazy-0hLbh5Fo.js.map → index.lazy-DY3VcR86.js.map} +1 -1
- package/studio/web/assets/{profile-CwBWGuVt.js → profile-DiV60L50.js} +2 -2
- package/studio/web/assets/{profile-CwBWGuVt.js.map → profile-DiV60L50.js.map} +1 -1
- package/studio/web/assets/{setComponentFile-Dhrmd8eR.js → setComponentFile-Bz6WI4jy.js} +2 -2
- package/studio/web/assets/{setComponentFile-Dhrmd8eR.js.map → setComponentFile-Bz6WI4jy.js.map} +1 -1
- package/studio/web/assets/{status-Bykq6QcD.js → status-fW6PBpPM.js} +2 -2
- package/studio/web/assets/{status-Bykq6QcD.js.map → status-fW6PBpPM.js.map} +1 -1
- package/studio/web/assets/{swagger-ui-react-CF94s29D.js → swagger-ui-react-DRYf7G3J.js} +2 -2
- package/studio/web/assets/{swagger-ui-react-CF94s29D.js.map → swagger-ui-react-DRYf7G3J.js.map} +1 -1
- package/studio/web/assets/{useEntityRestURL-D7vuaG2W.js → useEntityRestURL-14jWKu9J.js} +2 -2
- package/studio/web/assets/{useEntityRestURL-D7vuaG2W.js.map → useEntityRestURL-14jWKu9J.js.map} +1 -1
- package/studio/web/index.html +1 -1
- package/studio/web/assets/index-Cd3Zh3nK.js.map +0 -1
package/core/DESIGN.md
CHANGED
|
@@ -107,3 +107,27 @@ Adding a new system table (e.g. `hdb_deployment` in #641 Slice A) requires three
|
|
|
107
107
|
System tables replicate by default. To opt out, add the name to `NON_REPLICATING_SYSTEM_TABLES` in `resources/databases.ts`. The check happens after table init and sets `table.replicate = false` per-node.
|
|
108
108
|
|
|
109
109
|
If the table needs `audit: true`, set it both in the schema (for fresh installs) **and** on the `CreateTableObject` instance in the directive (for upgrades) — otherwise the two paths diverge.
|
|
110
|
+
|
|
111
|
+
## Table drops, the `dropping` tombstone, and ghost tables
|
|
112
|
+
|
|
113
|
+
A table is a set of RocksDB column families (`T/` plus `T/<attr>`) and a set of catalog rows
|
|
114
|
+
in the `__dbis__` store, with no transaction spanning the two. `Table.dropTable()` therefore
|
|
115
|
+
persists a `dropping: true` flag on the table's primary catalog entry (`T/`) before any
|
|
116
|
+
destructive work, then drops the column families (awaited - a failed drop must surface as the
|
|
117
|
+
operation's error, never a swallowed rejection), then removes the catalog rows. If the process
|
|
118
|
+
dies or a drop fails partway, the tombstone survives; both the boot-time schema load in
|
|
119
|
+
`databases.ts` (`completeInterruptedDrop`) and a same-name `table()` create complete the
|
|
120
|
+
interrupted drop instead of resurrecting the table. Without this, surviving catalog rows are
|
|
121
|
+
silently re-opened with create-if-missing on the next start, which resurrects "deleted" tables
|
|
122
|
+
(with their data, if the column families were never actually removed).
|
|
123
|
+
|
|
124
|
+
Two related traps: the create path's exclusive `update-attributes` lock is a synchronous spin
|
|
125
|
+
lock (`while (!tryLock()) {}`), so any throw inside the create window must release it or every
|
|
126
|
+
subsequent create on that database pins a worker at 100% CPU. And dropping then recreating a
|
|
127
|
+
same-named table within one process requires @harperfast/rocksdb-js >= the column-family
|
|
128
|
+
eviction fix (1.4.3 / rocksdb-js#<main PR>): older bindings keep the dropped column family's
|
|
129
|
+
by-name registry entry alive whenever other worker threads hold handles, so the recreate
|
|
130
|
+
silently reuses a dangling handle and every write fails with "Invalid column family specified
|
|
131
|
+
in write batch", poisoning the whole database env until restart. The regression suite for all
|
|
132
|
+
of this is `unitTests/resources/dropTableGhost.test.js` (it fails by design on pre-fix
|
|
133
|
+
bindings).
|
package/core/bin/copyDb.ts
CHANGED
|
@@ -381,20 +381,33 @@ export async function copyDbToRocks(sourceRootStore, sourceDatabase: string, tar
|
|
|
381
381
|
const sourceDbisDb = sourceRootStore.dbisDb;
|
|
382
382
|
|
|
383
383
|
const targetRootStore = openRocksDb(targetPath, { disableWAL: false });
|
|
384
|
+
// sharedStructuresKey wires the rocksdb-js getStructures/saveStructures closures
|
|
385
|
+
// so that the plain msgpackr.Encoder used here persists structures within the
|
|
386
|
+
// __dbis__ CF at Symbol.for('structures'). The runtime attributesDbi RecordEncoder
|
|
387
|
+
// takes the non-isRocksDB path (handleLocalTimeForGets is never called on it) and
|
|
388
|
+
// reads from the same CF key via superGetStructures. Without this, own structure
|
|
389
|
+
// IDs starting at 0x40 are minted in-memory and silently lost on restart →
|
|
390
|
+
// runtime decoder interprets 0x40 as fixint 64 → "Data read, but end of buffer
|
|
391
|
+
// not reached 64" (harper#1260).
|
|
384
392
|
const targetDbisDb = openRocksDb(targetPath, {
|
|
385
393
|
disableWAL: false,
|
|
386
394
|
name: INTERNAL_DBIS_NAME,
|
|
395
|
+
sharedStructuresKey: Symbol.for('structures'),
|
|
387
396
|
});
|
|
388
397
|
|
|
389
398
|
const STRUCTURES_KEY = Symbol.for('structures');
|
|
390
|
-
const copyStructures = (sourceDbi, storeName: string) => {
|
|
399
|
+
const copyStructures = (sourceDbi, storeName: string, extraTarget?: RocksDatabase) => {
|
|
391
400
|
const buffer = sourceDbi.getBinary?.(STRUCTURES_KEY);
|
|
392
401
|
if (buffer) {
|
|
393
|
-
|
|
402
|
+
const binaryBuffer = asBinary(buffer);
|
|
403
|
+
targetRootStore.putSync([STRUCTURES_KEY, storeName], binaryBuffer);
|
|
404
|
+
// Also write to the extra target CF when provided (e.g. __dbis__ CF,
|
|
405
|
+
// which the runtime RecordEncoder reads via its superGetStructures path).
|
|
406
|
+
extraTarget?.putSync(STRUCTURES_KEY, binaryBuffer);
|
|
394
407
|
}
|
|
395
408
|
};
|
|
396
409
|
|
|
397
|
-
copyStructures(sourceDbisDb, INTERNAL_DBIS_NAME);
|
|
410
|
+
copyStructures(sourceDbisDb, INTERNAL_DBIS_NAME, targetDbisDb);
|
|
398
411
|
|
|
399
412
|
let written;
|
|
400
413
|
let outstandingWrites = 0;
|
package/core/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "harper",
|
|
3
|
-
"version": "5.1.0-beta.
|
|
3
|
+
"version": "5.1.0-beta.3",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "harper",
|
|
9
|
-
"version": "5.1.0-beta.
|
|
9
|
+
"version": "5.1.0-beta.3",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@aws-sdk/client-s3": "^3.1012.0",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"@fastify/cors": "^11.2.0",
|
|
18
18
|
"@fastify/static": "^9.1.3",
|
|
19
19
|
"@harperfast/extended-iterable": "^1.0.1",
|
|
20
|
-
"@harperfast/rocksdb-js": "^2.
|
|
20
|
+
"@harperfast/rocksdb-js": "^2.1.0",
|
|
21
21
|
"@turf/area": "6.5.0",
|
|
22
22
|
"@turf/boolean-contains": "6.5.0",
|
|
23
23
|
"@turf/boolean-disjoint": "6.5.0",
|
|
@@ -95,7 +95,7 @@
|
|
|
95
95
|
},
|
|
96
96
|
"devDependencies": {
|
|
97
97
|
"@harperdb/code-guidelines": "^0.0.6",
|
|
98
|
-
"@harperfast/integration-testing": "^0.
|
|
98
|
+
"@harperfast/integration-testing": "^0.5.1",
|
|
99
99
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
100
100
|
"@types/busboy": "^1.5.4",
|
|
101
101
|
"@types/fs-extra": "^11.0.4",
|
|
@@ -130,8 +130,7 @@
|
|
|
130
130
|
"why-is-node-still-running": "^1.0.0"
|
|
131
131
|
},
|
|
132
132
|
"engines": {
|
|
133
|
-
"
|
|
134
|
-
"node": ">=20"
|
|
133
|
+
"node": "^22.18.0 || >=24.0.0"
|
|
135
134
|
},
|
|
136
135
|
"optionalDependencies": {
|
|
137
136
|
"bufferutil": "^4.0.9",
|
|
@@ -2898,9 +2897,9 @@
|
|
|
2898
2897
|
"license": "Apache-2.0"
|
|
2899
2898
|
},
|
|
2900
2899
|
"node_modules/@harperfast/integration-testing": {
|
|
2901
|
-
"version": "0.
|
|
2902
|
-
"resolved": "https://registry.npmjs.org/@harperfast/integration-testing/-/integration-testing-0.
|
|
2903
|
-
"integrity": "sha512-
|
|
2900
|
+
"version": "0.5.1",
|
|
2901
|
+
"resolved": "https://registry.npmjs.org/@harperfast/integration-testing/-/integration-testing-0.5.1.tgz",
|
|
2902
|
+
"integrity": "sha512-hHkDf9mgxdKFPqnlc1Dtz9je9WafUWGIofyzgfpmPWECxWqEo4NIa5CesJANtdKHSVsUtSiEt0E05y6+ZsBtPw==",
|
|
2904
2903
|
"dev": true,
|
|
2905
2904
|
"license": "Apache-2.0",
|
|
2906
2905
|
"dependencies": {
|
|
@@ -2923,13 +2922,13 @@
|
|
|
2923
2922
|
}
|
|
2924
2923
|
},
|
|
2925
2924
|
"node_modules/@harperfast/rocksdb-js": {
|
|
2926
|
-
"version": "2.
|
|
2927
|
-
"resolved": "https://registry.npmjs.org/@harperfast/rocksdb-js/-/rocksdb-js-2.
|
|
2928
|
-
"integrity": "sha512-
|
|
2925
|
+
"version": "2.1.0",
|
|
2926
|
+
"resolved": "https://registry.npmjs.org/@harperfast/rocksdb-js/-/rocksdb-js-2.1.0.tgz",
|
|
2927
|
+
"integrity": "sha512-1E6Yiu7U0HHVPzJQE3t3ifwyfqXjmmhaoBahN6JWzswu6TMkY3EpCNDozpZ2MVom/4b/7sUG9IRKUpX8h9XK/Q==",
|
|
2929
2928
|
"license": "Apache-2.0",
|
|
2930
2929
|
"dependencies": {
|
|
2931
2930
|
"@harperfast/extended-iterable": "1.0.3",
|
|
2932
|
-
"msgpackr": "2.0.
|
|
2931
|
+
"msgpackr": "2.0.4",
|
|
2933
2932
|
"ordered-binary": "1.6.1"
|
|
2934
2933
|
},
|
|
2935
2934
|
"bin": {
|
|
@@ -2939,20 +2938,20 @@
|
|
|
2939
2938
|
"node": "^22.18.0 || >=24.0.0"
|
|
2940
2939
|
},
|
|
2941
2940
|
"optionalDependencies": {
|
|
2942
|
-
"@harperfast/rocksdb-js-darwin-arm64": "2.
|
|
2943
|
-
"@harperfast/rocksdb-js-darwin-x64": "2.
|
|
2944
|
-
"@harperfast/rocksdb-js-linux-arm64-glibc": "2.
|
|
2945
|
-
"@harperfast/rocksdb-js-linux-arm64-musl": "2.
|
|
2946
|
-
"@harperfast/rocksdb-js-linux-x64-glibc": "2.
|
|
2947
|
-
"@harperfast/rocksdb-js-linux-x64-musl": "2.
|
|
2948
|
-
"@harperfast/rocksdb-js-win32-arm64": "2.
|
|
2949
|
-
"@harperfast/rocksdb-js-win32-x64": "2.
|
|
2941
|
+
"@harperfast/rocksdb-js-darwin-arm64": "2.1.0",
|
|
2942
|
+
"@harperfast/rocksdb-js-darwin-x64": "2.1.0",
|
|
2943
|
+
"@harperfast/rocksdb-js-linux-arm64-glibc": "2.1.0",
|
|
2944
|
+
"@harperfast/rocksdb-js-linux-arm64-musl": "2.1.0",
|
|
2945
|
+
"@harperfast/rocksdb-js-linux-x64-glibc": "2.1.0",
|
|
2946
|
+
"@harperfast/rocksdb-js-linux-x64-musl": "2.1.0",
|
|
2947
|
+
"@harperfast/rocksdb-js-win32-arm64": "2.1.0",
|
|
2948
|
+
"@harperfast/rocksdb-js-win32-x64": "2.1.0"
|
|
2950
2949
|
}
|
|
2951
2950
|
},
|
|
2952
2951
|
"node_modules/@harperfast/rocksdb-js-darwin-arm64": {
|
|
2953
|
-
"version": "2.
|
|
2954
|
-
"resolved": "https://registry.npmjs.org/@harperfast/rocksdb-js-darwin-arm64/-/rocksdb-js-darwin-arm64-2.
|
|
2955
|
-
"integrity": "sha512-
|
|
2952
|
+
"version": "2.1.0",
|
|
2953
|
+
"resolved": "https://registry.npmjs.org/@harperfast/rocksdb-js-darwin-arm64/-/rocksdb-js-darwin-arm64-2.1.0.tgz",
|
|
2954
|
+
"integrity": "sha512-KATHEEcKv1sBsuqaxgAd5ZqRNN63f/dedl5ilE0aZKubDjPHJFWLl1USZZXd4FMR7bYHzCV823S9RxZII1UTjA==",
|
|
2956
2955
|
"cpu": [
|
|
2957
2956
|
"arm64"
|
|
2958
2957
|
],
|
|
@@ -2966,9 +2965,9 @@
|
|
|
2966
2965
|
}
|
|
2967
2966
|
},
|
|
2968
2967
|
"node_modules/@harperfast/rocksdb-js-darwin-x64": {
|
|
2969
|
-
"version": "2.
|
|
2970
|
-
"resolved": "https://registry.npmjs.org/@harperfast/rocksdb-js-darwin-x64/-/rocksdb-js-darwin-x64-2.
|
|
2971
|
-
"integrity": "sha512-
|
|
2968
|
+
"version": "2.1.0",
|
|
2969
|
+
"resolved": "https://registry.npmjs.org/@harperfast/rocksdb-js-darwin-x64/-/rocksdb-js-darwin-x64-2.1.0.tgz",
|
|
2970
|
+
"integrity": "sha512-8PgipGp9uviZ+c8amA1MiKthekL4Qxmd/wPGbaa2P5MEj7cW3nXwuwuLrXFtj0plfbGJZDKHAoHH1MQc63JiHQ==",
|
|
2972
2971
|
"cpu": [
|
|
2973
2972
|
"x64"
|
|
2974
2973
|
],
|
|
@@ -2982,9 +2981,9 @@
|
|
|
2982
2981
|
}
|
|
2983
2982
|
},
|
|
2984
2983
|
"node_modules/@harperfast/rocksdb-js-linux-arm64-glibc": {
|
|
2985
|
-
"version": "2.
|
|
2986
|
-
"resolved": "https://registry.npmjs.org/@harperfast/rocksdb-js-linux-arm64-glibc/-/rocksdb-js-linux-arm64-glibc-2.
|
|
2987
|
-
"integrity": "sha512-
|
|
2984
|
+
"version": "2.1.0",
|
|
2985
|
+
"resolved": "https://registry.npmjs.org/@harperfast/rocksdb-js-linux-arm64-glibc/-/rocksdb-js-linux-arm64-glibc-2.1.0.tgz",
|
|
2986
|
+
"integrity": "sha512-kzlN++3RaECDeKLYP7a3A/7D1IrTu8DzVRdvQ90rjNi67ZhiTfPGLwkEJgSheGRaKOkvtXOcqaG+G0uLCmY2qg==",
|
|
2988
2987
|
"cpu": [
|
|
2989
2988
|
"arm64"
|
|
2990
2989
|
],
|
|
@@ -3001,9 +3000,9 @@
|
|
|
3001
3000
|
}
|
|
3002
3001
|
},
|
|
3003
3002
|
"node_modules/@harperfast/rocksdb-js-linux-arm64-musl": {
|
|
3004
|
-
"version": "2.
|
|
3005
|
-
"resolved": "https://registry.npmjs.org/@harperfast/rocksdb-js-linux-arm64-musl/-/rocksdb-js-linux-arm64-musl-2.
|
|
3006
|
-
"integrity": "sha512-
|
|
3003
|
+
"version": "2.1.0",
|
|
3004
|
+
"resolved": "https://registry.npmjs.org/@harperfast/rocksdb-js-linux-arm64-musl/-/rocksdb-js-linux-arm64-musl-2.1.0.tgz",
|
|
3005
|
+
"integrity": "sha512-Wm3GCuII2+2wUdpF14qMJpFBvYMS5LCNmqYqc8PLQdM9+1C0pp2xp/YlXz295lSJwDRBGpZQ4tT/fCIhQb3RZg==",
|
|
3007
3006
|
"cpu": [
|
|
3008
3007
|
"arm64"
|
|
3009
3008
|
],
|
|
@@ -3020,9 +3019,9 @@
|
|
|
3020
3019
|
}
|
|
3021
3020
|
},
|
|
3022
3021
|
"node_modules/@harperfast/rocksdb-js-linux-x64-glibc": {
|
|
3023
|
-
"version": "2.
|
|
3024
|
-
"resolved": "https://registry.npmjs.org/@harperfast/rocksdb-js-linux-x64-glibc/-/rocksdb-js-linux-x64-glibc-2.
|
|
3025
|
-
"integrity": "sha512-
|
|
3022
|
+
"version": "2.1.0",
|
|
3023
|
+
"resolved": "https://registry.npmjs.org/@harperfast/rocksdb-js-linux-x64-glibc/-/rocksdb-js-linux-x64-glibc-2.1.0.tgz",
|
|
3024
|
+
"integrity": "sha512-JOR19bhySF+fwFoxsR1JOURSxUySL1NE6wLBtWfLSA4SEQZXi/Xzkkq7Up92JuL9E4ATcEkVJjPy52azUUE4VA==",
|
|
3026
3025
|
"cpu": [
|
|
3027
3026
|
"x64"
|
|
3028
3027
|
],
|
|
@@ -3039,9 +3038,9 @@
|
|
|
3039
3038
|
}
|
|
3040
3039
|
},
|
|
3041
3040
|
"node_modules/@harperfast/rocksdb-js-linux-x64-musl": {
|
|
3042
|
-
"version": "2.
|
|
3043
|
-
"resolved": "https://registry.npmjs.org/@harperfast/rocksdb-js-linux-x64-musl/-/rocksdb-js-linux-x64-musl-2.
|
|
3044
|
-
"integrity": "sha512-
|
|
3041
|
+
"version": "2.1.0",
|
|
3042
|
+
"resolved": "https://registry.npmjs.org/@harperfast/rocksdb-js-linux-x64-musl/-/rocksdb-js-linux-x64-musl-2.1.0.tgz",
|
|
3043
|
+
"integrity": "sha512-HhUeWFqlEdjDPQecO1Exp3zm4LmXV2biyWPycXWBwXpitaI3YbDaUkiZHLT5QmGdYMHQou9s9RrcJ0B0xBpaYA==",
|
|
3045
3044
|
"cpu": [
|
|
3046
3045
|
"x64"
|
|
3047
3046
|
],
|
|
@@ -3058,9 +3057,9 @@
|
|
|
3058
3057
|
}
|
|
3059
3058
|
},
|
|
3060
3059
|
"node_modules/@harperfast/rocksdb-js-win32-arm64": {
|
|
3061
|
-
"version": "2.
|
|
3062
|
-
"resolved": "https://registry.npmjs.org/@harperfast/rocksdb-js-win32-arm64/-/rocksdb-js-win32-arm64-2.
|
|
3063
|
-
"integrity": "sha512-
|
|
3060
|
+
"version": "2.1.0",
|
|
3061
|
+
"resolved": "https://registry.npmjs.org/@harperfast/rocksdb-js-win32-arm64/-/rocksdb-js-win32-arm64-2.1.0.tgz",
|
|
3062
|
+
"integrity": "sha512-pUxyRGlyx+o0zU/ioUb4Z7ZQPy01iDU0gr7YQnOn9gCyDo1ibeeZVKDYZJ+WEpQCWEdea5YJVrSt597BQPAJiQ==",
|
|
3064
3063
|
"cpu": [
|
|
3065
3064
|
"arm64"
|
|
3066
3065
|
],
|
|
@@ -3074,9 +3073,9 @@
|
|
|
3074
3073
|
}
|
|
3075
3074
|
},
|
|
3076
3075
|
"node_modules/@harperfast/rocksdb-js-win32-x64": {
|
|
3077
|
-
"version": "2.
|
|
3078
|
-
"resolved": "https://registry.npmjs.org/@harperfast/rocksdb-js-win32-x64/-/rocksdb-js-win32-x64-2.
|
|
3079
|
-
"integrity": "sha512-
|
|
3076
|
+
"version": "2.1.0",
|
|
3077
|
+
"resolved": "https://registry.npmjs.org/@harperfast/rocksdb-js-win32-x64/-/rocksdb-js-win32-x64-2.1.0.tgz",
|
|
3078
|
+
"integrity": "sha512-C0XFhYIUAJxYKUV+P12lgzEhsH7NMzlSBq8WtEQun8PLI2WJIWpeo/UI7Z/YLKcO8dR8mYh1w/uui/vBC86sRg==",
|
|
3080
3079
|
"cpu": [
|
|
3081
3080
|
"x64"
|
|
3082
3081
|
],
|
|
@@ -3089,15 +3088,6 @@
|
|
|
3089
3088
|
"node": "^22.18.0 || >=24.0.0"
|
|
3090
3089
|
}
|
|
3091
3090
|
},
|
|
3092
|
-
"node_modules/@harperfast/rocksdb-js/node_modules/msgpackr": {
|
|
3093
|
-
"version": "2.0.2",
|
|
3094
|
-
"resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-2.0.2.tgz",
|
|
3095
|
-
"integrity": "sha512-c5hYOXFbP79Slh6Dzd2wzk+jnV7mX1UxfMYtilnY1NmalXPqG8DGb5cYCMBrW4AsH3zekBBZd4QrKz9NhtvYLQ==",
|
|
3096
|
-
"license": "MIT",
|
|
3097
|
-
"optionalDependencies": {
|
|
3098
|
-
"msgpackr-extract": "^3.0.4"
|
|
3099
|
-
}
|
|
3100
|
-
},
|
|
3101
3091
|
"node_modules/@hono/node-server": {
|
|
3102
3092
|
"version": "1.19.14",
|
|
3103
3093
|
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz",
|
package/core/resources/Table.ts
CHANGED
|
@@ -1017,6 +1017,29 @@ export function makeTable(options) {
|
|
|
1017
1017
|
}
|
|
1018
1018
|
|
|
1019
1019
|
static async dropTable() {
|
|
1020
|
+
if (databaseName === databasePath) {
|
|
1021
|
+
// Persist a drop tombstone on the primary catalog entry BEFORE any
|
|
1022
|
+
// destructive work. If the process dies or a column family drop fails
|
|
1023
|
+
// partway through, the tombstone survives with the catalog rows, and
|
|
1024
|
+
// the next startup (or a same-name create) completes the drop via
|
|
1025
|
+
// completeInterruptedDrop in databases.ts instead of resurrecting
|
|
1026
|
+
// the table.
|
|
1027
|
+
const primaryCatalogKey = TableResource.tableName + '/';
|
|
1028
|
+
const primaryMeta = (dbisDb as any).getSync(primaryCatalogKey);
|
|
1029
|
+
if (primaryMeta && !primaryMeta.dropping) {
|
|
1030
|
+
primaryMeta.dropping = true;
|
|
1031
|
+
// put is rebound to putSync on RocksDB stores; on LMDB it returns
|
|
1032
|
+
// a promise, so await it to make the tombstone durable before the
|
|
1033
|
+
// destructive work below
|
|
1034
|
+
const tombstoneWrite = (dbisDb as any).put(primaryCatalogKey, primaryMeta);
|
|
1035
|
+
if (tombstoneWrite?.then) await tombstoneWrite;
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
// Remove the table from the in-memory schema immediately so concurrent
|
|
1039
|
+
// requests get "table does not exist" instead of racing the column
|
|
1040
|
+
// family drops below. If a drop fails past this point the table stays
|
|
1041
|
+
// invisible, and the tombstone guarantees the drop completes on the
|
|
1042
|
+
// next startup (or on a same-name create).
|
|
1020
1043
|
delete databases[databaseName][tableName];
|
|
1021
1044
|
for (const entry of primaryStore.getRange({ versions: true, snapshot: false, lazy: true })) {
|
|
1022
1045
|
if (entry.metadataFlags & HAS_BLOBS && entry.value) {
|
|
@@ -1024,14 +1047,28 @@ export function makeTable(options) {
|
|
|
1024
1047
|
}
|
|
1025
1048
|
}
|
|
1026
1049
|
if (databaseName === databasePath) {
|
|
1027
|
-
// part of a database
|
|
1050
|
+
// part of a database.
|
|
1051
|
+
// Drop the column families and AWAIT them. Previously the catalog
|
|
1052
|
+
// metadata was removed (synchronous removeSync) before primaryStore.drop(),
|
|
1053
|
+
// and the drops were fire-and-forget (their rejections swallowed). If a
|
|
1054
|
+
// column family was corrupt/half-initialized the drop would fail, yet the
|
|
1055
|
+
// catalog entry was already gone - an orphaned "ghost" column family that
|
|
1056
|
+
// poisons same-name recreates. By awaiting the drops before touching the
|
|
1057
|
+
// catalog, a failed drop surfaces as the operation's error and the
|
|
1058
|
+
// tombstoned catalog rows survive for the startup reconcile.
|
|
1059
|
+
const drops = [];
|
|
1028
1060
|
for (const attribute of attributes) {
|
|
1029
|
-
dbisDb.remove(TableResource.tableName + '/' + attribute.name);
|
|
1030
1061
|
const index = indices[attribute.name];
|
|
1031
|
-
index
|
|
1062
|
+
if (index) drops.push(index.drop());
|
|
1063
|
+
}
|
|
1064
|
+
drops.push(primaryStore.drop());
|
|
1065
|
+
await Promise.all(drops);
|
|
1066
|
+
// Column families are gone; remove the catalog metadata (including
|
|
1067
|
+
// the tombstoned primary entry).
|
|
1068
|
+
for (const attribute of attributes) {
|
|
1069
|
+
dbisDb.remove(TableResource.tableName + '/' + attribute.name);
|
|
1032
1070
|
}
|
|
1033
1071
|
dbisDb.remove(TableResource.tableName + '/');
|
|
1034
|
-
primaryStore.drop();
|
|
1035
1072
|
await dbisDb.committed;
|
|
1036
1073
|
} else {
|
|
1037
1074
|
// legacy table per database
|
|
@@ -521,6 +521,27 @@ function initStores(
|
|
|
521
521
|
Object.defineProperty(value, 'key', { value: key, configurable: true });
|
|
522
522
|
}
|
|
523
523
|
|
|
524
|
+
// Complete any drops that were interrupted mid-flight. dropTable persists a
|
|
525
|
+
// `dropping` tombstone on the table's primary catalog entry before removing
|
|
526
|
+
// column families; if the process died or a column family drop failed
|
|
527
|
+
// partway, the tombstone survives alongside the catalog rows. Without this
|
|
528
|
+
// reconcile, those rows would silently resurrect the table below
|
|
529
|
+
// (recreating any missing column families as empty stores).
|
|
530
|
+
for (const [tableName, tableDef] of tablesToLoad) {
|
|
531
|
+
if (!tableDef.primary?.dropping) continue;
|
|
532
|
+
try {
|
|
533
|
+
completeInterruptedDrop(rootStore, attributesDbi, databaseName, tableName);
|
|
534
|
+
definedTables?.delete(tableName);
|
|
535
|
+
} catch (error) {
|
|
536
|
+
logger.error(
|
|
537
|
+
`Failed to complete interrupted drop of table ${databaseName}.${tableName}; will retry on next start`,
|
|
538
|
+
error
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
// whether or not cleanup succeeded, never load a table that was being dropped
|
|
542
|
+
tablesToLoad.delete(tableName);
|
|
543
|
+
}
|
|
544
|
+
|
|
524
545
|
for (const [tableName, tableDef] of tablesToLoad) {
|
|
525
546
|
let { attributes, primary: primaryAttribute } = tableDef;
|
|
526
547
|
if (!primaryAttribute) {
|
|
@@ -1067,7 +1088,8 @@ export function table<TableResourceType>(tableDefinition: TableDefinition): Tabl
|
|
|
1067
1088
|
}
|
|
1068
1089
|
|
|
1069
1090
|
exclusiveLock(); // get an exclusive lock on the database so we can verify that we are the only thread creating the table (and assigning the table id)
|
|
1070
|
-
|
|
1091
|
+
const existingTableMeta = (attributesDbi as any).getSync(dbiName);
|
|
1092
|
+
if (existingTableMeta && !existingTableMeta.dropping) {
|
|
1071
1093
|
// table was created while we were setting up
|
|
1072
1094
|
if (releaseExclusiveLock) releaseExclusiveLock();
|
|
1073
1095
|
resetDatabases();
|
|
@@ -1075,50 +1097,69 @@ export function table<TableResourceType>(tableDefinition: TableDefinition): Tabl
|
|
|
1075
1097
|
}
|
|
1076
1098
|
|
|
1077
1099
|
let primaryStore;
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1100
|
+
try {
|
|
1101
|
+
if (existingTableMeta?.dropping) {
|
|
1102
|
+
// A previous drop of this table was interrupted after its tombstone
|
|
1103
|
+
// was written. Complete it now (under the exclusive lock) so the
|
|
1104
|
+
// create below starts from a clean slate; treating the tombstoned
|
|
1105
|
+
// entry as an existing table would recurse forever on the stale
|
|
1106
|
+
// catalog row.
|
|
1107
|
+
completeInterruptedDrop(rootStore, attributesDbi, databaseName, tableName);
|
|
1108
|
+
}
|
|
1109
|
+
if (rootStore instanceof RocksDatabase) {
|
|
1110
|
+
primaryStore = openRocksDatabase(rootStore.path, { ...dbiInit, name: dbiName } as any);
|
|
1111
|
+
} else {
|
|
1112
|
+
primaryStore = (rootStore as any).openDB(dbiName, dbiInit as any);
|
|
1113
|
+
}
|
|
1114
|
+
primaryStore = handleLocalTimeForGets(primaryStore, rootStore);
|
|
1115
|
+
rootStore.databaseName = databaseName;
|
|
1116
|
+
primaryStore.tableId = attributesDbi.getSync(NEXT_TABLE_ID);
|
|
1117
|
+
logger.trace(`Assigning new table id ${primaryStore.tableId} for ${tableName}`);
|
|
1118
|
+
if (!primaryStore.tableId) primaryStore.tableId = 1;
|
|
1119
|
+
attributesDbi.put(NEXT_TABLE_ID, primaryStore.tableId + 1);
|
|
1089
1120
|
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
tableName,
|
|
1094
|
-
makeTable({
|
|
1095
|
-
primaryStore,
|
|
1096
|
-
auditStore,
|
|
1097
|
-
audit,
|
|
1098
|
-
sealed,
|
|
1099
|
-
splitSegments,
|
|
1100
|
-
replicate,
|
|
1101
|
-
trackDeletes,
|
|
1102
|
-
expirationMS: expiration && expiration * 1000,
|
|
1103
|
-
evictionMS: eviction && eviction * 1000,
|
|
1104
|
-
primaryKey,
|
|
1121
|
+
primaryKeyAttribute.tableId = primaryStore.tableId;
|
|
1122
|
+
Table = setTable(
|
|
1123
|
+
tables,
|
|
1105
1124
|
tableName,
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1125
|
+
makeTable({
|
|
1126
|
+
primaryStore,
|
|
1127
|
+
auditStore,
|
|
1128
|
+
audit,
|
|
1129
|
+
sealed,
|
|
1130
|
+
splitSegments,
|
|
1131
|
+
replicate,
|
|
1132
|
+
trackDeletes,
|
|
1133
|
+
expirationMS: expiration && expiration * 1000,
|
|
1134
|
+
evictionMS: eviction && eviction * 1000,
|
|
1135
|
+
primaryKey,
|
|
1136
|
+
tableName,
|
|
1137
|
+
tableId: primaryStore.tableId,
|
|
1138
|
+
databasePath: databaseName,
|
|
1139
|
+
databaseName,
|
|
1140
|
+
indices: {},
|
|
1141
|
+
attributes,
|
|
1142
|
+
schemaDefined,
|
|
1143
|
+
dbisDB: attributesDbi,
|
|
1144
|
+
description,
|
|
1145
|
+
properties,
|
|
1146
|
+
hidden,
|
|
1147
|
+
})
|
|
1148
|
+
);
|
|
1149
|
+
Table.schemaVersion = 1;
|
|
1150
|
+
hasChanges = true;
|
|
1120
1151
|
|
|
1121
|
-
|
|
1152
|
+
attributesDbi.put(dbiName, primaryKeyAttribute);
|
|
1153
|
+
} catch (error) {
|
|
1154
|
+
// A failure while opening/creating the column family or writing the
|
|
1155
|
+
// table id / catalog entry (e.g. into an env poisoned by a prior
|
|
1156
|
+
// dangling column family) must NOT leak the exclusive
|
|
1157
|
+
// 'update-attributes' spin lock. If it leaks, every subsequent
|
|
1158
|
+
// create_table / attribute update on this database spins forever
|
|
1159
|
+
// (a hard wedge that pins a worker at 100% CPU). Release before rethrow.
|
|
1160
|
+
if (releaseExclusiveLock) releaseExclusiveLock();
|
|
1161
|
+
throw error;
|
|
1162
|
+
}
|
|
1122
1163
|
}
|
|
1123
1164
|
const indices = Table.indices;
|
|
1124
1165
|
if (!attributesDbi) {
|
|
@@ -1171,8 +1212,14 @@ export function table<TableResourceType>(tableDefinition: TableDefinition): Tabl
|
|
|
1171
1212
|
let attributeDescriptor = attributesDbi.getSync(dbiKey);
|
|
1172
1213
|
if (attribute.isPrimaryKey) {
|
|
1173
1214
|
attributeDescriptor = attributeDescriptor || attributesDbi.getSync((dbiKey = tableName + '/')) || {};
|
|
1215
|
+
// Persist schemaDefined when the explicit live value disagrees with disk. Without this,
|
|
1216
|
+
// a stale `false` (from a v4-era write or replicated event) survives every reload: the
|
|
1217
|
+
// in-memory re-assert in the existing-Table branch only fixes the worker that ran @table,
|
|
1218
|
+
// but other workers' next disk-load re-reads the stale value.
|
|
1219
|
+
const schemaDefinedMismatch = schemaDefinedExplicit && attributeDescriptor.schemaDefined !== schemaDefined;
|
|
1174
1220
|
// primary key can't change indexing, but settings can change
|
|
1175
1221
|
if (
|
|
1222
|
+
schemaDefinedMismatch ||
|
|
1176
1223
|
(audit !== undefined && audit !== Table.audit) ||
|
|
1177
1224
|
(sealed !== undefined && sealed !== Table.sealed) ||
|
|
1178
1225
|
(replicate !== undefined && replicate !== Table.replicate) ||
|
|
@@ -1190,6 +1237,7 @@ export function table<TableResourceType>(tableDefinition: TableDefinition): Tabl
|
|
|
1190
1237
|
if (sealed !== undefined) updatedPrimaryAttribute.sealed = sealed;
|
|
1191
1238
|
if (replicate !== undefined) updatedPrimaryAttribute.replicate = replicate;
|
|
1192
1239
|
if (attribute.type) updatedPrimaryAttribute.type = attribute.type;
|
|
1240
|
+
if (schemaDefinedMismatch) updatedPrimaryAttribute.schemaDefined = schemaDefined;
|
|
1193
1241
|
hasChanges = true; // send out notification of the change
|
|
1194
1242
|
exclusiveLock();
|
|
1195
1243
|
attributesDbi.put(dbiKey, updatedPrimaryAttribute);
|
|
@@ -1538,6 +1586,50 @@ async function runIndexing(Table, attributes, indicesToRemove) {
|
|
|
1538
1586
|
}
|
|
1539
1587
|
}
|
|
1540
1588
|
|
|
1589
|
+
/**
|
|
1590
|
+
* Completes a table drop that was interrupted after its `dropping` tombstone
|
|
1591
|
+
* was written: drops any surviving table stores and removes the table's
|
|
1592
|
+
* catalog rows. Called from the boot-time schema load and from the create
|
|
1593
|
+
* path when a same-named table is created over a tombstoned entry. Callers
|
|
1594
|
+
* are expected to hold the database's exclusive lock or be in single-threaded
|
|
1595
|
+
* startup; the per-store drops tolerate races (another worker may be
|
|
1596
|
+
* completing the same drop concurrently).
|
|
1597
|
+
*/
|
|
1598
|
+
function completeInterruptedDrop(rootStore, attributesDbi, databaseName: string, tableName: string) {
|
|
1599
|
+
logger.warn(`Completing interrupted drop of table ${databaseName}.${tableName}`);
|
|
1600
|
+
if (rootStore instanceof RocksDatabase) {
|
|
1601
|
+
for (const columnName of (rootStore as any).columns) {
|
|
1602
|
+
if (columnName.startsWith(tableName + '/')) {
|
|
1603
|
+
try {
|
|
1604
|
+
const columnStore = openRocksDatabase(rootStore.path, { name: columnName } as any);
|
|
1605
|
+
columnStore.dropSync();
|
|
1606
|
+
columnStore.close();
|
|
1607
|
+
} catch (error) {
|
|
1608
|
+
logger.warn(`Failed dropping column family ${columnName} of ${databaseName}.${tableName}`, error);
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
} else {
|
|
1613
|
+
// LMDB reuses an existing named sub-database on open, so the stores must
|
|
1614
|
+
// be dropped too; removing only the catalog rows would let a same-name
|
|
1615
|
+
// recreate silently inherit the previous table's records.
|
|
1616
|
+
for (const { key, value } of attributesDbi.getRange({ start: tableName + '/', end: tableName + '0' })) {
|
|
1617
|
+
try {
|
|
1618
|
+
const objectStorage =
|
|
1619
|
+
value?.isPrimaryKey || (value?.indexed?.type && CUSTOM_INDEXES[value.indexed.type]?.useObjectStore);
|
|
1620
|
+
const store = (rootStore as any).openDB(key, createOpenDBIObject(!objectStorage, objectStorage) as any);
|
|
1621
|
+
// lmdb drop commits with the env's next transaction
|
|
1622
|
+
store.drop?.();
|
|
1623
|
+
} catch (error) {
|
|
1624
|
+
logger.warn(`Failed dropping store ${key} of ${databaseName}.${tableName}`, error);
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
for (const key of attributesDbi.getKeys({ start: tableName + '/', end: tableName + '0' })) {
|
|
1629
|
+
attributesDbi.remove(key);
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1541
1633
|
export function dropTableMeta({ table: tableName, database: databaseName }) {
|
|
1542
1634
|
const rootStore = database({ database: databaseName, table: tableName });
|
|
1543
1635
|
const removals = [];
|
|
@@ -109,6 +109,9 @@ async function processGraphQLSchema(gqlContent, urlPath, filePath, resources) {
|
|
|
109
109
|
for (const arg of directive.arguments) {
|
|
110
110
|
typeDef[arg.name.value] = coerceDirectiveValue(arg.value);
|
|
111
111
|
}
|
|
112
|
+
// @table is the canonical schema-defined declaration; pass the flag explicitly so
|
|
113
|
+
// the existing-Table re-assert in databases.ts::table() fires on every reload.
|
|
114
|
+
typeDef.schemaDefined = true;
|
|
112
115
|
if (typeDef.schema) typeDef.database = typeDef.schema;
|
|
113
116
|
if (!typeDef.table) typeDef.table = typeName;
|
|
114
117
|
if (typeDef.audit) typeDef.audit = typeDef.audit !== 'false';
|