@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,530 +1,700 @@
1
- import type { IncomingMessage, ServerResponse } from 'http'
2
- import type { DocumentNode, ExecutionResult, GraphQLSchema } from 'graphql'
3
- import type { TypedDocumentNode } from '@graphql-typed-document-node/core'
4
- import type { InitialisedList } from '../lib/core/initialise-lists'
5
- import type { SessionStrategy } from './session'
6
- import type { BaseNixxieTypeInfo, BaseListTypeInfo } from './type-info'
7
- import type { MaybePromise } from './utils'
8
-
9
- // ── Email service types (defined here so @nixxie-cms/email can implement without circular deps) ──
10
-
11
- export type NixxieEmailRecipient = string | { name?: string; address: string }
12
-
13
- export type NixxieEmailAttachment = {
14
- filename?: string
15
- content?: string | Buffer
16
- path?: string
17
- href?: string
18
- contentType?: string
19
- encoding?: string
20
- contentDisposition?: 'attachment' | 'inline'
21
- /** Content ID for inline embedded images (used with src="cid:…") */
22
- cid?: string
23
- }
24
-
25
- export type NixxieEmailSendOptions = {
26
- to: NixxieEmailRecipient | NixxieEmailRecipient[]
27
- cc?: NixxieEmailRecipient | NixxieEmailRecipient[]
28
- bcc?: NixxieEmailRecipient | NixxieEmailRecipient[]
29
- from?: NixxieEmailRecipient
30
- replyTo?: NixxieEmailRecipient | NixxieEmailRecipient[]
31
- subject: string
32
- /** Name of a template file (without extension) to render */
33
- template?: string
34
- /** Data passed to the template engine */
35
- data?: Record<string, unknown>
36
- /** Raw HTML body — used when not using a template */
37
- html?: string
38
- /** Plain-text body */
39
- text?: string
40
- attachments?: NixxieEmailAttachment[]
41
- headers?: Record<string, string>
42
- /** Skip the suppression-list check for this send */
43
- skipSuppressionCheck?: boolean
44
- }
45
-
46
- export type NixxieEmailQueueOptions = NixxieEmailSendOptions & {
47
- /** Schedule delivery for a future date */
48
- sendAt?: Date
49
- priority?: 'high' | 'normal' | 'low'
50
- }
51
-
52
- export type NixxieEmailSentInfo = {
53
- messageId: string
54
- accepted: string[]
55
- rejected: string[]
56
- response?: string
57
- timestamp: Date
58
- }
59
-
60
- export type NixxieEmailQueueStats = {
61
- pending: number
62
- processing: number
63
- failed: number
64
- completed: number
65
- }
66
-
67
- /**
68
- * Interface that @nixxie-cms/email's EmailService implements.
69
- * Defined in core so it can be referenced in NixxieContext without a circular dependency.
70
- */
71
- export type NixxieEmailService = {
72
- /** Send an email immediately. Resolves when the transport accepts the message. */
73
- send(options: NixxieEmailSendOptions): Promise<NixxieEmailSentInfo>
74
- /** Send the same email to multiple recipients individually. */
75
- bulk(
76
- recipients: NixxieEmailRecipient[],
77
- options: Omit<NixxieEmailSendOptions, 'to'>
78
- ): Promise<NixxieEmailSentInfo[]>
79
- /** Add an email to the outbound queue (non-blocking). Returns the job ID. */
80
- queue(options: NixxieEmailQueueOptions): string
81
- /** Add an address to the suppression list. */
82
- addToSuppressionList(email: string): void
83
- /** Remove an address from the suppression list. */
84
- removeFromSuppressionList(email: string): void
85
- /** Returns true if the address is suppressed. */
86
- isSuppressed(email: string): boolean
87
- /** Returns all currently suppressed addresses. */
88
- getSuppressionList(): string[]
89
- /** Returns live queue statistics. */
90
- getQueueStats(): NixxieEmailQueueStats
91
- /** Wait for the queue to drain, then stop processing. */
92
- flushQueue(): Promise<void>
93
- /** Gracefully shut down the service and close transport connections. */
94
- close(): Promise<void>
95
- }
96
-
97
- // ── Jobs service ──
98
-
99
- export type NixxieJobDefinition = {
100
- name: string
101
- /** Cron expression or interval in milliseconds */
102
- schedule: string | number
103
- /** Handler function invoked on each tick */
104
- handler: (ctx: { jobId: string; scheduledAt: Date }) => Promise<void> | void
105
- /** Immediately run the job once on registration */
106
- runImmediately?: boolean
107
- /** Disable job without removing the definition */
108
- enabled?: boolean
109
- /** Milliseconds before a running job is considered stuck */
110
- timeout?: number
111
- }
112
-
113
- export type NixxieJobStatus = 'idle' | 'running' | 'error' | 'disabled'
114
-
115
- export type NixxieJobInfo = {
116
- name: string
117
- status: NixxieJobStatus
118
- lastRun?: Date
119
- lastError?: string
120
- nextRun?: Date
121
- runs: number
122
- }
123
-
124
- export type NixxieJobsService = {
125
- /** Register a job definition. Call before start() or at any time for hot-registration. */
126
- define(job: NixxieJobDefinition): void
127
- /** Remove a registered job by name. */
128
- remove(name: string): void
129
- /** Manually trigger a job by name, regardless of its schedule. */
130
- trigger(name: string): Promise<void>
131
- /** List all registered jobs with their current status. */
132
- list(): NixxieJobInfo[]
133
- /** Get info for a specific job. */
134
- get(name: string): NixxieJobInfo | undefined
135
- /** Gracefully stop all running jobs. */
136
- close(): Promise<void>
137
- }
138
-
139
- // ── Cache service ──
140
-
141
- export type NixxieCacheSetOptions = {
142
- /** Time-to-live in milliseconds. Omit for no expiry. */
143
- ttl?: number
144
- /** Tags for group invalidation */
145
- tags?: string[]
146
- }
147
-
148
- export type NixxieCacheService = {
149
- /** Get a cached value. Returns undefined on miss. */
150
- get<T = unknown>(key: string): Promise<T | undefined>
151
- /** Set a cached value. */
152
- set(key: string, value: unknown, options?: NixxieCacheSetOptions): Promise<void>
153
- /** Delete a single key. */
154
- delete(key: string): Promise<void>
155
- /** Delete all keys matching a glob pattern. */
156
- deletePattern(pattern: string): Promise<void>
157
- /** Delete all keys with a given tag. */
158
- deleteByTag(tag: string): Promise<void>
159
- /** Returns true if the key exists and has not expired. */
160
- has(key: string): Promise<boolean>
161
- /**
162
- * Cache-aside helper. Returns the cached value if present,
163
- * otherwise calls fn(), caches the result, and returns it.
164
- */
165
- wrap<T>(key: string, fn: () => Promise<T>, options?: NixxieCacheSetOptions): Promise<T>
166
- /** Flush the entire cache. */
167
- clear(): Promise<void>
168
- /** Gracefully close cache connections. */
169
- close(): Promise<void>
170
- }
171
-
172
- // ── Audit service ──
173
-
174
- export type NixxieAuditAction =
175
- | 'create'
176
- | 'update'
177
- | 'delete'
178
- | 'read'
179
- | 'login'
180
- | 'logout'
181
- | (string & {})
182
-
183
- export type NixxieAuditEntry = {
184
- action: NixxieAuditAction
185
- /** List name (e.g. 'User', 'Post') */
186
- resource?: string
187
- /** Item ID that was acted upon */
188
- resourceId?: string
189
- /** Session / user who performed the action */
190
- actor?: {
191
- id?: string
192
- label?: string
193
- ip?: string
194
- }
195
- /** Fields that changed: { field: { from, to } } */
196
- changes?: Record<string, { from: unknown; to: unknown }>
197
- /** Arbitrary extra context */
198
- meta?: Record<string, unknown>
199
- timestamp?: Date
200
- }
201
-
202
- export type NixxieAuditQuery = {
203
- action?: NixxieAuditAction
204
- resource?: string
205
- resourceId?: string
206
- actorId?: string
207
- from?: Date
208
- to?: Date
209
- take?: number
210
- skip?: number
211
- }
212
-
213
- export type NixxieAuditRecord = NixxieAuditEntry & {
214
- id: string
215
- timestamp: Date
216
- }
217
-
218
- export type NixxieAuditService = {
219
- /** Log an audit event. */
220
- log(entry: NixxieAuditEntry): Promise<NixxieAuditRecord>
221
- /** Query the audit log. */
222
- query(filters?: NixxieAuditQuery): Promise<NixxieAuditRecord[]>
223
- /** Count matching entries. */
224
- count(filters?: NixxieAuditQuery): Promise<number>
225
- /** Delete entries older than a given date. Returns the number of deleted records. */
226
- purge(before: Date): Promise<number>
227
- /** Gracefully shut down (flush pending writes). */
228
- close(): Promise<void>
229
- }
230
-
231
- // ── Webhooks service ──
232
-
233
- export type NixxieWebhookSubscription = {
234
- id: string
235
- event: string
236
- url: string
237
- secret?: string
238
- enabled: boolean
239
- createdAt: Date
240
- }
241
-
242
- export type NixxieWebhookDelivery = {
243
- id: string
244
- subscriptionId: string
245
- event: string
246
- payload: unknown
247
- status: 'pending' | 'delivered' | 'failed'
248
- attempts: number
249
- lastAttemptAt?: Date
250
- responseStatus?: number
251
- responseBody?: string
252
- error?: string
253
- }
254
-
255
- export type NixxieWebhookSubscribeOptions = {
256
- /** Optional signing secret — if provided, each request gets an `X-Nixxie-Signature` header */
257
- secret?: string
258
- /** Custom headers to send with every delivery */
259
- headers?: Record<string, string>
260
- /** Max delivery retries (default: 3) */
261
- retries?: number
262
- }
263
-
264
- export type NixxieWebhooksService = {
265
- /** Register a webhook endpoint for an event. Returns the subscription ID. */
266
- subscribe(event: string, url: string, options?: NixxieWebhookSubscribeOptions): Promise<NixxieWebhookSubscription>
267
- /** Remove a subscription by ID. */
268
- unsubscribe(id: string): Promise<void>
269
- /** Fire an event, delivering to all matching subscribers. */
270
- trigger(event: string, payload: unknown): Promise<void>
271
- /** List all registered subscriptions. */
272
- list(event?: string): NixxieWebhookSubscription[]
273
- /** Get delivery history for a subscription. */
274
- deliveries(subscriptionId: string, take?: number): NixxieWebhookDelivery[]
275
- /** Enable or disable a subscription. */
276
- setEnabled(id: string, enabled: boolean): void
277
- /** Gracefully stop delivery workers. */
278
- close(): Promise<void>
279
- }
280
-
281
- // ── Rate limit service ──
282
-
283
- export type NixxieRateLimitResult = {
284
- allowed: boolean
285
- remaining: number
286
- resetAt: Date
287
- /** Total limit for the window */
288
- limit: number
289
- }
290
-
291
- export type NixxieRateLimitOptions = {
292
- /** Maximum number of requests in the window */
293
- limit: number
294
- /** Window duration in milliseconds */
295
- window: number
296
- /** Cost of this operation (default: 1) */
297
- cost?: number
298
- }
299
-
300
- export type NixxieRateLimitService = {
301
- /**
302
- * Check without consuming — returns whether the key is within the limit.
303
- * Does NOT decrement the counter.
304
- */
305
- check(key: string, options: NixxieRateLimitOptions): Promise<NixxieRateLimitResult>
306
- /**
307
- * Consume from the limit. Returns the updated result.
308
- * Does NOT throw on exceeded limits — callers should check `result.allowed`.
309
- */
310
- consume(key: string, options: NixxieRateLimitOptions): Promise<NixxieRateLimitResult>
311
- /** Reset the counter for a key. */
312
- reset(key: string): Promise<void>
313
- /** Gracefully shut down (close Redis connections etc.). */
314
- close(): Promise<void>
315
- }
316
-
317
- // ── Health service ──
318
-
319
- export type NixxieHealthCheckFn = () => Promise<{ ok: boolean; message?: string; details?: unknown }>
320
-
321
- export type NixxieHealthCheckResult = {
322
- name: string
323
- status: 'healthy' | 'unhealthy'
324
- /** Whether this check failing marks the whole system unhealthy (true) or just degraded (false) */
325
- critical: boolean
326
- message?: string
327
- details?: unknown
328
- /** How long the check took in milliseconds */
329
- latency: number
330
- }
331
-
332
- export type NixxieHealthReport = {
333
- /** Overall system status */
334
- status: 'healthy' | 'degraded' | 'unhealthy'
335
- timestamp: string
336
- /** process.uptime() in seconds */
337
- uptime: number
338
- /** process.memoryUsage().heapUsed in MB */
339
- memoryMb: number
340
- checks: NixxieHealthCheckResult[]
341
- }
342
-
343
- export type NixxieHealthService = {
344
- /**
345
- * Register a named health check.
346
- * @param critical If true (default), failure marks the system 'unhealthy'. If false, 'degraded'.
347
- */
348
- register(name: string, check: NixxieHealthCheckFn, options?: { critical?: boolean }): void
349
- /** Remove a registered check by name. */
350
- unregister(name: string): void
351
- /** Run all registered checks and return the full health report. */
352
- check(): Promise<NixxieHealthReport>
353
- /**
354
- * Returns an Express Router with two routes:
355
- * - GET /live — liveness (process is up, returns 200 immediately)
356
- * - GET /ready readiness (runs all checks, returns 200 or 503)
357
- *
358
- * Mount in extendExpressApp: `app.use('/health', context.services.health.router())`
359
- */
360
- router(options?: { livePath?: string; readyPath?: string }): unknown
361
- }
362
-
363
- // ── Context ──
364
-
365
- export type NixxieContext<TypeInfo extends BaseNixxieTypeInfo = BaseNixxieTypeInfo> = {
366
- db: NixxieDbAPI<TypeInfo['lists']>
367
- query: NixxieListsAPI<TypeInfo['lists']>
368
- graphql: NixxieGraphQLAPI
369
- prisma: TypeInfo['prisma']
370
- /** Runtime services configured in nixxie.ts available everywhere context is available */
371
- services: {
372
- email: NixxieEmailService | null
373
- jobs: NixxieJobsService | null
374
- cache: NixxieCacheService | null
375
- audit: NixxieAuditService | null
376
- webhooks: NixxieWebhooksService | null
377
- rateLimit: NixxieRateLimitService | null
378
- health: NixxieHealthService | null
379
- }
380
- transaction: <T>(
381
- f: (context: NixxieContext<TypeInfo>) => MaybePromise<T>,
382
- options?: {
383
- maxWait?: number
384
- timeout?: number
385
- isolationLevel?: {
386
- Serializable: 'Serializable'
387
- }
388
- }
389
- ) => Promise<T>
390
-
391
- req?: IncomingMessage
392
- res?: ServerResponse
393
- sessionStrategy?: SessionStrategy<TypeInfo['session'], TypeInfo>
394
- session?: TypeInfo['session']
395
- withRequest: (req: IncomingMessage, res?: ServerResponse) => Promise<NixxieContext<TypeInfo>>
396
- withSession: (session?: TypeInfo['session']) => NixxieContext<TypeInfo>
397
-
398
- // privilege escalation
399
- internal: () => NixxieContext<TypeInfo> // WARNING: name may change
400
- sudo: () => NixxieContext<TypeInfo>
401
-
402
- /**
403
- * WARNING: may change in patch
404
- */
405
- __internal: {
406
- sudo: boolean
407
- lists: Record<string, InitialisedList>
408
- prisma: {
409
- DbNull: unknown
410
- JsonNull: unknown
411
- }
412
- }
413
- }
414
-
415
- // List item API
416
-
417
- type UniqueWhereInput<ListTypeInfo extends BaseListTypeInfo> =
418
- false extends ListTypeInfo['isSingleton']
419
- ? { readonly where: ListTypeInfo['inputs']['uniqueWhere'] }
420
- : { readonly where?: ListTypeInfo['inputs']['uniqueWhere'] }
421
-
422
- type ListAPI<ListTypeInfo extends BaseListTypeInfo> = {
423
- findMany(
424
- args?: {
425
- readonly where?: ListTypeInfo['inputs']['where']
426
- readonly take?: number
427
- readonly skip?: number
428
- readonly orderBy?:
429
- | ListTypeInfo['inputs']['orderBy']
430
- | readonly ListTypeInfo['inputs']['orderBy'][]
431
- readonly cursor?: ListTypeInfo['inputs']['uniqueWhere']
432
- } & ResolveFields
433
- ): Promise<readonly Record<string, any>[]>
434
- findOne(args: UniqueWhereInput<ListTypeInfo> & ResolveFields): Promise<Record<string, any>>
435
- count(args?: { readonly where?: ListTypeInfo['inputs']['where'] }): Promise<number>
436
- updateOne(
437
- args: UniqueWhereInput<ListTypeInfo> & {
438
- readonly data: ListTypeInfo['inputs']['update']
439
- } & ResolveFields
440
- ): Promise<Record<string, any>>
441
- updateMany(
442
- args: {
443
- readonly data: readonly (UniqueWhereInput<ListTypeInfo> & {
444
- readonly data: ListTypeInfo['inputs']['update']
445
- })[]
446
- } & ResolveFields
447
- ): Promise<Record<string, any>[]>
448
- createOne(
449
- args: { readonly data: ListTypeInfo['inputs']['create'] } & ResolveFields
450
- ): Promise<Record<string, any>>
451
- createMany(
452
- args: {
453
- readonly data: readonly ListTypeInfo['inputs']['create'][]
454
- } & ResolveFields
455
- ): Promise<Record<string, any>[]>
456
- deleteOne(
457
- args: UniqueWhereInput<ListTypeInfo> & ResolveFields
458
- ): Promise<Record<string, any> | null>
459
- deleteMany(
460
- args: {
461
- readonly where: readonly ListTypeInfo['inputs']['uniqueWhere'][]
462
- } & ResolveFields
463
- ): Promise<Record<string, any>[]>
464
- }
465
-
466
- export type NixxieListsAPI<ListsTypeInfo extends Record<string, BaseListTypeInfo>> = {
467
- [Key in keyof ListsTypeInfo]: ListAPI<ListsTypeInfo[Key]>
468
- }
469
-
470
- type ResolveFields = {
471
- /**
472
- * @default 'id'
473
- */
474
- readonly query?: string
475
- }
476
-
477
- type DbAPI<ListTypeInfo extends BaseListTypeInfo> = {
478
- findMany(args?: {
479
- readonly where?: ListTypeInfo['inputs']['where']
480
- readonly take?: number
481
- readonly skip?: number
482
- readonly orderBy?:
483
- | ListTypeInfo['inputs']['orderBy']
484
- | readonly ListTypeInfo['inputs']['orderBy'][]
485
- readonly cursor?: ListTypeInfo['inputs']['uniqueWhere']
486
- }): Promise<readonly ListTypeInfo['item'][]>
487
- findOne(args: UniqueWhereInput<ListTypeInfo>): Promise<ListTypeInfo['item'] | null>
488
- count(args?: { readonly where?: ListTypeInfo['inputs']['where'] }): Promise<number>
489
- updateOne(
490
- args: UniqueWhereInput<ListTypeInfo> & {
491
- readonly data: ListTypeInfo['inputs']['update']
492
- }
493
- ): Promise<ListTypeInfo['item']>
494
- updateMany(args: {
495
- readonly data: readonly (UniqueWhereInput<ListTypeInfo> & {
496
- readonly data: ListTypeInfo['inputs']['update']
497
- })[]
498
- }): Promise<ListTypeInfo['item'][]>
499
- createOne(args: {
500
- readonly data: ListTypeInfo['inputs']['create']
501
- }): Promise<ListTypeInfo['item']>
502
- createMany(args: {
503
- readonly data: readonly ListTypeInfo['inputs']['create'][]
504
- }): Promise<ListTypeInfo['item'][]>
505
- deleteOne(args: UniqueWhereInput<ListTypeInfo>): Promise<ListTypeInfo['item']>
506
- deleteMany(args: {
507
- readonly where: readonly ListTypeInfo['inputs']['uniqueWhere'][]
508
- }): Promise<ListTypeInfo['item'][]>
509
- }
510
-
511
- export type NixxieDbAPI<ListsTypeInfo extends Record<string, BaseListTypeInfo>> = {
512
- [Key in keyof ListsTypeInfo]: DbAPI<ListsTypeInfo[Key]>
513
- }
514
-
515
- // GraphQL API
516
-
517
- export type NixxieGraphQLAPI = {
518
- schema: GraphQLSchema
519
- run: <TData, TVariables extends Record<string, any>>(
520
- args: GraphQLExecutionArguments<TData, TVariables>
521
- ) => Promise<TData>
522
- raw: <TData, TVariables extends Record<string, any>>(
523
- args: GraphQLExecutionArguments<TData, TVariables>
524
- ) => Promise<ExecutionResult<TData>>
525
- }
526
-
527
- type GraphQLExecutionArguments<TData, TVariables> = {
528
- query: string | DocumentNode | TypedDocumentNode<TData, TVariables>
529
- variables?: TVariables
530
- }
1
+ import type { IncomingMessage, ServerResponse } from 'http'
2
+ import type { DocumentNode, ExecutionResult, GraphQLSchema } from 'graphql'
3
+ import type { TypedDocumentNode } from '@graphql-typed-document-node/core'
4
+ import type { InitialisedList } from '../lib/core/initialise-lists'
5
+ import type { SessionStrategy } from './session'
6
+ import type { BaseNixxieTypeInfo, BaseListTypeInfo } from './type-info'
7
+ import type { MaybePromise } from './utils'
8
+
9
+ // ── Email service types (defined here so @nixxie-cms/email can implement without circular deps) ──
10
+
11
+ export type NixxieEmailRecipient = string | { name?: string; address: string }
12
+
13
+ export type NixxieEmailAttachment = {
14
+ filename?: string
15
+ content?: string | Buffer
16
+ path?: string
17
+ href?: string
18
+ contentType?: string
19
+ encoding?: string
20
+ contentDisposition?: 'attachment' | 'inline'
21
+ /** Content ID for inline embedded images (used with src="cid:…") */
22
+ cid?: string
23
+ }
24
+
25
+ export type NixxieEmailSendOptions = {
26
+ to: NixxieEmailRecipient | NixxieEmailRecipient[]
27
+ cc?: NixxieEmailRecipient | NixxieEmailRecipient[]
28
+ bcc?: NixxieEmailRecipient | NixxieEmailRecipient[]
29
+ from?: NixxieEmailRecipient
30
+ replyTo?: NixxieEmailRecipient | NixxieEmailRecipient[]
31
+ subject: string
32
+ /** Name of a template file (without extension) to render */
33
+ template?: string
34
+ /** Data passed to the template engine */
35
+ data?: Record<string, unknown>
36
+ /** Raw HTML body — used when not using a template */
37
+ html?: string
38
+ /** Plain-text body */
39
+ text?: string
40
+ attachments?: NixxieEmailAttachment[]
41
+ headers?: Record<string, string>
42
+ /** Skip the suppression-list check for this send */
43
+ skipSuppressionCheck?: boolean
44
+ }
45
+
46
+ export type NixxieEmailQueueOptions = NixxieEmailSendOptions & {
47
+ /** Schedule delivery for a future date */
48
+ sendAt?: Date
49
+ priority?: 'high' | 'normal' | 'low'
50
+ }
51
+
52
+ export type NixxieEmailSentInfo = {
53
+ messageId: string
54
+ accepted: string[]
55
+ rejected: string[]
56
+ response?: string
57
+ timestamp: Date
58
+ }
59
+
60
+ export type NixxieEmailQueueStats = {
61
+ pending: number
62
+ processing: number
63
+ failed: number
64
+ completed: number
65
+ }
66
+
67
+ /**
68
+ * Interface that @nixxie-cms/email's EmailService implements.
69
+ * Defined in core so it can be referenced in NixxieContext without a circular dependency.
70
+ */
71
+ export type NixxieEmailService = {
72
+ /** Send an email immediately. Resolves when the transport accepts the message. */
73
+ send(options: NixxieEmailSendOptions): Promise<NixxieEmailSentInfo>
74
+ /** Send the same email to multiple recipients individually. */
75
+ bulk(
76
+ recipients: NixxieEmailRecipient[],
77
+ options: Omit<NixxieEmailSendOptions, 'to'>
78
+ ): Promise<NixxieEmailSentInfo[]>
79
+ /** Add an email to the outbound queue (non-blocking). Returns the job ID. */
80
+ queue(options: NixxieEmailQueueOptions): string
81
+ /** Add an address to the suppression list. */
82
+ addToSuppressionList(email: string): void
83
+ /** Remove an address from the suppression list. */
84
+ removeFromSuppressionList(email: string): void
85
+ /** Returns true if the address is suppressed. */
86
+ isSuppressed(email: string): boolean
87
+ /** Returns all currently suppressed addresses. */
88
+ getSuppressionList(): string[]
89
+ /** Returns live queue statistics. */
90
+ getQueueStats(): NixxieEmailQueueStats
91
+ /** Wait for the queue to drain, then stop processing. */
92
+ flushQueue(): Promise<void>
93
+ /** Gracefully shut down the service and close transport connections. */
94
+ close(): Promise<void>
95
+ }
96
+
97
+ // ── Jobs service ──
98
+
99
+ export type NixxieJobDefinition = {
100
+ name: string
101
+ /** Cron expression or interval in milliseconds */
102
+ schedule: string | number
103
+ /** Handler function invoked on each tick */
104
+ handler: (ctx: { jobId: string; scheduledAt: Date }) => Promise<void> | void
105
+ /** Immediately run the job once on registration */
106
+ runImmediately?: boolean
107
+ /** Disable job without removing the definition */
108
+ enabled?: boolean
109
+ /** Milliseconds before a running job is considered stuck */
110
+ timeout?: number
111
+ }
112
+
113
+ export type NixxieJobStatus = 'idle' | 'running' | 'error' | 'disabled'
114
+
115
+ export type NixxieJobInfo = {
116
+ name: string
117
+ status: NixxieJobStatus
118
+ lastRun?: Date
119
+ lastError?: string
120
+ nextRun?: Date
121
+ runs: number
122
+ }
123
+
124
+ export type NixxieJobsService = {
125
+ /** Register a job definition. Call before start() or at any time for hot-registration. */
126
+ define(job: NixxieJobDefinition): void
127
+ /** Remove a registered job by name. */
128
+ remove(name: string): void
129
+ /** Manually trigger a job by name, regardless of its schedule. */
130
+ trigger(name: string): Promise<void>
131
+ /** List all registered jobs with their current status. */
132
+ list(): NixxieJobInfo[]
133
+ /** Get info for a specific job. */
134
+ get(name: string): NixxieJobInfo | undefined
135
+ /** Gracefully stop all running jobs. */
136
+ close(): Promise<void>
137
+ }
138
+
139
+ // ── Cache service ──
140
+
141
+ export type NixxieCacheSetOptions = {
142
+ /** Time-to-live in milliseconds. Omit for no expiry. */
143
+ ttl?: number
144
+ /** Tags for group invalidation */
145
+ tags?: string[]
146
+ }
147
+
148
+ export type NixxieCacheService = {
149
+ /** Get a cached value. Returns undefined on miss. */
150
+ get<T = unknown>(key: string): Promise<T | undefined>
151
+ /** Set a cached value. */
152
+ set(key: string, value: unknown, options?: NixxieCacheSetOptions): Promise<void>
153
+ /** Delete a single key. */
154
+ delete(key: string): Promise<void>
155
+ /** Delete all keys matching a glob pattern. */
156
+ deletePattern(pattern: string): Promise<void>
157
+ /** Delete all keys with a given tag. */
158
+ deleteByTag(tag: string): Promise<void>
159
+ /** Returns true if the key exists and has not expired. */
160
+ has(key: string): Promise<boolean>
161
+ /**
162
+ * Cache-aside helper. Returns the cached value if present,
163
+ * otherwise calls fn(), caches the result, and returns it.
164
+ */
165
+ wrap<T>(key: string, fn: () => Promise<T>, options?: NixxieCacheSetOptions): Promise<T>
166
+ /** Flush the entire cache. */
167
+ clear(): Promise<void>
168
+ /** Gracefully close cache connections. */
169
+ close(): Promise<void>
170
+ }
171
+
172
+ // ── Audit service ──
173
+
174
+ export type NixxieAuditAction =
175
+ | 'create'
176
+ | 'update'
177
+ | 'delete'
178
+ | 'read'
179
+ | 'login'
180
+ | 'logout'
181
+ | (string & {})
182
+
183
+ export type NixxieAuditEntry = {
184
+ action: NixxieAuditAction
185
+ /** List name (e.g. 'User', 'Post') */
186
+ resource?: string
187
+ /** Item ID that was acted upon */
188
+ resourceId?: string
189
+ /** Session / user who performed the action */
190
+ actor?: {
191
+ id?: string
192
+ label?: string
193
+ ip?: string
194
+ }
195
+ /** Fields that changed: { field: { from, to } } */
196
+ changes?: Record<string, { from: unknown; to: unknown }>
197
+ /** Arbitrary extra context */
198
+ meta?: Record<string, unknown>
199
+ timestamp?: Date
200
+ }
201
+
202
+ export type NixxieAuditQuery = {
203
+ action?: NixxieAuditAction
204
+ resource?: string
205
+ resourceId?: string
206
+ actorId?: string
207
+ from?: Date
208
+ to?: Date
209
+ take?: number
210
+ skip?: number
211
+ }
212
+
213
+ export type NixxieAuditRecord = NixxieAuditEntry & {
214
+ id: string
215
+ timestamp: Date
216
+ }
217
+
218
+ export type NixxieAuditService = {
219
+ /** Log an audit event. */
220
+ log(entry: NixxieAuditEntry): Promise<NixxieAuditRecord>
221
+ /** Query the audit log. */
222
+ query(filters?: NixxieAuditQuery): Promise<NixxieAuditRecord[]>
223
+ /** Count matching entries. */
224
+ count(filters?: NixxieAuditQuery): Promise<number>
225
+ /** Delete entries older than a given date. Returns the number of deleted records. */
226
+ purge(before: Date): Promise<number>
227
+ /** Gracefully shut down (flush pending writes). */
228
+ close(): Promise<void>
229
+ }
230
+
231
+ // ── Webhooks service ──
232
+
233
+ export type NixxieWebhookSubscription = {
234
+ id: string
235
+ event: string
236
+ url: string
237
+ secret?: string
238
+ enabled: boolean
239
+ createdAt: Date
240
+ }
241
+
242
+ export type NixxieWebhookDelivery = {
243
+ id: string
244
+ subscriptionId: string
245
+ event: string
246
+ payload: unknown
247
+ status: 'pending' | 'delivered' | 'failed'
248
+ attempts: number
249
+ lastAttemptAt?: Date
250
+ responseStatus?: number
251
+ responseBody?: string
252
+ error?: string
253
+ }
254
+
255
+ export type NixxieWebhookSubscribeOptions = {
256
+ /** Optional signing secret — if provided, each request gets an `X-Nixxie-Signature` header */
257
+ secret?: string
258
+ /** Custom headers to send with every delivery */
259
+ headers?: Record<string, string>
260
+ /** Max delivery retries (default: 3) */
261
+ retries?: number
262
+ }
263
+
264
+ export type NixxieWebhooksService = {
265
+ /** Register a webhook endpoint for an event. Returns the subscription ID. */
266
+ subscribe(event: string, url: string, options?: NixxieWebhookSubscribeOptions): Promise<NixxieWebhookSubscription>
267
+ /** Remove a subscription by ID. */
268
+ unsubscribe(id: string): Promise<void>
269
+ /** Fire an event, delivering to all matching subscribers. */
270
+ trigger(event: string, payload: unknown): Promise<void>
271
+ /** List all registered subscriptions. */
272
+ list(event?: string): NixxieWebhookSubscription[]
273
+ /** Get delivery history for a subscription. */
274
+ deliveries(subscriptionId: string, take?: number): NixxieWebhookDelivery[]
275
+ /** Enable or disable a subscription. */
276
+ setEnabled(id: string, enabled: boolean): void
277
+ /** Gracefully stop delivery workers. */
278
+ close(): Promise<void>
279
+ }
280
+
281
+ // ── Rate limit service ──
282
+
283
+ export type NixxieRateLimitResult = {
284
+ allowed: boolean
285
+ remaining: number
286
+ resetAt: Date
287
+ /** Total limit for the window */
288
+ limit: number
289
+ }
290
+
291
+ export type NixxieRateLimitOptions = {
292
+ /** Maximum number of requests in the window */
293
+ limit: number
294
+ /** Window duration in milliseconds */
295
+ window: number
296
+ /** Cost of this operation (default: 1) */
297
+ cost?: number
298
+ }
299
+
300
+ export type NixxieRateLimitService = {
301
+ /**
302
+ * Check without consuming — returns whether the key is within the limit.
303
+ * Does NOT decrement the counter.
304
+ */
305
+ check(key: string, options: NixxieRateLimitOptions): Promise<NixxieRateLimitResult>
306
+ /**
307
+ * Consume from the limit. Returns the updated result.
308
+ * Does NOT throw on exceeded limits — callers should check `result.allowed`.
309
+ */
310
+ consume(key: string, options: NixxieRateLimitOptions): Promise<NixxieRateLimitResult>
311
+ /** Reset the counter for a key. */
312
+ reset(key: string): Promise<void>
313
+ /** Gracefully shut down (close Redis connections etc.). */
314
+ close(): Promise<void>
315
+ }
316
+
317
+ // ── Health service ──
318
+
319
+ export type NixxieHealthCheckFn = () => Promise<{ ok: boolean; message?: string; details?: unknown }>
320
+
321
+ export type NixxieHealthCheckResult = {
322
+ name: string
323
+ status: 'healthy' | 'unhealthy'
324
+ /** Whether this check failing marks the whole system unhealthy (true) or just degraded (false) */
325
+ critical: boolean
326
+ message?: string
327
+ details?: unknown
328
+ /** How long the check took in milliseconds */
329
+ latency: number
330
+ }
331
+
332
+ export type NixxieHealthReport = {
333
+ /** Overall system status */
334
+ status: 'healthy' | 'degraded' | 'unhealthy'
335
+ timestamp: string
336
+ /** Application version, if configured */
337
+ version?: string
338
+ /** process.uptime() in seconds */
339
+ uptime: number
340
+ /** process.memoryUsage().heapUsed in MB */
341
+ memoryMb: number
342
+ checks: NixxieHealthCheckResult[]
343
+ }
344
+
345
+ export type NixxieHealthService = {
346
+ /**
347
+ * Register a named health check.
348
+ * @param critical If true (default), failure marks the system 'unhealthy'. If false, 'degraded'.
349
+ */
350
+ register(name: string, check: NixxieHealthCheckFn, options?: { critical?: boolean }): void
351
+ /** Remove a registered check by name. */
352
+ unregister(name: string): void
353
+ /** Run all registered checks and return the full health report. */
354
+ check(): Promise<NixxieHealthReport>
355
+ /**
356
+ * Returns an Express Router with two routes:
357
+ * - GET /live — liveness (process is up, returns 200 immediately)
358
+ * - GET /ready — readiness (runs all checks, returns 200 or 503)
359
+ *
360
+ * Mount in extendExpressApp: `app.use('/health', context.services.health.router())`
361
+ */
362
+ router(options?: { livePath?: string; readyPath?: string }): unknown
363
+ }
364
+
365
+ // ── Storage service ──
366
+
367
+ export type NixxieStoredFile = {
368
+ /** Storage key / path the file is stored under */
369
+ key: string
370
+ /** Public or signed URL for retrieving the file */
371
+ url: string
372
+ /** Size in bytes, if known */
373
+ size?: number
374
+ /** Detected or provided content type */
375
+ contentType?: string
376
+ }
377
+
378
+ export type NixxiePutOptions = {
379
+ /** MIME type to store alongside the object */
380
+ contentType?: string
381
+ /** Whether the object should be publicly readable (driver permitting). Default: false */
382
+ public?: boolean
383
+ /** Arbitrary metadata stored with the object */
384
+ metadata?: Record<string, string>
385
+ }
386
+
387
+ export type NixxieSignedUrlOptions = {
388
+ /** Expiry in seconds. Default: 900 (15 minutes) */
389
+ expiresIn?: number
390
+ /** 'get' for download URLs, 'put' for direct browser uploads. Default: 'get' */
391
+ operation?: 'get' | 'put'
392
+ /** Content type required on the upload when operation is 'put' */
393
+ contentType?: string
394
+ }
395
+
396
+ /** Pluggable blob storage. Implemented by @nixxie-cms/storage (local, S3, GCS, Azure). */
397
+ export type NixxieStorageService = {
398
+ /** Store a file and return its key + URL. */
399
+ put(key: string, data: Buffer | Uint8Array | string, options?: NixxiePutOptions): Promise<NixxieStoredFile>
400
+ /** Read a file's bytes. Resolves undefined when the key does not exist. */
401
+ get(key: string): Promise<Buffer | undefined>
402
+ /** Delete a file. No-op if it does not exist. */
403
+ delete(key: string): Promise<void>
404
+ /** Whether a key exists. */
405
+ has(key: string): Promise<boolean>
406
+ /** Public URL for a key (may be unsigned and require the object to be public). */
407
+ url(key: string): string
408
+ /** Create a time-limited signed URL for download or direct upload. */
409
+ signedUrl(key: string, options?: NixxieSignedUrlOptions): Promise<string>
410
+ /** List keys under a prefix. */
411
+ list(prefix?: string): Promise<string[]>
412
+ }
413
+
414
+ // ── Search service ──
415
+
416
+ export type NixxieSearchDocument = Record<string, unknown> & { id: string }
417
+
418
+ export type NixxieSearchQuery = {
419
+ /** Free-text query string. */
420
+ q: string
421
+ /** Maximum results to return. Default: 20 */
422
+ limit?: number
423
+ /** Number of results to skip (pagination). */
424
+ offset?: number
425
+ /** Equality filters applied to indexed attributes. */
426
+ filter?: Record<string, string | number | boolean>
427
+ /** Attributes to sort by, e.g. ['createdAt:desc']. */
428
+ sort?: string[]
429
+ }
430
+
431
+ export type NixxieSearchHit<T = NixxieSearchDocument> = {
432
+ document: T
433
+ /** Relevance score (driver-specific scale), when available. */
434
+ score?: number
435
+ }
436
+
437
+ export type NixxieSearchResults<T = NixxieSearchDocument> = {
438
+ hits: NixxieSearchHit<T>[]
439
+ /** Total matches, when the backend reports it. */
440
+ total: number
441
+ /** Query processing time in milliseconds, when available. */
442
+ tookMs?: number
443
+ }
444
+
445
+ /** Pluggable full-text search. Implemented by @nixxie-cms/search (memory, Meilisearch, Typesense, Algolia, Elasticsearch). */
446
+ export type NixxieSearchService = {
447
+ /** Create or update one or more documents in an index. */
448
+ index(indexName: string, documents: NixxieSearchDocument | NixxieSearchDocument[]): Promise<void>
449
+ /** Remove a document from an index by id. */
450
+ remove(indexName: string, id: string): Promise<void>
451
+ /** Run a search against an index. */
452
+ search<T = NixxieSearchDocument>(indexName: string, query: NixxieSearchQuery): Promise<NixxieSearchResults<T>>
453
+ /** Delete every document in an index. */
454
+ clear(indexName: string): Promise<void>
455
+ /** Gracefully close any open connections. */
456
+ close(): Promise<void>
457
+ }
458
+
459
+ // ── Notifications service ──
460
+
461
+ export type NixxieNotificationChannel = string
462
+
463
+ export type NixxieNotification = {
464
+ /** Channel(s) to deliver on. Omit to use every configured channel. */
465
+ channel?: NixxieNotificationChannel | NixxieNotificationChannel[]
466
+ /** Recipient address meaning depends on the channel (Slack channel id, phone number, etc.). */
467
+ to?: string
468
+ /** Short title / subject. */
469
+ title?: string
470
+ /** Body of the notification. */
471
+ body: string
472
+ /** Arbitrary structured data forwarded to the channel. */
473
+ data?: Record<string, unknown>
474
+ }
475
+
476
+ export type NixxieNotificationResult = {
477
+ channel: NixxieNotificationChannel
478
+ ok: boolean
479
+ /** Provider message id when delivery succeeded. */
480
+ id?: string
481
+ /** Error message when delivery failed. */
482
+ error?: string
483
+ }
484
+
485
+ /** Multi-channel notifications (Slack, Discord, SMS, webhook, custom). Implemented by @nixxie-cms/notifications. */
486
+ export type NixxieNotificationsService = {
487
+ /** Send a notification across the requested (or all configured) channels. */
488
+ send(notification: NixxieNotification): Promise<NixxieNotificationResult[]>
489
+ /** List the names of the configured channels. */
490
+ channels(): NixxieNotificationChannel[]
491
+ }
492
+
493
+ // ── AI service ──
494
+
495
+ export type NixxieAiMessage = {
496
+ role: 'system' | 'user' | 'assistant'
497
+ content: string
498
+ }
499
+
500
+ export type NixxieAiCompleteOptions = {
501
+ /** Override the configured model for this call. */
502
+ model?: string
503
+ /** System prompt prepended to the conversation. */
504
+ system?: string
505
+ /** Sampling temperature. */
506
+ temperature?: number
507
+ /** Maximum tokens to generate. */
508
+ maxTokens?: number
509
+ }
510
+
511
+ export type NixxieAiCompletion = {
512
+ text: string
513
+ model: string
514
+ usage?: { inputTokens?: number; outputTokens?: number }
515
+ }
516
+
517
+ /** Pluggable LLM + embeddings access. Implemented by @nixxie-cms/ai (Anthropic, OpenAI). */
518
+ export type NixxieAiService = {
519
+ /** Complete a single prompt. */
520
+ complete(prompt: string, options?: NixxieAiCompleteOptions): Promise<NixxieAiCompletion>
521
+ /** Complete a multi-turn conversation. */
522
+ chat(messages: NixxieAiMessage[], options?: NixxieAiCompleteOptions): Promise<NixxieAiCompletion>
523
+ /** Produce an embedding vector for a string. */
524
+ embed(text: string): Promise<number[]>
525
+ /** Produce embedding vectors for many strings in one call. */
526
+ embedMany(texts: string[]): Promise<number[][]>
527
+ }
528
+
529
+ // ── Context ──
530
+
531
+ export type NixxieContext<TypeInfo extends BaseNixxieTypeInfo = BaseNixxieTypeInfo> = {
532
+ db: NixxieDbAPI<TypeInfo['lists']>
533
+ query: NixxieListsAPI<TypeInfo['lists']>
534
+ graphql: NixxieGraphQLAPI
535
+ prisma: TypeInfo['prisma']
536
+ /** Runtime services configured in nixxie.ts — available everywhere context is available */
537
+ services: {
538
+ email: NixxieEmailService | null
539
+ jobs: NixxieJobsService | null
540
+ cache: NixxieCacheService | null
541
+ audit: NixxieAuditService | null
542
+ webhooks: NixxieWebhooksService | null
543
+ rateLimit: NixxieRateLimitService | null
544
+ health: NixxieHealthService | null
545
+ storage: NixxieStorageService | null
546
+ search: NixxieSearchService | null
547
+ notifications: NixxieNotificationsService | null
548
+ ai: NixxieAiService | null
549
+ }
550
+ transaction: <T>(
551
+ f: (context: NixxieContext<TypeInfo>) => MaybePromise<T>,
552
+ options?: {
553
+ maxWait?: number
554
+ timeout?: number
555
+ isolationLevel?: {
556
+ Serializable: 'Serializable'
557
+ }
558
+ }
559
+ ) => Promise<T>
560
+
561
+ req?: IncomingMessage
562
+ res?: ServerResponse
563
+ sessionStrategy?: SessionStrategy<TypeInfo['session'], TypeInfo>
564
+ session?: TypeInfo['session']
565
+ withRequest: (req: IncomingMessage, res?: ServerResponse) => Promise<NixxieContext<TypeInfo>>
566
+ withSession: (session?: TypeInfo['session']) => NixxieContext<TypeInfo>
567
+
568
+ // privilege escalation
569
+ internal: () => NixxieContext<TypeInfo> // WARNING: name may change
570
+ sudo: () => NixxieContext<TypeInfo>
571
+
572
+ /**
573
+ * WARNING: may change in patch
574
+ */
575
+ __internal: {
576
+ sudo: boolean
577
+ lists: Record<string, InitialisedList>
578
+ prisma: {
579
+ DbNull: unknown
580
+ JsonNull: unknown
581
+ }
582
+ }
583
+ }
584
+
585
+ // List item API
586
+
587
+ type UniqueWhereInput<ListTypeInfo extends BaseListTypeInfo> =
588
+ false extends ListTypeInfo['isSingleton']
589
+ ? { readonly where: ListTypeInfo['inputs']['uniqueWhere'] }
590
+ : { readonly where?: ListTypeInfo['inputs']['uniqueWhere'] }
591
+
592
+ type ListAPI<ListTypeInfo extends BaseListTypeInfo> = {
593
+ findMany(
594
+ args?: {
595
+ readonly where?: ListTypeInfo['inputs']['where']
596
+ readonly take?: number
597
+ readonly skip?: number
598
+ readonly orderBy?:
599
+ | ListTypeInfo['inputs']['orderBy']
600
+ | readonly ListTypeInfo['inputs']['orderBy'][]
601
+ readonly cursor?: ListTypeInfo['inputs']['uniqueWhere']
602
+ } & ResolveFields
603
+ ): Promise<readonly Record<string, any>[]>
604
+ findOne(args: UniqueWhereInput<ListTypeInfo> & ResolveFields): Promise<Record<string, any>>
605
+ count(args?: { readonly where?: ListTypeInfo['inputs']['where'] }): Promise<number>
606
+ updateOne(
607
+ args: UniqueWhereInput<ListTypeInfo> & {
608
+ readonly data: ListTypeInfo['inputs']['update']
609
+ } & ResolveFields
610
+ ): Promise<Record<string, any>>
611
+ updateMany(
612
+ args: {
613
+ readonly data: readonly (UniqueWhereInput<ListTypeInfo> & {
614
+ readonly data: ListTypeInfo['inputs']['update']
615
+ })[]
616
+ } & ResolveFields
617
+ ): Promise<Record<string, any>[]>
618
+ createOne(
619
+ args: { readonly data: ListTypeInfo['inputs']['create'] } & ResolveFields
620
+ ): Promise<Record<string, any>>
621
+ createMany(
622
+ args: {
623
+ readonly data: readonly ListTypeInfo['inputs']['create'][]
624
+ } & ResolveFields
625
+ ): Promise<Record<string, any>[]>
626
+ deleteOne(
627
+ args: UniqueWhereInput<ListTypeInfo> & ResolveFields
628
+ ): Promise<Record<string, any> | null>
629
+ deleteMany(
630
+ args: {
631
+ readonly where: readonly ListTypeInfo['inputs']['uniqueWhere'][]
632
+ } & ResolveFields
633
+ ): Promise<Record<string, any>[]>
634
+ }
635
+
636
+ export type NixxieListsAPI<ListsTypeInfo extends Record<string, BaseListTypeInfo>> = {
637
+ [Key in keyof ListsTypeInfo]: ListAPI<ListsTypeInfo[Key]>
638
+ }
639
+
640
+ type ResolveFields = {
641
+ /**
642
+ * @default 'id'
643
+ */
644
+ readonly query?: string
645
+ }
646
+
647
+ type DbAPI<ListTypeInfo extends BaseListTypeInfo> = {
648
+ findMany(args?: {
649
+ readonly where?: ListTypeInfo['inputs']['where']
650
+ readonly take?: number
651
+ readonly skip?: number
652
+ readonly orderBy?:
653
+ | ListTypeInfo['inputs']['orderBy']
654
+ | readonly ListTypeInfo['inputs']['orderBy'][]
655
+ readonly cursor?: ListTypeInfo['inputs']['uniqueWhere']
656
+ }): Promise<readonly ListTypeInfo['item'][]>
657
+ findOne(args: UniqueWhereInput<ListTypeInfo>): Promise<ListTypeInfo['item'] | null>
658
+ count(args?: { readonly where?: ListTypeInfo['inputs']['where'] }): Promise<number>
659
+ updateOne(
660
+ args: UniqueWhereInput<ListTypeInfo> & {
661
+ readonly data: ListTypeInfo['inputs']['update']
662
+ }
663
+ ): Promise<ListTypeInfo['item']>
664
+ updateMany(args: {
665
+ readonly data: readonly (UniqueWhereInput<ListTypeInfo> & {
666
+ readonly data: ListTypeInfo['inputs']['update']
667
+ })[]
668
+ }): Promise<ListTypeInfo['item'][]>
669
+ createOne(args: {
670
+ readonly data: ListTypeInfo['inputs']['create']
671
+ }): Promise<ListTypeInfo['item']>
672
+ createMany(args: {
673
+ readonly data: readonly ListTypeInfo['inputs']['create'][]
674
+ }): Promise<ListTypeInfo['item'][]>
675
+ deleteOne(args: UniqueWhereInput<ListTypeInfo>): Promise<ListTypeInfo['item']>
676
+ deleteMany(args: {
677
+ readonly where: readonly ListTypeInfo['inputs']['uniqueWhere'][]
678
+ }): Promise<ListTypeInfo['item'][]>
679
+ }
680
+
681
+ export type NixxieDbAPI<ListsTypeInfo extends Record<string, BaseListTypeInfo>> = {
682
+ [Key in keyof ListsTypeInfo]: DbAPI<ListsTypeInfo[Key]>
683
+ }
684
+
685
+ // GraphQL API
686
+
687
+ export type NixxieGraphQLAPI = {
688
+ schema: GraphQLSchema
689
+ run: <TData, TVariables extends Record<string, any>>(
690
+ args: GraphQLExecutionArguments<TData, TVariables>
691
+ ) => Promise<TData>
692
+ raw: <TData, TVariables extends Record<string, any>>(
693
+ args: GraphQLExecutionArguments<TData, TVariables>
694
+ ) => Promise<ExecutionResult<TData>>
695
+ }
696
+
697
+ type GraphQLExecutionArguments<TData, TVariables> = {
698
+ query: string | DocumentNode | TypedDocumentNode<TData, TVariables>
699
+ variables?: TVariables
700
+ }