json-rest-api 1.0.14 → 1.0.15

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 (118) hide show
  1. package/connectors_ideas/fastify.js +66 -68
  2. package/connectors_ideas/koa-plugin.js +50 -51
  3. package/eslint.config.js +11 -2
  4. package/examples/url-override-example.js +63 -63
  5. package/index.js +13 -13
  6. package/lib/enhanced-logger.js +51 -51
  7. package/lib/error-formatter.js +61 -61
  8. package/lib/rest-api-errors.js +28 -28
  9. package/package.json +1 -1
  10. package/plugins/core/bulk-operations-plugin.js +142 -145
  11. package/plugins/core/connectors/express-plugin.js +210 -217
  12. package/plugins/core/connectors/lib/busboy-detector.js +84 -84
  13. package/plugins/core/connectors/lib/formidable-detector.js +50 -50
  14. package/plugins/core/connectors/lib/request-helpers.js +74 -74
  15. package/plugins/core/file-handling-plugin.js +125 -125
  16. package/plugins/core/lib/anyapi/anyapi-registry.js +254 -254
  17. package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +287 -287
  18. package/plugins/core/lib/anyapi/schema-utils.js +101 -101
  19. package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +41 -41
  20. package/plugins/core/lib/dbTablesOperations.js +134 -135
  21. package/plugins/core/lib/fractional-positioning.js +74 -76
  22. package/plugins/core/lib/querying/knex-cross-table-search.js +320 -320
  23. package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +144 -144
  24. package/plugins/core/lib/querying/knex-pagination-helpers.js +164 -164
  25. package/plugins/core/lib/querying/knex-process-includes.js +50 -50
  26. package/plugins/core/lib/querying/knex-query-helpers-base.js +31 -31
  27. package/plugins/core/lib/querying/knex-query-helpers.js +451 -452
  28. package/plugins/core/lib/querying/knex-relationship-includes.js +1160 -1170
  29. package/plugins/core/lib/querying/knex-window-queries.js +89 -89
  30. package/plugins/core/lib/querying/url-helpers.js +35 -35
  31. package/plugins/core/lib/querying-writing/compile-schemas.js +97 -98
  32. package/plugins/core/lib/querying-writing/connectors-query-parser.js +31 -31
  33. package/plugins/core/lib/querying-writing/database-capabilities.js +94 -94
  34. package/plugins/core/lib/querying-writing/database-value-normalizers.js +76 -76
  35. package/plugins/core/lib/querying-writing/default-data-helpers.js +27 -27
  36. package/plugins/core/lib/querying-writing/field-utils.js +44 -42
  37. package/plugins/core/lib/querying-writing/knex-constants.js +15 -16
  38. package/plugins/core/lib/querying-writing/knex-field-helpers.js +117 -115
  39. package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +41 -41
  40. package/plugins/core/lib/querying-writing/payload-validators.js +275 -275
  41. package/plugins/core/lib/querying-writing/schema-helpers.js +129 -129
  42. package/plugins/core/lib/querying-writing/scope-validations.js +143 -146
  43. package/plugins/core/lib/querying-writing/simplified-helpers.js +133 -129
  44. package/plugins/core/lib/storage/storage-adapter.js +68 -68
  45. package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +31 -31
  46. package/plugins/core/lib/writing/many-to-many-manipulations.js +81 -81
  47. package/plugins/core/lib/writing/relationship-processor.js +58 -58
  48. package/plugins/core/rest-api-access.js +8 -8
  49. package/plugins/core/rest-api-anyapi-knex-plugin.js +920 -920
  50. package/plugins/core/rest-api-cors-plugin.js +170 -171
  51. package/plugins/core/rest-api-knex-plugin.js +482 -493
  52. package/plugins/core/rest-api-multihome-plugin.js +247 -251
  53. package/plugins/core/rest-api-plugin-hooks/compile-resource-schemas.js +5 -5
  54. package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +20 -21
  55. package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +44 -44
  56. package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +23 -23
  57. package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +15 -15
  58. package/plugins/core/rest-api-plugin-methods/add-route.js +9 -9
  59. package/plugins/core/rest-api-plugin-methods/check-permissions.js +5 -6
  60. package/plugins/core/rest-api-plugin-methods/common.js +295 -297
  61. package/plugins/core/rest-api-plugin-methods/delete-relationship.js +35 -37
  62. package/plugins/core/rest-api-plugin-methods/delete.js +57 -59
  63. package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +172 -171
  64. package/plugins/core/rest-api-plugin-methods/get-related.js +96 -96
  65. package/plugins/core/rest-api-plugin-methods/get-relationship.js +16 -18
  66. package/plugins/core/rest-api-plugin-methods/get.js +86 -86
  67. package/plugins/core/rest-api-plugin-methods/patch-relationship.js +16 -17
  68. package/plugins/core/rest-api-plugin-methods/patch.js +63 -64
  69. package/plugins/core/rest-api-plugin-methods/post-relationship.js +54 -55
  70. package/plugins/core/rest-api-plugin-methods/post.js +61 -62
  71. package/plugins/core/rest-api-plugin-methods/put.js +86 -87
  72. package/plugins/core/rest-api-plugin-methods/query.js +88 -88
  73. package/plugins/core/rest-api-plugin-methods/release.js +5 -6
  74. package/plugins/core/rest-api-plugin.js +76 -78
  75. package/plugins/core/rest-api-positioning-plugin.js +251 -252
  76. package/plugins/core/socketio-plugin.js +240 -236
  77. package/plugins/storage/local-storage.js +103 -103
  78. package/plugins/storage/s3-storage.js +41 -42
  79. package/quickTest.js +37 -37
  80. package/scripts/debug-anyapi-review-filter.mjs +18 -18
  81. package/scripts/inspect-anyapi-descriptor-country.mjs +10 -10
  82. package/scripts/inspect-anyapi-descriptor.mjs +10 -10
  83. package/scripts/run-docs.js +112 -113
  84. package/test-debug.js +28 -28
  85. package/tests/TEST_TEMPLATE.test.js +51 -51
  86. package/tests/anyapi-basic.test.js +42 -42
  87. package/tests/anyapi-cursor-pagination.test.js +48 -48
  88. package/tests/bulk-operations.test.js +208 -208
  89. package/tests/computed-fields.test.js +204 -204
  90. package/tests/cors.test.js +412 -413
  91. package/tests/curl.test.js +275 -276
  92. package/tests/custom-idproperty-relationships.test.js +266 -266
  93. package/tests/field-getters.test.js +132 -132
  94. package/tests/field-setters.test.js +110 -110
  95. package/tests/fixtures/api-configs.js +699 -699
  96. package/tests/helpers/storage-mode.js +32 -32
  97. package/tests/helpers/test-utils.js +76 -76
  98. package/tests/include-depth-validation.test.js +76 -76
  99. package/tests/includes.test.js +145 -145
  100. package/tests/multihome.test.js +175 -175
  101. package/tests/nested-includes.test.js +255 -255
  102. package/tests/pagination-cursor-multifield.test.js +164 -164
  103. package/tests/pagination-enhanced.test.js +225 -225
  104. package/tests/polymorphic-relationships.test.js +96 -96
  105. package/tests/polymorphic-simple.test.js +44 -44
  106. package/tests/positioning-advanced.test.js +226 -226
  107. package/tests/positioning-extended.test.js +399 -399
  108. package/tests/positioning-multifilter.test.js +204 -204
  109. package/tests/positioning.test.js +266 -266
  110. package/tests/queries.test.js +184 -184
  111. package/tests/query-limits.test.js +154 -155
  112. package/tests/relationship-endpoints.test.js +127 -127
  113. package/tests/relationships.test.js +178 -179
  114. package/tests/rest-api-access.test.js +86 -86
  115. package/tests/rest-api.test.js +103 -103
  116. package/tests/searchschema-merge.test.js +93 -93
  117. package/tests/socketio.test.js +326 -326
  118. package/tests/virtual-fields.test.js +164 -164
