@echovisionlab/geul-common 0.4.0 → 0.5.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/README.md CHANGED
@@ -10,6 +10,29 @@ pnpm add @echovisionlab/geul-common
10
10
  The package exports TypeScript source. Consumers must support `.ts` package
11
11
  exports. `yjs` is a peer dependency.
12
12
 
13
+ ## Validation schemas and zod locale
14
+
15
+ Exported validation schemas are [`zod/mini`](https://zod.dev/packages/mini)
16
+ schemas, so bundlers tree-shake them (classic `zod` cannot be tree-shaken).
17
+ Call `parse` and `safeParse` on them; compose them with the functional
18
+ `zod/mini` API (`z.optional(schema)`, `schema.check(...)`) rather than classic
19
+ method chains. Parse failures throw `z.core.$ZodError` (not the classic
20
+ `ZodError` class); use `z.flattenError` or `z.treeifyError` to format issues.
21
+
22
+ Common never configures zod globally. Custom refinement messages are always
23
+ English, but built-in messages (wrong type, unknown key, too small, and so on)
24
+ come from the configured zod locale, and `zod/mini` installs none by default:
25
+ without one they fall back to `Invalid input`. Consumers that surface or log
26
+ issue messages must configure the English locale once at startup (importing
27
+ classic `zod` anywhere in the process also installs it):
28
+
29
+ ```ts
30
+ import { config } from "zod/mini";
31
+ import en from "zod/v4/locales/en.js";
32
+
33
+ config(en());
34
+ ```
35
+
13
36
  ## Development
14
37
 
15
38
  ```sh
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@echovisionlab/geul-common",
3
3
  "description": "Shared TypeScript contracts and helpers for Geul editors, collaboration, localized content, media blocks, pages, posts, and runtime events.",
4
4
  "author": "state303 <state303@dsub.io>",
5
- "version": "0.4.0",
5
+ "version": "0.5.0",
6
6
  "private": false,
7
7
  "type": "module",
8
8
  "repository": {
@@ -16,7 +16,8 @@
16
16
  "files": [
17
17
  "README.md",
18
18
  "src",
19
- "!src/**/*.test.ts"
19
+ "!src/**/*.test.ts",
20
+ "!src/**/*.golden.json"
20
21
  ],
21
22
  "scripts": {
22
23
  "format": "prettier --write .",
@@ -1,17 +1,15 @@
1
- import { z } from "zod";
2
-
3
- export const artistCollabFieldsSchema = z
4
- .object({
5
- status: z.enum(["draft", "published"]).optional(),
6
- realName: z.string().optional(),
7
- countryCode: z.string().optional(),
8
- website: z.string().optional(),
9
- socialLinks: z.record(z.string(), z.string()).optional(),
10
- slug: z.string().optional(),
11
- labelIds: z.array(z.string()).optional(),
12
- parentArtistId: z.string().nullable().optional(),
13
- })
14
- .strict();
1
+ import * as z from "zod/mini";
2
+
3
+ export const artistCollabFieldsSchema = z.strictObject({
4
+ status: z.optional(z.enum(["draft", "published"])),
5
+ realName: z.optional(z.string()),
6
+ countryCode: z.optional(z.string()),
7
+ website: z.optional(z.string()),
8
+ socialLinks: z.optional(z.record(z.string(), z.string())),
9
+ slug: z.optional(z.string()),
10
+ labelIds: z.optional(z.array(z.string())),
11
+ parentArtistId: z.optional(z.nullable(z.string())),
12
+ });
15
13
 
16
14
  export const ARTIST_JSON_KEYS: ReadonlySet<
17
15
  keyof z.infer<typeof artistCollabFieldsSchema>
@@ -1,4 +1,4 @@
1
- import { z } from "zod";
1
+ import * as z from "zod/mini";
2
2
 
3
3
  export const campaignRecipientScopeSchema = z.enum([
4
4
  "SUBSCRIBED_USERS",
@@ -9,13 +9,11 @@ export type CampaignRecipientScope = z.infer<
9
9
  typeof campaignRecipientScopeSchema
10
10
  >;
11
11
 
12
- export const campaignCollabFieldsSchema = z
13
- .object({
14
- segmentId: z.string().nullable().optional(),
15
- layoutId: z.string().nullable().optional(),
16
- recipientScope: campaignRecipientScopeSchema.default("SUBSCRIBED_USERS"),
17
- })
18
- .strict();
12
+ export const campaignCollabFieldsSchema = z.strictObject({
13
+ segmentId: z.optional(z.nullable(z.string())),
14
+ layoutId: z.optional(z.nullable(z.string())),
15
+ recipientScope: z._default(campaignRecipientScopeSchema, "SUBSCRIBED_USERS"),
16
+ });
19
17
 
20
18
  export type CampaignCollabFields = z.infer<typeof campaignCollabFieldsSchema>;
21
19
  export type CampaignFieldValue = string | null;
@@ -1,5 +1,5 @@
1
1
  import type * as Y from "yjs";
2
- import { z } from "zod";
2
+ import * as z from "zod/mini";
3
3
 
4
4
  export const DOCUMENT_LAYOUT_FIELD_KEYS = [
5
5
  "contentHeight",
@@ -7,13 +7,11 @@ export const DOCUMENT_LAYOUT_FIELD_KEYS = [
7
7
  "footer",
8
8
  ] as const;
9
9
 
10
- export const documentLayoutSchema = z
11
- .object({
12
- contentHeight: z.enum(["content", "viewport"]),
13
- pageChrome: z.enum(["flow", "pinned"]),
14
- footer: z.enum(["flow", "pinned"]),
15
- })
16
- .strict();
10
+ export const documentLayoutSchema = z.strictObject({
11
+ contentHeight: z.enum(["content", "viewport"]),
12
+ pageChrome: z.enum(["flow", "pinned"]),
13
+ footer: z.enum(["flow", "pinned"]),
14
+ });
17
15
 
18
16
  export type DocumentLayout = z.infer<typeof documentLayoutSchema>;
19
17
 
@@ -7,7 +7,7 @@
7
7
 
8
8
  import { CollaborativeDocumentType } from "@echovisionlab/geul-proto/secure/collaboration_pb.ts";
9
9
  import type * as Y from "yjs";
10
- import { z } from "zod";
10
+ import * as z from "zod/mini";
11
11
  import { memberIdSchema } from "./member-id.ts";
12
12
  import type { BlockRoomDocumentType } from "./block-room-codec.ts";
13
13
 
@@ -115,14 +115,12 @@ export interface ParsedDocument {
115
115
  locale: string;
116
116
  }
117
117
 
118
- export const documentSaveOptionsSchema = z
119
- .object({
120
- /** Distinct authenticated Members whose edits are included in this persisted state. */
121
- contributorMemberIds: z.array(memberIdSchema).optional(),
122
- /** Requests a source version checkpoint after ordinary durable persistence. */
123
- versionCheckpoint: z.boolean().optional(),
124
- })
125
- .strict();
118
+ export const documentSaveOptionsSchema = z.strictObject({
119
+ /** Distinct authenticated Members whose edits are included in this persisted state. */
120
+ contributorMemberIds: z.optional(z.array(memberIdSchema)),
121
+ /** Requests a source version checkpoint after ordinary durable persistence. */
122
+ versionCheckpoint: z.optional(z.boolean()),
123
+ });
126
124
 
127
125
  export type DocumentSaveOptions = z.infer<typeof documentSaveOptionsSchema>;
128
126
 
@@ -11,18 +11,16 @@ import {
11
11
  formValidatorMessageTarget,
12
12
  } from "@echovisionlab/geul-proto/intra/form_locale_catalog.ts";
13
13
  import * as Y from "yjs";
14
- import { z } from "zod";
14
+ import * as z from "zod/mini";
15
15
 
16
16
  export const FORM_FIELDS_MAP_NAME = "form-fields";
17
17
  export const FORM_LOCALE_PRESENCE_MAP_NAME = "form-locale-presence";
18
18
  export const FORM_CANONICAL_CONTEXT_MAP_NAME = "form-canonical-context";
19
19
 
20
- export const formCollabFieldsSchema = z
21
- .object({
22
- title: z.string().optional(),
23
- schema: z.unknown().optional(),
24
- })
25
- .strict();
20
+ export const formCollabFieldsSchema = z.strictObject({
21
+ title: z.optional(z.string()),
22
+ schema: z.optional(z.unknown()),
23
+ });
26
24
 
27
25
  export const FORM_JSON_KEYS: ReadonlySet<
28
26
  keyof z.infer<typeof formCollabFieldsSchema>
@@ -1,14 +1,12 @@
1
- import { z } from "zod";
2
-
3
- export const labelCollabFieldsSchema = z
4
- .object({
5
- slug: z.string().optional(),
6
- countryCode: z.string().optional(),
7
- website: z.string().optional(),
8
- socialLinks: z.record(z.string(), z.string()).optional(),
9
- parentLabelId: z.string().nullable().optional(),
10
- })
11
- .strict();
1
+ import * as z from "zod/mini";
2
+
3
+ export const labelCollabFieldsSchema = z.strictObject({
4
+ slug: z.optional(z.string()),
5
+ countryCode: z.optional(z.string()),
6
+ website: z.optional(z.string()),
7
+ socialLinks: z.optional(z.record(z.string(), z.string())),
8
+ parentLabelId: z.optional(z.nullable(z.string())),
9
+ });
12
10
 
13
11
  export const LABEL_SHARED_FIELD_KEYS = Object.keys(
14
12
  labelCollabFieldsSchema.shape,
@@ -1,4 +1,4 @@
1
- import { z } from "zod";
1
+ import * as z from "zod/mini";
2
2
 
3
3
  export const MAP_THEME_META_MAP_NAME = "map-theme-meta";
4
4
  export const MAP_THEME_SETTINGS_MAP_NAME = "map-theme-settings";
@@ -31,42 +31,41 @@ const calloutFieldSchema = z.enum([
31
31
  "postalCode",
32
32
  ]);
33
33
 
34
- const MapThemeEditingNameSchema = z.string().refine(isValidMapThemeName, {
35
- message: `Map Theme name must contain 1-${MAP_THEME_NAME_MAX_LENGTH} Unicode code points`,
36
- });
34
+ const MapThemeEditingNameSchema = z.string().check(
35
+ z.refine(isValidMapThemeName, {
36
+ message: `Map Theme name must contain 1-${MAP_THEME_NAME_MAX_LENGTH} Unicode code points`,
37
+ }),
38
+ );
37
39
 
38
- const MapThemeCanonicalNameSchema = MapThemeEditingNameSchema.transform(
39
- (value) => value.trim(),
40
+ const MapThemeCanonicalNameSchema = z.pipe(
41
+ MapThemeEditingNameSchema,
42
+ z.transform((value: string) => value.trim()),
40
43
  );
41
44
 
42
- export const MapThemeDocumentEditingMetaSchema = z
43
- .object({
44
- name: MapThemeEditingNameSchema,
45
- })
46
- .strict();
45
+ export const MapThemeDocumentEditingMetaSchema = z.strictObject({
46
+ name: MapThemeEditingNameSchema,
47
+ });
47
48
 
48
- export const MapThemeDocumentMetaSchema = z
49
- .object({
50
- name: MapThemeCanonicalNameSchema,
51
- })
52
- .strict();
49
+ export const MapThemeDocumentMetaSchema = z.strictObject({
50
+ name: MapThemeCanonicalNameSchema,
51
+ });
53
52
 
54
53
  export type MapThemeDocumentMeta = z.infer<typeof MapThemeDocumentMetaSchema>;
55
54
 
56
55
  export const MAP_THEME_META_JSON_KEYS: ReadonlySet<keyof MapThemeDocumentMeta> =
57
56
  new Set([]);
58
57
 
59
- export const MapThemeDocumentSettingsSchema = z
60
- .object({
61
- calloutScale: z.number().min(0.5).max(2),
62
- calloutOffsetX: z.number().int().min(-50).max(50),
63
- calloutOffsetY: z.number().int().min(-50).max(50),
64
- calloutFields: z.array(calloutFieldSchema).min(1).max(8),
65
- showAreaLabels: z.boolean(),
66
- showPoiLabels: z.boolean(),
67
- attributionFontSize: z.number().int().min(9).max(14),
68
- })
69
- .strict();
58
+ export const MapThemeDocumentSettingsSchema = z.strictObject({
59
+ calloutScale: z.number().check(z.minimum(0.5), z.maximum(2)),
60
+ calloutOffsetX: z.int().check(z.minimum(-50), z.maximum(50)),
61
+ calloutOffsetY: z.int().check(z.minimum(-50), z.maximum(50)),
62
+ calloutFields: z
63
+ .array(calloutFieldSchema)
64
+ .check(z.minLength(1), z.maxLength(8)),
65
+ showAreaLabels: z.boolean(),
66
+ showPoiLabels: z.boolean(),
67
+ attributionFontSize: z.int().check(z.minimum(9), z.maximum(14)),
68
+ });
70
69
 
71
70
  export type MapThemeDocumentSettings = z.infer<
72
71
  typeof MapThemeDocumentSettingsSchema
@@ -101,39 +100,37 @@ export function isMapThemeColor(value: string): boolean {
101
100
  return rgba.slice(1, 4).every((channel) => Number(channel) <= 255);
102
101
  }
103
102
 
104
- export const MapThemeColorSchema = z
105
- .string()
106
- .trim()
107
- .max(50)
108
- .refine(isMapThemeColor, {
103
+ export const MapThemeColorSchema = z.string().check(
104
+ z.trim(),
105
+ z.maxLength(50),
106
+ z.refine(isMapThemeColor, {
109
107
  message: "Invalid map theme color",
110
- });
111
-
112
- export const MapThemeDocumentVariantSchema = z
113
- .object({
114
- backgroundColor: MapThemeColorSchema,
115
- waterColor: MapThemeColorSchema,
116
- landColor: MapThemeColorSchema,
117
- roadColor: MapThemeColorSchema,
118
- buildingFillColor: MapThemeColorSchema,
119
- buildingStrokeEnabled: z.boolean(),
120
- buildingStrokeColor: MapThemeColorSchema,
121
- calloutLineColor: MapThemeColorSchema,
122
- calloutHoverLineColor: MapThemeColorSchema,
123
- calloutTextColor: MapThemeColorSchema,
124
- calloutHoverTextColor: MapThemeColorSchema,
125
- calloutDescriptionColor: MapThemeColorSchema,
126
- calloutHoverDescriptionColor: MapThemeColorSchema,
127
- calloutBackgroundColor: MapThemeColorSchema,
128
- calloutHoverBackgroundColor: MapThemeColorSchema,
129
- attributionColor: MapThemeColorSchema,
130
- labelTextColor: MapThemeColorSchema,
131
- clusterColor: MapThemeColorSchema,
132
- clusterHoverColor: MapThemeColorSchema,
133
- clusterTextColor: MapThemeColorSchema,
134
- clusterTextHoverColor: MapThemeColorSchema,
135
- })
136
- .strict();
108
+ }),
109
+ );
110
+
111
+ export const MapThemeDocumentVariantSchema = z.strictObject({
112
+ backgroundColor: MapThemeColorSchema,
113
+ waterColor: MapThemeColorSchema,
114
+ landColor: MapThemeColorSchema,
115
+ roadColor: MapThemeColorSchema,
116
+ buildingFillColor: MapThemeColorSchema,
117
+ buildingStrokeEnabled: z.boolean(),
118
+ buildingStrokeColor: MapThemeColorSchema,
119
+ calloutLineColor: MapThemeColorSchema,
120
+ calloutHoverLineColor: MapThemeColorSchema,
121
+ calloutTextColor: MapThemeColorSchema,
122
+ calloutHoverTextColor: MapThemeColorSchema,
123
+ calloutDescriptionColor: MapThemeColorSchema,
124
+ calloutHoverDescriptionColor: MapThemeColorSchema,
125
+ calloutBackgroundColor: MapThemeColorSchema,
126
+ calloutHoverBackgroundColor: MapThemeColorSchema,
127
+ attributionColor: MapThemeColorSchema,
128
+ labelTextColor: MapThemeColorSchema,
129
+ clusterColor: MapThemeColorSchema,
130
+ clusterHoverColor: MapThemeColorSchema,
131
+ clusterTextColor: MapThemeColorSchema,
132
+ clusterTextHoverColor: MapThemeColorSchema,
133
+ });
137
134
 
138
135
  export type MapThemeDocumentVariant = z.infer<
139
136
  typeof MapThemeDocumentVariantSchema
@@ -143,14 +140,12 @@ export const MAP_THEME_VARIANT_JSON_KEYS: ReadonlySet<
143
140
  keyof MapThemeDocumentVariant
144
141
  > = new Set([]);
145
142
 
146
- export const MapThemeDocumentSnapshotSchema = z
147
- .object({
148
- name: MapThemeDocumentMetaSchema.shape.name,
149
- settings: MapThemeDocumentSettingsSchema,
150
- lightVariant: MapThemeDocumentVariantSchema,
151
- darkVariant: MapThemeDocumentVariantSchema,
152
- })
153
- .strict();
143
+ export const MapThemeDocumentSnapshotSchema = z.strictObject({
144
+ name: MapThemeDocumentMetaSchema.shape.name,
145
+ settings: MapThemeDocumentSettingsSchema,
146
+ lightVariant: MapThemeDocumentVariantSchema,
147
+ darkVariant: MapThemeDocumentVariantSchema,
148
+ });
154
149
 
155
150
  export type MapThemeDocumentSnapshot = z.infer<
156
151
  typeof MapThemeDocumentSnapshotSchema
@@ -1,4 +1,4 @@
1
- import { z } from "zod";
1
+ import * as z from "zod/mini";
2
2
 
3
3
  const CANONICAL_UUID_PATTERN =
4
4
  /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
@@ -6,4 +6,4 @@ const CANONICAL_UUID_PATTERN =
6
6
  /** Domain Member IDs cross service and durable-state boundaries in canonical form only. */
7
7
  export const memberIdSchema = z
8
8
  .string()
9
- .regex(CANONICAL_UUID_PATTERN, "Expected a canonical Member UUID");
9
+ .check(z.regex(CANONICAL_UUID_PATTERN, "Expected a canonical Member UUID"));
@@ -1,4 +1,4 @@
1
- import { z } from "zod";
1
+ import * as z from "zod/mini";
2
2
  import { memberIdSchema } from "./member-id.ts";
3
3
 
4
4
  export const metadataAiFieldSchema = z.enum(["summary"]);
@@ -11,28 +11,31 @@ export const metadataAiStatusSchema = z.enum([
11
11
  ]);
12
12
 
13
13
  export const metadataAiSharedStateSchema = z
14
- .object({
14
+ .strictObject({
15
15
  status: metadataAiStatusSchema,
16
- generationId: z.string().nullable(),
17
- jobId: z.string().nullable(),
18
- requesterMemberId: memberIdSchema.nullable(),
16
+ generationId: z.nullable(z.string()),
17
+ jobId: z.nullable(z.string()),
18
+ requesterMemberId: z.nullable(memberIdSchema),
19
19
  /** Non-authoritative Member nickname snapshot; never a Member lookup or mapping key. */
20
- requesterNickname: z.string().min(1).max(100).nullable(),
20
+ requesterNickname: z.nullable(
21
+ z.string().check(z.minLength(1), z.maxLength(100)),
22
+ ),
21
23
  requestedFields: z.array(metadataAiFieldSchema),
22
24
  allMetadata: z.boolean(),
23
- startedAt: z.number().int().nullable(),
24
- updatedAt: z.number().int().nullable(),
25
- orphanedAt: z.number().int().nullable(),
26
- autoClearAt: z.number().int().nullable(),
25
+ startedAt: z.nullable(z.int()),
26
+ updatedAt: z.nullable(z.int()),
27
+ orphanedAt: z.nullable(z.int()),
28
+ autoClearAt: z.nullable(z.int()),
27
29
  })
28
- .strict()
29
- .refine(
30
- (state) =>
31
- state.requesterNickname === null || state.requesterMemberId !== null,
32
- {
33
- message: "requesterNickname requires requesterMemberId",
34
- path: ["requesterNickname"],
35
- },
30
+ .check(
31
+ z.refine(
32
+ (state) =>
33
+ state.requesterNickname === null || state.requesterMemberId !== null,
34
+ {
35
+ message: "requesterNickname requires requesterMemberId",
36
+ path: ["requesterNickname"],
37
+ },
38
+ ),
36
39
  );
37
40
 
38
41
  export const METADATA_AI_MAP_NAME = "metadata-ai";
@@ -1,10 +1,10 @@
1
- import { z } from "zod";
1
+ import * as z from "zod/mini";
2
2
 
3
3
  export * from "./document-layout.ts";
4
4
 
5
5
  // Shared Page Yjs owns only section/column/block structure. Page settings are
6
6
  // changed through their owning DB mutations and must not be persisted here.
7
- export const pageCollabFieldsSchema = z.object({}).strict();
7
+ export const pageCollabFieldsSchema = z.strictObject({});
8
8
 
9
9
  export const PAGE_SOURCE_OWNED_FIELD_KEYS = ["title", "summary"] as const;
10
10
  export const PAGE_SHARED_FIELD_KEYS = [] as const;
@@ -1,13 +1,11 @@
1
- import { z } from "zod";
2
-
3
- export const postCollabFieldsSchema = z
4
- .object({
5
- title: z.string().optional(),
6
- summary: z.string().optional(),
7
- categoryIds: z.array(z.string()).optional(),
8
- tagIds: z.array(z.string()).optional(),
9
- })
10
- .strict();
1
+ import * as z from "zod/mini";
2
+
3
+ export const postCollabFieldsSchema = z.strictObject({
4
+ title: z.optional(z.string()),
5
+ summary: z.optional(z.string()),
6
+ categoryIds: z.optional(z.array(z.string())),
7
+ tagIds: z.optional(z.array(z.string())),
8
+ });
11
9
 
12
10
  export const POST_JSON_KEYS: ReadonlySet<
13
11
  keyof z.infer<typeof postCollabFieldsSchema>