@orakl/protocol 0.1.0 → 0.1.1

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.
@@ -441,10 +441,7 @@ export declare const CuratorHistoryEntryPageSchema: Schema.Struct<{
441
441
  readonly correct: boolean;
442
442
  }[] | null;
443
443
  readonly noAnswer: number;
444
- readonly source?: {
445
- readonly url: string;
446
- readonly label?: string | undefined;
447
- } | undefined;
444
+ readonly source?: unknown;
448
445
  readonly postAnswerNote?: string | undefined;
449
446
  readonly questionText?: string | undefined;
450
447
  readonly mediaUrl?: string | undefined;
@@ -1,5 +1,5 @@
1
1
  import { Schema } from "effect";
2
- import { MediaTypeSchema, QuestionSourceSchema } from "../question.schema.js";
2
+ import { MediaTypeSchema, StoredQuestionSourceSchema, } from "../question.schema.js";
3
3
  import { ThemeModeSchema, UserPowerSchema, UserRoleSchema, } from "./common.schema.js";
4
4
  /** Page-load response bodies for `src/api/routes/pages.ts`. */
5
5
  /** A JSON array, decoded mutable — Effect's `Schema.Array` types its `.Type`
@@ -265,7 +265,7 @@ const QuestionRecapEntrySchema = Schema.Struct({
265
265
  questionText: Schema.optional(Schema.String),
266
266
  mediaUrl: Schema.optional(Schema.String),
267
267
  mediaType: Schema.optional(MediaTypeSchema),
268
- source: Schema.optional(QuestionSourceSchema),
268
+ source: Schema.optional(StoredQuestionSourceSchema),
269
269
  postAnswerNote: Schema.optional(Schema.String),
270
270
  answered: Schema.Number,
271
271
  correct: Schema.Number,
@@ -19,9 +19,12 @@ export declare const GameAnswerRecordSchema: Schema.Struct<{
19
19
  correctAnswerText: Schema.optional<typeof Schema.String>;
20
20
  mediaUrl: Schema.optional<typeof Schema.String>;
21
21
  mediaType: Schema.optional<Schema.Literal<["image", "audio", "video"]>>;
22
- source: Schema.optional<Schema.Struct<{
23
- url: Schema.filter<Schema.filter<typeof Schema.String>>;
24
- label: Schema.optional<Schema.filter<typeof Schema.String>>;
25
- }>>;
22
+ source: Schema.optional<Schema.transform<typeof Schema.Unknown, Schema.UndefinedOr<Schema.SchemaClass<{
23
+ readonly url: string;
24
+ readonly label?: string | undefined;
25
+ }, {
26
+ readonly url: string;
27
+ readonly label?: string | undefined;
28
+ }, never>>>>;
26
29
  postAnswerNote: Schema.optional<typeof Schema.String>;
27
30
  }>;
@@ -1,5 +1,5 @@
1
1
  import { Schema } from "effect";
2
- import { MediaTypeSchema, QuestionSourceSchema } from "../question.schema.js";
2
+ import { MediaTypeSchema, StoredQuestionSourceSchema, } from "../question.schema.js";
3
3
  /** Schemas shared by more than one page-load body. */
4
4
  export const UserRoleSchema = Schema.Literal("anonymous", "display", "member", "curator", "admin");
5
5
  export const UserPowerSchema = Schema.Literal("can-access-mimic", "can-send-feedback", "can-import-quizzes", "can-manage-features", "can-add-questions", "can-manage-categories", "can-moderate-questions", "can-publish-questions", "is-power-user", "can-manage-avatars", "can-apply-for-curation", "can-1v1");
@@ -20,7 +20,7 @@ export const GameAnswerRecordSchema = Schema.Struct({
20
20
  correctAnswerText: Schema.optional(Schema.String),
21
21
  mediaUrl: Schema.optional(Schema.String),
22
22
  mediaType: Schema.optional(MediaTypeSchema),
23
- source: Schema.optional(QuestionSourceSchema),
23
+ source: Schema.optional(StoredQuestionSourceSchema),
24
24
  postAnswerNote: Schema.optional(Schema.String),
25
25
  });
26
26
  // Assignability check: a schema drift that drops or mistypes a field the
@@ -207,10 +207,13 @@ export declare const GameReviewPageSchema: Schema.Struct<{
207
207
  correctAnswerText: Schema.optional<typeof Schema.String>;
208
208
  mediaUrl: Schema.optional<typeof Schema.String>;
209
209
  mediaType: Schema.optional<Schema.Literal<["image", "audio", "video"]>>;
210
- source: Schema.optional<Schema.Struct<{
211
- url: Schema.filter<Schema.filter<typeof Schema.String>>;
212
- label: Schema.optional<Schema.filter<typeof Schema.String>>;
213
- }>>;
210
+ source: Schema.optional<Schema.transform<typeof Schema.Unknown, Schema.UndefinedOr<Schema.SchemaClass<{
211
+ readonly url: string;
212
+ readonly label?: string | undefined;
213
+ }, {
214
+ readonly url: string;
215
+ readonly label?: string | undefined;
216
+ }, never>>>>;
214
217
  postAnswerNote: Schema.optional<typeof Schema.String>;
215
218
  }>>>>;
216
219
  categoryName: Schema.mutable<Schema.Record$<typeof Schema.String, typeof Schema.String>>;
