@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.
@@ -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
+ ```