@hotmeshio/hotmesh 0.7.0 → 0.8.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.
Files changed (73) hide show
  1. package/.claude/settings.local.json +7 -0
  2. package/README.md +1 -1
  3. package/build/index.d.ts +1 -3
  4. package/build/index.js +1 -5
  5. package/build/modules/utils.js +3 -31
  6. package/build/package.json +16 -27
  7. package/build/services/activities/activity.d.ts +43 -6
  8. package/build/services/activities/activity.js +262 -54
  9. package/build/services/activities/await.js +2 -2
  10. package/build/services/activities/cycle.js +1 -1
  11. package/build/services/activities/hook.d.ts +5 -0
  12. package/build/services/activities/hook.js +22 -19
  13. package/build/services/activities/interrupt.js +17 -25
  14. package/build/services/activities/signal.d.ts +4 -2
  15. package/build/services/activities/signal.js +27 -24
  16. package/build/services/activities/worker.js +2 -2
  17. package/build/services/collator/index.d.ts +123 -25
  18. package/build/services/collator/index.js +224 -101
  19. package/build/services/connector/factory.d.ts +1 -1
  20. package/build/services/connector/factory.js +1 -11
  21. package/build/services/engine/index.d.ts +5 -5
  22. package/build/services/engine/index.js +36 -15
  23. package/build/services/router/consumption/index.js +1 -1
  24. package/build/services/search/factory.js +1 -9
  25. package/build/services/store/factory.js +1 -9
  26. package/build/services/store/index.d.ts +5 -0
  27. package/build/services/store/providers/postgres/kvsql.d.ts +4 -0
  28. package/build/services/store/providers/postgres/kvsql.js +4 -0
  29. package/build/services/store/providers/postgres/kvtransaction.d.ts +2 -0
  30. package/build/services/store/providers/postgres/kvtransaction.js +23 -0
  31. package/build/services/store/providers/postgres/kvtypes/hash/basic.d.ts +51 -0
  32. package/build/services/store/providers/postgres/kvtypes/hash/basic.js +193 -1
  33. package/build/services/store/providers/postgres/kvtypes/hash/index.d.ts +4 -0
  34. package/build/services/store/providers/postgres/kvtypes/hash/index.js +6 -0
  35. package/build/services/store/providers/postgres/postgres.d.ts +20 -0
  36. package/build/services/store/providers/postgres/postgres.js +38 -1
  37. package/build/services/stream/factory.js +1 -17
  38. package/build/services/stream/providers/postgres/scout.js +2 -2
  39. package/build/services/sub/factory.js +1 -9
  40. package/build/services/sub/index.d.ts +1 -1
  41. package/build/services/sub/providers/postgres/postgres.d.ts +1 -1
  42. package/build/services/sub/providers/postgres/postgres.js +25 -10
  43. package/build/services/task/index.d.ts +1 -1
  44. package/build/services/task/index.js +2 -6
  45. package/build/types/index.d.ts +0 -1
  46. package/build/types/index.js +1 -4
  47. package/build/types/provider.d.ts +1 -1
  48. package/index.ts +0 -4
  49. package/package.json +16 -27
  50. package/build/services/connector/providers/ioredis.d.ts +0 -9
  51. package/build/services/connector/providers/ioredis.js +0 -26
  52. package/build/services/connector/providers/redis.d.ts +0 -9
  53. package/build/services/connector/providers/redis.js +0 -38
  54. package/build/services/search/providers/redis/ioredis.d.ts +0 -23
  55. package/build/services/search/providers/redis/ioredis.js +0 -189
  56. package/build/services/search/providers/redis/redis.d.ts +0 -23
  57. package/build/services/search/providers/redis/redis.js +0 -202
  58. package/build/services/store/providers/redis/_base.d.ts +0 -137
  59. package/build/services/store/providers/redis/_base.js +0 -980
  60. package/build/services/store/providers/redis/ioredis.d.ts +0 -20
  61. package/build/services/store/providers/redis/ioredis.js +0 -190
  62. package/build/services/store/providers/redis/redis.d.ts +0 -18
  63. package/build/services/store/providers/redis/redis.js +0 -199
  64. package/build/services/stream/providers/redis/ioredis.d.ts +0 -61
  65. package/build/services/stream/providers/redis/ioredis.js +0 -272
  66. package/build/services/stream/providers/redis/redis.d.ts +0 -61
  67. package/build/services/stream/providers/redis/redis.js +0 -305
  68. package/build/services/sub/providers/redis/ioredis.d.ts +0 -20
  69. package/build/services/sub/providers/redis/ioredis.js +0 -161
  70. package/build/services/sub/providers/redis/redis.d.ts +0 -18
  71. package/build/services/sub/providers/redis/redis.js +0 -148
  72. package/build/types/redis.d.ts +0 -258
  73. package/build/types/redis.js +0 -11
@@ -3,20 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SearchServiceFactory = void 0;
4
4
  const utils_1 = require("../../modules/utils");
5
5
  const postgres_1 = require("./providers/postgres/postgres");