@@ -1,9 +1,9 @@
1
- import { describe, it, before, after, beforeEach } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import knexLib from 'knex';
4
- import { cleanTables } from './helpers/test-utils.js';
5
- import { createPositioningApi } from './fixtures/api-configs.js';
6
- import { PositioningPlugin } from '../plugins/core/rest-api-positioning-plugin.js';
1
+ import { describe, it, before, after, beforeEach } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import knexLib from 'knex'
4
+ import { cleanTables } from './helpers/test-utils.js'
5
+ import { createPositioningApi } from './fixtures/api-configs.js'
6
+ import { PositioningPlugin } from '../plugins/core/rest-api-positioning-plugin.js'
7
7
 
8
8
  // Create Knex instance for tests - always use SQLite in-memory
9
9
  const knex = knexLib({
@@ -12,32 +12,32 @@ const knex = knexLib({
12
12
  filename: ':memory:'
13
13
  },
14
14
  useNullAsDefault: true
15
- });
15
+ })
16
16
 
17
17
  // API instance that persists across tests
18
- let api;
18
+ let api
19
19
 
20
20
  describe('Positioning Plugin Tests', { timeout: 30000 }, () => {
21
21
  // IMPORTANT: before() runs ONCE for the entire test suite
22
22
  before(async () => {
23
23
  // Initialize API once
24
- api = await createPositioningApi(knex);
25
-
24
+ api = await createPositioningApi(knex)
25
+
26
26
  // Install positioning plugin with default configuration
27
27
  await api.use(PositioningPlugin, {
28
28
  field: 'position',
29
29
  filters: ['category'], // Tasks will be grouped by category
30
30
  defaultPosition: 'last',
31
31
  autoIndex: true
32
- });
33
- });
34
-
32
+ })
33
+ })
34
+
35
35
  // IMPORTANT: after() cleans up resources
