-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.js
More file actions
219 lines (212 loc) · 6.95 KB
/
index.js
File metadata and controls
219 lines (212 loc) · 6.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
/**
* @fileoverview JSON Super Set (JSS) - Extended JSON Serialization
*
* JSS extends standard JSON to support additional JavaScript types that
* JSON.stringify/parse cannot handle. This enables api-ape to transparently
* serialize and deserialize rich data types over WebSocket connections.
*
* ## Supported Types
*
* | Type | Tag | Description |
* |----------------|-----|---------------------------------------|
* | Date | `D` | Serialized as timestamp |
* | RegExp | `R` | Serialized as string pattern |
* | Error | `E` | Preserves name, message, and stack |
* | undefined | `U` | Explicitly represents undefined |
* | Map | `M` | Converted to/from object entries |
* | Set | `S` | Converted to/from array |
* | Circular Refs | `P` | Preserved via path pointers |
*
* ## Wire Format
*
* JSS encodes type information into object keys using a tag suffix:
*
* ```javascript
* // Original object
* { createdAt: new Date('2024-01-01'), pattern: /hello/i }
*
* // JSS encoded
* { "createdAt<!D>": 1704067200000, "pattern<!R>": "/hello/i" }
* ```
*
* ## Usage
*
* JSS provides a drop-in replacement for JSON.stringify/parse:
*
* ```javascript
* const jss = require('./jss')
*
* // Stringify (like JSON.stringify but handles extended types)
* const str = jss.stringify({ date: new Date(), regex: /test/ })
*
* // Parse (like JSON.parse but restores extended types)
* const obj = jss.parse(str)
* // obj.date is a Date instance
* // obj.regex is a RegExp instance
* ```
*
* ## API Methods
*
* - `stringify(obj)` - Convert object to JSS string (high-level)
* - `parse(str)` - Parse JSS string back to object (high-level)
* - `encode(obj)` - Convert object to JSS-encoded plain object
* - `decode(obj)` - Convert JSS-encoded object back to original
*
* ## Circular Reference Handling
*
* JSS can handle circular references using path pointers:
*
* ```javascript
* const obj = { name: 'root' }
* obj.self = obj // Circular reference
*
* const str = jss.stringify(obj) // No error!
* const restored = jss.parse(str)
* console.log(restored.self === restored) // true
* ```
*
* @module utils/jss
* @see {@link module:utils/utils/encode} for encoding implementation
* @see {@link module:utils/utils/decode} for decoding implementation
*
* @example
* // Basic usage with dates
* const jss = require('./jss')
*
* const data = {
* user: 'Alice',
* loginAt: new Date(),
* settings: new Map([['theme', 'dark'], ['lang', 'en']])
* }
*
* const serialized = jss.stringify(data)
* // Can be sent over WebSocket
*
* const restored = jss.parse(serialized)
* console.log(restored.loginAt instanceof Date) // true
* console.log(restored.settings instanceof Map) // true
*
* @example
* // Error serialization
* const jss = require('./jss')
*
* try {
* throw new TypeError('Invalid input')
* } catch (err) {
* const serialized = jss.stringify({ error: err })
* const restored = jss.parse(serialized)
*
* console.log(restored.error instanceof TypeError) // true
* console.log(restored.error.message) // 'Invalid input'
* console.log(restored.error.stack) // Original stack trace
* }
*
* @example
* // Low-level encode/decode for inspection
* const jss = require('./jss')
*
* const encoded = jss.encode({
* date: new Date('2024-01-01'),
* items: new Set([1, 2, 3])
* })
*
* console.log(encoded)
* // {
* // "date<!D>": 1704067200000,
* // "items<!S>": [1, 2, 3]
* // }
*
* const decoded = jss.decode(encoded)
* // Original types restored
*/
const { encode, stringify } = require("./utils/encode");
const { decode, parse } = require("./utils/decode");
const { register: custom, clearPlugins } = require("./utils/plugins");
/**
* Parse a JSS-encoded string back into an object with restored types
*
* This is the primary method for deserializing JSS data. It combines
* JSON.parse with type restoration for Date, RegExp, Error, Map, Set,
* undefined, and circular references.
*
* @function parse
* @param {string} encoded - JSS-encoded JSON string
* @returns {any} Decoded object with original types restored
* @throws {SyntaxError} If the string is not valid JSON
*
* @example
* const obj = jss.parse('{"date<!D>":1704067200000}')
* console.log(obj.date instanceof Date) // true
*/
/**
* Convert an object to a JSS-encoded JSON string
*
* This is the primary method for serializing objects with extended types.
* It handles Date, RegExp, Error, Map, Set, undefined, and circular
* references that would cause JSON.stringify to fail or lose information.
*
* @function stringify
* @param {any} obj - Object to serialize
* @returns {string} JSS-encoded JSON string
*
* @example
* const str = jss.stringify({
* when: new Date(),
* pattern: /\d+/g,
* items: new Set([1, 2, 3])
* })
*/
/**
* Encode an object to JSS format (without stringifying)
*
* Low-level method that converts extended types to their tagged
* representations. Useful for inspection or custom serialization.
*
* @function encode
* @param {any} obj - Object to encode
* @returns {Object} Plain object with tagged keys for extended types
*
* @example
* const encoded = jss.encode({ date: new Date() })
* // { "date<!D>": 1704067200000 }
*/
/**
* Decode a JSS-encoded object (without parsing from string)
*
* Low-level method that restores extended types from their tagged
* representations. Useful when working with already-parsed data.
*
* @function decode
* @param {Object} data - JSS-encoded plain object
* @returns {any} Object with original types restored
*
* @example
* const decoded = jss.decode({ "date<!D>": 1704067200000 })
* console.log(decoded.date instanceof Date) // true
*/
/**
* Register a custom type handler plugin
*
* Plugins extend JSS to handle custom types beyond the built-in set.
* Each plugin is identified by a single-character tag that appears in
* the serialized format (e.g., `"key<!X>": value`).
*
* @function custom
* @param {string} tag - Single character tag identifier (e.g., 'X', 'Z')
* @param {Object} config - Plugin configuration object
* @param {function(string|number, any): boolean} config.check - Determines if plugin handles value
* @param {function(string[], string|number, any, Object): any} config.encode - Transform for serialization
* @param {function(any, string[], Object): any} config.decode - Restore from serialization
* @param {function=} config.onSend - Optional send lifecycle hook
* @param {function=} config.onReceive - Optional receive lifecycle hook
* @throws {Error} If tag conflicts with built-in or existing custom type
*
* @example
* // Register a custom BigInt handler
* jss.custom('B', {
* check: (key, value) => typeof value === 'bigint',
* encode: (path, key, value) => value.toString(),
* decode: (value) => BigInt(value)
* })
*/
module.exports = { parse, stringify, encode, decode, custom, clearPlugins };