@hexclave/shared 1.0.52 → 1.0.54

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 (107) hide show
  1. package/dist/{admin-interface-D9wMduWc.d.ts → admin-interface-DuboQXpC.d.ts} +11 -11
  2. package/dist/{admin-interface-D9wMduWc.d.ts.map → admin-interface-DuboQXpC.d.ts.map} +1 -1
  3. package/dist/{admin-metrics-bsOkw4YQ.d.ts → admin-metrics-IxLyJ-cP.d.ts} +18 -18
  4. package/dist/{admin-metrics-bsOkw4YQ.d.ts.map → admin-metrics-IxLyJ-cP.d.ts.map} +1 -1
  5. package/dist/ai/llms/llms.d.ts.map +1 -1
  6. package/dist/ai/llms/llms.js +9 -0
  7. package/dist/ai/llms/llms.js.map +1 -1
  8. package/dist/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.js +4 -4
  9. package/dist/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.js.map +1 -1
  10. package/dist/{client-interface-BQz4jNAC.d.ts → client-interface-Ca6UZywk.d.ts} +9 -9
  11. package/dist/{client-interface-BQz4jNAC.d.ts.map → client-interface-Ca6UZywk.d.ts.map} +1 -1
  12. package/dist/config/schema.d.ts +1 -1
  13. package/dist/config-authoring.d.ts +1 -1
  14. package/dist/config-rendering.d.ts +9 -1
  15. package/dist/config-rendering.d.ts.map +1 -1
  16. package/dist/config-rendering.js +2 -2
  17. package/dist/config-rendering.js.map +1 -1
  18. package/dist/{current-user-BBoN3hsn.d.ts → current-user-DNxac4sS.d.ts} +15 -15
  19. package/dist/{current-user-BBoN3hsn.d.ts.map → current-user-DNxac4sS.d.ts.map} +1 -1
  20. package/dist/dates-BRAvSk6L.d.ts.map +1 -1
  21. package/dist/{email-outbox-B1wmVPRG.d.ts → email-outbox-ILs0LlGd.d.ts} +185 -185
  22. package/dist/{email-outbox-B1wmVPRG.d.ts.map → email-outbox-ILs0LlGd.d.ts.map} +1 -1
  23. package/dist/esm/ai/llms/llms.d.ts.map +1 -1
  24. package/dist/esm/ai/llms/llms.js +9 -0
  25. package/dist/esm/ai/llms/llms.js.map +1 -1
  26. package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.js +4 -4
  27. package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.js.map +1 -1
  28. package/dist/esm/config/schema.d.ts +99 -99
  29. package/dist/esm/config-rendering.d.ts +9 -1
  30. package/dist/esm/config-rendering.d.ts.map +1 -1
  31. package/dist/esm/config-rendering.js +2 -3
  32. package/dist/esm/config-rendering.js.map +1 -1
  33. package/dist/esm/interface/admin-metrics.d.ts +17 -17
  34. package/dist/esm/interface/conversations.d.ts +30 -30
  35. package/dist/esm/interface/crud/current-user.d.ts +14 -14
  36. package/dist/esm/interface/crud/email-outbox.d.ts +184 -184
  37. package/dist/esm/interface/crud/products.d.ts +17 -17
  38. package/dist/esm/interface/crud/project-api-keys.d.ts +4 -4
  39. package/dist/esm/interface/crud/projects.d.ts +28 -28
  40. package/dist/esm/interface/crud/team-member-profiles.d.ts +24 -24
  41. package/dist/esm/interface/crud/transactions.d.ts +15 -15
  42. package/dist/esm/interface/crud/transactions.d.ts.map +1 -1
  43. package/dist/esm/interface/crud/users.d.ts +16 -16
  44. package/dist/esm/interface/plan-usage.d.ts +1 -1
  45. package/dist/esm/interface/webhooks.d.ts +2 -2
  46. package/dist/esm/known-errors.d.ts +3 -3
  47. package/dist/esm/package.json +18 -0
  48. package/dist/esm/schema-fields.d.ts +5 -5
  49. package/dist/esm/sessions.d.ts +8 -8
  50. package/dist/esm/utils/dates.d.ts.map +1 -1
  51. package/dist/esm/utils/dates.js +2 -1
  52. package/dist/esm/utils/dates.js.map +1 -1
  53. package/dist/helpers/password.d.ts +1 -1
  54. package/dist/helpers/production-mode.d.ts +1 -1
  55. package/dist/index.d.ts +4 -4
  56. package/dist/interface/admin-interface.d.ts +2 -2
  57. package/dist/interface/admin-metrics.d.ts +1 -1
  58. package/dist/interface/client-interface.d.ts +1 -1
  59. package/dist/interface/conversations.d.ts +30 -30
  60. package/dist/interface/crud/current-user.d.ts +1 -1
  61. package/dist/interface/crud/email-outbox.d.ts +1 -1
  62. package/dist/interface/crud/products.d.ts +1 -1
  63. package/dist/interface/crud/project-api-keys.d.ts +1 -1
  64. package/dist/interface/crud/projects.d.ts +1 -1
  65. package/dist/interface/crud/team-member-profiles.d.ts +1 -1
  66. package/dist/interface/crud/transactions.d.ts +1 -1
  67. package/dist/interface/crud/users.d.ts +1 -1
  68. package/dist/interface/plan-usage.d.ts +1 -1
  69. package/dist/interface/server-interface.d.ts +1 -1
  70. package/dist/interface/webhooks.d.ts +1 -1
  71. package/dist/{known-errors-Bm4a61wL.d.ts → known-errors-g7JC8z58.d.ts} +4 -4
  72. package/dist/{known-errors-Bm4a61wL.d.ts.map → known-errors-g7JC8z58.d.ts.map} +1 -1
  73. package/dist/known-errors.d.ts +1 -1
  74. package/dist/{plan-usage-BaDxcLZ1.d.ts → plan-usage-D0kWw4HR.d.ts} +2 -2
  75. package/dist/{plan-usage-BaDxcLZ1.d.ts.map → plan-usage-D0kWw4HR.d.ts.map} +1 -1
  76. package/dist/{products-D4o8A2W0.d.ts → products-C98fCS-1.d.ts} +18 -18
  77. package/dist/{products-D4o8A2W0.d.ts.map → products-C98fCS-1.d.ts.map} +1 -1
  78. package/dist/{project-api-keys--7V5X_UW.d.ts → project-api-keys-P97ElZZe.d.ts} +5 -5
  79. package/dist/{project-api-keys--7V5X_UW.d.ts.map → project-api-keys-P97ElZZe.d.ts.map} +1 -1
  80. package/dist/{projects-CFTNXRfG.d.ts → projects-BM2a4sfO.d.ts} +29 -29
  81. package/dist/{projects-CFTNXRfG.d.ts.map → projects-BM2a4sfO.d.ts.map} +1 -1
  82. package/dist/{schema-DV0SQUbY.d.ts → schema-Bq1TSMFV.d.ts} +100 -100
  83. package/dist/{schema-DV0SQUbY.d.ts.map → schema-Bq1TSMFV.d.ts.map} +1 -1
  84. package/dist/{schema-fields-DZtDQEEO.d.ts → schema-fields-Cch3V5nd.d.ts} +6 -6
  85. package/dist/{schema-fields-DZtDQEEO.d.ts.map → schema-fields-Cch3V5nd.d.ts.map} +1 -1
  86. package/dist/schema-fields.d.ts +1 -1
  87. package/dist/{server-interface-BjY-eS4Q.d.ts → server-interface-yKuDPrQI.d.ts} +8 -8
  88. package/dist/{server-interface-BjY-eS4Q.d.ts.map → server-interface-yKuDPrQI.d.ts.map} +1 -1
  89. package/dist/{sessions-CI-0TBQb.d.ts → sessions-DCTyJhi3.d.ts} +10 -10
  90. package/dist/{sessions-CI-0TBQb.d.ts.map → sessions-DCTyJhi3.d.ts.map} +1 -1
  91. package/dist/sessions.d.ts +1 -1
  92. package/dist/{team-member-profiles-BoDEqx-0.d.ts → team-member-profiles-CaD1OJqt.d.ts} +25 -25
  93. package/dist/{team-member-profiles-BoDEqx-0.d.ts.map → team-member-profiles-CaD1OJqt.d.ts.map} +1 -1
  94. package/dist/{transactions-CHNUJ42J.d.ts → transactions-CrAZLdGo.d.ts} +16 -16
  95. package/dist/transactions-CrAZLdGo.d.ts.map +1 -0
  96. package/dist/{users-DHNRwoSz.d.ts → users-DSpJgn9Z.d.ts} +18 -18
  97. package/dist/{users-DHNRwoSz.d.ts.map → users-DSpJgn9Z.d.ts.map} +1 -1
  98. package/dist/utils/dates.js +2 -1
  99. package/dist/utils/dates.js.map +1 -1
  100. package/dist/{webhooks-8Ts1Clcb.d.ts → webhooks-_aXIgZBW.d.ts} +3 -3
  101. package/dist/{webhooks-8Ts1Clcb.d.ts.map → webhooks-_aXIgZBW.d.ts.map} +1 -1
  102. package/package.json +1 -1
  103. package/src/ai/llms/llms.ts +9 -0
  104. package/src/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.ts +4 -4
  105. package/src/config-rendering.ts +5 -6
  106. package/src/utils/dates.tsx +22 -1
  107. package/dist/transactions-CHNUJ42J.d.ts.map +0 -1
