@prosewire/sdk 0.2.0 → 0.4.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 +41 -0
- package/README.md +60 -5
- package/dist/import-node.d.mts +6 -0
- package/dist/import-node.d.mts.map +1 -0
- package/dist/import-node.mjs +49 -0
- package/dist/import-node.mjs.map +1 -0
- package/dist/index-BFz3f-oC.d.mts +1401 -0
- package/dist/index-BFz3f-oC.d.mts.map +1 -0
- package/dist/index.d.mts +2 -656
- package/dist/index.mjs +397 -63
- package/dist/index.mjs.map +1 -1
- package/package.json +15 -5
- package/dist/index.d.mts.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,10 +1,47 @@
|
|
|
1
1
|
import { Effect, Schema } from "effect";
|
|
2
2
|
import { HttpApi, HttpApiClient, HttpApiEndpoint, HttpApiGroup, HttpApiSchema, OpenApi } from "effect/unstable/httpapi";
|
|
3
3
|
import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/http";
|
|
4
|
+
//#region ../contract/src/import.ts
|
|
5
|
+
/** Imports use a streaming body rather than the buffered JSON HttpApi adapter. */
|
|
6
|
+
const portableImportPath = "/api/v1/import";
|
|
7
|
+
const portableImportReport = Schema.Struct({
|
|
8
|
+
mode: Schema.Literals(["preview", "apply"]),
|
|
9
|
+
fingerprint: Schema.String,
|
|
10
|
+
preserveStates: Schema.Boolean,
|
|
11
|
+
counts: Schema.Record(Schema.String, Schema.Int),
|
|
12
|
+
alreadyImported: Schema.Boolean
|
|
13
|
+
});
|
|
14
|
+
Schema.TaggedError()("ApiImportRejected", {
|
|
15
|
+
item: Schema.String,
|
|
16
|
+
message: Schema.String
|
|
17
|
+
}, { httpApiStatus: 400 });
|
|
18
|
+
Schema.TaggedError()("ApiImportConflict", {
|
|
19
|
+
item: Schema.String,
|
|
20
|
+
message: Schema.String
|
|
21
|
+
}, { httpApiStatus: 409 });
|
|
22
|
+
//#endregion
|
|
4
23
|
//#region ../contract/src/schemas.ts
|
|
5
24
|
const withDefault = (schema, value) => Schema.withDecodingDefaultKey(Effect.succeed(value))(schema);
|
|
6
|
-
const uuid
|
|
7
|
-
const isoDateTime = Schema.String.check(Schema.isPattern(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d
|
|
25
|
+
const uuid = Schema.String.check(Schema.isUUID());
|
|
26
|
+
const isoDateTime = Schema.String.check(Schema.isPattern(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-](?:[01]\d|2[0-3]):[0-5]\d)$/), Schema.makeFilter((value) => {
|
|
27
|
+
const [year, month, day, hour, minute, second] = value.slice(0, 19).split(/[-T:]/).map(Number);
|
|
28
|
+
if (year === void 0 || month === void 0 || day === void 0 || hour === void 0 || minute === void 0 || second === void 0) return false;
|
|
29
|
+
const days = [
|
|
30
|
+
31,
|
|
31
|
+
year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0) ? 29 : 28,
|
|
32
|
+
31,
|
|
33
|
+
30,
|
|
34
|
+
31,
|
|
35
|
+
30,
|
|
36
|
+
31,
|
|
37
|
+
31,
|
|
38
|
+
30,
|
|
39
|
+
31,
|
|
40
|
+
30,
|
|
41
|
+
31
|
|
42
|
+
];
|
|
43
|
+
return month >= 1 && month <= 12 && day >= 1 && day <= (days[month - 1] ?? 0) && hour < 24 && minute < 60 && second < 60;
|
|
44
|
+
}, { expected: "a valid ISO calendar date and time" }));
|
|
8
45
|
const url = Schema.String.check(Schema.isPattern(/^[a-z][a-z\d+.-]*:\/\/\S+$/i));
|
|
9
46
|
const nullable = (schema) => Schema.NullOr(schema);
|
|
10
47
|
const postStatus = Schema.Literals([
|
|
@@ -14,7 +51,7 @@ const postStatus = Schema.Literals([
|
|
|
14
51
|
"archived"
|
|
15
52
|
]);
|
|
16
53
|
const authorOutput = Schema.Struct({
|
|
17
|
-
id: uuid
|
|
54
|
+
id: uuid,
|
|
18
55
|
name: Schema.String,
|
|
19
56
|
slug: Schema.String,
|
|
20
57
|
bio: nullable(Schema.String),
|
|
@@ -23,19 +60,20 @@ const authorOutput = Schema.Struct({
|
|
|
23
60
|
credentials: nullable(Schema.String)
|
|
24
61
|
});
|
|
25
62
|
const categoryOutput = Schema.Struct({
|
|
26
|
-
id: uuid
|
|
63
|
+
id: uuid,
|
|
27
64
|
name: Schema.String,
|
|
28
65
|
slug: Schema.String,
|
|
29
66
|
description: nullable(Schema.String)
|
|
30
67
|
});
|
|
31
68
|
const postOutput = Schema.Struct({
|
|
32
|
-
id: uuid
|
|
33
|
-
blogId: uuid
|
|
69
|
+
id: uuid,
|
|
70
|
+
blogId: uuid,
|
|
34
71
|
title: Schema.String,
|
|
35
72
|
slug: Schema.String,
|
|
36
73
|
excerpt: Schema.String,
|
|
37
74
|
contentMarkdown: Schema.String,
|
|
38
75
|
contentHtml: Schema.String,
|
|
76
|
+
coverImageAssetId: nullable(uuid),
|
|
39
77
|
coverImageUrl: nullable(Schema.String),
|
|
40
78
|
coverImageAlt: nullable(Schema.String),
|
|
41
79
|
status: postStatus,
|
|
@@ -52,11 +90,41 @@ const postOutput = Schema.Struct({
|
|
|
52
90
|
author: authorOutput,
|
|
53
91
|
categories: Schema.Array(categoryOutput)
|
|
54
92
|
});
|
|
55
|
-
const
|
|
93
|
+
const postRevisionSnapshot = Schema.Struct({
|
|
94
|
+
authorId: uuid,
|
|
95
|
+
title: Schema.String,
|
|
96
|
+
slug: Schema.String,
|
|
97
|
+
excerpt: Schema.String,
|
|
98
|
+
contentMarkdown: Schema.String,
|
|
99
|
+
coverImageAssetId: Schema.optionalKey(nullable(uuid)),
|
|
100
|
+
coverImageUrl: nullable(Schema.String),
|
|
101
|
+
coverImageAlt: nullable(Schema.String),
|
|
102
|
+
status: postStatus,
|
|
103
|
+
locale: Schema.String,
|
|
104
|
+
featured: Schema.Boolean,
|
|
105
|
+
seoTitle: nullable(Schema.String),
|
|
106
|
+
seoDescription: nullable(Schema.String),
|
|
107
|
+
focusKeyword: nullable(Schema.String),
|
|
108
|
+
canonicalUrl: nullable(Schema.String),
|
|
109
|
+
scheduledAt: nullable(isoDateTime),
|
|
110
|
+
publishedAt: nullable(isoDateTime),
|
|
111
|
+
archivedAt: nullable(isoDateTime),
|
|
112
|
+
categoryIds: nullable(Schema.Array(uuid))
|
|
113
|
+
});
|
|
114
|
+
const postRevisionOutput = Schema.Struct({
|
|
115
|
+
id: uuid,
|
|
116
|
+
postId: uuid,
|
|
117
|
+
editorId: nullable(Schema.String),
|
|
118
|
+
version: Schema.Int,
|
|
119
|
+
snapshot: postRevisionSnapshot,
|
|
120
|
+
createdAt: isoDateTime
|
|
121
|
+
});
|
|
122
|
+
const authorId = uuid;
|
|
56
123
|
const title = Schema.Trim.pipe(Schema.check(Schema.isMinLength(1), Schema.isMaxLength(180)));
|
|
57
124
|
const slug = Schema.Trim.pipe(Schema.check(Schema.isMinLength(1), Schema.isMaxLength(120), Schema.isPattern(/^[a-z0-9]+(?:-[a-z0-9]+)*$/)));
|
|
58
125
|
const excerpt = Schema.String.check(Schema.isMaxLength(500));
|
|
59
126
|
const contentMarkdown = Schema.String;
|
|
127
|
+
const coverImageAssetId = nullable(uuid);
|
|
60
128
|
const coverImageUrl = nullable(url);
|
|
61
129
|
const coverImageAlt = nullable(Schema.String.check(Schema.isMaxLength(180)));
|
|
62
130
|
const locale = Schema.String.check(Schema.isMinLength(2), Schema.isMaxLength(10));
|
|
@@ -66,18 +134,19 @@ const seoDescription = nullable(Schema.String.check(Schema.isMaxLength(180)));
|
|
|
66
134
|
const focusKeyword = nullable(Schema.String.check(Schema.isMaxLength(120)));
|
|
67
135
|
const canonicalUrl = nullable(url);
|
|
68
136
|
const scheduledAt = nullable(isoDateTime);
|
|
69
|
-
const categoryIds = Schema.Array(uuid
|
|
137
|
+
const categoryIds = Schema.Array(uuid);
|
|
70
138
|
const postCreateInput = Schema.Struct({
|
|
71
|
-
blogId: uuid
|
|
139
|
+
blogId: uuid,
|
|
72
140
|
authorId,
|
|
73
141
|
title,
|
|
74
142
|
slug,
|
|
75
143
|
excerpt: Schema.optionalKey(excerpt),
|
|
76
144
|
contentMarkdown: withDefault(contentMarkdown, ""),
|
|
145
|
+
coverImageAssetId: Schema.optionalKey(coverImageAssetId),
|
|
77
146
|
coverImageUrl: Schema.optionalKey(coverImageUrl),
|
|
78
147
|
coverImageAlt: Schema.optionalKey(coverImageAlt),
|
|
79
148
|
status: withDefault(postStatus, "draft"),
|
|
80
|
-
locale:
|
|
149
|
+
locale: Schema.optionalKey(locale),
|
|
81
150
|
featured: withDefault(featured, false),
|
|
82
151
|
seoTitle: Schema.optionalKey(seoTitle),
|
|
83
152
|
seoDescription: Schema.optionalKey(seoDescription),
|
|
@@ -92,6 +161,7 @@ const postUpdateInput = Schema.Struct({
|
|
|
92
161
|
slug: Schema.optionalKey(slug),
|
|
93
162
|
excerpt: Schema.optionalKey(excerpt),
|
|
94
163
|
contentMarkdown: Schema.optionalKey(contentMarkdown),
|
|
164
|
+
coverImageAssetId: Schema.optionalKey(coverImageAssetId),
|
|
95
165
|
coverImageUrl: Schema.optionalKey(coverImageUrl),
|
|
96
166
|
coverImageAlt: Schema.optionalKey(coverImageAlt),
|
|
97
167
|
status: Schema.optionalKey(postStatus),
|
|
@@ -105,11 +175,12 @@ const postUpdateInput = Schema.Struct({
|
|
|
105
175
|
categoryIds: Schema.optionalKey(categoryIds)
|
|
106
176
|
});
|
|
107
177
|
const blogOutput = Schema.Struct({
|
|
108
|
-
id: uuid
|
|
178
|
+
id: uuid,
|
|
109
179
|
name: Schema.String,
|
|
110
180
|
slug: Schema.String,
|
|
111
181
|
description: Schema.String,
|
|
112
182
|
locale: Schema.String,
|
|
183
|
+
locales: Schema.Array(Schema.String),
|
|
113
184
|
accentColor: Schema.String,
|
|
114
185
|
customCss: Schema.String,
|
|
115
186
|
publicUrl: nullable(Schema.String),
|
|
@@ -117,7 +188,7 @@ const blogOutput = Schema.Struct({
|
|
|
117
188
|
updatedAt: isoDateTime
|
|
118
189
|
});
|
|
119
190
|
const publicBlogOutput = Schema.Struct({
|
|
120
|
-
id: uuid
|
|
191
|
+
id: uuid,
|
|
121
192
|
name: Schema.String,
|
|
122
193
|
slug: Schema.String,
|
|
123
194
|
description: Schema.String,
|
|
@@ -128,7 +199,7 @@ const publicBlogOutput = Schema.Struct({
|
|
|
128
199
|
updatedAt: isoDateTime
|
|
129
200
|
});
|
|
130
201
|
const publicPostOutput = Schema.Struct({
|
|
131
|
-
id: uuid
|
|
202
|
+
id: uuid,
|
|
132
203
|
slug: Schema.String,
|
|
133
204
|
title: Schema.String,
|
|
134
205
|
excerpt: Schema.String,
|
|
@@ -141,7 +212,7 @@ const publicPostOutput = Schema.Struct({
|
|
|
141
212
|
featured: Schema.Boolean,
|
|
142
213
|
publishedAt: nullable(isoDateTime),
|
|
143
214
|
updatedAt: isoDateTime,
|
|
144
|
-
readingMinutes: Schema.
|
|
215
|
+
readingMinutes: Schema.Finite,
|
|
145
216
|
seoTitle: nullable(Schema.String),
|
|
146
217
|
seoDescription: nullable(Schema.String),
|
|
147
218
|
canonicalUrl: nullable(Schema.String),
|
|
@@ -162,80 +233,239 @@ const publicPostResult = Schema.Struct({
|
|
|
162
233
|
blog: publicBlogOutput,
|
|
163
234
|
post: publicPostOutput
|
|
164
235
|
});
|
|
236
|
+
const publicRedirectOutput = Schema.Struct({
|
|
237
|
+
fromPath: Schema.String,
|
|
238
|
+
toPath: Schema.String,
|
|
239
|
+
statusCode: Schema.Literals([
|
|
240
|
+
301,
|
|
241
|
+
302,
|
|
242
|
+
307,
|
|
243
|
+
308
|
|
244
|
+
])
|
|
245
|
+
});
|
|
165
246
|
const paginatedPosts = Schema.Struct({
|
|
166
247
|
items: Schema.Array(postOutput),
|
|
167
248
|
total: Schema.Int,
|
|
168
249
|
page: Schema.Int,
|
|
169
250
|
pageSize: Schema.Int
|
|
170
251
|
});
|
|
252
|
+
const mediaAssetStatus = Schema.Literals([
|
|
253
|
+
"pending",
|
|
254
|
+
"processing",
|
|
255
|
+
"ready",
|
|
256
|
+
"failed",
|
|
257
|
+
"deleted"
|
|
258
|
+
]);
|
|
259
|
+
const mediaVariantOutput = Schema.Struct({
|
|
260
|
+
kind: Schema.Literals([
|
|
261
|
+
"original",
|
|
262
|
+
"large",
|
|
263
|
+
"thumbnail"
|
|
264
|
+
]),
|
|
265
|
+
url,
|
|
266
|
+
mimeType: Schema.String,
|
|
267
|
+
byteSize: Schema.Int,
|
|
268
|
+
width: Schema.Int,
|
|
269
|
+
height: Schema.Int,
|
|
270
|
+
checksumSha256: Schema.String
|
|
271
|
+
});
|
|
272
|
+
const mediaReferenceOutput = Schema.Struct({
|
|
273
|
+
postId: uuid,
|
|
274
|
+
title: Schema.String,
|
|
275
|
+
slug: Schema.String
|
|
276
|
+
});
|
|
277
|
+
const mediaAssetOutput = Schema.Struct({
|
|
278
|
+
id: uuid,
|
|
279
|
+
blogId: uuid,
|
|
280
|
+
filename: Schema.String,
|
|
281
|
+
mimeType: Schema.String,
|
|
282
|
+
byteSize: Schema.Int,
|
|
283
|
+
storageBytes: Schema.Int,
|
|
284
|
+
width: nullable(Schema.Int),
|
|
285
|
+
height: nullable(Schema.Int),
|
|
286
|
+
checksumSha256: nullable(Schema.String),
|
|
287
|
+
status: mediaAssetStatus,
|
|
288
|
+
url: nullable(url),
|
|
289
|
+
variants: Schema.Array(mediaVariantOutput),
|
|
290
|
+
references: Schema.Array(mediaReferenceOutput),
|
|
291
|
+
uploadedAt: nullable(isoDateTime),
|
|
292
|
+
createdAt: isoDateTime,
|
|
293
|
+
updatedAt: isoDateTime
|
|
294
|
+
});
|
|
295
|
+
const mediaUsageOutput = Schema.Struct({
|
|
296
|
+
usedBytes: Schema.Int,
|
|
297
|
+
quotaBytes: Schema.Int,
|
|
298
|
+
remainingBytes: Schema.Int
|
|
299
|
+
});
|
|
300
|
+
const mediaListOutput = Schema.Struct({
|
|
301
|
+
items: Schema.Array(mediaAssetOutput),
|
|
302
|
+
usage: mediaUsageOutput,
|
|
303
|
+
configured: Schema.Boolean,
|
|
304
|
+
maxUploadBytes: Schema.Int
|
|
305
|
+
});
|
|
306
|
+
const mediaStartUploadInput = Schema.Struct({
|
|
307
|
+
blogId: uuid,
|
|
308
|
+
filename: Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(255)),
|
|
309
|
+
mimeType: Schema.String,
|
|
310
|
+
byteSize: Schema.Int.check(Schema.isGreaterThan(0))
|
|
311
|
+
});
|
|
312
|
+
const mediaUploadReservationOutput = Schema.Struct({
|
|
313
|
+
asset: mediaAssetOutput,
|
|
314
|
+
upload: Schema.Struct({
|
|
315
|
+
url,
|
|
316
|
+
method: Schema.Literal("PUT"),
|
|
317
|
+
headers: Schema.Record(Schema.String, Schema.String),
|
|
318
|
+
expiresAt: isoDateTime
|
|
319
|
+
}),
|
|
320
|
+
usage: mediaUsageOutput
|
|
321
|
+
});
|
|
171
322
|
//#endregion
|
|
172
323
|
//#region ../contract/src/router.ts
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
324
|
+
const apiErrorStatusByTag = {
|
|
325
|
+
ApiInputRejected: 400,
|
|
326
|
+
ApiPostConflict: 409,
|
|
327
|
+
ApiAuthenticationFailed: 401,
|
|
328
|
+
ApiAccessDenied: 403,
|
|
329
|
+
ApiPostNotFound: 404,
|
|
330
|
+
ApiRevisionNotFound: 404,
|
|
331
|
+
ApiMediaNotFound: 404,
|
|
332
|
+
ApiMediaConflict: 409,
|
|
333
|
+
ApiMediaTooLarge: 413,
|
|
334
|
+
ApiMediaUnavailable: 503,
|
|
335
|
+
ApiUnavailable: 500
|
|
336
|
+
};
|
|
337
|
+
const privateApiPaths = {
|
|
338
|
+
prefix: "/api/v1",
|
|
339
|
+
health: "/health",
|
|
340
|
+
blogs: "/blogs",
|
|
341
|
+
posts: "/posts",
|
|
342
|
+
media: "/media"
|
|
343
|
+
};
|
|
344
|
+
var ApiPostConflict = class extends Schema.TaggedError()("ApiPostConflict", { message: Schema.String }, { httpApiStatus: apiErrorStatusByTag.ApiPostConflict }) {};
|
|
345
|
+
var ApiInputRejected = class extends Schema.TaggedError()("ApiInputRejected", { message: Schema.String }, { httpApiStatus: apiErrorStatusByTag.ApiInputRejected }) {};
|
|
346
|
+
var ApiAuthenticationFailed = class extends Schema.TaggedError()("ApiAuthenticationFailed", { message: Schema.String }, { httpApiStatus: apiErrorStatusByTag.ApiAuthenticationFailed }) {};
|
|
347
|
+
var ApiAccessDenied = class extends Schema.TaggedError()("ApiAccessDenied", { message: Schema.String }, { httpApiStatus: apiErrorStatusByTag.ApiAccessDenied }) {};
|
|
348
|
+
var ApiPostNotFound = class extends Schema.TaggedError()("ApiPostNotFound", { message: Schema.String }, { httpApiStatus: apiErrorStatusByTag.ApiPostNotFound }) {};
|
|
349
|
+
var ApiRevisionNotFound = class extends Schema.TaggedError()("ApiRevisionNotFound", { message: Schema.String }, { httpApiStatus: apiErrorStatusByTag.ApiRevisionNotFound }) {};
|
|
350
|
+
var ApiMediaNotFound = class extends Schema.TaggedError()("ApiMediaNotFound", { message: Schema.String }, { httpApiStatus: apiErrorStatusByTag.ApiMediaNotFound }) {};
|
|
351
|
+
var ApiMediaConflict = class extends Schema.TaggedError()("ApiMediaConflict", { message: Schema.String }, { httpApiStatus: apiErrorStatusByTag.ApiMediaConflict }) {};
|
|
352
|
+
var ApiMediaTooLarge = class extends Schema.TaggedError()("ApiMediaTooLarge", { message: Schema.String }, { httpApiStatus: apiErrorStatusByTag.ApiMediaTooLarge }) {};
|
|
353
|
+
var ApiMediaUnavailable = class extends Schema.TaggedError()("ApiMediaUnavailable", { message: Schema.String }, { httpApiStatus: apiErrorStatusByTag.ApiMediaUnavailable }) {};
|
|
354
|
+
var ApiUnavailable = class extends Schema.TaggedError()("ApiUnavailable", { message: Schema.String }, { httpApiStatus: apiErrorStatusByTag.ApiUnavailable }) {};
|
|
178
355
|
const apiErrors = [
|
|
356
|
+
ApiPostConflict,
|
|
179
357
|
ApiInputRejected,
|
|
180
358
|
ApiAuthenticationFailed,
|
|
181
359
|
ApiAccessDenied,
|
|
182
360
|
ApiPostNotFound,
|
|
361
|
+
ApiRevisionNotFound,
|
|
362
|
+
ApiMediaNotFound,
|
|
363
|
+
ApiMediaConflict,
|
|
364
|
+
ApiMediaTooLarge,
|
|
365
|
+
ApiMediaUnavailable,
|
|
183
366
|
ApiUnavailable
|
|
184
367
|
];
|
|
185
|
-
const
|
|
186
|
-
const
|
|
187
|
-
const
|
|
188
|
-
const
|
|
368
|
+
const privateApiPostId = Schema.String.check(Schema.isUUID());
|
|
369
|
+
const privateApiRevisionId = Schema.String.check(Schema.isUUID());
|
|
370
|
+
const privateApiMediaId = Schema.String.check(Schema.isUUID());
|
|
371
|
+
const privateApiPageNumber = Schema.FiniteFromString.pipe(Schema.check(Schema.isInt(), Schema.isGreaterThanOrEqualTo(1)));
|
|
372
|
+
const privateApiPageSize = Schema.FiniteFromString.pipe(Schema.check(Schema.isInt(), Schema.isGreaterThanOrEqualTo(1), Schema.isLessThanOrEqualTo(100)));
|
|
373
|
+
const privateApiPostListQueryFields = {
|
|
374
|
+
blog: Schema.optionalKey(Schema.String),
|
|
375
|
+
search: Schema.optionalKey(Schema.String),
|
|
376
|
+
status: Schema.optionalKey(postStatus),
|
|
377
|
+
page: Schema.optionalKey(privateApiPageNumber),
|
|
378
|
+
pageSize: Schema.optionalKey(privateApiPageSize)
|
|
379
|
+
};
|
|
380
|
+
Schema.Struct(privateApiPostListQueryFields);
|
|
381
|
+
const health = HttpApiGroup.make("health").add(HttpApiEndpoint.get("check", privateApiPaths.health, {
|
|
189
382
|
success: Schema.Struct({
|
|
190
383
|
status: Schema.Literal("ok"),
|
|
191
384
|
version: Schema.String
|
|
192
385
|
}),
|
|
193
386
|
error: ApiUnavailable
|
|
194
387
|
}).annotate(OpenApi.Summary, "Database readiness probe"));
|
|
195
|
-
const blogs = HttpApiGroup.make("blogs").add(HttpApiEndpoint.get("list",
|
|
388
|
+
const blogs = HttpApiGroup.make("blogs").add(HttpApiEndpoint.get("list", privateApiPaths.blogs, {
|
|
196
389
|
success: Schema.Array(blogOutput),
|
|
197
390
|
error: apiErrors
|
|
198
391
|
}).annotate(OpenApi.Summary, "List blogs"));
|
|
199
|
-
const posts = HttpApiGroup.make("posts").add(HttpApiEndpoint.get("list",
|
|
200
|
-
query:
|
|
201
|
-
blog: Schema.optionalKey(Schema.String),
|
|
202
|
-
search: Schema.optionalKey(Schema.String),
|
|
203
|
-
status: Schema.optionalKey(postStatus),
|
|
204
|
-
page: Schema.optionalKey(pageNumber$1),
|
|
205
|
-
pageSize: Schema.optionalKey(pageSize$1)
|
|
206
|
-
},
|
|
392
|
+
const posts = HttpApiGroup.make("posts").add(HttpApiEndpoint.get("list", privateApiPaths.posts, {
|
|
393
|
+
query: privateApiPostListQueryFields,
|
|
207
394
|
success: paginatedPosts,
|
|
208
395
|
error: apiErrors
|
|
209
|
-
}).annotate(OpenApi.Summary, "List posts")).add(HttpApiEndpoint.get("get",
|
|
210
|
-
params: { id:
|
|
396
|
+
}).annotate(OpenApi.Summary, "List posts")).add(HttpApiEndpoint.get("get", `${privateApiPaths.posts}/:id`, {
|
|
397
|
+
params: { id: privateApiPostId },
|
|
211
398
|
success: postOutput,
|
|
212
399
|
error: apiErrors
|
|
213
|
-
}).annotate(OpenApi.Summary, "Get a post")).add(HttpApiEndpoint.post("create",
|
|
400
|
+
}).annotate(OpenApi.Summary, "Get a post")).add(HttpApiEndpoint.post("create", privateApiPaths.posts, {
|
|
214
401
|
payload: postCreateInput,
|
|
215
402
|
success: postOutput.pipe(HttpApiSchema.status(200)),
|
|
216
403
|
error: apiErrors
|
|
217
|
-
}).annotate(OpenApi.Summary, "Create a post")).add(HttpApiEndpoint.patch("update",
|
|
218
|
-
params: { id:
|
|
404
|
+
}).annotate(OpenApi.Summary, "Create a post")).add(HttpApiEndpoint.patch("update", `${privateApiPaths.posts}/:id`, {
|
|
405
|
+
params: { id: privateApiPostId },
|
|
219
406
|
payload: postUpdateInput,
|
|
220
407
|
success: postOutput,
|
|
221
408
|
error: apiErrors
|
|
222
|
-
}).annotate(OpenApi.Summary, "Update a post")).add(HttpApiEndpoint.delete("archive",
|
|
223
|
-
params: { id:
|
|
409
|
+
}).annotate(OpenApi.Summary, "Update a post")).add(HttpApiEndpoint.delete("archive", `${privateApiPaths.posts}/:id`, {
|
|
410
|
+
params: { id: privateApiPostId },
|
|
224
411
|
success: Schema.Struct({ ok: Schema.Literal(true) }),
|
|
225
412
|
error: apiErrors
|
|
226
|
-
}).annotate(OpenApi.Summary, "Archive a post"))
|
|
227
|
-
|
|
228
|
-
Schema.
|
|
229
|
-
|
|
230
|
-
|
|
413
|
+
}).annotate(OpenApi.Summary, "Archive a post")).add(HttpApiEndpoint.get("listRevisions", `${privateApiPaths.posts}/:id/revisions`, {
|
|
414
|
+
params: { id: privateApiPostId },
|
|
415
|
+
success: Schema.Array(postRevisionOutput),
|
|
416
|
+
error: apiErrors
|
|
417
|
+
}).annotate(OpenApi.Summary, "List post revisions")).add(HttpApiEndpoint.post("restoreRevision", `${privateApiPaths.posts}/:id/revisions/:revisionId/restore`, {
|
|
418
|
+
params: {
|
|
419
|
+
id: privateApiPostId,
|
|
420
|
+
revisionId: privateApiRevisionId
|
|
421
|
+
},
|
|
422
|
+
success: postOutput,
|
|
423
|
+
error: apiErrors
|
|
424
|
+
}).annotate(OpenApi.Summary, "Restore a post revision"));
|
|
425
|
+
const media = HttpApiGroup.make("media").add(HttpApiEndpoint.get("list", privateApiPaths.media, {
|
|
426
|
+
success: mediaListOutput,
|
|
427
|
+
error: apiErrors
|
|
428
|
+
}).annotate(OpenApi.Summary, "List media assets and storage usage")).add(HttpApiEndpoint.get("get", `${privateApiPaths.media}/:id`, {
|
|
429
|
+
params: { id: privateApiMediaId },
|
|
430
|
+
success: mediaAssetOutput,
|
|
431
|
+
error: apiErrors
|
|
432
|
+
}).annotate(OpenApi.Summary, "Get a media asset")).add(HttpApiEndpoint.post("startUpload", `${privateApiPaths.media}/uploads`, {
|
|
433
|
+
payload: mediaStartUploadInput,
|
|
434
|
+
success: mediaUploadReservationOutput.pipe(HttpApiSchema.status(201)),
|
|
435
|
+
error: apiErrors
|
|
436
|
+
}).annotate(OpenApi.Summary, "Reserve storage and sign a direct upload")).add(HttpApiEndpoint.post("completeUpload", `${privateApiPaths.media}/:id/complete`, {
|
|
437
|
+
params: { id: privateApiMediaId },
|
|
438
|
+
success: mediaAssetOutput,
|
|
439
|
+
error: apiErrors
|
|
440
|
+
}).annotate(OpenApi.Summary, "Validate and process an uploaded image")).add(HttpApiEndpoint.delete("delete", `${privateApiPaths.media}/:id`, {
|
|
441
|
+
params: { id: privateApiMediaId },
|
|
442
|
+
success: Schema.Struct({ ok: Schema.Literal(true) }),
|
|
443
|
+
error: apiErrors
|
|
444
|
+
}).annotate(OpenApi.Summary, "Delete an unreferenced media asset"));
|
|
445
|
+
const api = HttpApi.make("ProsewireApi").add(health, blogs, posts, media).prefix(privateApiPaths.prefix).annotate(OpenApi.Title, "Prosewire private API").annotate(OpenApi.Version, "1.0.0");
|
|
446
|
+
//#endregion
|
|
447
|
+
//#region ../contract/src/transport.ts
|
|
448
|
+
const path = (route) => `${privateApiPaths.prefix}${route}`;
|
|
449
|
+
path(privateApiPaths.health);
|
|
450
|
+
path(privateApiPaths.blogs);
|
|
451
|
+
path(privateApiPaths.posts);
|
|
452
|
+
path(privateApiPaths.media);
|
|
231
453
|
//#endregion
|
|
232
454
|
//#region src/index.ts
|
|
455
|
+
var ProsewireImportError = class extends Schema.TaggedError()("ProsewireImportError", {
|
|
456
|
+
status: Schema.Int,
|
|
457
|
+
item: Schema.String,
|
|
458
|
+
reason: Schema.String
|
|
459
|
+
}) {
|
|
460
|
+
get message() {
|
|
461
|
+
return `${this.item}: ${this.reason}`;
|
|
462
|
+
}
|
|
463
|
+
};
|
|
233
464
|
function normalizedCreateInput(input) {
|
|
234
465
|
return {
|
|
235
466
|
...input,
|
|
236
467
|
contentMarkdown: input.contentMarkdown ?? "",
|
|
237
468
|
status: input.status ?? "draft",
|
|
238
|
-
locale: input.locale ?? "en",
|
|
239
469
|
featured: input.featured ?? false,
|
|
240
470
|
categoryIds: input.categoryIds ?? []
|
|
241
471
|
};
|
|
@@ -249,16 +479,61 @@ function createEffectClient(options) {
|
|
|
249
479
|
const provideFetch = (effect) => effect.pipe(Effect.provideService(FetchHttpClient.Fetch, options.fetch ?? globalThis.fetch));
|
|
250
480
|
return {
|
|
251
481
|
health: () => provideFetch(generated.health.check()),
|
|
482
|
+
imports: { run: (input) => Effect.tryPromise({
|
|
483
|
+
try: async (signal) => {
|
|
484
|
+
const query = new URLSearchParams({
|
|
485
|
+
mode: input.mode ?? "preview",
|
|
486
|
+
preserveStates: String(input.preserveStates ?? false)
|
|
487
|
+
});
|
|
488
|
+
const response = await (options.fetch ?? globalThis.fetch)(`${baseUrl}${portableImportPath}?${query}`, {
|
|
489
|
+
method: "POST",
|
|
490
|
+
headers: {
|
|
491
|
+
"content-type": input.contentType,
|
|
492
|
+
...options.apiKey ? { authorization: `Bearer ${options.apiKey}` } : {}
|
|
493
|
+
},
|
|
494
|
+
body: input.body,
|
|
495
|
+
duplex: "half",
|
|
496
|
+
signal
|
|
497
|
+
});
|
|
498
|
+
const body = await response.json();
|
|
499
|
+
if (!response.ok) {
|
|
500
|
+
const failure = Schema.decodeUnknownSync(Schema.Struct({
|
|
501
|
+
item: Schema.optionalKey(Schema.String),
|
|
502
|
+
message: Schema.String
|
|
503
|
+
}))(body);
|
|
504
|
+
throw new ProsewireImportError({
|
|
505
|
+
status: response.status,
|
|
506
|
+
item: failure.item ?? "request",
|
|
507
|
+
reason: failure.message
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
return Schema.decodeUnknownPromise(portableImportReport)(body);
|
|
511
|
+
},
|
|
512
|
+
catch: (cause) => cause instanceof ProsewireImportError ? cause : new ProsewireImportError({
|
|
513
|
+
status: 0,
|
|
514
|
+
item: "request",
|
|
515
|
+
reason: cause instanceof Error ? cause.message : "Publication import failed"
|
|
516
|
+
})
|
|
517
|
+
}) },
|
|
252
518
|
blogs: { list: () => provideFetch(generated.blogs.list()) },
|
|
253
519
|
posts: {
|
|
254
520
|
list: (input = {}) => provideFetch(generated.posts.list({ query: input })),
|
|
255
521
|
get: (input) => provideFetch(generated.posts.get(input)),
|
|
522
|
+
revisions: (input) => provideFetch(generated.posts.listRevisions(input)),
|
|
256
523
|
create: (input) => provideFetch(generated.posts.create({ payload: normalizedCreateInput(input) })),
|
|
257
524
|
update: (input) => provideFetch(generated.posts.update({
|
|
258
525
|
params: input.params,
|
|
259
526
|
payload: input.body
|
|
260
527
|
})),
|
|
261
|
-
archive: (input) => provideFetch(generated.posts.archive(input))
|
|
528
|
+
archive: (input) => provideFetch(generated.posts.archive(input)),
|
|
529
|
+
restore: (input) => provideFetch(generated.posts.restoreRevision(input))
|
|
530
|
+
},
|
|
531
|
+
media: {
|
|
532
|
+
list: () => provideFetch(generated.media.list()),
|
|
533
|
+
get: (input) => provideFetch(generated.media.get(input)),
|
|
534
|
+
startUpload: (input) => provideFetch(generated.media.startUpload({ payload: input })),
|
|
535
|
+
completeUpload: (input) => provideFetch(generated.media.completeUpload(input)),
|
|
536
|
+
delete: (input) => provideFetch(generated.media.delete(input))
|
|
262
537
|
}
|
|
263
538
|
};
|
|
264
539
|
}
|
|
@@ -266,16 +541,36 @@ function createClient(options) {
|
|
|
266
541
|
const client = createEffectClient(options);
|
|
267
542
|
return {
|
|
268
543
|
health: () => Effect.runPromise(client.health()),
|
|
544
|
+
imports: { run: (input) => Effect.runPromise(client.imports.run(input)) },
|
|
269
545
|
blogs: { list: () => Effect.runPromise(client.blogs.list()) },
|
|
270
546
|
posts: {
|
|
271
547
|
list: (input = {}) => Effect.runPromise(client.posts.list(input)),
|
|
272
548
|
get: (input) => Effect.runPromise(client.posts.get(input)),
|
|
549
|
+
revisions: (input) => Effect.runPromise(client.posts.revisions(input)),
|
|
273
550
|
create: (input) => Effect.runPromise(client.posts.create(input)),
|
|
274
551
|
update: (input) => Effect.runPromise(client.posts.update(input)),
|
|
275
|
-
archive: (input) => Effect.runPromise(client.posts.archive(input))
|
|
552
|
+
archive: (input) => Effect.runPromise(client.posts.archive(input)),
|
|
553
|
+
restore: (input) => Effect.runPromise(client.posts.restore(input))
|
|
554
|
+
},
|
|
555
|
+
media: {
|
|
556
|
+
list: () => Effect.runPromise(client.media.list()),
|
|
557
|
+
get: (input) => Effect.runPromise(client.media.get(input)),
|
|
558
|
+
startUpload: (input) => Effect.runPromise(client.media.startUpload(input)),
|
|
559
|
+
completeUpload: (input) => Effect.runPromise(client.media.completeUpload(input)),
|
|
560
|
+
delete: (input) => Effect.runPromise(client.media.delete(input))
|
|
276
561
|
}
|
|
277
562
|
};
|
|
278
563
|
}
|
|
564
|
+
var ProsewireRequestError = class extends Error {
|
|
565
|
+
status;
|
|
566
|
+
url;
|
|
567
|
+
constructor(response) {
|
|
568
|
+
super(`Prosewire request failed (${String(response.status)})`);
|
|
569
|
+
this.name = "ProsewireRequestError";
|
|
570
|
+
this.status = response.status;
|
|
571
|
+
this.url = response.url;
|
|
572
|
+
}
|
|
573
|
+
};
|
|
279
574
|
async function decodeResponse(schema, response) {
|
|
280
575
|
return Schema.decodeUnknownPromise(schema)(await response.json());
|
|
281
576
|
}
|
|
@@ -283,33 +578,72 @@ function createPublicClient(options) {
|
|
|
283
578
|
const request = options.fetch ?? fetch;
|
|
284
579
|
const base = options.baseUrl.replace(/\/$/, "");
|
|
285
580
|
const blog = encodeURIComponent(options.blog);
|
|
581
|
+
const postUrl = (slug) => `${base}/api/public/${blog}/posts/${encodeURIComponent(slug)}`;
|
|
582
|
+
const listPosts = async (input = {}) => {
|
|
583
|
+
const query = new URLSearchParams();
|
|
584
|
+
if (input.search) query.set("search", input.search);
|
|
585
|
+
if (input.category) query.set("category", input.category);
|
|
586
|
+
if (input.page) query.set("page", String(input.page));
|
|
587
|
+
if (input.pageSize) query.set("pageSize", String(input.pageSize));
|
|
588
|
+
if (input.limit) query.set("limit", String(input.limit));
|
|
589
|
+
const suffix = query.size ? `?${query.toString()}` : "";
|
|
590
|
+
const response = await request(`${base}/api/public/${blog}/posts${suffix}`);
|
|
591
|
+
if (!response.ok) throw new ProsewireRequestError(response);
|
|
592
|
+
return decodeResponse(publicPostPage, response);
|
|
593
|
+
};
|
|
594
|
+
const resolvePost = async (slug) => {
|
|
595
|
+
const response = await request(postUrl(slug));
|
|
596
|
+
if (response.status === 404) return { status: "not-found" };
|
|
597
|
+
if (!response.ok) throw new ProsewireRequestError(response);
|
|
598
|
+
const result = await decodeResponse(publicPostResult, response);
|
|
599
|
+
return result.post.slug === slug ? {
|
|
600
|
+
status: "found",
|
|
601
|
+
...result
|
|
602
|
+
} : {
|
|
603
|
+
status: "redirect",
|
|
604
|
+
slug: result.post.slug,
|
|
605
|
+
...result
|
|
606
|
+
};
|
|
607
|
+
};
|
|
286
608
|
return {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
609
|
+
listPosts,
|
|
610
|
+
async listAllPosts(input = {}) {
|
|
611
|
+
const posts = [];
|
|
612
|
+
let page = 1;
|
|
613
|
+
while (true) {
|
|
614
|
+
const result = await listPosts({
|
|
615
|
+
...input,
|
|
616
|
+
page,
|
|
617
|
+
pageSize: 100
|
|
618
|
+
});
|
|
619
|
+
posts.push(...result.posts);
|
|
620
|
+
if (!result.pagination.hasMore) return posts;
|
|
621
|
+
page += 1;
|
|
622
|
+
}
|
|
298
623
|
},
|
|
299
624
|
async getPost(slug) {
|
|
300
|
-
const
|
|
301
|
-
if (
|
|
302
|
-
return
|
|
625
|
+
const result = await resolvePost(slug);
|
|
626
|
+
if (result.status === "not-found") throw new ProsewireRequestError(new Response(null, { status: 404 }));
|
|
627
|
+
return {
|
|
628
|
+
blog: result.blog,
|
|
629
|
+
post: result.post
|
|
630
|
+
};
|
|
631
|
+
},
|
|
632
|
+
resolvePost,
|
|
633
|
+
async listRedirects() {
|
|
634
|
+
const response = await request(`${base}/api/public/${blog}/redirects`);
|
|
635
|
+
if (!response.ok) throw new ProsewireRequestError(response);
|
|
636
|
+
return Schema.decodeUnknownPromise(Schema.Array(publicRedirectOutput))(await response.json());
|
|
303
637
|
},
|
|
304
638
|
async getRendered(path = "") {
|
|
305
639
|
const encodedPath = path.replace(/^\//, "").split("/").filter(Boolean).map(encodeURIComponent).join("/");
|
|
306
640
|
const response = await request(`${base}/api/rendered/${blog}/${encodedPath}`);
|
|
307
|
-
if (!response.ok) throw new
|
|
641
|
+
if (!response.ok) throw new ProsewireRequestError(response);
|
|
308
642
|
return response.text();
|
|
309
643
|
}
|
|
310
644
|
};
|
|
311
645
|
}
|
|
312
646
|
//#endregion
|
|
313
|
-
export { createClient, createEffectClient, createPublicClient };
|
|
647
|
+
export { ProsewireImportError, ProsewireRequestError, createClient, createEffectClient, createPublicClient };
|
|
314
648
|
|
|
315
649
|
//# sourceMappingURL=index.mjs.map
|