@spoosh/core 0.15.1 → 0.17.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/index.js CHANGED
@@ -26,6 +26,7 @@ __export(src_exports, {
26
26
  __DEV__: () => __DEV__,
27
27
  buildUrl: () => buildUrl,
28
28
  clone: () => clone,
29
+ composeAdapter: () => composeAdapter,
29
30
  containsFile: () => containsFile,
30
31
  createClient: () => createClient,
31
32
  createEventEmitter: () => createEventEmitter,
@@ -39,12 +40,14 @@ __export(src_exports, {
39
40
  createSelectorProxy: () => createSelectorProxy,
40
41
  createSpooshPlugin: () => createSpooshPlugin,
41
42
  createStateManager: () => createStateManager,
43
+ createSubscriptionController: () => createSubscriptionController,
42
44
  createTracer: () => createTracer,
43
45
  executeFetch: () => executeFetch,
44
46
  extractMethodFromSelector: () => extractMethodFromSelector,
45
47
  extractPathFromSelector: () => extractPathFromSelector,
46
48
  fetchTransport: () => fetchTransport,
47
49
  form: () => form,
50
+ generateSelfTagFromKey: () => generateSelfTagFromKey,
48
51
  generateTags: () => generateTags,
49
52
  getContentType: () => getContentType,
50
53
  isAbortError: () => isAbortError,
@@ -716,7 +719,7 @@ function createSelectorProxy(onCapture) {
716
719
  {},
717
720
  {
718
721
  get(_, prop) {
719
- if (HTTP_METHODS.includes(prop)) {
722
+ if (typeof prop === "string" && prop === prop.toUpperCase()) {
720
723
  const selectorFn = (options) => {
721
724
  onCapture?.({
722
725
  call: { path, method: prop, options },
@@ -759,7 +762,19 @@ function createInitialState() {
759
762
  function generateSelfTagFromKey(key) {
760
763
  try {
761
764
  const parsed = JSON.parse(key);
762
- return parsed.path;
765
+ const rawPath = parsed.path;
766
+ if (!rawPath) return void 0;
767
+ const path = Array.isArray(rawPath) ? rawPath.join("/") : rawPath;
768
+ const params = parsed.options?.params ?? parsed.pageRequest?.params;
769
+ if (!params) return path;
770
+ return path.split("/").map((segment) => {
771
+ if (segment.startsWith(":")) {
772
+ const paramName = segment.slice(1);
773
+ const value = params[paramName];
774
+ return value !== void 0 ? String(value) : segment;
775
+ }
776
+ return segment;
777
+ }).join("/");
763
778
  } catch {
764
779
  return void 0;
765
780
  }
@@ -996,7 +1011,7 @@ function createPluginExecutor(initialPlugins = []) {
996
1011
  const createPluginAccessor = (context) => ({
997
1012
  get(name) {
998
1013
  const plugin = plugins.find((p) => p.name === name);
999
- return plugin?.exports?.(context);
1014
+ return plugin?.internal?.(context);
1000
1015
  }
1001
1016
  });
1002
1017
  const executeLifecycleImpl = async (phase, operationType, context) => {
@@ -1063,6 +1078,9 @@ function createPluginExecutor(initialPlugins = []) {
1063
1078
  getPlugins() {
1064
1079
  return frozenPlugins;
1065
1080
  },
1081
+ getPluginsForOperation(operationType) {
1082
+ return frozenPlugins.filter((p) => p.operations.includes(operationType));
1083
+ },
1066
1084
  registerContextEnhancer(enhancer) {
1067
1085
  contextEnhancers.push(enhancer);
1068
1086
  },
@@ -1095,6 +1113,7 @@ var Spoosh = class _Spoosh {
1095
1113
  baseUrl;
1096
1114
  defaultOptions;
1097
1115
  _plugins;
1116
+ _transports = /* @__PURE__ */ new Map();
1098
1117
  /**
1099
1118
  * Creates a new Spoosh instance.
1100
1119
  *
@@ -1119,10 +1138,13 @@ var Spoosh = class _Spoosh {
1119
1138
  * });
1120
1139
  * ```
1121
1140
  */
1122
- constructor(baseUrl, defaultOptions, plugins) {
1141
+ constructor(baseUrl, defaultOptions, plugins, transports) {
1123
1142
  this.baseUrl = baseUrl;
1124
1143
  this.defaultOptions = defaultOptions || {};
1125
1144
  this._plugins = plugins || [];
1145
+ if (transports) {
1146
+ this._transports = transports;
1147
+ }
1126
1148
  }
1127
1149
  /**
1128
1150
  * Adds plugins to the Spoosh instance.
@@ -1146,8 +1168,37 @@ var Spoosh = class _Spoosh {
1146
1168
  return new _Spoosh(
1147
1169
  this.baseUrl,
1148
1170
  this.defaultOptions,
1149
- plugins
1171
+ plugins,
1172
+ this._transports
1173
+ );
1174
+ }
1175
+ /**
1176
+ * Registers transport implementations for real-time operations.
1177
+ *
1178
+ * @param transports - Array of transport instances to register
1179
+ * @returns This Spoosh instance for method chaining
1180
+ *
1181
+ * @example
1182
+ * ```ts
1183
+ * import { sse } from '@spoosh/transport-sse';
1184
+ *
1185
+ * const spoosh = new Spoosh<Schema, Error>('/api')
1186
+ * .withTransports([sse()])
1187
+ * .use([...]);
1188
+ * ```
1189
+ */
1190
+ withTransports(transports) {
1191
+ const newTransports = new Map(this._transports);
1192
+ for (const transport of transports) {
1193
+ newTransports.set(transport.name, transport);
1194
+ }
1195
+ const instance = new _Spoosh(
1196
+ this.baseUrl,
1197
+ this.defaultOptions,
1198
+ this._plugins,
1199
+ newTransports
1150
1200
  );
1201
+ return instance;
1151
1202
  }
1152
1203
  /**
1153
1204
  * Cached instance of the underlying SpooshInstance.
@@ -1174,6 +1225,7 @@ var Spoosh = class _Spoosh {
1174
1225
  stateManager,
1175
1226
  eventEmitter,
1176
1227
  pluginExecutor,
1228
+ transports: this._transports,
1177
1229
  config: {
1178
1230
  baseUrl: this.baseUrl,
1179
1231
  defaultOptions: this.defaultOptions
@@ -1181,7 +1233,8 @@ var Spoosh = class _Spoosh {
1181
1233
  _types: {
1182
1234
  schema: void 0,
1183
1235
  defaultError: void 0,
1184
- plugins: this._plugins
1236
+ plugins: this._plugins,
1237
+ transports: void 0
1185
1238
  }
1186
1239
  };
1187
1240
  }
@@ -1291,6 +1344,18 @@ var Spoosh = class _Spoosh {
1291
1344
  get _types() {
1292
1345
  return this.getInstance()._types;
1293
1346
  }
1347
+ /**
1348
+ * Map of registered transport implementations.
1349
+ *
1350
+ * @example
1351
+ * ```ts
1352
+ * const { transports } = client;
1353
+ * const sseTransport = transports.get('sse');
1354
+ * ```
1355
+ */
1356
+ get transports() {
1357
+ return this.getInstance().transports;
1358
+ }
1294
1359
  };
1295
1360
 
1296
1361
  // src/createClient.ts
@@ -1302,6 +1367,16 @@ function createClient(baseUrl, defaultOptions) {
1302
1367
  });
1303
1368
  }
1304
1369
 
1370
+ // src/transport/compose.ts
1371
+ function composeAdapter(baseAdapter, plugins) {
1372
+ return plugins.reduce((adapter, plugin) => {
1373
+ if (plugin.wrapAdapter) {
1374
+ return plugin.wrapAdapter(adapter);
1375
+ }
1376
+ return adapter;
1377
+ }, baseAdapter);
1378
+ }
1379
+
1305
1380
  // src/controllers/base/controller.ts
1306
1381
  function createOperationController(options) {
1307
1382
  const {
@@ -2245,3 +2320,190 @@ function createQueueController(config, context) {
2245
2320
  }
2246
2321
  };
2247
2322
  }
2323
+
2324
+ // src/controllers/subscription/controller.ts
2325
+ function createSubscriptionController(options) {
2326
+ const {
2327
+ channel,
2328
+ baseAdapter,
2329
+ pluginExecutor,
2330
+ operationType,
2331
+ stateManager,
2332
+ eventEmitter,
2333
+ queryKey,
2334
+ path,
2335
+ method,
2336
+ instanceId
2337
+ } = options;
2338
+ const plugins = pluginExecutor.getPluginsForOperation(operationType);
2339
+ const adapter = composeAdapter(
2340
+ baseAdapter,
2341
+ plugins
2342
+ );
2343
+ let handle = null;
2344
+ const subscribers = /* @__PURE__ */ new Set();
2345
+ let subscriptionVersion = 0;
2346
+ let cachedState = {
2347
+ data: void 0,
2348
+ error: void 0,
2349
+ isConnected: false
2350
+ };
2351
+ const updateStateFromHandle = () => {
2352
+ if (!handle) return;
2353
+ const newData = handle.getData();
2354
+ const newError = handle.getError();
2355
+ if (newData !== cachedState.data || newError !== cachedState.error) {
2356
+ cachedState = {
2357
+ data: newData,
2358
+ error: newError,
2359
+ isConnected: true
2360
+ };
2361
+ }
2362
+ };
2363
+ const notify = () => {
2364
+ subscribers.forEach((callback) => callback());
2365
+ };
2366
+ const createContext = () => {
2367
+ const baseCtx = pluginExecutor.createContext({
2368
+ operationType,
2369
+ path,
2370
+ method,
2371
+ queryKey,
2372
+ tags: [],
2373
+ requestTimestamp: Date.now(),
2374
+ instanceId,
2375
+ request: { headers: {} },
2376
+ temp: /* @__PURE__ */ new Map(),
2377
+ stateManager,
2378
+ eventEmitter
2379
+ });
2380
+ return {
2381
+ ...baseCtx,
2382
+ channel
2383
+ };
2384
+ };
2385
+ return {
2386
+ subscribe: ((callbackOrVoid) => {
2387
+ if (callbackOrVoid === void 0) {
2388
+ subscriptionVersion++;
2389
+ const thisVersion = subscriptionVersion;
2390
+ if (handle) {
2391
+ handle.unsubscribe();
2392
+ handle = null;
2393
+ }
2394
+ cachedState = { data: void 0, error: void 0, isConnected: false };
2395
+ notify();
2396
+ const ctx = createContext();
2397
+ ctx.onData = (data) => {
2398
+ if (thisVersion !== subscriptionVersion) {
2399
+ return;
2400
+ }
2401
+ cachedState = {
2402
+ data,
2403
+ error: cachedState.error,
2404
+ isConnected: true
2405
+ };
2406
+ notify();
2407
+ };
2408
+ ctx.onError = (error) => {
2409
+ if (thisVersion !== subscriptionVersion) {
2410
+ return;
2411
+ }
2412
+ cachedState = {
2413
+ data: cachedState.data,
2414
+ error,
2415
+ isConnected: cachedState.isConnected
2416
+ };
2417
+ notify();
2418
+ };
2419
+ ctx.onDisconnect = () => {
2420
+ if (thisVersion !== subscriptionVersion) {
2421
+ return;
2422
+ }
2423
+ cachedState = {
2424
+ ...cachedState,
2425
+ isConnected: false
2426
+ };
2427
+ notify();
2428
+ };
2429
+ return adapter.subscribe(ctx).then((newHandle) => {
2430
+ if (thisVersion !== subscriptionVersion) {
2431
+ newHandle.unsubscribe();
2432
+ return newHandle;
2433
+ }
2434
+ handle = newHandle;
2435
+ const handleError = newHandle.getError();
2436
+ if (handleError) {
2437
+ cachedState = {
2438
+ ...cachedState,
2439
+ error: handleError,
2440
+ isConnected: false
2441
+ };
2442
+ } else {
2443
+ updateStateFromHandle();
2444
+ cachedState = {
2445
+ ...cachedState,
2446
+ isConnected: true
2447
+ };
2448
+ }
2449
+ notify();
2450
+ return handle;
2451
+ }).catch((err) => {
2452
+ if (thisVersion !== subscriptionVersion) {
2453
+ return null;
2454
+ }
2455
+ cachedState = {
2456
+ ...cachedState,
2457
+ error: err,
2458
+ isConnected: false
2459
+ };
2460
+ notify();
2461
+ return null;
2462
+ });
2463
+ }
2464
+ subscribers.add(callbackOrVoid);
2465
+ return () => {
2466
+ subscribers.delete(callbackOrVoid);
2467
+ };
2468
+ }),
2469
+ emit: async (message) => {
2470
+ const ctx = createContext();
2471
+ ctx.message = message;
2472
+ return adapter.emit(ctx);
2473
+ },
2474
+ unsubscribe: () => {
2475
+ subscriptionVersion++;
2476
+ if (handle) {
2477
+ handle.unsubscribe();
2478
+ handle = null;
2479
+ }
2480
+ cachedState = {
2481
+ ...cachedState,
2482
+ isConnected: false
2483
+ };
2484
+ notify();
2485
+ },
2486
+ getState: () => cachedState,
2487
+ mount: () => {
2488
+ },
2489
+ unmount: () => {
2490
+ subscriptionVersion++;
2491
+ if (handle) {
2492
+ handle.unsubscribe();
2493
+ handle = null;
2494
+ }
2495
+ cachedState = {
2496
+ ...cachedState,
2497
+ isConnected: false
2498
+ };
2499
+ notify();
2500
+ },
2501
+ setDisconnected: () => {
2502
+ cachedState = {
2503
+ ...cachedState,
2504
+ isConnected: false
2505
+ };
2506
+ notify();
2507
+ }
2508
+ };
2509
+ }