@hexclave/shared 1.0.28 → 1.0.30

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.
Files changed (48) hide show
  1. package/dist/config/schema.d.ts +48 -48
  2. package/dist/esm/config/schema.d.ts +48 -48
  3. package/dist/esm/hooks/use-async-callback.js +1 -1
  4. package/dist/esm/hooks/use-async-external-store.js +1 -1
  5. package/dist/esm/hooks/use-strict-memo.js +1 -1
  6. package/dist/esm/interface/admin-interface.js +1 -1
  7. package/dist/esm/interface/admin-metrics.d.ts +7 -7
  8. package/dist/esm/interface/client-interface.js +1 -1
  9. package/dist/esm/interface/conversations.d.ts +2 -2
  10. package/dist/esm/interface/crud/current-user.d.ts +6 -6
  11. package/dist/esm/interface/crud/email-outbox.d.ts +72 -72
  12. package/dist/esm/interface/crud/invoices.d.ts +2 -2
  13. package/dist/esm/interface/crud/products.d.ts +4 -4
  14. package/dist/esm/interface/crud/project-api-keys.d.ts +2 -2
  15. package/dist/esm/interface/crud/team-member-profiles.d.ts +10 -10
  16. package/dist/esm/interface/crud/transactions.d.ts +2 -2
  17. package/dist/esm/interface/crud/users.d.ts +4 -4
  18. package/dist/esm/interface/server-interface.js +1 -1
  19. package/dist/esm/schema-fields.d.ts +1 -1
  20. package/dist/esm/sessions.d.ts +2 -2
  21. package/dist/esm/utils/promises.d.ts +12 -1
  22. package/dist/esm/utils/promises.d.ts.map +1 -1
  23. package/dist/esm/utils/promises.js +58 -2
  24. package/dist/esm/utils/promises.js.map +1 -1
  25. package/dist/hooks/use-async-callback.js +1 -1
  26. package/dist/hooks/use-async-external-store.js +1 -1
  27. package/dist/hooks/use-strict-memo.js +1 -1
  28. package/dist/interface/admin-interface.js +1 -1
  29. package/dist/interface/admin-metrics.d.ts +7 -7
  30. package/dist/interface/client-interface.js +1 -1
  31. package/dist/interface/conversations.d.ts +2 -2
  32. package/dist/interface/crud/current-user.d.ts +6 -6
  33. package/dist/interface/crud/email-outbox.d.ts +72 -72
  34. package/dist/interface/crud/invoices.d.ts +2 -2
  35. package/dist/interface/crud/products.d.ts +4 -4
  36. package/dist/interface/crud/project-api-keys.d.ts +2 -2
  37. package/dist/interface/crud/team-member-profiles.d.ts +10 -10
  38. package/dist/interface/crud/transactions.d.ts +2 -2
  39. package/dist/interface/crud/users.d.ts +4 -4
  40. package/dist/interface/server-interface.js +1 -1
  41. package/dist/schema-fields.d.ts +1 -1
  42. package/dist/sessions.d.ts +2 -2
  43. package/dist/utils/promises.d.ts +12 -1
  44. package/dist/utils/promises.d.ts.map +1 -1
  45. package/dist/utils/promises.js +58 -1
  46. package/dist/utils/promises.js.map +1 -1
  47. package/package.json +1 -1
  48. package/src/utils/promises.tsx +65 -0
@@ -26,8 +26,8 @@ declare const customerProductReadSchema: yup$1.ObjectSchema<{
26
26
  }>;
27
27
  display_name: string;
28
28
  included_items: Record<string, {
29
- quantity?: number | undefined;
30
29
  repeat?: "never" | DayInterval | undefined;
30
+ quantity?: number | undefined;
31
31
  expires?: "never" | "when-purchase-expires" | "when-repeated" | undefined;
32
32
  }>;
33
33
  };
@@ -61,8 +61,8 @@ declare const customerProductReadSchema: yup$1.ObjectSchema<{
61
61
  }>;
62
62
  display_name: string;
63
63
  included_items: Record<string, {
64
- quantity?: number | undefined;
65
64
  repeat?: "never" | DayInterval | undefined;
65
+ quantity?: number | undefined;
66
66
  expires?: "never" | "when-purchase-expires" | "when-repeated" | undefined;
67
67
  }>;
68
68
  };
@@ -124,8 +124,8 @@ declare const customerProductsListResponseSchema: yup$1.ObjectSchema<{
124
124
  }>;
125
125
  display_name: string;
126
126
  included_items: Record<string, {
127
- quantity?: number | undefined;
128
127
  repeat?: "never" | DayInterval | undefined;
128
+ quantity?: number | undefined;
129
129
  expires?: "never" | "when-purchase-expires" | "when-repeated" | undefined;
130
130
  }>;
131
131
  };
@@ -152,8 +152,8 @@ declare const customerProductsListResponseSchema: yup$1.ObjectSchema<{
152
152
  }>;
153
153
  display_name: string;
154
154
  included_items: Record<string, {
155
- quantity?: number | undefined;
156
155
  repeat?: "never" | DayInterval | undefined;
156
+ quantity?: number | undefined;
157
157
  expires?: "never" | "when-purchase-expires" | "when-repeated" | undefined;
158
158
  }>;
159
159
  };
