@read-frog/api-contract 0.25.0 → 0.27.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/dist/index.d.ts +1044 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +67 -9
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/contracts/card.ts +16 -0
- package/src/contracts/notebase-row.ts +2 -2
- package/src/contracts/speech.ts +20 -0
- package/src/public-errors.ts +24 -0
- package/src/schemas/card.ts +10 -0
- package/src/schemas/notebase-row.ts +30 -0
- package/src/schemas/notebase.ts +6 -1
- package/src/schemas/speech.ts +23 -0
- package/src/schemas/web-sync.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@read-frog/api-contract",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.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.16.1",
|
|
28
|
-
"@read-frog/definitions": "0.
|
|
28
|
+
"@read-frog/definitions": "0.15.0",
|
|
29
29
|
"zod": "^4.6.7"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
package/src/contracts/card.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { defineAgentProcedure } from "./shared";
|
|
|
2
2
|
import { oc } from "@orpc/contract";
|
|
3
3
|
import { pickPublicErrorMap } from "#/public-errors";
|
|
4
4
|
import {
|
|
5
|
+
CardDeleteInputSchema,
|
|
6
|
+
CardDeleteOutputSchema,
|
|
5
7
|
CardGenerateInputSchema,
|
|
6
8
|
CardGenerateOutputSchema,
|
|
7
9
|
CardGetInputSchema,
|
|
@@ -11,6 +13,20 @@ import {
|
|
|
11
13
|
} from "#/schemas/card";
|
|
12
14
|
|
|
13
15
|
export const cardContract = {
|
|
16
|
+
delete: oc
|
|
17
|
+
.errors(
|
|
18
|
+
pickPublicErrorMap(
|
|
19
|
+
"CARD_NOT_FOUND",
|
|
20
|
+
"NOTEBASE_NOT_FOUND",
|
|
21
|
+
"CARD_DELETE_UNSUPPORTED",
|
|
22
|
+
"RESOURCE_UPDATE_CONFLICT",
|
|
23
|
+
"CELL_VALIDATION_FAILED",
|
|
24
|
+
),
|
|
25
|
+
)
|
|
26
|
+
.route({ method: "DELETE", path: "/cards/{id}", summary: "Delete card", tags: ["Cards"] })
|
|
27
|
+
.input(CardDeleteInputSchema)
|
|
28
|
+
.output(CardDeleteOutputSchema),
|
|
29
|
+
|
|
14
30
|
list: defineAgentProcedure({
|
|
15
31
|
method: "GET",
|
|
16
32
|
path: "/cards",
|
|
@@ -92,7 +92,7 @@ export const notebaseRowContract = {
|
|
|
92
92
|
path: "/notebase-rows/{notebaseRowId}",
|
|
93
93
|
summary: "Update note",
|
|
94
94
|
description:
|
|
95
|
-
"Merge supplied fields into an owned note, preserving omitted fields. Use null to clear a field. expectedUpdatedAt guards against editing stale data; reread on a modification conflict.",
|
|
95
|
+
"Merge supplied fields into an owned note, preserving omitted fields. Use null to clear a field. expectedUpdatedAt guards against editing stale data; reread on a modification conflict. In a cloze field, a deletion's number ({{c1::…}}) is its card's identity: keep the numbers of the deletions you keep, and leave any gaps for the save to close. Renumbering them yourself hands a card's review history to other text, and can delete it.",
|
|
96
96
|
tags: ["Notebase Rows"],
|
|
97
97
|
})
|
|
98
98
|
.errors(pickPublicErrorMap("NOTEBASE_ROW_NOT_FOUND", "CELL_VALIDATION_FAILED"))
|
|
@@ -105,7 +105,7 @@ export const notebaseRowContract = {
|
|
|
105
105
|
path: "/notebase-rows/batch",
|
|
106
106
|
summary: "Update notes",
|
|
107
107
|
description:
|
|
108
|
-
"Update 1–50 notes atomically in one owned notebase. Each row supplies its ID and fields to merge; omitted fields are preserved and null clears a field. Supply each note's expectedUpdatedAt to guard against stale edits. A missing row, invalid field or modification conflict rolls back the whole batch. Items are returned in input order.",
|
|
108
|
+
"Update 1–50 notes atomically in one owned notebase. Each row supplies its ID and fields to merge; omitted fields are preserved and null clears a field. Supply each note's expectedUpdatedAt to guard against stale edits. A missing row, invalid field or modification conflict rolls back the whole batch. Items are returned in input order. In a cloze field, a deletion's number ({{c1::…}}) is its card's identity: keep the numbers of the deletions you keep, and leave any gaps for the save to close. Renumbering them yourself hands a card's review history to other text, and can delete it.",
|
|
109
109
|
tags: ["Notebase Rows"],
|
|
110
110
|
})
|
|
111
111
|
.errors(
|
package/src/contracts/speech.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { oc } from "@orpc/contract";
|
|
2
|
+
import { z } from "zod";
|
|
2
3
|
import {
|
|
4
|
+
speechDeleteAllAudioOutputSchema,
|
|
5
|
+
speechDeleteAudioInputSchema,
|
|
3
6
|
speechPreferencesSchema,
|
|
4
7
|
speechPreferencesUpdateInputSchema,
|
|
5
8
|
speechSynthesizeInputSchema,
|
|
@@ -42,4 +45,21 @@ export const speechContract = {
|
|
|
42
45
|
})
|
|
43
46
|
.input(speechSynthesizeInputSchema)
|
|
44
47
|
.output(speechSynthesizeOutputSchema),
|
|
48
|
+
deleteAudio: oc
|
|
49
|
+
.route({
|
|
50
|
+
method: "POST",
|
|
51
|
+
path: "/speech/audio/delete",
|
|
52
|
+
summary: "Forget the audio kept for some texts, so each is read anew next time",
|
|
53
|
+
tags: ["Speech"],
|
|
54
|
+
})
|
|
55
|
+
.input(speechDeleteAudioInputSchema)
|
|
56
|
+
.output(z.void()),
|
|
57
|
+
deleteAllAudio: oc
|
|
58
|
+
.route({
|
|
59
|
+
method: "DELETE",
|
|
60
|
+
path: "/speech/audio",
|
|
61
|
+
summary: "Forget all the audio kept for the authenticated user's texts",
|
|
62
|
+
tags: ["Speech"],
|
|
63
|
+
})
|
|
64
|
+
.output(speechDeleteAllAudioOutputSchema),
|
|
45
65
|
};
|
package/src/public-errors.ts
CHANGED
|
@@ -21,6 +21,24 @@ export const CellValidationFailureReasonSchema = z.enum([
|
|
|
21
21
|
"type_mismatch",
|
|
22
22
|
"invalid_select_option",
|
|
23
23
|
"invalid_date_format",
|
|
24
|
+
// A cloze deletion with a blank answer; receivedValue is the deletion as
|
|
25
|
+
// written (`{{c1::}}`).
|
|
26
|
+
"empty_cloze_answer",
|
|
27
|
+
// A cloze opening that never closes (unpaired braces, or no `}}`);
|
|
28
|
+
// receivedValue is the opening as written (`{{c1::`).
|
|
29
|
+
"unclosed_cloze",
|
|
30
|
+
// A cloze deletion whose ordinal list names no group (`{{c0::a}}`);
|
|
31
|
+
// receivedValue is the deletion as written.
|
|
32
|
+
"invalid_cloze_ordinal",
|
|
33
|
+
// A cloze deletion that cuts across a formula's delimiters (`\(a{{c1::b\) c}}`);
|
|
34
|
+
// receivedValue is the deletion as written.
|
|
35
|
+
"cloze_crosses_math",
|
|
36
|
+
// A cloze deletion in a formula that holds half of a `\left`/`\right` or
|
|
37
|
+
// `\begin`/`\end` pair; receivedValue is the deletion as written.
|
|
38
|
+
"cloze_splits_math_pair",
|
|
39
|
+
// More cloze groups in one note than CLOZE_MAX_GROUPS; receivedValue is
|
|
40
|
+
// the note's group count.
|
|
41
|
+
"too_many_cloze_groups",
|
|
24
42
|
]);
|
|
25
43
|
export type CellValidationFailureReason = z.infer<typeof CellValidationFailureReasonSchema>;
|
|
26
44
|
|
|
@@ -42,6 +60,8 @@ export const CellValidationFailedDataSchema = z
|
|
|
42
60
|
reason: CellValidationFailureReasonSchema,
|
|
43
61
|
/** Index of the failing row within a batch input (createMany / updateMany / initialRows). */
|
|
44
62
|
rowIndex: z.number().int().min(0).optional(),
|
|
63
|
+
/** The failing note, when the check ran over stored notes (a column type change). */
|
|
64
|
+
notebaseRowId: z.string().optional(),
|
|
45
65
|
})
|
|
46
66
|
.strict();
|
|
47
67
|
export type CellValidationFailedData = z.infer<typeof CellValidationFailedDataSchema>;
|
|
@@ -103,6 +123,10 @@ export const SpeechDailyLimitReachedDataSchema = z.strictObject({
|
|
|
103
123
|
});
|
|
104
124
|
|
|
105
125
|
export const PUBLIC_APP_ERROR_DEFS = {
|
|
126
|
+
CARD_DELETE_UNSUPPORTED: {
|
|
127
|
+
message: "This card cannot be deleted independently of its note.",
|
|
128
|
+
status: 400,
|
|
129
|
+
},
|
|
106
130
|
RESOURCE_UPDATE_CONFLICT: {
|
|
107
131
|
message: "The resource changed. Read it again before updating.",
|
|
108
132
|
status: 409,
|
package/src/schemas/card.ts
CHANGED
|
@@ -114,6 +114,16 @@ export type CardGetInput = z.infer<typeof CardGetInputSchema>;
|
|
|
114
114
|
export const CardGetOutputSchema = renderedCardSchema;
|
|
115
115
|
export type CardGetOutput = z.infer<typeof CardGetOutputSchema>;
|
|
116
116
|
|
|
117
|
+
export const CardDeleteInputSchema = z.strictObject({
|
|
118
|
+
id: z.uuid(),
|
|
119
|
+
expectedVariantKey: z.string().min(1),
|
|
120
|
+
expectedRowUpdatedAt: expectedUpdatedAtSchema,
|
|
121
|
+
});
|
|
122
|
+
export type CardDeleteInput = z.infer<typeof CardDeleteInputSchema>;
|
|
123
|
+
|
|
124
|
+
export const CardDeleteOutputSchema = z.object({ txid: z.number() });
|
|
125
|
+
export type CardDeleteOutput = z.infer<typeof CardDeleteOutputSchema>;
|
|
126
|
+
|
|
117
127
|
export const CardGenerateInputSchema = z.object({
|
|
118
128
|
notebaseId: z.uuid(),
|
|
119
129
|
templateId: z.uuid().optional(),
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { paginationShape, pageSchema } from "./pagination";
|
|
2
2
|
import { deletionResultSchema, expectedUpdatedAtSchema } from "./resource-mutation";
|
|
3
|
+
import {
|
|
4
|
+
noteCreatedViaSchema,
|
|
5
|
+
noteOriginInputSchema,
|
|
6
|
+
noteOriginSchema,
|
|
7
|
+
} from "@read-frog/definitions";
|
|
3
8
|
import { z } from "zod";
|
|
4
9
|
|
|
5
10
|
export const notebaseRowCellsSchema = z.record(z.string(), z.unknown());
|
|
@@ -9,15 +14,38 @@ export const notebaseRowSchema = z.object({
|
|
|
9
14
|
notebaseId: z.string(),
|
|
10
15
|
cells: notebaseRowCellsSchema,
|
|
11
16
|
position: z.number(),
|
|
17
|
+
origin: noteOriginSchema
|
|
18
|
+
.nullish()
|
|
19
|
+
.describe(
|
|
20
|
+
"Where the note's content came from, set when it was created: a web page (url, title), a book on the user's shelf (ebookId, its title then, and cfi, the EPUB location of the selection), or a video (url, title, seconds into it). Null when it has none, or when it was created before this was recorded.",
|
|
21
|
+
),
|
|
22
|
+
createdVia: noteCreatedViaSchema
|
|
23
|
+
.nullish()
|
|
24
|
+
.describe(
|
|
25
|
+
'Which client created the note: "web", "extension", "mobile", "mcp" (with the MCP client\'s id and the name it registered under) or "unknown". Null for notes created before this was recorded.',
|
|
26
|
+
),
|
|
12
27
|
createdAt: z.coerce.date(),
|
|
13
28
|
updatedAt: z.coerce.date(),
|
|
14
29
|
});
|
|
15
30
|
export type NotebaseRow = z.infer<typeof notebaseRowSchema>;
|
|
16
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Where a new note's content came from; see `noteOriginInputSchema`. Optional
|
|
34
|
+
* metadata never costs the note: an origin that does not parse is dropped and
|
|
35
|
+
* the note is saved without one.
|
|
36
|
+
*/
|
|
37
|
+
export const notebaseRowOriginInputSchema = noteOriginInputSchema
|
|
38
|
+
.optional()
|
|
39
|
+
.catch(undefined)
|
|
40
|
+
.describe(
|
|
41
|
+
"Where the note's content came from: a web page or video by its http(s) URL, or a book on the user's shelf by its id. Stored once and never edited. Optional: an origin that cannot be used is left out and the note is still saved.",
|
|
42
|
+
);
|
|
43
|
+
|
|
17
44
|
export const notebaseRowCreateDataSchema = z
|
|
18
45
|
.object({
|
|
19
46
|
id: z.uuid().optional(),
|
|
20
47
|
cells: notebaseRowCellsSchema.optional(),
|
|
48
|
+
origin: notebaseRowOriginInputSchema,
|
|
21
49
|
})
|
|
22
50
|
.strict();
|
|
23
51
|
export type NotebaseRowCreateData = z.infer<typeof notebaseRowCreateDataSchema>;
|
|
@@ -88,6 +116,8 @@ export const NotebaseRowUpdateOutputSchema = z.object({
|
|
|
88
116
|
notebaseId: z.uuid(),
|
|
89
117
|
cells: notebaseRowCellsSchema,
|
|
90
118
|
position: z.number().int(),
|
|
119
|
+
origin: notebaseRowSchema.shape.origin,
|
|
120
|
+
createdVia: notebaseRowSchema.shape.createdVia,
|
|
91
121
|
createdAt: z.date(),
|
|
92
122
|
updatedAt: z.date(),
|
|
93
123
|
txid: z.number(),
|
package/src/schemas/notebase.ts
CHANGED
|
@@ -8,7 +8,11 @@ import {
|
|
|
8
8
|
} from "@read-frog/definitions";
|
|
9
9
|
import { z } from "zod";
|
|
10
10
|
import { notebaseColumnSchema } from "./notebase-column";
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
NOTEBASE_ROW_CREATE_MANY_MAX_ROWS,
|
|
13
|
+
notebaseRowOriginInputSchema,
|
|
14
|
+
notebaseRowSchema,
|
|
15
|
+
} from "./notebase-row";
|
|
12
16
|
import { notebaseViewSchema } from "./notebase-view";
|
|
13
17
|
|
|
14
18
|
export const NotebaseListInputSchema = z.object({});
|
|
@@ -55,6 +59,7 @@ const notebaseInitialRowSchema = z
|
|
|
55
59
|
.object({
|
|
56
60
|
id: z.uuid().optional(),
|
|
57
61
|
cells: z.record(z.string(), z.unknown()),
|
|
62
|
+
origin: notebaseRowOriginInputSchema,
|
|
58
63
|
})
|
|
59
64
|
.strict();
|
|
60
65
|
|
package/src/schemas/speech.ts
CHANGED
|
@@ -109,6 +109,29 @@ export const speechSynthesizeInputSchema = z
|
|
|
109
109
|
.strict();
|
|
110
110
|
export type SpeechSynthesizeInput = z.infer<typeof speechSynthesizeInputSchema>;
|
|
111
111
|
|
|
112
|
+
/** The most texts one request forgets the audio of. */
|
|
113
|
+
export const SPEECH_DELETE_AUDIO_MAX_TEXTS = 50;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Texts whose kept audio to forget, each as it is asked to be read aloud
|
|
117
|
+
* (`speech.synthesize`): the next time one is, it is read anew. Audio for a
|
|
118
|
+
* text that has none kept is no error.
|
|
119
|
+
*/
|
|
120
|
+
export const speechDeleteAudioInputSchema = z
|
|
121
|
+
.object({
|
|
122
|
+
texts: z.array(speechSynthesizeInputSchema).min(1).max(SPEECH_DELETE_AUDIO_MAX_TEXTS),
|
|
123
|
+
})
|
|
124
|
+
.strict();
|
|
125
|
+
export type SpeechDeleteAudioInput = z.infer<typeof speechDeleteAudioInputSchema>;
|
|
126
|
+
|
|
127
|
+
export const speechDeleteAllAudioOutputSchema = z
|
|
128
|
+
.object({
|
|
129
|
+
// How many audio files there were.
|
|
130
|
+
deleted: z.number().int().nonnegative(),
|
|
131
|
+
})
|
|
132
|
+
.strict();
|
|
133
|
+
export type SpeechDeleteAllAudioOutput = z.infer<typeof speechDeleteAllAudioOutputSchema>;
|
|
134
|
+
|
|
112
135
|
/**
|
|
113
136
|
* Where to download the audio, an MP3: a link to fetch at once, which stops
|
|
114
137
|
* working at `expiresAt`.
|