pinejs-client-fetch 1.0.2 → 1.0.3

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.
Files changed (2) hide show
  1. package/build/index.umd.js +90 -26
  2. package/package.json +3 -3
@@ -38,9 +38,11 @@
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.PinejsClientCore = void 0;
40
40
  function isArray(value) {
41
+ // See: https://github.com/microsoft/TypeScript/issues/17002
41
42
  return Array.isArray(value);
42
43
  }
43
44
  const noop = () => {
45
+ // noop
44
46
  };
45
47
  const deprecated = (() => {
46
48
  const deprecationMessages = {
@@ -50,6 +52,14 @@
50
52
  countWithNestedOperationInFilter: "'`$filter: { a: { $count: { $op: number } } }` is deprecated, please use `$filter: { $eq: [ { a: { $count: {} } }, number ] }` instead.",
51
53
  countInOrderBy: "'`$orderby: 'a/$count'` is deprecated, please use `$orderby: { a: { $count: {...} } }` instead.",
52
54
  non$filterOptionIn$expand$count: 'using OData options other than $filter in a `$expand: { a: { $count: {...} } }` is deprecated, please remove them.',
55
+ urlInGetOrCreate: 'Passing `url` to `getOrCreate` is deprecated as it is unsupported and may have adverse effects, please use a query object instead.',
56
+ urlInUpsert: 'Passing `url` to `upsert` is deprecated as it is unsupported and may have adverse effects, please use a query object instead.',
57
+ urlInCompile: 'Passing `url` to `compile` is deprecated, please use a query object instead or use `request` directly.',
58
+ urlInGet: 'Passing `url` to `get` is deprecated, please use a query object instead or use `request` directly.',
59
+ urlInPost: 'Passing `url` to `post` is deprecated, please use a query object instead or use `request` directly.',
60
+ urlInPatch: 'Passing `url` to `patch` is deprecated, please use a query object instead or use `request` directly.',
61
+ urlInPut: 'Passing `url` to `put` is deprecated, please use a query object instead or use `request` directly.',
62
+ urlInDelete: 'Passing `url` to `delete` is deprecated, please use a query object instead or use `request` directly.',
53
63
  };
54
64
  const result = {};
55
65
  for (const key of Object.keys(deprecationMessages)) {
@@ -114,6 +124,9 @@
114
124
  return;
115
125
  }
116
126
  this.restartTimeout();
127
+ // Catch errors in event subscribers so that they don't trigger
128
+ // the 'catch' below, and that subsequent subscribers will still
129
+ // be called
117
130
  this.subscribers.data.forEach((fn) => {
118
131
  try {
119
132
  fn(response);
@@ -179,6 +192,7 @@
179
192
  isBoolean(value) ||
180
193
  isDate(value));
181
194
  };
195
+ // Escape a resource name (string), or resource path (array)
182
196
  const escapeResource = (resource) => {
183
197
  if (isString(resource)) {
184
198
  resource = encodeURIComponent(resource);
@@ -191,6 +205,7 @@
191
205
  }
192
206
  return resource.replace(trailingCountRegex, '/$count');
193
207
  };
208
+ // Escape a primitive value
194
209
  const escapeValue = (value) => {
195
210
  if (isString(value)) {
196
211
  value = value.replace(/'/g, "''");
@@ -223,6 +238,7 @@
223
238
  throw new Error('Expected a string or array, got: ' + typeof strOrArray);
224
239
  }
225
240
  };
241
+ // Join together a bunch of statements making sure the whole lot is correctly parenthesised
226
242
  const bracketJoin = (arr, separator) => {
227
243
  if (arr.length === 1) {
228
244
  return arr[0];
@@ -243,6 +259,7 @@
243
259
  });
244
260
  return resultArr;
245
261
  };
262
+ // Add the parentKey + operator if it exists.
246
263
  const addParentKey = (filter, parentKey, operator = ' eq ') => {
247
264
  if (parentKey != null) {
248
265
  if (isArray(filter)) {
@@ -267,6 +284,7 @@
267
284
  for (const index of Object.keys(params)) {
268
285
  const param = params[index];
269
286
  let paramStr = `(${buildFilter(param).join('')})`;
287
+ // Escape $ for filter.replace
270
288
  paramStr = paramStr.replace(/\$/g, '$$$$');
271
289
  filter = filter.replace(new RegExp(`\\$${index}([^a-zA-Z0-9]|$)`, 'g'), `${paramStr}$1`);
272
290
  }
@@ -327,6 +345,7 @@
327
345
  throw new Error(`Expected null/string/number/obj/array, got: ${typeof filter}`);
328
346
  }
329
347
  };
348
+ // Handle special cases for all the different $ operators.
330
349
  const handleFilterOperator = (filter, operator, parentKey) => {
331
350
  switch (operator) {
332
351
  case '$ne':
@@ -341,6 +360,7 @@
341
360
  case '$div':
342
361
  case '$mod':
343
362
  return filterOperation(filter, operator, parentKey);
363
+ // break
344
364
  case '$contains':
345
365
  case '$endswith':
346
366
  case '$startswith':
@@ -397,12 +417,14 @@
397
417
  }
398
418
  return addParentKey(`duration'${durationString}'`, parentKey);
399
419
  }
420
+ // break
400
421
  case '$raw': {
401
422
  const filterx = filter;
402
423
  if (isString(filterx)) {
403
424
  return addParentKey(`(${filterx})`, parentKey);
404
425
  }
405
426
  else if (!isPrimitive(filterx)) {
427
+ // This needs to use the mutable version `Array.isArray` or otherwise the destructuring gets the wrong types?
406
428
  if (Array.isArray(filterx)) {
407
429
  const [rawFilter, ...params] = filterx;
408
430
  if (!isString(rawFilter)) {
@@ -433,6 +455,7 @@
433
455
  }
434
456
  throw new Error(`Expected string/array/object for ${operator}, got: ${typeof filterx}`);
435
457
  }
458
+ // break
436
459
  case '$': {
437
460
  const resource = escapeResource(filter);
438
461
  return addParentKey(resource, parentKey);
@@ -441,6 +464,7 @@
441
464
  let keys = ['$count'];
442
465
  if (parentKey != null &&
443
466
  isObject(filter) &&
467
+ // Handles the `$filter: $op: [ {a: {$count: {'...'}}}, value]`` case.
444
468
  (Object.keys(filter).length === 0 ||
445
469
  Object.prototype.hasOwnProperty.call(filter, '$filter'))) {
446
470
  keys = parentKey.slice(0, parentKey.length - 1);
@@ -450,14 +474,17 @@
450
474
  if (parentKey != null) {
451
475
  keys = parentKey.concat(keys);
452
476
  }
477
+ // Handles the `$filter: a: $count: value` case.
453
478
  deprecated.countWithNestedOperationInFilter();
454
479
  return buildFilter(filter, keys);
455
480
  }
481
+ // break
456
482
  case '$and':
457
483
  case '$or': {
458
484
  const filterStr = buildFilter(filter, undefined, ` ${operator.slice(1)} `);
459
485
  return addParentKey(filterStr, parentKey);
460
486
  }
487
+ // break
461
488
  case '$in': {
462
489
  filter = filter;
463
490
  if (isPrimitive(filter)) {
@@ -486,21 +513,24 @@
486
513
  throw new Error(`Expected null/string/number/bool/obj/array, got: ${typeof filter}`);
487
514
  }
488
515
  }
516
+ // break
489
517
  case '$not': {
490
518
  const filterStr = `not(${buildFilter(filter).join('')})`;
491
519
  return addParentKey(filterStr, parentKey);
492
520
  }
521
+ // break
493
522
  case '$any':
494
523
  case '$all': {
495
- filter = filter;
496
- const alias = filter.$alias;
497
- const expr = filter.$expr;
524
+ const filterx = filter;
525
+ const alias = filterx.$alias;
526
+ const expr = filterx.$expr;
498
527
  if (alias == null) {
499
528
  throw new Error(`Lambda expression (${operator}) has no alias defined.`);
500
529
  }
501
530
  if (expr == null) {
502
531
  throw new Error(`Lambda expression (${operator}) has no expr defined.`);
503
532
  }
533
+ // Disable the expandFilter deprecation notice when inside a lambda expr
504
534
  const deprecatedFn = (deprecated.expandFilter = noop);
505
535
  let filterStr;
506
536
  try {
@@ -512,6 +542,7 @@
512
542
  filterStr = `${operator.slice(1)}(${alias}:${filterStr})`;
513
543
  return addParentKey(filterStr, parentKey, '/');
514
544
  }
545
+ // break
515
546
  default:
516
547
  throw new Error(`Unrecognised operator: '${operator}'`);
517
548
  }
@@ -521,10 +552,10 @@
521
552
  if (value === undefined) {
522
553
  throw new Error(`'${key}' was present on a filter object but undefined, did you mean to use null instead?`);
523
554
  }
524
- if (key[0] === '$') {
555
+ if (key.startsWith('$')) {
525
556
  return handleFilterOperator(value, key, parentKey);
526
557
  }
527
- else if (key[0] === '@') {
558
+ else if (key.startsWith('@')) {
528
559
  const parameterAlias = escapeParameterAlias(value);
529
560
  return addParentKey(parameterAlias, parentKey);
530
561
  }
@@ -548,6 +579,7 @@
548
579
  return buildFilter(value, parentKey);
549
580
  });
550
581
  };
582
+ // Turn a filter query object into an OData $filter string
551
583
  const buildFilter = (filter, parentKey, joinStr) => {
552
584
  if (isPrimitive(filter)) {
553
585
  const filterStr = escapeValue(filter);
@@ -586,10 +618,13 @@
586
618
  return join(result);
587
619
  }
588
620
  else if (isObject(orderby)) {
589
- const _a = orderby, { $dir } = _a, $orderby = __rest(_a, ["$dir"]);
621
+ const { $dir } = orderby, $orderby = __rest(orderby, ["$dir"]);
590
622
  const result = mapObj($orderby, (dirOrOptions, key) => {
591
623
  let propertyPath = key;
592
624
  let dir = $dir;
625
+ if (dirOrOptions == null) {
626
+ throw new Error(`Orderby object values must not be null, got null for ${key}`);
627
+ }
593
628
  if (typeof dirOrOptions === 'string') {
594
629
  dir = dirOrOptions;
595
630
  }
@@ -656,12 +691,14 @@
656
691
  break;
657
692
  }
658
693
  default:
659
- if (option[0] === '@') {
694
+ // Escape parameter aliases as primitives
695
+ if (option.startsWith('@')) {
660
696
  if (!isPrimitive(value)) {
661
697
  throw new Error(`Unknown type for parameter alias option '${option}': ${typeof value}`);
662
698
  }
663
699
  compiledValue = '' + escapeValue(value);
664
700
  }
701
+ // Unknown values are left as-is
665
702
  else if (isArray(value)) {
666
703
  compiledValue = join(value);
667
704
  }
@@ -685,8 +722,12 @@
685
722
  }
686
723
  options = options.$count;
687
724
  parentKey += '/$count';
725
+ // Check whether there is anything else other than $filter in the $count
726
+ // and error b/c it's invalid.
688
727
  if (Object.keys(options).length >
689
728
  (Object.prototype.hasOwnProperty.call(options, '$filter') ? 1 : 0)) {
729
+ // TODO: Remove the optionOperation check in the next major,
730
+ // so that it throws for all operators.
690
731
  if (optionOperation === '$expand') {
691
732
  deprecated.non$filterOptionIn$expand$count();
692
733
  }
@@ -696,7 +737,7 @@
696
737
  }
697
738
  }
698
739
  const optionsArray = mapObj(options, (value, key) => {
699
- if (key[0] === '$') {
740
+ if (key.startsWith('$')) {
700
741
  if (!isValidOption(key)) {
701
742
  throw new Error(`Unknown key option '${key}'`);
702
743
  }
@@ -716,9 +757,12 @@
716
757
  };
717
758
  const handleExpandObject = (expand) => {
718
759
  const expands = mapObj(expand, (value, key) => {
719
- if (key[0] === '$') {
760
+ if (key.startsWith('$')) {
720
761
  throw new Error('Cannot have expand options without first expanding something!');
721
762
  }
763
+ if (value == null) {
764
+ throw new Error(`Expand object values must not be null, got null for ${key}`);
765
+ }
722
766
  if (isPrimitive(value)) {
723
767
  const jsonValue = JSON.stringify(value);
724
768
  throw new Error(`'$expand: ${key}: ${jsonValue}' is invalid, use '$expand: ${key}: $expand: ${jsonValue}' instead.`);
@@ -762,6 +806,7 @@
762
806
  if (typeof retryAfterRaw !== 'string') {
763
807
  return;
764
808
  }
809
+ // TODO: Add support for parsing and converting HTTP date format values to delay.
765
810
  const retryAfterSeconds = parseInt(retryAfterRaw, 10);
766
811
  if (!Number.isInteger(retryAfterSeconds) || retryAfterSeconds < 0) {
767
812
  return;
@@ -775,6 +820,7 @@
775
820
  'retry',
776
821
  ];
777
822
  class PinejsClientCore {
823
+ // `backendParams` must be used by a backend for any additional parameters it may have.
778
824
  constructor(params) {
779
825
  this.apiPrefix = '/';
780
826
  this.passthrough = {};
@@ -799,11 +845,12 @@
799
845
  callWithRetry(fnCall, retry) {
800
846
  return __awaiter(this, void 0, void 0, function* () {
801
847
  var _a, _b, _c, _d, _e, _f, _g;
848
+ // Explicitly passing retry as false disables retrying for this call.
802
849
  if (retry === false || (retry == null && this.retry === false)) {
803
850
  return yield fnCall();
804
851
  }
805
852
  const retryDefaultParameters = this.retry || {};
806
- const retryParameters = retry || {};
853
+ const retryParameters = retry !== null && retry !== void 0 ? retry : {};
807
854
  const minDelayMs = (_a = retryParameters.minDelayMs) !== null && _a !== void 0 ? _a : retryDefaultParameters.minDelayMs;
808
855
  const maxDelayMs = (_b = retryParameters.maxDelayMs) !== null && _b !== void 0 ? _b : retryDefaultParameters.maxDelayMs;
809
856
  const maxAttempts = (_c = retryParameters.maxAttempts) !== null && _c !== void 0 ? _c : retryDefaultParameters.maxAttempts;
@@ -823,6 +870,7 @@
823
870
  const getRetryAfterHeader = (_e = retryParameters.getRetryAfterHeader) !== null && _e !== void 0 ? _e : retryDefaultParameters.getRetryAfterHeader;
824
871
  let attempt = 1;
825
872
  const canRetryHandler = (_g = (_f = retryParameters.canRetry) !== null && _f !== void 0 ? _f : retryDefaultParameters.canRetry) !== null && _g !== void 0 ? _g : this.canRetryDefaultHandler;
873
+ // eslint-disable-next-line no-constant-condition -- we handle retry logic/delaying within the loop
826
874
  while (true) {
827
875
  try {
828
876
  return yield fnCall();
@@ -832,6 +880,8 @@
832
880
  throw err;
833
881
  }
834
882
  let delayMs = Math.min(Math.pow(2, (attempt - 1)) * minDelayMs, maxDelayMs);
883
+ // note that attempt is incremented before calling onRetryHandler because
884
+ // retries effectively begin with attempt number 2.
835
885
  attempt++;
836
886
  const retryAfterDelayMs = getRetryAfterHeaderDelayMs(getRetryAfterHeader, err);
837
887
  if (retryAfterDelayMs != null && retryAfterDelayMs > delayMs) {
@@ -845,6 +895,7 @@
845
895
  }
846
896
  });
847
897
  }
898
+ // `backendParams` must be used by a backend for any additional parameters it may have.
848
899
  clone(params, backendParams) {
849
900
  if (isString(params)) {
850
901
  params = { apiPrefix: params };
@@ -871,8 +922,8 @@
871
922
  }
872
923
  get(params) {
873
924
  return __awaiter(this, void 0, void 0, function* () {
874
- if (isString(params)) {
875
- throw new Error('`get(url)` is no longer supported, please use `get({ url })` instead.');
925
+ if (params.url != null) {
926
+ deprecated.urlInGet();
876
927
  }
877
928
  const result = yield this.request(Object.assign(Object.assign({}, params), { method: 'GET' }));
878
929
  return this._transformGetResult(params, result);
@@ -886,6 +937,7 @@
886
937
  throw new Error("Invalid response received, the 'd' property is missing.");
887
938
  }
888
939
  if (params.id != null) {
940
+ // singular
889
941
  if (data.d.length > 1) {
890
942
  throw new Error('Returned multiple results when only one was expected.');
891
943
  }
@@ -905,32 +957,35 @@
905
957
  return new Poll(requestFn, pollInterval);
906
958
  }
907
959
  put(params) {
908
- if (isString(params)) {
909
- throw new Error('`put(url)` is no longer supported, please use `put({ url })` instead.');
960
+ if (params.url != null) {
961
+ deprecated.urlInPut();
910
962
  }
911
963
  return this.request(Object.assign(Object.assign({}, params), { method: 'PUT' }));
912
964
  }
913
965
  patch(params) {
914
- if (isString(params)) {
915
- throw new Error('`patch(url)` is no longer supported, please use `patch({ url })` instead.');
966
+ if (params.url != null) {
967
+ deprecated.urlInPatch();
916
968
  }
917
969
  return this.request(Object.assign(Object.assign({}, params), { method: 'PATCH' }));
918
970
  }
919
971
  post(params) {
920
- if (isString(params)) {
921
- throw new Error('`post(url)` is no longer supported, please use `post({ url })` instead.');
972
+ if (params.url != null) {
973
+ deprecated.urlInPost();
922
974
  }
923
975
  return this.request(Object.assign(Object.assign({}, params), { method: 'POST' }));
924
976
  }
925
977
  delete(params) {
926
- if (isString(params)) {
927
- throw new Error('`delete(url)` is no longer supported, please use `delete({ url })` instead.');
978
+ if (params.url != null) {
979
+ deprecated.urlInDelete();
928
980
  }
929
981
  params.method = 'DELETE';
930
982
  return this.request(Object.assign(Object.assign({}, params), { method: 'DELETE' }));
931
983
  }
932
984
  getOrCreate(params) {
933
985
  return __awaiter(this, void 0, void 0, function* () {
986
+ if ('url' in params && params.url != null) {
987
+ deprecated.urlInGetOrCreate();
988
+ }
934
989
  const { id, body } = params, restParams = __rest(params, ["id", "body"]);
935
990
  if (params.resource.endsWith('/$count')) {
936
991
  throw new Error('getOrCreate does not support $count on resources');
@@ -950,6 +1005,9 @@
950
1005
  }
951
1006
  upsert(params) {
952
1007
  return __awaiter(this, void 0, void 0, function* () {
1008
+ if ('url' in params && params.url != null) {
1009
+ deprecated.urlInUpsert();
1010
+ }
953
1011
  const { id, body } = params, restParams = __rest(params, ["id", "body"]);
954
1012
  if (!isObject(id)) {
955
1013
  throw new Error('The id property must be an object');
@@ -978,11 +1036,13 @@
978
1036
  });
979
1037
  }
980
1038
  prepare(params) {
1039
+ var _a;
981
1040
  if (isString(params)) {
982
1041
  throw new Error('`prepare(url)` is no longer supported, please use `prepare({ url })` instead.');
983
1042
  }
984
- const compiledUrl = this.compile(params);
985
- const urlQueryParamsStr = compiledUrl.indexOf('?') === -1 ? '?' : '&';
1043
+ // precompile the URL string to improve performance
1044
+ const compiledUrl = (_a = params.url) !== null && _a !== void 0 ? _a : this.compile(params);
1045
+ const urlQueryParamsStr = !compiledUrl.includes('?') ? '?' : '&';
986
1046
  if (params.method == null) {
987
1047
  params.method = 'GET';
988
1048
  }
@@ -1029,6 +1089,7 @@
1029
1089
  throw new Error('Params must be an object not a string');
1030
1090
  }
1031
1091
  if (params.url != null) {
1092
+ deprecated.urlInCompile();
1032
1093
  return params.url;
1033
1094
  }
1034
1095
  else {
@@ -1076,7 +1137,7 @@
1076
1137
  let queryOptions = [];
1077
1138
  if (options != null) {
1078
1139
  queryOptions = mapObj(options, (value, option) => {
1079
- if (option[0] === '$' && !isValidOption(option)) {
1140
+ if (option.startsWith('$') && !isValidOption(option)) {
1080
1141
  throw new Error(`Unknown odata option '${option}'`);
1081
1142
  }
1082
1143
  return buildOption(option, value);
@@ -1092,7 +1153,7 @@
1092
1153
  }
1093
1154
  }
1094
1155
  request(params, overrides) {
1095
- var _a;
1156
+ var _a, _b;
1096
1157
  if (overrides !== undefined) {
1097
1158
  throw new Error('request(params, overrides)` is unsupported, please use `request({ ...params, ...overrides })` instead.');
1098
1159
  }
@@ -1102,12 +1163,15 @@
1102
1163
  let { method, apiPrefix } = params;
1103
1164
  const { body, passthrough = {}, retry } = params;
1104
1165
  apiPrefix = apiPrefix !== null && apiPrefix !== void 0 ? apiPrefix : this.apiPrefix;
1105
- const url = apiPrefix + this.compile(params);
1166
+ const url = apiPrefix + ((_a = params.url) !== null && _a !== void 0 ? _a : this.compile(params));
1106
1167
  method = method !== null && method !== void 0 ? method : 'GET';
1107
1168
  method = method.toUpperCase();
1108
- const opts = Object.assign(Object.assign(Object.assign(Object.assign({}, this.passthrough), ((_a = this.passthroughByMethod[method]) !== null && _a !== void 0 ? _a : {})), passthrough), { url,
1169
+ // Filter to prevent accidental parameter passthrough.
1170
+ const opts = Object.assign(Object.assign(Object.assign(Object.assign({}, this.passthrough), ((_b = this.passthroughByMethod[method]) !== null && _b !== void 0 ? _b : {})), passthrough), { url,
1109
1171
  body,
1110
1172
  method });
1173
+ // Do not await this._request result, so that we can preserve
1174
+ // the potentially enhanced promise-like result.
1111
1175
  return this.callWithRetry(() => this._request(opts), retry);
1112
1176
  }
1113
1177
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinejs-client-fetch",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "This module provides the nodejs interface for the pinejs API using request.",
5
5
  "main": "build/index.js",
6
6
  "browser": "build/index.umd.js",
@@ -49,7 +49,7 @@
49
49
  "chai-as-promised": "^7.1.2",
50
50
  "fetch-ponyfill": "^7.1.0",
51
51
  "husky": "^9.0.11",
52
- "karma": "^5.2.3",
52
+ "karma": "^6.0.0",
53
53
  "lint-staged": "^15.2.5",
54
54
  "mocha": "^10.4.0",
55
55
  "rollup": "^2.79.1",
@@ -60,6 +60,6 @@
60
60
  "pinejs-client-core": "^6.14.7"
61
61
  },
62
62
  "versionist": {
63
- "publishedAt": "2024-06-06T15:44:29.125Z"
63
+ "publishedAt": "2025-03-19T16:05:12.830Z"
64
64
  }
65
65
  }