@@ -78,9 +78,9 @@ declare const userApiKeysCrud: CrudSchemaFromOptions<{
78
78
  is_public: undefined;
79
79
  user_id: undefined;
80
80
  }, "">, userApiKeysCreateOutputSchema: yup$1.ObjectSchema<{
81
+ description: string;
81
82
  user_id: string;
82
83
  type: "user";
83
- description: string;
84
84
  id: string;
85
85
  expires_at_millis: number | undefined;
86
86
  manually_revoked_at_millis: number | undefined;
@@ -176,9 +176,9 @@ declare const teamApiKeysCrud: CrudSchemaFromOptions<{
176
176
  is_public: undefined;
177
177
  team_id: undefined;
178
178
  }, "">, teamApiKeysCreateOutputSchema: yup$1.ObjectSchema<{
179
+ description: string;
179
180
  type: "team";
180
181
  team_id: string;
181
- description: string;
182
182
  id: string;
183
183
  expires_at_millis: number | undefined;
184
184
  manually_revoked_at_millis: number | undefined;
@@ -24,18 +24,18 @@ declare const teamMemberProfilesCrudServerReadSchema: yup$1.ObjectSchema<{
24
24
  type: "anonymous" | "email_not_verified" | "restricted_by_administrator";
25
25
  } | null;
26
26
  primary_email: string | null;
27
+ selected_team_id: string | null;
28
+ is_anonymous: boolean;
29
+ is_restricted: boolean;
30
+ requires_totp_mfa: boolean;
27
31
  display_name: string | null;
28
32
  client_metadata: {} | null;
29
33
  client_read_only_metadata: {} | null;
30
34
  server_metadata: {} | null;
31
35
  id: string;
32
- selected_team_id: string | null;
33
- is_anonymous: boolean;
34
- is_restricted: boolean;
35
- requires_totp_mfa: boolean;
36
- profile_image_url: string | null;
37
36
  primary_email_verified: boolean;
38
37
  primary_email_auth_enabled: boolean;
38
+ profile_image_url: string | null;
39
39
  selected_team: {
40
40
  client_metadata?: {} | null | undefined;
41
41
  client_read_only_metadata?: {} | null | undefined;
@@ -147,18 +147,18 @@ declare const teamMemberProfilesCrud: CrudSchemaFromOptions<{
147
147
  type: "anonymous" | "email_not_verified" | "restricted_by_administrator";
148
148
  } | null;
149
149
  primary_email: string | null;
150
+ selected_team_id: string | null;
151
+ is_anonymous: boolean;
152
+ is_restricted: boolean;
153
+ requires_totp_mfa: boolean;
150
154
  display_name: string | null;
151
155
  client_metadata: {} | null;
152
156
  client_read_only_metadata: {} | null;
153
157
  server_metadata: {} | null;
154
158
  id: string;
155
- selected_team_id: string | null;
156
- is_anonymous: boolean;
157
- is_restricted: boolean;
158
- requires_totp_mfa: boolean;
159
- profile_image_url: string | null;
160
159
  primary_email_verified: boolean;
161
160
  primary_email_auth_enabled: boolean;
161
+ profile_image_url: string | null;
162
162
  selected_team: {
163
163
  client_metadata?: {} | null | undefined;
164
164
  client_read_only_metadata?: {} | null | undefined;
@@ -58,8 +58,8 @@ declare const transactionEntrySchema: yup$1.MixedSchema<{
58
58
  }>;
59
59
  display_name: string;
60
60
  included_items: Record<string, {
61
- quantity?: number | undefined;
62
61
  repeat?: "never" | DayInterval | undefined;
62
+ quantity?: number | undefined;
63
63
  expires?: "never" | "when-purchase-expires" | "when-repeated" | undefined;
64
64
  }>;
65
65
  };
@@ -142,8 +142,8 @@ declare const transactionSchema: yup$1.ObjectSchema<{
142
142
  }>;
143
143
  display_name: string;
144
144
  included_items: Record<string, {
145
- quantity?: number | undefined;
146
145
  repeat?: "never" | DayInterval | undefined;
146
+ quantity?: number | undefined;
147
147
  expires?: "never" | "when-purchase-expires" | "when-repeated" | undefined;
148
148
  }>;
149
149
  };
@@ -163,15 +163,15 @@ declare const usersCrudServerReadSchema: yup$1.ObjectSchema<{
163
163
  }, "">;
164
164
  declare const usersCrudServerCreateSchema: yup$1.ObjectSchema<{
165
165
  primary_email: string | null | undefined;
166
+ is_anonymous: boolean | undefined;
166
167
  display_name: string | null | undefined;
167
168
  client_metadata: {} | null | undefined;
168
169
  client_read_only_metadata: {} | null | undefined;
169
170
  server_metadata: {} | null | undefined;
170
- is_anonymous: boolean | undefined;
171
171
  password: string | null | undefined;
172
- profile_image_url: string | null | undefined;
173
172
  primary_email_verified: boolean | undefined;
174
173
  primary_email_auth_enabled: boolean | undefined;
174
+ profile_image_url: string | null | undefined;
175
175
  otp_auth_enabled: boolean | undefined;
176
176
  passkey_auth_enabled: boolean | undefined;
177
177
  restricted_by_admin: boolean | undefined;
@@ -369,15 +369,15 @@ declare const usersCrud: CrudSchemaFromOptions<{
369
369
  }, "">;
370
370
  serverCreateSchema: yup$1.ObjectSchema<{
371
371
  primary_email: string | null | undefined;
372
+ is_anonymous: boolean | undefined;
372
373
  display_name: string | null | undefined;
373
374
  client_metadata: {} | null | undefined;
374
375
  client_read_only_metadata: {} | null | undefined;
375
376
  server_metadata: {} | null | undefined;
376
- is_anonymous: boolean | undefined;
377
377
  password: string | null | undefined;
378
- profile_image_url: string | null | undefined;
379
378
  primary_email_verified: boolean | undefined;
380
379
  primary_email_auth_enabled: boolean | undefined;
380
+ profile_image_url: string | null | undefined;
381
381
  otp_auth_enabled: boolean | undefined;
382
382
  passkey_auth_enabled: boolean | undefined;
383
383
  restricted_by_admin: boolean | undefined;
@@ -3,8 +3,8 @@ const require_chunk = require('../chunk-BE-pF4vm.js');
3
3
  let ___utils_errors_js = require("../utils/errors.js");
4
4
  let ___utils_objects_js = require("../utils/objects.js");
5
5
  let ___utils_results_js = require("../utils/results.js");
6
- let ___known_errors_js = require("../known-errors.js");
7
6
  let ___utils_urls_js = require("../utils/urls.js");
7
+ let ___known_errors_js = require("../known-errors.js");
8
8
  let ___sessions_js = require("../sessions.js");
9
9
  let __client_interface_js = require("./client-interface.js");
10
10
  let ___helpers_vault_client_side_js = require("../helpers/vault/client-side.js");
@@ -314,8 +314,8 @@ declare const inlineProductSchema: yup$1.ObjectSchema<{
314
314
  free_trial?: DayInterval | undefined;
315
315
  }>;
316
316
  included_items: Record<string, {
317
- quantity?: number | undefined;
318
317
  repeat?: "never" | DayInterval | undefined;
318
+ quantity?: number | undefined;
319
319
  expires?: "never" | "when-purchase-expires" | "when-repeated" | undefined;
320
320
  }>;
321
321
  client_metadata: {} | null | undefined;
@@ -9,6 +9,7 @@ declare class AccessToken {
9
9
  private constructor();
10
10
  get payload(): {
11
11
  exp?: number | undefined;
12
+ sub: string;
12
13
  project_id: string;
13
14
  refresh_token_id: string;
14
15
  email: string | null;
@@ -16,12 +17,11 @@ declare class AccessToken {
16
17
  type: "anonymous" | "email_not_verified" | "restricted_by_administrator";
17
18
  } | null;
18
19
  branch_id: string;
19
- sub: string;
20
- name: string | null;
21
20
  iat: number;
22
21
  iss: string;
23
22
  aud: string;
24
23
  role: "authenticated";
24
+ name: string | null;
25
25
  email_verified: boolean;
26
26
  selected_team_id: string | null;
27
27
  signed_up_at: number;
@@ -51,6 +51,17 @@ declare class TimeoutError extends Error {
51
51
  }
52
52
  declare function timeout<T>(promiseOrFunc: Promise<T> | (() => Promise<T>), ms: number): Promise<Result<T, TimeoutError>>;
53
53
  declare function timeoutThrow<T>(promise: Promise<T>, ms: number): Promise<T>;
54
+ /**
55
+ * Maps over `items` with `fn`, running at most `concurrency` invocations at a time.
56
+ *
57
+ * Unlike `Promise.all(items.map(fn))`, this bounds the number of in-flight
58
+ * promises, which matters when `fn` hits a shared resource (e.g. a database) and
59
+ * an unbounded fan-out could exhaust connections or overload a replica. Results
60
+ * are returned in input order regardless of completion order, and the first
61
+ * rejection aborts further scheduling — already in-flight workers still settle
62
+ * but no new items are started.
63
+ */
64
+ declare function mapWithConcurrency<T, R>(items: readonly T[], concurrency: number, fn: (item: T, index: number) => Promise<R>): Promise<R[]>;
54
65
  type RateLimitOptions = {
55
66
  /**
56
67
  * The number of requests to process in parallel. Currently only 1 is supported.
@@ -76,5 +87,5 @@ type RateLimitOptions = {
76
87
  declare function rateLimited<T>(func: () => Promise<T>, options: RateLimitOptions): () => Promise<T>;
77
88
  declare function throttled<T, A extends any[]>(func: (...args: A) => Promise<T>, delayMs: number): (...args: A) => Promise<T>;
78
89
  //#endregion
79
- export { RateLimitOptions, ReactPromise, concatStacktracesIfRejected, createPromise, ignoreUnhandledRejection, neverResolve, pending, rateLimited, rejected, resolved, runAsynchronously, runAsynchronouslyWithAlert, throttled, timeout, timeoutThrow, wait, waitUntil };
90
+ export { RateLimitOptions, ReactPromise, concatStacktracesIfRejected, createPromise, ignoreUnhandledRejection, mapWithConcurrency, neverResolve, pending, rateLimited, rejected, resolved, runAsynchronously, runAsynchronouslyWithAlert, throttled, timeout, timeoutThrow, wait, waitUntil };
80
91
  //# sourceMappingURL=promises.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"promises.d.ts","names":[],"sources":["../../src/utils/promises.tsx"],"mappings":";;;KAQY,YAAA,MAAkB,OAAA,CAAQ,CAAA;EAChC,MAAA;EAAoB,MAAA;AAAA;EACpB,MAAA;EAAqB,KAAA,EAAO,CAAA;AAAA;EAC5B,MAAA;AAAA;AAAA,KAGD,OAAA,OAAc,KAAA,EAAO,CAAA;AAAA,KACrB,MAAA,IAAU,MAAA;AAAA,iBACC,aAAA,GAAA,CAAiB,QAAA,GAAW,OAAA,EAAS,OAAA,CAAQ,CAAA,GAAI,MAAA,EAAQ,MAAA,YAAkB,YAAA,CAAa,CAAA;;;;;iBAwExF,QAAA,GAAA,CAAY,KAAA,EAAO,CAAA,GAAI,YAAA,CAAa,CAAA;;;;;iBA0CpC,QAAA,GAAA,CAAY,MAAA,YAAkB,YAAA,CAAa,CAAA;AAAA,iBA6C3C,YAAA,CAAA,GAAgB,YAAA;AAAA,iBAchB,OAAA,GAAA,CAAW,OAAA,EAAS,OAAA,CAAQ,CAAA,GAAI,OAAA;EAAW,oBAAA;AAAA,IAAwC,YAAA,CAAa,CAAA;;;;;AA/KrF;;iBAuNX,wBAAA,WAAmC,OAAA,MAAA,CAAc,OAAA,EAAS,CAAA;;;AArN1E;iBA0OgB,2BAAA,GAAA,CAA+B,OAAA,EAAS,OAAA,CAAQ,CAAA;AAAA,iBAW1C,IAAA,CAAK,EAAA,WAAU,OAAA;AAAA,iBAgCf,SAAA,CAAU,IAAA,EAAM,IAAA,GAAI,OAAA;AAAA,iBAsB1B,0BAAA,CAAA,GAA8B,IAAA,EAAM,UAAA,QAAkB,iBAAA;AAAA,iBAgCtD,iBAAA,CACd,aAAA,SAAsB,OAAA,0BAAiC,OAAA,wBACvD,OAAA;EACE,cAAA;EACA,OAAA,IAAW,KAAA,EAAO,KAAA;AAAA;AAAA,cAoChB,YAAA,SAAqB,KAAA;EAAA,SACG,EAAA;cAAA,EAAA;AAAA;AAAA,iBAMR,OAAA,GAAA,CAAW,aAAA,EAAe,OAAA,CAAQ,CAAA,WAAY,OAAA,CAAQ,CAAA,IAAK,EAAA,WAAa,OAAA,CAAQ,MAAA,CAAO,CAAA,EAAG,YAAA;AAAA,iBA0B1F,YAAA,GAAA,CAAgB,OAAA,EAAS,OAAA,CAAQ,CAAA,GAAI,EAAA,WAAa,OAAA,CAAQ,CAAA;AAAA,KAgBpE,gBAAA;EApaqB;;;EAwa/B,WAAA;EAxauG;AAwEzG;;EAqWE,UAAA;EArWiC;;;EA0WjC,UAAA;EA1WiD;;;EA+WjD,KAAA;EA/WqC;;;EAoXrC,UAAA;AAAA;AAAA,iBAGc,WAAA,GAAA,CACd,IAAA,QAAY,OAAA,CAAQ,CAAA,GACpB,OAAA,EAAS,gBAAA,SACF,OAAA,CAAQ,CAAA;AAAA,iBA0DD,SAAA,oBAAA,CAA8B,IAAA,MAAU,IAAA,EAAM,CAAA,KAAM,OAAA,CAAQ,CAAA,GAAI,OAAA,eAAsB,IAAA,EAAM,CAAA,KAAM,OAAA,CAAQ,CAAA"}
1
+ {"version":3,"file":"promises.d.ts","names":[],"sources":["../../src/utils/promises.tsx"],"mappings":";;;KAQY,YAAA,MAAkB,OAAA,CAAQ,CAAA;EAChC,MAAA;EAAoB,MAAA;AAAA;EACpB,MAAA;EAAqB,KAAA,EAAO,CAAA;AAAA;EAC5B,MAAA;AAAA;AAAA,KAGD,OAAA,OAAc,KAAA,EAAO,CAAA;AAAA,KACrB,MAAA,IAAU,MAAA;AAAA,iBACC,aAAA,GAAA,CAAiB,QAAA,GAAW,OAAA,EAAS,OAAA,CAAQ,CAAA,GAAI,MAAA,EAAQ,MAAA,YAAkB,YAAA,CAAa,CAAA;;;;;iBAwExF,QAAA,GAAA,CAAY,KAAA,EAAO,CAAA,GAAI,YAAA,CAAa,CAAA;;;;;iBA0CpC,QAAA,GAAA,CAAY,MAAA,YAAkB,YAAA,CAAa,CAAA;AAAA,iBA6C3C,YAAA,CAAA,GAAgB,YAAA;AAAA,iBAchB,OAAA,GAAA,CAAW,OAAA,EAAS,OAAA,CAAQ,CAAA,GAAI,OAAA;EAAW,oBAAA;AAAA,IAAwC,YAAA,CAAa,CAAA;;;;;AA/KrF;;iBAuNX,wBAAA,WAAmC,OAAA,MAAA,CAAc,OAAA,EAAS,CAAA;;;AArN1E;iBA0OgB,2BAAA,GAAA,CAA+B,OAAA,EAAS,OAAA,CAAQ,CAAA;AAAA,iBAW1C,IAAA,CAAK,EAAA,WAAU,OAAA;AAAA,iBAgCf,SAAA,CAAU,IAAA,EAAM,IAAA,GAAI,OAAA;AAAA,iBAsB1B,0BAAA,CAAA,GAA8B,IAAA,EAAM,UAAA,QAAkB,iBAAA;AAAA,iBAgCtD,iBAAA,CACd,aAAA,SAAsB,OAAA,0BAAiC,OAAA,wBACvD,OAAA;EACE,cAAA;EACA,OAAA,IAAW,KAAA,EAAO,KAAA;AAAA;AAAA,cAoChB,YAAA,SAAqB,KAAA;EAAA,SACG,EAAA;cAAA,EAAA;AAAA;AAAA,iBAMR,OAAA,GAAA,CAAW,aAAA,EAAe,OAAA,CAAQ,CAAA,WAAY,OAAA,CAAQ,CAAA,IAAK,EAAA,WAAa,OAAA,CAAQ,MAAA,CAAO,CAAA,EAAG,YAAA;AAAA,iBA0B1F,YAAA,GAAA,CAAgB,OAAA,EAAS,OAAA,CAAQ,CAAA,GAAI,EAAA,WAAa,OAAA,CAAQ,CAAA;;;;;;;AA5UhF;;;;iBAsWsB,kBAAA,MAAA,CACpB,KAAA,WAAgB,CAAA,IAChB,WAAA,UACA,EAAA,GAAK,IAAA,EAAM,CAAA,EAAG,KAAA,aAAkB,OAAA,CAAQ,CAAA,IACvC,OAAA,CAAQ,CAAA;AAAA,KAmDC,gBAAA;EA7ZuC;;;EAiajD,WAAA;EAja0B;;;EAsa1B,UAAA;EAtamD;AA0CrD;;EAiYE,UAAA;EAjYwD;;;EAsYxD,KAAA;EAtYyD;;;EA2YzD,UAAA;AAAA;AAAA,iBAGc,WAAA,GAAA,CACd,IAAA,QAAY,OAAA,CAAQ,CAAA,GACpB,OAAA,EAAS,gBAAA,SACF,OAAA,CAAQ,CAAA;AAAA,iBA0DD,SAAA,oBAAA,CAA8B,IAAA,MAAU,IAAA,EAAM,CAAA,KAAM,OAAA,CAAQ,CAAA,GAAI,OAAA,eAAsB,IAAA,EAAM,CAAA,KAAM,OAAA,CAAQ,CAAA"}
@@ -1,11 +1,11 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
  const require_chunk = require('../chunk-BE-pF4vm.js');
3
+ let ___index_js = require("../index.js");
3
4
  let __errors_js = require("./errors.js");
4
5
  let __env_js = require("./env.js");
5
6
  let __maps_js = require("./maps.js");
6
7
  let __results_js = require("./results.js");
7
8
  let __telemetry_js = require("./telemetry.js");
8
- let ___index_js = require("../index.js");
9
9
  let __uuids_js = require("./uuids.js");
10
10
 
11
11
  //#region src/utils/promises.tsx
@@ -284,6 +284,62 @@ async function timeoutThrow(promise, ms) {
284
284
  await expect(timeoutThrow(slowPromise, 10)).rejects.toThrow("Timeout after 10ms");
285
285
  await expect(timeoutThrow(slowPromise, 10)).rejects.toBeInstanceOf(TimeoutError);
286
286
  });
287
+ /**
288
+ * Maps over `items` with `fn`, running at most `concurrency` invocations at a time.
289
+ *
290
+ * Unlike `Promise.all(items.map(fn))`, this bounds the number of in-flight
291
+ * promises, which matters when `fn` hits a shared resource (e.g. a database) and
292
+ * an unbounded fan-out could exhaust connections or overload a replica. Results
293
+ * are returned in input order regardless of completion order, and the first
294
+ * rejection aborts further scheduling — already in-flight workers still settle
295
+ * but no new items are started.
296
+ */
297
+ async function mapWithConcurrency(items, concurrency, fn) {
298
+ if (!Number.isInteger(concurrency) || concurrency < 1) throw new __errors_js.HexclaveAssertionError(`mapWithConcurrency requires a positive integer concurrency, got ${concurrency}`);
299
+ const results = new Array(items.length);
300
+ let nextIndex = 0;
301
+ let aborted = false;
302
+ const worker = async () => {
303
+ while (!aborted) {
304
+ const index = nextIndex++;
305
+ if (index >= items.length) return;
306
+ try {
307
+ results[index] = await fn(items[index], index);
308
+ } catch (error) {
309
+ aborted = true;
310
+ throw error;
311
+ }
312
+ }
313
+ };
314
+ const workerCount = Math.min(concurrency, items.length);
315
+ await Promise.all(Array.from({ length: workerCount }, () => worker()));
316
+ return results;
317
+ }
318
+ (void 0)?.test("mapWithConcurrency", async ({ expect }) => {
319
+ expect(await mapWithConcurrency([
320
+ 30,
321
+ 10,
322
+ 20
323
+ ], 3, async (ms, index) => {
324
+ await wait(ms);
325
+ return `${index}:${ms}`;
326
+ })).toEqual([
327
+ "0:30",
328
+ "1:10",
329
+ "2:20"
330
+ ]);
331
+ let inFlight = 0;
332
+ let maxInFlight = 0;
333
+ await mapWithConcurrency(Array.from({ length: 10 }, (_, i) => i), 3, async () => {
334
+ inFlight++;
335
+ maxInFlight = Math.max(maxInFlight, inFlight);
336
+ await wait(5);
337
+ inFlight--;
338
+ });
339
+ expect(maxInFlight).toBe(3);
340
+ expect(await mapWithConcurrency([], 4, async () => 1)).toEqual([]);
341
+ await expect(mapWithConcurrency([1], 0, async (x) => x)).rejects.toThrow("positive integer concurrency");
342
+ });
287
343
  function rateLimited(func, options) {
288
344
  let waitUntil = performance.now();
289
345
  let queue = [];
@@ -334,6 +390,7 @@ function throttled(func, delayMs) {
334
390
  exports.concatStacktracesIfRejected = concatStacktracesIfRejected;
335
391
  exports.createPromise = createPromise;
336
392
  exports.ignoreUnhandledRejection = ignoreUnhandledRejection;
393
+ exports.mapWithConcurrency = mapWithConcurrency;
337
394
  exports.neverResolve = neverResolve;
338
395
  exports.pending = pending;
339
396
  exports.rateLimited = rateLimited;
@@ -1 +1 @@
1
- {"version":3,"file":"promises.js","names":[],"sources":["../../src/utils/promises.tsx"],"sourcesContent":["import { KnownError } from \"..\";\nimport { getProcessEnv } from \"./env\";\nimport { HexclaveAssertionError, captureError, concatStacktraces, errorToNiceString } from \"./errors\";\nimport { DependenciesMap } from \"./maps\";\nimport { Result } from \"./results\";\nimport { traceSpan } from \"./telemetry\";\nimport { generateUuid } from \"./uuids\";\n\nexport type ReactPromise<T> = Promise<T> & (\n | { status: \"rejected\", reason: unknown }\n | { status: \"fulfilled\", value: T }\n | { status: \"pending\" }\n);\n\ntype Resolve<T> = (value: T) => void;\ntype Reject = (reason: unknown) => void;\nexport function createPromise<T>(callback: (resolve: Resolve<T>, reject: Reject) => void): ReactPromise<T> {\n let status = \"pending\" as \"fulfilled\" | \"rejected\" | \"pending\";\n let valueOrReason: T | unknown | undefined = undefined;\n let resolve: Resolve<T> | null = null;\n let reject: Reject | null = null;\n const promise = new Promise<T>((res, rej) => {\n resolve = (value) => {\n if (status !== \"pending\") return;\n status = \"fulfilled\";\n valueOrReason = value;\n res(value);\n };\n reject = (reason) => {\n if (status !== \"pending\") return;\n status = \"rejected\";\n valueOrReason = reason;\n rej(reason);\n };\n });\n\n callback(resolve!, reject!);\n return Object.assign(promise, {\n status: status,\n ...status === \"fulfilled\" ? { value: valueOrReason as T } : {},\n ...status === \"rejected\" ? { reason: valueOrReason } : {},\n } as any);\n}\nimport.meta.vitest?.test(\"createPromise\", async ({ expect }) => {\n // Test resolved promise\n const resolvedPromise = createPromise<number>((resolve) => {\n resolve(42);\n });\n expect(resolvedPromise.status).toBe(\"fulfilled\");\n expect((resolvedPromise as any).value).toBe(42);\n expect(await resolvedPromise).toBe(42);\n\n // Test rejected promise\n const error = new Error(\"Test error\");\n const rejectedPromise = createPromise<number>((_, reject) => {\n reject(error);\n });\n expect(rejectedPromise.status).toBe(\"rejected\");\n expect((rejectedPromise as any).reason).toBe(error);\n await expect(rejectedPromise).rejects.toBe(error);\n\n // Test pending promise\n const pendingPromise = createPromise<number>(() => {\n // Do nothing, leave it pending\n });\n expect(pendingPromise.status).toBe(\"pending\");\n expect((pendingPromise as any).value).toBeUndefined();\n expect((pendingPromise as any).reason).toBeUndefined();\n\n // Test that resolving after already resolved does nothing\n let resolveCount = 0;\n const multiResolvePromise = createPromise<number>((resolve) => {\n resolve(1);\n resolveCount++;\n resolve(2);\n resolveCount++;\n });\n expect(resolveCount).toBe(2); // Both resolve calls executed\n expect(multiResolvePromise.status).toBe(\"fulfilled\");\n expect((multiResolvePromise as any).value).toBe(1); // Only first resolve took effect\n expect(await multiResolvePromise).toBe(1);\n});\n\nlet resolvedCache: DependenciesMap<[unknown], ReactPromise<unknown>> | null = null;\n/**\n * Like Promise.resolve(...), but also adds the status and value properties for use with React's `use` hook, and caches\n * the value so that invoking `resolved` twice returns the same promise.\n */\nexport function resolved<T>(value: T): ReactPromise<T> {\n resolvedCache ??= new DependenciesMap<[unknown], ReactPromise<unknown>>();\n if (resolvedCache.has([value])) {\n return resolvedCache.get([value]) as ReactPromise<T>;\n }\n\n const res = Object.assign(Promise.resolve(value), {\n status: \"fulfilled\",\n value,\n } as const);\n resolvedCache.set([value], res);\n return res;\n}\nimport.meta.vitest?.test(\"resolved\", async ({ expect }) => {\n // Test with primitive value\n const promise1 = resolved(42);\n expect(promise1.status).toBe(\"fulfilled\");\n // Need to use type assertion since value is only available when status is \"fulfilled\"\n expect((promise1 as { value: number }).value).toBe(42);\n expect(await promise1).toBe(42);\n\n // Test with object value\n const obj = { test: true };\n const promise2 = resolved(obj);\n expect(promise2.status).toBe(\"fulfilled\");\n expect((promise2 as { value: typeof obj }).value).toBe(obj);\n expect(await promise2).toBe(obj);\n\n // Test caching (same reference for same value)\n const promise3 = resolved(42);\n expect(promise3).toBe(promise1); // Same reference due to caching\n\n // Test with different value (different reference)\n const promise4 = resolved(43);\n expect(promise4).not.toBe(promise1);\n});\n\nlet rejectedCache: DependenciesMap<[unknown], ReactPromise<unknown>> | null = null;\n/**\n * Like Promise.reject(...), but also adds the status and value properties for use with React's `use` hook, and caches\n * the value so that invoking `rejected` twice returns the same promise.\n */\nexport function rejected<T>(reason: unknown): ReactPromise<T> {\n rejectedCache ??= new DependenciesMap<[unknown], ReactPromise<unknown>>();\n if (rejectedCache.has([reason])) {\n return rejectedCache.get([reason]) as ReactPromise<T>;\n }\n\n const promise = Promise.reject(reason);\n ignoreUnhandledRejection(promise);\n const res = Object.assign(promise, {\n status: \"rejected\",\n reason: reason,\n } as const);\n rejectedCache.set([reason], res);\n return res;\n}\nimport.meta.vitest?.test(\"rejected\", ({ expect }) => {\n // Test with error object\n const error = new Error(\"Test error\");\n const promise1 = rejected<number>(error);\n expect(promise1.status).toBe(\"rejected\");\n // Need to use type assertion since reason is only available when status is \"rejected\"\n expect((promise1 as { reason: Error }).reason).toBe(error);\n\n // Test with string reason\n const promise2 = rejected<string>(\"error message\");\n expect(promise2.status).toBe(\"rejected\");\n expect((promise2 as { reason: string }).reason).toBe(\"error message\");\n\n // Test caching (same reference for same reason)\n const promise3 = rejected<number>(error);\n expect(promise3).toBe(promise1); // Same reference due to caching\n\n // Test with different reason (different reference)\n const differentError = new Error(\"Different error\");\n const promise4 = rejected<number>(differentError);\n expect(promise4).not.toBe(promise1);\n\n // Note: We're not using await expect(promise).rejects to avoid unhandled rejections\n});\n\n// We'll skip the rejection test for pending() since it's causing unhandled rejections\n// The function is already well tested through other tests like rejected() and createPromise()\n\n\nconst neverResolvePromise = pending(new Promise<never>(() => {}));\nexport function neverResolve(): ReactPromise<never> {\n return neverResolvePromise;\n}\nimport.meta.vitest?.test(\"neverResolve\", ({ expect }) => {\n const promise = neverResolve();\n expect(promise.status).toBe(\"pending\");\n expect((promise as any).value).toBeUndefined();\n expect((promise as any).reason).toBeUndefined();\n\n // Test that multiple calls return the same promise\n const promise2 = neverResolve();\n expect(promise2).toBe(promise);\n});\n\nexport function pending<T>(promise: Promise<T>, options: { disableErrorWrapping?: boolean } = {}): ReactPromise<T> {\n const res = promise.then(\n value => {\n res.status = \"fulfilled\";\n (res as any).value = value;\n return value;\n },\n actualReason => {\n res.status = \"rejected\";\n (res as any).reason = actualReason;\n throw actualReason;\n },\n ) as ReactPromise<T>;\n res.status = \"pending\";\n return res;\n}\nimport.meta.vitest?.test(\"pending\", async ({ expect }) => {\n // Test with a promise that resolves\n const resolvePromise = Promise.resolve(42);\n const pendingPromise = pending(resolvePromise);\n\n // Initially it should be pending\n expect(pendingPromise.status).toBe(\"pending\");\n\n // After resolution, it should be fulfilled\n await resolvePromise;\n // Need to wait a tick for the then handler to execute\n await new Promise(resolve => setTimeout(resolve, 0));\n expect(pendingPromise.status).toBe(\"fulfilled\");\n expect((pendingPromise as { value: number }).value).toBe(42);\n\n // For the rejection test, we'll use a separate test to avoid unhandled rejections\n});\n\n/**\n * Should be used to wrap Promises that are not immediately awaited, so they don't throw an unhandled promise rejection\n * error.\n *\n * Vercel kills serverless functions on unhandled promise rejection errors, so this is important.\n */\nexport function ignoreUnhandledRejection<T extends Promise<any>>(promise: T): void {\n promise.catch(() => {});\n}\nimport.meta.vitest?.test(\"ignoreUnhandledRejection\", async ({ expect }) => {\n // Test with a promise that resolves\n const resolvePromise = Promise.resolve(42);\n ignoreUnhandledRejection(resolvePromise);\n expect(await resolvePromise).toBe(42); // Should still resolve to the same value\n\n // Test with a promise that rejects\n // The promise should still reject, but the rejection is caught internally\n // so it doesn't cause an unhandled rejection error\n const error = new Error(\"Test error\");\n const rejectPromise = Promise.reject(error);\n ignoreUnhandledRejection(rejectPromise);\n await expect(rejectPromise).rejects.toBe(error);\n});\n\n/**\n * See concatStacktraces for more information.\n */\nexport function concatStacktracesIfRejected<T>(promise: Promise<T>): void {\n const currentError = new Error();\n promise.catch(error => {\n if (error instanceof Error) {\n concatStacktraces(error, currentError);\n } else {\n // we can only concatenate errors, so we'll just ignore the non-error\n }\n });\n}\n\nexport async function wait(ms: number) {\n if (!Number.isFinite(ms) || ms < 0) {\n throw new HexclaveAssertionError(`wait() requires a non-negative integer number of milliseconds to wait. (found: ${ms}ms)`);\n }\n if (ms >= 2**31) {\n throw new HexclaveAssertionError(\"The maximum timeout for wait() is 2147483647ms (2**31 - 1). (found: ${ms}ms)\");\n }\n return await traceSpan({ description: 'wait(...)', attributes: { 'stack.wait.ms': ms } }, async (span) => {\n return await new Promise<void>(resolve => setTimeout(resolve, ms));\n });\n}\nimport.meta.vitest?.test(\"wait\", async ({ expect }) => {\n // Test with valid input\n const start = Date.now();\n await wait(10);\n const elapsed = Date.now() - start;\n expect(elapsed).toBeGreaterThanOrEqual(5); // Allow some flexibility in timing\n\n // Test with zero\n await expect(wait(0)).resolves.toBeUndefined();\n\n // Test with negative number\n await expect(wait(-10)).rejects.toThrow(\"wait() requires a non-negative integer\");\n\n // Test with non-finite number\n await expect(wait(NaN)).rejects.toThrow(\"wait() requires a non-negative integer\");\n await expect(wait(Infinity)).rejects.toThrow(\"wait() requires a non-negative integer\");\n\n // Test with too large number\n await expect(wait(2**31)).rejects.toThrow(\"The maximum timeout for wait()\");\n});\n\nexport async function waitUntil(date: Date) {\n return await wait(date.getTime() - Date.now());\n}\nimport.meta.vitest?.test(\"waitUntil\", async ({ expect }) => {\n // Test with future date\n const futureDate = new Date(Date.now() + 10);\n const start = Date.now();\n await waitUntil(futureDate);\n const elapsed = Date.now() - start;\n expect(elapsed).toBeGreaterThanOrEqual(5); // Allow some flexibility in timing\n\n // Test with past date - this will throw because wait() requires non-negative time\n // We need to verify it throws the correct error\n try {\n await waitUntil(new Date(Date.now() - 1000));\n expect.fail(\"Should have thrown an error\");\n } catch (error) {\n expect(error).toBeInstanceOf(HexclaveAssertionError);\n expect((error as Error).message).toContain(\"wait() requires a non-negative integer\");\n }\n});\n\nexport function runAsynchronouslyWithAlert(...args: Parameters<typeof runAsynchronously>) {\n return runAsynchronously(\n args[0],\n {\n ...args[1],\n onError: error => {\n const nodeEnv = getProcessEnv(\"NODE_ENV\");\n if (KnownError.isKnownError(error) && nodeEnv?.includes(\"production\")) {\n alert(error.message);\n } else {\n alert(`An unhandled error occurred. Please ${nodeEnv === \"development\" ? `check the browser console for the full error.` : \"report this to the developer.\"}\\n\\n${error}`);\n }\n args[1]?.onError?.(error);\n },\n },\n ...args.slice(2) as [],\n );\n}\nimport.meta.vitest?.test(\"runAsynchronouslyWithAlert\", ({ expect }) => {\n // Simple test to verify the function calls runAsynchronously\n // We can't easily test the alert functionality without mocking\n const testFn = () => Promise.resolve(\"test\");\n const testOptions = { noErrorLogging: true };\n\n // Just verify it doesn't throw\n expect(() => runAsynchronouslyWithAlert(testFn, testOptions)).not.toThrow();\n\n // We can't easily test the error handling without mocking, so we'll\n // just verify the function exists and can be called\n expect(typeof runAsynchronouslyWithAlert).toBe(\"function\");\n});\n\nexport function runAsynchronously(\n promiseOrFunc: void | Promise<unknown> | (() => void | Promise<unknown>) | undefined,\n options: {\n noErrorLogging?: boolean,\n onError?: (error: Error) => void,\n } = {},\n): void {\n if (typeof promiseOrFunc === \"function\") {\n promiseOrFunc = promiseOrFunc();\n }\n if (promiseOrFunc) {\n concatStacktracesIfRejected(promiseOrFunc);\n promiseOrFunc.catch(error => {\n options.onError?.(error);\n const newError = new HexclaveAssertionError(\n \"Uncaught error in asynchronous function: \" + errorToNiceString(error),\n { cause: error },\n );\n if (!options.noErrorLogging) {\n captureError(\"runAsynchronously\", newError);\n }\n });\n }\n}\nimport.meta.vitest?.test(\"runAsynchronously\", ({ expect }) => {\n // Simple test to verify the function exists and can be called\n const testFn = () => Promise.resolve(\"test\");\n\n // Just verify it doesn't throw\n expect(() => runAsynchronously(testFn)).not.toThrow();\n expect(() => runAsynchronously(Promise.resolve(\"test\"))).not.toThrow();\n expect(() => runAsynchronously(undefined)).not.toThrow();\n\n // We can't easily test the error handling without mocking, so we'll\n // just verify the function exists and can be called with options\n expect(() => runAsynchronously(testFn, { noErrorLogging: true })).not.toThrow();\n expect(() => runAsynchronously(testFn, { onError: () => {} })).not.toThrow();\n});\n\n\nclass TimeoutError extends Error {\n constructor(public readonly ms: number) {\n super(`Timeout after ${ms}ms`);\n this.name = \"TimeoutError\";\n }\n}\n\nexport async function timeout<T>(promiseOrFunc: Promise<T> | (() => Promise<T>), ms: number): Promise<Result<T, TimeoutError>> {\n const promise = typeof promiseOrFunc === \"function\" ? promiseOrFunc() : promiseOrFunc;\n return await Promise.race([\n promise.then(value => Result.ok(value)),\n wait(ms).then(() => Result.error(new TimeoutError(ms))),\n ]);\n}\nimport.meta.vitest?.test(\"timeout\", async ({ expect }) => {\n // Test with a promise that resolves quickly\n const fastPromise = Promise.resolve(42);\n const fastResult = await timeout(fastPromise, 100);\n expect(fastResult.status).toBe(\"ok\");\n if (fastResult.status === \"ok\") {\n expect(fastResult.data).toBe(42);\n }\n\n // Test with a promise that takes longer than the timeout\n const slowPromise = new Promise(resolve => setTimeout(() => resolve(\"too late\"), 50));\n const slowResult = await timeout(slowPromise, 10);\n expect(slowResult.status).toBe(\"error\");\n if (slowResult.status === \"error\") {\n expect(slowResult.error).toBeInstanceOf(TimeoutError);\n expect((slowResult.error as TimeoutError).ms).toBe(10);\n }\n});\n\nexport async function timeoutThrow<T>(promise: Promise<T>, ms: number): Promise<T> {\n return Result.orThrow(await timeout(promise, ms));\n}\nimport.meta.vitest?.test(\"timeoutThrow\", async ({ expect }) => {\n // Test with a promise that resolves quickly\n const fastPromise = Promise.resolve(42);\n const fastResult = await timeoutThrow(fastPromise, 100);\n expect(fastResult).toBe(42);\n\n // Test with a promise that takes longer than the timeout\n const slowPromise = new Promise(resolve => setTimeout(() => resolve(\"too late\"), 50));\n await expect(timeoutThrow(slowPromise, 10)).rejects.toThrow(\"Timeout after 10ms\");\n await expect(timeoutThrow(slowPromise, 10)).rejects.toBeInstanceOf(TimeoutError);\n});\n\n\nexport type RateLimitOptions = {\n /**\n * The number of requests to process in parallel. Currently only 1 is supported.\n */\n concurrency: 1,\n\n /**\n * If true, multiple requests waiting at the same time will be reduced to just one. Default is false.\n */\n batchCalls?: boolean,\n\n /**\n * Waits for throttleMs since the start of last request before starting the next request. Default is 0.\n */\n throttleMs?: number,\n\n /**\n * Waits for gapMs since the end of last request before starting the next request. Default is 0.\n */\n gapMs?: number,\n\n /**\n * Waits until there have been no new requests for debounceMs before starting a new request. Default is 0.\n */\n debounceMs?: number,\n};\n\nexport function rateLimited<T>(\n func: () => Promise<T>,\n options: RateLimitOptions,\n): () => Promise<T> {\n let waitUntil = performance.now();\n let queue: [(t: T) => void, (e: unknown) => void][] = [];\n let addedToQueueCallbacks = new Map<string, () => void>;\n\n const next = async () => {\n while (true) {\n if (waitUntil > performance.now()) {\n await wait(Math.max(1, waitUntil - performance.now() + 1));\n } else if (queue.length === 0) {\n const uuid = generateUuid();\n await new Promise<void>(resolve => {\n addedToQueueCallbacks.set(uuid, resolve);\n });\n addedToQueueCallbacks.delete(uuid);\n } else {\n break;\n }\n }\n const nextFuncs = options.batchCalls ? queue.splice(0, queue.length) : [queue.shift()!];\n\n const start = performance.now();\n const value = await Result.fromPromise(func());\n const end = performance.now();\n\n waitUntil = Math.max(\n waitUntil,\n start + (options.throttleMs ?? 0),\n end + (options.gapMs ?? 0),\n );\n\n for (const nextFunc of nextFuncs) {\n if (value.status === \"ok\") {\n nextFunc[0](value.data);\n } else {\n nextFunc[1](value.error);\n }\n }\n };\n\n runAsynchronously(async () => {\n while (true) {\n await next();\n }\n });\n\n return () => {\n return new Promise<T>((resolve, reject) => {\n waitUntil = Math.max(\n waitUntil,\n performance.now() + (options.debounceMs ?? 0),\n );\n queue.push([resolve, reject]);\n addedToQueueCallbacks.forEach(cb => cb());\n });\n };\n}\n\nexport function throttled<T, A extends any[]>(func: (...args: A) => Promise<T>, delayMs: number): (...args: A) => Promise<T> {\n let timeout: ReturnType<typeof setTimeout> | null = null;\n let nextAvailable: Promise<T> | null = null;\n return async (...args) => {\n while (nextAvailable !== null) {\n await nextAvailable;\n }\n nextAvailable = new Promise<T>(resolve => {\n timeout = setTimeout(() => {\n nextAvailable = null;\n resolve(func(...args));\n }, delayMs);\n });\n return await nextAvailable;\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"promises.js","names":[],"sources":["../../src/utils/promises.tsx"],"sourcesContent":["import { KnownError } from \"..\";\nimport { getProcessEnv } from \"./env\";\nimport { HexclaveAssertionError, captureError, concatStacktraces, errorToNiceString } from \"./errors\";\nimport { DependenciesMap } from \"./maps\";\nimport { Result } from \"./results\";\nimport { traceSpan } from \"./telemetry\";\nimport { generateUuid } from \"./uuids\";\n\nexport type ReactPromise<T> = Promise<T> & (\n | { status: \"rejected\", reason: unknown }\n | { status: \"fulfilled\", value: T }\n | { status: \"pending\" }\n);\n\ntype Resolve<T> = (value: T) => void;\ntype Reject = (reason: unknown) => void;\nexport function createPromise<T>(callback: (resolve: Resolve<T>, reject: Reject) => void): ReactPromise<T> {\n let status = \"pending\" as \"fulfilled\" | \"rejected\" | \"pending\";\n let valueOrReason: T | unknown | undefined = undefined;\n let resolve: Resolve<T> | null = null;\n let reject: Reject | null = null;\n const promise = new Promise<T>((res, rej) => {\n resolve = (value) => {\n if (status !== \"pending\") return;\n status = \"fulfilled\";\n valueOrReason = value;\n res(value);\n };\n reject = (reason) => {\n if (status !== \"pending\") return;\n status = \"rejected\";\n valueOrReason = reason;\n rej(reason);\n };\n });\n\n callback(resolve!, reject!);\n return Object.assign(promise, {\n status: status,\n ...status === \"fulfilled\" ? { value: valueOrReason as T } : {},\n ...status === \"rejected\" ? { reason: valueOrReason } : {},\n } as any);\n}\nimport.meta.vitest?.test(\"createPromise\", async ({ expect }) => {\n // Test resolved promise\n const resolvedPromise = createPromise<number>((resolve) => {\n resolve(42);\n });\n expect(resolvedPromise.status).toBe(\"fulfilled\");\n expect((resolvedPromise as any).value).toBe(42);\n expect(await resolvedPromise).toBe(42);\n\n // Test rejected promise\n const error = new Error(\"Test error\");\n const rejectedPromise = createPromise<number>((_, reject) => {\n reject(error);\n });\n expect(rejectedPromise.status).toBe(\"rejected\");\n expect((rejectedPromise as any).reason).toBe(error);\n await expect(rejectedPromise).rejects.toBe(error);\n\n // Test pending promise\n const pendingPromise = createPromise<number>(() => {\n // Do nothing, leave it pending\n });\n expect(pendingPromise.status).toBe(\"pending\");\n expect((pendingPromise as any).value).toBeUndefined();\n expect((pendingPromise as any).reason).toBeUndefined();\n\n // Test that resolving after already resolved does nothing\n let resolveCount = 0;\n const multiResolvePromise = createPromise<number>((resolve) => {\n resolve(1);\n resolveCount++;\n resolve(2);\n resolveCount++;\n });\n expect(resolveCount).toBe(2); // Both resolve calls executed\n expect(multiResolvePromise.status).toBe(\"fulfilled\");\n expect((multiResolvePromise as any).value).toBe(1); // Only first resolve took effect\n expect(await multiResolvePromise).toBe(1);\n});\n\nlet resolvedCache: DependenciesMap<[unknown], ReactPromise<unknown>> | null = null;\n/**\n * Like Promise.resolve(...), but also adds the status and value properties for use with React's `use` hook, and caches\n * the value so that invoking `resolved` twice returns the same promise.\n */\nexport function resolved<T>(value: T): ReactPromise<T> {\n resolvedCache ??= new DependenciesMap<[unknown], ReactPromise<unknown>>();\n if (resolvedCache.has([value])) {\n return resolvedCache.get([value]) as ReactPromise<T>;\n }\n\n const res = Object.assign(Promise.resolve(value), {\n status: \"fulfilled\",\n value,\n } as const);\n resolvedCache.set([value], res);\n return res;\n}\nimport.meta.vitest?.test(\"resolved\", async ({ expect }) => {\n // Test with primitive value\n const promise1 = resolved(42);\n expect(promise1.status).toBe(\"fulfilled\");\n // Need to use type assertion since value is only available when status is \"fulfilled\"\n expect((promise1 as { value: number }).value).toBe(42);\n expect(await promise1).toBe(42);\n\n // Test with object value\n const obj = { test: true };\n const promise2 = resolved(obj);\n expect(promise2.status).toBe(\"fulfilled\");\n expect((promise2 as { value: typeof obj }).value).toBe(obj);\n expect(await promise2).toBe(obj);\n\n // Test caching (same reference for same value)\n const promise3 = resolved(42);\n expect(promise3).toBe(promise1); // Same reference due to caching\n\n // Test with different value (different reference)\n const promise4 = resolved(43);\n expect(promise4).not.toBe(promise1);\n});\n\nlet rejectedCache: DependenciesMap<[unknown], ReactPromise<unknown>> | null = null;\n/**\n * Like Promise.reject(...), but also adds the status and value properties for use with React's `use` hook, and caches\n * the value so that invoking `rejected` twice returns the same promise.\n */\nexport function rejected<T>(reason: unknown): ReactPromise<T> {\n rejectedCache ??= new DependenciesMap<[unknown], ReactPromise<unknown>>();\n if (rejectedCache.has([reason])) {\n return rejectedCache.get([reason]) as ReactPromise<T>;\n }\n\n const promise = Promise.reject(reason);\n ignoreUnhandledRejection(promise);\n const res = Object.assign(promise, {\n status: \"rejected\",\n reason: reason,\n } as const);\n rejectedCache.set([reason], res);\n return res;\n}\nimport.meta.vitest?.test(\"rejected\", ({ expect }) => {\n // Test with error object\n const error = new Error(\"Test error\");\n const promise1 = rejected<number>(error);\n expect(promise1.status).toBe(\"rejected\");\n // Need to use type assertion since reason is only available when status is \"rejected\"\n expect((promise1 as { reason: Error }).reason).toBe(error);\n\n // Test with string reason\n const promise2 = rejected<string>(\"error message\");\n expect(promise2.status).toBe(\"rejected\");\n expect((promise2 as { reason: string }).reason).toBe(\"error message\");\n\n // Test caching (same reference for same reason)\n const promise3 = rejected<number>(error);\n expect(promise3).toBe(promise1); // Same reference due to caching\n\n // Test with different reason (different reference)\n const differentError = new Error(\"Different error\");\n const promise4 = rejected<number>(differentError);\n expect(promise4).not.toBe(promise1);\n\n // Note: We're not using await expect(promise).rejects to avoid unhandled rejections\n});\n\n// We'll skip the rejection test for pending() since it's causing unhandled rejections\n// The function is already well tested through other tests like rejected() and createPromise()\n\n\nconst neverResolvePromise = pending(new Promise<never>(() => {}));\nexport function neverResolve(): ReactPromise<never> {\n return neverResolvePromise;\n}\nimport.meta.vitest?.test(\"neverResolve\", ({ expect }) => {\n const promise = neverResolve();\n expect(promise.status).toBe(\"pending\");\n expect((promise as any).value).toBeUndefined();\n expect((promise as any).reason).toBeUndefined();\n\n // Test that multiple calls return the same promise\n const promise2 = neverResolve();\n expect(promise2).toBe(promise);\n});\n\nexport function pending<T>(promise: Promise<T>, options: { disableErrorWrapping?: boolean } = {}): ReactPromise<T> {\n const res = promise.then(\n value => {\n res.status = \"fulfilled\";\n (res as any).value = value;\n return value;\n },\n actualReason => {\n res.status = \"rejected\";\n (res as any).reason = actualReason;\n throw actualReason;\n },\n ) as ReactPromise<T>;\n res.status = \"pending\";\n return res;\n}\nimport.meta.vitest?.test(\"pending\", async ({ expect }) => {\n // Test with a promise that resolves\n const resolvePromise = Promise.resolve(42);\n const pendingPromise = pending(resolvePromise);\n\n // Initially it should be pending\n expect(pendingPromise.status).toBe(\"pending\");\n\n // After resolution, it should be fulfilled\n await resolvePromise;\n // Need to wait a tick for the then handler to execute\n await new Promise(resolve => setTimeout(resolve, 0));\n expect(pendingPromise.status).toBe(\"fulfilled\");\n expect((pendingPromise as { value: number }).value).toBe(42);\n\n // For the rejection test, we'll use a separate test to avoid unhandled rejections\n});\n\n/**\n * Should be used to wrap Promises that are not immediately awaited, so they don't throw an unhandled promise rejection\n * error.\n *\n * Vercel kills serverless functions on unhandled promise rejection errors, so this is important.\n */\nexport function ignoreUnhandledRejection<T extends Promise<any>>(promise: T): void {\n promise.catch(() => {});\n}\nimport.meta.vitest?.test(\"ignoreUnhandledRejection\", async ({ expect }) => {\n // Test with a promise that resolves\n const resolvePromise = Promise.resolve(42);\n ignoreUnhandledRejection(resolvePromise);\n expect(await resolvePromise).toBe(42); // Should still resolve to the same value\n\n // Test with a promise that rejects\n // The promise should still reject, but the rejection is caught internally\n // so it doesn't cause an unhandled rejection error\n const error = new Error(\"Test error\");\n const rejectPromise = Promise.reject(error);\n ignoreUnhandledRejection(rejectPromise);\n await expect(rejectPromise).rejects.toBe(error);\n});\n\n/**\n * See concatStacktraces for more information.\n */\nexport function concatStacktracesIfRejected<T>(promise: Promise<T>): void {\n const currentError = new Error();\n promise.catch(error => {\n if (error instanceof Error) {\n concatStacktraces(error, currentError);\n } else {\n // we can only concatenate errors, so we'll just ignore the non-error\n }\n });\n}\n\nexport async function wait(ms: number) {\n if (!Number.isFinite(ms) || ms < 0) {\n throw new HexclaveAssertionError(`wait() requires a non-negative integer number of milliseconds to wait. (found: ${ms}ms)`);\n }\n if (ms >= 2**31) {\n throw new HexclaveAssertionError(\"The maximum timeout for wait() is 2147483647ms (2**31 - 1). (found: ${ms}ms)\");\n }\n return await traceSpan({ description: 'wait(...)', attributes: { 'stack.wait.ms': ms } }, async (span) => {\n return await new Promise<void>(resolve => setTimeout(resolve, ms));\n });\n}\nimport.meta.vitest?.test(\"wait\", async ({ expect }) => {\n // Test with valid input\n const start = Date.now();\n await wait(10);\n const elapsed = Date.now() - start;\n expect(elapsed).toBeGreaterThanOrEqual(5); // Allow some flexibility in timing\n\n // Test with zero\n await expect(wait(0)).resolves.toBeUndefined();\n\n // Test with negative number\n await expect(wait(-10)).rejects.toThrow(\"wait() requires a non-negative integer\");\n\n // Test with non-finite number\n await expect(wait(NaN)).rejects.toThrow(\"wait() requires a non-negative integer\");\n await expect(wait(Infinity)).rejects.toThrow(\"wait() requires a non-negative integer\");\n\n // Test with too large number\n await expect(wait(2**31)).rejects.toThrow(\"The maximum timeout for wait()\");\n});\n\nexport async function waitUntil(date: Date) {\n return await wait(date.getTime() - Date.now());\n}\nimport.meta.vitest?.test(\"waitUntil\", async ({ expect }) => {\n // Test with future date\n const futureDate = new Date(Date.now() + 10);\n const start = Date.now();\n await waitUntil(futureDate);\n const elapsed = Date.now() - start;\n expect(elapsed).toBeGreaterThanOrEqual(5); // Allow some flexibility in timing\n\n // Test with past date - this will throw because wait() requires non-negative time\n // We need to verify it throws the correct error\n try {\n await waitUntil(new Date(Date.now() - 1000));\n expect.fail(\"Should have thrown an error\");\n } catch (error) {\n expect(error).toBeInstanceOf(HexclaveAssertionError);\n expect((error as Error).message).toContain(\"wait() requires a non-negative integer\");\n }\n});\n\nexport function runAsynchronouslyWithAlert(...args: Parameters<typeof runAsynchronously>) {\n return runAsynchronously(\n args[0],\n {\n ...args[1],\n onError: error => {\n const nodeEnv = getProcessEnv(\"NODE_ENV\");\n if (KnownError.isKnownError(error) && nodeEnv?.includes(\"production\")) {\n alert(error.message);\n } else {\n alert(`An unhandled error occurred. Please ${nodeEnv === \"development\" ? `check the browser console for the full error.` : \"report this to the developer.\"}\\n\\n${error}`);\n }\n args[1]?.onError?.(error);\n },\n },\n ...args.slice(2) as [],\n );\n}\nimport.meta.vitest?.test(\"runAsynchronouslyWithAlert\", ({ expect }) => {\n // Simple test to verify the function calls runAsynchronously\n // We can't easily test the alert functionality without mocking\n const testFn = () => Promise.resolve(\"test\");\n const testOptions = { noErrorLogging: true };\n\n // Just verify it doesn't throw\n expect(() => runAsynchronouslyWithAlert(testFn, testOptions)).not.toThrow();\n\n // We can't easily test the error handling without mocking, so we'll\n // just verify the function exists and can be called\n expect(typeof runAsynchronouslyWithAlert).toBe(\"function\");\n});\n\nexport function runAsynchronously(\n promiseOrFunc: void | Promise<unknown> | (() => void | Promise<unknown>) | undefined,\n options: {\n noErrorLogging?: boolean,\n onError?: (error: Error) => void,\n } = {},\n): void {\n if (typeof promiseOrFunc === \"function\") {\n promiseOrFunc = promiseOrFunc();\n }\n if (promiseOrFunc) {\n concatStacktracesIfRejected(promiseOrFunc);\n promiseOrFunc.catch(error => {\n options.onError?.(error);\n const newError = new HexclaveAssertionError(\n \"Uncaught error in asynchronous function: \" + errorToNiceString(error),\n { cause: error },\n );\n if (!options.noErrorLogging) {\n captureError(\"runAsynchronously\", newError);\n }\n });\n }\n}\nimport.meta.vitest?.test(\"runAsynchronously\", ({ expect }) => {\n // Simple test to verify the function exists and can be called\n const testFn = () => Promise.resolve(\"test\");\n\n // Just verify it doesn't throw\n expect(() => runAsynchronously(testFn)).not.toThrow();\n expect(() => runAsynchronously(Promise.resolve(\"test\"))).not.toThrow();\n expect(() => runAsynchronously(undefined)).not.toThrow();\n\n // We can't easily test the error handling without mocking, so we'll\n // just verify the function exists and can be called with options\n expect(() => runAsynchronously(testFn, { noErrorLogging: true })).not.toThrow();\n expect(() => runAsynchronously(testFn, { onError: () => {} })).not.toThrow();\n});\n\n\nclass TimeoutError extends Error {\n constructor(public readonly ms: number) {\n super(`Timeout after ${ms}ms`);\n this.name = \"TimeoutError\";\n }\n}\n\nexport async function timeout<T>(promiseOrFunc: Promise<T> | (() => Promise<T>), ms: number): Promise<Result<T, TimeoutError>> {\n const promise = typeof promiseOrFunc === \"function\" ? promiseOrFunc() : promiseOrFunc;\n return await Promise.race([\n promise.then(value => Result.ok(value)),\n wait(ms).then(() => Result.error(new TimeoutError(ms))),\n ]);\n}\nimport.meta.vitest?.test(\"timeout\", async ({ expect }) => {\n // Test with a promise that resolves quickly\n const fastPromise = Promise.resolve(42);\n const fastResult = await timeout(fastPromise, 100);\n expect(fastResult.status).toBe(\"ok\");\n if (fastResult.status === \"ok\") {\n expect(fastResult.data).toBe(42);\n }\n\n // Test with a promise that takes longer than the timeout\n const slowPromise = new Promise(resolve => setTimeout(() => resolve(\"too late\"), 50));\n const slowResult = await timeout(slowPromise, 10);\n expect(slowResult.status).toBe(\"error\");\n if (slowResult.status === \"error\") {\n expect(slowResult.error).toBeInstanceOf(TimeoutError);\n expect((slowResult.error as TimeoutError).ms).toBe(10);\n }\n});\n\nexport async function timeoutThrow<T>(promise: Promise<T>, ms: number): Promise<T> {\n return Result.orThrow(await timeout(promise, ms));\n}\nimport.meta.vitest?.test(\"timeoutThrow\", async ({ expect }) => {\n // Test with a promise that resolves quickly\n const fastPromise = Promise.resolve(42);\n const fastResult = await timeoutThrow(fastPromise, 100);\n expect(fastResult).toBe(42);\n\n // Test with a promise that takes longer than the timeout\n const slowPromise = new Promise(resolve => setTimeout(() => resolve(\"too late\"), 50));\n await expect(timeoutThrow(slowPromise, 10)).rejects.toThrow(\"Timeout after 10ms\");\n await expect(timeoutThrow(slowPromise, 10)).rejects.toBeInstanceOf(TimeoutError);\n});\n\n\n/**\n * Maps over `items` with `fn`, running at most `concurrency` invocations at a time.\n *\n * Unlike `Promise.all(items.map(fn))`, this bounds the number of in-flight\n * promises, which matters when `fn` hits a shared resource (e.g. a database) and\n * an unbounded fan-out could exhaust connections or overload a replica. Results\n * are returned in input order regardless of completion order, and the first\n * rejection aborts further scheduling — already in-flight workers still settle\n * but no new items are started.\n */\nexport async function mapWithConcurrency<T, R>(\n items: readonly T[],\n concurrency: number,\n fn: (item: T, index: number) => Promise<R>,\n): Promise<R[]> {\n if (!Number.isInteger(concurrency) || concurrency < 1) {\n throw new HexclaveAssertionError(`mapWithConcurrency requires a positive integer concurrency, got ${concurrency}`);\n }\n const results = new Array<R>(items.length);\n let nextIndex = 0;\n let aborted = false;\n const worker = async () => {\n while (!aborted) {\n // Claim an index synchronously before awaiting so workers never process the same item.\n const index = nextIndex++;\n if (index >= items.length) return;\n try {\n // Bounds-checked above; `?? throwErr(…)` is unsuitable because T may legitimately be null/undefined\n results[index] = await fn(items[index] as T, index);\n } catch (error) {\n aborted = true;\n throw error;\n }\n }\n };\n const workerCount = Math.min(concurrency, items.length);\n await Promise.all(Array.from({ length: workerCount }, () => worker()));\n return results;\n}\nimport.meta.vitest?.test(\"mapWithConcurrency\", async ({ expect }) => {\n // Preserves input order regardless of completion order.\n const ordered = await mapWithConcurrency([30, 10, 20], 3, async (ms, index) => {\n await wait(ms);\n return `${index}:${ms}`;\n });\n expect(ordered).toEqual([\"0:30\", \"1:10\", \"2:20\"]);\n\n // Never exceeds the configured concurrency.\n let inFlight = 0;\n let maxInFlight = 0;\n await mapWithConcurrency(Array.from({ length: 10 }, (_, i) => i), 3, async () => {\n inFlight++;\n maxInFlight = Math.max(maxInFlight, inFlight);\n await wait(5);\n inFlight--;\n });\n expect(maxInFlight).toBe(3);\n\n // Empty input spawns no workers and returns an empty array.\n expect(await mapWithConcurrency([], 4, async () => 1)).toEqual([]);\n\n // Invalid concurrency fails loudly.\n await expect(mapWithConcurrency([1], 0, async (x) => x)).rejects.toThrow(\"positive integer concurrency\");\n});\n\nexport type RateLimitOptions = {\n /**\n * The number of requests to process in parallel. Currently only 1 is supported.\n */\n concurrency: 1,\n\n /**\n * If true, multiple requests waiting at the same time will be reduced to just one. Default is false.\n */\n batchCalls?: boolean,\n\n /**\n * Waits for throttleMs since the start of last request before starting the next request. Default is 0.\n */\n throttleMs?: number,\n\n /**\n * Waits for gapMs since the end of last request before starting the next request. Default is 0.\n */\n gapMs?: number,\n\n /**\n * Waits until there have been no new requests for debounceMs before starting a new request. Default is 0.\n */\n debounceMs?: number,\n};\n\nexport function rateLimited<T>(\n func: () => Promise<T>,\n options: RateLimitOptions,\n): () => Promise<T> {\n let waitUntil = performance.now();\n let queue: [(t: T) => void, (e: unknown) => void][] = [];\n let addedToQueueCallbacks = new Map<string, () => void>;\n\n const next = async () => {\n while (true) {\n if (waitUntil > performance.now()) {\n await wait(Math.max(1, waitUntil - performance.now() + 1));\n } else if (queue.length === 0) {\n const uuid = generateUuid();\n await new Promise<void>(resolve => {\n addedToQueueCallbacks.set(uuid, resolve);\n });\n addedToQueueCallbacks.delete(uuid);\n } else {\n break;\n }\n }\n const nextFuncs = options.batchCalls ? queue.splice(0, queue.length) : [queue.shift()!];\n\n const start = performance.now();\n const value = await Result.fromPromise(func());\n const end = performance.now();\n\n waitUntil = Math.max(\n waitUntil,\n start + (options.throttleMs ?? 0),\n end + (options.gapMs ?? 0),\n );\n\n for (const nextFunc of nextFuncs) {\n if (value.status === \"ok\") {\n nextFunc[0](value.data);\n } else {\n nextFunc[1](value.error);\n }\n }\n };\n\n runAsynchronously(async () => {\n while (true) {\n await next();\n }\n });\n\n return () => {\n return new Promise<T>((resolve, reject) => {\n waitUntil = Math.max(\n waitUntil,\n performance.now() + (options.debounceMs ?? 0),\n );\n queue.push([resolve, reject]);\n addedToQueueCallbacks.forEach(cb => cb());\n });\n };\n}\n\nexport function throttled<T, A extends any[]>(func: (...args: A) => Promise<T>, delayMs: number): (...args: A) => Promise<T> {\n let timeout: ReturnType<typeof setTimeout> | null = null;\n let nextAvailable: Promise<T> | null = null;\n return async (...args) => {\n while (nextAvailable !== null) {\n await nextAvailable;\n }\n nextAvailable = new Promise<T>(resolve => {\n timeout = setTimeout(() => {\n nextAvailable = null;\n resolve(func(...args));\n }, delayMs);\n });\n return await nextAvailable;\n };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hexclave/shared",
3
- "version": "1.0.28",
3
+ "version": "1.0.30",
4
4
  "repository": "https://github.com/hexclave/hexclave",
5
5
  "files": [
6
6
  "README.md",
@@ -434,6 +434,71 @@ import.meta.vitest?.test("timeoutThrow", async ({ expect }) => {
434
434
  });
435
435
 
436
436
 
437
+ /**
438
+ * Maps over `items` with `fn`, running at most `concurrency` invocations at a time.
439
+ *
440
+ * Unlike `Promise.all(items.map(fn))`, this bounds the number of in-flight
441
+ * promises, which matters when `fn` hits a shared resource (e.g. a database) and
442
+ * an unbounded fan-out could exhaust connections or overload a replica. Results
443
+ * are returned in input order regardless of completion order, and the first
444
+ * rejection aborts further scheduling — already in-flight workers still settle
445
+ * but no new items are started.
446
+ */
447
+ export async function mapWithConcurrency<T, R>(
448
+ items: readonly T[],
449
+ concurrency: number,
450
+ fn: (item: T, index: number) => Promise<R>,
451
+ ): Promise<R[]> {
452
+ if (!Number.isInteger(concurrency) || concurrency < 1) {
453
+ throw new HexclaveAssertionError(`mapWithConcurrency requires a positive integer concurrency, got ${concurrency}`);
454
+ }
455
+ const results = new Array<R>(items.length);
456
+ let nextIndex = 0;
457
+ let aborted = false;
458
+ const worker = async () => {
459
+ while (!aborted) {
460
+ // Claim an index synchronously before awaiting so workers never process the same item.
461
+ const index = nextIndex++;
462
+ if (index >= items.length) return;
463
+ try {
464
+ // Bounds-checked above; `?? throwErr(…)` is unsuitable because T may legitimately be null/undefined
465
+ results[index] = await fn(items[index] as T, index);
466
+ } catch (error) {
467
+ aborted = true;
468
+ throw error;
469
+ }
470
+ }
471
+ };
472
+ const workerCount = Math.min(concurrency, items.length);
473
+ await Promise.all(Array.from({ length: workerCount }, () => worker()));
474
+ return results;
475
+ }
476
+ import.meta.vitest?.test("mapWithConcurrency", async ({ expect }) => {
477
+ // Preserves input order regardless of completion order.
478
+ const ordered = await mapWithConcurrency([30, 10, 20], 3, async (ms, index) => {
479
+ await wait(ms);
480
+ return `${index}:${ms}`;
481
+ });
482
+ expect(ordered).toEqual(["0:30", "1:10", "2:20"]);
483
+
484
+ // Never exceeds the configured concurrency.
485
+ let inFlight = 0;
486
+ let maxInFlight = 0;
487
+ await mapWithConcurrency(Array.from({ length: 10 }, (_, i) => i), 3, async () => {
488
+ inFlight++;
489
+ maxInFlight = Math.max(maxInFlight, inFlight);
490
+ await wait(5);
491
+ inFlight--;
492
+ });
493
+ expect(maxInFlight).toBe(3);
494
+
495
+ // Empty input spawns no workers and returns an empty array.
496
+ expect(await mapWithConcurrency([], 4, async () => 1)).toEqual([]);
497
+
498
+ // Invalid concurrency fails loudly.
499
+ await expect(mapWithConcurrency([1], 0, async (x) => x)).rejects.toThrow("positive integer concurrency");
500
+ });
501
+
437
502
  export type RateLimitOptions = {
438
503
  /**
439
504
  * The number of requests to process in parallel. Currently only 1 is supported.