@symbo.ls/utils 3.14.774 → 3.14.776
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/dist/cjs/keys.js +1 -1
- package/dist/cjs/methods.js +7 -7
- package/dist/cjs/object.js +19 -18
- package/dist/cjs/sharedLibraries.js +1 -1
- package/dist/cjs/string.js +3 -3
- package/dist/esm/keys.js +1 -1
- package/dist/esm/methods.js +4 -4
- package/dist/esm/object.js +19 -18
- package/dist/esm/sharedLibraries.js +1 -1
- package/dist/esm/string.js +3 -3
- package/keys.js +37 -0
- package/methods.js +24 -1
- package/object.js +378 -12
- package/package.json +1 -1
- package/sharedLibraries.js +96 -4
- package/string.js +43 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
import{isObject as
|
|
1
|
+
import{isObject as l,isString as u}from"./types.js";import{isProtectedElementMethod as d}from"./keys.js";import{isLoudDevRuntime as O}from"./env.js";const w="system",B="https://smbls-kv.nika-980.workers.dev",E="https://api.symbols.app";function h(n){const t=String(n||"").trim();if(!t)return{owner:w,key:"",full:""};let o=null,e=t;if(t.includes("/")){const r=t.indexOf("/");o=t.slice(0,r),e=t.slice(r+1)}e=e.replace(/\.symbo\.ls$/iu,""),o||(o=w);const s=e?`${o}/${e}`:"";return{owner:o,key:e,full:s}}const b=(n,t,o,e)=>{for(const s in t){const r=e?`${e}/${s}`:s;o&&o.has(r)||(s in n?l(n[s])&&l(t[s])&&b(n[s],t[s],o,r):n[s]=t[s])}},g=(n,t,o)=>{if(!l(n)||!t.size)return n;const e=`${o}/`;let s=!1;for(const a of t)if(a.startsWith(e)){s=!0;break}if(!s)return n;const r={};for(const a in n){if(!Object.prototype.hasOwnProperty.call(n,a))continue;const i=`${o}/${a}`;t.has(i)||(r[a]=g(n[a],t,i))}return r},F=new Set,U=new Set(["components","pages","functions","snippets","methods"]),k=(n,t)=>t===""&&U.has(n),$=["fontFamily","fontfamily","font_family","FONT_FAMILY"],P=n=>{const t=new Map;if(!l(n))return t;for(const o of $){const e=n[o];if(l(e))for(const s in e)!l(e[s])||!e[s].isDefault||(t.has(o)||t.set(o,new Set),t.get(o).add(s))}return t},M=(n,t)=>{for(const o of $){const e=n[o];if(!l(e))continue;const s=t.get(o);let r=null;for(const a in e){const i=e[a];if(!l(i)||!i.isDefault||s&&s.has(a))continue;const c={...i};delete c.isDefault,r||(r={...e}),r[a]=c}r&&(n[o]=r)}},D=n=>n.replace(/\.js$/iu,""),L=n=>{const t=new Set;if(!Array.isArray(n))return t;for(const o of n){if(!u(o))continue;const e=o.split("/").map(s=>D(s.trim())).filter(Boolean).join("/");e&&t.add(e)}return t},x=(n,t={})=>({library:n,ignoreList:Array.isArray(t.ignoreList)?t.ignoreList:[]}),A=n=>l(n)&&Array.isArray(n.ignoreList)&&("library"in n||"key"in n||"name"in n),S=n=>n.library!==void 0?n.library:n.key??n.name,m=new Set,V=()=>{m.clear()},T=(n,t)=>{const o=t.filter(e=>!m.has(e));if(o.length){for(const e of o)m.add(e);O()&&console.warn(`[smbls] shared library "${n}" ships ${o.map(e=>`methods.${e}`).join(", ")} \u2014 named like a built-in element method. Refused: the built-in wins; a library cannot replace an element method on the consuming app. If the app wants this version, declare it in the app's OWN methods.`)}},C=(n,t,o)=>{let e=null;for(const s in n)if(d(s)&&(t.has(`methods/${s}`)||o.push(s),!e)){e={};for(const r in n)d(r)||(e[r]=n[r])}return e||n},N=(n,t,o,e="library")=>{const s=L(t.sharedLibIgnore);t.sharePages===!1&&s.add("pages");const r=s.size?new Set([...o,...s]):o,a=[];for(const i in t)if(Object.prototype.hasOwnProperty.call(t,i)&&!(i==="sharePages"||i==="sharedLibIgnore")&&i!=="app"&&!r.has(i)){if(l(t[i])&&l(n[i])){if(i==="designSystem")b(n[i],t[i],r,i);else for(const c in t[i])if(!r.has(`${i}/${c}`)){if(i==="methods"&&d(c)){c in n[i]||a.push(c);continue}c in n[i]?k(i,n[i][c])&&!k(i,t[i][c])&&(n[i][c]=t[i][c]):n[i][c]=t[i][c]}}else if(!(i in n)){const c=t[i],f=i==="methods"&&c&&typeof c=="object"?C(c,r,a):c;n[i]=g(f,r,i)}}a.length&&T(e,a)},G=(n,t,o)=>{if(n!==t){const e=S(n);if(u(e)&&e)return e;if(n.key||n.name)return n.key||n.name}return t&&(t.key||t.name)||`sharedLibraries[${o}]`},q=(n,t)=>{if(!t||!t.length)return;const o=P(n.designSystem);for(let e=0;e<t.length;e++){const s=t[e];let r=s,a=F;A(r)&&(a=L(r.ignoreList),r=r.library),l(r)&&N(n,r,a,G(s,r,e))}o.size&&M(n.designSystem,o)};async function W(n,t={}){if((t.provider||"kv")==="api")return _(n,t);const e=await z(n,t);return e||_(n,t)}async function z(n,t={}){const o=t.kvBaseUrl||B,e=t.env||"production",s=`${o}/kv/${encodeURIComponent(n)}?env=${e}`;try{const r=await fetch(s,{method:"GET"});return r.ok&&(await r.json())?.value||null}catch{return null}}async function _(n,t={}){const o=t.apiBaseUrl||E,{key:e}=h(n);try{const s=`${o}/core/projects/libraries/available?search=${encodeURIComponent(e)}&limit=10`,r=await fetch(s,{method:"GET"});if(!r.ok)return null;const a=await r.json(),c=(a?.items||a?.data||(Array.isArray(a)?a:[])).find(j=>{const{key:R}=h(j?.key);return R.toLowerCase()===e.toLowerCase()});if(!c?.id&&!c?._id)return null;const f=c.id||c._id,I=`${o}/core/projects/${encodeURIComponent(f)}/data?branch=main`,p=await fetch(I,{method:"GET"});if(!p.ok)return null;const y=await p.json();return y?.data||y||null}catch{return null}}async function v(n,t={}){if(l(n))return n;if(u(n)){const{full:o}=h(n);if(!o)return null;try{const e=await W(o,t);return e||console.warn(`[smbls] Shared library "${o}" not found`),e}catch(e){return console.warn(`[smbls] Failed to fetch shared library "${o}":`,e.message),null}}return null}async function H(n,t={}){return!n||!n.length?[]:(await Promise.all(n.map(async e=>{if(A(e)){const s=await v(S(e),t);return s?{library:s,ignoreList:e.ignoreList}:null}return v(e,t)}))).filter(Boolean)}export{V as __resetBuiltinMethodWarnings,b as deepDefaults,W as fetchLibraryData,A as isWrappedLibrary,q as mergeSharedLibraries,L as normalizeIgnoreList,h as normalizeLibraryKey,H as resolveSharedLibraries,x as sharedLibrary};
|
package/dist/esm/string.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
const N=(
|
|
2
|
-
`);let
|
|
3
|
-
`).replace(/\/\/\/\/\/tilde/g,"`").replace(/\/\/\/\/\/dlrsgn/g,"$"),
|
|
1
|
+
const N=(t,r)=>{for(const e of r)if(t.includes(e))return!0;return!1},O=(t,r)=>{const e=new RegExp(`[${r.join("\\")}]`,"g");return t.replace(e,"")},w={2:/{{\s*((?:\.\.\/)*)([\w\d.]+)\s*(?:\|\s*([\w\d.]+))?\s*}}/g,3:/{{{(\s*(?:\.\.\/)*)([\w\d.]+)\s*(?:\|\s*([\w\d.]+))?\s*}}}/g},x=(t,r)=>r.split(".").reduce((e,s)=>e?.[s],t),A=(t,r)=>{if(!t||!t.__ref)return!0;let e=t;for(;e;){if(e.allowRuntimeTemplates===!0)return!0;const s=e.__ref&&e.__ref.__authoredTemplates;if(s&&s.has(r))return!0;e=e.parent}return!1};function R(t,r,e={}){const{bracketsLength:s=2}=e,f=s===3?"{{{":"{{";if(!t.includes(f)||!A(this,t))return t;const m=w[s],l=r||this.state||{},i=this;return t.replace(m,(u,d,n,a)=>{const p=n.trim();if(a){const c=i?.context,o=c?.functions?.[a]||c?.utils?.[a]||c?.methods?.[a]||c?.snippets?.[a]||i?.[a];if(o&&typeof o=="function")try{return String(o.call(i,p)??"")}catch{return""}return""}if(d){const c=(d.match(/\.\.\//g)||[]).length;let o=l;for(let g=0;g<c;g++){if(!o||!o.parent)return"";o=o.parent}if(p==="parent")return String(o.value??"");const h=x(o,p);return h!=null&&typeof h=="object"?"":String(h??"")}else{const c=x(l,p);if(c!=null&&typeof c!="object")return String(c);const o=i?.context?.polyglot;if(o?.translations){const h=l?.root?.lang||l?.lang||i?.context?.state?.lang||o.defaultLang||"en",g=o.translations[h];if(g){const C=x(g,p);if(C!=null&&typeof C!="object")return String(C);for(const _ in g){const b=g[_];if(b&&typeof b=="object"&&!Array.isArray(b)){const S=x(b,p);if(S!=null&&typeof S!="object")return String(S)}}}const y=x(o.translations,p);if(y!=null&&typeof y!="object")return String(y)}return""}})}const I=t=>`${t.charAt(0).toLowerCase()}${t.slice(1)}`,k=(t,r)=>{const e=t.split(`
|
|
2
|
+
`);let s=-1,f=-1,m=-1,l=-1;const i=new RegExp(`\\b${r}\\b\\s*:\\s*`);let u=0,d=!1;for(let n=0;n<e.length;n++)if(i.test(e[n])&&!d){if(d=!0,s=n+1,m=e[n].indexOf(r)+1,e[n].includes("{}")){f=s,l=e[n].indexOf("{}")+3;break}const a=e[n].slice(m+r.length);if(a.includes("{")||a.includes("["))u=1;else{f=n+1,l=e[n].length+1;break}}else if(d&&(u+=(e[n].match(/{/g)||[]).length,u+=(e[n].match(/\[/g)||[]).length,u-=(e[n].match(/}/g)||[]).length,u-=(e[n].match(/]/g)||[]).length,u===0)){f=n+1,l=e[n].lastIndexOf("}")!==-1?e[n].lastIndexOf("}")+2:e[n].length+1;break}return{startColumn:m,endColumn:l,startLineNumber:s,endLineNumber:f}},L=/\\([0-7]{1,3})/g,E=t=>t.replace(L,(r,e)=>String.fromCharCode(parseInt(e,8))),$=t=>t.replace(/\n/g,"/////n").replace(/`/g,"/////tilde").replace(/\$/g,"/////dlrsgn"),F=t=>t.replace(/\/\/\/\/\/n/g,`
|
|
3
|
+
`).replace(/\/\/\/\/\/tilde/g,"`").replace(/\/\/\/\/\/dlrsgn/g,"$"),j=/[^a-zA-Z0-9\s]/g,T=t=>t.replace(j,r=>"%"+r.charCodeAt(0).toString(16).toUpperCase()),v=t=>t.replace(/%[0-9A-Fa-f]{2}/g,r=>String.fromCharCode(parseInt(r.slice(1),16)));export{v as customDecodeURIComponent,T as customEncodeURIComponent,F as decodeNewlines,$ as encodeNewlines,k as findKeyPosition,I as lowercaseFirstLetter,R as replaceLiteralsWithObjectFields,E as replaceOctalEscapeSequences,N as stringIncludesAny,O as trimStringFromSymbols};
|
package/keys.js
CHANGED
|
@@ -148,6 +148,43 @@ export const METHODS = new Set([
|
|
|
148
148
|
'preventContentRecreate'
|
|
149
149
|
])
|
|
150
150
|
|
|
151
|
+
// Is `name` a built-in element method (or a reserved element key)? Every method
|
|
152
|
+
// on @symbo.ls/element's prototype is in METHODS — locked by
|
|
153
|
+
// element/__tests__/libraryMethodsCannotShadowBuiltins.test.js.
|
|
154
|
+
export const isBuiltinElementMethod = (name) => METHODS.has(name)
|
|
155
|
+
|
|
156
|
+
// The built-in element methods a SHARED LIBRARY may replace — the OVERRIDE
|
|
157
|
+
// POINTS (FW-LIBRARY-METHODS-CANNOT-SHADOW-BUILTIN-ELEMENT-METHODS-1). They are
|
|
158
|
+
// observability hooks, not element semantics: the element's own tree, state
|
|
159
|
+
// and rendering never depend on what they do, so a library's version cannot
|
|
160
|
+
// break the element the way a stale getRoot / getRootState copy did — and
|
|
161
|
+
// apps link libraries precisely to route these outputs. Every other built-in
|
|
162
|
+
// is protected: a library copy is refused, the built-in wins. The app's OWN
|
|
163
|
+
// methods win over both, as for any method.
|
|
164
|
+
export const ELEMENT_METHOD_OVERRIDE_POINTS = new Set([
|
|
165
|
+
// Message logging. The built-in is a debug dump — element `log(...keys)`
|
|
166
|
+
// prints `{ key: el[key] }`; a library's `log(...params)` prints its
|
|
167
|
+
// messages, which is what its own methods call it for.
|
|
168
|
+
'log',
|
|
169
|
+
// Warning channel. The built-in prints only in test/development; a library
|
|
170
|
+
// routes warnings in every environment (console, telemetry) and continues.
|
|
171
|
+
'warn',
|
|
172
|
+
// Error channel. The built-in THROWS in test/development and is silent in
|
|
173
|
+
// production; a library reports and continues (`return this.error('…')` is
|
|
174
|
+
// an early exit that must not become an exception), with its own hooks
|
|
175
|
+
// (`onError`, `verbose`, `debugger` options).
|
|
176
|
+
'error',
|
|
177
|
+
// Debug inspector for an element — a development aid a library may style
|
|
178
|
+
// or redirect.
|
|
179
|
+
'verbose'
|
|
180
|
+
])
|
|
181
|
+
|
|
182
|
+
// A built-in element method that a library can NOT replace: METHODS minus the
|
|
183
|
+
// override points. The single test the shared-library merge and the element's
|
|
184
|
+
// fold / `el.call` paths use.
|
|
185
|
+
export const isProtectedElementMethod = (name) =>
|
|
186
|
+
METHODS.has(name) && !ELEMENT_METHOD_OVERRIDE_POINTS.has(name)
|
|
187
|
+
|
|
151
188
|
export const METHODS_EXL = new Set([
|
|
152
189
|
'node', 'context', 'extends', '__element', '__ref',
|
|
153
190
|
...METHODS,
|
package/methods.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { triggerEventOn } from './triggerEvent.js'
|
|
4
4
|
import { DOMQ_PROPERTIES, METHODS, PARSED_DOMQ_PROPERTIES } from './keys.js'
|
|
5
5
|
import { isDefined, isFunction, isObject, isObjectLike } from './types.js'
|
|
6
|
-
import { deepClone } from './object.js'
|
|
6
|
+
import { deepClone, isFailedRevival } from './object.js'
|
|
7
7
|
import { isProduction } from './env.js'
|
|
8
8
|
import { removeValueFromArray } from './array.js'
|
|
9
9
|
import { OPTIONS } from './cache.js'
|
|
@@ -214,6 +214,25 @@ export const setActiveRootState = (state) => {
|
|
|
214
214
|
_activeRootState = state || null
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
+
/**
|
|
218
|
+
* `prepareContext` registers the PREPARED root state — a plain object — before
|
|
219
|
+
* the app root element exists. The root element then builds its OWN reactive
|
|
220
|
+
* store from that object (@symbo.ls/element createElementState), so the
|
|
221
|
+
* registered object is only a boot-time copy: it names no element, never sees
|
|
222
|
+
* a write and has no store methods. Every `getRootState()` fallback read went
|
|
223
|
+
* stale, and `getRoot()`'s fallback (`getRootState()?.__element`) could never
|
|
224
|
+
* resolve the app for an element whose `__ref` was cleared by `remove()`
|
|
225
|
+
* (FW-ROOT-STATE-REGISTERED-AS-A-DETACHED-PLAIN-COPY-1).
|
|
226
|
+
*
|
|
227
|
+
* @symbo.ls/element calls this when it builds a store from `source`: if the
|
|
228
|
+
* registry holds exactly `source`, it now holds the live `store`. Gated on
|
|
229
|
+
* identity, so an unrelated element tree created later can never take the
|
|
230
|
+
* registry over.
|
|
231
|
+
*/
|
|
232
|
+
export const promoteActiveRootState = (source, store) => {
|
|
233
|
+
if (source && store && _activeRootState === source) _activeRootState = store
|
|
234
|
+
}
|
|
235
|
+
|
|
217
236
|
const readFallbackState = () => {
|
|
218
237
|
// The framework-managed registry (set by prepareContext during
|
|
219
238
|
// create(...)) is the single source of truth for the active root
|
|
@@ -472,6 +491,10 @@ export function call (fnKey, ...args) {
|
|
|
472
491
|
context.snippets?.[fnKey]
|
|
473
492
|
)
|
|
474
493
|
if (!fn) return
|
|
494
|
+
// A lazily revived function whose source does not compile stands for the
|
|
495
|
+
// string eager revival left (object.js "LAZY REVIVAL"), which this helper
|
|
496
|
+
// could not call either: undefined, as before.
|
|
497
|
+
if (isFailedRevival(fn)) return
|
|
475
498
|
try {
|
|
476
499
|
const result = fn.call(this, ...args)
|
|
477
500
|
if (result && typeof result.then === 'function') {
|
package/object.js
CHANGED
|
@@ -387,16 +387,32 @@ const RE_MODULE_PREFIX = /^(export|import)\s/
|
|
|
387
387
|
export const hasFunction = (str) => {
|
|
388
388
|
if (!str) return false
|
|
389
389
|
|
|
390
|
-
|
|
390
|
+
// SMBLS-BOOT-LAZY-EVAL-1: this runs on EVERY string of a project context
|
|
391
|
+
// at boot (Pulse: ~26k strings, 2 MB), so it must reject a non-function
|
|
392
|
+
// string cheaply. Both steps below are exact — the result is identical to
|
|
393
|
+
// the previous form (`str.trim().replace(/\n\s*/g, ' ').trim()` then every
|
|
394
|
+
// pattern), pinned by __tests__/hasFunctionEquivalence.test.js:
|
|
395
|
+
// • no newline collapsing: every pattern reads `\s`, `[^)]`, `[^}]`,
|
|
396
|
+
// `[^=]` or `[\s\S]` where a newline run and a space both match, and
|
|
397
|
+
// nothing else looks at whitespace;
|
|
398
|
+
// • the pre-filter: every pattern needs `=>`, or a `function` / `async`
|
|
399
|
+
// / `(` prefix, or a `(` plus a closing `}` (method shorthand) — a
|
|
400
|
+
// string with none of those can match none of them.
|
|
401
|
+
const trimmed = str.trim()
|
|
391
402
|
|
|
392
403
|
if (trimmed === '' || trimmed === '{}' || trimmed === '[]') return false
|
|
404
|
+
const hasArrow = trimmed.includes('=>')
|
|
405
|
+
if (!hasArrow &&
|
|
406
|
+
trimmed.charCodeAt(0) !== 40 && // '(' — `(function …`
|
|
407
|
+
!trimmed.startsWith('function') &&
|
|
408
|
+
!trimmed.startsWith('async') &&
|
|
409
|
+
!(trimmed.charCodeAt(trimmed.length - 1) === 125 && trimmed.includes('('))) return false
|
|
393
410
|
if (RE_MODULE_PREFIX.test(trimmed)) return false
|
|
394
411
|
|
|
395
412
|
const isFn = FN_PATTERNS.some((pattern) => pattern.test(trimmed))
|
|
396
413
|
if (!isFn) return false
|
|
397
414
|
|
|
398
415
|
const firstChar = trimmed.charCodeAt(0)
|
|
399
|
-
const hasArrow = trimmed.includes('=>')
|
|
400
416
|
// '{' = 123, '[' = 91
|
|
401
417
|
if (firstChar === 123 && !hasArrow) return false // object literal
|
|
402
418
|
if (firstChar === 91) return false // array literal
|
|
@@ -518,8 +534,12 @@ const stripTopLevelDeclaration = (src, name) => {
|
|
|
518
534
|
// the structural walker used inline. Returns either the eval'd value or
|
|
519
535
|
// the original string (with a console.warn) so callers can assign the
|
|
520
536
|
// result directly. `label` is used in warning messages so the surface
|
|
521
|
-
// of the failure (object key vs array index) is identifiable.
|
|
522
|
-
|
|
537
|
+
// of the failure (object key vs array index) is identifiable. `lazy` only
|
|
538
|
+
// changes the warning's description of the outcome: a lazily revived
|
|
539
|
+
// function (see LAZY REVIVAL below) compiles on its first use, and when the
|
|
540
|
+
// source does not compile it stands for the string an eager revival leaves
|
|
541
|
+
// in place.
|
|
542
|
+
const _destringifyFnString = (str, label, opts, lazy) => {
|
|
523
543
|
// Module SOURCE files are stored as plain strings on project data
|
|
524
544
|
// (`code` fields driving CodePreview etc. — they start with
|
|
525
545
|
// `export …` / `import …`). They are display text, not function
|
|
@@ -594,7 +614,10 @@ const _destringifyFnString = (str, label, opts) => {
|
|
|
594
614
|
if (typeof console !== 'undefined' && console.warn) {
|
|
595
615
|
console.warn(
|
|
596
616
|
`[smbls] deepDestringifyFunctions: eval failed on ${label} — ` +
|
|
597
|
-
|
|
617
|
+
(lazy
|
|
618
|
+
? 'revived lazily, so its source compiled on this first use; it acts as the string it replaces ' +
|
|
619
|
+
'(a call returns the source text, el.call treats it as not callable). Reason: '
|
|
620
|
+
: 'value will be left as a string. Reason: ') + msg + '.\n' +
|
|
598
621
|
'First 200 chars of source: ' + String(str).slice(0, 200)
|
|
599
622
|
)
|
|
600
623
|
}
|
|
@@ -602,12 +625,350 @@ const _destringifyFnString = (str, label, opts) => {
|
|
|
602
625
|
}
|
|
603
626
|
}
|
|
604
627
|
|
|
628
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
629
|
+
// LAZY REVIVAL — SMBLS-BOOT-LAZY-EVAL-1
|
|
630
|
+
//
|
|
631
|
+
// Measured on anseta/pulse 1.31.1 (runtime 3.14.808, Lighthouse mobile):
|
|
632
|
+
// the page context carries 3,735 function strings (1.82 MB of source), and
|
|
633
|
+
// the eager walk ran one indirect eval per string before the first render —
|
|
634
|
+
// the largest self time of the whole boot (561 ms unthrottled, ~2.2 s at
|
|
635
|
+
// 4x CPU). A page calls a small share of them: one route's props and a few
|
|
636
|
+
// dozen registered functions.
|
|
637
|
+
//
|
|
638
|
+
// `deepDestringifyFunctions(obj, dest, { lazy: true })` keeps each source
|
|
639
|
+
// and installs a WRAPPER that compiles it on the first call — through the
|
|
640
|
+
// SAME `opts.window.eval` and the SAME recovery passes (`_destringifyFnString`
|
|
641
|
+
// above) the eager walk uses — caches the result and calls straight through
|
|
642
|
+
// from then on. Opt-in: the default stays eager, because tooling reads the
|
|
643
|
+
// eager result (frank's toFS prints a string that failed to compile as a
|
|
644
|
+
// quoted literal, helmet tests for "still a string", the workspace re-realm
|
|
645
|
+
// pass reads `Function.prototype.toString`). The runtime boot paths opt in:
|
|
646
|
+
// the published/runner page scripts and prepareContext's rehydrate.
|
|
647
|
+
//
|
|
648
|
+
// What a wrapper preserves (each pinned in __tests__/deepDestringifyLazy.test.js):
|
|
649
|
+
// • identity — the wrapper IS the function for its whole life. It never
|
|
650
|
+
// swaps itself out after compiling, so WeakMap keys, listener dedupe and
|
|
651
|
+
// `===` stay stable. Two slots with equal source get two wrappers, as
|
|
652
|
+
// two evals gave two functions.
|
|
653
|
+
// • `this`, every argument, the return value (an async source still
|
|
654
|
+
// returns its promise), and `new` for a plain `function` source
|
|
655
|
+
// (`Reflect.construct` with the wrapper as new.target, so instances are
|
|
656
|
+
// `instanceof` the wrapper the outside world holds).
|
|
657
|
+
// • `typeof` 'function', `.call`/`.apply`/`.bind`.
|
|
658
|
+
// • `.toString()` — the ORIGINAL source (trimmed, as an eval'd function
|
|
659
|
+
// expression reports it), without compiling. frank, the editor, SSR and
|
|
660
|
+
// inspectors re-serialize through it.
|
|
661
|
+
// • `.length` — the compiled function's arity, read through an accessor
|
|
662
|
+
// that compiles on demand (only the dev-only stashed-callback advisory
|
|
663
|
+
// in element/create.js and `Function.prototype.bind` read it).
|
|
664
|
+
// • `.name` — what the eager eval would report: the declared name of a
|
|
665
|
+
// `function` or method source, '' for an arrow or anonymous function.
|
|
666
|
+
// • no `.prototype`, not constructible, for arrow / async / method
|
|
667
|
+
// sources — a method-shorthand wrapper, like the eval'd value; an
|
|
668
|
+
// async source's wrapper inherits %AsyncFunction.prototype%, so
|
|
669
|
+
// `instanceof`, `.constructor` and the toStringTag say AsyncFunction.
|
|
670
|
+
//
|
|
671
|
+
// What cannot be preserved: `Function.prototype.toString.call(wrapper)`
|
|
672
|
+
// reads the wrapper's own text, not the stored source — no function object
|
|
673
|
+
// can answer it without compiling. `getFunctionSource(fn)` below answers it
|
|
674
|
+
// for any function; the one in-repo caller (element's dev advisory) uses it.
|
|
675
|
+
// Two more, kept on purpose (PR #187 review N2, N3):
|
|
676
|
+
// • `.caller` / `.arguments` on the wrapper of a plain `function` source
|
|
677
|
+
// throw (the wrapper is strict code). A sloppy wrapper would answer them,
|
|
678
|
+
// but it would also coerce `this` (undefined → globalThis, primitives →
|
|
679
|
+
// objects) before a strict source sees it; `this` is the contract.
|
|
680
|
+
// • every call runs one extra frame, so the deepest recursion through a
|
|
681
|
+
// registry reference roughly halves (probe: 8,959 → 4,607 frames).
|
|
682
|
+
// Swapping the slot to the compiled function after the first call would
|
|
683
|
+
// remove the frame but split identity (a listener added with the wrapper
|
|
684
|
+
// could no longer be removed with the slot's value).
|
|
685
|
+
//
|
|
686
|
+
// Only an UNAMBIGUOUS function expression is revived lazily (see
|
|
687
|
+
// `lazyStateFor`): an arrow, a plain/async `function`, or a method
|
|
688
|
+
// shorthand, whose head the regexes below prove. Everything else `hasFunction`
|
|
689
|
+
// accepts — prose that starts with "function", a generator (its `.prototype`
|
|
690
|
+
// is a generator prototype), a named function that refers to itself by name
|
|
691
|
+
// (its self-binding would be the compiled function, not the wrapper), a
|
|
692
|
+
// `(function…)` wrapped form — keeps the eager path exactly as before, string
|
|
693
|
+
// fallback and boot-time warning included.
|
|
694
|
+
//
|
|
695
|
+
// A lazily revived source that fails to compile warns with today's
|
|
696
|
+
// diagnostic (prefix, reason, first 200 chars) on its FIRST USE instead of
|
|
697
|
+
// at boot, and from then on stands for the STRING eager revival leaves in
|
|
698
|
+
// place (PR #187 review S4): every call returns that string, so a prop DOMQL
|
|
699
|
+
// evaluates by calling it (`text`, `if`, `hide`, attributes, CSS, custom
|
|
700
|
+
// props, `state`) gets exactly the value the string gave — a broken `if`
|
|
701
|
+
// stays truthy, prose in `text` still shows — and `el.call` / utils `call`
|
|
702
|
+
// ask `isFailedRevival` first and treat it as not callable, exactly as they
|
|
703
|
+
// treated the string (undefined, and el.call's "resolves to a non-function
|
|
704
|
+
// (string)" error). What remains different: `typeof` is 'function', and a
|
|
705
|
+
// DIRECT call from other code returns the string where calling a string
|
|
706
|
+
// threw a TypeError.
|
|
707
|
+
//
|
|
708
|
+
// `{ lazy: 'context' }` is the page scripts' mode for a whole project
|
|
709
|
+
// context: only the CODE buckets (LAZY_CONTEXT_CODE_KEYS) revive lazily;
|
|
710
|
+
// data — state, designSystem, polyglot, files, schema, config values —
|
|
711
|
+
// keeps eager revival, so a function-looking data string that does not
|
|
712
|
+
// compile stays the string it is, for code that READS it as a value.
|
|
713
|
+
// `{ lazy: true }` is lazy everywhere: the bucket-level callers
|
|
714
|
+
// (prepareContext's rehydrate of components, pages, functions, …).
|
|
715
|
+
//
|
|
716
|
+
// Identical sources are NOT shared. It would be safe — the compiled
|
|
717
|
+
// function never leaves its wrapper, and the self-referencing shapes that
|
|
718
|
+
// could observe it keep the eager path — but it does not pay: of the 388
|
|
719
|
+
// compiles the first render of Pulse /home triggers, 82 repeat a source
|
|
720
|
+
// (~6 ms of eval), against a per-realm cache every wrapper would consult.
|
|
721
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
722
|
+
|
|
723
|
+
const LAZY_KIND_PLAIN = 1 // `function` — constructible, has its own `.prototype`
|
|
724
|
+
const LAZY_KIND_CALLABLE = 2 // arrow / async function / method — neither
|
|
725
|
+
|
|
726
|
+
// wrapper → { src, name, kind, async, fn, value, key, index, opts }
|
|
727
|
+
// `fn` is undefined until the first compile, then the compiled function, or
|
|
728
|
+
// null when the source did not compile to one — `value` is then what eager
|
|
729
|
+
// revival leaves in the slot (the source string).
|
|
730
|
+
const LAZY_FN_STATE = new WeakMap()
|
|
731
|
+
|
|
732
|
+
// Every head below must PROVE its shape up to the body's opening brace or the
|
|
733
|
+
// arrow. Parameters are accepted without strings, template literals or slashes
|
|
734
|
+
// (comments, regex literals, division) and with at most one level of nested
|
|
735
|
+
// parens; a head these cannot prove falls back to the eager path — it is
|
|
736
|
+
// never misread.
|
|
737
|
+
const RE_LAZY_FUNCTION_HEAD = /^(async\s+)?function(?![\w$])\s*(\*)?\s*([A-Za-z_$][\w$]*)?\s*\((?:[^()'"`/]|\([^()'"`/]*\))*\)\s*\{/
|
|
738
|
+
const RE_LAZY_ARROW_IDENT_HEAD = /^(async\s+)?[A-Za-z_$][\w$]*\s*=>/
|
|
739
|
+
const RE_LAZY_ARROW_PARENS_HEAD = /^(async\s*)?\((?:[^()'"`/]|\([^()'"`/]*\))*\)\s*=>/
|
|
740
|
+
const RE_LAZY_METHOD_HEAD = /^(async\s+)?([A-Za-z_$][\w$]*)\s*\((?:[^()'"`/]|\([^()'"`/]*\))*\)\s*\{/
|
|
741
|
+
|
|
742
|
+
// A method-shorthand head named like a keyword is prose or a statement
|
|
743
|
+
// (`if (x) { … }`, `for (…) { … }`), never a revivable method.
|
|
744
|
+
const LAZY_NON_METHOD_NAMES = new Set([
|
|
745
|
+
'if', 'for', 'while', 'switch', 'catch', 'with', 'do', 'return', 'typeof',
|
|
746
|
+
'void', 'delete', 'new', 'in', 'of', 'instanceof', 'function', 'class',
|
|
747
|
+
'const', 'let', 'var', 'await', 'yield', 'import', 'export', 'try', 'else',
|
|
748
|
+
'throw', 'case', 'default', 'break', 'continue', 'debugger', 'finally',
|
|
749
|
+
'super', 'this', 'null', 'true', 'false', 'async', 'get', 'set', 'static'
|
|
750
|
+
])
|
|
751
|
+
|
|
752
|
+
const _isIdentCharCode = (c) =>
|
|
753
|
+
(c >= 48 && c <= 57) || (c >= 65 && c <= 90) || (c >= 97 && c <= 122) || c === 36 || c === 95
|
|
754
|
+
|
|
755
|
+
// `name` appears in `src` at or after `from` as a whole identifier token.
|
|
756
|
+
// Conservative on purpose (a property named like the function also counts):
|
|
757
|
+
// a false hit only means the eager path.
|
|
758
|
+
const _mentionsIdentifier = (src, name, from) => {
|
|
759
|
+
let i = src.indexOf(name, from)
|
|
760
|
+
while (i !== -1) {
|
|
761
|
+
const end = i + name.length
|
|
762
|
+
if ((i === 0 || !_isIdentCharCode(src.charCodeAt(i - 1))) &&
|
|
763
|
+
(end >= src.length || !_isIdentCharCode(src.charCodeAt(end)))) return true
|
|
764
|
+
i = src.indexOf(name, end)
|
|
765
|
+
}
|
|
766
|
+
return false
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
const _lastNonSpaceCharCode = (s) => {
|
|
770
|
+
let i = s.length - 1
|
|
771
|
+
while (i >= 0 && s.charCodeAt(i) <= 32) i--
|
|
772
|
+
return i < 0 ? -1 : s.charCodeAt(i)
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// Classify one source string for lazy revival: a fresh state object, or null
|
|
776
|
+
// when the eager path must revive it (see the header above for each case).
|
|
777
|
+
const lazyStateFor = (src) => {
|
|
778
|
+
let s = src
|
|
779
|
+
let start = 0
|
|
780
|
+
while (start < s.length && s.charCodeAt(start) <= 32) start++
|
|
781
|
+
if (start) s = s.slice(start)
|
|
782
|
+
|
|
783
|
+
let m = RE_LAZY_FUNCTION_HEAD.exec(s)
|
|
784
|
+
if (m) {
|
|
785
|
+
if (m[2]) return null // generator
|
|
786
|
+
if (_lastNonSpaceCharCode(s) !== 125) return null // must end with its body's `}`
|
|
787
|
+
const name = m[3] || ''
|
|
788
|
+
if (name && _mentionsIdentifier(s, name, m[0].indexOf('('))) return null // self-reference (params or body)
|
|
789
|
+
if (s.indexOf('arguments.callee') !== -1) return null
|
|
790
|
+
return { src, name, kind: m[1] ? LAZY_KIND_CALLABLE : LAZY_KIND_PLAIN, async: !!m[1], fn: undefined }
|
|
791
|
+
}
|
|
792
|
+
m = RE_LAZY_ARROW_IDENT_HEAD.exec(s) || RE_LAZY_ARROW_PARENS_HEAD.exec(s)
|
|
793
|
+
if (m) {
|
|
794
|
+
return { src, name: '', kind: LAZY_KIND_CALLABLE, async: !!m[1], fn: undefined }
|
|
795
|
+
}
|
|
796
|
+
m = RE_LAZY_METHOD_HEAD.exec(s)
|
|
797
|
+
if (m && !LAZY_NON_METHOD_NAMES.has(m[2]) && _lastNonSpaceCharCode(s) === 125) {
|
|
798
|
+
if (s.indexOf('arguments.callee') !== -1) return null
|
|
799
|
+
return { src, name: m[2], kind: LAZY_KIND_CALLABLE, async: !!m[1], fn: undefined }
|
|
800
|
+
}
|
|
801
|
+
return null
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
const _lazyLabel = (state) => state.index === -1
|
|
805
|
+
? `"${state.key}"`
|
|
806
|
+
: `array index ${state.index} (prop "${state.key}")`
|
|
807
|
+
|
|
808
|
+
const compileLazy = (state) => {
|
|
809
|
+
const out = _destringifyFnString(state.src, _lazyLabel(state), state.opts, true)
|
|
810
|
+
let fn = null
|
|
811
|
+
if (typeof out === 'function') {
|
|
812
|
+
fn = out
|
|
813
|
+
} else {
|
|
814
|
+
// Not a function: the value eager revival leaves in the slot — the
|
|
815
|
+
// source string when it did not compile (`_destringifyFnString` has
|
|
816
|
+
// warned), or, unreachable for a head the regexes proved, whatever the
|
|
817
|
+
// expression evaluated to. The wrapper stands for it from now on.
|
|
818
|
+
state.value = out
|
|
819
|
+
if (typeof out !== 'string' && typeof console !== 'undefined' && console.warn) {
|
|
820
|
+
console.warn(
|
|
821
|
+
`[smbls] deepDestringifyFunctions: ${_lazyLabel(state)} did not evaluate to a function — ` +
|
|
822
|
+
'the lazily revived function returns that value.\n' +
|
|
823
|
+
'First 200 chars of source: ' + String(state.src).slice(0, 200)
|
|
824
|
+
)
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
state.fn = fn
|
|
828
|
+
state.opts = null // the eval handle is no longer needed
|
|
829
|
+
return fn
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
// Kept out of the wrappers' own text so `Function.prototype.toString.call`
|
|
833
|
+
// on a wrapper names no global at all (the workspace re-realm gate reads it).
|
|
834
|
+
const _constructLazy = (fn, args, newTarget) => Reflect.construct(fn, args, newTarget)
|
|
835
|
+
|
|
836
|
+
function lazyFunctionToString () {
|
|
837
|
+
const state = LAZY_FN_STATE.get(this)
|
|
838
|
+
return state ? state.src.trim() : Function.prototype.toString.call(this)
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// Shared by every wrapper — one defineProperties call, no per-wrapper
|
|
842
|
+
// descriptor objects.
|
|
843
|
+
const LAZY_FN_DESCRIPTORS = {
|
|
844
|
+
length: {
|
|
845
|
+
get () {
|
|
846
|
+
const state = LAZY_FN_STATE.get(this)
|
|
847
|
+
if (!state) return 0
|
|
848
|
+
const fn = state.fn === undefined ? compileLazy(state) : state.fn
|
|
849
|
+
return fn ? fn.length : 0
|
|
850
|
+
},
|
|
851
|
+
configurable: true
|
|
852
|
+
},
|
|
853
|
+
name: {
|
|
854
|
+
get () {
|
|
855
|
+
const state = LAZY_FN_STATE.get(this)
|
|
856
|
+
return state ? state.name : ''
|
|
857
|
+
},
|
|
858
|
+
configurable: true
|
|
859
|
+
},
|
|
860
|
+
toString: { value: lazyFunctionToString, writable: true, configurable: true }
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// An async source's wrapper inherits from %AsyncFunction.prototype% (review
|
|
864
|
+
// N1): `Object.prototype.toString` reports '[object AsyncFunction]',
|
|
865
|
+
// `.constructor` is AsyncFunction and `instanceof` holds — while the wrapper
|
|
866
|
+
// itself stays a plain synchronous call that hands back the inner promise
|
|
867
|
+
// (an `async` wrapper would add microtask ticks to every call).
|
|
868
|
+
const ASYNC_FUNCTION_PROTOTYPE = Object.getPrototypeOf(async function () {})
|
|
869
|
+
|
|
870
|
+
const makeLazyFunction = (state) => {
|
|
871
|
+
let wrapper
|
|
872
|
+
if (state.kind === LAZY_KIND_PLAIN) {
|
|
873
|
+
wrapper = function () {
|
|
874
|
+
const fn = state.fn === undefined ? compileLazy(state) : state.fn
|
|
875
|
+
if (fn === null) return state.value
|
|
876
|
+
return new.target
|
|
877
|
+
? _constructLazy(fn, arguments, new.target)
|
|
878
|
+
: fn.apply(this, arguments)
|
|
879
|
+
}
|
|
880
|
+
} else {
|
|
881
|
+
// A method shorthand: receives `this`, has no `.prototype`, and throws
|
|
882
|
+
// on `new` — the same as the arrow / async / method it stands for.
|
|
883
|
+
wrapper = ({
|
|
884
|
+
lazy () {
|
|
885
|
+
const fn = state.fn === undefined ? compileLazy(state) : state.fn
|
|
886
|
+
if (fn === null) return state.value
|
|
887
|
+
return fn.apply(this, arguments)
|
|
888
|
+
}
|
|
889
|
+
}).lazy
|
|
890
|
+
}
|
|
891
|
+
LAZY_FN_STATE.set(wrapper, state)
|
|
892
|
+
Object.defineProperties(wrapper, LAZY_FN_DESCRIPTORS)
|
|
893
|
+
if (state.async) Object.setPrototypeOf(wrapper, ASYNC_FUNCTION_PROTOTYPE)
|
|
894
|
+
return wrapper
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// Revive one function string lazily when its head allows, eagerly otherwise.
|
|
898
|
+
const _reviveLazily = (str, key, index, opts) => {
|
|
899
|
+
const state = lazyStateFor(str)
|
|
900
|
+
if (!state) {
|
|
901
|
+
return _destringifyFnString(
|
|
902
|
+
str,
|
|
903
|
+
index === -1 ? `"${key}"` : `array index ${index} (prop "${key}")`,
|
|
904
|
+
opts
|
|
905
|
+
)
|
|
906
|
+
}
|
|
907
|
+
state.key = key
|
|
908
|
+
state.index = index
|
|
909
|
+
state.opts = opts
|
|
910
|
+
return makeLazyFunction(state)
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
/**
|
|
914
|
+
* True for a function `deepDestringifyFunctions(…, { lazy: true })` revived
|
|
915
|
+
* lazily (compiled or not yet).
|
|
916
|
+
*/
|
|
917
|
+
export const isLazyFunction = (fn) => typeof fn === 'function' && LAZY_FN_STATE.has(fn)
|
|
918
|
+
|
|
919
|
+
/**
|
|
920
|
+
* The source text of any function: the stored source of a lazily revived
|
|
921
|
+
* one (without compiling it), `Function.prototype.toString` for the rest.
|
|
922
|
+
* Use this instead of `Function.prototype.toString.call(fn)`, which reads a
|
|
923
|
+
* lazy wrapper's own text.
|
|
924
|
+
*/
|
|
925
|
+
export const getFunctionSource = (fn) => {
|
|
926
|
+
const state = LAZY_FN_STATE.get(fn)
|
|
927
|
+
return state ? state.src.trim() : Function.prototype.toString.call(fn)
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
/**
|
|
931
|
+
* True when `fn` is a lazily revived function whose source does not compile
|
|
932
|
+
* — it stands for the string eager revival would have left, so a caller that
|
|
933
|
+
* treated that string as "not callable" (`el.call`, utils `call`) must treat
|
|
934
|
+
* `fn` the same. Compiles `fn` if it has not run yet (the caller was about to
|
|
935
|
+
* call it). False for every other value.
|
|
936
|
+
*/
|
|
937
|
+
export const isFailedRevival = (fn) => {
|
|
938
|
+
const state = typeof fn === 'function' ? LAZY_FN_STATE.get(fn) : undefined
|
|
939
|
+
if (!state) return false
|
|
940
|
+
return (state.fn === undefined ? compileLazy(state) : state.fn) === null
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
// The CODE buckets of a project context — `{ lazy: 'context' }` revives
|
|
944
|
+
// lazily only under these top-level keys (see LAZY REVIVAL above).
|
|
945
|
+
const LAZY_CONTEXT_CODE_KEYS = new Set([
|
|
946
|
+
'components', 'pages', 'functions', 'methods', 'snippets', 'app', 'globalScope'
|
|
947
|
+
])
|
|
948
|
+
|
|
949
|
+
const DEFAULT_DESTRINGIFY_OPTS = { window: { eval: __globalEval } }
|
|
950
|
+
|
|
605
951
|
export const deepDestringifyFunctions = (
|
|
606
952
|
obj,
|
|
607
953
|
destringified = {},
|
|
608
|
-
opts =
|
|
954
|
+
opts = DEFAULT_DESTRINGIFY_OPTS
|
|
609
955
|
) => {
|
|
610
956
|
if (!obj || typeof obj !== 'object') return destringified
|
|
957
|
+
// `opts.window.eval` is the one field every runtime since 2026-04 reads,
|
|
958
|
+
// so a page script can pass `{ lazy: true, window }` to ANY runtime: an
|
|
959
|
+
// older one ignores `lazy` and evals eagerly through `window.eval` (an
|
|
960
|
+
// indirect, global-scope eval — the same as `__globalEval`). An options
|
|
961
|
+
// object without a usable `window.eval` falls back to the global eval
|
|
962
|
+
// instead of throwing on the first function string.
|
|
963
|
+
if (!opts || !opts.window || typeof opts.window.eval !== 'function') {
|
|
964
|
+
opts = { ...opts, window: { eval: __globalEval } }
|
|
965
|
+
}
|
|
966
|
+
// `lazy: true` — lazy everywhere. `lazy: 'context'` — `obj` is a project
|
|
967
|
+
// context: lazy only below its code buckets, eager for everything else.
|
|
968
|
+
const lazyAll = opts.lazy === true
|
|
969
|
+
const lazyContext = opts.lazy === 'context'
|
|
970
|
+
const lazyBelow = (src, prop, lazyHere) => lazyAll ||
|
|
971
|
+
(lazyContext && (src === obj ? LAZY_CONTEXT_CODE_KEYS.has(prop) : lazyHere))
|
|
611
972
|
|
|
612
973
|
// Iterative walk with explicit stack so deeply-nested project data
|
|
613
974
|
// (a frank-readback of a complex DOMQL tree, for instance) doesn't
|
|
@@ -615,25 +976,30 @@ export const deepDestringifyFunctions = (
|
|
|
615
976
|
// form did. Stack frame: [src, dest] where dest is the container
|
|
616
977
|
// results land in. Array items handled in-place; nested objects /
|
|
617
978
|
// arrays get a fresh child container pushed onto the stack.
|
|
618
|
-
const stack = [[obj, destringified]]
|
|
979
|
+
const stack = [[obj, destringified, lazyAll]]
|
|
619
980
|
while (stack.length) {
|
|
620
|
-
const [src, dest] = stack.pop()
|
|
981
|
+
const [src, dest, lazyHere] = stack.pop()
|
|
621
982
|
for (const prop in src) {
|
|
622
983
|
if (!Object.prototype.hasOwnProperty.call(src, prop)) continue
|
|
623
984
|
const objProp = src[prop]
|
|
624
985
|
if (isString(objProp)) {
|
|
625
986
|
if (hasFunction(objProp)) {
|
|
626
|
-
dest[prop] =
|
|
987
|
+
dest[prop] = lazyHere
|
|
988
|
+
? _reviveLazily(objProp, prop, -1, opts)
|
|
989
|
+
: _destringifyFnString(objProp, `"${prop}"`, opts)
|
|
627
990
|
} else {
|
|
628
991
|
dest[prop] = objProp
|
|
629
992
|
}
|
|
630
993
|
} else if (isArray(objProp)) {
|
|
631
994
|
const arr = (dest[prop] = [])
|
|
995
|
+
const lazyItems = lazyBelow(src, prop, lazyHere)
|
|
632
996
|
for (let i = 0; i < objProp.length; i++) {
|
|
633
997
|
const arrProp = objProp[i]
|
|
634
998
|
if (isString(arrProp)) {
|
|
635
999
|
arr.push(hasFunction(arrProp)
|
|
636
|
-
?
|
|
1000
|
+
? (lazyItems
|
|
1001
|
+
? _reviveLazily(arrProp, prop, i, opts)
|
|
1002
|
+
: _destringifyFnString(arrProp, `array index ${i} (prop "${prop}")`, opts))
|
|
637
1003
|
: arrProp)
|
|
638
1004
|
} else if (isObject(arrProp)) {
|
|
639
1005
|
const tagged = _rehydrateTagged(arrProp)
|
|
@@ -642,7 +1008,7 @@ export const deepDestringifyFunctions = (
|
|
|
642
1008
|
} else {
|
|
643
1009
|
const child = {}
|
|
644
1010
|
arr.push(child)
|
|
645
|
-
stack.push([arrProp, child])
|
|
1011
|
+
stack.push([arrProp, child, lazyItems])
|
|
646
1012
|
}
|
|
647
1013
|
} else {
|
|
648
1014
|
arr.push(arrProp)
|
|
@@ -665,7 +1031,7 @@ export const deepDestringifyFunctions = (
|
|
|
665
1031
|
const child = (dest[prop] && typeof dest[prop] === 'object' && !isArray(dest[prop]))
|
|
666
1032
|
? dest[prop]
|
|
667
1033
|
: (dest[prop] = {})
|
|
668
|
-
stack.push([objProp, child])
|
|
1034
|
+
stack.push([objProp, child, lazyBelow(src, prop, lazyHere)])
|
|
669
1035
|
} else {
|
|
670
1036
|
dest[prop] = objProp
|
|
671
1037
|
}
|