@read-frog/api-contract 0.19.0 → 0.20.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.19.0",
3
+ "version": "0.20.0",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -25,7 +25,7 @@
25
25
  },
26
26
  "dependencies": {
27
27
  "@orpc/contract": "^1.15.2",
28
- "@read-frog/definitions": "0.8.0",
28
+ "@read-frog/definitions": "0.9.0",
29
29
  "zod": "^4.6.5"
30
30
  },
31
31
  "devDependencies": {
@@ -42,10 +42,16 @@ export const cardTemplateContract = {
42
42
  path: "/card-templates",
43
43
  summary: "Create card template",
44
44
  description:
45
- "Create a template in an owned notebase using existing field IDs in its patterns. This generates cards for existing notes.",
45
+ 'Create a template in an owned notebase; it generates a card for every existing note. Each face (front, back) is either { columns: [column IDs], style }, those columns in a built-in style that stays editable in the website\'s simple mode, or { layout: "<HTML + Liquid>" } for a custom design that references a column as {{ ["<column id>"] }}. Prefer columns faces. Get column IDs from the notebase schema.',
46
46
  tags: ["Card Templates"],
47
47
  })
48
- .errors(pickPublicErrorMap("NOTEBASE_NOT_FOUND", "CARD_TEMPLATE_INVALID_NOTEBASE_COLUMNS"))
48
+ .errors(
49
+ pickPublicErrorMap(
50
+ "NOTEBASE_NOT_FOUND",
51
+ "CARD_TEMPLATE_INVALID_NOTEBASE_COLUMNS",
52
+ "CARD_TEMPLATE_INVALID_LAYOUT",
53
+ ),
54
+ )
49
55
  .errors(pickPublicErrorMap("RESOURCE_ALREADY_EXISTS"))
50
56
  .input(CardTemplateCreateInputSchema)
51
57
  .output(CardTemplateCreateOutputSchema),
@@ -55,10 +61,16 @@ export const cardTemplateContract = {
55
61
  path: "/card-templates/{id}",
56
62
  summary: "Update card template",
57
63
  description:
58
- "Change an owned template name or definition. Rendered cards reflect the saved template. expectedUpdatedAt guards against editing stale data.",
64
+ "Change an owned template name or faces (see creating a template for the face shapes). Rendered cards reflect the saved template. expectedUpdatedAt guards against editing stale data.",
59
65
  tags: ["Card Templates"],
60
66
  })
61
- .errors(pickPublicErrorMap("CARD_TEMPLATE_NOT_FOUND", "CARD_TEMPLATE_INVALID_NOTEBASE_COLUMNS"))
67
+ .errors(
68
+ pickPublicErrorMap(
69
+ "CARD_TEMPLATE_NOT_FOUND",
70
+ "CARD_TEMPLATE_INVALID_NOTEBASE_COLUMNS",
71
+ "CARD_TEMPLATE_INVALID_LAYOUT",
72
+ ),
73
+ )
62
74
  .errors(pickPublicErrorMap("RESOURCE_UPDATE_CONFLICT"))
63
75
  .input(CardTemplateUpdateInputSchema)
64
76
  .output(CardTemplateUpdateOutputSchema),
@@ -0,0 +1,22 @@
1
+ import { oc } from "@orpc/contract";
2
+ import { masteryGoalOutputSchema, masteryGoalUpdateInputSchema } from "#/schemas/mastery-goal";
3
+
4
+ export const masteryGoalContract = {
5
+ get: oc
6
+ .route({
7
+ method: "GET",
8
+ path: "/mastery-goal",
9
+ summary: "Get the authenticated user's mastered-cards goal",
10
+ tags: ["Goals"],
11
+ })
12
+ .output(masteryGoalOutputSchema),
13
+ update: oc
14
+ .route({
15
+ method: "PUT",
16
+ path: "/mastery-goal",
17
+ summary: "Set or clear the authenticated user's mastered-cards goal",
18
+ tags: ["Goals"],
19
+ })
20
+ .input(masteryGoalUpdateInputSchema)
21
+ .output(masteryGoalOutputSchema),
22
+ };
package/src/index.ts CHANGED
@@ -5,6 +5,7 @@ import { cardContract } from "./contracts/card";
5
5
  import { cardTemplateContract } from "./contracts/card-template";
6
6
  import { ebookContract } from "./contracts/ebook";
7
7
  import { hostedAiContract } from "./contracts/hosted-ai";
8
+ import { masteryGoalContract } from "./contracts/mastery-goal";
8
9
  import { mcpConnectionContract } from "./contracts/mcp-connection";
