@read-frog/api-contract 0.23.0 → 0.25.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 +2149 -80
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +732 -110
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
- package/src/contracts/billing.ts +1 -0
- package/src/contracts/ebook.ts +7 -15
- package/src/contracts/hosted-ai.ts +11 -0
- package/src/contracts/mcp-connection.ts +11 -0
- package/src/contracts/media.ts +60 -0
- package/src/contracts/notebase-column.ts +1 -1
- package/src/contracts/pdf-translation.ts +140 -0
- package/src/contracts/srs.ts +32 -0
- package/src/contracts/stats.ts +16 -0
- package/src/contracts/telemetry.ts +17 -0
- package/src/index.ts +9 -0
- package/src/public-errors.ts +126 -4
- package/src/schemas/billing.ts +19 -0
- package/src/schemas/ebook.ts +7 -0
- package/src/schemas/email-preferences.ts +44 -0
- package/src/schemas/hosted-ai.ts +15 -0
- package/src/schemas/mcp-connection.ts +17 -0
- package/src/schemas/media.ts +77 -0
- package/src/schemas/notebase.ts +3 -0
- package/src/schemas/pdf-translation.ts +240 -0
- package/src/schemas/srs.ts +178 -1
- package/src/schemas/web-sync.ts +121 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@read-frog/api-contract",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.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.
|
|
28
|
-
"@read-frog/definitions": "0.
|
|
29
|
-
"zod": "^4.6.
|
|
27
|
+
"@orpc/contract": "^1.16.1",
|
|
28
|
+
"@read-frog/definitions": "0.13.0",
|
|
29
|
+
"zod": "^4.6.7"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@repo/tsdown-config": "0.0.0",
|
|
33
33
|
"@repo/typescript-config": "0.0.0",
|
|
34
|
-
"@types/node": "^26.6.
|
|
35
|
-
"tsdown": "^0.23.
|
|
34
|
+
"@types/node": "^26.6.6",
|
|
35
|
+
"tsdown": "^0.23.4"
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
38
|
"build": "tsdown",
|
package/src/contracts/billing.ts
CHANGED
package/src/contracts/ebook.ts
CHANGED
|
@@ -23,7 +23,6 @@ const ebookProcedure = oc;
|
|
|
23
23
|
|
|
24
24
|
export const ebookContract = {
|
|
25
25
|
list: ebookProcedure
|
|
26
|
-
.errors(pickPublicErrorMap("EBOOK_BETA_RESTRICTED"))
|
|
27
26
|
.route({
|
|
28
27
|
method: "GET",
|
|
29
28
|
path: "/ebooks",
|
|
@@ -34,7 +33,7 @@ export const ebookContract = {
|
|
|
34
33
|
.output(ebookListOutputSchema),
|
|
35
34
|
|
|
36
35
|
get: ebookProcedure
|
|
37
|
-
.errors(pickPublicErrorMap("
|
|
36
|
+
.errors(pickPublicErrorMap("EBOOK_NOT_FOUND"))
|
|
38
37
|
.route({
|
|
39
38
|
method: "GET",
|
|
40
39
|
path: "/ebooks/{id}",
|
|
@@ -45,9 +44,7 @@ export const ebookContract = {
|
|
|
45
44
|
.output(ebookGetOutputSchema),
|
|
46
45
|
|
|
47
46
|
requestUpload: ebookProcedure
|
|
48
|
-
.errors(
|
|
49
|
-
pickPublicErrorMap("EBOOK_BETA_RESTRICTED", "EBOOK_LIMIT_EXCEEDED", "EBOOK_FILE_TOO_LARGE"),
|
|
50
|
-
)
|
|
47
|
+
.errors(pickPublicErrorMap("EBOOK_LIMIT_EXCEEDED", "EBOOK_FILE_TOO_LARGE"))
|
|
51
48
|
.route({
|
|
52
49
|
method: "POST",
|
|
53
50
|
path: "/ebooks/uploads",
|
|
@@ -59,12 +56,7 @@ export const ebookContract = {
|
|
|
59
56
|
|
|
60
57
|
finalizeUpload: ebookProcedure
|
|
61
58
|
.errors(
|
|
62
|
-
pickPublicErrorMap(
|
|
63
|
-
"EBOOK_BETA_RESTRICTED",
|
|
64
|
-
"EBOOK_NOT_FOUND",
|
|
65
|
-
"EBOOK_UPLOAD_NOT_COMPLETED",
|
|
66
|
-
"EBOOK_FILE_TOO_LARGE",
|
|
67
|
-
),
|
|
59
|
+
pickPublicErrorMap("EBOOK_NOT_FOUND", "EBOOK_UPLOAD_NOT_COMPLETED", "EBOOK_FILE_TOO_LARGE"),
|
|
68
60
|
)
|
|
69
61
|
.route({
|
|
70
62
|
method: "POST",
|
|
@@ -76,7 +68,7 @@ export const ebookContract = {
|
|
|
76
68
|
.output(ebookFinalizeUploadOutputSchema),
|
|
77
69
|
|
|
78
70
|
getDownloadUrl: ebookProcedure
|
|
79
|
-
.errors(pickPublicErrorMap("
|
|
71
|
+
.errors(pickPublicErrorMap("EBOOK_NOT_FOUND"))
|
|
80
72
|
.route({
|
|
81
73
|
method: "GET",
|
|
82
74
|
path: "/ebooks/{id}/download-url",
|
|
@@ -87,7 +79,7 @@ export const ebookContract = {
|
|
|
87
79
|
.output(ebookGetDownloadUrlOutputSchema),
|
|
88
80
|
|
|
89
81
|
updateProgress: ebookProcedure
|
|
90
|
-
.errors(pickPublicErrorMap("
|
|
82
|
+
.errors(pickPublicErrorMap("EBOOK_NOT_FOUND"))
|
|
91
83
|
.route({
|
|
92
84
|
method: "PATCH",
|
|
93
85
|
path: "/ebooks/{id}/progress",
|
|
@@ -98,7 +90,7 @@ export const ebookContract = {
|
|
|
98
90
|
.output(ebookUpdateProgressOutputSchema),
|
|
99
91
|
|
|
100
92
|
rename: ebookProcedure
|
|
101
|
-
.errors(pickPublicErrorMap("
|
|
93
|
+
.errors(pickPublicErrorMap("EBOOK_NOT_FOUND"))
|
|
102
94
|
.route({
|
|
103
95
|
method: "PATCH",
|
|
104
96
|
path: "/ebooks/{id}",
|
|
@@ -109,7 +101,7 @@ export const ebookContract = {
|
|
|
109
101
|
.output(ebookRenameOutputSchema),
|
|
110
102
|
|
|
111
103
|
delete: ebookProcedure
|
|
112
|
-
.errors(pickPublicErrorMap("
|
|
104
|
+
.errors(pickPublicErrorMap("EBOOK_NOT_FOUND"))
|
|
113
105
|
.route({
|
|
114
106
|
method: "DELETE",
|
|
115
107
|
path: "/ebooks/{id}",
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { eventIterator, oc } from "@orpc/contract";
|
|
2
2
|
import { pickPublicErrorMap } from "#/public-errors";
|
|
3
3
|
import {
|
|
4
|
+
HostedAiCreditPoolsInputSchema,
|
|
5
|
+
HostedAiCreditPoolsOutputSchema,
|
|
4
6
|
HostedAiNoteSuggestionStreamInputSchema,
|
|
5
7
|
HostedAiStatusInputSchema,
|
|
6
8
|
HostedAiStatusOutputSchema,
|
|
@@ -15,6 +17,15 @@ const hostedAiStreamErrors = pickPublicErrorMap(
|
|
|
15
17
|
);
|
|
16
18
|
|
|
17
19
|
export const hostedAiContract = {
|
|
20
|
+
pools: oc
|
|
21
|
+
.route({
|
|
22
|
+
method: "GET",
|
|
23
|
+
path: "/hosted-ai/pools",
|
|
24
|
+
summary: "Get shared Built-in AI credit pools for a feature",
|
|
25
|
+
tags: ["Hosted AI"],
|
|
26
|
+
})
|
|
27
|
+
.input(HostedAiCreditPoolsInputSchema)
|
|
28
|
+
.output(HostedAiCreditPoolsOutputSchema),
|
|
18
29
|
status: oc
|
|
19
30
|
.route({
|
|
20
31
|
method: "GET",
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { oc } from "@orpc/contract";
|
|
2
|
+
import { z } from "zod";
|
|
2
3
|
import {
|
|
4
|
+
mcpCallUsageSchema,
|
|
3
5
|
mcpConnectionListOutputSchema,
|
|
4
6
|
mcpConnectionRevokeInputSchema,
|
|
5
7
|
mcpConnectionRevokeOutputSchema,
|
|
@@ -23,4 +25,13 @@ export const mcpConnectionContract = {
|
|
|
23
25
|
})
|
|
24
26
|
.input(mcpConnectionRevokeInputSchema)
|
|
25
27
|
.output(mcpConnectionRevokeOutputSchema),
|
|
28
|
+
usage: oc
|
|
29
|
+
.route({
|
|
30
|
+
method: "GET",
|
|
31
|
+
path: "/mcp-connections/usage",
|
|
32
|
+
summary: "Get the MCP tool calls the caller has used of their plan's daily limit",
|
|
33
|
+
tags: ["MCP Connections"],
|
|
34
|
+
})
|
|
35
|
+
.input(z.object({}).strict())
|
|
36
|
+
.output(mcpCallUsageSchema),
|
|
26
37
|
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { oc } from "@orpc/contract";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { pickPublicErrorMap } from "#/public-errors";
|
|
4
|
+
import {
|
|
5
|
+
mediaAssetIdInputSchema,
|
|
6
|
+
mediaAssetOutputSchema,
|
|
7
|
+
mediaRequestUploadInputSchema,
|
|
8
|
+
mediaRequestUploadOutputSchema,
|
|
9
|
+
mediaUsageOutputSchema,
|
|
10
|
+
} from "#/schemas/media";
|
|
11
|
+
|
|
12
|
+
export const mediaContract = {
|
|
13
|
+
requestUpload: oc
|
|
14
|
+
.errors(
|
|
15
|
+
pickPublicErrorMap("MEDIA_BETA_RESTRICTED", "MEDIA_FILE_TOO_LARGE", "MEDIA_QUOTA_EXCEEDED"),
|
|
16
|
+
)
|
|
17
|
+
.route({
|
|
18
|
+
method: "POST",
|
|
19
|
+
path: "/media/uploads",
|
|
20
|
+
summary: "Reserve space and get a signed upload URL for a media file",
|
|
21
|
+
tags: ["Media"],
|
|
22
|
+
})
|
|
23
|
+
.input(mediaRequestUploadInputSchema)
|
|
24
|
+
.output(mediaRequestUploadOutputSchema),
|
|
25
|
+
|
|
26
|
+
completeUpload: oc
|
|
27
|
+
.errors(
|
|
28
|
+
pickPublicErrorMap("MEDIA_BETA_RESTRICTED", "MEDIA_NOT_FOUND", "MEDIA_UPLOAD_NOT_COMPLETED"),
|
|
29
|
+
)
|
|
30
|
+
.route({
|
|
31
|
+
method: "POST",
|
|
32
|
+
path: "/media/{id}/complete",
|
|
33
|
+
summary: "Queue an uploaded media file for validation",
|
|
34
|
+
tags: ["Media"],
|
|
35
|
+
})
|
|
36
|
+
.input(mediaAssetIdInputSchema)
|
|
37
|
+
.output(mediaAssetOutputSchema),
|
|
38
|
+
|
|
39
|
+
get: oc
|
|
40
|
+
.errors(pickPublicErrorMap("MEDIA_BETA_RESTRICTED", "MEDIA_NOT_FOUND"))
|
|
41
|
+
.route({
|
|
42
|
+
method: "GET",
|
|
43
|
+
path: "/media/{id}",
|
|
44
|
+
summary: "Get a media asset and its upload status",
|
|
45
|
+
tags: ["Media"],
|
|
46
|
+
})
|
|
47
|
+
.input(mediaAssetIdInputSchema)
|
|
48
|
+
.output(mediaAssetOutputSchema),
|
|
49
|
+
|
|
50
|
+
usage: oc
|
|
51
|
+
.errors(pickPublicErrorMap("MEDIA_BETA_RESTRICTED"))
|
|
52
|
+
.route({
|
|
53
|
+
method: "GET",
|
|
54
|
+
path: "/media/usage",
|
|
55
|
+
summary: "Get the media space used, reserved and allowed",
|
|
56
|
+
tags: ["Media"],
|
|
57
|
+
})
|
|
58
|
+
.input(z.object({}).strict())
|
|
59
|
+
.output(mediaUsageOutputSchema),
|
|
60
|
+
};
|
|
@@ -44,7 +44,7 @@ export const notebaseColumnContract = {
|
|
|
44
44
|
path: "/notebase-columns/{notebaseColumnId}",
|
|
45
45
|
summary: "Delete notebase field",
|
|
46
46
|
description:
|
|
47
|
-
|
|
47
|
+
'Delete an owned field and remove its values from notes. Primary fields cannot be deleted, nor can a field a template references: one a columns face lists, or a layout face names as {{ ["<column id>"] }}. A layout that loops over ctx.fields, as the default dictionary template\'s back does to show every other filled field, references none of them, so those can be deleted and simply leave the card.',
|
|
48
48
|
tags: ["Notebase Columns"],
|
|
49
49
|
})
|
|
50
50
|
.errors(
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { oc } from "@orpc/contract";
|
|
2
|
+
import { pickPublicErrorMap } from "#/public-errors";
|
|
3
|
+
import {
|
|
4
|
+
pdfTranslationDeleteOutputSchema,
|
|
5
|
+
pdfTranslationDownloadOutputSchema,
|
|
6
|
+
pdfTranslationGetOutputSchema,
|
|
7
|
+
pdfTranslationJobIdInputSchema,
|
|
8
|
+
pdfTranslationJobResourceSchema,
|
|
9
|
+
pdfTranslationListInputSchema,
|
|
10
|
+
pdfTranslationListOutputSchema,
|
|
11
|
+
pdfTranslationOutputInputSchema,
|
|
12
|
+
pdfTranslationPreviewOutputSchema,
|
|
13
|
+
pdfTranslationRequestUploadInputSchema,
|
|
14
|
+
pdfTranslationRequestUploadOutputSchema,
|
|
15
|
+
pdfTranslationStatusInputSchema,
|
|
16
|
+
pdfTranslationStatusOutputSchema,
|
|
17
|
+
pdfTranslationSubmitInputSchema,
|
|
18
|
+
pdfTranslationSubmitOutputSchema,
|
|
19
|
+
} from "#/schemas/pdf-translation";
|
|
20
|
+
|
|
21
|
+
const pdfTranslationProcedure = oc.errors(pickPublicErrorMap("PDF_TRANSLATION_UNAVAILABLE"));
|
|
22
|
+
|
|
23
|
+
export const pdfTranslationContract = {
|
|
24
|
+
status: pdfTranslationProcedure
|
|
25
|
+
.route({
|
|
26
|
+
method: "GET",
|
|
27
|
+
path: "/pdf-translations/status",
|
|
28
|
+
summary: "How many free PDF translations the account has left",
|
|
29
|
+
tags: ["PDF Translation"],
|
|
30
|
+
})
|
|
31
|
+
.input(pdfTranslationStatusInputSchema)
|
|
32
|
+
.output(pdfTranslationStatusOutputSchema),
|
|
33
|
+
|
|
34
|
+
list: pdfTranslationProcedure
|
|
35
|
+
.route({
|
|
36
|
+
method: "GET",
|
|
37
|
+
path: "/pdf-translations",
|
|
38
|
+
summary: "List recent PDF translations",
|
|
39
|
+
tags: ["PDF Translation"],
|
|
40
|
+
})
|
|
41
|
+
.input(pdfTranslationListInputSchema)
|
|
42
|
+
.output(pdfTranslationListOutputSchema),
|
|
43
|
+
|
|
44
|
+
requestUpload: pdfTranslationProcedure
|
|
45
|
+
.errors(pickPublicErrorMap("PDF_TRANSLATION_FILE_TOO_LARGE"))
|
|
46
|
+
.route({
|
|
47
|
+
method: "POST",
|
|
48
|
+
path: "/pdf-translations/uploads",
|
|
49
|
+
summary: "Request a signed upload for a PDF to translate",
|
|
50
|
+
tags: ["PDF Translation"],
|
|
51
|
+
})
|
|
52
|
+
.input(pdfTranslationRequestUploadInputSchema)
|
|
53
|
+
.output(pdfTranslationRequestUploadOutputSchema),
|
|
54
|
+
|
|
55
|
+
submit: pdfTranslationProcedure
|
|
56
|
+
.errors(
|
|
57
|
+
pickPublicErrorMap(
|
|
58
|
+
"HOSTED_AI_TIER_RESTRICTED",
|
|
59
|
+
"PDF_TRANSLATION_UPLOAD_NOT_FOUND",
|
|
60
|
+
"PDF_TRANSLATION_UPLOAD_EXPIRED",
|
|
61
|
+
"PDF_TRANSLATION_UPLOAD_NOT_COMPLETED",
|
|
62
|
+
"PDF_TRANSLATION_FILE_TOO_LARGE",
|
|
63
|
+
"PDF_TRANSLATION_INVALID_PDF",
|
|
64
|
+
"PDF_TRANSLATION_ALREADY_TRANSLATED",
|
|
65
|
+
"PDF_TRANSLATION_PAGE_LIMIT_EXCEEDED",
|
|
66
|
+
"PDF_TRANSLATION_QUEUE_FULL",
|
|
67
|
+
"PDF_TRANSLATION_IDEMPOTENCY_CONFLICT",
|
|
68
|
+
"PDF_TRANSLATION_INSUFFICIENT_CREDIT",
|
|
69
|
+
),
|
|
70
|
+
)
|
|
71
|
+
.route({
|
|
72
|
+
method: "POST",
|
|
73
|
+
path: "/pdf-translations",
|
|
74
|
+
summary: "Translate uploaded PDFs",
|
|
75
|
+
tags: ["PDF Translation"],
|
|
76
|
+
})
|
|
77
|
+
.input(pdfTranslationSubmitInputSchema)
|
|
78
|
+
.output(pdfTranslationSubmitOutputSchema),
|
|
79
|
+
|
|
80
|
+
get: pdfTranslationProcedure
|
|
81
|
+
.errors(pickPublicErrorMap("PDF_TRANSLATION_NOT_FOUND"))
|
|
82
|
+
.route({
|
|
83
|
+
method: "GET",
|
|
84
|
+
path: "/pdf-translations/{id}",
|
|
85
|
+
summary: "Get a PDF translation's status, with the others submitted with it",
|
|
86
|
+
tags: ["PDF Translation"],
|
|
87
|
+
})
|
|
88
|
+
.input(pdfTranslationJobIdInputSchema)
|
|
89
|
+
.output(pdfTranslationGetOutputSchema),
|
|
90
|
+
|
|
91
|
+
cancel: pdfTranslationProcedure
|
|
92
|
+
.errors(pickPublicErrorMap("PDF_TRANSLATION_NOT_FOUND", "PDF_TRANSLATION_NOT_CANCELLABLE"))
|
|
93
|
+
.route({
|
|
94
|
+
method: "POST",
|
|
95
|
+
path: "/pdf-translations/{id}/cancel",
|
|
96
|
+
summary: "Cancel a PDF translation",
|
|
97
|
+
tags: ["PDF Translation"],
|
|
98
|
+
})
|
|
99
|
+
.input(pdfTranslationJobIdInputSchema)
|
|
100
|
+
.output(pdfTranslationJobResourceSchema),
|
|
101
|
+
|
|
102
|
+
getPreviewUrl: pdfTranslationProcedure
|
|
103
|
+
.errors(pickPublicErrorMap("PDF_TRANSLATION_NOT_FOUND", "PDF_TRANSLATION_NOT_READY"))
|
|
104
|
+
.route({
|
|
105
|
+
method: "GET",
|
|
106
|
+
path: "/pdf-translations/{id}/preview-url",
|
|
107
|
+
summary: "Get a short-lived URL to read a translated PDF in the browser",
|
|
108
|
+
tags: ["PDF Translation"],
|
|
109
|
+
})
|
|
110
|
+
.input(pdfTranslationOutputInputSchema)
|
|
111
|
+
.output(pdfTranslationPreviewOutputSchema),
|
|
112
|
+
|
|
113
|
+
getDownloadUrl: pdfTranslationProcedure
|
|
114
|
+
.errors(
|
|
115
|
+
pickPublicErrorMap(
|
|
116
|
+
"PDF_TRANSLATION_NOT_FOUND",
|
|
117
|
+
"PDF_TRANSLATION_NOT_READY",
|
|
118
|
+
"PDF_TRANSLATION_DOWNLOAD_RESTRICTED",
|
|
119
|
+
),
|
|
120
|
+
)
|
|
121
|
+
.route({
|
|
122
|
+
method: "GET",
|
|
123
|
+
path: "/pdf-translations/{id}/download-url",
|
|
124
|
+
summary: "Get a download URL for a translated PDF",
|
|
125
|
+
tags: ["PDF Translation"],
|
|
126
|
+
})
|
|
127
|
+
.input(pdfTranslationOutputInputSchema)
|
|
128
|
+
.output(pdfTranslationDownloadOutputSchema),
|
|
129
|
+
|
|
130
|
+
delete: pdfTranslationProcedure
|
|
131
|
+
.errors(pickPublicErrorMap("PDF_TRANSLATION_NOT_FOUND", "PDF_TRANSLATION_NOT_DELETABLE"))
|
|
132
|
+
.route({
|
|
133
|
+
method: "DELETE",
|
|
134
|
+
path: "/pdf-translations/{id}",
|
|
135
|
+
summary: "Delete a finished PDF translation and its files",
|
|
136
|
+
tags: ["PDF Translation"],
|
|
137
|
+
})
|
|
138
|
+
.input(pdfTranslationJobIdInputSchema)
|
|
139
|
+
.output(pdfTranslationDeleteOutputSchema),
|
|
140
|
+
};
|
package/src/contracts/srs.ts
CHANGED
|
@@ -20,6 +20,10 @@ import {
|
|
|
20
20
|
srsSetCardBuriedOutputSchema,
|
|
21
21
|
srsSetCardSuspendedInputSchema,
|
|
22
22
|
srsSetCardSuspendedOutputSchema,
|
|
23
|
+
srsApplyPostponeInputSchema,
|
|
24
|
+
srsApplyPostponeOutputSchema,
|
|
25
|
+
srsPreviewPostponeInputSchema,
|
|
26
|
+
srsPreviewPostponeOutputSchema,
|
|
23
27
|
} from "#/schemas/srs";
|
|
24
28
|
|
|
25
29
|
export const srsContract = {
|
|
@@ -141,4 +145,32 @@ export const srsContract = {
|
|
|
141
145
|
)
|
|
142
146
|
.input(srsSetCardSuspendedInputSchema)
|
|
143
147
|
.output(srsSetCardSuspendedOutputSchema),
|
|
148
|
+
|
|
149
|
+
previewPostpone: oc
|
|
150
|
+
.errors(
|
|
151
|
+
pickPublicErrorMap(
|
|
152
|
+
"NOTEBASE_NOT_FOUND",
|
|
153
|
+
"RESCHEDULE_PREVIEW_TIMEOUT",
|
|
154
|
+
"RESCHEDULE_DAILY_LIMIT_INVALID",
|
|
155
|
+
),
|
|
156
|
+
)
|
|
157
|
+
.route({
|
|
158
|
+
method: "POST",
|
|
159
|
+
path: "/srs/postpone/preview",
|
|
160
|
+
summary: "Preview postpone",
|
|
161
|
+
tags: ["SRS"],
|
|
162
|
+
})
|
|
163
|
+
.input(srsPreviewPostponeInputSchema)
|
|
164
|
+
.output(srsPreviewPostponeOutputSchema),
|
|
165
|
+
|
|
166
|
+
applyPostpone: oc
|
|
167
|
+
.errors(pickPublicErrorMap("NOTEBASE_NOT_FOUND", "RESCHEDULE_PLAN_EXPIRED", "RESCHEDULE_STALE"))
|
|
168
|
+
.route({
|
|
169
|
+
method: "POST",
|
|
170
|
+
path: "/srs/postpone",
|
|
171
|
+
summary: "Apply postpone",
|
|
172
|
+
tags: ["SRS"],
|
|
173
|
+
})
|
|
174
|
+
.input(srsApplyPostponeInputSchema)
|
|
175
|
+
.output(srsApplyPostponeOutputSchema),
|
|
144
176
|
};
|
package/src/contracts/stats.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { defineAgentProcedure } from "./shared";
|
|
1
2
|
import { oc } from "@orpc/contract";
|
|
2
3
|
import { pickPublicErrorMap } from "#/public-errors";
|
|
3
4
|
import { notebaseRowGrowthStatsOutputSchema } from "#/schemas/notebase-stats";
|
|
5
|
+
import { srsBacklogStatsInputSchema, srsBacklogStatsOutputSchema } from "#/schemas/srs";
|
|
4
6
|
import { srsMemoryGrowthStatsOutputSchema } from "#/schemas/srs-stats";
|
|
5
7
|
import { activityStatsOutputSchema, statsInputSchema } from "#/schemas/stats";
|
|
6
8
|
|
|
@@ -37,4 +39,18 @@ export const statsContract = {
|
|
|
37
39
|
})
|
|
38
40
|
.input(statsInputSchema)
|
|
39
41
|
.output(notebaseRowGrowthStatsOutputSchema),
|
|
42
|
+
|
|
43
|
+
// A statistic like the rest, so reviews, which invalidate every `stats` query,
|
|
44
|
+
// mark it stale too.
|
|
45
|
+
backlogStats: defineAgentProcedure({
|
|
46
|
+
method: "GET",
|
|
47
|
+
path: "/srs/stats/backlog",
|
|
48
|
+
summary: "Get review backlogs",
|
|
49
|
+
description:
|
|
50
|
+
"Read overdue review backlogs for owned awake notebases in the supplied timezone. Only a notebase whose overdue reviews fill two or more days of its daily review limit is listed; the rest are left out. A notebase whose daily review limit is 0 or unlimited (-1) never returns anything, even when its ID is requested. Does not return note content or card faces.",
|
|
51
|
+
tags: ["Statistics"],
|
|
52
|
+
})
|
|
53
|
+
.errors(pickPublicErrorMap("NOTEBASE_NOT_FOUND"))
|
|
54
|
+
.input(srsBacklogStatsInputSchema)
|
|
55
|
+
.output(srsBacklogStatsOutputSchema),
|
|
40
56
|
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { oc } from "@orpc/contract";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { reportWebSyncInputSchema } from "#/schemas/web-sync";
|
|
4
|
+
|
|
5
|
+
export const telemetryContract = {
|
|
6
|
+
// Public: a page whose session ended still reports the streams that failed
|
|
7
|
+
// because of it.
|
|
8
|
+
reportWebSync: oc
|
|
9
|
+
.route({
|
|
10
|
+
method: "POST",
|
|
11
|
+
path: "/telemetry/web-sync",
|
|
12
|
+
summary: "Report problems the web app had keeping its synced data live",
|
|
13
|
+
tags: ["Telemetry"],
|
|
14
|
+
})
|
|
15
|
+
.input(reportWebSyncInputSchema)
|
|
16
|
+
.output(z.void()),
|
|
17
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -8,12 +8,15 @@ import { featureAccessContract } from "./contracts/feature-access";
|
|
|
8
8
|
import { hostedAiContract } from "./contracts/hosted-ai";
|
|
9
9
|
import { masteryGoalContract } from "./contracts/mastery-goal";
|
|
10
10
|
import { mcpConnectionContract } from "./contracts/mcp-connection";
|
|
11
|
+
import { mediaContract } from "./contracts/media";
|
|
11
12
|
import { notebaseContract } from "./contracts/notebase";
|
|
12
13
|
import { notebaseColumnContract } from "./contracts/notebase-column";
|
|
13
14
|
import { notebaseRowContract } from "./contracts/notebase-row";
|
|
15
|
+
import { pdfTranslationContract } from "./contracts/pdf-translation";
|
|
14
16
|
import { speechContract } from "./contracts/speech";
|
|
15
17
|
import { srsContract } from "./contracts/srs";
|
|
16
18
|
import { statsContract } from "./contracts/stats";
|
|
19
|
+
import { telemetryContract } from "./contracts/telemetry";
|
|
17
20
|
import { userContract } from "./contracts/user";
|
|
18
21
|
import { videoTranscriptContract } from "./contracts/video-transcript";
|
|
19
22
|
|
|
@@ -27,12 +30,15 @@ export const contract = {
|
|
|
27
30
|
hostedAi: hostedAiContract,
|
|
28
31
|
masteryGoal: masteryGoalContract,
|
|
29
32
|
mcpConnection: mcpConnectionContract,
|
|
33
|
+
media: mediaContract,
|
|
30
34
|
notebase: notebaseContract,
|
|
31
35
|
notebaseColumn: notebaseColumnContract,
|
|
32
36
|
notebaseRow: notebaseRowContract,
|
|
37
|
+
pdfTranslation: pdfTranslationContract,
|
|
33
38
|
speech: speechContract,
|
|
34
39
|
srs: srsContract,
|
|
35
40
|
stats: statsContract,
|
|
41
|
+
telemetry: telemetryContract,
|
|
36
42
|
user: userContract,
|
|
37
43
|
videoTranscript: videoTranscriptContract,
|
|
38
44
|
};
|
|
@@ -48,6 +54,7 @@ export * from "./schemas/ebook";
|
|
|
48
54
|
export * from "./schemas/email-preferences";
|
|
49
55
|
export * from "./schemas/feature-access";
|
|
50
56
|
export * from "./schemas/hosted-ai";
|
|
57
|
+
export * from "./schemas/media";
|
|
51
58
|
export * from "./schemas/mastery-goal";
|
|
52
59
|
export * from "./schemas/mcp-connection";
|
|
53
60
|
export * from "./schemas/notebase";
|
|
@@ -55,6 +62,7 @@ export * from "./schemas/notebase-column";
|
|
|
55
62
|
export * from "./schemas/notebase-row";
|
|
56
63
|
export * from "./schemas/notebase-stats";
|
|
57
64
|
export * from "./schemas/notebase-view";
|
|
65
|
+
export * from "./schemas/pdf-translation";
|
|
58
66
|
export * from "./schemas/speech";
|
|
59
67
|
export * from "./schemas/srs";
|
|
60
68
|
export * from "./schemas/srs-stats";
|
|
@@ -62,6 +70,7 @@ export * from "./schemas/stats";
|
|
|
62
70
|
export * from "./schemas/timezone";
|
|
63
71
|
export * from "./schemas/user";
|
|
64
72
|
export * from "./schemas/video-transcript";
|
|
73
|
+
export * from "./schemas/web-sync";
|
|
65
74
|
|
|
66
75
|
export * from "./schemas/pagination";
|
|
67
76
|
export * from "./schemas/resource-mutation";
|
package/src/public-errors.ts
CHANGED
|
@@ -5,6 +5,10 @@ import {
|
|
|
5
5
|
HostedAiModelTierSchema,
|
|
6
6
|
HostedAiUpgradePlanSchema,
|
|
7
7
|
} from "./schemas/hosted-ai";
|
|
8
|
+
import {
|
|
9
|
+
PdfTranslationInsufficientCreditDataSchema,
|
|
10
|
+
PdfTranslationUploadErrorDataSchema,
|
|
11
|
+
} from "./schemas/pdf-translation";
|
|
8
12
|
|
|
9
13
|
interface PublicAppErrorDefinition {
|
|
10
14
|
data?: z.ZodTypeAny;
|
|
@@ -216,6 +220,10 @@ export const PUBLIC_APP_ERROR_DEFS = {
|
|
|
216
220
|
message: "A subscription change is already scheduled",
|
|
217
221
|
status: 409,
|
|
218
222
|
},
|
|
223
|
+
BILLING_CHECKOUT_CONFLICT: {
|
|
224
|
+
message: "Please start a new checkout from the pricing page",
|
|
225
|
+
status: 409,
|
|
226
|
+
},
|
|
219
227
|
CARD_NOT_REVIEWABLE: {
|
|
220
228
|
message: "Card cannot be reviewed in its current state",
|
|
221
229
|
status: 409,
|
|
@@ -248,6 +256,22 @@ export const PUBLIC_APP_ERROR_DEFS = {
|
|
|
248
256
|
message: "Card status change is unavailable",
|
|
249
257
|
status: 409,
|
|
250
258
|
},
|
|
259
|
+
RESCHEDULE_PLAN_EXPIRED: {
|
|
260
|
+
message: "The reschedule plan has expired; preview it again",
|
|
261
|
+
status: 409,
|
|
262
|
+
},
|
|
263
|
+
RESCHEDULE_STALE: {
|
|
264
|
+
message: "Cards changed after the reschedule preview; preview it again",
|
|
265
|
+
status: 409,
|
|
266
|
+
},
|
|
267
|
+
RESCHEDULE_PREVIEW_TIMEOUT: {
|
|
268
|
+
message: "Planning the reschedule took too long",
|
|
269
|
+
status: 503,
|
|
270
|
+
},
|
|
271
|
+
RESCHEDULE_DAILY_LIMIT_INVALID: {
|
|
272
|
+
message: "The daily limit is outside the range a reschedule may suggest",
|
|
273
|
+
status: 422,
|
|
274
|
+
},
|
|
251
275
|
NOTEBASE_VIEW_NOT_FOUND: {
|
|
252
276
|
message: "Notebase view not found",
|
|
253
277
|
status: 404,
|
|
@@ -265,10 +289,6 @@ export const PUBLIC_APP_ERROR_DEFS = {
|
|
|
265
289
|
message: "Concurrent position conflict, please retry",
|
|
266
290
|
status: 409,
|
|
267
291
|
},
|
|
268
|
-
EBOOK_BETA_RESTRICTED: {
|
|
269
|
-
message: "Ebooks are currently in beta for selected accounts",
|
|
270
|
-
status: 403,
|
|
271
|
-
},
|
|
272
292
|
EBOOK_NOT_FOUND: {
|
|
273
293
|
message: "Ebook not found",
|
|
274
294
|
status: 404,
|
|
@@ -285,12 +305,114 @@ export const PUBLIC_APP_ERROR_DEFS = {
|
|
|
285
305
|
message: "Ebook upload has not been completed",
|
|
286
306
|
status: 409,
|
|
287
307
|
},
|
|
308
|
+
MEDIA_BETA_RESTRICTED: {
|
|
309
|
+
message: "Media uploads are currently in beta for selected accounts",
|
|
310
|
+
status: 403,
|
|
311
|
+
},
|
|
312
|
+
MEDIA_NOT_FOUND: {
|
|
313
|
+
message: "Media file not found",
|
|
314
|
+
status: 404,
|
|
315
|
+
},
|
|
316
|
+
MEDIA_FILE_TOO_LARGE: {
|
|
317
|
+
message: "Media file exceeds the maximum allowed size",
|
|
318
|
+
status: 413,
|
|
319
|
+
},
|
|
320
|
+
MEDIA_QUOTA_EXCEEDED: {
|
|
321
|
+
message: "Not enough media storage space left on your plan",
|
|
322
|
+
status: 403,
|
|
323
|
+
},
|
|
324
|
+
MEDIA_UPLOAD_NOT_COMPLETED: {
|
|
325
|
+
message: "Media upload has not been completed",
|
|
326
|
+
status: 409,
|
|
327
|
+
},
|
|
288
328
|
// An email's unsubscribe or preferences link that does not verify, or whose
|
|
289
329
|
// account no longer exists. The page asks the reader to sign in instead.
|
|
290
330
|
EMAIL_LINK_INVALID: {
|
|
291
331
|
message: "This email link is not valid",
|
|
292
332
|
status: 400,
|
|
293
333
|
},
|
|
334
|
+
// The deployment has no worker to run translations (no Batch job
|
|
335
|
+
// definition or PDF bucket configured).
|
|
336
|
+
PDF_TRANSLATION_UNAVAILABLE: {
|
|
337
|
+
message: "PDF translation is not available right now",
|
|
338
|
+
status: 503,
|
|
339
|
+
},
|
|
340
|
+
PDF_TRANSLATION_NOT_FOUND: {
|
|
341
|
+
message: "PDF translation not found",
|
|
342
|
+
status: 404,
|
|
343
|
+
},
|
|
344
|
+
PDF_TRANSLATION_UPLOAD_NOT_FOUND: {
|
|
345
|
+
data: PdfTranslationUploadErrorDataSchema,
|
|
346
|
+
message: "PDF upload not found",
|
|
347
|
+
status: 404,
|
|
348
|
+
},
|
|
349
|
+
PDF_TRANSLATION_UPLOAD_EXPIRED: {
|
|
350
|
+
data: PdfTranslationUploadErrorDataSchema,
|
|
351
|
+
message: "The PDF upload has expired; upload the file again",
|
|
352
|
+
status: 410,
|
|
353
|
+
},
|
|
354
|
+
PDF_TRANSLATION_UPLOAD_NOT_COMPLETED: {
|
|
355
|
+
data: PdfTranslationUploadErrorDataSchema,
|
|
356
|
+
message: "The PDF upload has not been completed",
|
|
357
|
+
status: 409,
|
|
358
|
+
},
|
|
359
|
+
PDF_TRANSLATION_FILE_TOO_LARGE: {
|
|
360
|
+
message: "The PDF exceeds the maximum allowed size",
|
|
361
|
+
status: 413,
|
|
362
|
+
},
|
|
363
|
+
PDF_TRANSLATION_INVALID_PDF: {
|
|
364
|
+
data: PdfTranslationUploadErrorDataSchema,
|
|
365
|
+
message: "The file is not a readable PDF",
|
|
366
|
+
status: 422,
|
|
367
|
+
},
|
|
368
|
+
// Every PDF a translation delivers is marked as one, and none is translated
|
|
369
|
+
// again.
|
|
370
|
+
PDF_TRANSLATION_ALREADY_TRANSLATED: {
|
|
371
|
+
data: PdfTranslationUploadErrorDataSchema,
|
|
372
|
+
message: "This PDF is already a translation; upload the original instead",
|
|
373
|
+
status: 422,
|
|
374
|
+
},
|
|
375
|
+
PDF_TRANSLATION_PAGE_LIMIT_EXCEEDED: {
|
|
376
|
+
data: PdfTranslationUploadErrorDataSchema,
|
|
377
|
+
message: "The PDF has more pages than one translation allows",
|
|
378
|
+
status: 422,
|
|
379
|
+
},
|
|
380
|
+
// An account's unfinished translations run one at a time, and no more than
|
|
381
|
+
// PDF_TRANSLATION_MAX_BATCH_FILES of them wait at once.
|
|
382
|
+
PDF_TRANSLATION_QUEUE_FULL: {
|
|
383
|
+
message: "Too many PDF translations are waiting; try again when some have finished",
|
|
384
|
+
status: 409,
|
|
385
|
+
},
|
|
386
|
+
PDF_TRANSLATION_IDEMPOTENCY_CONFLICT: {
|
|
387
|
+
message: "This idempotency key was already used for a different PDF translation",
|
|
388
|
+
status: 409,
|
|
389
|
+
},
|
|
390
|
+
// The estimate is over what is left of the Built-in AI credit window.
|
|
391
|
+
// Running out midway stops the job instead (its error category is
|
|
392
|
+
// quota_exhausted).
|
|
393
|
+
PDF_TRANSLATION_INSUFFICIENT_CREDIT: {
|
|
394
|
+
data: PdfTranslationInsufficientCreditDataSchema,
|
|
395
|
+
message: "Not enough Built-in AI credit left to translate this PDF",
|
|
396
|
+
status: 429,
|
|
397
|
+
},
|
|
398
|
+
PDF_TRANSLATION_NOT_CANCELLABLE: {
|
|
399
|
+
message: "This PDF translation has already finished",
|
|
400
|
+
status: 409,
|
|
401
|
+
},
|
|
402
|
+
PDF_TRANSLATION_NOT_READY: {
|
|
403
|
+
message: "The translated PDF is not available",
|
|
404
|
+
status: 409,
|
|
405
|
+
},
|
|
406
|
+
// A Free account reads its one translation online; downloading it, as any
|
|
407
|
+
// translated PDF, needs a paid plan.
|
|
408
|
+
PDF_TRANSLATION_DOWNLOAD_RESTRICTED: {
|
|
409
|
+
message: "Downloading a translated PDF needs a Pro or Ultra plan",
|
|
410
|
+
status: 403,
|
|
411
|
+
},
|
|
412
|
+
PDF_TRANSLATION_NOT_DELETABLE: {
|
|
413
|
+
message: "Cancel this PDF translation before deleting it",
|
|
414
|
+
status: 409,
|
|
415
|
+
},
|
|
294
416
|
} as const satisfies Record<string, PublicAppErrorDefinition>;
|
|
295
417
|
|
|
296
418
|
export type PublicAppErrorCode = keyof typeof PUBLIC_APP_ERROR_DEFS;
|