@burdenoff/be-sdk 2026.525.1 → 2026.527.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.
package/dist/index.js CHANGED
@@ -42,7 +42,7 @@ export { DEFAULT_CIRCUIT_BREAKER_CONFIG, DEFAULT_GATEWAY_RETRY_CONFIG, DEFAULT_G
42
42
  import { logger } from './chunk-CBZNP6RF.js';
43
43
  export { logger_exports as Logger } from './chunk-CBZNP6RF.js';
44
44
  export { dataloader_exports as DataLoader } from './chunk-NUB5GQTU.js';
45
- import { __name } from './chunk-XE4OXN2W.js';
45
+ import { __name, __export } from './chunk-XE4OXN2W.js';
46
46
  import { GraphQLClient } from 'graphql-request';
47
47
  import { AsyncLocalStorage } from 'async_hooks';
48
48
 
@@ -2192,6 +2192,475 @@ function createTenantBackendClient(config) {
2192
2192
  }
2193
2193
  __name(createTenantBackendClient, "createTenantBackendClient");
2194
2194
 
2195
+ // src/platform/index.ts
2196
+ var platform_exports = {};
2197
+ __export(platform_exports, {
2198
+ CircuitBreakerOpenError: () => CircuitBreakerOpenError,
2199
+ LLMRouter: () => LLMRouter,
2200
+ __resetHttpClientBreakers: () => __resetHttpClientBreakers,
2201
+ buildCacheKey: () => buildCacheKey,
2202
+ buildLoaders: () => buildLoaders,
2203
+ clampLimit: () => clampLimit,
2204
+ createHttpClient: () => createHttpClient,
2205
+ dropOldPartitions: () => dropOldPartitions,
2206
+ ensureMonthlyPartitions: () => ensureMonthlyPartitions,
2207
+ getNamespaceVersion: () => getNamespaceVersion,
2208
+ httpClient: () => httpClient,
2209
+ invalidateNamespace: () => invalidateNamespace,
2210
+ monthlyPartitionName: () => monthlyPartitionName,
2211
+ paginate: () => paginate,
2212
+ rateLimitIncr: () => rateLimitIncr,
2213
+ resolveReferenceLoader: () => resolveReferenceLoader,
2214
+ withLeaderLock: () => withLeaderLock
2215
+ });
2216
+
2217
+ // src/platform/httpClient.ts
2218
+ var breakers = /* @__PURE__ */ new Map();
2219
+ function hostOf(url) {
2220
+ try {
2221
+ return new URL(url).host;
2222
+ } catch {
2223
+ return url;
2224
+ }
2225
+ }
2226
+ __name(hostOf, "hostOf");
2227
+ function getBreaker(host) {
2228
+ let b = breakers.get(host);
2229
+ if (!b) {
2230
+ b = {
2231
+ failures: 0,
2232
+ openedAt: null,
2233
+ state: "CLOSED"
2234
+ };
2235
+ breakers.set(host, b);
2236
+ }
2237
+ return b;
2238
+ }
2239
+ __name(getBreaker, "getBreaker");
2240
+ function defaultRetryOn(status) {
2241
+ return status === 408 || status === 425 || status === 429 || status >= 500 && status <= 599;
2242
+ }
2243
+ __name(defaultRetryOn, "defaultRetryOn");
2244
+ function jitter(delay) {
2245
+ return Math.floor(Math.random() * delay);
2246
+ }
2247
+ __name(jitter, "jitter");
2248
+ function sleep(ms) {
2249
+ return new Promise((r) => setTimeout(r, ms));
2250
+ }
2251
+ __name(sleep, "sleep");
2252
+ var CircuitBreakerOpenError = class extends Error {
2253
+ static {
2254
+ __name(this, "CircuitBreakerOpenError");
2255
+ }
2256
+ constructor(host) {
2257
+ super(`circuit breaker OPEN for host=${host}`);
2258
+ this.name = "CircuitBreakerOpenError";
2259
+ }
2260
+ };
2261
+ function __resetHttpClientBreakers() {
2262
+ breakers.clear();
2263
+ }
2264
+ __name(__resetHttpClientBreakers, "__resetHttpClientBreakers");
2265
+ async function executeOnce(url, init, timeoutMs) {
2266
+ const signal = init.signal ?? AbortSignal.timeout(timeoutMs);
2267
+ return fetch(url, {
2268
+ ...init,
2269
+ signal
2270
+ });
2271
+ }
2272
+ __name(executeOnce, "executeOnce");
2273
+ async function httpClient(url, init = {}, config = {}) {
2274
+ const fullUrl = config.baseUrl && !/^https?:\/\//i.test(url) ? `${config.baseUrl.replace(/\/$/, "")}/${url.replace(/^\//, "")}` : url;
2275
+ const host = hostOf(fullUrl);
2276
+ const breaker = getBreaker(host);
2277
+ const breakerThreshold = config.breaker?.threshold ?? 5;
2278
+ const cooldownMs = config.breaker?.cooldownMs ?? 3e4;
2279
+ if (breaker.state === "OPEN") {
2280
+ if (breaker.openedAt && Date.now() - breaker.openedAt >= cooldownMs) {
2281
+ breaker.state = "HALF_OPEN";
2282
+ logger.info("httpClient.breaker.halfOpen", {
2283
+ host
2284
+ });
2285
+ } else {
2286
+ throw new CircuitBreakerOpenError(host);
2287
+ }
2288
+ }
2289
+ const attempts = init.retry?.attempts ?? config.retry?.attempts ?? 3;
2290
+ const initialDelay = init.retry?.initialDelayMs ?? config.retry?.initialDelayMs ?? 100;
2291
+ const maxDelay = init.retry?.maxDelayMs ?? config.retry?.maxDelayMs ?? 2e3;
2292
+ const retryOn = init.retry?.retryOn ?? config.retry?.retryOn ?? defaultRetryOn;
2293
+ const timeoutMs = init.timeoutMs ?? config.defaultTimeoutMs ?? 5e3;
2294
+ const headers = {
2295
+ ...config.defaultHeaders ?? {},
2296
+ ...init.headers ?? {}
2297
+ };
2298
+ let lastErr;
2299
+ for (let attempt = 1; attempt <= attempts; attempt++) {
2300
+ try {
2301
+ const response = await executeOnce(fullUrl, {
2302
+ ...init,
2303
+ headers
2304
+ }, timeoutMs);
2305
+ if (response.status >= 500) {
2306
+ breaker.failures += 1;
2307
+ if (breaker.failures >= breakerThreshold) {
2308
+ breaker.state = "OPEN";
2309
+ breaker.openedAt = Date.now();
2310
+ logger.warn("httpClient.breaker.open", {
2311
+ host,
2312
+ failures: breaker.failures
2313
+ });
2314
+ }
2315
+ if (attempt < attempts && retryOn(response.status)) {
2316
+ const delay = Math.min(initialDelay * 2 ** (attempt - 1), maxDelay);
2317
+ await sleep(jitter(delay));
2318
+ continue;
2319
+ }
2320
+ return response;
2321
+ }
2322
+ breaker.failures = 0;
2323
+ if (breaker.state === "HALF_OPEN") {
2324
+ breaker.state = "CLOSED";
2325
+ breaker.openedAt = null;
2326
+ logger.info("httpClient.breaker.closed", {
2327
+ host
2328
+ });
2329
+ }
2330
+ if (attempt < attempts && retryOn(response.status)) {
2331
+ const delay = Math.min(initialDelay * 2 ** (attempt - 1), maxDelay);
2332
+ await sleep(jitter(delay));
2333
+ continue;
2334
+ }
2335
+ return response;
2336
+ } catch (err) {
2337
+ lastErr = err;
2338
+ breaker.failures += 1;
2339
+ if (breaker.failures >= breakerThreshold) {
2340
+ breaker.state = "OPEN";
2341
+ breaker.openedAt = Date.now();
2342
+ logger.warn("httpClient.breaker.open", {
2343
+ host,
2344
+ failures: breaker.failures
2345
+ });
2346
+ }
2347
+ if (attempt >= attempts) break;
2348
+ const delay = Math.min(initialDelay * 2 ** (attempt - 1), maxDelay);
2349
+ await sleep(jitter(delay));
2350
+ }
2351
+ }
2352
+ logger.error("httpClient.exhausted", {
2353
+ url: fullUrl,
2354
+ attempts,
2355
+ host
2356
+ });
2357
+ throw lastErr instanceof Error ? lastErr : new Error(`httpClient failed: ${String(lastErr)}`);
2358
+ }
2359
+ __name(httpClient, "httpClient");
2360
+ function createHttpClient(config = {}) {
2361
+ return (url, init = {}) => httpClient(url, init, config);
2362
+ }
2363
+ __name(createHttpClient, "createHttpClient");
2364
+
2365
+ // src/platform/leaderLock.ts
2366
+ var RELEASE_LUA = `
2367
+ if redis.call("get", KEYS[1]) == ARGV[1] then
2368
+ return redis.call("del", KEYS[1])
2369
+ else
2370
+ return 0
2371
+ end
2372
+ `;
2373
+ var RENEW_LUA = `
2374
+ if redis.call("get", KEYS[1]) == ARGV[1] then
2375
+ return redis.call("pexpire", KEYS[1], ARGV[2])
2376
+ else
2377
+ return 0
2378
+ end
2379
+ `;
2380
+ function generateToken() {
2381
+ return `${Date.now()}-${Math.random().toString(36).slice(2)}-${process.pid}`;
2382
+ }
2383
+ __name(generateToken, "generateToken");
2384
+ async function withLeaderLock(redis, name, fn, options) {
2385
+ const key = `besdk:leader:${name}`;
2386
+ const token = generateToken();
2387
+ const ttlMs = options.ttlMs;
2388
+ const renewMs = options.renewMs ?? Math.max(1e3, Math.floor(ttlMs / 3));
2389
+ const ttlSeconds = Math.max(1, Math.ceil(ttlMs / 1e3));
2390
+ const acquired = await redis.set(key, token, "EX", ttlSeconds, "NX");
2391
+ if (acquired !== "OK") {
2392
+ return {
2393
+ acquired: false
2394
+ };
2395
+ }
2396
+ logger.info("leaderLock.acquired", {
2397
+ name,
2398
+ token,
2399
+ ttlMs
2400
+ });
2401
+ let stopped = false;
2402
+ const renewTimer = setInterval(() => {
2403
+ if (stopped) return;
2404
+ redis.eval(RENEW_LUA, 1, key, token, String(ttlMs)).catch((err) => {
2405
+ logger.warn("leaderLock.renew.failed", {
2406
+ name,
2407
+ err: String(err)
2408
+ });
2409
+ });
2410
+ }, renewMs);
2411
+ if (typeof renewTimer.unref === "function") {
2412
+ renewTimer.unref();
2413
+ }
2414
+ try {
2415
+ const result = await fn();
2416
+ return {
2417
+ acquired: true,
2418
+ result
2419
+ };
2420
+ } finally {
2421
+ stopped = true;
2422
+ clearInterval(renewTimer);
2423
+ try {
2424
+ await redis.eval(RELEASE_LUA, 1, key, token);
2425
+ logger.info("leaderLock.released", {
2426
+ name,
2427
+ token
2428
+ });
2429
+ } catch (err) {
2430
+ logger.warn("leaderLock.release.failed", {
2431
+ name,
2432
+ err: String(err)
2433
+ });
2434
+ }
2435
+ }
2436
+ }
2437
+ __name(withLeaderLock, "withLeaderLock");
2438
+
2439
+ // src/platform/rateLimit.ts
2440
+ async function rateLimitIncr(redis, input) {
2441
+ const fullKey = `besdk:rl:${input.key}`;
2442
+ const count = await redis.incr(fullKey);
2443
+ if (count === 1) {
2444
+ await redis.pexpire(fullKey, input.windowMs);
2445
+ }
2446
+ let ttl = -1;
2447
+ try {
2448
+ ttl = await redis.pttl(fullKey);
2449
+ } catch {
2450
+ ttl = input.windowMs;
2451
+ }
2452
+ const resetAt = Date.now() + (ttl > 0 ? ttl : input.windowMs);
2453
+ const remaining = Math.max(0, input.limit - count);
2454
+ return {
2455
+ allowed: count <= input.limit,
2456
+ count,
2457
+ remaining,
2458
+ resetAt
2459
+ };
2460
+ }
2461
+ __name(rateLimitIncr, "rateLimitIncr");
2462
+
2463
+ // src/platform/paginator.ts
2464
+ function clampLimit(input, opts = {}) {
2465
+ const def = opts.defaultLimit ?? 50;
2466
+ const max = opts.maxLimit ?? 200;
2467
+ const min = opts.minLimit ?? 1;
2468
+ if (input == null || Number.isNaN(input)) return def;
2469
+ return Math.min(Math.max(Math.floor(input), min), max);
2470
+ }
2471
+ __name(clampLimit, "clampLimit");
2472
+ async function paginate(model, input, opts = {}) {
2473
+ const take = clampLimit(input.take ?? void 0, opts);
2474
+ const items = await model.findMany({
2475
+ take: take + 1,
2476
+ skip: input.cursor ? 1 : 0,
2477
+ cursor: input.cursor ? {
2478
+ id: input.cursor
2479
+ } : void 0,
2480
+ where: input.where,
2481
+ orderBy: input.orderBy
2482
+ });
2483
+ const hasMore = items.length > take;
2484
+ const trimmed = hasMore ? items.slice(0, take) : items;
2485
+ const nextCursor = hasMore && trimmed.length > 0 ? trimmed[trimmed.length - 1].id : null;
2486
+ return {
2487
+ items: trimmed,
2488
+ hasMore,
2489
+ nextCursor
2490
+ };
2491
+ }
2492
+ __name(paginate, "paginate");
2493
+
2494
+ // src/platform/dataLoaderFactory.ts
2495
+ function buildLoaders(ctx, builder) {
2496
+ return builder(ctx);
2497
+ }
2498
+ __name(buildLoaders, "buildLoaders");
2499
+ function resolveReferenceLoader(loaderName, keyField = "id") {
2500
+ return async (reference, ctx) => {
2501
+ const loader = ctx.loaders[loaderName];
2502
+ if (!loader) {
2503
+ throw new Error(`resolveReferenceLoader: loader "${loaderName}" not found on ctx.loaders`);
2504
+ }
2505
+ const key = reference[keyField];
2506
+ if (typeof key !== "string") {
2507
+ return null;
2508
+ }
2509
+ return loader.load(key);
2510
+ };
2511
+ }
2512
+ __name(resolveReferenceLoader, "resolveReferenceLoader");
2513
+
2514
+ // src/platform/cacheInvalidation.ts
2515
+ var VERSION_PREFIX = "besdk:cv:";
2516
+ async function getNamespaceVersion(redis, namespace) {
2517
+ const v = await redis.get(`${VERSION_PREFIX}${namespace}`);
2518
+ if (!v) return 1;
2519
+ const n = Number.parseInt(v, 10);
2520
+ return Number.isFinite(n) && n > 0 ? n : 1;
2521
+ }
2522
+ __name(getNamespaceVersion, "getNamespaceVersion");
2523
+ async function buildCacheKey(redis, namespace, ...parts) {
2524
+ const ver = await getNamespaceVersion(redis, namespace);
2525
+ return `${namespace}:v${ver}:${parts.join(":")}`;
2526
+ }
2527
+ __name(buildCacheKey, "buildCacheKey");
2528
+ async function invalidateNamespace(redis, namespace) {
2529
+ return redis.incr(`${VERSION_PREFIX}${namespace}`);
2530
+ }
2531
+ __name(invalidateNamespace, "invalidateNamespace");
2532
+
2533
+ // src/platform/pgPartition.ts
2534
+ function pad2(n) {
2535
+ return n < 10 ? `0${n}` : `${n}`;
2536
+ }
2537
+ __name(pad2, "pad2");
2538
+ function monthlyPartitionName(table, year, month) {
2539
+ return `${table}_y${year}m${pad2(month)}`;
2540
+ }
2541
+ __name(monthlyPartitionName, "monthlyPartitionName");
2542
+ async function ensureMonthlyPartitions(prisma, opts) {
2543
+ const created = [];
2544
+ const cur = new Date(Date.UTC(opts.from.getUTCFullYear(), opts.from.getUTCMonth(), 1));
2545
+ const end = new Date(Date.UTC(opts.to.getUTCFullYear(), opts.to.getUTCMonth(), 1));
2546
+ while (cur <= end) {
2547
+ const y = cur.getUTCFullYear();
2548
+ const m = cur.getUTCMonth() + 1;
2549
+ const next = new Date(Date.UTC(y, m, 1));
2550
+ const partition = monthlyPartitionName(opts.table, y, m);
2551
+ const lower = `${y}-${pad2(m)}-01`;
2552
+ const upper = `${next.getUTCFullYear()}-${pad2(next.getUTCMonth() + 1)}-01`;
2553
+ await prisma.$executeRawUnsafe(`CREATE TABLE IF NOT EXISTS "${partition}" PARTITION OF "${opts.table}" FOR VALUES FROM ('${lower}') TO ('${upper}');`);
2554
+ created.push(partition);
2555
+ cur.setUTCMonth(cur.getUTCMonth() + 1);
2556
+ }
2557
+ return created;
2558
+ }
2559
+ __name(ensureMonthlyPartitions, "ensureMonthlyPartitions");
2560
+ async function dropOldPartitions(prisma, opts) {
2561
+ const rows = await prisma.$queryRawUnsafe(`SELECT child.relname AS partition_name
2562
+ FROM pg_inherits
2563
+ JOIN pg_class parent ON pg_inherits.inhparent = parent.oid
2564
+ JOIN pg_class child ON pg_inherits.inhrelid = child.oid
2565
+ WHERE parent.relname = $1`, opts.table);
2566
+ const dropped = [];
2567
+ const cutoffYear = opts.beforeDate.getUTCFullYear();
2568
+ const cutoffMonth = opts.beforeDate.getUTCMonth() + 1;
2569
+ for (const row of rows) {
2570
+ const m = row.partition_name.match(/_y(\d{4})m(\d{2})$/);
2571
+ if (!m) continue;
2572
+ const y = Number(m[1]);
2573
+ const mo = Number(m[2]);
2574
+ if (y < cutoffYear || y === cutoffYear && mo < cutoffMonth) {
2575
+ await prisma.$executeRawUnsafe(`DROP TABLE IF EXISTS "${row.partition_name}";`);
2576
+ dropped.push(row.partition_name);
2577
+ }
2578
+ }
2579
+ return dropped;
2580
+ }
2581
+ __name(dropOldPartitions, "dropOldPartitions");
2582
+
2583
+ // src/platform/llmClient.ts
2584
+ var DEFAULTS = {
2585
+ timeoutMs: 3e4,
2586
+ maxAttempts: 3,
2587
+ initialDelayMs: 500,
2588
+ maxOutputTokensCap: 4096
2589
+ };
2590
+ function jitter2(n) {
2591
+ return Math.floor(Math.random() * n);
2592
+ }
2593
+ __name(jitter2, "jitter");
2594
+ function sleep2(ms) {
2595
+ return new Promise((r) => setTimeout(r, ms));
2596
+ }
2597
+ __name(sleep2, "sleep");
2598
+ var LLMRouter = class {
2599
+ static {
2600
+ __name(this, "LLMRouter");
2601
+ }
2602
+ adapters = /* @__PURE__ */ new Map();
2603
+ opts;
2604
+ constructor(opts = {}) {
2605
+ this.opts = {
2606
+ ...DEFAULTS,
2607
+ ...opts
2608
+ };
2609
+ }
2610
+ register(adapter) {
2611
+ this.adapters.set(adapter.name, adapter);
2612
+ }
2613
+ resolveChain() {
2614
+ const envChain = process.env["LLM_PROVIDER_CHAIN"]?.trim();
2615
+ if (envChain) {
2616
+ return envChain.split(",").map((s) => s.trim()).filter(Boolean);
2617
+ }
2618
+ return Array.from(this.adapters.keys());
2619
+ }
2620
+ async complete(req) {
2621
+ const cappedReq = {
2622
+ ...req,
2623
+ maxTokens: Math.min(req.maxTokens ?? this.opts.maxOutputTokensCap, this.opts.maxOutputTokensCap)
2624
+ };
2625
+ const chain = this.resolveChain();
2626
+ if (chain.length === 0) {
2627
+ throw new Error("LLMRouter: no providers registered");
2628
+ }
2629
+ let lastErr = null;
2630
+ for (let attempt = 0; attempt < this.opts.maxAttempts; attempt++) {
2631
+ const providerName = chain[attempt % chain.length];
2632
+ const adapter = providerName ? this.adapters.get(providerName) : void 0;
2633
+ if (!adapter) {
2634
+ logger.warn("llmRouter.provider.notRegistered", {
2635
+ providerName
2636
+ });
2637
+ continue;
2638
+ }
2639
+ const timeoutMs = req.timeoutMs ?? this.opts.timeoutMs;
2640
+ const ctrl = new AbortController();
2641
+ const t2 = setTimeout(() => ctrl.abort(), timeoutMs);
2642
+ try {
2643
+ const response = await adapter.complete(cappedReq, ctrl.signal);
2644
+ return response;
2645
+ } catch (err) {
2646
+ lastErr = err;
2647
+ logger.warn("llmRouter.attempt.failed", {
2648
+ provider: adapter.name,
2649
+ attempt,
2650
+ err: String(err)
2651
+ });
2652
+ if (attempt < this.opts.maxAttempts - 1) {
2653
+ const delay = this.opts.initialDelayMs * 2 ** attempt;
2654
+ await sleep2(jitter2(delay));
2655
+ }
2656
+ } finally {
2657
+ clearTimeout(t2);
2658
+ }
2659
+ }
2660
+ throw lastErr instanceof Error ? lastErr : new Error(`LLMRouter exhausted ${this.opts.maxAttempts} attempts`);
2661
+ }
2662
+ };
2663
+
2195
2664
  // src/index.ts
2196
2665
  var BurdenoffSDK = class {
2197
2666
  static {
@@ -2623,6 +3092,6 @@ function createSDK(config, context) {
2623
3092
  }
2624
3093
  __name(createSDK, "createSDK");
2625
3094
 
2626
- export { BootstrapModule, BurdenoffGraphQLClient, BurdenoffSDK, ContextManager, PaymentsModule, SearchModule, TenantBackendClient, TranslationModule, createBootstrap, createSDK, createTenantBackendClient, index_default as default };
3095
+ export { BootstrapModule, BurdenoffGraphQLClient, BurdenoffSDK, ContextManager, PaymentsModule, platform_exports as Platform, SearchModule, TenantBackendClient, TranslationModule, createBootstrap, createSDK, createTenantBackendClient, index_default as default };
2627
3096
  //# sourceMappingURL=index.js.map
2628
3097
  //# sourceMappingURL=index.js.map