@spoosh/core 0.15.0 → 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,
@@ -37,7 +38,9 @@ __export(src_exports, {
37
38
  createProxyHandler: () => createProxyHandler,
38
39
  createQueueController: () => createQueueController,
39
40
  createSelectorProxy: () => createSelectorProxy,
41
+ createSpooshPlugin: () => createSpooshPlugin,
40
42
  createStateManager: () => createStateManager,
43
+ createSubscriptionController: () => createSubscriptionController,
41
44
  createTracer: () => createTracer,
42
45
  executeFetch: () => executeFetch,
43
46
  extractMethodFromSelector: () => extractMethodFromSelector,
@@ -715,7 +718,7 @@ function createSelectorProxy(onCapture) {
715
718
  {},
716
719
  {
717
720
  get(_, prop) {
718
- if (HTTP_METHODS.includes(prop)) {
721
+ if (typeof prop === "string" && prop === prop.toUpperCase()) {
719
722
  const selectorFn = (options) => {
720
723
  onCapture?.({
721
724
  call: { path, method: prop, options },
@@ -1062,6 +1065,9 @@ function createPluginExecutor(initialPlugins = []) {
1062
1065
  getPlugins() {
1063
1066
  return frozenPlugins;
1064
1067
  },
1068
+ getPluginsForOperation(operationType) {
1069
+ return frozenPlugins.filter((p) => p.operations.includes(operationType));
1070
+ },
1065
1071
  registerContextEnhancer(enhancer) {
1066
1072
  contextEnhancers.push(enhancer);
1067
1073
  },
@@ -1084,11 +1090,17 @@ function createPluginRegistry(plugins) {
1084
1090
  };
1085
1091
  }
1086
1092
 
1093
+ // src/plugins/create.ts
1094
+ function createSpooshPlugin(definition) {
1095
+ return definition;
1096
+ }
1097
+
1087
1098
  // src/Spoosh.ts
1088
1099
  var Spoosh = class _Spoosh {
1089
1100
  baseUrl;
1090
1101
  defaultOptions;
1091
1102
  _plugins;
1103
+ _transports = /* @__PURE__ */ new Map();
1092
1104
  /**
1093
1105
  * Creates a new Spoosh instance.
1094
1106
  *
@@ -1113,10 +1125,13 @@ var Spoosh = class _Spoosh {
1113
1125
  * });
1114
1126
  * ```
1115
1127
  */
1116
- constructor(baseUrl, defaultOptions, plugins) {
1128
+ constructor(baseUrl, defaultOptions, plugins, transports) {
1117
1129
  this.baseUrl = baseUrl;
1118
1130
  this.defaultOptions = defaultOptions || {};
1119
1131
  this._plugins = plugins || [];
1132
+ if (transports) {
1133
+ this._transports = transports;
1134
+ }
1120
1135
  }
1121
1136
  /**
1122
1137
  * Adds plugins to the Spoosh instance.
@@ -1140,8 +1155,37 @@ var Spoosh = class _Spoosh {
1140
1155
  return new _Spoosh(
1141
1156
  this.baseUrl,
1142
1157
  this.defaultOptions,
1143
- 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
1144
1187
  );
1188
+ return instance;
1145
1189
  }
1146
1190
  /**
1147
1191
  * Cached instance of the underlying SpooshInstance.
@@ -1168,6 +1212,7 @@ var Spoosh = class _Spoosh {
1168
1212
  stateManager,
1169
1213
  eventEmitter,
1170
1214
  pluginExecutor,
1215
+ transports: this._transports,
1171
1216
  config: {
1172
1217
  baseUrl: this.baseUrl,
1173
1218
  defaultOptions: this.defaultOptions
@@ -1175,7 +1220,8 @@ var Spoosh = class _Spoosh {
1175
1220
  _types: {
1176
1221
  schema: void 0,
1177
1222
  defaultError: void 0,
1178
- plugins: this._plugins
1223
+ plugins: this._plugins,
1224
+ transports: void 0
1179
1225
  }
1180
1226
  };
1181
1227
  }
@@ -1285,6 +1331,18 @@ var Spoosh = class _Spoosh {
1285
1331
  get _types() {
1286
1332
  return this.getInstance()._types;
1287
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
+ }
1288
1346
  };
1289
1347
 
1290
1348
  // src/createClient.ts
@@ -1296,6 +1354,16 @@ function createClient(baseUrl, defaultOptions) {
1296
1354
  });
1297
1355
  }
1298
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
+
1299
1367
  // src/controllers/base/controller.ts
1300
1368
  function createOperationController(options) {
1301
1369
  const {
@@ -2239,3 +2307,190 @@ function createQueueController(config, context) {
2239
2307
  }
2240
2308
  };
2241
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
  },
@@ -1015,11 +1018,17 @@ function createPluginRegistry(plugins) {
1015
1018
  };
1016
1019
  }
1017
1020
 
1021
+ // src/plugins/create.ts
1022
+ function createSpooshPlugin(definition) {
1023
+ return definition;
1024
+ }
1025
+
1018
1026
  // src/Spoosh.ts
1019
1027
  var Spoosh = class _Spoosh {
1020
1028
  baseUrl;
1021
1029
  defaultOptions;
1022
1030
  _plugins;
1031
+ _transports = /* @__PURE__ */ new Map();
1023
1032
  /**
1024
1033
  * Creates a new Spoosh instance.
1025
1034
  *
@@ -1044,10 +1053,13 @@ var Spoosh = class _Spoosh {
1044
1053
  * });
1045
1054
  * ```
1046
1055
  */
1047
- constructor(baseUrl, defaultOptions, plugins) {
1056
+ constructor(baseUrl, defaultOptions, plugins, transports) {
1048
1057
  this.baseUrl = baseUrl;
1049
1058
  this.defaultOptions = defaultOptions || {};
1050
1059
  this._plugins = plugins || [];
1060
+ if (transports) {
1061
+ this._transports = transports;
1062
+ }
1051
1063
  }
1052
1064
  /**
1053
1065
  * Adds plugins to the Spoosh instance.
@@ -1071,8 +1083,37 @@ var Spoosh = class _Spoosh {
1071
1083
  return new _Spoosh(
1072
1084
  this.baseUrl,
1073
1085
  this.defaultOptions,
1074
- 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
1075
1115
  );
1116
+ return instance;
1076
1117
  }
1077
1118
  /**
1078
1119
  * Cached instance of the underlying SpooshInstance.
@@ -1099,6 +1140,7 @@ var Spoosh = class _Spoosh {
1099
1140
  stateManager,
1100
1141
  eventEmitter,
1101
1142
  pluginExecutor,
1143
+ transports: this._transports,
1102
1144
  config: {
1103
1145
  baseUrl: this.baseUrl,
1104
1146
  defaultOptions: this.defaultOptions
@@ -1106,7 +1148,8 @@ var Spoosh = class _Spoosh {
1106
1148
  _types: {
1107
1149
  schema: void 0,
1108
1150
  defaultError: void 0,
1109
- plugins: this._plugins
1151
+ plugins: this._plugins,
1152
+ transports: void 0
1110
1153
  }
1111
1154
  };
1112
1155
  }
@@ -1216,6 +1259,18 @@ var Spoosh = class _Spoosh {
1216
1259
  get _types() {
1217
1260
  return this.getInstance()._types;
1218
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
+ }
1219
1274
  };
1220
1275
 
1221
1276
  // src/createClient.ts
@@ -1227,6 +1282,16 @@ function createClient(baseUrl, defaultOptions) {
1227
1282
  });
1228
1283
  }
1229
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
+
1230
1295
  // src/controllers/base/controller.ts
1231
1296
  function createOperationController(options) {
1232
1297
  const {
@@ -2170,6 +2235,193 @@ function createQueueController(config, context) {
2170
2235
  }
2171
2236
  };
2172
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
+ }
2173
2425
  export {
2174
2426
  HTTP_METHODS,
2175
2427
  Semaphore,
@@ -2177,6 +2429,7 @@ export {
2177
2429
  __DEV__,
2178
2430
  buildUrl,
2179
2431
  clone,
2432
+ composeAdapter,
2180
2433
  containsFile,
2181
2434
  createClient,
2182
2435
  createEventEmitter,
@@ -2188,7 +2441,9 @@ export {
2188
2441
  createProxyHandler,
2189
2442
  createQueueController,
2190
2443
  createSelectorProxy,
2444
+ createSpooshPlugin,
2191
2445
  createStateManager,
2446
+ createSubscriptionController,
2192
2447
  createTracer,
2193
2448
  executeFetch,
2194
2449
  extractMethodFromSelector,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spoosh/core",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "license": "MIT",
5
5
  "description": "Type-safe API toolkit with plugin middleware system",
6
6
  "keywords": [