@nixxie-cms/core 1.0.0 → 1.0.1

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 (186) hide show
  1. package/README.md +2 -2
  2. package/admin-ui/components/dist/nixxie-cms-core-admin-ui-components.cjs.js +4 -4
  3. package/admin-ui/components/dist/nixxie-cms-core-admin-ui-components.esm.js +4 -4
  4. package/admin-ui/context/dist/nixxie-cms-core-admin-ui-context.cjs.js +2 -2
  5. package/admin-ui/context/dist/nixxie-cms-core-admin-ui-context.esm.js +2 -2
  6. package/context/dist/nixxie-cms-core-context.cjs.js +2 -2
  7. package/context/dist/nixxie-cms-core-context.esm.js +2 -2
  8. package/dist/{CreateItemDialog-33335548.esm.js → CreateItemDialog-7008b050.esm.js} +1 -1
  9. package/dist/{CreateItemDialog-56cf59b7.cjs.js → CreateItemDialog-a0cab315.cjs.js} +1 -1
  10. package/dist/{PageContainer-7db73317.esm.js → PageContainer-5ae731cc.esm.js} +25 -18
  11. package/dist/{PageContainer-27c27f10.cjs.js → PageContainer-abd7159f.cjs.js} +25 -18
  12. package/dist/{admin-meta-graphql-6f7f5331.esm.js → admin-meta-graphql-0e6e606e.esm.js} +1 -1
  13. package/dist/{admin-meta-graphql-c8f926e9.cjs.js → admin-meta-graphql-306c224a.cjs.js} +1 -1
  14. package/dist/{context-3132c3ed.esm.js → context-af9957ed.esm.js} +2 -2
  15. package/dist/{context-e7a45152.cjs.js → context-b5204629.cjs.js} +2 -2
  16. package/dist/declarations/src/admin-ui/components/Navigation.d.ts.map +1 -1
  17. package/dist/declarations/src/admin-ui/components/PageContainer.d.ts.map +1 -1
  18. package/dist/declarations/src/helpers.d.ts.map +1 -1
  19. package/dist/declarations/src/index.d.ts +1 -0
  20. package/dist/declarations/src/index.d.ts.map +1 -1
  21. package/dist/declarations/src/internal-unstable/admin-ui/id-field-view.d.ts.map +1 -0
  22. package/dist/declarations/src/internal-unstable/admin-ui/pages/App/index.d.ts.map +1 -0
  23. package/dist/declarations/src/internal-unstable/admin-ui/pages/CreateItemPage/index.d.ts.map +1 -0
  24. package/dist/declarations/src/internal-unstable/admin-ui/pages/HomePage/index.d.ts.map +1 -0
  25. package/dist/declarations/src/internal-unstable/admin-ui/pages/ItemPage/index.d.ts.map +1 -0
  26. package/dist/declarations/src/internal-unstable/admin-ui/pages/ListPage/index.d.ts.map +1 -0
  27. package/dist/declarations/src/internal-unstable/admin-ui/pages/NoAccessPage/index.d.ts.map +1 -0
  28. package/dist/declarations/src/internal-unstable/artifacts.d.ts.map +1 -0
  29. package/dist/declarations/src/lib/core/initialise-lists.d.ts +1 -1
  30. package/dist/declarations/src/schema.d.ts.map +1 -1
  31. package/dist/declarations/src/types/config/index.d.ts +60 -1
  32. package/dist/declarations/src/types/config/index.d.ts.map +1 -1
  33. package/dist/declarations/src/types/config/lists.d.ts +4 -4
  34. package/dist/declarations/src/types/context.d.ts +150 -0
  35. package/dist/declarations/src/types/context.d.ts.map +1 -1
  36. package/dist/declarations/src/types/next-fields.d.ts +1 -1
  37. package/dist/{express-e9ed9a7d.cjs.js → express-455ae20c.cjs.js} +1 -1
  38. package/dist/{express-6743b918.esm.js → express-7559ca2d.esm.js} +1 -1
  39. package/dist/{index-ac01583b.cjs.js → index-89635494.cjs.js} +4 -4
  40. package/dist/{index-24b78415.esm.js → index-baa799e0.esm.js} +4 -4
  41. package/dist/nixxie-cms-core.cjs.js +104 -77
  42. package/dist/nixxie-cms-core.esm.js +104 -77
  43. package/dist/{non-null-graphql-5315718c.esm.js → non-null-graphql-a84ed64d.esm.js} +1 -1
  44. package/dist/{non-null-graphql-17b83ddc.cjs.js → non-null-graphql-add6bb3d.cjs.js} +1 -1
  45. package/dist/{resolve-hooks-66fe8a8e.cjs.js → resolve-hooks-165a9ce2.cjs.js} +1 -1
  46. package/dist/{resolve-hooks-17aafd37.esm.js → resolve-hooks-6813a045.esm.js} +2 -2
  47. package/dist/{system-dfec2f0a.esm.js → system-03e49e4f.esm.js} +8 -4
  48. package/dist/{system-48c5f6df.cjs.js → system-a321642d.cjs.js} +8 -4
  49. package/dist/{useFilter-0b5a1ee6.esm.js → useFilter-9b6db1f9.esm.js} +1 -1
  50. package/dist/{useFilter-1a4e6900.cjs.js → useFilter-acc9d413.cjs.js} +1 -1
  51. package/fields/dist/nixxie-cms-core-fields.cjs.js +16 -16
  52. package/fields/dist/nixxie-cms-core-fields.esm.js +17 -17
  53. package/fields/types/bytes/dist/nixxie-cms-core-fields-types-bytes.cjs.js +3 -3
  54. package/fields/types/bytes/dist/nixxie-cms-core-fields-types-bytes.esm.js +3 -3
  55. package/fields/types/bytes/views/dist/nixxie-cms-core-fields-types-bytes-views.cjs.js +1 -1
  56. package/fields/types/bytes/views/dist/nixxie-cms-core-fields-types-bytes-views.esm.js +1 -1
  57. package/fields/types/password/dist/nixxie-cms-core-fields-types-password.cjs.js +3 -3
  58. package/fields/types/password/dist/nixxie-cms-core-fields-types-password.esm.js +3 -3
  59. package/fields/types/relationship/views/dist/nixxie-cms-core-fields-types-relationship-views.cjs.js +4 -4
  60. package/fields/types/relationship/views/dist/nixxie-cms-core-fields-types-relationship-views.esm.js +4 -4
  61. package/fields/types/select/views/dist/nixxie-cms-core-fields-types-select-views.cjs.js +1 -1
  62. package/fields/types/select/views/dist/nixxie-cms-core-fields-types-select-views.esm.js +1 -1
  63. package/fields/types/text/views/dist/nixxie-cms-core-fields-types-text-views.cjs.js +1 -1
  64. package/fields/types/text/views/dist/nixxie-cms-core-fields-types-text-views.esm.js +1 -1
  65. package/internal-unstable/admin-ui/id-field-view/dist/nixxie-cms-core-internal-unstable-admin-ui-id-field-view.cjs.d.ts +2 -0
  66. package/internal-unstable/admin-ui/id-field-view/dist/nixxie-cms-core-internal-unstable-admin-ui-id-field-view.cjs.js +244 -0
  67. package/internal-unstable/admin-ui/id-field-view/dist/nixxie-cms-core-internal-unstable-admin-ui-id-field-view.esm.js +235 -0
  68. package/internal-unstable/admin-ui/id-field-view/package.json +4 -0
  69. package/internal-unstable/admin-ui/next-config/package.json +4 -0
  70. package/internal-unstable/admin-ui/pages/App/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-App.cjs.d.ts +2 -0
  71. package/internal-unstable/admin-ui/pages/App/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-App.cjs.js +59 -0
  72. package/internal-unstable/admin-ui/pages/App/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-App.esm.js +55 -0
  73. package/internal-unstable/admin-ui/pages/App/package.json +4 -0
  74. package/internal-unstable/admin-ui/pages/CreateItemPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-CreateItemPage.cjs.d.ts +2 -0
  75. package/internal-unstable/admin-ui/pages/CreateItemPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-CreateItemPage.cjs.js +116 -0
  76. package/internal-unstable/admin-ui/pages/CreateItemPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-CreateItemPage.esm.js +112 -0
  77. package/internal-unstable/admin-ui/pages/CreateItemPage/package.json +4 -0
  78. package/internal-unstable/admin-ui/pages/HomePage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-HomePage.cjs.d.ts +2 -0
  79. package/internal-unstable/admin-ui/pages/HomePage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-HomePage.cjs.js +336 -0
  80. package/internal-unstable/admin-ui/pages/HomePage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-HomePage.esm.js +332 -0
  81. package/internal-unstable/admin-ui/pages/HomePage/package.json +4 -0
  82. package/internal-unstable/admin-ui/pages/ItemPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-ItemPage.cjs.d.ts +2 -0
  83. package/internal-unstable/admin-ui/pages/ItemPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-ItemPage.cjs.js +463 -0
  84. package/internal-unstable/admin-ui/pages/ItemPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-ItemPage.esm.js +455 -0
  85. package/internal-unstable/admin-ui/pages/ItemPage/package.json +4 -0
  86. package/internal-unstable/admin-ui/pages/ListPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-ListPage.cjs.d.ts +2 -0
  87. package/internal-unstable/admin-ui/pages/ListPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-ListPage.cjs.js +1195 -0
  88. package/internal-unstable/admin-ui/pages/ListPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-ListPage.esm.js +1187 -0
  89. package/internal-unstable/admin-ui/pages/ListPage/package.json +4 -0
  90. package/internal-unstable/admin-ui/pages/NoAccessPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-NoAccessPage.cjs.d.ts +2 -0
  91. package/internal-unstable/admin-ui/pages/NoAccessPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-NoAccessPage.cjs.js +40 -0
  92. package/internal-unstable/admin-ui/pages/NoAccessPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-NoAccessPage.esm.js +35 -0
  93. package/internal-unstable/admin-ui/pages/NoAccessPage/package.json +4 -0
  94. package/internal-unstable/artifacts/dist/nixxie-cms-core-internal-unstable-artifacts.cjs.d.ts +2 -0
  95. package/internal-unstable/artifacts/dist/nixxie-cms-core-internal-unstable-artifacts.cjs.js +51 -0
  96. package/internal-unstable/artifacts/dist/nixxie-cms-core-internal-unstable-artifacts.esm.js +38 -0
  97. package/internal-unstable/artifacts/package.json +4 -0
  98. package/package.json +44 -44
  99. package/scripts/cli/dist/nixxie-cms-core-scripts-cli.cjs.js +15 -15
  100. package/scripts/cli/dist/nixxie-cms-core-scripts-cli.esm.js +15 -15
  101. package/scripts/dist/nixxie-cms-core-scripts.cjs.js +3 -3
  102. package/scripts/dist/nixxie-cms-core-scripts.esm.js +3 -3
  103. package/src/admin-ui/admin-meta-graphql.ts +168 -168
  104. package/src/admin-ui/components/CommandPalette.tsx +433 -431
  105. package/src/admin-ui/components/Navigation.tsx +389 -385
  106. package/src/admin-ui/components/PageContainer.tsx +311 -310
  107. package/src/admin-ui/components/WelcomeDialog.tsx +1 -1
  108. package/src/admin-ui/context.tsx +338 -338
  109. package/src/admin-ui/templates/app.ts +60 -60
  110. package/src/admin-ui/templates/create-item.ts +5 -5
  111. package/src/admin-ui/templates/home.ts +2 -2
  112. package/src/admin-ui/templates/item.tsx +5 -5
  113. package/src/admin-ui/templates/list.tsx +5 -5
  114. package/src/admin-ui/templates/no-access.ts +7 -7
  115. package/src/fields/types/bigInt/index.ts +181 -181
  116. package/src/fields/types/bytes/index.ts +275 -275
  117. package/src/fields/types/calendarDay/index.ts +194 -194
  118. package/src/fields/types/checkbox/index.ts +76 -76
  119. package/src/fields/types/decimal/index.ts +182 -182
  120. package/src/fields/types/file/index.ts +168 -168
  121. package/src/fields/types/float/index.ts +133 -133
  122. package/src/fields/types/image/index.ts +244 -244
  123. package/src/fields/types/integer/index.ts +156 -156
  124. package/src/fields/types/json/index.ts +77 -77
  125. package/src/fields/types/multiselect/index.ts +212 -212
  126. package/src/fields/types/password/index.ts +241 -241
  127. package/src/fields/types/relationship/index.ts +381 -381
  128. package/src/fields/types/relationship/views/RelationshipTable.tsx +190 -190
  129. package/src/fields/types/select/index.ts +226 -226
  130. package/src/fields/types/text/index.ts +207 -207
  131. package/src/fields/types/timestamp/index.ts +116 -116
  132. package/src/fields/types/virtual/index.ts +108 -108
  133. package/src/helpers.ts +342 -316
  134. package/src/index.ts +4 -0
  135. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/id-field-view.tsx +167 -167
  136. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/App/index.tsx +22 -22
  137. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/CreateItemPage/index.tsx +71 -71
  138. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/HomePage/index.tsx +333 -333
  139. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ItemPage/common.tsx +358 -358
  140. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ItemPage/index.tsx +483 -483
  141. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ListPage/FilterAdd.tsx +221 -221
  142. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ListPage/PaginationControls.tsx +170 -170
  143. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ListPage/Tag.tsx +72 -72
  144. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ListPage/index.tsx +1006 -1006
  145. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/NoAccessPage/index.tsx +24 -24
  146. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/artifacts.ts +5 -5
  147. package/src/lib/context/createContext.ts +165 -161
  148. package/src/lib/core/initialise-lists.ts +1097 -1097
  149. package/src/lib/id-field.ts +214 -214
  150. package/src/lib/telemetry.ts +342 -342
  151. package/src/schema.ts +237 -233
  152. package/src/scripts/telemetry.ts +1 -1
  153. package/src/types/config/index.ts +400 -333
  154. package/src/types/config/lists.ts +4 -4
  155. package/src/types/context.ts +700 -530
  156. package/src/types/next-fields.ts +499 -499
  157. package/src/types/telemetry.ts +51 -51
  158. package/tests/telemetry.test.ts +361 -361
  159. package/CHANGELOG.md +0 -3158
  160. package/___internal-do-not-use-will-break-in-patch/admin-ui/id-field-view/package.json +0 -4
  161. package/___internal-do-not-use-will-break-in-patch/admin-ui/next-config/package.json +0 -4
  162. package/___internal-do-not-use-will-break-in-patch/admin-ui/pages/App/package.json +0 -4
  163. package/___internal-do-not-use-will-break-in-patch/admin-ui/pages/CreateItemPage/package.json +0 -4
  164. package/___internal-do-not-use-will-break-in-patch/admin-ui/pages/HomePage/package.json +0 -4
  165. package/___internal-do-not-use-will-break-in-patch/admin-ui/pages/ItemPage/package.json +0 -4
  166. package/___internal-do-not-use-will-break-in-patch/admin-ui/pages/ListPage/package.json +0 -4
  167. package/___internal-do-not-use-will-break-in-patch/admin-ui/pages/NoAccessPage/package.json +0 -4
  168. package/___internal-do-not-use-will-break-in-patch/artifacts/package.json +0 -4
  169. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/admin-ui/id-field-view.d.ts.map +0 -1
  170. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/App/index.d.ts.map +0 -1
  171. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/CreateItemPage/index.d.ts.map +0 -1
  172. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/HomePage/index.d.ts.map +0 -1
  173. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/ItemPage/index.d.ts.map +0 -1
  174. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/ListPage/index.d.ts.map +0 -1
  175. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/NoAccessPage/index.d.ts.map +0 -1
  176. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/artifacts.d.ts.map +0 -1
  177. /package/dist/{common-1a350e11.cjs.js → common-5933f758.cjs.js} +0 -0
  178. /package/dist/{common-29fc82e6.esm.js → common-ea5c441a.esm.js} +0 -0
  179. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/id-field-view.d.ts +0 -0
  180. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/App/index.d.ts +0 -0
  181. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/CreateItemPage/index.d.ts +0 -0
  182. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/HomePage/index.d.ts +0 -0
  183. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ItemPage/index.d.ts +0 -0
  184. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ListPage/index.d.ts +0 -0
  185. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/NoAccessPage/index.d.ts +0 -0
  186. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/artifacts.d.ts +0 -0
