@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/dist/main.js +1692 -1792
- package/package.json +1 -1
- package/src/api/qik.content.js +26 -280
- package/src/version.js +1 -1
package/package.json
CHANGED
package/src/api/qik.content.js
CHANGED
|
@@ -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
|
-
*
|
|
81
|
-
* Uses concurrent workers for faster loading
|
|
71
|
+
* Yield to the event loop to keep the UI responsive
|
|
82
72
|
* @private
|
|
83
73
|
*/
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
*
|
|
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
|
|
121
|
-
|
|
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
|
-
|
|
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
|
-
|
|
304
|
-
|
|
305
|
-
|
|
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
|
-
|
|
314
|
-
|
|
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 (
|
|
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
|
+
export const version = "0.2.0";
|