@burdenoff/be-sdk 2026.703.5 → 2026.704.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
@@ -1,12 +1,12 @@
1
+ import { ActivityModule, UsageModule, TrackerModule } from './chunk-XN3DNXXV.js';
2
+ export { ActivityModule, TrackerModule, UsageModule } from './chunk-XN3DNXXV.js';
1
3
  export { prisma_exports as Prisma } from './chunk-254FNUIC.js';
4
+ import { RBACModule } from './chunk-S32JOMMW.js';
5
+ export { RBACModule, createRBACDirective, createRBACDirectiveForLayer, createRBACDirectiveFromEnv } from './chunk-S32JOMMW.js';
2
6
  export { RESOURCE_CAP_DEFAULTS, ResourceCapExceededError, ResourceCapsModule, getResourceCaps } from './chunk-VDDOGIWY.js';
3
7
  import './chunk-GTEMQXCA.js';
4
8
  export { sharing_exports as Sharing } from './chunk-XD5ZK727.js';
5
9
  export { TemporalClient, TemporalClientFactory, TemporalModule, TemporalWorker, TemporalWorkerFactory, TemporalWorkerManager } from './chunk-IC7YDBGE.js';
6
- import { ActivityModule, UsageModule, TrackerModule } from './chunk-XN3DNXXV.js';
7
- export { ActivityModule, TrackerModule, UsageModule } from './chunk-XN3DNXXV.js';
8
- import { RBACModule } from './chunk-S32JOMMW.js';
9
- export { RBACModule, createRBACDirective, createRBACDirectiveForLayer, createRBACDirectiveFromEnv } from './chunk-S32JOMMW.js';
10
10
  export { instrumentation_exports as Instrumentation } from './chunk-ACYGOENW.js';
11
11
  import './chunk-REF7PANB.js';
12
12
  export { middleware_exports as Middleware } from './chunk-2HANKZZM.js';