36
36
  after(async () => {
37
37
  // Always destroy knex connection to allow tests to exit
38
- await knex.destroy();
39
- });
40
-
38
+ await knex.destroy()
39
+ })
40
+
41
41
  // IMPORTANT: beforeEach() cleans data but does NOT recreate API
42
42
  beforeEach(async () => {
43
43
  // Clean all tables - list ALL tables your tests use
@@ -46,515 +46,515 @@ describe('Positioning Plugin Tests', { timeout: 30000 }, () => {
46
46
  'positioning_tasks',
47
47
  'positioning_projects',
48
48
  'positioning_items'
49
- ]);
50
- });
49
+ ])
50
+ })
51
51
 
52
52
  describe('Basic Positioning', () => {
53
- it('should add position field to first item automatically', async () => {
53
+ it('should add position field to first item automatically', async () => {
54
54
  // Create a category first
55
55
  const category = await api.resources.categories.post({
56
56
  name: 'Work Tasks'
57
- });
58
-
57
+ })
58
+
59
59
  // Create first task
60
60
  const result = await api.resources.tasks.post({
61
61
  title: 'First Task',
62
62
  category: category.id
63
- });
64
-
65
- assert.equal(result.title, 'First Task');
63
+ })
64
+
65
+ assert.equal(result.title, 'First Task')
66
66
  // Should have position assigned
67
- assert(result.position, 'Position should be assigned');
68
- assert.equal(typeof result.position, 'string');
69
- });
70
-
71
- it('should position items at the end by default', async () => {
67
+ assert(result.position, 'Position should be assigned')
68
+ assert.equal(typeof result.position, 'string')
69
+ })
70
+
71
+ it('should position items at the end by default', async () => {
72
72
  // Create a category
73
- const category = await api.resources.categories.post({ name: 'Test Category' });
74
-
73
+ const category = await api.resources.categories.post({ name: 'Test Category' })
74
+
75
75
  // Create multiple tasks
76
76
  const task1 = await api.resources.tasks.post({
77
77
  title: 'Task 1',
78
78
  category: category.id
79
- });
80
-
79
+ })
80
+
81
81
  const task2 = await api.resources.tasks.post({
82
82
  title: 'Task 2',
83
83
  category: category.id
84
- });
85
-
84
+ })
85
+
86
86
  const task3 = await api.resources.tasks.post({
87
87
  title: 'Task 3',
88
88
  category: category.id
89
- });
90
-
89
+ })
90
+
91
91
  // Verify positions are in order
92
- const pos1 = task1.position;
93
- const pos2 = task2.position;
94
- const pos3 = task3.position;
95
-
96
- assert(pos1 < pos2, 'Task 1 should be before Task 2');
97
- assert(pos2 < pos3, 'Task 2 should be before Task 3');
98
- });
99
-
100
- it('should maintain separate position sequences per filter group', async () => {
92
+ const pos1 = task1.position
93
+ const pos2 = task2.position
94
+ const pos3 = task3.position
95
+
96
+ assert(pos1 < pos2, 'Task 1 should be before Task 2')
97
+ assert(pos2 < pos3, 'Task 2 should be before Task 3')
98
+ })
99
+
100
+ it('should maintain separate position sequences per filter group', async () => {
101
101
  // Create two categories
102
- const category1 = await api.resources.categories.post({ name: 'Category 1' });
103
- const category2 = await api.resources.categories.post({ name: 'Category 2' });
104
-
102
+ const category1 = await api.resources.categories.post({ name: 'Category 1' })
103
+ const category2 = await api.resources.categories.post({ name: 'Category 2' })
104
+
105
105
  // Create tasks in different categories
106
106
  const task1Cat1 = await api.resources.tasks.post({
107
107
  title: 'Cat1 Task 1',
108
108
  category: category1.id
109
- });
110
-
109
+ })
110
+
111
111
  const task1Cat2 = await api.resources.tasks.post({
112
112
  title: 'Cat2 Task 1',
113
113
  category: category2.id
114
- });
115
-
114
+ })
115
+
116
116
  const task2Cat1 = await api.resources.tasks.post({
117
117
  title: 'Cat1 Task 2',
118
118
  category: category1.id
119
- });
120
-
119
+ })
120
+
121
121
  // Positions should be independent per category
