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. jora is a `devDependency` (`catalog:inlined` in the
18+ * workspace catalog) rather than a regular `dependency`, so tsdown vendors
19+ * it straight into this package's own `dist` on both the node and browser
20+ * builds — the on-demand `import()` resolves a local chunk, and neither
21+ * side needs consumers to install jora themselves.
1222 */
23+ import type { Jora } from 'jora'
1324import type { NodePath , QueryOutcome , SuggestItem , SuggestOutcome } from './contract'
1425import type { NormalizeOptions } from './normalize'
15- import jora from 'jora'
1626import { navigate , normalize } from './normalize'
1727
1828export type { SuggestItem , SuggestOutcome } from './contract'
@@ -45,51 +55,62 @@ function isSetLike(v: unknown): v is Set<unknown> {
4555 && typeof ( v as Map < unknown , unknown > ) . get !== 'function'
4656}
4757
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'
58+ type CreateQuery = ReturnType < Jora [ 'setup' ] >
59+
60+ /**
61+ * jora loads on first use and is cached for the process lifetime — a single
62+ * `import('jora')` + `setup()`, however many queries follow.
63+ */
64+ let createQueryPromise : Promise < CreateQuery > | undefined
65+
66+ function getCreateQuery ( ) : Promise < CreateQuery > {
67+ return createQueryPromise ??= import ( 'jora' ) . then ( ( { default : jora } ) => jora . setup ( {
68+ methods : {
69+ /** Map(-like or normalized tag) -> plain object (string-coerced keys). */
70+ fromMap : ( v ) => {
71+ if ( isMapLike ( v ) )
72+ return Object . fromEntries ( v . entries ( ) )
73+ if ( isMapTag ( v ) )
74+ return v . value ?? Object . fromEntries ( ( v . entries ?? [ ] ) . map ( e => [ String ( e . key ) , e . value ] ) )
75+ return v
76+ } ,
77+ /** Map(-like or normalized tag) -> [{ key, value }] preserving key identity. */
78+ mapEntries : ( v ) => {
79+ if ( isMapLike ( v ) )
80+ return [ ...v . entries ( ) ] . map ( ( [ key , value ] ) => ( { key, value } ) )
81+ if ( isMapTag ( v ) ) {
82+ if ( v . entries )
83+ return v . entries
84+ return Object . entries ( v . value ?? { } ) . map ( ( [ key , value ] ) => ( { key, value } ) )
85+ }
86+ return [ ]
87+ } ,
88+ /** Set(-like or normalized tag) -> array. */
89+ fromSet : ( v ) => {
90+ if ( isSetLike ( v ) )
91+ return [ ...v ]
92+ if ( isSetTag ( v ) )
93+ return v . values ?? [ ]
94+ return v
95+ } ,
96+ /** Constructor name of any value. */
97+ typeOf : ( v ) => {
98+ if ( v === null )
99+ return 'null'
100+ if ( typeof v !== 'object' )
101+ return typeof v
102+ return ( v as object ) . constructor ?. name ?? 'Object'
103+ } ,
104+ /** All own keys (incl. non-enumerable), as strings. */
105+ ownKeys : v => ( v && typeof v === 'object' ) ? Reflect . ownKeys ( v ) . map ( String ) : [ ] ,
84106 } ,
85- /** All own keys (incl. non-enumerable), as strings. */
86- ownKeys : v => ( v && typeof v === 'object' ) ? Reflect . ownKeys ( v ) . map ( String ) : [ ] ,
87- } ,
88- } )
107+ } ) )
108+ }
89109
90- export function runQuery ( target : unknown , query : string , options ?: NormalizeOptions ) : QueryOutcome {
110+ export async function runQuery ( target : unknown , query : string , options ?: NormalizeOptions ) : Promise < QueryOutcome > {
91111 try {
92112 const started = performance . now ( )
113+ const createQuery = await getCreateQuery ( )
93114 const raw = createQuery ( query ) ( target )
94115 const queryMs = Math . round ( ( performance . now ( ) - started ) * 100 ) / 100
95116 const { data, stats } = normalize ( raw , options )
@@ -110,9 +131,10 @@ export function runQuery(target: unknown, query: string, options?: NormalizeOpti
110131 * 'depth'` marker the client is expanding, so the same filter options must be
111132 * threaded through (they shift array indices and drop keys).
112133 */
113- export function runQueryAtPath ( target : unknown , query : string , path : NodePath , options ?: NormalizeOptions ) : QueryOutcome {
134+ export async function runQueryAtPath ( target : unknown , query : string , path : NodePath , options ?: NormalizeOptions ) : Promise < QueryOutcome > {
114135 try {
115136 const started = performance . now ( )
137+ const createQuery = await getCreateQuery ( )
116138 const raw = createQuery ( query ) ( target )
117139 const node = navigate ( raw , path , options )
118140 const queryMs = Math . round ( ( performance . now ( ) - started ) * 100 ) / 100
@@ -140,9 +162,10 @@ interface JoraStatEntry {
140162 * its candidates in a nested `suggestions` array — flattened here into plain,
141163 * RPC-safe completion items.
142164 */
143- export function suggest ( target : unknown , query : string , pos : number , limit = 30 ) : SuggestOutcome {
165+ export async function suggest ( target : unknown , query : string , pos : number , limit = 30 ) : Promise < SuggestOutcome > {
144166 try {
145167 const started = performance . now ( )
168+ const createQuery = await getCreateQuery ( )
146169 const statApi = createQuery ( query , { tolerant : true , stat : true } ) ( target ) as {
147170 suggestion : ( pos : number , opts ?: { limit ?: number } ) => JoraStatEntry [ ] | null
148171 }
0 commit comments