@@ -0,0 +1,59 @@
1
+ type ShardId = string;
2
+ type ShardTier = 'pool' | 'bridge' | 'silo';
3
+ type ShardState = 'active' | 'draining' | 'read_only';
4
+ interface ShardCatalogEntry {
5
+ shardId: ShardId;
6
+ layer?: string;
7
+ connectionString: string;
8
+ region?: string;
9
+ capacityWeight?: number;
10
+ state?: ShardState;
11
+ }
12
+ interface TenantShardMapping {
13
+ workspaceId: string;
14
+ shardId: ShardId;
15
+ tier?: ShardTier;
16
+ state?: 'active' | 'migrating' | 'read_only';
17
+ migratingTo?: ShardId | null;
18
+ }
19
+ interface ShardDirectory {
20
+ resolveShardId(workspaceId: string): Promise<ShardId>;
21
+ getShard(shardId: ShardId): Promise<ShardCatalogEntry | undefined>;
22
+ listShards(): Promise<ShardCatalogEntry[]>;
23
+ }
24
+ type ShardedClientFactory<TClient> = (connectionString: string, shardId: ShardId) => TClient;
25
+
26
+ declare class StaticShardDirectory implements ShardDirectory {
27
+ private readonly entry;
28
+ constructor(defaultShardId: ShardId, defaultConnectionString: string, layer?: string);
29
+ resolveShardId(_workspaceId: string): Promise<ShardId>;
30
+ getShard(shardId: ShardId): Promise<ShardCatalogEntry | undefined>;
31
+ listShards(): Promise<ShardCatalogEntry[]>;
32
+ }
33
+ declare class EnvShardDirectory implements ShardDirectory {
34
+ private readonly defaultShardId;
35
+ private readonly catalog;
36
+ private readonly overrides;
37
+ constructor(defaultShardId: ShardId, defaultConnectionString: string, env?: NodeJS.ProcessEnv);
38
+ resolveShardId(workspaceId: string): Promise<ShardId>;
39
+ getShard(shardId: ShardId): Promise<ShardCatalogEntry | undefined>;
40
+ listShards(): Promise<ShardCatalogEntry[]>;
41
+ }
42
+
43
+ interface ShardRouterOptions {
44
+ maxClients?: number;
45
+ }
46
+ declare class ShardRouter<TClient> {
47
+ private readonly directory;
48
+ private readonly factory;
49
+ private readonly clients;
50
+ private readonly maxClients;
51
+ constructor(directory: ShardDirectory, factory: ShardedClientFactory<TClient>, options?: ShardRouterOptions);
52
+ resolveShardId(workspaceId: string): Promise<ShardId>;
53
+ getClientForWorkspace(workspaceId: string): Promise<TClient>;
54
+ getClientForShard(shardId: ShardId): Promise<TClient>;
55
+ cachedShardIds(): ShardId[];
56
+ }
57
+ declare function createShardRouterFromEnv<TClient>(factory: ShardedClientFactory<TClient>, options?: ShardRouterOptions, env?: NodeJS.ProcessEnv): ShardRouter<TClient>;
58
+
59
+ export { EnvShardDirectory, type ShardCatalogEntry, type ShardDirectory, type ShardId, ShardRouter, type ShardRouterOptions, type ShardState, type ShardTier, type ShardedClientFactory, StaticShardDirectory, type TenantShardMapping, createShardRouterFromEnv };
@@ -0,0 +1,152 @@
1
+ import { logger } from '../chunk-CBZNP6RF.js';
2
+ import { __name } from '../chunk-XE4OXN2W.js';
3
+
4
+ // src/sharding/directory.ts
5
+ var StaticShardDirectory = class {
6
+ static {
7
+ __name(this, "StaticShardDirectory");
8
+ }
9
+ entry;
10
+ constructor(defaultShardId, defaultConnectionString, layer) {
11
+ this.entry = {
12
+ shardId: defaultShardId,
13
+ connectionString: defaultConnectionString,
14
+ layer,
15
+ state: "active"
16
+ };
17
+ }
18
+ async resolveShardId(_workspaceId) {
19
+ return this.entry.shardId;
20
+ }
21
+ async getShard(shardId) {
22
+ return shardId === this.entry.shardId ? this.entry : void 0;
23
+ }
24
+ async listShards() {
25
+ return [
26
+ this.entry
27
+ ];
28
+ }
29
+ };
30
+ var EnvShardDirectory = class {
31
+ static {
32
+ __name(this, "EnvShardDirectory");
33
+ }
34
+ defaultShardId;
35
+ catalog;
36
+ overrides;
37
+ constructor(defaultShardId, defaultConnectionString, env = process.env) {
38
+ this.defaultShardId = defaultShardId;
39
+ this.catalog = /* @__PURE__ */ new Map();
40
+ this.catalog.set(defaultShardId, {
41
+ shardId: defaultShardId,
42
+ connectionString: defaultConnectionString,
43
+ state: "active"
44
+ });
45
+ const rawCatalog = env["SHARD_CATALOG"];
46
+ if (rawCatalog && rawCatalog.trim() !== "") {
47
+ const parsed = JSON.parse(rawCatalog);
48
+ for (const entry of parsed) {
49
+ if (entry && typeof entry.shardId === "string" && typeof entry.connectionString === "string") {
50
+ this.catalog.set(entry.shardId, {
51
+ state: "active",
52
+ ...entry
53
+ });
54
+ }
55
+ }
56
+ }
57
+ this.overrides = /* @__PURE__ */ new Map();
58
+ const rawMap = env["TENANT_SHARD_MAP"];
59
+ if (rawMap && rawMap.trim() !== "") {
60
+ const parsed = JSON.parse(rawMap);
61
+ for (const [workspaceId, shardId] of Object.entries(parsed)) {
62
+ this.overrides.set(workspaceId, shardId);
63
+ }
64
+ }
65
+ }
66
+ async resolveShardId(workspaceId) {
67
+ const mapped = this.overrides.get(workspaceId);
68
+ if (mapped && this.catalog.has(mapped)) {
69
+ return mapped;
70
+ }
71
+ return this.defaultShardId;
72
+ }
73
+ async getShard(shardId) {
74
+ return this.catalog.get(shardId);
75
+ }
76
+ async listShards() {
77
+ return Array.from(this.catalog.values());
78
+ }
79
+ };
80
+
81
+ // src/sharding/router.ts
82
+ var ShardRouter = class {
83
+ static {
84
+ __name(this, "ShardRouter");
85
+ }
86
+ directory;
87
+ factory;
88
+ clients = /* @__PURE__ */ new Map();
89
+ maxClients;
90
+ constructor(directory, factory, options) {
91
+ this.directory = directory;
92
+ this.factory = factory;
93
+ this.maxClients = options?.maxClients ?? 32;
94
+ }
95
+ /** Resolve which shard a workspace lives on. */
96
+ async resolveShardId(workspaceId) {
97
+ return this.directory.resolveShardId(workspaceId);
98
+ }
99
+ /** Get (or lazily create) the client for a workspace's shard. */
100
+ async getClientForWorkspace(workspaceId) {
101
+ const shardId = await this.directory.resolveShardId(workspaceId);
102
+ return this.getClientForShard(shardId);
103
+ }
104
+ /** Get (or lazily create) the client for a specific shard. */
105
+ async getClientForShard(shardId) {
106
+ const existing = this.clients.get(shardId);
107
+ if (existing !== void 0) {
108
+ this.clients.delete(shardId);
109
+ this.clients.set(shardId, existing);
110
+ return existing;
111
+ }
112
+ const shard = await this.directory.getShard(shardId);
113
+ if (!shard) {
114
+ throw new Error(`[ShardRouter] unknown shard '${shardId}' \u2014 not in the shard catalog`);
115
+ }
116
+ const client = this.factory(shard.connectionString, shardId);
117
+ this.clients.set(shardId, client);
118
+ logger.info("[ShardRouter] shard client created", {
119
+ shardId,
120
+ layer: shard.layer
121
+ });
122
+ if (this.clients.size > this.maxClients) {
123
+ const oldest = this.clients.keys().next().value;
124
+ if (oldest !== void 0 && oldest !== shardId) {
125
+ this.clients.delete(oldest);
126
+ logger.info("[ShardRouter] evicted LRU shard client", {
127
+ shardId: oldest
128
+ });
129
+ }
130
+ }
131
+ return client;
132
+ }
133
+ /** Currently-cached shard ids (for diagnostics/tests). */
134
+ cachedShardIds() {
135
+ return Array.from(this.clients.keys());
136
+ }
137
+ };
138
+ function createShardRouterFromEnv(factory, options, env = process.env) {
139
+ const databaseUrl = env["DATABASE_URL"];
140
+ if (!databaseUrl) {
141
+ throw new Error("[ShardRouter] DATABASE_URL is required to build the default shard");
142
+ }
143
+ const defaultShardId = env["DEFAULT_SHARD_ID"]?.trim() || "shard-01";
144
+ const enabled = (env["SHARDING_ENABLED"] ?? "false").toLowerCase() === "true";
145
+ const directory = enabled ? new EnvShardDirectory(defaultShardId, databaseUrl, env) : new StaticShardDirectory(defaultShardId, databaseUrl);
146
+ return new ShardRouter(directory, factory, options);
147
+ }
148
+ __name(createShardRouterFromEnv, "createShardRouterFromEnv");
149
+
150
+ export { EnvShardDirectory, ShardRouter, StaticShardDirectory, createShardRouterFromEnv };
151
+ //# sourceMappingURL=index.js.map
152
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/sharding/directory.ts","../../src/sharding/router.ts"],"names":["StaticShardDirectory","entry","defaultShardId","defaultConnectionString","layer","shardId","connectionString","state","resolveShardId","_workspaceId","getShard","undefined","listShards","EnvShardDirectory","catalog","overrides","env","process","Map","set","rawCatalog","trim","parsed","JSON","parse","rawMap","workspaceId","Object","entries","mapped","get","has","Array","from","values","ShardRouter","clients","maxClients","directory","factory","options","getClientForWorkspace","getClientForShard","existing","delete","shard","Error","client","logger","info","size","oldest","keys","next","value","cachedShardIds","createShardRouterFromEnv","databaseUrl","enabled","toLowerCase"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Shard directory implementations.\n *\n * - {@link StaticShardDirectory}: the safe default — every workspace resolves to\n * a single shard (the current DATABASE_URL). Pure passthrough, no data moved.\n * - {@link EnvShardDirectory}: reads a shard catalog + optional per-tenant\n * overrides from environment (still defaults every unmapped workspace to the\n * default shard, so it is behavior-preserving unless a catalog is configured).\n *\n * @module sharding/directory\n */\n\nimport type { ShardCatalogEntry, ShardDirectory, ShardId } from './types.js';\n\n/**\n * Single-shard directory: every workspace → the default shard. This is the\n * \"Step 1\" foundation — introducing the router as pure indirection with no data\n * placement changes.\n */\nexport class StaticShardDirectory implements ShardDirectory {\n private readonly entry: ShardCatalogEntry;\n\n constructor(defaultShardId: ShardId, defaultConnectionString: string, layer?: string) {\n this.entry = { shardId: defaultShardId, connectionString: defaultConnectionString, layer, state: 'active' };\n }\n\n async resolveShardId(_workspaceId: string): Promise<ShardId> {\n return this.entry.shardId;\n }\n\n async getShard(shardId: ShardId): Promise<ShardCatalogEntry | undefined> {\n return shardId === this.entry.shardId ? this.entry : undefined;\n }\n\n async listShards(): Promise<ShardCatalogEntry[]> {\n return [this.entry];\n }\n}\n\n/**\n * Environment-driven directory.\n *\n * - `SHARD_CATALOG`: optional JSON array of ShardCatalogEntry. When unset, the\n * catalog is just the default shard (built from `defaultConnectionString`).\n * - `TENANT_SHARD_MAP`: optional JSON object `{ \"<workspaceId>\": \"<shardId>\" }`\n * of explicit overrides (e.g. whales promoted to a silo shard). Any workspace\n * NOT listed falls back to `defaultShardId`.\n *\n * With neither env set, this is identical to {@link StaticShardDirectory}. This\n * is the migration on-ramp: place a handful of workspaces on new shards via the\n * override map before a real Redis/DB-backed directory exists.\n */\nexport class EnvShardDirectory implements ShardDirectory {\n private readonly catalog: Map<ShardId, ShardCatalogEntry>;\n private readonly overrides: Map<string, ShardId>;\n\n constructor(\n private readonly defaultShardId: ShardId,\n defaultConnectionString: string,\n env: NodeJS.ProcessEnv = process.env\n ) {\n this.catalog = new Map();\n // Always register the default shard first (overridable by the catalog).\n this.catalog.set(defaultShardId, {\n shardId: defaultShardId,\n connectionString: defaultConnectionString,\n state: 'active',\n });\n\n const rawCatalog = env['SHARD_CATALOG'];\n if (rawCatalog && rawCatalog.trim() !== '') {\n const parsed = JSON.parse(rawCatalog) as ShardCatalogEntry[];\n for (const entry of parsed) {\n if (entry && typeof entry.shardId === 'string' && typeof entry.connectionString === 'string') {\n this.catalog.set(entry.shardId, { state: 'active', ...entry });\n }\n }\n }\n\n this.overrides = new Map();\n const rawMap = env['TENANT_SHARD_MAP'];\n if (rawMap && rawMap.trim() !== '') {\n const parsed = JSON.parse(rawMap) as Record<string, string>;\n for (const [workspaceId, shardId] of Object.entries(parsed)) {\n this.overrides.set(workspaceId, shardId);\n }\n }\n }\n\n async resolveShardId(workspaceId: string): Promise<ShardId> {\n const mapped = this.overrides.get(workspaceId);\n // Only honor an override that points at a known shard; otherwise fail safe to default.\n if (mapped && this.catalog.has(mapped)) {\n return mapped;\n }\n return this.defaultShardId;\n }\n\n async getShard(shardId: ShardId): Promise<ShardCatalogEntry | undefined> {\n return this.catalog.get(shardId);\n }\n\n async listShards(): Promise<ShardCatalogEntry[]> {\n return Array.from(this.catalog.values());\n }\n}\n","/**\n * Shard router — resolves a workspace to a shard client.\n *\n * The router owns a per-shard client cache (one client per shard per process,\n * lazily created). Given a `workspaceId` it consults the {@link ShardDirectory}\n * and returns the client for that workspace's shard. With the default\n * single-shard directory, every workspace resolves to the same shard, so there\n * is exactly one client and behavior is identical to today.\n *\n * The router never opens connections itself — it delegates client construction\n * to the caller-supplied {@link ShardedClientFactory} (so the consuming service\n * supplies its own generated PrismaClient constructor + pool wiring).\n *\n * @module sharding/router\n */\n\nimport { logger } from '../logger/index.js';\nimport { StaticShardDirectory, EnvShardDirectory } from './directory.js';\nimport type { ShardDirectory, ShardId, ShardedClientFactory } from './types.js';\n\nexport interface ShardRouterOptions {\n /**\n * Soft cap on cached shard clients per process (LRU-evicted). With a single\n * shard this is never hit. Defaults to 32.\n */\n maxClients?: number;\n}\n\n/**\n * Routes `workspaceId` → the Prisma client for that workspace's shard.\n */\nexport class ShardRouter<TClient> {\n private readonly clients = new Map<ShardId, TClient>();\n private readonly maxClients: number;\n\n constructor(\n private readonly directory: ShardDirectory,\n private readonly factory: ShardedClientFactory<TClient>,\n options?: ShardRouterOptions\n ) {\n this.maxClients = options?.maxClients ?? 32;\n }\n\n /** Resolve which shard a workspace lives on. */\n async resolveShardId(workspaceId: string): Promise<ShardId> {\n return this.directory.resolveShardId(workspaceId);\n }\n\n /** Get (or lazily create) the client for a workspace's shard. */\n async getClientForWorkspace(workspaceId: string): Promise<TClient> {\n const shardId = await this.directory.resolveShardId(workspaceId);\n return this.getClientForShard(shardId);\n }\n\n /** Get (or lazily create) the client for a specific shard. */\n async getClientForShard(shardId: ShardId): Promise<TClient> {\n const existing = this.clients.get(shardId);\n if (existing !== undefined) {\n // Refresh LRU recency.\n this.clients.delete(shardId);\n this.clients.set(shardId, existing);\n return existing;\n }\n\n const shard = await this.directory.getShard(shardId);\n if (!shard) {\n throw new Error(`[ShardRouter] unknown shard '${shardId}' — not in the shard catalog`);\n }\n\n const client = this.factory(shard.connectionString, shardId);\n this.clients.set(shardId, client);\n logger.info('[ShardRouter] shard client created', { shardId, layer: shard.layer });\n\n // LRU eviction (never triggers with a single shard).\n if (this.clients.size > this.maxClients) {\n const oldest = this.clients.keys().next().value;\n if (oldest !== undefined && oldest !== shardId) {\n this.clients.delete(oldest);\n logger.info('[ShardRouter] evicted LRU shard client', { shardId: oldest });\n }\n }\n\n return client;\n }\n\n /** Currently-cached shard ids (for diagnostics/tests). */\n cachedShardIds(): ShardId[] {\n return Array.from(this.clients.keys());\n }\n}\n\n/**\n * Build a {@link ShardRouter} from environment, defaulting to single-shard\n * passthrough. Reads:\n * - `DATABASE_URL` (required): the default shard's connection string.\n * - `DEFAULT_SHARD_ID` (default 'shard-01').\n * - `SHARDING_ENABLED` (default 'false'): when false, uses the single-shard\n * {@link StaticShardDirectory}; when true, uses {@link EnvShardDirectory}\n * (which still passes through unless `SHARD_CATALOG`/`TENANT_SHARD_MAP` are set).\n *\n * With defaults, this is a behavior-preserving indirection over the current\n * single database — the safe \"Step 1\" of horizontal scale-out.\n */\nexport function createShardRouterFromEnv<TClient>(\n factory: ShardedClientFactory<TClient>,\n options?: ShardRouterOptions,\n env: NodeJS.ProcessEnv = process.env\n): ShardRouter<TClient> {\n const databaseUrl = env['DATABASE_URL'];\n if (!databaseUrl) {\n throw new Error('[ShardRouter] DATABASE_URL is required to build the default shard');\n }\n const defaultShardId = env['DEFAULT_SHARD_ID']?.trim() || 'shard-01';\n const enabled = (env['SHARDING_ENABLED'] ?? 'false').toLowerCase() === 'true';\n\n const directory: ShardDirectory = enabled\n ? new EnvShardDirectory(defaultShardId, databaseUrl, env)\n : new StaticShardDirectory(defaultShardId, databaseUrl);\n\n return new ShardRouter<TClient>(directory, factory, options);\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/be-sdk",
3
- "version": "2026.703.5",
3
+ "version": "2026.704.1",
4
4
  "description": "Backend SDK for Burdenoff products providing unified access to notifications, RBAC, activity tracking, usage monitoring, and more",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -71,8 +71,8 @@
