@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.mjs CHANGED
@@ -646,7 +646,7 @@ function createSelectorProxy(onCapture) {
646
646
  {},
647
647
  {
648
648
  get(_, prop) {
649
- if (HTTP_METHODS.includes(prop)) {
649
+ if (typeof prop === "string" && prop === prop.toUpperCase()) {
650
650
  const selectorFn = (options) => {
651
651
  onCapture?.({
652
652
  call: { path, method: prop, options },
@@ -689,7 +689,19 @@ function createInitialState() {
689
689
  function generateSelfTagFromKey(key) {
690
690
  try {
691
691
  const parsed = JSON.parse(key);
692
- return parsed.path;
692
+ const rawPath = parsed.path;
693
+ if (!rawPath) return void 0;
694
+ const path = Array.isArray(rawPath) ? rawPath.join("/") : rawPath;
695
+ const params = parsed.options?.params ?? parsed.pageRequest?.params;
696
+ if (!params) return path;
697
+ return path.split("/").map((segment) => {
698
+ if (segment.startsWith(":")) {
699
+ const paramName = segment.slice(1);
700
+ const value = params[paramName];
701
+ return value !== void 0 ? String(value) : segment;
702
+ }
703
+ return segment;
704
+ }).join("/");
693
705
  } catch {
694
706
  return void 0;
695
707
  }
@@ -926,7 +938,7 @@ function createPluginExecutor(initialPlugins = []) {
926
938
  const createPluginAccessor = (context) => ({
927
939
  get(name) {
928
940
  const plugin = plugins.find((p) => p.name === name);
929
- return plugin?.exports?.(context);
941
+ return plugin?.internal?.(context);
930
942
  }
931
943
  });
932
944
  const executeLifecycleImpl = async (phase, operationType, context) => {
@@ -993,6 +1005,9 @@ function createPluginExecutor(initialPlugins = []) {
993
1005
  getPlugins() {
994
1006
  return frozenPlugins;
995
1007
  },
1008
+ getPluginsForOperation(operationType) {
1009
+ return frozenPlugins.filter((p) => p.operations.includes(operationType));
1010
+ },
996
1011
  registerContextEnhancer(enhancer) {
997
1012
  contextEnhancers.push(enhancer);
998
1013
  },
@@ -1025,6 +1040,7 @@ var Spoosh = class _Spoosh {
1025
1040
  baseUrl;
1026
1041
  defaultOptions;
1027
1042
  _plugins;
1043
+ _transports = /* @__PURE__ */ new Map();
1028
1044
  /**
1029
1045
  * Creates a new Spoosh instance.
1030
1046
  *
@@ -1049,10 +1065,13 @@ var Spoosh = class _Spoosh {
1049
1065
  * });
1050
1066
  * ```
1051
1067
  */
1052
- constructor(baseUrl, defaultOptions, plugins) {
1068
+ constructor(baseUrl, defaultOptions, plugins, transports) {
1053
1069
  this.baseUrl = baseUrl;
1054
1070
  this.defaultOptions = defaultOptions || {};
1055
1071
  this._plugins = plugins || [];
1072
+ if (transports) {
1073
+ this._transports = transports;
1074
+ }
1056
1075
  }
1057
1076
  /**
1058
1077
  * Adds plugins to the Spoosh instance.
@@ -1076,8 +1095,37 @@ var Spoosh = class _Spoosh {
1076
1095
  return new _Spoosh(
1077
1096
  this.baseUrl,
1078
1097
  this.defaultOptions,
1079
- plugins
1098
+ plugins,
1099
+ this._transports
1100
+ );
1101
+ }
1102
+ /**
1103
+ * Registers transport implementations for real-time operations.
1104
+ *
1105
+ * @param transports - Array of transport instances to register
1106
+ * @returns This Spoosh instance for method chaining
1107
+ *
1108
+ * @example
1109
+ * ```ts
1110
+ * import { sse } from '@spoosh/transport-sse';
1111
+ *
1112
+ * const spoosh = new Spoosh<Schema, Error>('/api')
1113
+ * .withTransports([sse()])
1114
+ * .use([...]);
1115
+ * ```
1116
+ */
1117
+ withTransports(transports) {
1118
+ const newTransports = new Map(this._transports);
1119
+ for (const transport of transports) {
1120
+ newTransports.set(transport.name, transport);
1121
+ }
1122
+ const instance = new _Spoosh(
1123
+ this.baseUrl,
1124
+ this.defaultOptions,
1125
+ this._plugins,
1126
+ newTransports
1080
1127
  );
1128
+ return instance;
1081
1129
  }
1082
1130
  /**
1083
1131
  * Cached instance of the underlying SpooshInstance.
@@ -1104,6 +1152,7 @@ var Spoosh = class _Spoosh {
1104
1152
  stateManager,
1105
1153
  eventEmitter,
1106
1154
  pluginExecutor,
1155
+ transports: this._transports,
1107
1156
  config: {
1108
1157
  baseUrl: this.baseUrl,
1109
1158
  defaultOptions: this.defaultOptions
@@ -1111,7 +1160,8 @@ var Spoosh = class _Spoosh {
1111
1160
  _types: {
1112
1161
  schema: void 0,
1113
1162
  defaultError: void 0,
1114
- plugins: this._plugins
1163
+ plugins: this._plugins,
1164
+ transports: void 0
1115
1165
  }
1116
1166
  };
1117
1167
  }
@@ -1221,6 +1271,18 @@ var Spoosh = class _Spoosh {
1221
1271
  get _types() {
1222
1272
  return this.getInstance()._types;
1223
1273
  }
1274
+ /**
1275
+ * Map of registered transport implementations.
1276
+ *
1277
+ * @example
1278
+ * ```ts
1279
+ * const { transports } = client;
1280
+ * const sseTransport = transports.get('sse');
1281
+ * ```
1282
+ */
1283
+ get transports() {
1284
+ return this.getInstance().transports;
1285
+ }
1224
1286
  };
1225
1287
 
1226
1288
  // src/createClient.ts
@@ -1232,6 +1294,16 @@ function createClient(baseUrl, defaultOptions) {
1232
1294
  });
1233
1295
  }
1234
1296
 
1297
+ // src/transport/compose.ts
1298
+ function composeAdapter(baseAdapter, plugins) {
1299
+ return plugins.reduce((adapter, plugin) => {
1300
+ if (plugin.wrapAdapter) {
1301
+ return plugin.wrapAdapter(adapter);
1302
+ }
1303
+ return adapter;
1304
+ }, baseAdapter);
1305
+ }
1306
+
1235
1307
  // src/controllers/base/controller.ts
1236
1308
  function createOperationController(options) {
1237
1309
  const {
@@ -2175,6 +2247,193 @@ function createQueueController(config, context) {
2175
2247
  }
2176
2248
  };
2177
2249
  }
2250
+
2251
+ // src/controllers/subscription/controller.ts
2252
+ function createSubscriptionController(options) {
2253
+ const {
2254
+ channel,
2255
+ baseAdapter,
2256
+ pluginExecutor,
2257
+ operationType,
2258
+ stateManager,
2259
+ eventEmitter,
2260
+ queryKey,
2261
+ path,
2262
+ method,
2263
+ instanceId
2264
+ } = options;
2265
+ const plugins = pluginExecutor.getPluginsForOperation(operationType);
2266
+ const adapter = composeAdapter(
2267
+ baseAdapter,
2268
+ plugins
2269
+ );
2270
+ let handle = null;
2271
+ const subscribers = /* @__PURE__ */ new Set();
2272
+ let subscriptionVersion = 0;
2273
+ let cachedState = {
2274
+ data: void 0,
2275
+ error: void 0,
2276
+ isConnected: false
2277
+ };
2278
+ const updateStateFromHandle = () => {
2279
+ if (!handle) return;
2280
+ const newData = handle.getData();
2281
+ const newError = handle.getError();
2282
+ if (newData !== cachedState.data || newError !== cachedState.error) {
2283
+ cachedState = {
2284
+ data: newData,
2285
+ error: newError,
2286
+ isConnected: true
2287
+ };
2288
+ }
2289
+ };
2290
+ const notify = () => {
2291
+ subscribers.forEach((callback) => callback());
2292
+ };
2293
+ const createContext = () => {
2294
+ const baseCtx = pluginExecutor.createContext({
2295
+ operationType,
2296
+ path,
2297
+ method,
2298
+ queryKey,
2299
+ tags: [],
2300
+ requestTimestamp: Date.now(),
2301
+ instanceId,
2302
+ request: { headers: {} },
2303
+ temp: /* @__PURE__ */ new Map(),
2304
+ stateManager,
2305
+ eventEmitter
2306
+ });
2307
+ return {
2308
+ ...baseCtx,
2309
+ channel
2310
+ };
2311
+ };
2312
+ return {
2313
+ subscribe: ((callbackOrVoid) => {
2314
+ if (callbackOrVoid === void 0) {
2315
+ subscriptionVersion++;
2316
+ const thisVersion = subscriptionVersion;
2317
+ if (handle) {
2318
+ handle.unsubscribe();
2319
+ handle = null;
2320
+ }
2321
+ cachedState = { data: void 0, error: void 0, isConnected: false };
2322
+ notify();
2323
+ const ctx = createContext();
2324
+ ctx.onData = (data) => {
2325
+ if (thisVersion !== subscriptionVersion) {
2326
+ return;
2327
+ }
2328
+ cachedState = {
2329
+ data,
2330
+ error: cachedState.error,
2331
+ isConnected: true
2332
+ };
2333
+ notify();
2334
+ };
2335
+ ctx.onError = (error) => {
2336
+ if (thisVersion !== subscriptionVersion) {
2337
+ return;
2338
+ }
2339
+ cachedState = {
2340
+ data: cachedState.data,
2341
+ error,
2342
+ isConnected: cachedState.isConnected
2343
+ };
2344
+ notify();
2345
+ };
2346
+ ctx.onDisconnect = () => {
2347
+ if (thisVersion !== subscriptionVersion) {
2348
+ return;
2349
+ }
2350
+ cachedState = {
2351
+ ...cachedState,
2352
+ isConnected: false
2353
+ };
2354
+ notify();
2355
+ };
2356
+ return adapter.subscribe(ctx).then((newHandle) => {
2357
+ if (thisVersion !== subscriptionVersion) {
2358
+ newHandle.unsubscribe();
2359
+ return newHandle;
2360
+ }
2361
+ handle = newHandle;
2362
+ const handleError = newHandle.getError();
2363
+ if (handleError) {
2364
+ cachedState = {
2365
+ ...cachedState,
2366
+ error: handleError,
2367
+ isConnected: false
2368
+ };
2369
+ } else {
2370
+ updateStateFromHandle();
2371
+ cachedState = {
2372
+ ...cachedState,
2373
+ isConnected: true
2374
+ };
2375
+ }
2376
+ notify();
2377
+ return handle;
2378
+ }).catch((err) => {
2379
+ if (thisVersion !== subscriptionVersion) {
2380
+ return null;
2381
+ }
2382
+ cachedState = {
2383
+ ...cachedState,
2384
+ error: err,
2385
+ isConnected: false
2386
+ };
2387
+ notify();
2388
+ return null;
2389
+ });
2390
+ }
2391
+ subscribers.add(callbackOrVoid);
2392
+ return () => {
2393
+ subscribers.delete(callbackOrVoid);
2394
+ };
2395
+ }),
2396
+ emit: async (message) => {
2397
+ const ctx = createContext();
2398
+ ctx.message = message;
2399
+ return adapter.emit(ctx);
2400
+ },
2401
+ unsubscribe: () => {
2402
+ subscriptionVersion++;
2403
+ if (handle) {
2404
+ handle.unsubscribe();
2405
+ handle = null;
2406
+ }
2407
+ cachedState = {
2408
+ ...cachedState,
2409
+ isConnected: false
2410
+ };
2411
+ notify();
2412
+ },
2413
+ getState: () => cachedState,
2414
+ mount: () => {
2415
+ },
2416
+ unmount: () => {
2417
+ subscriptionVersion++;
2418
+ if (handle) {
2419
+ handle.unsubscribe();
2420
+ handle = null;
2421
+ }
2422
+ cachedState = {
2423
+ ...cachedState,
2424
+ isConnected: false
2425
+ };
2426
+ notify();
2427
+ },
2428
+ setDisconnected: () => {
2429
+ cachedState = {
2430
+ ...cachedState,
2431
+ isConnected: false
2432
+ };
2433
+ notify();
2434
+ }
2435
+ };
2436
+ }
2178
2437
  export {
2179
2438
  HTTP_METHODS,
2180
2439
  Semaphore,
@@ -2182,6 +2441,7 @@ export {
2182
2441
  __DEV__,
2183
2442
  buildUrl,
2184
2443
  clone,
2444
+ composeAdapter,
2185
2445
  containsFile,
2186
2446
  createClient,
2187
2447
  createEventEmitter,
@@ -2195,12 +2455,14 @@ export {
2195
2455
  createSelectorProxy,
2196
2456
  createSpooshPlugin,
2197
2457
  createStateManager,
2458
+ createSubscriptionController,
2198
2459
  createTracer,
2199
2460
  executeFetch,
2200
2461
  extractMethodFromSelector,
2201
2462
  extractPathFromSelector,
2202
2463
  fetchTransport,
2203
2464
  form,
2465
+ generateSelfTagFromKey,
2204
2466
  generateTags,
2205
2467
  getContentType,
2206
2468
  isAbortError,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spoosh/core",
3
- "version": "0.15.1",
3
+ "version": "0.17.0",
4
4
  "license": "MIT",
5
5
  "description": "Type-safe API toolkit with plugin middleware system",
6
6
  "keywords": [
@@ -39,6 +39,6 @@
39
39
  "build": "tsup",
40
40
  "typecheck": "tsc --noEmit",
41
41
  "lint": "eslint src --max-warnings 0",
42
- "format": "prettier --write 'src/**/*.ts'"
42
+ "format": "prettier --write 'src/**/*.ts' '*.md'"
43
43
  }
44
44
  }