@objectstack/runtime 9.10.0 → 10.0.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.d.cts CHANGED
@@ -117,6 +117,19 @@ interface StandaloneStackResult {
117
117
  requires?: string[];
118
118
  objects?: any[];
119
119
  manifest?: any;
120
+ /**
121
+ * App-declared RBAC metadata, surfaced so the CLI (`serve`/`dev`/`start`)
122
+ * can wire it without a host `objectstack.config.ts`. In particular the
123
+ * `serve` command reads `permissions[]` to honour an app-declared default
124
+ * profile (ADR-0056 D7 — `appDefaultProfileName` → SecurityPlugin
125
+ * `fallbackPermissionSet`) and reads both `roles[]` and `permissions[]` to
126
+ * register application org roles with Better-Auth. Without these the
127
+ * artifact-serve path silently fell back to the built-in `member_default`
128
+ * (owner-only), so an `isDefault` profile declared purely in app metadata
129
+ * was ignored under `objectstack dev`.
130
+ */
131
+ permissions?: any[];
132
+ roles?: any[];
120
133
  }
121
134
  declare function createStandaloneStack(config?: StandaloneStackConfig): Promise<StandaloneStackResult>;
122
135
 
package/dist/index.d.ts CHANGED
@@ -117,6 +117,19 @@ interface StandaloneStackResult {
117
117
  requires?: string[];
118
118
  objects?: any[];
119
119
  manifest?: any;
120
+ /**
121
+ * App-declared RBAC metadata, surfaced so the CLI (`serve`/`dev`/`start`)
122
+ * can wire it without a host `objectstack.config.ts`. In particular the
123
+ * `serve` command reads `permissions[]` to honour an app-declared default
124
+ * profile (ADR-0056 D7 — `appDefaultProfileName` → SecurityPlugin
125
+ * `fallbackPermissionSet`) and reads both `roles[]` and `permissions[]` to
126
+ * register application org roles with Better-Auth. Without these the
127
+ * artifact-serve path silently fell back to the built-in `member_default`
128
+ * (owner-only), so an `isDefault` profile declared purely in app metadata
129
+ * was ignored under `objectstack dev`.
130
+ */
131
+ permissions?: any[];
132
+ roles?: any[];
120
133
  }
121
134
  declare function createStandaloneStack(config?: StandaloneStackConfig): Promise<StandaloneStackResult>;
122
135
 
package/dist/index.js CHANGED
@@ -963,6 +963,35 @@ var init_app_plugin = __esm({
963
963
  error: err?.message ?? String(err)
964
964
  });
965
965
  }
966
+ try {
967
+ const metadata = ctx.getService("metadata");
968
+ if (typeof metadata?.registerInMemory === "function") {
969
+ const securityBundle = this.bundle.manifest ? { ...this.bundle.manifest, ...this.bundle } : this.bundle;
970
+ const SECURITY_FIELDS = [
971
+ ["roles", "role"],
972
+ ["permissions", "permission"],
973
+ ["sharingRules", "sharing_rule"],
974
+ ["policies", "policy"]
975
+ ];
976
+ let count = 0;
977
+ for (const [field, type] of SECURITY_FIELDS) {
978
+ const arr = securityBundle?.[field];
979
+ if (!Array.isArray(arr)) continue;
980
+ for (const item of arr) {
981
+ if (!item?.name) continue;
982
+ metadata.registerInMemory(type, item.name, item);
983
+ count += 1;
984
+ }
985
+ }
986
+ if (count > 0) {
987
+ ctx.logger.info("Registered stack-declared security metadata", { appId, count });
988
+ }
989
+ }
990
+ } catch (err) {
991
+ ctx.logger.warn("[AppPlugin] failed to register security metadata", {
992
+ error: err?.message ?? String(err)
993
+ });
994
+ }
966
995
  const stackBundle = this.bundle.default || this.bundle;
967
996
  const runtime = stackBundle && typeof stackBundle.onEnable === "function" ? stackBundle : this.bundle;