122
- assert(task1Cat1.position);
123
- assert(task1Cat2.position);
124
- assert(task2Cat1.position);
125
-
122
+ assert(task1Cat1.position)
123
+ assert(task1Cat2.position)
124
+ assert(task2Cat1.position)
125
+
126
126
  // Task 2 in category 1 should be after task 1 in category 1
127
- assert(task1Cat1.position < task2Cat1.position);
128
-
127
+ assert(task1Cat1.position < task2Cat1.position)
128
+
129
129
  // But positions in different categories can be similar
130
130
  // (they're independent sequences)
131
- });
132
- });
131
+ })
132
+ })
133
133
 
134
134
  describe('Positioning with beforeId', () => {
135
- it('should position item before specified id', async () => {
135
+ it('should position item before specified id', async () => {
136
136
  // Create category
137
- const category = await api.resources.categories.post({ name: 'Test Category' });
138
-
137
+ const category = await api.resources.categories.post({ name: 'Test Category' })
138
+
139
139
  // Create initial tasks
140
140
  const task1 = await api.resources.tasks.post({
141
141
  title: 'Task 1',
142
142
  category: category.id
143
- });
144
-
143
+ })
144
+
145
145
  const task2 = await api.resources.tasks.post({
146
146
  title: 'Task 2',
147
147
  category: category.id
148
- });
149
-
148
+ })
149
+
150
150
  // Create task that should go between 1 and 2
151
151
  const taskMiddle = await api.resources.tasks.post({
152
152
  title: 'Middle Task',
153
153
  category: category.id,
154
154
  beforeId: task2.id
155
- });
156
-
155
+ })
156
+
157
157
  // Verify positioning
158
- const pos1 = task1.position;
159
- const posMiddle = taskMiddle.position;
160
- const pos2 = task2.position;
161
-
162
- assert(pos1 < posMiddle, 'Task 1 should be before Middle Task');
163
- assert(posMiddle < pos2, 'Middle Task should be before Task 2');
164
- });
165
-
166
- it('should position at beginning when beforeId is first item', async () => {
158
+ const pos1 = task1.position
159
+ const posMiddle = taskMiddle.position
160
+ const pos2 = task2.position
161
+
162
+ assert(pos1 < posMiddle, 'Task 1 should be before Middle Task')
163
+ assert(posMiddle < pos2, 'Middle Task should be before Task 2')
164
+ })
165
+
166
+ it('should position at beginning when beforeId is first item', async () => {
167
167
  // Create category
168
- const category = await api.resources.categories.post({ name: 'Test Category' });
169
-
168
+ const category = await api.resources.categories.post({ name: 'Test Category' })
169
+
170
170
  // Create initial tasks
171
171
  const task1 = await api.resources.tasks.post({
172
172
  title: 'Task 1',
173
173
  category: category.id
174
- });
175
-
174
+ })
175
+
176
176
  const task2 = await api.resources.tasks.post({
177
177
  title: 'Task 2',
178
178
  category: category.id
179
- });
180
-
179
+ })
180
+
181
181
  // Create task at beginning
182
182
  const taskFirst = await api.resources.tasks.post({
183
183
  title: 'First Task',
184
184
  category: category.id,
185
185
  beforeId: task1.id
186
- });
187
-
186
+ })
187
+
188
188
  // Verify it's first
189
- const posFirst = taskFirst.position;
190
- const pos1 = task1.position;
191
- const pos2 = task2.position;
192
-
193
- assert(posFirst < pos1, 'First Task should be before Task 1');
194
- assert(pos1 < pos2, 'Task 1 should be before Task 2');
195
- });
196
- });
189
+ const posFirst = taskFirst.position
190
+ const pos1 = task1.position
191
+ const pos2 = task2.position
192
+
193
+ assert(posFirst < pos1, 'First Task should be before Task 1')
194
+ assert(pos1 < pos2, 'Task 1 should be before Task 2')
195
+ })
196
+ })
197
197
 
