pulse-js-framework 1.7.15 → 1.7.16

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/cli/index.js CHANGED
@@ -9,6 +9,7 @@ import { dirname, join, resolve, relative } from 'path';
9
9
  import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, watch } from 'fs';
10
10
  import { log } from './logger.js';
11
11
  import { findPulseFiles, parseArgs } from './utils/file-utils.js';
12
+ import { runHelp } from './help.js';
12
13
 
13
14
  const __filename = fileURLToPath(import.meta.url);
14
15
  const __dirname = dirname(__filename);
@@ -136,10 +137,28 @@ function suggestCommand(input) {
136
137
  */
137
138
  async function main() {
138
139
  const args = process.argv.slice(2);
139
- const command = args[0] || 'help';
140
+ let command = args[0] || 'help';
141
+
142
+ // Handle global --help and -h flags
143
+ if (command === '--help' || command === '-h') {
144
+ command = 'help';
145
+ }
146
+
147
+ // Handle --version and -v flags
148
+ if (command === '--version' || command === '-v') {
149
+ command = 'version';
150
+ }
151
+
152
+ // Handle command-specific help: pulse <cmd> --help or pulse <cmd> -h
153
+ const cmdArgs = args.slice(1);
154
+ if (cmdArgs.includes('--help') || cmdArgs.includes('-h')) {
155
+ // Show help for the specific command
156
+ await commands.help([command]);
157
+ return;
158
+ }
140
159
 
141
160
  if (command in commands) {
142
- await commands[command](args.slice(1));
161
+ await commands[command](cmdArgs);
143
162
  } else {
144
163
  log.error(`Unknown command: ${command}`);
145
164
 
@@ -156,110 +175,10 @@ async function main() {
156
175
 
157
176
  /**
158
177
  * Show help message
178
+ * Supports: pulse help, pulse help <command>
159
179
  */
160
- function showHelp() {
161
- log.info(`
162
- Pulse Framework CLI v${VERSION}
163
-
164
- Usage: pulse <command> [options]
165
-
166
- Commands:
167
- create <name> Create a new Pulse project
168
- init [options] Initialize project in current directory
169
- dev [port] Start development server (default: 3000)
170
- build Build for production (minified)
171
- preview [port] Preview production build (default: 4173)
172
- compile <file> Compile a .pulse file to JavaScript
173
- mobile <cmd> Mobile app commands (init, build, run)
174
- lint [files] Validate .pulse files for errors and style
175
- format [files] Format .pulse files consistently
176
- analyze Analyze bundle size and dependencies
177
- test [files] Run tests with coverage support
178
- doctor Run project diagnostics
179
- scaffold <type> Generate components, pages, stores
180
- docs Generate API documentation from JSDoc
181
- release <type> Create a new release (patch, minor, major)
182
- docs-test Test documentation (syntax, imports, HTTP)
183
- version Show version number
184
- help Show this help message
185
-
186
- Create/Init Options:
187
- --typescript Create TypeScript project
188
- --minimal Create minimal project structure
189
-
190
- Compile Options:
191
- --watch, -w Watch files and recompile on changes
192
- --dry-run Show what would be compiled without writing
193
- --output, -o Output directory (default: same as input)
194
-
195
- Lint Options:
196
- --fix Auto-fix fixable issues
197
- --watch, -w Watch files and re-lint on changes
198
- --dry-run Show fixes without applying (use with --fix)
199
-
200
- Format Options:
201
- --check Check formatting without writing (dry-run)
202
- --watch, -w Watch files and re-format on changes
203
- --write Write formatted output (default)
204
-
205
- Analyze Options:
206
- --json Output analysis as JSON
207
- --verbose Show detailed metrics
208
-
209
- Test Options:
210
- --coverage, -c Collect code coverage
211
- --watch, -w Watch files and re-run tests
212
- --filter, -f Filter tests by name pattern
213
- --timeout, -t Test timeout in ms (default: 30000)
214
- --bail, -b Stop on first failure
215
- --create <name> Generate a new test file
216
-
217
- Doctor Options:
218
- --verbose, -v Show detailed diagnostics
219
- --json Output as JSON
220
-
221
- Scaffold Options:
222
- --dir, -d <path> Output directory
223
- --force, -f Overwrite existing files
224
- --props Include props section (components)
225
-
226
- Docs Options:
227
- --generate, -g Generate documentation
228
- --format, -f Output format: markdown, json, html
229
- --output, -o Output directory (default: docs/api)
230
-
231
- Release Options:
232
- --dry-run Show what would be done without making changes
233
- --no-push Create commit and tag but don't push
234
- --title <text> Release title for changelog
235
- --skip-prompt Use empty changelog (for automation)
236
- --skip-docs-test Skip documentation tests before release
237
- --from-commits Auto-extract changelog from git commits since last tag
238
-
239
- Examples:
240
- pulse create my-app
241
- pulse create my-app --typescript
242
- pulse init --typescript
243
- pulse dev
244
- pulse build
245
- pulse test
246
- pulse test --coverage --watch
247
- pulse test --create MyComponent
248
- pulse doctor
249
- pulse doctor --verbose
250
- pulse scaffold component Button
251
- pulse scaffold page Dashboard
252
- pulse scaffold store user
253
- pulse docs --generate
254
- pulse docs --generate --format html
255
- pulse compile src/App.pulse
256
- pulse lint src/ --fix
257
- pulse format --check
258
- pulse analyze --json
259
- pulse release patch
260
-
261
- Documentation: https://github.com/vincenthirtz/pulse-js-framework
262
- `);
180
+ function showHelp(args = []) {
181
+ runHelp(args);
263
182
  }
264
183
 
265
184
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pulse-js-framework",
3
- "version": "1.7.15",
3
+ "version": "1.7.16",
4
4
  "description": "A declarative DOM framework with CSS selector-based structure and reactive pulsations",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -109,7 +109,7 @@
109
109
  "LICENSE"
110
110
  ],
111
111
  "scripts": {
112
- "test": "npm run test:compiler && npm run test:sourcemap && npm run test:pulse && npm run test:dom && npm run test:dom-element && npm run test:dom-adapter && npm run test:enhanced-mock-adapter && npm run test:router && npm run test:store && npm run test:context && npm run test:hmr && npm run test:lint && npm run test:format && npm run test:analyze && npm run test:cli && npm run test:cli-ui && npm run test:lru-cache && npm run test:utils && npm run test:docs && npm run test:docs-nav && npm run test:async && npm run test:form && npm run test:http && npm run test:devtools && npm run test:native && npm run test:a11y && npm run test:a11y-enhanced && npm run test:logger && npm run test:errors && npm run test:security && npm run test:websocket && npm run test:graphql && npm run test:doctor && npm run test:scaffold && npm run test:test-runner && npm run test:build && npm run test:integration && npm run test:context-stress && npm run test:form-edge-cases && npm run test:graphql-subscriptions && npm run test:http-edge-cases && npm run test:integration-advanced && npm run test:websocket-stress",
112
+ "test": "npm run test:compiler && npm run test:sourcemap && npm run test:pulse && npm run test:dom && npm run test:dom-element && npm run test:dom-adapter && npm run test:enhanced-mock-adapter && npm run test:router && npm run test:store && npm run test:context && npm run test:hmr && npm run test:lint && npm run test:format && npm run test:analyze && npm run test:cli && npm run test:cli-ui && npm run test:lru-cache && npm run test:utils && npm run test:docs && npm run test:docs-nav && npm run test:async && npm run test:form && npm run test:http && npm run test:devtools && npm run test:native && npm run test:a11y && npm run test:a11y-enhanced && npm run test:logger && npm run test:errors && npm run test:security && npm run test:websocket && npm run test:graphql && npm run test:doctor && npm run test:scaffold && npm run test:test-runner && npm run test:build && npm run test:integration && npm run test:context-stress && npm run test:form-edge-cases && npm run test:graphql-subscriptions && npm run test:http-edge-cases && npm run test:integration-advanced && npm run test:websocket-stress && npm run test:ssr",
113
113
  "test:compiler": "node test/compiler.test.js",
114
114
  "test:sourcemap": "node test/sourcemap.test.js",
115
115
  "test:pulse": "node test/pulse.test.js",
@@ -153,6 +153,7 @@
153
153
  "test:http-edge-cases": "node test/http-edge-cases.test.js",
154
154
  "test:integration-advanced": "node test/integration-advanced.test.js",
155
155
  "test:websocket-stress": "node test/websocket-stress.test.js",
156
+ "test:ssr": "node test/ssr.test.js",
156
157
  "build:netlify": "node scripts/build-netlify.js",
157
158
  "version": "node scripts/sync-version.js",
158
159
  "docs": "node cli/index.js dev docs"
package/runtime/async.js CHANGED
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  import { pulse, effect, batch, onCleanup } from './pulse.js';
10
+ import { getSSRAsyncContext, registerAsync, getCachedAsync, hasCachedAsync } from './ssr-async.js';
10
11
 
11
12
  // ============================================================================
12
13
  // Versioned Async - Centralized Race Condition Handling
@@ -328,6 +329,44 @@ export function useAsync(asyncFn, options = {}) {
328
329
  retryDelay = 1000
329
330
  } = options;
330
331
 
332
+ // SSR MODE: Check for cached data or register async operation
333
+ const ssrCtx = getSSRAsyncContext();
334
+ if (ssrCtx) {
335
+ // Check if we already have cached data (second render pass)
336
+ if (hasCachedAsync(asyncFn)) {
337
+ const cachedData = getCachedAsync(asyncFn);
338
+ return {
339
+ data: pulse(cachedData),
340
+ error: pulse(null),
341
+ loading: pulse(false),
342
+ status: pulse('success'),
343
+ execute: () => Promise.resolve(cachedData),
344
+ reset: () => {},
345
+ abort: () => {}
346
+ };
347
+ }
348
+
349
+ // First render pass: register async operation for collection
350
+ if (immediate) {
351
+ const promise = asyncFn().catch(err => {
352
+ // Store error for SSR error handling
353
+ return null;
354
+ });
355
+ registerAsync(asyncFn, promise);
356
+ }
357
+
358
+ // Return loading state for first pass
359
+ return {
360
+ data: pulse(initialData),
361
+ error: pulse(null),
362
+ loading: pulse(true),
363
+ status: pulse('loading'),
364
+ execute: () => Promise.resolve(initialData),
365
+ reset: () => {},
366
+ abort: () => {}
367
+ };
368
+ }
369
+
331
370
  const data = pulse(initialData);
332
371
  const error = pulse(null);
333
372
  const loading = pulse(false);
@@ -10,6 +10,16 @@ import { loggers } from './logger.js';
10
10
  import { safeSetAttribute } from './utils.js';
11
11
  import { getAdapter } from './dom-adapter.js';
12
12
  import { parseSelector } from './dom-selector.js';
13
+ import {
14
+ isHydratingMode,
15
+ getHydrationContext,
16
+ getCurrentNode,
17
+ advanceCursor,
18
+ enterChild,
19
+ exitChild,
20
+ registerListener,
21
+ warnMismatch
22
+ } from './ssr-hydrator.js';
13
23
 
14
24
  const log = loggers.dom;
15
25
 
@@ -214,6 +224,62 @@ function applyRoleRequirements(element, role, attrs, dom) {
214
224
  export function el(selector, ...children) {
215
225
  const dom = getAdapter();
216
226
  const config = parseSelector(selector);
227
+
228
+ // HYDRATION MODE: Reuse existing DOM element
229
+ if (isHydratingMode()) {
230
+ const ctx = getHydrationContext();
231
+ const existing = getCurrentNode(ctx);
232
+
233
+ // Verify element matches
234
+ if (existing && existing.nodeType === 1) {
235
+ const tag = existing.tagName?.toLowerCase();
236
+ if (tag !== config.tag) {
237
+ warnMismatch(ctx, `<${config.tag}>`, existing);
238
+ }
239
+
240
+ // Process children to attach event handlers from attributes
241
+ const [attrs, childContent] = separateAttrsAndChildren(children);
242
+
243
+ if (attrs) {
244
+ // Attach event handlers to existing element
245
+ for (const [key, value] of Object.entries(attrs)) {
246
+ if (key.startsWith('on') && typeof value === 'function') {
247
+ const event = key.slice(2).toLowerCase();
248
+ registerListener(ctx, existing, event, value);
249
+ }
250
+ // Handle reactive attributes
251
+ else if (typeof value === 'function' && !key.startsWith('on')) {
252
+ effect(() => {
253
+ const result = value();
254
+ if (key === 'class' || key === 'className') {
255
+ existing.className = result || '';
256
+ } else if (key === 'style' && typeof result === 'string') {
257
+ existing.style.cssText = result;
258
+ } else if (result != null) {
259
+ existing.setAttribute(key, result);
260
+ } else {
261
+ existing.removeAttribute(key);
262
+ }
263
+ });
264
+ }
265
+ }
266
+ }
267
+
268
+ // Enter child scope and process children
269
+ enterChild(ctx, existing);
270
+ for (const child of childContent) {
271
+ hydrateChild(existing, child, ctx);
272
+ }
273
+ exitChild(ctx, existing);
274
+
275
+ return existing;
276
+ }
277
+
278
+ // No matching element found, warn and fall through to create
279
+ warnMismatch(ctx, `<${config.tag}>`, existing);
280
+ }
281
+
282
+ // NORMAL MODE: Create new element
217
283
  const element = dom.createElement(config.tag);
218
284
 
219
285
  if (config.id) {
@@ -240,6 +306,47 @@ export function el(selector, ...children) {
240
306
  return element;
241
307
  }
242
308
 
309
+ /**
310
+ * Separate attributes object from children in el() arguments
311
+ * @private
312
+ */
313
+ function separateAttrsAndChildren(children) {
314
+ if (children.length === 0) {
315
+ return [null, []];
316
+ }
317
+
318
+ const first = children[0];
319
+ if (first && typeof first === 'object' && !Array.isArray(first) &&
320
+ !(first instanceof Node) && !(first.nodeType)) {
321
+ return [first, children.slice(1)];
322
+ }
323
+
324
+ return [null, children];
325
+ }
326
+
327
+ /**
328
+ * Hydrate a child element (attach listeners without creating DOM)
329
+ * @private
330
+ */
331
+ function hydrateChild(parent, child, ctx) {
332
+ if (child == null || child === false) return;
333
+
334
+ if (typeof child === 'string' || typeof child === 'number') {
335
+ // Text content - just advance cursor
336
+ advanceCursor(ctx);
337
+ } else if (typeof child === 'function') {
338
+ // Reactive child - set up effect but skip initial DOM creation
339
+ effect(() => {
340
+ child(); // Execute to track dependencies, but don't modify DOM on first run in hydration
341
+ });
342
+ } else if (Array.isArray(child)) {
343
+ for (const c of child) {
344
+ hydrateChild(parent, c, ctx);
345
+ }
346
+ }
347
+ // Node children are handled by recursive el() calls
348
+ }
349
+
243
350
  /**
244
351
  * Append a child to an element, handling various types
245
352
  *
package/runtime/index.js CHANGED
@@ -12,6 +12,7 @@ export * from './a11y.js';
12
12
  export * from './context.js';
13
13
  export * from './websocket.js';
14
14
  export * from './graphql.js';
15
+ export * from './ssr.js';
15
16
 
16
17
  export { default as PulseCore } from './pulse.js';
17
18
  export { default as PulseDOM } from './dom.js';
@@ -23,3 +24,4 @@ export { default as PulseA11y } from './a11y.js';
23
24
  export { default as PulseContext } from './context.js';
24
25
  export { default as PulseWebSocket } from './websocket.js';
25
26
  export { default as PulseGraphQL } from './graphql.js';
27
+ export { default as PulseSSR } from './ssr.js';
package/runtime/pulse.js CHANGED
@@ -23,6 +23,35 @@ import { Errors } from './errors.js';
23
23
 
24
24
  const log = loggers.pulse;
25
25
 
26
+ // =============================================================================
27
+ // SSR MODE FLAG
28
+ // =============================================================================
29
+
30
+ /**
31
+ * SSR mode flag - when true, effects run once without setting up subscriptions.
32
+ * This is set by the SSR module during server-side rendering.
33
+ * @type {boolean}
34
+ */
35
+ let ssrModeEnabled = false;
36
+
37
+ /**
38
+ * Check if SSR mode is enabled.
39
+ * In SSR mode, effects run once but don't subscribe to changes.
40
+ * @returns {boolean}
41
+ */
42
+ export function isSSRMode() {
43
+ return ssrModeEnabled;
44
+ }
45
+
46
+ /**
47
+ * Set SSR mode (used internally by ssr.js).
48
+ * @param {boolean} enabled - Whether to enable SSR mode
49
+ * @internal
50
+ */
51
+ export function setSSRMode(enabled) {
52
+ ssrModeEnabled = enabled;
53
+ }
54
+
26
55
  // =============================================================================
27
56
  // REACTIVE DEPENDENCY TRACKING ALGORITHM
28
57
  // =============================================================================
@@ -885,6 +914,17 @@ export function effect(fn, options = {}) {
885
914
  const { id: customId, onError } = options;
886
915
  const effectId = customId || `effect_${++effectIdCounter}`;
887
916
 
917
+ // SSR MODE: Run effect once without subscriptions
918
+ if (ssrModeEnabled) {
919
+ try {
920
+ fn();
921
+ } catch (e) {
922
+ log.warn(`SSR effect error (${effectId}):`, e.message);
923
+ }
924
+ // Return noop cleanup function
925
+ return () => {};
926
+ }
927
+
888
928
  // Capture module ID at creation time for HMR tracking
889
929
  const moduleId = activeContext.currentModuleId;
890
930
 
@@ -0,0 +1,229 @@
1
+ /**
2
+ * Pulse SSR Async Context - Async operation collection for SSR
3
+ *
4
+ * Collects and manages async operations during server-side rendering.
5
+ * Enables data prefetching before HTML generation.
6
+ */
7
+
8
+ // ============================================================================
9
+ // SSR Async Context
10
+ // ============================================================================
11
+
12
+ /**
13
+ * Context for collecting async operations during SSR.
14
+ * Tracks pending promises and caches resolved data for re-renders.
15
+ */
16
+ export class SSRAsyncContext {
17
+ constructor() {
18
+ /** @type {Array<{key: any, promise: Promise}>} */
19
+ this.pending = [];
20
+
21
+ /** @type {Map<any, any>} */
22
+ this.resolved = new Map();
23
+
24
+ /** @type {Map<any, Error>} */
25
+ this.errors = new Map();
26
+
27
+ /** @type {boolean} */
28
+ this.collecting = true;
29
+ }
30
+
31
+ /**
32
+ * Register an async operation for collection.
33
+ * @param {any} key - Unique key for this operation (usually the async function)
34
+ * @param {Promise} promise - The promise to track
35
+ */
36
+ register(key, promise) {
37
+ if (!this.collecting) return;
38
+
39
+ // Wrap promise to capture result
40
+ const tracked = promise
41
+ .then(data => {
42
+ this.resolved.set(key, data);
43
+ return data;
44
+ })
45
+ .catch(error => {
46
+ this.errors.set(key, error);
47
+ throw error;
48
+ });
49
+
50
+ this.pending.push({ key, promise: tracked });
51
+ }
52
+
53
+ /**
54
+ * Check if a result is already cached.
55
+ * @param {any} key - Operation key
56
+ * @returns {boolean} True if result is cached
57
+ */
58
+ has(key) {
59
+ return this.resolved.has(key);
60
+ }
61
+
62
+ /**
63
+ * Get cached result for a key.
64
+ * @param {any} key - Operation key
65
+ * @returns {any} Cached result or undefined
66
+ */
67
+ get(key) {
68
+ return this.resolved.get(key);
69
+ }
70
+
71
+ /**
72
+ * Get error for a key (if the operation failed).
73
+ * @param {any} key - Operation key
74
+ * @returns {Error|undefined} Error or undefined
75
+ */
76
+ getError(key) {
77
+ return this.errors.get(key);
78
+ }
79
+
80
+ /**
81
+ * Wait for all pending async operations to complete.
82
+ * @param {number} [timeout=5000] - Maximum wait time in ms
83
+ * @returns {Promise<void>}
84
+ * @throws {Error} If timeout is exceeded
85
+ */
86
+ async waitAll(timeout = 5000) {
87
+ if (this.pending.length === 0) return;
88
+
89
+ const timeoutPromise = new Promise((_, reject) => {
90
+ setTimeout(() => {
91
+ reject(new Error(`[Pulse SSR] Async operations timed out after ${timeout}ms`));
92
+ }, timeout);
93
+ });
94
+
95
+ // Wait for all promises, catching individual errors
96
+ const allSettled = Promise.all(
97
+ this.pending.map(p => p.promise.catch(() => null))
98
+ );
99
+
100
+ await Promise.race([allSettled, timeoutPromise]);
101
+
102
+ // Stop collecting after wait
103
+ this.collecting = false;
104
+ }
105
+
106
+ /**
107
+ * Get the number of pending operations.
108
+ * @returns {number}
109
+ */
110
+ get pendingCount() {
111
+ return this.pending.length;
112
+ }
113
+
114
+ /**
115
+ * Get the number of resolved operations.
116
+ * @returns {number}
117
+ */
118
+ get resolvedCount() {
119
+ return this.resolved.size;
120
+ }
121
+
122
+ /**
123
+ * Get the number of failed operations.
124
+ * @returns {number}
125
+ */
126
+ get errorCount() {
127
+ return this.errors.size;
128
+ }
129
+
130
+ /**
131
+ * Get all resolved data as a plain object.
132
+ * @returns {Object} Map of key → value
133
+ */
134
+ getAllResolved() {
135
+ const result = {};
136
+ for (const [key, value] of this.resolved) {
137
+ // Use string key if function, otherwise try to serialize
138
+ const keyStr = typeof key === 'function' ? key.name || 'anonymous' : String(key);
139
+ result[keyStr] = value;
140
+ }
141
+ return result;
142
+ }
143
+
144
+ /**
145
+ * Reset the context for a new render pass.
146
+ */
147
+ reset() {
148
+ this.pending = [];
149
+ this.resolved.clear();
150
+ this.errors.clear();
151
+ this.collecting = true;
152
+ }
153
+ }
154
+
155
+ // ============================================================================
156
+ // Global SSR Async Context
157
+ // ============================================================================
158
+
159
+ /** @type {SSRAsyncContext|null} */
160
+ let ssrAsyncContext = null;
161
+
162
+ /**
163
+ * Get the current SSR async context.
164
+ * Returns null if not in SSR mode.
165
+ * @returns {SSRAsyncContext|null}
166
+ */
167
+ export function getSSRAsyncContext() {
168
+ return ssrAsyncContext;
169
+ }
170
+
171
+ /**
172
+ * Set the SSR async context.
173
+ * @param {SSRAsyncContext|null} ctx - Context to set, or null to clear
174
+ */
175
+ export function setSSRAsyncContext(ctx) {
176
+ ssrAsyncContext = ctx;
177
+ }
178
+
179
+ /**
180
+ * Check if currently in SSR async collection mode.
181
+ * @returns {boolean}
182
+ */
183
+ export function isCollectingAsync() {
184
+ return ssrAsyncContext !== null && ssrAsyncContext.collecting;
185
+ }
186
+
187
+ /**
188
+ * Register an async operation in the current SSR context.
189
+ * No-op if not in SSR mode.
190
+ * @param {any} key - Unique key for this operation
191
+ * @param {Promise} promise - The promise to track
192
+ */
193
+ export function registerAsync(key, promise) {
194
+ if (ssrAsyncContext) {
195
+ ssrAsyncContext.register(key, promise);
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Get cached async result from current SSR context.
201
+ * @param {any} key - Operation key
202
+ * @returns {any} Cached result or undefined
203
+ */
204
+ export function getCachedAsync(key) {
205
+ return ssrAsyncContext?.get(key);
206
+ }
207
+
208
+ /**
209
+ * Check if an async result is cached in current SSR context.
210
+ * @param {any} key - Operation key
211
+ * @returns {boolean}
212
+ */
213
+ export function hasCachedAsync(key) {
214
+ return ssrAsyncContext?.has(key) ?? false;
215
+ }
216
+
217
+ // ============================================================================
218
+ // Exports
219
+ // ============================================================================
220
+
221
+ export default {
222
+ SSRAsyncContext,
223
+ getSSRAsyncContext,
224
+ setSSRAsyncContext,
225
+ isCollectingAsync,
226
+ registerAsync,
227
+ getCachedAsync,
228
+ hasCachedAsync
229
+ };