@hotmeshio/hotmesh 0.5.1 → 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.
- package/README.md +9 -5
- package/build/package.json +16 -14
- package/build/services/hotmesh/index.d.ts +9 -11
- package/build/services/hotmesh/index.js +9 -11
- package/build/services/memflow/entity.d.ts +168 -4
- package/build/services/memflow/entity.js +177 -15
- package/build/services/memflow/workflow/index.d.ts +2 -4
- package/build/services/memflow/workflow/index.js +2 -4
- package/build/services/memflow/workflow/interruption.d.ts +6 -4
- package/build/services/memflow/workflow/interruption.js +6 -4
- package/build/services/memflow/workflow/waitFor.js +1 -0
- package/build/services/search/index.d.ts +10 -0
- package/build/services/search/providers/postgres/postgres.d.ts +12 -0
- package/build/services/search/providers/postgres/postgres.js +209 -0
- package/build/services/search/providers/redis/ioredis.d.ts +4 -0
- package/build/services/search/providers/redis/ioredis.js +13 -0
- package/build/services/search/providers/redis/redis.d.ts +4 -0
- package/build/services/search/providers/redis/redis.js +13 -0
- package/build/services/store/providers/postgres/kvsql.d.ts +13 -37
- package/build/services/store/providers/postgres/kvsql.js +2 -2
- package/build/services/store/providers/postgres/kvtypes/hash/basic.d.ts +16 -0
- package/build/services/store/providers/postgres/kvtypes/hash/basic.js +480 -0
- package/build/services/store/providers/postgres/kvtypes/hash/expire.d.ts +5 -0
- package/build/services/store/providers/postgres/kvtypes/hash/expire.js +33 -0
- package/build/services/store/providers/postgres/kvtypes/hash/index.d.ts +29 -0
- package/build/services/store/providers/postgres/kvtypes/hash/index.js +190 -0
- package/build/services/store/providers/postgres/kvtypes/hash/jsonb.d.ts +14 -0
- package/build/services/store/providers/postgres/kvtypes/hash/jsonb.js +699 -0
- package/build/services/store/providers/postgres/kvtypes/hash/scan.d.ts +10 -0
- package/build/services/store/providers/postgres/kvtypes/hash/scan.js +91 -0
- package/build/services/store/providers/postgres/kvtypes/hash/types.d.ts +19 -0
- package/build/services/store/providers/postgres/kvtypes/hash/types.js +2 -0
- package/build/services/store/providers/postgres/kvtypes/hash/utils.d.ts +18 -0
- package/build/services/store/providers/postgres/kvtypes/hash/utils.js +90 -0
- package/build/types/memflow.d.ts +1 -1
- package/build/types/meshdata.d.ts +1 -1
- package/package.json +16 -14
- package/build/services/store/providers/postgres/kvtypes/hash.d.ts +0 -60
- 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;
|