@@ -258,10 +261,13 @@ export declare const SoloReviewPageSchema: Schema.Struct<{
258
261
  questionId: Schema.NullOr<typeof Schema.String>;
259
262
  mediaUrl: Schema.NullOr<typeof Schema.String>;
260
263
  mediaType: Schema.NullOr<Schema.Literal<["image", "audio", "video"]>>;
261
- source: Schema.NullOr<Schema.Struct<{
262
- url: Schema.filter<Schema.filter<typeof Schema.String>>;
263
- label: Schema.optional<Schema.filter<typeof Schema.String>>;
264
- }>>;
264
+ source: Schema.Union<[typeof Schema.Null, Schema.transform<typeof Schema.Unknown, Schema.UndefinedOr<Schema.SchemaClass<{
265
+ readonly url: string;
266
+ readonly label?: string | undefined;
267
+ }, {
268
+ readonly url: string;
269
+ readonly label?: string | undefined;
270
+ }, never>>>]>;
265
271
  postAnswerNote: Schema.NullOr<typeof Schema.String>;
266
272
  }>>>;
267
273
  alreadyFlaggedIds: Schema.mutable<Schema.Array$<typeof Schema.String>>;
@@ -1,5 +1,5 @@
1
1
  import { Schema } from "effect";
2
- import { MediaTypeSchema, QuestionSourceSchema } from "../question.schema.js";
2
+ import { MediaTypeSchema, StoredQuestionSourceSchema, } from "../question.schema.js";
3
3
  import { GameAnswerRecordSchema, PlayerRoleSchema, UserPowerSchema, UserRoleSchema, } from "./common.schema.js";
4
4
  /** Page-load response bodies for `src/api/routes/pages-history.ts and admin-feature-flags.ts`. */
5
5
  /** Achievement badge id (mirrors `BadgeId` in `src/lib/badges.ts`). */
@@ -176,7 +176,8 @@ const SoloRunReviewEntrySchema = Schema.Struct({
176
176
  questionId: Schema.NullOr(Schema.String),
177
177
  mediaUrl: Schema.NullOr(Schema.String),
178
178
  mediaType: Schema.NullOr(MediaTypeSchema),
179
- source: Schema.NullOr(QuestionSourceSchema),
179
+ // Null first: the stored-source transform accepts any value, null included.
180
+ source: Schema.Union(Schema.Null, StoredQuestionSourceSchema),
180
181
  postAnswerNote: Schema.NullOr(Schema.String),
181
182
  });
182
183
  /** Body of `GET /api/pages/history/solo/:id`. */
@@ -132,10 +132,13 @@ export declare const QuizResultsPageSchema: Schema.Struct<{
132
132
  correctAnswerText: Schema.optional<typeof Schema.String>;
133
133
  mediaUrl: Schema.optional<typeof Schema.String>;
134
134
  mediaType: Schema.optional<Schema.Literal<["image", "audio", "video"]>>;
135
- source: Schema.optional<Schema.Struct<{
136
- url: Schema.filter<Schema.filter<typeof Schema.String>>;
137
- label: Schema.optional<Schema.filter<typeof Schema.String>>;
138
- }>>;
135
+ source: Schema.optional<Schema.transform<typeof Schema.Unknown, Schema.UndefinedOr<Schema.SchemaClass<{
136
+ readonly url: string;
137
+ readonly label?: string | undefined;
138
+ }, {
139
+ readonly url: string;
140
+ readonly label?: string | undefined;
141
+ }, never>>>>;
139
142
  postAnswerNote: Schema.optional<typeof Schema.String>;
140
143
  }>>>>;
141
144
  canFlag: typeof Schema.Boolean;
@@ -14,6 +14,18 @@ export declare const QuestionSourceSchema: Schema.Struct<{
14
14
  url: Schema.filter<Schema.filter<typeof Schema.String>>;
15
15
  label: Schema.optional<Schema.filter<typeof Schema.String>>;
16
16
  }>;
17
+ /**
18
+ * Read-side source: any stored value that fails `QuestionSourceSchema`
19
+ * (bad url, bad label, wrong shape) decodes to undefined, so one bad source
20
+ * never fails a whole page.
21
+ */
22
+ export declare const StoredQuestionSourceSchema: Schema.transform<typeof Schema.Unknown, Schema.UndefinedOr<Schema.SchemaClass<{
23
+ readonly url: string;
24
+ readonly label?: string | undefined;
25
+ }, {
26
+ readonly url: string;
27
+ readonly label?: string | undefined;
28
+ }, never>>>;
17
29
  export declare const PostAnswerNoteSchema: Schema.filter<typeof Schema.String>;
18
30
  declare const TextChoiceSchema: Schema.Struct<{
19
31
  type: Schema.Literal<["text_choice"]>;
@@ -30,6 +30,17 @@ export const QuestionSourceSchema = Schema.Struct({
30
30
  message: () => "Source label must be 200 characters or fewer",
31
31
  }))),
32
32
  });
33
+ const isQuestionSource = Schema.is(QuestionSourceSchema);
34
+ /**
35
+ * Read-side source: any stored value that fails `QuestionSourceSchema`
36
+ * (bad url, bad label, wrong shape) decodes to undefined, so one bad source
37
+ * never fails a whole page.
38
+ */
39
+ export const StoredQuestionSourceSchema = Schema.transform(Schema.Unknown, Schema.UndefinedOr(Schema.typeSchema(QuestionSourceSchema)), {
40
+ strict: true,
41
+ decode: (source) => (isQuestionSource(source) ? source : undefined),
42
+ encode: (source) => source,
43
+ });
33
44
  export const PostAnswerNoteSchema = Schema.String.pipe(Schema.maxLength(160, {
34
45
  message: () => "Post-answer note must be 160 characters or fewer",
35
46
  }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orakl/protocol",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Orakl wire contract: WebSocket frames and API response schemas.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {