@e22m4u/js-repository 0.0.31

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 (183) hide show
  1. package/.c8rc +9 -0
  2. package/.commitlintrc +5 -0
  3. package/.editorconfig +13 -0
  4. package/.eslintignore +1 -0
  5. package/.eslintrc.cjs +27 -0
  6. package/.husky/commit-msg +4 -0
  7. package/.husky/pre-commit +9 -0
  8. package/.mocharc.cjs +7 -0
  9. package/.prettierrc +7 -0
  10. package/LICENSE +21 -0
  11. package/README.md +523 -0
  12. package/mocha.setup.js +10 -0
  13. package/package.json +57 -0
  14. package/src/adapter/adapter-loader.d.ts +16 -0
  15. package/src/adapter/adapter-loader.js +63 -0
  16. package/src/adapter/adapter-loader.spec.js +31 -0
  17. package/src/adapter/adapter-registry.d.ts +14 -0
  18. package/src/adapter/adapter-registry.js +36 -0
  19. package/src/adapter/adapter-registry.spec.js +36 -0
  20. package/src/adapter/adapter.d.ts +118 -0
  21. package/src/adapter/adapter.js +181 -0
  22. package/src/adapter/adapter.spec.js +144 -0
  23. package/src/adapter/builtin/memory-adapter.d.ts +118 -0
  24. package/src/adapter/builtin/memory-adapter.js +342 -0
  25. package/src/adapter/builtin/memory-adapter.spec.js +2925 -0
  26. package/src/adapter/decorator/data-sanitizing-decorator.d.ts +13 -0
  27. package/src/adapter/decorator/data-sanitizing-decorator.js +44 -0
  28. package/src/adapter/decorator/data-sanitizing-decorator.spec.js +59 -0
  29. package/src/adapter/decorator/data-validation-decorator.d.ts +13 -0
  30. package/src/adapter/decorator/data-validation-decorator.js +41 -0
  31. package/src/adapter/decorator/data-validation-decorator.spec.js +59 -0
  32. package/src/adapter/decorator/default-values-decorator.d.ts +13 -0
  33. package/src/adapter/decorator/default-values-decorator.js +57 -0
  34. package/src/adapter/decorator/default-values-decorator.spec.js +141 -0
  35. package/src/adapter/decorator/fields-filtering-decorator.d.ts +13 -0
  36. package/src/adapter/decorator/fields-filtering-decorator.js +72 -0
  37. package/src/adapter/decorator/fields-filtering-decorator.spec.js +119 -0
  38. package/src/adapter/decorator/inclusion-decorator.d.ts +13 -0
  39. package/src/adapter/decorator/inclusion-decorator.js +78 -0
  40. package/src/adapter/decorator/inclusion-decorator.spec.js +117 -0
  41. package/src/adapter/decorator/index.d.ts +5 -0
  42. package/src/adapter/decorator/index.js +5 -0
  43. package/src/adapter/index.d.ts +3 -0
  44. package/src/adapter/index.js +3 -0
  45. package/src/definition/datasource/datasource-definition-validator.d.ts +14 -0
  46. package/src/definition/datasource/datasource-definition-validator.js +33 -0
  47. package/src/definition/datasource/datasource-definition-validator.spec.js +63 -0
  48. package/src/definition/datasource/datasource-definition.d.ts +7 -0
  49. package/src/definition/datasource/index.d.ts +2 -0
  50. package/src/definition/datasource/index.js +1 -0
  51. package/src/definition/definition-registry.d.ts +50 -0
  52. package/src/definition/definition-registry.js +98 -0
  53. package/src/definition/definition-registry.spec.js +78 -0
  54. package/src/definition/index.d.ts +3 -0
  55. package/src/definition/index.js +3 -0
  56. package/src/definition/model/index.d.ts +7 -0
  57. package/src/definition/model/index.js +6 -0
  58. package/src/definition/model/model-data-sanitizer.d.ts +15 -0
  59. package/src/definition/model/model-data-sanitizer.js +33 -0
  60. package/src/definition/model/model-data-validator.d.ts +32 -0
  61. package/src/definition/model/model-data-validator.js +144 -0
  62. package/src/definition/model/model-data-validator.spec.js +1889 -0
  63. package/src/definition/model/model-definition-utils.d.ts +161 -0
  64. package/src/definition/model/model-definition-utils.js +371 -0
  65. package/src/definition/model/model-definition-utils.spec.js +1474 -0
  66. package/src/definition/model/model-definition-validator.d.ts +14 -0
  67. package/src/definition/model/model-definition-validator.js +83 -0
  68. package/src/definition/model/model-definition-validator.spec.js +143 -0
  69. package/src/definition/model/model-definition.d.ts +28 -0
  70. package/src/definition/model/properties/data-type.d.ts +11 -0
  71. package/src/definition/model/properties/data-type.js +11 -0
  72. package/src/definition/model/properties/default-values-definition-validator.d.ts +15 -0
  73. package/src/definition/model/properties/default-values-definition-validator.js +53 -0
  74. package/src/definition/model/properties/default-values-definition-validator.spec.js +136 -0
  75. package/src/definition/model/properties/index.d.ts +5 -0
  76. package/src/definition/model/properties/index.js +4 -0
  77. package/src/definition/model/properties/primary-keys-definition-validator.d.ts +15 -0
  78. package/src/definition/model/properties/primary-keys-definition-validator.js +55 -0
  79. package/src/definition/model/properties/primary-keys-definition-validator.spec.js +145 -0
  80. package/src/definition/model/properties/properties-definition-validator.d.ts +15 -0
  81. package/src/definition/model/properties/properties-definition-validator.js +194 -0
  82. package/src/definition/model/properties/properties-definition-validator.spec.js +373 -0
  83. package/src/definition/model/properties/property-definition.d.ts +20 -0
  84. package/src/definition/model/relations/index.d.ts +3 -0
  85. package/src/definition/model/relations/index.js +2 -0
  86. package/src/definition/model/relations/relation-definition.d.ts +254 -0
  87. package/src/definition/model/relations/relation-type.d.ts +9 -0
  88. package/src/definition/model/relations/relation-type.js +9 -0
  89. package/src/definition/model/relations/relations-definition-validator.d.ts +15 -0
  90. package/src/definition/model/relations/relations-definition-validator.js +449 -0
  91. package/src/definition/model/relations/relations-definition-validator.spec.js +772 -0
  92. package/src/errors/index.d.ts +3 -0
  93. package/src/errors/index.js +3 -0
  94. package/src/errors/invalid-argument-error.d.ts +6 -0
  95. package/src/errors/invalid-argument-error.js +6 -0
  96. package/src/errors/invalid-argument-error.spec.js +33 -0
  97. package/src/errors/invalid-operator-value-error.d.ts +13 -0
  98. package/src/errors/invalid-operator-value-error.js +24 -0
  99. package/src/errors/invalid-operator-value-error.spec.js +11 -0
  100. package/src/errors/not-implemented-error.d.ts +6 -0
  101. package/src/errors/not-implemented-error.js +6 -0
  102. package/src/errors/not-implemented-error.spec.js +33 -0
  103. package/src/filter/fields-clause-tool.d.ts +38 -0
  104. package/src/filter/fields-clause-tool.js +88 -0
  105. package/src/filter/fields-clause-tool.spec.js +133 -0
  106. package/src/filter/filter.d.ts +335 -0
  107. package/src/filter/include-clause-tool.d.ts +53 -0
  108. package/src/filter/include-clause-tool.js +364 -0
  109. package/src/filter/include-clause-tool.spec.js +653 -0
  110. package/src/filter/index.d.ts +7 -0
  111. package/src/filter/index.js +6 -0
  112. package/src/filter/operator-clause-tool.d.ts +223 -0
  113. package/src/filter/operator-clause-tool.js +515 -0
  114. package/src/filter/operator-clause-tool.spec.js +1064 -0
  115. package/src/filter/order-clause-tool.d.ts +32 -0
  116. package/src/filter/order-clause-tool.js +97 -0
  117. package/src/filter/order-clause-tool.spec.js +438 -0
  118. package/src/filter/slice-clause-tool.d.ts +30 -0
  119. package/src/filter/slice-clause-tool.js +65 -0
  120. package/src/filter/slice-clause-tool.spec.js +117 -0
  121. package/src/filter/where-clause-tool.d.ts +23 -0
  122. package/src/filter/where-clause-tool.js +165 -0
  123. package/src/filter/where-clause-tool.spec.js +280 -0
  124. package/src/index.d.ts +9 -0
  125. package/src/index.js +8 -0
  126. package/src/relations/belongs-to-resolver.d.ts +46 -0
  127. package/src/relations/belongs-to-resolver.js +242 -0
  128. package/src/relations/belongs-to-resolver.spec.js +1047 -0
  129. package/src/relations/has-many-resolver.d.ts +67 -0
  130. package/src/relations/has-many-resolver.js +317 -0
  131. package/src/relations/has-many-resolver.spec.js +2911 -0
  132. package/src/relations/has-one-resolver.d.ts +67 -0
  133. package/src/relations/has-one-resolver.js +311 -0
  134. package/src/relations/has-one-resolver.spec.js +2274 -0
  135. package/src/relations/index.d.ts +4 -0
  136. package/src/relations/index.js +4 -0
  137. package/src/relations/references-many-resolver.d.ts +27 -0
  138. package/src/relations/references-many-resolver.js +113 -0
  139. package/src/relations/references-many-resolver.spec.js +631 -0
  140. package/src/repository/index.d.ts +2 -0
  141. package/src/repository/index.js +2 -0
  142. package/src/repository/repository-registry.d.ts +29 -0
  143. package/src/repository/repository-registry.js +57 -0
  144. package/src/repository/repository-registry.spec.js +38 -0
  145. package/src/repository/repository.d.ts +164 -0
  146. package/src/repository/repository.js +207 -0
  147. package/src/repository/repository.spec.js +202 -0
  148. package/src/schema.d.ts +37 -0
  149. package/src/schema.js +41 -0
  150. package/src/types.d.ts +30 -0
  151. package/src/utils/capitalize.d.ts +6 -0
  152. package/src/utils/capitalize.js +10 -0
  153. package/src/utils/capitalize.spec.js +14 -0
  154. package/src/utils/clone-deep.d.ts +6 -0
  155. package/src/utils/clone-deep.js +61 -0
  156. package/src/utils/clone-deep.spec.js +28 -0
  157. package/src/utils/exclude-object-keys.d.ts +10 -0
  158. package/src/utils/exclude-object-keys.js +20 -0
  159. package/src/utils/exclude-object-keys.spec.js +49 -0
  160. package/src/utils/get-ctor-name.d.ts +6 -0
  161. package/src/utils/get-ctor-name.js +11 -0
  162. package/src/utils/get-ctor-name.spec.js +17 -0
  163. package/src/utils/get-value-by-path.d.ts +12 -0
  164. package/src/utils/get-value-by-path.js +23 -0
  165. package/src/utils/get-value-by-path.spec.js +36 -0
  166. package/src/utils/index.d.ts +10 -0
  167. package/src/utils/index.js +10 -0
  168. package/src/utils/is-ctor.d.ts +7 -0
  169. package/src/utils/is-ctor.js +10 -0
  170. package/src/utils/is-ctor.spec.js +26 -0
  171. package/src/utils/is-pure-object.d.ts +6 -0
  172. package/src/utils/is-pure-object.js +15 -0
  173. package/src/utils/is-pure-object.spec.js +25 -0
  174. package/src/utils/select-object-keys.d.ts +10 -0
  175. package/src/utils/select-object-keys.js +37 -0
  176. package/src/utils/select-object-keys.spec.js +40 -0
  177. package/src/utils/singularize.d.ts +6 -0
  178. package/src/utils/singularize.js +22 -0
  179. package/src/utils/singularize.spec.js +23 -0
  180. package/src/utils/string-to-regexp.d.ts +10 -0
  181. package/src/utils/string-to-regexp.js +22 -0
  182. package/src/utils/string-to-regexp.spec.js +35 -0
  183. package/tsconfig.json +9 -0
