@spoosh/core 0.15.1 → 0.16.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,6 +40,7 @@ __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,
@@ -716,7 +718,7 @@ function createSelectorProxy(onCapture) {
716
718
  {},
717
719
  {
718
720
  get(_, prop) {
719
- if (HTTP_METHODS.includes(prop)) {
721
+ if (typeof prop === "string" && prop === prop.toUpperCase()) {
720
722
  const selectorFn = (options) => {
721
723
  onCapture?.({
722
724
  call: { path, method: prop, options },
@@ -1063,6 +1065,9 @@ function createPluginExecutor(initialPlugins = []) {
1063
1065
  getPlugins() {
1064
1066
  return frozenPlugins;
1065
1067
  },
1068
+ getPluginsForOperation(operationType) {
1069
+ return frozenPlugins.filter((p) => p.operations.includes(operationType));
1070
+ },
1066
1071
  registerContextEnhancer(enhancer) {
1067
1072
  contextEnhancers.push(enhancer);
1068
1073
  },
@@ -1095,6 +1100,7 @@ var Spoosh = class _Spoosh {
1095
1100
  baseUrl;
1096
1101
  defaultOptions;
1097
1102
  _plugins;
1103
+ _transports = /* @__PURE__ */ new Map();
1098
1104
  /**
1099
1105
  * Creates a new Spoosh instance.
1100
1106
  *
@@ -1119,10 +1125,13 @@ var Spoosh = class _Spoosh {
1119
1125
  * });
1120
1126
  * ```
1121
1127
  */
1122
- constructor(baseUrl, defaultOptions, plugins) {
1128
+ constructor(baseUrl, defaultOptions, plugins, transports) {
1123
1129
  this.baseUrl = baseUrl;
1124
1130
  this.defaultOptions = defaultOptions || {};
1125
1131
  this._plugins = plugins || [];
1132
+ if (transports) {
1133
+ this._transports = transports;
1134
+ }
1126
1135
  }
1127
1136
  /**
1128
1137
  * Adds plugins to the Spoosh instance.
@@ -1146,8 +1155,37 @@ var Spoosh = class _Spoosh {
1146
1155
  return new _Spoosh(
1147
1156
  this.baseUrl,
1148
1157
  this.defaultOptions,
1149
- plugins
1158
+ plugins,
1159
+ this._transports
1160
+ );
1161
+ }
1162
+ /**
1163
+ * Registers transport implementations for real-time operations.
1164
+ *
1165
+ * @param transports - Array of transport instances to register
1166
+ * @returns This Spoosh instance for method chaining
1167
+ *
1168
+ * @example
1169
+ * ```ts
1170
+ * import { sse } from '@spoosh/transport-sse';
1171
+ *
1172
+ * const spoosh = new Spoosh<Schema, Error>('/api')
1173
+ * .withTransports([sse()])
1174
+ * .use([...]);
1175
+ * ```
1176
+ */
1177
+ withTransports(transports) {
1178
+ const newTransports = new Map(this._transports);
1179
+ for (const transport of transports) {
1180
+ newTransports.set(transport.name, transport);
1181
+ }
1182
+ const instance = new _Spoosh(
1183
+ this.baseUrl,
1184
+ this.defaultOptions,
1185
+ this._plugins,
1186
+ newTransports
1150
1187
  );
1188
+ return instance;
1151
1189
  }
1152
1190
  /**
1153
1191
  * Cached instance of the underlying SpooshInstance.
@@ -1174,6 +1212,7 @@ var Spoosh = class _Spoosh {
1174
1212
  stateManager,
1175
1213
  eventEmitter,
1176
1214
  pluginExecutor,
1215
+ transports: this._transports,
1177
1216
  config: {
1178
1217
  baseUrl: this.baseUrl,
1179
1218
  defaultOptions: this.defaultOptions
@@ -1181,7 +1220,8 @@ var Spoosh = class _Spoosh {
1181
1220
  _types: {
1182
1221
  schema: void 0,
1183
1222
  defaultError: void 0,
1184
- plugins: this._plugins
1223
+ plugins: this._plugins,
1224
+ transports: void 0
1185
1225
  }
1186
1226
  };
1187
1227
  }
@@ -1291,6 +1331,18 @@ var Spoosh = class _Spoosh {
1291
1331
  get _types() {
1292
1332
  return this.getInstance()._types;
1293
1333
  }
1334
+ /**
1335
+ * Map of registered transport implementations.
1336
+ *
1337
+ * @example
1338
+ * ```ts
1339
+ * const { transports } = client;
1340
+ * const sseTransport = transports.get('sse');
1341
+ * ```
1342
+ */
1343
+ get transports() {
1344
+ return this.getInstance().transports;
1345
+ }
1294
1346
  };
1295
1347
 
1296
1348
  // src/createClient.ts
@@ -1302,6 +1354,16 @@ function createClient(baseUrl, defaultOptions) {
1302
1354
  });
1303
1355
  }
1304
1356
 
1357
+ // src/transport/compose.ts
1358
+ function composeAdapter(baseAdapter, plugins) {
1359
+ return plugins.reduce((adapter, plugin) => {
1360
+ if (plugin.wrapAdapter) {
1361
+ return plugin.wrapAdapter(adapter);
1362
+ }
1363
+ return adapter;
1364
+ }, baseAdapter);
1365
+ }
1366
+
1305
1367
  // src/controllers/base/controller.ts
1306
1368
  function createOperationController(options) {
1307
1369
  const {
@@ -2245,3 +2307,190 @@ function createQueueController(config, context) {
2245
2307
  }
2246
2308
  };
2247
2309
  }
2310
+
2311
+ // src/controllers/subscription/controller.ts
2312
+ function createSubscriptionController(options) {
2313
+ const {
2314
+ channel,
2315
+ baseAdapter,
2316
+ pluginExecutor,
2317
+ operationType,
2318
+ stateManager,
2319
+ eventEmitter,
2320
+ queryKey,
2321
+ path,
2322
+ method,
2323
+ instanceId
2324
+ } = options;
2325
+ const plugins = pluginExecutor.getPluginsForOperation(operationType);
2326
+ const adapter = composeAdapter(
2327
+ baseAdapter,
2328
+ plugins
2329
+ );
2330
+ let handle = null;
2331
+ const subscribers = /* @__PURE__ */ new Set();
2332
+ let subscriptionVersion = 0;
2333
+ let cachedState = {
2334
+ data: void 0,
2335
+ error: void 0,
2336
+ isConnected: false
2337
+ };
2338
+ const updateStateFromHandle = () => {
2339
+ if (!handle) return;
2340
+ const newData = handle.getData();
2341
+ const newError = handle.getError();
2342
+ if (newData !== cachedState.data || newError !== cachedState.error) {
2343
+ cachedState = {
2344
+ data: newData,
2345
+ error: newError,
2346
+ isConnected: true
2347
+ };
2348
+ }
2349
+ };
2350
+ const notify = () => {
2351
+ subscribers.forEach((callback) => callback());
2352
+ };
2353
+ const createContext = () => {
2354
+ const baseCtx = pluginExecutor.createContext({
2355
+ operationType,
2356
+ path,
2357
+ method,
2358
+ queryKey,
2359
+ tags: [],
2360
+ requestTimestamp: Date.now(),
2361
+ instanceId,
2362
+ request: { headers: {} },
2363
+ temp: /* @__PURE__ */ new Map(),
2364
+ stateManager,
2365
+ eventEmitter
2366
+ });
2367
+ return {
2368
+ ...baseCtx,
2369
+ channel
2370
+ };
2371
+ };
2372
+ return {
2373
+ subscribe: ((callbackOrVoid) => {
2374
+ if (callbackOrVoid === void 0) {
2375
+ subscriptionVersion++;
2376
+ const thisVersion = subscriptionVersion;
2377
+ if (handle) {
2378
+ handle.unsubscribe();
2379
+ handle = null;
2380
+ }
2381
+ cachedState = { data: void 0, error: void 0, isConnected: false };
2382
+ notify();
2383
+ const ctx = createContext();
2384
+ ctx.onData = (data) => {
2385
+ if (thisVersion !== subscriptionVersion) {
2386
+ return;
2387
+ }
2388
+ cachedState = {
2389
+ data,
2390
+ error: cachedState.error,
2391
+ isConnected: true
2392
+ };
2393
+ notify();
2394
+ };
2395
+ ctx.onError = (error) => {
2396
+ if (thisVersion !== subscriptionVersion) {
2397
+ return;
2398
+ }
2399
+ cachedState = {
2400
+ data: cachedState.data,
2401
+ error,
2402
+ isConnected: cachedState.isConnected
2403
+ };
2404
+ notify();
2405
+ };
2406
+ ctx.onDisconnect = () => {
2407
+ if (thisVersion !== subscriptionVersion) {
2408
+ return;
2409
+ }
2410
+ cachedState = {
2411
+ ...cachedState,
2412
+ isConnected: false
2413
+ };
2414
+ notify();
2415
+ };
2416
+ return adapter.subscribe(ctx).then((newHandle) => {
2417
+ if (thisVersion !== subscriptionVersion) {
2418
+ newHandle.unsubscribe();
2419
+ return newHandle;
2420
+ }
2421
+ handle = newHandle;
2422
+ const handleError = newHandle.getError();
2423
+ if (handleError) {
2424
+ cachedState = {
2425
+ ...cachedState,
2426
+ error: handleError,
2427
+ isConnected: false
2428
+ };
2429
+ } else {
2430
+ updateStateFromHandle();
2431
+ cachedState = {
2432
+ ...cachedState,
2433
+ isConnected: true
2434
+ };
2435
+ }
2436
+ notify();
2437
+ return handle;
2438
+ }).catch((err) => {
2439
+ if (thisVersion !== subscriptionVersion) {
2440
+ return null;
2441
+ }
2442
+ cachedState = {
2443
+ ...cachedState,
2444
+ error: err,
2445
+ isConnected: false
2446
+ };
2447
+ notify();
2448
+ return null;
2449
+ });
2450
+ }
2451
+ subscribers.add(callbackOrVoid);
2452
+ return () => {
2453
+ subscribers.delete(callbackOrVoid);
2454
+ };
2455
+ }),
2456
+ emit: async (message) => {
2457
+ const ctx = createContext();
2458
+ ctx.message = message;
2459
+ return adapter.emit(ctx);
2460
+ },
2461
+ unsubscribe: () => {
2462
+ subscriptionVersion++;
2463
+ if (handle) {
2464
+ handle.unsubscribe();
2465
+ handle = null;
2466
+ }
2467
+ cachedState = {
2468
+ ...cachedState,
2469
+ isConnected: false
2470
+ };
2471
+ notify();
2472
+ },
2473
+ getState: () => cachedState,
2474
+ mount: () => {
2475
+ },
2476
+ unmount: () => {
2477
+ subscriptionVersion++;
2478
+ if (handle) {
2479
+ handle.unsubscribe();
2480
+ handle = null;
2481
+ }
2482
+ cachedState = {
2483
+ ...cachedState,
2484
+ isConnected: false
2485
+ };
2486
+ notify();
2487
+ },
2488
+ setDisconnected: () => {
2489
+ cachedState = {
2490
+ ...cachedState,
2491
+ isConnected: false
2492
+ };
2493
+ notify();
2494
+ }
2495
+ };
2496
+ }
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 },
@@ -993,6 +993,9 @@ function createPluginExecutor(initialPlugins = []) {
993
993
  getPlugins() {
994
994
  return frozenPlugins;
995
995
  },
996
+ getPluginsForOperation(operationType) {
997
+ return frozenPlugins.filter((p) => p.operations.includes(operationType));
998
+ },
996
999
  registerContextEnhancer(enhancer) {
997
1000
  contextEnhancers.push(enhancer);
998
1001
  },
@@ -1025,6 +1028,7 @@ var Spoosh = class _Spoosh {
1025
1028
  baseUrl;
1026
1029
  defaultOptions;
1027
1030
  _plugins;
1031
+ _transports = /* @__PURE__ */ new Map();
1028
1032
  /**
1029
1033
  * Creates a new Spoosh instance.
1030
1034
  *
@@ -1049,10 +1053,13 @@ var Spoosh = class _Spoosh {
1049
1053
  * });
1050
1054
  * ```
1051
1055
  */
1052
- constructor(baseUrl, defaultOptions, plugins) {
1056
+ constructor(baseUrl, defaultOptions, plugins, transports) {
1053
1057
  this.baseUrl = baseUrl;
1054
1058
  this.defaultOptions = defaultOptions || {};
1055
1059
  this._plugins = plugins || [];
1060
+ if (transports) {
1061
+ this._transports = transports;
1062
+ }
1056
1063
  }
1057
1064
  /**
1058
1065
  * Adds plugins to the Spoosh instance.
@@ -1076,8 +1083,37 @@ var Spoosh = class _Spoosh {
1076
1083
  return new _Spoosh(
1077
1084
  this.baseUrl,
1078
1085
  this.defaultOptions,
1079
- plugins
1086
+ plugins,
1087
+ this._transports
1088
+ );
1089
+ }
1090
+ /**
1091
+ * Registers transport implementations for real-time operations.
1092
+ *
1093
+ * @param transports - Array of transport instances to register
1094
+ * @returns This Spoosh instance for method chaining
1095
+ *
1096
+ * @example
1097
+ * ```ts
1098
+ * import { sse } from '@spoosh/transport-sse';
1099
+ *
1100
+ * const spoosh = new Spoosh<Schema, Error>('/api')
1101
+ * .withTransports([sse()])
1102
+ * .use([...]);
1103
+ * ```
1104
+ */
1105
+ withTransports(transports) {
1106
+ const newTransports = new Map(this._transports);
1107
+ for (const transport of transports) {
1108
+ newTransports.set(transport.name, transport);
1109
+ }
1110
+ const instance = new _Spoosh(
1111
+ this.baseUrl,
1112
+ this.defaultOptions,
1113
+ this._plugins,
1114
+ newTransports
1080
1115
  );
1116
+ return instance;
1081
1117
  }
1082
1118
  /**
1083
1119
  * Cached instance of the underlying SpooshInstance.
@@ -1104,6 +1140,7 @@ var Spoosh = class _Spoosh {
1104
1140
  stateManager,
1105
1141
  eventEmitter,
1106
1142
  pluginExecutor,
1143
+ transports: this._transports,
1107
1144
  config: {
1108
1145
  baseUrl: this.baseUrl,
1109
1146
  defaultOptions: this.defaultOptions
@@ -1111,7 +1148,8 @@ var Spoosh = class _Spoosh {
1111
1148
  _types: {
1112
1149
  schema: void 0,
1113
1150
  defaultError: void 0,
1114
- plugins: this._plugins
1151
+ plugins: this._plugins,
1152
+ transports: void 0
1115
1153
  }
1116
1154
  };
1117
1155
  }
@@ -1221,6 +1259,18 @@ var Spoosh = class _Spoosh {
1221
1259
  get _types() {
1222
1260
  return this.getInstance()._types;
1223
1261
  }
1262
+ /**
1263
+ * Map of registered transport implementations.
1264
+ *
1265
+ * @example
1266
+ * ```ts
1267
+ * const { transports } = client;
1268
+ * const sseTransport = transports.get('sse');
1269
+ * ```
1270
+ */
1271
+ get transports() {
1272
+ return this.getInstance().transports;
1273
+ }
1224
1274
  };
1225
1275
 
1226
1276
  // src/createClient.ts
@@ -1232,6 +1282,16 @@ function createClient(baseUrl, defaultOptions) {
1232
1282
  });
1233
1283
  }
1234
1284
 
1285
+ // src/transport/compose.ts
1286
+ function composeAdapter(baseAdapter, plugins) {
1287
+ return plugins.reduce((adapter, plugin) => {
1288
+ if (plugin.wrapAdapter) {
1289
+ return plugin.wrapAdapter(adapter);
1290
+ }
1291
+ return adapter;
1292
+ }, baseAdapter);
1293
+ }
1294
+
1235
1295
  // src/controllers/base/controller.ts
1236
1296
  function createOperationController(options) {
1237
1297
  const {
@@ -2175,6 +2235,193 @@ function createQueueController(config, context) {
2175
2235
  }
2176
2236
  };
2177
2237
  }
2238
+
2239
+ // src/controllers/subscription/controller.ts
2240
+ function createSubscriptionController(options) {
2241
+ const {
2242
+ channel,
2243
+ baseAdapter,
2244
+ pluginExecutor,
2245
+ operationType,
2246
+ stateManager,
2247
+ eventEmitter,
2248
+ queryKey,
2249
+ path,
2250
+ method,
2251
+ instanceId
2252
+ } = options;
2253
+ const plugins = pluginExecutor.getPluginsForOperation(operationType);
2254
+ const adapter = composeAdapter(
2255
+ baseAdapter,
2256
+ plugins
2257
+ );
2258
+ let handle = null;
2259
+ const subscribers = /* @__PURE__ */ new Set();
2260
+ let subscriptionVersion = 0;
2261
+ let cachedState = {
2262
+ data: void 0,
2263
+ error: void 0,
2264
+ isConnected: false
2265
+ };
2266
+ const updateStateFromHandle = () => {
2267
+ if (!handle) return;
2268
+ const newData = handle.getData();
2269
+ const newError = handle.getError();
2270
+ if (newData !== cachedState.data || newError !== cachedState.error) {
2271
+ cachedState = {
2272
+ data: newData,
2273
+ error: newError,
2274
+ isConnected: true
2275
+ };
2276
+ }
2277
+ };
2278
+ const notify = () => {
2279
+ subscribers.forEach((callback) => callback());
2280
+ };
2281
+ const createContext = () => {
2282
+ const baseCtx = pluginExecutor.createContext({
2283
+ operationType,
2284
+ path,
2285
+ method,
2286
+ queryKey,
2287
+ tags: [],
2288
+ requestTimestamp: Date.now(),
2289
+ instanceId,
2290
+ request: { headers: {} },
2291
+ temp: /* @__PURE__ */ new Map(),
2292
+ stateManager,
2293
+ eventEmitter
2294
+ });
2295
+ return {
2296
+ ...baseCtx,
2297
+ channel
2298
+ };
2299
+ };
2300
+ return {
2301
+ subscribe: ((callbackOrVoid) => {
2302
+ if (callbackOrVoid === void 0) {
2303
+ subscriptionVersion++;
2304
+ const thisVersion = subscriptionVersion;
2305
+ if (handle) {
2306
+ handle.unsubscribe();
2307
+ handle = null;
2308
+ }
2309
+ cachedState = { data: void 0, error: void 0, isConnected: false };
2310
+ notify();
2311
+ const ctx = createContext();
2312
+ ctx.onData = (data) => {
2313
+ if (thisVersion !== subscriptionVersion) {
2314
+ return;
2315
+ }
2316
+ cachedState = {
2317
+ data,
2318
+ error: cachedState.error,
2319
+ isConnected: true
2320
+ };
2321
+ notify();
2322
+ };
2323
+ ctx.onError = (error) => {
2324
+ if (thisVersion !== subscriptionVersion) {
2325
+ return;
2326
+ }
2327
+ cachedState = {
2328
+ data: cachedState.data,
2329
+ error,
2330
+ isConnected: cachedState.isConnected
2331
+ };
2332
+ notify();
2333
+ };
2334
+ ctx.onDisconnect = () => {
2335
+ if (thisVersion !== subscriptionVersion) {
2336
+ return;
2337
+ }
2338
+ cachedState = {
2339
+ ...cachedState,
2340
+ isConnected: false
2341
+ };
2342
+ notify();
2343
+ };
2344
+ return adapter.subscribe(ctx).then((newHandle) => {
2345
+ if (thisVersion !== subscriptionVersion) {
2346
+ newHandle.unsubscribe();
2347
+ return newHandle;
2348
+ }
2349
+ handle = newHandle;
2350
+ const handleError = newHandle.getError();
2351
+ if (handleError) {
2352
+ cachedState = {
2353
+ ...cachedState,
2354
+ error: handleError,
2355
+ isConnected: false
2356
+ };
2357
+ } else {
2358
+ updateStateFromHandle();
2359
+ cachedState = {
2360
+ ...cachedState,
2361
+ isConnected: true
2362
+ };
2363
+ }
2364
+ notify();
2365
+ return handle;
2366
+ }).catch((err) => {
2367
+ if (thisVersion !== subscriptionVersion) {
2368
+ return null;
2369
+ }
2370
+ cachedState = {
2371
+ ...cachedState,
2372
+ error: err,
2373
+ isConnected: false
2374
+ };
2375
+ notify();
2376
+ return null;
2377
+ });
2378
+ }
2379
+ subscribers.add(callbackOrVoid);
2380
+ return () => {
2381
+ subscribers.delete(callbackOrVoid);
2382
+ };
2383
+ }),
2384
+ emit: async (message) => {
2385
+ const ctx = createContext();
2386
+ ctx.message = message;
2387
+ return adapter.emit(ctx);
2388
+ },
2389
+ unsubscribe: () => {
2390
+ subscriptionVersion++;
2391
+ if (handle) {
2392
+ handle.unsubscribe();
2393
+ handle = null;
2394
+ }
2395
+ cachedState = {
2396
+ ...cachedState,
2397
+ isConnected: false
2398
+ };
2399
+ notify();
2400
+ },
2401
+ getState: () => cachedState,
2402
+ mount: () => {
2403
+ },
2404
+ unmount: () => {
2405
+ subscriptionVersion++;
2406
+ if (handle) {
2407
+ handle.unsubscribe();
2408
+ handle = null;
2409
+ }
2410
+ cachedState = {
2411
+ ...cachedState,
2412
+ isConnected: false
2413
+ };
2414
+ notify();
2415
+ },
2416
+ setDisconnected: () => {
2417
+ cachedState = {
2418
+ ...cachedState,
2419
+ isConnected: false
2420
+ };
2421
+ notify();
2422
+ }
2423
+ };
2424
+ }
2178
2425
  export {
2179
2426
  HTTP_METHODS,
2180
2427
  Semaphore,
@@ -2182,6 +2429,7 @@ export {
2182
2429
  __DEV__,
2183
2430
  buildUrl,
2184
2431
  clone,
2432
+ composeAdapter,
2185
2433
  containsFile,
2186
2434
  createClient,
2187
2435
  createEventEmitter,
@@ -2195,6 +2443,7 @@ export {
2195
2443
  createSelectorProxy,
2196
2444
  createSpooshPlugin,
2197
2445
  createStateManager,
2446
+ createSubscriptionController,
2198
2447
  createTracer,
2199
2448
  executeFetch,
2200
2449
  extractMethodFromSelector,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spoosh/core",
3
- "version": "0.15.1",
3
+ "version": "0.16.0",
4
4
  "license": "MIT",
5
5
  "description": "Type-safe API toolkit with plugin middleware system",
6
6
  "keywords": [