@@ -1,5 +1,5 @@
1
1
  import { c as CrudTypeOf, s as CrudSchemaFromOptions } from "./crud-CqmEW9s6.js";
2
- import { t as WebhookEvent } from "./webhooks-8Ts1Clcb.js";
2
+ import { t as WebhookEvent } from "./webhooks-_aXIgZBW.js";
3
3
  import * as yup$1 from "yup";
4
4
  import { InferType } from "yup";
5
5
 
@@ -80,9 +80,9 @@ declare const usersCrudServerReadSchema: yup$1.ObjectSchema<{
80
80
  client_metadata?: {} | null | undefined;
81
81
  client_read_only_metadata?: {} | null | undefined;
82
82
  server_metadata?: {} | null | undefined;
83
- display_name: string;
84
83
  id: string;
85
84
  created_at_millis: number;
85
+ display_name: string;
86
86
  profile_image_url: string | null;
87
87
  } | null;
88
88
  selected_team_id: string | null;
@@ -162,15 +162,14 @@ declare const usersCrudServerReadSchema: yup$1.ObjectSchema<{
162
162
  requires_totp_mfa: undefined;
163
163
  }, "">;
164
164
  declare const usersCrudServerCreateSchema: yup$1.ObjectSchema<{
165
- primary_email: string | null | undefined;
165
+ country_code: string | null | undefined;
166
166
  display_name: string | null | undefined;
167
+ primary_email: string | null | undefined;
168
+ profile_image_url: string | null | undefined;
169
+ is_anonymous: boolean | undefined;
167
170
  client_metadata: {} | null | undefined;
168
171
  client_read_only_metadata: {} | null | undefined;
169
172
  server_metadata: {} | null | undefined;
170
- is_anonymous: boolean | undefined;
171
- password: string | null | undefined;
172
- country_code: string | null | undefined;
173
- profile_image_url: string | null | undefined;
174
173
  primary_email_verified: boolean | undefined;
175
174
  primary_email_auth_enabled: boolean | undefined;
176
175
  otp_auth_enabled: boolean | undefined;
@@ -184,12 +183,13 @@ declare const usersCrudServerCreateSchema: yup$1.ObjectSchema<{
184
183
  free_trial_abuse: number;
185
184
  };
186
185
  } | undefined;
186
+ password: string | null | undefined;
187
187
  password_hash: string | undefined;
188
188
  totp_secret_base64: string | null | undefined;
189
189
  } & {
190
190
  oauth_providers: {
191
- email: string | null;
192
191
  id: string;
192
+ email: string | null;
193
193
  account_id: string;
194
194
  }[] | undefined;
195
195
  is_anonymous: boolean | undefined;
@@ -233,9 +233,9 @@ declare const usersCrud: CrudSchemaFromOptions<{
233
233
  client_metadata?: {} | null | undefined;
234
234
  client_read_only_metadata?: {} | null | undefined;
235
235
  server_metadata?: {} | null | undefined;
236
- display_name: string;
237
236
  id: string;
238
237
  created_at_millis: number;
238
+ display_name: string;
239
239
  profile_image_url: string | null;
240
240
  } | null;
241
241
  selected_team_id: string | null;
@@ -368,15 +368,14 @@ declare const usersCrud: CrudSchemaFromOptions<{
368
368
  };
369
369
  }, "">;
370
370
  serverCreateSchema: yup$1.ObjectSchema<{
371
- primary_email: string | null | undefined;
371
+ country_code: string | null | undefined;
372
372
  display_name: string | null | undefined;
373
+ primary_email: string | null | undefined;
374
+ profile_image_url: string | null | undefined;
375
+ is_anonymous: boolean | undefined;
373
376
  client_metadata: {} | null | undefined;
374
377
  client_read_only_metadata: {} | null | undefined;
375
378
  server_metadata: {} | null | undefined;
376
- is_anonymous: boolean | undefined;
377
- password: string | null | undefined;
378
- country_code: string | null | undefined;
379
- profile_image_url: string | null | undefined;
380
379
  primary_email_verified: boolean | undefined;
381
380
  primary_email_auth_enabled: boolean | undefined;
382
381
  otp_auth_enabled: boolean | undefined;
@@ -390,12 +389,13 @@ declare const usersCrud: CrudSchemaFromOptions<{
390
389
  free_trial_abuse: number;
391
390
  };
392
391
  } | undefined;
392
+ password: string | null | undefined;
393
393
  password_hash: string | undefined;
394
394
  totp_secret_base64: string | null | undefined;