71
71
  "deps:status": "bun scripts/deps-mode.ts status",
72
72
  "deps:workspace": "bun scripts/deps-mode.ts workspace",
73
73
  "dev": "tsx watch ./src/index.ts",
74
- "build": "NODE_OPTIONS=--max-old-space-size=4096 tsup src/index.ts src/init/index.ts src/logger/index.ts src/cache/index.ts src/database/index.ts src/event-queue/index.ts src/graph/index.ts src/middleware/index.ts src/observability/index.ts src/graphql/index.ts src/graphql/services/index.ts src/prisma/index.ts src/utils/index.ts src/notifications/index.ts src/tracker/index.ts src/export/index.ts src/dataloader/index.ts src/prisma/generator/index.ts src/server/index.ts src/i18n/index.ts src/instrumentation/index.ts src/rbac/index.ts src/temporal/index.ts src/client/internal-client.ts src/layer/index.ts src/gateway/index.ts src/gateway/security/index.ts src/crypto/index.ts src/feature-flags/index.ts src/telemetry/index.ts src/auth/index.ts src/captcha/index.ts src/metrics/index.ts src/platform/index.ts src/resource-caps/index.ts src/sharing/index.ts --format esm --dts --clean --shims",
75
- "build:watch": "tsup src/index.ts src/init/index.ts src/logger/index.ts src/cache/index.ts src/database/index.ts src/event-queue/index.ts src/graph/index.ts src/middleware/index.ts src/observability/index.ts src/graphql/index.ts src/graphql/services/index.ts src/prisma/index.ts src/utils/index.ts src/notifications/index.ts src/tracker/index.ts src/export/index.ts src/dataloader/index.ts src/prisma/generator/index.ts src/server/index.ts src/i18n/index.ts src/instrumentation/index.ts src/rbac/index.ts src/temporal/index.ts src/client/internal-client.ts src/layer/index.ts src/gateway/index.ts src/gateway/security/index.ts src/crypto/index.ts src/feature-flags/index.ts src/telemetry/index.ts src/auth/index.ts src/captcha/index.ts src/metrics/index.ts src/platform/index.ts src/resource-caps/index.ts src/sharing/index.ts --format esm --dts --watch --shims",
74
+ "build": "NODE_OPTIONS=--max-old-space-size=4096 tsup src/index.ts src/init/index.ts src/logger/index.ts src/cache/index.ts src/database/index.ts src/event-queue/index.ts src/graph/index.ts src/middleware/index.ts src/observability/index.ts src/graphql/index.ts src/graphql/services/index.ts src/prisma/index.ts src/utils/index.ts src/notifications/index.ts src/tracker/index.ts src/export/index.ts src/dataloader/index.ts src/prisma/generator/index.ts src/server/index.ts src/i18n/index.ts src/instrumentation/index.ts src/rbac/index.ts src/temporal/index.ts src/client/internal-client.ts src/layer/index.ts src/gateway/index.ts src/gateway/security/index.ts src/crypto/index.ts src/feature-flags/index.ts src/telemetry/index.ts src/auth/index.ts src/captcha/index.ts src/metrics/index.ts src/platform/index.ts src/resource-caps/index.ts src/sharding/index.ts src/sharing/index.ts --format esm --dts --clean --shims",
75
+ "build:watch": "tsup src/index.ts src/init/index.ts src/logger/index.ts src/cache/index.ts src/database/index.ts src/event-queue/index.ts src/graph/index.ts src/middleware/index.ts src/observability/index.ts src/graphql/index.ts src/graphql/services/index.ts src/prisma/index.ts src/utils/index.ts src/notifications/index.ts src/tracker/index.ts src/export/index.ts src/dataloader/index.ts src/prisma/generator/index.ts src/server/index.ts src/i18n/index.ts src/instrumentation/index.ts src/rbac/index.ts src/temporal/index.ts src/client/internal-client.ts src/layer/index.ts src/gateway/index.ts src/gateway/security/index.ts src/crypto/index.ts src/feature-flags/index.ts src/telemetry/index.ts src/auth/index.ts src/captcha/index.ts src/metrics/index.ts src/platform/index.ts src/resource-caps/index.ts src/sharding/index.ts src/sharing/index.ts --format esm --dts --watch --shims",
76
76
  "typecheck": "tsc --noEmit",
77
77
  "lint": "eslint ./src",
78
78
  "lint:sanity": "if [ \"${CI:-}\" = \"true\" ]; then bun run lint; else bun run lint -- --cache --cache-location .eslintcache; fi",