@friggframework/core 2.0.0--canary.580.c0487bc.0 → 2.0.0--canary.580.235db2b.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.
@@ -182,44 +182,73 @@ class ProcessRepositoryPostgres extends ProcessRepositoryInterface {
182
182
  byColumn[col] ??
183
183
  (byColumn[col] = `COALESCE("${col}", '{}'::jsonb)`);
184
184
 
185
+ /**
186
+ * Postgres `jsonb_set(target, path, value, create_missing=true)`
187
+ * only creates the LEAF segment if missing — intermediate segments
188
+ * that don't exist as objects cause the call to return `target`
189
+ * unchanged (silent no-op). For a path like `context.a.b.c` on a
190
+ * doc where `context.a` is missing, we'd bail on the write.
191
+ *
192
+ * This helper wraps `prev` in a chain of `jsonb_set` calls that
193
+ * ensure each intermediate prefix path is an object, preserving
194
+ * its contents if it's already present:
195
+ *
196
+ * ensureParents(prev, ['a','b','c'])
197
+ * ⇒ jsonb_set(
198
+ * jsonb_set(prev, '{a}', COALESCE(prev#>'{a}', '{}'::jsonb), true),
199
+ * '{a,b}', COALESCE(${that}#>'{a,b}', '{}'::jsonb), true)
200
+ *
201
+ * The caller then wraps this result with its own `jsonb_set` for
202
+ * the leaf segment. Depth-1 paths skip this entirely (no parents
203
+ * to synthesize).
204
+ */
205
+ const ensureParents = (prevExpr, segments) => {
206
+ let cur = prevExpr;
207
+ for (let i = 1; i < segments.length; i++) {
208
+ const parentPath = `'{${segments.slice(0, i).join(',')}}'`;
209
+ cur = `jsonb_set(${cur}, ${parentPath}, COALESCE(${cur} #> ${parentPath}, '{}'::jsonb), true)`;
210
+ }
211
+ return cur;
212
+ };
213
+
185
214
  const wrapIncrement = (col, segments, delta) => {
186
- // Build the text path for #>> (text accessor).
187
215
  const textPath = `'{${segments.join(',')}}'`;
188
- // Build the jsonb path for jsonb_set (text[] type).
189
216
  const jsonbPath = `'{${segments.join(',')}}'`;
190
217
  const prev = seed(col);
191
- const nextValue = `to_jsonb(COALESCE((${prev} #>> ${textPath})::numeric, 0) + ${bind(delta)})`;
192
- byColumn[col] = `jsonb_set(${prev}, ${jsonbPath}, ${nextValue}, true)`;
218
+ const guarded = ensureParents(prev, segments);
219
+ const nextValue = `to_jsonb(COALESCE((${guarded} #>> ${textPath})::numeric, 0) + ${bind(delta)})`;
220
+ byColumn[col] = `jsonb_set(${guarded}, ${jsonbPath}, ${nextValue}, true)`;
193
221
  };
194
222
 
195
223
  const wrapSet = (col, segments, value) => {
196
224
  const jsonbPath = `'{${segments.join(',')}}'`;
197
225
  const prev = seed(col);
226
+ const guarded = ensureParents(prev, segments);
198
227
  // $n::jsonb — values are serialized to JSON by Prisma when
199
228
  // passed as a parameter, then cast back into jsonb.
200
- byColumn[col] = `jsonb_set(${prev}, ${jsonbPath}, ${bind(JSON.stringify(value))}::jsonb, true)`;
229
+ byColumn[col] = `jsonb_set(${guarded}, ${jsonbPath}, ${bind(JSON.stringify(value))}::jsonb, true)`;
201
230
  };
202
231
 
203
232
  const wrapPushSlice = (col, segments, spec) => {
204
233
  const jsonbPath = `'{${segments.join(',')}}'`;
205
- const textPath = `'{${segments.join(',')}}'`;
206
234
  const prev = seed(col);
207
- // Read existing array (COALESCE to empty), concat new values,
208
- // slice to last keepLast via jsonb_array_elements + offset.
209
- // Simpler: build as jsonb with jsonb_path_query_array.
210
- const existingArr = `COALESCE((${prev} #> ${jsonbPath}), '[]'::jsonb)`;
211
- const newArr = `${existingArr} || ${bind(JSON.stringify(spec.values))}::jsonb`;
212
- // Keep last N: jsonb_array has no native slice, so use
213
- // jsonb_agg over a windowed subquery. We construct via
214
- // array slicing on the combined array.
215
- const len = `jsonb_array_length(${newArr})`;
216
- const startIdx = `GREATEST(0, ${len} - ${bind(spec.keepLast)})`;
235
+ const guarded = ensureParents(prev, segments);
236
+ // Construct the sliced array in a CTE to evaluate `${newArr}`
237
+ // exactly ONCE (vs. the inline form that Postgres would still
238
+ // execute correctly but expand three times). Order is
239
+ // explicitly preserved by `jsonb_agg(... ORDER BY idx)`;
240
+ // without the ORDER BY, aggregate order is implementation-
241
+ // defined even with WITH ORDINALITY.
217
242
  const sliced = `(
218
- SELECT COALESCE(jsonb_agg(elem), '[]'::jsonb)
219
- FROM jsonb_array_elements(${newArr}) WITH ORDINALITY AS t(elem, idx)
220
- WHERE idx > ${startIdx}
243
+ WITH combined AS (
244
+ SELECT COALESCE((${guarded} #> ${jsonbPath}), '[]'::jsonb) || ${bind(JSON.stringify(spec.values))}::jsonb AS arr
245
+ )
246
+ SELECT COALESCE(jsonb_agg(elem ORDER BY idx), '[]'::jsonb)
247
+ FROM combined,
248
+ jsonb_array_elements((SELECT arr FROM combined)) WITH ORDINALITY AS t(elem, idx)
249
+ WHERE idx > GREATEST(0, jsonb_array_length((SELECT arr FROM combined)) - ${bind(spec.keepLast)})
221
250
  )`;
222
- byColumn[col] = `jsonb_set(${prev}, ${jsonbPath}, ${sliced}, true)`;
251
+ byColumn[col] = `jsonb_set(${guarded}, ${jsonbPath}, ${sliced}, true)`;
223
252
  };
224
253
 
225
254
  for (const [path, delta] of Object.entries(ops.increment)) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@friggframework/core",
3
3
  "prettier": "@friggframework/prettier-config",
4
- "version": "2.0.0--canary.580.c0487bc.0",
4
+ "version": "2.0.0--canary.580.235db2b.0",
5
5
  "dependencies": {
6
6
  "@aws-sdk/client-apigatewaymanagementapi": "^3.588.0",
7
7
  "@aws-sdk/client-kms": "^3.588.0",
@@ -38,9 +38,9 @@
38
38
  }
39
39
  },
40
40
  "devDependencies": {
41
- "@friggframework/eslint-config": "2.0.0--canary.580.c0487bc.0",
42
- "@friggframework/prettier-config": "2.0.0--canary.580.c0487bc.0",
43
- "@friggframework/test": "2.0.0--canary.580.c0487bc.0",
41
+ "@friggframework/eslint-config": "2.0.0--canary.580.235db2b.0",
42
+ "@friggframework/prettier-config": "2.0.0--canary.580.235db2b.0",
43
+ "@friggframework/test": "2.0.0--canary.580.235db2b.0",
44
44
  "@prisma/client": "^6.17.0",
45
45
  "@types/lodash": "4.17.15",
46
46
  "@typescript-eslint/eslint-plugin": "^8.0.0",
@@ -80,5 +80,5 @@
80
80
  "publishConfig": {
81
81
  "access": "public"
82
82
  },
83
- "gitHead": "c0487bcbed4095fd7cd8c750e07ee4c8eab26a1a"
83
+ "gitHead": "235db2bdc62c375401507fe546c3b77d8cbc5bc7"
84
84
  }