@nostale/cli 0.1.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.
- package/LICENSE +21 -0
- package/README.md +36 -0
- package/assets/docker-compose.server.yml +70 -0
- package/dist/commands/config/generation.js +23 -0
- package/dist/commands/config/providers.js +138 -0
- package/dist/commands/config/sources.js +145 -0
- package/dist/commands/config/templates.js +117 -0
- package/dist/commands/config/tokens.js +83 -0
- package/dist/commands/config.js +43 -0
- package/dist/commands/doctor.js +98 -0
- package/dist/commands/setup.js +5 -0
- package/dist/commands/start.js +23 -0
- package/dist/commands/status.js +17 -0
- package/dist/commands/stop.js +16 -0
- package/dist/index.js +23 -0
- package/dist/lib/api-config.js +21 -0
- package/dist/lib/env-writer.js +41 -0
- package/dist/lib/http.js +117 -0
- package/dist/lib/install-root.js +75 -0
- package/dist/lib/paths.js +12 -0
- package/dist/lib/ports.js +19 -0
- package/dist/lib/process-manager.js +151 -0
- package/dist/lib/repo-root.js +34 -0
- package/dist/lib/wizard.js +267 -0
- package/dist/tui/prompts.js +54 -0
- package/package.json +69 -0
- package/services/mcp-server/index.cjs +48326 -0
- package/services/migrations/001_init_rag_v1.sql +128 -0
- package/services/migrations/002_doc_lifecycle.sql +96 -0
- package/services/migrations/004_mcp_tokens.sql +11 -0
- package/services/migrations/005_settings.sql +28 -0
- package/services/migrations/006_document_sources.sql +14 -0
- package/services/migrations/007_generation_settings.sql +9 -0
- package/services/migrations/008_llm_provider_configs_unique.sql +5 -0
- package/services/wasm/tree-sitter-c.wasm +0 -0
- package/services/wasm/tree-sitter-c_sharp.wasm +0 -0
- package/services/wasm/tree-sitter-cpp.wasm +0 -0
- package/services/wasm/tree-sitter-dart.wasm +0 -0
- package/services/wasm/tree-sitter-go.wasm +0 -0
- package/services/wasm/tree-sitter-java.wasm +0 -0
- package/services/wasm/tree-sitter-javascript.wasm +0 -0
- package/services/wasm/tree-sitter-kotlin.wasm +0 -0
- package/services/wasm/tree-sitter-php.wasm +0 -0
- package/services/wasm/tree-sitter-python.wasm +0 -0
- package/services/wasm/tree-sitter-rust.wasm +0 -0
- package/services/wasm/tree-sitter-tsx.wasm +0 -0
- package/services/wasm/tree-sitter-typescript.wasm +0 -0
- package/services/wasm/tree-sitter.wasm +0 -0
- package/services/wiki-api/index.cjs +82370 -0
- package/services/wiki-web/.output/nitro.json +15 -0
- package/services/wiki-web/.output/public/_nuxt/68caad34b1e80fa12b3a.4Cpu5VkB.woff +0 -0
- package/services/wiki-web/.output/public/_nuxt/6b042d50787d4a81d659.D7JGeKLp.svg +1835 -0
- package/services/wiki-web/.output/public/_nuxt/9065bfe481020b04b68a.CMveUrS1.woff2 +0 -0
- package/services/wiki-web/.output/public/_nuxt/B5j-AdCY.js +1 -0
- package/services/wiki-web/.output/public/_nuxt/BAgZ2ZFT.js +1 -0
- package/services/wiki-web/.output/public/_nuxt/BcxnhBGC.js +1 -0
- package/services/wiki-web/.output/public/_nuxt/BeIDAJKt.js +1 -0
- package/services/wiki-web/.output/public/_nuxt/BebqDWPm.js +1 -0
- package/services/wiki-web/.output/public/_nuxt/CFx_Wb7e.js +1 -0
- package/services/wiki-web/.output/public/_nuxt/CSEkmdhK.js +1 -0
- package/services/wiki-web/.output/public/_nuxt/CXYMfke_.js +1 -0
- package/services/wiki-web/.output/public/_nuxt/Cap3hpB7.js +1 -0
- package/services/wiki-web/.output/public/_nuxt/Ckd7UvSj.js +3 -0
- package/services/wiki-web/.output/public/_nuxt/D0DJQL09.js +1 -0
- package/services/wiki-web/.output/public/_nuxt/DRDlUXmk.js +66 -0
- package/services/wiki-web/.output/public/_nuxt/DW42QLqA.js +1 -0
- package/services/wiki-web/.output/public/_nuxt/DdDtdYim.js +1 -0
- package/services/wiki-web/.output/public/_nuxt/Dm1y7TW1.js +1 -0
- package/services/wiki-web/.output/public/_nuxt/DptiLPkM.js +4 -0
- package/services/wiki-web/.output/public/_nuxt/QFl-TMXl.js +1 -0
- package/services/wiki-web/.output/public/_nuxt/_id_.JvKV2jiN.css +1 -0
- package/services/wiki-web/.output/public/_nuxt/a9c9056a643325579e2a.CCNAgZDa.ttf +0 -0
- package/services/wiki-web/.output/public/_nuxt/bojElwT6.js +1 -0
- package/services/wiki-web/.output/public/_nuxt/builds/latest.json +1 -0
- package/services/wiki-web/.output/public/_nuxt/builds/meta/f68e5546-2919-478b-b4ad-702826e8383c.json +1 -0
- package/services/wiki-web/.output/public/_nuxt/d495aba62101f856c80f.DJ4Lg1TH.eot +0 -0
- package/services/wiki-web/.output/public/_nuxt/entry.isoIPAS3.css +1 -0
- package/services/wiki-web/.output/public/_nuxt/f8QtLg7g.js +1 -0
- package/services/wiki-web/.output/public/_nuxt/nhMvlSQm.js +1 -0
- package/services/wiki-web/.output/public/_nuxt/pFBugkEL.js +1 -0
- package/services/wiki-web/.output/public/_nuxt/plus-jakarta-sans-latin-ext-wght-normal.DmpS2jIq.woff2 +0 -0
- package/services/wiki-web/.output/public/_nuxt/plus-jakarta-sans-latin-wght-normal.eXO_dkmS.woff2 +0 -0
- package/services/wiki-web/.output/public/_nuxt/plus-jakarta-sans-vietnamese-wght-normal.qRpaaN48.woff2 +0 -0
- package/services/wiki-web/.output/public/favicon.svg +6 -0
- package/services/wiki-web/.output/public/robots.txt +2 -0
- package/services/wiki-web/.output/server/chunks/_/error-500.mjs +19 -0
- package/services/wiki-web/.output/server/chunks/_/error-500.mjs.map +1 -0
- package/services/wiki-web/.output/server/chunks/build/client.precomputed.mjs +4 -0
- package/services/wiki-web/.output/server/chunks/build/client.precomputed.mjs.map +1 -0
- package/services/wiki-web/.output/server/chunks/nitro/nitro.mjs +5352 -0
- package/services/wiki-web/.output/server/chunks/nitro/nitro.mjs.map +1 -0
- package/services/wiki-web/.output/server/chunks/routes/api-proxy/_...path_.mjs +34 -0
- package/services/wiki-web/.output/server/chunks/routes/api-proxy/_...path_.mjs.map +1 -0
- package/services/wiki-web/.output/server/chunks/routes/renderer.mjs +369 -0
- package/services/wiki-web/.output/server/chunks/routes/renderer.mjs.map +1 -0
- package/services/wiki-web/.output/server/chunks/virtual/_virtual_spa-template.mjs +4 -0
- package/services/wiki-web/.output/server/chunks/virtual/_virtual_spa-template.mjs.map +1 -0
- package/services/wiki-web/.output/server/index.mjs +10 -0
- package/services/wiki-web/.output/server/index.mjs.map +1 -0
- package/services/wiki-web/.output/server/node_modules/@babel/parser/lib/index.js +14599 -0
- package/services/wiki-web/.output/server/node_modules/@babel/parser/package.json +50 -0
- package/services/wiki-web/.output/server/node_modules/@vue/compiler-core/dist/compiler-core.cjs.prod.js +6759 -0
- package/services/wiki-web/.output/server/node_modules/@vue/compiler-core/package.json +58 -0
- package/services/wiki-web/.output/server/node_modules/@vue/compiler-dom/dist/compiler-dom.cjs.prod.js +689 -0
- package/services/wiki-web/.output/server/node_modules/@vue/compiler-dom/package.json +57 -0
- package/services/wiki-web/.output/server/node_modules/@vue/compiler-ssr/dist/compiler-ssr.cjs.js +1413 -0
- package/services/wiki-web/.output/server/node_modules/@vue/compiler-ssr/package.json +34 -0
- package/services/wiki-web/.output/server/node_modules/@vue/reactivity/dist/reactivity.cjs.prod.js +1899 -0
- package/services/wiki-web/.output/server/node_modules/@vue/reactivity/package.json +55 -0
- package/services/wiki-web/.output/server/node_modules/@vue/runtime-core/dist/runtime-core.cjs.prod.js +6940 -0
- package/services/wiki-web/.output/server/node_modules/@vue/runtime-core/package.json +52 -0
- package/services/wiki-web/.output/server/node_modules/@vue/runtime-dom/dist/runtime-dom.cjs.prod.js +1822 -0
- package/services/wiki-web/.output/server/node_modules/@vue/runtime-dom/package.json +60 -0
- package/services/wiki-web/.output/server/node_modules/@vue/server-renderer/dist/server-renderer.cjs.prod.js +896 -0
- package/services/wiki-web/.output/server/node_modules/@vue/server-renderer/package.json +55 -0
- package/services/wiki-web/.output/server/node_modules/@vue/shared/dist/shared.cjs.prod.js +604 -0
- package/services/wiki-web/.output/server/node_modules/@vue/shared/package.json +47 -0
- package/services/wiki-web/.output/server/node_modules/devalue/index.js +4 -0
- package/services/wiki-web/.output/server/node_modules/devalue/package.json +40 -0
- package/services/wiki-web/.output/server/node_modules/devalue/src/base64.js +60 -0
- package/services/wiki-web/.output/server/node_modules/devalue/src/constants.js +12 -0
- package/services/wiki-web/.output/server/node_modules/devalue/src/parse.js +283 -0
- package/services/wiki-web/.output/server/node_modules/devalue/src/stringify.js +401 -0
- package/services/wiki-web/.output/server/node_modules/devalue/src/uneval.js +552 -0
- package/services/wiki-web/.output/server/node_modules/devalue/src/utils.js +160 -0
- package/services/wiki-web/.output/server/node_modules/entities/dist/commonjs/decode-codepoint.js +77 -0
- package/services/wiki-web/.output/server/node_modules/entities/dist/commonjs/decode.js +568 -0
- package/services/wiki-web/.output/server/node_modules/entities/dist/commonjs/generated/decode-data-html.js +7 -0
- package/services/wiki-web/.output/server/node_modules/entities/dist/commonjs/generated/decode-data-xml.js +7 -0
- package/services/wiki-web/.output/server/node_modules/entities/dist/commonjs/internal/bin-trie-flags.js +21 -0
- package/services/wiki-web/.output/server/node_modules/entities/dist/commonjs/internal/decode-shared.js +31 -0
- package/services/wiki-web/.output/server/node_modules/entities/dist/commonjs/package.json +3 -0
- package/services/wiki-web/.output/server/node_modules/entities/package.json +120 -0
- package/services/wiki-web/.output/server/node_modules/estree-walker/dist/umd/estree-walker.js +344 -0
- package/services/wiki-web/.output/server/node_modules/estree-walker/package.json +37 -0
- package/services/wiki-web/.output/server/node_modules/hookable/dist/index.mjs +257 -0
- package/services/wiki-web/.output/server/node_modules/hookable/package.json +53 -0
- package/services/wiki-web/.output/server/node_modules/source-map-js/lib/array-set.js +121 -0
- package/services/wiki-web/.output/server/node_modules/source-map-js/lib/base64-vlq.js +140 -0
- package/services/wiki-web/.output/server/node_modules/source-map-js/lib/base64.js +67 -0
- package/services/wiki-web/.output/server/node_modules/source-map-js/lib/binary-search.js +111 -0
- package/services/wiki-web/.output/server/node_modules/source-map-js/lib/mapping-list.js +79 -0
- package/services/wiki-web/.output/server/node_modules/source-map-js/lib/quick-sort.js +132 -0
- package/services/wiki-web/.output/server/node_modules/source-map-js/lib/source-map-consumer.js +1188 -0
- package/services/wiki-web/.output/server/node_modules/source-map-js/lib/source-map-generator.js +444 -0
- package/services/wiki-web/.output/server/node_modules/source-map-js/lib/source-node.js +413 -0
- package/services/wiki-web/.output/server/node_modules/source-map-js/lib/util.js +594 -0
- package/services/wiki-web/.output/server/node_modules/source-map-js/package.json +71 -0
- package/services/wiki-web/.output/server/node_modules/source-map-js/source-map.js +8 -0
- package/services/wiki-web/.output/server/node_modules/ufo/dist/index.mjs +645 -0
- package/services/wiki-web/.output/server/node_modules/ufo/package.json +48 -0
- package/services/wiki-web/.output/server/node_modules/unhead/dist/parser.mjs +508 -0
- package/services/wiki-web/.output/server/node_modules/unhead/dist/plugins.mjs +101 -0
- package/services/wiki-web/.output/server/node_modules/unhead/dist/server.mjs +180 -0
- package/services/wiki-web/.output/server/node_modules/unhead/dist/shared/unhead.B3VbJo7Y.mjs +207 -0
- package/services/wiki-web/.output/server/node_modules/unhead/dist/shared/unhead.BYvz9V1x.mjs +43 -0
- package/services/wiki-web/.output/server/node_modules/unhead/dist/shared/unhead.CaI5ZD4O.mjs +182 -0
- package/services/wiki-web/.output/server/node_modules/unhead/dist/shared/unhead.CbpEuj3y.mjs +71 -0
- package/services/wiki-web/.output/server/node_modules/unhead/dist/shared/unhead.D4vSFytZ.mjs +236 -0
- package/services/wiki-web/.output/server/node_modules/unhead/dist/shared/unhead.DQc16pHI.mjs +196 -0
- package/services/wiki-web/.output/server/node_modules/unhead/dist/shared/unhead.TxTMM7cM.mjs +166 -0
- package/services/wiki-web/.output/server/node_modules/unhead/dist/shared/unhead.yem5I2v_.mjs +38 -0
- package/services/wiki-web/.output/server/node_modules/unhead/dist/utils.mjs +5 -0
- package/services/wiki-web/.output/server/node_modules/unhead/package.json +105 -0
- package/services/wiki-web/.output/server/node_modules/vue/dist/vue.cjs.js +80 -0
- package/services/wiki-web/.output/server/node_modules/vue/dist/vue.cjs.prod.js +66 -0
- package/services/wiki-web/.output/server/node_modules/vue/index.js +7 -0
- package/services/wiki-web/.output/server/node_modules/vue/index.mjs +1 -0
- package/services/wiki-web/.output/server/node_modules/vue/package.json +112 -0
- package/services/wiki-web/.output/server/node_modules/vue/server-renderer/index.mjs +1 -0
- package/services/wiki-web/.output/server/node_modules/vue-bundle-renderer/dist/runtime.mjs +249 -0
- package/services/wiki-web/.output/server/node_modules/vue-bundle-renderer/package.json +57 -0
- package/services/wiki-web/.output/server/package.json +26 -0
- package/services/wiki-worker/index.cjs +66661 -0
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DevalueError,
|
|
3
|
+
enumerable_symbols,
|
|
4
|
+
escaped,
|
|
5
|
+
get_type,
|
|
6
|
+
is_plain_object,
|
|
7
|
+
is_primitive,
|
|
8
|
+
stringify_key,
|
|
9
|
+
stringify_string,
|
|
10
|
+
valid_array_indices
|
|
11
|
+
} from './utils.js';
|
|
12
|
+
|
|
13
|
+
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$';
|
|
14
|
+
const unsafe_chars = /[<\b\f\n\r\t\0\u2028\u2029]/g;
|
|
15
|
+
const reserved =
|
|
16
|
+
/^(?:do|if|in|for|int|let|new|try|var|byte|case|char|else|enum|goto|long|this|void|with|await|break|catch|class|const|final|float|short|super|throw|while|yield|delete|double|export|import|native|return|switch|throws|typeof|boolean|default|extends|finally|package|private|abstract|continue|debugger|function|volatile|interface|protected|transient|implements|instanceof|synchronized)$/;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Turn a value into the JavaScript that creates an equivalent value
|
|
20
|
+
* @param {any} value
|
|
21
|
+
* @param {(value: any, uneval: (value: any) => string) => string | void} [replacer]
|
|
22
|
+
*/
|
|
23
|
+
export function uneval(value, replacer) {
|
|
24
|
+
const counts = new Map();
|
|
25
|
+
|
|
26
|
+
/** @type {string[]} */
|
|
27
|
+
const keys = [];
|
|
28
|
+
|
|
29
|
+
const custom = new Map();
|
|
30
|
+
|
|
31
|
+
/** @param {any} thing */
|
|
32
|
+
function walk(thing) {
|
|
33
|
+
if (!is_primitive(thing)) {
|
|
34
|
+
if (counts.has(thing)) {
|
|
35
|
+
counts.set(thing, counts.get(thing) + 1);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
counts.set(thing, 1);
|
|
40
|
+
|
|
41
|
+
if (replacer) {
|
|
42
|
+
const str = replacer(thing, (value) => uneval(value, replacer));
|
|
43
|
+
|
|
44
|
+
if (typeof str === 'string') {
|
|
45
|
+
custom.set(thing, str);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (typeof thing === 'function') {
|
|
51
|
+
throw new DevalueError(`Cannot stringify a function`, keys, thing, value);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const type = get_type(thing);
|
|
55
|
+
|
|
56
|
+
switch (type) {
|
|
57
|
+
case 'Number':
|
|
58
|
+
case 'BigInt':
|
|
59
|
+
case 'String':
|
|
60
|
+
case 'Boolean':
|
|
61
|
+
case 'Date':
|
|
62
|
+
case 'RegExp':
|
|
63
|
+
case 'URL':
|
|
64
|
+
case 'URLSearchParams':
|
|
65
|
+
return;
|
|
66
|
+
|
|
67
|
+
case 'Array':
|
|
68
|
+
/** @type {any[]} */ (thing).forEach((value, i) => {
|
|
69
|
+
keys.push(`[${i}]`);
|
|
70
|
+
walk(value);
|
|
71
|
+
keys.pop();
|
|
72
|
+
});
|
|
73
|
+
break;
|
|
74
|
+
|
|
75
|
+
case 'Set':
|
|
76
|
+
Array.from(thing).forEach(walk);
|
|
77
|
+
break;
|
|
78
|
+
|
|
79
|
+
case 'Map':
|
|
80
|
+
for (const [key, value] of thing) {
|
|
81
|
+
keys.push(`.get(${is_primitive(key) ? stringify_primitive(key) : '...'})`);
|
|
82
|
+
walk(value);
|
|
83
|
+
keys.pop();
|
|
84
|
+
}
|
|
85
|
+
break;
|
|
86
|
+
|
|
87
|
+
case 'Int8Array':
|
|
88
|
+
case 'Uint8Array':
|
|
89
|
+
case 'Uint8ClampedArray':
|
|
90
|
+
case 'Int16Array':
|
|
91
|
+
case 'Uint16Array':
|
|
92
|
+
case 'Float16Array':
|
|
93
|
+
case 'Int32Array':
|
|
94
|
+
case 'Uint32Array':
|
|
95
|
+
case 'Float32Array':
|
|
96
|
+
case 'Float64Array':
|
|
97
|
+
case 'BigInt64Array':
|
|
98
|
+
case 'BigUint64Array':
|
|
99
|
+
case 'DataView':
|
|
100
|
+
walk(thing.buffer);
|
|
101
|
+
return;
|
|
102
|
+
|
|
103
|
+
case 'ArrayBuffer':
|
|
104
|
+
return;
|
|
105
|
+
|
|
106
|
+
case 'Temporal.Duration':
|
|
107
|
+
case 'Temporal.Instant':
|
|
108
|
+
case 'Temporal.PlainDate':
|
|
109
|
+
case 'Temporal.PlainTime':
|
|
110
|
+
case 'Temporal.PlainDateTime':
|
|
111
|
+
case 'Temporal.PlainMonthDay':
|
|
112
|
+
case 'Temporal.PlainYearMonth':
|
|
113
|
+
case 'Temporal.ZonedDateTime':
|
|
114
|
+
return;
|
|
115
|
+
|
|
116
|
+
default:
|
|
117
|
+
if (!is_plain_object(thing)) {
|
|
118
|
+
throw new DevalueError(`Cannot stringify arbitrary non-POJOs`, keys, thing, value);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (enumerable_symbols(thing).length > 0) {
|
|
122
|
+
throw new DevalueError(`Cannot stringify POJOs with symbolic keys`, keys, thing, value);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
for (const key of Object.keys(thing)) {
|
|
126
|
+
if (key === '__proto__') {
|
|
127
|
+
throw new DevalueError(
|
|
128
|
+
`Cannot stringify objects with __proto__ keys`,
|
|
129
|
+
keys,
|
|
130
|
+
thing,
|
|
131
|
+
value
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
keys.push(stringify_key(key));
|
|
136
|
+
walk(thing[key]);
|
|
137
|
+
keys.pop();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
} else if (typeof thing === 'symbol') {
|
|
141
|
+
throw new DevalueError(`Cannot stringify a Symbol primitive`, keys, thing, value);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
walk(value);
|
|
146
|
+
|
|
147
|
+
const names = new Map();
|
|
148
|
+
|
|
149
|
+
Array.from(counts)
|
|
150
|
+
.filter((entry) => entry[1] > 1)
|
|
151
|
+
.sort((a, b) => b[1] - a[1])
|
|
152
|
+
.forEach((entry, i) => {
|
|
153
|
+
names.set(entry[0], get_name(i));
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* @param {any} thing
|
|
158
|
+
* @returns {string}
|
|
159
|
+
*/
|
|
160
|
+
function stringify(thing) {
|
|
161
|
+
if (names.has(thing)) {
|
|
162
|
+
return names.get(thing);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (is_primitive(thing)) {
|
|
166
|
+
return stringify_primitive(thing);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (custom.has(thing)) {
|
|
170
|
+
return custom.get(thing);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const type = get_type(thing);
|
|
174
|
+
|
|
175
|
+
switch (type) {
|
|
176
|
+
case 'Number':
|
|
177
|
+
case 'String':
|
|
178
|
+
case 'Boolean':
|
|
179
|
+
case 'BigInt':
|
|
180
|
+
return `Object(${stringify(thing.valueOf())})`;
|
|
181
|
+
|
|
182
|
+
case 'RegExp':
|
|
183
|
+
const { source, flags } = thing;
|
|
184
|
+
return flags
|
|
185
|
+
? `new RegExp(${stringify_string(source)},"${flags}")`
|
|
186
|
+
: `new RegExp(${stringify_string(source)})`;
|
|
187
|
+
|
|
188
|
+
case 'Date':
|
|
189
|
+
return `new Date(${thing.getTime()})`;
|
|
190
|
+
|
|
191
|
+
case 'URL':
|
|
192
|
+
return `new URL(${stringify_string(thing.toString())})`;
|
|
193
|
+
|
|
194
|
+
case 'URLSearchParams':
|
|
195
|
+
return `new URLSearchParams(${stringify_string(thing.toString())})`;
|
|
196
|
+
|
|
197
|
+
case 'Array': {
|
|
198
|
+
// For dense arrays (no holes), we iterate normally.
|
|
199
|
+
// When we encounter the first hole, we call Object.keys
|
|
200
|
+
// to determine the sparseness, then decide between:
|
|
201
|
+
// - Array literal with holes: [,"a",,] (default)
|
|
202
|
+
// - Object.assign: Object.assign(Array(n),{...}) (for very sparse arrays)
|
|
203
|
+
// Only the Object.assign path avoids iterating every slot, which
|
|
204
|
+
// is what protects against the DoS of e.g. `arr[1000000] = 1`.
|
|
205
|
+
let has_holes = false;
|
|
206
|
+
|
|
207
|
+
let result = '[';
|
|
208
|
+
|
|
209
|
+
for (let i = 0; i < thing.length; i += 1) {
|
|
210
|
+
if (i > 0) result += ',';
|
|
211
|
+
|
|
212
|
+
if (Object.hasOwn(thing, i)) {
|
|
213
|
+
result += stringify(thing[i]);
|
|
214
|
+
} else if (!has_holes) {
|
|
215
|
+
// Decide between array literal and Object.assign.
|
|
216
|
+
//
|
|
217
|
+
// Array literal: holes are consecutive commas.
|
|
218
|
+
// For example, [, "a", ,] is written as [,"a",,].
|
|
219
|
+
// Each hole costs 1 char (a comma).
|
|
220
|
+
//
|
|
221
|
+
// Object.assign: populated indices are listed explicitly.
|
|
222
|
+
// For example, [, "a", ,] would be written as
|
|
223
|
+
// Object.assign(Array(3),{1:"a"}). This avoids paying
|
|
224
|
+
// per-hole, but has a large fixed overhead for the
|
|
225
|
+
// "Object.assign(Array(n),{...})" wrapper, and each
|
|
226
|
+
// element costs extra chars for its index and colon.
|
|
227
|
+
//
|
|
228
|
+
// The serialized values are the same size either way, so
|
|
229
|
+
// the choice comes down to the structural overhead:
|
|
230
|
+
//
|
|
231
|
+
// Array literal overhead:
|
|
232
|
+
// 1 char per element or hole (comma separators)
|
|
233
|
+
// + 2 chars for "[" and "]"
|
|
234
|
+
// = L + 2
|
|
235
|
+
//
|
|
236
|
+
// Object.assign overhead:
|
|
237
|
+
// "Object.assign(Array(" — 20 chars
|
|
238
|
+
// + length — d chars
|
|
239
|
+
// + "),{" — 3 chars
|
|
240
|
+
// + for each populated element:
|
|
241
|
+
// index + ":" + "," — (d + 2) chars
|
|
242
|
+
// + "})" — 2 chars
|
|
243
|
+
// = (25 + d) + P * (d + 2)
|
|
244
|
+
//
|
|
245
|
+
// where L is the array length, P is the number of
|
|
246
|
+
// populated elements, and d is the number of digits
|
|
247
|
+
// in L (an upper bound on the digits in any index).
|
|
248
|
+
//
|
|
249
|
+
// Object.assign is cheaper when:
|
|
250
|
+
// (25 + d) + P * (d + 2) < L + 2
|
|
251
|
+
const populated_keys = valid_array_indices(/** @type {any[]} */ (thing));
|
|
252
|
+
const population = populated_keys.length;
|
|
253
|
+
const d = String(thing.length).length;
|
|
254
|
+
|
|
255
|
+
const hole_cost = thing.length + 2;
|
|
256
|
+
const sparse_cost = 25 + d + population * (d + 2);
|
|
257
|
+
|
|
258
|
+
if (hole_cost > sparse_cost) {
|
|
259
|
+
const entries = populated_keys.map((k) => `${k}:${stringify(thing[k])}`).join(',');
|
|
260
|
+
return `Object.assign(Array(${thing.length}),{${entries}})`;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Re-process this index as a hole in the array literal
|
|
264
|
+
has_holes = true;
|
|
265
|
+
i -= 1;
|
|
266
|
+
}
|
|
267
|
+
// else: already decided on array literal, hole is just an empty slot
|
|
268
|
+
// (the comma separator is all we need — no content for this position)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const tail = thing.length === 0 || thing.length - 1 in thing ? '' : ',';
|
|
272
|
+
return result + tail + ']';
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
case 'Set':
|
|
276
|
+
case 'Map':
|
|
277
|
+
return `new ${type}([${Array.from(thing).map(stringify).join(',')}])`;
|
|
278
|
+
|
|
279
|
+
case 'Int8Array':
|
|
280
|
+
case 'Uint8Array':
|
|
281
|
+
case 'Uint8ClampedArray':
|
|
282
|
+
case 'Int16Array':
|
|
283
|
+
case 'Uint16Array':
|
|
284
|
+
case 'Float16Array':
|
|
285
|
+
case 'Int32Array':
|
|
286
|
+
case 'Uint32Array':
|
|
287
|
+
case 'Float32Array':
|
|
288
|
+
case 'Float64Array':
|
|
289
|
+
case 'BigInt64Array':
|
|
290
|
+
case 'BigUint64Array': {
|
|
291
|
+
let str = `new ${type}`;
|
|
292
|
+
|
|
293
|
+
if (!names.has(thing.buffer)) {
|
|
294
|
+
const array = new thing.constructor(thing.buffer);
|
|
295
|
+
str += `([${array}])`;
|
|
296
|
+
} else {
|
|
297
|
+
str += `(${stringify(thing.buffer)})`;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// handle subarrays
|
|
301
|
+
if (thing.byteLength !== thing.buffer.byteLength) {
|
|
302
|
+
const start = thing.byteOffset / thing.BYTES_PER_ELEMENT;
|
|
303
|
+
const end = start + thing.length;
|
|
304
|
+
str += `.subarray(${start},${end})`;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return str;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
case 'DataView': {
|
|
311
|
+
let str = `new DataView`;
|
|
312
|
+
|
|
313
|
+
if (!names.has(thing.buffer)) {
|
|
314
|
+
str += `(new Uint8Array([${new Uint8Array(thing.buffer)}]).buffer`;
|
|
315
|
+
} else {
|
|
316
|
+
str += `(${stringify(thing.buffer)}`;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// handle subviews
|
|
320
|
+
if (thing.byteLength !== thing.buffer.byteLength) {
|
|
321
|
+
str += `,${thing.startOffset},${thing.byteLength}`;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return str + ')';
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
case 'ArrayBuffer': {
|
|
328
|
+
const ui8 = new Uint8Array(thing);
|
|
329
|
+
return `new Uint8Array([${ui8.toString()}]).buffer`;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
case 'Temporal.Duration':
|
|
333
|
+
case 'Temporal.Instant':
|
|
334
|
+
case 'Temporal.PlainDate':
|
|
335
|
+
case 'Temporal.PlainTime':
|
|
336
|
+
case 'Temporal.PlainDateTime':
|
|
337
|
+
case 'Temporal.PlainMonthDay':
|
|
338
|
+
case 'Temporal.PlainYearMonth':
|
|
339
|
+
case 'Temporal.ZonedDateTime':
|
|
340
|
+
return `${type}.from(${stringify_string(thing.toString())})`;
|
|
341
|
+
|
|
342
|
+
default:
|
|
343
|
+
const keys = Object.keys(thing);
|
|
344
|
+
const obj = keys.map((key) => `${safe_key(key)}:${stringify(thing[key])}`).join(',');
|
|
345
|
+
const proto = Object.getPrototypeOf(thing);
|
|
346
|
+
if (proto === null) {
|
|
347
|
+
return keys.length > 0 ? `{${obj},__proto__:null}` : `{__proto__:null}`;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return `{${obj}}`;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const str = stringify(value);
|
|
355
|
+
|
|
356
|
+
if (names.size) {
|
|
357
|
+
/** @type {string[]} */
|
|
358
|
+
const params = [];
|
|
359
|
+
|
|
360
|
+
/** @type {string[]} */
|
|
361
|
+
const statements = [];
|
|
362
|
+
|
|
363
|
+
/** @type {string[]} */
|
|
364
|
+
const values = [];
|
|
365
|
+
|
|
366
|
+
names.forEach((name, thing) => {
|
|
367
|
+
params.push(name);
|
|
368
|
+
|
|
369
|
+
if (custom.has(thing)) {
|
|
370
|
+
values.push(/** @type {string} */ (custom.get(thing)));
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (is_primitive(thing)) {
|
|
375
|
+
values.push(stringify_primitive(thing));
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const type = get_type(thing);
|
|
380
|
+
|
|
381
|
+
switch (type) {
|
|
382
|
+
case 'Number':
|
|
383
|
+
case 'String':
|
|
384
|
+
case 'Boolean':
|
|
385
|
+
case 'BigInt':
|
|
386
|
+
values.push(`Object(${stringify(thing.valueOf())})`);
|
|
387
|
+
break;
|
|
388
|
+
|
|
389
|
+
case 'RegExp':
|
|
390
|
+
const { source, flags } = thing;
|
|
391
|
+
const regexp = flags
|
|
392
|
+
? `new RegExp(${stringify_string(source)},"${flags}")`
|
|
393
|
+
: `new RegExp(${stringify_string(source)})`
|
|
394
|
+
values.push(regexp);
|
|
395
|
+
break;
|
|
396
|
+
|
|
397
|
+
case 'Date':
|
|
398
|
+
values.push(`new Date(${thing.getTime()})`);
|
|
399
|
+
break;
|
|
400
|
+
|
|
401
|
+
case 'URL':
|
|
402
|
+
values.push(`new URL(${stringify_string(thing.toString())})`);
|
|
403
|
+
break;
|
|
404
|
+
|
|
405
|
+
case 'URLSearchParams':
|
|
406
|
+
values.push(`new URLSearchParams(${stringify_string(thing.toString())})`);
|
|
407
|
+
break;
|
|
408
|
+
|
|
409
|
+
case 'Array':
|
|
410
|
+
values.push(`Array(${thing.length})`);
|
|
411
|
+
/** @type {any[]} */ (thing).forEach((v, i) => {
|
|
412
|
+
statements.push(`${name}[${i}]=${stringify(v)}`);
|
|
413
|
+
});
|
|
414
|
+
break;
|
|
415
|
+
|
|
416
|
+
case 'Set':
|
|
417
|
+
values.push(`new Set`);
|
|
418
|
+
statements.push(
|
|
419
|
+
`${name}.${Array.from(thing)
|
|
420
|
+
.map((v) => `add(${stringify(v)})`)
|
|
421
|
+
.join('.')}`
|
|
422
|
+
);
|
|
423
|
+
break;
|
|
424
|
+
|
|
425
|
+
case 'Map':
|
|
426
|
+
values.push(`new Map`);
|
|
427
|
+
statements.push(
|
|
428
|
+
`${name}.${Array.from(thing)
|
|
429
|
+
.map(([k, v]) => `set(${stringify(k)}, ${stringify(v)})`)
|
|
430
|
+
.join('.')}`
|
|
431
|
+
);
|
|
432
|
+
break;
|
|
433
|
+
|
|
434
|
+
case 'Int8Array':
|
|
435
|
+
case 'Uint8Array':
|
|
436
|
+
case 'Uint8ClampedArray':
|
|
437
|
+
case 'Int16Array':
|
|
438
|
+
case 'Uint16Array':
|
|
439
|
+
case 'Float16Array':
|
|
440
|
+
case 'Int32Array':
|
|
441
|
+
case 'Uint32Array':
|
|
442
|
+
case 'Float32Array':
|
|
443
|
+
case 'Float64Array':
|
|
444
|
+
case 'BigInt64Array':
|
|
445
|
+
case 'BigUint64Array': {
|
|
446
|
+
let str = `new ${type}`;
|
|
447
|
+
|
|
448
|
+
if (!names.has(thing.buffer)) {
|
|
449
|
+
const array = new thing.constructor(thing.buffer);
|
|
450
|
+
str += `([${array}])`;
|
|
451
|
+
} else {
|
|
452
|
+
str += `(${stringify(thing.buffer)})`;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// handle subarrays
|
|
456
|
+
if (thing.byteLength !== thing.buffer.byteLength) {
|
|
457
|
+
const start = thing.byteOffset / thing.BYTES_PER_ELEMENT;
|
|
458
|
+
const end = start + thing.length;
|
|
459
|
+
str += `.subarray(${start},${end})`;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
values.push(`{}`);
|
|
463
|
+
statements.push(`${name}=${str}`);
|
|
464
|
+
break;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
case 'DataView': {
|
|
468
|
+
let str = `new DataView`;
|
|
469
|
+
|
|
470
|
+
if (!names.has(thing.buffer)) {
|
|
471
|
+
str += `(new Uint8Array([${new Uint8Array(thing.buffer)}]).buffer`;
|
|
472
|
+
} else {
|
|
473
|
+
str += `(${stringify(thing.buffer)}`;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// handle subviews
|
|
477
|
+
if (thing.byteLength !== thing.buffer.byteLength) {
|
|
478
|
+
str += `,${thing.byteOffset},${thing.byteLength}`;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
str += ')';
|
|
482
|
+
|
|
483
|
+
values.push(`{}`);
|
|
484
|
+
statements.push(`${name}=${str}`);
|
|
485
|
+
break;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
case 'ArrayBuffer':
|
|
489
|
+
values.push(`new Uint8Array([${new Uint8Array(thing)}]).buffer`);
|
|
490
|
+
break;
|
|
491
|
+
|
|
492
|
+
default:
|
|
493
|
+
values.push(Object.getPrototypeOf(thing) === null ? 'Object.create(null)' : '{}');
|
|
494
|
+
Object.keys(thing).forEach((key) => {
|
|
495
|
+
statements.push(`${name}${safe_prop(key)}=${stringify(thing[key])}`);
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
statements.push(`return ${str}`);
|
|
501
|
+
|
|
502
|
+
return `(function(${params.join(',')}){${statements.join(';')}}(${values.join(',')}))`;
|
|
503
|
+
} else {
|
|
504
|
+
return str;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/** @param {number} num */
|
|
509
|
+
function get_name(num) {
|
|
510
|
+
let name = '';
|
|
511
|
+
|
|
512
|
+
do {
|
|
513
|
+
name = chars[num % chars.length] + name;
|
|
514
|
+
num = ~~(num / chars.length) - 1;
|
|
515
|
+
} while (num >= 0);
|
|
516
|
+
|
|
517
|
+
return reserved.test(name) ? `${name}0` : name;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/** @param {string} c */
|
|
521
|
+
function escape_unsafe_char(c) {
|
|
522
|
+
return escaped[c] || c;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/** @param {string} str */
|
|
526
|
+
function escape_unsafe_chars(str) {
|
|
527
|
+
return str.replace(unsafe_chars, escape_unsafe_char);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/** @param {string} key */
|
|
531
|
+
function safe_key(key) {
|
|
532
|
+
return /^[_$a-zA-Z][_$a-zA-Z0-9]*$/.test(key) ? key : escape_unsafe_chars(JSON.stringify(key));
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/** @param {string} key */
|
|
536
|
+
function safe_prop(key) {
|
|
537
|
+
return /^[_$a-zA-Z][_$a-zA-Z0-9]*$/.test(key)
|
|
538
|
+
? `.${key}`
|
|
539
|
+
: `[${escape_unsafe_chars(JSON.stringify(key))}]`;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/** @param {any} thing */
|
|
543
|
+
function stringify_primitive(thing) {
|
|
544
|
+
const type = typeof thing;
|
|
545
|
+
if (type === 'string') return stringify_string(thing);
|
|
546
|
+
if (thing === void 0) return 'void 0';
|
|
547
|
+
if (thing === 0 && 1 / thing < 0) return '-0';
|
|
548
|
+
const str = String(thing);
|
|
549
|
+
if (type === 'number') return str.replace(/^(-)?0\./, '$1.');
|
|
550
|
+
if (type === 'bigint') return thing + 'n';
|
|
551
|
+
return str;
|
|
552
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { MAX_ARRAY_INDEX, MAX_ARRAY_LEN } from './constants.js';
|
|
2
|
+
|
|
3
|
+
/** @type {Record<string, string>} */
|
|
4
|
+
export const escaped = {
|
|
5
|
+
'<': '\\u003C',
|
|
6
|
+
'\\': '\\\\',
|
|
7
|
+
'\b': '\\b',
|
|
8
|
+
'\f': '\\f',
|
|
9
|
+
'\n': '\\n',
|
|
10
|
+
'\r': '\\r',
|
|
11
|
+
'\t': '\\t',
|
|
12
|
+
'\u2028': '\\u2028',
|
|
13
|
+
'\u2029': '\\u2029'
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export class DevalueError extends Error {
|
|
17
|
+
/**
|
|
18
|
+
* @param {string} message
|
|
19
|
+
* @param {string[]} keys
|
|
20
|
+
* @param {any} [value] - The value that failed to be serialized
|
|
21
|
+
* @param {any} [root] - The root value being serialized
|
|
22
|
+
*/
|
|
23
|
+
constructor(message, keys, value, root) {
|
|
24
|
+
super(message);
|
|
25
|
+
this.name = 'DevalueError';
|
|
26
|
+
this.path = keys.join('');
|
|
27
|
+
this.value = value;
|
|
28
|
+
this.root = root;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** @param {any} thing */
|
|
33
|
+
export function is_primitive(thing) {
|
|
34
|
+
return thing === null || (typeof thing !== 'object' && typeof thing !== 'function');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const object_proto_names = /* @__PURE__ */ Object.getOwnPropertyNames(Object.prototype)
|
|
38
|
+
.sort()
|
|
39
|
+
.join('\0');
|
|
40
|
+
|
|
41
|
+
/** @param {any} thing */
|
|
42
|
+
export function is_plain_object(thing) {
|
|
43
|
+
const proto = Object.getPrototypeOf(thing);
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
proto === Object.prototype ||
|
|
47
|
+
proto === null ||
|
|
48
|
+
Object.getPrototypeOf(proto) === null ||
|
|
49
|
+
Object.getOwnPropertyNames(proto).sort().join('\0') === object_proto_names
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** @param {any} thing */
|
|
54
|
+
export function get_type(thing) {
|
|
55
|
+
return Object.prototype.toString.call(thing).slice(8, -1);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** @param {string} char */
|
|
59
|
+
function get_escaped_char(char) {
|
|
60
|
+
switch (char) {
|
|
61
|
+
case '"':
|
|
62
|
+
return '\\"';
|
|
63
|
+
case '<':
|
|
64
|
+
return '\\u003C';
|
|
65
|
+
case '\\':
|
|
66
|
+
return '\\\\';
|
|
67
|
+
case '\n':
|
|
68
|
+
return '\\n';
|
|
69
|
+
case '\r':
|
|
70
|
+
return '\\r';
|
|
71
|
+
case '\t':
|
|
72
|
+
return '\\t';
|
|
73
|
+
case '\b':
|
|
74
|
+
return '\\b';
|
|
75
|
+
case '\f':
|
|
76
|
+
return '\\f';
|
|
77
|
+
case '\u2028':
|
|
78
|
+
return '\\u2028';
|
|
79
|
+
case '\u2029':
|
|
80
|
+
return '\\u2029';
|
|
81
|
+
default:
|
|
82
|
+
return char < ' ' ? `\\u${char.charCodeAt(0).toString(16).padStart(4, '0')}` : '';
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** @param {string} str */
|
|
87
|
+
export function stringify_string(str) {
|
|
88
|
+
let result = '';
|
|
89
|
+
let last_pos = 0;
|
|
90
|
+
const len = str.length;
|
|
91
|
+
|
|
92
|
+
for (let i = 0; i < len; i += 1) {
|
|
93
|
+
const char = str[i];
|
|
94
|
+
const replacement = get_escaped_char(char);
|
|
95
|
+
if (replacement) {
|
|
96
|
+
result += str.slice(last_pos, i) + replacement;
|
|
97
|
+
last_pos = i + 1;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return `"${last_pos === 0 ? str : result + str.slice(last_pos)}"`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** @param {Record<string | symbol, any>} object */
|
|
105
|
+
export function enumerable_symbols(object) {
|
|
106
|
+
return Object.getOwnPropertySymbols(object).filter(
|
|
107
|
+
(symbol) => Object.getOwnPropertyDescriptor(object, symbol).enumerable
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const is_identifier = /^[a-zA-Z_$][a-zA-Z_$0-9]*$/;
|
|
112
|
+
|
|
113
|
+
/** @param {string} key */
|
|
114
|
+
export function stringify_key(key) {
|
|
115
|
+
return is_identifier.test(key) ? '.' + key : '[' + JSON.stringify(key) + ']';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** @param {number} n */
|
|
119
|
+
export function is_valid_array_index(n) {
|
|
120
|
+
if (!Number.isInteger(n)) return false;
|
|
121
|
+
if (n < 0) return false;
|
|
122
|
+
if (n > MAX_ARRAY_INDEX) return false;
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** @param {number} n */
|
|
127
|
+
export function is_valid_array_len(n) {
|
|
128
|
+
if (!Number.isInteger(n)) return false;
|
|
129
|
+
if (n < 0) return false;
|
|
130
|
+
if (n > MAX_ARRAY_LEN) return false;
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** @param {string} s */
|
|
135
|
+
function is_valid_array_index_string(s) {
|
|
136
|
+
if (s.length === 0) return false;
|
|
137
|
+
if (s.length > 1 && s.charCodeAt(0) === 48) return false; // leading zero
|
|
138
|
+
for (let i = 0; i < s.length; i++) {
|
|
139
|
+
const c = s.charCodeAt(i);
|
|
140
|
+
if (c < 48 || c > 57) return false;
|
|
141
|
+
}
|
|
142
|
+
// by this point we know it's a string of digits, but it has to be within
|
|
143
|
+
// the range of valid array indices
|
|
144
|
+
return is_valid_array_index(+s);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Finds the populated indices of an array.
|
|
149
|
+
* @param {unknown[]} array
|
|
150
|
+
*/
|
|
151
|
+
export function valid_array_indices(array) {
|
|
152
|
+
const keys = Object.keys(array);
|
|
153
|
+
for (var i = keys.length - 1; i >= 0; i--) {
|
|
154
|
+
if (is_valid_array_index_string(keys[i])) {
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
keys.length = i + 1;
|
|
159
|
+
return keys;
|
|
160
|
+
}
|