@@ -1,168 +1,168 @@
1
- import type {
2
- BaseNixxieTypeInfo,
3
- BaseListTypeInfo,
4
- CommonFieldConfig,
5
- FieldHooks,
6
- FieldTypeFunc,
7
- NixxieContext,
8
- MaybePromise,
9
- StorageStrategy,
10
- } from '../../../types'
11
- import { fieldType } from '../../../types'
12
- import { g } from '../../..'
13
- import { merge } from '../../resolve-hooks'
14
- import type { InferValueFromArg, InferValueFromInputType } from '@graphql-ts/schema'
15
- import { randomBytes } from 'node:crypto'
16
-
17
- export type FieldTypeInfo = {
18
- item: undefined
19
- inputs: {
20
- create: InferValueFromInputType<typeof FileFieldInput> | null | undefined
21
- update: InferValueFromInputType<typeof FileFieldInput> | null | undefined
22
- where: undefined
23
- uniqueWhere: undefined
24
- orderBy: undefined
25
- }
26
- prisma: {
27
- create: undefined
28
- update: undefined
29
- }
30
- }
31
-
32
- export type FileFieldConfig<ListTypeInfo extends BaseListTypeInfo> = CommonFieldConfig<
33
- ListTypeInfo,
34
- FieldTypeInfo
35
- > & {
36
- storage: StorageStrategy<ListTypeInfo['all']>
37
- transformName?: (originalFilename: string) => MaybePromise<string>
38
- db?: {
39
- extendPrismaSchema?: (field: string) => string
40
- }
41
- }
42
-
43
- const FileFieldInput = g.inputObject({
44
- name: 'FileFieldInput',
45
- fields: {
46
- upload: g.arg({ type: g.nonNull(g.Upload) }),
47
- },
48
- })
49
-
50
- const inputArg = g.arg({ type: FileFieldInput })
51
-
52
- const FileFieldOutput = g.object<{
53
- filename: string
54
- filesize: number
55
- url: (_args: {}, context: NixxieContext) => Promise<string>
56
- }>()({
57
- name: 'FileFieldOutput',
58
- fields: {
59
- filename: g.field({ type: g.nonNull(g.String) }),
60
- filesize: g.field({ type: g.nonNull(g.Int) }),
61
- url: g.field({ type: g.nonNull(g.String) }),
62
- },
63
- })
64
-
65
- async function inputResolver(
66
- storage: StorageStrategy<BaseNixxieTypeInfo>,
67
- transformName: (originalFilename: string) => Promise<string> | string,
68
- context: NixxieContext,
69
- data: InferValueFromArg<typeof inputArg>
70
- ) {
71
- if (data === null || data === undefined) return { filename: data, filesize: data }
72
- const upload = await data.upload
73
- const stream = upload.createReadStream()
74
- let filesize = 0
75
- stream.on('data', data => {
76
- filesize += data.length
77
- })
78
-
79
- const filename = await transformName(upload.filename)
80
- await storage.put(filename, stream, { contentType: 'application/octet-stream' }, context)
81
- return { filename, filesize }
82
- }
83
-
84
- export function file<ListTypeInfo extends BaseListTypeInfo>(
85
- config: FileFieldConfig<ListTypeInfo>
86
- ): FieldTypeFunc<ListTypeInfo> {
87
- const { transformName = defaultTransformName } = config
88
- return meta => {
89
- const { fieldKey } = meta
90
-
91
- if ('isIndexed' in config) {
92
- throw Error("isIndexed: 'unique' is not a supported option for field type file")
93
- }
94
-
95
- const afterOperationResolver: Extract<
96
- FieldHooks<BaseListTypeInfo, FieldTypeInfo>['afterOperation'],
97
- (args: any) => any
98
- > = async function afterOperationResolver(args) {
99
- if (args.operation === 'update' || args.operation === 'delete') {
100
- const filenameKey = `${fieldKey}_filename`
101
- const oldFilename = args.originalItem[filenameKey] as string | null | undefined
102
- const newFilename = args.item?.[filenameKey] as string | null | undefined
103
-
104
- // this will occur on an update where a file already existed but has been
105
- // changed, or on a delete, where there is no longer an item
106
- // but not when the old and new filenames are the same (in that case, presumably the file has been overwritten)
107
- if (typeof oldFilename === 'string' && oldFilename !== newFilename) {
108
- await config.storage.delete(oldFilename, args.context)
109
- }
110
- }
111
- }
112
-
113
- return fieldType({
114
- kind: 'multi',
115
- extendPrismaSchema: config.db?.extendPrismaSchema,
116
- fields: {
117
- filesize: { kind: 'scalar', scalar: 'Int', mode: 'optional' },
118
- filename: { kind: 'scalar', scalar: 'String', mode: 'optional' },
119
- },
120
- })({
121
- ...config,
122
- hooks: {
123
- ...config.hooks,
124
- afterOperation: merge(config.hooks?.afterOperation, {
125
- update: afterOperationResolver,
126
- delete: afterOperationResolver,
127
- }),
128
- },
129
- input: {
130
- create: {
131
- arg: inputArg,
132
- resolve: (data, context) => inputResolver(config.storage, transformName, context, data),
133
- },
134
- update: {
135
- arg: inputArg,
136
- resolve: (data, context) => inputResolver(config.storage, transformName, context, data),
137
- },
138
- },
139
- output: g.field({
140
- type: FileFieldOutput,
141
- resolve({ value: { filesize, filename } }) {
142
- if (filename === null) return null
143
- if (filesize === null) return null
144
- return {
145
- filename,
146
- filesize,
147
- url: async (_, context) => config.storage.url(filename, context),
148
- }
149
- },
150
- }),
151
- __ksTelemetryFieldTypeName: '@nixxie-cms/file',
152
- views: '@nixxie-cms/core/fields/types/file/views',
153
- })
154
- }
155
- }
156
-
157
- // appends a 128-bit random identifier to the filename to prevent guessing
158
- function defaultTransformName(path: string) {
159
- // this regex lazily matches for any characters that aren't a new line
160
- // it then optionally matches the last instance of a "." symbol
161
- // followed by any alphanumerical character before the end of the string
162
- const [, name, ext] = path.match(/^([^:\n].*?)(\.[A-Za-z0-9]{0,10})?$/) as RegExpMatchArray
163
-
164
- const id = randomBytes(16).toString('base64url')
165
- const urlSafeName = name.replace(/[^A-Za-z0-9]/g, '-')
166
- if (ext) return `${urlSafeName}-${id}${ext}`
167
- return `${urlSafeName}-${id}`
168
- }
1
+ import type {
2
+ BaseNixxieTypeInfo,
3
+ BaseListTypeInfo,
4
+ CommonFieldConfig,
5
+ FieldHooks,
6
+ FieldTypeFunc,
7
+ NixxieContext,
8
+ MaybePromise,
9
+ StorageStrategy,
10
+ } from '../../../types'
11
+ import { fieldType } from '../../../types'
12
+ import { g } from '../../..'
13
+ import { merge } from '../../resolve-hooks'
14
+ import type { InferValueFromArg, InferValueFromInputType } from '@graphql-ts/schema'
15
+ import { randomBytes } from 'node:crypto'
16
+
17
+ export type FieldTypeInfo = {
18
+ item: undefined
19
+ inputs: {
20
+ create: InferValueFromInputType<typeof FileFieldInput> | null | undefined
21
+ update: InferValueFromInputType<typeof FileFieldInput> | null | undefined
22
+ where: undefined
23
+ uniqueWhere: undefined
24
+ orderBy: undefined
25
+ }
26
+ prisma: {
27
+ create: undefined
28
+ update: undefined
29
+ }
30
+ }
31
+
32
+ export type FileFieldConfig<ListTypeInfo extends BaseListTypeInfo> = CommonFieldConfig<
33
+ ListTypeInfo,
34
+ FieldTypeInfo
35
+ > & {
36
+ storage: StorageStrategy<ListTypeInfo['all']>
37
+ transformName?: (originalFilename: string) => MaybePromise<string>
38
+ db?: {
39
+ extendPrismaSchema?: (field: string) => string
40
+ }
41
+ }
42
+
43
+ const FileFieldInput = g.inputObject({
44
+ name: 'FileFieldInput',
45
+ fields: {
46
+ upload: g.arg({ type: g.nonNull(g.Upload) }),
47
+ },
48
+ })
49
+
50
+ const inputArg = g.arg({ type: FileFieldInput })
51
+
52
+ const FileFieldOutput = g.object<{
53
+ filename: string
54
+ filesize: number
55
+ url: (_args: {}, context: NixxieContext) => Promise<string>
56
+ }>()({
57
+ name: 'FileFieldOutput',
58
+ fields: {
59
+ filename: g.field({ type: g.nonNull(g.String) }),
60
+ filesize: g.field({ type: g.nonNull(g.Int) }),
61
+ url: g.field({ type: g.nonNull(g.String) }),
62
+ },
63
+ })
64
+
65
+ async function inputResolver(
66
+ storage: StorageStrategy<BaseNixxieTypeInfo>,
67
+ transformName: (originalFilename: string) => Promise<string> | string,
68
+ context: NixxieContext,
69
+ data: InferValueFromArg<typeof inputArg>
70
+ ) {
71
+ if (data === null || data === undefined) return { filename: data, filesize: data }
72
+ const upload = await data.upload
73
+ const stream = upload.createReadStream()
74
+ let filesize = 0
75
+ stream.on('data', data => {
76
+ filesize += data.length
77
+ })
78
+
79
+ const filename = await transformName(upload.filename)
80
+ await storage.put(filename, stream, { contentType: 'application/octet-stream' }, context)
81
+ return { filename, filesize }
82
+ }
83
+
84
+ export function file<ListTypeInfo extends BaseListTypeInfo>(
85
+ config: FileFieldConfig<ListTypeInfo>
86
+ ): FieldTypeFunc<ListTypeInfo> {
87
+ const { transformName = defaultTransformName } = config
88
+ return meta => {
89
+ const { fieldKey } = meta
90
+
91
+ if ('isIndexed' in config) {
92
+ throw Error("isIndexed: 'unique' is not a supported option for field type file")
93
+ }
94
+
95
+ const afterOperationResolver: Extract<
96
+ FieldHooks<BaseListTypeInfo, FieldTypeInfo>['afterOperation'],
97
+ (args: any) => any
98
+ > = async function afterOperationResolver(args) {
99
+ if (args.operation === 'update' || args.operation === 'delete') {
100
+ const filenameKey = `${fieldKey}_filename`
101
+ const oldFilename = args.originalItem[filenameKey] as string | null | undefined
102
+ const newFilename = args.item?.[filenameKey] as string | null | undefined
103
+
104
+ // this will occur on an update where a file already existed but has been
105
+ // changed, or on a delete, where there is no longer an item
106
+ // but not when the old and new filenames are the same (in that case, presumably the file has been overwritten)
107
+ if (typeof oldFilename === 'string' && oldFilename !== newFilename) {
108
+ await config.storage.delete(oldFilename, args.context)
109
+ }
110
+ }
111
+ }
112
+
113
+ return fieldType({
114
+ kind: 'multi',
115
+ extendPrismaSchema: config.db?.extendPrismaSchema,
116
+ fields: {
117
+ filesize: { kind: 'scalar', scalar: 'Int', mode: 'optional' },
118
+ filename: { kind: 'scalar', scalar: 'String', mode: 'optional' },
119
+ },
120
+ })({
121
+ ...config,
122
+ hooks: {
123
+ ...config.hooks,
124
+ afterOperation: merge(config.hooks?.afterOperation, {
125
+ update: afterOperationResolver,
126
+ delete: afterOperationResolver,
127
+ }),
128
+ },
129
+ input: {
130
+ create: {
131
+ arg: inputArg,
132
+ resolve: (data, context) => inputResolver(config.storage, transformName, context, data),
133
+ },
134
+ update: {
135
+ arg: inputArg,
136
+ resolve: (data, context) => inputResolver(config.storage, transformName, context, data),
137
+ },
138
+ },
139
+ output: g.field({
140
+ type: FileFieldOutput,
141
+ resolve({ value: { filesize, filename } }) {
142
+ if (filename === null) return null
143
+ if (filesize === null) return null
144
+ return {
145
+ filename,
146
+ filesize,
147
+ url: async (_, context) => config.storage.url(filename, context),
148
+ }
149
+ },
150
+ }),
151
+ __nxTelemetryFieldTypeName: '@nixxie-cms/file',
152
+ views: '@nixxie-cms/core/fields/types/file/views',
153
+ })
154
+ }
155
+ }
156
+
157
+ // appends a 128-bit random identifier to the filename to prevent guessing
158
+ function defaultTransformName(path: string) {
159
+ // this regex lazily matches for any characters that aren't a new line
160
+ // it then optionally matches the last instance of a "." symbol
161
+ // followed by any alphanumerical character before the end of the string
162
+ const [, name, ext] = path.match(/^([^:\n].*?)(\.[A-Za-z0-9]{0,10})?$/) as RegExpMatchArray
163
+
164
+ const id = randomBytes(16).toString('base64url')
165
+ const urlSafeName = name.replace(/[^A-Za-z0-9]/g, '-')
166
+ if (ext) return `${urlSafeName}-${id}${ext}`
167
+ return `${urlSafeName}-${id}`
168
+ }
@@ -1,133 +1,133 @@
1
- import type { SimpleFieldTypeInfo } from '../../../types'
2
- import {
3
- type BaseListTypeInfo,
4
- type FieldTypeFunc,
5
- type CommonFieldConfig,
6
- fieldType,
7
- orderDirectionEnum,
8
- } from '../../../types'
9
- import { g } from '../../..'
10
- import { filters } from '../../filters'
11
- import { makeValidateHook, defaultIsRequired } from '../../non-null-graphql'
12
- import type { controller } from './views'
13
-
14
- export type FloatFieldConfig<ListTypeInfo extends BaseListTypeInfo> = CommonFieldConfig<
15
- ListTypeInfo,
16
- SimpleFieldTypeInfo<'Float'>
17
- > & {
18
- isIndexed?: boolean | 'unique'
19
- defaultValue?: number | null
20
- validation?: {
21
- isRequired?: boolean
22
- min?: number
23
- max?: number
24
- }
25
- db?: {
26
- isNullable?: boolean
27
- map?: string
28
- extendPrismaSchema?: (field: string) => string
29
- }
30
- }
31
-
32
- export function float<ListTypeInfo extends BaseListTypeInfo>(
33
- config: FloatFieldConfig<ListTypeInfo> = {}
34
- ): FieldTypeFunc<ListTypeInfo> {
35
- const { defaultValue: defaultValue_, isIndexed, validation = {} } = config
36
-
37
- const { isRequired = false, min, max } = validation
38
- const defaultValue = defaultValue_ ?? null
39
-
40
- return meta => {
41
- if (defaultValue !== null && !Number.isFinite(defaultValue)) {
42
- throw new Error(
43
- `${meta.listKey}.${meta.fieldKey} specifies a default value of: ${defaultValue} but it must be a valid finite number`
44
- )
45
- }
46
- if (min !== undefined && !Number.isFinite(min)) {
47
- throw new Error(
48
- `${meta.listKey}.${meta.fieldKey} specifies validation.min: ${min} but it must be a valid finite number`
49
- )
50
- }
51
- if (max !== undefined && !Number.isFinite(max)) {
52
- throw new Error(
53
- `${meta.listKey}.${meta.fieldKey} specifies validation.max: ${max} but it must be a valid finite number`
54
- )
55
- }
56
- if (min !== undefined && max !== undefined && min > max) {
57
- throw new Error(
58
- `${meta.listKey}.${meta.fieldKey} specifies a validation.max that is less than the validation.min, and therefore has no valid options`
59
- )
60
- }
61
-
62
- const hasAdditionalValidation = min !== undefined || max !== undefined
63
- const { mode, validate } = makeValidateHook(
64
- meta,
65
- config,
66
- hasAdditionalValidation
67
- ? ({ resolvedData, operation, addValidationError }) => {
68
- if (operation === 'delete') return
69
-
70
- const value = resolvedData[meta.fieldKey]
71
- if (typeof value === 'number') {
72
- if (min !== undefined && value < min) {
73
- addValidationError(`value must be greater than or equal to ${min}`)
74
- }
75
-
76
- if (max !== undefined && value > max) {
77
- addValidationError(`value must be less than or equal to ${max}`)
78
- }
79
- }
80
- }
81
- : undefined
82
- )
83
-
84
- return fieldType({
85
- kind: 'scalar',
86
- mode,
87
- scalar: 'Float',
88
- index: isIndexed === true ? 'index' : isIndexed || undefined,
89
- default:
90
- typeof defaultValue === 'number' ? { kind: 'literal', value: defaultValue } : undefined,
91
- map: config.db?.map,
92
- extendPrismaSchema: config.db?.extendPrismaSchema,
93
- })({
94
- ...config,
95
- ...defaultIsRequired(config, isRequired),
96
- hooks: {
97
- ...config.hooks,
98
- validate,
99
- },
100
- input: {
101
- uniqueWhere: isIndexed === 'unique' ? { arg: g.arg({ type: g.Float }) } : undefined,
102
- where: {
103
- arg: g.arg({ type: filters[meta.provider].Float[mode] }),
104
- resolve: mode === 'optional' ? filters.resolveCommon : undefined,
105
- },
106
- create: {
107
- arg: g.arg({
108
- type: g.Float,
109
- defaultValue: typeof defaultValue === 'number' ? defaultValue : undefined,
110
- }),
111
- resolve(value) {
112
- if (value === undefined) return defaultValue
113
- return value
114
- },
115
- },
116
- update: { arg: g.arg({ type: g.Float }) },
117
- orderBy: { arg: g.arg({ type: orderDirectionEnum }) },
118
- },
119
- output: g.field({ type: g.Float }),
120
- __ksTelemetryFieldTypeName: '@nixxie-cms/float',
121
- views: '@nixxie-cms/core/fields/types/float/views',
122
- getAdminMeta(): Parameters<typeof controller>[0]['fieldMeta'] {
123
- return {
124
- validation: {
125
- min: min ?? null,
126
- max: max ?? null,
127
- },
128
- defaultValue: defaultValue === null ? null : defaultValue.toString(),
129
- }
130
- },
131
- })
132
- }
133
- }
1
+ import type { SimpleFieldTypeInfo } from '../../../types'
2
+ import {
3
+ type BaseListTypeInfo,
4
+ type FieldTypeFunc,
5
+ type CommonFieldConfig,
6
+ fieldType,
7
+ orderDirectionEnum,
8
+ } from '../../../types'
9
+ import { g } from '../../..'
10
+ import { filters } from '../../filters'
11
+ import { makeValidateHook, defaultIsRequired } from '../../non-null-graphql'
12
+ import type { controller } from './views'
13
+
14
+ export type FloatFieldConfig<ListTypeInfo extends BaseListTypeInfo> = CommonFieldConfig<
15
+ ListTypeInfo,
16
+ SimpleFieldTypeInfo<'Float'>
17
+ > & {
18
+ isIndexed?: boolean | 'unique'
19
+ defaultValue?: number | null
20
+ validation?: {
21
+ isRequired?: boolean
22
+ min?: number
23
+ max?: number
24
+ }
25
+ db?: {
26
+ isNullable?: boolean
27
+ map?: string
28
+ extendPrismaSchema?: (field: string) => string
29
+ }
30
+ }
31
+
32
+ export function float<ListTypeInfo extends BaseListTypeInfo>(
33
+ config: FloatFieldConfig<ListTypeInfo> = {}
34
+ ): FieldTypeFunc<ListTypeInfo> {
35
+ const { defaultValue: defaultValue_, isIndexed, validation = {} } = config
36
+
37
+ const { isRequired = false, min, max } = validation
38
+ const defaultValue = defaultValue_ ?? null
39
+
40
+ return meta => {
41
+ if (defaultValue !== null && !Number.isFinite(defaultValue)) {
42
+ throw new Error(
43
+ `${meta.listKey}.${meta.fieldKey} specifies a default value of: ${defaultValue} but it must be a valid finite number`
44
+ )
45
+ }
46
+ if (min !== undefined && !Number.isFinite(min)) {
47
+ throw new Error(
48
+ `${meta.listKey}.${meta.fieldKey} specifies validation.min: ${min} but it must be a valid finite number`
49
+ )
50
+ }
51
+ if (max !== undefined && !Number.isFinite(max)) {
52
+ throw new Error(
53
+ `${meta.listKey}.${meta.fieldKey} specifies validation.max: ${max} but it must be a valid finite number`
54
+ )
55
+ }
56
+ if (min !== undefined && max !== undefined && min > max) {
57
+ throw new Error(
58
+ `${meta.listKey}.${meta.fieldKey} specifies a validation.max that is less than the validation.min, and therefore has no valid options`
59
+ )
60
+ }
61
+
62
+ const hasAdditionalValidation = min !== undefined || max !== undefined
63
+ const { mode, validate } = makeValidateHook(
64
+ meta,
65
+ config,
66
+ hasAdditionalValidation
67
+ ? ({ resolvedData, operation, addValidationError }) => {
68
+ if (operation === 'delete') return
69
+
70
+ const value = resolvedData[meta.fieldKey]
71
+ if (typeof value === 'number') {
72
+ if (min !== undefined && value < min) {
73
+ addValidationError(`value must be greater than or equal to ${min}`)
74
+ }
75
+
76
+ if (max !== undefined && value > max) {
77
+ addValidationError(`value must be less than or equal to ${max}`)
78
+ }
79
+ }
80
+ }
81
+ : undefined
82
+ )
83
+
84
+ return fieldType({
85
+ kind: 'scalar',
86
+ mode,
87
+ scalar: 'Float',
88
+ index: isIndexed === true ? 'index' : isIndexed || undefined,
89
+ default:
90
+ typeof defaultValue === 'number' ? { kind: 'literal', value: defaultValue } : undefined,
91
+ map: config.db?.map,
92
+ extendPrismaSchema: config.db?.extendPrismaSchema,
93
+ })({
94
+ ...config,
95
+ ...defaultIsRequired(config, isRequired),
96
+ hooks: {
97
+ ...config.hooks,
98
+ validate,
99
+ },
100
+ input: {
101
+ uniqueWhere: isIndexed === 'unique' ? { arg: g.arg({ type: g.Float }) } : undefined,
102
+ where: {
103
+ arg: g.arg({ type: filters[meta.provider].Float[mode] }),
104
+ resolve: mode === 'optional' ? filters.resolveCommon : undefined,
105
+ },
106
+ create: {
107
+ arg: g.arg({
108
+ type: g.Float,
109
+ defaultValue: typeof defaultValue === 'number' ? defaultValue : undefined,
110
+ }),
111
+ resolve(value) {
112
+ if (value === undefined) return defaultValue
113
+ return value
114
+ },
115
+ },
116
+ update: { arg: g.arg({ type: g.Float }) },
117
+ orderBy: { arg: g.arg({ type: orderDirectionEnum }) },
118
+ },
119
+ output: g.field({ type: g.Float }),
120
+ __nxTelemetryFieldTypeName: '@nixxie-cms/float',
121
+ views: '@nixxie-cms/core/fields/types/float/views',
122
+ getAdminMeta(): Parameters<typeof controller>[0]['fieldMeta'] {
123
+ return {
124
+ validation: {
125
+ min: min ?? null,
126
+ max: max ?? null,
127
+ },
128
+ defaultValue: defaultValue === null ? null : defaultValue.toString(),
129
+ }
130
+ },
131
+ })
132
+ }
133
+ }