99 * forms (`{ $type: 'Map', value }`), keeping queries portable;
1010 * - suggestions come from jora's stat mode, flattened into plain
1111 * RPC-safe completion items.
12+ *
13+ * jora itself loads lazily, on the first query: `import('jora')` only runs
14+ * once `runQuery`/`runQueryAtPath`/`suggest` are actually called, so simply
15+ * registering the data-inspector's RPC functions (which happens on every
16+ * host that sets it up, whether or not anyone opens the panel) never pays
17+ * for parsing jora. The node build inlines jora into its own output (see
18+ * `tsdown.config.ts`'s `deps.alwaysBundle`) so that on-demand `import()`
19+ * resolves a local chunk instead of a `node_modules` lookup.
1220 */
21+ import type { Jora } from 'jora'
1322import type { NodePath , QueryOutcome , SuggestItem , SuggestOutcome } from './contract'
1423import type { NormalizeOptions } from './normalize'
15- import jora from 'jora'
1624import { navigate , normalize } from './normalize'
1725
1826export type { SuggestItem , SuggestOutcome } from './contract'
@@ -45,51 +53,62 @@ function isSetLike(v: unknown): v is Set<unknown> {
4553 && typeof ( v as Map < unknown , unknown > ) . get !== 'function'
4654}
4755
48- const createQuery = jora . setup ( {
49- methods : {
50- /** Map(-like or normalized tag) -> plain object (string-coerced keys). */
51- fromMap : ( v ) => {
52- if ( isMapLike ( v ) )
53- return Object . fromEntries ( v . entries ( ) )
54- if ( isMapTag ( v ) )
55- return v . value ?? Object . fromEntries ( ( v . entries ?? [ ] ) . map ( e => [ String ( e . key ) , e . value ] ) )
56- return v
57- } ,
58- /** Map(-like or normalized tag) -> [{ key, value }] preserving key identity. */
59- mapEntries : ( v ) => {
60- if ( isMapLike ( v ) )
61- return [ ...v . entries ( ) ] . map ( ( [ key , value ] ) => ( { key, value } ) )
62- if ( isMapTag ( v ) ) {
63- if ( v . entries )
64- return v . entries
65- return Object . entries ( v . value ?? { } ) . map ( ( [ key , value ] ) => ( { key, value } ) )
66- }
67- return [ ]
68- } ,
69- /** Set(-like or normalized tag) -> array. */
70- fromSet : ( v ) => {
71- if ( isSetLike ( v ) )
72- return [ ...v ]
73- if ( isSetTag ( v ) )
74- return v . values ?? [ ]
75- return v
76- } ,
77- /** Constructor name of any value. */
78- typeOf : ( v ) => {
79- if ( v === null )
80- return 'null'
81- if ( typeof v !== 'object' )
82- return typeof v
83- return ( v as object ) . constructor ?. name ?? 'Object'
56+ type CreateQuery = ReturnType < Jora [ 'setup' ] >
57+
58+ /**
59+ * jora loads on first use and is cached for the process lifetime — a single
60+ * `import('jora')` + `setup()`, however many queries follow.
61+ */
62+ let createQueryPromise : Promise < CreateQuery > | undefined
63+
64+ function getCreateQuery ( ) : Promise < CreateQuery > {
65+ return createQueryPromise ??= import ( 'jora' ) . then ( ( { default : jora } ) => jora . setup ( {
66+ methods : {
67+ /** Map(-like or normalized tag) -> plain object (string-coerced keys). */
68+ fromMap : ( v ) => {
69+ if ( isMapLike ( v ) )
70+ return Object . fromEntries ( v . entries ( ) )
71+ if ( isMapTag ( v ) )
72+ return v . value ?? Object . fromEntries ( ( v . entries ?? [ ] ) . map ( e => [ String ( e . key ) , e . value ] ) )
73+ return v
74+ } ,
75+ /** Map(-like or normalized tag) -> [{ key, value }] preserving key identity. */
76+ mapEntries : ( v ) => {
77+ if ( isMapLike ( v ) )
78+ return [ ...v . entries ( ) ] . map ( ( [ key , value ] ) => ( { key, value } ) )
79+ if ( isMapTag ( v ) ) {
80+ if ( v . entries )
81+ return v . entries
82+ return Object . entries ( v . value ?? { } ) . map ( ( [ key , value ] ) => ( { key, value } ) )
83+ }
84+ return [ ]
85+ } ,
86+ /** Set(-like or normalized tag) -> array. */
87+ fromSet : ( v ) => {
88+ if ( isSetLike ( v ) )
89+ return [ ...v ]
90+ if ( isSetTag ( v ) )
91+ return v . values ?? [ ]
92+ return v
93+ } ,
94+ /** Constructor name of any value. */
95+ typeOf : ( v ) => {
96+ if ( v === null )
97+ return 'null'
98+ if ( typeof v !== 'object' )
99+ return typeof v
100+ return ( v as object ) . constructor ?. name ?? 'Object'
101+ } ,
102+ /** All own keys (incl. non-enumerable), as strings. */
103+ ownKeys : v => ( v && typeof v === 'object' ) ? Reflect . ownKeys ( v ) . map ( String ) : [ ] ,
84104 } ,
85- /** All own keys (incl. non-enumerable), as strings. */
86- ownKeys : v => ( v && typeof v === 'object' ) ? Reflect . ownKeys ( v ) . map ( String ) : [ ] ,
87- } ,
88- } )
105+ } ) )
106+ }
89107
90- export function runQuery ( target : unknown , query : string , options ?: NormalizeOptions ) : QueryOutcome {
108+ export async function runQuery ( target : unknown , query : string , options ?: NormalizeOptions ) : Promise < QueryOutcome > {
91109 try {
92110 const started = performance . now ( )
111+ const createQuery = await getCreateQuery ( )
93112 const raw = createQuery ( query ) ( target )
94113 const queryMs = Math . round ( ( performance . now ( ) - started ) * 100 ) / 100
95114 const { data, stats } = normalize ( raw , options )
@@ -110,9 +129,10 @@ export function runQuery(target: unknown, query: string, options?: NormalizeOpti
110129 * 'depth'` marker the client is expanding, so the same filter options must be
111130 * threaded through (they shift array indices and drop keys).
112131 */
113- export function runQueryAtPath ( target : unknown , query : string , path : NodePath , options ?: NormalizeOptions ) : QueryOutcome {
132+ export async function runQueryAtPath ( target : unknown , query : string , path : NodePath , options ?: NormalizeOptions ) : Promise < QueryOutcome > {
114133 try {
115134 const started = performance . now ( )
135+ const createQuery = await getCreateQuery ( )
116136 const raw = createQuery ( query ) ( target )
117137 const node = navigate ( raw , path , options )
118138 const queryMs = Math . round ( ( performance . now ( ) - started ) * 100 ) / 100
@@ -140,9 +160,10 @@ interface JoraStatEntry {
140160 * its candidates in a nested `suggestions` array — flattened here into plain,
141161 * RPC-safe completion items.
142162 */
143- export function suggest ( target : unknown , query : string , pos : number , limit = 30 ) : SuggestOutcome {
163+ export async function suggest ( target : unknown , query : string , pos : number , limit = 30 ) : Promise < SuggestOutcome > {
144164 try {
145165 const started = performance . now ( )
166+ const createQuery = await getCreateQuery ( )
146167 const statApi = createQuery ( query , { tolerant : true , stat : true } ) ( target ) as {
147168 suggestion : ( pos : number , opts ?: { limit ?: number } ) => JoraStatEntry [ ] | null
148169 }
0 commit comments