@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.js CHANGED
@@ -211,6 +211,18 @@ var RouteGroupBuilder = class {
211
211
  // src/rest-server.ts
212
212
  var logError = (...args) => globalThis.console?.error(...args);
213
213
  function mapDataError(error, object) {
214
+ if (error?.code === "CONCURRENT_UPDATE" || error?.name === "ConcurrentUpdateError") {
215
+ return {
216
+ status: 409,
217
+ body: {
218
+ error: error?.message ?? "Record was modified by another user",
219
+ code: "CONCURRENT_UPDATE",
220
+ ...error?.currentVersion ? { currentVersion: error.currentVersion } : {},
221
+ ...error?.currentRecord ? { currentRecord: error.currentRecord } : {},
222
+ ...object ? { object } : {}
223
+ }
224
+ };
225
+ }
214
226
  if (error?.code === "VALIDATION_FAILED" || error?.name === "ValidationError") {
215
227
  return {
216
228
  status: 400,
@@ -1102,11 +1114,17 @@ var RestServer = class {
1102
1114
  }
1103
1115
  const body = req.body ?? {};
1104
1116
  const item = body && typeof body === "object" && "metadata" in body ? body.metadata : body && typeof body === "object" && "item" in body ? body.item : body;
1117
+ const ifMatchHeader = req.headers?.["if-match"] ?? req.headers?.["If-Match"];
1118
+ const parentVersion = typeof ifMatchHeader === "string" ? ifMatchHeader.replace(/^"|"$/g, "") : void 0;
1119
+ const actorHeader = req.headers?.["x-actor"] ?? req.headers?.["X-Actor"] ?? req.user?.id ?? req.userId;
1120
+ const actor = typeof actorHeader === "string" ? actorHeader : void 0;
1105
1121
  const result = await p.saveMetaItem({
1106
1122
  type: req.params.type,
1107
1123
  name: req.params.name,
1108
1124
  item,
1109
- ...projectId ? { projectId } : {}
1125
+ ...projectId ? { projectId } : {},
1126
+ ...parentVersion !== void 0 ? { parentVersion } : {},
1127
+ ...actor ? { actor } : {}
1110
1128
  });
1111
1129
  res.json(result);
1112
1130
  } catch (error) {
@@ -1132,10 +1150,16 @@ var RestServer = class {
1132
1150
  });
1133
1151
  return;
1134
1152
  }
1153
+ const ifMatchHeader = req.headers?.["if-match"] ?? req.headers?.["If-Match"];
1154
+ const parentVersion = typeof ifMatchHeader === "string" ? ifMatchHeader.replace(/^"|"$/g, "") : void 0;
1155
+ const actorHeader = req.headers?.["x-actor"] ?? req.headers?.["X-Actor"] ?? req.user?.id ?? req.userId;
1156
+ const actor = typeof actorHeader === "string" ? actorHeader : void 0;
1135
1157
  const result = await p.deleteMetaItem({
1136
1158
  type: req.params.type,
1137
1159
  name: req.params.name,
1138
- ...projectId ? { projectId } : {}
1160
+ ...projectId ? { projectId } : {},
1161
+ ...parentVersion !== void 0 ? { parentVersion } : {},
1162
+ ...actor ? { actor } : {}
1139
1163
  });
1140
1164
  res.json(result);
1141
1165
  } catch (error) {
@@ -1148,6 +1172,39 @@ var RestServer = class {
1148
1172
  tags: ["metadata"]
1149
1173
  }
1150
1174
  });
