@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/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$2 = Schema.String.check(Schema.isUUID());
7
- const isoDateTime = Schema.String.check(Schema.isPattern(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/));
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$2,
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$2,
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$2,
33
- blogId: uuid$2,
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 authorId = uuid$2;
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$2);
137
+ const categoryIds = Schema.Array(uuid);
70
138
  const postCreateInput = Schema.Struct({
71
- blogId: uuid$2,
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: withDefault(locale, "en"),
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$2,
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$2,
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$2,
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.Number,
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
- var ApiInputRejected = class extends Schema.TaggedError()("ApiInputRejected", { message: Schema.String }, { httpApiStatus: 400 }) {};
174
- var ApiAuthenticationFailed = class extends Schema.TaggedError()("ApiAuthenticationFailed", { message: Schema.String }, { httpApiStatus: 401 }) {};
175
- var ApiAccessDenied = class extends Schema.TaggedError()("ApiAccessDenied", { message: Schema.String }, { httpApiStatus: 403 }) {};
176
- var ApiPostNotFound = class extends Schema.TaggedError()("ApiPostNotFound", { message: Schema.String }, { httpApiStatus: 404 }) {};
177
- var ApiUnavailable = class extends Schema.TaggedError()("ApiUnavailable", { message: Schema.String }, { httpApiStatus: 500 }) {};
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 uuid$1 = Schema.String.check(Schema.isUUID());
186
- const pageNumber$1 = Schema.NumberFromString.pipe(Schema.check(Schema.isInt(), Schema.isGreaterThanOrEqualTo(1)));
187
- const pageSize$1 = Schema.NumberFromString.pipe(Schema.check(Schema.isInt(), Schema.isGreaterThanOrEqualTo(1), Schema.isLessThanOrEqualTo(100)));
188
- const health = HttpApiGroup.make("health").add(HttpApiEndpoint.get("check", "/health", {
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", "/blogs", {
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", "/posts", {
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", "/posts/:id", {
210
- params: { id: uuid$1 },
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", "/posts", {
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", "/posts/:id", {
218
- params: { id: uuid$1 },
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", "/posts/:id", {
223
- params: { id: uuid$1 },
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
- const api = HttpApi.make("ProsewireApi").add(health, blogs, posts).prefix("/api/v1").annotate(OpenApi.Title, "Prosewire private API").annotate(OpenApi.Version, "1.0.0");
228
- Schema.String.check(Schema.isUUID());
229
- Schema.NumberFromString.pipe(Schema.check(Schema.isInt(), Schema.isGreaterThanOrEqualTo(1)));
230
- Schema.NumberFromString.pipe(Schema.check(Schema.isInt(), Schema.isGreaterThanOrEqualTo(1), Schema.isLessThanOrEqualTo(100)));
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
- async listPosts(input = {}) {
288
- const query = new URLSearchParams();
289
- if (input.search) query.set("search", input.search);
290
- if (input.category) query.set("category", input.category);
291
- if (input.page) query.set("page", String(input.page));
292
- if (input.pageSize) query.set("pageSize", String(input.pageSize));
293
- if (input.limit) query.set("limit", String(input.limit));
294
- const suffix = query.size ? `?${query.toString()}` : "";
295
- const response = await request(`${base}/api/public/${blog}/posts${suffix}`);
296
- if (!response.ok) throw new Error(`Prosewire request failed (${String(response.status)})`);
297
- return decodeResponse(publicPostPage, response);
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 response = await request(`${base}/api/public/${blog}/posts/${encodeURIComponent(slug)}`);
301
- if (!response.ok) throw new Error(`Prosewire request failed (${String(response.status)})`);
302
- return decodeResponse(publicPostResult, response);
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 Error(`Prosewire request failed (${String(response.status)})`);
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