198
198
  describe('Position Updates', () => {
199
- it('should reposition item when updated with beforeId', async () => {
199
+ it('should reposition item when updated with beforeId', async () => {
200
200
  // Create category
201
- const category = await api.resources.categories.post({ name: 'Test Category' });
202
-
201
+ const category = await api.resources.categories.post({ name: 'Test Category' })
202
+
203
203
  // Create three tasks
204
204
  const task1 = await api.resources.tasks.post({
205
205
  title: 'Task 1',
206
206
  category: category.id
207
- });
208
-
207
+ })
208
+
209
209
  const task2 = await api.resources.tasks.post({
210
210
  title: 'Task 2',
211
211
  category: category.id
212
- });
213
-
212
+ })
213
+
214
214
  const task3 = await api.resources.tasks.post({
215
215
  title: 'Task 3',
216
216
  category: category.id
217
- });
218
-
217
+ })
218
+
219
219
  // Move task 3 before task 2
220
220
  const updated = await api.resources.tasks.patch({
221
221
  id: task3.id,
222
222
  beforeId: task2.id
223
- });
224
-
223
+ })
224
+
225
225
  // Get fresh data
226
226
  const { data: allTasks } = await api.resources.tasks.query({
227
227
  queryParams: {
228
228
  filters: { category: category.id },
229
229
  sort: ['position']
230
230
  }
231
- });
232
-
231
+ })
232
+
233
233
  // Verify order is now: Task 1, Task 3, Task 2
234
- assert.equal(allTasks[0].title, 'Task 1');
235
- assert.equal(allTasks[1].title, 'Task 3');
236
- assert.equal(allTasks[2].title, 'Task 2');
237
- });
238
-
239
- it('should maintain position when updating without beforeId', async () => {
234
+ assert.equal(allTasks[0].title, 'Task 1')
235
+ assert.equal(allTasks[1].title, 'Task 3')
236
+ assert.equal(allTasks[2].title, 'Task 2')
237
+ })
238
+
239
+ it('should maintain position when updating without beforeId', async () => {
240
240
  // Create category
241
- const category = await api.resources.categories.post({ name: 'Test Category' });
242
-
241
+ const category = await api.resources.categories.post({ name: 'Test Category' })
242
+
243
243
  // Create task
244
244
  const task = await api.resources.tasks.post({
245
245
  title: 'Original Title',
246
246
  category: category.id
247
- });
248
-
249
- const originalPosition = task.position;
250
-
247
+ })
248
+
249
+ const originalPosition = task.position
250
+
251
251
  // Update title without beforeId
252
252
  const updated = await api.resources.tasks.patch({
253
253
  id: task.id,
254
254
  title: 'Updated Title'
255
- });
256
-
255
+ })
256
+
257
257
  // Position should remain the same
258
- assert.equal(updated.position, originalPosition);
259
- assert.equal(updated.title, 'Updated Title');
260
- });
261
- });
258
+ assert.equal(updated.position, originalPosition)
259
+ assert.equal(updated.title, 'Updated Title')
260
+ })
261
+ })
262
262
 
263
263
  describe('Manual Position Override', () => {
264
- it('should ignore manual position setting', async () => {
264
+ it('should ignore manual position setting', async () => {
265
265
  // Create category
266
- const category = await api.resources.categories.post({ name: 'Test Category' });
267
-
266
+ const category = await api.resources.categories.post({ name: 'Test Category' })
267
+
268
268
  // Create task with manual position attempt
269
269
  const task = await api.resources.tasks.post({
270
270
  title: 'Manual Position Task',
271
271
  category: category.id,
272
272
  position: 'zzz' // Manual position should be ignored
273
- });
274
-
273
+ })
274
+
275
275
  // Position should be automatically assigned, not 'zzz'
276
- assert.notEqual(task.position, 'zzz');
277
- assert(task.position); // Should have a position
278
- assert.equal(task.position, 'a0'); // First item gets 'a0'
279
- });
280
-
281
- it('should ignore manual position and use beforeId', async () => {
276
+ assert.notEqual(task.position, 'zzz')
277
+ assert(task.position) // Should have a position
278
+ assert.equal(task.position, 'a0') // First item gets 'a0'
279
+ })
280
+
281
+ it('should ignore manual position and use beforeId', async () => {
282
282
  // Create category
283
- const category = await api.resources.categories.post({ name: 'Test Category' });
284
-
283
+ const category = await api.resources.categories.post({ name: 'Test Category' })
284
+
285
285
  // Create initial task
286
286
  const task1 = await api.resources.tasks.post({
287
287
  title: 'Task 1',
288
288
  category: category.id
289
- });
290
-
289
+ })
290
+
291
291
  // Create task with both manual position and beforeId
292
292
  const task2 = await api.resources.tasks.post({
293
293
  title: 'Task 2',
294
294
  category: category.id,
295
295
  position: 'zzz', // Should be ignored
296
296
  beforeId: task1.id // Should be used
297
- });
298
-
297
+ })
298
+
299
299
  // Manual position should be ignored, beforeId should work
300
- assert.notEqual(task2.position, 'zzz');
301
- assert(task2.position < task1.position); // Should be positioned before task1
302
- });
303
- });
300
+ assert.notEqual(task2.position, 'zzz')
301
+ assert(task2.position < task1.position) // Should be positioned before task1
302
+ })
303
+ })
304
304
 
305
305
  describe('Position Configuration', () => {
306
- it('should handle different field name configuration', async () => {
306
+ it('should handle different field name configuration', async () => {
307
307
  // Create a new API with different position field name
308
308
  const customApi = await createPositioningApi(knex, {
309
309
  apiName: 'positioning-custom-field-test',
310
310
  tablePrefix: 'pos_custom'
311
- });
312
-
311
+ })
312
+
313
313
  await customApi.use(PositioningPlugin, {
314
314
  field: 'sort_order', // Different field name
315
315
  filters: ['project'],
316
316
  defaultPosition: 'last'
317
- });
318
-
317
+ })
318
+
319
319
  // Create project
320
- const project = await customApi.resources.projects.post({ name: 'Test Project' });
321
-
320
+ const project = await customApi.resources.projects.post({ name: 'Test Project' })
321
+
322
322
  // Create item
323
323
  const item = await customApi.resources.items.post({
324
324
  name: 'Test Item',
325
325
  project: project.id
326
- });
327
-
326
+ })
327
+
328
328
  // Should have sort_order field, not position
329
- assert(item.sort_order, 'Should have sort_order field');
330
- assert(!item.position, 'Should not have position field');
331
- });
332
-
333
- it('should handle multiple filter fields', async () => {
329
+ assert(item.sort_order, 'Should have sort_order field')
330
+ assert(!item.position, 'Should not have position field')
331
+ })
332
+
333
+ it('should handle multiple filter fields', async () => {
334
334
  // Create a new API with multiple filters
335
335
  const multiFilterApi = await createPositioningApi(knex, {
336
336
  apiName: 'positioning-multi-filter-test',
337
337
  tablePrefix: 'pos_multi'
338
- });
339
-
338
+ })
339
+
340
340
  await multiFilterApi.use(PositioningPlugin, {
341
341
  field: 'position',
342
342
  filters: ['project', 'status'], // Multiple filters
343
343
  defaultPosition: 'last'
344
- });
345
-
344
+ })
345
+
346
346
  // Create project
347
- const project = await multiFilterApi.resources.projects.post({ name: 'Multi Filter Project' });
348
-
347
+ const project = await multiFilterApi.resources.projects.post({ name: 'Multi Filter Project' })
348
+
349
349
  // Create items with different statuses
350
350
  const item1Active = await multiFilterApi.resources.items.post({
351
351
  name: 'Active Item 1',
352
352
  project: project.id,
353
353
  status: 'active'
354
- });
355
-
354
+ })
355
+
356
356
  const item2Active = await multiFilterApi.resources.items.post({
357
357
  name: 'Active Item 2',
358
358
  project: project.id,
359
359
  status: 'active'
360
- });
361
-
360
+ })
361
+
362
362
  const item1Archived = await multiFilterApi.resources.items.post({
363
363
  name: 'Archived Item 1',
364
364
  project: project.id,
365
365
  status: 'archived'
366
- });
367
-
366
+ })
367
+
368
368
  // Active items should be in sequence
369
- assert(item1Active.position < item2Active.position);
370
-
369
+ assert(item1Active.position < item2Active.position)
370
+
371
371
  // Archived item should have its own sequence
372
372
  // (its position might be similar to active item 1, but they're in different groups)
373
- });
374
-
375
- it('should handle defaultPosition first configuration', async () => {
373
+ })
374
+
375
+ it('should handle defaultPosition first configuration', async () => {
376
376
  // Create API with defaultPosition: 'first'
377
377
  const firstApi = await createPositioningApi(knex, {
378
378
  apiName: 'positioning-first-test',
379
379
  tablePrefix: 'pos_first'
380
- });
381
-
380
+ })
381
+
382
382
  await firstApi.use(PositioningPlugin, {
383
383
  field: 'position',
384
384
  filters: ['category'],
385
385
  defaultPosition: 'first' // Items go to beginning by default
386
- });
387
-
386
+ })
387
+
388
388
  // Create category
389
- const category = await firstApi.resources.categories.post({ name: 'First Default Category' });
390
-
389
+ const category = await firstApi.resources.categories.post({ name: 'First Default Category' })
390
+
391
391
  // Create tasks - they should be added at the beginning
392
392
  const task1 = await firstApi.resources.tasks.post({
393
393
  title: 'Task 1',
394
394
  category: category.id
395
- });
396
-
395
+ })
396
+
397
397
  const task2 = await firstApi.resources.tasks.post({
398
398
  title: 'Task 2',
399
399
  category: category.id
400
- });
401
-
400
+ })
401
+
402
402
  const task3 = await firstApi.resources.tasks.post({
403
403
  title: 'Task 3',
404
404
  category: category.id
405
- });
406
-
405
+ })
406
+
407
407
  // With defaultPosition: 'first', the order should be reversed
408
408
  // Task 3 should be first, then Task 2, then Task 1
409
- assert(task3.position < task2.position);
410
- assert(task2.position < task1.position);
411
- });
412
- });
409
+ assert(task3.position < task2.position)
410
+ assert(task2.position < task1.position)
411
+ })
412
+ })
413
413
 
414
414
  describe('Error Handling', () => {
415
- it('should handle invalid beforeId gracefully', async () => {
415
+ it('should handle invalid beforeId gracefully', async () => {
416
416
  // Create category
417
- const category = await api.resources.categories.post({ name: 'Test Category' });
418
-
417
+ const category = await api.resources.categories.post({ name: 'Test Category' })
418
+
419
419
  // Create task with non-existent beforeId
420
420
  const task = await api.resources.tasks.post({
421
421
  title: 'Task with Invalid BeforeId',
422
422
  category: category.id,
423
423
  beforeId: '99999' // Non-existent ID
424
- });
425
-
424
+ })
425
+
426
426
  // Should still create the task (positioned at end)
427
- assert(task.position);
428
- assert.equal(task.title, 'Task with Invalid BeforeId');
429
- });
430
-
431
- it('should handle null filter values', async () => {
427
+ assert(task.position)
428
+ assert.equal(task.title, 'Task with Invalid BeforeId')
429
+ })
430
+
431
+ it('should handle null filter values', async () => {
432
432
  // Create task without category (null filter)
433
433
  const task = await api.resources.tasks.post({
434
434
  title: 'Task without Category',
435
435
  category: null
436
- });
437
-
436
+ })
437
+
438
438
  // Should still get position
439
- assert(task.position);
440
-
439
+ assert(task.position)
440
+
441
441
  // Create another task without category
442
442
  const task2 = await api.resources.tasks.post({
443
443
  title: 'Another Task without Category',
444
444
  category: null
445
- });
446
-
445
+ })
446
+
447
447
  // They should be in sequence
448
- assert(task.position < task2.position);
449
- });
450
- });
448
+ assert(task.position < task2.position)
449
+ })
450
+ })
451
451
 
452
452
  describe('Excluded Resources', () => {
453
- it('should not add positioning to excluded resources', async () => {
453
+ it('should not add positioning to excluded resources', async () => {
454
454
  // Create API with excluded resources
455
455
  const excludeApi = await createPositioningApi(knex, {
456
456
  apiName: 'positioning-exclude-test',
457
457
  tablePrefix: 'pos_exclude'
458
- });
459
-
458
+ })
459
+
460
460
  await excludeApi.use(PositioningPlugin, {
461
461
  field: 'position',
462
462
  filters: [],
463
463
  excludeResources: ['categories'] // Exclude categories from positioning
464
- });
465
-
464
+ })
465
+
466
466
  // Create category - should not have position
467
- const category = await excludeApi.resources.categories.post({ name: 'Excluded Category' });
468
-
469
- assert(!category.position, 'Category should not have position');
470
-
467
+ const category = await excludeApi.resources.categories.post({ name: 'Excluded Category' })
468
+
469
+ assert(!category.position, 'Category should not have position')
470
+
471
471
  // Create task - should have position
472
472
  const task = await excludeApi.resources.tasks.post({
473
473
  title: 'Task with Position',
474
474
  category: category.id
475
- });
476
-
477
- assert(task.position, 'Task should have position');
478
- });
479
- });
475
+ })
476
+
477
+ assert(task.position, 'Task should have position')
478
+ })
479
+ })
480
480
 
