@aurabase/core 0.4.0 → 0.4.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.cjs CHANGED
@@ -235,6 +235,56 @@ function unwrapEnvelope(body) {
235
235
  }
236
236
  return body;
237
237
  }
238
+ async function readTextBounded(res, timeoutMs) {
239
+ const body = res.body;
240
+ if (!body || typeof body.getReader !== "function" || !(timeoutMs > 0)) {
241
+ try {
242
+ return { ok: true, text: await res.text() };
243
+ } catch (err) {
244
+ return {
245
+ ok: false,
246
+ timedOut: false,
247
+ message: err instanceof Error ? err.message : "read failed"
248
+ };
249
+ }
250
+ }
251
+ let timer;
252
+ const TIMED_OUT = /* @__PURE__ */ Symbol("body-timeout");
253
+ try {
254
+ const reader = body.getReader();
255
+ const decoder = new TextDecoder();
256
+ let out = "";
257
+ const deadline = new Promise((resolve) => {
258
+ timer = setTimeout(() => resolve(TIMED_OUT), timeoutMs);
259
+ timer.unref?.();
260
+ });
261
+ for (; ; ) {
262
+ const step = await Promise.race([reader.read(), deadline]);
263
+ if (step === TIMED_OUT) {
264
+ await reader.cancel().catch(() => {
265
+ });
266
+ return {
267
+ ok: false,
268
+ timedOut: true,
269
+ message: `Response body not fully received within ${timeoutMs}ms`
270
+ };
271
+ }
272
+ if (step.done) {
273
+ out += decoder.decode();
274
+ return { ok: true, text: out };
275
+ }
276
+ if (step.value) out += decoder.decode(step.value, { stream: true });
277
+ }
278
+ } catch (err) {
279
+ return {
280
+ ok: false,
281
+ timedOut: false,
282
+ message: err instanceof Error ? err.message : "read failed"
283
+ };
284
+ } finally {
285
+ if (timer) clearTimeout(timer);
286
+ }
287
+ }
238
288
  var inflightRefreshes = /* @__PURE__ */ new WeakMap();
