@nixxie-cms/core 1.0.3 → 2.0.0
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/CHANGELOG.md +36 -0
- package/CHANGES-1.1.md +134 -0
- package/context/dist/nixxie-cms-core-context.cjs.js +4 -3
- package/context/dist/nixxie-cms-core-context.esm.js +3 -2
- package/dist/declarations/src/access.d.ts +2 -2
- package/dist/declarations/src/access.d.ts.map +1 -1
- package/dist/declarations/src/admin-ui/components/Navigation.d.ts +2 -2
- package/dist/declarations/src/admin-ui/components/Navigation.d.ts.map +1 -1
- package/dist/declarations/src/admin-ui/context.d.ts +6 -6
- package/dist/declarations/src/admin-ui/context.d.ts.map +1 -1
- package/dist/declarations/src/admin-ui/utils/Fields.d.ts +3 -3
- package/dist/declarations/src/admin-ui/utils/Fields.d.ts.map +1 -1
- package/dist/declarations/src/admin-ui/utils/filters.d.ts +5 -5
- package/dist/declarations/src/admin-ui/utils/filters.d.ts.map +1 -1
- package/dist/declarations/src/admin-ui/utils/useCreateItem.d.ts +3 -3
- package/dist/declarations/src/admin-ui/utils/useCreateItem.d.ts.map +1 -1
- package/dist/declarations/src/admin-ui/utils/utils.d.ts +2 -2
- package/dist/declarations/src/admin-ui/utils/utils.d.ts.map +1 -1
- package/dist/declarations/src/context.d.ts +1 -1
- package/dist/declarations/src/context.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/bigInt/index.d.ts +3 -3
- package/dist/declarations/src/fields/types/bigInt/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/bytes/index.d.ts +3 -3
- package/dist/declarations/src/fields/types/bytes/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/calendarDay/index.d.ts +3 -3
- package/dist/declarations/src/fields/types/calendarDay/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/checkbox/index.d.ts +3 -3
- package/dist/declarations/src/fields/types/checkbox/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/decimal/index.d.ts +3 -3
- package/dist/declarations/src/fields/types/decimal/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/file/index.d.ts +4 -4
- package/dist/declarations/src/fields/types/file/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/float/index.d.ts +3 -3
- package/dist/declarations/src/fields/types/float/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/image/index.d.ts +4 -4
- package/dist/declarations/src/fields/types/image/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/integer/index.d.ts +3 -3
- package/dist/declarations/src/fields/types/integer/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/json/index.d.ts +3 -3
- package/dist/declarations/src/fields/types/json/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/multiselect/index.d.ts +3 -3
- package/dist/declarations/src/fields/types/multiselect/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/multiselect/views/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/password/index.d.ts +3 -3
- package/dist/declarations/src/fields/types/password/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/relationship/index.d.ts +8 -8
- package/dist/declarations/src/fields/types/relationship/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/relationship/views/ComboboxMany.d.ts +3 -3
- package/dist/declarations/src/fields/types/relationship/views/ComboboxMany.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/relationship/views/ComboboxSingle.d.ts +3 -3
- package/dist/declarations/src/fields/types/relationship/views/ComboboxSingle.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/relationship/views/index.d.ts +3 -3
- package/dist/declarations/src/fields/types/relationship/views/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/relationship/views/types.d.ts +3 -3
- package/dist/declarations/src/fields/types/relationship/views/types.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/select/index.d.ts +3 -3
- package/dist/declarations/src/fields/types/select/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/text/index.d.ts +3 -3
- package/dist/declarations/src/fields/types/text/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/timestamp/index.d.ts +3 -3
- package/dist/declarations/src/fields/types/timestamp/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/virtual/index.d.ts +7 -7
- package/dist/declarations/src/fields/types/virtual/index.d.ts.map +1 -1
- package/dist/declarations/src/helpers.d.ts +249 -13
- package/dist/declarations/src/helpers.d.ts.map +1 -1
- package/dist/declarations/src/index.d.ts +9 -4
- package/dist/declarations/src/index.d.ts.map +1 -1
- package/dist/declarations/src/internal-unstable/admin-ui/pages/ListPage/index.d.ts.map +1 -1
- package/dist/declarations/src/lib/admin-meta.d.ts +11 -11
- package/dist/declarations/src/lib/admin-meta.d.ts.map +1 -1
- package/dist/declarations/src/lib/core/access-control.d.ts +18 -18
- package/dist/declarations/src/lib/core/access-control.d.ts.map +1 -1
- package/dist/declarations/src/lib/core/cascade.d.ts +47 -0
- package/dist/declarations/src/lib/core/cascade.d.ts.map +1 -0
- package/dist/declarations/src/lib/core/initialise-lists.d.ts +27 -24
- package/dist/declarations/src/lib/core/initialise-lists.d.ts.map +1 -1
- package/dist/declarations/src/lib/env.d.ts +9 -0
- package/dist/declarations/src/lib/env.d.ts.map +1 -0
- package/dist/declarations/src/lib/system.d.ts +1 -1
- package/dist/declarations/src/lib/system.d.ts.map +1 -1
- package/dist/declarations/src/list-features.d.ts +162 -0
- package/dist/declarations/src/list-features.d.ts.map +1 -0
- package/dist/declarations/src/schema.d.ts +24 -23
- package/dist/declarations/src/schema.d.ts.map +1 -1
- package/dist/declarations/src/session.d.ts +75 -0
- package/dist/declarations/src/session.d.ts.map +1 -1
- package/dist/declarations/src/types/admin-meta.d.ts +11 -11
- package/dist/declarations/src/types/admin-meta.d.ts.map +1 -1
- package/dist/declarations/src/types/config/access-control.d.ts +42 -42
- package/dist/declarations/src/types/config/access-control.d.ts.map +1 -1
- package/dist/declarations/src/types/config/fields.d.ts +19 -19
- package/dist/declarations/src/types/config/fields.d.ts.map +1 -1
- package/dist/declarations/src/types/config/hooks.d.ts +131 -131
- package/dist/declarations/src/types/config/hooks.d.ts.map +1 -1
- package/dist/declarations/src/types/config/index.d.ts +190 -8
- package/dist/declarations/src/types/config/index.d.ts.map +1 -1
- package/dist/declarations/src/types/config/lists.d.ts +146 -108
- package/dist/declarations/src/types/config/lists.d.ts.map +1 -1
- package/dist/declarations/src/types/context.d.ts +507 -47
- package/dist/declarations/src/types/context.d.ts.map +1 -1
- package/dist/declarations/src/types/next-fields.d.ts +28 -28
- package/dist/declarations/src/types/next-fields.d.ts.map +1 -1
- package/dist/declarations/src/types/type-info.d.ts +3 -3
- package/dist/declarations/src/types/type-info.d.ts.map +1 -1
- package/dist/{express-455ae20c.cjs.js → express-84d534c2.cjs.js} +6 -6
- package/dist/{express-7559ca2d.esm.js → express-d0a4ce99.esm.js} +6 -6
- package/dist/{index-15c8f81e.esm.js → index-5d8b0b4e.esm.js} +363 -183
- package/dist/index-6055753b.cjs.js +393 -0
- package/dist/{index-42045902.cjs.js → index-ac29f382.cjs.js} +363 -185
- package/dist/index-f1703b7b.esm.js +386 -0
- package/dist/nixxie-cms-core.cjs.js +1388 -30
- package/dist/nixxie-cms-core.esm.js +1362 -24
- package/dist/{non-null-graphql-add6bb3d.cjs.js → non-null-graphql-4a44c122.cjs.js} +1 -1
- package/dist/{non-null-graphql-a84ed64d.esm.js → non-null-graphql-8c5feaae.esm.js} +1 -1
- package/dist/{resolve-hooks-165a9ce2.cjs.js → resolve-hooks-10a5f84c.cjs.js} +240 -6
- package/dist/{resolve-hooks-6813a045.esm.js → resolve-hooks-9e676794.esm.js} +238 -7
- package/dist/{system-a321642d.cjs.js → system-6b37a5f8.cjs.js} +33 -7
- package/dist/{system-03e49e4f.esm.js → system-e591d821.esm.js} +33 -7
- package/fields/dist/nixxie-cms-core-fields.cjs.js +29 -576
- package/fields/dist/nixxie-cms-core-fields.esm.js +18 -565
- package/fields/types/bytes/dist/nixxie-cms-core-fields-types-bytes.cjs.js +4 -2
- package/fields/types/bytes/dist/nixxie-cms-core-fields-types-bytes.esm.js +4 -2
- package/fields/types/multiselect/views/dist/nixxie-cms-core-fields-types-multiselect-views.cjs.js +1 -6
- package/fields/types/multiselect/views/dist/nixxie-cms-core-fields-types-multiselect-views.esm.js +1 -6
- package/fields/types/password/dist/nixxie-cms-core-fields-types-password.cjs.js +4 -2
- package/fields/types/password/dist/nixxie-cms-core-fields-types-password.esm.js +4 -2
- package/internal-unstable/artifacts/dist/nixxie-cms-core-internal-unstable-artifacts.cjs.js +4 -3
- package/internal-unstable/artifacts/dist/nixxie-cms-core-internal-unstable-artifacts.esm.js +4 -3
- package/package.json +4 -4
- package/scripts/cli/dist/nixxie-cms-core-scripts-cli.cjs.js +4 -3
- package/scripts/cli/dist/nixxie-cms-core-scripts-cli.esm.js +4 -3
- package/scripts/dist/nixxie-cms-core-scripts.cjs.js +4 -3
- package/scripts/dist/nixxie-cms-core-scripts.esm.js +4 -3
- package/session/dist/nixxie-cms-core-session.cjs.js +286 -0
- package/session/dist/nixxie-cms-core-session.esm.js +279 -1
- package/src/access.ts +25 -25
- package/src/admin-ui/admin-meta-graphql.ts +5 -5
- package/src/admin-ui/components/CreateButtonLink.tsx +46 -46
- package/src/admin-ui/components/Navigation.tsx +3 -3
- package/src/admin-ui/context.tsx +6 -6
- package/src/admin-ui/utils/Fields.tsx +241 -241
- package/src/admin-ui/utils/actionData.ts +36 -36
- package/src/admin-ui/utils/filters.ts +148 -148
- package/src/admin-ui/utils/useCreateItem.ts +171 -171
- package/src/admin-ui/utils/utils.tsx +127 -127
- package/src/context.ts +1 -1
- package/src/fields/non-null-graphql.ts +115 -115
- package/src/fields/types/bigInt/index.ts +6 -6
- package/src/fields/types/bytes/index.ts +6 -6
- package/src/fields/types/calendarDay/index.ts +18 -19
- package/src/fields/types/checkbox/index.ts +6 -6
- package/src/fields/types/decimal/index.ts +6 -6
- package/src/fields/types/file/index.ts +8 -8
- package/src/fields/types/float/index.ts +6 -6
- package/src/fields/types/image/index.ts +8 -8
- package/src/fields/types/integer/index.ts +6 -6
- package/src/fields/types/json/index.ts +5 -5
- package/src/fields/types/multiselect/index.ts +7 -7
- package/src/fields/types/multiselect/views/index.tsx +149 -151
- package/src/fields/types/password/index.ts +6 -6
- package/src/fields/types/relationship/index.ts +13 -13
- package/src/fields/types/relationship/views/ComboboxMany.tsx +110 -110
- package/src/fields/types/relationship/views/ComboboxSingle.tsx +115 -115
- package/src/fields/types/relationship/views/ContextualActions.tsx +139 -139
- package/src/fields/types/relationship/views/index.tsx +492 -492
- package/src/fields/types/relationship/views/types.ts +46 -46
- package/src/fields/types/relationship/views/useApolloQuery.ts +185 -185
- package/src/fields/types/relationship/views/useFilter.tsx +109 -109
- package/src/fields/types/select/index.ts +6 -6
- package/src/fields/types/text/index.ts +6 -6
- package/src/fields/types/timestamp/index.ts +23 -21
- package/src/fields/types/virtual/index.ts +11 -11
- package/src/helpers.ts +773 -42
- package/src/index.ts +66 -24
- package/src/internal-unstable/admin-ui/pages/ItemPage/common.tsx +4 -4
- package/src/internal-unstable/admin-ui/pages/ItemPage/index.tsx +5 -5
- package/src/internal-unstable/admin-ui/pages/ListPage/index.tsx +8 -8
- package/src/lib/admin-meta.ts +369 -369
- package/src/lib/context/createContext.ts +6 -0
- package/src/lib/core/access-control.ts +434 -434
- package/src/lib/core/cascade.ts +236 -0
- package/src/lib/core/initialise-lists.ts +49 -33
- package/src/lib/core/mutations/index.ts +7 -0
- package/src/lib/core/mutations/nested-mutation-many-input-resolvers.ts +145 -145
- package/src/lib/core/mutations/nested-mutation-one-input-resolvers.ts +71 -71
- package/src/lib/core/queries/output-field.ts +178 -178
- package/src/lib/env.ts +50 -0
- package/src/lib/id-field.ts +2 -2
- package/src/lib/system.ts +221 -207
- package/src/lib/typescript-schema-printer.ts +227 -227
- package/src/list-features.ts +476 -0
- package/src/schema.ts +92 -22
- package/src/session.ts +225 -0
- package/src/types/admin-meta.ts +218 -218
- package/src/types/config/access-control.ts +186 -186
- package/src/types/config/fields.ts +96 -96
- package/src/types/config/hooks.ts +529 -529
- package/src/types/config/index.ts +206 -7
- package/src/types/config/lists.ts +606 -565
- package/src/types/context.ts +592 -55
- package/src/types/next-fields.ts +31 -31
- package/src/types/type-info.ts +38 -38
- package/src/types/type-tests.ts +21 -21
|
@@ -1,178 +1,178 @@
|
|
|
1
|
-
import { type CacheHint, maybeCacheControlFromInfo } from '@apollo/cache-control-types'
|
|
2
|
-
import DataLoader from 'dataloader'
|
|
3
|
-
import type { GraphQLResolveInfo } from 'graphql'
|
|
4
|
-
|
|
5
|
-
import { g } from '../../..'
|
|
6
|
-
import type {
|
|
7
|
-
BaseItem,
|
|
8
|
-
|
|
9
|
-
FieldAccessControlFunction,
|
|
10
|
-
FieldReadItemAccessArgs,
|
|
11
|
-
FindManyArgsValue,
|
|
12
|
-
NixxieContext,
|
|
13
|
-
NextFieldType,
|
|
14
|
-
} from '../../../types'
|
|
15
|
-
import { getAccessFilters, getOperationAccess, getOperationFieldAccess } from '../access-control'
|
|
16
|
-
import type { InitialisedList } from '../initialise-lists'
|
|
17
|
-
import type { ResolvedDBField, ResolvedRelationDBField } from '../resolve-relationships'
|
|
18
|
-
import { type IdType, getDBFieldKeyForFieldOnMultiField, weakMemoize } from '../utils'
|
|
19
|
-
import * as queries from './resolvers'
|
|
20
|
-
import { accessControlledFilter } from './resolvers'
|
|
21
|
-
|
|
22
|
-
function getRelationVal(
|
|
23
|
-
dbField: ResolvedRelationDBField,
|
|
24
|
-
id: IdType,
|
|
25
|
-
foreignList: InitialisedList,
|
|
26
|
-
context: NixxieContext,
|
|
27
|
-
info: GraphQLResolveInfo,
|
|
28
|
-
fk: IdType | null | undefined
|
|
29
|
-
) {
|
|
30
|
-
const oppositeDbField = foreignList.resolvedDbFields[dbField.field]
|
|
31
|
-
if (oppositeDbField.kind !== 'relation') throw new Error('failed assert')
|
|
32
|
-
|
|
33
|
-
if (dbField.mode === 'many') {
|
|
34
|
-
const relationFilter = {
|
|
35
|
-
[dbField.field]: oppositeDbField.mode === 'many' ? { some: { id } } : { id },
|
|
36
|
-
}
|
|
37
|
-
return {
|
|
38
|
-
findMany: async (args: FindManyArgsValue) =>
|
|
39
|
-
queries.findMany(args, foreignList, context, info, relationFilter),
|
|
40
|
-
count: async ({ where }: { where: Record<string, unknown> }) =>
|
|
41
|
-
queries.count({ where }, foreignList, context, info, relationFilter),
|
|
42
|
-
}
|
|
43
|
-
} else {
|
|
44
|
-
return async () => {
|
|
45
|
-
if (fk === null) {
|
|
46
|
-
// If the foreign key is explicitly null, there's no need to anything else,
|
|
47
|
-
// since we know the related item doesn't exist.
|
|
48
|
-
return null
|
|
49
|
-
}
|
|
50
|
-
// for one-to-many relationships, the one side always owns the foreign key
|
|
51
|
-
// so that means we have the id for the related item and we're fetching it by _its_ id.
|
|
52
|
-
// for the a one-to-one relationship though, the id might be on the related item
|
|
53
|
-
// so we need to fetch the related item by the id of the current item on the foreign key field
|
|
54
|
-
const currentItemOwnsForeignKey = fk !== undefined
|
|
55
|
-
return fetchRelatedItem(context)(foreignList)(
|
|
56
|
-
currentItemOwnsForeignKey ? 'id' : `${dbField.field}Id`
|
|
57
|
-
)(currentItemOwnsForeignKey ? fk : id)
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function memoize<Arg, Return>(cb: (arg: Arg) => Return) {
|
|
63
|
-
const cache = new Map<Arg, Return>()
|
|
64
|
-
return (arg: Arg) => {
|
|
65
|
-
if (!cache.has(arg)) {
|
|
66
|
-
const result = cb(arg)
|
|
67
|
-
cache.set(arg, result)
|
|
68
|
-
}
|
|
69
|
-
return cache.get(arg)!
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const fetchRelatedItem = weakMemoize((context: NixxieContext) =>
|
|
74
|
-
weakMemoize((foreignList: InitialisedList) =>
|
|
75
|
-
memoize((idFieldKey: string) => {
|
|
76
|
-
const relatedItemLoader = new DataLoader(
|
|
77
|
-
(keys: readonly IdType[]) => fetchRelatedItems(context, foreignList, idFieldKey, keys),
|
|
78
|
-
{ cache: false }
|
|
79
|
-
)
|
|
80
|
-
return (id: IdType) => relatedItemLoader.load(id)
|
|
81
|
-
})
|
|
82
|
-
)
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
async function fetchRelatedItems(
|
|
86
|
-
context: NixxieContext,
|
|
87
|
-
foreignList: InitialisedList,
|
|
88
|
-
idFieldKey: string,
|
|
89
|
-
toFetch: readonly IdType[]
|
|
90
|
-
) {
|
|
91
|
-
const operationAccess = await getOperationAccess(foreignList, context, 'query')
|
|
92
|
-
if (!operationAccess) {
|
|
93
|
-
return toFetch.map(() => undefined)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const accessFilters = await getAccessFilters(foreignList, context, 'query')
|
|
97
|
-
if (accessFilters === false) {
|
|
98
|
-
return toFetch.map(() => undefined)
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const toFetchUnique = Array.from(new Set(toFetch))
|
|
102
|
-
const resolvedWhere = await accessControlledFilter(
|
|
103
|
-
foreignList,
|
|
104
|
-
context,
|
|
105
|
-
{ [idFieldKey]: { in: toFetchUnique } },
|
|
106
|
-
accessFilters
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
const results = await context.prisma[foreignList.listKey].findMany({ where: resolvedWhere })
|
|
110
|
-
const resultsById = new Map(results.map((x: any) => [x[idFieldKey], x]))
|
|
111
|
-
return toFetch.map(id => resultsById.get(id))
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function getValueForDBField(
|
|
115
|
-
item: BaseItem,
|
|
116
|
-
dbField: ResolvedDBField,
|
|
117
|
-
id: IdType,
|
|
118
|
-
fieldPath: string,
|
|
119
|
-
context: NixxieContext,
|
|
120
|
-
lists: Record<string, InitialisedList>,
|
|
121
|
-
info: GraphQLResolveInfo
|
|
122
|
-
) {
|
|
123
|
-
if (dbField.kind === 'multi') {
|
|
124
|
-
return Object.fromEntries(
|
|
125
|
-
Object.keys(dbField.fields).map(innerDBFieldKey => {
|
|
126
|
-
const keyOnDbValue = getDBFieldKeyForFieldOnMultiField(fieldPath, innerDBFieldKey)
|
|
127
|
-
return [innerDBFieldKey, item[keyOnDbValue] as any]
|
|
128
|
-
})
|
|
129
|
-
)
|
|
130
|
-
}
|
|
131
|
-
if (dbField.kind === 'relation') {
|
|
132
|
-
// If we're holding a foreign key value, let's take advantage of that.
|
|
133
|
-
let fk: IdType | undefined
|
|
134
|
-
if (dbField.mode === 'one' && dbField.foreignIdField.kind !== 'none') {
|
|
135
|
-
fk = item[`${fieldPath}Id`] as IdType
|
|
136
|
-
}
|
|
137
|
-
return getRelationVal(dbField, id, lists[dbField.list], context, info, fk)
|
|
138
|
-
} else {
|
|
139
|
-
return item[fieldPath] as any
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
export function outputTypeField(
|
|
144
|
-
output: NextFieldType['output'],
|
|
145
|
-
dbField: ResolvedDBField,
|
|
146
|
-
cacheHint: CacheHint | undefined,
|
|
147
|
-
access: FieldAccessControlFunction<FieldReadItemAccessArgs<
|
|
148
|
-
listKey: string,
|
|
149
|
-
fieldKey: string,
|
|
150
|
-
lists: Record<string, InitialisedList>
|
|
151
|
-
) {
|
|
152
|
-
const list = lists[listKey]
|
|
153
|
-
|
|
154
|
-
return g.field({
|
|
155
|
-
type: output.type,
|
|
156
|
-
deprecationReason: output.deprecationReason,
|
|
157
|
-
description: output.description,
|
|
158
|
-
args: output.args,
|
|
159
|
-
extensions: output.extensions,
|
|
160
|
-
async resolve(item: BaseItem, args, context, info) {
|
|
161
|
-
const id = item.id as IdType
|
|
162
|
-
const fieldAccess = await getOperationFieldAccess(item, list, fieldKey, context, 'read')
|
|
163
|
-
if (!fieldAccess) return null
|
|
164
|
-
|
|
165
|
-
// only static cache hints are supported at the field level until a use-case makes it clear what parameters a dynamic hint would take
|
|
166
|
-
if (cacheHint && info) {
|
|
167
|
-
maybeCacheControlFromInfo(info)?.setCacheHint(cacheHint)
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const value = getValueForDBField(item, dbField, id, fieldKey, context, lists, info)
|
|
171
|
-
if (output.resolve) {
|
|
172
|
-
return output.resolve({ value, item: item }, args, context, info)
|
|
173
|
-
} else {
|
|
174
|
-
return value
|
|
175
|
-
}
|
|
176
|
-
},
|
|
177
|
-
})
|
|
178
|
-
}
|
|
1
|
+
import { type CacheHint, maybeCacheControlFromInfo } from '@apollo/cache-control-types'
|
|
2
|
+
import DataLoader from 'dataloader'
|
|
3
|
+
import type { GraphQLResolveInfo } from 'graphql'
|
|
4
|
+
|
|
5
|
+
import { g } from '../../..'
|
|
6
|
+
import type {
|
|
7
|
+
BaseItem,
|
|
8
|
+
BaseCollectionTypeInfo,
|
|
9
|
+
FieldAccessControlFunction,
|
|
10
|
+
FieldReadItemAccessArgs,
|
|
11
|
+
FindManyArgsValue,
|
|
12
|
+
NixxieContext,
|
|
13
|
+
NextFieldType,
|
|
14
|
+
} from '../../../types'
|
|
15
|
+
import { getAccessFilters, getOperationAccess, getOperationFieldAccess } from '../access-control'
|
|
16
|
+
import type { InitialisedList } from '../initialise-lists'
|
|
17
|
+
import type { ResolvedDBField, ResolvedRelationDBField } from '../resolve-relationships'
|
|
18
|
+
import { type IdType, getDBFieldKeyForFieldOnMultiField, weakMemoize } from '../utils'
|
|
19
|
+
import * as queries from './resolvers'
|
|
20
|
+
import { accessControlledFilter } from './resolvers'
|
|
21
|
+
|
|
22
|
+
function getRelationVal(
|
|
23
|
+
dbField: ResolvedRelationDBField,
|
|
24
|
+
id: IdType,
|
|
25
|
+
foreignList: InitialisedList,
|
|
26
|
+
context: NixxieContext,
|
|
27
|
+
info: GraphQLResolveInfo,
|
|
28
|
+
fk: IdType | null | undefined
|
|
29
|
+
) {
|
|
30
|
+
const oppositeDbField = foreignList.resolvedDbFields[dbField.field]
|
|
31
|
+
if (oppositeDbField.kind !== 'relation') throw new Error('failed assert')
|
|
32
|
+
|
|
33
|
+
if (dbField.mode === 'many') {
|
|
34
|
+
const relationFilter = {
|
|
35
|
+
[dbField.field]: oppositeDbField.mode === 'many' ? { some: { id } } : { id },
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
findMany: async (args: FindManyArgsValue) =>
|
|
39
|
+
queries.findMany(args, foreignList, context, info, relationFilter),
|
|
40
|
+
count: async ({ where }: { where: Record<string, unknown> }) =>
|
|
41
|
+
queries.count({ where }, foreignList, context, info, relationFilter),
|
|
42
|
+
}
|
|
43
|
+
} else {
|
|
44
|
+
return async () => {
|
|
45
|
+
if (fk === null) {
|
|
46
|
+
// If the foreign key is explicitly null, there's no need to anything else,
|
|
47
|
+
// since we know the related item doesn't exist.
|
|
48
|
+
return null
|
|
49
|
+
}
|
|
50
|
+
// for one-to-many relationships, the one side always owns the foreign key
|
|
51
|
+
// so that means we have the id for the related item and we're fetching it by _its_ id.
|
|
52
|
+
// for the a one-to-one relationship though, the id might be on the related item
|
|
53
|
+
// so we need to fetch the related item by the id of the current item on the foreign key field
|
|
54
|
+
const currentItemOwnsForeignKey = fk !== undefined
|
|
55
|
+
return fetchRelatedItem(context)(foreignList)(
|
|
56
|
+
currentItemOwnsForeignKey ? 'id' : `${dbField.field}Id`
|
|
57
|
+
)(currentItemOwnsForeignKey ? fk : id)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function memoize<Arg, Return>(cb: (arg: Arg) => Return) {
|
|
63
|
+
const cache = new Map<Arg, Return>()
|
|
64
|
+
return (arg: Arg) => {
|
|
65
|
+
if (!cache.has(arg)) {
|
|
66
|
+
const result = cb(arg)
|
|
67
|
+
cache.set(arg, result)
|
|
68
|
+
}
|
|
69
|
+
return cache.get(arg)!
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const fetchRelatedItem = weakMemoize((context: NixxieContext) =>
|
|
74
|
+
weakMemoize((foreignList: InitialisedList) =>
|
|
75
|
+
memoize((idFieldKey: string) => {
|
|
76
|
+
const relatedItemLoader = new DataLoader(
|
|
77
|
+
(keys: readonly IdType[]) => fetchRelatedItems(context, foreignList, idFieldKey, keys),
|
|
78
|
+
{ cache: false }
|
|
79
|
+
)
|
|
80
|
+
return (id: IdType) => relatedItemLoader.load(id)
|
|
81
|
+
})
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
async function fetchRelatedItems(
|
|
86
|
+
context: NixxieContext,
|
|
87
|
+
foreignList: InitialisedList,
|
|
88
|
+
idFieldKey: string,
|
|
89
|
+
toFetch: readonly IdType[]
|
|
90
|
+
) {
|
|
91
|
+
const operationAccess = await getOperationAccess(foreignList, context, 'query')
|
|
92
|
+
if (!operationAccess) {
|
|
93
|
+
return toFetch.map(() => undefined)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const accessFilters = await getAccessFilters(foreignList, context, 'query')
|
|
97
|
+
if (accessFilters === false) {
|
|
98
|
+
return toFetch.map(() => undefined)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const toFetchUnique = Array.from(new Set(toFetch))
|
|
102
|
+
const resolvedWhere = await accessControlledFilter(
|
|
103
|
+
foreignList,
|
|
104
|
+
context,
|
|
105
|
+
{ [idFieldKey]: { in: toFetchUnique } },
|
|
106
|
+
accessFilters
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
const results = await context.prisma[foreignList.listKey].findMany({ where: resolvedWhere })
|
|
110
|
+
const resultsById = new Map(results.map((x: any) => [x[idFieldKey], x]))
|
|
111
|
+
return toFetch.map(id => resultsById.get(id))
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function getValueForDBField(
|
|
115
|
+
item: BaseItem,
|
|
116
|
+
dbField: ResolvedDBField,
|
|
117
|
+
id: IdType,
|
|
118
|
+
fieldPath: string,
|
|
119
|
+
context: NixxieContext,
|
|
120
|
+
lists: Record<string, InitialisedList>,
|
|
121
|
+
info: GraphQLResolveInfo
|
|
122
|
+
) {
|
|
123
|
+
if (dbField.kind === 'multi') {
|
|
124
|
+
return Object.fromEntries(
|
|
125
|
+
Object.keys(dbField.fields).map(innerDBFieldKey => {
|
|
126
|
+
const keyOnDbValue = getDBFieldKeyForFieldOnMultiField(fieldPath, innerDBFieldKey)
|
|
127
|
+
return [innerDBFieldKey, item[keyOnDbValue] as any]
|
|
128
|
+
})
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
if (dbField.kind === 'relation') {
|
|
132
|
+
// If we're holding a foreign key value, let's take advantage of that.
|
|
133
|
+
let fk: IdType | undefined
|
|
134
|
+
if (dbField.mode === 'one' && dbField.foreignIdField.kind !== 'none') {
|
|
135
|
+
fk = item[`${fieldPath}Id`] as IdType
|
|
136
|
+
}
|
|
137
|
+
return getRelationVal(dbField, id, lists[dbField.list], context, info, fk)
|
|
138
|
+
} else {
|
|
139
|
+
return item[fieldPath] as any
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function outputTypeField(
|
|
144
|
+
output: NextFieldType['output'],
|
|
145
|
+
dbField: ResolvedDBField,
|
|
146
|
+
cacheHint: CacheHint | undefined,
|
|
147
|
+
access: FieldAccessControlFunction<FieldReadItemAccessArgs<BaseCollectionTypeInfo>>,
|
|
148
|
+
listKey: string,
|
|
149
|
+
fieldKey: string,
|
|
150
|
+
lists: Record<string, InitialisedList>
|
|
151
|
+
) {
|
|
152
|
+
const list = lists[listKey]
|
|
153
|
+
|
|
154
|
+
return g.field({
|
|
155
|
+
type: output.type,
|
|
156
|
+
deprecationReason: output.deprecationReason,
|
|
157
|
+
description: output.description,
|
|
158
|
+
args: output.args,
|
|
159
|
+
extensions: output.extensions,
|
|
160
|
+
async resolve(item: BaseItem, args, context, info) {
|
|
161
|
+
const id = item.id as IdType
|
|
162
|
+
const fieldAccess = await getOperationFieldAccess(item, list, fieldKey, context, 'read')
|
|
163
|
+
if (!fieldAccess) return null
|
|
164
|
+
|
|
165
|
+
// only static cache hints are supported at the field level until a use-case makes it clear what parameters a dynamic hint would take
|
|
166
|
+
if (cacheHint && info) {
|
|
167
|
+
maybeCacheControlFromInfo(info)?.setCacheHint(cacheHint)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const value = getValueForDBField(item, dbField, id, fieldKey, context, lists, info)
|
|
171
|
+
if (output.resolve) {
|
|
172
|
+
return output.resolve({ value, item: item }, args, context, info)
|
|
173
|
+
} else {
|
|
174
|
+
return value
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
})
|
|
178
|
+
}
|
package/src/lib/env.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { NixxieEnvSpec, NixxieEnvVarSpec } from '../types'
|
|
2
|
+
|
|
3
|
+
function normalise(spec: NixxieEnvSpec): Record<string, NixxieEnvVarSpec> {
|
|
4
|
+
if (Array.isArray(spec)) {
|
|
5
|
+
return Object.fromEntries((spec as readonly string[]).map(name => [name, {}]))
|
|
6
|
+
}
|
|
7
|
+
return spec as Record<string, NixxieEnvVarSpec>
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Validate `env` (default `process.env`) against a spec. Applies `default`s to the env
|
|
12
|
+
* object for unset variables, then throws one aggregated error listing every missing or
|
|
13
|
+
* malformed variable. Called automatically when the config declares `env`, and exported
|
|
14
|
+
* for standalone use.
|
|
15
|
+
*/
|
|
16
|
+
export function validateEnv(
|
|
17
|
+
spec: NixxieEnvSpec,
|
|
18
|
+
env: Record<string, string | undefined> = process.env
|
|
19
|
+
): void {
|
|
20
|
+
const problems: string[] = []
|
|
21
|
+
|
|
22
|
+
for (const [name, requirement] of Object.entries(normalise(spec))) {
|
|
23
|
+
let value = env[name]
|
|
24
|
+
|
|
25
|
+
if ((value === undefined || value === '') && requirement.default !== undefined) {
|
|
26
|
+
env[name] = requirement.default
|
|
27
|
+
value = requirement.default
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const describe = requirement.description ? ` — ${requirement.description}` : ''
|
|
31
|
+
|
|
32
|
+
if (value === undefined || value === '') {
|
|
33
|
+
if (requirement.required !== false) {
|
|
34
|
+
problems.push(` • ${name} is not set${describe}`)
|
|
35
|
+
}
|
|
36
|
+
continue
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (requirement.pattern && !requirement.pattern.test(value)) {
|
|
40
|
+
problems.push(` • ${name} does not match ${requirement.pattern}${describe}`)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (problems.length) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
`Environment validation failed (${problems.length} problem${problems.length === 1 ? '' : 's'}):\n` +
|
|
47
|
+
problems.join('\n')
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
}
|
package/src/lib/id-field.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { GArg, GInputObjectType, InferValueFromArg } from '../graphql-ts'
|
|
2
2
|
import type { ScalarDBField } from '../types'
|
|
3
3
|
import {
|
|
4
|
-
type
|
|
4
|
+
type BaseCollectionTypeInfo,
|
|
5
5
|
type DatabaseProvider,
|
|
6
6
|
type FieldTypeFunc,
|
|
7
7
|
type IdFieldConfig,
|
|
@@ -140,7 +140,7 @@ function unpack(
|
|
|
140
140
|
throw new Error(`Unknown id type ${kind}`)
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
export function idFieldType(config: IdFieldConfig): FieldTypeFunc<
|
|
143
|
+
export function idFieldType(config: IdFieldConfig): FieldTypeFunc<BaseCollectionTypeInfo> {
|
|
144
144
|
const kind = config.kind
|
|
145
145
|
const dbFieldOptions = unpack(config)
|
|
146
146
|
const parseTypeFn = {
|