@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.
- package/.claude/settings.local.json +7 -0
- package/README.md +1 -1
- package/build/index.d.ts +1 -3
- package/build/index.js +1 -5
- package/build/modules/utils.js +3 -31
- package/build/package.json +16 -27
- package/build/services/activities/activity.d.ts +43 -6
- package/build/services/activities/activity.js +262 -54
- package/build/services/activities/await.js +2 -2
- package/build/services/activities/cycle.js +1 -1
- package/build/services/activities/hook.d.ts +5 -0
- package/build/services/activities/hook.js +22 -19
- package/build/services/activities/interrupt.js +17 -25
- package/build/services/activities/signal.d.ts +4 -2
- package/build/services/activities/signal.js +27 -24
- package/build/services/activities/worker.js +2 -2
- package/build/services/collator/index.d.ts +123 -25
- package/build/services/collator/index.js +224 -101
- package/build/services/connector/factory.d.ts +1 -1
- package/build/services/connector/factory.js +1 -11
- package/build/services/engine/index.d.ts +5 -5
- package/build/services/engine/index.js +36 -15
- package/build/services/router/consumption/index.js +1 -1
- package/build/services/search/factory.js +1 -9
- package/build/services/store/factory.js +1 -9
- package/build/services/store/index.d.ts +5 -0
- package/build/services/store/providers/postgres/kvsql.d.ts +4 -0
- package/build/services/store/providers/postgres/kvsql.js +4 -0
- package/build/services/store/providers/postgres/kvtransaction.d.ts +2 -0
- package/build/services/store/providers/postgres/kvtransaction.js +23 -0
- package/build/services/store/providers/postgres/kvtypes/hash/basic.d.ts +51 -0
- package/build/services/store/providers/postgres/kvtypes/hash/basic.js +193 -1
- package/build/services/store/providers/postgres/kvtypes/hash/index.d.ts +4 -0
- package/build/services/store/providers/postgres/kvtypes/hash/index.js +6 -0
- package/build/services/store/providers/postgres/postgres.d.ts +20 -0
- package/build/services/store/providers/postgres/postgres.js +38 -1
- package/build/services/stream/factory.js +1 -17
- package/build/services/stream/providers/postgres/scout.js +2 -2
- package/build/services/sub/factory.js +1 -9
- package/build/services/sub/index.d.ts +1 -1
- package/build/services/sub/providers/postgres/postgres.d.ts +1 -1
- package/build/services/sub/providers/postgres/postgres.js +25 -10
- package/build/services/task/index.d.ts +1 -1
- package/build/services/task/index.js +2 -6
- package/build/types/index.d.ts +0 -1
- package/build/types/index.js +1 -4
- package/build/types/provider.d.ts +1 -1
- package/index.ts +0 -4
- package/package.json +16 -27
- package/build/services/connector/providers/ioredis.d.ts +0 -9
- package/build/services/connector/providers/ioredis.js +0 -26
- package/build/services/connector/providers/redis.d.ts +0 -9
- package/build/services/connector/providers/redis.js +0 -38
- package/build/services/search/providers/redis/ioredis.d.ts +0 -23
- package/build/services/search/providers/redis/ioredis.js +0 -189
- package/build/services/search/providers/redis/redis.d.ts +0 -23
- package/build/services/search/providers/redis/redis.js +0 -202
- package/build/services/store/providers/redis/_base.d.ts +0 -137
- package/build/services/store/providers/redis/_base.js +0 -980
- package/build/services/store/providers/redis/ioredis.d.ts +0 -20
- package/build/services/store/providers/redis/ioredis.js +0 -190
- package/build/services/store/providers/redis/redis.d.ts +0 -18
- package/build/services/store/providers/redis/redis.js +0 -199
- package/build/services/stream/providers/redis/ioredis.d.ts +0 -61
- package/build/services/stream/providers/redis/ioredis.js +0 -272
- package/build/services/stream/providers/redis/redis.d.ts +0 -61
- package/build/services/stream/providers/redis/redis.js +0 -305
- package/build/services/sub/providers/redis/ioredis.d.ts +0 -20
- package/build/services/sub/providers/redis/ioredis.js +0 -161
- package/build/services/sub/providers/redis/redis.d.ts +0 -18
- package/build/services/sub/providers/redis/redis.js +0 -148
- package/build/types/redis.d.ts +0 -258
- package/build/types/redis.js +0 -11
|
@@ -42,32 +42,108 @@ class CollatorService {
|
|
|
42
42
|
dimensions.push('0');
|
|
43
43
|
return dimensions.join(',');
|
|
44
44
|
}
|
|
45
|
+
// ──────────────────────────────────────────────────
|
|
46
|
+
// Leg1 notarization
|
|
47
|
+
// ──────────────────────────────────────────────────
|
|
48
|
+
/**
|
|
49
|
+
* Leg1 entry: increment attempt counter (+1T).
|
|
50
|
+
* NOT bundled with Leg1 work — exists only to mark entry.
|
|
51
|
+
*/
|
|
45
52
|
static async notarizeEntry(activity, transaction) {
|
|
46
|
-
|
|
47
|
-
const amount = await activity.store.collate(activity.context.metadata.jid, activity.metadata.aid, -100000000000000, this.getDimensionalAddress(activity), transaction);
|
|
53
|
+
const amount = await activity.store.collate(activity.context.metadata.jid, activity.metadata.aid, this.WEIGHTS.LEG1_ENTRY, this.getDimensionalAddress(activity), transaction);
|
|
48
54
|
this.verifyInteger(amount, 1, 'enter');
|
|
49
55
|
return amount;
|
|
50
56
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
57
|
+
/**
|
|
58
|
+
* Leg1 completion: increment +100B to mark Leg1 complete.
|
|
59
|
+
* For cycle=true activities, also pre-seeds the Leg2 entry counter (+1)
|
|
60
|
+
* so the first real Leg2 gets adjacentIndex=1 (new dimension).
|
|
61
|
+
* MUST be bundled in the same transaction as Leg1 durable work.
|
|
62
|
+
*/
|
|
63
|
+
static async notarizeLeg1Completion(activity, transaction) {
|
|
64
|
+
const amount = activity.config.cycle
|
|
65
|
+
? this.WEIGHTS.LEG1_COMPLETE + this.WEIGHTS.LEG2_ENTRY
|
|
66
|
+
: this.WEIGHTS.LEG1_COMPLETE;
|
|
67
|
+
return await activity.store.collate(activity.context.metadata.jid, activity.metadata.aid, amount, this.getDimensionalAddress(activity), transaction);
|
|
56
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Leg1 early exit: marks Leg1 complete for activities that
|
|
71
|
+
* only run Leg1 and fully close (e.g., Cycle).
|
|
72
|
+
* Increment +100B (Leg1 completion marker).
|
|
73
|
+
*/
|
|
57
74
|
static async notarizeEarlyExit(activity, transaction) {
|
|
58
|
-
|
|
59
|
-
return await activity.store.collate(activity.context.metadata.jid, activity.metadata.aid, -11000000000000, this.getDimensionalAddress(activity), transaction);
|
|
75
|
+
return await activity.store.collate(activity.context.metadata.jid, activity.metadata.aid, this.WEIGHTS.LEG1_COMPLETE, this.getDimensionalAddress(activity), transaction);
|
|
60
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* Leg1 early completion: marks Leg1 complete for Leg1-only
|
|
79
|
+
* activities that spawn children (e.g., Signal, Hook passthrough,
|
|
80
|
+
* Interrupt-another). Increment +100B.
|
|
81
|
+
*/
|
|
61
82
|
static async notarizeEarlyCompletion(activity, transaction) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
83
|
+
return await activity.store.collate(activity.context.metadata.jid, activity.metadata.aid, this.WEIGHTS.LEG1_COMPLETE, this.getDimensionalAddress(activity), transaction);
|
|
84
|
+
}
|
|
85
|
+
// ──────────────────────────────────────────────────
|
|
86
|
+
// Leg2 notarization (entry + 3-step protocol)
|
|
87
|
+
// ──────────────────────────────────────────────────
|
|
88
|
+
/**
|
|
89
|
+
* Leg2 entry: atomically increments the activity ledger (+1) and
|
|
90
|
+
* seeds the GUID ledger with the ordinal IF NOT EXISTS.
|
|
91
|
+
* Returns [activityLedger, guidLedger] after the compound operation.
|
|
92
|
+
*/
|
|
93
|
+
static async notarizeLeg2Entry(activity, guid, transaction) {
|
|
94
|
+
const jid = activity.context.metadata.jid;
|
|
95
|
+
const localMulti = transaction || activity.store.transact();
|
|
96
|
+
//compound: increment activity Leg2 counter and seed GUID ledger
|
|
97
|
+
await activity.store.collateLeg2Entry(jid, activity.metadata.aid, guid, this.getDimensionalAddress(activity, true), localMulti);
|
|
98
|
+
const results = await localMulti.exec();
|
|
99
|
+
const result = results[0];
|
|
100
|
+
const [amountConcrete, amountSynthetic] = Array.isArray(result)
|
|
101
|
+
? result
|
|
102
|
+
: [result, result];
|
|
103
|
+
this.verifyInteger(amountConcrete, 2, 'enter');
|
|
104
|
+
this.verifySyntheticInteger(amountSynthetic);
|
|
105
|
+
return [amountConcrete, amountSynthetic];
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Step 1: Mark Leg2 work done (+10B on GUID ledger only).
|
|
109
|
+
* MUST be bundled with Leg2 durable work writes.
|
|
110
|
+
*/
|
|
111
|
+
static async notarizeStep1(activity, guid, transaction) {
|
|
112
|
+
const jid = activity.context.metadata.jid;
|
|
113
|
+
await activity.store.collateSynthetic(jid, guid, this.WEIGHTS.STEP1_WORK, transaction);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Step 2: Mark children spawned (+1B on GUID ledger only).
|
|
117
|
+
* The job semaphore update and GUID job-closed snapshot are handled
|
|
118
|
+
* by the compound `setStatusAndCollateGuid` primitive, which MUST
|
|
119
|
+
* be called in the same transaction.
|
|
120
|
+
*/
|
|
121
|
+
static async notarizeStep2(activity, guid, transaction) {
|
|
122
|
+
const jid = activity.context.metadata.jid;
|
|
123
|
+
await activity.store.collateSynthetic(jid, guid, this.WEIGHTS.STEP2_SPAWN, transaction);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Step 3: Mark job completion tasks done (+100M on GUID ledger only).
|
|
127
|
+
* MUST be bundled with job completion durable writes.
|
|
128
|
+
*/
|
|
129
|
+
static async notarizeStep3(activity, guid, transaction) {
|
|
130
|
+
const jid = activity.context.metadata.jid;
|
|
131
|
+
await activity.store.collateSynthetic(jid, guid, this.WEIGHTS.STEP3_CLEANUP, transaction);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Finalize: close the activity to new Leg2 GUIDs (+10T).
|
|
135
|
+
* Only for non-cycle activities after final SUCCESS/ERROR.
|
|
136
|
+
*/
|
|
137
|
+
static async notarizeFinalize(activity, transaction) {
|
|
138
|
+
if (!activity.config.cycle) {
|
|
139
|
+
await activity.store.collate(activity.context.metadata.jid, activity.metadata.aid, this.WEIGHTS.FINALIZE, this.getDimensionalAddress(activity), transaction);
|
|
140
|
+
}
|
|
68
141
|
}
|
|
142
|
+
// ──────────────────────────────────────────────────
|
|
143
|
+
// Inception (trigger duplicate detection)
|
|
144
|
+
// ──────────────────────────────────────────────────
|
|
69
145
|
/**
|
|
70
|
-
* sets the synthetic inception key (in case
|
|
146
|
+
* sets the synthetic inception key (in case an overage occurs).
|
|
71
147
|
*/
|
|
72
148
|
static async notarizeInception(activity, guid, transaction) {
|
|
73
149
|
if (guid) {
|
|
@@ -84,43 +160,60 @@ class CollatorService {
|
|
|
84
160
|
}
|
|
85
161
|
return false;
|
|
86
162
|
}
|
|
163
|
+
// ──────────────────────────────────────────────────
|
|
164
|
+
// GUID ledger extraction (step-level resume)
|
|
165
|
+
// ──────────────────────────────────────────────────
|
|
87
166
|
/**
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
* hook activities are atomized during compilation to create a synthetic DAG that
|
|
91
|
-
* is used to track the status of the graph in a distributed environment. The
|
|
92
|
-
* synthetic key represents different dimensional realities and is used to
|
|
93
|
-
* track re-entry overages (it distinguishes between the original and re-entry).
|
|
94
|
-
* The essential challenge is: is this a re-entry that is purposeful in
|
|
95
|
-
* order to induce cycles, or is the re-entry due to a failure in the system?
|
|
167
|
+
* Check if Step 1 (work done) is complete on the GUID ledger.
|
|
168
|
+
* Position 5 (10B digit) > 0.
|
|
96
169
|
*/
|
|
97
|
-
static
|
|
98
|
-
|
|
99
|
-
const localMulti = transaction || activity.store.transact();
|
|
100
|
-
//increment by 1_000_000 (indicates re-entry and is used to drive the 'dimensional address' for adjacent activities (minus 1))
|
|
101
|
-
await activity.store.collate(jid, activity.metadata.aid, 1000000, this.getDimensionalAddress(activity, true), localMulti);
|
|
102
|
-
await activity.store.collateSynthetic(jid, guid, 1000000, localMulti);
|
|
103
|
-
const [_amountConcrete, _amountSynthetic] = await localMulti.exec();
|
|
104
|
-
const amountConcrete = Array.isArray(_amountConcrete)
|
|
105
|
-
? _amountConcrete[1]
|
|
106
|
-
: _amountConcrete;
|
|
107
|
-
const amountSynthetic = Array.isArray(_amountSynthetic)
|
|
108
|
-
? _amountSynthetic[1]
|
|
109
|
-
: _amountSynthetic;
|
|
110
|
-
this.verifyInteger(amountConcrete, 2, 'enter');
|
|
111
|
-
this.verifySyntheticInteger(amountSynthetic);
|
|
112
|
-
return amountConcrete;
|
|
170
|
+
static isGuidStep1Done(guidLedger) {
|
|
171
|
+
return this.getDigitAtPosition(guidLedger, 5) > 0;
|
|
113
172
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
173
|
+
/**
|
|
174
|
+
* Check if Step 2 (children spawned) is complete on the GUID ledger.
|
|
175
|
+
* Position 6 (1B digit) > 0.
|
|
176
|
+
*/
|
|
177
|
+
static isGuidStep2Done(guidLedger) {
|
|
178
|
+
return this.getDigitAtPosition(guidLedger, 6) > 0;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Check if Step 3 (job completion tasks) is complete on the GUID ledger.
|
|
182
|
+
* Position 7 (100M digit) > 0.
|
|
183
|
+
*/
|
|
184
|
+
static isGuidStep3Done(guidLedger) {
|
|
185
|
+
return this.getDigitAtPosition(guidLedger, 7) > 0;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Check if this GUID was responsible for closing the job.
|
|
189
|
+
* Position 4 (100B digit) > 0 (job closed snapshot).
|
|
190
|
+
*/
|
|
191
|
+
static isGuidJobClosed(guidLedger) {
|
|
192
|
+
return this.getDigitAtPosition(guidLedger, 4) > 0;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Get the attempt count from the GUID ledger (last 8 digits).
|
|
196
|
+
*/
|
|
197
|
+
static getGuidAttemptCount(guidLedger) {
|
|
198
|
+
return guidLedger % 100000000;
|
|
117
199
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
200
|
+
// ──────────────────────────────────────────────────
|
|
201
|
+
// Digit extraction
|
|
202
|
+
// ──────────────────────────────────────────────────
|
|
203
|
+
/**
|
|
204
|
+
* Gets the digit at a 1-indexed position from a 15-digit ledger value.
|
|
205
|
+
* The value is left-padded to 15 digits before extraction.
|
|
206
|
+
*/
|
|
207
|
+
static getDigitAtPosition(num, position) {
|
|
208
|
+
const numStr = num.toString().padStart(this.targetLength, '0');
|
|
209
|
+
if (position < 1 || position > this.targetLength) {
|
|
210
|
+
return 0;
|
|
211
|
+
}
|
|
212
|
+
return parseInt(numStr[position - 1], 10);
|
|
123
213
|
}
|
|
214
|
+
/**
|
|
215
|
+
* @deprecated Use getDigitAtPosition (1-indexed) instead
|
|
216
|
+
*/
|
|
124
217
|
static getDigitAtIndex(num, targetDigitIndex) {
|
|
125
218
|
const numStr = num.toString();
|
|
126
219
|
if (targetDigitIndex < 0 || targetDigitIndex >= numStr.length) {
|
|
@@ -129,77 +222,79 @@ class CollatorService {
|
|
|
129
222
|
const digit = parseInt(numStr[targetDigitIndex], 10);
|
|
130
223
|
return digit;
|
|
131
224
|
}
|
|
225
|
+
/**
|
|
226
|
+
* Extracts the dimensional index from the Leg2 entry counter.
|
|
227
|
+
* Non-cycle activities: first Leg2 → leg2Count=1 → 1-1=0 (same dimension as Leg1).
|
|
228
|
+
* Cycle activities: first Leg2 → leg2Count=2 (pre-seeded +1) → 2-1=1 (new dimension).
|
|
229
|
+
*/
|
|
132
230
|
static getDimensionalIndex(num) {
|
|
133
|
-
const
|
|
134
|
-
if (
|
|
231
|
+
const leg2EntryCount = num % 100000000;
|
|
232
|
+
if (leg2EntryCount <= 0) {
|
|
135
233
|
return null;
|
|
136
234
|
}
|
|
137
|
-
|
|
138
|
-
const extractedInt = parseInt(extractedStr, 10);
|
|
139
|
-
return extractedInt - 1;
|
|
140
|
-
}
|
|
141
|
-
static isDuplicate(num, targetDigitIndex) {
|
|
142
|
-
return this.getDigitAtIndex(num, targetDigitIndex) < 8;
|
|
143
|
-
}
|
|
144
|
-
static isInactive(num) {
|
|
145
|
-
return this.getDigitAtIndex(num, 2) < 9;
|
|
146
|
-
}
|
|
147
|
-
static isPrimed(amount, leg) {
|
|
148
|
-
//activity entry is not allowed if paths not properly pre-set
|
|
149
|
-
if (leg == 1) {
|
|
150
|
-
return amount != -100000000000000;
|
|
151
|
-
}
|
|
152
|
-
else {
|
|
153
|
-
return (this.getDigitAtIndex(amount, 0) < 9 &&
|
|
154
|
-
this.getDigitAtIndex(amount, 1) < 9);
|
|
155
|
-
}
|
|
235
|
+
return leg2EntryCount - 1;
|
|
156
236
|
}
|
|
237
|
+
// ──────────────────────────────────────────────────
|
|
238
|
+
// Verification
|
|
239
|
+
// ──────────────────────────────────────────────────
|
|
157
240
|
/**
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
241
|
+
* Verifies the GUID ledger value for step-level resume decisions.
|
|
242
|
+
* The GUID ledger is seeded with an ordinal position (last 8 digits)
|
|
243
|
+
* on first entry; step markers drive all resume/reject logic.
|
|
244
|
+
*
|
|
245
|
+
* Fully processed: Step 3 done, or Steps 1+2 done without job closure.
|
|
246
|
+
* Crash recovery: Any incomplete step combination is allowed for resume.
|
|
164
247
|
*/
|
|
165
248
|
static verifySyntheticInteger(amount) {
|
|
166
|
-
const
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
249
|
+
const step2Done = this.isGuidStep2Done(amount);
|
|
250
|
+
const step3Done = this.isGuidStep3Done(amount);
|
|
251
|
+
const jobClosed = this.isGuidJobClosed(amount);
|
|
252
|
+
if (step3Done) {
|
|
253
|
+
//all steps complete; nothing more to do
|
|
170
254
|
throw new errors_1.CollationError(amount, 2, 'enter', collator_1.CollationFaultType.INACTIVE);
|
|
171
255
|
}
|
|
172
|
-
|
|
173
|
-
//
|
|
174
|
-
throw new errors_1.CollationError(amount, 2, 'enter', collator_1.CollationFaultType.
|
|
256
|
+
if (step2Done && !jobClosed) {
|
|
257
|
+
//steps 1+2 done but this GUID didn't close the job; no Step 3 needed
|
|
258
|
+
throw new errors_1.CollationError(amount, 2, 'enter', collator_1.CollationFaultType.INACTIVE);
|
|
175
259
|
}
|
|
260
|
+
//all other cases: allow entry
|
|
261
|
+
// - no steps done (fresh entry or pre-step-1 crash recovery)
|
|
262
|
+
// - step 1 done, step 2 not (crash after step 1)
|
|
263
|
+
// - steps 1+2 done, job closed, step 3 not (crash recovery for step 3)
|
|
176
264
|
}
|
|
265
|
+
/**
|
|
266
|
+
* Verifies the activity ledger value at entry boundaries.
|
|
267
|
+
*
|
|
268
|
+
* Leg1 enter: pos 3 (1T digit) must be > 0 after +1T (proves seed exists).
|
|
269
|
+
* pos 4 (100B) must be 0 (Leg1 not yet complete).
|
|
270
|
+
* If pos 3 > 1 and pos 4 == 1, it's a stale/replayed message.
|
|
271
|
+
*
|
|
272
|
+
* Leg2 enter: pos 4 (100B) must be > 0 (Leg1 complete, reentry authorized).
|
|
273
|
+
* pos 2 (10T) must be 0 (not finalized) — cycle activities exempt.
|
|
274
|
+
*/
|
|
177
275
|
static verifyInteger(amount, leg, stage) {
|
|
178
276
|
let faultType;
|
|
179
277
|
if (leg === 1 && stage === 'enter') {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
faultType = collator_1.CollationFaultType.DUPLICATE;
|
|
185
|
-
}
|
|
186
|
-
else if (amount != 899000000000000) {
|
|
187
|
-
faultType = collator_1.CollationFaultType.INVALID;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
else if (leg === 1 && stage === 'exit') {
|
|
191
|
-
if (amount === -10000000000000) {
|
|
278
|
+
const leg1Attempts = this.getDigitAtPosition(amount, 3);
|
|
279
|
+
const leg1Complete = this.getDigitAtPosition(amount, 4);
|
|
280
|
+
if (leg1Attempts === 0) {
|
|
281
|
+
//seed was not set (no authorization)
|
|
192
282
|
faultType = collator_1.CollationFaultType.MISSING;
|
|
193
283
|
}
|
|
194
|
-
else if (
|
|
284
|
+
else if (leg1Complete > 0) {
|
|
285
|
+
//Leg1 already completed — stale/replayed message
|
|
195
286
|
faultType = collator_1.CollationFaultType.DUPLICATE;
|
|
196
287
|
}
|
|
197
288
|
}
|
|
198
289
|
else if (leg === 2 && stage === 'enter') {
|
|
199
|
-
|
|
290
|
+
const leg1Complete = this.getDigitAtPosition(amount, 4);
|
|
291
|
+
const finalized = this.getDigitAtPosition(amount, 2);
|
|
292
|
+
if (leg1Complete === 0) {
|
|
293
|
+
//Leg1 not complete — reentry not authorized
|
|
200
294
|
faultType = collator_1.CollationFaultType.FORBIDDEN;
|
|
201
295
|
}
|
|
202
|
-
else if (
|
|
296
|
+
else if (finalized > 0) {
|
|
297
|
+
//activity finalized — no new Leg2 GUIDs accepted
|
|
203
298
|
faultType = collator_1.CollationFaultType.INACTIVE;
|
|
204
299
|
}
|
|
205
300
|
}
|
|
@@ -207,6 +302,9 @@ class CollatorService {
|
|
|
207
302
|
throw new errors_1.CollationError(amount, leg, stage, faultType);
|
|
208
303
|
}
|
|
209
304
|
}
|
|
305
|
+
// ──────────────────────────────────────────────────
|
|
306
|
+
// Dimensional address resolution
|
|
307
|
+
// ──────────────────────────────────────────────────
|
|
210
308
|
static getDimensionsById(ancestors, dad) {
|
|
211
309
|
//ancestors is an ordered list of all ancestors, starting with the trigger (['t1', 'a1', 'a2'])
|
|
212
310
|
//dad is the dimensional address of the ancestors list (',0,5,3')
|
|
@@ -221,18 +319,26 @@ class CollatorService {
|
|
|
221
319
|
});
|
|
222
320
|
return map;
|
|
223
321
|
}
|
|
322
|
+
// ──────────────────────────────────────────────────
|
|
323
|
+
// Seeds
|
|
324
|
+
// ──────────────────────────────────────────────────
|
|
224
325
|
/**
|
|
225
|
-
* All non-trigger activities are assigned a status seed by their parent
|
|
326
|
+
* All non-trigger activities are assigned a status seed by their parent.
|
|
327
|
+
* Seed: 100000000000000 (pos 1 = 1, authorized for entry)
|
|
226
328
|
*/
|
|
227
329
|
static getSeed() {
|
|
228
|
-
return '
|
|
330
|
+
return '100000000000000';
|
|
229
331
|
}
|
|
230
332
|
/**
|
|
231
|
-
* All trigger activities are assigned a status seed in a completed state
|
|
333
|
+
* All trigger activities are assigned a status seed in a completed state.
|
|
334
|
+
* Seed: 101100000000001 (authorized, 1 Leg1 entry, Leg1 complete, 1 Leg2 entry)
|
|
232
335
|
*/
|
|
233
336
|
static getTriggerSeed() {
|
|
234
|
-
return '
|
|
337
|
+
return '101100000000001';
|
|
235
338
|
}
|
|
339
|
+
// ──────────────────────────────────────────────────
|
|
340
|
+
// Compiler
|
|
341
|
+
// ──────────────────────────────────────────────────
|
|
236
342
|
/**
|
|
237
343
|
* entry point for compiler-type activities. This is called by the compiler
|
|
238
344
|
* to bind the sorted activity IDs to the trigger activity. These are then used
|
|
@@ -291,3 +397,20 @@ class CollatorService {
|
|
|
291
397
|
exports.CollatorService = CollatorService;
|
|
292
398
|
//max int digit count that supports `hincrby`
|
|
293
399
|
CollatorService.targetLength = 15;
|
|
400
|
+
/**
|
|
401
|
+
* Positional weights for the 15-digit activity/GUID ledger.
|
|
402
|
+
*
|
|
403
|
+
* Position: 1 2 3 4 5 6 7 8-15
|
|
404
|
+
* Weight: 100T 10T 1T 100B 10B 1B 100M 10M..1
|
|
405
|
+
*/
|
|
406
|
+
CollatorService.WEIGHTS = {
|
|
407
|
+
AUTH: 100000000000000,
|
|
408
|
+
FINALIZE: 10000000000000,
|
|
409
|
+
LEG1_ENTRY: 1000000000000,
|
|
410
|
+
LEG1_COMPLETE: 100000000000,
|
|
411
|
+
STEP1_WORK: 10000000000,
|
|
412
|
+
STEP2_SPAWN: 1000000000,
|
|
413
|
+
STEP3_CLEANUP: 100000000,
|
|
414
|
+
LEG2_ENTRY: 1,
|
|
415
|
+
GUID_SNAPSHOT: 100000000000, // 100B on GUID ledger (job closed snapshot)
|
|
416
|
+
};
|
|
@@ -3,7 +3,7 @@ import { ProviderConfig, ProviderNativeClient } from '../../types/provider';
|
|
|
3
3
|
export declare class ConnectorService {
|
|
4
4
|
static disconnectAll(): Promise<void>;
|
|
5
5
|
/**
|
|
6
|
-
* Connect to a provider (
|
|
6
|
+
* Connect to a provider (nats, postgres) and return the native
|
|
7
7
|
* client. Connections are handled by the engine and worker routers at
|
|
8
8
|
* initialization, but the factory method provided here is useful
|
|
9
9
|
* for testing provider configurations.
|
|
@@ -2,19 +2,15 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ConnectorService = void 0;
|
|
4
4
|
const utils_1 = require("../../modules/utils");
|
|
5
|
-
const ioredis_1 = require("./providers/ioredis");
|
|
6
5
|
const nats_1 = require("./providers/nats");
|
|
7
6
|
const postgres_1 = require("./providers/postgres");
|
|
8
|
-
const redis_1 = require("./providers/redis");
|
|
9
7
|
class ConnectorService {
|
|
10
8
|
static async disconnectAll() {
|
|
11
|
-
await redis_1.RedisConnection.disconnectAll();
|
|
12
|
-
await ioredis_1.RedisConnection.disconnectAll();
|
|
13
9
|
await postgres_1.PostgresConnection.disconnectAll();
|
|
14
10
|
await nats_1.NatsConnection.disconnectAll();
|
|
15
11
|
}
|
|
16
12
|
/**
|
|
17
|
-
* Connect to a provider (
|
|
13
|
+
* Connect to a provider (nats, postgres) and return the native
|
|
18
14
|
* client. Connections are handled by the engine and worker routers at
|
|
19
15
|
* initialization, but the factory method provided here is useful
|
|
20
16
|
* for testing provider configurations.
|
|
@@ -81,12 +77,6 @@ class ConnectorService {
|
|
|
81
77
|
let clientInstance;
|
|
82
78
|
const id = (0, utils_1.guid)();
|
|
83
79
|
switch (providerType) {
|
|
84
|
-
case 'redis':
|
|
85
|
-
clientInstance = await redis_1.RedisConnection.connect(id, providerClass, options, { provider: providerName });
|
|
86
|
-
break;
|
|
87
|
-
case 'ioredis':
|
|
88
|
-
clientInstance = await ioredis_1.RedisConnection.connect(id, providerClass, options, { provider: providerName });
|
|
89
|
-
break;
|
|
90
80
|
case 'nats':
|
|
91
81
|
clientInstance = await nats_1.NatsConnection.connect(id, providerClass, options, { provider: providerName });
|
|
92
82
|
break;
|
|
@@ -143,7 +143,7 @@ declare class EngineService {
|
|
|
143
143
|
/**
|
|
144
144
|
* @private
|
|
145
145
|
*/
|
|
146
|
-
execAdjacentParent(context: JobState, jobOutput: JobOutput, emit?: boolean): Promise<string>;
|
|
146
|
+
execAdjacentParent(context: JobState, jobOutput: JobOutput, emit?: boolean, transaction?: ProviderTransaction): Promise<string>;
|
|
147
147
|
/**
|
|
148
148
|
* @private
|
|
149
149
|
*/
|
|
@@ -163,7 +163,7 @@ declare class EngineService {
|
|
|
163
163
|
/**
|
|
164
164
|
* @private
|
|
165
165
|
*/
|
|
166
|
-
hook(topic: string, data: JobData, status?: StreamStatus, code?: StreamCode): Promise<string>;
|
|
166
|
+
hook(topic: string, data: JobData, status?: StreamStatus, code?: StreamCode, transaction?: ProviderTransaction): Promise<string>;
|
|
167
167
|
/**
|
|
168
168
|
* @private
|
|
169
169
|
*/
|
|
@@ -199,7 +199,7 @@ declare class EngineService {
|
|
|
199
199
|
/**
|
|
200
200
|
* @private
|
|
201
201
|
*/
|
|
202
|
-
pubOneTimeSubs(context: JobState, jobOutput: JobOutput, emit?: boolean): Promise<void>;
|
|
202
|
+
pubOneTimeSubs(context: JobState, jobOutput: JobOutput, emit?: boolean, transaction?: ProviderTransaction): Promise<void>;
|
|
203
203
|
/**
|
|
204
204
|
* @private
|
|
205
205
|
*/
|
|
@@ -207,7 +207,7 @@ declare class EngineService {
|
|
|
207
207
|
/**
|
|
208
208
|
* @private
|
|
209
209
|
*/
|
|
210
|
-
pubPermSubs(context: JobState, jobOutput: JobOutput, emit?: boolean): Promise<void>;
|
|
210
|
+
pubPermSubs(context: JobState, jobOutput: JobOutput, emit?: boolean, transaction?: ProviderTransaction): Promise<void>;
|
|
211
211
|
/**
|
|
212
212
|
* @private
|
|
213
213
|
*/
|
|
@@ -227,7 +227,7 @@ declare class EngineService {
|
|
|
227
227
|
/**
|
|
228
228
|
* @private
|
|
229
229
|
*/
|
|
230
|
-
runJobCompletionTasks(context: JobState, options?: JobCompletionOptions): Promise<string | void>;
|
|
230
|
+
runJobCompletionTasks(context: JobState, options?: JobCompletionOptions, transaction?: ProviderTransaction): Promise<string | void>;
|
|
231
231
|
/**
|
|
232
232
|
* Job hash expiration is typically reliant on the metadata field
|
|
233
233
|
* if the activity concludes normally. However, if the job is `interrupted`,
|
|
@@ -381,7 +381,7 @@ class EngineService {
|
|
|
381
381
|
/**
|
|
382
382
|
* @private
|
|
383
383
|
*/
|
|
384
|
-
async execAdjacentParent(context, jobOutput, emit = false) {
|
|
384
|
+
async execAdjacentParent(context, jobOutput, emit = false, transaction) {
|
|
385
385
|
if (this.hasParentJob(context)) {
|
|
386
386
|
//errors are stringified `StreamError` objects
|
|
387
387
|
const error = this.resolveError(jobOutput.metadata);
|
|
@@ -414,7 +414,7 @@ class EngineService {
|
|
|
414
414
|
streamData.status = stream_1.StreamStatus.SUCCESS;
|
|
415
415
|
streamData.code = enums_1.HMSH_CODE_SUCCESS;
|
|
416
416
|
}
|
|
417
|
-
return (await this.router?.publishMessage(null, streamData));
|
|
417
|
+
return (await this.router?.publishMessage(null, streamData, transaction));
|
|
418
418
|
}
|
|
419
419
|
}
|
|
420
420
|
/**
|
|
@@ -461,7 +461,7 @@ class EngineService {
|
|
|
461
461
|
/**
|
|
462
462
|
* @private
|
|
463
463
|
*/
|
|
464
|
-
async hook(topic, data, status = stream_1.StreamStatus.SUCCESS, code = 200) {
|
|
464
|
+
async hook(topic, data, status = stream_1.StreamStatus.SUCCESS, code = 200, transaction) {
|
|
465
465
|
const hookRule = await this.taskService.getHookRule(topic);
|
|
466
466
|
const [aid] = await this.getSchema(`.${hookRule.to}`);
|
|
467
467
|
const streamData = {
|
|
@@ -475,7 +475,7 @@ class EngineService {
|
|
|
475
475
|
},
|
|
476
476
|
data,
|
|
477
477
|
};
|
|
478
|
-
return (await this.router?.publishMessage(null, streamData));
|
|
478
|
+
return (await this.router?.publishMessage(null, streamData, transaction));
|
|
479
479
|
}
|
|
480
480
|
/**
|
|
481
481
|
* @private
|
|
@@ -621,7 +621,7 @@ class EngineService {
|
|
|
621
621
|
/**
|
|
622
622
|
* @private
|
|
623
623
|
*/
|
|
624
|
-
async pubOneTimeSubs(context, jobOutput, emit = false) {
|
|
624
|
+
async pubOneTimeSubs(context, jobOutput, emit = false, transaction) {
|
|
625
625
|
//todo: subscriber should query for the job...only publish minimum context needed
|
|
626
626
|
if (this.hasOneTimeSubscription(context)) {
|
|
627
627
|
const message = {
|
|
@@ -629,7 +629,7 @@ class EngineService {
|
|
|
629
629
|
topic: context.metadata.jid,
|
|
630
630
|
job: (0, utils_1.restoreHierarchy)(jobOutput),
|
|
631
631
|
};
|
|
632
|
-
this.subscribe.publish(key_1.KeyType.QUORUM, message, this.appId, context.metadata.ngn);
|
|
632
|
+
await this.subscribe.publish(key_1.KeyType.QUORUM, message, this.appId, context.metadata.ngn, transaction);
|
|
633
633
|
}
|
|
634
634
|
}
|
|
635
635
|
/**
|
|
@@ -644,7 +644,7 @@ class EngineService {
|
|
|
644
644
|
/**
|
|
645
645
|
* @private
|
|
646
646
|
*/
|
|
647
|
-
async pubPermSubs(context, jobOutput, emit = false) {
|
|
647
|
+
async pubPermSubs(context, jobOutput, emit = false, transaction) {
|
|
648
648
|
const topic = await this.getPublishesTopic(context);
|
|
649
649
|
if (topic) {
|
|
650
650
|
const message = {
|
|
@@ -652,7 +652,7 @@ class EngineService {
|
|
|
652
652
|
topic,
|
|
653
653
|
job: (0, utils_1.restoreHierarchy)(jobOutput),
|
|
654
654
|
};
|
|
655
|
-
this.subscribe.publish(key_1.KeyType.QUORUM, message, this.appId, `${topic}.${context.metadata.jid}
|
|
655
|
+
await this.subscribe.publish(key_1.KeyType.QUORUM, message, this.appId, `${topic}.${context.metadata.jid}`, transaction);
|
|
656
656
|
}
|
|
657
657
|
}
|
|
658
658
|
/**
|
|
@@ -683,20 +683,41 @@ class EngineService {
|
|
|
683
683
|
/**
|
|
684
684
|
* @private
|
|
685
685
|
*/
|
|
686
|
-
async runJobCompletionTasks(context, options = {}) {
|
|
686
|
+
async runJobCompletionTasks(context, options = {}, transaction) {
|
|
687
687
|
//'emit' indicates the job is still active
|
|
688
688
|
const isAwait = this.hasParentJob(context, true);
|
|
689
689
|
const isOneTimeSub = this.hasOneTimeSubscription(context);
|
|
690
690
|
const topic = await this.getPublishesTopic(context);
|
|
691
691
|
let msgId;
|
|
692
|
+
let jobOutput;
|
|
692
693
|
if (isAwait || isOneTimeSub || topic) {
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
694
|
+
jobOutput = await this.getState(context.metadata.tpc, context.metadata.jid);
|
|
695
|
+
//only send RESULT to parent for non-severed children (execChild).
|
|
696
|
+
//startChild (px=true) children already sent RESULT at spawn time
|
|
697
|
+
//via Trigger.execAdjacentParent; sending again here would cause
|
|
698
|
+
//a duplicate semaphore decrement in collation workflows.
|
|
699
|
+
if (isAwait) {
|
|
700
|
+
msgId = await this.execAdjacentParent(context, jobOutput, options.emit, transaction);
|
|
701
|
+
}
|
|
702
|
+
if (transaction) {
|
|
703
|
+
//transactional: await to queue NOTIFY in the transaction
|
|
704
|
+
await this.pubOneTimeSubs(context, jobOutput, options.emit, transaction);
|
|
705
|
+
await this.pubPermSubs(context, jobOutput, options.emit, transaction);
|
|
706
|
+
}
|
|
707
|
+
else {
|
|
708
|
+
//non-transactional: fire-and-forget to avoid race with inline
|
|
709
|
+
//trigger processing (callback registered after pub() returns)
|
|
710
|
+
this.pubOneTimeSubs(context, jobOutput, options.emit);
|
|
711
|
+
this.pubPermSubs(context, jobOutput, options.emit);
|
|
712
|
+
}
|
|
697
713
|
}
|
|
698
714
|
if (!options.emit) {
|
|
699
|
-
|
|
715
|
+
if (transaction) {
|
|
716
|
+
await this.taskService.registerJobForCleanup(context.metadata.jid, this.resolveExpires(context, options), options, transaction);
|
|
717
|
+
}
|
|
718
|
+
else {
|
|
719
|
+
this.taskService.registerJobForCleanup(context.metadata.jid, this.resolveExpires(context, options), options);
|
|
720
|
+
}
|
|
700
721
|
}
|
|
701
722
|
return msgId;
|
|
702
723
|
}
|
|
@@ -745,7 +766,7 @@ class EngineService {
|
|
|
745
766
|
}
|
|
746
767
|
const [state, status] = output;
|
|
747
768
|
const stateTree = (0, utils_1.restoreHierarchy)(state);
|
|
748
|
-
if (status && stateTree.metadata) {
|
|
769
|
+
if (status != null && stateTree.metadata) {
|
|
749
770
|
stateTree.metadata.js = status;
|
|
750
771
|
}
|
|
751
772
|
return stateTree;
|
|
@@ -75,7 +75,7 @@ class ConsumptionManager {
|
|
|
75
75
|
const features = this.stream.getProviderSpecificFeatures();
|
|
76
76
|
const supportsNotifications = features.supportsNotifications;
|
|
77
77
|
if (supportsNotifications) {
|
|
78
|
-
this.logger.
|
|
78
|
+
this.logger.debug(`router-stream-using-notifications`, {
|
|
79
79
|
group,
|
|
80
80
|
consumer,
|
|
81
81
|
stream,
|