239
289
  async function doRefresh(client) {
240
290
  const store = client.authStore;
@@ -248,26 +298,57 @@ async function doRefresh(client) {
248
298
  const epoch = store.removalEpoch;
249
299
  const headers = { "Content-Type": "application/json" };
250
300
  if (client.apiKey) headers["apikey"] = client.apiKey;
301
+ const timeoutMs = client.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
302
+ let timedOut = false;
303
+ let refreshTimer;
304
+ let refreshSignal;
305
+ if (timeoutMs > 0 && typeof AbortController !== "undefined") {
306
+ const ctrl = new AbortController();
307
+ refreshTimer = setTimeout(() => {
308
+ timedOut = true;
309
+ ctrl.abort();
310
+ }, timeoutMs);
311
+ refreshTimer.unref?.();
312
+ refreshSignal = ctrl.signal;
313
+ }
314
+ const clearRefreshTimer = () => {
315
+ if (refreshTimer) {
316
+ clearTimeout(refreshTimer);
317
+ refreshTimer = void 0;
318
+ }
319
+ };
251
320
  let res;
252
321
  try {
253
322
  res = await client.fetchFn(`${client.baseUrl}/v1/auth/${client.projectId}/refresh`, {
254
323
  method: "POST",
255
324
  headers,
256
- body: serializeBody({ refresh_token: rt })
325
+ body: serializeBody({ refresh_token: rt }),
326
+ signal: refreshSignal
257
327
  });
258
328
  } catch (err) {
329
+ clearRefreshTimer();
330
+ if (timedOut) {
331
+ emitAuraEvent(client.onEvent, client.debug, {
332
+ kind: "timeout",
333
+ url: `${client.baseUrl}/v1/auth/${client.projectId}/refresh`
334
+ });
335
+ }
259
336
  return {
260
337
  data: null,
261
338
  error: new AuraError(0, {
262
- error: { code: "network_error", message: err instanceof Error ? err.message : "Network request failed" }
339
+ error: timedOut ? { code: "timeout", message: `Refresh timed out after ${timeoutMs}ms` } : { code: "network_error", message: err instanceof Error ? err.message : "Network request failed" }
263
340
  })
264
341
  };
265
342
  }
343
+ clearRefreshTimer();
266
344
  if (!res.ok) {
267
345
  let body2 = null;
268
- try {
269
- body2 = await res.json();
270
- } catch {
346
+ const errRead = await readTextBounded(res, timeoutMs);
347
+ if (errRead.ok && errRead.text.trim() !== "") {
348
+ try {
349
+ body2 = JSON.parse(errRead.text);
350
+ } catch {
351
+ }
271
352
  }
272
353
  const code = typeof body2?.error?.code === "string" ? body2.error.code : null;
273
354
  const definitivelyRejected = res.status === 403 || res.status === 401 && code === "REFRESH_TOKEN_REJECTED";
@@ -279,8 +360,23 @@ async function doRefresh(client) {
279
360
  return { data: null, error: new AuraError(res.status, body2) };
280
361
  }
281
362
  let body;
363
+ const okRead = await readTextBounded(res, timeoutMs);
364
+ if (!okRead.ok) {
365
+ if (okRead.timedOut) {
366
+ emitAuraEvent(client.onEvent, client.debug, {
367
+ kind: "timeout",
368
+ url: `${client.baseUrl}/v1/auth/${client.projectId}/refresh`
369
+ });
370
+ }
371
+ return {
372
+ data: null,
373
+ error: new AuraError(0, {
374
+ error: okRead.timedOut ? { code: "timeout", message: `Refresh body not received within ${timeoutMs}ms` } : { code: "invalid_response", message: `Invalid refresh response (${okRead.message})` }
375
+ })
376
+ };
377
+ }
282
378
  try {
283
- body = await res.json();
379
+ body = JSON.parse(okRead.text);
284
380
  } catch {
285
381
  return {
286
382
  data: null,
@@ -373,6 +469,7 @@ async function fetchWithAuth(url, init, client) {
373
469
  const isFormData = typeof FormData !== "undefined" && init.body instanceof FormData;
374
470
  const timeoutMs = client.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
375
471
  const applyTimeout = timeoutMs > 0 && init.signal === void 0 && !isFormData;
472
+ const bodyTimeoutMs = init.signal === void 0 ? timeoutMs : 0;
376
473
  let attempt = 0;
377
474
  for (; ; ) {
378
475
  const headers = { ...init.headers };
@@ -462,26 +559,42 @@ async function fetchWithAuth(url, init, client) {
462
559
  }
463
560
  if (!res.ok) {
464
561
  let body2 = null;
465
- try {
466
- body2 = await res.json();
467
- } catch {
562
+ const errRead = await readTextBounded(res, bodyTimeoutMs);
563
+ if (errRead.ok && errRead.text.trim() !== "") {
564
+ try {
565
+ body2 = JSON.parse(errRead.text);
566
+ } catch {
567
+ }
568
+ } else if (!errRead.ok && errRead.timedOut) {
569
+ emitAuraEvent(client.onEvent, client.debug, { kind: "timeout", url });
468
570
  }
469
571
  return { data: null, error: new AuraError(res.status, body2) };
470
572
  }
471
- let raw;
472
- try {
473
- raw = await res.text();
474
- } catch (err) {
573
+ const read = await readTextBounded(res, bodyTimeoutMs);
574
+ if (!read.ok) {
575
+ if (read.timedOut) {
576
+ emitAuraEvent(client.onEvent, client.debug, { kind: "timeout", url });
577
+ return {
578
+ data: null,
579
+ error: new AuraError(0, {
580
+ error: {
581
+ code: "timeout",
582
+ message: `Response body not fully received within ${bodyTimeoutMs}ms (HTTP ${res.status})`
583
+ }
584
+ })
585
+ };
586
+ }
475
587
  return {
476
588
  data: null,
477
589
  error: new AuraError(0, {
478
590
  error: {
479
591
  code: "invalid_response",
480
- message: `Unreadable response body (HTTP ${res.status}): ${err instanceof Error ? err.message : "read failed"}`
592
+ message: `Unreadable response body (HTTP ${res.status}): ${read.message}`
481
593
  }
482
594
  })
483
595
  };
484
596
  }
597
+ const raw = read.text;
485
598
  if (raw.trim() === "") {
486
599
  return { data: null, error: null };
487
600
  }
@@ -709,6 +822,7 @@ exports.AuraError = AuraError;
709
822
  exports.BaseAuthStore = BaseAuthStore;
710
823
  exports.BaseService = BaseService;
711
824
  exports.DEFAULT_MAX_RETRIES = DEFAULT_MAX_RETRIES;
825
+ exports.DEFAULT_REQUEST_TIMEOUT_MS = DEFAULT_REQUEST_TIMEOUT_MS;
712
826
  exports.LocalAuthStore = LocalAuthStore;
713
827
  exports.RETRYABLE_METHODS = RETRYABLE_METHODS;
714
828
  exports.RETRYABLE_STATUS = RETRYABLE_STATUS;
package/dist/index.d.cts CHANGED
@@ -1098,6 +1098,8 @@ interface AuraClientLike {
1098
1098
  readonly debug?: boolean;
1099
1099
  }
1100
1100
 
1101
+ /** Timeout par défaut d'une requête HTTP (borne un serveur qui « hang »). */
1102
+ declare const DEFAULT_REQUEST_TIMEOUT_MS = 30000;
1101
1103
  /** Point d'entrée unique du refresh, dédupliqué par client (single-flight). */
1102
1104
  declare function refreshOnce(client: AuraClientLike): Promise<AuraResponse<TokenPair>>;
1103
1105
  interface FetchInit {
@@ -1228,4 +1230,4 @@ declare function abortableSleep(ms: number, signal?: AbortSignal): Promise<void>
1228
1230
  /** True si l'erreur `fetch` est une annulation utilisateur (jamais rejouée). */
1229
1231
  declare function isAbortError(err: unknown): boolean;
1230
1232
 
1231
- export { type AdminCreateUserRequest, type AdminSessionInfo, type AdminUpdateUserRequest, type AdminUser, type AuditLogEntry, type AuraClientLike, type AuraClientOptions, AuraError, type AuraErrorCode, type AuraFailure, type AuraNoContent, type AuraObservabilityEvent, type AuraResponse, type AuraSuccess, type AuthChangePayload, type AuthEvent, type AuthSettings, BaseAuthStore, BaseService, type BatchSendItemResult, type BatchSendResponse, type BroadcastAckMessage, type BroadcastFilter, type BroadcastPayload, type CdcPayload, type ChannelConfig, type ChatMessage, type ChatResponse, type CountMode, type CronJob, DEFAULT_MAX_RETRIES, type EmailTemplateUpdate, type EmbedRequest, type EmbedResponse, type FetchInit, type FileEntry, type FilterOp, type FunctionDef, type GenerateLinkResult, type GenerateLinkType, type GetClaimsResult, type IdentityInfo, type ImageTransform, type Job, type JwtClaims, LocalAuthStore, type LoginResponse, type MagicLinkRequest, type Meta, type MfaChallenge, type MfaEnrollResponse, type MfaVerifyRequest, type Notification, type NotificationAttempt, type NotificationChannelStats, type NotificationDeadLetter, type NotificationEvent, type NotificationProviderLog, type NotificationQueueStats, type NotificationRule, type NotificationSettings, type NotificationStats, type NotificationTemplate, type NotificationTemplatePreview, type OAuthSignInOptions, type ObjectMeta, type PasswordResetRequest, type PostgresChangesFilter, type PresenceFilter, type PresenceInfo, type PresenceState, type QueryFilter, type QueryOptions, RETRYABLE_METHODS, RETRYABLE_STATUS, type RagIngestRequest, type RagIngestResponse, type RagRequest, type RagResponse, type RealtimeEvent, type RealtimeListenType, type RealtimeSubscribeStatus, type SchemaColumn, type SchemaEnum, type SchemaFunction, type SchemaFunctionArg, type SchemaIndex, type SchemaTable, type SemanticSearchResult, type SendNotificationRequest, type SerializeCookieOptions, type ServerErrorMsg, type ServerEventMsg, type ServerMessage, type ServerPongMsg, type ServerPresenceStateMsg, type ServerSubscribedMsg, type ServerTokenRefreshedMsg, type SessionInfo, type SignInRequest, type SignUpRequest, type StorageBucket, type SubscribeCallback, type TokenPair, type UpdateUserRequest, type UploadResult, type User, type UserStats, abortableSleep, computeBackoff, emitAuraEvent, fetchWithAuth, isAbortError, isAuraError, isRetryableMethod, isRetryableStatus, isTokenExpired, parseCookieHeader, parseRetryAfter, refreshOnce, serializeBody, serializeCookieHeader };
1233
+ export { type AdminCreateUserRequest, type AdminSessionInfo, type AdminUpdateUserRequest, type AdminUser, type AuditLogEntry, type AuraClientLike, type AuraClientOptions, AuraError, type AuraErrorCode, type AuraFailure, type AuraNoContent, type AuraObservabilityEvent, type AuraResponse, type AuraSuccess, type AuthChangePayload, type AuthEvent, type AuthSettings, BaseAuthStore, BaseService, type BatchSendItemResult, type BatchSendResponse, type BroadcastAckMessage, type BroadcastFilter, type BroadcastPayload, type CdcPayload, type ChannelConfig, type ChatMessage, type ChatResponse, type CountMode, type CronJob, DEFAULT_MAX_RETRIES, DEFAULT_REQUEST_TIMEOUT_MS, type EmailTemplateUpdate, type EmbedRequest, type EmbedResponse, type FetchInit, type FileEntry, type FilterOp, type FunctionDef, type GenerateLinkResult, type GenerateLinkType, type GetClaimsResult, type IdentityInfo, type ImageTransform, type Job, type JwtClaims, LocalAuthStore, type LoginResponse, type MagicLinkRequest, type Meta, type MfaChallenge, type MfaEnrollResponse, type MfaVerifyRequest, type Notification, type NotificationAttempt, type NotificationChannelStats, type NotificationDeadLetter, type NotificationEvent, type NotificationProviderLog, type NotificationQueueStats, type NotificationRule, type NotificationSettings, type NotificationStats, type NotificationTemplate, type NotificationTemplatePreview, type OAuthSignInOptions, type ObjectMeta, type PasswordResetRequest, type PostgresChangesFilter, type PresenceFilter, type PresenceInfo, type PresenceState, type QueryFilter, type QueryOptions, RETRYABLE_METHODS, RETRYABLE_STATUS, type RagIngestRequest, type RagIngestResponse, type RagRequest, type RagResponse, type RealtimeEvent, type RealtimeListenType, type RealtimeSubscribeStatus, type SchemaColumn, type SchemaEnum, type SchemaFunction, type SchemaFunctionArg, type SchemaIndex, type SchemaTable, type SemanticSearchResult, type SendNotificationRequest, type SerializeCookieOptions, type ServerErrorMsg, type ServerEventMsg, type ServerMessage, type ServerPongMsg, type ServerPresenceStateMsg, type ServerSubscribedMsg, type ServerTokenRefreshedMsg, type SessionInfo, type SignInRequest, type SignUpRequest, type StorageBucket, type SubscribeCallback, type TokenPair, type UpdateUserRequest, type UploadResult, type User, type UserStats, abortableSleep, computeBackoff, emitAuraEvent, fetchWithAuth, isAbortError, isAuraError, isRetryableMethod, isRetryableStatus, isTokenExpired, parseCookieHeader, parseRetryAfter, refreshOnce, serializeBody, serializeCookieHeader };
package/dist/index.d.ts CHANGED
@@ -1098,6 +1098,8 @@ interface AuraClientLike {
1098
1098
  readonly debug?: boolean;
1099
1099
  }
1100
1100
 
1101
+ /** Timeout par défaut d'une requête HTTP (borne un serveur qui « hang »). */
1102
+ declare const DEFAULT_REQUEST_TIMEOUT_MS = 30000;
1101
1103
  /** Point d'entrée unique du refresh, dédupliqué par client (single-flight). */
1102
1104
  declare function refreshOnce(client: AuraClientLike): Promise<AuraResponse<TokenPair>>;
1103
1105
  interface FetchInit {
@@ -1228,4 +1230,4 @@ declare function abortableSleep(ms: number, signal?: AbortSignal): Promise<void>
1228
1230
  /** True si l'erreur `fetch` est une annulation utilisateur (jamais rejouée). */
1229
1231
  declare function isAbortError(err: unknown): boolean;
1230
1232
 
1231
- export { type AdminCreateUserRequest, type AdminSessionInfo, type AdminUpdateUserRequest, type AdminUser, type AuditLogEntry, type AuraClientLike, type AuraClientOptions, AuraError, type AuraErrorCode, type AuraFailure, type AuraNoContent, type AuraObservabilityEvent, type AuraResponse, type AuraSuccess, type AuthChangePayload, type AuthEvent, type AuthSettings, BaseAuthStore, BaseService, type BatchSendItemResult, type BatchSendResponse, type BroadcastAckMessage, type BroadcastFilter, type BroadcastPayload, type CdcPayload, type ChannelConfig, type ChatMessage, type ChatResponse, type CountMode, type CronJob, DEFAULT_MAX_RETRIES, type EmailTemplateUpdate, type EmbedRequest, type EmbedResponse, type FetchInit, type FileEntry, type FilterOp, type FunctionDef, type GenerateLinkResult, type GenerateLinkType, type GetClaimsResult, type IdentityInfo, type ImageTransform, type Job, type JwtClaims, LocalAuthStore, type LoginResponse, type MagicLinkRequest, type Meta, type MfaChallenge, type MfaEnrollResponse, type MfaVerifyRequest, type Notification, type NotificationAttempt, type NotificationChannelStats, type NotificationDeadLetter, type NotificationEvent, type NotificationProviderLog, type NotificationQueueStats, type NotificationRule, type NotificationSettings, type NotificationStats, type NotificationTemplate, type NotificationTemplatePreview, type OAuthSignInOptions, type ObjectMeta, type PasswordResetRequest, type PostgresChangesFilter, type PresenceFilter, type PresenceInfo, type PresenceState, type QueryFilter, type QueryOptions, RETRYABLE_METHODS, RETRYABLE_STATUS, type RagIngestRequest, type RagIngestResponse, type RagRequest, type RagResponse, type RealtimeEvent, type RealtimeListenType, type RealtimeSubscribeStatus, type SchemaColumn, type SchemaEnum, type SchemaFunction, type SchemaFunctionArg, type SchemaIndex, type SchemaTable, type SemanticSearchResult, type SendNotificationRequest, type SerializeCookieOptions, type ServerErrorMsg, type ServerEventMsg, type ServerMessage, type ServerPongMsg, type ServerPresenceStateMsg, type ServerSubscribedMsg, type ServerTokenRefreshedMsg, type SessionInfo, type SignInRequest, type SignUpRequest, type StorageBucket, type SubscribeCallback, type TokenPair, type UpdateUserRequest, type UploadResult, type User, type UserStats, abortableSleep, computeBackoff, emitAuraEvent, fetchWithAuth, isAbortError, isAuraError, isRetryableMethod, isRetryableStatus, isTokenExpired, parseCookieHeader, parseRetryAfter, refreshOnce, serializeBody, serializeCookieHeader };
1233
+ export { type AdminCreateUserRequest, type AdminSessionInfo, type AdminUpdateUserRequest, type AdminUser, type AuditLogEntry, type AuraClientLike, type AuraClientOptions, AuraError, type AuraErrorCode, type AuraFailure, type AuraNoContent, type AuraObservabilityEvent, type AuraResponse, type AuraSuccess, type AuthChangePayload, type AuthEvent, type AuthSettings, BaseAuthStore, BaseService, type BatchSendItemResult, type BatchSendResponse, type BroadcastAckMessage, type BroadcastFilter, type BroadcastPayload, type CdcPayload, type ChannelConfig, type ChatMessage, type ChatResponse, type CountMode, type CronJob, DEFAULT_MAX_RETRIES, DEFAULT_REQUEST_TIMEOUT_MS, type EmailTemplateUpdate, type EmbedRequest, type EmbedResponse, type FetchInit, type FileEntry, type FilterOp, type FunctionDef, type GenerateLinkResult, type GenerateLinkType, type GetClaimsResult, type IdentityInfo, type ImageTransform, type Job, type JwtClaims, LocalAuthStore, type LoginResponse, type MagicLinkRequest, type Meta, type MfaChallenge, type MfaEnrollResponse, type MfaVerifyRequest, type Notification, type NotificationAttempt, type NotificationChannelStats, type NotificationDeadLetter, type NotificationEvent, type NotificationProviderLog, type NotificationQueueStats, type NotificationRule, type NotificationSettings, type NotificationStats, type NotificationTemplate, type NotificationTemplatePreview, type OAuthSignInOptions, type ObjectMeta, type PasswordResetRequest, type PostgresChangesFilter, type PresenceFilter, type PresenceInfo, type PresenceState, type QueryFilter, type QueryOptions, RETRYABLE_METHODS, RETRYABLE_STATUS, type RagIngestRequest, type RagIngestResponse, type RagRequest, type RagResponse, type RealtimeEvent, type RealtimeListenType, type RealtimeSubscribeStatus, type SchemaColumn, type SchemaEnum, type SchemaFunction, type SchemaFunctionArg, type SchemaIndex, type SchemaTable, type SemanticSearchResult, type SendNotificationRequest, type SerializeCookieOptions, type ServerErrorMsg, type ServerEventMsg, type ServerMessage, type ServerPongMsg, type ServerPresenceStateMsg, type ServerSubscribedMsg, type ServerTokenRefreshedMsg, type SessionInfo, type SignInRequest, type SignUpRequest, type StorageBucket, type SubscribeCallback, type TokenPair, type UpdateUserRequest, type UploadResult, type User, type UserStats, abortableSleep, computeBackoff, emitAuraEvent, fetchWithAuth, isAbortError, isAuraError, isRetryableMethod, isRetryableStatus, isTokenExpired, parseCookieHeader, parseRetryAfter, refreshOnce, serializeBody, serializeCookieHeader };
package/dist/index.js CHANGED
@@ -233,6 +233,56 @@ function unwrapEnvelope(body) {
233
233
  }
234
234
  return body;
235
235
  }
236
+ async function readTextBounded(res, timeoutMs) {
237
+ const body = res.body;
238
+ if (!body || typeof body.getReader !== "function" || !(timeoutMs > 0)) {
239
+ try {
240
+ return { ok: true, text: await res.text() };
241
+ } catch (err) {
242
+ return {
243
+ ok: false,
244
+ timedOut: false,
245
+ message: err instanceof Error ? err.message : "read failed"
246
+ };
247
+ }
248
+ }
249
+ let timer;
250
+ const TIMED_OUT = /* @__PURE__ */ Symbol("body-timeout");
251
+ try {
252
+ const reader = body.getReader();
253
+ const decoder = new TextDecoder();
254
+ let out = "";
255
+ const deadline = new Promise((resolve) => {
256
+ timer = setTimeout(() => resolve(TIMED_OUT), timeoutMs);
257
+ timer.unref?.();
258
+ });
259
+ for (; ; ) {
260
+ const step = await Promise.race([reader.read(), deadline]);
261
+ if (step === TIMED_OUT) {
262
+ await reader.cancel().catch(() => {
263
+ });
264
+ return {
265
+ ok: false,
266
+ timedOut: true,
267
+ message: `Response body not fully received within ${timeoutMs}ms`
268
+ };
269
+ }
270
+ if (step.done) {
271
+ out += decoder.decode();
272
+ return { ok: true, text: out };
273
+ }
274
+ if (step.value) out += decoder.decode(step.value, { stream: true });
275
+ }
276
+ } catch (err) {
277
+ return {
278
+ ok: false,
279
+ timedOut: false,
280
+ message: err instanceof Error ? err.message : "read failed"
281
+ };
282
+ } finally {
283
+ if (timer) clearTimeout(timer);
284
+ }
285
+ }
236
286
  var inflightRefreshes = /* @__PURE__ */ new WeakMap();
237
287
  async function doRefresh(client) {
238
288
  const store = client.authStore;
@@ -246,26 +296,57 @@ async function doRefresh(client) {
246
296
  const epoch = store.removalEpoch;
247
297
  const headers = { "Content-Type": "application/json" };
248
298
  if (client.apiKey) headers["apikey"] = client.apiKey;
299
+ const timeoutMs = client.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
300
+ let timedOut = false;
301
+ let refreshTimer;
302
+ let refreshSignal;
303
+ if (timeoutMs > 0 && typeof AbortController !== "undefined") {
304
+ const ctrl = new AbortController();
305
+ refreshTimer = setTimeout(() => {
306
+ timedOut = true;
307
+ ctrl.abort();
308
+ }, timeoutMs);
309
+ refreshTimer.unref?.();
310
+ refreshSignal = ctrl.signal;
311
+ }
312
+ const clearRefreshTimer = () => {
313
+ if (refreshTimer) {
314
+ clearTimeout(refreshTimer);
315
+ refreshTimer = void 0;
316
+ }
317
+ };
249
318
  let res;
250
319
  try {
251
320
  res = await client.fetchFn(`${client.baseUrl}/v1/auth/${client.projectId}/refresh`, {
252
321
  method: "POST",
253
322
  headers,
254
- body: serializeBody({ refresh_token: rt })
323
+ body: serializeBody({ refresh_token: rt }),
324
+ signal: refreshSignal
255
325
  });
256
326
  } catch (err) {
327
+ clearRefreshTimer();
328
+ if (timedOut) {
329
+ emitAuraEvent(client.onEvent, client.debug, {
330
+ kind: "timeout",
331
+ url: `${client.baseUrl}/v1/auth/${client.projectId}/refresh`
332
+ });
333
+ }
257
334
  return {
258
335
  data: null,
259
336
  error: new AuraError(0, {
260
- error: { code: "network_error", message: err instanceof Error ? err.message : "Network request failed" }
337
+ error: timedOut ? { code: "timeout", message: `Refresh timed out after ${timeoutMs}ms` } : { code: "network_error", message: err instanceof Error ? err.message : "Network request failed" }
261
338
  })
262
339
  };
263
340
  }
341
+ clearRefreshTimer();
264
342
  if (!res.ok) {
265
343
  let body2 = null;
266
- try {
267
- body2 = await res.json();
268
- } catch {
344
+ const errRead = await readTextBounded(res, timeoutMs);
345
+ if (errRead.ok && errRead.text.trim() !== "") {
346
+ try {
347
+ body2 = JSON.parse(errRead.text);
348
+ } catch {
349
+ }
269
350
  }
270
351
  const code = typeof body2?.error?.code === "string" ? body2.error.code : null;
271
352
  const definitivelyRejected = res.status === 403 || res.status === 401 && code === "REFRESH_TOKEN_REJECTED";
@@ -277,8 +358,23 @@ async function doRefresh(client) {
277
358
  return { data: null, error: new AuraError(res.status, body2) };
278
359
  }
279
360
  let body;
361
+ const okRead = await readTextBounded(res, timeoutMs);
362
+ if (!okRead.ok) {
363
+ if (okRead.timedOut) {
364
+ emitAuraEvent(client.onEvent, client.debug, {
365
+ kind: "timeout",
366
+ url: `${client.baseUrl}/v1/auth/${client.projectId}/refresh`
367
+ });
368
+ }
369
+ return {
370
+ data: null,
371
+ error: new AuraError(0, {
372
+ error: okRead.timedOut ? { code: "timeout", message: `Refresh body not received within ${timeoutMs}ms` } : { code: "invalid_response", message: `Invalid refresh response (${okRead.message})` }
373
+ })
374
+ };
375
+ }
280
376
  try {
281
- body = await res.json();
377
+ body = JSON.parse(okRead.text);
282
378
  } catch {
283
379
  return {
284
380
  data: null,
@@ -371,6 +467,7 @@ async function fetchWithAuth(url, init, client) {
371
467
  const isFormData = typeof FormData !== "undefined" && init.body instanceof FormData;
372
468
  const timeoutMs = client.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
373
469
  const applyTimeout = timeoutMs > 0 && init.signal === void 0 && !isFormData;
470
+ const bodyTimeoutMs = init.signal === void 0 ? timeoutMs : 0;
374
471
  let attempt = 0;
375
472
  for (; ; ) {
376
473
  const headers = { ...init.headers };
@@ -460,26 +557,42 @@ async function fetchWithAuth(url, init, client) {
460
557
  }
461
558
  if (!res.ok) {
462
559
  let body2 = null;
463
- try {
464
- body2 = await res.json();
465
- } catch {
560
+ const errRead = await readTextBounded(res, bodyTimeoutMs);
561
+ if (errRead.ok && errRead.text.trim() !== "") {
562
+ try {
563
+ body2 = JSON.parse(errRead.text);
564
+ } catch {
565
+ }
566
+ } else if (!errRead.ok && errRead.timedOut) {
567
+ emitAuraEvent(client.onEvent, client.debug, { kind: "timeout", url });
466
568
  }
467
569
  return { data: null, error: new AuraError(res.status, body2) };
468
570
  }
469
- let raw;
470
- try {
471
- raw = await res.text();
472
- } catch (err) {
571
+ const read = await readTextBounded(res, bodyTimeoutMs);
572
+ if (!read.ok) {
573
+ if (read.timedOut) {
574
+ emitAuraEvent(client.onEvent, client.debug, { kind: "timeout", url });
575
+ return {
576
+ data: null,
577
+ error: new AuraError(0, {
578
+ error: {
579
+ code: "timeout",
580
+ message: `Response body not fully received within ${bodyTimeoutMs}ms (HTTP ${res.status})`
581
+ }
582
+ })
583
+ };
584
+ }
473
585
  return {
474
586
  data: null,
475
587
  error: new AuraError(0, {
476
588
  error: {
477
589
  code: "invalid_response",
478
- message: `Unreadable response body (HTTP ${res.status}): ${err instanceof Error ? err.message : "read failed"}`
590
+ message: `Unreadable response body (HTTP ${res.status}): ${read.message}`
479
591
  }
480
592
  })
481
593
  };
482
594
  }
595
+ const raw = read.text;
483
596
  if (raw.trim() === "") {
484
597
  return { data: null, error: null };
485
598
  }
@@ -703,4 +816,4 @@ function serializeCookieHeader(name, value, options = {}) {
703
816
  return parts.join("; ");
704
817
  }
705
818
 
706
- export { AuraError, BaseAuthStore, BaseService, DEFAULT_MAX_RETRIES, LocalAuthStore, RETRYABLE_METHODS, RETRYABLE_STATUS, abortableSleep, computeBackoff, emitAuraEvent, fetchWithAuth, isAbortError, isAuraError, isRetryableMethod, isRetryableStatus, isTokenExpired, parseCookieHeader, parseRetryAfter, refreshOnce, serializeBody, serializeCookieHeader };
819
+ export { AuraError, BaseAuthStore, BaseService, DEFAULT_MAX_RETRIES, DEFAULT_REQUEST_TIMEOUT_MS, LocalAuthStore, RETRYABLE_METHODS, RETRYABLE_STATUS, abortableSleep, computeBackoff, emitAuraEvent, fetchWithAuth, isAbortError, isAuraError, isRetryableMethod, isRetryableStatus, isTokenExpired, parseCookieHeader, parseRetryAfter, refreshOnce, serializeBody, serializeCookieHeader };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aurabase/core",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Cœur du SDK Aurabase : fetch résilient, erreurs, stores auth, types de réponse (infrastructure interne)",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -45,6 +45,7 @@
45
45
  "core"
46
46
  ],
47
47
  "devDependencies": {
48
+ "@types/node": "20.14.15",
48
49
  "tsup": "^8.0.0",
49
50
  "typescript": "^5.5.0",
50
51
  "vitest": "^4.1.6"
@@ -53,6 +54,7 @@
53
54
  "build": "tsup",
54
55
  "test": "vitest run && tsc --noEmit -p tsconfig.test-d.json",
55
56
  "test:types": "tsc --noEmit -p tsconfig.test-d.json",
56
- "typecheck": "tsc --noEmit"
57
+ "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json",
58
+ "typecheck:tests": "tsc --noEmit -p tsconfig.test.json"
57
59
  }
58
60
  }