@nixxie-cms/core 1.0.0 → 1.0.2

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 (187) 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 +44 -15
  100. package/scripts/cli/dist/nixxie-cms-core-scripts-cli.esm.js +44 -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/next-config.ts +29 -0
  115. package/src/admin-ui/templates/no-access.ts +7 -7
  116. package/src/fields/types/bigInt/index.ts +181 -181
  117. package/src/fields/types/bytes/index.ts +275 -275
  118. package/src/fields/types/calendarDay/index.ts +194 -194
  119. package/src/fields/types/checkbox/index.ts +76 -76
  120. package/src/fields/types/decimal/index.ts +182 -182
  121. package/src/fields/types/file/index.ts +168 -168
  122. package/src/fields/types/float/index.ts +133 -133
  123. package/src/fields/types/image/index.ts +244 -244
  124. package/src/fields/types/integer/index.ts +156 -156
  125. package/src/fields/types/json/index.ts +77 -77
  126. package/src/fields/types/multiselect/index.ts +212 -212
  127. package/src/fields/types/password/index.ts +241 -241
  128. package/src/fields/types/relationship/index.ts +381 -381
  129. package/src/fields/types/relationship/views/RelationshipTable.tsx +190 -190
  130. package/src/fields/types/select/index.ts +226 -226
  131. package/src/fields/types/text/index.ts +207 -207
  132. package/src/fields/types/timestamp/index.ts +116 -116
  133. package/src/fields/types/virtual/index.ts +108 -108
  134. package/src/helpers.ts +342 -316
  135. package/src/index.ts +4 -0
  136. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/id-field-view.tsx +167 -167
  137. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/App/index.tsx +22 -22
  138. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/CreateItemPage/index.tsx +71 -71
  139. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/HomePage/index.tsx +333 -333
  140. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ItemPage/common.tsx +358 -358
  141. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ItemPage/index.tsx +483 -483
  142. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ListPage/FilterAdd.tsx +221 -221
  143. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ListPage/PaginationControls.tsx +170 -170
  144. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ListPage/Tag.tsx +72 -72
  145. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ListPage/index.tsx +1006 -1006
  146. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/NoAccessPage/index.tsx +24 -24
  147. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/artifacts.ts +5 -5
  148. package/src/lib/context/createContext.ts +165 -161
  149. package/src/lib/core/initialise-lists.ts +1097 -1097
  150. package/src/lib/id-field.ts +214 -214
  151. package/src/lib/telemetry.ts +342 -342
  152. package/src/schema.ts +237 -233
  153. package/src/scripts/telemetry.ts +1 -1
  154. package/src/types/config/index.ts +400 -333
  155. package/src/types/config/lists.ts +4 -4
  156. package/src/types/context.ts +700 -530
  157. package/src/types/next-fields.ts +499 -499
  158. package/src/types/telemetry.ts +51 -51
  159. package/tests/telemetry.test.ts +361 -361
  160. package/CHANGELOG.md +0 -3158
  161. package/___internal-do-not-use-will-break-in-patch/admin-ui/id-field-view/package.json +0 -4
  162. package/___internal-do-not-use-will-break-in-patch/admin-ui/next-config/package.json +0 -4
  163. package/___internal-do-not-use-will-break-in-patch/admin-ui/pages/App/package.json +0 -4
  164. package/___internal-do-not-use-will-break-in-patch/admin-ui/pages/CreateItemPage/package.json +0 -4
  165. package/___internal-do-not-use-will-break-in-patch/admin-ui/pages/HomePage/package.json +0 -4
  166. package/___internal-do-not-use-will-break-in-patch/admin-ui/pages/ItemPage/package.json +0 -4
  167. package/___internal-do-not-use-will-break-in-patch/admin-ui/pages/ListPage/package.json +0 -4
  168. package/___internal-do-not-use-will-break-in-patch/admin-ui/pages/NoAccessPage/package.json +0 -4
  169. package/___internal-do-not-use-will-break-in-patch/artifacts/package.json +0 -4
  170. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/admin-ui/id-field-view.d.ts.map +0 -1
  171. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/App/index.d.ts.map +0 -1
  172. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/CreateItemPage/index.d.ts.map +0 -1
  173. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/HomePage/index.d.ts.map +0 -1
  174. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/ItemPage/index.d.ts.map +0 -1
  175. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/ListPage/index.d.ts.map +0 -1
  176. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/NoAccessPage/index.d.ts.map +0 -1
  177. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/artifacts.d.ts.map +0 -1
  178. /package/dist/{common-1a350e11.cjs.js → common-5933f758.cjs.js} +0 -0
  179. /package/dist/{common-29fc82e6.esm.js → common-ea5c441a.esm.js} +0 -0
  180. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/id-field-view.d.ts +0 -0
  181. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/App/index.d.ts +0 -0
  182. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/CreateItemPage/index.d.ts +0 -0
  183. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/HomePage/index.d.ts +0 -0
  184. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ItemPage/index.d.ts +0 -0
  185. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ListPage/index.d.ts +0 -0
  186. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/NoAccessPage/index.d.ts +0 -0
  187. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/artifacts.d.ts +0 -0
