@hotmeshio/hotmesh 0.5.0 → 0.5.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 (49) hide show
  1. package/README.md +234 -237
  2. package/build/modules/errors.d.ts +9 -0
  3. package/build/modules/errors.js +9 -0
  4. package/build/package.json +16 -14
  5. package/build/services/hotmesh/index.d.ts +9 -11
  6. package/build/services/hotmesh/index.js +9 -11
  7. package/build/services/memflow/entity.d.ts +168 -4
  8. package/build/services/memflow/entity.js +177 -15
  9. package/build/services/memflow/index.d.ts +8 -0
  10. package/build/services/memflow/index.js +8 -0
  11. package/build/services/memflow/worker.js +25 -0
  12. package/build/services/memflow/workflow/execChild.js +1 -0
  13. package/build/services/memflow/workflow/execHook.d.ts +2 -2
  14. package/build/services/memflow/workflow/execHook.js +19 -9
  15. package/build/services/memflow/workflow/index.d.ts +2 -4
  16. package/build/services/memflow/workflow/index.js +2 -4
  17. package/build/services/memflow/workflow/interruption.d.ts +28 -0
  18. package/build/services/memflow/workflow/interruption.js +43 -0
  19. package/build/services/memflow/workflow/proxyActivities.js +1 -0
  20. package/build/services/memflow/workflow/sleepFor.js +1 -0
  21. package/build/services/memflow/workflow/waitFor.js +4 -4
  22. package/build/services/search/index.d.ts +10 -0
  23. package/build/services/search/providers/postgres/postgres.d.ts +12 -0
  24. package/build/services/search/providers/postgres/postgres.js +209 -0
  25. package/build/services/search/providers/redis/ioredis.d.ts +4 -0
  26. package/build/services/search/providers/redis/ioredis.js +13 -0
  27. package/build/services/search/providers/redis/redis.d.ts +4 -0
  28. package/build/services/search/providers/redis/redis.js +13 -0
  29. package/build/services/store/providers/postgres/kvsql.d.ts +13 -37
  30. package/build/services/store/providers/postgres/kvsql.js +2 -2
  31. package/build/services/store/providers/postgres/kvtypes/hash/basic.d.ts +16 -0
  32. package/build/services/store/providers/postgres/kvtypes/hash/basic.js +480 -0
  33. package/build/services/store/providers/postgres/kvtypes/hash/expire.d.ts +5 -0
  34. package/build/services/store/providers/postgres/kvtypes/hash/expire.js +33 -0
  35. package/build/services/store/providers/postgres/kvtypes/hash/index.d.ts +29 -0
  36. package/build/services/store/providers/postgres/kvtypes/hash/index.js +190 -0
  37. package/build/services/store/providers/postgres/kvtypes/hash/jsonb.d.ts +14 -0
  38. package/build/services/store/providers/postgres/kvtypes/hash/jsonb.js +699 -0
  39. package/build/services/store/providers/postgres/kvtypes/hash/scan.d.ts +10 -0
  40. package/build/services/store/providers/postgres/kvtypes/hash/scan.js +91 -0
  41. package/build/services/store/providers/postgres/kvtypes/hash/types.d.ts +19 -0
  42. package/build/services/store/providers/postgres/kvtypes/hash/types.js +2 -0
  43. package/build/services/store/providers/postgres/kvtypes/hash/utils.d.ts +18 -0
  44. package/build/services/store/providers/postgres/kvtypes/hash/utils.js +90 -0
  45. package/build/types/memflow.d.ts +1 -1
  46. package/build/types/meshdata.d.ts +1 -1
  47. package/package.json +16 -14
  48. package/build/services/store/providers/postgres/kvtypes/hash.d.ts +0 -60
  49. package/build/services/store/providers/postgres/kvtypes/hash.js +0 -1287
