@objectstack/runtime 7.7.0 → 7.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 CHANGED
@@ -324,6 +324,24 @@ var init_seed_loader = __esm({
324
324
  for (const ref of objectRefs) {
325
325
  const fieldValue = record[ref.field];
326
326
  if (fieldValue === void 0 || fieldValue === null) continue;
327
+ if (typeof fieldValue === "object") {
328
+ const wrapped = fieldValue.externalId;
329
+ const hint = wrapped !== void 0 ? ` Pass the natural key directly: ${ref.field}: ${JSON.stringify(wrapped)}.` : ` Pass the target's ${ref.targetField} value as a plain string.`;
330
+ const error = {
331
+ sourceObject: objectName,
332
+ field: ref.field,
333
+ targetObject: ref.targetObject,
334
+ targetField: ref.targetField,
335
+ attemptedValue: fieldValue,
336
+ recordIndex: i,
337
+ message: `Invalid reference for ${objectName}.${ref.field}: expected a ${ref.targetObject}.${ref.targetField} natural-key string but got an object.${hint}`
338
+ };
339
+ errors.push(error);
340
+ allErrors.push(error);
341
+ this.logger.warn(`[SeedLoader] ${error.message}`, { recordIndex: i });
342
+ record[ref.field] = null;
343
+ continue;
344
+ }
327
345
  if (typeof fieldValue !== "string" || this.looksLikeInternalId(fieldValue)) continue;
328
346
  const targetMap = insertedRecords.get(ref.targetObject);
329
347
  const resolvedId = targetMap?.get(String(fieldValue));
@@ -3589,7 +3607,8 @@ var _HttpDispatcher = class _HttpDispatcher {
3589
3607
  if (protocol && typeof protocol.getMetaItem === "function") {
3590
3608
  try {
3591
3609
  const organizationId = await this.resolveActiveOrganizationId(_context);
3592
- const data = await protocol.getMetaItem({ type: singularType, name, packageId, organizationId });
3610
+ const previewDrafts = query?.preview === "draft";
3611
+ const data = await protocol.getMetaItem({ type: singularType, name, packageId, organizationId, previewDrafts });
3593
3612
  return { handled: true, response: this.success(data) };
3594
3613
  } catch (e) {
3595
3614
  }
@@ -3607,6 +3626,23 @@ var _HttpDispatcher = class _HttpDispatcher {
3607
3626
  return { handled: true, response: this.error(e.message, 404) };
3608
3627
  }
3609
3628
  }
3629
+ if (parts.length === 1 && parts[0] === "_drafts" && (!method || method.toUpperCase() === "GET")) {
3630
+ const protocol = await this.resolveService("protocol");
3631
+ if (protocol && typeof protocol.listDrafts === "function") {
3632
+ try {
3633
+ const organizationId = await this.resolveActiveOrganizationId(_context);
3634
+ const data = await protocol.listDrafts({
3635
+ packageId: query?.packageId || void 0,
3636
+ type: query?.type || void 0,
3637
+ organizationId
3638
+ });
3639
+ return { handled: true, response: this.success(data) };
3640
+ } catch (e) {
3641
+ return { handled: true, response: this.error(e.message, 500) };
3642
+ }
3643
+ }
3644
+ return { handled: true, response: this.error("Draft listing not supported", 501) };
3645
+ }
3610
3646
  if (parts.length === 1) {
3611
3647
  const typeOrName = parts[0];
3612
3648
  const packageId = query?.package || void 0;
@@ -3614,7 +3650,8 @@ var _HttpDispatcher = class _HttpDispatcher {
3614
3650
  if (protocol && typeof protocol.getMetaItems === "function") {
3615
3651
  try {
3616
3652
  const organizationId = await this.resolveActiveOrganizationId(_context);
3617
- const data = await protocol.getMetaItems({ type: typeOrName, packageId, organizationId });
3653
+ const previewDrafts = query?.preview === "draft";
3654
+ const data = await protocol.getMetaItems({ type: typeOrName, packageId, organizationId, previewDrafts });
3618
3655
  if (data && (data.items !== void 0 || Array.isArray(data))) {
3619
3656
  return { handled: true, response: this.success(data) };
3620
3657
  }
@@ -3906,7 +3943,15 @@ var _HttpDispatcher = class _HttpDispatcher {
3906
3943
  return { handled: true, response: this.success({ packages, total: packages.length }) };
3907
3944
  }
3908
3945
  if (parts.length === 0 && m === "POST") {
3909
- const pkg = registry.installPackage(body.manifest || body, body.settings);
3946
+ const manifest = body.manifest || body;
3947
+ let pkg;
3948
+ const protocolSvc = await this.resolveService("protocol").catch(() => null);
3949
+ if (protocolSvc && typeof protocolSvc.installPackage === "function") {
3950
+ const out = await protocolSvc.installPackage({ manifest, settings: body.settings });
3951
+ pkg = out?.package ?? out;
3952
+ } else {
3953
+ pkg = registry.installPackage(manifest, body.settings);
3954
+ }
3910
3955
  const res = this.success(pkg);
3911
3956
  res.status = 201;
3912
3957
  return { handled: true, response: res };
@@ -3942,6 +3987,42 @@ var _HttpDispatcher = class _HttpDispatcher {
3942
3987
  }
3943
3988
  return { handled: true, response: this.error("Metadata service not available", 503) };
3944
3989
  }
3990
+ if (parts.length === 2 && parts[1] === "publish-drafts" && m === "POST") {
3991
+ const id = decodeURIComponent(parts[0]);
3992
+ const protocol = await this.resolveService("protocol");
3993
+ if (protocol && typeof protocol.publishPackageDrafts === "function") {
3994
+ try {
3995
+ const organizationId = await this.resolveActiveOrganizationId(_context);
3996
+ const result = await protocol.publishPackageDrafts({
3997
+ packageId: id,
3998
+ ...organizationId ? { organizationId } : {},
3999
+ ...body?.actor ? { actor: body.actor } : {}
4000
+ });
4001
+ return { handled: true, response: this.success(result) };
4002
+ } catch (e) {
4003
+ return { handled: true, response: this.error(e.message, e.statusCode || 500) };
4004
+ }
4005
+ }
4006
+ return { handled: true, response: this.error("Draft publishing not supported", 501) };
4007
+ }
4008
+ if (parts.length === 2 && parts[1] === "discard-drafts" && m === "POST") {
4009
+ const id = decodeURIComponent(parts[0]);
4010
+ const protocol = await this.resolveService("protocol");
4011
+ if (protocol && typeof protocol.discardPackageDrafts === "function") {
4012
+ try {
4013
+ const organizationId = await this.resolveActiveOrganizationId(_context);
4014
+ const result = await protocol.discardPackageDrafts({
4015
+ packageId: id,
4016
+ ...organizationId ? { organizationId } : {},
4017
+ ...body?.actor ? { actor: body.actor } : {}
4018
+ });
4019
+ return { handled: true, response: this.success(result) };
4020
+ } catch (e) {
4021
+ return { handled: true, response: this.error(e.message, e.statusCode || 500) };
4022
+ }
4023
+ }
4024
+ return { handled: true, response: this.error("Draft discarding not supported", 501) };
4025
+ }
3945
4026
  if (parts.length === 2 && parts[1] === "revert" && m === "POST") {
3946
4027
  const id = decodeURIComponent(parts[0]);
3947
4028
  const metadataService = await this.getService(import_system2.CoreServiceName.enum.metadata);
@@ -3967,9 +4048,27 @@ var _HttpDispatcher = class _HttpDispatcher {
3967
4048
  }
3968
4049
  if (parts.length === 1 && m === "DELETE") {
3969
4050
  const id = decodeURIComponent(parts[0]);
3970
- const success = registry.uninstallPackage(id);
3971
- if (!success) return { handled: true, response: this.error(`Package '${id}' not found`, 404) };
3972
- return { handled: true, response: this.success({ success: true }) };
4051
+ const registryRemoved = registry.uninstallPackage(id);
4052
+ let persisted = void 0;
4053
+ const protocol = await this.resolveService("protocol");
4054
+ if (protocol && typeof protocol.deletePackage === "function") {
4055
+ try {
4056
+ const organizationId = await this.resolveActiveOrganizationId(_context);
4057
+ const keepData = query?.keepData === "true" || query?.keepData === "1";
4058
+ persisted = await protocol.deletePackage({
4059
+ packageId: id,
4060
+ ...organizationId ? { organizationId } : {},
4061
+ ...keepData ? { keepData: true } : {}
4062
+ });
4063
+ } catch (e) {
4064
+ return { handled: true, response: this.error(e.message, e.statusCode || 500) };
4065
+ }
4066
+ }
4067
+ const deletedCount = persisted?.deletedCount ?? 0;
4068
+ if (!registryRemoved && deletedCount === 0) {
4069
+ return { handled: true, response: this.error(`Package '${id}' not found`, 404) };
4070
+ }
4071
+ return { handled: true, response: this.success({ success: true, registryRemoved, persisted }) };
3973
4072
  }
3974
4073
  } catch (e) {
3975
4074
  return { handled: true, response: this.error(e.message, e.statusCode || 500) };
@@ -5441,6 +5540,14 @@ function createDispatcherPlugin(config = {}) {
5441
5540
  errorResponse(err, res);
5442
5541
  }
5443
5542
  });
5543
+ server.post(`${prefix}/packages/:id/publish-drafts`, async (req, res) => {
5544
+ try {
5545
+ const result = await dispatcher.handlePackages(`/${req.params.id}/publish-drafts`, "POST", req.body, {}, { request: req });
5546
+ sendResult(result, res);
5547
+ } catch (err) {
5548
+ errorResponse(err, res);
5549
+ }
5550
+ });
5444
5551
  server.post(`${prefix}/packages/:id/revert`, async (req, res) => {
5445
5552
  try {
5446
5553
  const result = await dispatcher.handlePackages(`/${req.params.id}/revert`, "POST", req.body, {}, { request: req });
@@ -6542,6 +6649,16 @@ var CAPABILITY_PROVIDERS = {
6542
6649
  pkg: "@objectstack/service-ai",
6543
6650
  export: "AIServicePlugin"
6544
6651
  },
6652
+ // AI Studio — AI-driven metadata authoring ("online development"). This is
6653
+ // a commercial capability that ships in the private @objectstack/service-ai-studio
6654
+ // package (not part of the open-source framework). The dynamic import below
6655
+ // silently skips when the package isn't installed, so the open-source build
6656
+ // is unaffected; cloud and enterprise installs that ship the package light it
6657
+ // up. Pair with `ai` in `requires` (it attaches via the `ai:ready` hook).
6658
+ aiStudio: {
6659
+ pkg: "@objectstack/service-ai-studio",
6660
+ export: "AIStudioPlugin"
6661
+ },
6545
6662
  analytics: {
6546
6663
  pkg: "@objectstack/service-analytics",
6547
6664
  export: "AnalyticsServicePlugin",
@@ -6868,6 +6985,7 @@ var ArtifactKernelFactory = class {
6868
6985
  this.envRegistry = config.envRegistry;
6869
6986
  this.logger = config.logger ?? console;
6870
6987
  this.kernelConfig = config.kernelConfig;
6988
+ this.defaultRequires = config.defaultRequires ?? [];
6871
6989
  this.authBaseSecret = (config.authBaseSecret ?? (0, import_types3.readEnvWithDeprecation)("OS_AUTH_SECRET", ["AUTH_SECRET", "BETTER_AUTH_SECRET"]) ?? "").trim();
6872
6990
  }
6873
6991
  async create(environmentId) {
@@ -7034,7 +7152,10 @@ var ArtifactKernelFactory = class {
7034
7152
  });
7035
7153
  }
7036
7154
  const requiresRaw = (Array.isArray(bundle?.requires) ? bundle.requires : null) ?? (Array.isArray(sys?.requires) ? sys.requires : null) ?? [];
7037
- const requires = requiresRaw.filter((x) => typeof x === "string" && x.length > 0);
7155
+ const requires = [
7156
+ ...requiresRaw,
7157
+ ...this.defaultRequires
7158
+ ].filter((x) => typeof x === "string" && x.length > 0);
7038
7159
  if (requires.length > 0) {
7039
7160
  const installed = await loadCapabilities({
7040
7161
  kernel,
@@ -7453,6 +7574,61 @@ var AuthProxyPlugin = class {
7453
7574
  return c.text(`sso-exchange failed: ${err?.message ?? String(err)}`, 500);
7454
7575
  }
7455
7576
  }
7577
+ if (c.req.method === "POST" && subPath === "set-initial-password") {
7578
+ try {
7579
+ let body = {};
7580
+ try {
7581
+ body = await c.req.json();
7582
+ } catch {
7583
+ body = {};
7584
+ }
7585
+ const newPassword = body?.newPassword;
7586
+ if (typeof newPassword !== "string" || newPassword.length === 0) {
7587
+ return c.json({ success: false, error: { code: "invalid_request", message: "newPassword is required" } }, 400);
7588
+ }
7589
+ if (typeof authSvc?.getAuthContext !== "function") {
7590
+ return c.json({ success: false, error: { code: "unavailable", message: "Auth context unavailable" } }, 503);
7591
+ }
7592
+ let userId;
7593
+ try {
7594
+ const api = typeof authSvc.getApi === "function" ? await authSvc.getApi() : null;
7595
+ const session = await api?.getSession?.({ headers: c.req.raw.headers });
7596
+ userId = session?.user?.id ? String(session.user.id) : void 0;
7597
+ } catch {
7598
+ }
7599
+ if (!userId) {
7600
+ return c.json({ success: false, error: { code: "unauthorized", message: "Sign in first" } }, 401);
7601
+ }
7602
+ const setPwCtx = await authSvc.getAuthContext();
7603
+ if (!setPwCtx?.internalAdapter || !setPwCtx?.password) {
7604
+ return c.json({ success: false, error: { code: "unavailable", message: "Auth context unavailable" } }, 503);
7605
+ }
7606
+ const minLen = setPwCtx.password?.config?.minPasswordLength ?? 8;
7607
+ const maxLen = setPwCtx.password?.config?.maxPasswordLength ?? 128;
7608
+ if (newPassword.length < minLen) {
7609
+ return c.json({ success: false, error: { code: "password_too_short", message: `Password must be at least ${minLen} characters` } }, 400);
7610
+ }
7611
+ if (newPassword.length > maxLen) {
7612
+ return c.json({ success: false, error: { code: "password_too_long", message: `Password must be at most ${maxLen} characters` } }, 400);
7613
+ }
7614
+ const accounts = await setPwCtx.internalAdapter.findAccounts(userId);
7615
+ const existingCredential = accounts?.find?.((a) => a.providerId === "credential" && a.password);
7616
+ if (existingCredential) {
7617
+ return c.json({ success: false, error: { code: "credential_account_exists", message: "A local password is already set for this account. Use change-password instead." } }, 409);
7618
+ }
7619
+ const passwordHash = await setPwCtx.password.hash(newPassword);
7620
+ await setPwCtx.internalAdapter.createAccount({
7621
+ userId,
7622
+ providerId: "credential",
7623
+ accountId: userId,
7624
+ password: passwordHash
7625
+ });
7626
+ return c.json({ success: true });
7627
+ } catch (err) {
7628
+ ctx.logger?.error?.("[AuthProxyPlugin] set-initial-password failed", err instanceof Error ? err : new Error(String(err)));
7629
+ return c.json({ success: false, error: { code: "set_password_failed", message: String(err?.message ?? err) } }, 500);
7630
+ }
7631
+ }
7456
7632
  const fn = await resolveAuthHandler(authSvc);
7457
7633
  if (!fn) {
7458
7634
  return c.json({ error: "auth_service_unavailable", environmentId }, 503);
@@ -7638,13 +7814,7 @@ var MarketplaceProxyPlugin = class _MarketplaceProxyPlugin {
7638
7814
  }
7639
7815
  const target = `${cloudUrl}${incomingUrl.pathname}${incomingUrl.search}`;
7640
7816
  if (method !== "GET" && method !== "HEAD") {
7641
- return c.json({
7642
- success: false,
7643
- error: {
7644
- code: "marketplace_method_not_allowed",
7645
- message: `Marketplace proxy only forwards GET/HEAD; install via cloud.`
7646
- }
7647
- }, 405);
7817
+ return next();
7648
7818
  }
7649
7819
  const accept = c.req.header("accept") ?? "application/json";
7650
7820
  const acceptLang = c.req.header("accept-language") ?? "";
@@ -7868,7 +8038,8 @@ var RuntimeConfigPlugin = class {
7868
8038
  const rawApp = httpServer.getRawApp();
7869
8039
  const features = {
7870
8040
  installLocal: this.installLocal,
7871
- marketplace: true
8041
+ marketplace: true,
8042
+ aiStudio: this.aiStudio
7872
8043
  };
7873
8044
  let envRegistry = null;
7874
8045
  try {
@@ -7919,6 +8090,7 @@ var RuntimeConfigPlugin = class {
7919
8090
  };
7920
8091
  this.cloudUrl = config.controlPlaneUrl === "" ? "" : resolveCloudUrl(config.controlPlaneUrl) ?? "";
7921
8092
  this.installLocal = !!config.installLocal;
8093
+ this.aiStudio = config.aiStudio !== false;
7922
8094
  this.singleEnvironment = !!config.singleEnvironment;
7923
8095
  const envName = (typeof process !== "undefined" ? process.env?.OS_PRODUCT_NAME : void 0)?.trim();
7924
8096
  const envShort = (typeof process !== "undefined" ? process.env?.OS_PRODUCT_SHORT_NAME : void 0)?.trim();
@@ -8110,7 +8282,8 @@ var ObjectOSEnvironmentPlugin = class {
8110
8282
  const factory = new ArtifactKernelFactory({
8111
8283
  client,
8112
8284
  envRegistry,
8113
- logger: ctx.logger
8285
+ logger: ctx.logger,
8286
+ defaultRequires: this.config.defaultRequires
8114
8287
  });
8115
8288
  const kernelManager = new KernelManager({
8116
8289
  factory,
@@ -8167,7 +8340,17 @@ async function createObjectOSStack(config) {
8167
8340
  };
8168
8341
  const enginePlugins = await createHostEnginePlugins();
8169
8342
  return {
8170
- plugins: [...enginePlugins, new ObjectOSEnvironmentPlugin(merged), new AuthProxyPlugin(), new MarketplaceProxyPlugin({ controlPlaneUrl: merged.controlPlaneUrl === "file" ? void 0 : merged.controlPlaneUrl }), new RuntimeConfigPlugin({ controlPlaneUrl: merged.controlPlaneUrl === "file" ? void 0 : merged.controlPlaneUrl, installLocal: false })],
8343
+ plugins: [
8344
+ ...enginePlugins,
8345
+ new ObjectOSEnvironmentPlugin(merged),
8346
+ new AuthProxyPlugin(),
8347
+ new MarketplaceProxyPlugin({ controlPlaneUrl: merged.controlPlaneUrl === "file" ? void 0 : merged.controlPlaneUrl }),
8348
+ new RuntimeConfigPlugin({ controlPlaneUrl: merged.controlPlaneUrl === "file" ? void 0 : merged.controlPlaneUrl, installLocal: false }),
8349
+ // Host-supplied product/policy plugins (the official seam — see
8350
+ // ObjectOSStackConfig.extraPlugins). Appended last so they mount
8351
+ // after the framework defaults.
8352
+ ...config.extraPlugins ?? []
8353
+ ],
8171
8354
  api: {
8172
8355
  enableProjectScoping: true,
8173
8356
  projectResolution: "auto",