@loro-extended/change 0.2.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.
@@ -0,0 +1,261 @@
1
+ import type {
2
+ ArrayValueShape,
3
+ ContainerOrValueShape,
4
+ DocShape,
5
+ ListContainerShape,
6
+ MapContainerShape,
7
+ MovableListContainerShape,
8
+ ObjectValueShape,
9
+ RecordContainerShape,
10
+ RecordValueShape,
11
+ UnionValueShape,
12
+ ValueShape,
13
+ } from "./shape.js"
14
+ import type { InferPlainType } from "./types.js"
15
+
16
+ /**
17
+ * Validates a value against a ContainerShape or ValueShape schema
18
+ */
19
+ export function validateValue(
20
+ value: unknown,
21
+ schema: ContainerOrValueShape,
22
+ path: string = "",
23
+ ): unknown {
24
+ if (!schema || typeof schema !== "object" || !("_type" in schema)) {
25
+ throw new Error(`Invalid schema at path ${path}: missing _type`)
26
+ }
27
+
28
+ const currentPath = path || "root"
29
+
30
+ // Handle ContainerShape types
31
+ if (schema._type === "text") {
32
+ if (typeof value !== "string") {
33
+ throw new Error(
34
+ `Expected string at path ${currentPath}, got ${typeof value}`,
35
+ )
36
+ }
37
+ return value
38
+ }
39
+
40
+ if (schema._type === "counter") {
41
+ if (typeof value !== "number") {
42
+ throw new Error(
43
+ `Expected number at path ${currentPath}, got ${typeof value}`,
44
+ )
45
+ }
46
+ return value
47
+ }
48
+
49
+ if (schema._type === "list" || schema._type === "movableList") {
50
+ if (!Array.isArray(value)) {
51
+ throw new Error(
52
+ `Expected array at path ${currentPath}, got ${typeof value}`,
53
+ )
54
+ }
55
+ const listSchema = schema as ListContainerShape | MovableListContainerShape
56
+ return value.map((item, index) =>
57
+ validateValue(item, listSchema.shape, `${currentPath}[${index}]`),
58
+ )
59
+ }
60
+
61
+ if (schema._type === "map") {
62
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
63
+ throw new Error(
64
+ `Expected object at path ${currentPath}, got ${typeof value}`,
65
+ )
66
+ }
67
+ const mapSchema = schema as MapContainerShape
68
+ const result: Record<string, unknown> = {}
69
+
70
+ // Validate each property in the map shape
71
+ for (const [key, nestedSchema] of Object.entries(mapSchema.shapes)) {
72
+ const nestedPath = `${currentPath}.${key}`
73
+ const nestedValue = (value as Record<string, unknown>)[key]
74
+ result[key] = validateValue(nestedValue, nestedSchema, nestedPath)
75
+ }
76
+ return result
77
+ }
78
+
79
+ if (schema._type === "record") {
80
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
81
+ throw new Error(
82
+ `Expected object at path ${currentPath}, got ${typeof value}`,
83
+ )
84
+ }
85
+ const recordSchema = schema as RecordContainerShape
86
+ const result: Record<string, unknown> = {}
87
+
88
+ // Validate each property in the record
89
+ for (const [key, nestedValue] of Object.entries(value)) {
90
+ const nestedPath = `${currentPath}.${key}`
91
+ result[key] = validateValue(nestedValue, recordSchema.shape, nestedPath)
92
+ }
93
+ return result
94
+ }
95
+
96
+ if (schema._type === "tree") {
97
+ if (!Array.isArray(value)) {
98
+ throw new Error(
99
+ `Expected array for tree at path ${currentPath}, got ${typeof value}`,
100
+ )
101
+ }
102
+ // Trees can contain any structure, so we just validate it's an array
103
+ return value
104
+ }
105
+
106
+ // Handle ValueShape types
107
+ if (schema._type === "value") {
108
+ const valueSchema = schema as ValueShape
109
+
110
+ switch (valueSchema.valueType) {
111
+ case "string":
112
+ if (typeof value !== "string") {
113
+ throw new Error(
114
+ `Expected string at path ${currentPath}, got ${typeof value}`,
115
+ )
116
+ }
117
+ return value
118
+
119
+ case "number":
120
+ if (typeof value !== "number") {
121
+ throw new Error(
122
+ `Expected number at path ${currentPath}, got ${typeof value}`,
123
+ )
124
+ }
125
+ return value
126
+
127
+ case "boolean":
128
+ if (typeof value !== "boolean") {
129
+ throw new Error(
130
+ `Expected boolean at path ${currentPath}, got ${typeof value}`,
131
+ )
132
+ }
133
+ return value
134
+
135
+ case "null":
136
+ if (value !== null) {
137
+ throw new Error(
138
+ `Expected null at path ${currentPath}, got ${typeof value}`,
139
+ )
140
+ }
141
+ return value
142
+
143
+ case "undefined":
144
+ if (value !== undefined) {
145
+ throw new Error(
146
+ `Expected undefined at path ${currentPath}, got ${typeof value}`,
147
+ )
148
+ }
149
+ return value
150
+
151
+ case "uint8array":
152
+ if (!(value instanceof Uint8Array)) {
153
+ throw new Error(
154
+ `Expected Uint8Array at path ${currentPath}, got ${typeof value}`,
155
+ )
156
+ }
157
+ return value
158
+
159
+ case "object": {
160
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
161
+ throw new Error(
162
+ `Expected object at path ${currentPath}, got ${typeof value}`,
163
+ )
164
+ }
165
+ const objectSchema = valueSchema as ObjectValueShape
166
+ const result: Record<string, unknown> = {}
167
+
168
+ // Validate each property in the object shape
169
+ for (const [key, nestedSchema] of Object.entries(objectSchema.shape)) {
170
+ const nestedPath = `${currentPath}.${key}`
171
+ const nestedValue = (value as Record<string, unknown>)[key]
172
+ result[key] = validateValue(nestedValue, nestedSchema, nestedPath)
173
+ }
174
+ return result
175
+ }
176
+
177
+ case "record": {
178
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
179
+ throw new Error(
180
+ `Expected object at path ${currentPath}, got ${typeof value}`,
181
+ )
182
+ }
183
+ const recordSchema = valueSchema as RecordValueShape
184
+ const result: Record<string, unknown> = {}
185
+
186
+ // Validate each property in the record
187
+ for (const [key, nestedValue] of Object.entries(value)) {
188
+ const nestedPath = `${currentPath}.${key}`
189
+ result[key] = validateValue(
190
+ nestedValue,
191
+ recordSchema.shape,
192
+ nestedPath,
193
+ )
194
+ }
195
+ return result
196
+ }
197
+
198
+ case "array": {
199
+ if (!Array.isArray(value)) {
200
+ throw new Error(
201
+ `Expected array at path ${currentPath}, got ${typeof value}`,
202
+ )
203
+ }
204
+ const arraySchema = valueSchema as ArrayValueShape
205
+ return value.map((item, index) =>
206
+ validateValue(item, arraySchema.shape, `${currentPath}[${index}]`),
207
+ )
208
+ }
209
+
210
+ case "union": {
211
+ const unionSchema = valueSchema as UnionValueShape
212
+ let lastError: Error | null = null
213
+
214
+ // Try to validate against each shape in the union
215
+ for (const shape of unionSchema.shapes) {
216
+ try {
217
+ return validateValue(value, shape, currentPath)
218
+ } catch (error) {
219
+ lastError = error as Error
220
+ }
221
+ }
222
+
223
+ throw new Error(
224
+ `Value at path ${currentPath} does not match any union type: ${lastError?.message}`,
225
+ )
226
+ }
227
+
228
+ default:
229
+ throw new Error(`Unknown value type: ${(valueSchema as any).valueType}`)
230
+ }
231
+ }
232
+
233
+ throw new Error(`Unknown schema type: ${(schema as any)._type}`)
234
+ }
235
+
236
+ /**
237
+ * Validates empty state against schema structure without using Zod
238
+ * Combines the functionality of createEmptyStateValidator and createValueValidator
239
+ */
240
+ export function validateEmptyState<T extends DocShape>(
241
+ emptyState: unknown,
242
+ schema: T,
243
+ ): InferPlainType<T> {
244
+ if (
245
+ !emptyState ||
246
+ typeof emptyState !== "object" ||
247
+ Array.isArray(emptyState)
248
+ ) {
249
+ throw new Error("Empty state must be an object")
250
+ }
251
+
252
+ const result: Record<string, unknown> = {}
253
+
254
+ // Validate each property in the document schema
255
+ for (const [key, schemaValue] of Object.entries(schema.shapes)) {
256
+ const value = (emptyState as Record<string, unknown>)[key]
257
+ result[key] = validateValue(value, schemaValue, key)
258
+ }
259
+
260
+ return result as InferPlainType<T>
261
+ }