9
10
  import { notebaseContract } from "./contracts/notebase";
10
11
  import { notebaseColumnContract } from "./contracts/notebase-column";
@@ -21,6 +22,7 @@ export const contract = {
21
22
  cardTemplate: cardTemplateContract,
22
23
  ebook: ebookContract,
23
24
  hostedAi: hostedAiContract,
25
+ masteryGoal: masteryGoalContract,
24
26
  mcpConnection: mcpConnectionContract,
25
27
  notebase: notebaseContract,
26
28
  notebaseColumn: notebaseColumnContract,
@@ -40,6 +42,7 @@ export * from "./schemas/billing";
40
42
  export * from "./schemas/card";
41
43
  export * from "./schemas/ebook";
42
44
  export * from "./schemas/hosted-ai";
45
+ export * from "./schemas/mastery-goal";
43
46
  export * from "./schemas/mcp-connection";
44
47
  export * from "./schemas/notebase";
45
48
  export * from "./schemas/notebase-column";
@@ -45,6 +45,33 @@ export const CardTemplateInvalidColumnsDataSchema = z
45
45
  .strict();
46
46
  export type CardTemplateInvalidColumnsData = z.infer<typeof CardTemplateInvalidColumnsDataSchema>;
47
47
 
48
+ export const CardTemplateInvalidLayoutDataSchema = z
49
+ .object({
50
+ errors: z.array(
51
+ z
52
+ .object({
53
+ side: z.enum(["front", "back"]),
54
+ message: z.string(),
55
+ // UTF-16 offsets into that face's layout, when the parser knows them.
56
+ from: z.number().int().min(0).optional(),
57
+ to: z.number().int().min(0).optional(),
58
+ })
59
+ .strict(),
60
+ ),
61
+ })
62
+ .strict();
63
+ export type CardTemplateInvalidLayoutData = z.infer<typeof CardTemplateInvalidLayoutDataSchema>;
64
+
65
+ // The templates that use the column, which keep it from being deleted.
66
+ export const CardTemplateNotebaseColumnInUseDataSchema = z
67
+ .object({
68
+ templates: z.array(z.object({ id: z.uuid(), name: z.string() }).strict()),
69
+ })
70
+ .strict();
71
+ export type CardTemplateNotebaseColumnInUseData = z.infer<
72
+ typeof CardTemplateNotebaseColumnInUseDataSchema
73
+ >;
74
+
48
75
  export const HostedAiTierRestrictedDataSchema = z.strictObject({
49
76
  modelTier: HostedAiModelTierSchema,
50
77
  });
@@ -148,7 +175,13 @@ export const PUBLIC_APP_ERROR_DEFS = {
148
175
  message: "Card template references unknown notebase columns",
149
176
  status: 422,
150
177
  },
178
+ CARD_TEMPLATE_INVALID_LAYOUT: {
179
+ data: CardTemplateInvalidLayoutDataSchema,
180
+ message: "Card template layout does not parse",
181
+ status: 422,
182
+ },
151
183
  CARD_TEMPLATE_NOTEBASE_COLUMN_IN_USE: {
184
+ data: CardTemplateNotebaseColumnInUseDataSchema,
152
185
  message: "Notebase column is used by a card template",
153
186
  status: 409,
154
187
  },
@@ -0,0 +1,23 @@
1
+ import { z } from "zod";
2
+
3
+ export const MASTERY_GOAL_TARGET_MAX = 1_000_000;
4
+
5
+ /** How many mastered cards to aim for: the cards `stats.srsMemoryGrowth` counts. */
6
+ export const masteryGoalTargetSchema = z.number().int().min(1).max(MASTERY_GOAL_TARGET_MAX);
7
+ export type MasteryGoalTarget = z.infer<typeof masteryGoalTargetSchema>;
8
+
9
+ /** `null` clears the goal. */
10
+ export const masteryGoalUpdateInputSchema = z
11
+ .object({
12
+ target: masteryGoalTargetSchema.nullable(),
13
+ })
14
+ .strict();
15
+ export type MasteryGoalUpdateInput = z.infer<typeof masteryGoalUpdateInputSchema>;
16
+
17
+ /** The goal's target, or `null` while none is set. */
18
+ export const masteryGoalOutputSchema = z
19
+ .object({
20
+ target: masteryGoalTargetSchema.nullable(),
21
+ })
22
+ .strict();
23
+ export type MasteryGoalOutput = z.infer<typeof masteryGoalOutputSchema>;