481
481
  describe('Simplified API Mode', () => {
482
- it('should handle positioning in simplified mode', async () => {
482
+ it('should handle positioning in simplified mode', async () => {
483
483
  // We're already using simplified mode throughout these tests!
484
484
  // This test just verifies it works as expected
485
-
485
+
486
486
  // Create category
487
- const category = await api.resources.categories.post({ name: 'Simplified Category' });
488
-
487
+ const category = await api.resources.categories.post({ name: 'Simplified Category' })
488
+
489
489
  // Create tasks in simplified mode
490
490
  const task1 = await api.resources.tasks.post({
491
491
  title: 'Simplified Task 1',
492
492
  category: category.id
493
- });
494
-
493
+ })
494
+
495
495
  const task2 = await api.resources.tasks.post({
496
496
  title: 'Simplified Task 2',
497
497
  category: category.id,
498
498
  beforeId: task1.id
499
- });
500
-
499
+ })
500
+
501
501
  // Verify positioning
502
- assert(task1.position, 'Task 1 should have position');
503
- assert(task2.position, 'Task 2 should have position');
504
- assert(task2.position < task1.position, 'Task 2 should be before Task 1');
505
- });
506
- });
502
+ assert(task1.position, 'Task 1 should have position')
503
+ assert(task2.position, 'Task 2 should have position')
504
+ assert(task2.position < task1.position, 'Task 2 should be before Task 1')
505
+ })
506
+ })
507
507
 
508
508
  describe('Query Ordering', () => {
509
- it('should allow sorting by position field', async () => {
509
+ it('should allow sorting by position field', async () => {
510
510
  // Create category
511
- const category = await api.resources.categories.post({ name: 'Sort Test Category' });
512
-
511
+ const category = await api.resources.categories.post({ name: 'Sort Test Category' })
512
+
513
513
  // Create tasks in specific order
514
514
  const taskC = await api.resources.tasks.post({
515
515
  title: 'C Task',
516
516
  category: category.id
517
- });
518
-
517
+ })
518
+
519
519
  const taskA = await api.resources.tasks.post({
520
520
  title: 'A Task',
521
521
  category: category.id,
522
522
  beforeId: taskC.id
523
- });
524
-
523
+ })
524
+
525
525
  const taskB = await api.resources.tasks.post({
526
526
  title: 'B Task',
527
527
  category: category.id,
528
528
  beforeId: taskC.id
529
- });
530
-
529
+ })
530
+
531
531
  // Query with position sort
532
532
  const { data: sorted } = await api.resources.tasks.query({
533
533
  queryParams: {
534
534
  filters: { category: category.id },
535
535
  sort: ['position']
536
536
  }
537
- });
538
-
537
+ })
538
+
539
539
  // Should be in position order: A, B, C
540
540
  // Note: When both A and B are positioned before C, they maintain creation order
541
- assert.equal(sorted.length, 3);
542
- assert.equal(sorted[0].title, 'A Task');
543
- assert.equal(sorted[1].title, 'B Task');
544
- assert.equal(sorted[2].title, 'C Task');
545
-
541
+ assert.equal(sorted.length, 3)
542
+ assert.equal(sorted[0].title, 'A Task')
543
+ assert.equal(sorted[1].title, 'B Task')
544
+ assert.equal(sorted[2].title, 'C Task')
545
+
546
546
  // Query with reverse position sort
547
547
  const { data: reverseSorted } = await api.resources.tasks.query({
548
548
  queryParams: {
549
549
  filters: { category: category.id },
550
550
  sort: ['-position']
551
551
  }
552
- });
553
-
552
+ })
553
+
554
554
  // Should be in reverse order: C, B, A
555
- assert.equal(reverseSorted[0].title, 'C Task');
556
- assert.equal(reverseSorted[1].title, 'B Task');
557
- assert.equal(reverseSorted[2].title, 'A Task');
558
- });
559
- });
560
- });
555
+ assert.equal(reverseSorted[0].title, 'C Task')
556
+ assert.equal(reverseSorted[1].title, 'B Task')
557
+ assert.equal(reverseSorted[2].title, 'A Task')
558
+ })
559
+ })
560
+ })