@objectstack/objectql 4.1.0 → 4.2.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/dist/index.d.mts +22 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +57 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +57 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
package/dist/index.mjs
CHANGED
|
@@ -619,6 +619,25 @@ function simpleHash(str) {
|
|
|
619
619
|
}
|
|
620
620
|
return Math.abs(hash).toString(16);
|
|
621
621
|
}
|
|
622
|
+
var ConcurrentUpdateError = class extends Error {
|
|
623
|
+
constructor(opts) {
|
|
624
|
+
super(opts.message ?? "Record was modified by another user");
|
|
625
|
+
this.code = "CONCURRENT_UPDATE";
|
|
626
|
+
this.status = 409;
|
|
627
|
+
this.name = "ConcurrentUpdateError";
|
|
628
|
+
this.currentVersion = opts.currentVersion;
|
|
629
|
+
this.currentRecord = opts.currentRecord;
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
function normaliseVersionToken(v) {
|
|
633
|
+
if (v === null || v === void 0) return null;
|
|
634
|
+
const s = String(v).trim();
|
|
635
|
+
if (!s) return null;
|
|
636
|
+
if (s.length >= 2 && s.startsWith('"') && s.endsWith('"')) {
|
|
637
|
+
return s.slice(1, -1);
|
|
638
|
+
}
|
|
639
|
+
return s;
|
|
640
|
+
}
|
|
622
641
|
var SERVICE_CONFIG = {
|
|
623
642
|
auth: { route: "/api/v1/auth", plugin: "plugin-auth" },
|
|
624
643
|
automation: { route: "/api/v1/automation", plugin: "plugin-automation" },
|
|
@@ -1212,6 +1231,7 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
|
|
|
1212
1231
|
};
|
|
1213
1232
|
}
|
|
1214
1233
|
async updateData(request) {
|
|
1234
|
+
await this.assertVersionMatch(request.object, request.id, request.expectedVersion, request.context);
|
|
1215
1235
|
const opts = { where: { id: request.id } };
|
|
1216
1236
|
if (request.context !== void 0) opts.context = request.context;
|
|
1217
1237
|
const result = await this.engine.update(request.object, request.data, opts);
|
|
@@ -1222,6 +1242,7 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
|
|
|
1222
1242
|
};
|
|
1223
1243
|
}
|
|
1224
1244
|
async deleteData(request) {
|
|
1245
|
+
await this.assertVersionMatch(request.object, request.id, request.expectedVersion, request.context);
|
|
1225
1246
|
const opts = { where: { id: request.id } };
|
|
1226
1247
|
if (request.context !== void 0) opts.context = request.context;
|
|
1227
1248
|
await this.engine.delete(request.object, opts);
|
|
@@ -1231,6 +1252,42 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
|
|
|
1231
1252
|
success: true
|
|
1232
1253
|
};
|
|
1233
1254
|
}
|
|
1255
|
+
/**
|
|
1256
|
+
* Optimistic Concurrency Control gate shared by updateData/deleteData.
|
|
1257
|
+
*
|
|
1258
|
+
* When the caller passes a non-empty `expectedVersion` token (typically
|
|
1259
|
+
* the `updated_at` value they read), this fetches the current record
|
|
1260
|
+
* and compares its `updated_at` against the token. Mismatch → throw
|
|
1261
|
+
* `ConcurrentUpdateError` which the REST layer maps to 409.
|
|
1262
|
+
*
|
|
1263
|
+
* Behaviour:
|
|
1264
|
+
* - Empty/missing token → no check (opt-in semantics; existing callers
|
|
1265
|
+
* that haven't yet adopted OCC are unaffected).
|
|
1266
|
+
* - Record not found → no check; downstream `engine.update` will
|
|
1267
|
+
* surface the usual `RECORD_NOT_FOUND` 404. We intentionally do not
|
|
1268
|
+
* treat "missing record" as a concurrency conflict.
|
|
1269
|
+
* - Record has no `updated_at` field (timestamps disabled) → no check.
|
|
1270
|
+
* Logging would be noisy here; OCC is opt-in and the absence of a
|
|
1271
|
+
* version column is an explicit "this object doesn't support OCC"
|
|
1272
|
+
* signal.
|
|
1273
|
+
*/
|
|
1274
|
+
async assertVersionMatch(object, id, expectedVersion, context) {
|
|
1275
|
+
const expected = normaliseVersionToken(expectedVersion);
|
|
1276
|
+
if (!expected) return;
|
|
1277
|
+
const findOpts = { where: { id } };
|
|
1278
|
+
if (context !== void 0) findOpts.context = context;
|
|
1279
|
+
const current = await this.engine.findOne(object, findOpts);
|
|
1280
|
+
if (!current) return;
|
|
1281
|
+
const currentVersion = normaliseVersionToken(current.updated_at);
|
|
1282
|
+
if (!currentVersion) return;
|
|
1283
|
+
if (currentVersion !== expected) {
|
|
1284
|
+
throw new ConcurrentUpdateError({
|
|
1285
|
+
currentVersion,
|
|
1286
|
+
currentRecord: current,
|
|
1287
|
+
message: `Record ${object}/${id} was modified by another user (current version ${currentVersion}, expected ${expected})`
|
|
1288
|
+
});
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1234
1291
|
// ==========================================
|
|
1235
1292
|
// Global Search (M10.5)
|
|
1236
1293
|
// ==========================================
|