@harperfast/harper-pro 5.0.0-alpha.9 → 5.0.0-beta.2

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 (181) hide show
  1. package/core/.dockerignore +9 -0
  2. package/core/.git-blame-ignore-revs +2 -0
  3. package/core/.github/workflows/create-release.yaml +4 -4
  4. package/core/.github/workflows/integration-tests.yml +12 -10
  5. package/core/.github/workflows/notify-release-published.yaml +1 -1
  6. package/core/.github/workflows/publish-docker.yaml +2 -2
  7. package/core/.github/workflows/publish-npm.yaml +4 -4
  8. package/core/CONTRIBUTING.md +1 -1
  9. package/core/Dockerfile +62 -0
  10. package/core/build-tools/build-studio.sh +12 -0
  11. package/core/build-tools/build.sh +22 -0
  12. package/core/build-tools/download-prebuilds.js +13 -0
  13. package/core/components/Logger.ts +14 -0
  14. package/core/components/Scope.ts +35 -11
  15. package/core/components/componentLoader.ts +27 -10
  16. package/core/components/operations.js +10 -2
  17. package/core/config/configUtils.js +1 -1
  18. package/core/dataLayer/CreateTableObject.js +2 -2
  19. package/core/dataLayer/schema.js +7 -5
  20. package/core/dataLayer/schemaDescribe.js +1 -1
  21. package/core/index.d.ts +11 -6
  22. package/core/index.js +2 -0
  23. package/core/integrationTests/README.md +24 -0
  24. package/core/integrationTests/apiTests/tests/10_otherRoleTests.mjs +6 -6
  25. package/core/integrationTests/apiTests/tests/12_configuration.mjs +1 -1
  26. package/core/integrationTests/apiTests/tests/14_tokenAuth.mjs +2 -2
  27. package/core/integrationTests/apiTests/tests/16_terminologyUpdates.mjs +4 -4
  28. package/core/integrationTests/apiTests/tests/1_environmentSetup.mjs +1 -1
  29. package/core/integrationTests/apiTests/tests/2_dataLoad.mjs +4 -4
  30. package/core/integrationTests/apiTests/tests/3_sqlTests.mjs +3 -3
  31. package/core/integrationTests/apiTests/tests/4_noSqlTests.mjs +12 -12
  32. package/core/integrationTests/apiTests/tests/5_noSqlRoleTesting.mjs +8 -8
  33. package/core/integrationTests/apiTests/tests/7_jobsAndJobRoleTesting.mjs +10 -12
  34. package/core/integrationTests/apiTests/tests/8_deleteTests.mjs +8 -8
  35. package/core/integrationTests/apiTests/tests/9_transactions.mjs +2 -2
  36. package/core/integrationTests/apiTests/utils/search.mjs +1 -1
  37. package/core/integrationTests/apiTests/utils/table.mjs +1 -1
  38. package/core/integrationTests/server/operation-user-rbac.test.ts +1 -1
  39. package/core/integrationTests/server/operations-server.test.ts +1 -1
  40. package/core/integrationTests/server/storage-reclamation.test.ts +1 -1
  41. package/core/integrationTests/utils/README.md +1 -15
  42. package/core/integrationTests/utils/harperLifecycle.ts +33 -21
  43. package/core/package.json +23 -5
  44. package/core/resources/ResourceInterface.ts +1 -1
  45. package/core/resources/Table.ts +26 -11
  46. package/core/resources/analytics/read.ts +33 -26
  47. package/core/resources/analytics/write.ts +3 -7
  48. package/core/resources/databases.ts +29 -18
  49. package/core/resources/search.ts +10 -5
  50. package/core/security/auth.ts +1 -1
  51. package/core/security/jsLoader.ts +302 -83
  52. package/core/security/keys.js +11 -12
  53. package/core/security/user.ts +3 -3
  54. package/core/server/REST.ts +18 -2
  55. package/core/server/Server.ts +2 -1
  56. package/core/server/fastifyRoutes.ts +1 -0
  57. package/core/server/http.ts +13 -9
  58. package/core/server/loadRootComponents.js +1 -0
  59. package/core/server/operationsServer.ts +2 -1
  60. package/core/server/threads/manageThreads.js +49 -35
  61. package/core/static/defaultConfig.yaml +3 -0
  62. package/core/unitTests/apiTests/RESTProperties-test.mjs +2 -2
  63. package/core/unitTests/apiTests/basicREST-test.mjs +2 -2
  64. package/core/unitTests/components/Scope.test.js +54 -16
  65. package/core/unitTests/components/fixtures/testJSWithDeps/child-dir/circular.js +4 -0
  66. package/core/unitTests/components/fixtures/testJSWithDeps/child-dir/in-child-dir.js +4 -0
  67. package/core/unitTests/components/fixtures/testJSWithDeps/child-dir/typestrip.ts +2 -0
  68. package/core/unitTests/components/fixtures/testJSWithDeps/resources.js +43 -0
  69. package/core/unitTests/components/fixtures/testJSWithDeps/test-child-process.js +18 -0
  70. package/core/unitTests/components/globalIsolation.test.js +87 -1
  71. package/core/unitTests/config/configUtils.test.js +1 -260
  72. package/core/unitTests/resources/query.test.js +16 -1
  73. package/core/unitTests/resources/vectorIndex.test.js +1 -1
  74. package/core/unitTests/server/fastifyRoutes/operations.test.js +1 -1
  75. package/core/unitTests/testUtils.js +0 -17
  76. package/core/utility/hdbTerms.ts +3 -0
  77. package/core/utility/installation.ts +2 -5
  78. package/core/utility/lmdb/commonUtility.js +21 -10
  79. package/dist/core/{resources/ResourceInterfaceV2.js → components/Logger.js} +1 -1
  80. package/dist/core/components/Logger.js.map +1 -0
  81. package/dist/core/components/Scope.js +18 -10
  82. package/dist/core/components/Scope.js.map +1 -1
  83. package/dist/core/components/componentLoader.js +17 -10
  84. package/dist/core/components/componentLoader.js.map +1 -1
  85. package/dist/core/components/operations.js +2 -2
  86. package/dist/core/components/operations.js.map +1 -1
  87. package/dist/core/config/configUtils.js +1 -1
  88. package/dist/core/config/configUtils.js.map +1 -1
  89. package/dist/core/dataLayer/CreateTableObject.js +2 -2
  90. package/dist/core/dataLayer/CreateTableObject.js.map +1 -1
  91. package/dist/core/dataLayer/schema.js +6 -5
  92. package/dist/core/dataLayer/schema.js.map +1 -1
  93. package/dist/core/dataLayer/schemaDescribe.js +1 -1
  94. package/dist/core/dataLayer/schemaDescribe.js.map +1 -1
  95. package/dist/core/index.js +2 -0
  96. package/dist/core/index.js.map +1 -1
  97. package/dist/core/resources/Table.js +12 -4
  98. package/dist/core/resources/Table.js.map +1 -1
  99. package/dist/core/resources/analytics/read.js +32 -22
  100. package/dist/core/resources/analytics/read.js.map +1 -1
  101. package/dist/core/resources/analytics/write.js +3 -6
  102. package/dist/core/resources/analytics/write.js.map +1 -1
  103. package/dist/core/resources/databases.js +22 -19
  104. package/dist/core/resources/databases.js.map +1 -1
  105. package/dist/core/resources/search.js +11 -5
  106. package/dist/core/resources/search.js.map +1 -1
  107. package/dist/core/security/auth.js +1 -1
  108. package/dist/core/security/auth.js.map +1 -1
  109. package/dist/core/security/jsLoader.js +265 -73
  110. package/dist/core/security/jsLoader.js.map +1 -1
  111. package/dist/core/security/keys.js +11 -12
  112. package/dist/core/security/keys.js.map +1 -1
  113. package/dist/core/security/user.js +3 -3
  114. package/dist/core/security/user.js.map +1 -1
  115. package/dist/core/server/REST.js +16 -2
  116. package/dist/core/server/REST.js.map +1 -1
  117. package/dist/core/server/Server.js.map +1 -1
  118. package/dist/core/server/fastifyRoutes.js +2 -0
  119. package/dist/core/server/fastifyRoutes.js.map +1 -1
  120. package/dist/core/server/http.js +12 -6
  121. package/dist/core/server/http.js.map +1 -1
  122. package/dist/core/server/loadRootComponents.js +1 -0
  123. package/dist/core/server/loadRootComponents.js.map +1 -1
  124. package/dist/core/server/operationsServer.js +3 -1
  125. package/dist/core/server/operationsServer.js.map +1 -1
  126. package/dist/core/server/threads/manageThreads.js +50 -35
  127. package/dist/core/server/threads/manageThreads.js.map +1 -1
  128. package/dist/core/utility/hdbTerms.js +3 -0
  129. package/dist/core/utility/hdbTerms.js.map +1 -1
  130. package/dist/core/utility/installation.js.map +1 -1
  131. package/dist/core/utility/lmdb/commonUtility.js +20 -13
  132. package/dist/core/utility/lmdb/commonUtility.js.map +1 -1
  133. package/dist/licensing/usageLicensing.js.map +1 -1
  134. package/dist/replication/knownNodes.js +5 -37
  135. package/dist/replication/knownNodes.js.map +1 -1
  136. package/dist/replication/nodeIdMapping.js +2 -35
  137. package/dist/replication/nodeIdMapping.js.map +1 -1
  138. package/dist/replication/replicationConnection.js +15 -6
  139. package/dist/replication/replicationConnection.js.map +1 -1
  140. package/dist/replication/replicator.js +3 -2
  141. package/dist/replication/replicator.js.map +1 -1
  142. package/dist/replication/setNode.js +1 -1
  143. package/dist/replication/setNode.js.map +1 -1
  144. package/dist/security/certificate.js.map +1 -1
  145. package/licensing/usageLicensing.ts +3 -2
  146. package/npm-shrinkwrap.json +303 -282
  147. package/package.json +4 -3
  148. package/replication/knownNodes.ts +3 -2
  149. package/replication/nodeIdMapping.ts +1 -1
  150. package/replication/replicationConnection.ts +33 -8
  151. package/replication/replicator.ts +7 -2
  152. package/replication/setNode.ts +1 -1
  153. package/security/certificate.ts +2 -1
  154. package/studio/web/assets/{index-v3wIpSYx.js → index-CWN9Wp5V.js} +2 -2
  155. package/studio/web/assets/{index-v3wIpSYx.js.map → index-CWN9Wp5V.js.map} +1 -1
  156. package/studio/web/assets/{index-ChCctErQ.js → index-CzghSAn2.js} +2 -2
  157. package/studio/web/assets/{index-ChCctErQ.js.map → index-CzghSAn2.js.map} +1 -1
  158. package/studio/web/assets/{index-Qu8D43wo.js → index-DMDhGP7N.js} +5 -5
  159. package/studio/web/assets/{index-Qu8D43wo.js.map → index-DMDhGP7N.js.map} +1 -1
  160. package/studio/web/assets/{index.lazy-tVSPM7bX.js → index.lazy-C-yDTGUy.js} +2 -2
  161. package/studio/web/assets/{index.lazy-tVSPM7bX.js.map → index.lazy-C-yDTGUy.js.map} +1 -1
  162. package/studio/web/assets/{profiler-C9as4sv-.js → profiler-0fZAOscv.js} +2 -2
  163. package/studio/web/assets/{profiler-C9as4sv-.js.map → profiler-0fZAOscv.js.map} +1 -1
  164. package/studio/web/assets/{react-redux-RRIhZnM6.js → react-redux-BIxqK8O6.js} +2 -2
  165. package/studio/web/assets/{react-redux-RRIhZnM6.js.map → react-redux-BIxqK8O6.js.map} +1 -1
  166. package/studio/web/assets/{startRecording-DYa4zCXV.js → startRecording-Ca3Gf2MY.js} +2 -2
  167. package/studio/web/assets/{startRecording-DYa4zCXV.js.map → startRecording-Ca3Gf2MY.js.map} +1 -1
  168. package/studio/web/index.html +1 -1
  169. package/core/resources/ResourceInterfaceV2.ts +0 -53
  170. package/core/resources/ResourceV2.ts +0 -67
  171. package/core/resources/analytics/profile.ts +0 -109
  172. package/core/unitTests/apiTests/analytics-test.mjs +0 -38
  173. package/core/v1.d.ts +0 -47
  174. package/core/v1.js +0 -38
  175. package/core/v2.d.ts +0 -47
  176. package/core/v2.js +0 -38
  177. package/dist/core/resources/ResourceInterfaceV2.js.map +0 -1
  178. package/dist/core/resources/ResourceV2.js +0 -27
  179. package/dist/core/resources/ResourceV2.js.map +0 -1
  180. package/dist/core/resources/analytics/profile.js +0 -144
  181. package/dist/core/resources/analytics/profile.js.map +0 -1
