json-rest-api 1.0.21 → 1.0.23
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/CLAUDE.md +1 -2
- package/JSKIT_BACKEND_MIGRATION_TRACKER.txt +180 -0
- package/SINGLE_VALIDATION_CONTRACT_TODO.txt +172 -0
- package/docs/API.md +51 -14
- package/docs/COMPARISON.md +2 -2
- package/docs/GUIDE/GUIDE_1_Initial_Setup.md +28 -1
- package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +5 -1
- package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +3 -1
- package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +12 -60
- package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +2 -2
- package/docs/GUIDE/GUIDE_3_Field_Transformations.md +5 -1
- package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +2 -0
- package/docs/GUIDE/GUIDE_X_Autofiltering.md +355 -0
- package/docs/GUIDE/GUIDE_X_Fastify.md +78 -0
- package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +250 -0
- package/docs/GUIDE/GUIDE_X_Query_Projections.md +159 -0
- package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +199 -0
- package/docs/GUIDE/index.md +18 -9
- package/docs/QUICKSTART.md +5 -1
- package/docs/index.md +1 -1
- package/index.js +3 -2
- package/package.json +4 -4
- package/plugins/core/connectors/express-plugin.js +97 -184
- package/plugins/core/connectors/fastify-plugin.js +206 -0
- package/plugins/core/connectors/lib/connector-core.js +195 -0
- package/plugins/core/connectors/lib/transport-http-helpers.js +122 -0
- package/plugins/core/connectors/lib/transport-route-schemas.js +92 -0
- package/plugins/core/lib/dbIntrospection.js +1078 -0
- package/plugins/core/lib/dbTablesOperations.js +1104 -160
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +5 -2
- package/plugins/core/lib/querying/knex-process-includes.js +3 -4
- package/plugins/core/lib/querying/knex-query-helpers.js +17 -71
- package/plugins/core/lib/querying/knex-relationship-includes.js +466 -206
- package/plugins/core/lib/querying/query-builder-utils.js +19 -0
- package/plugins/core/lib/querying/query-field-sort-helpers.js +135 -0
- package/plugins/core/lib/querying/sort-helpers.js +33 -0
- package/plugins/core/lib/querying/storage-adapter-utils.js +51 -0
- package/plugins/core/lib/querying/url-helpers.js +8 -2
- package/plugins/core/lib/querying-writing/compile-schemas.js +12 -1
- package/plugins/core/lib/querying-writing/knex-field-helpers.js +83 -13
- package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +13 -12
- package/plugins/core/lib/querying-writing/query-field-helpers.js +88 -0
- package/plugins/core/lib/querying-writing/request-contracts.js +590 -0
- package/plugins/core/lib/storage/canonical-storage-mapping.js +113 -0
- package/plugins/core/lib/storage/storage-adapter.js +79 -2
- package/plugins/core/lib/storage/storage-mapping.js +204 -0
- package/plugins/core/rest-api-anyapi-knex-plugin.js +355 -228
- package/plugins/core/rest-api-autofilter-plugin.js +414 -0
- package/plugins/core/rest-api-knex-plugin.js +249 -211
- package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +10 -5
- package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +12 -6
- package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +41 -0
- package/plugins/core/rest-api-plugin-methods/common.js +121 -83
- package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +6 -1
- package/plugins/core/rest-api-plugin-methods/get.js +23 -14
- package/plugins/core/rest-api-plugin-methods/patch.js +28 -12
- package/plugins/core/rest-api-plugin-methods/post.js +13 -8
- package/plugins/core/rest-api-plugin-methods/put.js +45 -21
- package/plugins/core/rest-api-plugin-methods/query.js +15 -53
- package/plugins/core/rest-api-plugin.js +2 -0
- package/plugins/core/rest-api-positioning-plugin.js +1 -1
- package/plugins/core/rest-api-query-projections-plugin.js +130 -0
- package/plugins/core/socketio-plugin.js +1 -3
- package/tests/autofilter.test.js +463 -0
- package/tests/custom-id-simplified-post.test.js +75 -0
- package/tests/custom-idproperty-relationships.test.js +33 -0
- package/tests/db-introspection.test.js +518 -0
- package/tests/db-table-operations.test.js +781 -0
- package/tests/fastify-plugin.test.js +141 -0
- package/tests/field-getters.test.js +1 -2
- package/tests/field-setters.test.js +0 -4
- package/tests/fixtures/api-configs.js +188 -219
- package/tests/helpers/fake-fastify.js +99 -0
- package/tests/http-connectors-parity.test.js +235 -0
- package/tests/projected-fields.test.js +319 -0
- package/tests/relationships.test.js +3 -3
- package/tests/searchschema-knex-behavior.test.js +187 -0
- package/tests/searchschema-merge.test.js +21 -69
- package/tests/storage-mapping.test.js +210 -0
- package/docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md +0 -707
- package/docs/GUIDE/GUIDE_X_Multihome.md +0 -666
- package/plugins/core/lib/querying-writing/payload-validators.js +0 -1314
- package/plugins/core/rest-api-access.js +0 -459
- package/plugins/core/rest-api-multihome-plugin.js +0 -314
- package/tests/multihome.test.js +0 -553
- package/tests/multihome.test.js.bak +0 -550
- package/tests/ownership-optout.test.js +0 -76
- package/tests/rest-api-access.test.js +0 -206
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
import { RestApiValidationError } from '../../lib/rest-api-errors.js'
|
|
2
|
+
import { createStorageAdapterUtilities } from './lib/querying/storage-adapter-utils.js'
|
|
3
|
+
|
|
4
|
+
const PUBLIC_PRESET_NAME = 'public'
|
|
5
|
+
|
|
6
|
+
function valuesEqual (left, right) {
|
|
7
|
+
if (left === right) return true
|
|
8
|
+
if (left === null || right === null) return left === right
|
|
9
|
+
if (left === undefined || right === undefined) return left === right
|
|
10
|
+
return String(left) === String(right)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function buildAutofilterContextError ({ scopeName, filter }) {
|
|
14
|
+
const resolverName = filter.resolverName || '<inline>'
|
|
15
|
+
const error = new Error(
|
|
16
|
+
`Missing autofilter value for resolver '${resolverName}' on resource '${scopeName}'`
|
|
17
|
+
)
|
|
18
|
+
error.code = 'REST_API_AUTOFILTER_CONTEXT'
|
|
19
|
+
return error
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function buildAutofilterConsistencyError ({ filter, resolvedValue }) {
|
|
23
|
+
const fieldPath = filter.inputPath
|
|
24
|
+
const expectedValue = resolvedValue === null ? 'null' : String(resolvedValue)
|
|
25
|
+
|
|
26
|
+
return new RestApiValidationError(
|
|
27
|
+
`Scoped field '${filter.field}' must match the resolved autofilter value`,
|
|
28
|
+
{
|
|
29
|
+
fields: [fieldPath],
|
|
30
|
+
violations: [{
|
|
31
|
+
field: fieldPath,
|
|
32
|
+
rule: 'autofilter_consistency',
|
|
33
|
+
message: `Field must match autofilter value '${expectedValue}'`
|
|
34
|
+
}]
|
|
35
|
+
}
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function ensureResolverMap (resolverMap = {}) {
|
|
40
|
+
const normalized = {}
|
|
41
|
+
|
|
42
|
+
for (const [name, resolver] of Object.entries(resolverMap)) {
|
|
43
|
+
if (typeof resolver !== 'function') {
|
|
44
|
+
throw new Error(`AutoFilter resolver '${name}' must be a function.`)
|
|
45
|
+
}
|
|
46
|
+
normalized[name] = resolver
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return normalized
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function getPresetFilters (presetName, presets) {
|
|
53
|
+
const preset = presets[presetName]
|
|
54
|
+
|
|
55
|
+
if (!preset) {
|
|
56
|
+
throw new Error(`Unknown autofilter preset '${presetName}'.`)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (Array.isArray(preset)) {
|
|
60
|
+
return preset
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (typeof preset === 'object' && preset !== null && Array.isArray(preset.filters)) {
|
|
64
|
+
return preset.filters
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
throw new Error(`Autofilter preset '${presetName}' must be an array or an object with a filters array.`)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function normalizeFilterDefinition ({ filterDef, resolvers, schemaStructure, scopeName }) {
|
|
71
|
+
if (!filterDef || typeof filterDef !== 'object' || Array.isArray(filterDef)) {
|
|
72
|
+
throw new Error(`Invalid autofilter definition on resource '${scopeName}'.`)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const field = String(filterDef.field || '').trim()
|
|
76
|
+
if (!field) {
|
|
77
|
+
throw new Error(`Autofilter definitions on resource '${scopeName}' must include a field.`)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const fieldDef = schemaStructure[field]
|
|
81
|
+
if (!fieldDef) {
|
|
82
|
+
throw new Error(`Autofilter field '${field}' does not exist on resource '${scopeName}'.`)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (fieldDef.virtual === true || fieldDef.computed === true) {
|
|
86
|
+
throw new Error(`Autofilter field '${field}' on resource '${scopeName}' must be a persisted field.`)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const resolverDef = filterDef.resolve ?? filterDef.resolver
|
|
90
|
+
let resolve
|
|
91
|
+
let resolverName
|
|
92
|
+
|
|
93
|
+
if (typeof resolverDef === 'string') {
|
|
94
|
+
resolve = resolvers[resolverDef]
|
|
95
|
+
resolverName = resolverDef
|
|
96
|
+
if (!resolve) {
|
|
97
|
+
throw new Error(`Unknown autofilter resolver '${resolverDef}' on resource '${scopeName}'.`)
|
|
98
|
+
}
|
|
99
|
+
} else if (typeof resolverDef === 'function') {
|
|
100
|
+
resolve = resolverDef
|
|
101
|
+
resolverName = '<inline>'
|
|
102
|
+
} else {
|
|
103
|
+
throw new Error(`Autofilter field '${field}' on resource '${scopeName}' must define a resolver.`)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const relationshipName = fieldDef.belongsTo && fieldDef.as
|
|
107
|
+
? fieldDef.as
|
|
108
|
+
: null
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
field,
|
|
112
|
+
fieldDef,
|
|
113
|
+
resolve,
|
|
114
|
+
resolverName,
|
|
115
|
+
required: filterDef.required !== false,
|
|
116
|
+
relationshipName,
|
|
117
|
+
relationshipType: relationshipName ? fieldDef.belongsTo : null,
|
|
118
|
+
inputPath: relationshipName
|
|
119
|
+
? `data.relationships.${relationshipName}.data.id`
|
|
120
|
+
: `data.attributes.${field}`
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function compileAutoFilterDefinition ({ scopeName, scopeOptions = {}, schemaStructure = {}, resolvers, presets }) {
|
|
125
|
+
const definition = scopeOptions.autofilter
|
|
126
|
+
|
|
127
|
+
if (definition === undefined || definition === null || definition === false) {
|
|
128
|
+
return null
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
let presetName = null
|
|
132
|
+
const rawFilters = []
|
|
133
|
+
|
|
134
|
+
if (typeof definition === 'string') {
|
|
135
|
+
presetName = definition
|
|
136
|
+
rawFilters.push(...getPresetFilters(presetName, presets))
|
|
137
|
+
} else if (Array.isArray(definition)) {
|
|
138
|
+
rawFilters.push(...definition)
|
|
139
|
+
} else if (typeof definition === 'object') {
|
|
140
|
+
if (definition.preset) {
|
|
141
|
+
presetName = definition.preset
|
|
142
|
+
rawFilters.push(...getPresetFilters(presetName, presets))
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (Array.isArray(definition.filters)) {
|
|
146
|
+
rawFilters.push(...definition.filters)
|
|
147
|
+
} else if (!definition.preset) {
|
|
148
|
+
throw new Error(`Autofilter definition for resource '${scopeName}' must include a filters array or preset.`)
|
|
149
|
+
}
|
|
150
|
+
} else {
|
|
151
|
+
throw new Error(`Invalid autofilter definition for resource '${scopeName}'.`)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
preset: presetName,
|
|
156
|
+
filters: rawFilters.map((filterDef) => normalizeFilterDefinition({
|
|
157
|
+
filterDef,
|
|
158
|
+
resolvers,
|
|
159
|
+
schemaStructure,
|
|
160
|
+
scopeName
|
|
161
|
+
}))
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async function resolveFilterValue ({ filter, context, scopeName, api, helpers, scopes, vars, log }) {
|
|
166
|
+
const value = await filter.resolve({
|
|
167
|
+
context,
|
|
168
|
+
scopeName,
|
|
169
|
+
filter,
|
|
170
|
+
api,
|
|
171
|
+
helpers,
|
|
172
|
+
scopes,
|
|
173
|
+
vars,
|
|
174
|
+
log
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
if (value === undefined && filter.required) {
|
|
178
|
+
throw buildAutofilterContextError({ scopeName, filter })
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return value
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function ensureInputContainers (context) {
|
|
185
|
+
const data = context.inputRecord.data
|
|
186
|
+
data.attributes = data.attributes || {}
|
|
187
|
+
data.relationships = data.relationships || {}
|
|
188
|
+
return data
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function applyResolvedInputValue ({ context, filter, resolvedValue, injectMissing }) {
|
|
192
|
+
const data = ensureInputContainers(context)
|
|
193
|
+
const { attributes, relationships } = data
|
|
194
|
+
|
|
195
|
+
if (filter.relationshipName) {
|
|
196
|
+
const relationship = relationships[filter.relationshipName]
|
|
197
|
+
const relationshipData = relationship?.data
|
|
198
|
+
|
|
199
|
+
if (relationshipData !== undefined) {
|
|
200
|
+
if (relationshipData === null) {
|
|
201
|
+
if (resolvedValue !== null) {
|
|
202
|
+
throw buildAutofilterConsistencyError({ filter, resolvedValue })
|
|
203
|
+
}
|
|
204
|
+
return
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (Array.isArray(relationshipData) || !valuesEqual(relationshipData?.id, resolvedValue)) {
|
|
208
|
+
throw buildAutofilterConsistencyError({ filter, resolvedValue })
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (relationshipData?.type && filter.relationshipType && relationshipData.type !== filter.relationshipType) {
|
|
212
|
+
throw buildAutofilterConsistencyError({ filter, resolvedValue })
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (attributes[filter.field] !== undefined && !valuesEqual(attributes[filter.field], resolvedValue)) {
|
|
219
|
+
throw buildAutofilterConsistencyError({ filter, resolvedValue })
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (injectMissing) {
|
|
223
|
+
relationships[filter.relationshipName] = resolvedValue === null
|
|
224
|
+
? { data: null }
|
|
225
|
+
: {
|
|
226
|
+
data: {
|
|
227
|
+
type: filter.relationshipType,
|
|
228
|
+
id: String(resolvedValue)
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const existingValue = attributes[filter.field]
|
|
237
|
+
if (existingValue !== undefined) {
|
|
238
|
+
if (!valuesEqual(existingValue, resolvedValue)) {
|
|
239
|
+
throw buildAutofilterConsistencyError({ filter, resolvedValue })
|
|
240
|
+
}
|
|
241
|
+
return
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (injectMissing) {
|
|
245
|
+
attributes[filter.field] = resolvedValue
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async function enforceScopedInput ({ compiledConfig, context, scopeName, injectMissing, api, helpers, scopes, vars, log }) {
|
|
250
|
+
if (!compiledConfig || compiledConfig.filters.length === 0) return
|
|
251
|
+
|
|
252
|
+
for (const filter of compiledConfig.filters) {
|
|
253
|
+
const resolvedValue = await resolveFilterValue({
|
|
254
|
+
filter,
|
|
255
|
+
context,
|
|
256
|
+
scopeName,
|
|
257
|
+
api,
|
|
258
|
+
helpers,
|
|
259
|
+
scopes,
|
|
260
|
+
vars,
|
|
261
|
+
log
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
if (resolvedValue === undefined) continue
|
|
265
|
+
|
|
266
|
+
applyResolvedInputValue({
|
|
267
|
+
context,
|
|
268
|
+
filter,
|
|
269
|
+
resolvedValue,
|
|
270
|
+
injectMissing
|
|
271
|
+
})
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export const AutoFilterPlugin = {
|
|
276
|
+
name: 'autofilter',
|
|
277
|
+
dependencies: ['rest-api', 'rest-api-knex|rest-api-anyapi-knex'],
|
|
278
|
+
|
|
279
|
+
install ({ api, addHook, vars, helpers, log, scopes, pluginOptions = {} }) {
|
|
280
|
+
if (!api.knex?.instance) {
|
|
281
|
+
throw new Error('AutoFilterPlugin requires a storage plugin with knex support (rest-api-knex or rest-api-anyapi-knex).')
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const state = {
|
|
285
|
+
resolvers: ensureResolverMap(pluginOptions.resolvers || {}),
|
|
286
|
+
presets: {
|
|
287
|
+
[PUBLIC_PRESET_NAME]: { filters: [] },
|
|
288
|
+
...(pluginOptions.presets || {})
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
vars.autofilter = {
|
|
293
|
+
presets: Object.keys(state.presets),
|
|
294
|
+
resolvers: Object.keys(state.resolvers)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
addHook('scope:added', 'compile-autofilter', {}, ({ context }) => {
|
|
298
|
+
const { scopeName, scopeOptions = {} } = context
|
|
299
|
+
const scope = scopes[scopeName]
|
|
300
|
+
const schemaStructure = scope?.vars?.schemaInfo?.schemaStructure || scopeOptions.schema || {}
|
|
301
|
+
|
|
302
|
+
scope.vars.autofilter = compileAutoFilterDefinition({
|
|
303
|
+
scopeName,
|
|
304
|
+
scopeOptions,
|
|
305
|
+
schemaStructure,
|
|
306
|
+
resolvers: state.resolvers,
|
|
307
|
+
presets: state.presets
|
|
308
|
+
})
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
addHook('knexQueryFiltering', 'autofilter-scope', {}, async ({ context }) => {
|
|
312
|
+
const { query, tableName, scopeName } = context.knexQuery || {}
|
|
313
|
+
if (!query || !scopeName) return
|
|
314
|
+
|
|
315
|
+
const compiledConfig = scopes[scopeName]?.vars?.autofilter
|
|
316
|
+
if (!compiledConfig || compiledConfig.filters.length === 0) return
|
|
317
|
+
const adapterUtils = createStorageAdapterUtilities({ context }, {
|
|
318
|
+
getStorageAdapter: helpers.getStorageAdapter
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
for (const filter of compiledConfig.filters) {
|
|
322
|
+
const resolvedValue = await resolveFilterValue({
|
|
323
|
+
filter,
|
|
324
|
+
context,
|
|
325
|
+
scopeName,
|
|
326
|
+
api,
|
|
327
|
+
helpers,
|
|
328
|
+
scopes,
|
|
329
|
+
vars,
|
|
330
|
+
log
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
if (resolvedValue === undefined) continue
|
|
334
|
+
|
|
335
|
+
const columnRef = adapterUtils.translateColumn(scopeName, filter.field, tableName)
|
|
336
|
+
const translatedValue = adapterUtils.translateFilterValue(scopeName, filter.field, resolvedValue)
|
|
337
|
+
|
|
338
|
+
if (translatedValue === null) {
|
|
339
|
+
query.whereNull(columnRef)
|
|
340
|
+
} else {
|
|
341
|
+
query.where(columnRef, translatedValue)
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
})
|
|
345
|
+
|
|
346
|
+
addHook('beforeProcessingPost', 'autofilter-stamp-post', {}, async ({ context, scopeName }) => {
|
|
347
|
+
await enforceScopedInput({
|
|
348
|
+
compiledConfig: scopes[scopeName]?.vars?.autofilter,
|
|
349
|
+
context,
|
|
350
|
+
scopeName,
|
|
351
|
+
injectMissing: true,
|
|
352
|
+
api,
|
|
353
|
+
helpers,
|
|
354
|
+
scopes,
|
|
355
|
+
vars,
|
|
356
|
+
log
|
|
357
|
+
})
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
addHook('beforeProcessingPut', 'autofilter-stamp-put', {}, async ({ context, scopeName }) => {
|
|
361
|
+
await enforceScopedInput({
|
|
362
|
+
compiledConfig: scopes[scopeName]?.vars?.autofilter,
|
|
363
|
+
context,
|
|
364
|
+
scopeName,
|
|
365
|
+
injectMissing: true,
|
|
366
|
+
api,
|
|
367
|
+
helpers,
|
|
368
|
+
scopes,
|
|
369
|
+
vars,
|
|
370
|
+
log
|
|
371
|
+
})
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
addHook('beforeProcessingPatch', 'autofilter-validate-patch', {}, async ({ context, scopeName }) => {
|
|
375
|
+
await enforceScopedInput({
|
|
376
|
+
compiledConfig: scopes[scopeName]?.vars?.autofilter,
|
|
377
|
+
context,
|
|
378
|
+
scopeName,
|
|
379
|
+
injectMissing: false,
|
|
380
|
+
api,
|
|
381
|
+
helpers,
|
|
382
|
+
scopes,
|
|
383
|
+
vars,
|
|
384
|
+
log
|
|
385
|
+
})
|
|
386
|
+
})
|
|
387
|
+
|
|
388
|
+
api.autofilter = {
|
|
389
|
+
getConfig: () => ({
|
|
390
|
+
presets: Object.keys(state.presets),
|
|
391
|
+
resolvers: Object.keys(state.resolvers)
|
|
392
|
+
}),
|
|
393
|
+
|
|
394
|
+
getScopeConfig: (scopeName) => {
|
|
395
|
+
const compiledConfig = scopes[scopeName]?.vars?.autofilter
|
|
396
|
+
if (!compiledConfig) return null
|
|
397
|
+
|
|
398
|
+
return {
|
|
399
|
+
preset: compiledConfig.preset,
|
|
400
|
+
filters: compiledConfig.filters.map((filter) => ({
|
|
401
|
+
field: filter.field,
|
|
402
|
+
resolver: filter.resolverName,
|
|
403
|
+
required: filter.required
|
|
404
|
+
}))
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
log.info('AutoFilter plugin installed', {
|
|
410
|
+
presets: Object.keys(state.presets),
|
|
411
|
+
resolvers: Object.keys(state.resolvers)
|
|
412
|
+
})
|
|
413
|
+
}
|
|
414
|
+
}
|