thatcher 1.0.35 → 1.0.36
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/package.json +1 -1
- package/src/lib/busybase-store.js +30 -3
package/package.json
CHANGED
|
@@ -254,15 +254,42 @@ export async function create(entity, data, user) {
|
|
|
254
254
|
return record;
|
|
255
255
|
}
|
|
256
256
|
|
|
257
|
-
|
|
257
|
+
// opts.expectedVersion: optional optimistic-concurrency guard. When supplied,
|
|
258
|
+
// the write is conditioned on the row's internal _version counter still
|
|
259
|
+
// matching the value the caller last read (a plain read-then-write otherwise
|
|
260
|
+
// has no way to detect a concurrent writer landing in between -- whichever
|
|
261
|
+
// caller writes second wins outright, silently discarding the first caller's
|
|
262
|
+
// change). A stale expectedVersion throws a distinguishable 'conflict' error
|
|
263
|
+
// instead of clobbering; the caller re-reads and retries or surfaces the
|
|
264
|
+
// conflict. _version increments on every write regardless of whether the
|
|
265
|
+
// guard is used, so a caller can always read the CURRENT _version to guard
|
|
266
|
+
// its NEXT write. A dedicated counter, not updated_at, because updated_at
|
|
267
|
+
// (now(), second-precision epoch) can collide within the same second under a
|
|
268
|
+
// genuine fast race, silently defeating the guard exactly when it matters
|
|
269
|
+
// most; _version is a plain integer increment with no precision ceiling.
|
|
270
|
+
// Backward compatible: omitting opts.expectedVersion is the exact prior
|
|
271
|
+
// unconditional-write behaviour, unchanged for every existing caller; _version
|
|
272
|
+
// is added as a new column, ignored by every reader that doesn't ask for it.
|
|
273
|
+
export async function update(entity, id, data, opts = {}) {
|
|
258
274
|
const spec = specOf(entity);
|
|
259
275
|
const tbl = tableName(entity);
|
|
260
276
|
|
|
261
277
|
const existing = await get(entity, id);
|
|
262
278
|
if (!existing) throw new Error(`${entity} with id ${id} not found`);
|
|
263
279
|
|
|
264
|
-
const
|
|
265
|
-
|
|
280
|
+
const currentVersion = Number(existing._version) || 0;
|
|
281
|
+
const patch = { ...data, updated_at: now(), _version: currentVersion + 1 };
|
|
282
|
+
let builder = client().from(tbl).update(patch).eq('id', id);
|
|
283
|
+
if (opts.expectedVersion != null) {
|
|
284
|
+
builder = builder.eq('_version', opts.expectedVersion);
|
|
285
|
+
}
|
|
286
|
+
const { data: rows, error } = await builder;
|
|
287
|
+
if (error) throw new Error(`BusyBase update failed: ${error.message || error}`);
|
|
288
|
+
if (opts.expectedVersion != null && Array.isArray(rows) && rows.length === 0) {
|
|
289
|
+
const conflictErr = new Error(`${entity} ${id} was modified by another writer since it was last read`);
|
|
290
|
+
conflictErr.code = 'conflict';
|
|
291
|
+
throw conflictErr;
|
|
292
|
+
}
|
|
266
293
|
return get(entity, id);
|
|
267
294
|
}
|
|
268
295
|
|