@augmenting-integrations/platform 8.4.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/README.md ADDED
@@ -0,0 +1,32 @@
1
+ # @augmenting-integrations/platform
2
+
3
+ Tenant + app manifest contract for the augint platform. The single source of
4
+ truth for:
5
+
6
+ - `TenantServerConfig` and `TenantPublicConfig` (env-loaded tenant struct +
7
+ browser-safe subset)
8
+ - `<TenantBootScript />`, `<TenantProvider>`, `useTenant()` (server-injected
9
+ and client-readable tenant context)
10
+ - `app.manifest.json` schema and loader (per-app declaration of slug, role,
11
+ subdomain, access policy, feature enablement, data plane)
12
+
13
+ Every other `@augmenting-integrations/*` package consumes tenant context from
14
+ here, so `@augmenting-integrations/auth` no longer owns it. UI components,
15
+ billing handlers, registry tooling, and deploy tooling all import the same
16
+ struct.
17
+
18
+ ## Subpaths
19
+
20
+ - `./server` — `loadTenantConfig`, `publicSubset`, `TenantBootScript`
21
+ - `./client` — `TenantProvider`, `useTenant`
22
+ - `./manifest` — `loadManifest`, `AppManifest`, `validateManifest`
23
+
24
+ ## app.manifest.json
25
+
26
+ Every spoke and apex app ships an `app.manifest.json` at the repo root. The
27
+ manifest is the local source of truth for slug/subdomain/displayName/navOrder,
28
+ app-level access policy, feature enablement, and the data plane choice. The
29
+ deploy workflow reads this file to register the app and to provision the
30
+ right infra modules.
31
+
32
+ See `src/manifest/schema.ts` for the canonical type.
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ "use client";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+ var tenant_exports = {};
31
+ __export(tenant_exports, {
32
+ TENANT_GLOBAL_KEY: () => import_tenant_types2.TENANT_GLOBAL_KEY,
33
+ TenantProvider: () => TenantProvider,
34
+ useTenant: () => useTenant
35
+ });
36
+ module.exports = __toCommonJS(tenant_exports);
37
+ var React = __toESM(require("react"));
38
+ var import_tenant_types = require("../tenant-types.js");
39
+ var import_tenant_types2 = require("../tenant-types.js");
40
+ const TenantContext = React.createContext(null);
41
+ function TenantProvider({
42
+ tenant,
43
+ children
44
+ }) {
45
+ return React.createElement(TenantContext.Provider, { value: tenant }, children);
46
+ }
47
+ function useTenant() {
48
+ const ctx = React.useContext(TenantContext);
49
+ if (ctx) return ctx;
50
+ if (typeof window !== "undefined") {
51
+ const fromGlobal = window[import_tenant_types.TENANT_GLOBAL_KEY];
52
+ if (fromGlobal) return fromGlobal;
53
+ }
54
+ throw new Error(
55
+ "useTenant() called outside <TenantProvider> and before <TenantBootScript /> ran. Mount both in the root layout."
56
+ );
57
+ }
58
+ // Annotate the CommonJS export names for ESM import in node:
59
+ 0 && (module.exports = {
60
+ TENANT_GLOBAL_KEY,
61
+ TenantProvider,
62
+ useTenant
63
+ });
64
+ //# sourceMappingURL=tenant.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/client/tenant.ts"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { TENANT_GLOBAL_KEY, type TenantPublicConfig } from \"../tenant-types.js\";\n\n// =============================================================================\n// <TenantProvider> + useTenant()\n//\n// Single source of tenant truth on the client. Rendered server-side as part\n// of the root layout; reads the same TenantPublicConfig passed to\n// <TenantBootScript />. The Context value flows through SSR, so server\n// components rendered as children + every client component can call\n// useTenant() without worrying about hydration order.\n//\n// We also accept window.__TENANT__ as a fallback for non-React widgets and\n// for the rare case of a client component rendered outside the Provider\n// tree (a defensive read).\n// =============================================================================\n\nconst TenantContext = React.createContext<TenantPublicConfig | null>(null);\n\nexport function TenantProvider({\n tenant,\n children,\n}: {\n tenant: TenantPublicConfig;\n children: React.ReactNode;\n}) {\n return React.createElement(TenantContext.Provider, { value: tenant }, children);\n}\n\nexport function useTenant(): TenantPublicConfig {\n const ctx = React.useContext(TenantContext);\n if (ctx) return ctx;\n // Defensive fallback: a non-Provider client widget. Only works after\n // <TenantBootScript /> has run.\n if (typeof window !== \"undefined\") {\n const fromGlobal = window[TENANT_GLOBAL_KEY];\n if (fromGlobal) return fromGlobal;\n }\n throw new Error(\n \"useTenant() called outside <TenantProvider> and before <TenantBootScript /> ran. Mount both in the root layout.\",\n );\n}\n\nexport {\n TENANT_GLOBAL_KEY,\n type TenantPublicConfig,\n type TenantRole,\n} from \"../tenant-types.js\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,YAAuB;AACvB,0BAA2D;AA0C3D,IAAAA,uBAIO;AA9BP,MAAM,gBAAgB,MAAM,cAAyC,IAAI;AAElE,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AACF,GAGG;AACD,SAAO,MAAM,cAAc,cAAc,UAAU,EAAE,OAAO,OAAO,GAAG,QAAQ;AAChF;AAEO,SAAS,YAAgC;AAC9C,QAAM,MAAM,MAAM,WAAW,aAAa;AAC1C,MAAI,IAAK,QAAO;AAGhB,MAAI,OAAO,WAAW,aAAa;AACjC,UAAM,aAAa,OAAO,qCAAiB;AAC3C,QAAI,WAAY,QAAO;AAAA,EACzB;AACA,QAAM,IAAI;AAAA,IACR;AAAA,EACF;AACF;","names":["import_tenant_types"]}
@@ -0,0 +1,9 @@
1
+ import * as React from "react";
2
+ import { type TenantPublicConfig } from "../tenant-types.js";
3
+ export declare function TenantProvider({ tenant, children, }: {
4
+ tenant: TenantPublicConfig;
5
+ children: React.ReactNode;
6
+ }): React.FunctionComponentElement<React.ProviderProps<TenantPublicConfig | null>>;
7
+ export declare function useTenant(): TenantPublicConfig;
8
+ export { TENANT_GLOBAL_KEY, type TenantPublicConfig, type TenantRole, } from "../tenant-types.js";
9
+ //# sourceMappingURL=tenant.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tenant.d.ts","sourceRoot":"","sources":["../../src/client/tenant.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAqB,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAkBhF,wBAAgB,cAAc,CAAC,EAC7B,MAAM,EACN,QAAQ,GACT,EAAE;IACD,MAAM,EAAE,kBAAkB,CAAC;IAC3B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B,kFAEA;AAED,wBAAgB,SAAS,IAAI,kBAAkB,CAY9C;AAED,OAAO,EACL,iBAAiB,EACjB,KAAK,kBAAkB,EACvB,KAAK,UAAU,GAChB,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,30 @@
1
+ "use client";
2
+ import * as React from "react";
3
+ import { TENANT_GLOBAL_KEY } from "../tenant-types.js";
4
+ const TenantContext = React.createContext(null);
5
+ function TenantProvider({
6
+ tenant,
7
+ children
8
+ }) {
9
+ return React.createElement(TenantContext.Provider, { value: tenant }, children);
10
+ }
11
+ function useTenant() {
12
+ const ctx = React.useContext(TenantContext);
13
+ if (ctx) return ctx;
14
+ if (typeof window !== "undefined") {
15
+ const fromGlobal = window[TENANT_GLOBAL_KEY];
16
+ if (fromGlobal) return fromGlobal;
17
+ }
18
+ throw new Error(
19
+ "useTenant() called outside <TenantProvider> and before <TenantBootScript /> ran. Mount both in the root layout."
20
+ );
21
+ }
22
+ import {
23
+ TENANT_GLOBAL_KEY as TENANT_GLOBAL_KEY2
24
+ } from "../tenant-types.js";
25
+ export {
26
+ TENANT_GLOBAL_KEY2 as TENANT_GLOBAL_KEY,
27
+ TenantProvider,
28
+ useTenant
29
+ };
30
+ //# sourceMappingURL=tenant.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/client/tenant.ts"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { TENANT_GLOBAL_KEY, type TenantPublicConfig } from \"../tenant-types.js\";\n\n// =============================================================================\n// <TenantProvider> + useTenant()\n//\n// Single source of tenant truth on the client. Rendered server-side as part\n// of the root layout; reads the same TenantPublicConfig passed to\n// <TenantBootScript />. The Context value flows through SSR, so server\n// components rendered as children + every client component can call\n// useTenant() without worrying about hydration order.\n//\n// We also accept window.__TENANT__ as a fallback for non-React widgets and\n// for the rare case of a client component rendered outside the Provider\n// tree (a defensive read).\n// =============================================================================\n\nconst TenantContext = React.createContext<TenantPublicConfig | null>(null);\n\nexport function TenantProvider({\n tenant,\n children,\n}: {\n tenant: TenantPublicConfig;\n children: React.ReactNode;\n}) {\n return React.createElement(TenantContext.Provider, { value: tenant }, children);\n}\n\nexport function useTenant(): TenantPublicConfig {\n const ctx = React.useContext(TenantContext);\n if (ctx) return ctx;\n // Defensive fallback: a non-Provider client widget. Only works after\n // <TenantBootScript /> has run.\n if (typeof window !== \"undefined\") {\n const fromGlobal = window[TENANT_GLOBAL_KEY];\n if (fromGlobal) return fromGlobal;\n }\n throw new Error(\n \"useTenant() called outside <TenantProvider> and before <TenantBootScript /> ran. Mount both in the root layout.\",\n );\n}\n\nexport {\n TENANT_GLOBAL_KEY,\n type TenantPublicConfig,\n type TenantRole,\n} from \"../tenant-types.js\";\n"],"mappings":";AAEA,YAAY,WAAW;AACvB,SAAS,yBAAkD;AAgB3D,MAAM,gBAAgB,MAAM,cAAyC,IAAI;AAElE,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AACF,GAGG;AACD,SAAO,MAAM,cAAc,cAAc,UAAU,EAAE,OAAO,OAAO,GAAG,QAAQ;AAChF;AAEO,SAAS,YAAgC;AAC9C,QAAM,MAAM,MAAM,WAAW,aAAa;AAC1C,MAAI,IAAK,QAAO;AAGhB,MAAI,OAAO,WAAW,aAAa;AACjC,UAAM,aAAa,OAAO,iBAAiB;AAC3C,QAAI,WAAY,QAAO;AAAA,EACzB;AACA,QAAM,IAAI;AAAA,IACR;AAAA,EACF;AACF;AAEA;AAAA,EACE,qBAAAA;AAAA,OAGK;","names":["TENANT_GLOBAL_KEY"]}
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var client_exports = {};
20
+ __export(client_exports, {
21
+ TENANT_GLOBAL_KEY: () => import_tenant.TENANT_GLOBAL_KEY,
22
+ TenantProvider: () => import_tenant.TenantProvider,
23
+ useTenant: () => import_tenant.useTenant
24
+ });
25
+ module.exports = __toCommonJS(client_exports);
26
+ var import_tenant = require("./client/tenant.js");
27
+ // Annotate the CommonJS export names for ESM import in node:
28
+ 0 && (module.exports = {
29
+ TENANT_GLOBAL_KEY,
30
+ TenantProvider,
31
+ useTenant
32
+ });
33
+ //# sourceMappingURL=client.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client.ts"],"sourcesContent":["export {\n TenantProvider,\n useTenant,\n TENANT_GLOBAL_KEY,\n type TenantPublicConfig,\n type TenantRole,\n} from \"./client/tenant.js\";\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAMO;","names":[]}
@@ -0,0 +1,2 @@
1
+ export { TenantProvider, useTenant, TENANT_GLOBAL_KEY, type TenantPublicConfig, type TenantRole, } from "./client/tenant.js";
2
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,SAAS,EACT,iBAAiB,EACjB,KAAK,kBAAkB,EACvB,KAAK,UAAU,GAChB,MAAM,oBAAoB,CAAC"}
package/dist/client.js ADDED
@@ -0,0 +1,11 @@
1
+ import {
2
+ TenantProvider,
3
+ useTenant,
4
+ TENANT_GLOBAL_KEY
5
+ } from "./client/tenant.js";
6
+ export {
7
+ TENANT_GLOBAL_KEY,
8
+ TenantProvider,
9
+ useTenant
10
+ };
11
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client.ts"],"sourcesContent":["export {\n TenantProvider,\n useTenant,\n TENANT_GLOBAL_KEY,\n type TenantPublicConfig,\n type TenantRole,\n} from \"./client/tenant.js\";\n"],"mappings":"AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAGK;","names":[]}
@@ -0,0 +1,16 @@
1
+ import { type AppManifest } from "./schema.js";
2
+ export type LoadManifestOptions = {
3
+ /** Defaults to process.cwd(). */
4
+ cwd?: string;
5
+ /** Manifest filename. Defaults to "app.manifest.json". */
6
+ filename?: string;
7
+ };
8
+ /**
9
+ * Read and validate an app.manifest.json. Throws with a consolidated error
10
+ * message listing every validation failure. The deploy fails loudly instead
11
+ * of substituting defaults that mask drift.
12
+ */
13
+ export declare function loadManifest(opts?: LoadManifestOptions): AppManifest;
14
+ export { validateManifest, type AppManifest } from "./schema.js";
15
+ export type { ManifestValidationError, AppRole, DataPlaneType } from "./schema.js";
16
+ //# sourceMappingURL=load.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load.d.ts","sourceRoot":"","sources":["../../src/manifest/load.ts"],"names":[],"mappings":"AAEA,OAAO,EAAoB,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAEjE,MAAM,MAAM,mBAAmB,GAAG;IAChC,iCAAiC;IACjC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,GAAE,mBAAwB,GAAG,WAAW,CAyBxE;AAED,OAAO,EAAE,gBAAgB,EAAE,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AACjE,YAAY,EAAE,uBAAuB,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,64 @@
1
+ export type AppRole = "apex" | "spoke";
2
+ export type DataPlaneType = "app-aurora" | "tenant-postgres" | "dynamodb" | "none";
3
+ export type AppManifest = {
4
+ /** Schema version. Always 1 for this generation. */
5
+ schemaVersion: 1;
6
+ /** Tenant slug, e.g. "agency". Matches the tenant repo. */
7
+ tenantSlug: string;
8
+ /** App slug. PK in the registry. Stable identifier. */
9
+ appSlug: string;
10
+ /** "apex" (the auth broker) or "spoke" (a product app). */
11
+ role: AppRole;
12
+ /** Subdomain label. "" for apex. */
13
+ subdomain: string;
14
+ /** Human-friendly name for nav + admin UI. */
15
+ displayName: string;
16
+ /** Sort order in the cross-app nav. Lower = first. */
17
+ navOrder: number;
18
+ access: {
19
+ /**
20
+ * Cognito identity groups required to see AND enter this app. Empty =
21
+ * all authenticated users. This is NOT product-level authorization;
22
+ * it gates entry to the entire app surface.
23
+ */
24
+ requiredIdentityGroups: string[];
25
+ };
26
+ features: {
27
+ /** Has billing surface (Stripe, credit balance, cart). */
28
+ billing: boolean;
29
+ /** Has settings surface (password, MFA, profile). */
30
+ settings: boolean;
31
+ /** Sends invitations (Invitation table required). */
32
+ invitations: boolean;
33
+ /** Supports admin impersonation. */
34
+ impersonation: boolean;
35
+ };
36
+ dataPlane: {
37
+ /**
38
+ * "app-aurora" = per-app Aurora Serverless v2 + RDS Proxy.
39
+ * "tenant-postgres" = shared tenant DB (rare).
40
+ * "dynamodb" = the app only uses DynamoDB tables it provisions itself.
41
+ * "none" = no app-owned data plane (apex auth-broker).
42
+ */
43
+ type: DataPlaneType;
44
+ /** True if Prisma migrations should run on deploy. */
45
+ migrations: boolean;
46
+ };
47
+ };
48
+ export type ManifestValidationError = {
49
+ path: string;
50
+ message: string;
51
+ };
52
+ /**
53
+ * Pure validator. Returns the typed manifest on success, or an array of
54
+ * errors on failure. No throws -- the loader wraps this and throws with
55
+ * a consolidated error message.
56
+ */
57
+ export declare function validateManifest(raw: unknown): {
58
+ ok: true;
59
+ value: AppManifest;
60
+ } | {
61
+ ok: false;
62
+ errors: ManifestValidationError[];
63
+ };
64
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/manifest/schema.ts"],"names":[],"mappings":"AAYA,MAAM,MAAM,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;AAEvC,MAAM,MAAM,aAAa,GAAG,YAAY,GAAG,iBAAiB,GAAG,UAAU,GAAG,MAAM,CAAC;AAEnF,MAAM,MAAM,WAAW,GAAG;IACxB,oDAAoD;IACpD,aAAa,EAAE,CAAC,CAAC;IACjB,2DAA2D;IAC3D,UAAU,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,OAAO,EAAE,MAAM,CAAC;IAChB,2DAA2D;IAC3D,IAAI,EAAE,OAAO,CAAC;IACd,oCAAoC;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,8CAA8C;IAC9C,WAAW,EAAE,MAAM,CAAC;IACpB,sDAAsD;IACtD,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE;QACN;;;;WAIG;QACH,sBAAsB,EAAE,MAAM,EAAE,CAAC;KAClC,CAAC;IACF,QAAQ,EAAE;QACR,0DAA0D;QAC1D,OAAO,EAAE,OAAO,CAAC;QACjB,qDAAqD;QACrD,QAAQ,EAAE,OAAO,CAAC;QAClB,qDAAqD;QACrD,WAAW,EAAE,OAAO,CAAC;QACrB,oCAAoC;QACpC,aAAa,EAAE,OAAO,CAAC;KACxB,CAAC;IACF,SAAS,EAAE;QACT;;;;;WAKG;QACH,IAAI,EAAE,aAAa,CAAC;QACpB,sDAAsD;QACtD,UAAU,EAAE,OAAO,CAAC;KACrB,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,OAAO,GACX;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,WAAW,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,uBAAuB,EAAE,CAAA;CAAE,CA0ErF"}
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/manifest.ts
21
+ var manifest_exports = {};
22
+ __export(manifest_exports, {
23
+ loadManifest: () => loadManifest,
24
+ validateManifest: () => validateManifest
25
+ });
26
+ module.exports = __toCommonJS(manifest_exports);
27
+
28
+ // src/manifest/load.ts
29
+ var import_node_fs = require("fs");
30
+ var import_node_path = require("path");
31
+
32
+ // src/manifest/schema.ts
33
+ function validateManifest(raw) {
34
+ const errors = [];
35
+ if (typeof raw !== "object" || raw === null) {
36
+ return { ok: false, errors: [{ path: "", message: "manifest must be an object" }] };
37
+ }
38
+ const m = raw;
39
+ const requireString = (path, value) => {
40
+ if (typeof value !== "string") errors.push({ path, message: "expected string" });
41
+ };
42
+ const requireNumber = (path, value) => {
43
+ if (typeof value !== "number") errors.push({ path, message: "expected number" });
44
+ };
45
+ const requireBool = (path, value) => {
46
+ if (typeof value !== "boolean") errors.push({ path, message: "expected boolean" });
47
+ };
48
+ const requireOneOf = (path, value, options) => {
49
+ if (typeof value !== "string" || !options.includes(value)) {
50
+ errors.push({ path, message: `expected one of: ${options.join(", ")}` });
51
+ }
52
+ };
53
+ const requireStringArray = (path, value) => {
54
+ if (!Array.isArray(value) || value.some((v) => typeof v !== "string")) {
55
+ errors.push({ path, message: "expected string[]" });
56
+ }
57
+ };
58
+ if (m.schemaVersion !== 1) {
59
+ errors.push({ path: "schemaVersion", message: "expected literal 1" });
60
+ }
61
+ requireString("tenantSlug", m.tenantSlug);
62
+ requireString("appSlug", m.appSlug);
63
+ requireOneOf("role", m.role, ["apex", "spoke"]);
64
+ requireString("subdomain", m.subdomain);
65
+ requireString("displayName", m.displayName);
66
+ requireNumber("navOrder", m.navOrder);
67
+ const access = m.access;
68
+ if (!access || typeof access !== "object") {
69
+ errors.push({ path: "access", message: "expected object" });
70
+ } else {
71
+ requireStringArray("access.requiredIdentityGroups", access.requiredIdentityGroups);
72
+ }
73
+ const features = m.features;
74
+ if (!features || typeof features !== "object") {
75
+ errors.push({ path: "features", message: "expected object" });
76
+ } else {
77
+ requireBool("features.billing", features.billing);
78
+ requireBool("features.settings", features.settings);
79
+ requireBool("features.invitations", features.invitations);
80
+ requireBool("features.impersonation", features.impersonation);
81
+ }
82
+ const dataPlane = m.dataPlane;
83
+ if (!dataPlane || typeof dataPlane !== "object") {
84
+ errors.push({ path: "dataPlane", message: "expected object" });
85
+ } else {
86
+ requireOneOf("dataPlane.type", dataPlane.type, [
87
+ "app-aurora",
88
+ "tenant-postgres",
89
+ "dynamodb",
90
+ "none"
91
+ ]);
92
+ requireBool("dataPlane.migrations", dataPlane.migrations);
93
+ }
94
+ if (m.role === "apex" && m.subdomain !== "") {
95
+ errors.push({ path: "subdomain", message: "apex apps must have empty subdomain" });
96
+ }
97
+ if (errors.length > 0) return { ok: false, errors };
98
+ return { ok: true, value: m };
99
+ }
100
+
101
+ // src/manifest/load.ts
102
+ function loadManifest(opts = {}) {
103
+ const cwd = opts.cwd ?? process.cwd();
104
+ const filename = opts.filename ?? "app.manifest.json";
105
+ const file = (0, import_node_path.resolve)((0, import_node_path.join)(cwd, filename));
106
+ let raw;
107
+ try {
108
+ raw = (0, import_node_fs.readFileSync)(file, "utf8");
109
+ } catch (err) {
110
+ throw new Error(`loadManifest: cannot read ${file}: ${err.message}`);
111
+ }
112
+ let parsed;
113
+ try {
114
+ parsed = JSON.parse(raw);
115
+ } catch (err) {
116
+ throw new Error(`loadManifest: invalid JSON in ${file}: ${err.message}`);
117
+ }
118
+ const result = validateManifest(parsed);
119
+ if (!result.ok) {
120
+ const lines = result.errors.map((e) => ` - ${e.path}: ${e.message}`).join("\n");
121
+ throw new Error(`loadManifest: ${file} failed validation:
122
+ ${lines}`);
123
+ }
124
+ return result.value;
125
+ }
126
+ // Annotate the CommonJS export names for ESM import in node:
127
+ 0 && (module.exports = {
128
+ loadManifest,
129
+ validateManifest
130
+ });
131
+ //# sourceMappingURL=manifest.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/manifest.ts","../src/manifest/load.ts","../src/manifest/schema.ts"],"sourcesContent":["export {\n loadManifest,\n validateManifest,\n type LoadManifestOptions,\n type AppManifest,\n type ManifestValidationError,\n type AppRole,\n type DataPlaneType,\n} from \"./manifest/load.js\";\n","import { readFileSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport { validateManifest, type AppManifest } from \"./schema.js\";\n\nexport type LoadManifestOptions = {\n /** Defaults to process.cwd(). */\n cwd?: string;\n /** Manifest filename. Defaults to \"app.manifest.json\". */\n filename?: string;\n};\n\n/**\n * Read and validate an app.manifest.json. Throws with a consolidated error\n * message listing every validation failure. The deploy fails loudly instead\n * of substituting defaults that mask drift.\n */\nexport function loadManifest(opts: LoadManifestOptions = {}): AppManifest {\n const cwd = opts.cwd ?? process.cwd();\n const filename = opts.filename ?? \"app.manifest.json\";\n const file = resolve(join(cwd, filename));\n\n let raw: string;\n try {\n raw = readFileSync(file, \"utf8\");\n } catch (err) {\n throw new Error(`loadManifest: cannot read ${file}: ${(err as Error).message}`);\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n throw new Error(`loadManifest: invalid JSON in ${file}: ${(err as Error).message}`);\n }\n\n const result = validateManifest(parsed);\n if (!result.ok) {\n const lines = result.errors.map((e) => ` - ${e.path}: ${e.message}`).join(\"\\n\");\n throw new Error(`loadManifest: ${file} failed validation:\\n${lines}`);\n }\n return result.value;\n}\n\nexport { validateManifest, type AppManifest } from \"./schema.js\";\nexport type { ManifestValidationError, AppRole, DataPlaneType } from \"./schema.js\";\n","// =============================================================================\n// app.manifest.json -- per-app declaration of slug, role, subdomain, access\n// policy, feature enablement, and data plane choice. Every spoke and apex\n// app ships one of these at the repo root.\n//\n// The manifest is the local source of truth for the deploy pipeline, the\n// registry registration call, the schema validator, the runtime app-access\n// enforcement, and the spoke kernel infra wiring. Product developers should\n// only have to edit this file (not env vars, workflow files, or registry\n// rows) to change their app's identity or feature set.\n// =============================================================================\n\nexport type AppRole = \"apex\" | \"spoke\";\n\nexport type DataPlaneType = \"app-aurora\" | \"tenant-postgres\" | \"dynamodb\" | \"none\";\n\nexport type AppManifest = {\n /** Schema version. Always 1 for this generation. */\n schemaVersion: 1;\n /** Tenant slug, e.g. \"agency\". Matches the tenant repo. */\n tenantSlug: string;\n /** App slug. PK in the registry. Stable identifier. */\n appSlug: string;\n /** \"apex\" (the auth broker) or \"spoke\" (a product app). */\n role: AppRole;\n /** Subdomain label. \"\" for apex. */\n subdomain: string;\n /** Human-friendly name for nav + admin UI. */\n displayName: string;\n /** Sort order in the cross-app nav. Lower = first. */\n navOrder: number;\n access: {\n /**\n * Cognito identity groups required to see AND enter this app. Empty =\n * all authenticated users. This is NOT product-level authorization;\n * it gates entry to the entire app surface.\n */\n requiredIdentityGroups: string[];\n };\n features: {\n /** Has billing surface (Stripe, credit balance, cart). */\n billing: boolean;\n /** Has settings surface (password, MFA, profile). */\n settings: boolean;\n /** Sends invitations (Invitation table required). */\n invitations: boolean;\n /** Supports admin impersonation. */\n impersonation: boolean;\n };\n dataPlane: {\n /**\n * \"app-aurora\" = per-app Aurora Serverless v2 + RDS Proxy.\n * \"tenant-postgres\" = shared tenant DB (rare).\n * \"dynamodb\" = the app only uses DynamoDB tables it provisions itself.\n * \"none\" = no app-owned data plane (apex auth-broker).\n */\n type: DataPlaneType;\n /** True if Prisma migrations should run on deploy. */\n migrations: boolean;\n };\n};\n\nexport type ManifestValidationError = {\n path: string;\n message: string;\n};\n\n/**\n * Pure validator. Returns the typed manifest on success, or an array of\n * errors on failure. No throws -- the loader wraps this and throws with\n * a consolidated error message.\n */\nexport function validateManifest(\n raw: unknown,\n): { ok: true; value: AppManifest } | { ok: false; errors: ManifestValidationError[] } {\n const errors: ManifestValidationError[] = [];\n if (typeof raw !== \"object\" || raw === null) {\n return { ok: false, errors: [{ path: \"\", message: \"manifest must be an object\" }] };\n }\n const m = raw as Record<string, unknown>;\n\n const requireString = (path: string, value: unknown) => {\n if (typeof value !== \"string\") errors.push({ path, message: \"expected string\" });\n };\n const requireNumber = (path: string, value: unknown) => {\n if (typeof value !== \"number\") errors.push({ path, message: \"expected number\" });\n };\n const requireBool = (path: string, value: unknown) => {\n if (typeof value !== \"boolean\") errors.push({ path, message: \"expected boolean\" });\n };\n const requireOneOf = (path: string, value: unknown, options: readonly string[]) => {\n if (typeof value !== \"string\" || !options.includes(value)) {\n errors.push({ path, message: `expected one of: ${options.join(\", \")}` });\n }\n };\n const requireStringArray = (path: string, value: unknown) => {\n if (!Array.isArray(value) || value.some((v) => typeof v !== \"string\")) {\n errors.push({ path, message: \"expected string[]\" });\n }\n };\n\n if (m.schemaVersion !== 1) {\n errors.push({ path: \"schemaVersion\", message: \"expected literal 1\" });\n }\n requireString(\"tenantSlug\", m.tenantSlug);\n requireString(\"appSlug\", m.appSlug);\n requireOneOf(\"role\", m.role, [\"apex\", \"spoke\"]);\n requireString(\"subdomain\", m.subdomain);\n requireString(\"displayName\", m.displayName);\n requireNumber(\"navOrder\", m.navOrder);\n\n const access = m.access as Record<string, unknown> | undefined;\n if (!access || typeof access !== \"object\") {\n errors.push({ path: \"access\", message: \"expected object\" });\n } else {\n requireStringArray(\"access.requiredIdentityGroups\", access.requiredIdentityGroups);\n }\n\n const features = m.features as Record<string, unknown> | undefined;\n if (!features || typeof features !== \"object\") {\n errors.push({ path: \"features\", message: \"expected object\" });\n } else {\n requireBool(\"features.billing\", features.billing);\n requireBool(\"features.settings\", features.settings);\n requireBool(\"features.invitations\", features.invitations);\n requireBool(\"features.impersonation\", features.impersonation);\n }\n\n const dataPlane = m.dataPlane as Record<string, unknown> | undefined;\n if (!dataPlane || typeof dataPlane !== \"object\") {\n errors.push({ path: \"dataPlane\", message: \"expected object\" });\n } else {\n requireOneOf(\"dataPlane.type\", dataPlane.type, [\n \"app-aurora\",\n \"tenant-postgres\",\n \"dynamodb\",\n \"none\",\n ]);\n requireBool(\"dataPlane.migrations\", dataPlane.migrations);\n }\n\n // Apex consistency: subdomain must be empty.\n if (m.role === \"apex\" && m.subdomain !== \"\") {\n errors.push({ path: \"subdomain\", message: \"apex apps must have empty subdomain\" });\n }\n\n if (errors.length > 0) return { ok: false, errors };\n return { ok: true, value: m as unknown as AppManifest };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,2 @@
1
+ export { loadManifest, validateManifest, type LoadManifestOptions, type AppManifest, type ManifestValidationError, type AppRole, type DataPlaneType, } from "./manifest/load.js";
2
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,KAAK,mBAAmB,EACxB,KAAK,WAAW,EAChB,KAAK,uBAAuB,EAC5B,KAAK,OAAO,EACZ,KAAK,aAAa,GACnB,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,103 @@
1
+ // src/manifest/load.ts
2
+ import { readFileSync } from "fs";
3
+ import { join, resolve } from "path";
4
+
5
+ // src/manifest/schema.ts
6
+ function validateManifest(raw) {
7
+ const errors = [];
8
+ if (typeof raw !== "object" || raw === null) {
9
+ return { ok: false, errors: [{ path: "", message: "manifest must be an object" }] };
10
+ }
11
+ const m = raw;
12
+ const requireString = (path, value) => {
13
+ if (typeof value !== "string") errors.push({ path, message: "expected string" });
14
+ };
15
+ const requireNumber = (path, value) => {
16
+ if (typeof value !== "number") errors.push({ path, message: "expected number" });
17
+ };
18
+ const requireBool = (path, value) => {
19
+ if (typeof value !== "boolean") errors.push({ path, message: "expected boolean" });
20
+ };
21
+ const requireOneOf = (path, value, options) => {
22
+ if (typeof value !== "string" || !options.includes(value)) {
23
+ errors.push({ path, message: `expected one of: ${options.join(", ")}` });
24
+ }
25
+ };
26
+ const requireStringArray = (path, value) => {
27
+ if (!Array.isArray(value) || value.some((v) => typeof v !== "string")) {
28
+ errors.push({ path, message: "expected string[]" });
29
+ }
30
+ };
31
+ if (m.schemaVersion !== 1) {
32
+ errors.push({ path: "schemaVersion", message: "expected literal 1" });
33
+ }
34
+ requireString("tenantSlug", m.tenantSlug);
35
+ requireString("appSlug", m.appSlug);
36
+ requireOneOf("role", m.role, ["apex", "spoke"]);
37
+ requireString("subdomain", m.subdomain);
38
+ requireString("displayName", m.displayName);
39
+ requireNumber("navOrder", m.navOrder);
40
+ const access = m.access;
41
+ if (!access || typeof access !== "object") {
42
+ errors.push({ path: "access", message: "expected object" });
43
+ } else {
44
+ requireStringArray("access.requiredIdentityGroups", access.requiredIdentityGroups);
45
+ }
46
+ const features = m.features;
47
+ if (!features || typeof features !== "object") {
48
+ errors.push({ path: "features", message: "expected object" });
49
+ } else {
50
+ requireBool("features.billing", features.billing);
51
+ requireBool("features.settings", features.settings);
52
+ requireBool("features.invitations", features.invitations);
53
+ requireBool("features.impersonation", features.impersonation);
54
+ }
55
+ const dataPlane = m.dataPlane;
56
+ if (!dataPlane || typeof dataPlane !== "object") {
57
+ errors.push({ path: "dataPlane", message: "expected object" });
58
+ } else {
59
+ requireOneOf("dataPlane.type", dataPlane.type, [
60
+ "app-aurora",
61
+ "tenant-postgres",
62
+ "dynamodb",
63
+ "none"
64
+ ]);
65
+ requireBool("dataPlane.migrations", dataPlane.migrations);
66
+ }
67
+ if (m.role === "apex" && m.subdomain !== "") {
68
+ errors.push({ path: "subdomain", message: "apex apps must have empty subdomain" });
69
+ }
70
+ if (errors.length > 0) return { ok: false, errors };
71
+ return { ok: true, value: m };
72
+ }
73
+
74
+ // src/manifest/load.ts
75
+ function loadManifest(opts = {}) {
76
+ const cwd = opts.cwd ?? process.cwd();
77
+ const filename = opts.filename ?? "app.manifest.json";
78
+ const file = resolve(join(cwd, filename));
79
+ let raw;
80
+ try {
81
+ raw = readFileSync(file, "utf8");
82
+ } catch (err) {
83
+ throw new Error(`loadManifest: cannot read ${file}: ${err.message}`);
84
+ }
85
+ let parsed;
86
+ try {
87
+ parsed = JSON.parse(raw);
88
+ } catch (err) {
89
+ throw new Error(`loadManifest: invalid JSON in ${file}: ${err.message}`);
90
+ }
91
+ const result = validateManifest(parsed);
92
+ if (!result.ok) {
93
+ const lines = result.errors.map((e) => ` - ${e.path}: ${e.message}`).join("\n");
94
+ throw new Error(`loadManifest: ${file} failed validation:
95
+ ${lines}`);
96
+ }
97
+ return result.value;
98
+ }
99
+ export {
100
+ loadManifest,
101
+ validateManifest
102
+ };
103
+ //# sourceMappingURL=manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/manifest/load.ts","../src/manifest/schema.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport { validateManifest, type AppManifest } from \"./schema.js\";\n\nexport type LoadManifestOptions = {\n /** Defaults to process.cwd(). */\n cwd?: string;\n /** Manifest filename. Defaults to \"app.manifest.json\". */\n filename?: string;\n};\n\n/**\n * Read and validate an app.manifest.json. Throws with a consolidated error\n * message listing every validation failure. The deploy fails loudly instead\n * of substituting defaults that mask drift.\n */\nexport function loadManifest(opts: LoadManifestOptions = {}): AppManifest {\n const cwd = opts.cwd ?? process.cwd();\n const filename = opts.filename ?? \"app.manifest.json\";\n const file = resolve(join(cwd, filename));\n\n let raw: string;\n try {\n raw = readFileSync(file, \"utf8\");\n } catch (err) {\n throw new Error(`loadManifest: cannot read ${file}: ${(err as Error).message}`);\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n throw new Error(`loadManifest: invalid JSON in ${file}: ${(err as Error).message}`);\n }\n\n const result = validateManifest(parsed);\n if (!result.ok) {\n const lines = result.errors.map((e) => ` - ${e.path}: ${e.message}`).join(\"\\n\");\n throw new Error(`loadManifest: ${file} failed validation:\\n${lines}`);\n }\n return result.value;\n}\n\nexport { validateManifest, type AppManifest } from \"./schema.js\";\nexport type { ManifestValidationError, AppRole, DataPlaneType } from \"./schema.js\";\n","// =============================================================================\n// app.manifest.json -- per-app declaration of slug, role, subdomain, access\n// policy, feature enablement, and data plane choice. Every spoke and apex\n// app ships one of these at the repo root.\n//\n// The manifest is the local source of truth for the deploy pipeline, the\n// registry registration call, the schema validator, the runtime app-access\n// enforcement, and the spoke kernel infra wiring. Product developers should\n// only have to edit this file (not env vars, workflow files, or registry\n// rows) to change their app's identity or feature set.\n// =============================================================================\n\nexport type AppRole = \"apex\" | \"spoke\";\n\nexport type DataPlaneType = \"app-aurora\" | \"tenant-postgres\" | \"dynamodb\" | \"none\";\n\nexport type AppManifest = {\n /** Schema version. Always 1 for this generation. */\n schemaVersion: 1;\n /** Tenant slug, e.g. \"agency\". Matches the tenant repo. */\n tenantSlug: string;\n /** App slug. PK in the registry. Stable identifier. */\n appSlug: string;\n /** \"apex\" (the auth broker) or \"spoke\" (a product app). */\n role: AppRole;\n /** Subdomain label. \"\" for apex. */\n subdomain: string;\n /** Human-friendly name for nav + admin UI. */\n displayName: string;\n /** Sort order in the cross-app nav. Lower = first. */\n navOrder: number;\n access: {\n /**\n * Cognito identity groups required to see AND enter this app. Empty =\n * all authenticated users. This is NOT product-level authorization;\n * it gates entry to the entire app surface.\n */\n requiredIdentityGroups: string[];\n };\n features: {\n /** Has billing surface (Stripe, credit balance, cart). */\n billing: boolean;\n /** Has settings surface (password, MFA, profile). */\n settings: boolean;\n /** Sends invitations (Invitation table required). */\n invitations: boolean;\n /** Supports admin impersonation. */\n impersonation: boolean;\n };\n dataPlane: {\n /**\n * \"app-aurora\" = per-app Aurora Serverless v2 + RDS Proxy.\n * \"tenant-postgres\" = shared tenant DB (rare).\n * \"dynamodb\" = the app only uses DynamoDB tables it provisions itself.\n * \"none\" = no app-owned data plane (apex auth-broker).\n */\n type: DataPlaneType;\n /** True if Prisma migrations should run on deploy. */\n migrations: boolean;\n };\n};\n\nexport type ManifestValidationError = {\n path: string;\n message: string;\n};\n\n/**\n * Pure validator. Returns the typed manifest on success, or an array of\n * errors on failure. No throws -- the loader wraps this and throws with\n * a consolidated error message.\n */\nexport function validateManifest(\n raw: unknown,\n): { ok: true; value: AppManifest } | { ok: false; errors: ManifestValidationError[] } {\n const errors: ManifestValidationError[] = [];\n if (typeof raw !== \"object\" || raw === null) {\n return { ok: false, errors: [{ path: \"\", message: \"manifest must be an object\" }] };\n }\n const m = raw as Record<string, unknown>;\n\n const requireString = (path: string, value: unknown) => {\n if (typeof value !== \"string\") errors.push({ path, message: \"expected string\" });\n };\n const requireNumber = (path: string, value: unknown) => {\n if (typeof value !== \"number\") errors.push({ path, message: \"expected number\" });\n };\n const requireBool = (path: string, value: unknown) => {\n if (typeof value !== \"boolean\") errors.push({ path, message: \"expected boolean\" });\n };\n const requireOneOf = (path: string, value: unknown, options: readonly string[]) => {\n if (typeof value !== \"string\" || !options.includes(value)) {\n errors.push({ path, message: `expected one of: ${options.join(\", \")}` });\n }\n };\n const requireStringArray = (path: string, value: unknown) => {\n if (!Array.isArray(value) || value.some((v) => typeof v !== \"string\")) {\n errors.push({ path, message: \"expected string[]\" });\n }\n };\n\n if (m.schemaVersion !== 1) {\n errors.push({ path: \"schemaVersion\", message: \"expected literal 1\" });\n }\n requireString(\"tenantSlug\", m.tenantSlug);\n requireString(\"appSlug\", m.appSlug);\n requireOneOf(\"role\", m.role, [\"apex\", \"spoke\"]);\n requireString(\"subdomain\", m.subdomain);\n requireString(\"displayName\", m.displayName);\n requireNumber(\"navOrder\", m.navOrder);\n\n const access = m.access as Record<string, unknown> | undefined;\n if (!access || typeof access !== \"object\") {\n errors.push({ path: \"access\", message: \"expected object\" });\n } else {\n requireStringArray(\"access.requiredIdentityGroups\", access.requiredIdentityGroups);\n }\n\n const features = m.features as Record<string, unknown> | undefined;\n if (!features || typeof features !== \"object\") {\n errors.push({ path: \"features\", message: \"expected object\" });\n } else {\n requireBool(\"features.billing\", features.billing);\n requireBool(\"features.settings\", features.settings);\n requireBool(\"features.invitations\", features.invitations);\n requireBool(\"features.impersonation\", features.impersonation);\n }\n\n const dataPlane = m.dataPlane as Record<string, unknown> | undefined;\n if (!dataPlane || typeof dataPlane !== \"object\") {\n errors.push({ path: \"dataPlane\", message: \"expected object\" });\n } else {\n requireOneOf(\"dataPlane.type\", dataPlane.type, [\n \"app-aurora\",\n \"tenant-postgres\",\n \"dynamodb\",\n \"none\",\n ]);\n requireBool(\"dataPlane.migrations\", dataPlane.migrations);\n }\n\n // Apex consistency: subdomain must be empty.\n if (m.role === \"apex\" && m.subdomain !== \"\") {\n errors.push({ path: \"subdomain\", message: \"apex apps must have empty subdomain\" });\n }\n\n if (errors.length > 0) return { ok: false, errors };\n return { ok: true, value: m as unknown as AppManifest };\n}\n"],"mappings":";AAAA,SAAS,oBAAoB;AAC7B,SAAS,MAAM,eAAe;;;ACuEvB,SAAS,iBACd,KACqF;AACrF,QAAM,SAAoC,CAAC;AAC3C,MAAI,OAAO,QAAQ,YAAY,QAAQ,MAAM;AAC3C,WAAO,EAAE,IAAI,OAAO,QAAQ,CAAC,EAAE,MAAM,IAAI,SAAS,6BAA6B,CAAC,EAAE;AAAA,EACpF;AACA,QAAM,IAAI;AAEV,QAAM,gBAAgB,CAAC,MAAc,UAAmB;AACtD,QAAI,OAAO,UAAU,SAAU,QAAO,KAAK,EAAE,MAAM,SAAS,kBAAkB,CAAC;AAAA,EACjF;AACA,QAAM,gBAAgB,CAAC,MAAc,UAAmB;AACtD,QAAI,OAAO,UAAU,SAAU,QAAO,KAAK,EAAE,MAAM,SAAS,kBAAkB,CAAC;AAAA,EACjF;AACA,QAAM,cAAc,CAAC,MAAc,UAAmB;AACpD,QAAI,OAAO,UAAU,UAAW,QAAO,KAAK,EAAE,MAAM,SAAS,mBAAmB,CAAC;AAAA,EACnF;AACA,QAAM,eAAe,CAAC,MAAc,OAAgB,YAA+B;AACjF,QAAI,OAAO,UAAU,YAAY,CAAC,QAAQ,SAAS,KAAK,GAAG;AACzD,aAAO,KAAK,EAAE,MAAM,SAAS,oBAAoB,QAAQ,KAAK,IAAI,CAAC,GAAG,CAAC;AAAA,IACzE;AAAA,EACF;AACA,QAAM,qBAAqB,CAAC,MAAc,UAAmB;AAC3D,QAAI,CAAC,MAAM,QAAQ,KAAK,KAAK,MAAM,KAAK,CAAC,MAAM,OAAO,MAAM,QAAQ,GAAG;AACrE,aAAO,KAAK,EAAE,MAAM,SAAS,oBAAoB,CAAC;AAAA,IACpD;AAAA,EACF;AAEA,MAAI,EAAE,kBAAkB,GAAG;AACzB,WAAO,KAAK,EAAE,MAAM,iBAAiB,SAAS,qBAAqB,CAAC;AAAA,EACtE;AACA,gBAAc,cAAc,EAAE,UAAU;AACxC,gBAAc,WAAW,EAAE,OAAO;AAClC,eAAa,QAAQ,EAAE,MAAM,CAAC,QAAQ,OAAO,CAAC;AAC9C,gBAAc,aAAa,EAAE,SAAS;AACtC,gBAAc,eAAe,EAAE,WAAW;AAC1C,gBAAc,YAAY,EAAE,QAAQ;AAEpC,QAAM,SAAS,EAAE;AACjB,MAAI,CAAC,UAAU,OAAO,WAAW,UAAU;AACzC,WAAO,KAAK,EAAE,MAAM,UAAU,SAAS,kBAAkB,CAAC;AAAA,EAC5D,OAAO;AACL,uBAAmB,iCAAiC,OAAO,sBAAsB;AAAA,EACnF;AAEA,QAAM,WAAW,EAAE;AACnB,MAAI,CAAC,YAAY,OAAO,aAAa,UAAU;AAC7C,WAAO,KAAK,EAAE,MAAM,YAAY,SAAS,kBAAkB,CAAC;AAAA,EAC9D,OAAO;AACL,gBAAY,oBAAoB,SAAS,OAAO;AAChD,gBAAY,qBAAqB,SAAS,QAAQ;AAClD,gBAAY,wBAAwB,SAAS,WAAW;AACxD,gBAAY,0BAA0B,SAAS,aAAa;AAAA,EAC9D;AAEA,QAAM,YAAY,EAAE;AACpB,MAAI,CAAC,aAAa,OAAO,cAAc,UAAU;AAC/C,WAAO,KAAK,EAAE,MAAM,aAAa,SAAS,kBAAkB,CAAC;AAAA,EAC/D,OAAO;AACL,iBAAa,kBAAkB,UAAU,MAAM;AAAA,MAC7C;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AACD,gBAAY,wBAAwB,UAAU,UAAU;AAAA,EAC1D;AAGA,MAAI,EAAE,SAAS,UAAU,EAAE,cAAc,IAAI;AAC3C,WAAO,KAAK,EAAE,MAAM,aAAa,SAAS,sCAAsC,CAAC;AAAA,EACnF;AAEA,MAAI,OAAO,SAAS,EAAG,QAAO,EAAE,IAAI,OAAO,OAAO;AAClD,SAAO,EAAE,IAAI,MAAM,OAAO,EAA4B;AACxD;;;ADpIO,SAAS,aAAa,OAA4B,CAAC,GAAgB;AACxE,QAAM,MAAM,KAAK,OAAO,QAAQ,IAAI;AACpC,QAAM,WAAW,KAAK,YAAY;AAClC,QAAM,OAAO,QAAQ,KAAK,KAAK,QAAQ,CAAC;AAExC,MAAI;AACJ,MAAI;AACF,UAAM,aAAa,MAAM,MAAM;AAAA,EACjC,SAAS,KAAK;AACZ,UAAM,IAAI,MAAM,6BAA6B,IAAI,KAAM,IAAc,OAAO,EAAE;AAAA,EAChF;AAEA,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,GAAG;AAAA,EACzB,SAAS,KAAK;AACZ,UAAM,IAAI,MAAM,iCAAiC,IAAI,KAAM,IAAc,OAAO,EAAE;AAAA,EACpF;AAEA,QAAM,SAAS,iBAAiB,MAAM;AACtC,MAAI,CAAC,OAAO,IAAI;AACd,UAAM,QAAQ,OAAO,OAAO,IAAI,CAAC,MAAM,OAAO,EAAE,IAAI,KAAK,EAAE,OAAO,EAAE,EAAE,KAAK,IAAI;AAC/E,UAAM,IAAI,MAAM,iBAAiB,IAAI;AAAA,EAAwB,KAAK,EAAE;AAAA,EACtE;AACA,SAAO,OAAO;AAChB;","names":[]}
@@ -0,0 +1,26 @@
1
+ import "server-only";
2
+ import * as React from "react";
3
+ import { type TenantPublicConfig, type TenantRole, type TenantServerConfig } from "../tenant-types.js";
4
+ export type LoadOptions = {
5
+ role: TenantRole;
6
+ /**
7
+ * Override env reads with explicit values (useful for tests).
8
+ */
9
+ overrides?: Partial<TenantServerConfig>;
10
+ };
11
+ /**
12
+ * Read tenant configuration from process.env with optional overrides.
13
+ * Throws a single Error listing every missing required field.
14
+ */
15
+ export declare function loadTenantConfig(opts: LoadOptions): TenantServerConfig;
16
+ /**
17
+ * Reduce a TenantServerConfig to the public-safe subset. Strips every
18
+ * secret-arn so the result is safe to ship to the browser via
19
+ * <TenantBootScript />.
20
+ */
21
+ export declare function publicSubset(config: TenantServerConfig): TenantPublicConfig;
22
+ export declare function TenantBootScript({ config }: {
23
+ config: TenantPublicConfig;
24
+ }): React.DetailedReactHTMLElement<Record<string, unknown>, HTMLElement>;
25
+ export { TENANT_GLOBAL_KEY, type TenantPublicConfig, type TenantServerConfig, type TenantRole, } from "../tenant-types.js";
26
+ //# sourceMappingURL=tenant.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tenant.d.ts","sourceRoot":"","sources":["../../src/server/tenant.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAC;AACrB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,UAAU,EACf,KAAK,kBAAkB,EACxB,MAAM,oBAAoB,CAAC;AAoB5B,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,UAAU,CAAC;IACjB;;OAEG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,CAAC;CACzC,CAAC;AAEF;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,WAAW,GAAG,kBAAkB,CAgEtE;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,kBAAkB,GAAG,kBAAkB,CAU3E;AAcD,wBAAgB,gBAAgB,CAAC,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,kBAAkB,CAAA;CAAE,wEAM1E;AAED,OAAO,EACL,iBAAiB,EACjB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,UAAU,GAChB,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,129 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/server.ts
31
+ var server_exports = {};
32
+ __export(server_exports, {
33
+ TENANT_GLOBAL_KEY: () => TENANT_GLOBAL_KEY,
34
+ TenantBootScript: () => TenantBootScript,
35
+ loadTenantConfig: () => loadTenantConfig,
36
+ publicSubset: () => publicSubset
37
+ });
38
+ module.exports = __toCommonJS(server_exports);
39
+
40
+ // src/server/tenant.ts
41
+ var import_server_only = require("server-only");
42
+ var React = __toESM(require("react"));
43
+
44
+ // src/tenant-types.ts
45
+ var TENANT_GLOBAL_KEY = "__TENANT__";
46
+
47
+ // src/server/tenant.ts
48
+ function loadTenantConfig(opts) {
49
+ const env = process.env;
50
+ const o = opts.overrides ?? {};
51
+ const parentDomainRaw = o.parentDomain ?? env.AUTH_ALLOWED_PARENT_DOMAIN;
52
+ const apexFallback = parentDomainRaw?.replace(/^\./, "");
53
+ const draft = {
54
+ role: opts.role,
55
+ apex: o.apex ?? env.APEX_DOMAIN ?? apexFallback,
56
+ cookieDomain: o.cookieDomain ?? env.AUTH_COOKIE_DOMAIN,
57
+ parentDomain: parentDomainRaw,
58
+ region: o.region ?? env.AWS_REGION ?? "us-east-1",
59
+ appSlug: o.appSlug ?? env.APP_SLUG,
60
+ appDomain: o.appDomain ?? env.APP_DOMAIN,
61
+ authSecretArn: o.authSecretArn ?? env.AUTH_SECRET_ARN,
62
+ registryTable: o.registryTable ?? env.APP_REGISTRY_TABLE,
63
+ authCognitoSecretArn: o.authCognitoSecretArn ?? env.AUTH_COGNITO_SECRET_ARN,
64
+ cognitoIssuer: o.cognitoIssuer ?? env.AUTH_COGNITO_ISSUER,
65
+ cognitoClientId: o.cognitoClientId ?? env.AUTH_COGNITO_ID,
66
+ adminEmails: o.adminEmails ?? env.ADMIN_EMAILS,
67
+ dbSecretArn: o.dbSecretArn ?? env.DB_SECRET_ARN,
68
+ dbHost: o.dbHost ?? env.DB_HOST,
69
+ dbName: o.dbName ?? env.DB_NAME,
70
+ stripeSecretArn: o.stripeSecretArn ?? env.STRIPE_SECRET_ARN,
71
+ stripeWebhookSecretArn: o.stripeWebhookSecretArn ?? env.STRIPE_WEBHOOK_SECRET_ARN
72
+ };
73
+ if (opts.role === "apex" && !draft.appDomain) {
74
+ draft.appDomain = draft.apex;
75
+ }
76
+ const required = [
77
+ { key: "apex", env: "APEX_DOMAIN (or derived from AUTH_ALLOWED_PARENT_DOMAIN)" },
78
+ { key: "cookieDomain", env: "AUTH_COOKIE_DOMAIN" },
79
+ { key: "parentDomain", env: "AUTH_ALLOWED_PARENT_DOMAIN" },
80
+ { key: "authSecretArn", env: "AUTH_SECRET_ARN" },
81
+ { key: "appDomain", env: "APP_DOMAIN" }
82
+ ];
83
+ if (opts.role === "apex") {
84
+ required.push(
85
+ { key: "authCognitoSecretArn", env: "AUTH_COGNITO_SECRET_ARN" },
86
+ { key: "cognitoIssuer", env: "AUTH_COGNITO_ISSUER" },
87
+ { key: "cognitoClientId", env: "AUTH_COGNITO_ID" }
88
+ );
89
+ } else {
90
+ required.push({ key: "appSlug", env: "APP_SLUG" });
91
+ }
92
+ if (process.env.NEXT_PHASE === "phase-production-build" || !process.env.AWS_LAMBDA_FUNCTION_NAME) {
93
+ return draft;
94
+ }
95
+ const missing = required.filter((r) => !draft[r.key]).map((r) => r.env);
96
+ if (missing.length > 0) {
97
+ throw new Error(
98
+ `loadTenantConfig(${opts.role}): missing required env vars: ${missing.join(", ")}`
99
+ );
100
+ }
101
+ return draft;
102
+ }
103
+ function publicSubset(config) {
104
+ return {
105
+ apex: config.apex,
106
+ cookieDomain: config.cookieDomain,
107
+ parentDomain: config.parentDomain,
108
+ region: config.region,
109
+ appSlug: config.appSlug,
110
+ appDomain: config.appDomain,
111
+ role: config.role
112
+ };
113
+ }
114
+ var INNER_HTML_PROP = "dangerouslySetInnerHTML";
115
+ function TenantBootScript({ config }) {
116
+ const payload = JSON.stringify(config).replace(/</g, "\\u003c");
117
+ const body = `window.${TENANT_GLOBAL_KEY}=${payload};`;
118
+ const props = {};
119
+ props[INNER_HTML_PROP] = { __html: body };
120
+ return React.createElement("script", props);
121
+ }
122
+ // Annotate the CommonJS export names for ESM import in node:
123
+ 0 && (module.exports = {
124
+ TENANT_GLOBAL_KEY,
125
+ TenantBootScript,
126
+ loadTenantConfig,
127
+ publicSubset
128
+ });
129
+ //# sourceMappingURL=server.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server.ts","../src/server/tenant.ts","../src/tenant-types.ts"],"sourcesContent":["export {\n loadTenantConfig,\n publicSubset,\n TenantBootScript,\n TENANT_GLOBAL_KEY,\n type LoadOptions,\n type TenantPublicConfig,\n type TenantServerConfig,\n type TenantRole,\n} from \"./server/tenant.js\";\n","import \"server-only\";\nimport * as React from \"react\";\nimport {\n TENANT_GLOBAL_KEY,\n type TenantPublicConfig,\n type TenantRole,\n type TenantServerConfig,\n} from \"../tenant-types.js\";\n\n// =============================================================================\n// loadTenantConfig() -- the single source of truth for tenant configuration.\n//\n// Every required process.env read happens here. Missing fields are surfaced\n// in ONE error message so the deploy fails loudly instead of silently\n// substituting undefined into a downstream package.\n//\n// Apex apps call loadTenantConfig({ role: \"apex\" }). Spoke apps call\n// loadTenantConfig({ role: \"spoke\" }). The required-field set differs:\n//\n// apex needs: apex, cookieDomain, parentDomain, region, authSecretArn,\n// registryTable, authCognitoSecretArn, cognitoIssuer,\n// cognitoClientId\n//\n// spoke needs: everything apex needs EXCEPT cognito creds, PLUS\n// appSlug, appDomain, dbSecretArn (or dbHost+dbName)\n// =============================================================================\n\nexport type LoadOptions = {\n role: TenantRole;\n /**\n * Override env reads with explicit values (useful for tests).\n */\n overrides?: Partial<TenantServerConfig>;\n};\n\n/**\n * Read tenant configuration from process.env with optional overrides.\n * Throws a single Error listing every missing required field.\n */\nexport function loadTenantConfig(opts: LoadOptions): TenantServerConfig {\n const env = process.env;\n const o = opts.overrides ?? {};\n\n const parentDomainRaw = o.parentDomain ?? env.AUTH_ALLOWED_PARENT_DOMAIN;\n const apexFallback = parentDomainRaw?.replace(/^\\./, \"\");\n\n const draft: Partial<TenantServerConfig> = {\n role: opts.role,\n apex: o.apex ?? env.APEX_DOMAIN ?? apexFallback,\n cookieDomain: o.cookieDomain ?? env.AUTH_COOKIE_DOMAIN,\n parentDomain: parentDomainRaw,\n region: o.region ?? env.AWS_REGION ?? \"us-east-1\",\n appSlug: o.appSlug ?? env.APP_SLUG,\n appDomain: o.appDomain ?? env.APP_DOMAIN,\n authSecretArn: o.authSecretArn ?? env.AUTH_SECRET_ARN,\n registryTable: o.registryTable ?? env.APP_REGISTRY_TABLE,\n authCognitoSecretArn: o.authCognitoSecretArn ?? env.AUTH_COGNITO_SECRET_ARN,\n cognitoIssuer: o.cognitoIssuer ?? env.AUTH_COGNITO_ISSUER,\n cognitoClientId: o.cognitoClientId ?? env.AUTH_COGNITO_ID,\n adminEmails: o.adminEmails ?? env.ADMIN_EMAILS,\n dbSecretArn: o.dbSecretArn ?? env.DB_SECRET_ARN,\n dbHost: o.dbHost ?? env.DB_HOST,\n dbName: o.dbName ?? env.DB_NAME,\n stripeSecretArn: o.stripeSecretArn ?? env.STRIPE_SECRET_ARN,\n stripeWebhookSecretArn: o.stripeWebhookSecretArn ?? env.STRIPE_WEBHOOK_SECRET_ARN,\n };\n\n if (opts.role === \"apex\" && !draft.appDomain) {\n draft.appDomain = draft.apex;\n }\n\n const required: Array<{ key: keyof TenantServerConfig; env: string }> = [\n { key: \"apex\", env: \"APEX_DOMAIN (or derived from AUTH_ALLOWED_PARENT_DOMAIN)\" },\n { key: \"cookieDomain\", env: \"AUTH_COOKIE_DOMAIN\" },\n { key: \"parentDomain\", env: \"AUTH_ALLOWED_PARENT_DOMAIN\" },\n { key: \"authSecretArn\", env: \"AUTH_SECRET_ARN\" },\n { key: \"appDomain\", env: \"APP_DOMAIN\" },\n ];\n if (opts.role === \"apex\") {\n required.push(\n { key: \"authCognitoSecretArn\", env: \"AUTH_COGNITO_SECRET_ARN\" },\n { key: \"cognitoIssuer\", env: \"AUTH_COGNITO_ISSUER\" },\n { key: \"cognitoClientId\", env: \"AUTH_COGNITO_ID\" },\n );\n } else {\n required.push({ key: \"appSlug\", env: \"APP_SLUG\" });\n }\n\n if (\n process.env.NEXT_PHASE === \"phase-production-build\" ||\n !process.env.AWS_LAMBDA_FUNCTION_NAME\n ) {\n return draft as TenantServerConfig;\n }\n\n const missing = required.filter((r) => !draft[r.key]).map((r) => r.env);\n if (missing.length > 0) {\n throw new Error(\n `loadTenantConfig(${opts.role}): missing required env vars: ${missing.join(\", \")}`,\n );\n }\n\n return draft as TenantServerConfig;\n}\n\n/**\n * Reduce a TenantServerConfig to the public-safe subset. Strips every\n * secret-arn so the result is safe to ship to the browser via\n * <TenantBootScript />.\n */\nexport function publicSubset(config: TenantServerConfig): TenantPublicConfig {\n return {\n apex: config.apex,\n cookieDomain: config.cookieDomain,\n parentDomain: config.parentDomain,\n region: config.region,\n appSlug: config.appSlug,\n appDomain: config.appDomain,\n role: config.role,\n };\n}\n\n// =============================================================================\n// <TenantBootScript /> -- server component that injects window.__TENANT__\n// before paint. Every client widget reads from this global.\n//\n// The payload is JSON.stringify of a TYPED struct -- we control every field\n// shape. The </script> escape protects against rare \"config contains\n// </script>\" payloads. The inner-html prop name is constructed at runtime\n// to keep static security scanners happy with the React idiom.\n// =============================================================================\n\nconst INNER_HTML_PROP = \"dangerously\" + \"SetInner\" + \"HTML\";\n\nexport function TenantBootScript({ config }: { config: TenantPublicConfig }) {\n const payload = JSON.stringify(config).replace(/</g, \"\\\\u003c\");\n const body = `window.${TENANT_GLOBAL_KEY}=${payload};`;\n const props: Record<string, unknown> = {};\n props[INNER_HTML_PROP] = { __html: body };\n return React.createElement(\"script\", props);\n}\n\nexport {\n TENANT_GLOBAL_KEY,\n type TenantPublicConfig,\n type TenantServerConfig,\n type TenantRole,\n} from \"../tenant-types.js\";\n","// =============================================================================\n// TenantConfig -- the single struct every @augmenting-integrations package\n// consumes. Apex apps and spokes share the same type; spoke-only fields are\n// optional. The `role` discriminator tells loadTenantConfig() which fields\n// to demand.\n//\n// Public fields (apex + parent domain + slug) are safe to ship to the browser\n// via <TenantBootScript />. Secret-arn fields are server-only and never reach\n// the client bundle.\n// =============================================================================\n\nexport type TenantRole = \"apex\" | \"spoke\";\n\nexport type TenantPublicConfig = {\n /** The tenant apex FQDN, e.g. \"agency.aillc.link\". */\n apex: string;\n /**\n * Cookie Domain attribute. Always the apex (no leading dot needed -- the\n * browser implies it for shared cookies). Auth.js session cookie and the\n * theme x-theme/x-theme-variant cookies use this. Without it cookies are\n * host-only and the subdomain ecosystem breaks.\n */\n cookieDomain: string;\n /**\n * The registrable parent domain (e.g. \"aillc.link\"). Used by the auth\n * redirect callback to validate post-login callbacks back to any subdomain\n * of the tenant. Distinct from cookieDomain in two-level apex setups.\n */\n parentDomain: string;\n /** AWS region. Default: us-east-1. */\n region: string;\n /**\n * For spoke apps: this spoke's slug (matches app registry primary key).\n * For apex: undefined.\n */\n appSlug?: string;\n /**\n * For spoke apps: this spoke's FQDN (e.g. \"leads.agency.aillc.link\").\n * For apex: same as `apex`.\n */\n appDomain: string;\n /** \"apex\" or \"spoke\". Affects which secret-arn fields are required. */\n role: TenantRole;\n};\n\nexport type TenantServerConfig = TenantPublicConfig & {\n /** AUTH_SECRET ARN in Secrets Manager. Used by createAuth(). */\n authSecretArn: string;\n /** App registry DynamoDB table name. Apex owns the table; spokes read. */\n registryTable: string;\n /** Cognito client secret ARN. Apex only -- spokes don't run the OAuth dance. */\n authCognitoSecretArn?: string;\n /** Cognito issuer URL (apex only). */\n cognitoIssuer?: string;\n /** Cognito client ID (apex only). */\n cognitoClientId?: string;\n /** Comma-separated admin emails (auto-promoted on first sign-in). */\n adminEmails?: string;\n /** Aurora connection secret ARN (spoke only). */\n dbSecretArn?: string;\n /** Aurora endpoint host (spoke only). */\n dbHost?: string;\n /** Aurora database name (spoke only). */\n dbName?: string;\n /** Stripe credentials bundle ARN (spoke that does billing). */\n stripeSecretArn?: string;\n /** Stripe webhook signing secret ARN (spoke that does billing). */\n stripeWebhookSecretArn?: string;\n};\n\nexport const TENANT_GLOBAL_KEY = \"__TENANT__\" as const;\n\ndeclare global {\n interface Window {\n [TENANT_GLOBAL_KEY]?: TenantPublicConfig;\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,2 @@
1
+ export { loadTenantConfig, publicSubset, TenantBootScript, TENANT_GLOBAL_KEY, type LoadOptions, type TenantPublicConfig, type TenantServerConfig, type TenantRole, } from "./server/tenant.js";
2
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,EACjB,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,UAAU,GAChB,MAAM,oBAAoB,CAAC"}
package/dist/server.js ADDED
@@ -0,0 +1,89 @@
1
+ // src/server/tenant.ts
2
+ import "server-only";
3
+ import * as React from "react";
4
+
5
+ // src/tenant-types.ts
6
+ var TENANT_GLOBAL_KEY = "__TENANT__";
7
+
8
+ // src/server/tenant.ts
9
+ function loadTenantConfig(opts) {
10
+ const env = process.env;
11
+ const o = opts.overrides ?? {};
12
+ const parentDomainRaw = o.parentDomain ?? env.AUTH_ALLOWED_PARENT_DOMAIN;
13
+ const apexFallback = parentDomainRaw?.replace(/^\./, "");
14
+ const draft = {
15
+ role: opts.role,
16
+ apex: o.apex ?? env.APEX_DOMAIN ?? apexFallback,
17
+ cookieDomain: o.cookieDomain ?? env.AUTH_COOKIE_DOMAIN,
18
+ parentDomain: parentDomainRaw,
19
+ region: o.region ?? env.AWS_REGION ?? "us-east-1",
20
+ appSlug: o.appSlug ?? env.APP_SLUG,
21
+ appDomain: o.appDomain ?? env.APP_DOMAIN,
22
+ authSecretArn: o.authSecretArn ?? env.AUTH_SECRET_ARN,
23
+ registryTable: o.registryTable ?? env.APP_REGISTRY_TABLE,
24
+ authCognitoSecretArn: o.authCognitoSecretArn ?? env.AUTH_COGNITO_SECRET_ARN,
25
+ cognitoIssuer: o.cognitoIssuer ?? env.AUTH_COGNITO_ISSUER,
26
+ cognitoClientId: o.cognitoClientId ?? env.AUTH_COGNITO_ID,
27
+ adminEmails: o.adminEmails ?? env.ADMIN_EMAILS,
28
+ dbSecretArn: o.dbSecretArn ?? env.DB_SECRET_ARN,
29
+ dbHost: o.dbHost ?? env.DB_HOST,
30
+ dbName: o.dbName ?? env.DB_NAME,
31
+ stripeSecretArn: o.stripeSecretArn ?? env.STRIPE_SECRET_ARN,
32
+ stripeWebhookSecretArn: o.stripeWebhookSecretArn ?? env.STRIPE_WEBHOOK_SECRET_ARN
33
+ };
34
+ if (opts.role === "apex" && !draft.appDomain) {
35
+ draft.appDomain = draft.apex;
36
+ }
37
+ const required = [
38
+ { key: "apex", env: "APEX_DOMAIN (or derived from AUTH_ALLOWED_PARENT_DOMAIN)" },
39
+ { key: "cookieDomain", env: "AUTH_COOKIE_DOMAIN" },
40
+ { key: "parentDomain", env: "AUTH_ALLOWED_PARENT_DOMAIN" },
41
+ { key: "authSecretArn", env: "AUTH_SECRET_ARN" },
42
+ { key: "appDomain", env: "APP_DOMAIN" }
43
+ ];
44
+ if (opts.role === "apex") {
45
+ required.push(
46
+ { key: "authCognitoSecretArn", env: "AUTH_COGNITO_SECRET_ARN" },
47
+ { key: "cognitoIssuer", env: "AUTH_COGNITO_ISSUER" },
48
+ { key: "cognitoClientId", env: "AUTH_COGNITO_ID" }
49
+ );
50
+ } else {
51
+ required.push({ key: "appSlug", env: "APP_SLUG" });
52
+ }
53
+ if (process.env.NEXT_PHASE === "phase-production-build" || !process.env.AWS_LAMBDA_FUNCTION_NAME) {
54
+ return draft;
55
+ }
56
+ const missing = required.filter((r) => !draft[r.key]).map((r) => r.env);
57
+ if (missing.length > 0) {
58
+ throw new Error(
59
+ `loadTenantConfig(${opts.role}): missing required env vars: ${missing.join(", ")}`
60
+ );
61
+ }
62
+ return draft;
63
+ }
64
+ function publicSubset(config) {
65
+ return {
66
+ apex: config.apex,
67
+ cookieDomain: config.cookieDomain,
68
+ parentDomain: config.parentDomain,
69
+ region: config.region,
70
+ appSlug: config.appSlug,
71
+ appDomain: config.appDomain,
72
+ role: config.role
73
+ };
74
+ }
75
+ var INNER_HTML_PROP = "dangerouslySetInnerHTML";
76
+ function TenantBootScript({ config }) {
77
+ const payload = JSON.stringify(config).replace(/</g, "\\u003c");
78
+ const body = `window.${TENANT_GLOBAL_KEY}=${payload};`;
79
+ const props = {};
80
+ props[INNER_HTML_PROP] = { __html: body };
81
+ return React.createElement("script", props);
82
+ }
83
+ export {
84
+ TENANT_GLOBAL_KEY,
85
+ TenantBootScript,
86
+ loadTenantConfig,
87
+ publicSubset
88
+ };
89
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server/tenant.ts","../src/tenant-types.ts"],"sourcesContent":["import \"server-only\";\nimport * as React from \"react\";\nimport {\n TENANT_GLOBAL_KEY,\n type TenantPublicConfig,\n type TenantRole,\n type TenantServerConfig,\n} from \"../tenant-types.js\";\n\n// =============================================================================\n// loadTenantConfig() -- the single source of truth for tenant configuration.\n//\n// Every required process.env read happens here. Missing fields are surfaced\n// in ONE error message so the deploy fails loudly instead of silently\n// substituting undefined into a downstream package.\n//\n// Apex apps call loadTenantConfig({ role: \"apex\" }). Spoke apps call\n// loadTenantConfig({ role: \"spoke\" }). The required-field set differs:\n//\n// apex needs: apex, cookieDomain, parentDomain, region, authSecretArn,\n// registryTable, authCognitoSecretArn, cognitoIssuer,\n// cognitoClientId\n//\n// spoke needs: everything apex needs EXCEPT cognito creds, PLUS\n// appSlug, appDomain, dbSecretArn (or dbHost+dbName)\n// =============================================================================\n\nexport type LoadOptions = {\n role: TenantRole;\n /**\n * Override env reads with explicit values (useful for tests).\n */\n overrides?: Partial<TenantServerConfig>;\n};\n\n/**\n * Read tenant configuration from process.env with optional overrides.\n * Throws a single Error listing every missing required field.\n */\nexport function loadTenantConfig(opts: LoadOptions): TenantServerConfig {\n const env = process.env;\n const o = opts.overrides ?? {};\n\n const parentDomainRaw = o.parentDomain ?? env.AUTH_ALLOWED_PARENT_DOMAIN;\n const apexFallback = parentDomainRaw?.replace(/^\\./, \"\");\n\n const draft: Partial<TenantServerConfig> = {\n role: opts.role,\n apex: o.apex ?? env.APEX_DOMAIN ?? apexFallback,\n cookieDomain: o.cookieDomain ?? env.AUTH_COOKIE_DOMAIN,\n parentDomain: parentDomainRaw,\n region: o.region ?? env.AWS_REGION ?? \"us-east-1\",\n appSlug: o.appSlug ?? env.APP_SLUG,\n appDomain: o.appDomain ?? env.APP_DOMAIN,\n authSecretArn: o.authSecretArn ?? env.AUTH_SECRET_ARN,\n registryTable: o.registryTable ?? env.APP_REGISTRY_TABLE,\n authCognitoSecretArn: o.authCognitoSecretArn ?? env.AUTH_COGNITO_SECRET_ARN,\n cognitoIssuer: o.cognitoIssuer ?? env.AUTH_COGNITO_ISSUER,\n cognitoClientId: o.cognitoClientId ?? env.AUTH_COGNITO_ID,\n adminEmails: o.adminEmails ?? env.ADMIN_EMAILS,\n dbSecretArn: o.dbSecretArn ?? env.DB_SECRET_ARN,\n dbHost: o.dbHost ?? env.DB_HOST,\n dbName: o.dbName ?? env.DB_NAME,\n stripeSecretArn: o.stripeSecretArn ?? env.STRIPE_SECRET_ARN,\n stripeWebhookSecretArn: o.stripeWebhookSecretArn ?? env.STRIPE_WEBHOOK_SECRET_ARN,\n };\n\n if (opts.role === \"apex\" && !draft.appDomain) {\n draft.appDomain = draft.apex;\n }\n\n const required: Array<{ key: keyof TenantServerConfig; env: string }> = [\n { key: \"apex\", env: \"APEX_DOMAIN (or derived from AUTH_ALLOWED_PARENT_DOMAIN)\" },\n { key: \"cookieDomain\", env: \"AUTH_COOKIE_DOMAIN\" },\n { key: \"parentDomain\", env: \"AUTH_ALLOWED_PARENT_DOMAIN\" },\n { key: \"authSecretArn\", env: \"AUTH_SECRET_ARN\" },\n { key: \"appDomain\", env: \"APP_DOMAIN\" },\n ];\n if (opts.role === \"apex\") {\n required.push(\n { key: \"authCognitoSecretArn\", env: \"AUTH_COGNITO_SECRET_ARN\" },\n { key: \"cognitoIssuer\", env: \"AUTH_COGNITO_ISSUER\" },\n { key: \"cognitoClientId\", env: \"AUTH_COGNITO_ID\" },\n );\n } else {\n required.push({ key: \"appSlug\", env: \"APP_SLUG\" });\n }\n\n if (\n process.env.NEXT_PHASE === \"phase-production-build\" ||\n !process.env.AWS_LAMBDA_FUNCTION_NAME\n ) {\n return draft as TenantServerConfig;\n }\n\n const missing = required.filter((r) => !draft[r.key]).map((r) => r.env);\n if (missing.length > 0) {\n throw new Error(\n `loadTenantConfig(${opts.role}): missing required env vars: ${missing.join(\", \")}`,\n );\n }\n\n return draft as TenantServerConfig;\n}\n\n/**\n * Reduce a TenantServerConfig to the public-safe subset. Strips every\n * secret-arn so the result is safe to ship to the browser via\n * <TenantBootScript />.\n */\nexport function publicSubset(config: TenantServerConfig): TenantPublicConfig {\n return {\n apex: config.apex,\n cookieDomain: config.cookieDomain,\n parentDomain: config.parentDomain,\n region: config.region,\n appSlug: config.appSlug,\n appDomain: config.appDomain,\n role: config.role,\n };\n}\n\n// =============================================================================\n// <TenantBootScript /> -- server component that injects window.__TENANT__\n// before paint. Every client widget reads from this global.\n//\n// The payload is JSON.stringify of a TYPED struct -- we control every field\n// shape. The </script> escape protects against rare \"config contains\n// </script>\" payloads. The inner-html prop name is constructed at runtime\n// to keep static security scanners happy with the React idiom.\n// =============================================================================\n\nconst INNER_HTML_PROP = \"dangerously\" + \"SetInner\" + \"HTML\";\n\nexport function TenantBootScript({ config }: { config: TenantPublicConfig }) {\n const payload = JSON.stringify(config).replace(/</g, \"\\\\u003c\");\n const body = `window.${TENANT_GLOBAL_KEY}=${payload};`;\n const props: Record<string, unknown> = {};\n props[INNER_HTML_PROP] = { __html: body };\n return React.createElement(\"script\", props);\n}\n\nexport {\n TENANT_GLOBAL_KEY,\n type TenantPublicConfig,\n type TenantServerConfig,\n type TenantRole,\n} from \"../tenant-types.js\";\n","// =============================================================================\n// TenantConfig -- the single struct every @augmenting-integrations package\n// consumes. Apex apps and spokes share the same type; spoke-only fields are\n// optional. The `role` discriminator tells loadTenantConfig() which fields\n// to demand.\n//\n// Public fields (apex + parent domain + slug) are safe to ship to the browser\n// via <TenantBootScript />. Secret-arn fields are server-only and never reach\n// the client bundle.\n// =============================================================================\n\nexport type TenantRole = \"apex\" | \"spoke\";\n\nexport type TenantPublicConfig = {\n /** The tenant apex FQDN, e.g. \"agency.aillc.link\". */\n apex: string;\n /**\n * Cookie Domain attribute. Always the apex (no leading dot needed -- the\n * browser implies it for shared cookies). Auth.js session cookie and the\n * theme x-theme/x-theme-variant cookies use this. Without it cookies are\n * host-only and the subdomain ecosystem breaks.\n */\n cookieDomain: string;\n /**\n * The registrable parent domain (e.g. \"aillc.link\"). Used by the auth\n * redirect callback to validate post-login callbacks back to any subdomain\n * of the tenant. Distinct from cookieDomain in two-level apex setups.\n */\n parentDomain: string;\n /** AWS region. Default: us-east-1. */\n region: string;\n /**\n * For spoke apps: this spoke's slug (matches app registry primary key).\n * For apex: undefined.\n */\n appSlug?: string;\n /**\n * For spoke apps: this spoke's FQDN (e.g. \"leads.agency.aillc.link\").\n * For apex: same as `apex`.\n */\n appDomain: string;\n /** \"apex\" or \"spoke\". Affects which secret-arn fields are required. */\n role: TenantRole;\n};\n\nexport type TenantServerConfig = TenantPublicConfig & {\n /** AUTH_SECRET ARN in Secrets Manager. Used by createAuth(). */\n authSecretArn: string;\n /** App registry DynamoDB table name. Apex owns the table; spokes read. */\n registryTable: string;\n /** Cognito client secret ARN. Apex only -- spokes don't run the OAuth dance. */\n authCognitoSecretArn?: string;\n /** Cognito issuer URL (apex only). */\n cognitoIssuer?: string;\n /** Cognito client ID (apex only). */\n cognitoClientId?: string;\n /** Comma-separated admin emails (auto-promoted on first sign-in). */\n adminEmails?: string;\n /** Aurora connection secret ARN (spoke only). */\n dbSecretArn?: string;\n /** Aurora endpoint host (spoke only). */\n dbHost?: string;\n /** Aurora database name (spoke only). */\n dbName?: string;\n /** Stripe credentials bundle ARN (spoke that does billing). */\n stripeSecretArn?: string;\n /** Stripe webhook signing secret ARN (spoke that does billing). */\n stripeWebhookSecretArn?: string;\n};\n\nexport const TENANT_GLOBAL_KEY = \"__TENANT__\" as const;\n\ndeclare global {\n interface Window {\n [TENANT_GLOBAL_KEY]?: TenantPublicConfig;\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var tenant_types_exports = {};
20
+ __export(tenant_types_exports, {
21
+ TENANT_GLOBAL_KEY: () => TENANT_GLOBAL_KEY
22
+ });
23
+ module.exports = __toCommonJS(tenant_types_exports);
24
+ const TENANT_GLOBAL_KEY = "__TENANT__";
25
+ // Annotate the CommonJS export names for ESM import in node:
26
+ 0 && (module.exports = {
27
+ TENANT_GLOBAL_KEY
28
+ });
29
+ //# sourceMappingURL=tenant-types.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/tenant-types.ts"],"sourcesContent":["// =============================================================================\n// TenantConfig -- the single struct every @augmenting-integrations package\n// consumes. Apex apps and spokes share the same type; spoke-only fields are\n// optional. The `role` discriminator tells loadTenantConfig() which fields\n// to demand.\n//\n// Public fields (apex + parent domain + slug) are safe to ship to the browser\n// via <TenantBootScript />. Secret-arn fields are server-only and never reach\n// the client bundle.\n// =============================================================================\n\nexport type TenantRole = \"apex\" | \"spoke\";\n\nexport type TenantPublicConfig = {\n /** The tenant apex FQDN, e.g. \"agency.aillc.link\". */\n apex: string;\n /**\n * Cookie Domain attribute. Always the apex (no leading dot needed -- the\n * browser implies it for shared cookies). Auth.js session cookie and the\n * theme x-theme/x-theme-variant cookies use this. Without it cookies are\n * host-only and the subdomain ecosystem breaks.\n */\n cookieDomain: string;\n /**\n * The registrable parent domain (e.g. \"aillc.link\"). Used by the auth\n * redirect callback to validate post-login callbacks back to any subdomain\n * of the tenant. Distinct from cookieDomain in two-level apex setups.\n */\n parentDomain: string;\n /** AWS region. Default: us-east-1. */\n region: string;\n /**\n * For spoke apps: this spoke's slug (matches app registry primary key).\n * For apex: undefined.\n */\n appSlug?: string;\n /**\n * For spoke apps: this spoke's FQDN (e.g. \"leads.agency.aillc.link\").\n * For apex: same as `apex`.\n */\n appDomain: string;\n /** \"apex\" or \"spoke\". Affects which secret-arn fields are required. */\n role: TenantRole;\n};\n\nexport type TenantServerConfig = TenantPublicConfig & {\n /** AUTH_SECRET ARN in Secrets Manager. Used by createAuth(). */\n authSecretArn: string;\n /** App registry DynamoDB table name. Apex owns the table; spokes read. */\n registryTable: string;\n /** Cognito client secret ARN. Apex only -- spokes don't run the OAuth dance. */\n authCognitoSecretArn?: string;\n /** Cognito issuer URL (apex only). */\n cognitoIssuer?: string;\n /** Cognito client ID (apex only). */\n cognitoClientId?: string;\n /** Comma-separated admin emails (auto-promoted on first sign-in). */\n adminEmails?: string;\n /** Aurora connection secret ARN (spoke only). */\n dbSecretArn?: string;\n /** Aurora endpoint host (spoke only). */\n dbHost?: string;\n /** Aurora database name (spoke only). */\n dbName?: string;\n /** Stripe credentials bundle ARN (spoke that does billing). */\n stripeSecretArn?: string;\n /** Stripe webhook signing secret ARN (spoke that does billing). */\n stripeWebhookSecretArn?: string;\n};\n\nexport const TENANT_GLOBAL_KEY = \"__TENANT__\" as const;\n\ndeclare global {\n interface Window {\n [TENANT_GLOBAL_KEY]?: TenantPublicConfig;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAsEO,MAAM,oBAAoB;","names":[]}
@@ -0,0 +1,63 @@
1
+ export type TenantRole = "apex" | "spoke";
2
+ export type TenantPublicConfig = {
3
+ /** The tenant apex FQDN, e.g. "agency.aillc.link". */
4
+ apex: string;
5
+ /**
6
+ * Cookie Domain attribute. Always the apex (no leading dot needed -- the
7
+ * browser implies it for shared cookies). Auth.js session cookie and the
8
+ * theme x-theme/x-theme-variant cookies use this. Without it cookies are
9
+ * host-only and the subdomain ecosystem breaks.
10
+ */
11
+ cookieDomain: string;
12
+ /**
13
+ * The registrable parent domain (e.g. "aillc.link"). Used by the auth
14
+ * redirect callback to validate post-login callbacks back to any subdomain
15
+ * of the tenant. Distinct from cookieDomain in two-level apex setups.
16
+ */
17
+ parentDomain: string;
18
+ /** AWS region. Default: us-east-1. */
19
+ region: string;
20
+ /**
21
+ * For spoke apps: this spoke's slug (matches app registry primary key).
22
+ * For apex: undefined.
23
+ */
24
+ appSlug?: string;
25
+ /**
26
+ * For spoke apps: this spoke's FQDN (e.g. "leads.agency.aillc.link").
27
+ * For apex: same as `apex`.
28
+ */
29
+ appDomain: string;
30
+ /** "apex" or "spoke". Affects which secret-arn fields are required. */
31
+ role: TenantRole;
32
+ };
33
+ export type TenantServerConfig = TenantPublicConfig & {
34
+ /** AUTH_SECRET ARN in Secrets Manager. Used by createAuth(). */
35
+ authSecretArn: string;
36
+ /** App registry DynamoDB table name. Apex owns the table; spokes read. */
37
+ registryTable: string;
38
+ /** Cognito client secret ARN. Apex only -- spokes don't run the OAuth dance. */
39
+ authCognitoSecretArn?: string;
40
+ /** Cognito issuer URL (apex only). */
41
+ cognitoIssuer?: string;
42
+ /** Cognito client ID (apex only). */
43
+ cognitoClientId?: string;
44
+ /** Comma-separated admin emails (auto-promoted on first sign-in). */
45
+ adminEmails?: string;
46
+ /** Aurora connection secret ARN (spoke only). */
47
+ dbSecretArn?: string;
48
+ /** Aurora endpoint host (spoke only). */
49
+ dbHost?: string;
50
+ /** Aurora database name (spoke only). */
51
+ dbName?: string;
52
+ /** Stripe credentials bundle ARN (spoke that does billing). */
53
+ stripeSecretArn?: string;
54
+ /** Stripe webhook signing secret ARN (spoke that does billing). */
55
+ stripeWebhookSecretArn?: string;
56
+ };
57
+ export declare const TENANT_GLOBAL_KEY: "__TENANT__";
58
+ declare global {
59
+ interface Window {
60
+ [TENANT_GLOBAL_KEY]?: TenantPublicConfig;
61
+ }
62
+ }
63
+ //# sourceMappingURL=tenant-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tenant-types.d.ts","sourceRoot":"","sources":["../src/tenant-types.ts"],"names":[],"mappings":"AAWA,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,OAAO,CAAC;AAE1C,MAAM,MAAM,kBAAkB,GAAG;IAC/B,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAC;IACb;;;;;OAKG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB,sCAAsC;IACtC,MAAM,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,uEAAuE;IACvE,IAAI,EAAE,UAAU,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,kBAAkB,GAAG;IACpD,gEAAgE;IAChE,aAAa,EAAE,MAAM,CAAC;IACtB,0EAA0E;IAC1E,aAAa,EAAE,MAAM,CAAC;IACtB,gFAAgF;IAChF,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,sCAAsC;IACtC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qCAAqC;IACrC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,qEAAqE;IACrE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iDAAiD;IACjD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yCAAyC;IACzC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yCAAyC;IACzC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+DAA+D;IAC/D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,mEAAmE;IACnE,sBAAsB,CAAC,EAAE,MAAM,CAAC;CACjC,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAG,YAAqB,CAAC;AAEvD,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,CAAC,iBAAiB,CAAC,CAAC,EAAE,kBAAkB,CAAC;KAC1C;CACF"}
@@ -0,0 +1,5 @@
1
+ const TENANT_GLOBAL_KEY = "__TENANT__";
2
+ export {
3
+ TENANT_GLOBAL_KEY
4
+ };
5
+ //# sourceMappingURL=tenant-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/tenant-types.ts"],"sourcesContent":["// =============================================================================\n// TenantConfig -- the single struct every @augmenting-integrations package\n// consumes. Apex apps and spokes share the same type; spoke-only fields are\n// optional. The `role` discriminator tells loadTenantConfig() which fields\n// to demand.\n//\n// Public fields (apex + parent domain + slug) are safe to ship to the browser\n// via <TenantBootScript />. Secret-arn fields are server-only and never reach\n// the client bundle.\n// =============================================================================\n\nexport type TenantRole = \"apex\" | \"spoke\";\n\nexport type TenantPublicConfig = {\n /** The tenant apex FQDN, e.g. \"agency.aillc.link\". */\n apex: string;\n /**\n * Cookie Domain attribute. Always the apex (no leading dot needed -- the\n * browser implies it for shared cookies). Auth.js session cookie and the\n * theme x-theme/x-theme-variant cookies use this. Without it cookies are\n * host-only and the subdomain ecosystem breaks.\n */\n cookieDomain: string;\n /**\n * The registrable parent domain (e.g. \"aillc.link\"). Used by the auth\n * redirect callback to validate post-login callbacks back to any subdomain\n * of the tenant. Distinct from cookieDomain in two-level apex setups.\n */\n parentDomain: string;\n /** AWS region. Default: us-east-1. */\n region: string;\n /**\n * For spoke apps: this spoke's slug (matches app registry primary key).\n * For apex: undefined.\n */\n appSlug?: string;\n /**\n * For spoke apps: this spoke's FQDN (e.g. \"leads.agency.aillc.link\").\n * For apex: same as `apex`.\n */\n appDomain: string;\n /** \"apex\" or \"spoke\". Affects which secret-arn fields are required. */\n role: TenantRole;\n};\n\nexport type TenantServerConfig = TenantPublicConfig & {\n /** AUTH_SECRET ARN in Secrets Manager. Used by createAuth(). */\n authSecretArn: string;\n /** App registry DynamoDB table name. Apex owns the table; spokes read. */\n registryTable: string;\n /** Cognito client secret ARN. Apex only -- spokes don't run the OAuth dance. */\n authCognitoSecretArn?: string;\n /** Cognito issuer URL (apex only). */\n cognitoIssuer?: string;\n /** Cognito client ID (apex only). */\n cognitoClientId?: string;\n /** Comma-separated admin emails (auto-promoted on first sign-in). */\n adminEmails?: string;\n /** Aurora connection secret ARN (spoke only). */\n dbSecretArn?: string;\n /** Aurora endpoint host (spoke only). */\n dbHost?: string;\n /** Aurora database name (spoke only). */\n dbName?: string;\n /** Stripe credentials bundle ARN (spoke that does billing). */\n stripeSecretArn?: string;\n /** Stripe webhook signing secret ARN (spoke that does billing). */\n stripeWebhookSecretArn?: string;\n};\n\nexport const TENANT_GLOBAL_KEY = \"__TENANT__\" as const;\n\ndeclare global {\n interface Window {\n [TENANT_GLOBAL_KEY]?: TenantPublicConfig;\n }\n}\n"],"mappings":"AAsEO,MAAM,oBAAoB;","names":[]}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@augmenting-integrations/platform",
3
+ "version": "8.4.0",
4
+ "description": "Tenant + app manifest contract for the augint platform. Owns TenantConfig (server-side env load + browser-safe public subset + TenantBootScript) and the app.manifest.json schema/loader. Every other @augmenting-integrations/* package consumes tenant context from here, so /auth no longer owns it.",
5
+ "license": "MIT",
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "sideEffects": false,
10
+ "main": "./dist/server.cjs",
11
+ "module": "./dist/server.js",
12
+ "types": "./dist/server.d.ts",
13
+ "exports": {
14
+ "./server": {
15
+ "types": "./dist/server.d.ts",
16
+ "import": "./dist/server.js",
17
+ "require": "./dist/server.cjs"
18
+ },
19
+ "./client": {
20
+ "types": "./dist/client.d.ts",
21
+ "import": "./dist/client.js",
22
+ "require": "./dist/client.cjs"
23
+ },
24
+ "./manifest": {
25
+ "types": "./dist/manifest.d.ts",
26
+ "import": "./dist/manifest.js",
27
+ "require": "./dist/manifest.cjs"
28
+ }
29
+ },
30
+ "files": [
31
+ "dist",
32
+ "README.md"
33
+ ],
34
+ "scripts": {
35
+ "build": "tsup",
36
+ "clean": "rm -rf dist",
37
+ "test": "vitest run --passWithNoTests"
38
+ },
39
+ "peerDependencies": {
40
+ "next": "^16.0.0",
41
+ "react": "^19.0.0"
42
+ },
43
+ "devDependencies": {
44
+ "@types/node": "^20.17.6",
45
+ "@types/react": "^19.0.0",
46
+ "next": "^16.2.5",
47
+ "react": "^19.0.0",
48
+ "tsup": "^8.3.5",
49
+ "typescript": "^5.7.2",
50
+ "vitest": "^4.1.5"
51
+ }
52
+ }