@objectstack/runtime 6.8.1 → 6.9.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 +151 -49
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +151 -49
- package/dist/index.js.map +1 -1
- package/package.json +18 -18
package/dist/index.cjs
CHANGED
|
@@ -1468,6 +1468,66 @@ var init_app_plugin = __esm({
|
|
|
1468
1468
|
} catch (err) {
|
|
1469
1469
|
ctx.logger.error("[AppPlugin] Failed to schedule approval-process registration", err, { appId });
|
|
1470
1470
|
}
|
|
1471
|
+
try {
|
|
1472
|
+
const jobs = Array.isArray(this.bundle.jobs) ? this.bundle.jobs : Array.isArray((this.bundle.manifest || {}).jobs) ? this.bundle.manifest.jobs : [];
|
|
1473
|
+
if (jobs.length > 0) {
|
|
1474
|
+
ctx.hook("kernel:ready", async () => {
|
|
1475
|
+
let svc;
|
|
1476
|
+
try {
|
|
1477
|
+
svc = ctx.getService("job");
|
|
1478
|
+
} catch {
|
|
1479
|
+
}
|
|
1480
|
+
if (!svc || typeof svc.schedule !== "function") {
|
|
1481
|
+
ctx.logger.warn("[AppPlugin] job service not registered \u2014 skipping declarative jobs", {
|
|
1482
|
+
appId,
|
|
1483
|
+
jobCount: jobs.length
|
|
1484
|
+
});
|
|
1485
|
+
return;
|
|
1486
|
+
}
|
|
1487
|
+
const fnMap = collectBundleFunctions(this.bundle);
|
|
1488
|
+
let ok = 0;
|
|
1489
|
+
for (const job of jobs) {
|
|
1490
|
+
const jobName = job?.name;
|
|
1491
|
+
if (!jobName) {
|
|
1492
|
+
ctx.logger.warn("[AppPlugin] skipping job without name", { appId, job });
|
|
1493
|
+
continue;
|
|
1494
|
+
}
|
|
1495
|
+
if (job.enabled === false) {
|
|
1496
|
+
ctx.logger.debug("[AppPlugin] job disabled \u2014 skipping", { appId, job: jobName });
|
|
1497
|
+
continue;
|
|
1498
|
+
}
|
|
1499
|
+
const handler = fnMap[job.handler];
|
|
1500
|
+
if (typeof handler !== "function") {
|
|
1501
|
+
ctx.logger.warn("[AppPlugin] job handler not found in bundle.functions \u2014 skipping", {
|
|
1502
|
+
appId,
|
|
1503
|
+
job: jobName,
|
|
1504
|
+
handler: job.handler
|
|
1505
|
+
});
|
|
1506
|
+
continue;
|
|
1507
|
+
}
|
|
1508
|
+
try {
|
|
1509
|
+
await svc.schedule(
|
|
1510
|
+
jobName,
|
|
1511
|
+
job.schedule,
|
|
1512
|
+
async (jobCtx) => {
|
|
1513
|
+
await handler({ ...jobCtx, jobId: jobName, bundle: this.bundle });
|
|
1514
|
+
}
|
|
1515
|
+
);
|
|
1516
|
+
ok++;
|
|
1517
|
+
} catch (err) {
|
|
1518
|
+
ctx.logger.warn("[AppPlugin] Failed to schedule job", {
|
|
1519
|
+
appId,
|
|
1520
|
+
job: jobName,
|
|
1521
|
+
error: err?.message ?? String(err)
|
|
1522
|
+
});
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
ctx.logger.info("[AppPlugin] Scheduled background jobs", { appId, count: ok });
|
|
1526
|
+
});
|
|
1527
|
+
}
|
|
1528
|
+
} catch (err) {
|
|
1529
|
+
ctx.logger.error("[AppPlugin] Failed to schedule background-job registration", err, { appId });
|
|
1530
|
+
}
|
|
1471
1531
|
this.emitCatalogEvent(ctx, "app:registered", sys);
|
|
1472
1532
|
await this.loadTranslations(ctx, appId);
|
|
1473
1533
|
const seedDatasets = [];
|
|
@@ -1540,7 +1600,7 @@ var init_app_plugin = __esm({
|
|
|
1540
1600
|
} catch (e) {
|
|
1541
1601
|
ctx.logger.warn("[Seeder] Failed to register seed-datasets/seed-replayer service", { error: e?.message });
|
|
1542
1602
|
}
|
|
1543
|
-
const multiTenant = String(process.env.OS_MULTI_TENANT ?? "
|
|
1603
|
+
const multiTenant = String(process.env.OS_MULTI_TENANT ?? "false").toLowerCase() !== "false";
|
|
1544
1604
|
if (multiTenant) {
|
|
1545
1605
|
ctx.logger.info("[Seeder] multi-tenant mode \u2014 skipping inline seed; per-org replay will run on sys_organization insert");
|
|
1546
1606
|
} else {
|
|
@@ -1595,10 +1655,22 @@ var init_app_plugin = __esm({
|
|
|
1595
1655
|
};
|
|
1596
1656
|
this.bundle = bundle;
|
|
1597
1657
|
this.projectContext = projectContext;
|
|
1598
|
-
const sys = bundle
|
|
1599
|
-
const appId = sys
|
|
1658
|
+
const sys = bundle?.manifest || bundle;
|
|
1659
|
+
const appId = sys?.id || sys?.name;
|
|
1660
|
+
if (!appId) {
|
|
1661
|
+
const bundleKeys = bundle && typeof bundle === "object" ? Object.keys(bundle).slice(0, 20).join(",") : typeof bundle;
|
|
1662
|
+
const sysKeys = sys && typeof sys === "object" ? Object.keys(sys).slice(0, 20).join(",") : typeof sys;
|
|
1663
|
+
const ctxHint = projectContext ? ` projectContext=${JSON.stringify({
|
|
1664
|
+
environmentId: projectContext.environmentId,
|
|
1665
|
+
packageId: projectContext.packageId,
|
|
1666
|
+
source: projectContext.source
|
|
1667
|
+
})}` : "";
|
|
1668
|
+
throw new Error(
|
|
1669
|
+
`[AppPlugin] bundle is missing manifest.id and manifest.name \u2014 cannot register as a plugin. bundleKeys=[${bundleKeys}] sysKeys=[${sysKeys}]${ctxHint}`
|
|
1670
|
+
);
|
|
1671
|
+
}
|
|
1600
1672
|
this.name = `plugin.app.${appId}`;
|
|
1601
|
-
this.version = sys
|
|
1673
|
+
this.version = sys?.version;
|
|
1602
1674
|
}
|
|
1603
1675
|
/**
|
|
1604
1676
|
* Emit a kernel hook so the control-plane `AppCatalogService` can
|
|
@@ -2528,8 +2600,12 @@ async function resolveExecutionContext(opts) {
|
|
|
2528
2600
|
if (!userId) {
|
|
2529
2601
|
try {
|
|
2530
2602
|
const authService = await opts.getService("auth");
|
|
2603
|
+
let api = authService?.api;
|
|
2604
|
+
if (!api && typeof authService?.getApi === "function") {
|
|
2605
|
+
api = await authService.getApi();
|
|
2606
|
+
}
|
|
2531
2607
|
const headersInstance = toHeaders(headers);
|
|
2532
|
-
const sessionData = await
|
|
2608
|
+
const sessionData = await api?.getSession?.({ headers: headersInstance });
|
|
2533
2609
|
userId = sessionData?.user?.id ?? sessionData?.session?.userId;
|
|
2534
2610
|
tenantId = tenantId ?? sessionData?.session?.activeOrganizationId;
|
|
2535
2611
|
ctx.accessToken = sessionData?.session?.token ?? ctx.accessToken;
|
|
@@ -5374,7 +5450,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
5374
5450
|
* Handle AI service routes (/ai/chat, /ai/models, /ai/conversations, etc.)
|
|
5375
5451
|
* Resolves the AI service and its built-in route handlers, then dispatches.
|
|
5376
5452
|
*/
|
|
5377
|
-
async handleAI(subPath, method, body, query,
|
|
5453
|
+
async handleAI(subPath, method, body, query, context) {
|
|
5378
5454
|
let aiService;
|
|
5379
5455
|
try {
|
|
5380
5456
|
aiService = await this.resolveService("ai");
|
|
@@ -5418,7 +5494,23 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
5418
5494
|
if (route.method !== method) continue;
|
|
5419
5495
|
const params = matchRoute(route.path, fullPath);
|
|
5420
5496
|
if (params === null) continue;
|
|
5421
|
-
const
|
|
5497
|
+
const ec = context.executionContext;
|
|
5498
|
+
const user = ec?.userId ? {
|
|
5499
|
+
userId: ec.userId,
|
|
5500
|
+
id: ec.userId,
|
|
5501
|
+
displayName: ec.userDisplayName ?? ec.userName ?? ec.userId,
|
|
5502
|
+
email: ec.userEmail,
|
|
5503
|
+
roles: Array.isArray(ec.roles) ? ec.roles : [],
|
|
5504
|
+
permissions: Array.isArray(ec.permissions) ? ec.permissions : [],
|
|
5505
|
+
organizationId: ec.tenantId
|
|
5506
|
+
} : void 0;
|
|
5507
|
+
const result = await route.handler({
|
|
5508
|
+
body,
|
|
5509
|
+
params,
|
|
5510
|
+
query,
|
|
5511
|
+
headers: context.request?.headers,
|
|
5512
|
+
user
|
|
5513
|
+
});
|
|
5422
5514
|
if (result.stream && result.events) {
|
|
5423
5515
|
return {
|
|
5424
5516
|
handled: true,
|
|
@@ -5913,13 +6005,22 @@ function resolveErrorReporter(ctx, override) {
|
|
|
5913
6005
|
}
|
|
5914
6006
|
|
|
5915
6007
|
// src/dispatcher-plugin.ts
|
|
5916
|
-
function mountRouteOnServer(route, server, routePath, securityHeaders) {
|
|
6008
|
+
function mountRouteOnServer(route, server, routePath, securityHeaders, resolveUser) {
|
|
5917
6009
|
const handler = async (req, res) => {
|
|
5918
6010
|
try {
|
|
6011
|
+
let user;
|
|
6012
|
+
if (resolveUser) {
|
|
6013
|
+
try {
|
|
6014
|
+
user = await resolveUser(req.headers ?? {});
|
|
6015
|
+
} catch {
|
|
6016
|
+
}
|
|
6017
|
+
}
|
|
5919
6018
|
const result = await route.handler({
|
|
5920
6019
|
body: req.body,
|
|
5921
6020
|
params: req.params,
|
|
5922
|
-
query: req.query
|
|
6021
|
+
query: req.query,
|
|
6022
|
+
headers: req.headers,
|
|
6023
|
+
user
|
|
5923
6024
|
});
|
|
5924
6025
|
if (result.stream && result.events) {
|
|
5925
6026
|
res.status(result.status);
|
|
@@ -6656,6 +6757,32 @@ function createDispatcherPlugin(config = {}) {
|
|
|
6656
6757
|
}
|
|
6657
6758
|
}
|
|
6658
6759
|
ctx.logger.info("Dispatcher bridge routes registered", { prefix, enableProjectScoping, projectResolution });
|
|
6760
|
+
const resolveRequestUser = async (headers) => {
|
|
6761
|
+
try {
|
|
6762
|
+
const authService = ctx.getService("auth");
|
|
6763
|
+
if (!authService) return void 0;
|
|
6764
|
+
let api = authService.api;
|
|
6765
|
+
if (!api && typeof authService.getApi === "function") {
|
|
6766
|
+
api = await authService.getApi();
|
|
6767
|
+
}
|
|
6768
|
+
if (!api?.getSession) return void 0;
|
|
6769
|
+
const headersInstance = headers instanceof Headers ? headers : new Headers(headers);
|
|
6770
|
+
const sessionData = await api.getSession({ headers: headersInstance });
|
|
6771
|
+
const userId = sessionData?.user?.id ?? sessionData?.session?.userId;
|
|
6772
|
+
if (!userId) return void 0;
|
|
6773
|
+
return {
|
|
6774
|
+
userId,
|
|
6775
|
+
id: userId,
|
|
6776
|
+
displayName: sessionData?.user?.name ?? sessionData?.user?.email ?? userId,
|
|
6777
|
+
email: sessionData?.user?.email,
|
|
6778
|
+
roles: [],
|
|
6779
|
+
permissions: [],
|
|
6780
|
+
organizationId: sessionData?.session?.activeOrganizationId
|
|
6781
|
+
};
|
|
6782
|
+
} catch {
|
|
6783
|
+
return void 0;
|
|
6784
|
+
}
|
|
6785
|
+
};
|
|
6659
6786
|
const toScopedPath = (routePath) => {
|
|
6660
6787
|
if (routePath.startsWith(prefix)) {
|
|
6661
6788
|
const tail = routePath.slice(prefix.length);
|
|
@@ -6668,11 +6795,11 @@ function createDispatcherPlugin(config = {}) {
|
|
|
6668
6795
|
const routePath = route.path.startsWith("/api/v1") ? route.path : `${prefix}${route.path}`;
|
|
6669
6796
|
let count = 0;
|
|
6670
6797
|
if (enableProjectScoping && projectResolution === "required") {
|
|
6671
|
-
if (mountRouteOnServer(route, server, toScopedPath(routePath), securityHeaders)) count++;
|
|
6798
|
+
if (mountRouteOnServer(route, server, toScopedPath(routePath), securityHeaders, resolveRequestUser)) count++;
|
|
6672
6799
|
} else {
|
|
6673
|
-
if (mountRouteOnServer(route, server, routePath, securityHeaders)) count++;
|
|
6800
|
+
if (mountRouteOnServer(route, server, routePath, securityHeaders, resolveRequestUser)) count++;
|
|
6674
6801
|
if (enableProjectScoping) {
|
|
6675
|
-
if (mountRouteOnServer(route, server, toScopedPath(routePath), securityHeaders)) count++;
|
|
6802
|
+
if (mountRouteOnServer(route, server, toScopedPath(routePath), securityHeaders, resolveRequestUser)) count++;
|
|
6676
6803
|
}
|
|
6677
6804
|
}
|
|
6678
6805
|
return count;
|
|
@@ -7734,39 +7861,7 @@ var ArtifactKernelFactory = class {
|
|
|
7734
7861
|
// intentionally do NOT pass crossSubDomainCookies here
|
|
7735
7862
|
// so cookies stay isolated per project subdomain.
|
|
7736
7863
|
trustedOrigins: trustedOriginsList.length ? trustedOriginsList : void 0,
|
|
7737
|
-
...oidcProviders ? { oidcProviders } : {}
|
|
7738
|
-
// Auto-provision a personal organization for every new
|
|
7739
|
-
// user. SecurityPlugin's ObjectQL middleware does this
|
|
7740
|
-
// for direct `ql.insert` calls, but better-auth's
|
|
7741
|
-
// adapter writes through `dataEngine` directly,
|
|
7742
|
-
// bypassing that middleware — so JIT-created SSO users
|
|
7743
|
-
// would otherwise land on the empty "create
|
|
7744
|
-
// organization" screen on first login.
|
|
7745
|
-
databaseHooks: {
|
|
7746
|
-
user: {
|
|
7747
|
-
create: {
|
|
7748
|
-
after: async (user) => {
|
|
7749
|
-
try {
|
|
7750
|
-
const ql = kernel.getService("objectql");
|
|
7751
|
-
if (!ql) return;
|
|
7752
|
-
const [{ ensureUserHasOrganization, cloneTenantSeedData }] = await Promise.all([
|
|
7753
|
-
import("@objectstack/plugin-security")
|
|
7754
|
-
]);
|
|
7755
|
-
await ensureUserHasOrganization(ql, user, {
|
|
7756
|
-
logger: this.logger,
|
|
7757
|
-
cloneSeedData: cloneTenantSeedData
|
|
7758
|
-
});
|
|
7759
|
-
} catch (e) {
|
|
7760
|
-
this.logger.warn?.("[ArtifactKernelFactory] auto-org provisioning hook failed", {
|
|
7761
|
-
environmentId,
|
|
7762
|
-
userId: user?.id,
|
|
7763
|
-
error: e?.message
|
|
7764
|
-
});
|
|
7765
|
-
}
|
|
7766
|
-
}
|
|
7767
|
-
}
|
|
7768
|
-
}
|
|
7769
|
-
}
|
|
7864
|
+
...oidcProviders ? { oidcProviders } : {}
|
|
7770
7865
|
}));
|
|
7771
7866
|
if (oidcProviders) {
|
|
7772
7867
|
this.logger.info?.("[ArtifactKernelFactory] platform SSO wired", {
|
|
@@ -7785,7 +7880,7 @@ var ArtifactKernelFactory = class {
|
|
|
7785
7880
|
}
|
|
7786
7881
|
try {
|
|
7787
7882
|
const { SecurityPlugin } = await import("@objectstack/plugin-security");
|
|
7788
|
-
const multiTenant = String(process.env.OS_MULTI_TENANT ?? "
|
|
7883
|
+
const multiTenant = String(process.env.OS_MULTI_TENANT ?? "false").toLowerCase() !== "false";
|
|
7789
7884
|
await kernel.use(new SecurityPlugin({ multiTenant }));
|
|
7790
7885
|
} catch (err) {
|
|
7791
7886
|
this.logger.warn?.("[ArtifactKernelFactory] SecurityPlugin not registered", {
|
|
@@ -7794,8 +7889,15 @@ var ArtifactKernelFactory = class {
|
|
|
7794
7889
|
});
|
|
7795
7890
|
}
|
|
7796
7891
|
const projectName = project.hostname ?? environmentId;
|
|
7797
|
-
const
|
|
7798
|
-
const
|
|
7892
|
+
const artifactAny = artifact;
|
|
7893
|
+
const topLevelManifest = artifactAny?.manifest && typeof artifactAny.manifest === "object" ? artifactAny.manifest : null;
|
|
7894
|
+
const topLevelFunctions = Array.isArray(artifactAny?.functions) ? artifactAny.functions : [];
|
|
7895
|
+
const bundle = {
|
|
7896
|
+
...artifact.metadata ?? {},
|
|
7897
|
+
...topLevelManifest ? { manifest: topLevelManifest } : {},
|
|
7898
|
+
functions: topLevelFunctions
|
|
7899
|
+
};
|
|
7900
|
+
const sys = bundle.manifest ?? bundle;
|
|
7799
7901
|
const packageId = sys?.packageId ?? sys?.package_id ?? bundle?.packageId;
|
|
7800
7902
|
const i18nCfg = bundle?.i18n ?? sys?.i18n ?? {};
|
|
7801
7903
|
const trArr = Array.isArray(bundle?.translations) ? bundle.translations : Array.isArray(sys?.translations) ? sys.translations : [];
|
|
@@ -9063,7 +9165,7 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
9063
9165
|
}
|
|
9064
9166
|
}
|
|
9065
9167
|
if (opts.seedNow && datasets.length > 0) {
|
|
9066
|
-
const multiTenant = String(process.env.OS_MULTI_TENANT ?? "
|
|
9168
|
+
const multiTenant = String(process.env.OS_MULTI_TENANT ?? "false").toLowerCase() !== "false";
|
|
9067
9169
|
try {
|
|
9068
9170
|
const ql = ctx.getService("objectql");
|
|
9069
9171
|
let metadata;
|