@qikdev/sdk 0.1.213 → 0.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qikdev/sdk",
3
- "version": "0.1.213",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "description": "Promise based Javascript SDK",
6
6
  "main": "./dist/main.js",
@@ -64,148 +64,30 @@ export default function (qik) {
64
64
  ///////////////////////////////////////////////////
65
65
 
66
66
  const glossaryCache = qik.cache.get("user:glossary");
67
- const glossaryIndexCache = qik.cache.get("user:glossary:index");
68
- const definitionCache = qik.cache.get("user:glossary:definitions");
69
67
 
70
68
  let inflightGlossaryRequest;
71
- let inflightGlossaryIndexRequest;
72
- let inflightDefinitionRequests = {};
73
- let indexItemsByKey = {};
74
- let hydrationQueue = [];
75
- let isHydrating = false;
76
-
77
- const CONCURRENT_HYDRATION_REQUESTS = 3;
78
69
 
79
70
  /**
80
- * Start background hydration of all definitions
81
- * Uses concurrent workers for faster loading
71
+ * Yield to the event loop to keep the UI responsive
82
72
  * @private
83
73
  */
84
- async function startBackgroundHydration(keys) {
85
- hydrationQueue = [...keys];
86
- if (isHydrating) return;
87
- isHydrating = true;
88
-
89
- // Worker function that pulls from the shared queue
90
- async function worker() {
91
- while (hydrationQueue.length > 0) {
92
- const key = hydrationQueue.shift();
93
-
94
- // Skip if already populated or loading
95
- if (indexItemsByKey[key]?.populated) continue;
96
- if (inflightDefinitionRequests[key]) continue;
97
-
98
- try {
99
- await loadDefinition(key);
100
- } catch (e) {
101
- console.warn(`Failed to hydrate ${key}:`, e);
102
- }
74
+ function yieldToMain() {
75
+ return new Promise((resolve) => {
76
+ if (typeof requestIdleCallback === "function") {
77
+ requestIdleCallback(resolve, { timeout: 50 });
78
+ } else {
79
+ setTimeout(resolve, 0);
103
80
  }
104
- }
105
-
106
- // Start concurrent workers
107
- const workers = [];
108
- for (let i = 0; i < CONCURRENT_HYDRATION_REQUESTS; i++) {
109
- workers.push(worker());
110
- }
111
- await Promise.all(workers);
112
-
113
- isHydrating = false;
81
+ });
114
82
  }
115
83
 
116
84
  /**
117
- * Load a single definition from the server
85
+ * Inflate compressed glossary data from the /glossary/compressed endpoint
86
+ * Processes in chunks to avoid blocking the main thread
118
87
  * @private
119
88
  */
120
- async function loadDefinition(key) {
121
- inflightDefinitionRequests[key] = qik.api.get(`/glossary/${key}`);
122
-
123
- try {
124
- const { data } = await inflightDefinitionRequests[key];
125
-
126
- // Merge full data into existing index item (mutate in place)
127
- const indexItem = indexItemsByKey[key];
128
- if (indexItem) {
129
- Object.assign(indexItem, data, { populated: true });
130
- }
131
-
132
- // Also cache separately for getDefinition()
133
- const cached = indexItem || { ...data, populated: true };
134
- definitionCache.set(key, cached);
135
- qik.dispatch("glossary:definition", { key, data: cached });
136
- return cached;
137
- } finally {
138
- inflightDefinitionRequests[key] = null;
139
- }
140
- }
141
-
142
- /**
143
- * Get a full definition for a specific type.
144
- * Returns cached if available, otherwise fetches from server.
145
- * If background hydration is in progress, prioritizes this key.
146
- * @alias content.getDefinition
147
- * @param {String} key The type key (e.g., 'article', 'profile')
148
- * @param {Object} options Additional options
149
- * @param {Boolean} options.reload Force reload from server
150
- * @returns {Promise<Object>} The full type definition
151
- * @example
152
- * const article = await sdk.content.getDefinition('article');
153
- * console.log(article.fields);
154
- */
155
- service.getDefinition = async function (key, options) {
156
- options = options || {};
157
-
158
- // Return cached if available and not forcing reload
159
- let cached = definitionCache.get(key);
160
- if (cached && !options.reload && !options.refresh) {
161
- return cached;
162
- }
163
-
164
- // If already loading, wait for it
165
- if (inflightDefinitionRequests[key]) {
166
- await inflightDefinitionRequests[key];
167
- return definitionCache.get(key);
168
- }
169
-
170
- // Prioritize: move to front of queue
171
- const queueIndex = hydrationQueue.indexOf(key);
172
- if (queueIndex > 0) {
173
- hydrationQueue.splice(queueIndex, 1);
174
- hydrationQueue.unshift(key);
175
- }
176
-
177
- // Load it now
178
- return loadDefinition(key);
179
- };
180
-
181
- /**
182
- * Start loading all remaining definitions in the background.
183
- * Requires glossary({ index: true }) to be called first.
184
- * Uses concurrent requests for faster loading.
185
- * @alias content.hydrateInBackground
186
- * @example
187
- * await sdk.content.glossary({ index: true });
188
- * sdk.content.hydrateInBackground();
189
- */
190
- service.hydrateInBackground = function () {
191
- const cached = glossaryIndexCache.get("index");
192
- if (!cached) {
193
- console.warn(
194
- "Call glossary({ index: true }) before hydrateInBackground()",
195
- );
196
- return;
197
- }
198
-
199
- const keys = cached.map((item) => item.key);
200
- startBackgroundHydration(keys);
201
- };
202
-
203
- /**
204
- * Inflate a single value from compressed format
205
- * @private
206
- */
207
- function createInflator(strings, keyMap) {
208
- return function inflate(val) {
89
+ async function inflateGlossary({ _s: strings, _k: keyMap, types }) {
90
+ function inflate(val) {
209
91
  if (typeof val === "string" && val[0] === "~") {
210
92
  return strings[parseInt(val.slice(1))];
211
93
  }
@@ -218,111 +100,22 @@ export default function (qik) {
218
100
  return result;
219
101
  }
220
102
  return val;
221
- };
222
- }
103
+ }
223
104
 
224
- /**
225
- * Create a lazy-inflating array that only inflates items when accessed
226
- * This avoids the upfront cost of inflating the entire glossary
227
- * @private
228
- */
229
- function createLazyGlossary({ _s: strings, _k: keyMap, types }) {
230
- const inflate = createInflator(strings, keyMap);
231
- const cache = new Array(types.length);
232
- const inflated = new Array(types.length).fill(false);
233
-
234
- return new Proxy(types, {
235
- get(target, prop) {
236
- // Handle array methods and properties
237
- if (prop === "length") return target.length;
238
- if (prop === Symbol.iterator) {
239
- return function* () {
240
- for (let i = 0; i < target.length; i++) {
241
- if (!inflated[i]) {
242
- cache[i] = inflate(target[i]);
243
- inflated[i] = true;
244
- }
245
- yield cache[i];
246
- }
247
- };
248
- }
249
- if (prop === "map") {
250
- return function (fn) {
251
- const result = [];
252
- for (let i = 0; i < target.length; i++) {
253
- if (!inflated[i]) {
254
- cache[i] = inflate(target[i]);
255
- inflated[i] = true;
256
- }
257
- result.push(fn(cache[i], i, this));
258
- }
259
- return result;
260
- };
261
- }
262
- if (prop === "filter") {
263
- return function (fn) {
264
- const result = [];
265
- for (let i = 0; i < target.length; i++) {
266
- if (!inflated[i]) {
267
- cache[i] = inflate(target[i]);
268
- inflated[i] = true;
269
- }
270
- if (fn(cache[i], i, this)) {
271
- result.push(cache[i]);
272
- }
273
- }
274
- return result;
275
- };
276
- }
277
- if (prop === "find") {
278
- return function (fn) {
279
- for (let i = 0; i < target.length; i++) {
280
- if (!inflated[i]) {
281
- cache[i] = inflate(target[i]);
282
- inflated[i] = true;
283
- }
284
- if (fn(cache[i], i, this)) {
285
- return cache[i];
286
- }
287
- }
288
- return undefined;
289
- };
290
- }
291
- if (prop === "forEach") {
292
- return function (fn) {
293
- for (let i = 0; i < target.length; i++) {
294
- if (!inflated[i]) {
295
- cache[i] = inflate(target[i]);
296
- inflated[i] = true;
297
- }
298
- fn(cache[i], i, this);
299
- }
300
- };
301
- }
105
+ const CHUNK_SIZE = 50;
106
+ const results = [];
302
107
 
303
- // Handle numeric indices
304
- const index = typeof prop === "string" ? parseInt(prop, 10) : prop;
305
- if (Number.isInteger(index) && index >= 0 && index < target.length) {
306
- if (!inflated[index]) {
307
- cache[index] = inflate(target[index]);
308
- inflated[index] = true;
309
- }
310
- return cache[index];
311
- }
108
+ for (let i = 0; i < types.length; i += CHUNK_SIZE) {
109
+ const chunk = types.slice(i, i + CHUNK_SIZE);
110
+ results.push(...chunk.map(inflate));
312
111
 
313
- // Pass through other properties
314
- return Reflect.get(target, prop);
315
- },
316
- });
317
- }
112
+ // Yield to main thread between chunks
113
+ if (i + CHUNK_SIZE < types.length) {
114
+ await yieldToMain();
115
+ }
116
+ }
318
117
 
319
- /**
320
- * Process compressed glossary data
321
- * Uses lazy inflation to avoid blocking the main thread
322
- * @private
323
- */
324
- function inflateGlossary(data) {
325
- return createLazyGlossary(data);
118
+ return results;
326
119
  }
327
120
 
328
121
  /**
@@ -336,62 +129,15 @@ export default function (qik) {
336
129
  * @param {Boolean} options.reload Force glossary to reload and not be cached.
337
130
  If false will retrieve content type data from the in memory cache
338
131
  * @param {Boolean} options.uncompressed Use the uncompressed endpoint
339
- * @param {Boolean} options.index Use lightweight index mode with background hydration
340
132
  * @example
341
133
  *
342
134
  * const { article, profile } = await sdk.content.glossary({hash:true});
343
135
  * // Use compressed endpoint for faster loading
344
136
  * const glossary = await sdk.content.glossary({compressed:true});
345
- * // Use lightweight index with background hydration
346
- * const glossary = await sdk.content.glossary({index:true, hash:true});
347
137
  */
348
138
  service.glossary = async function (options) {
349
139
  options = options || {};
350
140
 
351
- // Index mode: lightweight load with background hydration
352
- if (options.index) {
353
- let cached = glossaryIndexCache.get("index");
354
- const reload = options.reload || options.refresh || !cached;
355
-
356
- if (reload) {
357
- if (!inflightGlossaryIndexRequest) {
358
- inflightGlossaryIndexRequest = qik.api.get("/glossary/index");
359
- inflightGlossaryIndexRequest.then(
360
- () => {
361
- inflightGlossaryIndexRequest = null;
362
- },
363
- () => {
364
- inflightGlossaryIndexRequest = null;
365
- },
366
- );
367
- }
368
-
369
- const { data } = await inflightGlossaryIndexRequest;
370
-
371
- // Add populated:false to each item and build lookup map
372
- data.forEach((item) => {
373
- item.populated = false;
374
- indexItemsByKey[item.key] = item;
375
- });
376
-
377
- glossaryIndexCache.set("index", data);
378
- cached = data;
379
- qik.dispatch("glossary:index", data);
380
- // No auto-hydration - call hydrateInBackground() explicitly if needed
381
- }
382
-
383
- if (options.hash) {
384
- return qik.utils.hash(cached, "key");
385
- }
386
-
387
- if (options.hex) {
388
- return qik.utils.hash(cached, "hex");
389
- }
390
-
391
- return cached;
392
- }
393
-
394
- // Full glossary mode (existing behavior)
395
141
  // Check if we already have the glossary cached
396
142
  let cached = glossaryCache.get("glossary");
397
143
  const uncompressed = !!options.uncompressed;
@@ -416,9 +162,9 @@ export default function (qik) {
416
162
  // Now we have the glossary
417
163
  const { data } = await inflightGlossaryRequest;
418
164
 
419
- // Inflate if compressed format (lazy - inflates on access)
165
+ // Inflate if compressed format (async to avoid blocking UI)
420
166
  const glossaryData =
421
- data._s && data._k && data.types ? inflateGlossary(data) : data;
167
+ data._s && data._k && data.types ? await inflateGlossary(data) : data;
422
168
 
423
169
  // Cache it for later
424
170
  glossaryCache.set("glossary", glossaryData);
package/src/version.js CHANGED
@@ -1 +1 @@
1
- export const version = "0.1.213";
1
+ export const version = "0.2.0";