@objectstack/runtime 5.1.0 → 5.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.js CHANGED
@@ -178,6 +178,10 @@ var init_driver_plugin = __esm({
178
178
  });
179
179
 
180
180
  // src/seed-loader.ts
181
+ var seed_loader_exports = {};
182
+ __export(seed_loader_exports, {
183
+ SeedLoaderService: () => SeedLoaderService
184
+ });
181
185
  import { SeedLoaderConfigSchema } from "@objectstack/spec/data";
182
186
  import { resolveSeedRecord } from "@objectstack/formula";
183
187
  var DEFAULT_EXTERNAL_ID_FIELD, _SeedLoaderService, SeedLoaderService;
@@ -915,7 +919,7 @@ var init_quickjs_runner = __esm({
915
919
  evalRes.value.dispose();
916
920
  let pumps = 0;
917
921
  while (pumps < 1e3) {
918
- await new Promise((resolve) => setImmediate(resolve));
922
+ await new Promise((resolve2) => setImmediate(resolve2));
919
923
  const pending = runtime.executePendingJobs();
920
924
  if (pending.error) {
921
925
  const err = vm.dump(pending.error);
@@ -9198,7 +9202,7 @@ var require_utils = __commonJS({
9198
9202
  }
9199
9203
  }
9200
9204
  function get(url, options = {}) {
9201
- return new Promise((resolve, reject) => {
9205
+ return new Promise((resolve2, reject) => {
9202
9206
  let timeoutId;
9203
9207
  const request = http.get(url, options, (response) => {
9204
9208
  response.setEncoding("utf8");
@@ -9206,7 +9210,7 @@ var require_utils = __commonJS({
9206
9210
  response.on("data", (chunk) => body += chunk);
9207
9211
  response.on("end", () => {
9208
9212
  (0, timers_1.clearTimeout)(timeoutId);
9209
- resolve({ status: response.statusCode, body });
9213
+ resolve2({ status: response.statusCode, body });
9210
9214
  });
9211
9215
  }).on("error", (error2) => {
9212
9216
  (0, timers_1.clearTimeout)(timeoutId);
@@ -9225,13 +9229,13 @@ var require_utils = __commonJS({
9225
9229
  return host && match.test(host.toLowerCase()) ? true : false;
9226
9230
  }
9227
9231
  function promiseWithResolvers() {
9228
- let resolve;
9232
+ let resolve2;
9229
9233
  let reject;
9230
9234
  const promise = new Promise(function withResolversExecutor(promiseResolve, promiseReject) {
9231
- resolve = promiseResolve;
9235
+ resolve2 = promiseResolve;
9232
9236
  reject = promiseReject;
9233
9237
  });
9234
- return { promise, resolve, reject };
9238
+ return { promise, resolve: resolve2, reject };
9235
9239
  }
9236
9240
  function squashError(_error) {
9237
9241
  return;
@@ -9242,8 +9246,8 @@ var require_utils = __commonJS({
9242
9246
  exports.randomBytes = randomBytes;
9243
9247
  async function once(ee, name, options) {
9244
9248
  options?.signal?.throwIfAborted();
9245
- const { promise, resolve, reject } = promiseWithResolvers();
9246
- const onEvent = (data) => resolve(data);
9249
+ const { promise, resolve: resolve2, reject } = promiseWithResolvers();
9250
+ const onEvent = (data) => resolve2(data);
9247
9251
  const onError = (error2) => reject(error2);
9248
9252
  const abortListener = addAbortListener(options?.signal, function() {
9249
9253
  reject(this.reason);
@@ -11844,13 +11848,13 @@ var require_mongo_logger = __commonJS({
11844
11848
  function createStdioLogger(stream) {
11845
11849
  return {
11846
11850
  write: (log) => {
11847
- return new Promise((resolve, reject) => {
11851
+ return new Promise((resolve2, reject) => {
11848
11852
  const logLine = (0, util_1.inspect)(log, { compact: true, breakLength: Infinity });
11849
11853
  stream.write(`${logLine}
11850
11854
  `, "utf-8", (error2) => {
11851
11855
  if (error2)
11852
11856
  return reject(error2);
11853
- resolve(true);
11857
+ resolve2(true);
11854
11858
  });
11855
11859
  });
11856
11860
  }
@@ -22625,20 +22629,20 @@ var require_compression = __commonJS({
22625
22629
  ]);
22626
22630
  var ZSTD_COMPRESSION_LEVEL = 3;
22627
22631
  var zlibInflate = (buf) => {
22628
- return new Promise((resolve, reject) => {
22632
+ return new Promise((resolve2, reject) => {
22629
22633
  zlib.inflate(buf, (error2, result) => {
22630
22634
  if (error2)
22631
22635
  return reject(error2);
22632
- resolve(result);
22636
+ resolve2(result);
22633
22637
  });
22634
22638
  });
22635
22639
  };
22636
22640
  var zlibDeflate = (buf, options) => {
22637
- return new Promise((resolve, reject) => {
22641
+ return new Promise((resolve2, reject) => {
22638
22642
  zlib.deflate(buf, options, (error2, result) => {
22639
22643
  if (error2)
22640
22644
  return reject(error2);
22641
- resolve(result);
22645
+ resolve2(result);
22642
22646
  });
22643
22647
  });
22644
22648
  };
@@ -23359,7 +23363,7 @@ var require_state_machine = __commonJS({
23359
23363
  socket = tls.connect(socketOptions, () => {
23360
23364
  socket.write(message);
23361
23365
  });
23362
- const { promise: willResolveKmsRequest, reject: rejectOnTlsSocketError, resolve } = (0, utils_1.promiseWithResolvers)();
23366
+ const { promise: willResolveKmsRequest, reject: rejectOnTlsSocketError, resolve: resolve2 } = (0, utils_1.promiseWithResolvers)();
23363
23367
  abortListener = (0, utils_1.addAbortListener)(options?.signal, function() {
23364
23368
  destroySockets();
23365
23369
  rejectOnTlsSocketError(this.reason);
@@ -23371,7 +23375,7 @@ var require_state_machine = __commonJS({
23371
23375
  request.addResponse(buffer.read(bytesNeeded));
23372
23376
  }
23373
23377
  if (request.bytesNeeded <= 0) {
23374
- resolve();
23378
+ resolve2();
23375
23379
  }
23376
23380
  });
23377
23381
  await (options?.timeoutContext?.csotEnabled() ? Promise.all([
@@ -24952,8 +24956,8 @@ var require_on_data = __commonJS({
24952
24956
  }
24953
24957
  if (finished)
24954
24958
  return closeHandler();
24955
- const { promise, resolve, reject } = (0, utils_1.promiseWithResolvers)();
24956
- unconsumedPromises.push({ resolve, reject });
24959
+ const { promise, resolve: resolve2, reject } = (0, utils_1.promiseWithResolvers)();
24960
+ unconsumedPromises.push({ resolve: resolve2, reject });
24957
24961
  return promise;
24958
24962
  },
24959
24963
  return() {
@@ -26120,13 +26124,13 @@ var require_connect = __commonJS({
26120
26124
  socket.setNoDelay(noDelay);
26121
26125
  socket.setTimeout(connectTimeoutMS);
26122
26126
  let cancellationHandler = null;
26123
- const { promise: connectedSocket, resolve, reject } = (0, utils_1.promiseWithResolvers)();
26127
+ const { promise: connectedSocket, resolve: resolve2, reject } = (0, utils_1.promiseWithResolvers)();
26124
26128
  if (existingSocket) {
26125
- resolve(socket);
26129
+ resolve2(socket);
26126
26130
  } else {
26127
26131
  const start = performance.now();
26128
26132
  const connectEvent = useTLS ? "secureConnect" : "connect";
26129
- socket.once(connectEvent, () => resolve(socket)).once("error", (cause) => reject(new error_1.MongoNetworkError(error_1.MongoError.buildErrorMessage(cause), { cause }))).once("timeout", () => {
26133
+ socket.once(connectEvent, () => resolve2(socket)).once("error", (cause) => reject(new error_1.MongoNetworkError(error_1.MongoError.buildErrorMessage(cause), { cause }))).once("timeout", () => {
26130
26134
  reject(new error_1.MongoNetworkTimeoutError(`Socket '${connectEvent}' timed out after ${performance.now() - start | 0}ms (connectTimeoutMS: ${connectTimeoutMS})`));
26131
26135
  }).once("close", () => reject(new error_1.MongoNetworkError(`Socket closed after ${performance.now() - start | 0} during connection establishment`)));
26132
26136
  if (options.cancellationToken != null) {
@@ -26650,10 +26654,10 @@ var require_connection_pool = __commonJS({
26650
26654
  async checkOut(options) {
26651
26655
  const checkoutTime = (0, utils_1.processTimeMS)();
26652
26656
  this.emitAndLog(_ConnectionPool.CONNECTION_CHECK_OUT_STARTED, new connection_pool_events_1.ConnectionCheckOutStartedEvent(this));
26653
- const { promise, resolve, reject } = (0, utils_1.promiseWithResolvers)();
26657
+ const { promise, resolve: resolve2, reject } = (0, utils_1.promiseWithResolvers)();
26654
26658
  const timeout = options.timeoutContext.connectionCheckoutTimeout;
26655
26659
  const waitQueueMember = {
26656
- resolve,
26660
+ resolve: resolve2,
26657
26661
  reject,
26658
26662
  cancelled: false,
26659
26663
  checkoutTime
@@ -27888,13 +27892,13 @@ var require_connection_string = __commonJS({
27888
27892
  var LB_REPLICA_SET_ERROR = "loadBalanced option not supported with a replicaSet option";
27889
27893
  var LB_DIRECT_CONNECTION_ERROR = "loadBalanced option not supported when directConnection is provided";
27890
27894
  function retryDNSTimeoutFor(rrtype) {
27891
- const resolve = rrtype === "SRV" ? (address) => dns.promises.resolve(address, "SRV") : (address) => dns.promises.resolve(address, "TXT");
27895
+ const resolve2 = rrtype === "SRV" ? (address) => dns.promises.resolve(address, "SRV") : (address) => dns.promises.resolve(address, "TXT");
27892
27896
  return async function dnsReqRetryTimeout(lookupAddress) {
27893
27897
  try {
27894
- return await resolve(lookupAddress);
27898
+ return await resolve2(lookupAddress);
27895
27899
  } catch (firstDNSError) {
27896
27900
  if (firstDNSError.code === dns.TIMEOUT) {
27897
- return await resolve(lookupAddress);
27901
+ return await resolve2(lookupAddress);
27898
27902
  } else {
27899
27903
  throw firstDNSError;
27900
27904
  }
@@ -31533,13 +31537,13 @@ var require_topology = __commonJS({
31533
31537
  }
31534
31538
  return transaction.server;
31535
31539
  }
31536
- const { promise: serverPromise, resolve, reject } = (0, utils_1.promiseWithResolvers)();
31540
+ const { promise: serverPromise, resolve: resolve2, reject } = (0, utils_1.promiseWithResolvers)();
31537
31541
  const waitQueueMember = {
31538
31542
  serverSelector,
31539
31543
  topologyDescription: this.description,
31540
31544
  mongoLogger: this.client.mongoLogger,
31541
31545
  transaction,
31542
- resolve,
31546
+ resolve: resolve2,
31543
31547
  reject,
31544
31548
  cancelled: false,
31545
31549
  startTime: (0, utils_1.processTimeMS)(),
@@ -35158,12 +35162,28 @@ import { ObjectKernel as ObjectKernel4 } from "@objectstack/core";
35158
35162
 
35159
35163
  // src/runtime.ts
35160
35164
  import { ObjectKernel } from "@objectstack/core";
35165
+ import {
35166
+ ClusterServicePlugin,
35167
+ MetadataClusterBridgePlugin
35168
+ } from "@objectstack/service-cluster";
35161
35169
  var Runtime = class {
35162
35170
  constructor(config = {}) {
35163
35171
  this.kernel = new ObjectKernel(config.kernel);
35164
35172
  if (config.server) {
35165
35173
  this.kernel.registerService("http.server", config.server);
35166
35174
  }
35175
+ if (config.cluster !== false) {
35176
+ const opts = this.normalizeClusterOptions(config.cluster);
35177
+ this.kernel.use(new ClusterServicePlugin(opts));
35178
+ this.kernel.use(new MetadataClusterBridgePlugin());
35179
+ }
35180
+ }
35181
+ normalizeClusterOptions(raw) {
35182
+ if (!raw) return {};
35183
+ if (typeof raw === "object" && ("cluster" in raw || "config" in raw) && !("driver" in raw)) {
35184
+ return raw;
35185
+ }
35186
+ return { config: raw };
35167
35187
  }
35168
35188
  /**
35169
35189
  * Register a plugin
@@ -35465,6 +35485,23 @@ async function resolveExecutionContext(opts) {
35465
35485
  }
35466
35486
  }
35467
35487
  }
35488
+ if (tenantId) {
35489
+ const orgMembers = await tryFind(
35490
+ ql,
35491
+ "sys_member",
35492
+ { organization_id: tenantId },
35493
+ 1e3
35494
+ );
35495
+ const orgUserIds = Array.from(
35496
+ new Set(
35497
+ orgMembers.map((m) => m.user_id ?? m.userId).filter((v) => typeof v === "string" && v.length > 0)
35498
+ )
35499
+ );
35500
+ if (!orgUserIds.includes(userId)) orgUserIds.push(userId);
35501
+ ctx.org_user_ids = orgUserIds;
35502
+ } else {
35503
+ ctx.org_user_ids = [userId];
35504
+ }
35468
35505
  const upsRows = await tryFind(
35469
35506
  ql,
35470
35507
  "sys_user_permission_set",
@@ -38739,6 +38776,47 @@ function safeReport(reporter, err, ctx) {
38739
38776
  }
38740
38777
  }
38741
38778
 
38779
+ // src/observability/observability-service-plugin.ts
38780
+ import {
38781
+ OBSERVABILITY_METRICS_SERVICE,
38782
+ OBSERVABILITY_ERRORS_SERVICE
38783
+ } from "@objectstack/observability";
38784
+ var ObservabilityServicePlugin = class {
38785
+ constructor(options = {}) {
38786
+ this.name = "com.objectstack.observability.service";
38787
+ this.version = "1.0.0";
38788
+ this.type = "standard";
38789
+ this.options = options;
38790
+ }
38791
+ async init(ctx) {
38792
+ const metrics = this.options.metrics ?? new NoopMetricsRegistry();
38793
+ const errors2 = this.options.errors ?? new NoopErrorReporter();
38794
+ ctx.registerService(OBSERVABILITY_METRICS_SERVICE, metrics);
38795
+ ctx.registerService(OBSERVABILITY_ERRORS_SERVICE, errors2);
38796
+ ctx.logger.info(
38797
+ `ObservabilityServicePlugin: registered metrics=${metrics.constructor?.name ?? "unknown"} errors=${errors2.constructor?.name ?? "unknown"}`
38798
+ );
38799
+ }
38800
+ };
38801
+ function resolveMetrics(ctx, override) {
38802
+ if (override) return override;
38803
+ try {
38804
+ const m = ctx.getService(OBSERVABILITY_METRICS_SERVICE);
38805
+ if (m) return m;
38806
+ } catch {
38807
+ }
38808
+ return new NoopMetricsRegistry();
38809
+ }
38810
+ function resolveErrorReporter(ctx, override) {
38811
+ if (override) return override;
38812
+ try {
38813
+ const e = ctx.getService(OBSERVABILITY_ERRORS_SERVICE);
38814
+ if (e) return e;
38815
+ } catch {
38816
+ }
38817
+ return new NoopErrorReporter();
38818
+ }
38819
+
38742
38820
  // src/dispatcher-plugin.ts
38743
38821
  function mountRouteOnServer(route, server, routePath, securityHeaders) {
38744
38822
  const handler = async (req, res) => {
@@ -40462,7 +40540,39 @@ var ArtifactKernelFactory = class {
40462
40540
  // intentionally do NOT pass crossSubDomainCookies here
40463
40541
  // so cookies stay isolated per project subdomain.
40464
40542
  trustedOrigins: trustedOriginsList.length ? trustedOriginsList : void 0,
40465
- ...oidcProviders ? { oidcProviders } : {}
40543
+ ...oidcProviders ? { oidcProviders } : {},
40544
+ // Auto-provision a personal organization for every new
40545
+ // user. SecurityPlugin's ObjectQL middleware does this
40546
+ // for direct `ql.insert` calls, but better-auth's
40547
+ // adapter writes through `dataEngine` directly,
40548
+ // bypassing that middleware — so JIT-created SSO users
40549
+ // would otherwise land on the empty "create
40550
+ // organization" screen on first login.
40551
+ databaseHooks: {
40552
+ user: {
40553
+ create: {
40554
+ after: async (user) => {
40555
+ try {
40556
+ const ql = kernel.getService("objectql");
40557
+ if (!ql) return;
40558
+ const [{ ensureUserHasOrganization, cloneTenantSeedData }] = await Promise.all([
40559
+ import("@objectstack/plugin-security")
40560
+ ]);
40561
+ await ensureUserHasOrganization(ql, user, {
40562
+ logger: this.logger,
40563
+ cloneSeedData: cloneTenantSeedData
40564
+ });
40565
+ } catch (e) {
40566
+ this.logger.warn?.("[ArtifactKernelFactory] auto-org provisioning hook failed", {
40567
+ projectId,
40568
+ userId: user?.id,
40569
+ error: e?.message
40570
+ });
40571
+ }
40572
+ }
40573
+ }
40574
+ }
40575
+ }
40466
40576
  }));
40467
40577
  if (oidcProviders) {
40468
40578
  this.logger.info?.("[ArtifactKernelFactory] platform SSO wired", {
@@ -40855,6 +40965,114 @@ var AuthProxyPlugin = class {
40855
40965
  }
40856
40966
  };
40857
40967
 
40968
+ // src/cloud/cloud-url.ts
40969
+ var DEFAULT_CLOUD_URL = "https://cloud.objectos.app";
40970
+ function resolveCloudUrl(explicit) {
40971
+ const raw = (explicit ?? process.env.OS_CLOUD_URL ?? "").trim();
40972
+ const lower = raw.toLowerCase();
40973
+ if (lower === "off" || lower === "none" || lower === "local" || lower === "disabled") {
40974
+ return "";
40975
+ }
40976
+ const picked = raw || DEFAULT_CLOUD_URL;
40977
+ return picked.replace(/\/+$/, "");
40978
+ }
40979
+
40980
+ // src/cloud/marketplace-proxy-plugin.ts
40981
+ var MARKETPLACE_PREFIX = "/api/v1/marketplace";
40982
+ var MarketplaceProxyPlugin = class _MarketplaceProxyPlugin {
40983
+ constructor(config = {}) {
40984
+ this.name = "com.objectstack.runtime.marketplace-proxy";
40985
+ this.version = "1.0.0";
40986
+ this.init = async (_ctx) => {
40987
+ };
40988
+ this.start = async (ctx) => {
40989
+ ctx.hook("kernel:ready", async () => {
40990
+ let httpServer;
40991
+ try {
40992
+ httpServer = ctx.getService("http-server");
40993
+ } catch {
40994
+ ctx.logger?.warn?.("[MarketplaceProxyPlugin] http-server not available \u2014 marketplace routes not mounted");
40995
+ return;
40996
+ }
40997
+ if (!httpServer || typeof httpServer.getRawApp !== "function") {
40998
+ ctx.logger?.warn?.("[MarketplaceProxyPlugin] http-server missing getRawApp() \u2014 marketplace routes not mounted");
40999
+ return;
41000
+ }
41001
+ const rawApp = httpServer.getRawApp();
41002
+ const cloudUrl = this.cloudUrl;
41003
+ const handler = async (c, next) => {
41004
+ if (!cloudUrl) {
41005
+ return c.json({
41006
+ success: false,
41007
+ error: {
41008
+ code: "marketplace_unavailable",
41009
+ message: "No control-plane URL configured for this runtime (OS_CLOUD_URL)."
41010
+ }
41011
+ }, 503);
41012
+ }
41013
+ try {
41014
+ const incomingUrl = new URL(c.req.url);
41015
+ if (incomingUrl.pathname.startsWith(`${MARKETPLACE_PREFIX}/install-local`)) {
41016
+ return next();
41017
+ }
41018
+ const target = `${cloudUrl}${incomingUrl.pathname}${incomingUrl.search}`;
41019
+ const method = String(c.req.method ?? "GET").toUpperCase();
41020
+ if (method !== "GET" && method !== "HEAD") {
41021
+ return c.json({
41022
+ success: false,
41023
+ error: {
41024
+ code: "marketplace_method_not_allowed",
41025
+ message: `Marketplace proxy only forwards GET/HEAD; install via cloud.`
41026
+ }
41027
+ }, 405);
41028
+ }
41029
+ const resp = await fetch(target, {
41030
+ method,
41031
+ headers: {
41032
+ // Strip the inbound Host header — fetch will set
41033
+ // it to the cloud host. Forward only the
41034
+ // identifying headers cloud might log.
41035
+ "Accept": c.req.header("accept") ?? "application/json",
41036
+ "User-Agent": `objectos-marketplace-proxy/${_MarketplaceProxyPlugin.prototype.version ?? "1.0.0"}`
41037
+ }
41038
+ });
41039
+ const headers = new Headers();
41040
+ const passthroughHeaders = ["content-type", "cache-control", "etag", "last-modified"];
41041
+ for (const h of passthroughHeaders) {
41042
+ const v = resp.headers.get(h);
41043
+ if (v) headers.set(h, v);
41044
+ }
41045
+ const body = await resp.arrayBuffer();
41046
+ return new Response(body, { status: resp.status, headers });
41047
+ } catch (err) {
41048
+ const errObj = err instanceof Error ? err : new Error(err?.message ?? String(err));
41049
+ ctx.logger?.error?.("[MarketplaceProxyPlugin] proxy failed", errObj);
41050
+ return c.json({
41051
+ success: false,
41052
+ error: {
41053
+ code: "marketplace_proxy_failed",
41054
+ message: err?.message ?? String(err)
41055
+ }
41056
+ }, 502);
41057
+ }
41058
+ };
41059
+ if (typeof rawApp.all === "function") {
41060
+ rawApp.all(`${MARKETPLACE_PREFIX}/*`, handler);
41061
+ } else {
41062
+ for (const m of ["get", "head"]) {
41063
+ try {
41064
+ rawApp[m]?.(`${MARKETPLACE_PREFIX}/*`, handler);
41065
+ } catch {
41066
+ }
41067
+ }
41068
+ }
41069
+ ctx.logger?.info?.(`[MarketplaceProxyPlugin] mounted at ${MARKETPLACE_PREFIX}/* \u2192 ${cloudUrl || "(unconfigured)"}`);
41070
+ });
41071
+ };
41072
+ this.cloudUrl = resolveCloudUrl(config.controlPlaneUrl);
41073
+ }
41074
+ };
41075
+
40858
41076
  // src/cloud/file-artifact-api-client.ts
40859
41077
  import { readFile as readFile2, stat } from "fs/promises";
40860
41078
  import { resolve as resolvePath4 } from "path";
@@ -41081,7 +41299,7 @@ async function createObjectOSStack(config) {
41081
41299
  };
41082
41300
  const enginePlugins = await createHostEnginePlugins();
41083
41301
  return {
41084
- plugins: [...enginePlugins, new ObjectOSProjectPlugin(merged), new AuthProxyPlugin()],
41302
+ plugins: [...enginePlugins, new ObjectOSProjectPlugin(merged), new AuthProxyPlugin(), new MarketplaceProxyPlugin({ controlPlaneUrl: merged.controlPlaneUrl === "file" ? void 0 : merged.controlPlaneUrl })],
41085
41303
  api: {
41086
41304
  enableProjectScoping: true,
41087
41305
  projectResolution: "auto",
@@ -41095,6 +41313,462 @@ async function createObjectOSStack(config) {
41095
41313
  };
41096
41314
  }
41097
41315
 
41316
+ // src/cloud/marketplace-install-local-plugin.ts
41317
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, readdirSync, unlinkSync, writeFileSync } from "fs";
41318
+ import { join, resolve } from "path";
41319
+ var ROUTE_BASE = "/api/v1/marketplace/install-local";
41320
+ var DEFAULT_DIR = ".objectstack/installed-packages";
41321
+ function safeFilename(manifestId) {
41322
+ return manifestId.replace(/[^a-zA-Z0-9._-]/g, "_") + ".json";
41323
+ }
41324
+ var MarketplaceInstallLocalPlugin = class {
41325
+ constructor(config = {}) {
41326
+ this.name = "com.objectstack.runtime.marketplace-install-local";
41327
+ this.version = "1.0.0";
41328
+ this.init = async (_ctx) => {
41329
+ };
41330
+ this.start = async (ctx) => {
41331
+ ctx.hook("kernel:ready", async () => {
41332
+ await this.rehydrate(ctx);
41333
+ let httpServer;
41334
+ try {
41335
+ httpServer = ctx.getService("http-server");
41336
+ } catch {
41337
+ ctx.logger?.warn?.("[MarketplaceInstallLocal] http-server not available \u2014 install endpoints not mounted");
41338
+ return;
41339
+ }
41340
+ if (!httpServer || typeof httpServer.getRawApp !== "function") {
41341
+ ctx.logger?.warn?.("[MarketplaceInstallLocal] http-server missing getRawApp() \u2014 install endpoints not mounted");
41342
+ return;
41343
+ }
41344
+ const rawApp = httpServer.getRawApp();
41345
+ const postHandler = async (c) => this.handleInstall(c, ctx);
41346
+ const getHandler = async (c) => this.handleList(c);
41347
+ const deleteHandler = async (c) => this.handleUninstall(c, ctx);
41348
+ if (typeof rawApp.post === "function") rawApp.post(ROUTE_BASE, postHandler);
41349
+ if (typeof rawApp.get === "function") rawApp.get(ROUTE_BASE, getHandler);
41350
+ if (typeof rawApp.delete === "function") rawApp.delete(`${ROUTE_BASE}/:manifestId`, deleteHandler);
41351
+ ctx.logger?.info?.(`[MarketplaceInstallLocal] mounted at ${ROUTE_BASE} (storage: ${this.storageDir})`);
41352
+ });
41353
+ };
41354
+ /**
41355
+ * Re-register every cached manifest with the kernel's manifest service.
41356
+ * Safe to call on a kernel that already has the same manifest_id (the
41357
+ * underlying ObjectQL registry overwrites by id, but we still warn so
41358
+ * a developer can spot the dev-time clash between their config.ts and
41359
+ * a marketplace package).
41360
+ */
41361
+ this.rehydrate = async (ctx) => {
41362
+ const entries = this.readAll();
41363
+ if (entries.length === 0) return;
41364
+ let manifestService = null;
41365
+ try {
41366
+ manifestService = ctx.getService("manifest");
41367
+ } catch {
41368
+ ctx.logger?.warn?.("[MarketplaceInstallLocal] no `manifest` service \u2014 rehydrate skipped");
41369
+ return;
41370
+ }
41371
+ for (const entry of entries) {
41372
+ try {
41373
+ manifestService.register(entry.manifest);
41374
+ try {
41375
+ const ql = ctx.getService("objectql");
41376
+ if (ql && typeof ql.syncSchemas === "function") await ql.syncSchemas();
41377
+ } catch {
41378
+ }
41379
+ await this.applySideEffects(ctx, entry.manifest, { seedNow: false });
41380
+ ctx.logger?.info?.(`[MarketplaceInstallLocal] rehydrated ${entry.manifestId}@${entry.version}`);
41381
+ } catch (err) {
41382
+ ctx.logger?.error?.(`[MarketplaceInstallLocal] rehydrate failed for ${entry.manifestId}`, err instanceof Error ? err : new Error(String(err)));
41383
+ }
41384
+ }
41385
+ };
41386
+ this.handleInstall = async (c, ctx) => {
41387
+ if (!this.cloudUrl) {
41388
+ return c.json({ success: false, error: { code: "marketplace_unavailable", message: "OS_CLOUD_URL not configured." } }, 503);
41389
+ }
41390
+ const userId = await this.requireAuthenticatedUser(c, ctx);
41391
+ if (!userId) {
41392
+ return c.json({ success: false, error: { code: "unauthorized", message: "Authentication required to install packages." } }, 401);
41393
+ }
41394
+ let body = {};
41395
+ try {
41396
+ body = await c.req.json();
41397
+ } catch {
41398
+ }
41399
+ const packageId = String(body?.packageId ?? "").trim();
41400
+ const versionId = String(body?.versionId ?? "latest").trim() || "latest";
41401
+ if (!packageId) {
41402
+ return c.json({ success: false, error: { code: "bad_request", message: "packageId is required." } }, 400);
41403
+ }
41404
+ let payload;
41405
+ try {
41406
+ const url = `${this.cloudUrl}/api/v1/marketplace/packages/${encodeURIComponent(packageId)}/versions/${encodeURIComponent(versionId)}/manifest`;
41407
+ const resp = await fetch(url, { headers: { "Accept": "application/json" } });
41408
+ if (!resp.ok) {
41409
+ const text = await resp.text().catch(() => "");
41410
+ return c.json({
41411
+ success: false,
41412
+ error: { code: "cloud_fetch_failed", message: `Cloud returned ${resp.status}: ${text.slice(0, 200)}` }
41413
+ }, resp.status === 404 ? 404 : 502);
41414
+ }
41415
+ payload = await resp.json();
41416
+ } catch (err) {
41417
+ return c.json({
41418
+ success: false,
41419
+ error: { code: "cloud_fetch_failed", message: err?.message ?? String(err) }
41420
+ }, 502);
41421
+ }
41422
+ const data = payload?.data ?? payload;
41423
+ const manifest = data?.manifest;
41424
+ const resolvedVersionId = String(data?.version_id ?? versionId);
41425
+ const version = String(data?.version ?? "unknown");
41426
+ const manifestId = String(manifest?.id ?? manifest?.name ?? "");
41427
+ if (!manifest || !manifestId) {
41428
+ return c.json({ success: false, error: { code: "invalid_manifest", message: "Cloud returned an invalid manifest payload." } }, 502);
41429
+ }
41430
+ const conflict = this.findConflict(ctx, manifestId);
41431
+ if (conflict === "user-code") {
41432
+ return c.json({
41433
+ success: false,
41434
+ error: {
41435
+ code: "manifest_conflict",
41436
+ message: `manifest_id "${manifestId}" is already defined by this runtime's local code. Refusing to overwrite. Uninstall the local definition first.`
41437
+ }
41438
+ }, 409);
41439
+ }
41440
+ const entry = {
41441
+ packageId,
41442
+ versionId: resolvedVersionId,
41443
+ manifestId,
41444
+ version,
41445
+ manifest,
41446
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
41447
+ installedBy: userId
41448
+ };
41449
+ try {
41450
+ mkdirSync2(this.storageDir, { recursive: true });
41451
+ writeFileSync(join(this.storageDir, safeFilename(manifestId)), JSON.stringify(entry, null, 2), "utf8");
41452
+ } catch (err) {
41453
+ return c.json({
41454
+ success: false,
41455
+ error: { code: "storage_failed", message: `Failed to persist manifest: ${err?.message ?? err}` }
41456
+ }, 500);
41457
+ }
41458
+ try {
41459
+ const manifestService = ctx.getService("manifest");
41460
+ manifestService.register(manifest);
41461
+ } catch (err) {
41462
+ ctx.logger?.warn?.(`[MarketplaceInstallLocal] hot-register failed for ${manifestId} (will load on next restart): ${err?.message ?? err}`);
41463
+ }
41464
+ try {
41465
+ const ql = ctx.getService("objectql");
41466
+ if (ql && typeof ql.syncSchemas === "function") {
41467
+ await ql.syncSchemas();
41468
+ ctx.logger?.info?.(`[MarketplaceInstallLocal] syncSchemas() ran after registering ${manifestId}`);
41469
+ }
41470
+ } catch (err) {
41471
+ ctx.logger?.warn?.(`[MarketplaceInstallLocal] syncSchemas failed for ${manifestId}: ${err?.message ?? err}`);
41472
+ }
41473
+ const seededSummary = await this.applySideEffects(ctx, manifest, { seedNow: true, c });
41474
+ return c.json({
41475
+ success: true,
41476
+ data: {
41477
+ manifestId,
41478
+ version,
41479
+ versionId: resolvedVersionId,
41480
+ installedAt: entry.installedAt,
41481
+ hotLoaded: true,
41482
+ upgradedFrom: conflict === "marketplace" ? "previous-marketplace-version" : null,
41483
+ translationsLoaded: seededSummary.translationsLoaded,
41484
+ seeded: seededSummary.seeded,
41485
+ note: "App is now available in this runtime. Refresh the console to see it in the app switcher."
41486
+ }
41487
+ }, 200);
41488
+ };
41489
+ this.handleList = async (c) => {
41490
+ const entries = this.readAll();
41491
+ return c.json({
41492
+ success: true,
41493
+ data: {
41494
+ items: entries.map((e) => ({
41495
+ packageId: e.packageId,
41496
+ versionId: e.versionId,
41497
+ manifestId: e.manifestId,
41498
+ version: e.version,
41499
+ installedAt: e.installedAt,
41500
+ installedBy: e.installedBy
41501
+ })),
41502
+ total: entries.length,
41503
+ storageDir: this.storageDir
41504
+ }
41505
+ }, 200);
41506
+ };
41507
+ this.handleUninstall = async (c, ctx) => {
41508
+ const userId = await this.requireAuthenticatedUser(c, ctx);
41509
+ if (!userId) {
41510
+ return c.json({ success: false, error: { code: "unauthorized", message: "Authentication required." } }, 401);
41511
+ }
41512
+ const manifestId = String(c.req.param?.("manifestId") ?? c.req.params?.manifestId ?? "").trim();
41513
+ if (!manifestId) {
41514
+ return c.json({ success: false, error: { code: "bad_request", message: "manifestId path param required." } }, 400);
41515
+ }
41516
+ const file = join(this.storageDir, safeFilename(manifestId));
41517
+ if (!existsSync2(file)) {
41518
+ return c.json({ success: false, error: { code: "not_found", message: `No marketplace install for ${manifestId}.` } }, 404);
41519
+ }
41520
+ try {
41521
+ unlinkSync(file);
41522
+ } catch (err) {
41523
+ return c.json({ success: false, error: { code: "storage_failed", message: err?.message ?? String(err) } }, 500);
41524
+ }
41525
+ ctx.logger?.info?.(`[MarketplaceInstallLocal] uninstalled ${manifestId} (cached manifest removed; restart runtime to unload from running kernel)`);
41526
+ return c.json({
41527
+ success: true,
41528
+ data: {
41529
+ manifestId,
41530
+ note: "Cached manifest removed. The app remains loaded in the running kernel until the next restart (the kernel API does not support unregistering apps in-place)."
41531
+ }
41532
+ }, 200);
41533
+ };
41534
+ /**
41535
+ * Detect whether `manifestId` is already known to the kernel and classify
41536
+ * the source so we can refuse vs upgrade gracefully.
41537
+ *
41538
+ * 'none' — fresh install
41539
+ * 'marketplace' — previously installed by this plugin (allow upgrade)
41540
+ * 'user-code' — defined by AppPlugin from objectstack.config.ts
41541
+ * (refuse to avoid silently overwriting authored code)
41542
+ */
41543
+ this.findConflict = (ctx, manifestId) => {
41544
+ if (existsSync2(join(this.storageDir, safeFilename(manifestId)))) {
41545
+ return "marketplace";
41546
+ }
41547
+ try {
41548
+ const ql = ctx.getService("objectql");
41549
+ const packages = ql?.registry?.getAllPackages?.() ?? [];
41550
+ const hit = packages.find(
41551
+ (p) => (p?.manifest?.id ?? p?.id ?? p?.manifest?.name) === manifestId
41552
+ );
41553
+ if (hit) return "user-code";
41554
+ } catch {
41555
+ }
41556
+ return "none";
41557
+ };
41558
+ /**
41559
+ * Pull a userId out of the request's better-auth session, if any.
41560
+ * Returns null when there is no signed-in user. v1 does not check
41561
+ * admin role — UI gating + the auth requirement is sufficient for
41562
+ * dev / single-tenant runtimes. Stricter checks can be layered on
41563
+ * via a middleware in cloud-hosted multi-tenant deployments.
41564
+ */
41565
+ /**
41566
+ * Replicate the start-time side-effects that AppPlugin runs for
41567
+ * statically-declared apps but the `manifest` service does NOT:
41568
+ *
41569
+ * 1. Load `manifest.translations` (array of `Record<locale, data>`)
41570
+ * into the i18n service — auto-creating an in-memory fallback if
41571
+ * none is registered, matching AppPlugin's behaviour.
41572
+ *
41573
+ * 2. Merge `manifest.data` (an array of seed datasets) into the
41574
+ * kernel's `seed-datasets` service so SecurityPlugin's per-org
41575
+ * replay middleware picks them up on every future
41576
+ * sys_organization insert.
41577
+ *
41578
+ * 3. When `seedNow=true`, also run the seed immediately so the user
41579
+ * sees demo data without having to create a new org:
41580
+ * • single-tenant: run SeedLoaderService inline (mirrors
41581
+ * AppPlugin single-tenant branch)
41582
+ * • multi-tenant: invoke `seed-replayer` for the caller's
41583
+ * active org (resolved from the request session)
41584
+ *
41585
+ * Errors are logged but never thrown — install succeeds even if
41586
+ * post-register side-effects partially fail (the manifest itself is
41587
+ * already registered + cached). Returns a small summary for the
41588
+ * response envelope.
41589
+ */
41590
+ this.applySideEffects = async (ctx, manifest, opts) => {
41591
+ const appId = String(manifest?.id ?? "unknown");
41592
+ let translationsLoaded = 0;
41593
+ let seedSummary = { mode: "skipped", reason: "no-datasets" };
41594
+ try {
41595
+ const bundles = [];
41596
+ if (Array.isArray(manifest?.translations)) bundles.push(...manifest.translations);
41597
+ if (Array.isArray(manifest?.i18n)) bundles.push(...manifest.i18n);
41598
+ if (bundles.length > 0) {
41599
+ let i18nService;
41600
+ try {
41601
+ i18nService = ctx.getService("i18n");
41602
+ } catch {
41603
+ }
41604
+ if (!i18nService) {
41605
+ try {
41606
+ const mod = await import("@objectstack/core");
41607
+ const createMemoryI18n = mod.createMemoryI18n;
41608
+ if (typeof createMemoryI18n === "function") {
41609
+ i18nService = createMemoryI18n();
41610
+ ctx.registerService?.("i18n", i18nService);
41611
+ ctx.logger?.info?.(`[MarketplaceInstallLocal] auto-registered in-memory i18n fallback for "${appId}"`);
41612
+ }
41613
+ } catch {
41614
+ }
41615
+ }
41616
+ if (i18nService?.loadTranslations) {
41617
+ for (const bundle of bundles) {
41618
+ for (const [locale, data] of Object.entries(bundle)) {
41619
+ if (data && typeof data === "object") {
41620
+ try {
41621
+ i18nService.loadTranslations(locale, data);
41622
+ translationsLoaded++;
41623
+ } catch (err) {
41624
+ ctx.logger?.warn?.(`[MarketplaceInstallLocal] failed to load ${appId} translations for ${locale}: ${err?.message ?? err}`);
41625
+ }
41626
+ }
41627
+ }
41628
+ }
41629
+ ctx.logger?.info?.(`[MarketplaceInstallLocal] loaded ${translationsLoaded} locale bundle(s) for ${appId}`);
41630
+ }
41631
+ }
41632
+ } catch (err) {
41633
+ ctx.logger?.warn?.(`[MarketplaceInstallLocal] i18n side-effect failed for ${appId}: ${err?.message ?? err}`);
41634
+ }
41635
+ const datasets = Array.isArray(manifest?.data) ? manifest.data.filter((d) => d && d.object && Array.isArray(d.records)) : [];
41636
+ if (datasets.length > 0) {
41637
+ try {
41638
+ const kernel = ctx.kernel;
41639
+ let existing = [];
41640
+ try {
41641
+ const v = kernel?.getService?.("seed-datasets");
41642
+ if (Array.isArray(v)) existing = v;
41643
+ } catch {
41644
+ }
41645
+ const merged = [...existing, ...datasets];
41646
+ if (kernel?.registerService) kernel.registerService("seed-datasets", merged);
41647
+ else ctx.registerService?.("seed-datasets", merged);
41648
+ ctx.logger?.info?.(`[MarketplaceInstallLocal] merged ${datasets.length} seed dataset(s) into kernel (total: ${merged.length})`);
41649
+ } catch (err) {
41650
+ ctx.logger?.warn?.(`[MarketplaceInstallLocal] failed to merge seed-datasets: ${err?.message ?? err}`);
41651
+ }
41652
+ }
41653
+ if (opts.seedNow && datasets.length > 0) {
41654
+ const multiTenant = String(process.env.OS_MULTI_TENANT ?? "true").toLowerCase() !== "false";
41655
+ try {
41656
+ const ql = ctx.getService("objectql");
41657
+ let metadata;
41658
+ try {
41659
+ metadata = ctx.getService("metadata");
41660
+ } catch {
41661
+ }
41662
+ if (!ql || !metadata) {
41663
+ seedSummary = { mode: "skipped", reason: "objectql-or-metadata-missing" };
41664
+ } else {
41665
+ let organizationId;
41666
+ if (multiTenant) {
41667
+ const resolved = await this.resolveActiveOrgId(opts.c, ctx);
41668
+ if (resolved) organizationId = resolved;
41669
+ else {
41670
+ seedSummary = { mode: "skipped", reason: "multi-tenant-no-active-org" };
41671
+ ctx.logger?.warn?.("[MarketplaceInstallLocal] multi-tenant: no active org on request \u2014 data not seeded");
41672
+ }
41673
+ }
41674
+ if (!multiTenant || organizationId) {
41675
+ const [{ SeedLoaderService: SeedLoaderService2 }, { SeedLoaderRequestSchema }] = await Promise.all([
41676
+ Promise.resolve().then(() => (init_seed_loader(), seed_loader_exports)),
41677
+ import("@objectstack/spec/data")
41678
+ ]);
41679
+ const seedLoader = new SeedLoaderService2(ql, metadata, ctx.logger);
41680
+ const request = SeedLoaderRequestSchema.parse({
41681
+ datasets,
41682
+ config: {
41683
+ defaultMode: "upsert",
41684
+ multiPass: true,
41685
+ ...organizationId ? { organizationId } : {}
41686
+ }
41687
+ });
41688
+ const result = await seedLoader.load(request);
41689
+ seedSummary = {
41690
+ mode: "inline",
41691
+ inserted: result.summary.totalInserted,
41692
+ updated: result.summary.totalUpdated,
41693
+ errors: result.errors.length
41694
+ };
41695
+ ctx.logger?.info?.(`[MarketplaceInstallLocal] inline seed for ${appId}${organizationId ? ` (org=${organizationId})` : ""}: inserted=${seedSummary.inserted} updated=${seedSummary.updated} errors=${seedSummary.errors}`);
41696
+ }
41697
+ }
41698
+ } catch (err) {
41699
+ seedSummary = { mode: "skipped", reason: `seed-error: ${err?.message ?? err}` };
41700
+ ctx.logger?.warn?.(`[MarketplaceInstallLocal] seed run failed for ${appId}: ${err?.message ?? err}`);
41701
+ }
41702
+ }
41703
+ return { translationsLoaded, seeded: seedSummary };
41704
+ };
41705
+ /**
41706
+ * Best-effort active-org resolution. Reads the better-auth session
41707
+ * (same path as requireAuthenticatedUser) and returns
41708
+ * `session.activeOrganizationId`, falling back to the user's first
41709
+ * org membership.
41710
+ */
41711
+ this.resolveActiveOrgId = async (c, ctx) => {
41712
+ if (!c?.req?.raw?.headers) return null;
41713
+ try {
41714
+ const authService = ctx.getService("auth");
41715
+ let api = authService?.api;
41716
+ if (!api && typeof authService?.getApi === "function") api = await authService.getApi();
41717
+ if (!api?.getSession) return null;
41718
+ const session = await api.getSession({ headers: c.req.raw.headers });
41719
+ const direct = session?.session?.activeOrganizationId ?? session?.activeOrganizationId ?? null;
41720
+ if (direct) return String(direct);
41721
+ const userId = session?.user?.id;
41722
+ if (!userId) return null;
41723
+ try {
41724
+ const ql = ctx.getService("objectql");
41725
+ if (ql?.find) {
41726
+ const rows = await ql.find("sys_organization_member", { where: { user_id: userId }, limit: 1, context: { isSystem: true } });
41727
+ const row = Array.isArray(rows) ? rows[0] : rows?.items?.[0] ?? null;
41728
+ return row?.organization_id ? String(row.organization_id) : null;
41729
+ }
41730
+ } catch {
41731
+ }
41732
+ } catch {
41733
+ }
41734
+ return null;
41735
+ };
41736
+ this.requireAuthenticatedUser = async (c, ctx) => {
41737
+ try {
41738
+ const authService = ctx.getService("auth");
41739
+ let api = authService?.api;
41740
+ if (!api && typeof authService?.getApi === "function") {
41741
+ api = await authService.getApi();
41742
+ }
41743
+ if (api?.getSession && c?.req?.raw?.headers) {
41744
+ const session = await api.getSession({ headers: c.req.raw.headers });
41745
+ const userId = session?.user?.id ?? null;
41746
+ if (userId) return String(userId);
41747
+ }
41748
+ } catch {
41749
+ }
41750
+ const xUserId = c?.req?.header?.("x-user-id");
41751
+ if (xUserId) return String(xUserId);
41752
+ return null;
41753
+ };
41754
+ this.readAll = () => {
41755
+ if (!existsSync2(this.storageDir)) return [];
41756
+ const out = [];
41757
+ for (const name of readdirSync(this.storageDir)) {
41758
+ if (!name.endsWith(".json")) continue;
41759
+ try {
41760
+ const raw = readFileSync(join(this.storageDir, name), "utf8");
41761
+ out.push(JSON.parse(raw));
41762
+ } catch {
41763
+ }
41764
+ }
41765
+ return out;
41766
+ };
41767
+ this.cloudUrl = resolveCloudUrl(config.controlPlaneUrl);
41768
+ this.storageDir = config.storageDir ? resolve(config.storageDir) : resolve(process.cwd(), DEFAULT_DIR);
41769
+ }
41770
+ };
41771
+
41098
41772
  // src/index.ts
41099
41773
  init_platform_sso();
41100
41774
 
@@ -41134,6 +41808,7 @@ export {
41134
41808
  ArtifactEnvironmentRegistry,
41135
41809
  ArtifactKernelFactory,
41136
41810
  AuthProxyPlugin,
41811
+ DEFAULT_CLOUD_URL,
41137
41812
  DEFAULT_RATE_LIMITS,
41138
41813
  DriverPlugin,
41139
41814
  FileArtifactApiClient,
@@ -41142,10 +41817,15 @@ export {
41142
41817
  InMemoryErrorReporter,
41143
41818
  InMemoryMetricsRegistry,
41144
41819
  KernelManager,
41820
+ MarketplaceInstallLocalPlugin,
41821
+ MarketplaceProxyPlugin,
41145
41822
  MiddlewareManager,
41146
41823
  NoopErrorReporter,
41147
41824
  NoopMetricsRegistry,
41825
+ OBSERVABILITY_ERRORS_SERVICE,
41826
+ OBSERVABILITY_METRICS_SERVICE,
41148
41827
  ObjectKernel4 as ObjectKernel,
41828
+ ObservabilityServicePlugin,
41149
41829
  PLATFORM_SSO_PROVIDER_ID,
41150
41830
  QuickJSScriptRunner,
41151
41831
  RUNTIME_METRICS,
@@ -41182,7 +41862,10 @@ export {
41182
41862
  mergeRuntimeModule,
41183
41863
  parseTraceparent,
41184
41864
  readArtifactSource,
41865
+ resolveCloudUrl,
41185
41866
  resolveDefaultArtifactPath,
41867
+ resolveErrorReporter,
41868
+ resolveMetrics,
41186
41869
  resolveRequestId,
41187
41870
  seedPlatformSsoClient
41188
41871
  };