@objectstack/rest 14.3.0 → 14.6.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 +75 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +26 -1
- package/dist/index.d.ts +26 -1
- package/dist/index.js +75 -3
- package/dist/index.js.map +1 -1
- package/package.json +9 -9
package/dist/index.cjs
CHANGED
|
@@ -296,7 +296,14 @@ function buildFieldMetaMap(schema) {
|
|
|
296
296
|
label: typeof f.label === "string" ? f.label : void 0,
|
|
297
297
|
options: Array.isArray(f.options) ? f.options : void 0,
|
|
298
298
|
reference: typeof f.reference === "string" ? f.reference : void 0,
|
|
299
|
-
displayField: typeof f.displayField === "string" ? f.displayField : void 0
|
|
299
|
+
displayField: typeof f.displayField === "string" ? f.displayField : void 0,
|
|
300
|
+
required: f.required === true,
|
|
301
|
+
system: f.system === true,
|
|
302
|
+
readonly: f.readonly === true,
|
|
303
|
+
// Mirror the engine's `applyFieldDefaults` gate (`f.defaultValue == null`
|
|
304
|
+
// ⇒ no default): any non-null default — literal, expression object, or the
|
|
305
|
+
// `current_user` token — counts as satisfying a required field.
|
|
306
|
+
hasDefault: f.defaultValue != null
|
|
300
307
|
});
|
|
301
308
|
}
|
|
302
309
|
return map;
|
|
@@ -566,6 +573,27 @@ async function coerceFieldValue(raw, meta, ctx) {
|
|
|
566
573
|
}
|
|
567
574
|
return { value: trim && typeof raw === "string" ? raw.trim() : raw };
|
|
568
575
|
}
|
|
576
|
+
var REQUIRED_CHECK_SKIP = /* @__PURE__ */ new Set([
|
|
577
|
+
"id",
|
|
578
|
+
"created_at",
|
|
579
|
+
"created_by",
|
|
580
|
+
"updated_at",
|
|
581
|
+
"updated_by"
|
|
582
|
+
]);
|
|
583
|
+
function isBlankValue(v) {
|
|
584
|
+
return v === void 0 || v === null || typeof v === "string" && v.trim() === "";
|
|
585
|
+
}
|
|
586
|
+
function firstMissingRequiredField(data, metaMap) {
|
|
587
|
+
for (const meta of metaMap.values()) {
|
|
588
|
+
if (!meta.required) continue;
|
|
589
|
+
if (meta.system || meta.readonly) continue;
|
|
590
|
+
if (meta.hasDefault) continue;
|
|
591
|
+
if (meta.type === "autonumber") continue;
|
|
592
|
+
if (REQUIRED_CHECK_SKIP.has(meta.name)) continue;
|
|
593
|
+
if (isBlankValue(data[meta.name])) return meta.name;
|
|
594
|
+
}
|
|
595
|
+
return null;
|
|
596
|
+
}
|
|
569
597
|
async function coerceRow(rawRow, metaMap, ctx) {
|
|
570
598
|
const data = {};
|
|
571
599
|
const errors = [];
|
|
@@ -764,7 +792,11 @@ function runImport(opts) {
|
|
|
764
792
|
if (!handled) {
|
|
765
793
|
const willUpdate = existing && typeof existing === "object";
|
|
766
794
|
const willCreate = !willUpdate && (writeMode === "insert" || writeMode === "upsert");
|
|
767
|
-
|
|
795
|
+
const requiredMiss = willCreate && !runAutomations ? firstMissingRequiredField(data, metaMap) : null;
|
|
796
|
+
if (requiredMiss) {
|
|
797
|
+
errCount++;
|
|
798
|
+
results[i] = { row: rowNo, ok: false, action: "failed", field: requiredMiss, code: "required", error: `${requiredMiss} is required` };
|
|
799
|
+
} else if (!willUpdate && !willCreate) {
|
|
768
800
|
skipped++;
|
|
769
801
|
results[i] = { row: rowNo, ok: true, action: "skipped", code: "NO_MATCH" };
|
|
770
802
|
} else if (dryRun) {
|
|
@@ -2510,9 +2542,49 @@ var RestServer = class _RestServer {
|
|
|
2510
2542
|
}
|
|
2511
2543
|
}
|
|
2512
2544
|
/**
|
|
2513
|
-
* Register metadata
|
|
2545
|
+
* Register the metadata routes behind the SAME `requireAuth` gate the
|
|
2546
|
+
* `/data` routes use.
|
|
2547
|
+
*
|
|
2548
|
+
* `registerMetadataEndpoints` builds ~17 `/meta/*` routes but — unlike the
|
|
2549
|
+
* `/data` handlers — never calls {@link enforceAuth}: its handlers assumed
|
|
2550
|
+
* the `requireAuth` gate rejected anonymous callers "upstream", yet nothing
|
|
2551
|
+
* upstream covers `/meta`, so on a `requireAuth` deployment an anonymous
|
|
2552
|
+
* caller could read object / field schemas. On a tenant-less runtime host
|
|
2553
|
+
* those are SYSTEM-object schemas and the host is publicly reachable — a
|
|
2554
|
+
* real leak.
|
|
2555
|
+
*
|
|
2556
|
+
* Rather than add the gate to every handler (and have the next new route
|
|
2557
|
+
* forget it — the exact failure mode that caused this), wrap the route
|
|
2558
|
+
* registrar for the duration of registration so every meta route, present
|
|
2559
|
+
* and future, inherits it. The check is a no-op when `requireAuth` is off
|
|
2560
|
+
* (demo / single-tenant), so the previously-public metadata surface there
|
|
2561
|
+
* is unchanged; an authenticated user passes exactly as on `/data`.
|
|
2514
2562
|
*/
|
|
2515
2563
|
registerMetadataEndpoints(basePath) {
|
|
2564
|
+
const realRouteManager = this.routeManager;
|
|
2565
|
+
const guardedRouteManager = {
|
|
2566
|
+
register: (entry) => {
|
|
2567
|
+
const inner = entry.handler;
|
|
2568
|
+
if (typeof inner !== "function") return realRouteManager.register(entry);
|
|
2569
|
+
return realRouteManager.register({
|
|
2570
|
+
...entry,
|
|
2571
|
+
handler: async (req, res) => {
|
|
2572
|
+
const environmentId = req?.params?.environmentId;
|
|
2573
|
+
const context = await this.resolveExecCtx(environmentId, req).catch(() => void 0);
|
|
2574
|
+
if (this.enforceAuth(req, res, context)) return;
|
|
2575
|
+
return inner(req, res);
|
|
2576
|
+
}
|
|
2577
|
+
});
|
|
2578
|
+
}
|
|
2579
|
+
};
|
|
2580
|
+
this.routeManager = guardedRouteManager;
|
|
2581
|
+
try {
|
|
2582
|
+
this.registerMetadataEndpointsInner(basePath);
|
|
2583
|
+
} finally {
|
|
2584
|
+
this.routeManager = realRouteManager;
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
registerMetadataEndpointsInner(basePath) {
|
|
2516
2588
|
const { metadata } = this.config;
|
|
2517
2589
|
const metaPath = `${basePath}${metadata.prefix}`;
|
|
2518
2590
|
const isScoped = basePath.includes("/environments/:environmentId");
|