@read-frog/api-contract 0.15.0 → 0.17.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 +149 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +117 -2
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/contracts/mcp-connection.ts +26 -0
- package/src/contracts/notebase-row.ts +31 -0
- package/src/index.ts +3 -0
- package/src/public-errors.ts +1 -1
- package/src/schemas/billing.ts +26 -0
- package/src/schemas/mcp-connection.ts +28 -0
- package/src/schemas/notebase-row.ts +60 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@read-frog/api-contract",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@orpc/contract": "^1.14.6",
|
|
28
28
|
"zod": "^4.5.4",
|
|
29
|
-
"@read-frog/definitions": "0.
|
|
29
|
+
"@read-frog/definitions": "0.6.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@types/node": "^26.1.0",
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { oc } from "@orpc/contract";
|
|
2
|
+
import {
|
|
3
|
+
mcpConnectionListOutputSchema,
|
|
4
|
+
mcpConnectionRevokeInputSchema,
|
|
5
|
+
mcpConnectionRevokeOutputSchema,
|
|
6
|
+
} from "#/schemas/mcp-connection";
|
|
7
|
+
|
|
8
|
+
export const mcpConnectionContract = {
|
|
9
|
+
list: oc
|
|
10
|
+
.route({
|
|
11
|
+
method: "GET",
|
|
12
|
+
path: "/mcp-connections",
|
|
13
|
+
summary: "List the OAuth clients the caller has authorized",
|
|
14
|
+
tags: ["MCP Connections"],
|
|
15
|
+
})
|
|
16
|
+
.output(mcpConnectionListOutputSchema),
|
|
17
|
+
revoke: oc
|
|
18
|
+
.route({
|
|
19
|
+
method: "POST",
|
|
20
|
+
path: "/mcp-connections/revoke",
|
|
21
|
+
summary: "Withdraw a client's authorization and revoke its tokens",
|
|
22
|
+
tags: ["MCP Connections"],
|
|
23
|
+
})
|
|
24
|
+
.input(mcpConnectionRevokeInputSchema)
|
|
25
|
+
.output(mcpConnectionRevokeOutputSchema),
|
|
26
|
+
};
|
|
@@ -13,10 +13,14 @@ import {
|
|
|
13
13
|
NotebaseRowCreateManyOutputSchema,
|
|
14
14
|
NotebaseRowCreateOutputSchema,
|
|
15
15
|
NotebaseRowDeleteInputSchema,
|
|
16
|
+
NotebaseRowDeleteManyInputSchema,
|
|
17
|
+
NotebaseRowDeleteManyOutputSchema,
|
|
16
18
|
NotebaseRowDeleteOutputSchema,
|
|
17
19
|
NotebaseRowReorderInputSchema,
|
|
18
20
|
NotebaseRowReorderOutputSchema,
|
|
19
21
|
NotebaseRowUpdateInputSchema,
|
|
22
|
+
NotebaseRowUpdateManyInputSchema,
|
|
23
|
+
NotebaseRowUpdateManyOutputSchema,
|
|
20
24
|
NotebaseRowUpdateOutputSchema,
|
|
21
25
|
} from "#/schemas/notebase-row";
|
|
22
26
|
|
|
@@ -96,6 +100,21 @@ export const notebaseRowContract = {
|
|
|
96
100
|
.input(NotebaseRowUpdateInputSchema)
|
|
97
101
|
.output(NotebaseRowUpdateOutputSchema),
|
|
98
102
|
|
|
103
|
+
updateMany: defineAgentProcedure({
|
|
104
|
+
method: "PATCH",
|
|
105
|
+
path: "/notebase-rows/batch",
|
|
106
|
+
summary: "Update notes",
|
|
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.",
|
|
109
|
+
tags: ["Notebase Rows"],
|
|
110
|
+
})
|
|
111
|
+
.errors(
|
|
112
|
+
pickPublicErrorMap("NOTEBASE_NOT_FOUND", "NOTEBASE_ROW_NOT_FOUND", "CELL_VALIDATION_FAILED"),
|
|
113
|
+
)
|
|
114
|
+
.errors(pickPublicErrorMap("RESOURCE_UPDATE_CONFLICT"))
|
|
115
|
+
.input(NotebaseRowUpdateManyInputSchema)
|
|
116
|
+
.output(NotebaseRowUpdateManyOutputSchema),
|
|
117
|
+
|
|
99
118
|
delete: defineAgentProcedure({
|
|
100
119
|
method: "DELETE",
|
|
101
120
|
path: "/notebase-rows/{notebaseRowId}",
|
|
@@ -108,6 +127,18 @@ export const notebaseRowContract = {
|
|
|
108
127
|
.input(NotebaseRowDeleteInputSchema)
|
|
109
128
|
.output(NotebaseRowDeleteOutputSchema),
|
|
110
129
|
|
|
130
|
+
deleteMany: defineAgentProcedure({
|
|
131
|
+
method: "POST",
|
|
132
|
+
path: "/notebase-rows/batch/delete",
|
|
133
|
+
summary: "Delete notes",
|
|
134
|
+
description:
|
|
135
|
+
"Permanently delete 1–50 owned notes atomically, including their derived cards and review records. Notes may belong to different notebases. If any ID is missing or inaccessible, nothing is deleted. Return deleted IDs in input order.",
|
|
136
|
+
tags: ["Notebase Rows"],
|
|
137
|
+
})
|
|
138
|
+
.errors(pickPublicErrorMap("NOTEBASE_ROW_NOT_FOUND", "CONCURRENT_POSITION_CONFLICT"))
|
|
139
|
+
.input(NotebaseRowDeleteManyInputSchema)
|
|
140
|
+
.output(NotebaseRowDeleteManyOutputSchema),
|
|
141
|
+
|
|
111
142
|
reorder: oc
|
|
112
143
|
.errors(pickPublicErrorMap("NOTEBASE_NOT_FOUND", "CONCURRENT_POSITION_CONFLICT"))
|
|
113
144
|
.route({
|
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 { mcpConnectionContract } from "./contracts/mcp-connection";
|
|
8
9
|
import { notebaseContract } from "./contracts/notebase";
|
|
9
10
|
import { notebaseColumnContract } from "./contracts/notebase-column";
|
|
10
11
|
import { notebaseRowContract } from "./contracts/notebase-row";
|
|
@@ -20,6 +21,7 @@ export const contract = {
|
|
|
20
21
|
cardTemplate: cardTemplateContract,
|
|
21
22
|
ebook: ebookContract,
|
|
22
23
|
hostedAi: hostedAiContract,
|
|
24
|
+
mcpConnection: mcpConnectionContract,
|
|
23
25
|
notebase: notebaseContract,
|
|
24
26
|
notebaseColumn: notebaseColumnContract,
|
|
25
27
|
notebaseRow: notebaseRowContract,
|
|
@@ -38,6 +40,7 @@ export * from "./schemas/billing";
|
|
|
38
40
|
export * from "./schemas/card";
|
|
39
41
|
export * from "./schemas/ebook";
|
|
40
42
|
export * from "./schemas/hosted-ai";
|
|
43
|
+
export * from "./schemas/mcp-connection";
|
|
41
44
|
export * from "./schemas/notebase";
|
|
42
45
|
export * from "./schemas/notebase-column";
|
|
43
46
|
export * from "./schemas/notebase-row";
|
package/src/public-errors.ts
CHANGED
|
@@ -32,7 +32,7 @@ export const CellValidationFailedDataSchema = z
|
|
|
32
32
|
notebaseColumnId: z.string(),
|
|
33
33
|
details: CellValidationFailureDetailsSchema,
|
|
34
34
|
reason: CellValidationFailureReasonSchema,
|
|
35
|
-
/** Index of the failing row within a batch input (createMany / initialRows). */
|
|
35
|
+
/** Index of the failing row within a batch input (createMany / updateMany / initialRows). */
|
|
36
36
|
rowIndex: z.number().int().min(0).optional(),
|
|
37
37
|
})
|
|
38
38
|
.strict();
|
package/src/schemas/billing.ts
CHANGED
|
@@ -18,6 +18,32 @@ export type BillingSubscriptionStatus = z.infer<typeof billingSubscriptionStatus
|
|
|
18
18
|
export const billingPlanSchema = z.enum(["free", "pro", "ultra"]);
|
|
19
19
|
export type BillingPlan = z.infer<typeof billingPlanSchema>;
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Whether a plan may use a feature, given the plans that feature allows.
|
|
23
|
+
*
|
|
24
|
+
* An explicit set rather than a minimum tier: there is no rank table to keep in
|
|
25
|
+
* step with the plan enum, and any combination is expressible by writing it
|
|
26
|
+
* down. The cost is that nothing derives from it automatically -- a feature's
|
|
27
|
+
* pricing-card placement is a companion edit, noted at each `*_ALLOWED_PLANS`.
|
|
28
|
+
*/
|
|
29
|
+
export function isPlanAllowed(plan: BillingPlan, allowed: readonly BillingPlan[]): boolean {
|
|
30
|
+
return allowed.includes(plan);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Plans that may use the remote MCP server.
|
|
35
|
+
*
|
|
36
|
+
* Read by the server-side gate AND by the website's copy, so the page cannot
|
|
37
|
+
* promise access the API refuses. `satisfies` keeps the literal tuple type so
|
|
38
|
+
* the UI can render the plan names from this constant instead of hardcoding
|
|
39
|
+
* them.
|
|
40
|
+
*
|
|
41
|
+
* Companion edit when this changes: the feature bullet in
|
|
42
|
+
* `apps/www/src/routes/pricing.tsx` lives on the lowest allowed plan's card
|
|
43
|
+
* (higher cards inherit it through their "everything in <lower plan>" line).
|
|
44
|
+
*/
|
|
45
|
+
export const MCP_ALLOWED_PLANS = ["ultra"] as const satisfies readonly BillingPlan[];
|
|
46
|
+
|
|
21
47
|
// Plans a user can actively check out into (the free tier has no price).
|
|
22
48
|
export const billingCheckoutPlanSchema = z.enum(["pro", "ultra"]);
|
|
23
49
|
export type BillingCheckoutPlan = z.infer<typeof billingCheckoutPlanSchema>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const mcpConnectionSchema = z
|
|
4
|
+
.object({
|
|
5
|
+
/** OAuth client identifier. For a CIMD client this is an https URL. */
|
|
6
|
+
clientId: z.string(),
|
|
7
|
+
/** The client's registered display name, absent when it never gave one. */
|
|
8
|
+
clientName: z.string().nullable(),
|
|
9
|
+
scopes: z.array(z.string()),
|
|
10
|
+
requestedUserInfoClaims: z.array(z.string()),
|
|
11
|
+
grantedAt: z.date().nullable(),
|
|
12
|
+
updatedAt: z.date().nullable(),
|
|
13
|
+
/**
|
|
14
|
+
* When this client last obtained an access token — the closest honest
|
|
15
|
+
* proxy for "last used" the OAuth tables record. A token is issued on
|
|
16
|
+
* connect and on every refresh, so an idle client's value stops advancing.
|
|
17
|
+
*/
|
|
18
|
+
lastAuthorizedAt: z.date().nullable(),
|
|
19
|
+
})
|
|
20
|
+
.strict();
|
|
21
|
+
export type McpConnection = z.infer<typeof mcpConnectionSchema>;
|
|
22
|
+
|
|
23
|
+
export const mcpConnectionListOutputSchema = z.array(mcpConnectionSchema);
|
|
24
|
+
|
|
25
|
+
export const mcpConnectionRevokeInputSchema = z.object({ clientId: z.string().min(1) }).strict();
|
|
26
|
+
export type McpConnectionRevokeInput = z.infer<typeof mcpConnectionRevokeInputSchema>;
|
|
27
|
+
|
|
28
|
+
export const mcpConnectionRevokeOutputSchema = z.object({ clientId: z.string() }).strict();
|
|
@@ -94,6 +94,42 @@ export const NotebaseRowUpdateOutputSchema = z.object({
|
|
|
94
94
|
});
|
|
95
95
|
export type NotebaseRowUpdateOutput = z.infer<typeof NotebaseRowUpdateOutputSchema>;
|
|
96
96
|
|
|
97
|
+
export const NOTEBASE_ROW_UPDATE_MANY_MAX_ROWS = 50;
|
|
98
|
+
|
|
99
|
+
export const NotebaseRowUpdateManyInputSchema = z
|
|
100
|
+
.object({
|
|
101
|
+
notebaseId: z.uuid(),
|
|
102
|
+
rows: z
|
|
103
|
+
.array(
|
|
104
|
+
z.object({
|
|
105
|
+
id: z.uuid(),
|
|
106
|
+
data: notebaseRowUpdateDataSchema,
|
|
107
|
+
}),
|
|
108
|
+
)
|
|
109
|
+
.min(1)
|
|
110
|
+
.max(NOTEBASE_ROW_UPDATE_MANY_MAX_ROWS),
|
|
111
|
+
})
|
|
112
|
+
.superRefine((input, ctx) => {
|
|
113
|
+
const seenIds = new Set<string>();
|
|
114
|
+
input.rows.forEach((row, index) => {
|
|
115
|
+
if (seenIds.has(row.id)) {
|
|
116
|
+
ctx.addIssue({
|
|
117
|
+
code: "custom",
|
|
118
|
+
message: `Duplicate row id "${row.id}".`,
|
|
119
|
+
path: ["rows", index, "id"],
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
seenIds.add(row.id);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
export type NotebaseRowUpdateManyInput = z.infer<typeof NotebaseRowUpdateManyInputSchema>;
|
|
126
|
+
|
|
127
|
+
export const NotebaseRowUpdateManyOutputSchema = z.object({
|
|
128
|
+
items: z.array(notebaseRowSchema),
|
|
129
|
+
txid: z.number(),
|
|
130
|
+
});
|
|
131
|
+
export type NotebaseRowUpdateManyOutput = z.infer<typeof NotebaseRowUpdateManyOutputSchema>;
|
|
132
|
+
|
|
97
133
|
export const NotebaseRowDeleteInputSchema = z.object({
|
|
98
134
|
notebaseRowId: z.uuid(),
|
|
99
135
|
});
|
|
@@ -102,6 +138,30 @@ export type NotebaseRowDeleteInput = z.infer<typeof NotebaseRowDeleteInputSchema
|
|
|
102
138
|
export const NotebaseRowDeleteOutputSchema = deletionResultSchema;
|
|
103
139
|
export type NotebaseRowDeleteOutput = z.infer<typeof NotebaseRowDeleteOutputSchema>;
|
|
104
140
|
|
|
141
|
+
export const NOTEBASE_ROW_DELETE_MANY_MAX_ROWS = 50;
|
|
142
|
+
|
|
143
|
+
export const NotebaseRowDeleteManyInputSchema = z
|
|
144
|
+
.object({
|
|
145
|
+
ids: z.array(z.uuid()).min(1).max(NOTEBASE_ROW_DELETE_MANY_MAX_ROWS),
|
|
146
|
+
})
|
|
147
|
+
.superRefine((input, ctx) => {
|
|
148
|
+
const seenIds = new Set<string>();
|
|
149
|
+
input.ids.forEach((id, index) => {
|
|
150
|
+
if (seenIds.has(id)) {
|
|
151
|
+
ctx.addIssue({
|
|
152
|
+
code: "custom",
|
|
153
|
+
message: `Duplicate row id "${id}".`,
|
|
154
|
+
path: ["ids", index],
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
seenIds.add(id);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
export type NotebaseRowDeleteManyInput = z.infer<typeof NotebaseRowDeleteManyInputSchema>;
|
|
161
|
+
|
|
162
|
+
export const NotebaseRowDeleteManyOutputSchema = z.object({ ids: z.array(z.uuid()) });
|
|
163
|
+
export type NotebaseRowDeleteManyOutput = z.infer<typeof NotebaseRowDeleteManyOutputSchema>;
|
|
164
|
+
|
|
105
165
|
export const NotebaseRowReorderInputSchema = z.object({
|
|
106
166
|
notebaseId: z.uuid(),
|
|
107
167
|
ids: z.array(z.uuid()),
|