@weipertda/sigiljs 0.0.3 → 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.
Files changed (49) hide show
  1. package/README.md +157 -155
  2. package/docs/README.md +20 -0
  3. package/docs/arrays.md +55 -0
  4. package/docs/cli.md +48 -0
  5. package/docs/compiled-validators.md +42 -0
  6. package/docs/contributing.md +43 -0
  7. package/docs/exact-mode.md +82 -0
  8. package/docs/examples.md +92 -0
  9. package/docs/introduction.md +29 -0
  10. package/docs/license.md +23 -0
  11. package/docs/named-sigils.md +93 -0
  12. package/docs/objects.md +44 -0
  13. package/docs/optional.md +48 -0
  14. package/docs/quickstart.md +65 -0
  15. package/docs/realtype.md +36 -0
  16. package/docs/roadmap.md +40 -0
  17. package/docs/sigils.md +94 -0
  18. package/docs/{functions.md,maps.md,sets.md,tuples.md,intersection.md,rest.md,examples.md,performance.md,contributing.md,license.md +0 -0
  19. package/examples/api-response.js +21 -0
  20. package/examples/api-response.md +27 -0
  21. package/examples/api-validation.js +0 -0
  22. package/examples/api-validation.md +0 -0
  23. package/examples/basic-user.js +16 -0
  24. package/examples/basic-user.md +22 -0
  25. package/examples/basic-validation.js +0 -0
  26. package/examples/basic-validation.md +0 -0
  27. package/examples/config-file.md +16 -0
  28. package/examples/config-validation.js +18 -0
  29. package/examples/config-validation.md +24 -0
  30. package/examples/login-request.js +19 -0
  31. package/examples/login-request.md +25 -0
  32. package/examples/nested-order.js +28 -0
  33. package/examples/nested-order.md +34 -0
  34. package/examples/realtype-demo.js +6 -0
  35. package/examples/realtype-demo.md +12 -0
  36. package/examples/scripts/config-file.js +0 -0
  37. package/package.json +14 -6
  38. package/src/core/assert.js +173 -50
  39. package/src/core/compile.js +43 -9
  40. package/src/core/errors.js +29 -6
  41. package/src/core/normalize.js +1 -1
  42. package/src/core/parser.js +4 -3
  43. package/src/core/partial.js +1 -0
  44. package/src/core/registry.js +33 -0
  45. package/src/core/validate.js +3 -0
  46. package/src/index.js +1 -0
  47. package/src/playground/playground.js +1 -1
  48. package/src/sigil.js +56 -16
  49. package/CHANGELOG.md +0 -28
package/README.md CHANGED
@@ -2,260 +2,262 @@
2
2
 
3
3
  Write types. Validate reality.
4
4
 
5
- ## What is SigilJS?
5
+ SigilJS is a tiny, dependency-free JavaScript library for describing and validating data shapes using **sigils** (inline type expressions).
6
6
 
7
- SigilJS is a tiny JavaScript library for describing and validating data using **sigils**.
8
-
9
- A sigil is a small expression (type expression) that describes what your data should look like.
10
-
11
- Sigils are compiled into fast validators, so repeated checks stay efficient.
12
-
13
-
14
- No TypeScript.
15
-
16
- No dependencies.
17
-
18
- Just JavaScript.
7
+ No TypeScript compiler, no heavy schemas, and zero runtime dependencies. Just write your types as strings, and SigilJS compiles them into blazingly fast validator functions.
19
8
 
20
9
  ---
21
10
 
22
11
  ## Installation
23
12
 
13
+ Install using **Bun**:
24
14
  ```bash
25
-
26
- bun add @weipertda/sigiljs
27
-
15
+ bun add @antistructured/sigiljs
28
16
  ```
29
17
 
30
- or
31
-
18
+ Or using **npm**:
32
19
  ```bash
33
-
34
- npm install @weipertda/sigiljs
35
-
20
+ npm install @antistructured/sigiljs
36
21
  ```
37
22
 
38
23
  ---
39
24
 
40
- ### Create a Sigil
41
-
42
- ```javascript
43
-
44
- import { Sigil } from "@weipertda/sigiljs"
45
-
46
- const Email = Sigil`string`
47
-
48
- ```
49
-
50
- ### Validate a Value
51
-
52
- ```javascript
53
-
54
- Email.check("hello@example.com")
55
- // true
56
-
57
- Email.check(42)
58
- // false
59
-
60
- ```
61
-
62
- ### Optional Values
63
-
64
- Use `?` to mark optional values.
65
-
66
- ```javascript
67
-
68
- const MaybeName = Sigil`string?`
69
-
70
- ```
71
-
72
- Matches:
25
+ ## Quick Example
73
26
 
74
27
  ```javascript
28
+ import { Sigil } from "@antistructured/sigiljs";
75
29
 
76
- string
77
- undefined
78
-
30
+ // 1. Define your data schema using backticks
31
+ const User = Sigil`
32
+ {
33
+ name: string
34
+ age?: number
35
+ tags: string[]
36
+ }
37
+ `;
38
+
39
+ // 2. Validate data shapes (returns boolean)
40
+ User.check({
41
+ name: "Alice",
42
+ tags: ["admin", "dev"]
43
+ }); // true
44
+
45
+ User.check({
46
+ name: "Bob",
47
+ age: "thirty", // Wrong type!
48
+ tags: []
49
+ }); // false
79
50
  ```
80
51
 
81
52
  ---
82
53
 
83
- ### Arrays
84
-
85
- ```javascript
86
-
87
- const Tags = Sigil`string[]`
54
+ ## Why SigilJS?
88
55
 
89
- Tags.check(["js", "bun"])
90
- // true
56
+ JavaScript runtime type checking is notoriously fragile:
57
+ 1. `typeof` is notoriously weak and inconsistent (e.g., `typeof []` is `"object"`).
58
+ 2. TypeScript types completely disappear at runtime, leaving you vulnerable to invalid API responses, bad database loads, and malformed client payloads.
59
+ 3. Existing schema libraries (like Zod) require verbose builder chains and carry significant bundle sizes.
91
60
 
92
- ```
61
+ SigilJS solves this by giving you a **clean, string-based type DSL** that looks like standard TypeScript/flow but remains fully active and optimized at runtime.
93
62
 
94
63
  ---
95
64
 
96
- ### Unions
97
-
98
- ```javascript
65
+ ## Sigil vs Zod
99
66
 
100
- const ID = Sigil`string | number`
67
+ If you have used validation libraries like Zod, you might wonder how SigilJS compares. Zod is a robust, highly expressive builder library with a powerful ecosystem. However, SigilJS approach is built on a different developer philosophy:
101
68
 
102
- ```
69
+ * **Zod** uses an **expressive builder API** to construct validation trees.
70
+ * **SigilJS** uses **readable type expressions** that look like standard JavaScript/TypeScript types, compiled once into optimized runtime validator functions.
103
71
 
104
- Matches either type.
72
+ ### Side-by-Side Comparison
105
73
 
106
- ---
74
+ ```javascript
75
+ // Zod: Constructing a schema using method-chaining builders
76
+ import { z } from "zod";
107
77
 
108
- ### Object Validation
78
+ const UserZod = z.object({
79
+ name: z.string(),
80
+ age: z.number().optional()
81
+ });
109
82
 
110
- ```javascript
83
+ // SigilJS: Writing natural, readable type expressions
84
+ import { Sigil } from "@antistructured/sigiljs";
111
85
 
112
- const User = Sigil`
86
+ const UserSigil = Sigil`
113
87
  {
114
88
  name: string
115
89
  age?: number
116
90
  }
117
- `
118
-
91
+ `;
119
92
  ```
120
93
 
121
- Optional properties use ` ? `.
94
+ | Feature | Zod | SigilJS |
95
+ |---------|-----|---------|
96
+ | **Philosophy** | Expressive builder API | Readable type expressions compiled to JS closures |
97
+ | **Syntax** | Method chaining (`z.string().optional()`) | Standard inline type DSL (``Sigil`string?```) |
98
+ | **Performance** | Runtime AST traversal | Compiled once to fast, cacheable validator functions |
99
+ | **Recursive Schemas** | Verbose `z.lazy()` wrapper | Native, lazy resolution using Named Sigils |
100
+ | **Bundle Size** | ~15-20kB gzipped | Tiny footprint (< 4kB), zero runtime dependencies |
122
101
 
123
102
  ---
124
103
 
125
- ### Nested Objects
104
+ ## Core API
105
+
106
+ SigilJS exports the core `Sigil` class, along with two convenient single-letter aliases: `S` (recommended shorthand) and `T` (legacy/optional alias).
126
107
 
127
108
  ```javascript
109
+ import { Sigil, S, T, SigilValidationError } from "@antistructured/sigiljs";
128
110
 
129
- const Order = Sigil`
130
- {
131
- id: string
132
- customer: {
133
- name: string
134
- email: string
135
- }
136
- items: {
137
- name: string
138
- price: number
139
- }[]
140
- }
141
- `
111
+ // All three are identical:
112
+ const schema1 = Sigil`string`;
113
+ const schema2 = S`string`;
114
+ const schema3 = T`string`;
115
+ ```
142
116
 
117
+ ### `.check(value)`
118
+ Returns a boolean indicating if the value matches the schema.
119
+ ```javascript
120
+ S`string`.check("hello"); // true
121
+ S`string`.check(123); // false
143
122
  ```
144
123
 
145
- ---
124
+ ### `.assert(value)`
125
+ Validates the data, returning `true` on success. On failure, it throws a structured `SigilValidationError` containing detailed diagnostic information:
146
126
 
147
- ### Runtime Type Detection
127
+ ```javascript
128
+ try {
129
+ S`{ age: number }`.assert({ age: "thirty" });
130
+ } catch (error) {
131
+ if (error instanceof SigilValidationError) {
132
+ console.log(error.code); // "SIGIL_VALIDATION_FAILED"
133
+ console.log(error.message); // "Expected property \"age\" to be number, got string"
134
+ console.log(error.path); // ["age"]
135
+ console.log(error.expected); // "number"
136
+ console.log(error.actual); // "string"
137
+ }
138
+ }
139
+ ```
148
140
 
149
- SigilJS also provides a better ` typeof `.
141
+ ### Compiled Validators (`.validator` & `.compile()`)
142
+ Sigils are parsed and compiled once. If you want to bypass the Sigil instance overhead entirely on ultra-hot paths, you can retrieve the stable pre-compiled validator closure:
150
143
 
151
144
  ```javascript
145
+ const User = S`{ name: string }`;
152
146
 
153
- import { realType } from "@weipertda/sigiljs"
154
-
155
- realType([]) // "array"
156
- realType(null) // "null"
157
- realType(new Map()) // "map"
147
+ // Retrieve the pre-compiled function
148
+ const validateUser = User.validator; // or User.compile()
158
149
 
150
+ validateUser({ name: "Dana" }); // true
159
151
  ```
160
152
 
161
153
  ---
162
154
 
163
- ## Why SigilJS?
164
-
165
- Sigil cleanly solves four problems:
155
+ ## Exact Mode
166
156
 
167
- 1. JavaScript's native ` typeof ` is inconsistent and too weak for real type work.
157
+ By default, SigilJS runs in **Normal Mode**, allowing objects to have extra undeclared keys. To enforce strict shape matches where extra properties are disallowed, use **Exact Mode** via `Sigil.exact` (or `S.exact` / `T.exact`).
168
158
 
169
159
  ```javascript
160
+ // Normal Mode: allows extra keys
161
+ const NormalUser = S`{ name: string }`;
162
+ NormalUser.check({ name: "Dana", admin: true }); // true
170
163
 
171
- typeof []
172
- // "object" 😬
173
-
164
+ // Exact Mode: rejects extra keys
165
+ const ExactUser = S.exact`{ name: string }`;
166
+ ExactUser.check({ name: "Dana", admin: true }); // false
174
167
  ```
175
168
 
176
- 2. TypeScript solves mostly compile-time problems, often adds friction, and disappears at runtime.
169
+ Exact mode rules apply recursively down nested objects defined inside the exact block.
177
170
 
178
- * TypeScript helps during development, but once your program runs the types are gone.
179
-
180
- * SigilJS solves the runtime side of the problem.
171
+ ---
181
172
 
182
- * Describe your data once, then validate it anywhere.
173
+ ## Named Sigils & Composition
183
174
 
184
- ```typescript
175
+ To build large, maintainable schemas or support self-referential/circular schemas, you can define globally-reusable named sigils using `Sigil.define` (or its alias `Sigil.named`).
185
176
 
186
- // TypeScript disappears at runtime
187
- const x: string = 123;
177
+ ```javascript
178
+ // 1. Register a named sigil in the registry
179
+ Sigil.define("Email")`string`;
188
180
 
189
- // No runtime error
181
+ Sigil.define("Address")`
182
+ {
183
+ street: string
184
+ city: string
185
+ }
186
+ `;
190
187
 
188
+ // 2. Compose them into larger schemas by referring to them by name
189
+ const User = S`
190
+ {
191
+ name: string
192
+ email: Email
193
+ address: Address
194
+ }
195
+ `;
191
196
  ```
192
197
 
193
- 3. Existing runtime validation libraries are dependency-heavy, allocation-happy, or ergonomically off.
198
+ ### Self-Referential / Recursive Schemas
199
+ Since names are resolved lazily at validation/compilation time, schemas can reference themselves or each other circularly:
194
200
 
195
- 4. JavaScript lacks a native-feeling type expression system for runtime truth.
201
+ ```javascript
202
+ // Node references Node[]
203
+ Sigil.define("Node")`
204
+ {
205
+ name: string
206
+ children?: Node[]
207
+ }
208
+ `;
209
+ ```
196
210
 
197
211
  ---
198
212
 
199
- ### Accurate Runtime Types
213
+ ## `realType()`
200
214
 
201
- Replacing the gaps in ` typeof ` — ` realType ` correctly identifies ` null `, ` NaN `, arrays, async and generator functions, maps, sets, and arbitrary custom classes through hooks.
215
+ SigilJS includes `realType()`, an advanced runtime type-detector that resolves the shortcomings of JavaScript's `typeof`.
202
216
 
203
217
  ```javascript
218
+ import { realType } from "@antistructured/sigiljs";
204
219
 
205
- import { realType } from '@weipertda/sigiljs';
206
-
207
- realType('x'); // "string"
208
- realType(null); // "null"
220
+ realType("hello"); // "string"
221
+ realType(42); // "number"
209
222
  realType(NaN); // "nan"
223
+ realType(null); // "null"
210
224
  realType([]); // "array"
211
225
  realType(new Map()); // "map"
212
- realType(async function() {}); // "asyncfunction"
213
-
226
+ realType(async () => {}); // "asyncfunction"
227
+ realType(function* () {}); // "generatorfunction"
214
228
  ```
215
229
 
216
- You can even provide custom override hooks to map instances directly back to nominal strings:
230
+ ### Custom Hooks
231
+ You can supply custom override hooks to map instances of custom classes back to nominal type strings:
217
232
 
218
233
  ```javascript
219
-
220
- realType(myThing, {
221
- hooks: [ v => v instanceof MyThing ? 'mything' : null ]
222
- }); // "mything"
223
-
234
+ class DatabaseConnection {}
235
+ const conn = new DatabaseConnection();
236
+
237
+ realType(conn, {
238
+ hooks: [
239
+ val => val instanceof DatabaseConnection ? 'db_connection' : null
240
+ ]
241
+ }); // "db_connection"
224
242
  ```
225
243
 
226
- SigilJS solves runtime type validation with a tiny, dependency-free, runtime-native type system.
227
-
228
244
  ---
229
245
 
230
- ## Documentation
246
+ ## Examples
231
247
 
232
- See the [docs/](docs/README.md) folder for full, detailed documentation __(WIP)__.
248
+ To see runnable patterns, CLI setups, and advanced test suites, check the [examples/](examples/) directory.
233
249
 
234
250
  ---
235
251
 
236
- ## Examples
252
+ ## Roadmap
237
253
 
238
- See the [examples/](examples/) folder for runnable examples __(WIP)__.
254
+ Upcoming additions to the SigilJS roadmap:
255
+ - [ ] **Sigil.partial**: Construct partial sub-schemas dynamically.
256
+ - [ ] **Format validation rules**: Regex support and common formats (e.g. uuid, ipv4).
257
+ - [ ] **Schema migrations / transformers**: Parse and transform input types into mapped runtime types.
239
258
 
240
259
  ---
241
260
 
242
261
  ## License
243
262
 
244
263
  MIT
245
-
246
- ---
247
-
248
- ## CLI Playground
249
-
250
- You can securely test out Sigil validator schemas against JSON inputs directly from your shell:
251
-
252
- ```bash
253
-
254
- bun run src/playground.js '{"name": "Doug"}' '{name: string, age?: number}'
255
- # ✅ Validation passed
256
-
257
- ```
258
-
259
- ## Performance Philosophy
260
-
261
- SigilJS embraces a Functional Core / Imperative Shell architecture. It takes your schema string, turns it into a typed token stream, drops parse grouping artifacts, flattens branches, optimizes primitive unions, and finally generates a blazingly fast validator closure mapped dynamically from the ground up to minimize allocations on the hot path. Repeated tagged template passes are thoroughly memoized.
package/docs/README.md ADDED
@@ -0,0 +1,20 @@
1
+ # SigilJS Documentation
2
+
3
+ Welcome to the SigilJS documentation! Here you will find guides and specs covering all the core concepts and APIs of SigilJS.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Introduction](introduction.md) — Why SigilJS exists and the problems it solves.
8
+ - [Quickstart](quickstart.md) — Get up and running in 60 seconds.
9
+ - [Sigils](sigils.md) — The core concept of sigils and type blueprints.
10
+ - [Object Schemas](objects.md) — Validating object key/value maps and nested objects.
11
+ - [Arrays](arrays.md) — Working with array syntax, array-postfix operators, and nested arrays.
12
+ - [Optional Fields](optional.md) — Handling optional properties and nullable fields.
13
+ - [Exact Mode](exact-mode.md) — Rejecting extra properties on objects.
14
+ - [Named Sigils & Composition](named-sigils.md) — Reusable schemas, composition, and circular references.
15
+ - [Compiled Validators](compiled-validators.md) — The performance model and direct compiled validator reuse.
16
+ - [realType()](realtype.md) — Deep runtime type-detection with support for custom hooks.
17
+ - [CLI Playground](cli.md) — Testing schemas in your terminal.
18
+ - [Examples](examples.md) — Concrete usage examples.
19
+ - [Roadmap](roadmap.md) — Project milestones towards v1.0.0.
20
+ - [Contributing](contributing.md) — Guidelines for contributing to SigilJS.
package/docs/arrays.md ADDED
@@ -0,0 +1,55 @@
1
+ # Arrays
2
+
3
+ Use ` [] ` to describe arrays.
4
+
5
+ ```javascript
6
+
7
+ Sigil`number[]`
8
+
9
+ ```
10
+
11
+ Example:
12
+
13
+ ```javascript
14
+
15
+ const Scores = Sigil`number[]`
16
+
17
+ Scores.check([10, 20, 30])
18
+
19
+ ```
20
+
21
+ ---
22
+
23
+ Nested arrays are also supported.
24
+
25
+ ```javascript
26
+
27
+ Sigil`string[][]`
28
+
29
+ ```
30
+
31
+ ---
32
+
33
+ ## Arrays of Objects
34
+
35
+ ```javascript
36
+
37
+ const Orders = Sigil`
38
+ {
39
+ id: string
40
+ items: {
41
+ name: string
42
+ price: number
43
+ }[]
44
+ }
45
+ `
46
+
47
+ ```
48
+
49
+ Example:
50
+
51
+ ```javascript
52
+
53
+ Orders.check(data)
54
+
55
+ ```
package/docs/cli.md ADDED
@@ -0,0 +1,48 @@
1
+ # CLI Playground
2
+
3
+ SigilJS comes with a CLI tool for experimenting with schemas and validating raw JSON data directly in your terminal.
4
+
5
+ ## Running the Playground
6
+
7
+ You can run the playground script using Bun:
8
+
9
+ ```bash
10
+ bun run src/playground/playground.js <json-data> <schema>
11
+ ```
12
+
13
+ ### Argument Format
14
+ 1. `<json-data>`: A valid JSON string representing the data you want to validate.
15
+ 2. `<schema>`: The Sigil validation schema string (omit the `Sigil` tag and backticks).
16
+
17
+ ## Examples
18
+
19
+ ### Valid Data
20
+
21
+ ```bash
22
+ bun run src/playground/playground.js '{"name": "Alice", "age": 30}' '{ name: string, age?: number }'
23
+ # Output:
24
+ # ✅ Validation passed
25
+ ```
26
+
27
+ ### Invalid Data
28
+
29
+ ```bash
30
+ bun run src/playground/playground.js '{"name": "Alice", "age": "thirty"}' '{ name: string, age?: number }'
31
+ # Output:
32
+ # ❌ Validation failed
33
+ # Code: SIGIL_VALIDATION_FAILED
34
+ # Message: Expected property "age" to be number, got string
35
+ # Path: age
36
+ # Expected: number
37
+ # Actual: string
38
+ ```
39
+
40
+ ### Invalid JSON
41
+
42
+ If the data argument is not valid JSON, the playground reports it:
43
+
44
+ ```bash
45
+ bun run src/playground/playground.js 'invalid-json' '{ name: string }'
46
+ # Output:
47
+ # ❌ Error parsing JSON: Unexpected token i in JSON at position 0
48
+ ```
@@ -0,0 +1,42 @@
1
+ # Compiled Validators
2
+
3
+ Every Sigil is parsed and compiled once, then reused for all subsequent validations. This design ensures maximum performance and minimal runtime overhead.
4
+
5
+ ## How It Works
6
+
7
+ When you create a Sigil using the template tag, SigilJS parses the type expression into an Abstract Syntax Tree (AST), normalizes it, and compiles it into an optimized validator function.
8
+
9
+ This compiled validator is cached, meaning that repeated validations do not perform any string parsing, AST traversal, or tokenizing.
10
+
11
+ ```javascript
12
+ import { Sigil } from "@antistructured/sigiljs";
13
+
14
+ const User = Sigil`
15
+ {
16
+ name: string
17
+ age: number
18
+ }
19
+ `;
20
+
21
+ // Under the hood, this uses the cached, pre-compiled validator function
22
+ User.check({ name: "Alice", age: 30 });
23
+ ```
24
+
25
+ ## Direct Validator Access
26
+
27
+ If you need direct access to the compiled validator function, you can access the `.validator` property on any Sigil instance, or call `.compile()`. Both return the exact same function reference:
28
+
29
+ ```javascript
30
+ const User = Sigil`{ name: string }`;
31
+
32
+ // Retrieve the stable, compiled validator function
33
+ const validateUser = User.validator;
34
+
35
+ // Validate directly using the function
36
+ const isValid = validateUser({ name: "Bob" }); // true
37
+
38
+ // User.compile() returns the same cached reference
39
+ console.log(User.validator === User.compile()); // true
40
+ ```
41
+
42
+ By reusing the compiled validator reference, you bypass any intermediate Sigil object logic, achieving optimal execution speed.
@@ -0,0 +1,43 @@
1
+ # Contributing
2
+
3
+ First off, thank you for considering contributing to SigilJS.
4
+
5
+ ## Local Development
6
+
7
+ SigilJS uses [Bun](https://bun.sh) for package management and testing.
8
+
9
+ 1. Clone the repository:
10
+ ```bash
11
+
12
+ git clone https://github.com/weipertda/sigiljs.git
13
+ cd sigiljs
14
+
15
+ ```
16
+
17
+ 2. Install dependencies:
18
+
19
+ ```bash
20
+
21
+ bun install
22
+
23
+ ```
24
+
25
+ 3. Run the tests:
26
+
27
+ ```bash
28
+
29
+ bun test
30
+
31
+ ```
32
+
33
+ ## Pull Requests
34
+
35
+ 1. **Create a branch** for your feature or bugfix.
36
+ 2. **Write tests** that prove your bug was fixed or your feature works.
37
+ 3. **Format your code** using `bun run format`.
38
+ 4. **Ensure all tests pass** with `bun test`.
39
+ 5. Submit your PR with a clear description of the problem and your solution.
40
+
41
+ ## Code Style
42
+
43
+ SigilJS keeps things simple and avoids dependencies where possible. If you're adding a feature, consider if it really belongs in the core library or if it can be built in user-land using existing sigils.