@burdenoff/be-sdk 2026.510.79
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 +173 -0
- package/configs/eslint/index.js +492 -0
- package/configs/tsconfig/tsconfig.json +53 -0
- package/dist/EventQueueFactory-6NMSGECR.js +8 -0
- package/dist/EventQueueFactory-6NMSGECR.js.map +1 -0
- package/dist/auth/index.d.ts +66 -0
- package/dist/auth/index.js +8 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/cache/index.d.ts +2 -0
- package/dist/cache/index.js +6 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/captcha/index.d.ts +1 -0
- package/dist/captcha/index.js +4 -0
- package/dist/captcha/index.js.map +1 -0
- package/dist/chunk-2HANKZZM.js +245 -0
- package/dist/chunk-2HANKZZM.js.map +1 -0
- package/dist/chunk-2SD5L2F2.js +816 -0
- package/dist/chunk-2SD5L2F2.js.map +1 -0
- package/dist/chunk-3O2RQOPV.js +1068 -0
- package/dist/chunk-3O2RQOPV.js.map +1 -0
- package/dist/chunk-4B43DZIS.js +560 -0
- package/dist/chunk-4B43DZIS.js.map +1 -0
- package/dist/chunk-4XAEES25.js +2444 -0
- package/dist/chunk-4XAEES25.js.map +1 -0
- package/dist/chunk-4XGGWW36.js +1405 -0
- package/dist/chunk-4XGGWW36.js.map +1 -0
- package/dist/chunk-5LXDPGDH.js +245 -0
- package/dist/chunk-5LXDPGDH.js.map +1 -0
- package/dist/chunk-AI7L22ZJ.js +472 -0
- package/dist/chunk-AI7L22ZJ.js.map +1 -0
- package/dist/chunk-CBZNP6RF.js +286 -0
- package/dist/chunk-CBZNP6RF.js.map +1 -0
- package/dist/chunk-ELLX3K54.js +613 -0
- package/dist/chunk-ELLX3K54.js.map +1 -0
- package/dist/chunk-F5XEAGHQ.js +1875 -0
- package/dist/chunk-F5XEAGHQ.js.map +1 -0
- package/dist/chunk-FNZEQOJY.js +628 -0
- package/dist/chunk-FNZEQOJY.js.map +1 -0
- package/dist/chunk-GTEMQXCA.js +516 -0
- package/dist/chunk-GTEMQXCA.js.map +1 -0
- package/dist/chunk-HIWTWIGQ.js +226 -0
- package/dist/chunk-HIWTWIGQ.js.map +1 -0
- package/dist/chunk-IYE2NTRX.js +764 -0
- package/dist/chunk-IYE2NTRX.js.map +1 -0
- package/dist/chunk-J26YGR2U.js +1484 -0
- package/dist/chunk-J26YGR2U.js.map +1 -0
- package/dist/chunk-JG2FTZPI.js +509 -0
- package/dist/chunk-JG2FTZPI.js.map +1 -0
- package/dist/chunk-JVBTNSPG.js +1551 -0
- package/dist/chunk-JVBTNSPG.js.map +1 -0
- package/dist/chunk-L7ZMN2FJ.js +12 -0
- package/dist/chunk-L7ZMN2FJ.js.map +1 -0
- package/dist/chunk-M5IMU3US.js +473 -0
- package/dist/chunk-M5IMU3US.js.map +1 -0
- package/dist/chunk-MLW2HPS5.js +1267 -0
- package/dist/chunk-MLW2HPS5.js.map +1 -0
- package/dist/chunk-MNCGUYLJ.js +269 -0
- package/dist/chunk-MNCGUYLJ.js.map +1 -0
- package/dist/chunk-MOCUBSTW.js +373 -0
- package/dist/chunk-MOCUBSTW.js.map +1 -0
- package/dist/chunk-NGMEI42S.js +103 -0
- package/dist/chunk-NGMEI42S.js.map +1 -0
- package/dist/chunk-NUB5GQTU.js +270 -0
- package/dist/chunk-NUB5GQTU.js.map +1 -0
- package/dist/chunk-OVSYMNCI.js +145 -0
- package/dist/chunk-OVSYMNCI.js.map +1 -0
- package/dist/chunk-QLVKMWFA.js +94 -0
- package/dist/chunk-QLVKMWFA.js.map +1 -0
- package/dist/chunk-SAG6ZYWQ.js +89 -0
- package/dist/chunk-SAG6ZYWQ.js.map +1 -0
- package/dist/chunk-SAK2JCA7.js +198 -0
- package/dist/chunk-SAK2JCA7.js.map +1 -0
- package/dist/chunk-SBS4S2PJ.js +100 -0
- package/dist/chunk-SBS4S2PJ.js.map +1 -0
- package/dist/chunk-SNEOLWXT.js +148 -0
- package/dist/chunk-SNEOLWXT.js.map +1 -0
- package/dist/chunk-TKBD2ONF.js +564 -0
- package/dist/chunk-TKBD2ONF.js.map +1 -0
- package/dist/chunk-UBXFIIAW.js +127 -0
- package/dist/chunk-UBXFIIAW.js.map +1 -0
- package/dist/chunk-VI4VSNTI.js +57 -0
- package/dist/chunk-VI4VSNTI.js.map +1 -0
- package/dist/chunk-VWCN5PMD.js +268 -0
- package/dist/chunk-VWCN5PMD.js.map +1 -0
- package/dist/chunk-XC2YD6CF.js +913 -0
- package/dist/chunk-XC2YD6CF.js.map +1 -0
- package/dist/chunk-XE4OXN2W.js +10 -0
- package/dist/chunk-XE4OXN2W.js.map +1 -0
- package/dist/chunk-YMZEUTD2.js +110 -0
- package/dist/chunk-YMZEUTD2.js.map +1 -0
- package/dist/chunk-Z4IRLBWO.js +758 -0
- package/dist/chunk-Z4IRLBWO.js.map +1 -0
- package/dist/client/internal-client.d.ts +7 -0
- package/dist/client/internal-client.js +5 -0
- package/dist/client/internal-client.js.map +1 -0
- package/dist/client-CSjIrYKS.d.ts +77 -0
- package/dist/config-UKE2E3BW.js +4 -0
- package/dist/config-UKE2E3BW.js.map +1 -0
- package/dist/crypto/index.d.ts +1 -0
- package/dist/crypto/index.js +4 -0
- package/dist/crypto/index.js.map +1 -0
- package/dist/database/index.d.ts +117 -0
- package/dist/database/index.js +7 -0
- package/dist/database/index.js.map +1 -0
- package/dist/dataloader/index.d.ts +1 -0
- package/dist/dataloader/index.js +4 -0
- package/dist/dataloader/index.js.map +1 -0
- package/dist/esm-K6H7EIWK.js +1450 -0
- package/dist/esm-K6H7EIWK.js.map +1 -0
- package/dist/event-queue/index.d.ts +6 -0
- package/dist/event-queue/index.js +10 -0
- package/dist/event-queue/index.js.map +1 -0
- package/dist/export/index.d.ts +2 -0
- package/dist/export/index.js +9 -0
- package/dist/export/index.js.map +1 -0
- package/dist/feature-flags/index.d.ts +1 -0
- package/dist/feature-flags/index.js +4 -0
- package/dist/feature-flags/index.js.map +1 -0
- package/dist/gateway/index.d.ts +4 -0
- package/dist/gateway/index.js +8 -0
- package/dist/gateway/index.js.map +1 -0
- package/dist/gateway/security/index.d.ts +2 -0
- package/dist/gateway/security/index.js +5 -0
- package/dist/gateway/security/index.js.map +1 -0
- package/dist/graph/index.d.ts +9 -0
- package/dist/graph/index.js +13 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/graph-emitter-Bk0FqD53.d.ts +47 -0
- package/dist/graphql/index.d.ts +2 -0
- package/dist/graphql/index.js +7 -0
- package/dist/graphql/index.js.map +1 -0
- package/dist/graphql/services/index.d.ts +24 -0
- package/dist/graphql/services/index.js +5 -0
- package/dist/graphql/services/index.js.map +1 -0
- package/dist/graphql-BAVrUgBF.d.ts +22 -0
- package/dist/i18n/index.d.ts +2 -0
- package/dist/i18n/index.js +5 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/i18n-DA6IPnYy.d.ts +101 -0
- package/dist/index-4gKQKZ2B.d.ts +71 -0
- package/dist/index-B18rrFru.d.ts +42 -0
- package/dist/index-B70b5mYS.d.ts +33 -0
- package/dist/index-BMq3lFjj.d.ts +247 -0
- package/dist/index-BODgvRtL.d.ts +316 -0
- package/dist/index-BS2idbnr.d.ts +874 -0
- package/dist/index-BV3G48cH.d.ts +124 -0
- package/dist/index-CHbVw5M7.d.ts +73 -0
- package/dist/index-Cq_xNjcv.d.ts +602 -0
- package/dist/index-CrQgidpx.d.ts +77 -0
- package/dist/index-CyVEg86m.d.ts +309 -0
- package/dist/index-DOcBnmMe.d.ts +166 -0
- package/dist/index-DSOpj8DI.d.ts +17 -0
- package/dist/index-DW9q22GH.d.ts +75 -0
- package/dist/index-DlFDFyWD.d.ts +55 -0
- package/dist/index-DoK0bjhH.d.ts +607 -0
- package/dist/index-Dtf6FBaD.d.ts +199 -0
- package/dist/index-KVWGHMbS.d.ts +415 -0
- package/dist/index-PaojfSdd.d.ts +71 -0
- package/dist/index-RU_M5c74.d.ts +163 -0
- package/dist/index-UhkH2eaB.d.ts +192 -0
- package/dist/index-e_18DzAg.d.ts +18 -0
- package/dist/index-jOuh_TKn.d.ts +59 -0
- package/dist/index.d.ts +584 -0
- package/dist/index.js +2626 -0
- package/dist/index.js.map +1 -0
- package/dist/init/index.d.ts +2 -0
- package/dist/init/index.js +12 -0
- package/dist/init/index.js.map +1 -0
- package/dist/instrumentation/index.d.ts +3 -0
- package/dist/instrumentation/index.js +5 -0
- package/dist/instrumentation/index.js.map +1 -0
- package/dist/layer/index.d.ts +2 -0
- package/dist/layer/index.js +6 -0
- package/dist/layer/index.js.map +1 -0
- package/dist/layer-DDip18KY.d.ts +67 -0
- package/dist/logger/index.d.ts +2 -0
- package/dist/logger/index.js +4 -0
- package/dist/logger/index.js.map +1 -0
- package/dist/middleware/index.d.ts +3 -0
- package/dist/middleware/index.js +5 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/notifications/index.d.ts +4 -0
- package/dist/notifications/index.js +7 -0
- package/dist/notifications/index.js.map +1 -0
- package/dist/observability/index.d.ts +3 -0
- package/dist/observability/index.js +5 -0
- package/dist/observability/index.js.map +1 -0
- package/dist/pino-logger-RYLGXPHZ.js +5 -0
- package/dist/pino-logger-RYLGXPHZ.js.map +1 -0
- package/dist/prisma/generator/index.d.ts +1 -0
- package/dist/prisma/generator/index.js +228 -0
- package/dist/prisma/generator/index.js.map +1 -0
- package/dist/prisma/index.d.ts +5 -0
- package/dist/prisma/index.js +12 -0
- package/dist/prisma/index.js.map +1 -0
- package/dist/prisma-extension-krXnYnrN.d.ts +30 -0
- package/dist/rbac/index.d.ts +116 -0
- package/dist/rbac/index.js +7 -0
- package/dist/rbac/index.js.map +1 -0
- package/dist/sdk-THO3IWEE.js +136 -0
- package/dist/sdk-THO3IWEE.js.map +1 -0
- package/dist/server/index.d.ts +359 -0
- package/dist/server/index.js +884 -0
- package/dist/server/index.js.map +1 -0
- package/dist/telemetry/index.d.ts +83 -0
- package/dist/telemetry/index.js +209 -0
- package/dist/telemetry/index.js.map +1 -0
- package/dist/temporal/index.d.ts +148 -0
- package/dist/temporal/index.js +6 -0
- package/dist/temporal/index.js.map +1 -0
- package/dist/tracker/index.d.ts +5 -0
- package/dist/tracker/index.js +9 -0
- package/dist/tracker/index.js.map +1 -0
- package/dist/types-Bo2wkjSP.d.ts +35 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +4 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/workspace-guard-LHBNK4TG.js +5 -0
- package/dist/workspace-guard-LHBNK4TG.js.map +1 -0
- package/package.json +241 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { logger } from './chunk-CBZNP6RF.js';
|
|
2
|
+
import { __name } from './chunk-XE4OXN2W.js';
|
|
3
|
+
|
|
4
|
+
// src/server/workspace-guard.ts
|
|
5
|
+
var WorkspaceRequiredError = class WorkspaceRequiredError2 extends Error {
|
|
6
|
+
static {
|
|
7
|
+
__name(this, "WorkspaceRequiredError");
|
|
8
|
+
}
|
|
9
|
+
extensions;
|
|
10
|
+
constructor(message) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = "WorkspaceRequiredError";
|
|
13
|
+
this.extensions = {
|
|
14
|
+
code: "WORKSPACE_REQUIRED",
|
|
15
|
+
http: {
|
|
16
|
+
status: 403
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
function isFieldPublic(field) {
|
|
22
|
+
const directives = field?.astNode?.directives;
|
|
23
|
+
if (!directives || directives.length === 0) return false;
|
|
24
|
+
for (const dir of directives) {
|
|
25
|
+
const name = dir.name?.value;
|
|
26
|
+
if (name === "public") return true;
|
|
27
|
+
if (name === "rbac") {
|
|
28
|
+
const args = dir.arguments ?? [];
|
|
29
|
+
for (const arg of args) {
|
|
30
|
+
if (arg.name?.value === "public" && arg.value?.kind === "BooleanValue" && arg.value.value === true) {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
__name(isFieldPublic, "isFieldPublic");
|
|
39
|
+
function resolveOperationType(document, operationName) {
|
|
40
|
+
const defs = document?.definitions ?? [];
|
|
41
|
+
for (const def of defs) {
|
|
42
|
+
if (def.kind !== "OperationDefinition") ;
|
|
43
|
+
if (def.operation && (!operationName || def.name?.value === operationName)) {
|
|
44
|
+
return def.operation;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return "query";
|
|
48
|
+
}
|
|
49
|
+
__name(resolveOperationType, "resolveOperationType");
|
|
50
|
+
function allRootFieldsPublic(schema, document, operationName) {
|
|
51
|
+
if (!schema || !document) return false;
|
|
52
|
+
const opType = resolveOperationType(document, operationName);
|
|
53
|
+
let rootType;
|
|
54
|
+
if (opType === "mutation") {
|
|
55
|
+
rootType = schema.getMutationType?.();
|
|
56
|
+
} else if (opType === "subscription") {
|
|
57
|
+
rootType = schema.getSubscriptionType?.();
|
|
58
|
+
} else {
|
|
59
|
+
rootType = schema.getQueryType?.();
|
|
60
|
+
}
|
|
61
|
+
if (!rootType) return false;
|
|
62
|
+
const fields = rootType.getFields();
|
|
63
|
+
const defs = document.definitions ?? [];
|
|
64
|
+
const opDef = defs.find((d) => d.operation && (!operationName || d.name?.value === operationName));
|
|
65
|
+
const selections = opDef?.selectionSet?.selections ?? [];
|
|
66
|
+
if (selections.length === 0) return false;
|
|
67
|
+
for (const sel of selections) {
|
|
68
|
+
if (sel.kind && sel.kind !== "Field") return false;
|
|
69
|
+
const fieldName = sel.name?.value;
|
|
70
|
+
if (!fieldName) return false;
|
|
71
|
+
if (fieldName.startsWith("__")) continue;
|
|
72
|
+
const fieldDef = fields[fieldName];
|
|
73
|
+
if (!isFieldPublic(fieldDef)) return false;
|
|
74
|
+
}
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
__name(allRootFieldsPublic, "allRootFieldsPublic");
|
|
78
|
+
function createWorkspaceGuardPlugin(config) {
|
|
79
|
+
const enabled = config?.enabled ?? true;
|
|
80
|
+
const whitelistedOperations = new Set(config?.whitelistedOperations ?? []);
|
|
81
|
+
const allowPublicDirective = config?.allowPublicDirective ?? true;
|
|
82
|
+
const errorMessage = config?.errorMessage ?? "Workspace context required. Ensure the request includes a valid workspace context (x-workspace-id header).";
|
|
83
|
+
if (!enabled) {
|
|
84
|
+
logger.info("[WorkspaceGuard] Workspace guard disabled");
|
|
85
|
+
return {};
|
|
86
|
+
}
|
|
87
|
+
logger.info("[WorkspaceGuard] Workspace guard enabled", {
|
|
88
|
+
whitelistedOperations: [
|
|
89
|
+
...whitelistedOperations
|
|
90
|
+
],
|
|
91
|
+
allowPublicDirective
|
|
92
|
+
});
|
|
93
|
+
return {
|
|
94
|
+
onExecute({ args }) {
|
|
95
|
+
let operationName = args.operationName;
|
|
96
|
+
if (!operationName && args.document?.definitions) {
|
|
97
|
+
for (const def of args.document.definitions) {
|
|
98
|
+
if (def.operation && def.name?.value) {
|
|
99
|
+
operationName = def.name.value;
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (operationName && whitelistedOperations.has(operationName)) {
|
|
105
|
+
return void 0;
|
|
106
|
+
}
|
|
107
|
+
if (allowPublicDirective && allRootFieldsPublic(args.schema, args.document, operationName)) {
|
|
108
|
+
return void 0;
|
|
109
|
+
}
|
|
110
|
+
const context = args.contextValue;
|
|
111
|
+
const workspaceId = context?.workspace?.id;
|
|
112
|
+
const headerWorkspaceId = context?.request?.headers?.get?.("x-workspace-id");
|
|
113
|
+
if (!workspaceId && !headerWorkspaceId) {
|
|
114
|
+
logger.warn("[WorkspaceGuard] Blocked operation without workspace context", {
|
|
115
|
+
operationName: operationName ?? "<anonymous>"
|
|
116
|
+
});
|
|
117
|
+
throw new WorkspaceRequiredError(errorMessage);
|
|
118
|
+
}
|
|
119
|
+
return void 0;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
__name(createWorkspaceGuardPlugin, "createWorkspaceGuardPlugin");
|
|
124
|
+
|
|
125
|
+
export { createWorkspaceGuardPlugin };
|
|
126
|
+
//# sourceMappingURL=chunk-UBXFIIAW.js.map
|
|
127
|
+
//# sourceMappingURL=chunk-UBXFIIAW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server/workspace-guard.ts"],"names":["WorkspaceRequiredError","Error","extensions","message","name","code","http","status","isFieldPublic","field","directives","astNode","length","dir","value","args","arguments","arg","kind","resolveOperationType","document","operationName","defs","definitions","def","operation","allRootFieldsPublic","schema","opType","rootType","getMutationType","getSubscriptionType","getQueryType","fields","getFields","opDef","find","d","selections","selectionSet","sel","fieldName","startsWith","fieldDef","createWorkspaceGuardPlugin","config","enabled","whitelistedOperations","Set","allowPublicDirective","errorMessage","logger","info","onExecute","has","undefined","context","contextValue","workspaceId","workspace","id","headerWorkspaceId","request","headers","get","warn"],"mappings":"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","file":"chunk-UBXFIIAW.js","sourcesContent":["/**\n * Workspace Guard Plugin for GraphQL Yoga\n *\n * Blocks GraphQL operations that lack a valid workspace context (x-workspace-id header).\n * This provides defense-in-depth at the GraphQL layer — even if the gateway allows a request\n * through, the service itself will reject it if workspace context is missing.\n *\n * **Whitelisted operations** (e.g., workspace creation) can bypass the guard.\n *\n * **Public schema fields** marked with `@public` or `@rbac(public: true)` are auto-bypassed\n * when `allowPublicDirective` is enabled (default true). This lets anonymous public-link\n * resolvers (e.g. `accessShare`) be reached through the federation gateway without an\n * `x-workspace-id` header.\n *\n * @module server/workspace-guard\n * @category Security\n */\n\nimport { logger } from '../logger/index.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Configuration for the workspace guard plugin\n */\nexport interface WorkspaceGuardConfig {\n /**\n * Enable the workspace guard.\n * @default true\n */\n enabled?: boolean;\n\n /**\n * GraphQL operation names that can run without workspace context.\n * These are typically operations like `createWorkspace` that execute\n * before a workspace exists.\n *\n * Matching is case-sensitive against the operation name in the GraphQL document.\n *\n * @default []\n *\n * @example\n * ```typescript\n * whitelistedOperations: ['createWorkspace', 'validateTokens']\n * ```\n */\n whitelistedOperations?: string[];\n\n /**\n * Custom error message when workspace context is missing.\n * @default 'Workspace context required. Provide a valid X-Workspace-Authorization header.'\n */\n errorMessage?: string;\n\n /**\n * If true, fields whose schema definition carries the `@public` directive or\n * `@rbac(public: true)` are bypassed by the guard automatically — no need to\n * list them in `whitelistedOperations`. The check uses the GraphQL schema\n * passed to the executor at request time.\n *\n * Set to false to fall back to the legacy operation-name allowlist only.\n *\n * @default true\n */\n allowPublicDirective?: boolean;\n}\n\n// ============================================================================\n// GraphQL Error\n// ============================================================================\n\n/**\n * Workspace required error with proper GraphQL error extensions\n */\nclass WorkspaceRequiredError extends Error {\n public readonly extensions: { code: string; http: { status: number } };\n\n constructor(message: string) {\n super(message);\n this.name = 'WorkspaceRequiredError';\n this.extensions = {\n code: 'WORKSPACE_REQUIRED',\n http: { status: 403 },\n };\n }\n}\n\n// ============================================================================\n// Schema directive helpers\n// ============================================================================\n\n/**\n * Minimal structural typings for the bits of the GraphQL schema we look at.\n * We avoid importing the GraphQL types directly so the SDK keeps `graphql`\n * as a peer dep without a runtime hard dependency.\n */\ninterface FieldLikeDef {\n astNode?: {\n directives?: ReadonlyArray<{\n name: { value: string };\n arguments?: ReadonlyArray<{\n name: { value: string };\n value: { kind: string; value?: unknown };\n }>;\n }>;\n };\n}\n\ninterface SchemaLike {\n getQueryType?: () => { getFields: () => Record<string, FieldLikeDef> } | null | undefined;\n getMutationType?: () => { getFields: () => Record<string, FieldLikeDef> } | null | undefined;\n getSubscriptionType?: () => { getFields: () => Record<string, FieldLikeDef> } | null | undefined;\n}\n\n/**\n * Returns true iff the field carries either:\n * - `@public` directive, OR\n * - `@rbac(public: true)` directive (any arg-order)\n */\nfunction isFieldPublic(field: FieldLikeDef | undefined): boolean {\n const directives = field?.astNode?.directives;\n if (!directives || directives.length === 0) return false;\n\n for (const dir of directives) {\n const name = dir.name?.value;\n if (name === 'public') return true;\n if (name === 'rbac') {\n const args = dir.arguments ?? [];\n for (const arg of args) {\n if (arg.name?.value === 'public' && arg.value?.kind === 'BooleanValue' && arg.value.value === true) {\n return true;\n }\n }\n }\n }\n return false;\n}\n\ninterface DocumentLike {\n definitions?: ReadonlyArray<{\n kind?: string;\n operation?: string;\n name?: { value?: string };\n selectionSet?: {\n selections?: ReadonlyArray<{\n kind?: string;\n name?: { value?: string };\n }>;\n };\n }>;\n}\n\n/**\n * Resolve the operation type ('query' | 'mutation' | 'subscription') for the\n * primary operation in the document. Falls back to 'query'.\n */\nfunction resolveOperationType(\n document: DocumentLike | undefined,\n operationName: string | null | undefined\n): 'query' | 'mutation' | 'subscription' {\n const defs = document?.definitions ?? [];\n for (const def of defs) {\n if (def.kind !== 'OperationDefinition') {\n // Older parsers may not set kind; fall through to the operation property check below.\n }\n if (def.operation && (!operationName || def.name?.value === operationName)) {\n return def.operation as 'query' | 'mutation' | 'subscription';\n }\n }\n return 'query';\n}\n\n/**\n * Returns true iff every root field in the document selection set is marked\n * public on the schema. Empty selection sets return false (fail-closed).\n */\nfunction allRootFieldsPublic(\n schema: SchemaLike | undefined,\n document: DocumentLike | undefined,\n operationName: string | null | undefined\n): boolean {\n if (!schema || !document) return false;\n\n const opType = resolveOperationType(document, operationName);\n let rootType: ReturnType<NonNullable<SchemaLike['getQueryType']>> | undefined;\n if (opType === 'mutation') {\n rootType = schema.getMutationType?.();\n } else if (opType === 'subscription') {\n rootType = schema.getSubscriptionType?.();\n } else {\n rootType = schema.getQueryType?.();\n }\n\n if (!rootType) return false;\n\n const fields = rootType.getFields();\n const defs = document.definitions ?? [];\n\n // Find the operation definition matching operationName (or first one).\n const opDef = defs.find((d) => d.operation && (!operationName || d.name?.value === operationName));\n const selections = opDef?.selectionSet?.selections ?? [];\n if (selections.length === 0) return false;\n\n for (const sel of selections) {\n // Only consider plain Field selections (skip fragments — fragments at the root\n // shouldn't drive a public bypass; fail-closed there).\n if (sel.kind && sel.kind !== 'Field') return false;\n const fieldName = sel.name?.value;\n if (!fieldName) return false;\n // __schema / __type introspection is always allowed without workspace context;\n // treat them as public.\n if (fieldName.startsWith('__')) continue;\n const fieldDef = fields[fieldName];\n if (!isFieldPublic(fieldDef)) return false;\n }\n return true;\n}\n\n// ============================================================================\n// Plugin\n// ============================================================================\n\n/**\n * Create a GraphQL Yoga plugin that guards operations requiring workspace context.\n *\n * The plugin inspects the GraphQL context for workspace information (set by the SDK's\n * buildContext from the x-workspace-id header). If missing and the operation is not\n * whitelisted (and not all-public per directive), it throws a WORKSPACE_REQUIRED error.\n *\n * @param config - Guard configuration\n * @returns A Yoga-compatible plugin\n *\n * @example\n * ```typescript\n * import { createWorkspaceGuardPlugin } from '@burdenoff/be-sdk/server';\n *\n * const guardPlugin = createWorkspaceGuardPlugin({\n * whitelistedOperations: ['createWorkspace'],\n * allowPublicDirective: true, // default\n * });\n * ```\n */\nexport function createWorkspaceGuardPlugin(config?: WorkspaceGuardConfig) {\n const enabled = config?.enabled ?? true;\n const whitelistedOperations = new Set(config?.whitelistedOperations ?? []);\n const allowPublicDirective = config?.allowPublicDirective ?? true;\n const errorMessage =\n config?.errorMessage ??\n 'Workspace context required. Ensure the request includes a valid workspace context (x-workspace-id header).';\n\n if (!enabled) {\n logger.info('[WorkspaceGuard] Workspace guard disabled');\n return {};\n }\n\n logger.info('[WorkspaceGuard] Workspace guard enabled', {\n whitelistedOperations: [...whitelistedOperations],\n allowPublicDirective,\n });\n\n return {\n onExecute({\n args,\n }: {\n args: {\n schema?: SchemaLike;\n document?: DocumentLike;\n operationName?: string | null;\n contextValue?: { workspace?: { id?: string }; request?: Request };\n };\n }) {\n // Extract operation name from args or document\n let operationName = args.operationName;\n\n if (!operationName && args.document?.definitions) {\n for (const def of args.document.definitions) {\n if (def.operation && def.name?.value) {\n operationName = def.name.value;\n break;\n }\n }\n }\n\n // Check if operation is whitelisted by name\n if (operationName && whitelistedOperations.has(operationName)) {\n return undefined;\n }\n\n // Check schema-directive-based public bypass (Option C)\n if (allowPublicDirective && allRootFieldsPublic(args.schema, args.document, operationName)) {\n return undefined;\n }\n\n // Check for workspace context\n const context = args.contextValue as { workspace?: { id?: string }; request?: Request } | undefined;\n const workspaceId = context?.workspace?.id;\n\n // Also check the raw header as a fallback\n const headerWorkspaceId = context?.request?.headers?.get?.('x-workspace-id');\n\n if (!workspaceId && !headerWorkspaceId) {\n logger.warn('[WorkspaceGuard] Blocked operation without workspace context', {\n operationName: operationName ?? '<anonymous>',\n });\n throw new WorkspaceRequiredError(errorMessage);\n }\n\n return undefined;\n },\n };\n}\n"]}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { logger } from './chunk-CBZNP6RF.js';
|
|
2
|
+
import { __name } from './chunk-XE4OXN2W.js';
|
|
3
|
+
import { GraphQLClient } from 'graphql-request';
|
|
4
|
+
|
|
5
|
+
function createInternalClient(serviceUrl, serviceIdentity, additionalHeaders) {
|
|
6
|
+
const INTERNAL_TOKEN = process.env["INTERNAL_SERVICE_TOKEN"];
|
|
7
|
+
if (!INTERNAL_TOKEN) {
|
|
8
|
+
const error = new Error("INTERNAL_SERVICE_TOKEN environment variable is not configured. This is required for internal service-to-service communication with RBAC bypass.");
|
|
9
|
+
logger.error("[Internal Client] Missing INTERNAL_SERVICE_TOKEN", {
|
|
10
|
+
serviceIdentity,
|
|
11
|
+
serviceUrl
|
|
12
|
+
});
|
|
13
|
+
throw error;
|
|
14
|
+
}
|
|
15
|
+
if (!serviceIdentity || serviceIdentity.trim().length === 0) {
|
|
16
|
+
throw new Error("serviceIdentity is required and cannot be empty");
|
|
17
|
+
}
|
|
18
|
+
if (!serviceUrl || serviceUrl.trim().length === 0) {
|
|
19
|
+
throw new Error("serviceUrl is required and cannot be empty");
|
|
20
|
+
}
|
|
21
|
+
logger.debug("[Internal Client] Creating internal client", {
|
|
22
|
+
serviceUrl,
|
|
23
|
+
serviceIdentity
|
|
24
|
+
});
|
|
25
|
+
const headers = {
|
|
26
|
+
"x-bypass-permission": INTERNAL_TOKEN,
|
|
27
|
+
"x-service-identity": serviceIdentity,
|
|
28
|
+
"x-call-type": "internal",
|
|
29
|
+
...additionalHeaders
|
|
30
|
+
};
|
|
31
|
+
return new GraphQLClient(serviceUrl, {
|
|
32
|
+
headers
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
__name(createInternalClient, "createInternalClient");
|
|
36
|
+
function createInternalClientForWorkspaces(serviceIdentity, additionalHeaders) {
|
|
37
|
+
const WORKSPACES_GATEWAY_URL = process.env["WORKSPACES_GATEWAY_URL"] || "http://localhost:4001/workspaces/graphql";
|
|
38
|
+
logger.debug("[Internal Client] Creating Workspaces client", {
|
|
39
|
+
serviceIdentity,
|
|
40
|
+
gatewayUrl: WORKSPACES_GATEWAY_URL
|
|
41
|
+
});
|
|
42
|
+
return createInternalClient(WORKSPACES_GATEWAY_URL, serviceIdentity, additionalHeaders);
|
|
43
|
+
}
|
|
44
|
+
__name(createInternalClientForWorkspaces, "createInternalClientForWorkspaces");
|
|
45
|
+
function createInternalClientForTenant(serviceIdentity, additionalHeaders) {
|
|
46
|
+
const TENANT_GATEWAY_URL = process.env["TENANT_GATEWAY_URL"] || "http://localhost:4002/tenant/graphql";
|
|
47
|
+
logger.debug("[Internal Client] Creating Tenant client", {
|
|
48
|
+
serviceIdentity,
|
|
49
|
+
gatewayUrl: TENANT_GATEWAY_URL
|
|
50
|
+
});
|
|
51
|
+
return createInternalClient(TENANT_GATEWAY_URL, serviceIdentity, additionalHeaders);
|
|
52
|
+
}
|
|
53
|
+
__name(createInternalClientForTenant, "createInternalClientForTenant");
|
|
54
|
+
|
|
55
|
+
export { createInternalClient, createInternalClientForTenant, createInternalClientForWorkspaces };
|
|
56
|
+
//# sourceMappingURL=chunk-VI4VSNTI.js.map
|
|
57
|
+
//# sourceMappingURL=chunk-VI4VSNTI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/client/internal-client.ts"],"names":["createInternalClient","serviceUrl","serviceIdentity","additionalHeaders","INTERNAL_TOKEN","process","env","error","Error","logger","trim","length","debug","headers","GraphQLClient","createInternalClientForWorkspaces","WORKSPACES_GATEWAY_URL","gatewayUrl","createInternalClientForTenant","TENANT_GATEWAY_URL"],"mappings":";;;;AAiFO,SAASA,oBAAAA,CACdC,UAAAA,EACAC,eAAAA,EACAC,iBAAAA,EAA0C;AAE1C,EAAA,MAAMC,cAAAA,GAAiBC,OAAAA,CAAQC,GAAAA,CAAI,wBAAA,CAAA;AAEnC,EAAA,IAAI,CAACF,cAAAA,EAAgB;AACnB,IAAA,MAAMG,KAAAA,GAAQ,IAAIC,KAAAA,CAChB,iJACE,CAAA;AAEJC,IAAAA,MAAAA,CAAOF,MAAM,kDAAA,EAAoD;AAC/DL,MAAAA,eAAAA;AACAD,MAAAA;KACF,CAAA;AACA,IAAA,MAAMM,KAAAA;AACR,EAAA;AAEA,EAAA,IAAI,CAACL,eAAAA,IAAmBA,eAAAA,CAAgBQ,IAAAA,EAAI,CAAGC,WAAW,CAAA,EAAG;AAC3D,IAAA,MAAM,IAAIH,MAAM,iDAAA,CAAA;AAClB,EAAA;AAEA,EAAA,IAAI,CAACP,UAAAA,IAAcA,UAAAA,CAAWS,IAAAA,EAAI,CAAGC,WAAW,CAAA,EAAG;AACjD,IAAA,MAAM,IAAIH,MAAM,4CAAA,CAAA;AAClB,EAAA;AAEAC,EAAAA,MAAAA,CAAOG,MAAM,4CAAA,EAA8C;AACzDX,IAAAA,UAAAA;AACAC,IAAAA;GACF,CAAA;AAiBA,EAAA,MAAMW,OAAAA,GAAkC;IACtC,qBAAA,EAAuBT,cAAAA;IACvB,oBAAA,EAAsBF,eAAAA;IACtB,aAAA,EAAe,UAAA;IACf,GAAGC;AACL,GAAA;AAEA,EAAA,OAAO,IAAIW,cAAcb,UAAAA,EAAY;AAAEY,IAAAA;GAAQ,CAAA;AACjD;AAvDgBb,MAAAA,CAAAA,oBAAAA,EAAAA,sBAAAA,CAAAA;AAiFT,SAASe,iCAAAA,CACdb,iBACAC,iBAAAA,EAA0C;AAE1C,EAAA,MAAMa,sBAAAA,GAAyBX,OAAAA,CAAQC,GAAAA,CAAI,wBAAA,CAAA,IAA6B,0CAAA;AAExEG,EAAAA,MAAAA,CAAOG,MAAM,8CAAA,EAAgD;AAC3DV,IAAAA,eAAAA;IACAe,UAAAA,EAAYD;GACd,CAAA;AAEA,EAAA,OAAOhB,oBAAAA,CAAqBgB,sBAAAA,EAAwBd,eAAAA,EAAiBC,iBAAAA,CAAAA;AACvE;AAZgBY,MAAAA,CAAAA,iCAAAA,EAAAA,mCAAAA,CAAAA;AAuCT,SAASG,6BAAAA,CACdhB,iBACAC,iBAAAA,EAA0C;AAE1C,EAAA,MAAMgB,kBAAAA,GAAqBd,OAAAA,CAAQC,GAAAA,CAAI,oBAAA,CAAA,IAAyB,sCAAA;AAEhEG,EAAAA,MAAAA,CAAOG,MAAM,0CAAA,EAA4C;AACvDV,IAAAA,eAAAA;IACAe,UAAAA,EAAYE;GACd,CAAA;AAEA,EAAA,OAAOnB,oBAAAA,CAAqBmB,kBAAAA,EAAoBjB,eAAAA,EAAiBC,iBAAAA,CAAAA;AACnE;AAZgBe,MAAAA,CAAAA,6BAAAA,EAAAA,+BAAAA,CAAAA","file":"chunk-VI4VSNTI.js","sourcesContent":["/**\n * Internal Client for Service-to-Service Communication\n *\n * This module provides helper functions to create GraphQL clients for internal\n * service-to-service calls with automatic RBAC bypass.\n *\n * Features:\n * - Automatic injection of bypass headers (x-bypass-permission, x-service-identity)\n * - Pre-configured for workspaces and tenant gateways\n * - Environment variable validation\n *\n * ⚠️ IMPORTANT: Header Transformation Architecture\n * ================================================\n * This client sends headers to the INTERNAL API GATEWAY, which transforms them\n * before forwarding to downstream services with RBAC directives.\n *\n * Header Flow:\n * 1. This client → Gateway: x-bypass-permission, x-service-identity, x-call-type\n * 2. Gateway → Service: x-rbac-bypass, x-bypass-source, x-bypass-timestamp\n *\n * The headers sent by this client DO NOT match the headers expected by the RBAC\n * directive (src/rbac/directive.ts). This is INTENTIONAL - the gateway handles\n * the transformation as part of the security architecture.\n *\n * @module client/internal-client\n * @example\n * ```typescript\n * import { createInternalClient } from '@burdenoff/be-sdk/client/internal';\n *\n * const client = createInternalClient(\n * 'http://localhost:4001/workspaces/graphql',\n * 'scheduler-service'\n * );\n *\n * const result = await client.request(`\n * mutation {\n * createProject(input: { name: \"Test\" }) {\n * id\n * }\n * }\n * `);\n * ```\n */\n\nimport { GraphQLClient } from 'graphql-request';\nimport { logger } from '../logger/index.js';\n\n/**\n * Create GraphQL client for internal service-to-service calls\n *\n * This client automatically injects bypass headers for RBAC bypass:\n * - x-bypass-permission: Internal service token (from env)\n * - x-service-identity: Service identifier\n * - x-call-type: \"internal\"\n *\n * IMPORTANT: This should ONLY be used for internal service-to-service calls,\n * never for user-initiated requests.\n *\n * @param serviceUrl - GraphQL endpoint URL (e.g., 'http://localhost:4001/workspaces/graphql')\n * @param serviceIdentity - Service identifier (e.g., 'scheduler-service')\n * @param additionalHeaders - Optional additional headers to include\n * @returns Configured GraphQL client with bypass headers\n * @throws Error if INTERNAL_SERVICE_TOKEN is not configured\n *\n * @example\n * ```typescript\n * const client = createInternalClient(\n * process.env.WORKSPACES_GATEWAY_URL,\n * 'scheduler-service'\n * );\n *\n * const result = await client.request(`\n * query {\n * workspace(id: \"ws123\") {\n * id\n * name\n * }\n * }\n * `);\n * ```\n */\nexport function createInternalClient(\n serviceUrl: string,\n serviceIdentity: string,\n additionalHeaders?: Record<string, string>\n): GraphQLClient {\n const INTERNAL_TOKEN = process.env['INTERNAL_SERVICE_TOKEN'];\n\n if (!INTERNAL_TOKEN) {\n const error = new Error(\n 'INTERNAL_SERVICE_TOKEN environment variable is not configured. ' +\n 'This is required for internal service-to-service communication with RBAC bypass.'\n );\n logger.error('[Internal Client] Missing INTERNAL_SERVICE_TOKEN', {\n serviceIdentity,\n serviceUrl,\n });\n throw error;\n }\n\n if (!serviceIdentity || serviceIdentity.trim().length === 0) {\n throw new Error('serviceIdentity is required and cannot be empty');\n }\n\n if (!serviceUrl || serviceUrl.trim().length === 0) {\n throw new Error('serviceUrl is required and cannot be empty');\n }\n\n logger.debug('[Internal Client] Creating internal client', {\n serviceUrl,\n serviceIdentity,\n });\n\n // ⚠️ ARCHITECTURE NOTE: Header Transformation Flow\n // ================================================\n // These headers are sent to the INTERNAL API GATEWAY, NOT directly to downstream services.\n //\n // Flow:\n // 1. Service A (this client) → Internal API Gateway\n // Headers: x-bypass-permission, x-service-identity, x-call-type\n //\n // 2. Internal API Gateway → Service B (with RBAC directive)\n // Headers transformed to: x-rbac-bypass, x-bypass-source, x-bypass-timestamp\n //\n // The RBAC directive (src/rbac/directive.ts:472-474) expects the TRANSFORMED headers,\n // not these original headers. This is intentional and part of the security architecture.\n //\n // DO NOT \"fix\" this to match RBAC directive headers - the gateway handles the transformation.\n const headers: Record<string, string> = {\n 'x-bypass-permission': INTERNAL_TOKEN,\n 'x-service-identity': serviceIdentity,\n 'x-call-type': 'internal',\n ...additionalHeaders,\n };\n\n return new GraphQLClient(serviceUrl, { headers });\n}\n\n/**\n * Create GraphQL client for Workspaces Gateway (internal)\n *\n * Convenience wrapper for createInternalClient that uses WORKSPACES_GATEWAY_URL\n * from environment variables.\n *\n * @param serviceIdentity - Service identifier (e.g., 'scheduler-service')\n * @param additionalHeaders - Optional additional headers\n * @returns Configured GraphQL client\n * @throws Error if WORKSPACES_GATEWAY_URL or INTERNAL_SERVICE_TOKEN not configured\n *\n * @example\n * ```typescript\n * const client = createInternalClientForWorkspaces('scheduler-service');\n *\n * const result = await client.request(`\n * mutation {\n * generateReport(workspaceId: \"ws123\") {\n * id\n * }\n * }\n * `);\n * ```\n */\nexport function createInternalClientForWorkspaces(\n serviceIdentity: string,\n additionalHeaders?: Record<string, string>\n): GraphQLClient {\n const WORKSPACES_GATEWAY_URL = process.env['WORKSPACES_GATEWAY_URL'] || 'http://localhost:4001/workspaces/graphql';\n\n logger.debug('[Internal Client] Creating Workspaces client', {\n serviceIdentity,\n gatewayUrl: WORKSPACES_GATEWAY_URL,\n });\n\n return createInternalClient(WORKSPACES_GATEWAY_URL, serviceIdentity, additionalHeaders);\n}\n\n/**\n * Create GraphQL client for Tenant Gateway (internal)\n *\n * Convenience wrapper for createInternalClient that uses TENANT_GATEWAY_URL\n * from environment variables.\n *\n * @param serviceIdentity - Service identifier (e.g., 'notification-dispatcher')\n * @param additionalHeaders - Optional additional headers\n * @returns Configured GraphQL client\n * @throws Error if TENANT_GATEWAY_URL or INTERNAL_SERVICE_TOKEN not configured\n *\n * @example\n * ```typescript\n * const client = createInternalClientForTenant('notification-dispatcher');\n *\n * const result = await client.request(`\n * query {\n * notifications(workspaceId: \"ws123\") {\n * id\n * type\n * }\n * }\n * `);\n * ```\n */\nexport function createInternalClientForTenant(\n serviceIdentity: string,\n additionalHeaders?: Record<string, string>\n): GraphQLClient {\n const TENANT_GATEWAY_URL = process.env['TENANT_GATEWAY_URL'] || 'http://localhost:4002/tenant/graphql';\n\n logger.debug('[Internal Client] Creating Tenant client', {\n serviceIdentity,\n gatewayUrl: TENANT_GATEWAY_URL,\n });\n\n return createInternalClient(TENANT_GATEWAY_URL, serviceIdentity, additionalHeaders);\n}\n"]}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { __export, __name } from './chunk-XE4OXN2W.js';
|
|
2
|
+
|
|
3
|
+
// src/feature-flags/index.ts
|
|
4
|
+
var feature_flags_exports = {};
|
|
5
|
+
__export(feature_flags_exports, {
|
|
6
|
+
FeatureFlagProvider: () => FeatureFlagProvider
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
// src/feature-flags/provider.ts
|
|
10
|
+
var EVALUATE_QUERY = `
|
|
11
|
+
query EvaluateFeatureFlags($input: EvaluateFeatureFlagsInput!) {
|
|
12
|
+
evaluateFeatureFlags(input: $input) {
|
|
13
|
+
evaluations {
|
|
14
|
+
key
|
|
15
|
+
value
|
|
16
|
+
variant
|
|
17
|
+
reason
|
|
18
|
+
flagType
|
|
19
|
+
errorCode
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
`;
|
|
24
|
+
var FeatureFlagProvider = class {
|
|
25
|
+
static {
|
|
26
|
+
__name(this, "FeatureFlagProvider");
|
|
27
|
+
}
|
|
28
|
+
gatewayUrl;
|
|
29
|
+
cacheTtlMs;
|
|
30
|
+
serviceToken;
|
|
31
|
+
defaultActorType;
|
|
32
|
+
/** In-memory cache keyed by `${workspaceId}:${actorId}` */
|
|
33
|
+
cache = /* @__PURE__ */ new Map();
|
|
34
|
+
constructor(config) {
|
|
35
|
+
this.gatewayUrl = config.gatewayUrl;
|
|
36
|
+
this.cacheTtlMs = (config.cacheTtlSeconds ?? 60) * 1e3;
|
|
37
|
+
this.serviceToken = config.serviceToken;
|
|
38
|
+
this.defaultActorType = config.defaultActorType ?? "service";
|
|
39
|
+
}
|
|
40
|
+
// =========================================================================
|
|
41
|
+
// Public Resolution Methods (OpenFeature-compatible interface)
|
|
42
|
+
// =========================================================================
|
|
43
|
+
/**
|
|
44
|
+
* Resolve a boolean feature flag
|
|
45
|
+
*
|
|
46
|
+
* @param flagKey - The flag key (e.g. "calendar.recurring-events")
|
|
47
|
+
* @param defaultValue - Default value if resolution fails
|
|
48
|
+
* @param context - Evaluation context with actor and workspace info
|
|
49
|
+
* @returns Resolution details with the resolved boolean value
|
|
50
|
+
*/
|
|
51
|
+
async resolveBoolean(flagKey, defaultValue, context) {
|
|
52
|
+
const evaluation = await this.getEvaluation(flagKey, context);
|
|
53
|
+
if (!evaluation) {
|
|
54
|
+
return {
|
|
55
|
+
value: defaultValue,
|
|
56
|
+
reason: "DEFAULT",
|
|
57
|
+
flagType: "BOOLEAN"
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
value: Boolean(evaluation.value),
|
|
62
|
+
variant: evaluation.variant ?? void 0,
|
|
63
|
+
reason: evaluation.reason,
|
|
64
|
+
flagType: evaluation.flagType,
|
|
65
|
+
errorCode: evaluation.errorCode ?? void 0
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Resolve a string feature flag
|
|
70
|
+
*/
|
|
71
|
+
async resolveString(flagKey, defaultValue, context) {
|
|
72
|
+
const evaluation = await this.getEvaluation(flagKey, context);
|
|
73
|
+
if (!evaluation) {
|
|
74
|
+
return {
|
|
75
|
+
value: defaultValue,
|
|
76
|
+
reason: "DEFAULT",
|
|
77
|
+
flagType: "STRING"
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
value: String(evaluation.value),
|
|
82
|
+
variant: evaluation.variant ?? void 0,
|
|
83
|
+
reason: evaluation.reason,
|
|
84
|
+
flagType: evaluation.flagType,
|
|
85
|
+
errorCode: evaluation.errorCode ?? void 0
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Resolve a number feature flag
|
|
90
|
+
*/
|
|
91
|
+
async resolveNumber(flagKey, defaultValue, context) {
|
|
92
|
+
const evaluation = await this.getEvaluation(flagKey, context);
|
|
93
|
+
if (!evaluation) {
|
|
94
|
+
return {
|
|
95
|
+
value: defaultValue,
|
|
96
|
+
reason: "DEFAULT",
|
|
97
|
+
flagType: "NUMBER"
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
value: Number(evaluation.value),
|
|
102
|
+
variant: evaluation.variant ?? void 0,
|
|
103
|
+
reason: evaluation.reason,
|
|
104
|
+
flagType: evaluation.flagType,
|
|
105
|
+
errorCode: evaluation.errorCode ?? void 0
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Resolve an object (JSON) feature flag
|
|
110
|
+
*/
|
|
111
|
+
async resolveObject(flagKey, defaultValue, context) {
|
|
112
|
+
const evaluation = await this.getEvaluation(flagKey, context);
|
|
113
|
+
if (!evaluation) {
|
|
114
|
+
return {
|
|
115
|
+
value: defaultValue,
|
|
116
|
+
reason: "DEFAULT",
|
|
117
|
+
flagType: "JSON"
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
value: evaluation.value,
|
|
122
|
+
variant: evaluation.variant ?? void 0,
|
|
123
|
+
reason: evaluation.reason,
|
|
124
|
+
flagType: evaluation.flagType,
|
|
125
|
+
errorCode: evaluation.errorCode ?? void 0
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
// =========================================================================
|
|
129
|
+
// Convenience Methods
|
|
130
|
+
// =========================================================================
|
|
131
|
+
/**
|
|
132
|
+
* Quick boolean check — returns just the value.
|
|
133
|
+
* Defaults to `true` (default-allow philosophy) if the flag is not found.
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* ```typescript
|
|
137
|
+
* if (await provider.isEnabled('calendar.recurring-events', ctx)) {
|
|
138
|
+
* // Feature is enabled
|
|
139
|
+
* }
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
142
|
+
async isEnabled(flagKey, context) {
|
|
143
|
+
const result = await this.resolveBoolean(flagKey, true, context ?? {
|
|
144
|
+
workspaceId: "system",
|
|
145
|
+
actorType: "service",
|
|
146
|
+
actorId: "boot"
|
|
147
|
+
});
|
|
148
|
+
return result.value;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Bulk evaluate multiple flags at once.
|
|
152
|
+
* Returns a map of key -> ResolutionDetails.
|
|
153
|
+
*/
|
|
154
|
+
async evaluateAll(context, keys) {
|
|
155
|
+
const evaluations = await this.fetchEvaluations(context, keys);
|
|
156
|
+
const result = /* @__PURE__ */ new Map();
|
|
157
|
+
for (const [key, evaluation] of evaluations) {
|
|
158
|
+
result.set(key, {
|
|
159
|
+
value: evaluation.value,
|
|
160
|
+
variant: evaluation.variant ?? void 0,
|
|
161
|
+
reason: evaluation.reason,
|
|
162
|
+
flagType: evaluation.flagType,
|
|
163
|
+
errorCode: evaluation.errorCode ?? void 0
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
return result;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Invalidate the cache for a specific workspace+actor combination,
|
|
170
|
+
* or all cache entries if no context is provided.
|
|
171
|
+
*/
|
|
172
|
+
invalidateCache(context) {
|
|
173
|
+
if (context) {
|
|
174
|
+
const cacheKey = this.buildCacheKey(context);
|
|
175
|
+
this.cache.delete(cacheKey);
|
|
176
|
+
} else {
|
|
177
|
+
this.cache.clear();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// =========================================================================
|
|
181
|
+
// Private Methods
|
|
182
|
+
// =========================================================================
|
|
183
|
+
/**
|
|
184
|
+
* Get a single flag evaluation, fetching from cache or API
|
|
185
|
+
*/
|
|
186
|
+
async getEvaluation(flagKey, context) {
|
|
187
|
+
try {
|
|
188
|
+
const evaluations = await this.fetchEvaluations(context);
|
|
189
|
+
return evaluations.get(flagKey) ?? null;
|
|
190
|
+
} catch {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Fetch flag evaluations — uses cache if available, otherwise calls the API
|
|
196
|
+
*/
|
|
197
|
+
async fetchEvaluations(context, keys) {
|
|
198
|
+
const cacheKey = this.buildCacheKey(context);
|
|
199
|
+
const cached = this.cache.get(cacheKey);
|
|
200
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
201
|
+
return cached.evaluations;
|
|
202
|
+
}
|
|
203
|
+
if (!context?.workspaceId) {
|
|
204
|
+
return /* @__PURE__ */ new Map();
|
|
205
|
+
}
|
|
206
|
+
try {
|
|
207
|
+
const response = await fetch(this.gatewayUrl, {
|
|
208
|
+
method: "POST",
|
|
209
|
+
headers: {
|
|
210
|
+
"Content-Type": "application/json",
|
|
211
|
+
...context.actorId && {
|
|
212
|
+
"x-actor-id": context.actorId
|
|
213
|
+
},
|
|
214
|
+
"x-actor-type": context.actorType ?? this.defaultActorType,
|
|
215
|
+
"x-workspace-id": context.workspaceId,
|
|
216
|
+
...this.serviceToken && {
|
|
217
|
+
Authorization: `Bearer ${this.serviceToken}`
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
body: JSON.stringify({
|
|
221
|
+
query: EVALUATE_QUERY,
|
|
222
|
+
variables: {
|
|
223
|
+
input: {
|
|
224
|
+
workspaceId: context.workspaceId,
|
|
225
|
+
keys: keys ?? null
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
})
|
|
229
|
+
});
|
|
230
|
+
if (!response.ok) {
|
|
231
|
+
return /* @__PURE__ */ new Map();
|
|
232
|
+
}
|
|
233
|
+
const json = await response.json();
|
|
234
|
+
if (json.errors || !json.data?.evaluateFeatureFlags?.evaluations) {
|
|
235
|
+
return /* @__PURE__ */ new Map();
|
|
236
|
+
}
|
|
237
|
+
const evaluations = /* @__PURE__ */ new Map();
|
|
238
|
+
for (const evaluation of json.data.evaluateFeatureFlags.evaluations) {
|
|
239
|
+
evaluations.set(evaluation.key, evaluation);
|
|
240
|
+
}
|
|
241
|
+
this.cache.set(cacheKey, {
|
|
242
|
+
evaluations,
|
|
243
|
+
expiresAt: Date.now() + this.cacheTtlMs
|
|
244
|
+
});
|
|
245
|
+
return evaluations;
|
|
246
|
+
} catch {
|
|
247
|
+
return /* @__PURE__ */ new Map();
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Build a cache key from the evaluation context.
|
|
252
|
+
*
|
|
253
|
+
* Safe against undefined/null context — callers like isEnabled() may
|
|
254
|
+
* pass no context during service boot (e.g. checking a default-allow
|
|
255
|
+
* feature flag at startup before the first request builds a real
|
|
256
|
+
* actor context). Without the guard, isEnabled(flag) used to throw
|
|
257
|
+
* "undefined is not an object (evaluating 'context.workspaceId')"
|
|
258
|
+
* and crash the svc at startServer().
|
|
259
|
+
*/
|
|
260
|
+
buildCacheKey(context) {
|
|
261
|
+
const ctx = context ?? {};
|
|
262
|
+
return `${ctx.workspaceId ?? "system"}:${ctx.actorId ?? "anonymous"}:${ctx.actorType ?? "user"}`;
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
export { FeatureFlagProvider, feature_flags_exports };
|
|
267
|
+
//# sourceMappingURL=chunk-VWCN5PMD.js.map
|
|
268
|
+
//# sourceMappingURL=chunk-VWCN5PMD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/feature-flags/index.ts","../src/feature-flags/provider.ts"],"names":["EVALUATE_QUERY","FeatureFlagProvider","gatewayUrl","cacheTtlMs","serviceToken","defaultActorType","cache","Map","config","cacheTtlSeconds","resolveBoolean","flagKey","defaultValue","context","evaluation","getEvaluation","value","reason","flagType","Boolean","variant","undefined","errorCode","resolveString","String","resolveNumber","Number","resolveObject","isEnabled","result","workspaceId","actorType","actorId","evaluateAll","keys","evaluations","fetchEvaluations","key","set","invalidateCache","cacheKey","buildCacheKey","delete","clear","get","cached","expiresAt","Date","now","response","fetch","method","headers","Authorization","body","JSON","stringify","query","variables","input","ok","json","errors","data","evaluateFeatureFlags","ctx"],"mappings":"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","file":"chunk-VWCN5PMD.js","sourcesContent":["/**\n * Feature Flags Module\n *\n * In-house feature flag evaluation for Burdenoff backend services.\n * Compatible with OpenFeature specification concepts.\n *\n * @module feature-flags\n * @category FeatureFlags\n *\n * @example\n * ```typescript\n * import { FeatureFlagProvider } from '@burdenoff/be-sdk/feature-flags';\n *\n * // Create provider (typically done once at service startup)\n * const provider = new FeatureFlagProvider({\n * gatewayUrl: process.env.GLOBAL_INT_GATEWAY_URL || 'http://global-int-gateway:4000/global/graphql',\n * cacheTtlSeconds: 60,\n * });\n *\n * // Check a boolean flag\n * const ctx = { actorId: 'user-123', actorType: 'user' as const, workspaceId: 'ws-456' };\n * const isEnabled = await provider.isEnabled('calendar.recurring-events', ctx);\n *\n * // Resolve with full details\n * const details = await provider.resolveBoolean('calendar.recurring-events', true, ctx);\n * console.log(details.value); // true\n * console.log(details.reason); // 'TARGETING_MATCH'\n * console.log(details.variant); // 'user-opted-in'\n * ```\n */\n\nexport { FeatureFlagProvider } from './provider.js';\nexport type {\n EvaluationContext,\n FeatureFlagProviderConfig,\n FlagEvaluation,\n FlagType,\n ResolutionDetails,\n} from './types.js';\n","/**\n * Burdenoff Feature Flags Server Provider\n *\n * Server-side feature flag provider for backend services.\n * Evaluates flags via the global-tenant-svc GraphQL API through the internal gateway.\n * Supports in-memory caching with configurable TTL.\n *\n * @module feature-flags/provider\n * @category FeatureFlags\n *\n * @example\n * ```typescript\n * import { FeatureFlagProvider } from '@burdenoff/be-sdk/feature-flags';\n *\n * const provider = new FeatureFlagProvider({\n * gatewayUrl: 'http://global-int-gateway:4000/global/graphql',\n * cacheTtlSeconds: 60,\n * });\n *\n * const isEnabled = await provider.resolveBoolean('calendar.recurring-events', false, {\n * actorId: 'user-123',\n * actorType: 'user',\n * workspaceId: 'ws-456',\n * });\n *\n * console.log(isEnabled.value); // true or false\n * console.log(isEnabled.reason); // 'TARGETING_MATCH', 'DEFAULT', etc.\n * ```\n */\n\nimport type { EvaluationContext, FeatureFlagProviderConfig, FlagEvaluation, ResolutionDetails } from './types.js';\n\ninterface CacheEntry {\n evaluations: Map<string, FlagEvaluation>;\n expiresAt: number;\n}\n\nconst EVALUATE_QUERY = `\n query EvaluateFeatureFlags($input: EvaluateFeatureFlagsInput!) {\n evaluateFeatureFlags(input: $input) {\n evaluations {\n key\n value\n variant\n reason\n flagType\n errorCode\n }\n }\n }\n`;\n\n/**\n * Burdenoff Feature Flag Server Provider\n *\n * Evaluates feature flags by querying the global-tenant-svc through\n * the internal gateway. Results are cached in-memory per workspace+actor\n * combination with a configurable TTL.\n */\nexport class FeatureFlagProvider {\n private readonly gatewayUrl: string;\n private readonly cacheTtlMs: number;\n private readonly serviceToken?: string;\n private readonly defaultActorType: string;\n\n /** In-memory cache keyed by `${workspaceId}:${actorId}` */\n private readonly cache = new Map<string, CacheEntry>();\n\n constructor(config: FeatureFlagProviderConfig) {\n this.gatewayUrl = config.gatewayUrl;\n this.cacheTtlMs = (config.cacheTtlSeconds ?? 60) * 1000;\n this.serviceToken = config.serviceToken;\n this.defaultActorType = config.defaultActorType ?? 'service';\n }\n\n // =========================================================================\n // Public Resolution Methods (OpenFeature-compatible interface)\n // =========================================================================\n\n /**\n * Resolve a boolean feature flag\n *\n * @param flagKey - The flag key (e.g. \"calendar.recurring-events\")\n * @param defaultValue - Default value if resolution fails\n * @param context - Evaluation context with actor and workspace info\n * @returns Resolution details with the resolved boolean value\n */\n async resolveBoolean(\n flagKey: string,\n defaultValue: boolean,\n context: EvaluationContext\n ): Promise<ResolutionDetails<boolean>> {\n const evaluation = await this.getEvaluation(flagKey, context);\n if (!evaluation) {\n return {\n value: defaultValue,\n reason: 'DEFAULT',\n flagType: 'BOOLEAN',\n };\n }\n\n return {\n value: Boolean(evaluation.value),\n variant: evaluation.variant ?? undefined,\n reason: evaluation.reason,\n flagType: evaluation.flagType,\n errorCode: evaluation.errorCode ?? undefined,\n };\n }\n\n /**\n * Resolve a string feature flag\n */\n async resolveString(\n flagKey: string,\n defaultValue: string,\n context: EvaluationContext\n ): Promise<ResolutionDetails<string>> {\n const evaluation = await this.getEvaluation(flagKey, context);\n if (!evaluation) {\n return {\n value: defaultValue,\n reason: 'DEFAULT',\n flagType: 'STRING',\n };\n }\n\n return {\n value: String(evaluation.value),\n variant: evaluation.variant ?? undefined,\n reason: evaluation.reason,\n flagType: evaluation.flagType,\n errorCode: evaluation.errorCode ?? undefined,\n };\n }\n\n /**\n * Resolve a number feature flag\n */\n async resolveNumber(\n flagKey: string,\n defaultValue: number,\n context: EvaluationContext\n ): Promise<ResolutionDetails<number>> {\n const evaluation = await this.getEvaluation(flagKey, context);\n if (!evaluation) {\n return {\n value: defaultValue,\n reason: 'DEFAULT',\n flagType: 'NUMBER',\n };\n }\n\n return {\n value: Number(evaluation.value),\n variant: evaluation.variant ?? undefined,\n reason: evaluation.reason,\n flagType: evaluation.flagType,\n errorCode: evaluation.errorCode ?? undefined,\n };\n }\n\n /**\n * Resolve an object (JSON) feature flag\n */\n async resolveObject<T extends Record<string, unknown>>(\n flagKey: string,\n defaultValue: T,\n context: EvaluationContext\n ): Promise<ResolutionDetails<T>> {\n const evaluation = await this.getEvaluation(flagKey, context);\n if (!evaluation) {\n return {\n value: defaultValue,\n reason: 'DEFAULT',\n flagType: 'JSON',\n };\n }\n\n return {\n value: evaluation.value as T,\n variant: evaluation.variant ?? undefined,\n reason: evaluation.reason,\n flagType: evaluation.flagType,\n errorCode: evaluation.errorCode ?? undefined,\n };\n }\n\n // =========================================================================\n // Convenience Methods\n // =========================================================================\n\n /**\n * Quick boolean check — returns just the value.\n * Defaults to `true` (default-allow philosophy) if the flag is not found.\n *\n * @example\n * ```typescript\n * if (await provider.isEnabled('calendar.recurring-events', ctx)) {\n * // Feature is enabled\n * }\n * ```\n */\n async isEnabled(flagKey: string, context?: EvaluationContext): Promise<boolean> {\n const result = await this.resolveBoolean(\n flagKey,\n true,\n context ?? { workspaceId: 'system', actorType: 'service', actorId: 'boot' }\n );\n return result.value;\n }\n\n /**\n * Bulk evaluate multiple flags at once.\n * Returns a map of key -> ResolutionDetails.\n */\n async evaluateAll(context: EvaluationContext, keys?: string[]): Promise<Map<string, ResolutionDetails<unknown>>> {\n const evaluations = await this.fetchEvaluations(context, keys);\n const result = new Map<string, ResolutionDetails<unknown>>();\n\n for (const [key, evaluation] of evaluations) {\n result.set(key, {\n value: evaluation.value,\n variant: evaluation.variant ?? undefined,\n reason: evaluation.reason,\n flagType: evaluation.flagType,\n errorCode: evaluation.errorCode ?? undefined,\n });\n }\n\n return result;\n }\n\n /**\n * Invalidate the cache for a specific workspace+actor combination,\n * or all cache entries if no context is provided.\n */\n invalidateCache(context?: EvaluationContext): void {\n if (context) {\n const cacheKey = this.buildCacheKey(context);\n this.cache.delete(cacheKey);\n } else {\n this.cache.clear();\n }\n }\n\n // =========================================================================\n // Private Methods\n // =========================================================================\n\n /**\n * Get a single flag evaluation, fetching from cache or API\n */\n private async getEvaluation(flagKey: string, context: EvaluationContext | undefined): Promise<FlagEvaluation | null> {\n try {\n const evaluations = await this.fetchEvaluations(context);\n return evaluations.get(flagKey) ?? null;\n } catch {\n return null;\n }\n }\n\n /**\n * Fetch flag evaluations — uses cache if available, otherwise calls the API\n */\n private async fetchEvaluations(\n context: EvaluationContext | undefined,\n keys?: string[]\n ): Promise<Map<string, FlagEvaluation>> {\n const cacheKey = this.buildCacheKey(context);\n const cached = this.cache.get(cacheKey);\n\n if (cached && cached.expiresAt > Date.now()) {\n return cached.evaluations;\n }\n\n // Without a workspace context there's nothing the backend can evaluate\n // against — fall through to caller defaults instead of hitting the API.\n if (!context?.workspaceId) {\n return new Map();\n }\n\n // Fetch from API\n try {\n const response = await fetch(this.gatewayUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(context.actorId && { 'x-actor-id': context.actorId }),\n 'x-actor-type': context.actorType ?? this.defaultActorType,\n 'x-workspace-id': context.workspaceId,\n ...(this.serviceToken && { Authorization: `Bearer ${this.serviceToken}` }),\n },\n body: JSON.stringify({\n query: EVALUATE_QUERY,\n variables: {\n input: {\n workspaceId: context.workspaceId,\n keys: keys ?? null,\n },\n },\n }),\n });\n\n if (!response.ok) {\n // Return empty map on error — default-allow handled by callers\n return new Map();\n }\n\n const json = (await response.json()) as {\n data?: {\n evaluateFeatureFlags?: {\n evaluations?: FlagEvaluation[];\n };\n };\n errors?: Array<{ message: string }>;\n };\n\n if (json.errors || !json.data?.evaluateFeatureFlags?.evaluations) {\n return new Map();\n }\n\n const evaluations = new Map<string, FlagEvaluation>();\n for (const evaluation of json.data.evaluateFeatureFlags.evaluations) {\n evaluations.set(evaluation.key, evaluation);\n }\n\n // Cache the results\n this.cache.set(cacheKey, {\n evaluations,\n expiresAt: Date.now() + this.cacheTtlMs,\n });\n\n return evaluations;\n } catch {\n // On network error, return empty map (default-allow handled by callers)\n return new Map();\n }\n }\n\n /**\n * Build a cache key from the evaluation context.\n *\n * Safe against undefined/null context — callers like isEnabled() may\n * pass no context during service boot (e.g. checking a default-allow\n * feature flag at startup before the first request builds a real\n * actor context). Without the guard, isEnabled(flag) used to throw\n * \"undefined is not an object (evaluating 'context.workspaceId')\"\n * and crash the svc at startServer().\n */\n private buildCacheKey(context: EvaluationContext | undefined): string {\n const ctx = context ?? ({} as EvaluationContext);\n return `${ctx.workspaceId ?? 'system'}:${ctx.actorId ?? 'anonymous'}:${ctx.actorType ?? 'user'}`;\n }\n}\n"]}
|