395
395
  } & {
396
396
  oauth_providers: {
397
- email: string | null;
398
397
  id: string;
398
+ email: string | null;
399
399
  account_id: string;
400
400
  }[] | undefined;
401
401
  is_anonymous: boolean | undefined;
@@ -467,9 +467,9 @@ declare const userCreatedWebhookEvent: WebhookEvent<yup$1.ObjectSchema<{
467
467
  client_metadata?: {} | null | undefined;
468
468
  client_read_only_metadata?: {} | null | undefined;
469
469
  server_metadata?: {} | null | undefined;
470
- display_name: string;
471
470
  id: string;
472
471
  created_at_millis: number;
472
+ display_name: string;
473
473
  profile_image_url: string | null;
474
474
  } | null;
475
475
  selected_team_id: string | null;
@@ -558,9 +558,9 @@ declare const userUpdatedWebhookEvent: WebhookEvent<yup$1.ObjectSchema<{
558
558
  client_metadata?: {} | null | undefined;
559
559
  client_read_only_metadata?: {} | null | undefined;
560
560
  server_metadata?: {} | null | undefined;
561
- display_name: string;
562
561
  id: string;
563
562
  created_at_millis: number;
563
+ display_name: string;
564
564
  profile_image_url: string | null;
565
565
  } | null;
566
566
  selected_team_id: string | null;
@@ -650,4 +650,4 @@ declare const userDeletedWebhookEvent: WebhookEvent<yup$1.ObjectSchema<{
650
650
  }, "">>;
651
651
  //#endregion
652
652
  export { userCreatedWebhookEvent as a, usersCrud as c, usersCrudServerReadSchema as d, usersCrudServerUpdateSchema as f, signUpRiskScoresSchema as i, usersCrudServerCreateSchema as l, UsersCrud as n, userDeletedWebhookEvent as o, riskScoreFieldSchema as r, userUpdatedWebhookEvent as s, SignUpRiskScoresCrud as t, usersCrudServerDeleteSchema as u };
653
- //# sourceMappingURL=users-DHNRwoSz.d.ts.map
653
+ //# sourceMappingURL=users-DSpJgn9Z.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"users-DHNRwoSz.d.ts","names":[],"sources":["../src/interface/crud/users.ts"],"mappings":";;;;;;cAea,oBAAA,EAAoB,KAAA,CAAA,YAAA,SAAA,KAAA,CAAA,SAAA;AAAA,cACpB,sBAAA,EAAsB,KAAA,CAAA,YAAA;;;;;GAKjC,KAAA,CAAA,SAAA;;;;;;KACU,oBAAA,GAAuB,SAAS,QAAQ,sBAAA;AAAA,cAqBvC,2BAAA,EAA2B,KAAA,CAAA,YAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GAyBvC,KAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;cAEY,yBAAA,EAAyB,KAAA,CAAA,YAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CrC,KAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAEY,2BAAA,EAA2B,KAAA,CAAA,YAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAM3B,KAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAEA,2BAAA,EAA2B,KAAA,CAAA,WAAA,iBAAA,KAAA,CAAA,SAAA;AAAA,cAE3B,SAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAiCD,SAAA,GAAY,UAAU,QAAQ,SAAA;AAAA,cAc7B,uBAAA,EAAuB,YAAA,CAAA,KAAA,CAAA,YAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cACvB,uBAAA,EAAuB,YAAA,CAAA,KAAA,CAAA,YAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAQvB,uBAAA,EAAuB,YAAA,CAAA,KAAA,CAAA,YAAA"}
1
+ {"version":3,"file":"users-DSpJgn9Z.d.ts","names":[],"sources":["../src/interface/crud/users.ts"],"mappings":";;;;;;cAea,oBAAA,EAAoB,KAAA,CAAA,YAAA,SAAA,KAAA,CAAA,SAAA;AAAA,cACpB,sBAAA,EAAsB,KAAA,CAAA,YAAA;;;;;GAKjC,KAAA,CAAA,SAAA;;;;;;KACU,oBAAA,GAAuB,SAAS,QAAQ,sBAAA;AAAA,cAqBvC,2BAAA,EAA2B,KAAA,CAAA,YAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GAyBvC,KAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;cAEY,yBAAA,EAAyB,KAAA,CAAA,YAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CrC,KAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAEY,2BAAA,EAA2B,KAAA,CAAA,YAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAM3B,KAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAEA,2BAAA,EAA2B,KAAA,CAAA,WAAA,iBAAA,KAAA,CAAA,SAAA;AAAA,cAE3B,SAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAiCD,SAAA,GAAY,UAAU,QAAQ,SAAA;AAAA,cAc7B,uBAAA,EAAuB,YAAA,CAAA,KAAA,CAAA,YAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cACvB,uBAAA,EAAuB,YAAA,CAAA,KAAA,CAAA,YAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAQvB,uBAAA,EAAuB,YAAA,CAAA,KAAA,CAAA,YAAA"}
@@ -55,8 +55,9 @@ function getInputDatetimeLocalString(date) {
55
55
  date.setMinutes(date.getMinutes() - date.getTimezoneOffset());
56
56
  return date.toISOString().slice(0, 19);
57
57
  }
58
- function applyInterval(date, times, interval) {
58
+ function applyInterval(inputDate, times, interval) {
59
59
  if (!___schema_fields_js.intervalSchema.isValidSync(interval)) throw new __errors_js.HexclaveAssertionError(`Invalid interval`, { interval });
60
+ const date = new Date(inputDate);
60
61
  const [amount, unit] = interval;
61
62
  switch (unit) {
62
63
  case "millisecond":
@@ -1 +1 @@
1
- {"version":3,"file":"dates.js","names":["intervalSchema","HexclaveAssertionError"],"sources":["../../src/utils/dates.tsx"],"sourcesContent":["import { intervalSchema } from \"../schema-fields\";\nimport { HexclaveAssertionError } from \"./errors\";\nimport { remainder } from \"./math\";\n\nexport function isWeekend(date: Date): boolean {\n return date.getDay() === 0 || date.getDay() === 6;\n}\n\nimport.meta.vitest?.test(\"isWeekend\", ({ expect }) => {\n // Sunday (day 0)\n expect(isWeekend(new Date(2023, 0, 1))).toBe(true);\n // Saturday (day 6)\n expect(isWeekend(new Date(2023, 0, 7))).toBe(true);\n // Monday (day 1)\n expect(isWeekend(new Date(2023, 0, 2))).toBe(false);\n // Friday (day 5)\n expect(isWeekend(new Date(2023, 0, 6))).toBe(false);\n});\n\nconst agoUnits = [\n [60, 'second'],\n [60, 'minute'],\n [24, 'hour'],\n [7, 'day'],\n [5, 'week'],\n] as const;\n\nexport function fromNow(date: Date): string {\n return fromNowDetailed(date).result;\n}\n\nimport.meta.vitest?.test(\"fromNow\", ({ expect }) => {\n // Set a fixed date for testing\n const fixedDate = new Date(\"2023-01-15T12:00:00.000Z\");\n\n // Use Vitest's fake timers\n import.meta.vitest?.vi.useFakeTimers();\n import.meta.vitest?.vi.setSystemTime(fixedDate);\n\n // Test past times\n expect(fromNow(new Date(\"2023-01-15T11:59:50.000Z\"))).toBe(\"just now\");\n expect(fromNow(new Date(\"2023-01-15T11:59:00.000Z\"))).toBe(\"1 minute ago\");\n expect(fromNow(new Date(\"2023-01-15T11:00:00.000Z\"))).toBe(\"1 hour ago\");\n expect(fromNow(new Date(\"2023-01-14T12:00:00.000Z\"))).toBe(\"1 day ago\");\n expect(fromNow(new Date(\"2023-01-08T12:00:00.000Z\"))).toBe(\"1 week ago\");\n\n // Test future times\n expect(fromNow(new Date(\"2023-01-15T12:00:10.000Z\"))).toBe(\"just now\");\n expect(fromNow(new Date(\"2023-01-15T12:01:00.000Z\"))).toBe(\"in 1 minute\");\n expect(fromNow(new Date(\"2023-01-15T13:00:00.000Z\"))).toBe(\"in 1 hour\");\n expect(fromNow(new Date(\"2023-01-16T12:00:00.000Z\"))).toBe(\"in 1 day\");\n expect(fromNow(new Date(\"2023-01-22T12:00:00.000Z\"))).toBe(\"in 1 week\");\n\n // Test very old dates (should use date format)\n expect(fromNow(new Date(\"2022-01-15T12:00:00.000Z\"))).toMatch(/Jan 15, 2022/);\n\n // Restore real timers\n import.meta.vitest?.vi.useRealTimers();\n});\n\nexport function fromNowDetailed(date: Date): {\n result: string,\n /**\n * May be Infinity if the result will never change.\n */\n secondsUntilChange: number,\n} {\n if (!(date instanceof Date)) {\n throw new Error(`fromNow only accepts Date objects (received: ${date})`);\n }\n\n const now = new Date();\n const elapsed = now.getTime() - date.getTime();\n\n let remainingInUnit = Math.abs(elapsed) / 1000;\n if (remainingInUnit < 15) {\n return {\n result: 'just now',\n secondsUntilChange: 15 - remainingInUnit,\n };\n }\n let unitInSeconds = 1;\n for (const [nextUnitSize, unitName] of agoUnits) {\n const rounded = Math.round(remainingInUnit);\n if (rounded < nextUnitSize) {\n if (elapsed < 0) {\n return {\n result: `in ${rounded} ${unitName}${rounded === 1 ? '' : 's'}`,\n secondsUntilChange: remainder((remainingInUnit - rounded + 0.5) * unitInSeconds, unitInSeconds),\n };\n } else {\n return {\n result: `${rounded} ${unitName}${rounded === 1 ? '' : 's'} ago`,\n secondsUntilChange: remainder((rounded - remainingInUnit - 0.5) * unitInSeconds, unitInSeconds),\n };\n }\n }\n unitInSeconds *= nextUnitSize;\n remainingInUnit /= nextUnitSize;\n }\n\n return {\n result: date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }),\n secondsUntilChange: Infinity,\n };\n}\n\n/**\n * Returns a string representation of the given date in the format expected by the `datetime-local` input type.\n */\nexport function getInputDatetimeLocalString(date: Date): string {\n date = new Date(date);\n date.setMinutes(date.getMinutes() - date.getTimezoneOffset());\n return date.toISOString().slice(0, 19);\n}\n\nimport.meta.vitest?.test(\"getInputDatetimeLocalString\", ({ expect }) => {\n // Use Vitest's fake timers to ensure consistent timezone behavior\n import.meta.vitest?.vi.useFakeTimers();\n\n // Test with a specific date\n const mockDate = new Date(\"2023-01-15T12:30:45.000Z\");\n const result = getInputDatetimeLocalString(mockDate);\n\n // The result should be in the format YYYY-MM-DDTHH:MM:SS\n expect(result).toMatch(/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$/);\n\n // Test with different dates\n const dates = [\n new Date(\"2023-01-01T00:00:00.000Z\"),\n new Date(\"2023-06-15T23:59:59.000Z\"),\n new Date(\"2023-12-31T12:34:56.000Z\"),\n ];\n\n for (const date of dates) {\n const result = getInputDatetimeLocalString(date);\n expect(result).toMatch(/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$/);\n }\n\n // Restore real timers\n import.meta.vitest?.vi.useRealTimers();\n});\n\n\nexport type Interval = [number, 'millisecond' | 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'year'];\nexport type DayInterval = [number, 'day' | 'week' | 'month' | 'year'];\n\nfunction applyInterval(date: Date, times: number, interval: Interval): Date {\n if (!intervalSchema.isValidSync(interval)) {\n throw new HexclaveAssertionError(`Invalid interval`, { interval });\n }\n const [amount, unit] = interval;\n switch (unit) {\n case 'millisecond': {\n date.setMilliseconds(date.getMilliseconds() + amount * times);\n break;\n }\n case 'second': {\n date.setSeconds(date.getSeconds() + amount * times);\n break;\n }\n case 'minute': {\n date.setMinutes(date.getMinutes() + amount * times);\n break;\n }\n case 'hour': {\n date.setHours(date.getHours() + amount * times);\n break;\n }\n case 'day': {\n date.setDate(date.getDate() + amount * times);\n break;\n }\n case 'week': {\n date.setDate(date.getDate() + amount * times * 7);\n break;\n }\n case 'month': {\n date.setMonth(date.getMonth() + amount * times);\n break;\n }\n case 'year': {\n date.setFullYear(date.getFullYear() + amount * times);\n break;\n }\n default: {\n throw new HexclaveAssertionError(`Invalid interval despite schema validation`, { interval });\n }\n }\n return date;\n}\n\nexport function subtractInterval(date: Date, interval: Interval): Date {\n return applyInterval(date, -1, interval);\n}\n\n// One-shot local-time interval arithmetic that OVERFLOWS (Jan 31 + 1 month -> Mar 3, via\n// Date#setMonth) rather than clamping. Use this for single-step period math where JS/Stripe overflow\n// semantics are what we want (e.g. computing a subscription's currentPeriodEnd). For recurring\n// grant/reset BOUNDARIES, use `nthDayIntervalMillis` / `getIntervalsElapsed` instead — they clamp to\n// month-end and compute in UTC from a fixed anchor, and mixing the two would drift.\nexport function addInterval(date: Date, interval: Interval): Date {\n return applyInterval(date, 1, interval);\n}\n\nexport const FAR_FUTURE_DATE = new Date(8640000000000000); // 13 Sep 275760 00:00:00 UTC\n\nfunction getMsPerDayIntervalUnit(unit: 'day' | 'week'): number {\n if (unit === 'day') {\n return 24 * 60 * 60 * 1000;\n }\n return 7 * 24 * 60 * 60 * 1000;\n}\n\n\n// The number of full `repeat` intervals elapsed from `anchor` up to (and including) `to`, i.e. the\n// largest n >= 0 with the n-th boundary <= to. Boundaries are the SAME ones `nthDayIntervalMillis`\n// computes (anchor-relative, UTC, month-end clamped), so counting and boundary-computation never\n// disagree — a Jan 31 monthly anchor has elapsed one interval by Feb 28, matching the reset the\n// bulldozer item-repeat fold actually emits. (This is why it must NOT be built on `addInterval`,\n// which is local-time and overflows instead of clamping.)\nexport function getIntervalsElapsed(anchor: Date, to: Date, repeat: DayInterval): number {\n const [amount, unit] = repeat;\n const toMillis = to.getTime();\n if (toMillis <= anchor.getTime()) return 0;\n if (unit === 'day' || unit === 'week') {\n const msPerUnit = getMsPerDayIntervalUnit(unit);\n const diffMs = toMillis - anchor.getTime();\n return Math.floor(diffMs / (msPerUnit * amount));\n }\n // month/year: walk anchor-relative boundaries. The count is small in practice (bounded by the\n // number of billing periods since the anchor), so a linear walk is fine.\n let count = 0;\n while (nthDayIntervalMillis(anchor.getTime(), repeat, count + 1) <= toMillis) count += 1;\n return count;\n}\n\nimport.meta.vitest?.test(\"getIntervalsElapsed\", ({ expect }) => {\n const anchor = new Date('2025-01-01T00:00:00.000Z');\n const to = new Date('2025-01-15T00:00:00.000Z');\n expect(getIntervalsElapsed(anchor, to, [1, 'week'])).toBe(2);\n expect(getIntervalsElapsed(anchor, to, [3, 'day'])).toBe(4);\n\n // Jan 31 monthly anchor: the first boundary is clamped to Feb 28, which is <= Mar 1, so one\n // interval has elapsed (anchor-relative clamped math, consistent with nthDayIntervalMillis).\n const mAnchor = new Date('2023-01-31T00:00:00.000Z');\n const mTo = new Date('2023-03-01T00:00:00.000Z');\n expect(getIntervalsElapsed(mAnchor, mTo, [1, 'month'])).toBe(1);\n\n const yAnchor = new Date('2020-01-01T00:00:00.000Z');\n const yTo = new Date('2022-06-01T00:00:00.000Z');\n expect(getIntervalsElapsed(yAnchor, yTo, [1, 'year'])).toBe(2);\n});\n\n/**\n * The UTC millis of the `occurrence`-th (1-based) repeat of `interval` after `anchorMillis`.\n *\n * Each boundary is computed from the *original* anchor (never by stepping off the previous, possibly\n * clamped, boundary) so the anchor's day-of-month is preserved across resets: a Jan 31 anchor yields\n * Feb 28, Mar 31, Apr 30, ... (matching Stripe's billing-cycle behavior) rather than drifting to\n * Feb 28, Mar 28, ... . Month/year overflow (e.g. Jan 31 -> Feb) is clamped to the target month's\n * last day. Day/week are exact multiples (no calendar involved, so no drift possible).\n *\n * Everything is done in UTC (unlike `addInterval`, which uses local-time Date accessors) so the\n * result is deterministic regardless of the server's timezone — this is relied upon by bulldozer\n * folds, which must be reproducible across machines.\n *\n * `occurrence` 0 returns the anchor itself; `getIntervalsElapsed` counts these same boundaries, so\n * the two are always consistent for a given DayInterval.\n */\nexport function nthDayIntervalMillis(anchorMillis: number, interval: DayInterval, occurrence: number): number {\n const [count, unit] = interval;\n const totalUnits = count * occurrence;\n if (unit === 'day' || unit === 'week') {\n return anchorMillis + totalUnits * getMsPerDayIntervalUnit(unit);\n }\n const anchor = new Date(anchorMillis);\n const monthsToAdd = unit === 'year' ? totalUnits * 12 : totalUnits;\n const absoluteMonth = anchor.getUTCFullYear() * 12 + anchor.getUTCMonth() + monthsToAdd;\n const targetYear = Math.floor(absoluteMonth / 12);\n const targetMonth = absoluteMonth - targetYear * 12;\n // Day 0 of the following month is the last day of the target month, so this gives its length.\n const daysInTargetMonth = new Date(Date.UTC(targetYear, targetMonth + 1, 0)).getUTCDate();\n const clampedDay = Math.min(anchor.getUTCDate(), daysInTargetMonth);\n return Date.UTC(\n targetYear,\n targetMonth,\n clampedDay,\n anchor.getUTCHours(),\n anchor.getUTCMinutes(),\n anchor.getUTCSeconds(),\n anchor.getUTCMilliseconds(),\n );\n}\n\nimport.meta.vitest?.test(\"nthDayIntervalMillis\", ({ expect }) => {\n const at = (iso: string) => new Date(iso).getTime();\n const iso = (millis: number) => new Date(millis).toISOString();\n\n // day / week are exact multiples (no drift), and honor the count.\n const dayAnchor = at('2025-01-01T08:30:00.000Z');\n expect(iso(nthDayIntervalMillis(dayAnchor, [1, 'day'], 1))).toBe('2025-01-02T08:30:00.000Z');\n expect(iso(nthDayIntervalMillis(dayAnchor, [3, 'day'], 4))).toBe('2025-01-13T08:30:00.000Z');\n expect(iso(nthDayIntervalMillis(dayAnchor, [2, 'week'], 3))).toBe('2025-02-12T08:30:00.000Z'); // 6 weeks = 42 days after Jan 1\n\n // Jan 31 monthly: clamp in short months, restore to the anchor day in long ones.\n const jan31 = at('2025-01-31T00:00:00.000Z');\n expect(iso(nthDayIntervalMillis(jan31, [1, 'month'], 1))).toBe('2025-02-28T00:00:00.000Z');\n expect(iso(nthDayIntervalMillis(jan31, [1, 'month'], 2))).toBe('2025-03-31T00:00:00.000Z');\n expect(iso(nthDayIntervalMillis(jan31, [1, 'month'], 3))).toBe('2025-04-30T00:00:00.000Z');\n\n // Anchor-day preservation: a Feb 28 anchor stays on day 28 (never jumps to Mar 31), unlike the\n // Jan 31 anchor above which restores to Mar 31. This is the evidence we compute from the anchor.\n const feb28 = at('2025-02-28T00:00:00.000Z');\n expect(iso(nthDayIntervalMillis(feb28, [1, 'month'], 1))).toBe('2025-03-28T00:00:00.000Z');\n\n // Leap-year Feb: Feb 29 anchor clamps to Feb 28 in a non-leap year and restores in the next leap year.\n const feb29 = at('2024-02-29T00:00:00.000Z');\n expect(iso(nthDayIntervalMillis(feb29, [1, 'year'], 1))).toBe('2025-02-28T00:00:00.000Z');\n expect(iso(nthDayIntervalMillis(feb29, [1, 'year'], 4))).toBe('2028-02-29T00:00:00.000Z');\n // Jan 31 monthly landing in a leap February clamps to Feb 29, not Feb 28.\n const jan31Leap = at('2024-01-31T00:00:00.000Z');\n expect(iso(nthDayIntervalMillis(jan31Leap, [1, 'month'], 1))).toBe('2024-02-29T00:00:00.000Z');\n\n // End-of-December rollover: absolute-month math carries the year.\n const dec31 = at('2025-12-31T00:00:00.000Z');\n expect(iso(nthDayIntervalMillis(dec31, [1, 'month'], 1))).toBe('2026-01-31T00:00:00.000Z');\n expect(iso(nthDayIntervalMillis(dec31, [1, 'month'], 2))).toBe('2026-02-28T00:00:00.000Z');\n expect(iso(nthDayIntervalMillis(dec31, [1, 'month'], 3))).toBe('2026-03-31T00:00:00.000Z');\n expect(iso(nthDayIntervalMillis(dec31, [1, 'year'], 1))).toBe('2026-12-31T00:00:00.000Z');\n\n // Time-of-day is preserved through month clamping.\n const withTime = at('2025-01-31T13:45:59.123Z');\n expect(iso(nthDayIntervalMillis(withTime, [1, 'month'], 1))).toBe('2025-02-28T13:45:59.123Z');\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"dates.js","names":["intervalSchema","HexclaveAssertionError"],"sources":["../../src/utils/dates.tsx"],"sourcesContent":["import { intervalSchema } from \"../schema-fields\";\nimport { HexclaveAssertionError } from \"./errors\";\nimport { remainder } from \"./math\";\n\nexport function isWeekend(date: Date): boolean {\n return date.getDay() === 0 || date.getDay() === 6;\n}\n\nimport.meta.vitest?.test(\"isWeekend\", ({ expect }) => {\n // Sunday (day 0)\n expect(isWeekend(new Date(2023, 0, 1))).toBe(true);\n // Saturday (day 6)\n expect(isWeekend(new Date(2023, 0, 7))).toBe(true);\n // Monday (day 1)\n expect(isWeekend(new Date(2023, 0, 2))).toBe(false);\n // Friday (day 5)\n expect(isWeekend(new Date(2023, 0, 6))).toBe(false);\n});\n\nconst agoUnits = [\n [60, 'second'],\n [60, 'minute'],\n [24, 'hour'],\n [7, 'day'],\n [5, 'week'],\n] as const;\n\nexport function fromNow(date: Date): string {\n return fromNowDetailed(date).result;\n}\n\nimport.meta.vitest?.test(\"fromNow\", ({ expect }) => {\n // Set a fixed date for testing\n const fixedDate = new Date(\"2023-01-15T12:00:00.000Z\");\n\n // Use Vitest's fake timers\n import.meta.vitest?.vi.useFakeTimers();\n import.meta.vitest?.vi.setSystemTime(fixedDate);\n\n // Test past times\n expect(fromNow(new Date(\"2023-01-15T11:59:50.000Z\"))).toBe(\"just now\");\n expect(fromNow(new Date(\"2023-01-15T11:59:00.000Z\"))).toBe(\"1 minute ago\");\n expect(fromNow(new Date(\"2023-01-15T11:00:00.000Z\"))).toBe(\"1 hour ago\");\n expect(fromNow(new Date(\"2023-01-14T12:00:00.000Z\"))).toBe(\"1 day ago\");\n expect(fromNow(new Date(\"2023-01-08T12:00:00.000Z\"))).toBe(\"1 week ago\");\n\n // Test future times\n expect(fromNow(new Date(\"2023-01-15T12:00:10.000Z\"))).toBe(\"just now\");\n expect(fromNow(new Date(\"2023-01-15T12:01:00.000Z\"))).toBe(\"in 1 minute\");\n expect(fromNow(new Date(\"2023-01-15T13:00:00.000Z\"))).toBe(\"in 1 hour\");\n expect(fromNow(new Date(\"2023-01-16T12:00:00.000Z\"))).toBe(\"in 1 day\");\n expect(fromNow(new Date(\"2023-01-22T12:00:00.000Z\"))).toBe(\"in 1 week\");\n\n // Test very old dates (should use date format)\n expect(fromNow(new Date(\"2022-01-15T12:00:00.000Z\"))).toMatch(/Jan 15, 2022/);\n\n // Restore real timers\n import.meta.vitest?.vi.useRealTimers();\n});\n\nexport function fromNowDetailed(date: Date): {\n result: string,\n /**\n * May be Infinity if the result will never change.\n */\n secondsUntilChange: number,\n} {\n if (!(date instanceof Date)) {\n throw new Error(`fromNow only accepts Date objects (received: ${date})`);\n }\n\n const now = new Date();\n const elapsed = now.getTime() - date.getTime();\n\n let remainingInUnit = Math.abs(elapsed) / 1000;\n if (remainingInUnit < 15) {\n return {\n result: 'just now',\n secondsUntilChange: 15 - remainingInUnit,\n };\n }\n let unitInSeconds = 1;\n for (const [nextUnitSize, unitName] of agoUnits) {\n const rounded = Math.round(remainingInUnit);\n if (rounded < nextUnitSize) {\n if (elapsed < 0) {\n return {\n result: `in ${rounded} ${unitName}${rounded === 1 ? '' : 's'}`,\n secondsUntilChange: remainder((remainingInUnit - rounded + 0.5) * unitInSeconds, unitInSeconds),\n };\n } else {\n return {\n result: `${rounded} ${unitName}${rounded === 1 ? '' : 's'} ago`,\n secondsUntilChange: remainder((rounded - remainingInUnit - 0.5) * unitInSeconds, unitInSeconds),\n };\n }\n }\n unitInSeconds *= nextUnitSize;\n remainingInUnit /= nextUnitSize;\n }\n\n return {\n result: date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }),\n secondsUntilChange: Infinity,\n };\n}\n\n/**\n * Returns a string representation of the given date in the format expected by the `datetime-local` input type.\n */\nexport function getInputDatetimeLocalString(date: Date): string {\n date = new Date(date);\n date.setMinutes(date.getMinutes() - date.getTimezoneOffset());\n return date.toISOString().slice(0, 19);\n}\n\nimport.meta.vitest?.test(\"getInputDatetimeLocalString\", ({ expect }) => {\n // Use Vitest's fake timers to ensure consistent timezone behavior\n import.meta.vitest?.vi.useFakeTimers();\n\n // Test with a specific date\n const mockDate = new Date(\"2023-01-15T12:30:45.000Z\");\n const result = getInputDatetimeLocalString(mockDate);\n\n // The result should be in the format YYYY-MM-DDTHH:MM:SS\n expect(result).toMatch(/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$/);\n\n // Test with different dates\n const dates = [\n new Date(\"2023-01-01T00:00:00.000Z\"),\n new Date(\"2023-06-15T23:59:59.000Z\"),\n new Date(\"2023-12-31T12:34:56.000Z\"),\n ];\n\n for (const date of dates) {\n const result = getInputDatetimeLocalString(date);\n expect(result).toMatch(/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$/);\n }\n\n // Restore real timers\n import.meta.vitest?.vi.useRealTimers();\n});\n\n\nexport type Interval = [number, 'millisecond' | 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'year'];\nexport type DayInterval = [number, 'day' | 'week' | 'month' | 'year'];\n\nfunction applyInterval(inputDate: Date, times: number, interval: Interval): Date {\n if (!intervalSchema.isValidSync(interval)) {\n throw new HexclaveAssertionError(`Invalid interval`, { interval });\n }\n // Operate on a copy: the Date setters below mutate in place, and callers routinely pass a date they\n // also keep a reference to (e.g. `{ currentPeriodStart: now, currentPeriodEnd: addInterval(now, ...) }`).\n // Mutating the input would alias both fields to the same advanced date, silently collapsing the period.\n const date = new Date(inputDate);\n const [amount, unit] = interval;\n switch (unit) {\n case 'millisecond': {\n date.setMilliseconds(date.getMilliseconds() + amount * times);\n break;\n }\n case 'second': {\n date.setSeconds(date.getSeconds() + amount * times);\n break;\n }\n case 'minute': {\n date.setMinutes(date.getMinutes() + amount * times);\n break;\n }\n case 'hour': {\n date.setHours(date.getHours() + amount * times);\n break;\n }\n case 'day': {\n date.setDate(date.getDate() + amount * times);\n break;\n }\n case 'week': {\n date.setDate(date.getDate() + amount * times * 7);\n break;\n }\n case 'month': {\n date.setMonth(date.getMonth() + amount * times);\n break;\n }\n case 'year': {\n date.setFullYear(date.getFullYear() + amount * times);\n break;\n }\n default: {\n throw new HexclaveAssertionError(`Invalid interval despite schema validation`, { interval });\n }\n }\n return date;\n}\n\nexport function subtractInterval(date: Date, interval: Interval): Date {\n return applyInterval(date, -1, interval);\n}\n\n// One-shot local-time interval arithmetic that OVERFLOWS (Jan 31 + 1 month -> Mar 3, via\n// Date#setMonth) rather than clamping. Use this for single-step period math where JS/Stripe overflow\n// semantics are what we want (e.g. computing a subscription's currentPeriodEnd). For recurring\n// grant/reset BOUNDARIES, use `nthDayIntervalMillis` / `getIntervalsElapsed` instead — they clamp to\n// month-end and compute in UTC from a fixed anchor, and mixing the two would drift.\nexport function addInterval(date: Date, interval: Interval): Date {\n return applyInterval(date, 1, interval);\n}\n\nimport.meta.vitest?.test(\"addInterval/subtractInterval do not mutate their input\", ({ expect }) => {\n const original = new Date('2026-07-20T00:00:00.000Z');\n const originalMillis = original.getTime();\n\n const later = addInterval(original, [1, 'month']);\n const earlier = subtractInterval(original, [1, 'month']);\n\n // The input must be untouched, and the result must be a distinct object. This guards the aliasing\n // footgun in `{ currentPeriodStart: now, currentPeriodEnd: addInterval(now, ...) }`, where mutating\n // `now` would collapse both fields to the same advanced instant (a zero-width period).\n expect(original.getTime()).toBe(originalMillis);\n expect(later).not.toBe(original);\n expect(earlier).not.toBe(original);\n expect(later).toEqual(new Date('2026-08-20T00:00:00.000Z'));\n expect(earlier).toEqual(new Date('2026-06-20T00:00:00.000Z'));\n});\n\nexport const FAR_FUTURE_DATE = new Date(8640000000000000); // 13 Sep 275760 00:00:00 UTC\n\nfunction getMsPerDayIntervalUnit(unit: 'day' | 'week'): number {\n if (unit === 'day') {\n return 24 * 60 * 60 * 1000;\n }\n return 7 * 24 * 60 * 60 * 1000;\n}\n\n\n// The number of full `repeat` intervals elapsed from `anchor` up to (and including) `to`, i.e. the\n// largest n >= 0 with the n-th boundary <= to. Boundaries are the SAME ones `nthDayIntervalMillis`\n// computes (anchor-relative, UTC, month-end clamped), so counting and boundary-computation never\n// disagree — a Jan 31 monthly anchor has elapsed one interval by Feb 28, matching the reset the\n// bulldozer item-repeat fold actually emits. (This is why it must NOT be built on `addInterval`,\n// which is local-time and overflows instead of clamping.)\nexport function getIntervalsElapsed(anchor: Date, to: Date, repeat: DayInterval): number {\n const [amount, unit] = repeat;\n const toMillis = to.getTime();\n if (toMillis <= anchor.getTime()) return 0;\n if (unit === 'day' || unit === 'week') {\n const msPerUnit = getMsPerDayIntervalUnit(unit);\n const diffMs = toMillis - anchor.getTime();\n return Math.floor(diffMs / (msPerUnit * amount));\n }\n // month/year: walk anchor-relative boundaries. The count is small in practice (bounded by the\n // number of billing periods since the anchor), so a linear walk is fine.\n let count = 0;\n while (nthDayIntervalMillis(anchor.getTime(), repeat, count + 1) <= toMillis) count += 1;\n return count;\n}\n\nimport.meta.vitest?.test(\"getIntervalsElapsed\", ({ expect }) => {\n const anchor = new Date('2025-01-01T00:00:00.000Z');\n const to = new Date('2025-01-15T00:00:00.000Z');\n expect(getIntervalsElapsed(anchor, to, [1, 'week'])).toBe(2);\n expect(getIntervalsElapsed(anchor, to, [3, 'day'])).toBe(4);\n\n // Jan 31 monthly anchor: the first boundary is clamped to Feb 28, which is <= Mar 1, so one\n // interval has elapsed (anchor-relative clamped math, consistent with nthDayIntervalMillis).\n const mAnchor = new Date('2023-01-31T00:00:00.000Z');\n const mTo = new Date('2023-03-01T00:00:00.000Z');\n expect(getIntervalsElapsed(mAnchor, mTo, [1, 'month'])).toBe(1);\n\n const yAnchor = new Date('2020-01-01T00:00:00.000Z');\n const yTo = new Date('2022-06-01T00:00:00.000Z');\n expect(getIntervalsElapsed(yAnchor, yTo, [1, 'year'])).toBe(2);\n});\n\n/**\n * The UTC millis of the `occurrence`-th (1-based) repeat of `interval` after `anchorMillis`.\n *\n * Each boundary is computed from the *original* anchor (never by stepping off the previous, possibly\n * clamped, boundary) so the anchor's day-of-month is preserved across resets: a Jan 31 anchor yields\n * Feb 28, Mar 31, Apr 30, ... (matching Stripe's billing-cycle behavior) rather than drifting to\n * Feb 28, Mar 28, ... . Month/year overflow (e.g. Jan 31 -> Feb) is clamped to the target month's\n * last day. Day/week are exact multiples (no calendar involved, so no drift possible).\n *\n * Everything is done in UTC (unlike `addInterval`, which uses local-time Date accessors) so the\n * result is deterministic regardless of the server's timezone — this is relied upon by bulldozer\n * folds, which must be reproducible across machines.\n *\n * `occurrence` 0 returns the anchor itself; `getIntervalsElapsed` counts these same boundaries, so\n * the two are always consistent for a given DayInterval.\n */\nexport function nthDayIntervalMillis(anchorMillis: number, interval: DayInterval, occurrence: number): number {\n const [count, unit] = interval;\n const totalUnits = count * occurrence;\n if (unit === 'day' || unit === 'week') {\n return anchorMillis + totalUnits * getMsPerDayIntervalUnit(unit);\n }\n const anchor = new Date(anchorMillis);\n const monthsToAdd = unit === 'year' ? totalUnits * 12 : totalUnits;\n const absoluteMonth = anchor.getUTCFullYear() * 12 + anchor.getUTCMonth() + monthsToAdd;\n const targetYear = Math.floor(absoluteMonth / 12);\n const targetMonth = absoluteMonth - targetYear * 12;\n // Day 0 of the following month is the last day of the target month, so this gives its length.\n const daysInTargetMonth = new Date(Date.UTC(targetYear, targetMonth + 1, 0)).getUTCDate();\n const clampedDay = Math.min(anchor.getUTCDate(), daysInTargetMonth);\n return Date.UTC(\n targetYear,\n targetMonth,\n clampedDay,\n anchor.getUTCHours(),\n anchor.getUTCMinutes(),\n anchor.getUTCSeconds(),\n anchor.getUTCMilliseconds(),\n );\n}\n\nimport.meta.vitest?.test(\"nthDayIntervalMillis\", ({ expect }) => {\n const at = (iso: string) => new Date(iso).getTime();\n const iso = (millis: number) => new Date(millis).toISOString();\n\n // day / week are exact multiples (no drift), and honor the count.\n const dayAnchor = at('2025-01-01T08:30:00.000Z');\n expect(iso(nthDayIntervalMillis(dayAnchor, [1, 'day'], 1))).toBe('2025-01-02T08:30:00.000Z');\n expect(iso(nthDayIntervalMillis(dayAnchor, [3, 'day'], 4))).toBe('2025-01-13T08:30:00.000Z');\n expect(iso(nthDayIntervalMillis(dayAnchor, [2, 'week'], 3))).toBe('2025-02-12T08:30:00.000Z'); // 6 weeks = 42 days after Jan 1\n\n // Jan 31 monthly: clamp in short months, restore to the anchor day in long ones.\n const jan31 = at('2025-01-31T00:00:00.000Z');\n expect(iso(nthDayIntervalMillis(jan31, [1, 'month'], 1))).toBe('2025-02-28T00:00:00.000Z');\n expect(iso(nthDayIntervalMillis(jan31, [1, 'month'], 2))).toBe('2025-03-31T00:00:00.000Z');\n expect(iso(nthDayIntervalMillis(jan31, [1, 'month'], 3))).toBe('2025-04-30T00:00:00.000Z');\n\n // Anchor-day preservation: a Feb 28 anchor stays on day 28 (never jumps to Mar 31), unlike the\n // Jan 31 anchor above which restores to Mar 31. This is the evidence we compute from the anchor.\n const feb28 = at('2025-02-28T00:00:00.000Z');\n expect(iso(nthDayIntervalMillis(feb28, [1, 'month'], 1))).toBe('2025-03-28T00:00:00.000Z');\n\n // Leap-year Feb: Feb 29 anchor clamps to Feb 28 in a non-leap year and restores in the next leap year.\n const feb29 = at('2024-02-29T00:00:00.000Z');\n expect(iso(nthDayIntervalMillis(feb29, [1, 'year'], 1))).toBe('2025-02-28T00:00:00.000Z');\n expect(iso(nthDayIntervalMillis(feb29, [1, 'year'], 4))).toBe('2028-02-29T00:00:00.000Z');\n // Jan 31 monthly landing in a leap February clamps to Feb 29, not Feb 28.\n const jan31Leap = at('2024-01-31T00:00:00.000Z');\n expect(iso(nthDayIntervalMillis(jan31Leap, [1, 'month'], 1))).toBe('2024-02-29T00:00:00.000Z');\n\n // End-of-December rollover: absolute-month math carries the year.\n const dec31 = at('2025-12-31T00:00:00.000Z');\n expect(iso(nthDayIntervalMillis(dec31, [1, 'month'], 1))).toBe('2026-01-31T00:00:00.000Z');\n expect(iso(nthDayIntervalMillis(dec31, [1, 'month'], 2))).toBe('2026-02-28T00:00:00.000Z');\n expect(iso(nthDayIntervalMillis(dec31, [1, 'month'], 3))).toBe('2026-03-31T00:00:00.000Z');\n expect(iso(nthDayIntervalMillis(dec31, [1, 'year'], 1))).toBe('2026-12-31T00:00:00.000Z');\n\n // Time-of-day is preserved through month clamping.\n const withTime = at('2025-01-31T13:45:59.123Z');\n expect(iso(nthDayIntervalMillis(withTime, [1, 'month'], 1))).toBe('2025-02-28T13:45:59.123Z');\n});\n"],"mappings":"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"}
@@ -20,9 +20,9 @@ declare const webhookEvents: readonly [WebhookEvent<yup$1.ObjectSchema<{
20
20
  client_metadata?: {} | null | undefined;
21
21
  client_read_only_metadata?: {} | null | undefined;
22
22
  server_metadata?: {} | null | undefined;
23
- display_name: string;
24
23
  id: string;
25
24
  created_at_millis: number;
25
+ display_name: string;
26
26
  profile_image_url: string | null;
27
27
  } | null;
28
28
  selected_team_id: string | null;
@@ -110,9 +110,9 @@ declare const webhookEvents: readonly [WebhookEvent<yup$1.ObjectSchema<{
110
110
  client_metadata?: {} | null | undefined;
111
111
  client_read_only_metadata?: {} | null | undefined;
112
112
  server_metadata?: {} | null | undefined;
113
- display_name: string;
114
113
  id: string;
115
114
  created_at_millis: number;
115
+ display_name: string;
116
116
  profile_image_url: string | null;
117
117
  } | null;
118
118
  selected_team_id: string | null;
@@ -323,4 +323,4 @@ declare const webhookEvents: readonly [WebhookEvent<yup$1.ObjectSchema<{
323
323
  }];
324
324
  //#endregion
325
325
  export { webhookEvents as n, WebhookEvent as t };
326
- //# sourceMappingURL=webhooks-8Ts1Clcb.d.ts.map
326
+ //# sourceMappingURL=webhooks-_aXIgZBW.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"webhooks-8Ts1Clcb.d.ts","names":[],"sources":["../src/interface/webhooks.ts"],"mappings":";;;KAMY,YAAA,WAAuB,KAAA,CAAI,MAAA;EACrC,IAAA;EACA,MAAA,EAAQ,CAAC;EACT,QAAA;IACE,OAAA;IACA,WAAA;IACA,IAAA;EAAA;AAAA;AAAA,cAIS,aAAA,YAAa,YAAA,CAAA,KAAA,CAAA,YAAA"}
1
+ {"version":3,"file":"webhooks-_aXIgZBW.d.ts","names":[],"sources":["../src/interface/webhooks.ts"],"mappings":";;;KAMY,YAAA,WAAuB,KAAA,CAAI,MAAA;EACrC,IAAA;EACA,MAAA,EAAQ,CAAC;EACT,QAAA;IACE,OAAA;IACA,WAAA;IACA,IAAA;EAAA;AAAA;AAAA,cAIS,aAAA,YAAa,YAAA,CAAA,KAAA,CAAA,YAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hexclave/shared",
3
- "version": "1.0.52",
3
+ "version": "1.0.54",
4
4
  "repository": "https://github.com/hexclave/hexclave",
5
5
  "files": [
6
6
  "README.md",
@@ -6,7 +6,16 @@ import { buildSkillSitePrompt, skillSitePrompt } from "../unified-prompts/skill-
6
6
  export const llmsTxt = deindent`
7
7
  # Hexclave
8
8
 
9
+ > Hexclave is an authentication and user management platform for SaaS apps, with teams, RBAC, payments, and analytics. Formerly Stack Auth.
10
+
9
11
  ${remindersPrompt}
12
+
13
+ ## Docs
14
+
15
+ - [Full documentation](https://skill.hexclave.com/full): LLM-optimized Hexclave documentation
16
+ - [Ask questions](https://skill.hexclave.com/ask): Q&A endpoint for Hexclave
17
+ - [Human documentation](https://docs.hexclave.com): Browse the docs, or add \`.md\` to a page URL for markdown
18
+ - [MCP server](https://mcp.hexclave.com): Hexclave documentation for MCP clients
10
19
  `;
11
20
 
12
21
  export const llmsFullTxt = skillSitePrompt;
@@ -411,12 +411,12 @@ function getRestBackendSetupPrompt(kind: "python" | "rest-api") {
411
411
  If this project already has a \`hexclave.config.ts\` file for another frontend or backend, reuse that same file so the whole project shares one Hexclave config. Otherwise, create a new \`hexclave.config.ts\` file in your workspace:
412
412
 
413
413
  \`\`\`ts hexclave.config.ts
414
- import type { HexclaveConfig } from "@hexclave/js/config";
414
+ import type { HexclaveConfig } from "@hexclave/js";
415
415
 
416
416
  export const config: HexclaveConfig = "show-onboarding";
417
417
  \`\`\`
418
418
 
419
- The \`/config\` entrypoint is lightweight and free of framework runtime code, so it can be safely loaded by tooling such as the local dashboard. If you later switch to a config object and want type-checking, wrap it with \`defineHexclaveConfig\` imported from the same \`@hexclave/js/config\` path (never from \`@hexclave/js\` directly, which would pull in the whole SDK and fail to load).
419
+ If you later switch to a config object and want type-checking, wrap it with \`defineHexclaveConfig\`, imported from the same \`@hexclave/js\` package.
420
420
 
421
421
  Run your backend through the Hexclave CLI so it starts the local dashboard and injects the Hexclave environment variables:
422
422
 
@@ -975,13 +975,13 @@ export function getSdkSetupPrompt(mainType: "ai-prompt" | "nextjs" | "react" | "
975
975
  First, create a \`hexclave.config.ts\` configuration file in the root directory of the workspace (or anywhere else):
976
976
 
977
977
  \`\`\`ts hexclave.config.ts
978
- import type { HexclaveConfig } from "${packageName}/config";
978
+ import type { HexclaveConfig } from "${packageName}";
979
979
 
980
980
  // default: show-onboarding, which shows the onboarding flow for this project when Hexclave starts
981
981
  export const config: HexclaveConfig = "show-onboarding";
982
982
  \`\`\`
983
983
 
984
- The \`/config\` entrypoint is lightweight and free of framework runtime code, so it can be safely loaded by tooling such as the local dashboard. If you later switch to a config object and want type-checking, wrap it with \`defineHexclaveConfig\` imported from the same \`${packageName}/config\` path (never from \`${packageName}\` directly, which would pull in the whole SDK and fail to load).
984
+ If you later switch to a config object and want type-checking, wrap it with \`defineHexclaveConfig\`, imported from the same \`${packageName}\` package.
985
985
 
986
986
  ${isAiPrompt ? deindent`
987
987
  If you already know which apps you want to enable and how to configure them, you can also set the \`config\` object to the desired configuration directly. Refer to the per-app setup instructions for more information. However, in most cases, you would probably want to let the user onboard manually through the show-onboarding flow.
@@ -9,7 +9,7 @@ const DEFAULT_CONFIG_IMPORT_PACKAGE = "@hexclave/js";
9
9
  * so projects pinned to the last legacy release still render a config file
10
10
  * that compiles against their installed SDK.
11
11
  */
12
- const CONFIG_IMPORT_PACKAGES = [
12
+ export const CONFIG_IMPORT_PACKAGES = [
13
13
  "@hexclave/next",
14
14
  "@hexclave/react",
15
15
  "@hexclave/tanstack-start",
@@ -53,8 +53,7 @@ export function renderConfigFileContent(config: unknown, importPackage?: string)
53
53
  throw new Error(`Config has conflicting keys that would be dropped during normalization: ${droppedKeys.map(k => JSON.stringify(k)).join(", ")}`);
54
54
  }
55
55
  const pkg = importPackage ?? DEFAULT_CONFIG_IMPORT_PACKAGE;
56
- const importSpecifier = pkg.startsWith("@hexclave/") ? `${pkg}/config` : pkg;
57
- const importLine = `import type { HexclaveConfig } from "${importSpecifier}";`;
56
+ const importLine = `import type { HexclaveConfig } from "${pkg}";`;
58
57
  return `${importLine}\n\nexport const config: HexclaveConfig = ${JSON.stringify(normalizedConfig, null, 2)};\n`;
59
58
  }
60
59
 
@@ -91,15 +90,15 @@ import.meta.vitest?.test("renderConfigFileContent rejects invalid config exports
91
90
 
92
91
  import.meta.vitest?.test("renderConfigFileContent uses custom import package", ({ expect }) => {
93
92
  const content = renderConfigFileContent({}, "@hexclave/next");
94
- expect(content).toContain('import type { HexclaveConfig } from "@hexclave/next/config";');
93
+ expect(content).toContain('import type { HexclaveConfig } from "@hexclave/next";');
95
94
  });
96
95
 
97
96
  import.meta.vitest?.test("renderConfigFileContent defaults to @hexclave/js", ({ expect }) => {
98
97
  const content = renderConfigFileContent({});
99
- expect(content).toContain('import type { HexclaveConfig } from "@hexclave/js/config";');
98
+ expect(content).toContain('import type { HexclaveConfig } from "@hexclave/js";');
100
99
  });
101
100
 
102
- import.meta.vitest?.test("renderConfigFileContent keeps legacy @stackframe packages on their root entrypoint", ({ expect }) => {
101
+ import.meta.vitest?.test("renderConfigFileContent imports from the SDK package root", ({ expect }) => {
103
102
  const content = renderConfigFileContent({}, "@stackframe/next");
104
103
  expect(content).toContain('import type { HexclaveConfig } from "@stackframe/next";');
105
104
  });
@@ -145,10 +145,14 @@ import.meta.vitest?.test("getInputDatetimeLocalString", ({ expect }) => {
145
145
  export type Interval = [number, 'millisecond' | 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'year'];
146
146
  export type DayInterval = [number, 'day' | 'week' | 'month' | 'year'];
147
147
 
148
- function applyInterval(date: Date, times: number, interval: Interval): Date {
148
+ function applyInterval(inputDate: Date, times: number, interval: Interval): Date {
149
149
  if (!intervalSchema.isValidSync(interval)) {
150
150
  throw new HexclaveAssertionError(`Invalid interval`, { interval });
151
151
  }
152
+ // Operate on a copy: the Date setters below mutate in place, and callers routinely pass a date they
153
+ // also keep a reference to (e.g. `{ currentPeriodStart: now, currentPeriodEnd: addInterval(now, ...) }`).
154
+ // Mutating the input would alias both fields to the same advanced date, silently collapsing the period.
155
+ const date = new Date(inputDate);
152
156
  const [amount, unit] = interval;
153
157
  switch (unit) {
154
158
  case 'millisecond': {
@@ -203,6 +207,23 @@ export function addInterval(date: Date, interval: Interval): Date {
203
207
  return applyInterval(date, 1, interval);
204
208
  }
205
209
 
210
+ import.meta.vitest?.test("addInterval/subtractInterval do not mutate their input", ({ expect }) => {
211
+ const original = new Date('2026-07-20T00:00:00.000Z');
212
+ const originalMillis = original.getTime();
213
+
214
+ const later = addInterval(original, [1, 'month']);
215
+ const earlier = subtractInterval(original, [1, 'month']);
216
+
217
+ // The input must be untouched, and the result must be a distinct object. This guards the aliasing
218
+ // footgun in `{ currentPeriodStart: now, currentPeriodEnd: addInterval(now, ...) }`, where mutating
219
+ // `now` would collapse both fields to the same advanced instant (a zero-width period).
220
+ expect(original.getTime()).toBe(originalMillis);
221
+ expect(later).not.toBe(original);
222
+ expect(earlier).not.toBe(original);
223
+ expect(later).toEqual(new Date('2026-08-20T00:00:00.000Z'));
224
+ expect(earlier).toEqual(new Date('2026-06-20T00:00:00.000Z'));
225
+ });
226
+
206
227
  export const FAR_FUTURE_DATE = new Date(8640000000000000); // 13 Sep 275760 00:00:00 UTC
207
228
 
208
229
  function getMsPerDayIntervalUnit(unit: 'day' | 'week'): number {
@@ -1 +0,0 @@
1
- {"version":3,"file":"transactions-CHNUJ42J.d.ts","names":[],"sources":["../src/interface/crud/transactions.ts"],"mappings":";;;;cAwFa,sBAAA,gBAAsB,WAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAMxB,WAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAEC,gBAAA,GAAmB,SAAS,QAAQ,sBAAA;AAAA,cAEnC,iBAAA;AAAA,KAUD,eAAA,WAA0B,iBAAiB;AAAA,cAE1C,iBAAA,gBAAiB,YAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mBAkBlB,WAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAEA,WAAA,GAAc,SAAS,QAAQ,iBAAA"}