@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.
- package/README.md +105 -1
- package/bin/init.mjs +104 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +84 -5
- package/package.json +25 -10
- package/src/cli/detect.ts +125 -0
- package/src/cli/index.ts +184 -0
- package/src/cli/installers/casl.ts +51 -0
- package/src/cli/installers/doctypes.ts +206 -0
- package/src/cli/installers/frontend.ts +68 -0
- package/src/cli/installers/grafserv.ts +308 -0
- package/src/cli/installers/graphql-client.ts +36 -0
- package/src/cli/installers/index.ts +10 -0
- package/src/cli/installers/rockfoil.ts +51 -0
- package/src/cli/prompts.ts +204 -0
- package/src/cli/utils/config.ts +260 -0
- package/src/cli/utils/index.ts +15 -0
- package/src/cli/utils/package.ts +128 -0
- package/src/cli/utils/plugin.ts +107 -0
- package/templates/Example.json +85 -0
- package/templates/example-table.json +60 -0
- package/templates/plugins.ts +81 -0
- package/templates/resolvers.ts +256 -0
- package/templates/schema.graphql +129 -0
|
@@ -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
|
+
}
|