@objectstack/runtime 9.11.0 → 10.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 +128 -45
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +128 -45
- package/dist/index.js.map +1 -1
- package/package.json +21 -19
package/dist/index.cjs
CHANGED
|
@@ -987,6 +987,65 @@ var init_app_plugin = __esm({
|
|
|
987
987
|
error: err?.message ?? String(err)
|
|
988
988
|
});
|
|
989
989
|
}
|
|
990
|
+
try {
|
|
991
|
+
const dsDefs = this.bundle.datasources;
|
|
992
|
+
const dsList = Array.isArray(dsDefs) ? dsDefs : dsDefs && typeof dsDefs === "object" ? Object.entries(dsDefs).map(([name, def]) => ({ name, ...def })) : [];
|
|
993
|
+
if (dsList.length > 0) {
|
|
994
|
+
let connection;
|
|
995
|
+
try {
|
|
996
|
+
connection = ctx.getService("datasource-connection");
|
|
997
|
+
} catch {
|
|
998
|
+
connection = void 0;
|
|
999
|
+
}
|
|
1000
|
+
if (typeof connection?.connectDeclared === "function") {
|
|
1001
|
+
const objects = Array.isArray(this.bundle.objects) ? this.bundle.objects : [];
|
|
1002
|
+
const results = await connection.connectDeclared({ datasources: dsList, objects });
|
|
1003
|
+
const connected = results.filter((r) => r.status === "connected");
|
|
1004
|
+
if (connected.length > 0) {
|
|
1005
|
+
ctx.logger.info("Auto-connected declared datasources", {
|
|
1006
|
+
appId,
|
|
1007
|
+
connected: connected.map((r) => r.name)
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
} else {
|
|
1011
|
+
ctx.logger.debug("No datasource-connection service \u2014 declared datasources stay metadata-only", { appId });
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
} catch (err) {
|
|
1015
|
+
ctx.logger.error(
|
|
1016
|
+
`[AppPlugin] declared-datasource auto-connect failed for app '${appId}': ${err?.message ?? String(err)}`
|
|
1017
|
+
);
|
|
1018
|
+
throw err;
|
|
1019
|
+
}
|
|
1020
|
+
try {
|
|
1021
|
+
const metadata = ctx.getService("metadata");
|
|
1022
|
+
if (typeof metadata?.registerInMemory === "function") {
|
|
1023
|
+
const securityBundle = this.bundle.manifest ? { ...this.bundle.manifest, ...this.bundle } : this.bundle;
|
|
1024
|
+
const SECURITY_FIELDS = [
|
|
1025
|
+
["roles", "role"],
|
|
1026
|
+
["permissions", "permission"],
|
|
1027
|
+
["sharingRules", "sharing_rule"],
|
|
1028
|
+
["policies", "policy"]
|
|
1029
|
+
];
|
|
1030
|
+
let count = 0;
|
|
1031
|
+
for (const [field, type] of SECURITY_FIELDS) {
|
|
1032
|
+
const arr = securityBundle?.[field];
|
|
1033
|
+
if (!Array.isArray(arr)) continue;
|
|
1034
|
+
for (const item of arr) {
|
|
1035
|
+
if (!item?.name) continue;
|
|
1036
|
+
metadata.registerInMemory(type, item.name, item);
|
|
1037
|
+
count += 1;
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
if (count > 0) {
|
|
1041
|
+
ctx.logger.info("Registered stack-declared security metadata", { appId, count });
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
} catch (err) {
|
|
1045
|
+
ctx.logger.warn("[AppPlugin] failed to register security metadata", {
|
|
1046
|
+
error: err?.message ?? String(err)
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
990
1049
|
const stackBundle = this.bundle.default || this.bundle;
|
|
991
1050
|
const runtime = stackBundle && typeof stackBundle.onEnable === "function" ? stackBundle : this.bundle;
|
|
992
1051
|
if (runtime && typeof runtime.onEnable === "function") {
|
|
@@ -1520,55 +1579,55 @@ async function createStandaloneStack(config) {
|
|
|
1520
1579
|
const explicitDriver = cfg.databaseDriver ?? process.env.OS_DATABASE_DRIVER?.trim();
|
|
1521
1580
|
const dbDriver = explicitDriver ?? detectDriverFromUrl(dbUrl);
|
|
1522
1581
|
let driverPlugin;
|
|
1523
|
-
if (dbDriver === "
|
|
1524
|
-
const { InMemoryDriver } = await import("@objectstack/driver-memory");
|
|
1525
|
-
driverPlugin = new DriverPlugin2(new InMemoryDriver());
|
|
1526
|
-
} else if (dbDriver === "postgres") {
|
|
1527
|
-
const { SqlDriver } = await import("@objectstack/driver-sql");
|
|
1528
|
-
driverPlugin = new DriverPlugin2(
|
|
1529
|
-
new SqlDriver({
|
|
1530
|
-
client: "pg",
|
|
1531
|
-
connection: dbUrl,
|
|
1532
|
-
pool: { min: 0, max: 5 }
|
|
1533
|
-
})
|
|
1534
|
-
);
|
|
1535
|
-
} else if (dbDriver === "mongodb") {
|
|
1536
|
-
let MongoDBDriver;
|
|
1537
|
-
try {
|
|
1538
|
-
({ MongoDBDriver } = await import("@objectstack/driver-mongodb"));
|
|
1539
|
-
} catch (err) {
|
|
1540
|
-
throw new Error(
|
|
1541
|
-
`[StandaloneStack] mongodb URL detected but @objectstack/driver-mongodb is not installed. Add it as a dependency or pass an explicit driverPlugin. (${err?.message ?? err})`
|
|
1542
|
-
);
|
|
1543
|
-
}
|
|
1544
|
-
driverPlugin = new DriverPlugin2(new MongoDBDriver({ url: dbUrl }));
|
|
1545
|
-
} else if (dbDriver === "sqlite-wasm") {
|
|
1582
|
+
if (dbDriver === "sqlite-wasm") {
|
|
1546
1583
|
const { SqliteWasmDriver } = await import("@objectstack/driver-sqlite-wasm");
|
|
1547
|
-
const filename = dbUrl.replace(/^wasm-sqlite:(\/\/)?/i, "").replace(/^file:(\/\/)?/i, "");
|
|
1548
|
-
if (filename
|
|
1584
|
+
const filename = dbUrl.replace(/^wasm-sqlite:(\/\/)?/i, "").replace(/^file:(\/\/)?/i, "") || ":memory:";
|
|
1585
|
+
if (filename !== ":memory:") {
|
|
1549
1586
|
(0, import_node_fs2.mkdirSync)((0, import_node_path3.resolve)(filename, ".."), { recursive: true });
|
|
1550
1587
|
}
|
|
1551
1588
|
driverPlugin = new DriverPlugin2(
|
|
1552
1589
|
new SqliteWasmDriver({
|
|
1553
|
-
filename
|
|
1554
|
-
persist: filename
|
|
1590
|
+
filename,
|
|
1591
|
+
persist: filename !== ":memory:" ? "on-write" : void 0
|
|
1555
1592
|
})
|
|
1556
1593
|
);
|
|
1557
1594
|
} else {
|
|
1558
|
-
const {
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1595
|
+
const { createDefaultDatasourceDriverFactory } = await import("@objectstack/service-datasource");
|
|
1596
|
+
let driverId;
|
|
1597
|
+
let driverConfig;
|
|
1598
|
+
if (dbDriver === "memory") {
|
|
1599
|
+
driverId = "memory";
|
|
1600
|
+
driverConfig = {};
|
|
1601
|
+
} else if (dbDriver === "postgres") {
|
|
1602
|
+
driverId = "postgres";
|
|
1603
|
+
driverConfig = { url: dbUrl };
|
|
1604
|
+
} else if (dbDriver === "mongodb") {
|
|
1605
|
+
driverId = "mongodb";
|
|
1606
|
+
driverConfig = { url: dbUrl };
|
|
1607
|
+
} else {
|
|
1608
|
+
driverId = "sqlite";
|
|
1609
|
+
const filename = dbUrl.replace(/^file:(\/\/)?/, "");
|
|
1610
|
+
if (!filename || /^[a-z][a-z0-9+.-]*:\/\//i.test(filename)) {
|
|
1611
|
+
throw new Error(
|
|
1612
|
+
`[StandaloneStack] sqlite driver was selected but the URL does not look like a file path: "${dbUrl}". Use file:/path/to/db.sqlite, or set OS_DATABASE_DRIVER explicitly.`
|
|
1613
|
+
);
|
|
1614
|
+
}
|
|
1615
|
+
(0, import_node_fs2.mkdirSync)((0, import_node_path3.resolve)(filename, ".."), { recursive: true });
|
|
1616
|
+
driverConfig = { filename };
|
|
1617
|
+
}
|
|
1618
|
+
let driverHandle;
|
|
1619
|
+
try {
|
|
1620
|
+
driverHandle = await createDefaultDatasourceDriverFactory().create({ driver: driverId, config: driverConfig });
|
|
1621
|
+
} catch (err) {
|
|
1622
|
+
if (dbDriver === "mongodb") {
|
|
1623
|
+
throw new Error(
|
|
1624
|
+
`[StandaloneStack] mongodb URL detected but @objectstack/driver-mongodb is not installed. Add it as a dependency or pass an explicit driverPlugin. (${err?.message ?? err})`
|
|
1625
|
+
);
|
|
1626
|
+
}
|
|
1627
|
+
throw err;
|
|
1564
1628
|
}
|
|
1565
|
-
(0, import_node_fs2.mkdirSync)((0, import_node_path3.resolve)(filename, ".."), { recursive: true });
|
|
1566
1629
|
driverPlugin = new DriverPlugin2(
|
|
1567
|
-
|
|
1568
|
-
client: "better-sqlite3",
|
|
1569
|
-
connection: { filename },
|
|
1570
|
-
useNullAsDefault: true
|
|
1571
|
-
})
|
|
1630
|
+
driverHandle?.driver ?? driverHandle
|
|
1572
1631
|
);
|
|
1573
1632
|
}
|
|
1574
1633
|
const artifactBundle = await loadArtifactBundle(artifactPath, {
|
|
@@ -1606,6 +1665,8 @@ async function createStandaloneStack(config) {
|
|
|
1606
1665
|
const requires = Array.isArray(artifactBundle?.requires) ? artifactBundle.requires.filter((c) => typeof c === "string") : void 0;
|
|
1607
1666
|
const objects = Array.isArray(artifactBundle?.objects) ? artifactBundle.objects : void 0;
|
|
1608
1667
|
const manifest = artifactBundle?.manifest;
|
|
1668
|
+
const permissions = Array.isArray(artifactBundle?.permissions) ? artifactBundle.permissions : void 0;
|
|
1669
|
+
const roles = Array.isArray(artifactBundle?.roles) ? artifactBundle.roles : void 0;
|
|
1609
1670
|
return {
|
|
1610
1671
|
plugins,
|
|
1611
1672
|
api: {
|
|
@@ -1614,7 +1675,9 @@ async function createStandaloneStack(config) {
|
|
|
1614
1675
|
},
|
|
1615
1676
|
...requires ? { requires } : {},
|
|
1616
1677
|
...objects ? { objects } : {},
|
|
1617
|
-
...manifest ? { manifest } : {}
|
|
1678
|
+
...manifest ? { manifest } : {},
|
|
1679
|
+
...permissions ? { permissions } : {},
|
|
1680
|
+
...roles ? { roles } : {}
|
|
1618
1681
|
};
|
|
1619
1682
|
}
|
|
1620
1683
|
var import_node_path3, import_node_fs2, import_node_os, import_zod, import_types2, StandaloneStackConfigSchema;
|
|
@@ -2065,25 +2128,33 @@ function coerceLocale(value) {
|
|
|
2065
2128
|
const s = typeof value === "string" ? value.trim() : value != null ? String(value).trim() : "";
|
|
2066
2129
|
return s || void 0;
|
|
2067
2130
|
}
|
|
2131
|
+
function coerceCurrency(value) {
|
|
2132
|
+
const s = typeof value === "string" ? value.trim().toUpperCase() : "";
|
|
2133
|
+
return /^[A-Z]{3}$/.test(s) ? s : void 0;
|
|
2134
|
+
}
|
|
2068
2135
|
async function resolveLocalization(opts, ql, sctx) {
|
|
2069
2136
|
try {
|
|
2070
2137
|
const settings = await opts.getService("settings");
|
|
2071
2138
|
if (settings && typeof settings.get === "function") {
|
|
2072
|
-
const [tzRes, localeRes] = await Promise.all([
|
|
2139
|
+
const [tzRes, localeRes, currencyRes] = await Promise.all([
|
|
2073
2140
|
settings.get("localization", "timezone", sctx).catch(() => void 0),
|
|
2074
|
-
settings.get("localization", "locale", sctx).catch(() => void 0)
|
|
2141
|
+
settings.get("localization", "locale", sctx).catch(() => void 0),
|
|
2142
|
+
settings.get("localization", "currency", sctx).catch(() => void 0)
|
|
2075
2143
|
]);
|
|
2076
2144
|
const tz = coerceTimeZone(tzRes?.value);
|
|
2077
2145
|
const locale = coerceLocale(localeRes?.value);
|
|
2078
|
-
|
|
2146
|
+
const currency = coerceCurrency(currencyRes?.value);
|
|
2147
|
+
if (tz || locale || currency) return { timezone: tz ?? "UTC", locale: locale ?? "en-US", currency };
|
|
2079
2148
|
}
|
|
2080
2149
|
} catch {
|
|
2081
2150
|
}
|
|
2082
2151
|
const tzRows = await tryFind(ql, "sys_setting", { namespace: "localization", key: "timezone", scope: "tenant" }, 1);
|
|
2083
2152
|
const localeRows = await tryFind(ql, "sys_setting", { namespace: "localization", key: "locale", scope: "tenant" }, 1);
|
|
2153
|
+
const currencyRows = await tryFind(ql, "sys_setting", { namespace: "localization", key: "currency", scope: "tenant" }, 1);
|
|
2084
2154
|
return {
|
|
2085
2155
|
timezone: coerceTimeZone(tzRows[0]?.value) ?? "UTC",
|
|
2086
|
-
locale: coerceLocale(localeRows[0]?.value) ?? "en-US"
|
|
2156
|
+
locale: coerceLocale(localeRows[0]?.value) ?? "en-US",
|
|
2157
|
+
currency: coerceCurrency(currencyRows[0]?.value)
|
|
2087
2158
|
};
|
|
2088
2159
|
}
|
|
2089
2160
|
async function resolveExecutionContext(opts) {
|
|
@@ -2138,6 +2209,13 @@ async function resolveExecutionContext(opts) {
|
|
|
2138
2209
|
}
|
|
2139
2210
|
}
|
|
2140
2211
|
}
|
|
2212
|
+
const userRoleRows = await tryFind(ql, "sys_user_role", { user_id: userId }, 200);
|
|
2213
|
+
for (const ur of userRoleRows) {
|
|
2214
|
+
const org = ur.organization_id ?? null;
|
|
2215
|
+
if (org && tenantId && org !== tenantId) continue;
|
|
2216
|
+
const r = ur.role;
|
|
2217
|
+
if (typeof r === "string" && r && !ctx.roles.includes(r)) ctx.roles.push(r);
|
|
2218
|
+
}
|
|
2141
2219
|
if (tenantId) {
|
|
2142
2220
|
const orgMembers = await tryFind(
|
|
2143
2221
|
ql,
|
|
@@ -2224,6 +2302,7 @@ async function resolveExecutionContext(opts) {
|
|
|
2224
2302
|
const localization = await resolveLocalization(opts, ql, { tenantId, userId });
|
|
2225
2303
|
ctx.timezone = localization.timezone;
|
|
2226
2304
|
ctx.locale = localization.locale;
|
|
2305
|
+
if (localization.currency) ctx.currency = localization.currency;
|
|
2227
2306
|
return ctx;
|
|
2228
2307
|
}
|
|
2229
2308
|
function isPermissionDeniedError(e) {
|
|
@@ -2721,7 +2800,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
2721
2800
|
*/
|
|
2722
2801
|
async enforceProjectMembership(context, path) {
|
|
2723
2802
|
if (!this.enforceMembership) return null;
|
|
2724
|
-
const skipPaths = ["/auth", "/cloud", "/health", "/discovery"];
|
|
2803
|
+
const skipPaths = ["/auth", "/cloud", "/health", "/ready", "/discovery"];
|
|
2725
2804
|
if (skipPaths.some((p) => path.startsWith(p))) return null;
|
|
2726
2805
|
if (/(^|\/)share-links\/[^/]+\/(resolve|messages)$/.test(path)) return null;
|
|
2727
2806
|
const environmentId = context.environmentId;
|
|
@@ -4538,6 +4617,10 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
4538
4617
|
})
|
|
4539
4618
|
};
|
|
4540
4619
|
}
|
|
4620
|
+
if (cleanPath === "/ready" && method === "GET") {
|
|
4621
|
+
const state = typeof this.kernel?.getState === "function" ? this.kernel.getState() : "running";
|
|
4622
|
+
return state === "running" ? { handled: true, response: this.success({ status: "ready", state }) } : { handled: true, response: this.error("Service not ready", 503, { state }) };
|
|
4623
|
+
}
|
|
4541
4624
|
if (cleanPath.startsWith("/auth")) {
|
|
4542
4625
|
return this.handleAuth(cleanPath.substring(5), method, body, context);
|
|
4543
4626
|
}
|