1175
+ this.routeManager.register({
1176
+ method: "GET",
1177
+ path: `${metaPath}/:type/:name/history`,
1178
+ handler: async (req, res) => {
1179
+ try {
1180
+ const projectId = isScoped ? req.params?.projectId : void 0;
1181
+ const p = await this.resolveProtocol(projectId, req);
1182
+ if (!p.historyMetaItem) {
1183
+ res.status(501).json({
1184
+ error: "History query not supported by protocol implementation"
1185
+ });
1186
+ return;
1187
+ }
1188
+ const sinceSeq = req.query?.sinceSeq !== void 0 ? Number(req.query.sinceSeq) : void 0;
1189
+ const limit = req.query?.limit !== void 0 ? Number(req.query.limit) : void 0;
1190
+ const result = await p.historyMetaItem({
1191
+ type: req.params.type,
1192
+ name: req.params.name,
1193
+ ...projectId ? { projectId } : {},
1194
+ ...sinceSeq !== void 0 && Number.isFinite(sinceSeq) ? { sinceSeq } : {},
1195
+ ...limit !== void 0 && Number.isFinite(limit) ? { limit } : {}
1196
+ });
1197
+ res.json(result);
1198
+ } catch (error) {
1199
+ logError("[REST] Unhandled error:", error);
1200
+ sendError(res, error);
1201
+ }
1202
+ },
1203
+ metadata: {
1204
+ summary: "List durable history events for a metadata item",
1205
+ tags: ["metadata"]
1206
+ }
1207
+ });
1151
1208
  if (metadata.endpoints.item !== false) {
1152
1209
  this.routeManager.register({
1153
1210
  method: "GET",
@@ -1188,11 +1245,17 @@ var RestServer = class {
1188
1245
  return;
1189
1246
  }
1190
1247
  const compoundName = `${req.params.section}/${req.params.name}`;
1248
+ const ifMatchHeader = req.headers?.["if-match"] ?? req.headers?.["If-Match"];
1249
+ const parentVersion = typeof ifMatchHeader === "string" ? ifMatchHeader.replace(/^"|"$/g, "") : void 0;
1250
+ const actorHeader = req.headers?.["x-actor"] ?? req.headers?.["X-Actor"] ?? req.user?.id ?? req.userId;
1251
+ const actor = typeof actorHeader === "string" ? actorHeader : void 0;
1191
1252
  const result = await p.saveMetaItem({
1192
1253
  type: req.params.type,
1193
1254
  name: compoundName,
1194
1255
  item: req.body,
1195
- ...projectId ? { projectId } : {}
1256
+ ...projectId ? { projectId } : {},
1257
+ ...parentVersion !== void 0 ? { parentVersion } : {},
1258
+ ...actor ? { actor } : {}
1196
1259
  });
1197
1260
  res.json(result);
1198
1261
  } catch (error) {
@@ -1381,10 +1444,19 @@ var RestServer = class {
1381
1444
  const p = await this.resolveProtocol(projectId, req);
1382
1445
  const context = await this.resolveExecCtx(projectId, req);
1383
1446
  if (this.enforceAuth(req, res, context)) return;
1447
+ const ifMatchHeader = req.headers?.["if-match"] ?? req.headers?.["If-Match"];
1448
+ const bodyVersion = req.body && typeof req.body === "object" ? req.body.expectedVersion : void 0;
1449
+ const expectedVersion = bodyVersion ?? ifMatchHeader;
1450
+ let data = req.body;
1451
+ if (data && typeof data === "object" && "expectedVersion" in data) {
1452
+ const { expectedVersion: _drop, ...rest } = data;
1453
+ data = rest;
1454
+ }
1384
1455
  const result = await p.updateData({
1385
1456
  object: req.params.object,
1386
1457
  id: req.params.id,
1387
- data: req.body,
1458
+ data,
1459
+ ...expectedVersion ? { expectedVersion: String(expectedVersion) } : {},
1388
1460
  ...projectId ? { projectId } : {},
1389
1461
  ...context ? { context } : {}
1390
1462
  });
@@ -1411,9 +1483,13 @@ var RestServer = class {
1411
1483
  const p = await this.resolveProtocol(projectId, req);
1412
1484
  const context = await this.resolveExecCtx(projectId, req);
1413
1485
  if (this.enforceAuth(req, res, context)) return;
1486
+ const ifMatchHeader = req.headers?.["if-match"] ?? req.headers?.["If-Match"];
1487
+ const queryVersion = req.query && typeof req.query === "object" ? req.query.expectedVersion : void 0;
1488
+ const expectedVersion = queryVersion ?? ifMatchHeader;
1414
1489
  const result = await p.deleteData({
1415
1490
  object: req.params.object,
1416
1491
  id: req.params.id,
1492
+ ...expectedVersion ? { expectedVersion: String(expectedVersion) } : {},
1417
1493
  ...projectId ? { projectId } : {},
1418
1494
  ...context ? { context } : {}
1419
1495
  });