@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.d.cts
CHANGED
|
@@ -98,6 +98,7 @@ declare const StandaloneStackConfigSchema: z.ZodObject<{
|
|
|
98
98
|
environmentId: z.ZodOptional<z.ZodString>;
|
|
99
99
|
artifactPath: z.ZodOptional<z.ZodString>;
|
|
100
100
|
projectRoot: z.ZodOptional<z.ZodString>;
|
|
101
|
+
dev: z.ZodOptional<z.ZodBoolean>;
|
|
101
102
|
}, z.core.$strip>;
|
|
102
103
|
type StandaloneStackConfig = z.input<typeof StandaloneStackConfigSchema>;
|
|
103
104
|
interface StandaloneStackResult {
|
package/dist/index.d.ts
CHANGED
|
@@ -98,6 +98,7 @@ declare const StandaloneStackConfigSchema: z.ZodObject<{
|
|
|
98
98
|
environmentId: z.ZodOptional<z.ZodString>;
|
|
99
99
|
artifactPath: z.ZodOptional<z.ZodString>;
|
|
100
100
|
projectRoot: z.ZodOptional<z.ZodString>;
|
|
101
|
+
dev: z.ZodOptional<z.ZodBoolean>;
|
|
101
102
|
}, z.core.$strip>;
|
|
102
103
|
type StandaloneStackConfig = z.input<typeof StandaloneStackConfigSchema>;
|
|
103
104
|
interface StandaloneStackResult {
|
package/dist/index.js
CHANGED
|
@@ -963,6 +963,36 @@ 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
|
+
}
|
|
966
996
|
try {
|
|
967
997
|
const metadata = ctx.getService("metadata");
|
|
968
998
|
if (typeof metadata?.registerInMemory === "function") {
|
|
@@ -1530,55 +1560,56 @@ async function createStandaloneStack(config) {
|
|
|
1530
1560
|
const explicitDriver = cfg.databaseDriver ?? process.env.OS_DATABASE_DRIVER?.trim();
|
|
1531
1561
|
const dbDriver = explicitDriver ?? detectDriverFromUrl(dbUrl);
|
|
1532
1562
|
let driverPlugin;
|
|
1533
|
-
if (dbDriver === "
|
|
1534
|
-
const { InMemoryDriver } = await import("@objectstack/driver-memory");
|
|
1535
|
-
driverPlugin = new DriverPlugin2(new InMemoryDriver());
|
|
1536
|
-
} else if (dbDriver === "postgres") {
|
|
1537
|
-
const { SqlDriver } = await import("@objectstack/driver-sql");
|
|
1538
|
-
driverPlugin = new DriverPlugin2(
|
|
1539
|
-
new SqlDriver({
|
|
1540
|
-
client: "pg",
|
|
1541
|
-
connection: dbUrl,
|
|
1542
|
-
pool: { min: 0, max: 5 }
|
|
1543
|
-
})
|
|
1544
|
-
);
|
|
1545
|
-
} else if (dbDriver === "mongodb") {
|
|
1546
|
-
let MongoDBDriver;
|
|
1547
|
-
try {
|
|
1548
|
-
({ MongoDBDriver } = await import("@objectstack/driver-mongodb"));
|
|
1549
|
-
} catch (err) {
|
|
1550
|
-
throw new Error(
|
|
1551
|
-
`[StandaloneStack] mongodb URL detected but @objectstack/driver-mongodb is not installed. Add it as a dependency or pass an explicit driverPlugin. (${err?.message ?? err})`
|
|
1552
|
-
);
|
|
1553
|
-
}
|
|
1554
|
-
driverPlugin = new DriverPlugin2(new MongoDBDriver({ url: dbUrl }));
|
|
1555
|
-
} else if (dbDriver === "sqlite-wasm") {
|
|
1563
|
+
if (dbDriver === "sqlite-wasm") {
|
|
1556
1564
|
const { SqliteWasmDriver } = await import("@objectstack/driver-sqlite-wasm");
|
|
1557
|
-
const filename = dbUrl.replace(/^wasm-sqlite:(\/\/)?/i, "").replace(/^file:(\/\/)?/i, "");
|
|
1558
|
-
if (filename
|
|
1565
|
+
const filename = dbUrl.replace(/^wasm-sqlite:(\/\/)?/i, "").replace(/^file:(\/\/)?/i, "") || ":memory:";
|
|
1566
|
+
if (filename !== ":memory:") {
|
|
1559
1567
|
mkdirSync2(resolvePath2(filename, ".."), { recursive: true });
|
|
1560
1568
|
}
|
|
1561
1569
|
driverPlugin = new DriverPlugin2(
|
|
1562
1570
|
new SqliteWasmDriver({
|
|
1563
|
-
filename
|
|
1564
|
-
persist: filename
|
|
1571
|
+
filename,
|
|
1572
|
+
persist: filename !== ":memory:" ? "on-write" : void 0
|
|
1565
1573
|
})
|
|
1566
1574
|
);
|
|
1567
1575
|
} else {
|
|
1568
|
-
const {
|
|
1569
|
-
const
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1576
|
+
const { createDefaultDatasourceDriverFactory } = await import("@objectstack/service-datasource");
|
|
1577
|
+
const factoryDev = cfg.dev ?? process.env.NODE_ENV === "development";
|
|
1578
|
+
let driverId;
|
|
1579
|
+
let driverConfig;
|
|
1580
|
+
if (dbDriver === "memory") {
|
|
1581
|
+
driverId = "memory";
|
|
1582
|
+
driverConfig = {};
|
|
1583
|
+
} else if (dbDriver === "postgres") {
|
|
1584
|
+
driverId = "postgres";
|
|
1585
|
+
driverConfig = { url: dbUrl };
|
|
1586
|
+
} else if (dbDriver === "mongodb") {
|
|
1587
|
+
driverId = "mongodb";
|
|
1588
|
+
driverConfig = { url: dbUrl };
|
|
1589
|
+
} else {
|
|
1590
|
+
driverId = "sqlite";
|
|
1591
|
+
const filename = dbUrl.replace(/^file:(\/\/)?/, "");
|
|
1592
|
+
if (!filename || /^[a-z][a-z0-9+.-]*:\/\//i.test(filename)) {
|
|
1593
|
+
throw new Error(
|
|
1594
|
+
`[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.`
|
|
1595
|
+
);
|
|
1596
|
+
}
|
|
1597
|
+
mkdirSync2(resolvePath2(filename, ".."), { recursive: true });
|
|
1598
|
+
driverConfig = { filename };
|
|
1599
|
+
}
|
|
1600
|
+
let driverHandle;
|
|
1601
|
+
try {
|
|
1602
|
+
driverHandle = await createDefaultDatasourceDriverFactory({ dev: factoryDev }).create({ driver: driverId, config: driverConfig });
|
|
1603
|
+
} catch (err) {
|
|
1604
|
+
if (dbDriver === "mongodb") {
|
|
1605
|
+
throw new Error(
|
|
1606
|
+
`[StandaloneStack] mongodb URL detected but @objectstack/driver-mongodb is not installed. Add it as a dependency or pass an explicit driverPlugin. (${err?.message ?? err})`
|
|
1607
|
+
);
|
|
1608
|
+
}
|
|
1609
|
+
throw err;
|
|
1574
1610
|
}
|
|
1575
|
-
mkdirSync2(resolvePath2(filename, ".."), { recursive: true });
|
|
1576
1611
|
driverPlugin = new DriverPlugin2(
|
|
1577
|
-
|
|
1578
|
-
client: "better-sqlite3",
|
|
1579
|
-
connection: { filename },
|
|
1580
|
-
useNullAsDefault: true
|
|
1581
|
-
})
|
|
1612
|
+
driverHandle?.driver ?? driverHandle
|
|
1582
1613
|
);
|
|
1583
1614
|
}
|
|
1584
1615
|
const artifactBundle = await loadArtifactBundle(artifactPath, {
|
|
@@ -1653,7 +1684,14 @@ var init_standalone_stack = __esm({
|
|
|
1653
1684
|
* Explicit `databaseUrl` / `OS_DATABASE_URL` / `OS_HOME` still take
|
|
1654
1685
|
* precedence over this default.
|
|
1655
1686
|
*/
|
|
1656
|
-
projectRoot: z.string().optional()
|
|
1687
|
+
projectRoot: z.string().optional(),
|
|
1688
|
+
/**
|
|
1689
|
+
* Dev gate for the sqlite driver factory's native-better-sqlite3 → wasm →
|
|
1690
|
+
* in-memory step-down (#2229). When omitted, defaults to
|
|
1691
|
+
* `process.env.NODE_ENV === 'development'`. In production a native load
|
|
1692
|
+
* failure is NOT silently swapped for wasm/mingo (fail-closed).
|
|
1693
|
+
*/
|
|
1694
|
+
dev: z.boolean().optional()
|
|
1657
1695
|
});
|
|
1658
1696
|
}
|
|
1659
1697
|
});
|
|
@@ -2135,14 +2173,12 @@ async function resolveExecutionContext(opts) {
|
|
|
2135
2173
|
} else {
|
|
2136
2174
|
ctx.org_user_ids = [userId];
|
|
2137
2175
|
}
|
|
2138
|
-
const upsRows = await tryFind(
|
|
2139
|
-
ql,
|
|
2140
|
-
"sys_user_permission_set",
|
|
2141
|
-
tenantId ? { user_id: userId, organization_id: tenantId } : { user_id: userId },
|
|
2142
|
-
100
|
|
2143
|
-
);
|
|
2176
|
+
const upsRows = await tryFind(ql, "sys_user_permission_set", { user_id: userId }, 100);
|
|
2144
2177
|
const psIds = new Set(
|
|
2145
|
-
upsRows.
|
|
2178
|
+
upsRows.filter((r) => {
|
|
2179
|
+
const org = r.organization_id ?? r.organizationId ?? null;
|
|
2180
|
+
return !(org && tenantId && org !== tenantId);
|
|
2181
|
+
}).map((r) => r.permission_set_id ?? r.permissionSetId).filter(Boolean)
|
|
2146
2182
|
);
|
|
2147
2183
|
if (ctx.roles.length > 0) {
|
|
2148
2184
|
const roleRows = await tryFind(ql, "sys_role", { name: { $in: ctx.roles } }, 100);
|
|
@@ -2702,7 +2738,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
2702
2738
|
*/
|
|
2703
2739
|
async enforceProjectMembership(context, path) {
|
|
2704
2740
|
if (!this.enforceMembership) return null;
|
|
2705
|
-
const skipPaths = ["/auth", "/cloud", "/health", "/discovery"];
|
|
2741
|
+
const skipPaths = ["/auth", "/cloud", "/health", "/ready", "/discovery"];
|
|
2706
2742
|
if (skipPaths.some((p) => path.startsWith(p))) return null;
|
|
2707
2743
|
if (/(^|\/)share-links\/[^/]+\/(resolve|messages)$/.test(path)) return null;
|
|
2708
2744
|
const environmentId = context.environmentId;
|
|
@@ -4148,6 +4184,28 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
4148
4184
|
if (!ql || typeof ql.executeAction !== "function") {
|
|
4149
4185
|
return { handled: true, response: this.error("Data engine not available", 503) };
|
|
4150
4186
|
}
|
|
4187
|
+
try {
|
|
4188
|
+
const actionSchema = (typeof ql.getSchema === "function" ? ql.getSchema(objectName) : void 0) ?? ql.registry?.getObject?.(objectName);
|
|
4189
|
+
const actionDef = Array.isArray(actionSchema?.actions) ? actionSchema.actions.find((a) => a?.name === actionName) : void 0;
|
|
4190
|
+
const required = Array.isArray(actionDef?.requiredPermissions) ? actionDef.requiredPermissions : [];
|
|
4191
|
+
if (required.length > 0) {
|
|
4192
|
+
const execCtx = _context?.executionContext;
|
|
4193
|
+
if (!execCtx?.isSystem) {
|
|
4194
|
+
const held = new Set(execCtx?.systemPermissions ?? []);
|
|
4195
|
+
const missing = required.filter((perm) => !held.has(perm));
|
|
4196
|
+
if (missing.length > 0) {
|
|
4197
|
+
return {
|
|
4198
|
+
handled: true,
|
|
4199
|
+
response: this.error(
|
|
4200
|
+
`Action '${actionName}' on '${objectName}' requires capability [${required.join(", ")}] \u2014 caller is missing [${missing.join(", ")}]`,
|
|
4201
|
+
403
|
|
4202
|
+
)
|
|
4203
|
+
};
|
|
4204
|
+
}
|
|
4205
|
+
}
|
|
4206
|
+
}
|
|
4207
|
+
} catch {
|
|
4208
|
+
}
|
|
4151
4209
|
const tryExecute = async (obj) => {
|
|
4152
4210
|
return ql.executeAction(obj, actionName, actionContext);
|
|
4153
4211
|
};
|
|
@@ -4519,6 +4577,10 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
4519
4577
|
})
|
|
4520
4578
|
};
|
|
4521
4579
|
}
|
|
4580
|
+
if (cleanPath === "/ready" && method === "GET") {
|
|
4581
|
+
const state = typeof this.kernel?.getState === "function" ? this.kernel.getState() : "running";
|
|
4582
|
+
return state === "running" ? { handled: true, response: this.success({ status: "ready", state }) } : { handled: true, response: this.error("Service not ready", 503, { state }) };
|
|
4583
|
+
}
|
|
4522
4584
|
if (cleanPath.startsWith("/auth")) {
|
|
4523
4585
|
return this.handleAuth(cleanPath.substring(5), method, body, context);
|
|
4524
4586
|
}
|
|
@@ -5206,6 +5268,14 @@ function createDispatcherPlugin(config = {}) {
|
|
|
5206
5268
|
errorResponse(err, res);
|
|
5207
5269
|
}
|
|
5208
5270
|
});
|
|
5271
|
+
server.get(`${prefix}/ready`, async (_req, res) => {
|
|
5272
|
+
try {
|
|
5273
|
+
const result = await dispatcher.dispatch("GET", "/ready", void 0, {}, { request: _req });
|
|
5274
|
+
sendResult(result, res);
|
|
5275
|
+
} catch (err) {
|
|
5276
|
+
errorResponse(err, res);
|
|
5277
|
+
}
|
|
5278
|
+
});
|
|
5209
5279
|
server.post(`${prefix}/auth/login`, async (req, res) => {
|
|
5210
5280
|
try {
|
|
5211
5281
|
const result = await dispatcher.handleAuth("login", "POST", req.body, { request: req });
|