@burdenoff/be-sdk 2026.528.1 → 2026.529.1

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.
@@ -87,6 +87,8 @@ var ResourceCapExceededError = class extends GraphQLError {
87
87
  super(`Resource cap exceeded for ${detail.resource}: ${detail.currentCount}/${detail.limit} on plan '${detail.planName}'.`, {
88
88
  extensions: {
89
89
  code: "RESOURCE_CAP_EXCEEDED",
90
+ userMessage: "You've reached your plan limit. Upgrade your plan to continue.",
91
+ technicalMessage: `Resource cap exceeded for ${detail.resource}: ${detail.currentCount}/${detail.limit} on plan '${detail.planName}'.`,
90
92
  resource: detail.resource,
91
93
  currentCount: detail.currentCount,
92
94
  limit: detail.limit,
@@ -261,5 +263,5 @@ function __resetResourceCapsForTests() {
261
263
  __name(__resetResourceCapsForTests, "__resetResourceCapsForTests");
262
264
 
263
265
  export { RESOURCE_CAP_DEFAULTS, ResourceCapExceededError, ResourceCapsModule, __resetResourceCapsForTests, getResourceCaps };
264
- //# sourceMappingURL=chunk-7BMKOD7N.js.map
265
- //# sourceMappingURL=chunk-7BMKOD7N.js.map
266
+ //# sourceMappingURL=chunk-VDDOGIWY.js.map
267
+ //# sourceMappingURL=chunk-VDDOGIWY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/resource-caps/index.ts"],"names":["RESOURCE_CAP_DEFAULTS","workspace","free","paidDefault","scope","workspaceMember","sandbox","vibe","agent","conversation","message","tag","file","group","calendar","scheduledTask","form","apiKey","webhook","ResourceCapExceededError","GraphQLError","detail","resource","currentCount","limit","planName","extensions","code","userMessage","technicalMessage","name","PLAN_QUERY","ResourceCapsModule","client","cacheTtlMs","defaultTier","planCache","Map","config","getResourceCap","userId","Error","defaults","plan","resolvePlan","override","overrides","planTier","isFallback","enforceResourceCap","cap","invalidate","delete","invalidateAll","clear","cached","get","Date","now","fetchedAt","fetchPlan","set","fallbackPlan","res","query","planNode","userBillingPlan","tier","normaliseTier","sanitizeOverrides","resourceCaps","err","logger","warn","raw","fallback","lc","toLowerCase","out","key","value","Object","entries","Number","isFinite","Math","max","floor","defaultInstance","getResourceCaps","__resetResourceCapsForTests"],"mappings":"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","file":"chunk-VDDOGIWY.js","sourcesContent":["/**\n * Resource Caps Module\n *\n * Enforces per-user / per-workspace resource ceilings to prevent unbounded\n * resource use on a multi-tenant SaaS. Caps come from the user's billing plan\n * (resolved via global-billing-svc) and fall back to static defaults defined\n * here when the plan lookup fails or the resource isn't overridden.\n *\n * Two plan tiers exist by default:\n * - free: tight per-user / per-workspace ceilings\n * - paid (default): ~100x looser; individual paid plans can override via the\n * `Plan.resourceCaps` JSON column in global-billing-svc.\n *\n * @example\n * ```ts\n * import { resourceCaps } from '@burdenoff/be-sdk/resource-caps';\n *\n * const { limit } = await resourceCaps.getResourceCap(userId, 'tag');\n * await resourceCaps.enforceResourceCap(userId, 'tag', currentCount);\n * ```\n */\n\nimport { GraphQLError } from 'graphql';\n\nimport type { BurdenoffGraphQLClient } from '../client/graphql.js';\nimport { logger } from '../logger/index.js';\n\n// ---------------------------------------------------------------------------\n// Resource registry\n// ---------------------------------------------------------------------------\n\n/**\n * Canonical resource names. Adding a new capped resource? Append it here,\n * extend RESOURCE_CAP_DEFAULTS, and reference it from the relevant svc.\n */\nexport type ResourceName =\n | 'workspace' // per user\n | 'workspaceMember' // per workspace\n | 'sandbox' // per user\n | 'vibe' // per user\n | 'agent' // per workspace\n | 'conversation' // per user/day\n | 'message' // per user/day\n | 'tag' // per workspace\n | 'file' // per user (count only)\n | 'group' // per workspace\n | 'calendar' // per user\n | 'scheduledTask' // per user\n | 'form' // per user\n | 'apiKey' // per user\n | 'webhook'; // per workspace\n\nexport type PlanTier = 'free' | 'paid';\n\nexport interface ResourceCapDefaults {\n free: number;\n paidDefault: number;\n /** Optional scope hint used by callers when wiring guards */\n scope: 'user' | 'workspace' | 'user/day';\n}\n\n/**\n * Static fallback caps. Real values may be overridden per plan in\n * global-billing-svc Plan.resourceCaps JSON.\n */\nexport const RESOURCE_CAP_DEFAULTS: Record<ResourceName, ResourceCapDefaults> = {\n workspace: { free: 3, paidDefault: 50, scope: 'user' },\n workspaceMember: { free: 5, paidDefault: 500, scope: 'workspace' },\n sandbox: { free: 2, paidDefault: 50, scope: 'user' },\n vibe: { free: 10, paidDefault: 500, scope: 'user' },\n agent: { free: 2, paidDefault: 20, scope: 'workspace' },\n conversation: { free: 20, paidDefault: 2000, scope: 'user/day' },\n message: { free: 200, paidDefault: 20000, scope: 'user/day' },\n tag: { free: 50, paidDefault: 5000, scope: 'workspace' },\n file: { free: 100, paidDefault: 10000, scope: 'user' },\n group: { free: 5, paidDefault: 500, scope: 'workspace' },\n calendar: { free: 3, paidDefault: 100, scope: 'user' },\n scheduledTask: { free: 5, paidDefault: 500, scope: 'user' },\n form: { free: 5, paidDefault: 500, scope: 'user' },\n apiKey: { free: 2, paidDefault: 50, scope: 'user' },\n webhook: { free: 2, paidDefault: 100, scope: 'workspace' },\n};\n\n// ---------------------------------------------------------------------------\n// Types & errors\n// ---------------------------------------------------------------------------\n\nexport interface ResourceCap {\n resource: ResourceName;\n limit: number;\n planName: string;\n planTier: PlanTier;\n /** True when the cap came from defaults, not from the billing service */\n isFallback: boolean;\n}\n\nexport interface ResourceCapsConfig {\n client?: BurdenoffGraphQLClient;\n /** Optional in-memory TTL for plan lookups; defaults to 60s */\n cacheTtlMs?: number;\n /** Default plan tier when billing lookup fails */\n defaultTier?: PlanTier;\n}\n\ninterface CachedPlan {\n planName: string;\n planTier: PlanTier;\n overrides: Partial<Record<ResourceName, number>>;\n fetchedAt: number;\n}\n\n/**\n * Thrown when an entity create operation would push the user past the cap.\n * The matching GraphQL error has extensions.code = RESOURCE_CAP_EXCEEDED\n * so clients can detect and route to an upgrade flow.\n */\nexport class ResourceCapExceededError extends GraphQLError {\n constructor(detail: { resource: ResourceName; currentCount: number; limit: number; planName: string }) {\n super(\n `Resource cap exceeded for ${detail.resource}: ${detail.currentCount}/${detail.limit} on plan '${detail.planName}'.`,\n {\n extensions: {\n code: 'RESOURCE_CAP_EXCEEDED',\n userMessage: \"You've reached your plan limit. Upgrade your plan to continue.\",\n technicalMessage: `Resource cap exceeded for ${detail.resource}: ${detail.currentCount}/${detail.limit} on plan '${detail.planName}'.`,\n resource: detail.resource,\n currentCount: detail.currentCount,\n limit: detail.limit,\n planName: detail.planName,\n },\n }\n );\n this.name = 'ResourceCapExceededError';\n }\n}\n\n// ---------------------------------------------------------------------------\n// Module\n// ---------------------------------------------------------------------------\n\nconst PLAN_QUERY = /* GraphQL */ `\n query GetUserPlanForCaps($userId: ID!) {\n userBillingPlan(userId: $userId) {\n planName\n tier\n resourceCaps\n }\n }\n`;\n\ninterface PlanQueryResponse {\n userBillingPlan?: {\n planName?: string | null;\n tier?: string | null;\n resourceCaps?: Record<string, number> | null;\n } | null;\n}\n\nexport class ResourceCapsModule {\n private readonly client?: BurdenoffGraphQLClient;\n private readonly cacheTtlMs: number;\n private readonly defaultTier: PlanTier;\n private readonly planCache = new Map<string, CachedPlan>();\n\n constructor(config: ResourceCapsConfig = {}) {\n this.client = config.client;\n this.cacheTtlMs = config.cacheTtlMs ?? 60_000;\n this.defaultTier = config.defaultTier ?? 'free';\n }\n\n /**\n * Resolve the cap for a given resource for the supplied user.\n * Order: plan-override → plan-tier-default → static free default.\n */\n async getResourceCap(userId: string, resource: ResourceName): Promise<ResourceCap> {\n if (!userId) {\n throw new Error('getResourceCap: userId is required');\n }\n const defaults = RESOURCE_CAP_DEFAULTS[resource];\n if (!defaults) {\n throw new Error(`getResourceCap: unknown resource '${resource}'`);\n }\n\n const plan = await this.resolvePlan(userId);\n const override = plan.overrides[resource];\n if (typeof override === 'number' && override >= 0) {\n return {\n resource,\n limit: override,\n planName: plan.planName,\n planTier: plan.planTier,\n isFallback: false,\n };\n }\n\n const limit = plan.planTier === 'paid' ? defaults.paidDefault : defaults.free;\n return {\n resource,\n limit,\n planName: plan.planName,\n planTier: plan.planTier,\n isFallback: plan.planName === 'fallback-free' || plan.planName === 'fallback-paid',\n };\n }\n\n /**\n * Throws ResourceCapExceededError when currentCount >= limit.\n * Use BEFORE the create transaction to avoid orphan rows.\n */\n async enforceResourceCap(userId: string, resource: ResourceName, currentCount: number): Promise<ResourceCap> {\n const cap = await this.getResourceCap(userId, resource);\n if (currentCount >= cap.limit) {\n throw new ResourceCapExceededError({\n resource,\n currentCount,\n limit: cap.limit,\n planName: cap.planName,\n });\n }\n return cap;\n }\n\n /** Invalidate cache for a user (e.g. after plan upgrade) */\n invalidate(userId: string): void {\n this.planCache.delete(userId);\n }\n\n /** Clear entire plan cache */\n invalidateAll(): void {\n this.planCache.clear();\n }\n\n // -------------------------------------------------------------------------\n // Internals\n // -------------------------------------------------------------------------\n\n private async resolvePlan(userId: string): Promise<CachedPlan> {\n const cached = this.planCache.get(userId);\n if (cached && Date.now() - cached.fetchedAt < this.cacheTtlMs) {\n return cached;\n }\n\n const plan = await this.fetchPlan(userId);\n this.planCache.set(userId, plan);\n return plan;\n }\n\n private async fetchPlan(userId: string): Promise<CachedPlan> {\n if (!this.client) {\n return this.fallbackPlan();\n }\n try {\n const res = await this.client.query<PlanQueryResponse>(PLAN_QUERY, { userId });\n const planNode = res?.userBillingPlan;\n if (!planNode) {\n return this.fallbackPlan();\n }\n const tier = normaliseTier(planNode.tier, this.defaultTier);\n const overrides = sanitizeOverrides(planNode.resourceCaps);\n return {\n planName: planNode.planName ?? (tier === 'paid' ? 'paid' : 'free'),\n planTier: tier,\n overrides,\n fetchedAt: Date.now(),\n };\n } catch (err) {\n logger.warn('resource-caps: plan lookup failed; falling back to default tier', {\n err,\n userId,\n });\n return this.fallbackPlan();\n }\n }\n\n private fallbackPlan(): CachedPlan {\n return {\n planName: this.defaultTier === 'paid' ? 'fallback-paid' : 'fallback-free',\n planTier: this.defaultTier,\n overrides: {},\n fetchedAt: Date.now(),\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction normaliseTier(raw: string | null | undefined, fallback: PlanTier): PlanTier {\n if (!raw) return fallback;\n const lc = raw.toLowerCase();\n if (lc === 'free' || lc === 'paid') return lc;\n // Treat anything non-free as paid (enterprise, business, pro, etc.)\n if (lc === 'enterprise' || lc === 'business' || lc === 'pro' || lc === 'team') {\n return 'paid';\n }\n return fallback;\n}\n\nfunction sanitizeOverrides(raw: Record<string, number> | null | undefined): Partial<Record<ResourceName, number>> {\n if (!raw || typeof raw !== 'object') return {};\n const out: Partial<Record<ResourceName, number>> = {};\n for (const [key, value] of Object.entries(raw)) {\n if (key in RESOURCE_CAP_DEFAULTS && typeof value === 'number' && Number.isFinite(value)) {\n out[key as ResourceName] = Math.max(0, Math.floor(value));\n }\n }\n return out;\n}\n\n// ---------------------------------------------------------------------------\n// Convenience singleton\n// ---------------------------------------------------------------------------\n\nlet defaultInstance: ResourceCapsModule | null = null;\n\n/**\n * Obtain a process-wide singleton. The first call binds the GraphQL client\n * (typically the SDK's internal client). Later calls return the same instance.\n */\nexport function getResourceCaps(config?: ResourceCapsConfig): ResourceCapsModule {\n if (!defaultInstance) {\n defaultInstance = new ResourceCapsModule(config);\n }\n return defaultInstance;\n}\n\n/** Reset the singleton (for tests) */\nexport function __resetResourceCapsForTests(): void {\n defaultInstance = null;\n}\n"]}
@@ -0,0 +1,506 @@
1
+ import { logger } from './chunk-CBZNP6RF.js';
2
+ import { __name } from './chunk-XE4OXN2W.js';
3
+ import { GraphQLError } from 'graphql';
4
+
5
+ var DEFAULT_ERROR_CODE = "INTERNAL_SERVER_ERROR";
6
+ var DEFAULT_HTTP_STATUS = 500;
7
+ var DEFAULT_USER_MESSAGES = {
8
+ BAD_REQUEST: "Please check the request and try again.",
9
+ BAD_USER_INPUT: "Please check the information and try again.",
10
+ CAPTCHA_FAILED: "The verification failed. Please try again.",
11
+ CAPTCHA_REQUIRED: "Please complete the verification to continue.",
12
+ COMPLEXITY_TOO_HIGH: "This request is too large to process. Please simplify it and try again.",
13
+ CONFLICT: "This change conflicts with existing data. Please refresh and try again.",
14
+ FORBIDDEN: "You do not have permission to do that.",
15
+ INTERNAL_SERVER_ERROR: "Something went wrong. Please try again.",
16
+ INVALID_PASSWORD: "The password you entered is incorrect.",
17
+ NOT_FOUND: "We could not find the requested item.",
18
+ PERMISSION_DENIED: "You do not have permission to do that.",
19
+ QUOTA_EXCEEDED: "You've reached your usage limit.",
20
+ QUOTA_EXHAUSTED: "You've reached your usage limit.",
21
+ RATE_LIMITED: "Too many requests. Please wait a moment and try again.",
22
+ RESOURCE_CAP_EXCEEDED: "You've reached your plan limit. Upgrade your plan to continue.",
23
+ STORAGE_QUOTA_EXCEEDED: "You've reached your storage limit. Upgrade your plan to continue.",
24
+ TIMEOUT: "The request took too long. Please try again.",
25
+ UNAUTHENTICATED: "Please sign in to continue.",
26
+ VALIDATION_ERROR: "Please check the information and try again.",
27
+ WORKSPACE_REQUIRED: "Please choose a workspace and try again."
28
+ };
29
+ var DEFAULT_STATUS_BY_CODE = {
30
+ BAD_REQUEST: 400,
31
+ BAD_USER_INPUT: 400,
32
+ CAPTCHA_FAILED: 400,
33
+ CAPTCHA_REQUIRED: 400,
34
+ COMPLEXITY_TOO_HIGH: 400,
35
+ CONFLICT: 409,
36
+ FORBIDDEN: 403,
37
+ INVALID_PASSWORD: 400,
38
+ NOT_FOUND: 404,
39
+ PERMISSION_DENIED: 403,
40
+ QUOTA_EXCEEDED: 429,
41
+ QUOTA_EXHAUSTED: 429,
42
+ RATE_LIMITED: 429,
43
+ RESOURCE_CAP_EXCEEDED: 429,
44
+ STORAGE_QUOTA_EXCEEDED: 429,
45
+ TIMEOUT: 504,
46
+ UNAUTHENTICATED: 401,
47
+ VALIDATION_ERROR: 400,
48
+ WORKSPACE_REQUIRED: 403
49
+ };
50
+ var SENSITIVE_EXTENSION_KEYS = /* @__PURE__ */ new Set([
51
+ "exception",
52
+ "originalError",
53
+ "stack",
54
+ "stacktrace"
55
+ ]);
56
+ var TECHNICAL_MESSAGE_PATTERNS = [
57
+ /\bprisma\b/i,
58
+ /\bsql\b/i,
59
+ /\bconstraint\b/i,
60
+ /\bstack\b/i,
61
+ /\bundefined\b/i,
62
+ /\bnull\b/i,
63
+ /\bECONN[A-Z_]*\b/,
64
+ /\bETIMEDOUT\b/,
65
+ /\bENOTFOUND\b/,
66
+ /\bP\d{4}\b/,
67
+ /Cannot (read|set|find|access)/i,
68
+ /Build failed \(exit \d+\)/i,
69
+ /JSON\.stringify/i
70
+ ];
71
+ var GRAPHQL_VALIDATION_MESSAGE_PATTERNS = [
72
+ /^Syntax Error:/i,
73
+ /^Cannot query field\b/i,
74
+ /^Cannot return null for non-nullable field\b/i,
75
+ /^Unknown (argument|directive|type)\b/i,
76
+ /^Variable "\$[^\"]+" got invalid value/i,
77
+ /^Field "[^"]+" of required type/i,
78
+ /^Field "[^"]+" must not have a selection/i,
79
+ /^Field "[^"]+" must have a selection/i,
80
+ /^Fragment "[^"]+" cannot be spread here/i,
81
+ /^There can be only one/i,
82
+ /Did you mean /i
83
+ ];
84
+ function isRecord(value) {
85
+ return typeof value === "object" && value !== null && !Array.isArray(value);
86
+ }
87
+ __name(isRecord, "isRecord");
88
+ function readString(record, key) {
89
+ const value = record[key];
90
+ return typeof value === "string" && value.trim().length > 0 ? value : null;
91
+ }
92
+ __name(readString, "readString");
93
+ function readExtensions(error) {
94
+ if (!isRecord(error)) return {};
95
+ const extensions = error["extensions"];
96
+ return isRecord(extensions) ? extensions : {};
97
+ }
98
+ __name(readExtensions, "readExtensions");
99
+ function readOriginalError(error) {
100
+ if (error instanceof GraphQLError && error.originalError instanceof Error) {
101
+ return error.originalError;
102
+ }
103
+ return void 0;
104
+ }
105
+ __name(readOriginalError, "readOriginalError");
106
+ function sanitizeExtensions(extensions) {
107
+ const sanitized = {};
108
+ for (const [key, value] of Object.entries(extensions)) {
109
+ if (!SENSITIVE_EXTENSION_KEYS.has(key)) {
110
+ sanitized[key] = value;
111
+ }
112
+ }
113
+ return sanitized;
114
+ }
115
+ __name(sanitizeExtensions, "sanitizeExtensions");
116
+ function normalizeCode(code) {
117
+ if (!code) return DEFAULT_ERROR_CODE;
118
+ return code.trim().replace(/[\s-]+/g, "_").toUpperCase();
119
+ }
120
+ __name(normalizeCode, "normalizeCode");
121
+ function inferCodeFromMessage(message) {
122
+ const lower = message.toLowerCase();
123
+ if (GRAPHQL_VALIDATION_MESSAGE_PATTERNS.some((pattern) => pattern.test(message))) {
124
+ return "BAD_USER_INPUT";
125
+ }
126
+ if (lower.includes("unauthenticated") || lower.includes("not authenticated") || lower.includes("authentication required") || lower.includes("unauthorized") || lower.includes("please sign in") || lower.includes("sign in to")) {
127
+ return "UNAUTHENTICATED";
128
+ }
129
+ if (lower.includes("workspace context") || lower.includes("switch to a workspace")) {
130
+ return "WORKSPACE_REQUIRED";
131
+ }
132
+ if (lower.includes("forbidden") || lower.includes("access denied") || lower.includes("permission denied") || lower.includes("not authorized")) {
133
+ return "FORBIDDEN";
134
+ }
135
+ if (lower.includes("not found") || lower.includes("disappeared")) {
136
+ return "NOT_FOUND";
137
+ }
138
+ if (lower.includes("already exists") || lower.includes("already taken") || lower.includes("conflict")) {
139
+ return "CONFLICT";
140
+ }
141
+ if (lower.includes("quota") || lower.includes("cap exceeded") || lower.includes("limit reached")) {
142
+ return "QUOTA_EXCEEDED";
143
+ }
144
+ if (lower.includes("timeout") || lower.includes("timed out")) {
145
+ return "TIMEOUT";
146
+ }
147
+ if (lower.includes("required") || lower.includes("invalid") || lower.includes("missing") || lower.includes("unsupported")) {
148
+ return "BAD_USER_INPUT";
149
+ }
150
+ return DEFAULT_ERROR_CODE;
151
+ }
152
+ __name(inferCodeFromMessage, "inferCodeFromMessage");
153
+ function inferCode(error, extensions, originalError) {
154
+ const extensionCode = typeof extensions["code"] === "string" ? extensions["code"] : null;
155
+ if (extensionCode) return normalizeCode(extensionCode);
156
+ if (isRecord(originalError)) {
157
+ const originalCode = readString(originalError, "code");
158
+ if (originalCode) return normalizeCode(originalCode);
159
+ }
160
+ if (isRecord(error)) {
161
+ const directCode = readString(error, "code");
162
+ if (directCode) return normalizeCode(directCode);
163
+ }
164
+ const message = readErrorMessage(error);
165
+ return inferCodeFromMessage(message);
166
+ }
167
+ __name(inferCode, "inferCode");
168
+ function readErrorMessage(error) {
169
+ if (error instanceof Error && error.message.trim().length > 0) {
170
+ return error.message;
171
+ }
172
+ if (isRecord(error)) {
173
+ const message = readString(error, "message");
174
+ if (message) return message;
175
+ }
176
+ return DEFAULT_USER_MESSAGES[DEFAULT_ERROR_CODE];
177
+ }
178
+ __name(readErrorMessage, "readErrorMessage");
179
+ function isLikelyTechnicalMessage(message) {
180
+ if (message.length > 220) return true;
181
+ if (message.includes("\n")) return true;
182
+ if (message.includes("{") && message.includes("}")) return true;
183
+ if (GRAPHQL_VALIDATION_MESSAGE_PATTERNS.some((pattern) => pattern.test(message))) return true;
184
+ return TECHNICAL_MESSAGE_PATTERNS.some((pattern) => pattern.test(message));
185
+ }
186
+ __name(isLikelyTechnicalMessage, "isLikelyTechnicalMessage");
187
+ function readExplicitUserMessage(extensions) {
188
+ return readString(extensions, "userMessage") ?? readString(extensions, "humanMessage") ?? readString(extensions, "displayMessage");
189
+ }
190
+ __name(readExplicitUserMessage, "readExplicitUserMessage");
191
+ function resolveUserMessage(code, message, extensions) {
192
+ const explicit = readExplicitUserMessage(extensions);
193
+ if (explicit) return explicit;
194
+ const fallback = DEFAULT_USER_MESSAGES[code] ?? DEFAULT_USER_MESSAGES[DEFAULT_ERROR_CODE] ?? "Something went wrong. Please try again.";
195
+ if (code === DEFAULT_ERROR_CODE) {
196
+ return fallback;
197
+ }
198
+ if (!isLikelyTechnicalMessage(message)) {
199
+ return message;
200
+ }
201
+ return fallback;
202
+ }
203
+ __name(resolveUserMessage, "resolveUserMessage");
204
+ function resolveTechnicalMessage(code, message, extensions, options) {
205
+ const explicit = readString(extensions, "technicalMessage");
206
+ if (explicit) return explicit;
207
+ if (options.environment === "production" && code === DEFAULT_ERROR_CODE) {
208
+ return options.serviceName ? `${options.serviceName} returned ${DEFAULT_ERROR_CODE}` : DEFAULT_ERROR_CODE;
209
+ }
210
+ return message;
211
+ }
212
+ __name(resolveTechnicalMessage, "resolveTechnicalMessage");
213
+ function readHttpStatus(extensions, code) {
214
+ const http = extensions["http"];
215
+ if (isRecord(http) && typeof http["status"] === "number") {
216
+ return http["status"];
217
+ }
218
+ return DEFAULT_STATUS_BY_CODE[code] ?? DEFAULT_HTTP_STATUS;
219
+ }
220
+ __name(readHttpStatus, "readHttpStatus");
221
+ function normalizeError(error, options = {}) {
222
+ const originalError = readOriginalError(error);
223
+ const originalExtensions = readExtensions(originalError);
224
+ const directExtensions = readExtensions(error);
225
+ const mergedExtensions = sanitizeExtensions({
226
+ ...originalExtensions,
227
+ ...directExtensions
228
+ });
229
+ const message = readErrorMessage(error);
230
+ const code = inferCode(error, mergedExtensions, originalError);
231
+ const userMessage = resolveUserMessage(code, message, mergedExtensions);
232
+ const technicalMessage = resolveTechnicalMessage(code, message, mergedExtensions, options);
233
+ const status = readHttpStatus(mergedExtensions, code);
234
+ const extensions = {
235
+ ...mergedExtensions,
236
+ code,
237
+ userMessage,
238
+ technicalMessage
239
+ };
240
+ if (options.serviceName) {
241
+ extensions["serviceName"] = options.serviceName;
242
+ }
243
+ if (!isRecord(extensions["http"])) {
244
+ extensions["http"] = {
245
+ status
246
+ };
247
+ }
248
+ return {
249
+ code,
250
+ userMessage,
251
+ technicalMessage,
252
+ extensions,
253
+ status
254
+ };
255
+ }
256
+ __name(normalizeError, "normalizeError");
257
+ var UserFacingGraphQLError = class extends GraphQLError {
258
+ static {
259
+ __name(this, "UserFacingGraphQLError");
260
+ }
261
+ constructor(options) {
262
+ const extensions = {
263
+ code: normalizeCode(options.code),
264
+ userMessage: options.userMessage,
265
+ technicalMessage: options.technicalMessage ?? options.userMessage
266
+ };
267
+ if (options.details) {
268
+ extensions["details"] = options.details;
269
+ }
270
+ if (options.httpStatus) {
271
+ extensions["http"] = {
272
+ status: options.httpStatus
273
+ };
274
+ }
275
+ super(options.userMessage, {
276
+ originalError: options.cause,
277
+ extensions
278
+ });
279
+ this.name = "UserFacingGraphQLError";
280
+ }
281
+ };
282
+ function toUserFacingGraphQLError(error, options = {}) {
283
+ const normalized = normalizeError(error, options);
284
+ if (error instanceof GraphQLError) {
285
+ return new GraphQLError(normalized.userMessage, {
286
+ nodes: error.nodes,
287
+ source: error.source,
288
+ positions: error.positions,
289
+ path: error.path,
290
+ originalError: error.originalError,
291
+ extensions: normalized.extensions
292
+ });
293
+ }
294
+ return new GraphQLError(normalized.userMessage, {
295
+ originalError: error instanceof Error ? error : void 0,
296
+ extensions: normalized.extensions
297
+ });
298
+ }
299
+ __name(toUserFacingGraphQLError, "toUserFacingGraphQLError");
300
+ function normalizeExecutionResultErrors(result, options = {}) {
301
+ if (Array.isArray(result)) {
302
+ result.forEach((entry) => normalizeExecutionResultErrors(entry, options));
303
+ return;
304
+ }
305
+ if (!isRecord(result)) return;
306
+ const errors = result["errors"];
307
+ if (!Array.isArray(errors) || errors.length === 0) return;
308
+ result["errors"] = errors.map((error) => toUserFacingGraphQLError(error, options));
309
+ }
310
+ __name(normalizeExecutionResultErrors, "normalizeExecutionResultErrors");
311
+ function normalizeGraphQLResponsePayload(payload, options = {}) {
312
+ normalizeExecutionResultErrors(payload, options);
313
+ return payload;
314
+ }
315
+ __name(normalizeGraphQLResponsePayload, "normalizeGraphQLResponsePayload");
316
+ async function normalizeGraphQLResponse(response, options = {}) {
317
+ const contentType = response.headers.get("content-type") ?? "";
318
+ if (!contentType.toLowerCase().includes("json")) {
319
+ return response;
320
+ }
321
+ const text = await response.text();
322
+ if (!text) {
323
+ return new Response(text, {
324
+ status: response.status,
325
+ statusText: response.statusText,
326
+ headers: response.headers
327
+ });
328
+ }
329
+ let payload;
330
+ try {
331
+ payload = JSON.parse(text);
332
+ } catch {
333
+ return new Response(text, {
334
+ status: response.status,
335
+ statusText: response.statusText,
336
+ headers: response.headers
337
+ });
338
+ }
339
+ normalizeGraphQLResponsePayload(payload, options);
340
+ return new Response(JSON.stringify(payload), {
341
+ status: response.status,
342
+ statusText: response.statusText,
343
+ headers: response.headers
344
+ });
345
+ }
346
+ __name(normalizeGraphQLResponse, "normalizeGraphQLResponse");
347
+ function createHumanReadableErrorPlugin(options = {}) {
348
+ return {
349
+ onExecute() {
350
+ return {
351
+ onExecuteDone({ result }) {
352
+ normalizeExecutionResultErrors(result, options);
353
+ }
354
+ };
355
+ }
356
+ };
357
+ }
358
+ __name(createHumanReadableErrorPlugin, "createHumanReadableErrorPlugin");
359
+ function formatHttpError(error, options = {}) {
360
+ const normalized = normalizeError(error, options);
361
+ return {
362
+ status: normalized.status,
363
+ body: {
364
+ error: normalized.userMessage,
365
+ message: normalized.userMessage,
366
+ userMessage: normalized.userMessage,
367
+ technicalMessage: normalized.technicalMessage,
368
+ code: normalized.code,
369
+ extensions: normalized.extensions
370
+ }
371
+ };
372
+ }
373
+ __name(formatHttpError, "formatHttpError");
374
+ function createHttpErrorResponse(error, options = {}) {
375
+ const formatted = formatHttpError(error, options);
376
+ return new Response(JSON.stringify(formatted.body), {
377
+ status: formatted.status,
378
+ headers: {
379
+ "Content-Type": "application/json"
380
+ }
381
+ });
382
+ }
383
+ __name(createHttpErrorResponse, "createHttpErrorResponse");
384
+
385
+ // src/server/workspace-guard.ts
386
+ var WorkspaceRequiredError = class WorkspaceRequiredError2 extends UserFacingGraphQLError {
387
+ static {
388
+ __name(this, "WorkspaceRequiredError");
389
+ }
390
+ constructor(message) {
391
+ super({
392
+ code: "WORKSPACE_REQUIRED",
393
+ userMessage: "Please choose a workspace and try again.",
394
+ technicalMessage: message,
395
+ httpStatus: 403
396
+ });
397
+ this.name = "WorkspaceRequiredError";
398
+ }
399
+ };
400
+ function isFieldPublic(field) {
401
+ const directives = field?.astNode?.directives;
402
+ if (!directives || directives.length === 0) return false;
403
+ for (const dir of directives) {
404
+ const name = dir.name?.value;
405
+ if (name === "public") return true;
406
+ if (name === "rbac") {
407
+ const args = dir.arguments ?? [];
408
+ for (const arg of args) {
409
+ if (arg.name?.value === "public" && arg.value?.kind === "BooleanValue" && arg.value.value === true) {
410
+ return true;
411
+ }
412
+ }
413
+ }
414
+ }
415
+ return false;
416
+ }
417
+ __name(isFieldPublic, "isFieldPublic");
418
+ function resolveOperationType(document, operationName) {
419
+ const defs = document?.definitions ?? [];
420
+ for (const def of defs) {
421
+ if (def.kind !== "OperationDefinition") ;
422
+ if (def.operation && (!operationName || def.name?.value === operationName)) {
423
+ return def.operation;
424
+ }
425
+ }
426
+ return "query";
427
+ }
428
+ __name(resolveOperationType, "resolveOperationType");
429
+ function allRootFieldsPublic(schema, document, operationName) {
430
+ if (!schema || !document) return false;
431
+ const opType = resolveOperationType(document, operationName);
432
+ let rootType;
433
+ if (opType === "mutation") {
434
+ rootType = schema.getMutationType?.();
435
+ } else if (opType === "subscription") {
436
+ rootType = schema.getSubscriptionType?.();
437
+ } else {
438
+ rootType = schema.getQueryType?.();
439
+ }
440
+ if (!rootType) return false;
441
+ const fields = rootType.getFields();
442
+ const defs = document.definitions ?? [];
443
+ const opDef = defs.find((d) => d.operation && (!operationName || d.name?.value === operationName));
444
+ const selections = opDef?.selectionSet?.selections ?? [];
445
+ if (selections.length === 0) return false;
446
+ for (const sel of selections) {
447
+ if (sel.kind && sel.kind !== "Field") return false;
448
+ const fieldName = sel.name?.value;
449
+ if (!fieldName) return false;
450
+ if (fieldName.startsWith("__")) continue;
451
+ const fieldDef = fields[fieldName];
452
+ if (!isFieldPublic(fieldDef)) return false;
453
+ }
454
+ return true;
455
+ }
456
+ __name(allRootFieldsPublic, "allRootFieldsPublic");
457
+ function createWorkspaceGuardPlugin(config) {
458
+ const enabled = config?.enabled ?? true;
459
+ const whitelistedOperations = new Set(config?.whitelistedOperations ?? []);
460
+ const allowPublicDirective = config?.allowPublicDirective ?? true;
461
+ const errorMessage = config?.errorMessage ?? "Workspace context required. Ensure the request includes a valid workspace context (x-workspace-id header).";
462
+ if (!enabled) {
463
+ logger.info("[WorkspaceGuard] Workspace guard disabled");
464
+ return {};
465
+ }
466
+ logger.info("[WorkspaceGuard] Workspace guard enabled", {
467
+ whitelistedOperations: [
468
+ ...whitelistedOperations
469
+ ],
470
+ allowPublicDirective
471
+ });
472
+ return {
473
+ onExecute({ args }) {
474
+ let operationName = args.operationName;
475
+ if (!operationName && args.document?.definitions) {
476
+ for (const def of args.document.definitions) {
477
+ if (def.operation && def.name?.value) {
478
+ operationName = def.name.value;
479
+ break;
480
+ }
481
+ }
482
+ }
483
+ if (operationName && whitelistedOperations.has(operationName)) {
484
+ return void 0;
485
+ }
486
+ if (allowPublicDirective && allRootFieldsPublic(args.schema, args.document, operationName)) {
487
+ return void 0;
488
+ }
489
+ const context = args.contextValue;
490
+ const workspaceId = context?.workspace?.id;
491
+ const headerWorkspaceId = context?.request?.headers?.get?.("x-workspace-id");
492
+ if (!workspaceId && !headerWorkspaceId) {
493
+ logger.warn("[WorkspaceGuard] Blocked operation without workspace context", {
494
+ operationName: operationName ?? "<anonymous>"
495
+ });
496
+ throw new WorkspaceRequiredError(errorMessage);
497
+ }
498
+ return void 0;
499
+ }
500
+ };
501
+ }
502
+ __name(createWorkspaceGuardPlugin, "createWorkspaceGuardPlugin");
503
+
504
+ export { UserFacingGraphQLError, createHttpErrorResponse, createHumanReadableErrorPlugin, createWorkspaceGuardPlugin, formatHttpError, normalizeError, normalizeExecutionResultErrors, normalizeGraphQLResponse, normalizeGraphQLResponsePayload, toUserFacingGraphQLError };
505
+ //# sourceMappingURL=chunk-VWNL2O3G.js.map
506
+ //# sourceMappingURL=chunk-VWNL2O3G.js.map