@objectstack/runtime 10.0.0 → 10.3.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 +118 -48
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +118 -48
- package/dist/index.js.map +1 -1
- package/package.json +21 -19
package/dist/index.cjs
CHANGED
|
@@ -987,6 +987,36 @@ 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
|
+
}
|
|
990
1020
|
try {
|
|
991
1021
|
const metadata = ctx.getService("metadata");
|
|
992
1022
|
if (typeof metadata?.registerInMemory === "function") {
|
|
@@ -1549,55 +1579,56 @@ async function createStandaloneStack(config) {
|
|
|
1549
1579
|
const explicitDriver = cfg.databaseDriver ?? process.env.OS_DATABASE_DRIVER?.trim();
|
|
1550
1580
|
const dbDriver = explicitDriver ?? detectDriverFromUrl(dbUrl);
|
|
1551
1581
|
let driverPlugin;
|
|
1552
|
-
if (dbDriver === "
|
|
1553
|
-
const { InMemoryDriver } = await import("@objectstack/driver-memory");
|
|
1554
|
-
driverPlugin = new DriverPlugin2(new InMemoryDriver());
|
|
1555
|
-
} else if (dbDriver === "postgres") {
|
|
1556
|
-
const { SqlDriver } = await import("@objectstack/driver-sql");
|
|
1557
|
-
driverPlugin = new DriverPlugin2(
|
|
1558
|
-
new SqlDriver({
|
|
1559
|
-
client: "pg",
|
|
1560
|
-
connection: dbUrl,
|
|
1561
|
-
pool: { min: 0, max: 5 }
|
|
1562
|
-
})
|
|
1563
|
-
);
|
|
1564
|
-
} else if (dbDriver === "mongodb") {
|
|
1565
|
-
let MongoDBDriver;
|
|
1566
|
-
try {
|
|
1567
|
-
({ MongoDBDriver } = await import("@objectstack/driver-mongodb"));
|
|
1568
|
-
} catch (err) {
|
|
1569
|
-
throw new Error(
|
|
1570
|
-
`[StandaloneStack] mongodb URL detected but @objectstack/driver-mongodb is not installed. Add it as a dependency or pass an explicit driverPlugin. (${err?.message ?? err})`
|
|
1571
|
-
);
|
|
1572
|
-
}
|
|
1573
|
-
driverPlugin = new DriverPlugin2(new MongoDBDriver({ url: dbUrl }));
|
|
1574
|
-
} else if (dbDriver === "sqlite-wasm") {
|
|
1582
|
+
if (dbDriver === "sqlite-wasm") {
|
|
1575
1583
|
const { SqliteWasmDriver } = await import("@objectstack/driver-sqlite-wasm");
|
|
1576
|
-
const filename = dbUrl.replace(/^wasm-sqlite:(\/\/)?/i, "").replace(/^file:(\/\/)?/i, "");
|
|
1577
|
-
if (filename
|
|
1584
|
+
const filename = dbUrl.replace(/^wasm-sqlite:(\/\/)?/i, "").replace(/^file:(\/\/)?/i, "") || ":memory:";
|
|
1585
|
+
if (filename !== ":memory:") {
|
|
1578
1586
|
(0, import_node_fs2.mkdirSync)((0, import_node_path3.resolve)(filename, ".."), { recursive: true });
|
|
1579
1587
|
}
|
|
1580
1588
|
driverPlugin = new DriverPlugin2(
|
|
1581
1589
|
new SqliteWasmDriver({
|
|
1582
|
-
filename
|
|
1583
|
-
persist: filename
|
|
1590
|
+
filename,
|
|
1591
|
+
persist: filename !== ":memory:" ? "on-write" : void 0
|
|
1584
1592
|
})
|
|
1585
1593
|
);
|
|
1586
1594
|
} else {
|
|
1587
|
-
const {
|
|
1588
|
-
const
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1595
|
+
const { createDefaultDatasourceDriverFactory } = await import("@objectstack/service-datasource");
|
|
1596
|
+
const factoryDev = cfg.dev ?? process.env.NODE_ENV === "development";
|
|
1597
|
+
let driverId;
|
|
1598
|
+
let driverConfig;
|
|
1599
|
+
if (dbDriver === "memory") {
|
|
1600
|
+
driverId = "memory";
|
|
1601
|
+
driverConfig = {};
|
|
1602
|
+
} else if (dbDriver === "postgres") {
|
|
1603
|
+
driverId = "postgres";
|
|
1604
|
+
driverConfig = { url: dbUrl };
|
|
1605
|
+
} else if (dbDriver === "mongodb") {
|
|
1606
|
+
driverId = "mongodb";
|
|
1607
|
+
driverConfig = { url: dbUrl };
|
|
1608
|
+
} else {
|
|
1609
|
+
driverId = "sqlite";
|
|
1610
|
+
const filename = dbUrl.replace(/^file:(\/\/)?/, "");
|
|
1611
|
+
if (!filename || /^[a-z][a-z0-9+.-]*:\/\//i.test(filename)) {
|
|
1612
|
+
throw new Error(
|
|
1613
|
+
`[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.`
|
|
1614
|
+
);
|
|
1615
|
+
}
|
|
1616
|
+
(0, import_node_fs2.mkdirSync)((0, import_node_path3.resolve)(filename, ".."), { recursive: true });
|
|
1617
|
+
driverConfig = { filename };
|
|
1618
|
+
}
|
|
1619
|
+
let driverHandle;
|
|
1620
|
+
try {
|
|
1621
|
+
driverHandle = await createDefaultDatasourceDriverFactory({ dev: factoryDev }).create({ driver: driverId, config: driverConfig });
|
|
1622
|
+
} catch (err) {
|
|
1623
|
+
if (dbDriver === "mongodb") {
|
|
1624
|
+
throw new Error(
|
|
1625
|
+
`[StandaloneStack] mongodb URL detected but @objectstack/driver-mongodb is not installed. Add it as a dependency or pass an explicit driverPlugin. (${err?.message ?? err})`
|
|
1626
|
+
);
|
|
1627
|
+
}
|
|
1628
|
+
throw err;
|
|
1593
1629
|
}
|
|
1594
|
-
(0, import_node_fs2.mkdirSync)((0, import_node_path3.resolve)(filename, ".."), { recursive: true });
|
|
1595
1630
|
driverPlugin = new DriverPlugin2(
|
|
1596
|
-
|
|
1597
|
-
client: "better-sqlite3",
|
|
1598
|
-
connection: { filename },
|
|
1599
|
-
useNullAsDefault: true
|
|
1600
|
-
})
|
|
1631
|
+
driverHandle?.driver ?? driverHandle
|
|
1601
1632
|
);
|
|
1602
1633
|
}
|
|
1603
1634
|
const artifactBundle = await loadArtifactBundle(artifactPath, {
|
|
@@ -1677,7 +1708,14 @@ var init_standalone_stack = __esm({
|
|
|
1677
1708
|
* Explicit `databaseUrl` / `OS_DATABASE_URL` / `OS_HOME` still take
|
|
1678
1709
|
* precedence over this default.
|
|
1679
1710
|
*/
|
|
1680
|
-
projectRoot: import_zod.z.string().optional()
|
|
1711
|
+
projectRoot: import_zod.z.string().optional(),
|
|
1712
|
+
/**
|
|
1713
|
+
* Dev gate for the sqlite driver factory's native-better-sqlite3 → wasm →
|
|
1714
|
+
* in-memory step-down (#2229). When omitted, defaults to
|
|
1715
|
+
* `process.env.NODE_ENV === 'development'`. In production a native load
|
|
1716
|
+
* failure is NOT silently swapped for wasm/mingo (fail-closed).
|
|
1717
|
+
*/
|
|
1718
|
+
dev: import_zod.z.boolean().optional()
|
|
1681
1719
|
});
|
|
1682
1720
|
}
|
|
1683
1721
|
});
|
|
@@ -2203,14 +2241,12 @@ async function resolveExecutionContext(opts) {
|
|
|
2203
2241
|
} else {
|
|
2204
2242
|
ctx.org_user_ids = [userId];
|
|
2205
2243
|
}
|
|
2206
|
-
const upsRows = await tryFind(
|
|
2207
|
-
ql,
|
|
2208
|
-
"sys_user_permission_set",
|
|
2209
|
-
tenantId ? { user_id: userId, organization_id: tenantId } : { user_id: userId },
|
|
2210
|
-
100
|
|
2211
|
-
);
|
|
2244
|
+
const upsRows = await tryFind(ql, "sys_user_permission_set", { user_id: userId }, 100);
|
|
2212
2245
|
const psIds = new Set(
|
|
2213
|
-
upsRows.
|
|
2246
|
+
upsRows.filter((r) => {
|
|
2247
|
+
const org = r.organization_id ?? r.organizationId ?? null;
|
|
2248
|
+
return !(org && tenantId && org !== tenantId);
|
|
2249
|
+
}).map((r) => r.permission_set_id ?? r.permissionSetId).filter(Boolean)
|
|
2214
2250
|
);
|
|
2215
2251
|
if (ctx.roles.length > 0) {
|
|
2216
2252
|
const roleRows = await tryFind(ql, "sys_role", { name: { $in: ctx.roles } }, 100);
|
|
@@ -2770,7 +2806,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
2770
2806
|
*/
|
|
2771
2807
|
async enforceProjectMembership(context, path) {
|
|
2772
2808
|
if (!this.enforceMembership) return null;
|
|
2773
|
-
const skipPaths = ["/auth", "/cloud", "/health", "/discovery"];
|
|
2809
|
+
const skipPaths = ["/auth", "/cloud", "/health", "/ready", "/discovery"];
|
|
2774
2810
|
if (skipPaths.some((p) => path.startsWith(p))) return null;
|
|
2775
2811
|
if (/(^|\/)share-links\/[^/]+\/(resolve|messages)$/.test(path)) return null;
|
|
2776
2812
|
const environmentId = context.environmentId;
|
|
@@ -4216,6 +4252,28 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
4216
4252
|
if (!ql || typeof ql.executeAction !== "function") {
|
|
4217
4253
|
return { handled: true, response: this.error("Data engine not available", 503) };
|
|
4218
4254
|
}
|
|
4255
|
+
try {
|
|
4256
|
+
const actionSchema = (typeof ql.getSchema === "function" ? ql.getSchema(objectName) : void 0) ?? ql.registry?.getObject?.(objectName);
|
|
4257
|
+
const actionDef = Array.isArray(actionSchema?.actions) ? actionSchema.actions.find((a) => a?.name === actionName) : void 0;
|
|
4258
|
+
const required = Array.isArray(actionDef?.requiredPermissions) ? actionDef.requiredPermissions : [];
|
|
4259
|
+
if (required.length > 0) {
|
|
4260
|
+
const execCtx = _context?.executionContext;
|
|
4261
|
+
if (!execCtx?.isSystem) {
|
|
4262
|
+
const held = new Set(execCtx?.systemPermissions ?? []);
|
|
4263
|
+
const missing = required.filter((perm) => !held.has(perm));
|
|
4264
|
+
if (missing.length > 0) {
|
|
4265
|
+
return {
|
|
4266
|
+
handled: true,
|
|
4267
|
+
response: this.error(
|
|
4268
|
+
`Action '${actionName}' on '${objectName}' requires capability [${required.join(", ")}] \u2014 caller is missing [${missing.join(", ")}]`,
|
|
4269
|
+
403
|
|
4270
|
+
)
|
|
4271
|
+
};
|
|
4272
|
+
}
|
|
4273
|
+
}
|
|
4274
|
+
}
|
|
4275
|
+
} catch {
|
|
4276
|
+
}
|
|
4219
4277
|
const tryExecute = async (obj) => {
|
|
4220
4278
|
return ql.executeAction(obj, actionName, actionContext);
|
|
4221
4279
|
};
|
|
@@ -4587,6 +4645,10 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
4587
4645
|
})
|
|
4588
4646
|
};
|
|
4589
4647
|
}
|
|
4648
|
+
if (cleanPath === "/ready" && method === "GET") {
|
|
4649
|
+
const state = typeof this.kernel?.getState === "function" ? this.kernel.getState() : "running";
|
|
4650
|
+
return state === "running" ? { handled: true, response: this.success({ status: "ready", state }) } : { handled: true, response: this.error("Service not ready", 503, { state }) };
|
|
4651
|
+
}
|
|
4590
4652
|
if (cleanPath.startsWith("/auth")) {
|
|
4591
4653
|
return this.handleAuth(cleanPath.substring(5), method, body, context);
|
|
4592
4654
|
}
|
|
@@ -5264,6 +5326,14 @@ function createDispatcherPlugin(config = {}) {
|
|
|
5264
5326
|
errorResponse(err, res);
|
|
5265
5327
|
}
|
|
5266
5328
|
});
|
|
5329
|
+
server.get(`${prefix}/ready`, async (_req, res) => {
|
|
5330
|
+
try {
|
|
5331
|
+
const result = await dispatcher.dispatch("GET", "/ready", void 0, {}, { request: _req });
|
|
5332
|
+
sendResult(result, res);
|
|
5333
|
+
} catch (err) {
|
|
5334
|
+
errorResponse(err, res);
|
|
5335
|
+
}
|
|
5336
|
+
});
|
|
5267
5337
|
server.post(`${prefix}/auth/login`, async (req, res) => {
|
|
5268
5338
|
try {
|
|
5269
5339
|
const result = await dispatcher.handleAuth("login", "POST", req.body, { request: req });
|