@objectstack/rest 4.1.1 → 5.0.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.cjs CHANGED
@@ -250,6 +250,18 @@ var RouteGroupBuilder = class {
250
250
  // src/rest-server.ts
251
251
  var logError = (...args) => globalThis.console?.error(...args);
252
252
  function mapDataError(error, object) {
253
+ if (error?.code === "CONCURRENT_UPDATE" || error?.name === "ConcurrentUpdateError") {
254
+ return {
255
+ status: 409,
256
+ body: {
257
+ error: error?.message ?? "Record was modified by another user",
258
+ code: "CONCURRENT_UPDATE",
259
+ ...error?.currentVersion ? { currentVersion: error.currentVersion } : {},
260
+ ...error?.currentRecord ? { currentRecord: error.currentRecord } : {},
261
+ ...object ? { object } : {}
262
+ }
263
+ };
264
+ }
253
265
  if (error?.code === "VALIDATION_FAILED" || error?.name === "ValidationError") {
254
266
  return {
255
267
  status: 400,
@@ -1141,11 +1153,17 @@ var RestServer = class {
1141
1153
  }
1142
1154
  const body = req.body ?? {};
1143
1155
  const item = body && typeof body === "object" && "metadata" in body ? body.metadata : body && typeof body === "object" && "item" in body ? body.item : body;
1156
+ const ifMatchHeader = req.headers?.["if-match"] ?? req.headers?.["If-Match"];
1157
+ const parentVersion = typeof ifMatchHeader === "string" ? ifMatchHeader.replace(/^"|"$/g, "") : void 0;
1158
+ const actorHeader = req.headers?.["x-actor"] ?? req.headers?.["X-Actor"] ?? req.user?.id ?? req.userId;
1159
+ const actor = typeof actorHeader === "string" ? actorHeader : void 0;
1144
1160
  const result = await p.saveMetaItem({
1145
1161
  type: req.params.type,
1146
1162
  name: req.params.name,
1147
1163
  item,
1148
- ...projectId ? { projectId } : {}
1164
+ ...projectId ? { projectId } : {},
1165
+ ...parentVersion !== void 0 ? { parentVersion } : {},
1166
+ ...actor ? { actor } : {}
1149
1167
  });
1150
1168
  res.json(result);
1151
1169
  } catch (error) {
@@ -1171,10 +1189,16 @@ var RestServer = class {
1171
1189
  });
1172
1190
  return;
1173
1191
  }
1192
+ const ifMatchHeader = req.headers?.["if-match"] ?? req.headers?.["If-Match"];
1193
+ const parentVersion = typeof ifMatchHeader === "string" ? ifMatchHeader.replace(/^"|"$/g, "") : void 0;
1194
+ const actorHeader = req.headers?.["x-actor"] ?? req.headers?.["X-Actor"] ?? req.user?.id ?? req.userId;
1195
+ const actor = typeof actorHeader === "string" ? actorHeader : void 0;
1174
1196
  const result = await p.deleteMetaItem({
1175
1197
  type: req.params.type,
1176
1198
  name: req.params.name,
1177
- ...projectId ? { projectId } : {}
1199
+ ...projectId ? { projectId } : {},
1200
+ ...parentVersion !== void 0 ? { parentVersion } : {},
1201
+ ...actor ? { actor } : {}
1178
1202
  });
1179
1203
  res.json(result);
1180
1204
  } catch (error) {
@@ -1187,6 +1211,39 @@ var RestServer = class {
1187
1211
  tags: ["metadata"]
1188
1212
  }
1189
1213
  });