@@ -0,0 +1,699 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createJsonbOperations = void 0;
4
+ const utils_1 = require("./utils");
5
+ function createJsonbOperations(context) {
6
+ return {
7
+ handleContextSet,
8
+ handleContextMerge,
9
+ handleContextDelete,
10
+ handleContextAppend,
11
+ handleContextPrepend,
12
+ handleContextRemove,
13
+ handleContextIncrement,
14
+ handleContextToggle,
15
+ handleContextSetIfNotExists,
16
+ handleContextGet,
17
+ handleContextGetPath,
18
+ };
19
+ function handleContextSet(key, fields, options) {
20
+ const tableName = context.tableForKey(key, 'hash');
21
+ const replayId = Object.keys(fields).find(k => k.includes('-') && k !== '@context');
22
+ const params = [];
23
+ let sql = '';
24
+ if (options?.nx) {
25
+ if (replayId) {
26
+ sql = `
27
+ WITH inserted_job AS (
28
+ INSERT INTO ${tableName} (id, key, context)
29
+ SELECT gen_random_uuid(), $1, $2::jsonb
30
+ WHERE NOT EXISTS (
31
+ SELECT 1 FROM ${tableName}
32
+ WHERE key = $1 AND is_live
33
+ )
34
+ RETURNING id, context::text as new_value
35
+ ),
36
+ replay_insert AS (
37
+ INSERT INTO ${tableName}_attributes (job_id, field, value, type)
38
+ SELECT id, $3, new_value, $4
39
+ FROM inserted_job
40
+ ON CONFLICT (job_id, field) DO UPDATE
41
+ SET value = EXCLUDED.value
42
+ RETURNING 1
43
+ )
44
+ SELECT new_value FROM inserted_job
45
+ `;
46
+ params.push(key, fields['@context'], replayId, (0, utils_1.deriveType)(replayId));
47
+ }
48
+ else {
49
+ sql = `
50
+ INSERT INTO ${tableName} (id, key, context)
51
+ SELECT gen_random_uuid(), $1, $2::jsonb
52
+ WHERE NOT EXISTS (
53
+ SELECT 1 FROM ${tableName}
54
+ WHERE key = $1 AND is_live
55
+ )
56
+ RETURNING context::text as new_value
57
+ `;
58
+ params.push(key, fields['@context']);
59
+ }
60
+ }
61
+ else {
62
+ if (replayId) {
63
+ sql = `
64
+ WITH updated_job AS (
65
+ UPDATE ${tableName}
66
+ SET context = $2::jsonb
67
+ WHERE key = $1 AND is_live
68
+ RETURNING id, context::text as new_value
69
+ ),
70
+ replay_insert AS (
71
+ INSERT INTO ${tableName}_attributes (job_id, field, value, type)
72
+ SELECT id, $3, new_value, $4
73
+ FROM updated_job
74
+ ON CONFLICT (job_id, field) DO UPDATE
75
+ SET value = EXCLUDED.value
76
+ RETURNING 1
77
+ )
78
+ SELECT new_value FROM updated_job
79
+ `;
80
+ params.push(key, fields['@context'], replayId, (0, utils_1.deriveType)(replayId));
81
+ }
82
+ else {
83
+ sql = `
84
+ UPDATE ${tableName}
85
+ SET context = $2::jsonb
86
+ WHERE key = $1 AND is_live
87
+ RETURNING context::text as new_value
88
+ `;
89
+ params.push(key, fields['@context']);
90
+ }
91
+ }
92
+ return { sql, params };
93
+ }
94
+ function handleContextMerge(key, fields, options) {
95
+ const tableName = context.tableForKey(key, 'hash');
96
+ const replayId = Object.keys(fields).find(k => k.includes('-') && k !== '@context:merge');
97
+ const params = [];
98
+ let sql = '';
99
+ if (options?.nx) {
100
+ sql = `
101
+ INSERT INTO ${tableName} (id, key, context)
102
+ SELECT gen_random_uuid(), $1, $2::jsonb
103
+ WHERE NOT EXISTS (
104
+ SELECT 1 FROM ${tableName}
105
+ WHERE key = $1 AND is_live
106
+ )
107
+ RETURNING context::text as new_value
108
+ `;
109
+ params.push(key, fields['@context:merge']);
110
+ }
111
+ else {
112
+ if (replayId) {
113
+ sql = `
114
+ WITH updated_job AS (
115
+ UPDATE ${tableName}
116
+ SET context = (
117
+ WITH RECURSIVE deep_merge(original, new_data, result) AS (
118
+ SELECT
119
+ COALESCE(context, '{}'::jsonb) as original,
120
+ $2::jsonb as new_data,
121
+ COALESCE(context, '{}'::jsonb) as result
122
+ FROM ${tableName}
123
+ WHERE key = $1 AND is_live
124
+ ),
125
+ merged_data AS (
126
+ SELECT
127
+ (
128
+ SELECT jsonb_object_agg(
129
+ key,
130
+ CASE
131
+ WHEN jsonb_typeof(original -> key) = 'object' AND jsonb_typeof(new_data -> key) = 'object'
132
+ THEN (
133
+ WITH nested_keys AS (
134
+ SELECT unnest(ARRAY(SELECT jsonb_object_keys((original -> key) || (new_data -> key)))) as nested_key
135
+ )
136
+ SELECT jsonb_object_agg(
137
+ nested_key,
138
+ CASE
139
+ WHEN (new_data -> key) ? nested_key
140
+ THEN (new_data -> key) -> nested_key
141
+ ELSE (original -> key) -> nested_key
142
+ END
143
+ )
144
+ FROM nested_keys
145
+ )
146
+ WHEN new_data ? key
147
+ THEN new_data -> key
148
+ ELSE original -> key
149
+ END
150
+ )
151
+ FROM (
152
+ SELECT unnest(ARRAY(SELECT jsonb_object_keys(original || new_data))) as key
153
+ ) all_keys
154
+ ) as merged_context
155
+ FROM deep_merge
156
+ )
157
+ SELECT merged_context FROM merged_data
158
+ )
159
+ WHERE key = $1 AND is_live
160
+ RETURNING id, context::text as new_value
161
+ ),
162
+ replay_insert AS (
163
+ INSERT INTO ${tableName}_attributes (job_id, field, value, type)
164
+ SELECT id, $3, new_value, $4
165
+ FROM updated_job
166
+ ON CONFLICT (job_id, field) DO UPDATE
167
+ SET value = EXCLUDED.value
168
+ RETURNING 1
169
+ )
170
+ SELECT new_value FROM updated_job
171
+ `;
172
+ params.push(key, fields['@context:merge'], replayId, (0, utils_1.deriveType)(replayId));
173
+ }
174
+ else {
175
+ sql = `
176
+ UPDATE ${tableName}
177
+ SET context = (
178
+ WITH merged_data AS (
179
+ SELECT
180
+ (
181
+ SELECT jsonb_object_agg(
182
+ key,
183
+ CASE
184
+ WHEN jsonb_typeof(original -> key) = 'object' AND jsonb_typeof(new_data -> key) = 'object'
185
+ THEN (
186
+ WITH nested_keys AS (
187
+ SELECT unnest(ARRAY(SELECT jsonb_object_keys((original -> key) || (new_data -> key)))) as nested_key
188
+ )
189
+ SELECT jsonb_object_agg(
190
+ nested_key,
191
+ CASE
192
+ WHEN (new_data -> key) ? nested_key
193
+ THEN (new_data -> key) -> nested_key
194
+ ELSE (original -> key) -> nested_key
195
+ END
196
+ )
197
+ FROM nested_keys
198
+ )
199
+ WHEN new_data ? key
200
+ THEN new_data -> key
201
+ ELSE original -> key
202
+ END
203
+ )
204
+ FROM (
205
+ SELECT unnest(ARRAY(SELECT jsonb_object_keys(original || new_data))) as key
206
+ ) all_keys
207
+ ) as merged_context
208
+ FROM (
209
+ SELECT
210
+ COALESCE(context, '{}'::jsonb) as original,
211
+ $2::jsonb as new_data
212
+ FROM ${tableName}
213
+ WHERE key = $1 AND is_live
214
+ ) base_data
215
+ )
216
+ SELECT merged_context FROM merged_data
217
+ )
218
+ WHERE key = $1 AND is_live
219
+ RETURNING context::text as new_value
220
+ `;
221
+ params.push(key, fields['@context:merge']);
222
+ }
223
+ }
224
+ return { sql, params };
225
+ }
226
+ function handleContextDelete(key, fields, options) {
227
+ const tableName = context.tableForKey(key, 'hash');
228
+ const path = fields['@context:delete'];
229
+ const pathParts = path.split('.');
230
+ const replayId = Object.keys(fields).find(k => k.includes('-') && k !== '@context:delete');
231
+ const params = [];
232
+ let sql = '';
233
+ if (pathParts.length === 1) {
234
+ // Simple key deletion
235
+ if (replayId) {
236
+ sql = `
237
+ WITH updated_job AS (
238
+ UPDATE ${tableName}
239
+ SET context = context - $2
240
+ WHERE key = $1 AND is_live
241
+ RETURNING id, context::text as new_value
242
+ ),
243
+ replay_insert AS (
244
+ INSERT INTO ${tableName}_attributes (job_id, field, value, type)
245
+ SELECT id, $3, new_value, $4
246
+ FROM updated_job
247
+ ON CONFLICT (job_id, field) DO UPDATE
248
+ SET value = EXCLUDED.value
249
+ RETURNING 1
250
+ )
251
+ SELECT new_value FROM updated_job
252
+ `;
253
+ params.push(key, path, replayId, (0, utils_1.deriveType)(replayId));
254
+ }
255
+ else {
256
+ sql = `
257
+ UPDATE ${tableName}
258
+ SET context = context - $2
259
+ WHERE key = $1 AND is_live
260
+ RETURNING context::text as new_value
261
+ `;
262
+ params.push(key, path);
263
+ }
264
+ }
265
+ else {
266
+ // Nested path deletion using jsonb_set with null to remove
267
+ if (replayId) {
268
+ sql = `
269
+ WITH updated_job AS (
270
+ UPDATE ${tableName}
271
+ SET context = context #- $2::text[]
272
+ WHERE key = $1 AND is_live
273
+ RETURNING id, context::text as new_value
274
+ ),
275
+ replay_insert AS (
276
+ INSERT INTO ${tableName}_attributes (job_id, field, value, type)
277
+ SELECT id, $3, new_value, $4
278
+ FROM updated_job
279
+ ON CONFLICT (job_id, field) DO UPDATE
280
+ SET value = EXCLUDED.value
281
+ RETURNING 1
282
+ )
283
+ SELECT new_value FROM updated_job
284
+ `;
285
+ params.push(key, pathParts, replayId, (0, utils_1.deriveType)(replayId));
286
+ }
287
+ else {
288
+ sql = `
289
+ UPDATE ${tableName}
290
+ SET context = context #- $2::text[]
291
+ WHERE key = $1 AND is_live
292
+ RETURNING context::text as new_value
293
+ `;
294
+ params.push(key, pathParts);
295
+ }
296
+ }
297
+ return { sql, params };
298
+ }
299
+ function handleContextAppend(key, fields, options) {
300
+ const tableName = context.tableForKey(key, 'hash');
301
+ const { path, value } = JSON.parse(fields['@context:append']);
302
+ const pathParts = path.split('.');
303
+ const replayId = Object.keys(fields).find(k => k.includes('-') && k !== '@context:append');
304
+ const params = [];
305
+ let sql = '';
306
+ if (replayId) {
307
+ sql = `
308
+ WITH updated_job AS (
309
+ UPDATE ${tableName}
310
+ SET context = jsonb_set(
311
+ COALESCE(context, '{}'::jsonb),
312
+ $2::text[],
313
+ COALESCE(context #> $2::text[], '[]'::jsonb) || $3::jsonb,
314
+ true
315
+ )
316
+ WHERE key = $1 AND is_live
317
+ RETURNING id, (context #> $2::text[])::text as new_value
318
+ ),
319
+ replay_insert AS (
320
+ INSERT INTO ${tableName}_attributes (job_id, field, value, type)
321
+ SELECT id, $4, new_value, $5
322
+ FROM updated_job
323
+ ON CONFLICT (job_id, field) DO UPDATE
324
+ SET value = EXCLUDED.value
325
+ RETURNING 1
326
+ )
327
+ SELECT new_value FROM updated_job
328
+ `;
329
+ params.push(key, pathParts, JSON.stringify([value]), replayId, (0, utils_1.deriveType)(replayId));
330
+ }
331
+ else {
332
+ sql = `
333
+ UPDATE ${tableName}
334
+ SET context = jsonb_set(
335
+ COALESCE(context, '{}'::jsonb),
336
+ $2::text[],
337
+ COALESCE(context #> $2::text[], '[]'::jsonb) || $3::jsonb,
338
+ true
339
+ )
340
+ WHERE key = $1 AND is_live
341
+ RETURNING (context #> $2::text[])::text as new_value
342
+ `;
343
+ params.push(key, pathParts, JSON.stringify([value]));
344
+ }
345
+ return { sql, params };
346
+ }
347
+ function handleContextPrepend(key, fields, options) {
348
+ const tableName = context.tableForKey(key, 'hash');
349
+ const { path, value } = JSON.parse(fields['@context:prepend']);
350
+ const pathParts = path.split('.');
351
+ const replayId = Object.keys(fields).find(k => k.includes('-') && k !== '@context:prepend');
352
+ const params = [];
353
+ let sql = '';
354
+ if (replayId) {
355
+ sql = `
356
+ WITH updated_job AS (
357
+ UPDATE ${tableName}
358
+ SET context = jsonb_set(
359
+ COALESCE(context, '{}'::jsonb),
360
+ $2::text[],
361
+ $3::jsonb || COALESCE(context #> $2::text[], '[]'::jsonb),
362
+ true
363
+ )
364
+ WHERE key = $1 AND is_live
365
+ RETURNING id, (context #> $2::text[])::text as new_value
366
+ ),
367
+ replay_insert AS (
368
+ INSERT INTO ${tableName}_attributes (job_id, field, value, type)
369
+ SELECT id, $4, new_value, $5
370
+ FROM updated_job
371
+ ON CONFLICT (job_id, field) DO UPDATE
372
+ SET value = EXCLUDED.value
373
+ RETURNING 1
374
+ )
375
+ SELECT new_value FROM updated_job
376
+ `;
377
+ params.push(key, pathParts, JSON.stringify([value]), replayId, (0, utils_1.deriveType)(replayId));
378
+ }
379
+ else {
380
+ sql = `
381
+ UPDATE ${tableName}
382
+ SET context = jsonb_set(
383
+ COALESCE(context, '{}'::jsonb),
384
+ $2::text[],
385
+ $3::jsonb || COALESCE(context #> $2::text[], '[]'::jsonb),
386
+ true
387
+ )
388
+ WHERE key = $1 AND is_live
389
+ RETURNING (context #> $2::text[])::text as new_value
390
+ `;
391
+ params.push(key, pathParts, JSON.stringify([value]));
392
+ }
393
+ return { sql, params };
394
+ }
395
+ function handleContextRemove(key, fields, options) {
396
+ const tableName = context.tableForKey(key, 'hash');
397
+ const { path, index } = JSON.parse(fields['@context:remove']);
398
+ const pathParts = path.split('.');
399
+ const replayId = Object.keys(fields).find(k => k.includes('-') && k !== '@context:remove');
400
+ const params = [];
401
+ let sql = '';
402
+ if (replayId) {
403
+ sql = `
404
+ WITH updated_job AS (
405
+ UPDATE ${tableName}
406
+ SET context = jsonb_set(
407
+ COALESCE(context, '{}'::jsonb),
408
+ $2::text[],
409
+ (
410
+ SELECT jsonb_agg(value)
411
+ FROM (
412
+ SELECT value, row_number() OVER () - 1 as idx
413
+ FROM jsonb_array_elements(COALESCE(context #> $2::text[], '[]'::jsonb))
414
+ ) t
415
+ WHERE idx != $3
416
+ ),
417
+ true
418
+ )
419
+ WHERE key = $1 AND is_live
420
+ RETURNING id, (context #> $2::text[])::text as new_value
421
+ ),
422
+ replay_insert AS (
423
+ INSERT INTO ${tableName}_attributes (job_id, field, value, type)
424
+ SELECT id, $4, new_value, $5
425
+ FROM updated_job
426
+ ON CONFLICT (job_id, field) DO UPDATE
427
+ SET value = EXCLUDED.value
428
+ RETURNING 1
429
+ )
430
+ SELECT new_value FROM updated_job
431
+ `;
432
+ params.push(key, pathParts, index, replayId, (0, utils_1.deriveType)(replayId));
433
+ }
434
+ else {
435
+ sql = `
436
+ UPDATE ${tableName}
437
+ SET context = jsonb_set(
438
+ COALESCE(context, '{}'::jsonb),
439
+ $2::text[],
440
+ (
441
+ SELECT jsonb_agg(value)
442
+ FROM (
443
+ SELECT value, row_number() OVER () - 1 as idx
444
+ FROM jsonb_array_elements(COALESCE(context #> $2::text[], '[]'::jsonb))
445
+ ) t
446
+ WHERE idx != $3
447
+ ),
448
+ true
449
+ )
450
+ WHERE key = $1 AND is_live
451
+ RETURNING (context #> $2::text[])::text as new_value
452
+ `;
453
+ params.push(key, pathParts, index);
454
+ }
455
+ return { sql, params };
456
+ }
457
+ function handleContextIncrement(key, fields, options) {
458
+ const tableName = context.tableForKey(key, 'hash');
459
+ const { path, value } = JSON.parse(fields['@context:increment']);
460
+ const pathParts = path.split('.');
461
+ const replayId = Object.keys(fields).find(k => k.includes('-') && k !== '@context:increment');
462
+ const params = [];
463
+ let sql = '';
464
+ if (replayId) {
465
+ sql = `
466
+ WITH updated_job AS (
467
+ UPDATE ${tableName}
468
+ SET context = jsonb_set(
469
+ COALESCE(context, '{}'::jsonb),
470
+ $2::text[],
471
+ to_jsonb((COALESCE((context #> $2::text[])::text::numeric, 0) + $3)::numeric),
472
+ true
473
+ )
474
+ WHERE key = $1 AND is_live
475
+ RETURNING id, (context #> $2::text[])::text as new_value
476
+ ),
477
+ replay_insert AS (
478
+ INSERT INTO ${tableName}_attributes (job_id, field, value, type)
479
+ SELECT id, $4, new_value, $5
480
+ FROM updated_job
481
+ ON CONFLICT (job_id, field) DO UPDATE
482
+ SET value = EXCLUDED.value
483
+ RETURNING 1
484
+ )
485
+ SELECT new_value FROM updated_job
486
+ `;
487
+ params.push(key, pathParts, value, replayId, (0, utils_1.deriveType)(replayId));
488
+ }
489
+ else {
490
+ sql = `
491
+ UPDATE ${tableName}
492
+ SET context = jsonb_set(
493
+ COALESCE(context, '{}'::jsonb),
494
+ $2::text[],
495
+ to_jsonb((COALESCE((context #> $2::text[])::text::numeric, 0) + $3)::numeric),
496
+ true
497
+ )
498
+ WHERE key = $1 AND is_live
499
+ RETURNING (context #> $2::text[])::text as new_value
500
+ `;
501
+ params.push(key, pathParts, value);
502
+ }
503
+ return { sql, params };
504
+ }
505
+ function handleContextToggle(key, fields, options) {
506
+ const tableName = context.tableForKey(key, 'hash');
507
+ const path = fields['@context:toggle'];
508
+ const pathParts = path.split('.');
509
+ const replayId = Object.keys(fields).find(k => k.includes('-') && k !== '@context:toggle');
510
+ const params = [];
511
+ let sql = '';
512
+ if (replayId) {
513
+ sql = `
514
+ WITH updated_job AS (
515
+ UPDATE ${tableName}
516
+ SET context = jsonb_set(
517
+ COALESCE(context, '{}'::jsonb),
518
+ $2::text[],
519
+ to_jsonb(NOT COALESCE((context #> $2::text[])::text::boolean, false)),
520
+ true
521
+ )
522
+ WHERE key = $1 AND is_live
523
+ RETURNING id, (context #> $2::text[])::text as new_value
524
+ ),
525
+ replay_insert AS (
526
+ INSERT INTO ${tableName}_attributes (job_id, field, value, type)
527
+ SELECT id, $3, new_value, $4
528
+ FROM updated_job
529
+ ON CONFLICT (job_id, field) DO UPDATE
530
+ SET value = EXCLUDED.value
531
+ RETURNING 1
532
+ )
533
+ SELECT new_value FROM updated_job
534
+ `;
535
+ params.push(key, pathParts, replayId, (0, utils_1.deriveType)(replayId));
536
+ }
537
+ else {
538
+ sql = `
539
+ UPDATE ${tableName}
540
+ SET context = jsonb_set(
541
+ COALESCE(context, '{}'::jsonb),
542
+ $2::text[],
543
+ to_jsonb(NOT COALESCE((context #> $2::text[])::text::boolean, false)),
544
+ true
545
+ )
546
+ WHERE key = $1 AND is_live
547
+ RETURNING (context #> $2::text[])::text as new_value
548
+ `;
549
+ params.push(key, pathParts);
550
+ }
551
+ return { sql, params };
552
+ }
553
+ function handleContextSetIfNotExists(key, fields, options) {
554
+ const tableName = context.tableForKey(key, 'hash');
555
+ const { path, value } = JSON.parse(fields['@context:setIfNotExists']);
556
+ const pathParts = path.split('.');
557
+ const replayId = Object.keys(fields).find(k => k.includes('-') && k !== '@context:setIfNotExists');
558
+ const params = [];
559
+ let sql = '';
560
+ if (replayId) {
561
+ sql = `
562
+ WITH updated_job AS (
563
+ UPDATE ${tableName}
564
+ SET context = CASE
565
+ WHEN context #> $2::text[] IS NULL THEN
566
+ jsonb_set(COALESCE(context, '{}'::jsonb), $2::text[], $3::jsonb, true)
567
+ ELSE context
568
+ END
569
+ WHERE key = $1 AND is_live
570
+ RETURNING id, (context #> $2::text[])::text as new_value
571
+ ),
572
+ replay_insert AS (
573
+ INSERT INTO ${tableName}_attributes (job_id, field, value, type)
574
+ SELECT id, $4, new_value, $5
575
+ FROM updated_job
576
+ ON CONFLICT (job_id, field) DO UPDATE
577
+ SET value = EXCLUDED.value
578
+ RETURNING 1
579
+ )
580
+ SELECT new_value FROM updated_job
581
+ `;
582
+ params.push(key, pathParts, JSON.stringify(value), replayId, (0, utils_1.deriveType)(replayId));
583
+ }
584
+ else {
585
+ sql = `
586
+ UPDATE ${tableName}
587
+ SET context = CASE
588
+ WHEN context #> $2::text[] IS NULL THEN
589
+ jsonb_set(COALESCE(context, '{}'::jsonb), $2::text[], $3::jsonb, true)
590
+ ELSE context
591
+ END
592
+ WHERE key = $1 AND is_live
593
+ RETURNING (context #> $2::text[])::text as new_value
594
+ `;
595
+ params.push(key, pathParts, JSON.stringify(value));
596
+ }
597
+ return { sql, params };
598
+ }
599
+ function handleContextGetPath(key, fields, options) {
600
+ const tableName = context.tableForKey(key, 'hash');
601
+ const getField = Object.keys(fields).find(k => k.startsWith('@context:get:'));
602
+ const pathKey = getField.replace('@context:get:', '');
603
+ const pathParts = JSON.parse(fields[getField]);
604
+ const params = [];
605
+ // Extract the specific path and store it as a temporary field
606
+ const sql = `
607
+ INSERT INTO ${tableName}_attributes (job_id, field, value, type)
608
+ SELECT
609
+ job.id,
610
+ $2,
611
+ COALESCE((job.context #> $3::text[])::text, 'null'),
612
+ $4
613
+ FROM (
614
+ SELECT id, context FROM ${tableName} WHERE key = $1 AND is_live
615
+ ) AS job
616
+ ON CONFLICT (job_id, field) DO UPDATE
617
+ SET value = COALESCE((
618
+ SELECT context #> $3::text[]
619
+ FROM ${tableName}
620
+ WHERE key = $1 AND is_live
621
+ )::text, 'null')
622
+ RETURNING 1 as count
623
+ `;
624
+ params.push(key, getField, pathParts, (0, utils_1.deriveType)(getField));
625
+ return { sql, params };
626
+ }
627
+ function handleContextGet(key, fields, options) {
628
+ const tableName = context.tableForKey(key, 'hash');
629
+ const path = fields['@context:get'];
630
+ const replayId = Object.keys(fields).find(k => k.includes('-') && k !== '@context:get');
631
+ const params = [];
632
+ let sql = '';
633
+ if (path === '') {
634
+ // Get entire context
635
+ if (replayId) {
636
+ sql = `
637
+ WITH job_data AS (
638
+ SELECT id, context::text as context_value
639
+ FROM ${tableName}
640
+ WHERE key = $1 AND is_live
641
+ ),
642
+ replay_insert AS (
643
+ INSERT INTO ${tableName}_attributes (job_id, field, value, type)
644
+ SELECT id, $2, context_value, $3
645
+ FROM job_data
646
+ WHERE id IS NOT NULL
647
+ ON CONFLICT (job_id, field) DO UPDATE
648
+ SET value = EXCLUDED.value
649
+ RETURNING 1
650
+ )
651
+ SELECT context_value as new_value FROM job_data
652
+ `;
653
+ params.push(key, replayId, (0, utils_1.deriveType)(replayId));
654
+ }
655
+ else {
656
+ sql = `
657
+ SELECT context::text as new_value
658
+ FROM ${tableName}
659
+ WHERE key = $1 AND is_live
660
+ `;
661
+ params.push(key);
662
+ }
663
+ }
664
+ else {
665
+ // Get specific path
666
+ const pathParts = path.split('.');
667
+ if (replayId) {
668
+ sql = `
669
+ WITH job_data AS (
670
+ SELECT id, COALESCE((context #> $2::text[])::text, 'null') as path_value
671
+ FROM ${tableName}
672
+ WHERE key = $1 AND is_live
673
+ ),
674
+ replay_insert AS (
675
+ INSERT INTO ${tableName}_attributes (job_id, field, value, type)
676
+ SELECT id, $3, path_value, $4
677
+ FROM job_data
678
+ WHERE id IS NOT NULL
679
+ ON CONFLICT (job_id, field) DO UPDATE
680
+ SET value = EXCLUDED.value
681
+ RETURNING 1
682
+ )
683
+ SELECT path_value as new_value FROM job_data
684
+ `;
685
+ params.push(key, pathParts, replayId, (0, utils_1.deriveType)(replayId));
686
+ }
687
+ else {
688
+ sql = `
689
+ SELECT COALESCE((context #> $2::text[])::text, 'null') as new_value
690
+ FROM ${tableName}
691
+ WHERE key = $1 AND is_live
692
+ `;
693
+ params.push(key, pathParts);
694
+ }
695
+ }
696
+ return { sql, params };
697
+ }
698
+ }
699
+ exports.createJsonbOperations = createJsonbOperations;
@@ -0,0 +1,10 @@
1
+ import { HashContext, SqlResult, HScanResult, ProviderTransaction } from './types';
2
+ export declare function createScanOperations(context: HashContext['context']): {
3
+ hscan(key: string, cursor: string, count?: number, pattern?: string, multi?: ProviderTransaction): Promise<HScanResult>;
4
+ scan(cursor: number, count?: number, pattern?: string, multi?: ProviderTransaction): Promise<{
5
+ cursor: number;
6
+ keys: string[];
7
+ }>;
8
+ };
9
+ export declare function _hscan(context: HashContext['context'], key: string, cursor: string, count: number, pattern?: string): SqlResult;
10
+ export declare function _scan(context: HashContext['context'], cursor: number, count: number, pattern?: string): SqlResult;