@stonecrop/nuxt 0.7.3 → 0.7.5

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.
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Grafserv Plugins
3
+ * Add custom middleware and hooks via Grafserv plugins
4
+ *
5
+ * @see https://grafast.org/grafserv/plugins
6
+ */
7
+
8
+ import type { GraphileConfig } from 'graphile-config'
9
+
10
+ /**
11
+ * Example: Request logging plugin
12
+ */
13
+ const loggingPlugin: GraphileConfig.Plugin = {
14
+ name: 'request-logging',
15
+ version: '1.0.0',
16
+ grafserv: {
17
+ middleware: {
18
+ processGraphQLRequestBody: async (next, event) => {
19
+ const start = Date.now()
20
+ console.log('[GraphQL] Request started:', {
21
+ path: event.request.url,
22
+ method: event.request.method,
23
+ })
24
+
25
+ const result = await next()
26
+
27
+ const duration = Date.now() - start
28
+ console.log(`[GraphQL] Request completed in ${duration}ms`)
29
+
30
+ return result
31
+ },
32
+ },
33
+ },
34
+ }
35
+
36
+ /**
37
+ * Example: Authentication plugin
38
+ */
39
+ const authPlugin: GraphileConfig.Plugin = {
40
+ name: 'authentication',
41
+ version: '1.0.0',
42
+ grafserv: {
43
+ middleware: {
44
+ processGraphQLRequestBody: async (next, event) => {
45
+ // Extract authentication from headers
46
+ const authHeader = event.request.headers.get('authorization')
47
+
48
+ if (authHeader?.startsWith('Bearer ')) {
49
+ const token = authHeader.slice(7)
50
+ // TODO: Validate token and set user context
51
+ console.log('[Auth] Token received:', token)
52
+ } else {
53
+ console.log('[Auth] Anonymous request')
54
+ }
55
+
56
+ return next()
57
+ },
58
+ },
59
+ },
60
+ }
61
+
62
+ /**
63
+ * Export all plugins
64
+ * Import these in your nuxt.config.ts:
65
+ *
66
+ * import plugins from './server/plugins'
67
+ *
68
+ * export default defineNuxtConfig({
69
+ * grafserv: {
70
+ * preset: {
71
+ * plugins
72
+ * }
73
+ * }
74
+ * })
75
+ */
76
+ export const plugins: GraphileConfig.Plugin[] = [
77
+ loggingPlugin,
78
+ // authPlugin, // Uncomment to enable authentication
79
+ ]
80
+
81
+ export default plugins
@@ -0,0 +1,256 @@
1
+ /**
2
+ * Stonecrop GraphQL Resolvers
3
+ *
4
+ * This file contains resolver implementations for the Stonecrop GraphQL schema.
5
+ * Customize these resolvers to connect to your data sources.
6
+ *
7
+ * Grafast uses a planning-based execution model where resolvers return "steps"
8
+ * (execution plans) rather than raw data. Common step functions:
9
+ * - constant(value) - Returns a constant value
10
+ * - context() - Accesses request context
11
+ * - object({ ... }) - Creates an object with planned properties
12
+ * - access($step, 'property') - Accesses a property from another step
13
+ * - lambda($step, fn) - Transforms a step SYNCHRONOUSLY (no async/await!)
14
+ * - loadOne($step, fn) - Batch loads data ASYNCHRONOUSLY (for DB queries)
15
+ * - list([$a, $b]) - Combines multiple steps into a tuple
16
+ *
17
+ * CRITICAL RULES:
18
+ * 1. lambda functions MUST be synchronous. For async operations, use loadOne/loadMany.
19
+ * 2. Plan resolver args come pre-destructured with $ prefix: (_$parent, { $argName }) => ...
20
+ * 3. Never call access() on args - they're already steps!
21
+ */
22
+
23
+ // Import Grafast step functions
24
+ // IMPORTANT: Must import from 'grafast' not 'postgraphile/grafast' for Nitro bundling
25
+ import { constant, lambda, list } from 'grafast'
26
+
27
+ // Import Stonecrop middleware for doctype metadata
28
+ // import { getMeta, getAllMeta } from '@stonecrop/graphql-middleware'
29
+
30
+ export const resolvers = {
31
+ Query: {
32
+ plans: {
33
+ /**
34
+ * Health check endpoint
35
+ * Returns a constant object with health status
36
+ */
37
+ healthCheck() {
38
+ return constant({
39
+ status: 'healthy',
40
+ timestamp: new Date().toISOString(),
41
+ version: '1.0.0',
42
+ })
43
+ },
44
+
45
+ /**
46
+ * Get metadata for a specific doctype
47
+ * Connect this to your doctype registry or database
48
+ */
49
+ getMeta(_$parent, { $doctype }) {
50
+ // $doctype is already a step from destructured args
51
+
52
+ // TODO: Implement doctype metadata lookup
53
+ // For synchronous lookups, use lambda:
54
+ // return lambda($doctype, doctype => {
55
+ // const meta = getMeta(doctype) // Must be synchronous!
56
+ // return meta ? formatDoctypeMeta(meta) : null
57
+ // })
58
+ //
59
+ // For async lookups, use loadOne:
60
+ // return loadOne($doctype, async doctypes => {
61
+ // return await Promise.all(
62
+ // doctypes.map(async dt => {
63
+ // const meta = await fetchMeta(dt)
64
+ // return meta ? formatDoctypeMeta(meta) : null
65
+ // })
66
+ // )
67
+ // })
68
+
69
+ return lambda($doctype, doctype => {
70
+ console.log('getMeta called for:', doctype)
71
+ return null
72
+ })
73
+ },
74
+
75
+ /**
76
+ * Get all registered doctype metadata
77
+ * Returns an array of doctype metadata
78
+ */
79
+ stonecropAllMeta() {
80
+ // TODO: Implement - return all doctype metadata
81
+ // Example with graphql-middleware:
82
+ // return constant(getAllMeta().map(formatDoctypeMeta))
83
+
84
+ return constant([])
85
+ },
86
+
87
+ /**
88
+ * Get a single record by doctype and ID
89
+ */
90
+ stonecropRecord(_$parent, { $doctype, $id }) {
91
+ // Arguments come pre-destructured as steps with $ prefix
92
+
93
+ // TODO: Implement record fetching from your database
94
+ // Use loadOne for async database queries:
95
+ // return loadOne(list([$doctype, $id]), async pairs => {
96
+ // return await Promise.all(
97
+ // pairs.map(async ([doctype, id]) => {
98
+ // const record = await fetchRecordFromDB(doctype, id)
99
+ // return { data: record, doctype }
100
+ // })
101
+ // )
102
+ // })
103
+
104
+ return lambda(list([$doctype, $id]), ([doctype, id]) => {
105
+ console.log('stonecropRecord called:', { doctype, id })
106
+ return {
107
+ data: null,
108
+ doctype,
109
+ }
110
+ })
111
+ },
112
+
113
+ /**
114
+ * Get multiple records with filtering
115
+ */
116
+ stonecropRecords(_$parent, { $doctype, $filters, $orderBy, $limit, $offset }) {
117
+ // Arguments come pre-destructured as steps with $ prefix
118
+
119
+ // TODO: Implement records fetching from your database
120
+ // Use loadOne for async database queries:
121
+ // return loadOne(list([$doctype, $filters, $orderBy, $limit, $offset]), async queryParams => {
122
+ // return await Promise.all(
123
+ // queryParams.map(async ([doctype, filters, orderBy, limit, offset]) => {
124
+ // const result = await queryRecordsFromDB(doctype, { filters, orderBy, limit, offset })
125
+ // return { data: result.records, doctype, count: result.totalCount }
126
+ // })
127
+ // )
128
+ // })
129
+
130
+ return lambda(
131
+ list([$doctype, $filters, $orderBy, $limit, $offset]),
132
+ ([doctype, filters, orderBy, limit, offset]) => {
133
+ console.log('stonecropRecords called:', { doctype, filters, orderBy, limit, offset })
134
+ return {
135
+ data: [],
136
+ doctype,
137
+ count: 0,
138
+ }
139
+ }
140
+ )
141
+ },
142
+ },
143
+ },
144
+
145
+ Mutation: {
146
+ plans: {
147
+ /**
148
+ * Execute a doctype action (workflow actions like activate, archive, etc.)
149
+ */
150
+ stonecropAction(_$parent, { $doctype, $action, $args }) {
151
+ // Arguments come pre-destructured as steps with $ prefix
152
+
153
+ // TODO: Implement action execution
154
+ // Use loadOne for async operations:
155
+ // return loadOne(list([$doctype, $action, $args]), async actionParams => {
156
+ // return await Promise.all(
157
+ // actionParams.map(async ([doctype, action, actionArgs]) => {
158
+ // const result = await executeAction(doctype, action, actionArgs)
159
+ // return { success: result.success, data: result.data, error: result.error }
160
+ // })
161
+ // )
162
+ // })
163
+
164
+ return lambda(list([$doctype, $action, $args]), ([doctype, action, actionArgs]) => {
165
+ console.log('stonecropAction called:', { doctype, action, args: actionArgs })
166
+ return {
167
+ success: true,
168
+ data: null,
169
+ error: null,
170
+ }
171
+ })
172
+ },
173
+
174
+ /**
175
+ * Create a new record
176
+ */
177
+ stonecropCreate(_$parent, { $doctype, $input }) {
178
+ // Arguments come pre-destructured as steps with $ prefix
179
+
180
+ // TODO: Implement record creation
181
+ // Use loadOne for async database operations:
182
+ // return loadOne(list([$doctype, $input]), async createParams => {
183
+ // return await Promise.all(
184
+ // createParams.map(async ([doctype, input]) => {
185
+ // const newRecord = await createRecordInDB(doctype, input)
186
+ // return { data: newRecord, doctype }
187
+ // })
188
+ // )
189
+ // })
190
+
191
+ return lambda(list([$doctype, $input]), ([doctype, input]) => {
192
+ console.log('stonecropCreate called:', { doctype, input })
193
+ return {
194
+ data: { id: 'new-id', ...input },
195
+ doctype,
196
+ }
197
+ })
198
+ },
199
+
200
+ /**
201
+ * Update an existing record
202
+ */
203
+ stonecropUpdate(_$parent, { $doctype, $id, $patch }) {
204
+ // Arguments come pre-destructured as steps with $ prefix
205
+
206
+ // TODO: Implement record update
207
+ // Use loadOne for async database operations:
208
+ // return loadOne(list([$doctype, $id, $patch]), async updateParams => {
209
+ // return await Promise.all(
210
+ // updateParams.map(async ([doctype, id, patch]) => {
211
+ // const updatedRecord = await updateRecordInDB(doctype, id, patch)
212
+ // return { data: updatedRecord, doctype }
213
+ // })
214
+ // )
215
+ // })
216
+
217
+ return lambda(list([$doctype, $id, $patch]), ([doctype, id, patch]) => {
218
+ console.log('stonecropUpdate called:', { doctype, id, patch })
219
+ return {
220
+ data: { id, ...patch },
221
+ doctype,
222
+ }
223
+ })
224
+ },
225
+
226
+ /**
227
+ * Delete a record
228
+ */
229
+ stonecropDelete(_$parent, { $doctype, $id }) {
230
+ // Arguments come pre-destructured as steps with $ prefix
231
+
232
+ // TODO: Implement record deletion
233
+ // Use loadOne for async database operations:
234
+ // return loadOne(list([$doctype, $id]), async deleteParams => {
235
+ // return await Promise.all(
236
+ // deleteParams.map(async ([doctype, id]) => {
237
+ // await deleteRecordFromDB(doctype, id)
238
+ // return { success: true, data: { id }, error: null }
239
+ // })
240
+ // )
241
+ // })
242
+
243
+ return lambda(list([$doctype, $id]), ([doctype, id]) => {
244
+ console.log('stonecropDelete called:', { doctype, id })
245
+ return {
246
+ success: true,
247
+ data: { id },
248
+ error: null,
249
+ }
250
+ })
251
+ },
252
+ },
253
+ },
254
+ }
255
+
256
+ export default resolvers
@@ -0,0 +1,129 @@
1
+ # Stonecrop GraphQL Schema
2
+ # Add your type definitions here
3
+
4
+ scalar JSON
5
+
6
+ # ============================================
7
+ # Stonecrop Meta Types
8
+ # ============================================
9
+
10
+ """
11
+ Field metadata from doctype schema
12
+ """
13
+ type FieldMeta {
14
+ fieldname: String!
15
+ fieldtype: String!
16
+ label: String
17
+ required: Boolean
18
+ readOnly: Boolean
19
+ options: JSON
20
+ default: JSON
21
+ width: String
22
+ validation: JSON
23
+ }
24
+
25
+ """
26
+ Workflow metadata with states and actions
27
+ """
28
+ type WorkflowMeta {
29
+ states: [String!]
30
+ actions: JSON
31
+ }
32
+
33
+ """
34
+ Complete doctype metadata
35
+ """
36
+ type DoctypeMeta {
37
+ name: String!
38
+ slug: String
39
+ tableName: String
40
+ fields: [FieldMeta!]!
41
+ workflow: WorkflowMeta
42
+ inherits: String
43
+ listDoctype: String
44
+ parentDoctype: String
45
+ }
46
+
47
+ # ============================================
48
+ # Result Types
49
+ # ============================================
50
+
51
+ type HealthStatus {
52
+ status: String!
53
+ timestamp: String!
54
+ version: String!
55
+ }
56
+
57
+ type RecordResult {
58
+ data: JSON
59
+ doctype: String!
60
+ }
61
+
62
+ type RecordsResult {
63
+ data: [JSON!]!
64
+ doctype: String!
65
+ count: Int!
66
+ }
67
+
68
+ type ActionResult {
69
+ success: Boolean!
70
+ data: JSON
71
+ error: String
72
+ }
73
+
74
+ # ============================================
75
+ # Queries
76
+ # ============================================
77
+
78
+ type Query {
79
+ """
80
+ Health check endpoint
81
+ """
82
+ healthCheck: HealthStatus!
83
+
84
+ """
85
+ Get metadata for a doctype
86
+ """
87
+ getMeta(doctype: String!): DoctypeMeta
88
+
89
+ """
90
+ Get all registered doctype metadata
91
+ """
92
+ stonecropAllMeta: [DoctypeMeta!]!
93
+
94
+ """
95
+ Get a single record by doctype and ID
96
+ """
97
+ stonecropRecord(doctype: String!, id: String!): RecordResult
98
+
99
+ """
100
+ Get multiple records with optional filtering
101
+ """
102
+ stonecropRecords(doctype: String!, filters: JSON, orderBy: String, limit: Int, offset: Int): RecordsResult
103
+ }
104
+
105
+ # ============================================
106
+ # Mutations
107
+ # ============================================
108
+
109
+ type Mutation {
110
+ """
111
+ Execute a doctype action
112
+ """
113
+ stonecropAction(doctype: String!, action: String!, args: JSON): ActionResult!
114
+
115
+ """
116
+ Create a new record
117
+ """
118
+ stonecropCreate(doctype: String!, input: JSON!): RecordResult!
119
+
120
+ """
121
+ Update an existing record
122
+ """
123
+ stonecropUpdate(doctype: String!, id: String!, patch: JSON!): RecordResult
124
+
125
+ """
126
+ Delete a record
127
+ """
128
+ stonecropDelete(doctype: String!, id: String!): ActionResult!
129
+ }