@mblaney/holster 1.0.34 → 1.0.36

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 (67) hide show
  1. package/build/holster.js +3 -3
  2. package/build/holster.js.map +3 -3
  3. package/dist/holster.js +4 -0
  4. package/dist/holster.js.map +7 -0
  5. package/package.json +23 -5
  6. package/src/get.js +11 -19
  7. package/src/ham.js +17 -47
  8. package/src/holster.js +106 -28
  9. package/src/sea.js +0 -52
  10. package/src/store.js +7 -7
  11. package/src/wire.js +8 -2
  12. package/ts/src/array.ts +78 -0
  13. package/ts/src/buffer.ts +441 -0
  14. package/ts/src/dup.ts +49 -0
  15. package/ts/src/get.ts +83 -0
  16. package/ts/src/ham.ts +254 -0
  17. package/ts/src/holster.ts +931 -0
  18. package/ts/src/index.ts +35 -0
  19. package/ts/src/radisk.ts +585 -0
  20. package/ts/src/radix.ts +157 -0
  21. package/ts/src/schemas.ts +519 -0
  22. package/ts/src/sea-utils.ts +103 -0
  23. package/ts/src/sea.ts +388 -0
  24. package/ts/src/store.ts +321 -0
  25. package/ts/src/user.ts +381 -0
  26. package/ts/src/utils.ts +232 -0
  27. package/ts/src/wire.ts +829 -0
  28. package/.dockerignore +0 -9
  29. package/.github/workflows/node.js.yml +0 -31
  30. package/.github/workflows/npm-publish.yml +0 -33
  31. package/Dockerfile +0 -20
  32. package/examples/index.html +0 -33
  33. package/examples/index.js +0 -49
  34. package/examples/user.html +0 -31
  35. package/examples/user.js +0 -93
  36. package/examples/wire.html +0 -27
  37. package/examples/wire.js +0 -55
  38. package/test/holster.get.test.js +0 -94
  39. package/test/holster.lex.test.js +0 -196
  40. package/test/holster.off.test.js +0 -53
  41. package/test/holster.on.test.js +0 -264
  42. package/test/holster.put.test.js +0 -358
  43. package/test/holster.secure.test.js +0 -202
  44. package/test/holster.user.get.test.js +0 -114
  45. package/test/holster.user.lex.test.js +0 -149
  46. package/test/holster.user.off.test.js +0 -73
  47. package/test/holster.user.on.test.js +0 -300
  48. package/test/holster.user.put.test.js +0 -463
  49. package/test/kitty/! +0 -9
  50. package/test/names.js +0 -225
  51. package/test/radisk.cache.test.js +0 -213
  52. package/test/radisk.test.js +0 -331
  53. package/test/radix.test.js +0 -117
  54. package/test/sea.test.js +0 -284
  55. package/test/split.test.js +0 -317
  56. package/test/store.test.js +0 -316
  57. package/test/system/basic.test.js +0 -29
  58. package/test/system/chained-on.test.js +0 -41
  59. package/test/system/concurrent-listeners.test.js +0 -125
  60. package/test/system/immediate-read.test.js +0 -30
  61. package/test/system/listener-before-data.test.js +0 -51
  62. package/test/system/promise-wrapper.test.js +0 -47
  63. package/test/system/user-chained-on.test.js +0 -60
  64. package/test/system/user-concurrent-listeners.test.js +0 -142
  65. package/test/system/user-listener-before-data.test.js +0 -66
  66. package/test/user.test.js +0 -173
  67. package/test/wire.test.js +0 -158
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Holster TypeScript Entry Point
3
+ *
4
+ * This is the main entry point for the TypeScript version of Holster.
5
+ * It exports all the main modules and types.
6
+ */
7
+
8
+ // Main API
9
+ export { default as Holster, type HolsterAPI } from "./holster.ts"
10
+
11
+ // Core modules
12
+ export { default as Wire, type WireAPI } from "./wire.ts"
13
+ export { default as User, type UserInterface } from "./user.ts"
14
+ export { default as SEA } from "./sea.ts"
15
+ export { default as Store, type StoreInterface } from "./store.ts"
16
+
17
+ // Data structure modules
18
+ export { default as Ham } from "./ham.ts"
19
+ export { default as Get } from "./get.ts"
20
+ export { default as Radisk } from "./radisk.ts"
21
+ export { default as Radix } from "./radix.ts"
22
+ export { default as Dup, type DupInterface } from "./dup.ts"
23
+
24
+ // Utilities
25
+ export * as utils from "./utils.ts"
26
+ export * as seaUtils from "./sea-utils.ts"
27
+ export { default as SafeBuffer } from "./buffer.ts"
28
+ export { default as SeaArray } from "./array.ts"
29
+
30
+ // Types and Schemas
31
+ export * from "./schemas.ts"
32
+
33
+ // Default export
34
+ export { default } from "./holster.ts"
35
+
@@ -0,0 +1,585 @@
1
+ /**
2
+ * Radisk - Persistent radix tree storage
3
+ * Provides disk-backed radix tree with caching and batching
4
+ */
5
+
6
+ import Radix from "./radix.ts"
7
+ import type { RadixFunction } from "./schemas.ts"
8
+ import * as utils from "./utils.ts"
9
+ import type { EncodedValue, RadiskOptions, GraphValue, Relation } from "./schemas.ts"
10
+
11
+ // ASCII character for end of text
12
+ const etx = String.fromCharCode(3)
13
+ // ASCII character for enquiry
14
+ const enq = String.fromCharCode(5)
15
+ // ASCII character for unit separator
16
+ const unit = String.fromCharCode(31)
17
+
18
+ export interface RadiskInterface extends RadixFunction {
19
+ // Override call signature to support both get (with callback) and put (with value and optional callback)
20
+ // Accepts GraphValue (raw values) OR EncodedValue (tuples) - see src/radisk.js and tests
21
+ (key?: string, value?: GraphValue | EncodedValue | ((err?: string | null, value?: EncodedValue | Record<string, EncodedValue>) => void), cb?: (err?: string | null) => void): void
22
+
23
+ batch: RadixFunction & {
24
+ acks: Array<(err?: string | null) => void>
25
+ ed: number
26
+ timeout?: NodeJS.Timeout
27
+ }
28
+ thrash: {
29
+ (): void
30
+ at?: RadixFunction
31
+ ing?: boolean
32
+ more?: boolean
33
+ }
34
+ save: (rad: RadixFunction, cb: (err?: string | null) => void) => void
35
+ write: (file: string, rad: RadixFunction, cb: (err?: string | null) => void) => void
36
+ read: (key: string, cb: (err?: string | null, value?: EncodedValue | Record<string, EncodedValue>) => void) => void
37
+ parse: (file: string, cb: (err?: string | null, disk?: RadixFunction) => void) => void
38
+ }
39
+
40
+ /**
41
+ * Create a Radisk instance with persistent storage
42
+ */
43
+ const Radisk = (opt: RadiskOptions) => {
44
+ const cache = new Map<string, RadixFunction>()
45
+ let fileListCache: string[] | null = null
46
+ let fileListCacheTime = 0
47
+ const FILE_LIST_CACHE_TTL = 10000
48
+
49
+ const pendingReads = new Map<
50
+ string,
51
+ {
52
+ callbacks: Array<(err?: string, result?: EncodedValue | Record<string, EncodedValue>) => void>
53
+ fired: boolean
54
+ timeoutId: NodeJS.Timeout | null
55
+ }
56
+ >()
57
+
58
+ let lastMemoryCheck = 0
59
+ const MEMORY_CHECK_INTERVAL = 30000
60
+ const HEAP_WARNING_THRESHOLD = 0.8
61
+ const HEAP_CLEANUP_THRESHOLD = 0.9
62
+
63
+ const options = {
64
+ log: opt.log || console.log,
65
+ batch: opt.batch || 100,
66
+ write: opt.write || 1,
67
+ size: opt.size || 1024 * 1024,
68
+ memoryLimit: opt.memoryLimit || 500,
69
+ readTimeout: opt.readTimeout || 1000,
70
+ cache: opt.cache !== undefined ? opt.cache : true,
71
+ store: opt.store,
72
+ }
73
+
74
+ if (!options.store) {
75
+ throw new Error("Radisk needs `store` interface with `{get: fn, put: fn, list: fn}`")
76
+ }
77
+ if (!options.store.get) {
78
+ throw new Error("Radisk needs `store.get` interface with `(file, cb)`")
79
+ }
80
+ if (!options.store.put) {
81
+ throw new Error("Radisk needs `store.put` interface with `(file, data, cb)`")
82
+ }
83
+ if (!options.store.list) {
84
+ throw new Error("Radisk needs a streaming `store.list` interface with `(cb)`")
85
+ }
86
+
87
+ // After validation, store is guaranteed to be defined
88
+ const store = options.store
89
+
90
+ const perfLog = (operation: string, startTime: number, key: string, size?: number): void => {
91
+ const duration = Date.now() - startTime
92
+ if (duration > 2000) {
93
+ console.log(
94
+ `[RADISK-SLOW] ${operation}: ${duration}ms for ${key}${size ? ` (${size} bytes)` : ""}`
95
+ )
96
+ }
97
+ }
98
+
99
+ const checkMemoryUsage = (): void => {
100
+ if (typeof process === "undefined" || !process.memoryUsage) return
101
+
102
+ const now = Date.now()
103
+ if (now - lastMemoryCheck < MEMORY_CHECK_INTERVAL) return
104
+ lastMemoryCheck = now
105
+
106
+ const memUsage = process.memoryUsage()
107
+ const heapUsed = memUsage.heapUsed
108
+ const maxHeapSize = options.memoryLimit * 1024 * 1024
109
+ const heapUsageRatio = heapUsed / maxHeapSize
110
+
111
+ if (heapUsageRatio > HEAP_CLEANUP_THRESHOLD) {
112
+ const cacheSize = cache.size
113
+ console.log(
114
+ `[RADISK-MEMORY] High memory usage: ${Math.round(heapUsageRatio * 100)}% of heap limit. Clearing ${cacheSize} cached files.`
115
+ )
116
+ cache.clear()
117
+ if ((global as { gc?: () => void }).gc) {
118
+ (global as { gc: () => void }).gc()
119
+ }
120
+ lastMemoryCheck = now + MEMORY_CHECK_INTERVAL
121
+ } else if (heapUsageRatio > HEAP_WARNING_THRESHOLD) {
122
+ console.log(
123
+ `[RADISK-MEMORY] Memory warning: ${Math.round(heapUsageRatio * 100)}% of heap limit used. Cache size: ${cache.size} files.`
124
+ )
125
+ }
126
+ }
127
+
128
+ const radisk = ((key?: string, value?: GraphValue | EncodedValue | ((err?: string | null, value?: EncodedValue | Record<string, EncodedValue>) => void), cb?: (err?: string | null) => void) => {
129
+ if (typeof key !== "string") return undefined
130
+
131
+ if (typeof value === "function") {
132
+ const callback = value
133
+ const batchValue = radisk.batch(key)
134
+ if (typeof batchValue !== "undefined") {
135
+ return callback(undefined, batchValue as EncodedValue)
136
+ }
137
+
138
+ if (radisk.thrash.at) {
139
+ const thrashValue = radisk.thrash.at(key)
140
+ if (typeof thrashValue !== "undefined") {
141
+ return callback(undefined, thrashValue as EncodedValue)
142
+ }
143
+ }
144
+
145
+ if (pendingReads.has(key)) {
146
+ pendingReads.get(key)!.callbacks.push(callback)
147
+ return
148
+ }
149
+
150
+ const pending: {
151
+ callbacks: Array<(err?: string | null, value?: EncodedValue | Record<string, EncodedValue>) => void>
152
+ fired: boolean
153
+ timeoutId: NodeJS.Timeout | null
154
+ } = { callbacks: [callback], fired: false, timeoutId: null }
155
+ pendingReads.set(key, pending)
156
+
157
+ pending.timeoutId = setTimeout(() => {
158
+ if (!pending.fired) {
159
+ pending.fired = true
160
+ pendingReads.delete(key)
161
+ const err = "radisk read timeout"
162
+ pending.callbacks.forEach(cb => cb(err, undefined))
163
+ }
164
+ }, options.readTimeout)
165
+
166
+ const readStart = Date.now()
167
+ return radisk.read(key, (err, result) => {
168
+ if (pending.timeoutId) clearTimeout(pending.timeoutId)
169
+ perfLog("read", readStart, key)
170
+ if (!pending.fired) {
171
+ pending.fired = true
172
+ pendingReads.delete(key)
173
+ pending.callbacks.forEach(callback => callback(err, result))
174
+ }
175
+ })
176
+ }
177
+
178
+ radisk.batch(key, value as EncodedValue)
179
+ checkMemoryUsage()
180
+ if (cb) {
181
+ radisk.batch.acks.push(cb)
182
+ }
183
+ if (++radisk.batch.ed >= options.batch) {
184
+ return radisk.thrash()
185
+ }
186
+
187
+ clearTimeout(radisk.batch.timeout)
188
+ radisk.batch.timeout = setTimeout(radisk.thrash, options.write)
189
+ return undefined
190
+ }) as unknown as RadiskInterface
191
+
192
+ radisk.batch = Radix() as RadiskInterface["batch"]
193
+ radisk.batch.acks = []
194
+ radisk.batch.ed = 0
195
+
196
+ radisk.thrash = (() => {
197
+ if (radisk.thrash.ing) {
198
+ radisk.thrash.more = true
199
+ return
200
+ }
201
+
202
+ const thrashStart = Date.now()
203
+ clearTimeout(radisk.batch.timeout)
204
+ radisk.thrash.more = false
205
+ radisk.thrash.ing = true
206
+ const batch = (radisk.thrash.at = radisk.batch)
207
+ radisk.batch = Radix() as RadiskInterface["batch"]
208
+ radisk.batch.acks = []
209
+ radisk.batch.ed = 0
210
+ let i = 0
211
+ radisk.save(batch, err => {
212
+ if (++i > 1) return
213
+
214
+ perfLog("thrash", thrashStart, `batch-${batch.ed}`)
215
+ if (err) options.log(err)
216
+ batch.acks.forEach(cb => cb(err))
217
+ radisk.thrash.at = undefined
218
+ radisk.thrash.ing = false
219
+ if (radisk.thrash.more) radisk.thrash()
220
+ })
221
+ }) as RadiskInterface["thrash"]
222
+
223
+ radisk.save = (rad, cb) => {
224
+ const save = {
225
+ find: (_tree: unknown, key: string): boolean | undefined => {
226
+ if (key < (save.start || "")) return undefined
227
+ save.start = key
228
+ store.list(save.lex)
229
+ return true
230
+ },
231
+ lex: (file?: string): void => {
232
+ if (!file || file > (save.start || "")) {
233
+ save.end = file
234
+ if (save.start) save.mix(save.file || "!", save.start, save.end)
235
+ } else {
236
+ save.file = file
237
+ }
238
+ },
239
+ mix: (file: string, start: string, end?: string): void => {
240
+ save.start = save.end = save.file = undefined
241
+ if (cache.has(file)) {
242
+ const disk = cache.get(file)!
243
+ Radix.map(rad, (value, key) => {
244
+ if (key < start) return undefined
245
+ if (end && end < key) {
246
+ save.start = key
247
+ return undefined
248
+ }
249
+ disk(key, value)
250
+ return undefined
251
+ })
252
+ radisk.write(file, disk, save.next)
253
+ } else {
254
+ radisk.parse(file, (err, disk) => {
255
+ if (err) return cb(err)
256
+ Radix.map(rad, (value, key) => {
257
+ if (key < start) return undefined
258
+ if (end && end < key) {
259
+ save.start = key
260
+ return undefined
261
+ }
262
+ disk!(key, value)
263
+ return undefined
264
+ })
265
+ radisk.write(file, disk!, save.next)
266
+ })
267
+ }
268
+ },
269
+ next: (err?: string | null): void => {
270
+ if (err) return cb(err)
271
+ if (save.start) {
272
+ Radix.map(rad, save.find)
273
+ return
274
+ }
275
+ cb(err)
276
+ },
277
+ start: undefined as string | undefined,
278
+ end: undefined as string | undefined,
279
+ file: undefined as string | undefined,
280
+ }
281
+ Radix.map(rad, save.find)
282
+ }
283
+
284
+ radisk.write = (file, rad, cb) => {
285
+ const write = {
286
+ text: "",
287
+ limit: "",
288
+ done: false,
289
+ count: 0,
290
+ sub: undefined as RadixFunction | undefined,
291
+ each: (value: EncodedValue, _key: string, k: string, pre: string[]): void | boolean => {
292
+ if (write.done) return
293
+ write.count++
294
+ const valueStr =
295
+ typeof value === "undefined" ? "" : "=" + Radisk.encode(value)
296
+ const enc =
297
+ Radisk.encode(pre.length) + "#" + Radisk.encode(k) + valueStr + "\n"
298
+
299
+ if (
300
+ write.count > 1 &&
301
+ pre.length === 0 &&
302
+ write.text.length + enc.length > options.size
303
+ ) {
304
+ const endIdx = k.indexOf(enq)
305
+ write.limit = endIdx === -1 ? k : k.substring(0, endIdx)
306
+ if (write.limit !== file) {
307
+ write.done = true
308
+ write.sub = Radix()
309
+ Radix.map(rad, write.slice)
310
+ radisk.write(write.limit, write.sub, cb)
311
+ return undefined
312
+ }
313
+ }
314
+ write.text += enc
315
+ },
316
+ slice: (value: EncodedValue, key: string): void => {
317
+ if (key < write.limit) return
318
+ write.sub!(key, value)
319
+ },
320
+ }
321
+ Radix.map(rad, write.each, true)
322
+ const writeStart = Date.now()
323
+ store.put(file, write.text, err => {
324
+ perfLog("file-write", writeStart, file, write.text.length)
325
+ cb(err)
326
+ })
327
+ }
328
+
329
+ radisk.read = (key, cb) => {
330
+ const endIdx = key.indexOf(enq)
331
+ const soul = endIdx === -1 ? key : key.substring(0, endIdx)
332
+
333
+ const read = {
334
+ lex: (file?: string): void => {
335
+ if (!file) {
336
+ if (!read.file) {
337
+ cb("no file found", undefined)
338
+ return
339
+ }
340
+
341
+ if (options.cache && cache.has(read.file)) {
342
+ const cachedRadix = cache.get(read.file)!
343
+ read.value = cachedRadix(key) as EncodedValue | Record<string, EncodedValue> | undefined
344
+ return cb(undefined, read.value)
345
+ }
346
+
347
+ radisk.parse(read.file, read.it)
348
+ return
349
+ }
350
+
351
+ if (file > soul || file < (read.file || "")) return
352
+ read.file = file
353
+ },
354
+ it: (err?: string | null, disk?: RadixFunction): void => {
355
+ if (err) options.log(err)
356
+ if (disk) {
357
+ if (options.cache) {
358
+ cache.set(read.file!, disk)
359
+ checkMemoryUsage()
360
+ }
361
+ read.value = disk(key) as EncodedValue | Record<string, EncodedValue> | undefined
362
+ }
363
+ cb(err, read.value)
364
+ },
365
+ file: undefined as string | undefined,
366
+ value: undefined as EncodedValue | Record<string, EncodedValue> | undefined,
367
+ }
368
+
369
+ const now = Date.now()
370
+ if (
371
+ options.cache &&
372
+ fileListCache &&
373
+ now - fileListCacheTime < FILE_LIST_CACHE_TTL
374
+ ) {
375
+ fileListCache.forEach(file => read.lex(file))
376
+ read.lex()
377
+ } else {
378
+ const files: string[] = []
379
+ const originalLex = read.lex
380
+ read.lex = (file?: string): void => {
381
+ if (file) files.push(file)
382
+ return originalLex(file)
383
+ }
384
+
385
+ store.list((file?: string) => {
386
+ read.lex(file)
387
+ if (!file && options.cache) {
388
+ fileListCache = files
389
+ fileListCacheTime = now
390
+ }
391
+ })
392
+ }
393
+ }
394
+
395
+ radisk.parse = (file, cb) => {
396
+ const parse = {
397
+ disk: Radix(),
398
+ read: (err?: string | null, data?: string | EncodedValue | Record<string, unknown>): void => {
399
+ if (err) return cb(err)
400
+ if (!data) return cb(null, parse.disk)
401
+
402
+ let pre: string[] = []
403
+ let preString = ""
404
+ let tmp = parse.split(data as string)
405
+ while (tmp) {
406
+ let key: string | undefined
407
+ let value: EncodedValue | undefined
408
+ const tmpVal = tmp[1]
409
+ const i = typeof tmpVal === "number" ? tmpVal : (typeof tmpVal === "string" ? parseInt(tmpVal, 10) : 0)
410
+ tmp = parse.split(tmp[2]) || ("" as never)
411
+ if (tmp[0] === "#") {
412
+ const tmpKey = tmp[1]
413
+ key = typeof tmpKey === "string" ? tmpKey : String(tmpKey)
414
+ if (i < pre.length) {
415
+ pre.length = i
416
+ }
417
+ if (i <= pre.length) {
418
+ pre[i] = key
419
+ pre.length = i + 1
420
+ }
421
+ preString = pre.join("")
422
+ }
423
+ tmp = parse.split(tmp[2]) || ("" as never)
424
+ if (tmp[0] === "\n") continue
425
+
426
+ if (tmp[0] === "=") {
427
+ value = tmp[1] as EncodedValue
428
+ }
429
+ if (typeof key !== "undefined" && typeof value !== "undefined") {
430
+ parse.disk(preString, value)
431
+ }
432
+ tmp = parse.split(tmp[2])
433
+ }
434
+ cb(null, parse.disk)
435
+ },
436
+ split: (
437
+ data: string
438
+ ): [string, unknown, string] | undefined => {
439
+ if (!data) return undefined
440
+
441
+ const i = data.indexOf(unit)
442
+ if (i === -1) return undefined
443
+
444
+ const a = data.slice(0, i)
445
+ const o: { i?: number } = {}
446
+ return [a, Radisk.decode(data.slice(i), o), data.slice(i + o.i!)]
447
+ },
448
+ }
449
+ store.get(file, parse.read)
450
+ }
451
+
452
+ // TypeScript can't verify all properties are added dynamically, but they are at runtime
453
+ return radisk as unknown as RadiskInterface
454
+ }
455
+
456
+ /**
457
+ * Encode value for storage
458
+ */
459
+ Radisk.encode = (input: EncodedValue | GraphValue): string => {
460
+ let state = ""
461
+ let sig = ""
462
+ let data: GraphValue
463
+
464
+ if (Array.isArray(input) && (input.length === 2 || input.length === 3)) {
465
+ // input is EncodedValue - extract the GraphValue and metadata
466
+ state = etx + input[1]
467
+ if (input.length === 3 && input[2]) {
468
+ sig = etx + input[2]
469
+ }
470
+ data = input[0]!
471
+ } else {
472
+ // input is already a GraphValue
473
+ data = input as GraphValue
474
+ }
475
+
476
+ if (typeof data === "string") {
477
+ let i = 0
478
+ let current: string | undefined = undefined
479
+ let text = unit
480
+ while ((current = data[i++])) {
481
+ if (current === unit) text += unit
482
+ }
483
+ return text + '"' + data + state + sig + unit
484
+ }
485
+
486
+ const relId = utils.rel.is(data)
487
+ if (relId) return unit + "#" + relId + state + sig + unit
488
+
489
+ if (utils.num.is(data)) return unit + "+" + (data || 0) + state + sig + unit
490
+
491
+ if (data === true) return unit + "+" + state + sig + unit
492
+
493
+ if (data === false) return unit + "-" + state + sig + unit
494
+
495
+ if (data === null) return unit + " " + state + sig + unit
496
+
497
+ return undefined as never
498
+ }
499
+
500
+ /**
501
+ * Decode value from storage
502
+ */
503
+ Radisk.decode = (data: string, obj?: { i?: number }): EncodedValue | GraphValue | Relation | null | undefined => {
504
+ let i = -1
505
+ let n = 0
506
+ let current: string | undefined = undefined
507
+ let previous: string | undefined = undefined
508
+ let textStart = -1
509
+ let textEnd = -1
510
+ if (data[0] !== unit) return undefined
511
+
512
+ while ((current = data[++i])) {
513
+ if (previous) {
514
+ if (textStart === -1) textStart = i
515
+ if (current === unit) {
516
+ if (--n <= 0) {
517
+ textEnd = i
518
+ break
519
+ }
520
+ }
521
+ } else if (current === unit) {
522
+ n++
523
+ } else {
524
+ previous = current || "true"
525
+ }
526
+ }
527
+
528
+ const text =
529
+ textStart !== -1 ? data.slice(textStart, textEnd !== -1 ? textEnd : i) : ""
530
+
531
+ if (obj) obj.i = i + 1
532
+
533
+ const parts = text.split(etx)
534
+ const value = parts[0]!
535
+ const state = parts[1]
536
+ const sig = parts[2]
537
+
538
+ if (!state) {
539
+ if (previous === '"') return text
540
+
541
+ if (previous === "#") return utils.rel.ify(text)
542
+
543
+ if (previous === "+") {
544
+ if (text.length === 0) return true
545
+ return parseFloat(text)
546
+ }
547
+
548
+ if (previous === "-") return false
549
+
550
+ if (previous === " ") return null
551
+ } else {
552
+ const stateNum = parseFloat(state)
553
+ if (sig) {
554
+ if (previous === '"') return [value, stateNum, sig]
555
+
556
+ if (previous === "#") return [utils.rel.ify(value), stateNum, sig]
557
+
558
+ if (previous === "+") {
559
+ if (value.length === 0) return [true, stateNum, sig]
560
+ return [parseFloat(value), stateNum, sig]
561
+ }
562
+
563
+ if (previous === "-") return [false, stateNum, sig]
564
+
565
+ if (previous === " ") return [null, stateNum, sig]
566
+ } else {
567
+ if (previous === '"') return [value, stateNum]
568
+
569
+ if (previous === "#") return [utils.rel.ify(value), stateNum]
570
+
571
+ if (previous === "+") {
572
+ if (value.length === 0) return [true, stateNum]
573
+ return [parseFloat(value), stateNum]
574
+ }
575
+
576
+ if (previous === "-") return [false, stateNum]
577
+
578
+ if (previous === " ") return [null, stateNum]
579
+ }
580
+ }
581
+ return undefined
582
+ }
583
+
584
+ export default Radisk
585
+