1214
+ this.routeManager.register({
1215
+ method: "GET",
1216
+ path: `${metaPath}/:type/:name/history`,
1217
+ handler: async (req, res) => {
1218
+ try {
1219
+ const projectId = isScoped ? req.params?.projectId : void 0;
1220
+ const p = await this.resolveProtocol(projectId, req);
1221
+ if (!p.historyMetaItem) {
1222
+ res.status(501).json({
1223
+ error: "History query not supported by protocol implementation"
1224
+ });
1225
+ return;
1226
+ }
1227
+ const sinceSeq = req.query?.sinceSeq !== void 0 ? Number(req.query.sinceSeq) : void 0;
1228
+ const limit = req.query?.limit !== void 0 ? Number(req.query.limit) : void 0;
1229
+ const result = await p.historyMetaItem({
1230
+ type: req.params.type,
1231
+ name: req.params.name,
1232
+ ...projectId ? { projectId } : {},
1233
+ ...sinceSeq !== void 0 && Number.isFinite(sinceSeq) ? { sinceSeq } : {},
1234
+ ...limit !== void 0 && Number.isFinite(limit) ? { limit } : {}
1235
+ });
1236
+ res.json(result);
1237
+ } catch (error) {
1238
+ logError("[REST] Unhandled error:", error);
1239
+ sendError(res, error);
1240
+ }
1241
+ },
1242
+ metadata: {
1243
+ summary: "List durable history events for a metadata item",
1244
+ tags: ["metadata"]
1245
+ }
1246
+ });
1190
1247
  if (metadata.endpoints.item !== false) {
1191
1248
  this.routeManager.register({
1192
1249
  method: "GET",
@@ -1227,11 +1284,17 @@ var RestServer = class {
1227
1284
  return;
1228
1285
  }
1229
1286
  const compoundName = `${req.params.section}/${req.params.name}`;
1287
+ const ifMatchHeader = req.headers?.["if-match"] ?? req.headers?.["If-Match"];
1288
+ const parentVersion = typeof ifMatchHeader === "string" ? ifMatchHeader.replace(/^"|"$/g, "") : void 0;
1289
+ const actorHeader = req.headers?.["x-actor"] ?? req.headers?.["X-Actor"] ?? req.user?.id ?? req.userId;
1290
+ const actor = typeof actorHeader === "string" ? actorHeader : void 0;
1230
1291
  const result = await p.saveMetaItem({
1231
1292
  type: req.params.type,
1232
1293
  name: compoundName,
1233
1294
  item: req.body,
1234
- ...projectId ? { projectId } : {}
1295
+ ...projectId ? { projectId } : {},
1296
+ ...parentVersion !== void 0 ? { parentVersion } : {},
1297
+ ...actor ? { actor } : {}
1235
1298
  });
1236
1299
  res.json(result);
1237
1300
  } catch (error) {
@@ -1420,10 +1483,19 @@ var RestServer = class {
1420
1483
  const p = await this.resolveProtocol(projectId, req);
1421
1484
  const context = await this.resolveExecCtx(projectId, req);
1422
1485
  if (this.enforceAuth(req, res, context)) return;
1486
+ const ifMatchHeader = req.headers?.["if-match"] ?? req.headers?.["If-Match"];
1487
+ const bodyVersion = req.body && typeof req.body === "object" ? req.body.expectedVersion : void 0;
1488
+ const expectedVersion = bodyVersion ?? ifMatchHeader;
1489
+ let data = req.body;
1490
+ if (data && typeof data === "object" && "expectedVersion" in data) {
1491
+ const { expectedVersion: _drop, ...rest } = data;
1492
+ data = rest;
1493
+ }
1423
1494
  const result = await p.updateData({
1424
1495
  object: req.params.object,
1425
1496
  id: req.params.id,
1426
- data: req.body,
1497
+ data,
1498
+ ...expectedVersion ? { expectedVersion: String(expectedVersion) } : {},
1427
1499
  ...projectId ? { projectId } : {},
1428
1500
  ...context ? { context } : {}
1429
1501
  });
@@ -1450,9 +1522,13 @@ var RestServer = class {
1450
1522
  const p = await this.resolveProtocol(projectId, req);
1451
1523
  const context = await this.resolveExecCtx(projectId, req);
1452
1524
  if (this.enforceAuth(req, res, context)) return;
1525
+ const ifMatchHeader = req.headers?.["if-match"] ?? req.headers?.["If-Match"];
1526
+ const queryVersion = req.query && typeof req.query === "object" ? req.query.expectedVersion : void 0;
1527
+ const expectedVersion = queryVersion ?? ifMatchHeader;
1453
1528
  const result = await p.deleteData({
1454
1529
  object: req.params.object,
1455
1530
  id: req.params.id,
1531
+ ...expectedVersion ? { expectedVersion: String(expectedVersion) } : {},
1456
1532
  ...projectId ? { projectId } : {},
1457
1533
  ...context ? { context } : {}
1458
1534
  });