968
997
  if (runtime && typeof runtime.onEnable === "function") {
@@ -1587,6 +1616,8 @@ async function createStandaloneStack(config) {
1587
1616
  const requires = Array.isArray(artifactBundle?.requires) ? artifactBundle.requires.filter((c) => typeof c === "string") : void 0;
1588
1617
  const objects = Array.isArray(artifactBundle?.objects) ? artifactBundle.objects : void 0;
1589
1618
  const manifest = artifactBundle?.manifest;
1619
+ const permissions = Array.isArray(artifactBundle?.permissions) ? artifactBundle.permissions : void 0;
1620
+ const roles = Array.isArray(artifactBundle?.roles) ? artifactBundle.roles : void 0;
1590
1621
  return {
1591
1622
  plugins,
1592
1623
  api: {
@@ -1595,7 +1626,9 @@ async function createStandaloneStack(config) {
1595
1626
  },
1596
1627
  ...requires ? { requires } : {},
1597
1628
  ...objects ? { objects } : {},
1598
- ...manifest ? { manifest } : {}
1629
+ ...manifest ? { manifest } : {},
1630
+ ...permissions ? { permissions } : {},
1631
+ ...roles ? { roles } : {}
1599
1632
  };
1600
1633
  }
1601
1634
  var StandaloneStackConfigSchema;
@@ -1997,25 +2030,33 @@ function coerceLocale(value) {
1997
2030
  const s = typeof value === "string" ? value.trim() : value != null ? String(value).trim() : "";
1998
2031
  return s || void 0;
1999
2032
  }
2033
+ function coerceCurrency(value) {
2034
+ const s = typeof value === "string" ? value.trim().toUpperCase() : "";
2035
+ return /^[A-Z]{3}$/.test(s) ? s : void 0;
2036
+ }
2000
2037
  async function resolveLocalization(opts, ql, sctx) {
2001
2038
  try {
2002
2039
  const settings = await opts.getService("settings");
2003
2040
  if (settings && typeof settings.get === "function") {
2004
- const [tzRes, localeRes] = await Promise.all([
2041
+ const [tzRes, localeRes, currencyRes] = await Promise.all([
2005
2042
  settings.get("localization", "timezone", sctx).catch(() => void 0),
2006
- settings.get("localization", "locale", sctx).catch(() => void 0)
2043
+ settings.get("localization", "locale", sctx).catch(() => void 0),
2044
+ settings.get("localization", "currency", sctx).catch(() => void 0)
2007
2045
  ]);
2008
2046
  const tz = coerceTimeZone(tzRes?.value);
2009
2047
  const locale = coerceLocale(localeRes?.value);
2010
- if (tz || locale) return { timezone: tz ?? "UTC", locale: locale ?? "en-US" };
2048
+ const currency = coerceCurrency(currencyRes?.value);
2049
+ if (tz || locale || currency) return { timezone: tz ?? "UTC", locale: locale ?? "en-US", currency };
2011
2050
  }
2012
2051
  } catch {
2013
2052
  }
2014
2053
  const tzRows = await tryFind(ql, "sys_setting", { namespace: "localization", key: "timezone", scope: "tenant" }, 1);
2015
2054
  const localeRows = await tryFind(ql, "sys_setting", { namespace: "localization", key: "locale", scope: "tenant" }, 1);
2055
+ const currencyRows = await tryFind(ql, "sys_setting", { namespace: "localization", key: "currency", scope: "tenant" }, 1);
2016
2056
  return {
2017
2057
  timezone: coerceTimeZone(tzRows[0]?.value) ?? "UTC",
2018
- locale: coerceLocale(localeRows[0]?.value) ?? "en-US"
2058
+ locale: coerceLocale(localeRows[0]?.value) ?? "en-US",
2059
+ currency: coerceCurrency(currencyRows[0]?.value)
2019
2060
  };
2020
2061
  }
2021
2062
  async function resolveExecutionContext(opts) {
@@ -2048,6 +2089,7 @@ async function resolveExecutionContext(opts) {
2048
2089
  userId = sessionData?.user?.id ?? sessionData?.session?.userId;
2049
2090
  tenantId = tenantId ?? sessionData?.session?.activeOrganizationId;
2050
2091
  ctx.accessToken = sessionData?.session?.token ?? ctx.accessToken;
2092
+ if (sessionData?.user?.email) ctx.email = String(sessionData.user.email);
2051
2093
  } catch {
2052
2094
  }
2053
2095
  }
@@ -2056,6 +2098,10 @@ async function resolveExecutionContext(opts) {
2056
2098
  if (!userId) return ctx;
2057
2099
  const ql = await opts.getQl();
2058
2100
  if (!ql) return ctx;
2101
+ if (!ctx.email) {
2102
+ const userRows = await tryFind(ql, "sys_user", { id: userId }, 1);
2103
+ if (userRows[0]?.email) ctx.email = String(userRows[0].email);
2104
+ }
2059
2105
  const memberWhere = tenantId ? { user_id: userId, organization_id: tenantId } : { user_id: userId };
2060
2106
  const members = await tryFind(ql, "sys_member", memberWhere, 50);
2061
2107
  for (const m of members) {
@@ -2065,6 +2111,13 @@ async function resolveExecutionContext(opts) {
2065
2111
  }
2066
2112
  }
2067
2113
  }
2114
+ const userRoleRows = await tryFind(ql, "sys_user_role", { user_id: userId }, 200);
2115
+ for (const ur of userRoleRows) {
2116
+ const org = ur.organization_id ?? null;
2117
+ if (org && tenantId && org !== tenantId) continue;
2118
+ const r = ur.role;
2119
+ if (typeof r === "string" && r && !ctx.roles.includes(r)) ctx.roles.push(r);
2120
+ }
2068
2121
  if (tenantId) {
2069
2122
  const orgMembers = await tryFind(
2070
2123
  ql,
@@ -2151,6 +2204,7 @@ async function resolveExecutionContext(opts) {
2151
2204
  const localization = await resolveLocalization(opts, ql, { tenantId, userId });
2152
2205
  ctx.timezone = localization.timezone;
2153
2206
  ctx.locale = localization.locale;
2207
+ if (localization.currency) ctx.currency = localization.currency;
2154
2208
  return ctx;
2155
2209
  }
2156
2210
  function isPermissionDeniedError(e) {