@setluca/mcp 0.2.0 → 0.3.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/CHANGELOG.md +426 -0
- package/LICENSE +21 -0
- package/README.md +19 -258
- package/dist/annotations.js +35 -20
- package/dist/client-address.js +64 -0
- package/dist/completions.js +44 -27
- package/dist/config.js +89 -38
- package/dist/errors.js +99 -22
- package/dist/fields.js +68 -0
- package/dist/fixed-window.js +46 -0
- package/dist/generated/openapi-inputs.js +216 -5820
- package/dist/generated/openapi-outputs.js +19014 -14001
- package/dist/generated/route-catalog.js +80 -0
- package/dist/http.js +108 -44
- package/dist/index.js +4 -1
- package/dist/oauth-challenge.js +74 -0
- package/dist/oauth-resolver.js +58 -61
- package/dist/observability.js +31 -16
- package/dist/openapi-schema.js +175 -0
- package/dist/operations/catalog.js +17 -0
- package/dist/operations/groups/analytics.js +20 -10
- package/dist/operations/groups/bookings.js +43 -15
- package/dist/operations/groups/broadcasts.js +29 -58
- package/dist/operations/groups/cadences.js +3 -1
- package/dist/operations/groups/callEvents.js +19 -72
- package/dist/operations/groups/campaigns.js +28 -32
- package/dist/operations/groups/channels.js +23 -0
- package/dist/operations/groups/conversations.js +14 -25
- package/dist/operations/groups/insights.js +4 -0
- package/dist/operations/groups/integrations.js +15 -17
- package/dist/operations/groups/knowledge.js +5 -1
- package/dist/operations/groups/leads.js +22 -23
- package/dist/operations/groups/learning.js +13 -1
- package/dist/operations/groups/memory.js +4 -6
- package/dist/operations/groups/reports.js +1 -1
- package/dist/operations/groups/reviewQueue.js +17 -14
- package/dist/operations/groups/safety.js +1 -1
- package/dist/operations/groups/voice.js +0 -1
- package/dist/operations/groups/webhooks.js +16 -4
- package/dist/operations/manifest.js +9 -8
- package/dist/operations/registry.js +110 -116
- package/dist/operations.js +6 -5
- package/dist/optional-field.js +15 -3
- package/dist/page-contract.js +74 -0
- package/dist/pagination.js +149 -68
- package/dist/prompts.js +139 -88
- package/dist/provenance.js +2 -2
- package/dist/rate-limit.js +40 -0
- package/dist/remote.js +145 -65
- package/dist/resilience.js +120 -55
- package/dist/resolvers.js +125 -0
- package/dist/resources.js +17 -2
- package/dist/scopes.js +207 -0
- package/dist/serialization.js +32 -22
- package/dist/server.js +174 -191
- package/dist/standard-schema.js +215 -0
- package/dist/task-tools/analytics.js +145 -86
- package/dist/task-tools/bookings.js +176 -86
- package/dist/task-tools/cadences.js +30 -45
- package/dist/task-tools/calls.js +132 -125
- package/dist/task-tools/inbox.js +89 -53
- package/dist/task-tools/leads.js +49 -77
- package/dist/task-tools/runtime.js +80 -20
- package/dist/task-tools.js +1 -1
- package/dist/token-verification.js +42 -0
- package/dist/tool-cache.js +3 -2
- package/dist/tool.js +155 -100
- package/dist/version.js +1 -1
- package/package.json +40 -18
- package/dist/openapi-zod.js +0 -333
- package/dist/page-shape.js +0 -47
- package/dist/tsconfig.tsbuildinfo +0 -1
package/dist/openapi-zod.js
DELETED
|
@@ -1,333 +0,0 @@
|
|
|
1
|
-
import * as z from "zod/v4";
|
|
2
|
-
function isNullable(schema) {
|
|
3
|
-
return (schema.nullable === true ||
|
|
4
|
-
(Array.isArray(schema.type) && schema.type.includes("null")));
|
|
5
|
-
}
|
|
6
|
-
function nonNullTypes(schema) {
|
|
7
|
-
if (Array.isArray(schema.type)) {
|
|
8
|
-
return schema.type.filter((type) => type !== "null");
|
|
9
|
-
}
|
|
10
|
-
if (schema.type) {
|
|
11
|
-
return [schema.type];
|
|
12
|
-
}
|
|
13
|
-
return [];
|
|
14
|
-
}
|
|
15
|
-
function withDescription(schema, description) {
|
|
16
|
-
return description ? schema.describe(description) : schema;
|
|
17
|
-
}
|
|
18
|
-
const JsonLiteralSchema = z.union([
|
|
19
|
-
z.string(),
|
|
20
|
-
z.number(),
|
|
21
|
-
z.boolean(),
|
|
22
|
-
z.null(),
|
|
23
|
-
]);
|
|
24
|
-
function enumSchema(values) {
|
|
25
|
-
if (values.length === 0) {
|
|
26
|
-
return z.never();
|
|
27
|
-
}
|
|
28
|
-
const strings = z.array(z.string()).safeParse(values);
|
|
29
|
-
if (strings.success) {
|
|
30
|
-
const [first, ...rest] = strings.data;
|
|
31
|
-
return first === undefined ? z.never() : z.enum([first, ...rest]);
|
|
32
|
-
}
|
|
33
|
-
const literals = z.array(JsonLiteralSchema).safeParse(values);
|
|
34
|
-
if (!literals.success) {
|
|
35
|
-
return z.never();
|
|
36
|
-
}
|
|
37
|
-
const [firstValue, secondValue, ...restValues] = literals.data;
|
|
38
|
-
if (firstValue === undefined) {
|
|
39
|
-
return z.never();
|
|
40
|
-
}
|
|
41
|
-
const first = z.literal(firstValue);
|
|
42
|
-
if (secondValue === undefined) {
|
|
43
|
-
return first;
|
|
44
|
-
}
|
|
45
|
-
const second = z.literal(secondValue);
|
|
46
|
-
const rest = restValues.map((value) => z.literal(value));
|
|
47
|
-
return z.union([first, second, ...rest]);
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Zod 4 moved the string formats to top-level constructors: the `.uuid()` /
|
|
51
|
-
* `.url()` / `.datetime()` methods on a ZodString are deprecated. They all
|
|
52
|
-
* still subclass ZodString, so the length and pattern refinements below chain
|
|
53
|
-
* onto whichever base a format picks.
|
|
54
|
-
*/
|
|
55
|
-
function formatBase(format) {
|
|
56
|
-
if (format === "uuid") {
|
|
57
|
-
return z.uuid();
|
|
58
|
-
}
|
|
59
|
-
if (format === "uri") {
|
|
60
|
-
return z.url();
|
|
61
|
-
}
|
|
62
|
-
if (format === "date-time") {
|
|
63
|
-
return z.iso.datetime({ offset: true });
|
|
64
|
-
}
|
|
65
|
-
return z.string();
|
|
66
|
-
}
|
|
67
|
-
function stringSchema(schema) {
|
|
68
|
-
let result = formatBase(schema.format);
|
|
69
|
-
if (schema.minLength !== undefined) {
|
|
70
|
-
result = result.min(schema.minLength);
|
|
71
|
-
}
|
|
72
|
-
if (schema.maxLength !== undefined) {
|
|
73
|
-
result = result.max(schema.maxLength);
|
|
74
|
-
}
|
|
75
|
-
// Forcing this guard true is an equivalent mutation rather than an untested
|
|
76
|
-
// branch: with no pattern the body builds `new RegExp(undefined)`, which is
|
|
77
|
-
// the empty pattern `/(?:)/` and matches every string, so the constraint it
|
|
78
|
-
// adds rejects nothing. Forcing it false is a real change and is covered.
|
|
79
|
-
// Stryker disable next-line ConditionalExpression: an absent pattern compiles to /(?:)/, which constrains nothing
|
|
80
|
-
if (schema.pattern) {
|
|
81
|
-
// fallow-ignore-next-line security-sink -- patterns come from Luca's checked-in generated OpenAPI document at startup, never from MCP tool arguments or request data
|
|
82
|
-
result = result.regex(new RegExp(schema.pattern));
|
|
83
|
-
}
|
|
84
|
-
return result;
|
|
85
|
-
}
|
|
86
|
-
function numberSchema(schema, integer) {
|
|
87
|
-
let result = integer ? z.number().int() : z.number();
|
|
88
|
-
if (schema.minimum !== undefined) {
|
|
89
|
-
result =
|
|
90
|
-
schema.exclusiveMinimum === true
|
|
91
|
-
? result.gt(schema.minimum)
|
|
92
|
-
: result.min(schema.minimum);
|
|
93
|
-
}
|
|
94
|
-
if (schema.maximum !== undefined) {
|
|
95
|
-
result =
|
|
96
|
-
schema.exclusiveMaximum === true
|
|
97
|
-
? result.lt(schema.maximum)
|
|
98
|
-
: result.max(schema.maximum);
|
|
99
|
-
}
|
|
100
|
-
return result;
|
|
101
|
-
}
|
|
102
|
-
function arraySchema(schema) {
|
|
103
|
-
let result = z.array(schema.items ? schemaToZod(schema.items) : z.unknown());
|
|
104
|
-
if (schema.minItems !== undefined) {
|
|
105
|
-
result = result.min(schema.minItems);
|
|
106
|
-
}
|
|
107
|
-
if (schema.maxItems !== undefined) {
|
|
108
|
-
result = result.max(schema.maxItems);
|
|
109
|
-
}
|
|
110
|
-
return result;
|
|
111
|
-
}
|
|
112
|
-
function scalarSchema(schema, type) {
|
|
113
|
-
if (type === "string") {
|
|
114
|
-
return stringSchema(schema);
|
|
115
|
-
}
|
|
116
|
-
if (type === "integer" || type === "number") {
|
|
117
|
-
return numberSchema(schema, type === "integer");
|
|
118
|
-
}
|
|
119
|
-
return scalarSchemaWithoutConstraints(schema, type);
|
|
120
|
-
}
|
|
121
|
-
function scalarSchemaWithoutConstraints(schema, type) {
|
|
122
|
-
switch (type) {
|
|
123
|
-
case "boolean":
|
|
124
|
-
return z.boolean();
|
|
125
|
-
case "array":
|
|
126
|
-
return arraySchema(schema);
|
|
127
|
-
case "object":
|
|
128
|
-
return objectSchema(schema);
|
|
129
|
-
default:
|
|
130
|
-
return z.unknown();
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
function objectSchema(schema) {
|
|
134
|
-
const properties = schema.properties ?? {};
|
|
135
|
-
const required = new Set(schema.required ?? []);
|
|
136
|
-
const fields = {};
|
|
137
|
-
for (const [key, property] of Object.entries(properties)) {
|
|
138
|
-
const propertySchema = schemaToZod(property);
|
|
139
|
-
fields[key] = required.has(key)
|
|
140
|
-
? propertySchema
|
|
141
|
-
: propertySchema.optional();
|
|
142
|
-
}
|
|
143
|
-
const result = z.object(fields);
|
|
144
|
-
if (schema.additionalProperties === true) {
|
|
145
|
-
return result.catchall(z.unknown());
|
|
146
|
-
}
|
|
147
|
-
if (schema.additionalProperties !== undefined &&
|
|
148
|
-
schema.additionalProperties !== false) {
|
|
149
|
-
const object = z
|
|
150
|
-
.record(z.string(), z.unknown())
|
|
151
|
-
.safeParse(schema.additionalProperties);
|
|
152
|
-
if (object.success) {
|
|
153
|
-
return result.catchall(schemaToZod(schema.additionalProperties));
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
return result;
|
|
157
|
-
}
|
|
158
|
-
function schemaToZod(schema) {
|
|
159
|
-
const literal = literalOrEnumSchema(schema);
|
|
160
|
-
if (literal) {
|
|
161
|
-
return literal;
|
|
162
|
-
}
|
|
163
|
-
const composite = compositeSchema(schema);
|
|
164
|
-
if (composite) {
|
|
165
|
-
return composite;
|
|
166
|
-
}
|
|
167
|
-
return describedBaseSchema(schema);
|
|
168
|
-
}
|
|
169
|
-
function literalOrEnumSchema(schema) {
|
|
170
|
-
if (schema.const !== undefined) {
|
|
171
|
-
const literal = JsonLiteralSchema.safeParse(schema.const);
|
|
172
|
-
return literal.success ? z.literal(literal.data) : z.never();
|
|
173
|
-
}
|
|
174
|
-
if (schema.enum) {
|
|
175
|
-
return withDescription(enumSchema(schema.enum), schema.description);
|
|
176
|
-
}
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
function compositeSchema(schema) {
|
|
180
|
-
const variants = schema.anyOf ?? schema.oneOf;
|
|
181
|
-
if (variants?.length) {
|
|
182
|
-
return variantSchema(schema, variants);
|
|
183
|
-
}
|
|
184
|
-
const allOf = schema.allOf;
|
|
185
|
-
if (allOf?.length) {
|
|
186
|
-
return withDescription(schemaToZod(mergeAllOf(allOf)), schema.description);
|
|
187
|
-
}
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
function variantSchema(schema, variants) {
|
|
191
|
-
const unionItems = variants.map(schemaToZod);
|
|
192
|
-
if (unionItems.length === 1) {
|
|
193
|
-
const single = unionItems[0] ?? z.never();
|
|
194
|
-
return withDescription(isNullable(schema) ? single.nullable() : single, schema.description);
|
|
195
|
-
}
|
|
196
|
-
const [first, second, ...rest] = unionItems;
|
|
197
|
-
if (first === undefined) {
|
|
198
|
-
return z.never();
|
|
199
|
-
}
|
|
200
|
-
if (second === undefined) {
|
|
201
|
-
return first;
|
|
202
|
-
}
|
|
203
|
-
const union = z.union([first, second, ...rest]);
|
|
204
|
-
return withDescription(isNullable(schema) ? union.nullable() : union, schema.description);
|
|
205
|
-
}
|
|
206
|
-
function describedBaseSchema(schema) {
|
|
207
|
-
const base = baseSchema(schema);
|
|
208
|
-
return withDescription(isNullable(schema) ? base.nullable() : base, schema.description);
|
|
209
|
-
}
|
|
210
|
-
function mergeAllOf(allOf) {
|
|
211
|
-
const merged = {};
|
|
212
|
-
const properties = {};
|
|
213
|
-
const required = [];
|
|
214
|
-
for (const item of allOf) {
|
|
215
|
-
Object.assign(merged, item);
|
|
216
|
-
Object.assign(properties, item.properties);
|
|
217
|
-
required.push(...(item.required ?? []));
|
|
218
|
-
}
|
|
219
|
-
return {
|
|
220
|
-
...merged,
|
|
221
|
-
...(Object.keys(properties).length > 0 ? { properties } : undefined),
|
|
222
|
-
// Equivalent under this module's public contract: an empty `required: []`
|
|
223
|
-
// and an absent `required` produce the identical Zod object schema, because
|
|
224
|
-
// `objectSchema` derives its required-set from `schema.required ?? []` — both
|
|
225
|
-
// yield an all-optional object. The guard only avoids emitting a noise key.
|
|
226
|
-
// Stryker disable next-line ConditionalExpression,EqualityOperator: empty and absent `required` are indistinguishable downstream
|
|
227
|
-
...(required.length > 0 ? { required } : undefined),
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
function baseSchema(schema) {
|
|
231
|
-
const types = nonNullTypes(schema);
|
|
232
|
-
const [first, second, ...rest] = types;
|
|
233
|
-
if (first === undefined) {
|
|
234
|
-
return schema.properties ? objectSchema(schema) : z.unknown();
|
|
235
|
-
}
|
|
236
|
-
if (second === undefined) {
|
|
237
|
-
return scalarSchema(schema, first);
|
|
238
|
-
}
|
|
239
|
-
return z.union([
|
|
240
|
-
scalarSchema(schema, first),
|
|
241
|
-
scalarSchema(schema, second),
|
|
242
|
-
...rest.map((type) => scalarSchema(schema, type)),
|
|
243
|
-
]);
|
|
244
|
-
}
|
|
245
|
-
function relaxJsonSchema(schema) {
|
|
246
|
-
const relaxed = {};
|
|
247
|
-
// Forcing this guard true assigns `relaxed.type = undefined` when there is no
|
|
248
|
-
// type, which is indistinguishable downstream from omitting the key: both
|
|
249
|
-
// `nonNullTypes` and the object-routing in `baseSchema` treat an undefined
|
|
250
|
-
// `type` and an absent `type` identically, producing the same Zod schema.
|
|
251
|
-
// Stryker disable next-line ConditionalExpression: an undefined `type` and an absent one yield the identical relaxed schema
|
|
252
|
-
if (schema.type !== undefined) {
|
|
253
|
-
relaxed.type = schema.type;
|
|
254
|
-
}
|
|
255
|
-
if (isNullable(schema)) {
|
|
256
|
-
relaxed.nullable = true;
|
|
257
|
-
}
|
|
258
|
-
if (schema.properties) {
|
|
259
|
-
const properties = {};
|
|
260
|
-
for (const [key, value] of Object.entries(schema.properties)) {
|
|
261
|
-
properties[key] = relaxJsonSchema(value);
|
|
262
|
-
}
|
|
263
|
-
relaxed.properties = properties;
|
|
264
|
-
relaxed.required = [];
|
|
265
|
-
relaxed.additionalProperties = true;
|
|
266
|
-
// Defensive belt-and-suspenders: with `properties` present, `baseSchema`
|
|
267
|
-
// routes an absent type and an explicit `"object"` type to the same
|
|
268
|
-
// `objectSchema` call, so setting the type here never changes the produced
|
|
269
|
-
// Zod schema. Verified equivalent through both public entry points.
|
|
270
|
-
// Stryker disable next-line ConditionalExpression,BlockStatement: object routing is identical with or without an explicit type when properties exist
|
|
271
|
-
if (relaxed.type === undefined) {
|
|
272
|
-
// Stryker disable next-line StringLiteral: type value is inert here — properties already force objectSchema downstream
|
|
273
|
-
relaxed.type = "object";
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
if (schema.anyOf) {
|
|
277
|
-
relaxed.anyOf = schema.anyOf.map(relaxJsonSchema);
|
|
278
|
-
}
|
|
279
|
-
if (schema.oneOf) {
|
|
280
|
-
relaxed.oneOf = schema.oneOf.map(relaxJsonSchema);
|
|
281
|
-
}
|
|
282
|
-
if (schema.allOf) {
|
|
283
|
-
relaxed.allOf = schema.allOf.map(relaxJsonSchema);
|
|
284
|
-
}
|
|
285
|
-
// Array items and scalar formats/patterns/ranges/enums are intentionally
|
|
286
|
-
// dropped — see the doc comment.
|
|
287
|
-
return relaxed;
|
|
288
|
-
}
|
|
289
|
-
const untrustedProvenanceSchema = z
|
|
290
|
-
.object({ untrusted: z.literal(true), note: z.string() })
|
|
291
|
-
.loose();
|
|
292
|
-
/**
|
|
293
|
-
* The `structuredContent` fields for a tool: `{ result }`, plus an optional
|
|
294
|
-
* `provenance` marker for operations that carry lead-authored text. `result` is
|
|
295
|
-
* the operation's success-response body, deep-relaxed (see
|
|
296
|
-
* {@link relaxJsonSchema}) so real results validate while malformed ones fail
|
|
297
|
-
* loud.
|
|
298
|
-
*/
|
|
299
|
-
export function openApiToolOutputFields(input) {
|
|
300
|
-
const resultSchema = input.body
|
|
301
|
-
? schemaToZod(relaxJsonSchema(input.body))
|
|
302
|
-
: z.unknown();
|
|
303
|
-
return input.untrustedContent
|
|
304
|
-
? {
|
|
305
|
-
result: resultSchema,
|
|
306
|
-
provenance: untrustedProvenanceSchema.optional(),
|
|
307
|
-
}
|
|
308
|
-
: { result: resultSchema };
|
|
309
|
-
}
|
|
310
|
-
export function openApiToolInputFields(input) {
|
|
311
|
-
const fields = new Map(Object.entries(input.workspaceInput));
|
|
312
|
-
if (input.idempotencyRequired) {
|
|
313
|
-
for (const [key, schema] of Object.entries(input.idempotencyInput)) {
|
|
314
|
-
fields.set(key, schema);
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
const pathSchema = input.spec?.path
|
|
318
|
-
? schemaToZod(input.spec.path)
|
|
319
|
-
: undefined;
|
|
320
|
-
if (pathSchema instanceof z.ZodObject) {
|
|
321
|
-
// oxlint-disable-next-line typescript/dot-notation -- Zod owns this public property name; bracket access avoids classifying the library member as a local shape symbol.
|
|
322
|
-
for (const [key, schema] of Object.entries(pathSchema["shape"])) {
|
|
323
|
-
fields.set(key, schema);
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
if (input.spec?.query) {
|
|
327
|
-
fields.set("query", schemaToZod(input.spec.query).optional());
|
|
328
|
-
}
|
|
329
|
-
if (input.spec?.body) {
|
|
330
|
-
fields.set("body", schemaToZod(input.spec.body));
|
|
331
|
-
}
|
|
332
|
-
return Object.fromEntries(fields);
|
|
333
|
-
}
|
package/dist/page-shape.js
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { OPENAPI_INPUTS } from "./generated/openapi-inputs.js";
|
|
2
|
-
import { OPENAPI_OUTPUTS } from "./generated/openapi-outputs.js";
|
|
3
|
-
const INPUTS_BY_ROUTE = new Map(Object.entries(OPENAPI_INPUTS));
|
|
4
|
-
const OUTPUTS_BY_ROUTE = new Map(Object.entries(OPENAPI_OUTPUTS));
|
|
5
|
-
/**
|
|
6
|
-
* Every name a page cursor goes by in the manifest: `cursor` almost everywhere,
|
|
7
|
-
* and `before` on the conversation-messages route. This is an allowlist of two
|
|
8
|
-
* names, not inference — a schema cannot say which of a route's string query
|
|
9
|
-
* fields is a cursor without a naming convention, and there are two.
|
|
10
|
-
*/
|
|
11
|
-
const CURSOR_QUERY_PARAMS = ["cursor", "before"];
|
|
12
|
-
/** The query field a route takes its page cursor in, absent when it takes none. */
|
|
13
|
-
function cursorQueryParamFor(route) {
|
|
14
|
-
const query = INPUTS_BY_ROUTE.get(route)?.query;
|
|
15
|
-
return CURSOR_QUERY_PARAMS.find((name) => query?.properties?.[name] !== undefined);
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* The array field a page carries its records in, absent when the body hands
|
|
19
|
-
* back no cursor or declares no array. The items array is the first
|
|
20
|
-
* array-valued property the body declares, which is deterministic rather than a
|
|
21
|
-
* guess among equals: key order here is OpenAPI declaration order.
|
|
22
|
-
*/
|
|
23
|
-
function itemsKeyFor(route) {
|
|
24
|
-
const body = OUTPUTS_BY_ROUTE.get(route);
|
|
25
|
-
if (body?.type !== "object" || body.properties?.nextCursor === undefined) {
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
return Object.entries(body.properties).find(([key, schema]) => key !== "nextCursor" && schema?.type === "array")?.[0];
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* The page shape of a route, absent when the route does not paginate.
|
|
32
|
-
*
|
|
33
|
-
* Walking a list needs both halves: a body that hands back a cursor and an
|
|
34
|
-
* array to merge, and a query field to send the cursor in. Guessing from the
|
|
35
|
-
* operation id used to answer this, and was wrong fourteen times — twelve
|
|
36
|
-
* routes that answer with a plain array and no cursor, and two that paginate
|
|
37
|
-
* under an id that does not end in `.list`.
|
|
38
|
-
*/
|
|
39
|
-
export function pageShapeFor(method, path) {
|
|
40
|
-
const route = `${method} ${path}`;
|
|
41
|
-
const cursorQueryParam = cursorQueryParamFor(route);
|
|
42
|
-
const itemsKey = itemsKeyFor(route);
|
|
43
|
-
if (cursorQueryParam === undefined || itemsKey === undefined) {
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
return { cursorQueryParam, itemsKey };
|
|
47
|
-
}
|