Skip to content

Commit

Permalink
Fix Mutate Object Check | Documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
sinclairzx81 committed Dec 18, 2024
1 parent dd3fc5f commit 4123c21
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 20 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@sinclair/typebox",
"version": "0.34.11",
"version": "0.34.12",
"description": "Json Schema Type Builder with Static Type Resolution for TypeScript",
"keywords": [
"typescript",
Expand Down
52 changes: 39 additions & 13 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ License MIT
- [Options](#types-options)
- [Properties](#types-properties)
- [Generics](#types-generics)
- [Recursive](#types-recursive)
- [Modules](#types-modules)
- [Template Literal](#types-template-literal)
- [Indexed](#types-indexed)
Expand Down Expand Up @@ -759,33 +760,59 @@ const T = Nullable(Type.String()) // const T = {
type T = Static<typeof T> // type T = string | null
```
<a name='types-modules'></a>
<a name='types-recursive'></a>
### Module Types
### Recursive Types
TypeBox Modules are containers for related types. They provide a referential namespace, enabling types to reference one another via string identifiers. Modules support both singular and mutually recursive referencing within the context of a module, as well as referential computed types (such as Partial + Ref). All Module types must be imported before use. TypeBox represents an imported type with the `$defs` Json Schema keyword.
Use the Recursive function to create a singular recursive type.
#### Usage
```typescript
const Node = Type.Recursive(Self => Type.Object({ // const Node = {
id: Type.String(), // $id: 'Node',
nodes: Type.Array(Self) // type: 'object',
}), { $id: 'Node' }) // properties: {
// id: {
// type: 'string'
// },
// nodes: {
// type: 'array',
// items: {
// $ref: 'Node'
// }
// }
// },
// required: [
// 'id',
// 'nodes'
// ]
// }

The following creates a Module with User and PartialUser types. Note that the PartialUser type is specified as a Partial + Ref to the User type. It is not possible to perform a Partial operation directly on a reference, so TypeBox will return a TComputed type that defers the Partial operation until all types are resolved. The TComputed type is evaluated when calling Import on the Module.
type Node = Static<typeof Node> // type Node = {
// id: string
// nodes: Node[]
// }

```typescript
// Module with PartialUser and User types
function test(node: Node) {
const id = node.nodes[0].nodes[0].id // id is string
}
```
const Module = Type.Module({
<a name='types-modules'></a>
### Module Types
Module types are containers for a set of referential types. Modules act as namespaces, enabling types to reference one another via string identifiers. Modules support both singular and mutually recursive references, as well as deferred dereferencing for computed types such as Partial. Types imported from a module are expressed using the Json Schema `$defs` keyword.
```typescript
const Module = Type.Module({
PartialUser: Type.Partial(Type.Ref('User')), // TComputed<'Partial', [TRef<'User'>]>

User: Type.Object({ // TObject<{
id: Type.String(), // user: TString,
name: Type.String(), // name: TString,
email: Type.String() // email: TString
}), // }>

})

// Types must be imported before use.

const User = Module.Import('User') // const User: TImport<{...}, 'User'>

type User = Static<typeof User> // type User = {
Expand All @@ -803,7 +830,6 @@ type PartialUser = Static<typeof PartialUser> // type PartialUser = {
// }
```
<a name='types-template-literal'></a>
### Template Literal Types
Expand Down
14 changes: 10 additions & 4 deletions src/value/mutate/mutate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ import { ValuePointer } from '../pointer/index'
import { Clone } from '../clone/index'
import { TypeBoxError } from '../../type/error/index'

// ------------------------------------------------------------------
// IsStandardObject
// ------------------------------------------------------------------
function IsStandardObject(value: unknown): value is Record<PropertyKey, unknown> {
return IsObject(value) && !IsArray(value)
}
// ------------------------------------------------------------------
// Errors
// ------------------------------------------------------------------
Expand All @@ -44,7 +50,7 @@ export class ValueMutateError extends TypeBoxError {
// ------------------------------------------------------------------
export type Mutable = { [key: string]: unknown } | unknown[]
function ObjectType(root: Mutable, path: string, current: unknown, next: Record<string, unknown>) {
if (!IsObject(current)) {
if (!IsStandardObject(current)) {
ValuePointer.Set(root, path, Clone(next))
} else {
const currentKeys = Object.getOwnPropertyNames(current)
Expand Down Expand Up @@ -90,7 +96,7 @@ function ValueType(root: Mutable, path: string, current: unknown, next: unknown)
function Visit(root: Mutable, path: string, current: unknown, next: unknown) {
if (IsArray(next)) return ArrayType(root, path, current, next)
if (IsTypedArray(next)) return TypedArrayType(root, path, current, next)
if (IsObject(next)) return ObjectType(root, path, current, next)
if (IsStandardObject(next)) return ObjectType(root, path, current, next)
if (IsValueType(next)) return ValueType(root, path, current, next)
}
// ------------------------------------------------------------------
Expand All @@ -102,8 +108,8 @@ function IsNonMutableValue(value: unknown): value is Mutable {
function IsMismatchedValue(current: unknown, next: unknown) {
// prettier-ignore
return (
(IsObject(current) && IsArray(next)) ||
(IsArray(current) && IsObject(next))
(IsStandardObject(current) && IsArray(next)) ||
(IsArray(current) && IsStandardObject(next))
)
}
// ------------------------------------------------------------------
Expand Down
28 changes: 28 additions & 0 deletions test/runtime/value/mutate/mutate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,32 @@ describe('value/mutate/Mutate', () => {
Assert.NotEqual(A.x, X)
Assert.IsEqual(A.x, [1, 2, 3])
})
// ----------------------------------------------------------------
// https://github.com/sinclairzx81/typebox/issues/1119
// ----------------------------------------------------------------
it('Should mutate array 1', () => {
const A: unknown[] = []
Value.Mutate(A, [])
Assert.IsEqual(A, [])
})
it('Should mutate array 2', () => {
const A: unknown[] = []
Value.Mutate(A, [1])
Assert.IsEqual(A, [1])
})
it('Should mutate array 3', () => {
const A: unknown[] = [1, 2, 3]
Value.Mutate(A, [1, 2])
Assert.IsEqual(A, [1, 2])
})
it('Should mutate array 4', () => {
const A: unknown[] = [1, 2, 3]
Value.Mutate(A, [1, 2, 3, 4])
Assert.IsEqual(A, [1, 2, 3, 4])
})
it('Should mutate array 5', () => {
const A: unknown[] = [1, 2, 3]
Value.Mutate(A, [{}, {}, {}, [1, 2, 3]])
Assert.IsEqual(A, [{}, {}, {}, [1, 2, 3]])
})
})

0 comments on commit 4123c21

Please sign in to comment.