@@ -1,244 +1,244 @@
1
- import type {
2
- BaseListTypeInfo,
3
- FieldTypeFunc,
4
- CommonFieldConfig,
5
- NixxieContext,
6
- BaseNixxieTypeInfo,
7
- MaybePromise,
8
- FieldHooks,
9
- StorageStrategy,
10
- } from '../../../types'
11
- import { fieldType } from '../../../types'
12
- import { g } from '../../..'
13
- import { SUPPORTED_IMAGE_EXTENSIONS } from './utils'
14
- import { merge } from '../../resolve-hooks'
15
- import type { InferValueFromArg, InferValueFromInputType } from '@graphql-ts/schema'
16
- import { randomBytes } from 'node:crypto'
17
- import type { ImageExtension } from './internal-utils'
18
- import { getBytesFromStream, getImageMetadata, teeStream } from './internal-utils'
19
-
20
- export type FieldTypeInfo = {
21
- item: undefined
22
- inputs: {
23
- create: InferValueFromInputType<typeof ImageFieldInput> | null | undefined
24
- update: InferValueFromInputType<typeof ImageFieldInput> | null | undefined
25
- where: undefined
26
- uniqueWhere: undefined
27
- orderBy: undefined
28
- }
29
- prisma: {
30
- create: undefined
31
- update: undefined
32
- }
33
- }
34
-
35
- export type ImageFieldConfig<ListTypeInfo extends BaseListTypeInfo> = CommonFieldConfig<
36
- ListTypeInfo,
37
- FieldTypeInfo
38
- > & {
39
- storage: StorageStrategy<ListTypeInfo['all']>
40
- transformName?: (originalFilename: string, extension: string) => MaybePromise<string>
41
- db?: {
42
- extendPrismaSchema?: (field: string) => string
43
- }
44
- }
45
-
46
- // TODO: dynamic
47
- const ImageExtensionEnum = g.enum({
48
- name: 'ImageExtension',
49
- values: g.enumValues(SUPPORTED_IMAGE_EXTENSIONS),
50
- })
51
-
52
- const ImageFieldInput = g.inputObject({
53
- name: 'ImageFieldInput',
54
- fields: {
55
- upload: g.arg({ type: g.nonNull(g.Upload) }),
56
- },
57
- })
58
-
59
- const inputArg = g.arg({ type: ImageFieldInput })
60
-
61
- type ImageData = {
62
- id: string
63
- extension: ImageExtension
64
- filesize: number
65
- width: number
66
- height: number
67
- url: (_args: {}, context: NixxieContext) => Promise<string>
68
- }
69
-
70
- const ImageFieldOutput = g.object<ImageData>()({
71
- name: 'ImageFieldOutput',
72
- fields: {
73
- id: g.field({ type: g.nonNull(g.ID) }),
74
- url: g.field({ type: g.nonNull(g.String) }),
75
- extension: g.field({ type: g.nonNull(ImageExtensionEnum) }),
76
- filesize: g.field({ type: g.nonNull(g.Int) }),
77
- width: g.field({ type: g.nonNull(g.Int) }),
78
- height: g.field({ type: g.nonNull(g.Int) }),
79
- },
80
- })
81
-
82
- // this is a conservative estimate of the number of bytes
83
- // that we need to determine the image metadata
84
- // since 1Kib in memory will be fine
85
- const bytesToDetermineImageMetadata = 1024
86
-
87
- async function inputResolver(
88
- storage: StorageStrategy<BaseNixxieTypeInfo>,
89
- transformName: (originalFilename: string, extension: string) => MaybePromise<string>,
90
- context: NixxieContext,
91
- data: InferValueFromArg<typeof inputArg>
92
- ): Promise<{
93
- id: string | null | undefined
94
- extension: ImageExtension | null | undefined
95
- filesize: number | null | undefined
96
- width: number | null | undefined
97
- height: number | null | undefined
98
- }> {
99
- if (data === null || data === undefined) {
100
- return {
101
- id: data,
102
- extension: data,
103
- filesize: data,
104
- width: data,
105
- height: data,
106
- }
107
- }
108
-
109
- const upload = await data.upload
110
- let filesize = 0
111
- const _readable = upload.createReadStream()
112
- const [readableForFilesize, _readable2] = teeStream(_readable)
113
- const [readableForMetadata, readableForUpload] = teeStream(_readable2)
114
- readableForFilesize.on('data', data => {
115
- filesize += data.length
116
- })
117
-
118
- const buffer = await getBytesFromStream(readableForMetadata, bytesToDetermineImageMetadata)
119
- const metadata = getImageMetadata(buffer)
120
- if (!metadata) {
121
- throw new Error('File type not found')
122
- }
123
- const id = await transformName(upload.filename, metadata.extension)
124
- await storage.put(
125
- `${id}.${metadata.extension}`,
126
- readableForUpload,
127
- {
128
- contentType: {
129
- png: 'image/png',
130
- webp: 'image/webp',
131
- gif: 'image/gif',
132
- jpg: 'image/jpeg',
133
- }[metadata.extension],
134
- },
135
- context
136
- )
137
- return {
138
- filesize,
139
- id,
140
- extension: metadata.extension,
141
- height: metadata.height,
142
- width: metadata.width,
143
- }
144
- }
145
-
146
- const extensionsSet = new Set<string>(SUPPORTED_IMAGE_EXTENSIONS)
147
-
148
- function isValidImageExtension(extension: string): extension is ImageExtension {
149
- return extensionsSet.has(extension)
150
- }
151
-
152
- export function image<ListTypeInfo extends BaseListTypeInfo>(
153
- config: ImageFieldConfig<ListTypeInfo>
154
- ): FieldTypeFunc<ListTypeInfo> {
155
- const { transformName = defaultTransformName } = config
156
- return meta => {
157
- const { fieldKey } = meta
158
-
159
- if ('isIndexed' in config) {
160
- throw Error("isIndexed: 'unique' is not a supported option for field type image")
161
- }
162
-
163
- const afterOperationResolver: Extract<
164
- FieldHooks<BaseListTypeInfo, FieldTypeInfo>['afterOperation'],
165
- (args: any) => any
166
- > = async function afterOperationResolver(args) {
167
- if (args.operation === 'update' || args.operation === 'delete') {
168
- const idKey = `${fieldKey}_id`
169
- const oldId = args.originalItem?.[idKey] as string | null | undefined
170
- const newId = args.item?.[idKey] as string | null | undefined
171
- const extensionKey = `${fieldKey}_extension`
172
- const oldExtension = args.originalItem?.[extensionKey] as string | null | undefined
173
- const newExtension = args.item?.[extensionKey] as string | null | undefined
174
- // this will occur on an update where an image already existed but has been
175
- // changed, or on a delete, where there is no longer an item
176
- if (
177
- typeof oldId === 'string' &&
178
- typeof oldExtension === 'string' &&
179
- isValidImageExtension(oldExtension) &&
180
- (oldId !== newId || oldExtension !== newExtension)
181
- ) {
182
- await config.storage.delete(`${oldId}.${oldExtension}`, args.context)
183
- }
184
- }
185
- }
186
-
187
- return fieldType({
188
- kind: 'multi',
189
- extendPrismaSchema: config.db?.extendPrismaSchema,
190
- fields: {
191
- id: { kind: 'scalar', scalar: 'String', mode: 'optional' },
192
- extension: { kind: 'scalar', scalar: 'String', mode: 'optional' },
193
- filesize: { kind: 'scalar', scalar: 'Int', mode: 'optional' },
194
- width: { kind: 'scalar', scalar: 'Int', mode: 'optional' },
195
- height: { kind: 'scalar', scalar: 'Int', mode: 'optional' },
196
- },
197
- })({
198
- ...config,
199
- hooks: {
200
- ...config.hooks,
201
- afterOperation: merge(config.hooks?.afterOperation, {
202
- update: afterOperationResolver,
203
- delete: afterOperationResolver,
204
- }),
205
- },
206
- input: {
207
- create: {
208
- arg: inputArg,
209
- resolve: (data, context) => inputResolver(config.storage, transformName, context, data),
210
- },
211
- update: {
212
- arg: inputArg,
213
- resolve: (data, context) => inputResolver(config.storage, transformName, context, data),
214
- },
215
- },
216
- output: g.field({
217
- type: ImageFieldOutput,
218
- resolve({ value: { id, extension, filesize, width, height } }): ImageData | null {
219
- if (id === null) return null
220
- if (extension === null) return null
221
- if (filesize === null) return null
222
- if (width === null) return null
223
- if (height === null) return null
224
- if (!isValidImageExtension(extension)) return null // TODO: dynamic
225
-
226
- return {
227
- id,
228
- filesize,
229
- width,
230
- height,
231
- extension,
232
- url: async (_, context) => config.storage.url(`${id}.${extension}`, context),
233
- }
234
- },
235
- }),
236
- __ksTelemetryFieldTypeName: '@nixxie-cms/image',
237
- views: '@nixxie-cms/core/fields/types/image/views',
238
- })
239
- }
240
- }
241
-
242
- function defaultTransformName(_: string) {
243
- return randomBytes(16).toString('base64url')
244
- }
1
+ import type {
2
+ BaseListTypeInfo,
3
+ FieldTypeFunc,
4
+ CommonFieldConfig,
5
+ NixxieContext,
6
+ BaseNixxieTypeInfo,
7
+ MaybePromise,
8
+ FieldHooks,
9
+ StorageStrategy,
10
+ } from '../../../types'
11
+ import { fieldType } from '../../../types'
12
+ import { g } from '../../..'
13
+ import { SUPPORTED_IMAGE_EXTENSIONS } from './utils'
14
+ import { merge } from '../../resolve-hooks'
15
+ import type { InferValueFromArg, InferValueFromInputType } from '@graphql-ts/schema'
16
+ import { randomBytes } from 'node:crypto'
17
+ import type { ImageExtension } from './internal-utils'
18
+ import { getBytesFromStream, getImageMetadata, teeStream } from './internal-utils'
19
+
20
+ export type FieldTypeInfo = {
21
+ item: undefined
22
+ inputs: {
23
+ create: InferValueFromInputType<typeof ImageFieldInput> | null | undefined
24
+ update: InferValueFromInputType<typeof ImageFieldInput> | null | undefined
25
+ where: undefined
26
+ uniqueWhere: undefined
27
+ orderBy: undefined
28
+ }
29
+ prisma: {
30
+ create: undefined
31
+ update: undefined
32
+ }
33
+ }
34
+
35
+ export type ImageFieldConfig<ListTypeInfo extends BaseListTypeInfo> = CommonFieldConfig<
36
+ ListTypeInfo,
37
+ FieldTypeInfo
38
+ > & {
39
+ storage: StorageStrategy<ListTypeInfo['all']>
40
+ transformName?: (originalFilename: string, extension: string) => MaybePromise<string>
41
+ db?: {
42
+ extendPrismaSchema?: (field: string) => string
43
+ }
44
+ }
45
+
46
+ // TODO: dynamic
47
+ const ImageExtensionEnum = g.enum({
48
+ name: 'ImageExtension',
49
+ values: g.enumValues(SUPPORTED_IMAGE_EXTENSIONS),
50
+ })
51
+
52
+ const ImageFieldInput = g.inputObject({
53
+ name: 'ImageFieldInput',
54
+ fields: {
55
+ upload: g.arg({ type: g.nonNull(g.Upload) }),
56
+ },
57
+ })
58
+
59
+ const inputArg = g.arg({ type: ImageFieldInput })
60
+
61
+ type ImageData = {
62
+ id: string
63
+ extension: ImageExtension
64
+ filesize: number
65
+ width: number
66
+ height: number
67
+ url: (_args: {}, context: NixxieContext) => Promise<string>
68
+ }
69
+
70
+ const ImageFieldOutput = g.object<ImageData>()({
71
+ name: 'ImageFieldOutput',
72
+ fields: {
73
+ id: g.field({ type: g.nonNull(g.ID) }),
74
+ url: g.field({ type: g.nonNull(g.String) }),
75
+ extension: g.field({ type: g.nonNull(ImageExtensionEnum) }),
76
+ filesize: g.field({ type: g.nonNull(g.Int) }),
77
+ width: g.field({ type: g.nonNull(g.Int) }),
78
+ height: g.field({ type: g.nonNull(g.Int) }),
79
+ },
80
+ })
81
+
82
+ // this is a conservative estimate of the number of bytes
83
+ // that we need to determine the image metadata
84
+ // since 1Kib in memory will be fine
85
+ const bytesToDetermineImageMetadata = 1024
86
+
87
+ async function inputResolver(
88
+ storage: StorageStrategy<BaseNixxieTypeInfo>,
89
+ transformName: (originalFilename: string, extension: string) => MaybePromise<string>,
90
+ context: NixxieContext,
91
+ data: InferValueFromArg<typeof inputArg>
92
+ ): Promise<{
93
+ id: string | null | undefined
94
+ extension: ImageExtension | null | undefined
95
+ filesize: number | null | undefined
96
+ width: number | null | undefined
97
+ height: number | null | undefined
98
+ }> {
99
+ if (data === null || data === undefined) {
100
+ return {
101
+ id: data,
102
+ extension: data,
103
+ filesize: data,
104
+ width: data,
105
+ height: data,
106
+ }
107
+ }
108
+
109
+ const upload = await data.upload
110
+ let filesize = 0
111
+ const _readable = upload.createReadStream()
112
+ const [readableForFilesize, _readable2] = teeStream(_readable)
113
+ const [readableForMetadata, readableForUpload] = teeStream(_readable2)
114
+ readableForFilesize.on('data', data => {
115
+ filesize += data.length
116
+ })
117
+
118
+ const buffer = await getBytesFromStream(readableForMetadata, bytesToDetermineImageMetadata)
119
+ const metadata = getImageMetadata(buffer)
120
+ if (!metadata) {
121
+ throw new Error('File type not found')
122
+ }
123
+ const id = await transformName(upload.filename, metadata.extension)
124
+ await storage.put(
125
+ `${id}.${metadata.extension}`,
126
+ readableForUpload,
127
+ {
128
+ contentType: {
129
+ png: 'image/png',
130
+ webp: 'image/webp',
131
+ gif: 'image/gif',
132
+ jpg: 'image/jpeg',
133
+ }[metadata.extension],
134
+ },
135
+ context
136
+ )
137
+ return {
138
+ filesize,
139
+ id,
140
+ extension: metadata.extension,
141
+ height: metadata.height,
142
+ width: metadata.width,
143
+ }
144
+ }
145
+
146
+ const extensionsSet = new Set<string>(SUPPORTED_IMAGE_EXTENSIONS)
147
+
148
+ function isValidImageExtension(extension: string): extension is ImageExtension {
149
+ return extensionsSet.has(extension)
150
+ }
151
+
152
+ export function image<ListTypeInfo extends BaseListTypeInfo>(
153
+ config: ImageFieldConfig<ListTypeInfo>
154
+ ): FieldTypeFunc<ListTypeInfo> {
155
+ const { transformName = defaultTransformName } = config
156
+ return meta => {
157
+ const { fieldKey } = meta
158
+
159
+ if ('isIndexed' in config) {
160
+ throw Error("isIndexed: 'unique' is not a supported option for field type image")
161
+ }
162
+
163
+ const afterOperationResolver: Extract<
164
+ FieldHooks<BaseListTypeInfo, FieldTypeInfo>['afterOperation'],
165
+ (args: any) => any
166
+ > = async function afterOperationResolver(args) {
167
+ if (args.operation === 'update' || args.operation === 'delete') {
168
+ const idKey = `${fieldKey}_id`
169
+ const oldId = args.originalItem?.[idKey] as string | null | undefined
170
+ const newId = args.item?.[idKey] as string | null | undefined
171
+ const extensionKey = `${fieldKey}_extension`
172
+ const oldExtension = args.originalItem?.[extensionKey] as string | null | undefined
173
+ const newExtension = args.item?.[extensionKey] as string | null | undefined
174
+ // this will occur on an update where an image already existed but has been
175
+ // changed, or on a delete, where there is no longer an item
176
+ if (
177
+ typeof oldId === 'string' &&
178
+ typeof oldExtension === 'string' &&
179
+ isValidImageExtension(oldExtension) &&
180
+ (oldId !== newId || oldExtension !== newExtension)
181
+ ) {
182
+ await config.storage.delete(`${oldId}.${oldExtension}`, args.context)
183
+ }
184
+ }
185
+ }
186
+
187
+ return fieldType({
188
+ kind: 'multi',
189
+ extendPrismaSchema: config.db?.extendPrismaSchema,
190
+ fields: {
191
+ id: { kind: 'scalar', scalar: 'String', mode: 'optional' },
192
+ extension: { kind: 'scalar', scalar: 'String', mode: 'optional' },
193
+ filesize: { kind: 'scalar', scalar: 'Int', mode: 'optional' },
194
+ width: { kind: 'scalar', scalar: 'Int', mode: 'optional' },
195
+ height: { kind: 'scalar', scalar: 'Int', mode: 'optional' },
196
+ },
197
+ })({
198
+ ...config,
199
+ hooks: {
200
+ ...config.hooks,
201
+ afterOperation: merge(config.hooks?.afterOperation, {
202
+ update: afterOperationResolver,
203
+ delete: afterOperationResolver,
204
+ }),
205
+ },
206
+ input: {
207
+ create: {
208
+ arg: inputArg,
209
+ resolve: (data, context) => inputResolver(config.storage, transformName, context, data),
210
+ },
211
+ update: {
212
+ arg: inputArg,
213
+ resolve: (data, context) => inputResolver(config.storage, transformName, context, data),
214
+ },
215
+ },
216
+ output: g.field({
217
+ type: ImageFieldOutput,
218
+ resolve({ value: { id, extension, filesize, width, height } }): ImageData | null {
219
+ if (id === null) return null
220
+ if (extension === null) return null
221
+ if (filesize === null) return null
222
+ if (width === null) return null
223
+ if (height === null) return null
224
+ if (!isValidImageExtension(extension)) return null // TODO: dynamic
225
+
226
+ return {
227
+ id,
228
+ filesize,
229
+ width,
230
+ height,
231
+ extension,
232
+ url: async (_, context) => config.storage.url(`${id}.${extension}`, context),
233
+ }
234
+ },
235
+ }),
236
+ __nxTelemetryFieldTypeName: '@nixxie-cms/image',
237
+ views: '@nixxie-cms/core/fields/types/image/views',
238
+ })
239
+ }
240
+ }
241
+
242
+ function defaultTransformName(_: string) {
243
+ return randomBytes(16).toString('base64url')
244
+ }