@@ -0,0 +1,32 @@
1
+ import {ModelData} from '../types.js';
2
+ import {OrderClause} from './filter.js';
3
+ import {Service} from '@e22m4u/js-service';
4
+
5
+ /**
6
+ * Order clause tool.
7
+ */
8
+ export declare class OrderClauseTool extends Service {
9
+ /**
10
+ * Sort.
11
+ *
12
+ * @param entities
13
+ * @param clause
14
+ */
15
+ sort(entities: ModelData[], clause: OrderClause | undefined): void;
16
+
17
+ /**
18
+ * Validate order clause.
19
+ *
20
+ * @param clause
21
+ */
22
+ static validateOrderClause(clause: OrderClause | undefined): void;
23
+
24
+ /**
25
+ * Normalize order clause.
26
+ *
27
+ * @param clause
28
+ */
29
+ static normalizeOrderClause(
30
+ clause: OrderClause | undefined,
31
+ ): string[] | undefined;
32
+ }
@@ -0,0 +1,97 @@
1
+ import {Service} from '@e22m4u/js-service';
2
+ import {getValueByPath} from '../utils/index.js';
3
+ import {InvalidArgumentError} from '../errors/index.js';
4
+
5
+ /**
6
+ * Order clause tool.
7
+ */
8
+ export class OrderClauseTool extends Service {
9
+ /**
10
+ * Sort.
11
+ *
12
+ * @param {object[]} entities
13
+ * @param {string|string[]|undefined} clause
14
+ */
15
+ sort(entities, clause) {
16
+ if (!clause) return;
17
+ if (!Array.isArray(clause)) clause = [clause];
18
+ const mapping = [];
19
+ clause.forEach((key, index) => {
20
+ if (typeof key !== 'string')
21
+ throw new InvalidArgumentError(
22
+ 'The provided option "order" should be a String ' +
23
+ 'or an Array of String, but %v given.',
24
+ key,
25
+ );
26
+ let reverse = 1;
27
+ const matches = key.match(/\s+(A|DE)SC$/i);
28
+ if (matches) {
29
+ key = key.replace(/\s+(A|DE)SC/i, '');
30
+ if (matches[1].toLowerCase() === 'de') reverse = -1;
31
+ }
32
+ mapping[index] = {key: key, reverse};
33
+ });
34
+ entities.sort(compareFn.bind(mapping));
35
+ }
36
+
37
+ /**
38
+ * Validate order clause.
39
+ *
40
+ * @param {string|string[]|undefined} clause
41
+ */
42
+ static validateOrderClause(clause) {
43
+ if (!clause) return;
44
+ const tempClause = Array.isArray(clause) ? clause : [clause];
45
+ tempClause.forEach(key => {
46
+ if (!key || typeof key !== 'string')
47
+ throw new InvalidArgumentError(
48
+ 'The provided option "order" should be a non-empty String ' +
49
+ 'or an Array of String, but %v given.',
50
+ key,
51
+ );
52
+ });
53
+ }
54
+
55
+ /**
56
+ * Normalize order clause.
57
+ *
58
+ * @param {string|string[]|undefined} clause
59
+ * @returns {string[]|undefined}
60
+ */
61
+ static normalizeOrderClause(clause) {
62
+ if (!clause) return;
63
+ clause = Array.isArray(clause) ? clause : [clause];
64
+ clause.forEach(key => {
65
+ if (!key || typeof key !== 'string')
66
+ throw new InvalidArgumentError(
67
+ 'The provided option "order" should be a non-empty String ' +
68
+ 'or an Array of String, but %v given.',
69
+ key,
70
+ );
71
+ });
72
+ return clause;
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Compare fn.
78
+ *
79
+ * @param {*} a
80
+ * @param {*} b
81
+ * @returns {number}
82
+ */
83
+ function compareFn(a, b) {
84
+ let undefinedA, undefinedB;
85
+ for (let i = 0, l = this.length; i < l; i++) {
86
+ const aVal = getValueByPath(a, this[i].key);
87
+ const bVal = getValueByPath(b, this[i].key);
88
+ undefinedB = bVal === undefined && aVal !== undefined;
89
+ undefinedA = aVal === undefined && bVal !== undefined;
90
+ if (undefinedB || aVal > bVal) {
91
+ return this[i].reverse;
92
+ } else if (undefinedA || aVal < bVal) {
93
+ return -1 * this[i].reverse;
94
+ }
95
+ }
96
+ return 0;
97
+ }
@@ -0,0 +1,438 @@
1
+ import {expect} from 'chai';
2
+ import {format} from '@e22m4u/js-format';
3
+ import {OrderClauseTool} from './order-clause-tool.js';
4
+
5
+ const S = new OrderClauseTool();
6
+
7
+ describe('OrderClauseTool', function () {
8
+ describe('sort', function () {
9
+ describe('with number values', function () {
10
+ it('orders by a single field in ascending by default', function () {
11
+ const objects = [{foo: 2}, {foo: 3}, {foo: 1}, {foo: 4}];
12
+ S.sort(objects, 'foo');
13
+ expect(objects).to.have.length(4);
14
+ expect(objects[0].foo).to.be.eq(1);
15
+ expect(objects[1].foo).to.be.eq(2);
16
+ expect(objects[2].foo).to.be.eq(3);
17
+ expect(objects[3].foo).to.be.eq(4);
18
+ });
19
+
20
+ it('orders by a single field in descending', function () {
21
+ const objects = [{foo: 2}, {foo: 3}, {foo: 1}, {foo: 4}];
22
+ S.sort(objects, 'foo DESC');
23
+ expect(objects).to.have.length(4);
24
+ expect(objects[0].foo).to.be.eq(4);
25
+ expect(objects[1].foo).to.be.eq(3);
26
+ expect(objects[2].foo).to.be.eq(2);
27
+ expect(objects[3].foo).to.be.eq(1);
28
+ });
29
+
30
+ it('orders by a single field in ascending', function () {
31
+ const objects = [{foo: 2}, {foo: 3}, {foo: 1}, {foo: 4}];
32
+ S.sort(objects, 'foo ASC');
33
+ expect(objects).to.have.length(4);
34
+ expect(objects[0].foo).to.be.eq(1);
35
+ expect(objects[1].foo).to.be.eq(2);
36
+ expect(objects[2].foo).to.be.eq(3);
37
+ expect(objects[3].foo).to.be.eq(4);
38
+ });
39
+
40
+ it('orders by multiple fields in ascending by default', function () {
41
+ const objects = [
42
+ {foo: 2, bar: 2},
43
+ {foo: 2, bar: 3},
44
+ {foo: 2, bar: 1},
45
+ {foo: 1, bar: 4},
46
+ ];
47
+ S.sort(objects, ['foo', 'bar']);
48
+ expect(objects).to.have.length(4);
49
+ expect(objects[0].bar).to.be.eq(4);
50
+ expect(objects[1].bar).to.be.eq(1);
51
+ expect(objects[2].bar).to.be.eq(2);
52
+ expect(objects[3].bar).to.be.eq(3);
53
+ });
54
+
55
+ it('orders by multiple fields in descending', function () {
56
+ const objects = [
57
+ {foo: 2, bar: 2},
58
+ {foo: 2, bar: 3},
59
+ {foo: 2, bar: 1},
60
+ {foo: 1, bar: 4},
61
+ ];
62
+ S.sort(objects, ['foo DESC', 'bar DESC']);
63
+ expect(objects).to.have.length(4);
64
+ expect(objects[0].bar).to.be.eq(3);
65
+ expect(objects[1].bar).to.be.eq(2);
66
+ expect(objects[2].bar).to.be.eq(1);
67
+ expect(objects[3].bar).to.be.eq(4);
68
+ });
69
+
70
+ it('orders by multiple fields in ascending', function () {
71
+ const objects = [
72
+ {foo: 2, bar: 2},
73
+ {foo: 2, bar: 3},
74
+ {foo: 2, bar: 1},
75
+ {foo: 1, bar: 4},
76
+ ];
77
+ S.sort(objects, ['foo ASC', 'bar ASC']);
78
+ expect(objects).to.have.length(4);
79
+ expect(objects[0].bar).to.be.eq(4);
80
+ expect(objects[1].bar).to.be.eq(1);
81
+ expect(objects[2].bar).to.be.eq(2);
82
+ expect(objects[3].bar).to.be.eq(3);
83
+ });
84
+
85
+ it('orders by nested fields in ascending by default', function () {
86
+ const objects = [
87
+ {foo: {bar: 3}},
88
+ {foo: {bar: 4}},
89
+ {foo: {bar: 2}},
90
+ {foo: {bar: 1}},
91
+ ];
92
+ S.sort(objects, 'foo.bar');
93
+ expect(objects).to.have.length(4);
94
+ expect(objects[0].foo.bar).to.be.eq(1);
95
+ expect(objects[1].foo.bar).to.be.eq(2);
96
+ expect(objects[2].foo.bar).to.be.eq(3);
97
+ expect(objects[3].foo.bar).to.be.eq(4);
98
+ });
99
+
100
+ it('orders by nested fields in descending', function () {
101
+ const objects = [
102
+ {foo: {bar: 3}},
103
+ {foo: {bar: 4}},
104
+ {foo: {bar: 2}},
105
+ {foo: {bar: 1}},
106
+ ];
107
+ S.sort(objects, 'foo.bar DESC');
108
+ expect(objects).to.have.length(4);
109
+ expect(objects[0].foo.bar).to.be.eq(4);
110
+ expect(objects[1].foo.bar).to.be.eq(3);
111
+ expect(objects[2].foo.bar).to.be.eq(2);
112
+ expect(objects[3].foo.bar).to.be.eq(1);
113
+ });
114
+
115
+ it('orders by nested fields in ascending', function () {
116
+ const objects = [
117
+ {foo: {bar: 3}},
118
+ {foo: {bar: 4}},
119
+ {foo: {bar: 2}},
120
+ {foo: {bar: 1}},
121
+ ];
122
+ S.sort(objects, 'foo.bar ASC');
123
+ expect(objects).to.have.length(4);
124
+ expect(objects[0].foo.bar).to.be.eq(1);
125
+ expect(objects[1].foo.bar).to.be.eq(2);
126
+ expect(objects[2].foo.bar).to.be.eq(3);
127
+ expect(objects[3].foo.bar).to.be.eq(4);
128
+ });
129
+
130
+ it('orders by multiple fields with nested one', function () {
131
+ const objects = [
132
+ {foo: {bar: 2}, baz: 2},
133
+ {foo: {bar: 2}, baz: 3},
134
+ {foo: {bar: 2}, baz: 4},
135
+ {foo: {bar: 1}, baz: 1},
136
+ ];
137
+ S.sort(objects, ['foo.bar ASC', 'baz DESC']);
138
+ expect(objects).to.have.length(4);
139
+ expect(objects[0].baz).to.be.eq(1);
140
+ expect(objects[1].baz).to.be.eq(4);
141
+ expect(objects[2].baz).to.be.eq(3);
142
+ expect(objects[3].baz).to.be.eq(2);
143
+ });
144
+ });
145
+
146
+ describe('with string values', function () {
147
+ it('orders by a single field in ascending by default', function () {
148
+ const objects = [{foo: 'b'}, {foo: 'c'}, {foo: 'a'}, {foo: 'd'}];
149
+ S.sort(objects, 'foo');
150
+ expect(objects).to.have.length(4);
151
+ expect(objects[0].foo).to.be.eq('a');
152
+ expect(objects[1].foo).to.be.eq('b');
153
+ expect(objects[2].foo).to.be.eq('c');
154
+ expect(objects[3].foo).to.be.eq('d');
155
+ });
156
+
157
+ it('orders by a single field in descending', function () {
158
+ const objects = [{foo: 'b'}, {foo: 'c'}, {foo: 'a'}, {foo: 'd'}];
159
+ S.sort(objects, 'foo DESC');
160
+ expect(objects).to.have.length(4);
161
+ expect(objects[0].foo).to.be.eq('d');
162
+ expect(objects[1].foo).to.be.eq('c');
163
+ expect(objects[2].foo).to.be.eq('b');
164
+ expect(objects[3].foo).to.be.eq('a');
165
+ });
166
+
167
+ it('orders by a single field in ascending', function () {
168
+ const objects = [{foo: 'b'}, {foo: 'c'}, {foo: 'a'}, {foo: 'd'}];
169
+ S.sort(objects, 'foo ASC');
170
+ expect(objects).to.have.length(4);
171
+ expect(objects[0].foo).to.be.eq('a');
172
+ expect(objects[1].foo).to.be.eq('b');
173
+ expect(objects[2].foo).to.be.eq('c');
174
+ expect(objects[3].foo).to.be.eq('d');
175
+ });
176
+
177
+ it('orders by multiple fields in ascending by default', function () {
178
+ const objects = [
179
+ {foo: 'b', bar: 'b'},
180
+ {foo: 'b', bar: 'c'},
181
+ {foo: 'b', bar: 'a'},
182
+ {foo: 'a', bar: 'd'},
183
+ ];
184
+ S.sort(objects, ['foo', 'bar']);
185
+ expect(objects).to.have.length(4);
186
+ expect(objects[0].bar).to.be.eq('d');
187
+ expect(objects[1].bar).to.be.eq('a');
188
+ expect(objects[2].bar).to.be.eq('b');
189
+ expect(objects[3].bar).to.be.eq('c');
190
+ });
191
+
192
+ it('orders by multiple fields in descending', function () {
193
+ const objects = [
194
+ {foo: 'b', bar: 'b'},
195
+ {foo: 'b', bar: 'c'},
196
+ {foo: 'b', bar: 'a'},
197
+ {foo: 'a', bar: 'd'},
198
+ ];
199
+ S.sort(objects, ['foo DESC', 'bar DESC']);
200
+ expect(objects).to.have.length(4);
201
+ expect(objects[0].bar).to.be.eq('c');
202
+ expect(objects[1].bar).to.be.eq('b');
203
+ expect(objects[2].bar).to.be.eq('a');
204
+ expect(objects[3].bar).to.be.eq('d');
205
+ });
206
+
207
+ it('orders by multiple fields in ascending', function () {
208
+ const objects = [
209
+ {foo: 'b', bar: 'b'},
210
+ {foo: 'b', bar: 'c'},
211
+ {foo: 'b', bar: 'a'},
212
+ {foo: 'a', bar: 'd'},
213
+ ];
214
+ S.sort(objects, ['foo ASC', 'bar ASC']);
215
+ expect(objects).to.have.length(4);
216
+ expect(objects[0].bar).to.be.eq('d');
217
+ expect(objects[1].bar).to.be.eq('a');
218
+ expect(objects[2].bar).to.be.eq('b');
219
+ expect(objects[3].bar).to.be.eq('c');
220
+ });
221
+
222
+ it('orders by nested fields in ascending by default', function () {
223
+ const objects = [
224
+ {foo: {bar: 'c'}},
225
+ {foo: {bar: 'd'}},
226
+ {foo: {bar: 'b'}},
227
+ {foo: {bar: 'a'}},
228
+ ];
229
+ S.sort(objects, 'foo.bar');
230
+ expect(objects).to.have.length(4);
231
+ expect(objects[0].foo.bar).to.be.eq('a');
232
+ expect(objects[1].foo.bar).to.be.eq('b');
233
+ expect(objects[2].foo.bar).to.be.eq('c');
234
+ expect(objects[3].foo.bar).to.be.eq('d');
235
+ });
236
+
237
+ it('orders by nested fields in descending', function () {
238
+ const objects = [
239
+ {foo: {bar: 'c'}},
240
+ {foo: {bar: 'd'}},
241
+ {foo: {bar: 'b'}},
242
+ {foo: {bar: 'a'}},
243
+ ];
244
+ S.sort(objects, 'foo.bar DESC');
245
+ expect(objects).to.have.length(4);
246
+ expect(objects[0].foo.bar).to.be.eq('d');
247
+ expect(objects[1].foo.bar).to.be.eq('c');
248
+ expect(objects[2].foo.bar).to.be.eq('b');
249
+ expect(objects[3].foo.bar).to.be.eq('a');
250
+ });
251
+
252
+ it('orders by nested fields in ascending', function () {
253
+ const objects = [
254
+ {foo: {bar: 'c'}},
255
+ {foo: {bar: 'd'}},
256
+ {foo: {bar: 'b'}},
257
+ {foo: {bar: 'a'}},
258
+ ];
259
+ S.sort(objects, 'foo.bar ASC');
260
+ expect(objects).to.have.length(4);
261
+ expect(objects[0].foo.bar).to.be.eq('a');
262
+ expect(objects[1].foo.bar).to.be.eq('b');
263
+ expect(objects[2].foo.bar).to.be.eq('c');
264
+ expect(objects[3].foo.bar).to.be.eq('d');
265
+ });
266
+
267
+ it('orders by multiple fields with nested one', function () {
268
+ const objects = [
269
+ {foo: {bar: 'b'}, baz: 'b'},
270
+ {foo: {bar: 'b'}, baz: 'c'},
271
+ {foo: {bar: 'b'}, baz: 'd'},
272
+ {foo: {bar: 'a'}, baz: 'a'},
273
+ ];
274
+ S.sort(objects, ['foo.bar ASC', 'baz DESC']);
275
+ expect(objects).to.have.length(4);
276
+ expect(objects[0].baz).to.be.eq('a');
277
+ expect(objects[1].baz).to.be.eq('d');
278
+ expect(objects[2].baz).to.be.eq('c');
279
+ expect(objects[3].baz).to.be.eq('b');
280
+ });
281
+ });
282
+
283
+ describe('with number and string values', function () {
284
+ it('orders by number and string values in ascending by default', function () {
285
+ const objects = [
286
+ {foo: 2, bar: 'd'},
287
+ {foo: 2, bar: 'b'},
288
+ {foo: 2, bar: 'a'},
289
+ {foo: 1, bar: 'c'},
290
+ ];
291
+ S.sort(objects, ['foo', 'bar']);
292
+ expect(objects).to.have.length(4);
293
+ expect(objects[0].bar).to.be.eq('c');
294
+ expect(objects[1].bar).to.be.eq('a');
295
+ expect(objects[2].bar).to.be.eq('b');
296
+ expect(objects[3].bar).to.be.eq('d');
297
+ });
298
+
299
+ it('orders by number and string values in descending', function () {
300
+ const objects = [
301
+ {foo: 2, bar: 'd'},
302
+ {foo: 2, bar: 'b'},
303
+ {foo: 2, bar: 'a'},
304
+ {foo: 1, bar: 'c'},
305
+ ];
306
+ S.sort(objects, ['foo DESC', 'bar DESC']);
307
+ expect(objects).to.have.length(4);
308
+ expect(objects[0].bar).to.be.eq('d');
309
+ expect(objects[1].bar).to.be.eq('b');
310
+ expect(objects[2].bar).to.be.eq('a');
311
+ expect(objects[3].bar).to.be.eq('c');
312
+ });
313
+
314
+ it('orders by number and string values in ascending', function () {
315
+ const objects = [
316
+ {foo: 2, bar: 'd'},
317
+ {foo: 2, bar: 'b'},
318
+ {foo: 2, bar: 'a'},
319
+ {foo: 1, bar: 'c'},
320
+ ];
321
+ S.sort(objects, ['foo ASC', 'bar ASC']);
322
+ expect(objects).to.have.length(4);
323
+ expect(objects[0].bar).to.be.eq('c');
324
+ expect(objects[1].bar).to.be.eq('a');
325
+ expect(objects[2].bar).to.be.eq('b');
326
+ expect(objects[3].bar).to.be.eq('d');
327
+ });
328
+
329
+ it('orders by number and string values in mixed directions', function () {
330
+ const objects = [
331
+ {foo: 2, bar: 'd'},
332
+ {foo: 2, bar: 'b'},
333
+ {foo: 2, bar: 'a'},
334
+ {foo: 1, bar: 'c'},
335
+ ];
336
+ S.sort(objects, ['foo DESC', 'bar ASC']);
337
+ expect(objects).to.have.length(4);
338
+ expect(objects[0].bar).to.be.eq('a');
339
+ expect(objects[1].bar).to.be.eq('b');
340
+ expect(objects[2].bar).to.be.eq('d');
341
+ expect(objects[3].bar).to.be.eq('c');
342
+ });
343
+ });
344
+
345
+ it('does not throw an error if a field does not exist', function () {
346
+ const objects = [{foo: 1}, {foo: 2}, {foo: 3}, {foo: 4}];
347
+ S.sort(objects, 'bar');
348
+ expect(objects).to.have.length(4);
349
+ expect(objects[0].foo).to.be.eq(1);
350
+ expect(objects[1].foo).to.be.eq(2);
351
+ expect(objects[2].foo).to.be.eq(3);
352
+ expect(objects[3].foo).to.be.eq(4);
353
+ });
354
+
355
+ it('does not throw an error if a nested field does not exist', function () {
356
+ const objects = [
357
+ {foo: 1},
358
+ {foo: 2, bar: undefined},
359
+ {foo: 3, bar: {baz: undefined}},
360
+ {foo: 4, bar: {baz: 1}},
361
+ ];
362
+ S.sort(objects, 'bar.baz');
363
+ expect(objects).to.have.length(4);
364
+ expect(objects[0].foo).to.be.eq(1);
365
+ expect(objects[1].foo).to.be.eq(2);
366
+ expect(objects[2].foo).to.be.eq(3);
367
+ expect(objects[3].foo).to.be.eq(4);
368
+ });
369
+
370
+ it('throws an error if a given property is not a string', function () {
371
+ const throwable = () => S.sort([], 10);
372
+ expect(throwable).to.throw(
373
+ 'The provided option "order" should be a String ' +
374
+ 'or an Array of String, but 10 given.',
375
+ );
376
+ });
377
+ });
378
+
379
+ describe('validateOrderClause', function () {
380
+ it('requires a non-empty string or an array of non-empty strings', function () {
381
+ const validate = v => () => OrderClauseTool.validateOrderClause(v);
382
+ const error = v =>
383
+ format(
384
+ 'The provided option "order" should be a non-empty String ' +
385
+ 'or an Array of String, but %s given.',
386
+ v,
387
+ );
388
+ expect(validate(10)).to.throw(error('10'));
389
+ expect(validate(true)).to.throw(error('true'));
390
+ expect(validate({})).to.throw(error('Object'));
391
+ expect(validate([''])).to.throw(error('""'));
392
+ expect(validate([10])).to.throw(error('10'));
393
+ expect(validate([true])).to.throw(error('true'));
394
+ expect(validate([false])).to.throw(error('false'));
395
+ expect(validate([undefined])).to.throw(error('undefined'));
396
+ expect(validate([null])).to.throw(error('null'));
397
+ validate('')();
398
+ validate(false)();
399
+ validate(undefined)();
400
+ validate(null)();
401
+ validate('foo')();
402
+ validate(['foo'])();
403
+ });
404
+ });
405
+
406
+ describe('normalizeOrderClause', function () {
407
+ it('returns an array of strings', function () {
408
+ const fn = OrderClauseTool.normalizeOrderClause;
409
+ expect(fn('foo')).to.be.eql(['foo']);
410
+ expect(fn(['foo'])).to.be.eql(['foo']);
411
+ });
412
+
413
+ it('requires a non-empty string or an array of non-empty strings', function () {
414
+ const fn = clause => () => OrderClauseTool.normalizeOrderClause(clause);
415
+ const error = v =>
416
+ format(
417
+ 'The provided option "order" should be a non-empty String ' +
418
+ 'or an Array of String, but %s given.',
419
+ v,
420
+ );
421
+ expect(fn(10)).to.throw(error('10'));
422
+ expect(fn(true)).to.throw(error('true'));
423
+ expect(fn({})).to.throw(error('Object'));
424
+ expect(fn([''])).to.throw(error('""'));
425
+ expect(fn([10])).to.throw(error('10'));
426
+ expect(fn([true])).to.throw(error('true'));
427
+ expect(fn([false])).to.throw(error('false'));
428
+ expect(fn([undefined])).to.throw(error('undefined'));
429
+ expect(fn([null])).to.throw(error('null'));
430
+ expect(fn('')()).to.be.undefined;
431
+ expect(fn(false)()).to.be.undefined;
432
+ expect(fn(undefined)()).to.be.undefined;
433
+ expect(fn(null)()).to.be.undefined;
434
+ expect(fn('foo')()).to.be.eql(['foo']);
435
+ expect(fn(['foo'])()).to.be.eql(['foo']);
436
+ });
437
+ });
438
+ });
@@ -0,0 +1,30 @@
1
+ import {ModelData} from '../types.js';
2
+ import {Service} from '@e22m4u/js-service';
3
+
4
+ /**
5
+ * Slice clause tool.
6
+ */
7
+ export declare class SliceClauseTool extends Service {
8
+ /**
9
+ * Slice.
10
+ *
11
+ * @param entities
12
+ * @param skip
13
+ * @param limit
14
+ */
15
+ slice(entities: ModelData[], skip?: number, limit?: number): ModelData[];
16
+
17
+ /**
18
+ * Validate skip clause.
19
+ *
20
+ * @param skip
21
+ */
22
+ static validateSkipClause(skip: number | undefined): void;
23
+
24
+ /**
25
+ * Validate limit clause.
26
+ *
27
+ * @param limit
28
+ */
29
+ static validateLimitClause(limit: number | undefined): void;
30
+ }
@@ -0,0 +1,65 @@
1
+ import {Service} from '@e22m4u/js-service';
2
+ import {InvalidArgumentError} from '../errors/index.js';
3
+
4
+ /**
5
+ * Slice clause tool.
6
+ */
7
+ export class SliceClauseTool extends Service {
8
+ /**
9
+ * Filter.
10
+ *
11
+ * @param {object[]} entities
12
+ * @param {number|undefined} skip
13
+ * @param {number|undefined} limit
14
+ * @returns {object[]}
15
+ */
16
+ slice(entities, skip = undefined, limit = undefined) {
17
+ if (!Array.isArray(entities))
18
+ throw new InvalidArgumentError(
19
+ 'A first argument of SliceClauseTool.slice ' +
20
+ 'should be an Array, but %v given.',
21
+ entities,
22
+ );
23
+ if (skip && typeof skip !== 'number')
24
+ throw new InvalidArgumentError(
25
+ 'The provided option "skip" should be a Number, but %v given.',
26
+ skip,
27
+ );
28
+ if (limit && typeof limit !== 'number')
29
+ throw new InvalidArgumentError(
30
+ 'The provided option "limit" should be a Number, but %v given.',
31
+ limit,
32
+ );
33
+ skip = skip || 0;
34
+ limit = limit || entities.length;
35
+ return entities.slice(skip, skip + limit);
36
+ }
37
+
38
+ /**
39
+ * Validate skip clause.
40
+ *
41
+ * @param {number|undefined} skip
42
+ */
43
+ static validateSkipClause(skip) {
44
+ if (!skip) return;
45
+ if (typeof skip !== 'number')
46
+ throw new InvalidArgumentError(
47
+ 'The provided option "skip" should be a Number, but %v given.',
48
+ skip,
49
+ );
50
+ }
51
+
52
+ /**
53
+ * Validate limit clause.
54
+ *
55
+ * @param {number|undefined} limit
56
+ */
57
+ static validateLimitClause(limit) {
58
+ if (!limit) return;
59
+ if (typeof limit !== 'number')
60
+ throw new InvalidArgumentError(
61
+ 'The provided option "limit" should be a Number, but %v given.',
62
+ limit,
63
+ );
64
+ }
65
+ }