nucleus-core-ts 0.10.4 → 0.10.5

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/.build-ok CHANGED
@@ -1 +1 @@
1
- 0.10.4
1
+ 0.10.5
package/dist/index.js CHANGED
@@ -606,4 +606,4 @@ ${content}
606
606
  <script id="api-reference" data-url="${forHtml(documentUrl)}"></script>
607
607
  <script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
608
608
  </body>
609
- </html>`}function chainGuards(...guards){return async(context)=>{for(let guard5 of guards){let early=await guard5(context);if(early!==void 0)return early}return}}function docsGroup(groups,config){let swagger=config.swagger;if(swagger?.enabled===!1)return null;let base=swagger?.path??"/swagger",info=swagger?.documentation?.info,title=info?.title??"Nucleus API",description=info?.description??"Auto-generated API documentation",document2=buildOpenApiDocument(groups,swagger),page=viewerHtml(`${base}/json`,title,description);return{prefix:base,routes:[defineRoute({method:"GET",path:"/",detail:{summary:"API documentation",tags:["Documentation"]},handler:()=>new Response(page,{headers:{"content-type":"text/html; charset=utf8"}})}),defineRoute({method:"GET",path:"/json",detail:{summary:"OpenAPI document",tags:["Documentation"]},handler:()=>document2})]}}async function createNucleusServer(options){await buildNucleus(options.config);let groups=getMountedRouteGroups();if(groups.length===0)throw Error("createNucleusServer: the plugin mounted no route groups. That is a wiring "+"fault, not an empty install \u2014 refusing to serve rather than answering 404 to everything.");let resolved=getResolvedOptions2(),rawOptions=options.config.options,securityOf=(candidate)=>typeof candidate==="object"&&candidate!==null?candidate.security?.maxRequestBodyBytes:void 0,maxRequestBodyBytes=securityOf(resolved)??securityOf(rawOptions),storageCeiling=requiredBodyCeiling({storageEnabled:resolved?.storage?.enabled,maxFileSizeBytes:resolved?.storage?.maxFileSizeBytes,maxFiles:resolved?.storage?.formData?.maxFiles}),bunBodyCeiling=bunBodyCeilingFor({storageRequired:storageCeiling,guardCeiling:maxRequestBodyBytes});for(let step of getBootSteps())await step({config:{serve:{maxRequestBodySize:bunBodyCeiling}}});let hostRoutes=options.routes??[],docs=docsGroup([...groups,...hostRoutes],options.config);return serve({groups:[...groups,...docs?[docs]:[],...hostRoutes],maxRequestBodyBytes:bunBodyCeiling,onStop:[...getShutdownSteps(),...options.onStop??[]],socket:options.socket??getMountedSocket()??void 0,staticAssets:(()=>{let directory=getMountedStaticAssets();return directory?{prefix:"/nucleus-core",directory}:void 0})(),onRequest:chainGuards(...options.onRequest?[options.onRequest]:[],createInboundGuard({maxRequestBodyBytes}),getAuthMiddleware()),onError:getErrorHandler(),onAfter:getAfterHandler()??void 0,context:(()=>{let runtime=getMountedRuntime2();return runtime?{nucleus:runtime}:void 0})(),port:options.port,hostname:options.hostname})}init_AzureEmailService();init_Gmail();export{useUpload,usePubSubStore,usePubSub,uploadWithProgress,uploadPercent,uploadFraction,serverFetch,secondsRemaining,requiredBodyCeiling,parseCron2 as parseCron,matchesCron,isDue,generateVerificationEndpoints,generateTenantEndpoints,generateSystemTableEndpoints,generateMonitoringEndpoints,generateMarketplaceEndpoints,generateEndpointsFromConfig,generateDomainEndpoints,generateCohortEndpoints,generateChatEndpoints,generateAuthEndpoints,generateAllEndpoints,generateAdminEndpoints,describeCron,decodeHeaderList,createServerFactory,createRateWindow,createNucleusServer,createApiHook,buildProgress,buildNucleus,VERIFICATION_ENDPOINTS,TENANT_ENDPOINTS,ServerFetch,SYSTEM_TABLES,PAYMENT_ENDPOINTS,MONITORING_ENDPOINTS,MARKETPLACE_ENDPOINTS,GmailService,DOMAIN_ENDPOINTS,CONFIG_ENDPOINTS,COHORT_ENDPOINTS,CHAT_ENDPOINTS,AzureEmailService,AUTH_ENDPOINT_CONFIGS,AUTH_ENDPOINTS};
609
+ </html>`}function chainGuards(...guards){return async(context)=>{for(let guard5 of guards){let early=await guard5(context);if(early!==void 0)return early}return}}function docsGroup(groups,config){let swagger=config.swagger;if(swagger?.enabled===!1)return null;let base=swagger?.path??"/swagger",info=swagger?.documentation?.info,title=info?.title??"Nucleus API",description=info?.description??"Auto-generated API documentation",document2=buildOpenApiDocument(groups,swagger),page=viewerHtml(`${base}/json`,title,description);return{prefix:base,routes:[defineRoute({method:"GET",path:"/",detail:{summary:"API documentation",tags:["Documentation"]},handler:()=>new Response(page,{headers:{"content-type":"text/html; charset=utf8"}})}),defineRoute({method:"GET",path:"/json",detail:{summary:"OpenAPI document",tags:["Documentation"]},handler:()=>document2})]}}async function createNucleusServer(options){await buildNucleus(options.config);let groups=getMountedRouteGroups();if(groups.length===0)throw Error("createNucleusServer: the plugin mounted no route groups. That is a wiring "+"fault, not an empty install \u2014 refusing to serve rather than answering 404 to everything.");let resolved=getResolvedOptions2(),rawOptions=options.config.options,securityOf=(candidate)=>typeof candidate==="object"&&candidate!==null?candidate.security?.maxRequestBodyBytes:void 0,maxRequestBodyBytes=securityOf(resolved)??securityOf(rawOptions),storageCeiling=requiredBodyCeiling({storageEnabled:resolved?.storage?.enabled,maxFileSizeBytes:resolved?.storage?.maxFileSizeBytes,maxFiles:resolved?.storage?.formData?.maxFiles}),bunBodyCeiling=bunBodyCeilingFor({storageRequired:storageCeiling,guardCeiling:maxRequestBodyBytes});for(let step of getBootSteps())await step({config:{serve:{maxRequestBodySize:bunBodyCeiling}}});let hostRoutes=options.routes??[],resolvedSwagger=resolved?.swagger,outerSwagger=options.config.swagger,docs=docsGroup([...groups,...hostRoutes],{swagger:outerSwagger??resolvedSwagger});return serve({groups:[...groups,...docs?[docs]:[],...hostRoutes],maxRequestBodyBytes:bunBodyCeiling,onStop:[...getShutdownSteps(),...options.onStop??[]],socket:options.socket??getMountedSocket()??void 0,staticAssets:(()=>{let directory=getMountedStaticAssets();return directory?{prefix:"/nucleus-core",directory}:void 0})(),onRequest:chainGuards(...options.onRequest?[options.onRequest]:[],createInboundGuard({maxRequestBodyBytes}),getAuthMiddleware()),onError:getErrorHandler(),onAfter:getAfterHandler()??void 0,context:(()=>{let runtime=getMountedRuntime2();return runtime?{nucleus:runtime}:void 0})(),port:options.port,hostname:options.hostname})}init_AzureEmailService();init_Gmail();export{useUpload,usePubSubStore,usePubSub,uploadWithProgress,uploadPercent,uploadFraction,serverFetch,secondsRemaining,requiredBodyCeiling,parseCron2 as parseCron,matchesCron,isDue,generateVerificationEndpoints,generateTenantEndpoints,generateSystemTableEndpoints,generateMonitoringEndpoints,generateMarketplaceEndpoints,generateEndpointsFromConfig,generateDomainEndpoints,generateCohortEndpoints,generateChatEndpoints,generateAuthEndpoints,generateAllEndpoints,generateAdminEndpoints,describeCron,decodeHeaderList,createServerFactory,createRateWindow,createNucleusServer,createApiHook,buildProgress,buildNucleus,VERIFICATION_ENDPOINTS,TENANT_ENDPOINTS,ServerFetch,SYSTEM_TABLES,PAYMENT_ENDPOINTS,MONITORING_ENDPOINTS,MARKETPLACE_ENDPOINTS,GmailService,DOMAIN_ENDPOINTS,CONFIG_ENDPOINTS,COHORT_ENDPOINTS,CHAT_ENDPOINTS,AzureEmailService,AUTH_ENDPOINT_CONFIGS,AUTH_ENDPOINTS};
package/dist/server.js CHANGED
@@ -606,4 +606,4 @@ ${content}
606
606
  <script id="api-reference" data-url="${forHtml(documentUrl)}"></script>
