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.
- package/connectors_ideas/fastify.js +66 -68
- package/connectors_ideas/koa-plugin.js +50 -51
- package/eslint.config.js +11 -2
- package/examples/url-override-example.js +63 -63
- package/index.js +13 -13
- package/lib/enhanced-logger.js +51 -51
- package/lib/error-formatter.js +61 -61
- package/lib/rest-api-errors.js +28 -28
- package/package.json +1 -1
- package/plugins/core/bulk-operations-plugin.js +142 -145
- package/plugins/core/connectors/express-plugin.js +210 -217
- package/plugins/core/connectors/lib/busboy-detector.js +84 -84
- package/plugins/core/connectors/lib/formidable-detector.js +50 -50
- package/plugins/core/connectors/lib/request-helpers.js +74 -74
- package/plugins/core/file-handling-plugin.js +125 -125
- package/plugins/core/lib/anyapi/anyapi-registry.js +254 -254
- package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +287 -287
- package/plugins/core/lib/anyapi/schema-utils.js +101 -101
- package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +41 -41
- package/plugins/core/lib/dbTablesOperations.js +134 -135
- package/plugins/core/lib/fractional-positioning.js +74 -76
- package/plugins/core/lib/querying/knex-cross-table-search.js +320 -320
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +144 -144
- package/plugins/core/lib/querying/knex-pagination-helpers.js +164 -164
- package/plugins/core/lib/querying/knex-process-includes.js +50 -50
- package/plugins/core/lib/querying/knex-query-helpers-base.js +31 -31
- package/plugins/core/lib/querying/knex-query-helpers.js +451 -452
- package/plugins/core/lib/querying/knex-relationship-includes.js +1160 -1170
- package/plugins/core/lib/querying/knex-window-queries.js +89 -89
- package/plugins/core/lib/querying/url-helpers.js +35 -35
- package/plugins/core/lib/querying-writing/compile-schemas.js +97 -98
- package/plugins/core/lib/querying-writing/connectors-query-parser.js +31 -31
- package/plugins/core/lib/querying-writing/database-capabilities.js +94 -94
- package/plugins/core/lib/querying-writing/database-value-normalizers.js +76 -76
- package/plugins/core/lib/querying-writing/default-data-helpers.js +27 -27
- package/plugins/core/lib/querying-writing/field-utils.js +44 -42
- package/plugins/core/lib/querying-writing/knex-constants.js +15 -16
- package/plugins/core/lib/querying-writing/knex-field-helpers.js +117 -115
- package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +41 -41
- package/plugins/core/lib/querying-writing/payload-validators.js +275 -275
- package/plugins/core/lib/querying-writing/schema-helpers.js +129 -129
- package/plugins/core/lib/querying-writing/scope-validations.js +143 -146
- package/plugins/core/lib/querying-writing/simplified-helpers.js +133 -129
- package/plugins/core/lib/storage/storage-adapter.js +68 -68
- package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +31 -31
- package/plugins/core/lib/writing/many-to-many-manipulations.js +81 -81
- package/plugins/core/lib/writing/relationship-processor.js +58 -58
- package/plugins/core/rest-api-access.js +8 -8
- package/plugins/core/rest-api-anyapi-knex-plugin.js +920 -920
- package/plugins/core/rest-api-cors-plugin.js +170 -171
- package/plugins/core/rest-api-knex-plugin.js +482 -493
- package/plugins/core/rest-api-multihome-plugin.js +247 -251
- package/plugins/core/rest-api-plugin-hooks/compile-resource-schemas.js +5 -5
- package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +20 -21
- package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +44 -44
- package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +23 -23
- package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +15 -15
- package/plugins/core/rest-api-plugin-methods/add-route.js +9 -9
- package/plugins/core/rest-api-plugin-methods/check-permissions.js +5 -6
- package/plugins/core/rest-api-plugin-methods/common.js +295 -297
- package/plugins/core/rest-api-plugin-methods/delete-relationship.js +35 -37
- package/plugins/core/rest-api-plugin-methods/delete.js +57 -59
- package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +172 -171
- package/plugins/core/rest-api-plugin-methods/get-related.js +96 -96
- package/plugins/core/rest-api-plugin-methods/get-relationship.js +16 -18
- package/plugins/core/rest-api-plugin-methods/get.js +86 -86
- package/plugins/core/rest-api-plugin-methods/patch-relationship.js +16 -17
- package/plugins/core/rest-api-plugin-methods/patch.js +63 -64
- package/plugins/core/rest-api-plugin-methods/post-relationship.js +54 -55
- package/plugins/core/rest-api-plugin-methods/post.js +61 -62
- package/plugins/core/rest-api-plugin-methods/put.js +86 -87
- package/plugins/core/rest-api-plugin-methods/query.js +88 -88
- package/plugins/core/rest-api-plugin-methods/release.js +5 -6
- package/plugins/core/rest-api-plugin.js +76 -78
- package/plugins/core/rest-api-positioning-plugin.js +251 -252
- package/plugins/core/socketio-plugin.js +240 -236
- package/plugins/storage/local-storage.js +103 -103
- package/plugins/storage/s3-storage.js +41 -42
- package/quickTest.js +37 -37
- package/scripts/debug-anyapi-review-filter.mjs +18 -18
- package/scripts/inspect-anyapi-descriptor-country.mjs +10 -10
- package/scripts/inspect-anyapi-descriptor.mjs +10 -10
- package/scripts/run-docs.js +112 -113
- package/test-debug.js +28 -28
- package/tests/TEST_TEMPLATE.test.js +51 -51
- package/tests/anyapi-basic.test.js +42 -42
- package/tests/anyapi-cursor-pagination.test.js +48 -48
- package/tests/bulk-operations.test.js +208 -208
- package/tests/computed-fields.test.js +204 -204
- package/tests/cors.test.js +412 -413
- package/tests/curl.test.js +275 -276
- package/tests/custom-idproperty-relationships.test.js +266 -266
- package/tests/field-getters.test.js +132 -132
- package/tests/field-setters.test.js +110 -110
- package/tests/fixtures/api-configs.js +699 -699
- package/tests/helpers/storage-mode.js +32 -32
- package/tests/helpers/test-utils.js +76 -76
- package/tests/include-depth-validation.test.js +76 -76
- package/tests/includes.test.js +145 -145
- package/tests/multihome.test.js +175 -175
- package/tests/nested-includes.test.js +255 -255
- package/tests/pagination-cursor-multifield.test.js +164 -164
- package/tests/pagination-enhanced.test.js +225 -225
- package/tests/polymorphic-relationships.test.js +96 -96
- package/tests/polymorphic-simple.test.js +44 -44
- package/tests/positioning-advanced.test.js +226 -226
- package/tests/positioning-extended.test.js +399 -399
- package/tests/positioning-multifilter.test.js +204 -204
- package/tests/positioning.test.js +266 -266
- package/tests/queries.test.js +184 -184
- package/tests/query-limits.test.js +154 -155
- package/tests/relationship-endpoints.test.js +127 -127
- package/tests/relationships.test.js +178 -179
- package/tests/rest-api-access.test.js +86 -86
- package/tests/rest-api.test.js +103 -103
- package/tests/searchschema-merge.test.js +93 -93
- package/tests/socketio.test.js +326 -326
- package/tests/virtual-fields.test.js +164 -164
|
@@ -9,50 +9,50 @@
|
|
|
9
9
|
* @param {object} definition - The schema definition for the field
|
|
10
10
|
* @returns {object} The Knex column builder instance
|
|
11
11
|
*/
|
|
12
|
-
function mapTypeToKnex(table, columnName, definition) {
|
|
13
|
-
const { type, precision, scale, maxLength, unsigned } = definition
|
|
14
|
-
|
|
12
|
+
function mapTypeToKnex (table, columnName, definition) {
|
|
13
|
+
const { type, precision, scale, maxLength, unsigned } = definition
|
|
14
|
+
|
|
15
15
|
switch (type) {
|
|
16
16
|
case 'string':
|
|
17
|
-
return maxLength ? table.string(columnName, maxLength) : table.string(columnName)
|
|
18
|
-
|
|
17
|
+
return maxLength ? table.string(columnName, maxLength) : table.string(columnName)
|
|
18
|
+
|
|
19
19
|
case 'number':
|
|
20
20
|
if (precision !== undefined && scale !== undefined) {
|
|
21
|
-
return table.decimal(columnName, precision, scale)
|
|
21
|
+
return table.decimal(columnName, precision, scale)
|
|
22
22
|
}
|
|
23
|
-
return table.float(columnName)
|
|
24
|
-
|
|
23
|
+
return table.float(columnName)
|
|
24
|
+
|
|
25
25
|
case 'id':
|
|
26
|
-
const col = table.integer(columnName)
|
|
27
|
-
return unsigned !== false ? col.unsigned() : col
|
|
28
|
-
|
|
26
|
+
const col = table.integer(columnName)
|
|
27
|
+
return unsigned !== false ? col.unsigned() : col
|
|
28
|
+
|
|
29
29
|
case 'boolean':
|
|
30
|
-
return table.boolean(columnName)
|
|
31
|
-
|
|
30
|
+
return table.boolean(columnName)
|
|
31
|
+
|
|
32
32
|
case 'date':
|
|
33
|
-
return table.date(columnName)
|
|
34
|
-
|
|
33
|
+
return table.date(columnName)
|
|
34
|
+
|
|
35
35
|
case 'dateTime':
|
|
36
|
-
return table.datetime(columnName)
|
|
37
|
-
|
|
36
|
+
return table.datetime(columnName)
|
|
37
|
+
|
|
38
38
|
case 'time':
|
|
39
|
-
return table.time(columnName)
|
|
40
|
-
|
|
39
|
+
return table.time(columnName)
|
|
40
|
+
|
|
41
41
|
case 'timestamp':
|
|
42
|
-
return table.integer(columnName)
|
|
43
|
-
|
|
42
|
+
return table.integer(columnName)
|
|
43
|
+
|
|
44
44
|
case 'array':
|
|
45
45
|
case 'object':
|
|
46
46
|
case 'serialize':
|
|
47
|
-
return table.json(columnName)
|
|
48
|
-
|
|
47
|
+
return table.json(columnName)
|
|
48
|
+
|
|
49
49
|
case 'blob':
|
|
50
50
|
case 'file':
|
|
51
|
-
return table.binary(columnName)
|
|
52
|
-
|
|
51
|
+
return table.binary(columnName)
|
|
52
|
+
|
|
53
53
|
case 'none':
|
|
54
54
|
default:
|
|
55
|
-
return table.string(columnName)
|
|
55
|
+
return table.string(columnName)
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
|
|
@@ -61,47 +61,47 @@ function mapTypeToKnex(table, columnName, definition) {
|
|
|
61
61
|
* @param {object} column - The Knex column builder instance
|
|
62
62
|
* @param {object} definition - The schema definition for the field
|
|
63
63
|
*/
|
|
64
|
-
function applyConstraints(column, definition) {
|
|
64
|
+
function applyConstraints (column, definition) {
|
|
65
65
|
// Nullability
|
|
66
66
|
if (definition.nullable === true) {
|
|
67
|
-
column.nullable()
|
|
67
|
+
column.nullable()
|
|
68
68
|
} else if (definition.required === true || definition.nullable === false) {
|
|
69
|
-
column.notNullable()
|
|
69
|
+
column.notNullable()
|
|
70
70
|
}
|
|
71
|
-
|
|
71
|
+
|
|
72
72
|
// Default value
|
|
73
73
|
if (definition.defaultTo !== undefined) {
|
|
74
|
-
column.defaultTo(definition.defaultTo)
|
|
74
|
+
column.defaultTo(definition.defaultTo)
|
|
75
75
|
}
|
|
76
|
-
|
|
76
|
+
|
|
77
77
|
// Database-specific constraints
|
|
78
78
|
if (definition.unique === true) {
|
|
79
|
-
column.unique()
|
|
79
|
+
column.unique()
|
|
80
80
|
}
|
|
81
|
-
|
|
81
|
+
|
|
82
82
|
if (definition.primary === true) {
|
|
83
|
-
column.primary()
|
|
83
|
+
column.primary()
|
|
84
84
|
}
|
|
85
|
-
|
|
85
|
+
|
|
86
86
|
if (definition.index === true) {
|
|
87
|
-
column.index()
|
|
87
|
+
column.index()
|
|
88
88
|
}
|
|
89
|
-
|
|
89
|
+
|
|
90
90
|
if (definition.references) {
|
|
91
91
|
column.references(definition.references.column || 'id')
|
|
92
|
-
.inTable(definition.references.table)
|
|
93
|
-
|
|
92
|
+
.inTable(definition.references.table)
|
|
93
|
+
|
|
94
94
|
if (definition.references.onDelete) {
|
|
95
|
-
column.onDelete(definition.references.onDelete)
|
|
95
|
+
column.onDelete(definition.references.onDelete)
|
|
96
96
|
}
|
|
97
|
-
|
|
97
|
+
|
|
98
98
|
if (definition.references.onUpdate) {
|
|
99
|
-
column.onUpdate(definition.references.onUpdate)
|
|
99
|
+
column.onUpdate(definition.references.onUpdate)
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
|
-
|
|
102
|
+
|
|
103
103
|
if (definition.comment) {
|
|
104
|
-
column.comment(definition.comment)
|
|
104
|
+
column.comment(definition.comment)
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
|
|
@@ -116,9 +116,8 @@ function applyConstraints(column, definition) {
|
|
|
116
116
|
* @param {boolean} [options.timestamps=false] - Whether to add created_at/updated_at columns
|
|
117
117
|
* @returns {Promise} A promise that resolves when the table is created
|
|
118
118
|
*/
|
|
119
|
-
export async function createKnexTable(knex, schemaInfo, tableSchemaInstance,
|
|
120
|
-
const { autoIncrement = true, timestamps = false } = options
|
|
121
|
-
|
|
119
|
+
export async function createKnexTable (knex, schemaInfo, tableSchemaInstance, options = {}) {
|
|
120
|
+
const { autoIncrement = true, timestamps = false } = options
|
|
122
121
|
|
|
123
122
|
const tableName = schemaInfo.tableName
|
|
124
123
|
const idProperty = schemaInfo.idProperty
|
|
@@ -126,57 +125,57 @@ export async function createKnexTable(knex, schemaInfo, tableSchemaInstance, op
|
|
|
126
125
|
|
|
127
126
|
return knex.schema.createTable(tableName, (table) => {
|
|
128
127
|
// Check if schema has the idProperty field with primary key
|
|
129
|
-
const hasIdField = tableSchemaStructure[idProperty] && tableSchemaStructure[idProperty].primary === true
|
|
130
|
-
|
|
128
|
+
const hasIdField = tableSchemaStructure[idProperty] && tableSchemaStructure[idProperty].primary === true
|
|
129
|
+
|
|
131
130
|
// Add auto-incrementing ID if no primary key is defined and autoIncrement is true
|
|
132
131
|
if (!hasIdField && autoIncrement) {
|
|
133
|
-
table.increments(idProperty).primary()
|
|
132
|
+
table.increments(idProperty).primary()
|
|
134
133
|
}
|
|
135
|
-
|
|
134
|
+
|
|
136
135
|
// Process each field in the schema
|
|
137
136
|
for (const [fieldName, definition] of Object.entries(tableSchemaStructure)) {
|
|
138
137
|
// Skip if this is the ID field and we already handled it
|
|
139
138
|
if (fieldName === idProperty && !hasIdField && autoIncrement) {
|
|
140
|
-
continue
|
|
139
|
+
continue
|
|
141
140
|
}
|
|
142
|
-
|
|
141
|
+
|
|
143
142
|
// Create the column with the appropriate type
|
|
144
|
-
const column = mapTypeToKnex(table, fieldName, definition)
|
|
145
|
-
|
|
143
|
+
const column = mapTypeToKnex(table, fieldName, definition)
|
|
144
|
+
|
|
146
145
|
// Apply constraints
|
|
147
|
-
applyConstraints(column, definition)
|
|
146
|
+
applyConstraints(column, definition)
|
|
148
147
|
}
|
|
149
|
-
|
|
148
|
+
|
|
150
149
|
// Add timestamps if requested
|
|
151
150
|
if (timestamps) {
|
|
152
|
-
table.timestamps(true, true)
|
|
151
|
+
table.timestamps(true, true)
|
|
153
152
|
}
|
|
154
|
-
})
|
|
153
|
+
})
|
|
155
154
|
}
|
|
156
155
|
|
|
157
|
-
export async function addKnexFields(knex, tableName, schema) {
|
|
156
|
+
export async function addKnexFields (knex, tableName, schema) {
|
|
158
157
|
return knex.schema.alterTable(tableName, (table) => {
|
|
159
|
-
for (const [fieldName, definition] of Object.entries(schema.structure)) {
|
|
160
|
-
const column = mapTypeToKnex(table, fieldName, definition)
|
|
161
|
-
applyConstraints(column, definition)
|
|
158
|
+
for (const [fieldName, definition] of Object.entries(schema.structure)) {
|
|
159
|
+
const column = mapTypeToKnex(table, fieldName, definition)
|
|
160
|
+
applyConstraints(column, definition)
|
|
162
161
|
}
|
|
163
|
-
})
|
|
162
|
+
})
|
|
164
163
|
}
|
|
165
164
|
|
|
166
165
|
// Helper function to alter multiple fields in an existing table
|
|
167
|
-
export async function alterKnexFields(knex, tableName, fields, options = {}) {
|
|
166
|
+
export async function alterKnexFields (knex, tableName, fields, options = {}) {
|
|
168
167
|
return knex.schema.alterTable(tableName, (table) => {
|
|
169
168
|
for (const [fieldName, definition] of Object.entries(fields)) {
|
|
170
169
|
// Create the column with alter flag
|
|
171
|
-
const column = mapTypeToKnex(table, fieldName, definition)
|
|
170
|
+
const column = mapTypeToKnex(table, fieldName, definition)
|
|
172
171
|
|
|
173
172
|
// Mark this as an alteration
|
|
174
|
-
column.alter()
|
|
173
|
+
column.alter()
|
|
175
174
|
|
|
176
175
|
// Apply constraints (these will be reapplied with the alter)
|
|
177
|
-
applyConstraints(column, definition)
|
|
176
|
+
applyConstraints(column, definition)
|
|
178
177
|
}
|
|
179
|
-
})
|
|
178
|
+
})
|
|
180
179
|
}
|
|
181
180
|
|
|
182
181
|
/**
|
|
@@ -186,120 +185,120 @@ export async function alterKnexFields(knex, tableName, fields, options = {}) {
|
|
|
186
185
|
* @param {object} [options={}] - Additional options
|
|
187
186
|
* @returns {string} The migration code as a string
|
|
188
187
|
*/
|
|
189
|
-
export function generateKnexMigration(tableName, schema, options = {}) {
|
|
190
|
-
const { autoIncrement = true, timestamps = false } = options
|
|
191
|
-
|
|
188
|
+
export function generateKnexMigration (tableName, schema, options = {}) {
|
|
189
|
+
const { autoIncrement = true, timestamps = false } = options
|
|
190
|
+
|
|
192
191
|
let migration = `exports.up = function(knex) {
|
|
193
|
-
return knex.schema.createTable('${tableName}', (table) => {\n
|
|
194
|
-
|
|
192
|
+
return knex.schema.createTable('${tableName}', (table) => {\n`
|
|
193
|
+
|
|
195
194
|
// Check if schema has an 'id' field with primary key
|
|
196
|
-
const hasIdField = schema.structure.id && schema.structure.id.primary === true
|
|
197
|
-
|
|
195
|
+
const hasIdField = schema.structure.id && schema.structure.id.primary === true
|
|
196
|
+
|
|
198
197
|
// Add auto-incrementing ID if needed
|
|
199
198
|
if (!hasIdField && autoIncrement) {
|
|
200
|
-
migration +=
|
|
199
|
+
migration += ' table.increments(\'id\').primary();\n'
|
|
201
200
|
}
|
|
202
|
-
|
|
201
|
+
|
|
203
202
|
// Process each field
|
|
204
203
|
for (const [fieldName, definition] of Object.entries(schema.structure)) {
|
|
205
204
|
if (fieldName === 'id' && !hasIdField && autoIncrement) {
|
|
206
|
-
continue
|
|
205
|
+
continue
|
|
207
206
|
}
|
|
208
|
-
|
|
209
|
-
let line = ' '
|
|
210
|
-
|
|
207
|
+
|
|
208
|
+
let line = ' '
|
|
209
|
+
|
|
211
210
|
// Map type
|
|
212
211
|
switch (definition.type) {
|
|
213
212
|
case 'string':
|
|
214
|
-
line += definition.maxLength
|
|
213
|
+
line += definition.maxLength
|
|
215
214
|
? `table.string('${fieldName}', ${definition.maxLength})`
|
|
216
|
-
: `table.string('${fieldName}')
|
|
217
|
-
break
|
|
215
|
+
: `table.string('${fieldName}')`
|
|
216
|
+
break
|
|
218
217
|
case 'number':
|
|
219
218
|
if (definition.precision !== undefined && definition.scale !== undefined) {
|
|
220
|
-
line += `table.decimal('${fieldName}', ${definition.precision}, ${definition.scale})
|
|
219
|
+
line += `table.decimal('${fieldName}', ${definition.precision}, ${definition.scale})`
|
|
221
220
|
} else {
|
|
222
|
-
line += `table.float('${fieldName}')
|
|
221
|
+
line += `table.float('${fieldName}')`
|
|
223
222
|
}
|
|
224
|
-
break
|
|
223
|
+
break
|
|
225
224
|
case 'id':
|
|
226
|
-
line += `table.integer('${fieldName}')
|
|
227
|
-
if (definition.unsigned !== false) line += '.unsigned()'
|
|
228
|
-
break
|
|
225
|
+
line += `table.integer('${fieldName}')`
|
|
226
|
+
if (definition.unsigned !== false) line += '.unsigned()'
|
|
227
|
+
break
|
|
229
228
|
case 'boolean':
|
|
230
|
-
line += `table.boolean('${fieldName}')
|
|
231
|
-
break
|
|
229
|
+
line += `table.boolean('${fieldName}')`
|
|
230
|
+
break
|
|
232
231
|
case 'date':
|
|
233
|
-
line += `table.date('${fieldName}')
|
|
234
|
-
break
|
|
232
|
+
line += `table.date('${fieldName}')`
|
|
233
|
+
break
|
|
235
234
|
case 'dateTime':
|
|
236
|
-
line += `table.datetime('${fieldName}')
|
|
237
|
-
break
|
|
235
|
+
line += `table.datetime('${fieldName}')`
|
|
236
|
+
break
|
|
238
237
|
case 'time':
|
|
239
|
-
line += `table.time('${fieldName}')
|
|
240
|
-
break
|
|
238
|
+
line += `table.time('${fieldName}')`
|
|
239
|
+
break
|
|
241
240
|
case 'timestamp':
|
|
242
|
-
line += `table.integer('${fieldName}')
|
|
243
|
-
break
|
|
241
|
+
line += `table.integer('${fieldName}')`
|
|
242
|
+
break
|
|
244
243
|
case 'array':
|
|
245
244
|
case 'object':
|
|
246
245
|
case 'serialize':
|
|
247
|
-
line += `table.json('${fieldName}')
|
|
248
|
-
break
|
|
246
|
+
line += `table.json('${fieldName}')`
|
|
247
|
+
break
|
|
249
248
|
case 'blob':
|
|
250
249
|
case 'file':
|
|
251
|
-
line += `table.binary('${fieldName}')
|
|
252
|
-
break
|
|
250
|
+
line += `table.binary('${fieldName}')`
|
|
251
|
+
break
|
|
253
252
|
default:
|
|
254
|
-
line += `table.string('${fieldName}')
|
|
253
|
+
line += `table.string('${fieldName}')`
|
|
255
254
|
}
|
|
256
|
-
|
|
255
|
+
|
|
257
256
|
// Add constraints
|
|
258
257
|
if (definition.nullable === true) {
|
|
259
|
-
line += '.nullable()'
|
|
258
|
+
line += '.nullable()'
|
|
260
259
|
} else if (definition.required === true || definition.nullable === false) {
|
|
261
|
-
line += '.notNullable()'
|
|
260
|
+
line += '.notNullable()'
|
|
262
261
|
}
|
|
263
|
-
|
|
262
|
+
|
|
264
263
|
if (definition.defaultTo !== undefined) {
|
|
265
|
-
const defaultValue = typeof definition.defaultTo === 'string'
|
|
266
|
-
? `'${definition.defaultTo}'`
|
|
267
|
-
: definition.defaultTo
|
|
268
|
-
line += `.defaultTo(${defaultValue})
|
|
264
|
+
const defaultValue = typeof definition.defaultTo === 'string'
|
|
265
|
+
? `'${definition.defaultTo}'`
|
|
266
|
+
: definition.defaultTo
|
|
267
|
+
line += `.defaultTo(${defaultValue})`
|
|
269
268
|
}
|
|
270
|
-
|
|
271
|
-
if (definition.unique === true) line += '.unique()'
|
|
272
|
-
if (definition.primary === true) line += '.primary()'
|
|
273
|
-
if (definition.index === true) line += '.index()'
|
|
274
|
-
|
|
269
|
+
|
|
270
|
+
if (definition.unique === true) line += '.unique()'
|
|
271
|
+
if (definition.primary === true) line += '.primary()'
|
|
272
|
+
if (definition.index === true) line += '.index()'
|
|
273
|
+
|
|
275
274
|
if (definition.references) {
|
|
276
|
-
const refCol = definition.references.column || 'id'
|
|
277
|
-
line += `.references('${refCol}').inTable('${definition.references.table}')
|
|
275
|
+
const refCol = definition.references.column || 'id'
|
|
276
|
+
line += `.references('${refCol}').inTable('${definition.references.table}')`
|
|
278
277
|
if (definition.references.onDelete) {
|
|
279
|
-
line += `.onDelete('${definition.references.onDelete}')
|
|
278
|
+
line += `.onDelete('${definition.references.onDelete}')`
|
|
280
279
|
}
|
|
281
280
|
if (definition.references.onUpdate) {
|
|
282
|
-
line += `.onUpdate('${definition.references.onUpdate}')
|
|
281
|
+
line += `.onUpdate('${definition.references.onUpdate}')`
|
|
283
282
|
}
|
|
284
283
|
}
|
|
285
|
-
|
|
284
|
+
|
|
286
285
|
if (definition.comment) {
|
|
287
|
-
line += `.comment('${definition.comment}')
|
|
286
|
+
line += `.comment('${definition.comment}')`
|
|
288
287
|
}
|
|
289
|
-
|
|
290
|
-
migration += line + ';\n'
|
|
288
|
+
|
|
289
|
+
migration += line + ';\n'
|
|
291
290
|
}
|
|
292
|
-
|
|
291
|
+
|
|
293
292
|
if (timestamps) {
|
|
294
|
-
migration +=
|
|
293
|
+
migration += ' table.timestamps(true, true);\n'
|
|
295
294
|
}
|
|
296
|
-
|
|
295
|
+
|
|
297
296
|
migration += ` });
|
|
298
297
|
};
|
|
299
298
|
|
|
300
299
|
exports.down = function(knex) {
|
|
301
300
|
return knex.schema.dropTable('${tableName}');
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
return migration
|
|
305
|
-
}
|
|
301
|
+
};`
|
|
302
|
+
|
|
303
|
+
return migration
|
|
304
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { requirePackage } from 'hooked-api'
|
|
1
|
+
import { requirePackage } from 'hooked-api'
|
|
2
2
|
|
|
3
|
-
let generateKeyBetween
|
|
3
|
+
let generateKeyBetween
|
|
4
4
|
try {
|
|
5
|
-
({ generateKeyBetween } = await import('fractional-indexing'))
|
|
5
|
+
({ generateKeyBetween } = await import('fractional-indexing'))
|
|
6
6
|
} catch (e) {
|
|
7
|
-
requirePackage('fractional-indexing', 'positioning',
|
|
8
|
-
'Fractional indexing is required for the positioning plugin to generate sortable position keys. This is a peer dependency.')
|
|
7
|
+
requirePackage('fractional-indexing', 'positioning',
|
|
8
|
+
'Fractional indexing is required for the positioning plugin to generate sortable position keys. This is a peer dependency.')
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
/**
|
|
@@ -21,55 +21,53 @@ try {
|
|
|
21
21
|
* @param {string} positionField - Name of the position field
|
|
22
22
|
* @returns {string} New position key
|
|
23
23
|
*/
|
|
24
|
-
export function calculatePosition(items, beforeId, idField, positionField) {
|
|
24
|
+
export function calculatePosition (items, beforeId, idField, positionField) {
|
|
25
25
|
// Sort items by position using simple string comparison
|
|
26
26
|
// Important: fractional-indexing expects ASCII/Unicode ordering, not locale-specific
|
|
27
27
|
const sorted = [...items].sort((a, b) => {
|
|
28
|
-
const posA = a[positionField] || ''
|
|
29
|
-
const posB = b[positionField] || ''
|
|
30
|
-
return posA < posB ? -1 : posA > posB ? 1 : 0
|
|
31
|
-
})
|
|
32
|
-
|
|
28
|
+
const posA = a[positionField] || ''
|
|
29
|
+
const posB = b[positionField] || ''
|
|
30
|
+
return posA < posB ? -1 : posA > posB ? 1 : 0
|
|
31
|
+
})
|
|
33
32
|
|
|
34
33
|
// If no items, start in the middle
|
|
35
34
|
if (sorted.length === 0) {
|
|
36
|
-
return generateKeyBetween(null, null)
|
|
35
|
+
return generateKeyBetween(null, null) // Returns 'a0'
|
|
37
36
|
}
|
|
38
37
|
|
|
39
38
|
// Position at end
|
|
40
39
|
if (beforeId === null || beforeId === undefined) {
|
|
41
|
-
const lastItem = sorted[sorted.length - 1]
|
|
42
|
-
return generateKeyBetween(lastItem[positionField], null)
|
|
40
|
+
const lastItem = sorted[sorted.length - 1]
|
|
41
|
+
return generateKeyBetween(lastItem[positionField], null)
|
|
43
42
|
}
|
|
44
43
|
|
|
45
44
|
// Find the item to position before
|
|
46
|
-
const beforeIndex = sorted.findIndex(item => String(item[idField]) === String(beforeId))
|
|
47
|
-
|
|
45
|
+
const beforeIndex = sorted.findIndex(item => String(item[idField]) === String(beforeId))
|
|
46
|
+
|
|
48
47
|
// If beforeId not found, position at end
|
|
49
48
|
if (beforeIndex === -1) {
|
|
50
|
-
const lastItem = sorted[sorted.length - 1]
|
|
51
|
-
return generateKeyBetween(lastItem[positionField], null)
|
|
49
|
+
const lastItem = sorted[sorted.length - 1]
|
|
50
|
+
return generateKeyBetween(lastItem[positionField], null)
|
|
52
51
|
}
|
|
53
52
|
|
|
54
53
|
// Position before the found item
|
|
55
|
-
const beforeItem = sorted[beforeIndex]
|
|
56
|
-
const prevItem = beforeIndex > 0 ? sorted[beforeIndex - 1] : null
|
|
57
|
-
|
|
54
|
+
const beforeItem = sorted[beforeIndex]
|
|
55
|
+
const prevItem = beforeIndex > 0 ? sorted[beforeIndex - 1] : null
|
|
56
|
+
|
|
58
57
|
const result = generateKeyBetween(
|
|
59
58
|
prevItem ? prevItem[positionField] : null,
|
|
60
59
|
beforeItem[positionField]
|
|
61
|
-
)
|
|
60
|
+
)
|
|
62
61
|
|
|
63
|
-
|
|
64
|
-
return result;
|
|
62
|
+
return result
|
|
65
63
|
}
|
|
66
64
|
|
|
67
65
|
/**
|
|
68
66
|
* Initialize position for first item in a group
|
|
69
67
|
* @returns {string} Initial position key
|
|
70
68
|
*/
|
|
71
|
-
export function getInitialPosition() {
|
|
72
|
-
return generateKeyBetween(null, null)
|
|
69
|
+
export function getInitialPosition () {
|
|
70
|
+
return generateKeyBetween(null, null) // Returns 'a0'
|
|
73
71
|
}
|
|
74
72
|
|
|
75
73
|
/**
|
|
@@ -79,40 +77,40 @@ export function getInitialPosition() {
|
|
|
79
77
|
* @param {number} maxLength - Maximum position string length before rebalancing
|
|
80
78
|
* @returns {Array} Items with new positions (or original if no rebalance needed)
|
|
81
79
|
*/
|
|
82
|
-
export function rebalancePositions(items, positionField, maxLength = 50) {
|
|
80
|
+
export function rebalancePositions (items, positionField, maxLength = 50) {
|
|
83
81
|
// Check if rebalancing is needed
|
|
84
|
-
const needsRebalance = items.some(item =>
|
|
82
|
+
const needsRebalance = items.some(item =>
|
|
85
83
|
item[positionField] && item[positionField].length > maxLength
|
|
86
|
-
)
|
|
87
|
-
|
|
84
|
+
)
|
|
85
|
+
|
|
88
86
|
if (!needsRebalance) {
|
|
89
|
-
return items
|
|
87
|
+
return items
|
|
90
88
|
}
|
|
91
89
|
|
|
92
90
|
// Sort by current position
|
|
93
91
|
const sorted = [...items].sort((a, b) => {
|
|
94
|
-
const posA = a[positionField] || ''
|
|
95
|
-
const posB = b[positionField] || ''
|
|
96
|
-
return posA < posB ? -1 : posA > posB ? 1 : 0
|
|
97
|
-
})
|
|
92
|
+
const posA = a[positionField] || ''
|
|
93
|
+
const posB = b[positionField] || ''
|
|
94
|
+
return posA < posB ? -1 : posA > posB ? 1 : 0
|
|
95
|
+
})
|
|
98
96
|
|
|
99
97
|
// Generate evenly spaced positions
|
|
100
|
-
const rebalanced = []
|
|
101
|
-
let prevKey = null
|
|
102
|
-
|
|
98
|
+
const rebalanced = []
|
|
99
|
+
let prevKey = null
|
|
100
|
+
|
|
103
101
|
for (let i = 0; i < sorted.length; i++) {
|
|
104
102
|
// For even spacing, we generate keys sequentially
|
|
105
|
-
const newPosition = generateKeyBetween(prevKey, null)
|
|
106
|
-
|
|
103
|
+
const newPosition = generateKeyBetween(prevKey, null)
|
|
104
|
+
|
|
107
105
|
rebalanced.push({
|
|
108
106
|
...sorted[i],
|
|
109
107
|
[positionField]: newPosition
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
prevKey = newPosition
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
prevKey = newPosition
|
|
113
111
|
}
|
|
114
112
|
|
|
115
|
-
return rebalanced
|
|
113
|
+
return rebalanced
|
|
116
114
|
}
|
|
117
115
|
|
|
118
116
|
/**
|
|
@@ -120,13 +118,13 @@ export function rebalancePositions(items, positionField, maxLength = 50) {
|
|
|
120
118
|
* @param {string} position - Position value to check
|
|
121
119
|
* @returns {boolean} True if valid
|
|
122
120
|
*/
|
|
123
|
-
export function isValidPosition(position) {
|
|
121
|
+
export function isValidPosition (position) {
|
|
124
122
|
if (!position || typeof position !== 'string') {
|
|
125
|
-
return false
|
|
123
|
+
return false
|
|
126
124
|
}
|
|
127
|
-
|
|
125
|
+
|
|
128
126
|
// Fractional keys should match pattern: lowercase letters and digits
|
|
129
|
-
return /^[a-z0-9]+$/i.test(position)
|
|
127
|
+
return /^[a-z0-9]+$/i.test(position)
|
|
130
128
|
}
|
|
131
129
|
|
|
132
130
|
/**
|
|
@@ -135,8 +133,8 @@ export function isValidPosition(position) {
|
|
|
135
133
|
* @param {string} positionField - Name of the position field
|
|
136
134
|
* @returns {Array} Items without valid positions
|
|
137
135
|
*/
|
|
138
|
-
export function getUnpositionedItems(items, positionField) {
|
|
139
|
-
return items.filter(item => !isValidPosition(item[positionField]))
|
|
136
|
+
export function getUnpositionedItems (items, positionField) {
|
|
137
|
+
return items.filter(item => !isValidPosition(item[positionField]))
|
|
140
138
|
}
|
|
141
139
|
|
|
142
140
|
/**
|
|
@@ -146,45 +144,45 @@ export function getUnpositionedItems(items, positionField) {
|
|
|
146
144
|
* @param {string} orderByField - Optional field to order by initially
|
|
147
145
|
* @returns {Array} Items with positions assigned
|
|
148
146
|
*/
|
|
149
|
-
export function assignInitialPositions(items, positionField, orderByField = null) {
|
|
150
|
-
const positioned = items.filter(item => isValidPosition(item[positionField]))
|
|
151
|
-
const unpositioned = items.filter(item => !isValidPosition(item[positionField]))
|
|
152
|
-
|
|
147
|
+
export function assignInitialPositions (items, positionField, orderByField = null) {
|
|
148
|
+
const positioned = items.filter(item => isValidPosition(item[positionField]))
|
|
149
|
+
const unpositioned = items.filter(item => !isValidPosition(item[positionField]))
|
|
150
|
+
|
|
153
151
|
if (unpositioned.length === 0) {
|
|
154
|
-
return items
|
|
152
|
+
return items
|
|
155
153
|
}
|
|
156
154
|
|
|
157
155
|
// Sort unpositioned items if orderBy field provided
|
|
158
156
|
if (orderByField) {
|
|
159
157
|
unpositioned.sort((a, b) => {
|
|
160
|
-
const valA = a[orderByField]
|
|
161
|
-
const valB = b[orderByField]
|
|
162
|
-
if (valA < valB) return -1
|
|
163
|
-
if (valA > valB) return 1
|
|
164
|
-
return 0
|
|
165
|
-
})
|
|
158
|
+
const valA = a[orderByField]
|
|
159
|
+
const valB = b[orderByField]
|
|
160
|
+
if (valA < valB) return -1
|
|
161
|
+
if (valA > valB) return 1
|
|
162
|
+
return 0
|
|
163
|
+
})
|
|
166
164
|
}
|
|
167
165
|
|
|
168
166
|
// Get the last positioned item
|
|
169
167
|
const sortedPositioned = [...positioned].sort((a, b) => {
|
|
170
|
-
const posA = a[positionField] || ''
|
|
171
|
-
const posB = b[positionField] || ''
|
|
172
|
-
return posA < posB ? -1 : posA > posB ? 1 : 0
|
|
173
|
-
})
|
|
174
|
-
const lastPositioned = sortedPositioned[sortedPositioned.length - 1]
|
|
175
|
-
|
|
176
|
-
let lastKey = lastPositioned ? lastPositioned[positionField] : null
|
|
177
|
-
|
|
168
|
+
const posA = a[positionField] || ''
|
|
169
|
+
const posB = b[positionField] || ''
|
|
170
|
+
return posA < posB ? -1 : posA > posB ? 1 : 0
|
|
171
|
+
})
|
|
172
|
+
const lastPositioned = sortedPositioned[sortedPositioned.length - 1]
|
|
173
|
+
|
|
174
|
+
let lastKey = lastPositioned ? lastPositioned[positionField] : null
|
|
175
|
+
|
|
178
176
|
// Assign positions to unpositioned items
|
|
179
177
|
const newlyPositioned = unpositioned.map(item => {
|
|
180
|
-
const newKey = generateKeyBetween(lastKey, null)
|
|
181
|
-
lastKey = newKey
|
|
182
|
-
|
|
178
|
+
const newKey = generateKeyBetween(lastKey, null)
|
|
179
|
+
lastKey = newKey
|
|
180
|
+
|
|
183
181
|
return {
|
|
184
182
|
...item,
|
|
185
183
|
[positionField]: newKey
|
|
186
|
-
}
|
|
187
|
-
})
|
|
184
|
+
}
|
|
185
|
+
})
|
|
188
186
|
|
|
189
|
-
return [...positioned, ...newlyPositioned]
|
|
190
|
-
}
|
|
187
|
+
return [...positioned, ...newlyPositioned]
|
|
188
|
+
}
|