6
- const ioredis_1 = require("./providers/redis/ioredis");
7
- const redis_1 = require("./providers/redis/redis");
8
6
  class SearchServiceFactory {
9
7
  static async init(providerClient, storeProviderClient, namespace, appId, logger) {
10
8
  let service;
11
9
  if ((0, utils_1.identifyProvider)(providerClient) === 'postgres') {
12
10
  service = new postgres_1.PostgresSearchService(providerClient, storeProviderClient);
13
- }
14
- else if ((0, utils_1.identifyProvider)(providerClient) === 'redis') {
15
- service = new redis_1.RedisSearchService(providerClient, storeProviderClient);
16
- }
17
- else {
18
- service = new ioredis_1.IORedisSearchService(providerClient, storeProviderClient);
19
- }
11
+ } //etc
20
12
  await service.init(namespace, appId, logger);
21
13
  return service;
22
14
  }
@@ -2,19 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.StoreServiceFactory = void 0;
4
4
  const utils_1 = require("../../modules/utils");
5
- const ioredis_1 = require("./providers/redis/ioredis");
6
- const redis_1 = require("./providers/redis/redis");
7
5
  const postgres_1 = require("./providers/postgres/postgres");
8
6
  class StoreServiceFactory {
9
7
  static async init(providerClient, namespace, appId, logger) {
10
8
  let service;
11
- if ((0, utils_1.identifyProvider)(providerClient) === 'redis') {
12
- service = new redis_1.RedisStoreService(providerClient);
13
- }
14
- else if ((0, utils_1.identifyProvider)(providerClient) === 'ioredis') {
15
- service = new ioredis_1.IORedisStoreService(providerClient);
16
- }
17
- else if ((0, utils_1.identifyProvider)(providerClient) === 'postgres') {
9
+ if ((0, utils_1.identifyProvider)(providerClient) === 'postgres') {
18
10
  service = new postgres_1.PostgresStoreService(providerClient);
19
11
  } //etc
20
12
  await service.init(namespace, appId, logger);
@@ -40,6 +40,10 @@ declare abstract class StoreService<Provider extends ProviderClient, Transaction
40
40
  abstract getJobStats(jobKeys: string[]): Promise<JobStatsRange>;
41
41
  abstract getJobIds(indexKeys: string[], idRange: [number, number]): Promise<IdsData>;
42
42
  abstract setStatus(collationKeyStatus: number, jobId: string, appId: string, transaction?: TransactionProvider): Promise<any>;
43
+ abstract setStatusAndCollateGuid(statusDelta: number, // typically (N - 1)
44
+ threshold: number, // typically 0 (but supports 0,1,12,...)
45
+ jobId: string, appId: string, guidField: string, //
46
+ guidWeight: number, transaction?: ProviderTransaction): Promise<any>;
43
47
  abstract getStatus(jobId: string, appId: string): Promise<number>;
44
48
  abstract setStateNX(jobId: string, appId: string, status?: number, entity?: string): Promise<boolean>;
45
49
  abstract setState(state: StringAnyType, status: number | null, jobId: string, symbolNames: string[], dIds: StringStringType, transaction?: TransactionProvider): Promise<string>;
@@ -47,6 +51,7 @@ declare abstract class StoreService<Provider extends ProviderClient, Transaction
47
51
  abstract getState(jobId: string, consumes: Consumes, dIds: StringStringType): Promise<[StringAnyType, number] | undefined>;
48
52
  abstract getRaw(jobId: string): Promise<StringStringType>;
49
53
  abstract collate(jobId: string, activityId: string, amount: number, dIds: StringStringType, transaction?: TransactionProvider): Promise<number>;
54
+ abstract collateLeg2Entry(jobId: string, activityId: string, guid: string, dIds: StringStringType, transaction?: TransactionProvider): Promise<[number, number]>;
50
55
  abstract collateSynthetic(jobId: string, guid: string, amount: number, transaction?: TransactionProvider): Promise<number>;
51
56
  abstract getSchema(activityId: string, appVersion: AppVID): Promise<any>;
52
57
  abstract getSchemas(appVersion: AppVID): Promise<Record<string, any>>;
@@ -58,6 +58,10 @@ export declare class KVSQL {
58
58
  _hmget: (key: string, fields: string[]) => import("./kvtypes/hash/types").SqlResult;
59
59
  hgetall: (key: string, multi?: ProviderTransaction) => Promise<Record<string, string>>;
60
60
  hincrbyfloat: (key: string, field: string, increment: number, multi?: ProviderTransaction) => Promise<number>;
61
+ collateLeg2Entry: (key: string, activityField: string, increment: number, guidField: string, multi?: ProviderTransaction) => Promise<[number, number]>;
62
+ _collateLeg2Entry: (key: string, activityField: string, increment: number, guidField: string) => import("./kvtypes/hash/types").SqlResult;
63
+ setStatusAndCollateGuid: (key: string, statusDelta: number, threshold: number, guidField: string, guidWeight: number, multi?: ProviderTransaction) => Promise<number>;
64
+ _setStatusAndCollateGuid: (key: string, statusDelta: number, threshold: number, guidField: string, guidWeight: number) => import("./kvtypes/hash/types").SqlResult;
61
65
  _hincrbyfloat: (key: string, field: string, increment: number) => import("./kvtypes/hash/types").SqlResult;
62
66
  hscan: (key: string, cursor: string, count?: number, pattern?: string, multi?: ProviderTransaction) => Promise<import("./kvtypes/hash/index").HScanResult>;
63
67
  _hscan: (key: string, cursor: string, count: number, pattern?: string) => import("./kvtypes/hash/types").SqlResult;
@@ -34,6 +34,10 @@ class KVSQL {
34
34
  this._hmget = (...args) => this.hash._hmget(...args);
35
35
  this.hgetall = (...args) => this.hash.hgetall(...args);
36
36
  this.hincrbyfloat = (...args) => this.hash.hincrbyfloat(...args);
37
+ this.collateLeg2Entry = (...args) => this.hash.collateLeg2Entry(...args);
38
+ this._collateLeg2Entry = (...args) => this.hash._collateLeg2Entry(...args);
39
+ this.setStatusAndCollateGuid = (...args) => this.hash.setStatusAndCollateGuid(...args);
40
+ this._setStatusAndCollateGuid = (...args) => this.hash._setStatusAndCollateGuid(...args);
37
41
  this._hincrbyfloat = (...args) => this.hash._hincrbyfloat(...args);
38
42
  this.hscan = (...args) => this.hash.hscan(...args);
39
43
  this._hscan = (...args) => this.hash._hscan(...args);
@@ -31,6 +31,8 @@ export declare class KVTransaction implements KVSQLProviderTransaction {
31
31
  zrem(key: string, member: string): this;
32
32
  zrank(key: string, member: string): this;
33
33
  scan(cursor: number, count?: number): this;
34
+ collateLeg2Entry(key: string, activityField: string, increment: number, guidField: string): this;
35
+ setStatusAndCollateGuid(key: string, statusDelta: number, threshold: number, guidField: string, guidWeight: number): this;
34
36
  rename(oldKey: string, newKey: string): this;
35
37
  exec(): Promise<any[]>;
36
38
  }
@@ -8,8 +8,13 @@ const pg_format_1 = __importDefault(require("pg-format"));
8
8
  /**
9
9
  * Utility function to format SQL commands with parameters.
10
10
  * Replaces $1, $2, etc., with %L or %s based on parameter types.
11
+ * Commands with no parameters (e.g., NOTIFY with inline values)
12
+ * are returned as-is to avoid pg-format corrupting the SQL.
11
13
  */
12
14
  function formatSqlCommand(sql, params) {
15
+ if (!params || params.length === 0) {
16
+ return sql;
17
+ }
13
18
  const formatParams = [];
14
19
  // Replace $1, $2, etc., with %L or %s and collect parameters
15
20
  const formattedSql = sql.replace(/\$(\d+)/g, (match, p1) => {
@@ -182,6 +187,18 @@ class KVTransaction {
182
187
  };
183
188
  return this.addCommand(sql, params, 'object', transform);
184
189
  }
190
+ collateLeg2Entry(key, activityField, increment, guidField) {
191
+ const { sql, params } = this.kvsql._collateLeg2Entry(key, activityField, increment, guidField);
192
+ return this.addCommand(sql, params, 'array', (rows) => {
193
+ return [parseFloat(rows[0].activity_value), parseFloat(rows[0].guid_value)];
194
+ });
195
+ }
196
+ setStatusAndCollateGuid(key, statusDelta, threshold, guidField, guidWeight) {
197
+ const { sql, params } = this.kvsql._setStatusAndCollateGuid(key, statusDelta, threshold, guidField, guidWeight);
198
+ return this.addCommand(sql, params, 'number', (rows) => {
199
+ return parseFloat(rows[0].value);
200
+ });
201
+ }
185
202
  rename(oldKey, newKey) {
186
203
  const { sql, params } = this.kvsql._rename(oldKey, newKey);
187
204
  return this.addCommand(sql, params, 'void');
@@ -240,6 +257,12 @@ class KVTransaction {
240
257
  return results;
241
258
  }
242
259
  catch (err) {
260
+ console.error('kvtransaction-exec-error', {
261
+ error: err.message,
262
+ commandCount: this.commands.length,
263
+ commandTypes: this.commands.map(c => c.returnType),
264
+ commandSqlPreviews: this.commands.map(c => c.sql.substring(0, 80)),
265
+ });
243
266
  await client.query('ROLLBACK');
244
267
  throw err;
245
268
  }
@@ -7,6 +7,25 @@ export declare function createBasicOperations(context: HashContext['context']):
7
7
  hmget(key: string, fields: string[], multi?: ProviderTransaction): Promise<(string | null)[]>;
8
8
  hgetall(key: string, multi?: ProviderTransaction): Promise<Record<string, string>>;
9
9
  hincrbyfloat(key: string, field: string, increment: number, multi?: ProviderTransaction): Promise<number>;
10
+ /**
11
+ * 2b) KVSQL METHOD: Leg2 Entry Compound
12
+ * --------------------------------------
13
+ * Atomically increments the activity Leg2 entry counter and seeds
14
+ * the GUID ledger with the ordinal IF NOT EXISTS.
15
+ *
16
+ * Returns: [activityValue, guidValue] as [number, number]
17
+ */
18
+ collateLeg2Entry(key: string, activityField: string, increment: number, guidField: string, multi?: ProviderTransaction): Promise<[number, number]>;
19
+ /**
20
+ * 2) KVSQL METHOD (non-transactional + transactional)
21
+ * ---------------------------------------------------
22
+ * Matches hincrbyfloat() pattern:
23
+ * - when multi is present: enqueue command and return 0 immediately
24
+ * - when multi absent: execute immediately and return parsed numeric value
25
+ *
26
+ * Returns: thresholdHit (0/1) as number
27
+ */
28
+ setStatusAndCollateGuid(key: string, statusDelta: number, threshold: number, guidField: string, guidWeight: number, multi?: ProviderTransaction): Promise<number>;
10
29
  };
11
30
  export declare function _hset(context: HashContext['context'], key: string, fields: Record<string, string>, options?: HSetOptions): SqlResult;
12
31
  export declare function _hget(context: HashContext['context'], key: string, field: string): SqlResult;
@@ -14,3 +33,35 @@ export declare function _hdel(context: HashContext['context'], key: string, fiel
14
33
  export declare function _hmget(context: HashContext['context'], key: string, fields: string[]): SqlResult;
15
34
  export declare function _hgetall(context: HashContext['context'], key: string): SqlResult;
16
35
  export declare function _hincrbyfloat(context: HashContext['context'], key: string, field: string, increment: number): SqlResult;
36
+ /**
37
+ * 3b) LOW-LEVEL SQL GENERATOR: Leg2 Entry Compound
38
+ * -------------------------------------------------
39
+ * Atomically:
40
+ * - increments the activity collation field by +1 (Leg2 entry counter)
41
+ * - seeds the GUID ledger with the ordinal (last 8 digits of activity value)
42
+ * IF the GUID row does not yet exist; returns the existing value if it does
43
+ * - returns both (activity_value, guid_value) for the caller
44
+ *
45
+ * The GUID seed uses ON CONFLICT DO UPDATE SET value = existing.value
46
+ * (a no-op update) so that RETURNING fires in both insert and conflict cases.
47
+ */
48
+ export declare function _collateLeg2Entry(context: HashContext['context'], key: string, // jobKey
49
+ activityField: string, // serialized activity collation field name
50
+ increment: number, // always 1 for Leg2 entry
51
+ guidField: string): SqlResult;
52
+ /**
53
+ * 3) LOW-LEVEL SQL GENERATOR
54
+ * --------------------------
55
+ * This is the core compound statement. It must:
56
+ * - update jobs.status by statusDelta
57
+ * - compute thresholdHit (0/1) when status_after == threshold
58
+ * - increment the guidField by thresholdHit * guidWeight (typically 100B digit)
59
+ * - return thresholdHit as "value" so it fits the existing return parsing
60
+ *
61
+ * Notes:
62
+ * - Uses tableForKey(key, 'hash') which will resolve to the jobs table for job keys.
63
+ * - Assumes jobs_attributes table is `${jobsTableName}_attributes` (same as _hincrbyfloat)
64
+ * - Uses deriveType(guidField) to be consistent with your attribute typing system.
65
+ */
66
+ export declare function _setStatusAndCollateGuid(context: HashContext['context'], key: string, // jobKey
67
+ statusDelta: number, threshold: number, guidField: string, guidWeight: number): SqlResult;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports._hincrbyfloat = exports._hgetall = exports._hmget = exports._hdel = exports._hget = exports._hset = exports.createBasicOperations = void 0;
3
+ exports._setStatusAndCollateGuid = exports._collateLeg2Entry = exports._hincrbyfloat = exports._hgetall = exports._hmget = exports._hdel = exports._hget = exports._hset = exports.createBasicOperations = void 0;
4
4
  const utils_1 = require("./utils");
5
5
  function createBasicOperations(context) {
6
6
  return {
@@ -184,6 +184,77 @@ function createBasicOperations(context) {
184
184
  }
185
185
  }
186
186
  },
187
+ /**
188
+ * 2b) KVSQL METHOD: Leg2 Entry Compound
189
+ * --------------------------------------
190
+ * Atomically increments the activity Leg2 entry counter and seeds
191
+ * the GUID ledger with the ordinal IF NOT EXISTS.
192
+ *
193
+ * Returns: [activityValue, guidValue] as [number, number]
194
+ */
195
+ async collateLeg2Entry(key, // jobKey
196
+ activityField, // serialized activity collation field
197
+ increment, // always 1
198
+ guidField, // GUID string
199
+ multi) {
200
+ const { sql, params } = _collateLeg2Entry(context, key, activityField, increment, guidField);
201
+ if (multi) {
202
+ multi.addCommand(sql, params, 'array', (rows) => {
203
+ return [parseFloat(rows[0].activity_value), parseFloat(rows[0].guid_value)];
204
+ });
205
+ return Promise.resolve([0, 0]);
206
+ }
207
+ else {
208
+ try {
209
+ const res = await context.pgClient.query(sql, params);
210
+ return [parseFloat(res.rows[0].activity_value), parseFloat(res.rows[0].guid_value)];
211
+ }
212
+ catch (error) {
213
+ if (error?.message?.includes('closed') ||
214
+ error?.message?.includes('queryable')) {
215
+ return [0, 0];
216
+ }
217
+ throw error;
218
+ }
219
+ }
220
+ },
221
+ /**
222
+ * 2) KVSQL METHOD (non-transactional + transactional)
223
+ * ---------------------------------------------------
224
+ * Matches hincrbyfloat() pattern:
225
+ * - when multi is present: enqueue command and return 0 immediately
226
+ * - when multi absent: execute immediately and return parsed numeric value
227
+ *
228
+ * Returns: thresholdHit (0/1) as number
229
+ */
230
+ async setStatusAndCollateGuid(key, // jobKey
231
+ statusDelta, // semaphore delta
232
+ threshold, // desired threshold (usually 0)
233
+ guidField, // jobs_attributes.field for the guid ledger
234
+ guidWeight, // e.g. 100B digit increment
235
+ multi) {
236
+ const { sql, params } = _setStatusAndCollateGuid(context, key, statusDelta, threshold, guidField, guidWeight);
237
+ if (multi) {
238
+ multi.addCommand(sql, params, 'number', (rows) => {
239
+ return parseFloat(rows[0].value);
240
+ });
241
+ return Promise.resolve(0);
242
+ }
243
+ else {
244
+ try {
245
+ const res = await context.pgClient.query(sql, params);
246
+ return parseFloat(res.rows[0].value);
247
+ }
248
+ catch (error) {
249
+ // Connection closed during test cleanup - return 0
250
+ if (error?.message?.includes('closed') ||
251
+ error?.message?.includes('queryable')) {
252
+ return 0;
253
+ }
254
+ throw error;
255
+ }
256
+ }
257
+ },
187
258
  };
188
259
  }
189
260
  exports.createBasicOperations = createBasicOperations;
@@ -508,3 +579,124 @@ function _hincrbyfloat(context, key, field, increment) {
508
579
  }
509
580
  }
510
581
  exports._hincrbyfloat = _hincrbyfloat;
582
+ /**
583
+ * 3b) LOW-LEVEL SQL GENERATOR: Leg2 Entry Compound
584
+ * -------------------------------------------------
585
+ * Atomically:
586
+ * - increments the activity collation field by +1 (Leg2 entry counter)
587
+ * - seeds the GUID ledger with the ordinal (last 8 digits of activity value)
588
+ * IF the GUID row does not yet exist; returns the existing value if it does
589
+ * - returns both (activity_value, guid_value) for the caller
590
+ *
591
+ * The GUID seed uses ON CONFLICT DO UPDATE SET value = existing.value
592
+ * (a no-op update) so that RETURNING fires in both insert and conflict cases.
593
+ */
594
+ function _collateLeg2Entry(context, key, // jobKey
595
+ activityField, // serialized activity collation field name
596
+ increment, // always 1 for Leg2 entry
597
+ guidField) {
598
+ const jobsTableName = context.tableForKey(key, 'hash');
599
+ if (!(0, utils_1.isJobsTable)(jobsTableName)) {
600
+ throw new Error(`_collateLeg2Entry requires a jobs table key; got table ${jobsTableName}`);
601
+ }
602
+ const attrsTable = `${jobsTableName}_attributes`;
603
+ const sql = `
604
+ WITH activity_update AS (
605
+ INSERT INTO ${attrsTable} (job_id, field, value, type)
606
+ SELECT id, $2, ($3::double precision)::text, $4
607
+ FROM ${jobsTableName}
608
+ WHERE key = $1 AND is_live
609
+ ON CONFLICT (job_id, field) DO UPDATE
610
+ SET value = ((COALESCE(${attrsTable}.value, '0')::double precision) + $3::double precision)::text,
611
+ type = EXCLUDED.type
612
+ RETURNING job_id, value::double precision AS activity_value
613
+ ),
614
+ guid_upsert AS (
615
+ INSERT INTO ${attrsTable} (job_id, field, value, type)
616
+ SELECT
617
+ job_id,
618
+ $5,
619
+ ((activity_value::bigint) % 100000000)::text,
620
+ $6
621
+ FROM activity_update
622
+ ON CONFLICT (job_id, field) DO UPDATE
623
+ SET value = ${attrsTable}.value
624
+ RETURNING value::double precision AS guid_value
625
+ )
626
+ SELECT
627
+ (SELECT activity_value FROM activity_update) AS activity_value,
628
+ (SELECT guid_value FROM guid_upsert) AS guid_value
629
+ `;
630
+ return {
631
+ sql,
632
+ params: [
633
+ key,
634
+ activityField,
635
+ increment,
636
+ (0, utils_1.deriveType)(activityField),
637
+ guidField,
638
+ (0, utils_1.deriveType)(guidField), // $6
639
+ ],
640
+ };
641
+ }
642
+ exports._collateLeg2Entry = _collateLeg2Entry;
643
+ /**
644
+ * 3) LOW-LEVEL SQL GENERATOR
645
+ * --------------------------
646
+ * This is the core compound statement. It must:
647
+ * - update jobs.status by statusDelta
648
+ * - compute thresholdHit (0/1) when status_after == threshold
649
+ * - increment the guidField by thresholdHit * guidWeight (typically 100B digit)
650
+ * - return thresholdHit as "value" so it fits the existing return parsing
651
+ *
652
+ * Notes:
653
+ * - Uses tableForKey(key, 'hash') which will resolve to the jobs table for job keys.
654
+ * - Assumes jobs_attributes table is `${jobsTableName}_attributes` (same as _hincrbyfloat)
655
+ * - Uses deriveType(guidField) to be consistent with your attribute typing system.
656
+ */
657
+ function _setStatusAndCollateGuid(context, key, // jobKey
658
+ statusDelta, threshold, guidField, guidWeight) {
659
+ const jobsTableName = context.tableForKey(key, 'hash');
660
+ if (!(0, utils_1.isJobsTable)(jobsTableName)) {
661
+ throw new Error(`_setStatusAndCollateGuid requires a jobs table key; got table ${jobsTableName}`);
662
+ }
663
+ const sql = `
664
+ WITH status_update AS (
665
+ UPDATE ${jobsTableName}
666
+ SET status = status + $2
667
+ WHERE key = $1 AND is_live
668
+ RETURNING id AS job_id, status AS status_after
669
+ ),
670
+ hit AS (
671
+ SELECT
672
+ job_id,
673
+ CASE WHEN status_after = $3 THEN 1 ELSE 0 END AS threshold_hit,
674
+ CASE WHEN status_after = $3 THEN ($4::double precision) ELSE 0::double precision END AS guid_increment
675
+ FROM status_update
676
+ )
677
+ INSERT INTO ${jobsTableName}_attributes (job_id, field, value, type)
678
+ SELECT
679
+ job_id,
680
+ $5 AS field,
681
+ (guid_increment)::text AS value,
682
+ $6
683
+ FROM hit
684
+ ON CONFLICT (job_id, field) DO UPDATE
685
+ SET
686
+ value = ((COALESCE(${jobsTableName}_attributes.value, '0')::double precision) + EXCLUDED.value::double precision)::text,
687
+ type = EXCLUDED.type
688
+ RETURNING (SELECT threshold_hit::text FROM hit) AS value
689
+ `;
690
+ return {
691
+ sql,
692
+ params: [
693
+ key,
694
+ statusDelta,
695
+ threshold,
696
+ guidWeight,
697
+ guidField,
698
+ (0, utils_1.deriveType)(guidField), // $6
699
+ ],
700
+ };
701
+ }
702
+ exports._setStatusAndCollateGuid = _setStatusAndCollateGuid;
@@ -8,6 +8,8 @@ export declare const hashModule: (context: KVSQL) => {
8
8
  _hdel: (key: string, fields: string[]) => import("./types").SqlResult;
9
9
  _hmget: (key: string, fields: string[]) => import("./types").SqlResult;
10
10
  _hincrbyfloat: (key: string, field: string, increment: number) => import("./types").SqlResult;
11
+ _collateLeg2Entry: (key: string, activityField: string, increment: number, guidField: string) => import("./types").SqlResult;
12
+ _setStatusAndCollateGuid: (key: string, statusDelta: number, threshold: number, guidField: string, guidWeight: number) => import("./types").SqlResult;
11
13
  _hscan: (key: string, cursor: string, count: number, pattern?: string) => import("./types").SqlResult;
12
14
  _expire: (key: string, seconds: number) => import("./types").SqlResult;
13
15
  _scan: (cursor: number, count: number, pattern?: string) => import("./types").SqlResult;
@@ -24,6 +26,8 @@ export declare const hashModule: (context: KVSQL) => {
24
26
  hmget(key: string, fields: string[], multi?: import("./types").ProviderTransaction): Promise<string[]>;
25
27
  hgetall(key: string, multi?: import("./types").ProviderTransaction): Promise<Record<string, string>>;
26
28
  hincrbyfloat(key: string, field: string, increment: number, multi?: import("./types").ProviderTransaction): Promise<number>;
29
+ collateLeg2Entry(key: string, activityField: string, increment: number, guidField: string, multi?: import("./types").ProviderTransaction): Promise<[number, number]>;
30
+ setStatusAndCollateGuid(key: string, statusDelta: number, threshold: number, guidField: string, guidWeight: number, multi?: import("./types").ProviderTransaction): Promise<number>;
27
31
  };
28
32
  export { isJobsTable, deriveType } from './utils';
29
33
  export * from './types';
@@ -199,6 +199,12 @@ const hashModule = (context) => {
199
199
  _hincrbyfloat: (key, field, increment) => {
200
200
  return (0, basic_1._hincrbyfloat)(context, key, field, increment);
201
201
  },
202
+ _collateLeg2Entry: (key, activityField, increment, guidField) => {
203
+ return (0, basic_1._collateLeg2Entry)(context, key, activityField, increment, guidField);
204
+ },
205
+ _setStatusAndCollateGuid: (key, statusDelta, threshold, guidField, guidWeight) => {
206
+ return (0, basic_1._setStatusAndCollateGuid)(context, key, statusDelta, threshold, guidField, guidWeight);
207
+ },
202
208
  _hscan: (key, cursor, count, pattern) => {
203
209
  return (0, scan_1._hscan)(context, key, cursor, count, pattern);
204
210
  },
@@ -61,6 +61,20 @@ declare class PostgresStoreService extends StoreService<ProviderClient, Provider
61
61
  getJobStats(jobKeys: string[]): Promise<JobStatsRange>;
62
62
  getJobIds(indexKeys: string[], idRange: [number, number]): Promise<IdsData>;
63
63
  setStatus(collationKeyStatus: number, jobId: string, appId: string, transaction?: ProviderTransaction): Promise<any>;
64
+ /**
65
+ * 1) HIGH-LEVEL STORE METHOD (engine/activity-facing)
66
+ * ---------------------------------------------------
67
+ * Mirrors setStatus(), but performs the compound Step-2 requirement:
68
+ * - apply delta to job semaphore (jobs.status)
69
+ * - compute thresholdHit (0/1) for desired threshold
70
+ * - persist thresholdHit onto the Leg2 GUID ledger by incrementing the 100B digit (or other weight)
71
+ * - return thresholdHit (0/1)
72
+ */
73
+ setStatusAndCollateGuid(statusDelta: number, // typically (N - 1)
74
+ threshold: number, // typically 0 (but supports 0,1,12,...)
75
+ jobId: string, appId: string, guidField: string, // the jobs_attributes.field for the Leg2 GUID ledger row
76
+ guidWeight: number, // e.g. 100_000_000_000 for GUID 100B digit
77
+ transaction?: ProviderTransaction): Promise<number>;
64
78
  getStatus(jobId: string, appId: string): Promise<number>;
65
79
  setState({ ...state }: StringAnyType, status: number | null, jobId: string, symbolNames: string[], dIds: StringStringType, transaction?: ProviderTransaction): Promise<string>;
66
80
  /**
@@ -76,6 +90,12 @@ declare class PostgresStoreService extends StoreService<ProviderClient, Provider
76
90
  * in order to track their progress during processing.
77
91
  */
78
92
  collate(jobId: string, activityId: string, amount: number, dIds: StringStringType, transaction?: ProviderTransaction): Promise<number>;
93
+ /**
94
+ * Compound Leg2 entry: atomically increments the activity Leg2 entry
95
+ * counter and seeds the GUID ledger with the ordinal IF NOT EXISTS.
96
+ * Returns [activityValue, guidValue].
97
+ */
98
+ collateLeg2Entry(jobId: string, activityId: string, guid: string, dIds: StringStringType, transaction?: ProviderTransaction): Promise<[number, number]>;
79
99
  /**
80
100
  * Synthentic collation affects those activities in the graph
81
101
  * that represent the synthetic DAG that was materialized during compilation;
@@ -439,6 +439,23 @@ class PostgresStoreService extends __1.StoreService {
439
439
  const jobKey = this.mintKey(key_1.KeyType.JOB_STATE, { appId, jobId });
440
440
  return await this.kvsql(transaction).hincrbyfloat(jobKey, ':', collationKeyStatus);
441
441
  }
442
+ /**
443
+ * 1) HIGH-LEVEL STORE METHOD (engine/activity-facing)
444
+ * ---------------------------------------------------
445
+ * Mirrors setStatus(), but performs the compound Step-2 requirement:
446
+ * - apply delta to job semaphore (jobs.status)
447
+ * - compute thresholdHit (0/1) for desired threshold
448
+ * - persist thresholdHit onto the Leg2 GUID ledger by incrementing the 100B digit (or other weight)
449
+ * - return thresholdHit (0/1)
450
+ */
451
+ async setStatusAndCollateGuid(statusDelta, // typically (N - 1)
452
+ threshold, // typically 0 (but supports 0,1,12,...)
453
+ jobId, appId, guidField, // the jobs_attributes.field for the Leg2 GUID ledger row
454
+ guidWeight, // e.g. 100_000_000_000 for GUID 100B digit
455
+ transaction) {
456
+ const jobKey = this.mintKey(key_1.KeyType.JOB_STATE, { appId, jobId });
457
+ return await this.kvsql(transaction).setStatusAndCollateGuid(jobKey, statusDelta, threshold, guidField, guidWeight);
458
+ }
442
459
  async getStatus(jobId, appId) {
443
460
  const jobKey = this.mintKey(key_1.KeyType.JOB_STATE, { appId, jobId });
444
461
  const status = await this.kvsql().hget(jobKey, ':');
@@ -551,6 +568,26 @@ class PostgresStoreService extends __1.StoreService {
551
568
  const targetId = Object.keys(hashData)[0];
552
569
  return await this.kvsql(transaction).hincrbyfloat(jobKey, targetId, amount);
553
570
  }
571
+ /**
572
+ * Compound Leg2 entry: atomically increments the activity Leg2 entry
573
+ * counter and seeds the GUID ledger with the ordinal IF NOT EXISTS.
574
+ * Returns [activityValue, guidValue].
575
+ */
576
+ async collateLeg2Entry(jobId, activityId, guid, dIds, transaction) {
577
+ const jobKey = this.mintKey(key_1.KeyType.JOB_STATE, {
578
+ appId: this.appId,
579
+ jobId,
580
+ });
581
+ const collationKey = `${activityId}/output/metadata/as`;
582
+ const symbolNames = [activityId];
583
+ const symKeys = await this.getSymbolKeys(symbolNames);
584
+ const symVals = await this.getSymbolValues();
585
+ this.serializer.resetSymbols(symKeys, symVals, dIds);
586
+ const payload = { [collationKey]: '1' };
587
+ const hashData = this.serializer.package(payload, symbolNames);
588
+ const targetId = Object.keys(hashData)[0];
589
+ return await this.kvsql(transaction).collateLeg2Entry(jobKey, targetId, 1, guid);
590
+ }
554
591
  /**
555
592
  * Synthentic collation affects those activities in the graph
556
593
  * that represent the synthetic DAG that was materialized during compilation;
@@ -1044,7 +1081,7 @@ class PostgresStoreService extends __1.StoreService {
1044
1081
  const sql = (0, time_notify_1.getTimeNotifySql)(schemaName);
1045
1082
  // Execute the entire SQL as one statement (functions contain $$ blocks with semicolons)
1046
1083
  await client.query(sql);
1047
- this.logger.info('postgres-time-notifications-deployed', {
1084
+ this.logger.debug('postgres-time-notifications-deployed', {
1048
1085
  appId,
1049
1086
  schemaName,
1050
1087
  message: 'Time-aware notifications ENABLED - using LISTEN/NOTIFY instead of polling',
@@ -2,8 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.StreamServiceFactory = void 0;
4
4
  const utils_1 = require("../../modules/utils");
5
- const ioredis_1 = require("./providers/redis/ioredis");
6
- const redis_1 = require("./providers/redis/redis");
7
5
  const nats_1 = require("./providers/nats/nats");
8
6
  const postgres_1 = require("./providers/postgres/postgres");
9
7
  class StreamServiceFactory {
@@ -11,24 +9,10 @@ class StreamServiceFactory {
11
9
  let service;
12
10
  const providerType = (0, utils_1.identifyProvider)(provider);
13
11
  if (providerType === 'nats') {
14
- let redisStoreProvider;
15
- if ((0, utils_1.identifyProvider)(storeProvider) === 'redis') {
16
- redisStoreProvider = storeProvider;
17
- }
18
- else {
19
- //ioredis
20
- redisStoreProvider = storeProvider;
21
- }
22
- service = new nats_1.NatsStreamService(provider, redisStoreProvider);
12
+ service = new nats_1.NatsStreamService(provider, storeProvider);
23
13
  }
24
14
  else if (providerType === 'postgres') {
25
15
  service = new postgres_1.PostgresStreamService(provider, storeProvider);
26
- }
27
- else if (providerType === 'redis') {
28
- service = new redis_1.RedisStreamService(provider, storeProvider);
29
- }
30
- else if (providerType === 'ioredis') {
31
- service = new ioredis_1.IORedisStreamService(provider, storeProvider);
32
16
  } //etc register other providers here
33
17
  await service.init(namespace, appId, logger);
34
18
  return service;
@@ -31,7 +31,7 @@ class ScoutManager {
31
31
  this.pollForVisibleMessagesLoop().catch((error) => {
32
32
  this.logger.error('postgres-stream-router-scout-start-error', { error });
33
33
  });
34
- this.logger.info('postgres-stream-router-scout-started', {
34
+ this.logger.debug('postgres-stream-router-scout-started', {
35
35
  appId: this.appId,
36
36
  pollInterval: this.getRouterScoutInterval(),
37
37
  scoutInterval: enums_1.HMSH_ROUTER_SCOUT_INTERVAL_SECONDS,
@@ -204,7 +204,7 @@ class ScoutManager {
204
204
  */
205
205
  logPollingMetrics() {
206
206
  if (this.pollCount === 0) {
207
- this.logger.info('postgres-stream-router-scout-metrics', {
207
+ this.logger.debug('postgres-stream-router-scout-metrics', {
208
208
  message: 'No polling occurred during this session',
209
209
  appId: this.appId,
210
210
  });
@@ -2,10 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SubServiceFactory = void 0;
4
4
  const utils_1 = require("../../modules/utils");
5
- const redis_1 = require("./providers/redis/redis");
6
5
  const postgres_1 = require("./providers/postgres/postgres");
7
6
  const nats_1 = require("./providers/nats/nats");
8
- const ioredis_1 = require("./providers/redis/ioredis");
9
7
  class SubServiceFactory {
10
8
  static async init(providerSubClient, providerPubClient, namespace, appId, engineId, logger) {
11
9
  let service;
@@ -13,15 +11,9 @@ class SubServiceFactory {
13
11
  if (providerType === 'nats') {
14
12
  service = new nats_1.NatsSubService(providerSubClient, providerPubClient);
15
13
  }
16
- else if (providerType === 'redis') {
17
- service = new redis_1.RedisSubService(providerSubClient, providerPubClient);
18
- }
19
14
  else if (providerType === 'postgres') {
20
15
  service = new postgres_1.PostgresSubService(providerSubClient, providerPubClient);
21
- }
22
- else {
23
- service = new ioredis_1.IORedisSubService(providerSubClient, providerPubClient);
24
- }
16
+ } //etc
25
17
  await service.init(namespace, appId, engineId, logger);
26
18
  return service;
27
19
  }
@@ -17,6 +17,6 @@ declare abstract class SubService<ClientProvider extends ProviderClient> {
17
17
  abstract unsubscribe(keyType: KeyType.QUORUM, appId: string, topic?: string): Promise<void>;
18
18
  abstract psubscribe(keyType: KeyType.QUORUM, callback: SubscriptionCallback, appId: string, topic?: string): Promise<void>;
19
19
  abstract punsubscribe(keyType: KeyType.QUORUM, appId: string, topic?: string): Promise<void>;
20
- abstract publish(keyType: KeyType, message: Record<string, any>, appId: string, topic?: string): Promise<boolean>;
20
+ abstract publish(keyType: KeyType, message: Record<string, any>, appId: string, topic?: string, transaction?: ProviderTransaction): Promise<boolean>;
21
21
  }
22
22
  export { SubService };
@@ -22,7 +22,7 @@ declare class PostgresSubService extends SubService<PostgresClientType & Provide
22
22
  * Should be called when the SubService instance is being destroyed.
23
23
  */
24
24
  cleanup(): Promise<void>;
25
- publish(keyType: KeyType.QUORUM, message: Record<string, any>, appId: string, topic?: string): Promise<boolean>;
25
+ publish(keyType: KeyType.QUORUM, message: Record<string, any>, appId: string, topic?: string, transaction?: ProviderTransaction): Promise<boolean>;
26
26
  psubscribe(): Promise<void>;
27
27
  punsubscribe(): Promise<void>;
28
28
  }