@spoosh/core 0.4.3 → 0.6.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
@@ -20,7 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var src_exports = {};
22
22
  __export(src_exports, {
23
- HTTP_METHODS: () => HTTP_METHODS2,
23
+ HTTP_METHODS: () => HTTP_METHODS,
24
24
  Spoosh: () => Spoosh,
25
25
  applyMiddlewares: () => applyMiddlewares,
26
26
  buildUrl: () => buildUrl,
@@ -41,6 +41,7 @@ __export(src_exports, {
41
41
  extractMethodFromSelector: () => extractMethodFromSelector,
42
42
  extractPathFromSelector: () => extractPathFromSelector,
43
43
  generateTags: () => generateTags,
44
+ getContentType: () => getContentType,
44
45
  isJsonBody: () => isJsonBody,
45
46
  mergeHeaders: () => mergeHeaders,
46
47
  objectToFormData: () => objectToFormData,
@@ -167,6 +168,11 @@ function setHeaders(requestOptions, newHeaders) {
167
168
  };
168
169
  }
169
170
  }
171
+ function getContentType(headers) {
172
+ if (!headers) return void 0;
173
+ const headersObj = new Headers(headers);
174
+ return headersObj.get("content-type") ?? void 0;
175
+ }
170
176
 
171
177
  // src/utils/objectToFormData.ts
172
178
  function objectToFormData(obj) {
@@ -302,12 +308,6 @@ function buildInputFields(requestOptions) {
302
308
  if (requestOptions?.body !== void 0) {
303
309
  fields.body = requestOptions.body;
304
310
  }
305
- if (requestOptions?.formData !== void 0) {
306
- fields.formData = requestOptions.formData;
307
- }
308
- if (requestOptions?.urlEncoded !== void 0) {
309
- fields.urlEncoded = requestOptions.urlEncoded;
310
- }
311
311
  if (requestOptions?.params !== void 0) {
312
312
  fields.params = requestOptions.params;
313
313
  }
@@ -336,7 +336,7 @@ async function executeCoreFetch(config) {
336
336
  const maxRetries = requestOptions?.retries ?? 3;
337
337
  const baseDelay = requestOptions?.retryDelay ?? 1e3;
338
338
  const retryCount = maxRetries === false ? 0 : maxRetries;
339
- const finalPath = requestOptions?._pathTransformer?.(path) ?? path;
339
+ const finalPath = path;
340
340
  const url = buildUrl(baseUrl, finalPath, requestOptions?.query);
341
341
  let headers = await mergeHeaders(defaultHeaders, requestOptions?.headers);
342
342
  const fetchInit = {
@@ -361,22 +361,17 @@ async function executeCoreFetch(config) {
361
361
  if (requestOptions?.signal) {
362
362
  fetchInit.signal = requestOptions.signal;
363
363
  }
364
- if (requestOptions?.formData !== void 0) {
365
- fetchInit.body = objectToFormData(
366
- requestOptions.formData
367
- );
368
- } else if (requestOptions?.urlEncoded !== void 0) {
369
- fetchInit.body = objectToUrlEncoded(
370
- requestOptions.urlEncoded
371
- );
372
- headers = await mergeHeaders(headers, {
373
- "Content-Type": "application/x-www-form-urlencoded"
374
- });
375
- if (headers) {
376
- fetchInit.headers = headers;
377
- }
378
- } else if (requestOptions?.body !== void 0) {
379
- if (isJsonBody(requestOptions.body)) {
364
+ if (requestOptions?.body !== void 0) {
365
+ const contentType = getContentType(headers);
366
+ if (contentType?.includes("application/x-www-form-urlencoded")) {
367
+ fetchInit.body = objectToUrlEncoded(
368
+ requestOptions.body
369
+ );
370
+ } else if (contentType?.includes("multipart/form-data") || containsFile(requestOptions.body)) {
371
+ fetchInit.body = objectToFormData(
372
+ requestOptions.body
373
+ );
374
+ } else if (isJsonBody(requestOptions.body)) {
380
375
  fetchInit.body = JSON.stringify(requestOptions.body);
381
376
  headers = await mergeHeaders(headers, {
382
377
  "Content-Type": "application/json"
@@ -384,10 +379,6 @@ async function executeCoreFetch(config) {
384
379
  if (headers) {
385
380
  fetchInit.headers = headers;
386
381
  }
387
- } else if (containsFile(requestOptions.body)) {
388
- fetchInit.body = objectToFormData(
389
- requestOptions.body
390
- );
391
382
  } else {
392
383
  fetchInit.body = requestOptions.body;
393
384
  }
@@ -440,104 +431,89 @@ async function executeCoreFetch(config) {
440
431
  }
441
432
 
442
433
  // src/proxy/handler.ts
443
- var HTTP_METHODS = {
444
- $get: "GET",
445
- $post: "POST",
446
- $put: "PUT",
447
- $patch: "PATCH",
448
- $delete: "DELETE"
449
- };
434
+ function resolvePath2(path, params) {
435
+ const segments = path.split("/").filter(Boolean);
436
+ if (!params) {
437
+ return segments;
438
+ }
439
+ return segments.map((segment) => {
440
+ if (segment.startsWith(":")) {
441
+ const paramName = segment.slice(1);
442
+ const value = params[paramName];
443
+ return value !== void 0 ? String(value) : segment;
444
+ }
445
+ return segment;
446
+ });
447
+ }
450
448
  function createProxyHandler(config) {
451
449
  const {
452
450
  baseUrl,
453
451
  defaultOptions,
454
- path = [],
455
452
  fetchExecutor = executeFetch,
456
453
  nextTags
457
454
  } = config;
458
- const handler = {
459
- get(_target, prop) {
460
- if (typeof prop === "symbol") return void 0;
461
- const method = HTTP_METHODS[prop];
462
- if (method) {
463
- return (options) => fetchExecutor(
464
- baseUrl,
465
- path,
466
- method,
467
- defaultOptions,
468
- options,
469
- nextTags
470
- );
455
+ return ((path) => {
456
+ return new Proxy(
457
+ {},
458
+ {
459
+ get(_target, prop) {
460
+ if (typeof prop === "symbol") return void 0;
461
+ const method = prop;
462
+ if (!["GET", "POST", "PUT", "PATCH", "DELETE"].includes(method)) {
463
+ return void 0;
464
+ }
465
+ return (options) => {
466
+ const resolvedPath = resolvePath2(path, options?.params);
467
+ return fetchExecutor(
468
+ baseUrl,
469
+ resolvedPath,
470
+ method,
471
+ defaultOptions,
472
+ options,
473
+ nextTags
474
+ );
475
+ };
476
+ }
471
477
  }
472
- return createProxyHandler({
473
- baseUrl,
474
- defaultOptions,
475
- path: [...path, prop],
476
- fetchExecutor,
477
- nextTags
478
- });
479
- },
480
- // Handles function call syntax for dynamic segments: api.posts(123), api.posts(":id"), api.users(userId)
481
- // This is the only way to access dynamic segments in Spoosh.
482
- // The function call syntax allows TypeScript to capture the literal type, enabling params: { id: string } inference.
483
- apply(_target, _thisArg, args) {
484
- const [segment] = args;
485
- return createProxyHandler({
486
- baseUrl,
487
- defaultOptions,
488
- path: [...path, segment],
489
- fetchExecutor,
490
- nextTags
491
- });
492
- }
493
- };
494
- const noop = () => {
495
- };
496
- return new Proxy(noop, handler);
478
+ );
479
+ });
497
480
  }
498
481
 
499
482
  // src/proxy/selector-proxy.ts
500
- var HTTP_METHODS2 = [
501
- "$get",
502
- "$post",
503
- "$put",
504
- "$patch",
505
- "$delete"
506
- ];
483
+ var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
507
484
  function createSelectorProxy(onCapture) {
508
- const createProxy = (path = []) => {
509
- return new Proxy(() => {
510
- }, {
511
- get(_, prop) {
512
- if (HTTP_METHODS2.includes(prop)) {
513
- const selectorFn = (options) => {
485
+ const createMethodsProxy = (path) => {
486
+ return new Proxy(
487
+ {},
488
+ {
489
+ get(_, prop) {
490
+ if (HTTP_METHODS.includes(prop)) {
491
+ const selectorFn = (options) => {
492
+ onCapture?.({
493
+ call: { path, method: prop, options },
494
+ selector: null
495
+ });
496
+ return Promise.resolve({ data: void 0 });
497
+ };
498
+ selectorFn.__selectorPath = path;
499
+ selectorFn.__selectorMethod = prop;
514
500
  onCapture?.({
515
- call: { path, method: prop, options },
516
- selector: null
501
+ call: null,
502
+ selector: { path, method: prop }
517
503
  });
518
- return Promise.resolve({ data: void 0 });
519
- };
520
- selectorFn.__selectorPath = path;
521
- selectorFn.__selectorMethod = prop;
522
- onCapture?.({
523
- call: null,
524
- selector: { path, method: prop }
525
- });
526
- return selectorFn;
504
+ return selectorFn;
505
+ }
506
+ return void 0;
527
507
  }
528
- return createProxy([...path, prop]);
529
- },
530
- // Handles function call syntax for dynamic segments: api.posts("123"), api.users(userId)
531
- apply(_, __, args) {
532
- const [segment] = args;
533
- return createProxy([...path, String(segment)]);
534
508
  }
535
- });
509
+ );
536
510
  };
537
- return createProxy();
511
+ return ((path) => {
512
+ return createMethodsProxy(path);
513
+ });
538
514
  }
539
515
  function extractPathFromSelector(fn) {
540
- return fn.__selectorPath ?? [];
516
+ return fn.__selectorPath ?? "";
541
517
  }
542
518
  function extractMethodFromSelector(fn) {
543
519
  return fn.__selectorMethod;
@@ -598,7 +574,7 @@ function createStateManager() {
598
574
  const newEntry = {
599
575
  state: entry.state ?? createInitialState(),
600
576
  tags: entry.tags ?? [],
601
- pluginResult: /* @__PURE__ */ new Map(),
577
+ meta: /* @__PURE__ */ new Map(),
602
578
  selfTag: generateSelfTagFromKey(key),
603
579
  previousData: entry.previousData,
604
580
  stale: entry.stale
@@ -658,11 +634,11 @@ function createStateManager() {
658
634
  });
659
635
  return entries;
660
636
  },
661
- setPluginResult(key, data) {
637
+ setMeta(key, data) {
662
638
  const entry = cache.get(key);
663
639
  if (entry) {
664
640
  for (const [name, value] of Object.entries(data)) {
665
- entry.pluginResult.set(name, value);
641
+ entry.meta.set(name, value);
666
642
  }
667
643
  notifySubscribers(key);
668
644
  }
@@ -982,13 +958,13 @@ var Spoosh = class _Spoosh {
982
958
  * const { api } = client;
983
959
  *
984
960
  * // GET request
985
- * const { data } = await api.posts.$get();
961
+ * const { data } = await api("posts").GET();
986
962
  *
987
963
  * // POST request with body
988
- * const { data } = await api.posts.$post({ body: { title: 'Hello' } });
964
+ * const { data } = await api("posts").POST({ body: { title: 'Hello' } });
989
965
  *
990
966
  * // Dynamic path parameters
991
- * const { data } = await api.posts(postId).$get();
967
+ * const { data } = await api("posts/:id").GET({ params: { id: postId } });
992
968
  * ```
993
969
  */
994
970
  get api() {
@@ -1004,7 +980,7 @@ var Spoosh = class _Spoosh {
1004
980
  * const { stateManager } = client;
1005
981
  *
1006
982
  * // Get cached data
1007
- * const cache = stateManager.getCache('posts.$get');
983
+ * const cache = stateManager.getCache('posts.GET');
1008
984
  *
1009
985
  * // Invalidate cache by tag
1010
986
  * stateManager.invalidate(['posts']);
package/dist/index.mjs CHANGED
@@ -112,6 +112,11 @@ function setHeaders(requestOptions, newHeaders) {
112
112
  };
113
113
  }
114
114
  }
115
+ function getContentType(headers) {
116
+ if (!headers) return void 0;
117
+ const headersObj = new Headers(headers);
118
+ return headersObj.get("content-type") ?? void 0;
119
+ }
115
120
 
116
121
  // src/utils/objectToFormData.ts
117
122
  function objectToFormData(obj) {
@@ -247,12 +252,6 @@ function buildInputFields(requestOptions) {
247
252
  if (requestOptions?.body !== void 0) {
248
253
  fields.body = requestOptions.body;
249
254
  }
250
- if (requestOptions?.formData !== void 0) {
251
- fields.formData = requestOptions.formData;
252
- }
253
- if (requestOptions?.urlEncoded !== void 0) {
254
- fields.urlEncoded = requestOptions.urlEncoded;
255
- }
256
255
  if (requestOptions?.params !== void 0) {
257
256
  fields.params = requestOptions.params;
258
257
  }
@@ -281,7 +280,7 @@ async function executeCoreFetch(config) {
281
280
  const maxRetries = requestOptions?.retries ?? 3;
282
281
  const baseDelay = requestOptions?.retryDelay ?? 1e3;
283
282
  const retryCount = maxRetries === false ? 0 : maxRetries;
284
- const finalPath = requestOptions?._pathTransformer?.(path) ?? path;
283
+ const finalPath = path;
285
284
  const url = buildUrl(baseUrl, finalPath, requestOptions?.query);
286
285
  let headers = await mergeHeaders(defaultHeaders, requestOptions?.headers);
287
286
  const fetchInit = {
@@ -306,22 +305,17 @@ async function executeCoreFetch(config) {
306
305
  if (requestOptions?.signal) {
307
306
  fetchInit.signal = requestOptions.signal;
308
307
  }
309
- if (requestOptions?.formData !== void 0) {
310
- fetchInit.body = objectToFormData(
311
- requestOptions.formData
312
- );
313
- } else if (requestOptions?.urlEncoded !== void 0) {
314
- fetchInit.body = objectToUrlEncoded(
315
- requestOptions.urlEncoded
316
- );
317
- headers = await mergeHeaders(headers, {
318
- "Content-Type": "application/x-www-form-urlencoded"
319
- });
320
- if (headers) {
321
- fetchInit.headers = headers;
322
- }
323
- } else if (requestOptions?.body !== void 0) {
324
- if (isJsonBody(requestOptions.body)) {
308
+ if (requestOptions?.body !== void 0) {
309
+ const contentType = getContentType(headers);
310
+ if (contentType?.includes("application/x-www-form-urlencoded")) {
311
+ fetchInit.body = objectToUrlEncoded(
312
+ requestOptions.body
313
+ );
314
+ } else if (contentType?.includes("multipart/form-data") || containsFile(requestOptions.body)) {
315
+ fetchInit.body = objectToFormData(
316
+ requestOptions.body
317
+ );
318
+ } else if (isJsonBody(requestOptions.body)) {
325
319
  fetchInit.body = JSON.stringify(requestOptions.body);
326
320
  headers = await mergeHeaders(headers, {
327
321
  "Content-Type": "application/json"
@@ -329,10 +323,6 @@ async function executeCoreFetch(config) {
329
323
  if (headers) {
330
324
  fetchInit.headers = headers;
331
325
  }
332
- } else if (containsFile(requestOptions.body)) {
333
- fetchInit.body = objectToFormData(
334
- requestOptions.body
335
- );
336
326
  } else {
337
327
  fetchInit.body = requestOptions.body;
338
328
  }
@@ -385,104 +375,89 @@ async function executeCoreFetch(config) {
385
375
  }
386
376
 
387
377
  // src/proxy/handler.ts
388
- var HTTP_METHODS = {
389
- $get: "GET",
390
- $post: "POST",
391
- $put: "PUT",
392
- $patch: "PATCH",
393
- $delete: "DELETE"
394
- };
378
+ function resolvePath2(path, params) {
379
+ const segments = path.split("/").filter(Boolean);
380
+ if (!params) {
381
+ return segments;
382
+ }
383
+ return segments.map((segment) => {
384
+ if (segment.startsWith(":")) {
385
+ const paramName = segment.slice(1);
386
+ const value = params[paramName];
387
+ return value !== void 0 ? String(value) : segment;
388
+ }
389
+ return segment;
390
+ });
391
+ }
395
392
  function createProxyHandler(config) {
396
393
  const {
397
394
  baseUrl,
398
395
  defaultOptions,
399
- path = [],
400
396
  fetchExecutor = executeFetch,
401
397
  nextTags
402
398
  } = config;
403
- const handler = {
404
- get(_target, prop) {
405
- if (typeof prop === "symbol") return void 0;
406
- const method = HTTP_METHODS[prop];
407
- if (method) {
408
- return (options) => fetchExecutor(
409
- baseUrl,
410
- path,
411
- method,
412
- defaultOptions,
413
- options,
414
- nextTags
415
- );
399
+ return ((path) => {
400
+ return new Proxy(
401
+ {},
402
+ {
403
+ get(_target, prop) {
404
+ if (typeof prop === "symbol") return void 0;
405
+ const method = prop;
406
+ if (!["GET", "POST", "PUT", "PATCH", "DELETE"].includes(method)) {
407
+ return void 0;
408
+ }
409
+ return (options) => {
410
+ const resolvedPath = resolvePath2(path, options?.params);
411
+ return fetchExecutor(
412
+ baseUrl,
413
+ resolvedPath,
414
+ method,
415
+ defaultOptions,
416
+ options,
417
+ nextTags
418
+ );
419
+ };
420
+ }
416
421
  }
417
- return createProxyHandler({
418
- baseUrl,
419
- defaultOptions,
420
- path: [...path, prop],
421
- fetchExecutor,
422
- nextTags
423
- });
424
- },
425
- // Handles function call syntax for dynamic segments: api.posts(123), api.posts(":id"), api.users(userId)
426
- // This is the only way to access dynamic segments in Spoosh.
427
- // The function call syntax allows TypeScript to capture the literal type, enabling params: { id: string } inference.
428
- apply(_target, _thisArg, args) {
429
- const [segment] = args;
430
- return createProxyHandler({
431
- baseUrl,
432
- defaultOptions,
433
- path: [...path, segment],
434
- fetchExecutor,
435
- nextTags
436
- });
437
- }
438
- };
439
- const noop = () => {
440
- };
441
- return new Proxy(noop, handler);
422
+ );
423
+ });
442
424
  }
443
425
 
444
426
  // src/proxy/selector-proxy.ts
445
- var HTTP_METHODS2 = [
446
- "$get",
447
- "$post",
448
- "$put",
449
- "$patch",
450
- "$delete"
451
- ];
427
+ var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
452
428
  function createSelectorProxy(onCapture) {
453
- const createProxy = (path = []) => {
454
- return new Proxy(() => {
455
- }, {
456
- get(_, prop) {
457
- if (HTTP_METHODS2.includes(prop)) {
458
- const selectorFn = (options) => {
429
+ const createMethodsProxy = (path) => {
430
+ return new Proxy(
431
+ {},
432
+ {
433
+ get(_, prop) {
434
+ if (HTTP_METHODS.includes(prop)) {
435
+ const selectorFn = (options) => {
436
+ onCapture?.({
437
+ call: { path, method: prop, options },
438
+ selector: null
439
+ });
440
+ return Promise.resolve({ data: void 0 });
441
+ };
442
+ selectorFn.__selectorPath = path;
443
+ selectorFn.__selectorMethod = prop;
459
444
  onCapture?.({
460
- call: { path, method: prop, options },
461
- selector: null
445
+ call: null,
446
+ selector: { path, method: prop }
462
447
  });
463
- return Promise.resolve({ data: void 0 });
464
- };
465
- selectorFn.__selectorPath = path;
466
- selectorFn.__selectorMethod = prop;
467
- onCapture?.({
468
- call: null,
469
- selector: { path, method: prop }
470
- });
471
- return selectorFn;
448
+ return selectorFn;
449
+ }
450
+ return void 0;
472
451
  }
473
- return createProxy([...path, prop]);
474
- },
475
- // Handles function call syntax for dynamic segments: api.posts("123"), api.users(userId)
476
- apply(_, __, args) {
477
- const [segment] = args;
478
- return createProxy([...path, String(segment)]);
479
452
  }
480
- });
453
+ );
481
454
  };
482
- return createProxy();
455
+ return ((path) => {
456
+ return createMethodsProxy(path);
457
+ });
483
458
  }
484
459
  function extractPathFromSelector(fn) {
485
- return fn.__selectorPath ?? [];
460
+ return fn.__selectorPath ?? "";
486
461
  }
487
462
  function extractMethodFromSelector(fn) {
488
463
  return fn.__selectorMethod;
@@ -543,7 +518,7 @@ function createStateManager() {
543
518
  const newEntry = {
544
519
  state: entry.state ?? createInitialState(),
545
520
  tags: entry.tags ?? [],
546
- pluginResult: /* @__PURE__ */ new Map(),
521
+ meta: /* @__PURE__ */ new Map(),
547
522
  selfTag: generateSelfTagFromKey(key),
548
523
  previousData: entry.previousData,
549
524
  stale: entry.stale
@@ -603,11 +578,11 @@ function createStateManager() {
603
578
  });
604
579
  return entries;
605
580
  },
606
- setPluginResult(key, data) {
581
+ setMeta(key, data) {
607
582
  const entry = cache.get(key);
608
583
  if (entry) {
609
584
  for (const [name, value] of Object.entries(data)) {
610
- entry.pluginResult.set(name, value);
585
+ entry.meta.set(name, value);
611
586
  }
612
587
  notifySubscribers(key);
613
588
  }
@@ -927,13 +902,13 @@ var Spoosh = class _Spoosh {
927
902
  * const { api } = client;
928
903
  *
929
904
  * // GET request
930
- * const { data } = await api.posts.$get();
905
+ * const { data } = await api("posts").GET();
931
906
  *
932
907
  * // POST request with body
933
- * const { data } = await api.posts.$post({ body: { title: 'Hello' } });
908
+ * const { data } = await api("posts").POST({ body: { title: 'Hello' } });
934
909
  *
935
910
  * // Dynamic path parameters
936
- * const { data } = await api.posts(postId).$get();
911
+ * const { data } = await api("posts/:id").GET({ params: { id: postId } });
937
912
  * ```
938
913
  */
939
914
  get api() {
@@ -949,7 +924,7 @@ var Spoosh = class _Spoosh {
949
924
  * const { stateManager } = client;
950
925
  *
951
926
  * // Get cached data
952
- * const cache = stateManager.getCache('posts.$get');
927
+ * const cache = stateManager.getCache('posts.GET');
953
928
  *
954
929
  * // Invalidate cache by tag
955
930
  * stateManager.invalidate(['posts']);
@@ -1586,7 +1561,7 @@ function createInfiniteReadController(options) {
1586
1561
  return controller;
1587
1562
  }
1588
1563
  export {
1589
- HTTP_METHODS2 as HTTP_METHODS,
1564
+ HTTP_METHODS,
1590
1565
  Spoosh,
1591
1566
  applyMiddlewares,
1592
1567
  buildUrl,
@@ -1607,6 +1582,7 @@ export {
1607
1582
  extractMethodFromSelector,
1608
1583
  extractPathFromSelector,
1609
1584
  generateTags,
1585
+ getContentType,
1610
1586
  isJsonBody,
1611
1587
  mergeHeaders,
1612
1588
  objectToFormData,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spoosh/core",
3
- "version": "0.4.3",
3
+ "version": "0.6.0",
4
4
  "license": "MIT",
5
5
  "description": "Type-safe API client with plugin middleware system",
6
6
  "keywords": [