@stetcms/client 0.1.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/LICENSE +202 -0
- package/README.md +68 -0
- package/dist/codegen.d.ts +37 -0
- package/dist/codegen.js +112 -0
- package/dist/index.d.ts +571 -0
- package/dist/index.js +397 -0
- package/package.json +56 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
import { DEFAULT_ORIGIN, entryTypeName, fetchContentModel, renderContentModule } from "./codegen.js";
|
|
2
|
+
import { oc } from "@orpc/contract";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { createORPCClient, isDefinedError, safe } from "@orpc/client";
|
|
5
|
+
import { OpenAPILink } from "@orpc/openapi-client/fetch";
|
|
6
|
+
//#region ../../internal/api/src/contract.ts
|
|
7
|
+
const organizationSchema = z.object({
|
|
8
|
+
id: z.string(),
|
|
9
|
+
name: z.string(),
|
|
10
|
+
slug: z.string(),
|
|
11
|
+
logo: z.string().nullable(),
|
|
12
|
+
createdAt: z.iso.datetime()
|
|
13
|
+
});
|
|
14
|
+
const rateLimitError = { RATE_LIMITED: {
|
|
15
|
+
status: 429,
|
|
16
|
+
message: "Too many requests. Try again shortly."
|
|
17
|
+
} };
|
|
18
|
+
const authErrors = {
|
|
19
|
+
UNAUTHORIZED: { message: "Authentication required. Pass an organization API key in the `x-api-key` header." },
|
|
20
|
+
QUOTA_EXCEEDED: {
|
|
21
|
+
status: 429,
|
|
22
|
+
message: "The organization's API request limit for this period has been reached."
|
|
23
|
+
},
|
|
24
|
+
...rateLimitError
|
|
25
|
+
};
|
|
26
|
+
const health = oc.errors(rateLimitError).route({
|
|
27
|
+
method: "GET",
|
|
28
|
+
path: "/health",
|
|
29
|
+
summary: "Health check",
|
|
30
|
+
description: "Public liveness probe. Returns ok when the API is reachable.",
|
|
31
|
+
tags: ["System"]
|
|
32
|
+
}).output(z.object({ status: z.literal("ok") }));
|
|
33
|
+
const getOrg = oc.errors(authErrors).route({
|
|
34
|
+
method: "GET",
|
|
35
|
+
path: "/org",
|
|
36
|
+
summary: "Current organization",
|
|
37
|
+
description: "Details of the organization the API key is scoped to.",
|
|
38
|
+
tags: ["Organization"]
|
|
39
|
+
}).output(organizationSchema);
|
|
40
|
+
const usageSchema = z.object({
|
|
41
|
+
feature: z.enum([
|
|
42
|
+
"members",
|
|
43
|
+
"apiRequests",
|
|
44
|
+
"storage"
|
|
45
|
+
]),
|
|
46
|
+
used: z.number().int(),
|
|
47
|
+
cap: z.number().int().nullable(),
|
|
48
|
+
window: z.enum(["month"]).nullable()
|
|
49
|
+
});
|
|
50
|
+
const orgBillingSchema = z.object({
|
|
51
|
+
plan: z.enum(["free", "paid"]),
|
|
52
|
+
status: z.string().nullable(),
|
|
53
|
+
seats: z.number().int().nullable(),
|
|
54
|
+
periodEnd: z.iso.datetime().nullable(),
|
|
55
|
+
cancelAtPeriodEnd: z.boolean(),
|
|
56
|
+
usage: z.array(usageSchema)
|
|
57
|
+
});
|
|
58
|
+
const getOrgBilling = oc.errors(authErrors).route({
|
|
59
|
+
method: "GET",
|
|
60
|
+
path: "/org/billing",
|
|
61
|
+
summary: "Organization billing",
|
|
62
|
+
description: "The organization's plan, subscription state, and usage against the plan's limits.",
|
|
63
|
+
tags: ["Organization"]
|
|
64
|
+
}).output(orgBillingSchema);
|
|
65
|
+
const noteSchema = z.object({
|
|
66
|
+
/** The note as plain text, one line per block. Null until the room first saves. */
|
|
67
|
+
text: z.string().nullable(),
|
|
68
|
+
words: z.number().int(),
|
|
69
|
+
savedAt: z.iso.datetime().nullable()
|
|
70
|
+
});
|
|
71
|
+
const getNote = oc.errors(authErrors).route({
|
|
72
|
+
method: "GET",
|
|
73
|
+
path: "/org/notes",
|
|
74
|
+
summary: "Shared note",
|
|
75
|
+
description: "The organization's collaborative note as last saved by the realtime room. It trails live editing by a few seconds, so `savedAt` says which version this is.",
|
|
76
|
+
tags: ["Organization"]
|
|
77
|
+
}).output(noteSchema);
|
|
78
|
+
const webhookEventTypeSchema = z.enum([
|
|
79
|
+
"content.changed",
|
|
80
|
+
"member.joined",
|
|
81
|
+
"invitation.created",
|
|
82
|
+
"subscription.started",
|
|
83
|
+
"subscription.canceled",
|
|
84
|
+
"ping"
|
|
85
|
+
]);
|
|
86
|
+
const webhookEndpointSchema = z.object({
|
|
87
|
+
id: z.string(),
|
|
88
|
+
url: z.string(),
|
|
89
|
+
events: z.array(webhookEventTypeSchema),
|
|
90
|
+
enabled: z.boolean(),
|
|
91
|
+
createdAt: z.iso.datetime()
|
|
92
|
+
});
|
|
93
|
+
const webhookEndpointWithSecretSchema = webhookEndpointSchema.extend({ secret: z.string() });
|
|
94
|
+
const webhookDeliverySchema = z.object({
|
|
95
|
+
id: z.string(),
|
|
96
|
+
eventId: z.string(),
|
|
97
|
+
eventType: z.string(),
|
|
98
|
+
status: z.enum(["success", "failed"]),
|
|
99
|
+
responseStatus: z.number().int().nullable(),
|
|
100
|
+
attempts: z.number().int(),
|
|
101
|
+
createdAt: z.iso.datetime()
|
|
102
|
+
});
|
|
103
|
+
const webhookNotFound = { NOT_FOUND: { message: "No webhook endpoint with that id in this organization." } };
|
|
104
|
+
const listWebhooks = oc.errors(authErrors).route({
|
|
105
|
+
method: "GET",
|
|
106
|
+
path: "/webhooks",
|
|
107
|
+
summary: "List webhook endpoints",
|
|
108
|
+
description: "The organization's webhook endpoints, without their signing secrets.",
|
|
109
|
+
tags: ["Webhooks"]
|
|
110
|
+
}).output(z.array(webhookEndpointSchema));
|
|
111
|
+
const createWebhook = oc.errors({
|
|
112
|
+
...authErrors,
|
|
113
|
+
BAD_REQUEST: { message: "Webhook URLs must be https (plain http is allowed for localhost)." }
|
|
114
|
+
}).route({
|
|
115
|
+
method: "POST",
|
|
116
|
+
path: "/webhooks",
|
|
117
|
+
summary: "Create a webhook endpoint",
|
|
118
|
+
description: "Registers an endpoint for the given event types. The response includes the signing secret; verify deliveries with any Standard Webhooks library.",
|
|
119
|
+
tags: ["Webhooks"]
|
|
120
|
+
}).input(z.object({
|
|
121
|
+
url: z.url(),
|
|
122
|
+
events: z.array(webhookEventTypeSchema).min(1)
|
|
123
|
+
})).output(webhookEndpointWithSecretSchema);
|
|
124
|
+
const deleteWebhook = oc.errors({
|
|
125
|
+
...authErrors,
|
|
126
|
+
...webhookNotFound
|
|
127
|
+
}).route({
|
|
128
|
+
method: "DELETE",
|
|
129
|
+
path: "/webhooks/{id}",
|
|
130
|
+
summary: "Delete a webhook endpoint",
|
|
131
|
+
description: "Removes the endpoint and its delivery history.",
|
|
132
|
+
tags: ["Webhooks"]
|
|
133
|
+
}).input(z.object({ id: z.string() })).output(z.object({ id: z.string() }));
|
|
134
|
+
const rotateWebhookSecret = oc.errors({
|
|
135
|
+
...authErrors,
|
|
136
|
+
...webhookNotFound
|
|
137
|
+
}).route({
|
|
138
|
+
method: "POST",
|
|
139
|
+
path: "/webhooks/{id}/rotate-secret",
|
|
140
|
+
summary: "Rotate a webhook signing secret",
|
|
141
|
+
description: "Issues a new signing secret. The previous secret keeps signing deliveries for 24 hours so receivers can roll over without downtime.",
|
|
142
|
+
tags: ["Webhooks"]
|
|
143
|
+
}).input(z.object({ id: z.string() })).output(webhookEndpointWithSecretSchema);
|
|
144
|
+
const listWebhookDeliveries = oc.errors({
|
|
145
|
+
...authErrors,
|
|
146
|
+
...webhookNotFound
|
|
147
|
+
}).route({
|
|
148
|
+
method: "GET",
|
|
149
|
+
path: "/webhooks/{id}/deliveries",
|
|
150
|
+
summary: "List recent deliveries",
|
|
151
|
+
description: "The most recent delivery attempts for the endpoint, newest first.",
|
|
152
|
+
tags: ["Webhooks"]
|
|
153
|
+
}).input(z.object({ id: z.string() })).output(z.array(webhookDeliverySchema));
|
|
154
|
+
const contentFieldTypeSchema = z.enum([
|
|
155
|
+
"text",
|
|
156
|
+
"rich_text",
|
|
157
|
+
"number",
|
|
158
|
+
"checkbox",
|
|
159
|
+
"date",
|
|
160
|
+
"select",
|
|
161
|
+
"multi_select",
|
|
162
|
+
"link",
|
|
163
|
+
"person",
|
|
164
|
+
"asset",
|
|
165
|
+
"reference",
|
|
166
|
+
"multi_reference"
|
|
167
|
+
]);
|
|
168
|
+
const contentDeprecationSchema = z.object({
|
|
169
|
+
/** When the field was deleted from the model. */
|
|
170
|
+
at: z.iso.datetime(),
|
|
171
|
+
/** Who deleted it; absent when no signed-in user did, or the account is gone. */
|
|
172
|
+
by: z.string().optional()
|
|
173
|
+
});
|
|
174
|
+
const contentFieldSchema = z.object({
|
|
175
|
+
key: z.string(),
|
|
176
|
+
name: z.string(),
|
|
177
|
+
type: contentFieldTypeSchema,
|
|
178
|
+
/** Choices of a select or multi-select field; empty for other types. */
|
|
179
|
+
options: z.array(z.object({
|
|
180
|
+
name: z.string(),
|
|
181
|
+
color: z.string()
|
|
182
|
+
})),
|
|
183
|
+
/** Slug of the collection a reference or multi-reference field points at. */
|
|
184
|
+
collection: z.string().optional(),
|
|
185
|
+
/**
|
|
186
|
+
* Present once the field has been deleted from the model, naming when and
|
|
187
|
+
* by whom so a stale key can be traced back to the change that retired it.
|
|
188
|
+
* Editors stop seeing the field, but entries keep the last value it held
|
|
189
|
+
* and go on returning it, so a deletion costs a running site nothing. A
|
|
190
|
+
* generated client turns it into a deprecation rather than dropping the
|
|
191
|
+
* key, so code reading it keeps compiling. The key and its values go for
|
|
192
|
+
* good only when a developer purges the field from the Danger Zone, after
|
|
193
|
+
* which it leaves this list.
|
|
194
|
+
*/
|
|
195
|
+
deprecated: contentDeprecationSchema.optional()
|
|
196
|
+
});
|
|
197
|
+
const contentTypeSchema = z.object({
|
|
198
|
+
slug: z.string(),
|
|
199
|
+
name: z.string(),
|
|
200
|
+
/** A collection holds many entries; a map holds exactly one. */
|
|
201
|
+
kind: z.enum(["collection", "map"]),
|
|
202
|
+
fields: z.array(contentFieldSchema)
|
|
203
|
+
});
|
|
204
|
+
const contentEntrySchema = z.object({
|
|
205
|
+
id: z.string(),
|
|
206
|
+
slug: z.string(),
|
|
207
|
+
title: z.string(),
|
|
208
|
+
fields: z.record(z.string(), z.unknown()),
|
|
209
|
+
createdAt: z.iso.datetime(),
|
|
210
|
+
updatedAt: z.iso.datetime()
|
|
211
|
+
});
|
|
212
|
+
const contentNotFound = { NOT_FOUND: { message: "No content type with that slug in this organization." } };
|
|
213
|
+
const getContentModel = oc.errors(authErrors).route({
|
|
214
|
+
method: "GET",
|
|
215
|
+
path: "/model",
|
|
216
|
+
summary: "Content model",
|
|
217
|
+
description: "Every collection and map in the organization with its fields. The generated client is typed from this. Deleted fields are still listed, carrying a `deprecated` record of when and by whom, so a client regenerated after a deletion marks the key instead of dropping it and entries go on returning the last value it held.",
|
|
218
|
+
tags: ["Content"]
|
|
219
|
+
}).output(z.object({ types: z.array(contentTypeSchema) }));
|
|
220
|
+
const listContent = oc.errors({
|
|
221
|
+
...authErrors,
|
|
222
|
+
...contentNotFound
|
|
223
|
+
}).route({
|
|
224
|
+
method: "GET",
|
|
225
|
+
path: "/content/{type}",
|
|
226
|
+
summary: "List entries",
|
|
227
|
+
description: "A content type's entries with resolved field values; rich text bodies are markdown as last saved by the realtime room. A map returns its single entry as a one-element list.",
|
|
228
|
+
tags: ["Content"]
|
|
229
|
+
}).input(z.object({ type: z.string() })).output(z.object({
|
|
230
|
+
type: contentTypeSchema,
|
|
231
|
+
entries: z.array(contentEntrySchema)
|
|
232
|
+
}));
|
|
233
|
+
const getContent = oc.errors({
|
|
234
|
+
...authErrors,
|
|
235
|
+
NOT_FOUND: { message: "No such content type or entry in this organization." }
|
|
236
|
+
}).route({
|
|
237
|
+
method: "GET",
|
|
238
|
+
path: "/content/{type}/{slug}",
|
|
239
|
+
summary: "Get one entry",
|
|
240
|
+
description: "One entry of a collection, addressed by its slug.",
|
|
241
|
+
tags: ["Content"]
|
|
242
|
+
}).input(z.object({
|
|
243
|
+
type: z.string(),
|
|
244
|
+
slug: z.string()
|
|
245
|
+
})).output(contentEntrySchema);
|
|
246
|
+
const analyticsMetadataSchema = z.object({
|
|
247
|
+
/** Day-scoped visitor digest computed by the caller. Never an address. */
|
|
248
|
+
visitor: z.string().optional(),
|
|
249
|
+
country: z.string().optional(),
|
|
250
|
+
region: z.string().optional(),
|
|
251
|
+
city: z.string().optional(),
|
|
252
|
+
browser: z.string().optional(),
|
|
253
|
+
os: z.string().optional(),
|
|
254
|
+
device: z.enum([
|
|
255
|
+
"desktop",
|
|
256
|
+
"mobile",
|
|
257
|
+
"tablet"
|
|
258
|
+
]).optional()
|
|
259
|
+
});
|
|
260
|
+
const analyticsErrors = {
|
|
261
|
+
UNAUTHORIZED: authErrors.UNAUTHORIZED,
|
|
262
|
+
...rateLimitError
|
|
263
|
+
};
|
|
264
|
+
const analyticsEventSchema = z.object({
|
|
265
|
+
/** `$pageview`, or an event from the organization's tracking plan. */
|
|
266
|
+
name: z.string().min(1).max(120),
|
|
267
|
+
props: z.record(z.string(), z.unknown()).default({}),
|
|
268
|
+
/** Epoch milliseconds, stamped in the browser when the event happened. */
|
|
269
|
+
timestamp: z.number().int().nonnegative(),
|
|
270
|
+
url: z.string().optional(),
|
|
271
|
+
referrer: z.string().optional()
|
|
272
|
+
});
|
|
273
|
+
const contract = {
|
|
274
|
+
health,
|
|
275
|
+
analytics: {
|
|
276
|
+
ingest: oc.errors(analyticsErrors).route({
|
|
277
|
+
method: "POST",
|
|
278
|
+
path: "/events",
|
|
279
|
+
summary: "Record events",
|
|
280
|
+
description: "Stores a batch of analytics events. Sent by the handler you mounted in your own app, which validates them against your tracking plan first. Query strings are reduced to their campaign parameters on the way in.",
|
|
281
|
+
tags: ["Analytics"]
|
|
282
|
+
}).input(z.object({
|
|
283
|
+
context: z.record(z.string(), z.unknown()).default({}),
|
|
284
|
+
metadata: analyticsMetadataSchema.default({}),
|
|
285
|
+
events: z.array(analyticsEventSchema).min(1).max(100)
|
|
286
|
+
})).output(z.object({ accepted: z.number().int() })),
|
|
287
|
+
sync: oc.errors(analyticsErrors).route({
|
|
288
|
+
method: "PUT",
|
|
289
|
+
path: "/events/schema",
|
|
290
|
+
summary: "Publish the tracking plan",
|
|
291
|
+
description: "Replaces the organization's known events with the ones your code declares, so the dashboard can offer them. Sent by @stetcms/vite and `stet sync`.",
|
|
292
|
+
tags: ["Analytics"]
|
|
293
|
+
}).input(z.object({ events: z.array(z.object({
|
|
294
|
+
name: z.string().min(1),
|
|
295
|
+
props: z.array(z.string())
|
|
296
|
+
})) })).output(z.object({ synced: z.number().int() }))
|
|
297
|
+
},
|
|
298
|
+
content: {
|
|
299
|
+
model: getContentModel,
|
|
300
|
+
list: listContent,
|
|
301
|
+
get: getContent
|
|
302
|
+
},
|
|
303
|
+
org: {
|
|
304
|
+
current: getOrg,
|
|
305
|
+
billing: getOrgBilling,
|
|
306
|
+
note: getNote
|
|
307
|
+
},
|
|
308
|
+
webhooks: {
|
|
309
|
+
list: listWebhooks,
|
|
310
|
+
create: createWebhook,
|
|
311
|
+
delete: deleteWebhook,
|
|
312
|
+
rotateSecret: rotateWebhookSecret,
|
|
313
|
+
deliveries: listWebhookDeliveries
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
//#endregion
|
|
317
|
+
//#region src/content.ts
|
|
318
|
+
/** The path the API serves public content assets from. */
|
|
319
|
+
const assetPath = "/assets/";
|
|
320
|
+
/**
|
|
321
|
+
* Joins an asset path to the Stet origin. Anything already absolute is left
|
|
322
|
+
* alone, so a future API that returns whole URLs needs no change here.
|
|
323
|
+
*/
|
|
324
|
+
function assetUrl(url, origin) {
|
|
325
|
+
return url.startsWith(assetPath) ? `${origin}${url}` : url;
|
|
326
|
+
}
|
|
327
|
+
const assetPathInText = /(\]\(|src=["'])(\/assets\/)/g;
|
|
328
|
+
/**
|
|
329
|
+
* The same join applied inside a body's markdown, so an image an editor
|
|
330
|
+
* dropped into a body renders on your site as readily as an asset field does.
|
|
331
|
+
*/
|
|
332
|
+
function resolveAssetPaths(text, origin) {
|
|
333
|
+
return text.replace(assetPathInText, (_match, prefix) => `${prefix}${origin}${assetPath}`);
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Rewrites every asset URL in a response to a whole URL: an asset field's
|
|
337
|
+
* value, and the images an editor dropped into a rich text body.
|
|
338
|
+
*
|
|
339
|
+
* The API returns them relative to itself, because it is the same bytes
|
|
340
|
+
* whichever origin serves them and baking one in would pin stored content to
|
|
341
|
+
* a domain. Your site is a different origin, so a relative path would resolve
|
|
342
|
+
* against yours; the client owns the join because the origin is exactly what
|
|
343
|
+
* it was configured with.
|
|
344
|
+
*/
|
|
345
|
+
function resolveAssetUrls(payload, origin) {
|
|
346
|
+
if (Array.isArray(payload)) return payload.map((item) => resolveAssetUrls(item, origin));
|
|
347
|
+
if (typeof payload !== "object" || payload === null) return payload;
|
|
348
|
+
const entries = Object.entries(payload).map(([key, value]) => {
|
|
349
|
+
if (typeof value === "string") return [key, key === "url" ? assetUrl(value, origin) : resolveAssetPaths(value, origin)];
|
|
350
|
+
return [key, resolveAssetUrls(value, origin)];
|
|
351
|
+
});
|
|
352
|
+
return Object.fromEntries(entries);
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* The model-shaped client `stet.gen.ts` instantiates: `stet.posts.list()`,
|
|
356
|
+
* `stet.posts.get('hello-world')`, `stet.landing.get()`. The type parameter
|
|
357
|
+
* narrows which keys exist and what their entries look like; the runtime is
|
|
358
|
+
* one Proxy over the content API.
|
|
359
|
+
*/
|
|
360
|
+
function createContentClient(options = {}) {
|
|
361
|
+
const origin = options.origin ?? DEFAULT_ORIGIN;
|
|
362
|
+
const request = async (path) => {
|
|
363
|
+
const response = await (options.fetch ?? globalThis.fetch)(`${origin}/api/v1${path}`, { headers: options.apiKey === void 0 ? {} : { "x-api-key": options.apiKey } });
|
|
364
|
+
if (!response.ok) throw new Error(`Stet request ${path} failed with status ${response.status}.`);
|
|
365
|
+
return resolveAssetUrls(await response.json(), origin);
|
|
366
|
+
};
|
|
367
|
+
const listEntries = async (slug) => {
|
|
368
|
+
return (await request(`/content/${encodeURIComponent(slug)}`)).entries;
|
|
369
|
+
};
|
|
370
|
+
const clientFor = (slug) => ({
|
|
371
|
+
list: () => listEntries(slug),
|
|
372
|
+
get: async (entrySlug) => {
|
|
373
|
+
if (entrySlug !== void 0) return request(`/content/${encodeURIComponent(slug)}/${encodeURIComponent(entrySlug)}`);
|
|
374
|
+
const [entry] = await listEntries(slug);
|
|
375
|
+
if (entry === void 0) throw new Error(`Map "${slug}" has no entry.`);
|
|
376
|
+
return entry;
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
return new Proxy({}, { get: (_target, property) => {
|
|
380
|
+
if (typeof property !== "string" || property === "then") return;
|
|
381
|
+
return clientFor(property);
|
|
382
|
+
} });
|
|
383
|
+
}
|
|
384
|
+
//#endregion
|
|
385
|
+
//#region src/index.ts
|
|
386
|
+
function createStetClient(options = {}) {
|
|
387
|
+
return createORPCClient(new OpenAPILink(contract, {
|
|
388
|
+
url: `${options.origin ?? DEFAULT_ORIGIN}/api/v1`,
|
|
389
|
+
headers: () => {
|
|
390
|
+
if (options.apiKey === void 0) return {};
|
|
391
|
+
return { "x-api-key": options.apiKey };
|
|
392
|
+
},
|
|
393
|
+
fetch: options.fetch
|
|
394
|
+
}));
|
|
395
|
+
}
|
|
396
|
+
//#endregion
|
|
397
|
+
export { DEFAULT_ORIGIN, assetUrl, createContentClient, createStetClient, entryTypeName, fetchContentModel, isDefinedError, renderContentModule, resolveAssetPaths, safe };
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@stetcms/client",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Typed client for the Stet API, generated from the oRPC contract.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"api",
|
|
7
|
+
"client",
|
|
8
|
+
"orpc",
|
|
9
|
+
"stet"
|
|
10
|
+
],
|
|
11
|
+
"homepage": "https://github.com/jamiedavenport/stet/tree/main/published/client#readme",
|
|
12
|
+
"bugs": "https://github.com/jamiedavenport/stet/issues",
|
|
13
|
+
"license": "Apache-2.0",
|
|
14
|
+
"author": "Jamie Davenport (https://jxd.dev)",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/jamiedavenport/stet.git",
|
|
18
|
+
"directory": "published/client"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
"type": "module",
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"default": "./dist/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./codegen": {
|
|
30
|
+
"types": "./dist/codegen.d.ts",
|
|
31
|
+
"default": "./dist/codegen.js"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@orpc/client": "^1.14.8",
|
|
39
|
+
"@orpc/contract": "^1.14.8",
|
|
40
|
+
"@orpc/openapi-client": "^1.14.8",
|
|
41
|
+
"zod": "^4.4.3",
|
|
42
|
+
"@stetcms/config": "0.1.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"publint": "^0.3.21",
|
|
46
|
+
"typescript": "^5.9.3",
|
|
47
|
+
"vite-plus": "0.2.2",
|
|
48
|
+
"vitest": "^4.1.9",
|
|
49
|
+
"@repo/api": "0.0.0"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "vp pack",
|
|
53
|
+
"tc": "tsc --noEmit",
|
|
54
|
+
"test": "vp test run"
|
|
55
|
+
}
|
|
56
|
+
}
|