@read-frog/api-contract 0.17.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@read-frog/api-contract",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -24,15 +24,15 @@
24
24
  }
25
25
  },
26
26
  "dependencies": {
27
- "@orpc/contract": "^1.14.6",
28
- "zod": "^4.5.4",
29
- "@read-frog/definitions": "0.6.0"
27
+ "@orpc/contract": "^1.15.2",
28
+ "@read-frog/definitions": "0.6.1",
29
+ "zod": "^4.6.5"
30
30
  },
31
31
  "devDependencies": {
32
- "@types/node": "^26.1.0",
33
- "tsdown": "^0.22.14",
34
32
  "@repo/tsdown-config": "0.0.0",
35
- "@repo/typescript-config": "0.0.0"
33
+ "@repo/typescript-config": "0.0.0",
34
+ "@types/node": "^26.6.2",
35
+ "tsdown": "^0.23.0"
36
36
  },
37
37
  "scripts": {
38
38
  "build": "tsdown",
@@ -34,7 +34,7 @@ export const notebaseContract = {
34
34
  path: "/notebases/{id}/summary",
35
35
  summary: "Get notebase summary",
36
36
  description:
37
- "Read the name and public study settings of an owned notebase without loading note content.",
37
+ "Read the name, public study settings and note count of an owned notebase without loading note content.",
38
38
  tags: ["Notebases"],
39
39
  })
40
40
  .errors(pickPublicErrorMap("NOTEBASE_NOT_FOUND"))
@@ -164,6 +164,7 @@ export const NotebaseGetSummaryOutputSchema = notebaseSummarySchema.extend({
164
164
  srsNewPerDay: schedulingParamsShape.newPerDay,
165
165
  srsReviewsPerDay: schedulingParamsShape.reviewsPerDay,
166
166
  srsDesiredRetention: schedulingParamsShape.desiredRetention,
167
+ noteCount: z.number().int().nonnegative(),
167
168
  });
168
169
  export type NotebaseGetSummaryOutput = z.infer<typeof NotebaseGetSummaryOutputSchema>;
169
170
 
@@ -1,14 +1,36 @@
1
1
  import { z } from "zod";
2
2
 
3
3
  // @ref https://zenn.dev/herp_inc/articles/js-intl-date-time-format-performance
4
- const localeTimeZoneFormatter = new Map<string, Intl.DateTimeFormat>();
5
- function createTimezoneFormatter(timezone: string): Intl.DateTimeFormat {
6
- let formatter = localeTimeZoneFormatter.get(timezone);
7
- if (!formatter) {
8
- formatter = new Intl.DateTimeFormat("en-US", { timeZone: timezone });
9
- localeTimeZoneFormatter.set(timezone, formatter);
4
+ // Keyed by the lowercased id, because Intl matches ids case-insensitively: raw
5
+ // keys would let every casing of a zone pin its own entry for the life of the
6
+ // process. Links (Asia/Kolkata) share their canonical zone's formatter (~30 KB
7
+ // of ICU memory each), so the map holds one key per IANA name. Offset zones
8
+ // ("+08:00") are valid but stay uncached, because there are ~2.9k of them.
9
+ const timezoneFormatters = new Map<string, Intl.DateTimeFormat>();
10
+
11
+ // ASCII only, as Intl does: toLowerCase() would also fold look-alikes such as
12
+ // the Kelvin sign onto "k", letting an id Intl rejects hit a cached zone.
13
+ function toCacheKey(timezone: string) {
14
+ return timezone.replace(/[A-Z]/g, (letter) => letter.toLowerCase());
15
+ }
16
+
17
+ function getTimezoneFormatter(timezone: string): Intl.DateTimeFormat {
18
+ const key = toCacheKey(timezone);
19
+ const cached = timezoneFormatters.get(key);
20
+ if (cached) {
21
+ return cached;
10
22
  }
11
- return formatter;
23
+
24
+ const formatter = new Intl.DateTimeFormat("en-US", { timeZone: timezone });
25
+ const canonical = formatter.resolvedOptions().timeZone;
26
+ if (canonical.startsWith("+") || canonical.startsWith("-")) {
27
+ return formatter;
28
+ }
29
+
30
+ const canonicalKey = toCacheKey(canonical);
31
+ const shared = timezoneFormatters.get(canonicalKey) ?? formatter;
32
+ timezoneFormatters.set(canonicalKey, shared).set(key, shared);
33
+ return shared;
12
34
  }
13
35
 
14
36
  export const timezoneSchema = z
@@ -17,7 +39,7 @@ export const timezoneSchema = z
17
39
  .min(1)
18
40
  .refine((timezone) => {
19
41
  try {
20
- createTimezoneFormatter(timezone).format(new Date());
42
+ getTimezoneFormatter(timezone).format(new Date());
21
43
  return true;
22
44
  } catch {
23
45
  return false;