607
607
  <script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
608
608
  </body>
609
- </html>`}function chainGuards(...guards){return async(context)=>{for(let guard5 of guards){let early=await guard5(context);if(early!==void 0)return early}return}}function docsGroup(groups,config){let swagger=config.swagger;if(swagger?.enabled===!1)return null;let base=swagger?.path??"/swagger",info=swagger?.documentation?.info,title=info?.title??"Nucleus API",description=info?.description??"Auto-generated API documentation",document=buildOpenApiDocument(groups,swagger),page=viewerHtml(`${base}/json`,title,description);return{prefix:base,routes:[defineRoute({method:"GET",path:"/",detail:{summary:"API documentation",tags:["Documentation"]},handler:()=>new Response(page,{headers:{"content-type":"text/html; charset=utf8"}})}),defineRoute({method:"GET",path:"/json",detail:{summary:"OpenAPI document",tags:["Documentation"]},handler:()=>document})]}}async function createNucleusServer(options){await buildNucleus(options.config);let groups=getMountedRouteGroups();if(groups.length===0)throw Error("createNucleusServer: the plugin mounted no route groups. That is a wiring "+"fault, not an empty install \u2014 refusing to serve rather than answering 404 to everything.");let resolved=getResolvedOptions2(),rawOptions=options.config.options,securityOf=(candidate)=>typeof candidate==="object"&&candidate!==null?candidate.security?.maxRequestBodyBytes:void 0,maxRequestBodyBytes=securityOf(resolved)??securityOf(rawOptions),storageCeiling=requiredBodyCeiling({storageEnabled:resolved?.storage?.enabled,maxFileSizeBytes:resolved?.storage?.maxFileSizeBytes,maxFiles:resolved?.storage?.formData?.maxFiles}),bunBodyCeiling=bunBodyCeilingFor({storageRequired:storageCeiling,guardCeiling:maxRequestBodyBytes});for(let step of getBootSteps())await step({config:{serve:{maxRequestBodySize:bunBodyCeiling}}});let hostRoutes=options.routes??[],docs=docsGroup([...groups,...hostRoutes],options.config);return serve({groups:[...groups,...docs?[docs]:[],...hostRoutes],maxRequestBodyBytes:bunBodyCeiling,onStop:[...getShutdownSteps(),...options.onStop??[]],socket:options.socket??getMountedSocket()??void 0,staticAssets:(()=>{let directory=getMountedStaticAssets();return directory?{prefix:"/nucleus-core",directory}:void 0})(),onRequest:chainGuards(...options.onRequest?[options.onRequest]:[],createInboundGuard({maxRequestBodyBytes}),getAuthMiddleware()),onError:getErrorHandler(),onAfter:getAfterHandler()??void 0,context:(()=>{let runtime=getMountedRuntime2();return runtime?{nucleus:runtime}:void 0})(),port:options.port,hostname:options.hostname})}init_routeCollector();init_websocket();export{serve,defineWebSocketRoute,defineRoute,decodeHeaderList,createNucleusServer,collectRoutes,buildOpenApiDocument,buildNucleus};
609
+ </html>`}function chainGuards(...guards){return async(context)=>{for(let guard5 of guards){let early=await guard5(context);if(early!==void 0)return early}return}}function docsGroup(groups,config){let swagger=config.swagger;if(swagger?.enabled===!1)return null;let base=swagger?.path??"/swagger",info=swagger?.documentation?.info,title=info?.title??"Nucleus API",description=info?.description??"Auto-generated API documentation",document=buildOpenApiDocument(groups,swagger),page=viewerHtml(`${base}/json`,title,description);return{prefix:base,routes:[defineRoute({method:"GET",path:"/",detail:{summary:"API documentation",tags:["Documentation"]},handler:()=>new Response(page,{headers:{"content-type":"text/html; charset=utf8"}})}),defineRoute({method:"GET",path:"/json",detail:{summary:"OpenAPI document",tags:["Documentation"]},handler:()=>document})]}}async function createNucleusServer(options){await buildNucleus(options.config);let groups=getMountedRouteGroups();if(groups.length===0)throw Error("createNucleusServer: the plugin mounted no route groups. That is a wiring "+"fault, not an empty install \u2014 refusing to serve rather than answering 404 to everything.");let resolved=getResolvedOptions2(),rawOptions=options.config.options,securityOf=(candidate)=>typeof candidate==="object"&&candidate!==null?candidate.security?.maxRequestBodyBytes:void 0,maxRequestBodyBytes=securityOf(resolved)??securityOf(rawOptions),storageCeiling=requiredBodyCeiling({storageEnabled:resolved?.storage?.enabled,maxFileSizeBytes:resolved?.storage?.maxFileSizeBytes,maxFiles:resolved?.storage?.formData?.maxFiles}),bunBodyCeiling=bunBodyCeilingFor({storageRequired:storageCeiling,guardCeiling:maxRequestBodyBytes});for(let step of getBootSteps())await step({config:{serve:{maxRequestBodySize:bunBodyCeiling}}});let hostRoutes=options.routes??[],resolvedSwagger=resolved?.swagger,outerSwagger=options.config.swagger,docs=docsGroup([...groups,...hostRoutes],{swagger:outerSwagger??resolvedSwagger});return serve({groups:[...groups,...docs?[docs]:[],...hostRoutes],maxRequestBodyBytes:bunBodyCeiling,onStop:[...getShutdownSteps(),...options.onStop??[]],socket:options.socket??getMountedSocket()??void 0,staticAssets:(()=>{let directory=getMountedStaticAssets();return directory?{prefix:"/nucleus-core",directory}:void 0})(),onRequest:chainGuards(...options.onRequest?[options.onRequest]:[],createInboundGuard({maxRequestBodyBytes}),getAuthMiddleware()),onError:getErrorHandler(),onAfter:getAfterHandler()??void 0,context:(()=>{let runtime=getMountedRuntime2();return runtime?{nucleus:runtime}:void 0})(),port:options.port,hostname:options.hostname})}init_routeCollector();init_websocket();export{serve,defineWebSocketRoute,defineRoute,decodeHeaderList,createNucleusServer,collectRoutes,buildOpenApiDocument,buildNucleus};
@@ -3,6 +3,10 @@ interface RefreshResult {
3
3
  accessToken: string;
4
4
  setCookieHeaders: string[];
5
5
  }
