@objectstack/rest 5.1.0 → 5.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.cjs +169 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +168 -1
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.d.cts
CHANGED
|
@@ -343,6 +343,31 @@ declare class RestServer {
|
|
|
343
343
|
* Register discovery endpoints
|
|
344
344
|
*/
|
|
345
345
|
private registerDiscoveryEndpoints;
|
|
346
|
+
/**
|
|
347
|
+
* Register OpenAPI 3.1 spec + interactive docs viewer.
|
|
348
|
+
*
|
|
349
|
+
* GET <basePath>/openapi.json → enriched OpenAPI document
|
|
350
|
+
* GET <basePath>/docs → Scalar-rendered HTML (CDN, no dep)
|
|
351
|
+
*
|
|
352
|
+
* Enrichment at request time:
|
|
353
|
+
* - servers[0].url — derived from the request's Host header
|
|
354
|
+
* - paths — `{object}` placeholders expanded into
|
|
355
|
+
* one concrete path per registered object
|
|
356
|
+
* from the protocol's discovery metadata
|
|
357
|
+
*
|
|
358
|
+
* The base spec is loaded lazily from @objectstack/spec/openapi.json
|
|
359
|
+
* (shipped pre-generated by spec's build pipeline) so we don't pay
|
|
360
|
+
* the cost of regenerating on every request, and a missing or
|
|
361
|
+
* malformed file degrades to a stub instead of crashing.
|
|
362
|
+
*/
|
|
363
|
+
private registerOpenApiEndpoints;
|
|
364
|
+
/**
|
|
365
|
+
* Lazily load the OpenAPI spec JSON shipped by @objectstack/spec.
|
|
366
|
+
* Cached after first read. Resilient to missing files / parse errors
|
|
367
|
+
* so a degraded environment still boots.
|
|
368
|
+
*/
|
|
369
|
+
private _openApiSpecCache;
|
|
370
|
+
private loadOpenApiSpec;
|
|
346
371
|
/**
|
|
347
372
|
* Register metadata endpoints
|
|
348
373
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -343,6 +343,31 @@ declare class RestServer {
|
|
|
343
343
|
* Register discovery endpoints
|
|
344
344
|
*/
|
|
345
345
|
private registerDiscoveryEndpoints;
|
|
346
|
+
/**
|
|
347
|
+
* Register OpenAPI 3.1 spec + interactive docs viewer.
|
|
348
|
+
*
|
|
349
|
+
* GET <basePath>/openapi.json → enriched OpenAPI document
|
|
350
|
+
* GET <basePath>/docs → Scalar-rendered HTML (CDN, no dep)
|
|
351
|
+
*
|
|
352
|
+
* Enrichment at request time:
|
|
353
|
+
* - servers[0].url — derived from the request's Host header
|
|
354
|
+
* - paths — `{object}` placeholders expanded into
|
|
355
|
+
* one concrete path per registered object
|
|
356
|
+
* from the protocol's discovery metadata
|
|
357
|
+
*
|
|
358
|
+
* The base spec is loaded lazily from @objectstack/spec/openapi.json
|
|
359
|
+
* (shipped pre-generated by spec's build pipeline) so we don't pay
|
|
360
|
+
* the cost of regenerating on every request, and a missing or
|
|
361
|
+
* malformed file degrades to a stub instead of crashing.
|
|
362
|
+
*/
|
|
363
|
+
private registerOpenApiEndpoints;
|
|
364
|
+
/**
|
|
365
|
+
* Lazily load the OpenAPI spec JSON shipped by @objectstack/spec.
|
|
366
|
+
* Cached after first read. Resilient to missing files / parse errors
|
|
367
|
+
* so a degraded environment still boots.
|
|
368
|
+
*/
|
|
369
|
+
private _openApiSpecCache;
|
|
370
|
+
private loadOpenApiSpec;
|
|
346
371
|
/**
|
|
347
372
|
* Register metadata endpoints
|
|
348
373
|
*/
|
package/dist/index.js
CHANGED
|
@@ -404,6 +404,12 @@ function rowsToCsv(fields, rows, includeHeader) {
|
|
|
404
404
|
}
|
|
405
405
|
var RestServer = class {
|
|
406
406
|
constructor(server, protocol, config = {}, kernelManager, envRegistry, defaultProjectIdProvider, authServiceProvider, objectQLProvider, emailServiceProvider, sharingServiceProvider, reportsServiceProvider, approvalsServiceProvider, sharingRulesServiceProvider, i18nServiceProvider) {
|
|
407
|
+
/**
|
|
408
|
+
* Lazily load the OpenAPI spec JSON shipped by @objectstack/spec.
|
|
409
|
+
* Cached after first read. Resilient to missing files / parse errors
|
|
410
|
+
* so a degraded environment still boots.
|
|
411
|
+
*/
|
|
412
|
+
this._openApiSpecCache = void 0;
|
|
407
413
|
this.protocol = protocol;
|
|
408
414
|
this.config = this.normalizeConfig(config);
|
|
409
415
|
this.routeManager = new RouteManager(server);
|
|
@@ -670,12 +676,40 @@ var RestServer = class {
|
|
|
670
676
|
}
|
|
671
677
|
} catch {
|
|
672
678
|
}
|
|
679
|
+
let org_user_ids = [userId];
|
|
680
|
+
if (tenantId) {
|
|
681
|
+
try {
|
|
682
|
+
let ql;
|
|
683
|
+
if (kernel) {
|
|
684
|
+
ql = await kernel.getServiceAsync("objectql").catch(() => void 0);
|
|
685
|
+
}
|
|
686
|
+
if (!ql && this.objectQLProvider) {
|
|
687
|
+
ql = await this.objectQLProvider(projectId).catch(() => void 0);
|
|
688
|
+
}
|
|
689
|
+
if (ql && typeof ql.find === "function") {
|
|
690
|
+
const sysOpts = { context: { isSystem: true } };
|
|
691
|
+
const memberRows = await ql.find("sys_member", {
|
|
692
|
+
where: { organization_id: tenantId },
|
|
693
|
+
limit: 1e3,
|
|
694
|
+
...sysOpts
|
|
695
|
+
}).catch(() => []);
|
|
696
|
+
const ids = /* @__PURE__ */ new Set([userId]);
|
|
697
|
+
for (const m of memberRows ?? []) {
|
|
698
|
+
const uid = m.user_id ?? m.userId;
|
|
699
|
+
if (typeof uid === "string" && uid.length > 0) ids.add(uid);
|
|
700
|
+
}
|
|
701
|
+
org_user_ids = Array.from(ids);
|
|
702
|
+
}
|
|
703
|
+
} catch {
|
|
704
|
+
}
|
|
705
|
+
}
|
|
673
706
|
return {
|
|
674
707
|
userId,
|
|
675
708
|
tenantId,
|
|
676
709
|
roles,
|
|
677
710
|
permissions,
|
|
678
|
-
isSystem: false
|
|
711
|
+
isSystem: false,
|
|
712
|
+
org_user_ids
|
|
679
713
|
};
|
|
680
714
|
} catch {
|
|
681
715
|
return void 0;
|
|
@@ -817,6 +851,7 @@ var RestServer = class {
|
|
|
817
851
|
enableUi: api.enableUi ?? true,
|
|
818
852
|
enableBatch: api.enableBatch ?? true,
|
|
819
853
|
enableDiscovery: api.enableDiscovery ?? true,
|
|
854
|
+
enableOpenApi: api.enableOpenApi ?? true,
|
|
820
855
|
enableSearch: api.enableSearch ?? true,
|
|
821
856
|
enableProjectScoping: api.enableProjectScoping ?? false,
|
|
822
857
|
projectResolution: api.projectResolution ?? "auto",
|
|
@@ -896,6 +931,9 @@ var RestServer = class {
|
|
|
896
931
|
if (this.config.api.enableDiscovery) {
|
|
897
932
|
this.registerDiscoveryEndpoints(bp);
|
|
898
933
|
}
|
|
934
|
+
if (this.config.api.enableOpenApi ?? true) {
|
|
935
|
+
this.registerOpenApiEndpoints(bp);
|
|
936
|
+
}
|
|
899
937
|
if (this.config.api.enableMetadata) {
|
|
900
938
|
this.registerMetadataEndpoints(bp);
|
|
901
939
|
}
|
|
@@ -987,6 +1025,135 @@ var RestServer = class {
|
|
|
987
1025
|
}
|
|
988
1026
|
});
|
|
989
1027
|
}
|
|
1028
|
+
/**
|
|
1029
|
+
* Register OpenAPI 3.1 spec + interactive docs viewer.
|
|
1030
|
+
*
|
|
1031
|
+
* GET <basePath>/openapi.json → enriched OpenAPI document
|
|
1032
|
+
* GET <basePath>/docs → Scalar-rendered HTML (CDN, no dep)
|
|
1033
|
+
*
|
|
1034
|
+
* Enrichment at request time:
|
|
1035
|
+
* - servers[0].url — derived from the request's Host header
|
|
1036
|
+
* - paths — `{object}` placeholders expanded into
|
|
1037
|
+
* one concrete path per registered object
|
|
1038
|
+
* from the protocol's discovery metadata
|
|
1039
|
+
*
|
|
1040
|
+
* The base spec is loaded lazily from @objectstack/spec/openapi.json
|
|
1041
|
+
* (shipped pre-generated by spec's build pipeline) so we don't pay
|
|
1042
|
+
* the cost of regenerating on every request, and a missing or
|
|
1043
|
+
* malformed file degrades to a stub instead of crashing.
|
|
1044
|
+
*/
|
|
1045
|
+
registerOpenApiEndpoints(basePath) {
|
|
1046
|
+
const isScoped = basePath.includes("/projects/:projectId");
|
|
1047
|
+
const openApiHandler = async (req, res) => {
|
|
1048
|
+
try {
|
|
1049
|
+
const spec = await this.loadOpenApiSpec();
|
|
1050
|
+
if (!spec) {
|
|
1051
|
+
res.status?.(503);
|
|
1052
|
+
res.json({
|
|
1053
|
+
error: "openapi_unavailable",
|
|
1054
|
+
message: "OpenAPI spec is not bundled with this runtime."
|
|
1055
|
+
});
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
const enriched = { ...spec, servers: [...spec.servers ?? []] };
|
|
1059
|
+
const host = req.headers?.host ?? req.headers?.["host"];
|
|
1060
|
+
const proto = req.headers?.["x-forwarded-proto"] || req.protocol || "http";
|
|
1061
|
+
if (host) {
|
|
1062
|
+
enriched.servers = [
|
|
1063
|
+
{ url: `${proto}://${host}`, description: "Current server" },
|
|
1064
|
+
...spec.servers ?? []
|
|
1065
|
+
];
|
|
1066
|
+
}
|
|
1067
|
+
try {
|
|
1068
|
+
const projectId = isScoped ? req.params?.projectId : void 0;
|
|
1069
|
+
const protocol = await this.resolveProtocol(projectId, req);
|
|
1070
|
+
const items = await protocol?.getMetaItems?.({ type: "object" }).catch(() => null);
|
|
1071
|
+
const objects = Array.isArray(items?.items) ? items.items.map((i) => i?.name).filter(Boolean) : Array.isArray(items) ? items.map((i) => i?.name).filter(Boolean) : [];
|
|
1072
|
+
if (objects.length > 0 && enriched.paths) {
|
|
1073
|
+
const expanded = {};
|
|
1074
|
+
for (const [p, def] of Object.entries(enriched.paths)) {
|
|
1075
|
+
if (p.includes("{object}")) {
|
|
1076
|
+
expanded[p] = { ...def, "x-template": true };
|
|
1077
|
+
for (const obj of objects) {
|
|
1078
|
+
expanded[p.replace("{object}", obj)] = def;
|
|
1079
|
+
}
|
|
1080
|
+
} else {
|
|
1081
|
+
expanded[p] = def;
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
enriched.paths = expanded;
|
|
1085
|
+
}
|
|
1086
|
+
} catch {
|
|
1087
|
+
}
|
|
1088
|
+
if (enriched.info) {
|
|
1089
|
+
enriched.info = {
|
|
1090
|
+
...enriched.info,
|
|
1091
|
+
version: this.config.api.version || enriched.info.version
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
res.json(enriched);
|
|
1095
|
+
} catch (error) {
|
|
1096
|
+
logError("[REST] openapi.json error:", error);
|
|
1097
|
+
sendError(res, error);
|
|
1098
|
+
}
|
|
1099
|
+
};
|
|
1100
|
+
this.routeManager.register({
|
|
1101
|
+
method: "GET",
|
|
1102
|
+
path: `${basePath}/openapi.json`,
|
|
1103
|
+
handler: openApiHandler,
|
|
1104
|
+
metadata: {
|
|
1105
|
+
summary: "OpenAPI 3.1 specification (machine-readable)",
|
|
1106
|
+
tags: ["openapi"]
|
|
1107
|
+
}
|
|
1108
|
+
});
|
|
1109
|
+
this.routeManager.register({
|
|
1110
|
+
method: "GET",
|
|
1111
|
+
path: `${basePath}/docs`,
|
|
1112
|
+
handler: async (req, res) => {
|
|
1113
|
+
const reqPath = req.path || req.url || `${basePath}/docs`;
|
|
1114
|
+
const apiBase = reqPath.replace(/\/docs\/?$/, "");
|
|
1115
|
+
const specUrl = `${apiBase}/openapi.json`;
|
|
1116
|
+
const html = `<!doctype html>
|
|
1117
|
+
<html>
|
|
1118
|
+
<head>
|
|
1119
|
+
<meta charset="utf-8" />
|
|
1120
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
1121
|
+
<title>ObjectStack API Docs</title>
|
|
1122
|
+
</head>
|
|
1123
|
+
<body>
|
|
1124
|
+
<script id="api-reference" data-url="${specUrl}"></script>
|
|
1125
|
+
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
|
|
1126
|
+
</body>
|
|
1127
|
+
</html>`;
|
|
1128
|
+
if (res.setHeader) res.setHeader("content-type", "text/html; charset=utf-8");
|
|
1129
|
+
if (res.send) res.send(html);
|
|
1130
|
+
else if (res.body) res.body = html;
|
|
1131
|
+
else res.json?.(html);
|
|
1132
|
+
},
|
|
1133
|
+
metadata: {
|
|
1134
|
+
summary: "Interactive API docs (Scalar viewer)",
|
|
1135
|
+
tags: ["openapi"]
|
|
1136
|
+
}
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1139
|
+
async loadOpenApiSpec() {
|
|
1140
|
+
if (this._openApiSpecCache !== void 0) return this._openApiSpecCache;
|
|
1141
|
+
try {
|
|
1142
|
+
const mod = await import("module");
|
|
1143
|
+
const requireFn = mod.createRequire(import.meta.url);
|
|
1144
|
+
const pkgJsonPath = requireFn.resolve("@objectstack/spec/package.json");
|
|
1145
|
+
const pathMod = await import("path");
|
|
1146
|
+
const fsMod = await import("fs");
|
|
1147
|
+
const specPath = pathMod.join(pathMod.dirname(pkgJsonPath), "json-schema", "openapi.json");
|
|
1148
|
+
const raw = await fsMod.promises.readFile(specPath, "utf-8");
|
|
1149
|
+
this._openApiSpecCache = JSON.parse(raw);
|
|
1150
|
+
return this._openApiSpecCache;
|
|
1151
|
+
} catch (err) {
|
|
1152
|
+
logError("[REST] Failed to load OpenAPI spec:", err?.message ?? err);
|
|
1153
|
+
this._openApiSpecCache = null;
|
|
1154
|
+
return null;
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
990
1157
|
/**
|
|
991
1158
|
* Register metadata endpoints
|
|
992
1159
|
*/
|