@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.d.cts
CHANGED
|
@@ -117,6 +117,19 @@ interface StandaloneStackResult {
|
|
|
117
117
|
requires?: string[];
|
|
118
118
|
objects?: any[];
|
|
119
119
|
manifest?: any;
|
|
120
|
+
/**
|
|
121
|
+
* App-declared RBAC metadata, surfaced so the CLI (`serve`/`dev`/`start`)
|
|
122
|
+
* can wire it without a host `objectstack.config.ts`. In particular the
|
|
123
|
+
* `serve` command reads `permissions[]` to honour an app-declared default
|
|
124
|
+
* profile (ADR-0056 D7 — `appDefaultProfileName` → SecurityPlugin
|
|
125
|
+
* `fallbackPermissionSet`) and reads both `roles[]` and `permissions[]` to
|
|
126
|
+
* register application org roles with Better-Auth. Without these the
|
|
127
|
+
* artifact-serve path silently fell back to the built-in `member_default`
|
|
128
|
+
* (owner-only), so an `isDefault` profile declared purely in app metadata
|
|
129
|
+
* was ignored under `objectstack dev`.
|
|
130
|
+
*/
|
|
131
|
+
permissions?: any[];
|
|
132
|
+
roles?: any[];
|
|
120
133
|
}
|
|
121
134
|
declare function createStandaloneStack(config?: StandaloneStackConfig): Promise<StandaloneStackResult>;
|
|
122
135
|
|
package/dist/index.d.ts
CHANGED
|
@@ -117,6 +117,19 @@ interface StandaloneStackResult {
|
|
|
117
117
|
requires?: string[];
|
|
118
118
|
objects?: any[];
|
|
119
119
|
manifest?: any;
|
|
120
|
+
/**
|
|
121
|
+
* App-declared RBAC metadata, surfaced so the CLI (`serve`/`dev`/`start`)
|
|
122
|
+
* can wire it without a host `objectstack.config.ts`. In particular the
|
|
123
|
+
* `serve` command reads `permissions[]` to honour an app-declared default
|
|
124
|
+
* profile (ADR-0056 D7 — `appDefaultProfileName` → SecurityPlugin
|
|
125
|
+
* `fallbackPermissionSet`) and reads both `roles[]` and `permissions[]` to
|
|
126
|
+
* register application org roles with Better-Auth. Without these the
|
|
127
|
+
* artifact-serve path silently fell back to the built-in `member_default`
|
|
128
|
+
* (owner-only), so an `isDefault` profile declared purely in app metadata
|
|
129
|
+
* was ignored under `objectstack dev`.
|
|
130
|
+
*/
|
|
131
|
+
permissions?: any[];
|
|
132
|
+
roles?: any[];
|
|
120
133
|
}
|
|
121
134
|
declare function createStandaloneStack(config?: StandaloneStackConfig): Promise<StandaloneStackResult>;
|
|
122
135
|
|
package/dist/index.js
CHANGED
|
@@ -963,6 +963,65 @@ var init_app_plugin = __esm({
|
|
|
963
963
|
error: err?.message ?? String(err)
|
|
964
964
|
});
|
|
965
965
|
}
|
|
966
|
+
try {
|
|
967
|
+
const dsDefs = this.bundle.datasources;
|
|
968
|
+
const dsList = Array.isArray(dsDefs) ? dsDefs : dsDefs && typeof dsDefs === "object" ? Object.entries(dsDefs).map(([name, def]) => ({ name, ...def })) : [];
|
|
969
|
+
if (dsList.length > 0) {
|
|
970
|
+
let connection;
|
|
971
|
+
try {
|
|
972
|
+
connection = ctx.getService("datasource-connection");
|
|
973
|
+
} catch {
|
|
974
|
+
connection = void 0;
|
|
975
|
+
}
|
|
976
|
+
if (typeof connection?.connectDeclared === "function") {
|
|
977
|
+
const objects = Array.isArray(this.bundle.objects) ? this.bundle.objects : [];
|
|
978
|
+
const results = await connection.connectDeclared({ datasources: dsList, objects });
|
|
979
|
+
const connected = results.filter((r) => r.status === "connected");
|
|
980
|
+
if (connected.length > 0) {
|
|
981
|
+
ctx.logger.info("Auto-connected declared datasources", {
|
|
982
|
+
appId,
|
|
983
|
+
connected: connected.map((r) => r.name)
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
} else {
|
|
987
|
+
ctx.logger.debug("No datasource-connection service \u2014 declared datasources stay metadata-only", { appId });
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
} catch (err) {
|
|
991
|
+
ctx.logger.error(
|
|
992
|
+
`[AppPlugin] declared-datasource auto-connect failed for app '${appId}': ${err?.message ?? String(err)}`
|
|
993
|
+
);
|
|
994
|
+
throw err;
|
|
995
|
+
}
|
|
996
|
+
try {
|
|
997
|
+
const metadata = ctx.getService("metadata");
|
|
998
|
+
if (typeof metadata?.registerInMemory === "function") {
|
|
999
|
+
const securityBundle = this.bundle.manifest ? { ...this.bundle.manifest, ...this.bundle } : this.bundle;
|
|
1000
|
+
const SECURITY_FIELDS = [
|
|
1001
|
+
["roles", "role"],
|
|
1002
|
+
["permissions", "permission"],
|
|
1003
|
+
["sharingRules", "sharing_rule"],
|
|
1004
|
+
["policies", "policy"]
|
|
1005
|
+
];
|
|
1006
|
+
let count = 0;
|
|
1007
|
+
for (const [field, type] of SECURITY_FIELDS) {
|
|
1008
|
+
const arr = securityBundle?.[field];
|
|
1009
|
+
if (!Array.isArray(arr)) continue;
|
|
1010
|
+
for (const item of arr) {
|
|
1011
|
+
if (!item?.name) continue;
|
|
1012
|
+
metadata.registerInMemory(type, item.name, item);
|
|
1013
|
+
count += 1;
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
if (count > 0) {
|
|
1017
|
+
ctx.logger.info("Registered stack-declared security metadata", { appId, count });
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
} catch (err) {
|
|
1021
|
+
ctx.logger.warn("[AppPlugin] failed to register security metadata", {
|
|
1022
|
+
error: err?.message ?? String(err)
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
966
1025
|
const stackBundle = this.bundle.default || this.bundle;
|
|
967
1026
|
const runtime = stackBundle && typeof stackBundle.onEnable === "function" ? stackBundle : this.bundle;
|
|
968
1027
|
if (runtime && typeof runtime.onEnable === "function") {
|
|
@@ -1501,55 +1560,55 @@ async function createStandaloneStack(config) {
|
|
|
1501
1560
|
const explicitDriver = cfg.databaseDriver ?? process.env.OS_DATABASE_DRIVER?.trim();
|
|
1502
1561
|
const dbDriver = explicitDriver ?? detectDriverFromUrl(dbUrl);
|
|
1503
1562
|
let driverPlugin;
|
|
1504
|
-
if (dbDriver === "
|
|
1505
|
-
const { InMemoryDriver } = await import("@objectstack/driver-memory");
|
|
1506
|
-
driverPlugin = new DriverPlugin2(new InMemoryDriver());
|
|
1507
|
-
} else if (dbDriver === "postgres") {
|
|
1508
|
-
const { SqlDriver } = await import("@objectstack/driver-sql");
|
|
1509
|
-
driverPlugin = new DriverPlugin2(
|
|
1510
|
-
new SqlDriver({
|
|
1511
|
-
client: "pg",
|
|
1512
|
-
connection: dbUrl,
|
|
1513
|
-
pool: { min: 0, max: 5 }
|
|
1514
|
-
})
|
|
1515
|
-
);
|
|
1516
|
-
} else if (dbDriver === "mongodb") {
|
|
1517
|
-
let MongoDBDriver;
|
|
1518
|
-
try {
|
|
1519
|
-
({ MongoDBDriver } = await import("@objectstack/driver-mongodb"));
|
|
1520
|
-
} catch (err) {
|
|
1521
|
-
throw new Error(
|
|
1522
|
-
`[StandaloneStack] mongodb URL detected but @objectstack/driver-mongodb is not installed. Add it as a dependency or pass an explicit driverPlugin. (${err?.message ?? err})`
|
|
1523
|
-
);
|
|
1524
|
-
}
|
|
1525
|
-
driverPlugin = new DriverPlugin2(new MongoDBDriver({ url: dbUrl }));
|
|
1526
|
-
} else if (dbDriver === "sqlite-wasm") {
|
|
1563
|
+
if (dbDriver === "sqlite-wasm") {
|
|
1527
1564
|
const { SqliteWasmDriver } = await import("@objectstack/driver-sqlite-wasm");
|
|
1528
|
-
const filename = dbUrl.replace(/^wasm-sqlite:(\/\/)?/i, "").replace(/^file:(\/\/)?/i, "");
|
|
1529
|
-
if (filename
|
|
1565
|
+
const filename = dbUrl.replace(/^wasm-sqlite:(\/\/)?/i, "").replace(/^file:(\/\/)?/i, "") || ":memory:";
|
|
1566
|
+
if (filename !== ":memory:") {
|
|
1530
1567
|
mkdirSync2(resolvePath2(filename, ".."), { recursive: true });
|
|
1531
1568
|
}
|
|
1532
1569
|
driverPlugin = new DriverPlugin2(
|
|
1533
1570
|
new SqliteWasmDriver({
|
|
1534
|
-
filename
|
|
1535
|
-
persist: filename
|
|
1571
|
+
filename,
|
|
1572
|
+
persist: filename !== ":memory:" ? "on-write" : void 0
|
|
1536
1573
|
})
|
|
1537
1574
|
);
|
|
1538
1575
|
} else {
|
|
1539
|
-
const {
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1576
|
+
const { createDefaultDatasourceDriverFactory } = await import("@objectstack/service-datasource");
|
|
1577
|
+
let driverId;
|
|
1578
|
+
let driverConfig;
|
|
1579
|
+
if (dbDriver === "memory") {
|
|
1580
|
+
driverId = "memory";
|
|
1581
|
+
driverConfig = {};
|
|
1582
|
+
} else if (dbDriver === "postgres") {
|
|
1583
|
+
driverId = "postgres";
|
|
1584
|
+
driverConfig = { url: dbUrl };
|
|
1585
|
+
} else if (dbDriver === "mongodb") {
|
|
1586
|
+
driverId = "mongodb";
|
|
1587
|
+
driverConfig = { url: dbUrl };
|
|
1588
|
+
} else {
|
|
1589
|
+
driverId = "sqlite";
|
|
1590
|
+
const filename = dbUrl.replace(/^file:(\/\/)?/, "");
|
|
1591
|
+
if (!filename || /^[a-z][a-z0-9+.-]*:\/\//i.test(filename)) {
|
|
1592
|
+
throw new Error(
|
|
1593
|
+
`[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.`
|
|
1594
|
+
);
|
|
1595
|
+
}
|
|
1596
|
+
mkdirSync2(resolvePath2(filename, ".."), { recursive: true });
|
|
1597
|
+
driverConfig = { filename };
|
|
1598
|
+
}
|
|
1599
|
+
let driverHandle;
|
|
1600
|
+
try {
|
|
1601
|
+
driverHandle = await createDefaultDatasourceDriverFactory().create({ driver: driverId, config: driverConfig });
|
|
1602
|
+
} catch (err) {
|
|
1603
|
+
if (dbDriver === "mongodb") {
|
|
1604
|
+
throw new Error(
|
|
1605
|
+
`[StandaloneStack] mongodb URL detected but @objectstack/driver-mongodb is not installed. Add it as a dependency or pass an explicit driverPlugin. (${err?.message ?? err})`
|
|
1606
|
+
);
|
|
1607
|
+
}
|
|
1608
|
+
throw err;
|
|
1545
1609
|
}
|
|
1546
|
-
mkdirSync2(resolvePath2(filename, ".."), { recursive: true });
|
|
1547
1610
|
driverPlugin = new DriverPlugin2(
|
|
1548
|
-
|
|
1549
|
-
client: "better-sqlite3",
|
|
1550
|
-
connection: { filename },
|
|
1551
|
-
useNullAsDefault: true
|
|
1552
|
-
})
|
|
1611
|
+
driverHandle?.driver ?? driverHandle
|
|
1553
1612
|
);
|
|
1554
1613
|
}
|
|
1555
1614
|
const artifactBundle = await loadArtifactBundle(artifactPath, {
|
|
@@ -1587,6 +1646,8 @@ async function createStandaloneStack(config) {
|
|
|
1587
1646
|
const requires = Array.isArray(artifactBundle?.requires) ? artifactBundle.requires.filter((c) => typeof c === "string") : void 0;
|
|
1588
1647
|
const objects = Array.isArray(artifactBundle?.objects) ? artifactBundle.objects : void 0;
|
|
1589
1648
|
const manifest = artifactBundle?.manifest;
|
|
1649
|
+
const permissions = Array.isArray(artifactBundle?.permissions) ? artifactBundle.permissions : void 0;
|
|
1650
|
+
const roles = Array.isArray(artifactBundle?.roles) ? artifactBundle.roles : void 0;
|
|
1590
1651
|
return {
|
|
1591
1652
|
plugins,
|
|
1592
1653
|
api: {
|
|
@@ -1595,7 +1656,9 @@ async function createStandaloneStack(config) {
|
|
|
1595
1656
|
},
|
|
1596
1657
|
...requires ? { requires } : {},
|
|
1597
1658
|
...objects ? { objects } : {},
|
|
1598
|
-
...manifest ? { manifest } : {}
|
|
1659
|
+
...manifest ? { manifest } : {},
|
|
1660
|
+
...permissions ? { permissions } : {},
|
|
1661
|
+
...roles ? { roles } : {}
|
|
1599
1662
|
};
|
|
1600
1663
|
}
|
|
1601
1664
|
var StandaloneStackConfigSchema;
|
|
@@ -1997,25 +2060,33 @@ function coerceLocale(value) {
|
|
|
1997
2060
|
const s = typeof value === "string" ? value.trim() : value != null ? String(value).trim() : "";
|
|
1998
2061
|
return s || void 0;
|
|
1999
2062
|
}
|
|
2063
|
+
function coerceCurrency(value) {
|
|
2064
|
+
const s = typeof value === "string" ? value.trim().toUpperCase() : "";
|
|
2065
|
+
return /^[A-Z]{3}$/.test(s) ? s : void 0;
|
|
2066
|
+
}
|
|
2000
2067
|
async function resolveLocalization(opts, ql, sctx) {
|
|
2001
2068
|
try {
|
|
2002
2069
|
const settings = await opts.getService("settings");
|
|
2003
2070
|
if (settings && typeof settings.get === "function") {
|
|
2004
|
-
const [tzRes, localeRes] = await Promise.all([
|
|
2071
|
+
const [tzRes, localeRes, currencyRes] = await Promise.all([
|
|
2005
2072
|
settings.get("localization", "timezone", sctx).catch(() => void 0),
|
|
2006
|
-
settings.get("localization", "locale", sctx).catch(() => void 0)
|
|
2073
|
+
settings.get("localization", "locale", sctx).catch(() => void 0),
|
|
2074
|
+
settings.get("localization", "currency", sctx).catch(() => void 0)
|
|
2007
2075
|
]);
|
|
2008
2076
|
const tz = coerceTimeZone(tzRes?.value);
|
|
2009
2077
|
const locale = coerceLocale(localeRes?.value);
|
|
2010
|
-
|
|
2078
|
+
const currency = coerceCurrency(currencyRes?.value);
|
|
2079
|
+
if (tz || locale || currency) return { timezone: tz ?? "UTC", locale: locale ?? "en-US", currency };
|
|
2011
2080
|
}
|
|
2012
2081
|
} catch {
|
|
2013
2082
|
}
|
|
2014
2083
|
const tzRows = await tryFind(ql, "sys_setting", { namespace: "localization", key: "timezone", scope: "tenant" }, 1);
|
|
2015
2084
|
const localeRows = await tryFind(ql, "sys_setting", { namespace: "localization", key: "locale", scope: "tenant" }, 1);
|
|
2085
|
+
const currencyRows = await tryFind(ql, "sys_setting", { namespace: "localization", key: "currency", scope: "tenant" }, 1);
|
|
2016
2086
|
return {
|
|
2017
2087
|
timezone: coerceTimeZone(tzRows[0]?.value) ?? "UTC",
|
|
2018
|
-
locale: coerceLocale(localeRows[0]?.value) ?? "en-US"
|
|
2088
|
+
locale: coerceLocale(localeRows[0]?.value) ?? "en-US",
|
|
2089
|
+
currency: coerceCurrency(currencyRows[0]?.value)
|
|
2019
2090
|
};
|
|
2020
2091
|
}
|
|
2021
2092
|
async function resolveExecutionContext(opts) {
|
|
@@ -2070,6 +2141,13 @@ async function resolveExecutionContext(opts) {
|
|
|
2070
2141
|
}
|
|
2071
2142
|
}
|
|
2072
2143
|
}
|
|
2144
|
+
const userRoleRows = await tryFind(ql, "sys_user_role", { user_id: userId }, 200);
|
|
2145
|
+
for (const ur of userRoleRows) {
|
|
2146
|
+
const org = ur.organization_id ?? null;
|
|
2147
|
+
if (org && tenantId && org !== tenantId) continue;
|
|
2148
|
+
const r = ur.role;
|
|
2149
|
+
if (typeof r === "string" && r && !ctx.roles.includes(r)) ctx.roles.push(r);
|
|
2150
|
+
}
|
|
2073
2151
|
if (tenantId) {
|
|
2074
2152
|
const orgMembers = await tryFind(
|
|
2075
2153
|
ql,
|
|
@@ -2156,6 +2234,7 @@ async function resolveExecutionContext(opts) {
|
|
|
2156
2234
|
const localization = await resolveLocalization(opts, ql, { tenantId, userId });
|
|
2157
2235
|
ctx.timezone = localization.timezone;
|
|
2158
2236
|
ctx.locale = localization.locale;
|
|
2237
|
+
if (localization.currency) ctx.currency = localization.currency;
|
|
2159
2238
|
return ctx;
|
|
2160
2239
|
}
|
|
2161
2240
|
function isPermissionDeniedError(e) {
|
|
@@ -2653,7 +2732,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
2653
2732
|
*/
|
|
2654
2733
|
async enforceProjectMembership(context, path) {
|
|
2655
2734
|
if (!this.enforceMembership) return null;
|
|
2656
|
-
const skipPaths = ["/auth", "/cloud", "/health", "/discovery"];
|
|
2735
|
+
const skipPaths = ["/auth", "/cloud", "/health", "/ready", "/discovery"];
|
|
2657
2736
|
if (skipPaths.some((p) => path.startsWith(p))) return null;
|
|
2658
2737
|
if (/(^|\/)share-links\/[^/]+\/(resolve|messages)$/.test(path)) return null;
|
|
2659
2738
|
const environmentId = context.environmentId;
|
|
@@ -4470,6 +4549,10 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
4470
4549
|
})
|
|
4471
4550
|
};
|
|
4472
4551
|
}
|
|
4552
|
+
if (cleanPath === "/ready" && method === "GET") {
|
|
4553
|
+
const state = typeof this.kernel?.getState === "function" ? this.kernel.getState() : "running";
|
|
4554
|
+
return state === "running" ? { handled: true, response: this.success({ status: "ready", state }) } : { handled: true, response: this.error("Service not ready", 503, { state }) };
|
|
4555
|
+
}
|
|
4473
4556
|
if (cleanPath.startsWith("/auth")) {
|
|
4474
4557
|
return this.handleAuth(cleanPath.substring(5), method, body, context);
|
|
4475
4558
|
}
|