@loro-extended/change 0.6.0 → 0.8.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/src/types.ts CHANGED
@@ -5,21 +5,53 @@
5
5
 
6
6
  import type { ContainerShape, DocShape, Shape } from "./shape.js"
7
7
 
8
- // Input type inference - what developers can pass to push/insert methods
9
- export type InferPlainType<T> = T extends Shape<infer P, any, any> ? P : never
8
+ /**
9
+ * Infers the plain (JSON-serializable) type from any Shape.
10
+ *
11
+ * This is the recommended way to extract types from shapes.
12
+ * Works with DocShape, ContainerShape, and ValueShape.
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * const ChatSchema = Shape.doc({
17
+ * messages: Shape.list(Shape.map({
18
+ * id: Shape.plain.string(),
19
+ * content: Shape.text(),
20
+ * })),
21
+ * })
22
+ *
23
+ * // Extract the document type
24
+ * type ChatDoc = Infer<typeof ChatSchema>
25
+ * // Result: { messages: { id: string; content: string }[] }
26
+ *
27
+ * const PresenceSchema = Shape.plain.object({
28
+ * name: Shape.plain.string(),
29
+ * cursor: Shape.plain.object({ x: Shape.plain.number(), y: Shape.plain.number() }),
30
+ * })
31
+ *
32
+ * // Extract the presence type
33
+ * type Presence = Infer<typeof PresenceSchema>
34
+ * // Result: { name: string; cursor: { x: number; y: number } }
35
+ * ```
36
+ */
37
+ export type Infer<T> = T extends Shape<infer P, any, any> ? P : never
10
38
 
11
39
  export type InferDraftType<T> = T extends Shape<any, infer D, any> ? D : never
12
40
 
13
41
  /**
14
- * Extracts the valid empty state type from a shape.
42
+ * Extracts the valid placeholder type from a shape.
15
43
  *
16
44
  * For dynamic containers (list, record, etc.), this will be constrained to
17
45
  * empty values ([] or {}) to prevent users from expecting per-entry merging.
18
46
  */
19
- export type InferEmptyStateType<T> = T extends Shape<any, any, infer E>
20
- ? E
47
+ export type InferPlaceholderType<T> = T extends Shape<any, any, infer P>
48
+ ? P
21
49
  : never
22
50
 
23
51
  // Draft-specific type inference that properly handles the draft context
24
52
  export type Draft<T extends DocShape<Record<string, ContainerShape>>> =
25
53
  InferDraftType<T>
54
+
55
+ export type DeepReadonly<T> = {
56
+ readonly [P in keyof T]: DeepReadonly<T[P]>
57
+ }
@@ -197,6 +197,7 @@ export function isValueShape(
197
197
  "record",
198
198
  "array",
199
199
  "union",
200
+ "discriminatedUnion",
200
201
  ].includes(schema.valueType)
201
202
  )
202
203
  }
package/src/validation.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type {
2
2
  ArrayValueShape,
3
3
  ContainerOrValueShape,
4
+ DiscriminatedUnionValueShape,
4
5
  DocShape,
5
6
  ListContainerShape,
6
7
  MapContainerShape,
@@ -12,7 +13,7 @@ import type {
12
13
  UnionValueShape,
13
14
  ValueShape,
14
15
  } from "./shape.js"
15
- import type { InferPlainType } from "./types.js"
16
+ import type { Infer } from "./types.js"
16
17
 
17
18
  /**
18
19
  * Validates a value against a ContainerShape or ValueShape schema
@@ -233,6 +234,38 @@ export function validateValue(
233
234
  )
234
235
  }
235
236
 
237
+ case "discriminatedUnion": {
238
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
239
+ throw new Error(
240
+ `Expected object at path ${currentPath}, got ${typeof value}`,
241
+ )
242
+ }
243
+
244
+ const unionSchema = valueSchema as DiscriminatedUnionValueShape
245
+ const discriminantKey = unionSchema.discriminantKey
246
+ const discriminantValue = (value as Record<string, unknown>)[
247
+ discriminantKey
248
+ ]
249
+
250
+ if (typeof discriminantValue !== "string") {
251
+ throw new Error(
252
+ `Expected string for discriminant key "${discriminantKey}" at path ${currentPath}, got ${typeof discriminantValue}`,
253
+ )
254
+ }
255
+
256
+ const variantSchema = unionSchema.variants[discriminantValue]
257
+
258
+ if (!variantSchema) {
259
+ throw new Error(
260
+ `Invalid discriminant value "${discriminantValue}" at path ${currentPath}. Expected one of: ${Object.keys(
261
+ unionSchema.variants,
262
+ ).join(", ")}`,
263
+ )
264
+ }
265
+
266
+ return validateValue(value, variantSchema, currentPath)
267
+ }
268
+
236
269
  default:
237
270
  throw new Error(`Unknown value type: ${(valueSchema as any).valueType}`)
238
271
  }
@@ -242,28 +275,28 @@ export function validateValue(
242
275
  }
243
276
 
244
277
  /**
245
- * Validates empty state against schema structure without using Zod
246
- * Combines the functionality of createEmptyStateValidator and createValueValidator
278
+ * Validates placeholder against schema structure without using Zod
279
+ * Combines the functionality of createPlaceholderValidator and createValueValidator
247
280
  */
248
- export function validateEmptyState<T extends DocShape>(
249
- emptyState: unknown,
281
+ export function validatePlaceholder<T extends DocShape>(
282
+ placeholder: unknown,
250
283
  schema: T,
251
- ): InferPlainType<T> {
284
+ ): Infer<T> {
252
285
  if (
253
- !emptyState ||
254
- typeof emptyState !== "object" ||
255
- Array.isArray(emptyState)
286
+ !placeholder ||
287
+ typeof placeholder !== "object" ||
288
+ Array.isArray(placeholder)
256
289
  ) {
257
- throw new Error("Empty state must be an object")
290
+ throw new Error("Placeholder must be an object")
258
291
  }
259
292
 
260
293
  const result: Record<string, unknown> = {}
261
294
 
262
295
  // Validate each property in the document schema
263
296
  for (const [key, schemaValue] of Object.entries(schema.shapes)) {
264
- const value = (emptyState as Record<string, unknown>)[key]
297
+ const value = (placeholder as Record<string, unknown>)[key]
265
298
  result[key] = validateValue(value, schemaValue, key)
266
299
  }
267
300
 
268
- return result as InferPlainType<T>
301
+ return result as Infer<T>
269
302
  }