6
+ /** How long a definitive failure suppresses another attempt. */
7
+ export declare const REFRESH_FAILURE_MEMORY_MS = 5000;
8
+ /** Exposed for tests; also called when a refresh succeeds. */
9
+ export declare function clearRefreshFailure(dedupeKey: string): void;
6
10
  export declare function deduplicatedRefresh(refreshConfig: TokenRefreshConfig, refreshToken: string, logger: {
7
11
  info: (...args: unknown[]) => void;
8
12
  warn: (...args: unknown[]) => void;
@@ -117,6 +117,32 @@ async function performTokenRefresh(refreshConfig, refreshToken, logger, original
117
117
  return null;
118
118
  }
119
119
  }
120
+ /**
121
+ * A refresh that DEFINITIVELY failed, remembered just long enough to stop the
122
+ * next twenty requests from asking again.
123
+ *
124
+ * `pendingRefreshes` collapses refreshes that overlap in time; the moment one
125
+ * settles its key is dropped, so a page whose widgets fire in sequence gets a
126
+ * fresh attempt per widget. Measured on a live install with a revoked session:
127
+ * 16 refresh calls in one minute and 51 over ten, every one answered 401, every
128
+ * one a round trip to the auth service for an answer that could not change.
129
+ *
130
+ * Keyed by the same token tail as the dedupe, so a NEW sign-in — a different
131
+ * token — is never suppressed. Short-lived on purpose: this is here to collapse
132
+ * one page's burst, not to cache a verdict. Anything longer would turn a
133
+ * transient blip into a sign-out the user cannot retry their way out of.
134
+ */ const failedRefreshes = new Map();
135
+ /** How long a definitive failure suppresses another attempt. */ export const REFRESH_FAILURE_MEMORY_MS = 5000;
136
+ /** Exposed for tests; also called when a refresh succeeds. */ export function clearRefreshFailure(dedupeKey) {
137
+ failedRefreshes.delete(dedupeKey);
138
+ }
139
+ function refreshRecentlyFailed(dedupeKey, nowMs) {
140
+ const failedAt = failedRefreshes.get(dedupeKey);
141
+ if (failedAt === undefined) return false;
142
+ if (nowMs - failedAt <= REFRESH_FAILURE_MEMORY_MS) return true;
143
+ failedRefreshes.delete(dedupeKey);
144
+ return false;
145
+ }
120
146
  export function deduplicatedRefresh(refreshConfig, refreshToken, logger, originalCookieHeader) {
121
147
  const dedupeKey = refreshToken.slice(-16);
122
148
  const existing = pendingRefreshes.get(dedupeKey);
@@ -124,7 +150,22 @@ export function deduplicatedRefresh(refreshConfig, refreshToken, logger, origina
124
150
  logger.info('[TokenRefresh] Deduplicating concurrent refresh');
125
151
  return existing;
126
152
  }
127
- const promise = performTokenRefresh(refreshConfig, refreshToken, logger, originalCookieHeader).finally(()=>{
153
+ if (refreshRecentlyFailed(dedupeKey, Date.now())) {
154
+ // Said once per window rather than per request: the point of this branch is
155
+ // to stop a burst, and a log line per suppressed attempt would reproduce the
156
+ // noise in the log that the requests made on the wire.
157
+ return Promise.resolve(null);
158
+ }
159
+ const promise = performTokenRefresh(refreshConfig, refreshToken, logger, originalCookieHeader).then((result)=>{
160
+ /*
161
+ * `null` is the definitive answer — `performTokenRefresh` returns it when
162
+ * the auth service refused, not when the call blew up (that rejects). So
163
+ * remembering it here suppresses only refusals, and a network fault still
164
+ * gets retried by the next request.
165
+ */ if (result === null) failedRefreshes.set(dedupeKey, Date.now());
166
+ else clearRefreshFailure(dedupeKey);
167
+ return result;
168
+ }).finally(()=>{
128
169
  pendingRefreshes.delete(dedupeKey);
129
170
  });
130
171
  pendingRefreshes.set(dedupeKey, promise);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nucleus-core-ts",
3
- "version": "0.10.4",
3
+ "version": "0.10.5",
4
4
  "description": "Production-ready, enterprise-grade TypeScript framework for building multi-tenant APIs",
5
5
  "author": "Hidayet Can Özcan <hidayetcan@gmail.com>",
6
6
  "license": "SEE LICENSE IN LICENSE",