@@ -180,7 +180,7 @@ suite('Operations Server', (ctx: ContextWithHarper) => {
180
180
  operation: 'create_table',
181
181
  schema: 'csv_test',
182
182
  table: 'items',
183
- hash_attribute: 'id',
183
+ primary_key: 'id',
184
184
  }),
185
185
  });
186
186
 
@@ -78,7 +78,7 @@ suite('Storage reclamation', (ctx: ContextWithHarper) => {
78
78
  operation: 'create_table',
79
79
  schema: TEST_DATABASE,
80
80
  table: TEST_TABLE,
81
- hash_attribute: 'id',
81
+ primary_key: 'id',
82
82
  expiration: 2, // 2 second expiration (in seconds)
83
83
  eviction: 1, // 1 second eviction (in seconds)
84
84
  audit: true, // Enable audit logging
@@ -68,33 +68,19 @@ suite('My test suite', (ctx: ContextWithHarper) => {
68
68
 
69
69
  Configuration options for `setupHarper()`.
70
70
 
71
- **Interface Definition:**
72
-
73
71
  ```typescript
74
72
  export interface SetupHarperOptions {
75
- /**
76
- * Timeout in milliseconds to wait for Harper to start.
77
- * @default 30000
78
- */
79
73
  startupTimeoutMs?: number;
80
- /**
81
- * Additional configuration options to pass to the Harper CLI.
82
- */
83
74
  config: any;
84
- /**
85
- * Environment variables to set when running Harper.
86
- */
87
75
  env: any;
88
76
  }
89
77
  ```
90
78
 
91
79
  **Properties:**
92
80
 
93
- 30000 (5 seconds), or the value of the `HARPER_INTEGRATION_TEST_STARTUP_TIMEOUT_MS` environment variable if set.
94
-
95
81
  - **`config`** - `object` (optional) - Additional configuration options to pass to the Harper CLI.
96
82
  - **`env`** - `object` (optional) - Additional environment variables to set when starting Harper.
97
- - **`startupTimeoutMs`** - `number` (optional) - Timeout in milliseconds to wait for Harper to start. Defaults to
83
+ - **`startupTimeoutMs`** - `number` (optional) - Timeout in milliseconds to wait for Harper to start. Defaults to 30000, or the value of the `HARPER_INTEGRATION_TEST_STARTUP_TIMEOUT_MS` environment variable if set.
98
84
 
99
85
  **Environment Variables:**
100
86
 
@@ -112,9 +112,13 @@ interface RunHarperCommandOptions {
112
112
  */
113
113
  function runHarperCommand({ args, env, completionMessage, logDir }: RunHarperCommandOptions): Promise<ChildProcess> {
114
114
  const harperScript = getHarperScript();
115
- const proc = spawn('node', ['--trace-warnings', harperScript, ...args], {
116
- env: { ...process.env, ...env },
117
- });
115
+ const proc = spawn(
116
+ 'node',
117
+ ['--trace-warnings', '--force-node-api-uncaught-exceptions-policy=true', harperScript, ...args],
118
+ {
119
+ env: { ...process.env, ...env },
120
+ }
121
+ );
118
122
 
119
123
  let stdoutStream: WriteStream | undefined;
120
124
  let stderrStream: WriteStream | undefined;
@@ -156,12 +160,12 @@ function runHarperCommand({ args, env, completionMessage, logDir }: RunHarperCom
156
160
  proc.on('error', (error) => {
157
161
  reject(error);
158
162
  });
159
- proc.on('exit', (statusCode) => {
163
+ proc.on('exit', (statusCode, signal) => {
160
164
  clearTimeout(timer);
161
165
  if (statusCode === 0) {
162
166
  resolve(proc);
163
167
  } else {
164
- let errorMessage = `Harper process failed with exit code ${statusCode}`;
168
+ let errorMessage = `Harper process failed with exit code/signal ${statusCode ?? signal}`;
165
169
  stderrStream?.write(errorMessage);
166
170
  if (stderr) {
167
171
  errorMessage += `\n\nstderr:\n${stderr}`;
@@ -284,6 +288,29 @@ export async function startHarper(ctx: ContextWithHarper, options?: SetupHarperO
284
288
  return ctx;
285
289
  }
286
290
 
291
+ /**
292
+ * Kill harper process (can be used for teardown, or killing it before a restart)
293
+ * @param ctx
294
+ */
295
+ export async function killHarper(ctx: ContextWithHarper): Promise<void> {
296
+ await new Promise<void>((resolve) => {
297
+ let timer: NodeJS.Timeout;
298
+ ctx.harper.process.on('exit', () => {
299
+ resolve();
300
+ clearTimeout(timer);
301
+ });
302
+ ctx.harper.process.kill();
303
+ timer = setTimeout(() => {
304
+ try {
305
+ ctx.harper.process.kill('SIGKILL');
306
+ } catch {
307
+ // possible that the process terminated but the exit event hasn't fired yet
308
+ }
309
+ resolve();
310
+ }, 200);
311
+ });
312
+ }
313
+
287
314
  /**
288
315
  * Tears down a Harper instance and cleans up all resources.
289
316
  *
@@ -305,22 +332,7 @@ export async function startHarper(ctx: ContextWithHarper, options?: SetupHarperO
305
332
  * ```
306
333
  */
307
334
  export async function teardownHarper(ctx: ContextWithHarper): Promise<void> {
308
- await new Promise<void>((resolve) => {
309
- let timer: NodeJS.Timeout;
310
- ctx.harper.process.on('exit', () => {
311
- resolve();
312
- clearTimeout(timer);
313
- });
314
- ctx.harper.process.kill();
315
- timer = setTimeout(() => {
316
- try {
317
- ctx.harper.process.kill('SIGKILL');
318
- } catch {
319
- // possible that the process terminated but the exit event hasn't fired yet
320
- }
321
- resolve();
322
- }, 200);
323
- });
335
+ await killHarper(ctx);
324
336
 
325
337
  await releaseLoopbackAddress(ctx.harper.hostname);
326
338
 
package/core/package.json CHANGED
@@ -1,8 +1,7 @@
1
1
  {
2
2
  "name": "harper",
3
3
  "description": "Harper is an open-source Node.js performance platform that unifies database, cache, application, and messaging layers into one in-memory process.",
4
- "version": "5.0.0-alpha.8",
5
- "private": true,
4
+ "version": "5.0.0-beta.1",
6
5
  "license": "Apache-2.0",
7
6
  "homepage": "https://harper.fast",
8
7
  "bugs": {
@@ -24,7 +23,25 @@
24
23
  },
25
24
  "files": [
26
25
  "dist",
26
+ "bin",
27
+ "components",
28
+ "config",
29
+ "dataLayer",
30
+ "json",
31
+ "launchServiceScripts",
32
+ "resources",
33
+ "security",
34
+ "server",
35
+ "sqlTranslator",
36
+ "upgrade",
37
+ "utility",
38
+ "validation",
39
+ "studio",
27
40
  "static",
41
+ "index.*",
42
+ "v1.*",
43
+ "v2.*",
44
+ "README.md",
28
45
  "SECURITY.md",
29
46
  "SUPPORT.md",
30
47
  "CODE_OF_CONDUCT.md"
@@ -32,7 +49,7 @@
32
49
  "scripts": {
33
50
  "build": "tsc --project tsconfig.build.json",
34
51
  "build:watch": "npm run build -- --watch --incremental",
35
- "package": "npm run build; npm shrinkwrap && npm pack",
52
+ "package": "./build-tools/build.sh",
36
53
  "lint": "oxlint --deny-warnings .",
37
54
  "lint:required": "oxlint --quiet .",
38
55
  "lint:fix": "npm run lint -- --fix",
@@ -140,7 +157,7 @@
140
157
  "@fastify/cors": "~9.0.1",
141
158
  "@fastify/static": "~7.0.4",
142
159
  "@harperfast/extended-iterable": "^1.0.1",
143
- "@harperfast/rocksdb-js": "^0.1.11",
160
+ "@harperfast/rocksdb-js": "^0.1.12",
144
161
  "@turf/area": "6.5.0",
145
162
  "@turf/boolean-contains": "6.5.0",
146
163
  "@turf/boolean-disjoint": "6.5.0",
@@ -151,6 +168,7 @@
151
168
  "@turf/helpers": "6.5.0",
152
169
  "@turf/length": "6.5.0",
153
170
  "alasql": "4.6.6",
171
+ "amaro": "^1.1.8",
154
172
  "argon2": "0.44.0",
155
173
  "asn1js": "3.0.7",
156
174
  "cbor-x": "1.6.4",
@@ -175,7 +193,7 @@
175
193
  "json2csv": "5.0.7",
176
194
  "jsonata": "1.8.7",
177
195
  "jsonwebtoken": "9.0.3",
178
- "lmdb": "3.5.1",
196
+ "lmdb": "3.5.2",
179
197
  "lodash": "4.17.21",
180
198
  "mathjs": "11.12.0",
181
199
  "micromatch": "^4.0.8",
@@ -30,7 +30,7 @@ export interface ResourceInterface<Record extends object = any>
30
30
  allowUpdate(user: User, record: Promise<Record & RecordObject>, context: Context): boolean | Promise<boolean>;
31
31
  put?(
32
32
  record: Record & RecordObject,
33
- target: RequestTargetOrId
33
+ target?: RequestTargetOrId
34
34
  ): void | (Record & Partial<RecordObject>) | Promise<void | (Record & Partial<RecordObject>)>;
35
35
  patch?(
36
36
  record: Partial<Record & RecordObject>,
@@ -71,11 +71,17 @@ const { validateAttribute } = lmdbProcessRows;
71
71
 
72
72
  export type Attribute = {
73
73
  name: string;
74
- type: string;
74
+ type: 'ID' | 'Int' | 'Float' | 'Long' | 'String' | 'Boolean' | 'Date' | 'Bytes' | 'Any' | 'BigInt' | 'Blob' | string;
75
75
  assignCreatedTime?: boolean;
76
76
  assignUpdatedTime?: boolean;
77
+ nullable?: boolean;
77
78
  expiresAt?: boolean;
78
79
  isPrimaryKey?: boolean;
80
+ indexed?: unknown;
81
+ relationship?: unknown;
82
+ computed?: unknown;
83
+ properties?: Array<Attribute>;
84
+ elements?: Attribute;
79
85
  };
80
86
 
81
87
  type MaybePromise<T> = T | Promise<T>;
@@ -106,7 +112,7 @@ export interface Table {
106
112
  databasePath: string;
107
113
  tableName: string;
108
114
  databaseName: string;
109
- attributes: any[];
115
+ attributes: Attribute[];
110
116
  primaryKey: string;
111
117
  splitSegments?: boolean;
112
118
  replicate?: boolean;
@@ -141,7 +147,7 @@ export function makeTable(options) {
141
147
  } = options;
142
148
  let { expirationMS: expirationMs, evictionMS: evictionMs, audit, trackDeletes } = options;
143
149
  evictionMs ??= 0;
144
- let { attributes } = options;
150
+ let { attributes }: { attributes: Attribute[] } = options;
145
151
  if (!attributes) attributes = [];
146
152
  const updateRecord = recordUpdater(primaryStore, tableId, auditStore);
147
153
  let sourceLoad: any; // if a source has a load function (replicator), record it here
@@ -2233,11 +2239,12 @@ export function makeTable(options) {
2233
2239
  entries = transformToEntries(entries, select, context, readTxn, null);
2234
2240
  let ordered;
2235
2241
  // if we are doing post-ordering, we need to get records first, then sort them
2236
- results.iterate = function () {
2242
+ results.iterate = function (options: { async: boolean }) {
2237
2243
  let sortedArrayIterator: IterableIterator<any>;
2238
- const dbIterator = entries[Symbol.asyncIterator]
2239
- ? entries[Symbol.asyncIterator]()
2240
- : entries[Symbol.iterator]();
2244
+ const dbIterator =
2245
+ options?.async && entries[Symbol.asyncIterator]
2246
+ ? entries[Symbol.asyncIterator]()
2247
+ : entries[Symbol.iterator]();
2241
2248
  let dbDone: boolean;
2242
2249
  const dbOrderedAttribute = sort.dbOrderedAttribute;
2243
2250
  let enqueuedEntryForNextGroup: any;
@@ -2351,7 +2358,10 @@ export function makeTable(options) {
2351
2358
  };
2352
2359
  applySortingOnSelect(sort);
2353
2360
  } else {
2354
- results.iterate = (entries[Symbol.asyncIterator] || entries[Symbol.iterator]).bind(entries);
2361
+ results.iterate = (options: { async: boolean }) => {
2362
+ if (options?.async && entries[Symbol.asyncIterator]) return entries[Symbol.asyncIterator]();
2363
+ else return entries[Symbol.iterator]();
2364
+ };
2355
2365
  results = results.map(function (entry) {
2356
2366
  try {
2357
2367
  // because this is a part of a stream of results, we will often be continuing to iterate over the results when there are errors,
@@ -2893,7 +2903,7 @@ export function makeTable(options) {
2893
2903
  }
2894
2904
  validate(record: any, patch?: boolean) {
2895
2905
  let validationErrors;
2896
- const validateValue = (value, attribute, name) => {
2906
+ const validateValue = (value, attribute: Attribute, name) => {
2897
2907
  if (attribute.type && value != null) {
2898
2908
  if (patch && value.__op__) value = value.value;
2899
2909
  if (attribute.properties) {
@@ -3063,7 +3073,7 @@ export function makeTable(options) {
3063
3073
  getUpdatedTime() {
3064
3074
  return this.#version;
3065
3075
  }
3066
- static async addAttributes(attributesToAdd) {
3076
+ static async addAttributes(attributesToAdd: Attribute[]) {
3067
3077
  const new_attributes = attributes.slice(0);
3068
3078
  for (const attribute of attributesToAdd) {
3069
3079
  if (!attribute.name) throw new ClientError('Attribute name is required');
@@ -3927,7 +3937,12 @@ export function makeTable(options) {
3927
3937
  // it should be resolved now and we can use the value it saved.
3928
3938
  clearTimeout(timer);
3929
3939
  const entry = primaryStore.getEntry(id);
3930
- if (!entry || !entry.value || entry.metadataFlags & (INVALIDATED | EVICTED))
3940
+ if (
3941
+ !entry ||
3942
+ !entry.value ||
3943
+ entry.metadataFlags & (INVALIDATED | EVICTED) ||
3944
+ (entry.expiresAt != undefined && entry.expiresAt < Date.now())
3945
+ )
3931
3946
  // try again
3932
3947
  whenResolved(getFromSource(source, id, primaryStore.getEntry(id), context));
3933
3948
  else whenResolved(entry);
@@ -1,7 +1,7 @@
1
1
  import type { Metric } from './write.ts';
2
2
  import harperLogger from '../../utility/logging/harper_logger.js';
3
3
  const { forComponent } = harperLogger;
4
- import { getAnalyticsHostnameTable } from './hostnames.ts';
4
+ import { getAnalyticsHostnameTable, stableNodeId } from './hostnames.ts';
5
5
  import type { Condition, Conditions } from '../ResourceInterface.ts';
6
6
  import { METRIC, type BuiltInMetricName } from './metadata.ts';
7
7
  import { CONFIG_PARAMS } from '../../utility/hdbTerms.ts';
@@ -12,9 +12,11 @@ const defaultCustomMetricWindow = 1000 * 60 * 60 * 24 * 7;
12
12
 
13
13
  const log = forComponent('analytics').conditional;
14
14
 
15
- async function lookupHostname(nodeId: number): Promise<string> {
15
+ async function lookupHostname(nodeId: number): Promise<string | undefined> {
16
16
  const result = await getAnalyticsHostnameTable().get(nodeId);
17
- return result.hostname;
17
+ if (result?.hostname) return result.hostname;
18
+ if (nodeId === stableNodeId(server.hostname)) return server.hostname;
19
+ return undefined;
18
20
  }
19
21
 
20
22
  function isSelected(querySelect: string[], attr: string) {
@@ -57,25 +59,21 @@ function conformCondition(condition: Condition): Condition {
57
59
  };
58
60
  }
59
61
 
60
- async function coalesceResults(results: Metric[], window: number): Promise<Metric[]> {
61
- const coalescedResults: Metric[] = [];
62
- let coalesceId;
63
- let lastCoalescedId = new Map<string, number>();
62
+ async function* coalesceResults(results: Metric[], window: number): AsyncGenerator<Metric> {
63
+ let coalesceId: any;
64
64
  for await (const result of results) {
65
65
  const id = result.id;
66
66
  if (!coalesceId) {
67
67
  coalesceId = id;
68
68
  }
69
69
  const delta = Math.abs(id - coalesceId);
70
- if (delta < window && lastCoalescedId.get(result.node) !== id) {
71
- coalescedResults.push({ ...result, id: coalesceId });
72
- lastCoalescedId[result.node] = id;
70
+ if (delta < window) {
71
+ yield { ...result, id: coalesceId };
73
72
  } else {
74
- coalescedResults.push(result);
73
+ yield result;
75
74
  coalesceId = id;
76
75
  }
77
76
  }
78
- return coalescedResults;
79
77
  }
80
78
 
81
79
  interface GetAnalyticsOpts {
@@ -99,19 +97,27 @@ export async function get(metric: string, opts?: GetAnalyticsOpts): Promise<Metr
99
97
  select.push('id');
100
98
  }
101
99
 
102
- if (startTime) {
100
+ if (startTime && endTime) {
103
101
  conditions.push({
104
102
  attribute: 'id',
105
- comparator: 'greater_than_equal',
106
- value: startTime,
107
- });
108
- }
109
- if (endTime) {
110
- conditions.push({
111
- attribute: 'id',
112
- comparator: 'less_than',
113
- value: endTime,
103
+ comparator: 'between',
104
+ value: [startTime, endTime],
114
105
  });
106
+ } else {
107
+ if (startTime) {
108
+ conditions.push({
109
+ attribute: 'id',
110
+ comparator: 'greater_than_equal',
111
+ value: startTime,
112
+ });
113
+ }
114
+ if (endTime) {
115
+ conditions.push({
116
+ attribute: 'id',
117
+ comparator: 'less_than',
118
+ value: endTime,
119
+ });
120
+ }
115
121
  }
116
122
 
117
123
  const request = { conditions, allowConditionsOnDynamicAttributes: true };
@@ -128,16 +134,17 @@ export async function get(metric: string, opts?: GetAnalyticsOpts): Promise<Metr
128
134
  result['id'] = result['id'][0];
129
135
  if (isSelected(select, 'node')) {
130
136
  log.trace?.(`get_analytics lookup hostname for nodeId: ${nodeId}`);
131
- result['node'] = await lookupHostname(nodeId);
137
+ const hostname = await lookupHostname(nodeId);
138
+ result['node'] = hostname ?? nodeId;
132
139
  }
133
140
  log.trace?.(`get_analytics result:`, JSON.stringify(result));
134
141
  return result;
135
142
  });
136
143
 
137
144
  if (opts?.coalesceTime) {
138
- // coalescing window is the aggregate period plus 10% & converted to milliseconds
139
- const window = envGet(CONFIG_PARAMS.ANALYTICS_AGGREGATEPERIOD) * 1.1 * 1000;
140
- results = await coalesceResults(results, window);
145
+ // coalescing window is the aggregate period converted to milliseconds
146
+ const window = envGet(CONFIG_PARAMS.ANALYTICS_AGGREGATEPERIOD) * 1000;
147
+ results = coalesceResults(results, window);
141
148
  }
142
149
 
143
150
  return results;
@@ -1,5 +1,5 @@
1
1
  import { parentPort, threadId } from 'worker_threads';
2
- import { setChildListenerByType } from '../../server/threads/manageThreads.js';
2
+ import { onMessageByType } from '../../server/threads/manageThreads.js';
3
3
  import { getDatabases, table } from '../databases.ts';
4
4
  import type { Databases, Table, Tables } from '../databases.ts';
5
5
  import harperLogger from '../../utility/logging/harper_logger.js';
@@ -14,10 +14,6 @@ import { server } from '../../server/Server.ts';
14
14
  import * as fs from 'node:fs';
15
15
  import { getAnalyticsHostnameTable, nodeIds, stableNodeId } from './hostnames.ts';
16
16
  import { METRIC } from './metadata.ts';
17
- setTimeout(() => {
18
- // let everything load before we actually load and start the profiler
19
- import('./profile.ts');
20
- }, 1000);
21
17
  import { RocksDatabase } from '@harperfast/rocksdb-js';
22
18
 
23
19
  const log = forComponent('analytics').conditional;
@@ -230,7 +226,7 @@ export async function recordHostname() {
230
226
  hostname,
231
227
  };
232
228
  log.trace?.(`recordHostname storing hostname: ${JSON.stringify(hostnameRecord)}`);
233
- hostnamesTable.put(hostnameRecord.id, hostnameRecord);
229
+ await hostnamesTable.put(hostnameRecord.id, hostnameRecord);
234
230
  }
235
231
  }
236
232
 
@@ -654,7 +650,7 @@ function getAnalyticsTable() {
654
650
  );
655
651
  }
656
652
 
657
- setChildListenerByType(ANALYTICS_REPORT_TYPE, recordAnalytics);
653
+ if (!parentPort) onMessageByType(ANALYTICS_REPORT_TYPE, recordAnalytics);
658
654
  let scheduledTasksRunning;
659
655
  function startScheduledTasks() {
660
656
  scheduledTasksRunning = true;
@@ -1,3 +1,4 @@
1
+ import { EventEmitter } from 'node:events';
1
2
  import { initSync, getHdbBasePath, get as envGet } from '../utility/environment/environmentManager.js';
2
3
  import { INTERNAL_DBIS_NAME } from '../utility/lmdb/terms.js';
3
4
  import { open, compareKeys, type Database, type RootDatabase } from 'lmdb';
@@ -97,6 +98,14 @@ interface RocksRootDatabase extends RocksDatabaseEx {
97
98
 
98
99
  export type RootDatabaseKind = LMDBRootDatabase | RocksRootDatabase;
99
100
 
101
+ export type DatabaseWatcherEventMap = {
102
+ updateTable: [table: Table, originIsNotCluster?: boolean];
103
+ dropTable: [tableName: string, databaseName: string];
104
+ dropDatabase: [databaseName: string];
105
+ };
106
+
107
+ export const databaseEventsEmitter = new EventEmitter<DatabaseWatcherEventMap>();
108
+
100
109
  export const tables: Tables = Object.create(null);
101
110
  export const databases: Databases = Object.create(null);
102
111
 
@@ -127,8 +136,6 @@ _assignPackageExport('databases', databases);
127
136
  _assignPackageExport('tables', tables);
128
137
 
129
138
  const NEXT_TABLE_ID = Symbol.for('next-table-id');
130
- const tableListeners = [];
131
- const dbRemovalListeners = [];
132
139
  let loadedDatabases; // indicates if we have loaded databases from the file system yet
133
140
 
134
141
  // This is used to track all the databases that are found when iterating through the file system so that anything that is missing
@@ -577,9 +584,7 @@ function initStores(
577
584
  })
578
585
  );
579
586
  table.schemaVersion = 1;
580
- for (const listener of tableListeners) {
581
- listener(table);
582
- }
587
+ databaseEventsEmitter.emit('updateTable', table);
583
588
  }
584
589
  }
585
590
  return rootStore;
@@ -600,7 +605,7 @@ export function resetDatabases() {
600
605
  const table = db[tableName];
601
606
  if (table.primaryStore.path === path) {
602
607
  delete databases[store.databaseName];
603
- dbRemovalListeners.forEach((listener) => listener(store.databaseName));
608
+ databaseEventsEmitter.emit('dropDatabase', store.databaseName);
604
609
  break;
605
610
  }
606
611
  }
@@ -745,6 +750,7 @@ export async function dropDatabase(databaseName) {
745
750
  await unlink(rootStore.path);
746
751
  }
747
752
  }
753
+ databaseEventsEmitter.emit('dropTable', tableName, databaseName);
748
754
  }
749
755
  if (!rootStore) {
750
756
  rootStore = database({ database: databaseName, table: null });
@@ -762,7 +768,7 @@ export async function dropDatabase(databaseName) {
762
768
  delete tables[DEFINED_TABLES];
763
769
  }
764
770
  delete databases[databaseName];
765
- dbRemovalListeners.forEach((listener) => listener(databaseName));
771
+ databaseEventsEmitter.emit('dropDatabase', databaseName);
766
772
  await deleteRootBlobPathsForDB(rootStore);
767
773
  }
768
774
  // opens an index, consulting with custom indexes that may use alternate store configuration
@@ -1090,9 +1096,7 @@ export function table<TableResourceType>(tableDefinition: TableDefinition): Tabl
1090
1096
 
1091
1097
  Table.origin = origin;
1092
1098
  if (hasChanges) {
1093
- for (const listener of tableListeners) {
1094
- listener(Table, origin !== 'cluster');
1095
- }
1099
+ databaseEventsEmitter.emit('updateTable', Table, origin !== 'cluster');
1096
1100
  }
1097
1101
  if (expiration || eviction || scanInterval)
1098
1102
  Table.setTTLExpiration({
@@ -1244,24 +1248,31 @@ export function dropTableMeta({ table: tableName, database: databaseName }) {
1244
1248
  for (const key of dbisDb.getKeys({ start: tableName + '/', end: tableName + '0' })) {
1245
1249
  removals.push(dbisDb.remove(key));
1246
1250
  }
1251
+ databaseEventsEmitter.emit('dropTable', tableName, databaseName);
1247
1252
  return Promise.all(removals);
1248
1253
  }
1249
1254
 
1250
- export function onUpdatedTable(listener) {
1251
- tableListeners.push(listener);
1255
+ export function onUpdatedTable(listener: (table: Table) => void) {
1256
+ databaseEventsEmitter.on('updateTable', listener);
1257
+ return {
1258
+ remove() {
1259
+ databaseEventsEmitter.off('updateTable', listener);
1260
+ },
1261
+ };
1262
+ }
1263
+ export function onRemovedTable(listener: (tableName: string, databaseName: string) => void) {
1264
+ databaseEventsEmitter.on('dropTable', listener);
1252
1265
  return {
1253
1266
  remove() {
1254
- const index = tableListeners.indexOf(listener);
1255
- if (index > -1) tableListeners.splice(index, 1);
1267
+ databaseEventsEmitter.off('dropTable', listener);
1256
1268
  },
1257
1269
  };
1258
1270
  }
1259
- export function onRemovedDB(listener) {
1260
- dbRemovalListeners.push(listener);
1271
+ export function onRemovedDB(listener: (databaseName: string) => void) {
1272
+ databaseEventsEmitter.on('dropDatabase', listener);
1261
1273
  return {
1262
1274
  remove() {
1263
- const index = dbRemovalListeners.indexOf(listener);
1264
- if (index > -1) dbRemovalListeners.splice(index, 1);
1275
+ databaseEventsEmitter.off('dropDatabase', listener);
1265
1276
  },
1266
1277
  };
1267
1278
  }
@@ -337,11 +337,16 @@ export function searchByIndex(
337
337
  })
338
338
  );
339
339
  }
340
- : (entry) => {
341
- if (entry.value == null && !(entry.metadataFlags & (INVALIDATED | EVICTED))) return SKIP;
342
- Object.freeze(entry.value);
343
- recordRead(entry);
344
- return entry;
340
+ : function (entry) {
341
+ let result: any;
342
+ if (entry.value == null && !(entry.metadataFlags & (INVALIDATED | EVICTED))) result = SKIP;
343
+ else {
344
+ Object.freeze(entry.value);
345
+ recordRead(entry);
346
+ result = entry;
347
+ }
348
+ if (this.isSync) return result;
349
+ return new Promise((resolve) => setImmediate(() => resolve(result)));
345
350
  }
346
351
  );
347
352
  results.hasEntries = true;
@@ -102,7 +102,7 @@ export async function authentication(request, nextHandler) {
102
102
  break;
103
103
  }
104
104
  }
105
- request.session = session || (session = {});
105
+ request.session = session ? { ...session } : (session = {});
106
106
  }
107
107
 
108
108
  const authAuditLog = (username, status, strategy) => {