@atscript/typescript 0.1.19 → 0.1.20
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/dist/utils.cjs +88 -7
- package/dist/utils.d.ts +39 -2
- package/dist/utils.mjs +88 -8
- package/package.json +10 -5
- package/scripts/setup-skills.js +78 -0
- package/skills/atscript-typescript/.gitkeep +0 -0
- package/skills/atscript-typescript/SKILL.md +44 -0
- package/skills/atscript-typescript/annotations.md +240 -0
- package/skills/atscript-typescript/codegen.md +120 -0
- package/skills/atscript-typescript/core.md +153 -0
- package/skills/atscript-typescript/runtime.md +276 -0
- package/skills/atscript-typescript/syntax.md +252 -0
- package/skills/atscript-typescript/utilities.md +323 -0
- package/skills/atscript-typescript/validation.md +293 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
# Runtime Type System — @atscript/typescript
|
|
2
|
+
|
|
3
|
+
> Understanding `TAtscriptAnnotatedType`, reading/writing metadata, walking type definitions, and type structure.
|
|
4
|
+
|
|
5
|
+
## `TAtscriptAnnotatedType`
|
|
6
|
+
|
|
7
|
+
Every generated interface/type is a `TAtscriptAnnotatedType` — the core runtime representation:
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
interface TAtscriptAnnotatedType<T extends TAtscriptTypeDef = TAtscriptTypeDef> {
|
|
11
|
+
__is_atscript_annotated_type: true // brand for type checking
|
|
12
|
+
type: T // the type definition (shape)
|
|
13
|
+
metadata: TMetadataMap<AtscriptMetadata> // annotation metadata
|
|
14
|
+
optional?: boolean // whether this type is optional
|
|
15
|
+
validator(opts?): Validator // create a validator instance
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Checking if Something is an Annotated Type
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { isAnnotatedType } from '@atscript/typescript/utils'
|
|
23
|
+
|
|
24
|
+
if (isAnnotatedType(value)) {
|
|
25
|
+
// value is TAtscriptAnnotatedType
|
|
26
|
+
value.metadata.get('meta.label')
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Type Definitions (`type.kind`)
|
|
31
|
+
|
|
32
|
+
The `type` field describes the shape. There are 5 kinds:
|
|
33
|
+
|
|
34
|
+
### Final (Primitive) — `kind: ''`
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
interface TAtscriptTypeFinal {
|
|
38
|
+
kind: ''
|
|
39
|
+
designType: 'string' | 'number' | 'boolean' | 'undefined' | 'null' | 'any' | 'never' | 'phantom'
|
|
40
|
+
value?: string | number | boolean // for literal types
|
|
41
|
+
tags: Set<AtscriptPrimitiveTags>
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Object — `kind: 'object'`
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
interface TAtscriptTypeObject<K extends string = string> {
|
|
49
|
+
kind: 'object'
|
|
50
|
+
props: Map<K, TAtscriptAnnotatedType> // named properties
|
|
51
|
+
propsPatterns: Array<{ pattern: RegExp; def: TAtscriptAnnotatedType }> // pattern properties
|
|
52
|
+
tags: Set<AtscriptPrimitiveTags>
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Array — `kind: 'array'`
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
interface TAtscriptTypeArray {
|
|
60
|
+
kind: 'array'
|
|
61
|
+
of: TAtscriptAnnotatedType // element type
|
|
62
|
+
tags: Set<AtscriptPrimitiveTags>
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Complex (Union/Intersection/Tuple) — `kind: 'union' | 'intersection' | 'tuple'`
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
interface TAtscriptTypeComplex {
|
|
70
|
+
kind: 'union' | 'intersection' | 'tuple'
|
|
71
|
+
items: TAtscriptAnnotatedType[]
|
|
72
|
+
tags: Set<AtscriptPrimitiveTags>
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Reading Metadata
|
|
77
|
+
|
|
78
|
+
The `metadata` field is a typed `Map<keyof AtscriptMetadata, value>`:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import { User } from './models/user.as'
|
|
82
|
+
|
|
83
|
+
// Read annotations
|
|
84
|
+
const label = User.metadata.get('meta.label') // string | undefined
|
|
85
|
+
const required = User.metadata.get('meta.required') // { message?: string } | true | undefined
|
|
86
|
+
const minLen = User.metadata.get('expect.minLength') // { length: number; message?: string } | undefined
|
|
87
|
+
|
|
88
|
+
// Check if annotation exists
|
|
89
|
+
User.metadata.has('meta.sensitive') // boolean
|
|
90
|
+
|
|
91
|
+
// Iterate all annotations
|
|
92
|
+
for (const [key, value] of User.metadata.entries()) {
|
|
93
|
+
console.log(key, value)
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Reading Property Metadata
|
|
98
|
+
|
|
99
|
+
Navigate into object properties via `type.props`:
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
// Get a property's annotated type
|
|
103
|
+
const nameProp = User.type.props.get('name')!
|
|
104
|
+
|
|
105
|
+
// Read that property's metadata
|
|
106
|
+
nameProp.metadata.get('meta.label') // "Full Name"
|
|
107
|
+
nameProp.metadata.get('meta.required') // true or { message: "..." }
|
|
108
|
+
|
|
109
|
+
// Check if optional
|
|
110
|
+
nameProp.optional // boolean | undefined
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Nested Properties
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
const addressProp = User.type.props.get('address')!
|
|
117
|
+
if (addressProp.type.kind === 'object') {
|
|
118
|
+
const cityProp = addressProp.type.props.get('city')!
|
|
119
|
+
cityProp.metadata.get('meta.label')
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Writing Metadata at Runtime
|
|
124
|
+
|
|
125
|
+
Use the `annotate()` function for safe metadata mutation:
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
import { annotate } from '@atscript/typescript/utils'
|
|
129
|
+
|
|
130
|
+
// Set a single annotation value
|
|
131
|
+
annotate(User.metadata, 'meta.label', 'Updated Label')
|
|
132
|
+
|
|
133
|
+
// Append to an array annotation (asArray = true)
|
|
134
|
+
annotate(User.metadata, 'expect.pattern', { pattern: '^[A-Z]' }, true)
|
|
135
|
+
|
|
136
|
+
// Direct Map operations also work
|
|
137
|
+
User.metadata.set('meta.label', 'Direct Set')
|
|
138
|
+
User.metadata.delete('meta.sensitive')
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
The `annotate()` function handles array annotations correctly — if `asArray` is `true`, it appends to existing arrays or creates a new array.
|
|
142
|
+
|
|
143
|
+
## Walking Type Definitions
|
|
144
|
+
|
|
145
|
+
### Manual `switch` on `type.kind`
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
function inspect(def: TAtscriptAnnotatedType) {
|
|
149
|
+
switch (def.type.kind) {
|
|
150
|
+
case '': // final/primitive
|
|
151
|
+
console.log('Primitive:', def.type.designType)
|
|
152
|
+
break
|
|
153
|
+
case 'object': // object with props
|
|
154
|
+
for (const [name, prop] of def.type.props) {
|
|
155
|
+
console.log(` ${name}:`, prop.type.kind || prop.type.designType)
|
|
156
|
+
}
|
|
157
|
+
break
|
|
158
|
+
case 'array': // array
|
|
159
|
+
console.log('Array of:', def.type.of.type.kind)
|
|
160
|
+
break
|
|
161
|
+
case 'union':
|
|
162
|
+
case 'intersection':
|
|
163
|
+
case 'tuple':
|
|
164
|
+
console.log(`${def.type.kind} with ${def.type.items.length} items`)
|
|
165
|
+
break
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Using `forAnnotatedType()` Helper
|
|
171
|
+
|
|
172
|
+
A type-safe dispatch helper that covers all `kind` values:
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
import { forAnnotatedType } from '@atscript/typescript/utils'
|
|
176
|
+
|
|
177
|
+
const result = forAnnotatedType(someType, {
|
|
178
|
+
final(d) { return `primitive: ${d.type.designType}` },
|
|
179
|
+
object(d) { return `object with ${d.type.props.size} props` },
|
|
180
|
+
array(d) { return `array` },
|
|
181
|
+
union(d) { return `union of ${d.type.items.length}` },
|
|
182
|
+
intersection(d) { return `intersection of ${d.type.items.length}` },
|
|
183
|
+
tuple(d) { return `tuple of ${d.type.items.length}` },
|
|
184
|
+
// Optional: handle phantom types separately from final
|
|
185
|
+
phantom(d) { return `phantom` },
|
|
186
|
+
})
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
If `phantom` handler is provided, phantom types (`designType === 'phantom'`) are dispatched there instead of `final`. This allows consumers to skip phantom props cleanly.
|
|
190
|
+
|
|
191
|
+
## Tags
|
|
192
|
+
|
|
193
|
+
Each type definition has a `tags` Set containing primitive tags (e.g. `"string"`, `"number"`):
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
const nameProp = User.type.props.get('name')!
|
|
197
|
+
nameProp.type.tags.has('string') // true
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Tags come from primitive definitions and their extensions. They're useful for categorizing types at runtime.
|
|
201
|
+
|
|
202
|
+
## Phantom Types
|
|
203
|
+
|
|
204
|
+
Phantom types carry metadata but don't affect validation or data shape:
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
import { isPhantomType } from '@atscript/typescript/utils'
|
|
208
|
+
|
|
209
|
+
for (const [name, prop] of User.type.props) {
|
|
210
|
+
if (isPhantomType(prop)) {
|
|
211
|
+
// Skip phantom props in data processing
|
|
212
|
+
continue
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Checking Primitive Types
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
import { isAnnotatedTypeOfPrimitive } from '@atscript/typescript/utils'
|
|
221
|
+
|
|
222
|
+
// Returns true for final types and unions/intersections/tuples of all primitives
|
|
223
|
+
isAnnotatedTypeOfPrimitive(someType) // true if no objects or arrays
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Building Types at Runtime
|
|
227
|
+
|
|
228
|
+
Use `defineAnnotatedType()` to construct types programmatically:
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
import { defineAnnotatedType } from '@atscript/typescript/utils'
|
|
232
|
+
|
|
233
|
+
// Primitive
|
|
234
|
+
const strType = defineAnnotatedType().designType('string').tags('string').$type
|
|
235
|
+
|
|
236
|
+
// Object
|
|
237
|
+
const userType = defineAnnotatedType('object')
|
|
238
|
+
.prop('name', defineAnnotatedType().designType('string').$type)
|
|
239
|
+
.prop('age', defineAnnotatedType().designType('number').$type)
|
|
240
|
+
.prop('email', defineAnnotatedType().optional().designType('string').$type)
|
|
241
|
+
.$type
|
|
242
|
+
|
|
243
|
+
// Array
|
|
244
|
+
const listType = defineAnnotatedType('array')
|
|
245
|
+
.of(defineAnnotatedType().designType('string').$type)
|
|
246
|
+
.$type
|
|
247
|
+
|
|
248
|
+
// Union
|
|
249
|
+
const statusType = defineAnnotatedType('union')
|
|
250
|
+
.item(defineAnnotatedType().designType('string').value('active').$type)
|
|
251
|
+
.item(defineAnnotatedType().designType('string').value('inactive').$type)
|
|
252
|
+
.$type
|
|
253
|
+
|
|
254
|
+
// With metadata
|
|
255
|
+
const labeledType = defineAnnotatedType().designType('string')
|
|
256
|
+
.annotate('meta.label', 'My Label')
|
|
257
|
+
.annotate('expect.minLength', { length: 3 })
|
|
258
|
+
.$type
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### `TAnnotatedTypeHandle` Fluent API
|
|
262
|
+
|
|
263
|
+
| Method | Description |
|
|
264
|
+
|--------|-------------|
|
|
265
|
+
| `.designType(dt)` | Set primitive design type |
|
|
266
|
+
| `.value(v)` | Set literal value |
|
|
267
|
+
| `.tags(...tags)` | Add primitive tags |
|
|
268
|
+
| `.prop(name, type)` | Add named property (object kind) |
|
|
269
|
+
| `.propPattern(regex, type)` | Add pattern property (object kind) |
|
|
270
|
+
| `.of(type)` | Set element type (array kind) |
|
|
271
|
+
| `.item(type)` | Add item (union/intersection/tuple kind) |
|
|
272
|
+
| `.optional(flag?)` | Mark as optional |
|
|
273
|
+
| `.annotate(key, value, asArray?)` | Set metadata annotation |
|
|
274
|
+
| `.copyMetadata(from, ignore?)` | Copy metadata from another type |
|
|
275
|
+
| `.refTo(type, chain?)` | Reference another annotated type's definition |
|
|
276
|
+
| `.$type` | Get the final `TAtscriptAnnotatedType` |
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
# `.as` File Syntax — @atscript/typescript
|
|
2
|
+
|
|
3
|
+
> Writing Atscript files — interfaces, types, annotations, imports, exports, and property syntax.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
`.as` files look similar to TypeScript but add annotations (metadata) directly on types and properties. They compile to both `.d.ts` (type declarations) and `.js` (runtime metadata).
|
|
8
|
+
|
|
9
|
+
## Interfaces
|
|
10
|
+
|
|
11
|
+
Interfaces define object shapes with typed properties:
|
|
12
|
+
|
|
13
|
+
```as
|
|
14
|
+
interface User {
|
|
15
|
+
name: string
|
|
16
|
+
age: number
|
|
17
|
+
email?: string // optional property
|
|
18
|
+
active: boolean
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Exported Interfaces
|
|
23
|
+
|
|
24
|
+
```as
|
|
25
|
+
export interface User {
|
|
26
|
+
name: string
|
|
27
|
+
age: number
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Nested Objects
|
|
32
|
+
|
|
33
|
+
```as
|
|
34
|
+
interface User {
|
|
35
|
+
name: string
|
|
36
|
+
address: {
|
|
37
|
+
street: string
|
|
38
|
+
city: string
|
|
39
|
+
zip: number
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Types
|
|
45
|
+
|
|
46
|
+
Type aliases for unions, intersections, tuples, and primitives:
|
|
47
|
+
|
|
48
|
+
```as
|
|
49
|
+
type Status = "active" | "inactive" | "pending"
|
|
50
|
+
|
|
51
|
+
type StringOrNumber = string | number
|
|
52
|
+
|
|
53
|
+
type Pair = [string, number]
|
|
54
|
+
|
|
55
|
+
type ID = string
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Annotations
|
|
59
|
+
|
|
60
|
+
Annotations attach metadata to interfaces, types, or properties using `@` syntax:
|
|
61
|
+
|
|
62
|
+
```as
|
|
63
|
+
@meta.label "User Profile"
|
|
64
|
+
@meta.description "A registered user in the system"
|
|
65
|
+
interface User {
|
|
66
|
+
@meta.label "Full Name"
|
|
67
|
+
@meta.required
|
|
68
|
+
@expect.minLength 2
|
|
69
|
+
@expect.maxLength 100
|
|
70
|
+
name: string
|
|
71
|
+
|
|
72
|
+
@meta.label "Age"
|
|
73
|
+
@expect.min 0
|
|
74
|
+
@expect.max 150
|
|
75
|
+
@expect.int
|
|
76
|
+
age: number
|
|
77
|
+
|
|
78
|
+
@meta.label "Email Address"
|
|
79
|
+
@expect.pattern "^[^\s@]+@[^\s@]+\.[^\s@]+$"
|
|
80
|
+
email?: string
|
|
81
|
+
|
|
82
|
+
@meta.sensitive
|
|
83
|
+
password: string
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Annotation Syntax Rules
|
|
88
|
+
|
|
89
|
+
- Annotations go **above** the property or interface they annotate
|
|
90
|
+
- String arguments: `@meta.label "some text"`
|
|
91
|
+
- Number arguments: `@expect.min 0`
|
|
92
|
+
- No arguments (boolean flag): `@meta.sensitive`
|
|
93
|
+
- Multiple arguments: `@expect.pattern "^\\d+$" "i" "Must be digits"`
|
|
94
|
+
- Multiple annotations stack on the same target
|
|
95
|
+
|
|
96
|
+
### Annotate Blocks (Ad-hoc Annotations)
|
|
97
|
+
|
|
98
|
+
Add annotations to an existing interface without modifying it:
|
|
99
|
+
|
|
100
|
+
```as
|
|
101
|
+
// Non-mutating: creates a new alias with extra annotations
|
|
102
|
+
export annotate UserForm = User {
|
|
103
|
+
name {
|
|
104
|
+
@meta.placeholder "Enter your name"
|
|
105
|
+
}
|
|
106
|
+
email {
|
|
107
|
+
@meta.required
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Mutating: modifies the original interface's metadata
|
|
112
|
+
annotate User {
|
|
113
|
+
name {
|
|
114
|
+
@meta.placeholder "Enter your name"
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Primitive Types
|
|
120
|
+
|
|
121
|
+
### Built-in Primitives
|
|
122
|
+
|
|
123
|
+
| Primitive | Description |
|
|
124
|
+
|-----------|-------------|
|
|
125
|
+
| `string` | Text data |
|
|
126
|
+
| `number` | Numeric data |
|
|
127
|
+
| `boolean` | True/false |
|
|
128
|
+
| `null` | Null value |
|
|
129
|
+
| `void` / `undefined` | No value |
|
|
130
|
+
| `never` | Impossible type |
|
|
131
|
+
| `phantom` | Metadata-only type (no runtime/schema impact) |
|
|
132
|
+
|
|
133
|
+
### Primitive Extensions (Subtypes)
|
|
134
|
+
|
|
135
|
+
Use dot notation for specialized variants:
|
|
136
|
+
|
|
137
|
+
```as
|
|
138
|
+
interface User {
|
|
139
|
+
email: string.email // email pattern validation
|
|
140
|
+
phone: string.phone // phone number pattern
|
|
141
|
+
created: string.isoDate // ISO 8601 date
|
|
142
|
+
id: string.uuid // UUID v4 format
|
|
143
|
+
username: string.required // non-empty string
|
|
144
|
+
|
|
145
|
+
age: number.int // integer only
|
|
146
|
+
score: number.positive // >= 0
|
|
147
|
+
balance: number.negative // <= 0
|
|
148
|
+
timestamp: number.timestamp // integer timestamp
|
|
149
|
+
|
|
150
|
+
agreed: boolean.required // must be true (checkbox)
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
#### String Extensions
|
|
155
|
+
|
|
156
|
+
| Extension | Validation |
|
|
157
|
+
|-----------|-----------|
|
|
158
|
+
| `string.email` | Email format (`^[^\s@]+@[^\s@]+\.[^\s@]+$`) |
|
|
159
|
+
| `string.phone` | Phone format (`^\+?[0-9\s-]{10,15}$`) |
|
|
160
|
+
| `string.date` | Date string (YYYY-MM-DD, MM/DD/YYYY, etc.) |
|
|
161
|
+
| `string.isoDate` | ISO 8601 date/time |
|
|
162
|
+
| `string.uuid` | UUID v4 format |
|
|
163
|
+
| `string.required` | Non-empty (trimmed length >= 1) |
|
|
164
|
+
|
|
165
|
+
#### Number Extensions
|
|
166
|
+
|
|
167
|
+
| Extension | Validation |
|
|
168
|
+
|-----------|-----------|
|
|
169
|
+
| `number.int` | Integer (no decimals) |
|
|
170
|
+
| `number.positive` | >= 0 |
|
|
171
|
+
| `number.negative` | <= 0 |
|
|
172
|
+
| `number.single` | Single-precision float |
|
|
173
|
+
| `number.double` | Double-precision float |
|
|
174
|
+
| `number.timestamp` | Integer timestamp |
|
|
175
|
+
| `number.int.positive` | Integer >= 0 |
|
|
176
|
+
| `number.int.negative` | Integer <= 0 |
|
|
177
|
+
|
|
178
|
+
#### Boolean Extensions
|
|
179
|
+
|
|
180
|
+
| Extension | Validation |
|
|
181
|
+
|-----------|-----------|
|
|
182
|
+
| `boolean.required` | Must be `true` |
|
|
183
|
+
| `boolean.true` | Literal true |
|
|
184
|
+
| `boolean.false` | Literal false |
|
|
185
|
+
|
|
186
|
+
## Imports and Exports
|
|
187
|
+
|
|
188
|
+
```as
|
|
189
|
+
// Import from another .as file (no extension needed)
|
|
190
|
+
import { User, Address } from "./models/user"
|
|
191
|
+
|
|
192
|
+
// Export interfaces/types
|
|
193
|
+
export interface PublicUser {
|
|
194
|
+
name: string
|
|
195
|
+
email: string
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export type UserStatus = "active" | "inactive"
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Arrays
|
|
202
|
+
|
|
203
|
+
```as
|
|
204
|
+
interface Team {
|
|
205
|
+
members: User[] // array of User
|
|
206
|
+
scores: number[] // array of numbers
|
|
207
|
+
matrix: number[][] // nested array
|
|
208
|
+
tags: (string | number)[] // array of union
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Pattern Properties (Dynamic Keys)
|
|
213
|
+
|
|
214
|
+
```as
|
|
215
|
+
interface Config {
|
|
216
|
+
// Regular properties
|
|
217
|
+
name: string
|
|
218
|
+
|
|
219
|
+
// Pattern property — any key matching the regex
|
|
220
|
+
/^data_/: string // keys starting with "data_"
|
|
221
|
+
/^meta_/: number // keys starting with "meta_"
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Unions, Intersections, Tuples
|
|
226
|
+
|
|
227
|
+
```as
|
|
228
|
+
// Union types
|
|
229
|
+
type Result = Success | Error
|
|
230
|
+
|
|
231
|
+
// Intersection types
|
|
232
|
+
type Admin = User & Permissions
|
|
233
|
+
|
|
234
|
+
// Tuple types
|
|
235
|
+
type Coordinate = [number, number]
|
|
236
|
+
type Entry = [string, number, boolean]
|
|
237
|
+
|
|
238
|
+
// Literal unions
|
|
239
|
+
type Direction = "north" | "south" | "east" | "west"
|
|
240
|
+
type HttpCode = 200 | 400 | 404 | 500
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## Phantom Types
|
|
244
|
+
|
|
245
|
+
Phantom properties are metadata-only — they appear in the type system for traversal but don't affect data shape, validation, or JSON Schema:
|
|
246
|
+
|
|
247
|
+
```as
|
|
248
|
+
interface User {
|
|
249
|
+
name: string
|
|
250
|
+
collectionName: phantom // discoverable via type traversal, not validated
|
|
251
|
+
}
|
|
252
|
+
```
|