@stonecrop/nuxt 0.13.12 → 0.14.0
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 +12 -19
- package/bin/init.mjs +4 -1
- package/dist/module.d.mts +10 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +91 -50
- package/dist/runtime/app/components/DocBuilderActionsPanel.d.vue.ts +59 -0
- package/dist/runtime/app/components/DocBuilderActionsPanel.vue +175 -0
- package/dist/runtime/app/components/DocBuilderActionsPanel.vue.d.ts +59 -0
- package/dist/runtime/app/components/DocBuilderFieldsPanel.d.vue.ts +11 -0
- package/dist/runtime/app/components/DocBuilderFieldsPanel.vue +353 -0
- package/dist/runtime/app/components/DocBuilderFieldsPanel.vue.d.ts +11 -0
- package/dist/runtime/app/components/docbuilderActions.d.ts +62 -0
- package/dist/runtime/app/components/docbuilderActions.js +87 -0
- package/dist/runtime/app/composables/useClientAction.d.ts +30 -0
- package/dist/runtime/app/composables/useClientAction.js +51 -0
- package/dist/runtime/app/pages/DocBuilderDetail.vue +116 -71
- package/dist/runtime/app/pages/DocBuilderIndex.vue +83 -22
- package/dist/runtime/server/api/docbuilder/[doctype].get.d.ts +6 -1
- package/dist/runtime/server/api/docbuilder/[doctype].get.js +15 -5
- package/dist/runtime/server/api/docbuilder/doctypes.get.d.ts +5 -1
- package/dist/runtime/server/api/docbuilder/doctypes.get.js +5 -3
- package/dist/runtime/server/api/docbuilder/mergeDoctype.d.ts +11 -0
- package/dist/runtime/server/api/docbuilder/mergeDoctype.js +12 -0
- package/dist/runtime/server/api/docbuilder/save.post.d.ts +4 -1
- package/dist/runtime/server/api/docbuilder/save.post.js +48 -11
- package/dist/runtime/server/api/docbuilder/validate.post.d.ts +2 -1
- package/dist/runtime/server/api/docbuilder/validate.post.js +1 -1
- package/package.json +31 -24
- package/templates/Project.json +4 -10
- package/templates/Task.json +10 -17
- package/templates/resolvers.ts +49 -44
- package/templates/schema.graphql +23 -15
- package/templates/stonecrop.ts +3 -69
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stonecrop/nuxt",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Nuxt module for Stonecrop",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -38,45 +38,53 @@
|
|
|
38
38
|
],
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@nuxt/kit": "^4.4.4",
|
|
41
|
+
"h3": "^1.15.11",
|
|
41
42
|
"pinia": "^3.0.4",
|
|
42
|
-
"citty": "^0.2.
|
|
43
|
-
"consola": "^3.4.
|
|
44
|
-
"jiti": "^2.
|
|
43
|
+
"citty": "^0.2.2",
|
|
44
|
+
"consola": "^3.4.2",
|
|
45
|
+
"jiti": "^2.7.0",
|
|
45
46
|
"pathe": "^2.0.3",
|
|
46
47
|
"prompts": "^2.4.2",
|
|
47
|
-
"@stonecrop/aform": "0.
|
|
48
|
-
"@stonecrop/atable": "0.
|
|
49
|
-
"@stonecrop/
|
|
50
|
-
"@stonecrop/
|
|
51
|
-
"@stonecrop/
|
|
52
|
-
"@stonecrop/
|
|
53
|
-
"@stonecrop/
|
|
54
|
-
"@stonecrop/
|
|
48
|
+
"@stonecrop/aform": "0.14.0",
|
|
49
|
+
"@stonecrop/atable": "0.14.0",
|
|
50
|
+
"@stonecrop/desktop": "0.14.0",
|
|
51
|
+
"@stonecrop/code-editor": "0.14.0",
|
|
52
|
+
"@stonecrop/casl-middleware": "0.14.0",
|
|
53
|
+
"@stonecrop/graphql-client": "0.14.0",
|
|
54
|
+
"@stonecrop/graphql-middleware": "0.14.0",
|
|
55
|
+
"@stonecrop/node-editor": "0.14.0",
|
|
56
|
+
"@stonecrop/nuxt-grafserv": "0.14.0",
|
|
57
|
+
"@stonecrop/schema": "0.14.0",
|
|
58
|
+
"@stonecrop/stonecrop": "0.14.0",
|
|
59
|
+
"@stonecrop/themes": "0.14.0"
|
|
55
60
|
},
|
|
56
61
|
"devDependencies": {
|
|
57
62
|
"@eslint/js": "^10.0.1",
|
|
58
63
|
"@nuxt/devtools": "^3.2.4",
|
|
59
|
-
"@nuxt/eslint-config": "^1.
|
|
60
|
-
"@nuxt/eslint": "1.
|
|
64
|
+
"@nuxt/eslint-config": "^1.16.0",
|
|
65
|
+
"@nuxt/eslint": "1.16.0",
|
|
61
66
|
"@nuxt/module-builder": "^1.0.2",
|
|
62
|
-
"@nuxt/schema": "^4.4.
|
|
67
|
+
"@nuxt/schema": "^4.4.8",
|
|
63
68
|
"@nuxt/test-utils": "^4.0.3",
|
|
64
69
|
"@types/prompts": "^2.4.9",
|
|
65
70
|
"@vitest/coverage-istanbul": "^4.1.5",
|
|
66
71
|
"baseline-browser-mapping": "latest",
|
|
67
72
|
"browserslist": "latest",
|
|
68
73
|
"eslint-config-prettier": "^10.1.8",
|
|
69
|
-
"eslint-plugin-vue": "^10.9.
|
|
70
|
-
"eslint": "^10.
|
|
74
|
+
"eslint-plugin-vue": "^10.9.2",
|
|
75
|
+
"eslint": "^10.6.0",
|
|
71
76
|
"globals": "^17.6.0",
|
|
72
|
-
"
|
|
77
|
+
"graphql": "^16.14.0",
|
|
78
|
+
"nuxt": "^4.4.8",
|
|
79
|
+
"nuxt-graphql-middleware": "^5.4.0",
|
|
73
80
|
"postgraphile": "^5.0.3",
|
|
74
|
-
"typescript-eslint": "^8.
|
|
81
|
+
"typescript-eslint": "^8.62.1",
|
|
75
82
|
"typescript": "^6.0.3",
|
|
83
|
+
"monaco-editor": "^0.55.1",
|
|
76
84
|
"vite": "^7.3.2",
|
|
77
85
|
"vitest": "^4.1.5",
|
|
78
86
|
"vue-router": "^5.0.6",
|
|
79
|
-
"vue-tsc": "^3.
|
|
87
|
+
"vue-tsc": "^3.3.6",
|
|
80
88
|
"vue": "^3.5.33"
|
|
81
89
|
},
|
|
82
90
|
"engines": {
|
|
@@ -87,9 +95,8 @@
|
|
|
87
95
|
"prepublish": "rushx dev:prepare && nuxt-module-build build",
|
|
88
96
|
"build": "rushx dev:prepare && nuxt-module-build build",
|
|
89
97
|
"docs": "echo 'Nuxt modules use custom build system - skipping API docs generation'",
|
|
90
|
-
"dev": "rushx dev:prepare && nuxi dev playground",
|
|
91
|
-
"dev:
|
|
92
|
-
"dev:full": "rushx dev:prepare:full && nuxi dev fullstack",
|
|
98
|
+
"dev": "rushx dev:prepare && nuxi dev playground --host",
|
|
99
|
+
"dev:full": "rushx dev:prepare:full && nuxi dev fullstack --host",
|
|
93
100
|
"dev:prepare:full": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare fullstack",
|
|
94
101
|
"dev:build": "nuxi build playground",
|
|
95
102
|
"dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground && nuxi prepare fullstack",
|
|
@@ -98,6 +105,6 @@
|
|
|
98
105
|
"test:ui": "vitest --ui",
|
|
99
106
|
"test:coverage": "vitest run --coverage.enabled --coverage.provider=istanbul",
|
|
100
107
|
"test:watch": "vitest watch",
|
|
101
|
-
"test:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit"
|
|
108
|
+
"test:types": "vue-tsc --noEmit -p tsconfig.runtime.json && cd playground && vue-tsc --noEmit && cd ../fullstack && vue-tsc --noEmit"
|
|
102
109
|
}
|
|
103
110
|
}
|
package/templates/Project.json
CHANGED
|
@@ -6,35 +6,30 @@
|
|
|
6
6
|
"fieldname": "id",
|
|
7
7
|
"label": "ID",
|
|
8
8
|
"component": "ATextInput",
|
|
9
|
-
"fieldtype": "Data",
|
|
10
9
|
"mode": "display"
|
|
11
10
|
},
|
|
12
11
|
{
|
|
13
12
|
"fieldname": "title",
|
|
14
13
|
"label": "Title",
|
|
15
14
|
"component": "ATextInput",
|
|
16
|
-
"fieldtype": "Data",
|
|
17
15
|
"required": true
|
|
18
16
|
},
|
|
19
17
|
{
|
|
20
18
|
"fieldname": "description",
|
|
21
19
|
"label": "Description",
|
|
22
|
-
"component": "ATextarea"
|
|
23
|
-
"fieldtype": "Text"
|
|
20
|
+
"component": "ATextarea"
|
|
24
21
|
},
|
|
25
22
|
{
|
|
26
23
|
"fieldname": "status",
|
|
27
24
|
"label": "Status",
|
|
28
25
|
"component": "ADropdown",
|
|
29
|
-
"fieldtype": "Select",
|
|
30
26
|
"options": ["Active", "Archived"],
|
|
31
27
|
"default": "Active"
|
|
32
28
|
},
|
|
33
29
|
{
|
|
34
30
|
"fieldname": "createdAt",
|
|
35
31
|
"label": "Created At",
|
|
36
|
-
"component": "
|
|
37
|
-
"fieldtype": "Datetime",
|
|
32
|
+
"component": "ADateTime",
|
|
38
33
|
"mode": "display"
|
|
39
34
|
}
|
|
40
35
|
],
|
|
@@ -43,13 +38,12 @@
|
|
|
43
38
|
"actions": {
|
|
44
39
|
"save": {
|
|
45
40
|
"label": "Save",
|
|
46
|
-
"
|
|
41
|
+
"stateless": true
|
|
47
42
|
},
|
|
48
43
|
"archive": {
|
|
49
44
|
"label": "Archive Project",
|
|
50
|
-
"handler": "archive_project",
|
|
51
45
|
"allowedStates": ["Active"],
|
|
52
|
-
"
|
|
46
|
+
"nextState": "Archived"
|
|
53
47
|
}
|
|
54
48
|
}
|
|
55
49
|
}
|
package/templates/Task.json
CHANGED
|
@@ -6,49 +6,42 @@
|
|
|
6
6
|
"fieldname": "id",
|
|
7
7
|
"label": "ID",
|
|
8
8
|
"component": "ATextInput",
|
|
9
|
-
"fieldtype": "Data",
|
|
10
9
|
"mode": "display"
|
|
11
10
|
},
|
|
12
11
|
{
|
|
13
12
|
"fieldname": "title",
|
|
14
13
|
"label": "Title",
|
|
15
14
|
"component": "ATextInput",
|
|
16
|
-
"fieldtype": "Data",
|
|
17
15
|
"required": true
|
|
18
16
|
},
|
|
19
17
|
{
|
|
20
18
|
"fieldname": "projectId",
|
|
21
19
|
"label": "Project",
|
|
22
|
-
"component": "
|
|
23
|
-
"
|
|
24
|
-
"options": "project",
|
|
20
|
+
"component": "AFormLink",
|
|
21
|
+
"doctype": "project",
|
|
25
22
|
"required": true
|
|
26
23
|
},
|
|
27
24
|
{
|
|
28
25
|
"fieldname": "status",
|
|
29
26
|
"label": "Status",
|
|
30
27
|
"component": "ADropdown",
|
|
31
|
-
"fieldtype": "Select",
|
|
32
28
|
"options": ["Todo", "In Progress", "Done"],
|
|
33
29
|
"default": "Todo"
|
|
34
30
|
},
|
|
35
31
|
{
|
|
36
32
|
"fieldname": "description",
|
|
37
33
|
"label": "Description",
|
|
38
|
-
"component": "ATextarea"
|
|
39
|
-
"fieldtype": "Text"
|
|
34
|
+
"component": "ATextarea"
|
|
40
35
|
},
|
|
41
36
|
{
|
|
42
37
|
"fieldname": "dueDate",
|
|
43
38
|
"label": "Due Date",
|
|
44
|
-
"component": "
|
|
45
|
-
"fieldtype": "Date"
|
|
39
|
+
"component": "ADatePicker"
|
|
46
40
|
},
|
|
47
41
|
{
|
|
48
42
|
"fieldname": "createdAt",
|
|
49
43
|
"label": "Created At",
|
|
50
|
-
"component": "
|
|
51
|
-
"fieldtype": "Datetime",
|
|
44
|
+
"component": "ADateTime",
|
|
52
45
|
"mode": "display"
|
|
53
46
|
}
|
|
54
47
|
],
|
|
@@ -57,17 +50,17 @@
|
|
|
57
50
|
"actions": {
|
|
58
51
|
"save": {
|
|
59
52
|
"label": "Save",
|
|
60
|
-
"
|
|
53
|
+
"stateless": true
|
|
61
54
|
},
|
|
62
55
|
"start_task": {
|
|
63
56
|
"label": "Start Task",
|
|
64
|
-
"
|
|
65
|
-
"
|
|
57
|
+
"allowedStates": ["Todo"],
|
|
58
|
+
"nextState": "In Progress"
|
|
66
59
|
},
|
|
67
60
|
"complete_task": {
|
|
68
61
|
"label": "Complete",
|
|
69
|
-
"
|
|
70
|
-
"
|
|
62
|
+
"allowedStates": ["In Progress"],
|
|
63
|
+
"nextState": "Done"
|
|
71
64
|
}
|
|
72
65
|
}
|
|
73
66
|
}
|
package/templates/resolvers.ts
CHANGED
|
@@ -9,16 +9,16 @@
|
|
|
9
9
|
* - loadOne($step, fn) — batch-load data ASYNCHRONOUSLY (for DB queries)
|
|
10
10
|
* - object({ ... }) — group multiple steps into a single step object
|
|
11
11
|
*
|
|
12
|
-
* Data reads go through loadOne
|
|
13
|
-
*
|
|
12
|
+
* Data reads go through loadOne. Workflow state transitions are applied by the
|
|
13
|
+
* stonecropAction resolver itself (server-owns-transition, guarded by allowedStates);
|
|
14
|
+
* side-effecting saves go through registered handlers (project:save, task:save).
|
|
14
15
|
*
|
|
15
16
|
* To connect a real database, replace the imports from ./data with your
|
|
16
17
|
* PostGraphile setup. See: https://stonecrop.io/docs/guides/postgraphile
|
|
17
18
|
*/
|
|
18
19
|
|
|
19
20
|
import { constant, lambda, loadOne, object } from 'grafast'
|
|
20
|
-
import { getMeta, getAllMeta,
|
|
21
|
-
import type { ActionContext } from '@stonecrop/graphql-middleware'
|
|
21
|
+
import { getMeta, getAllMeta, applyGuardedTransition } from '@stonecrop/graphql-middleware'
|
|
22
22
|
import type { DoctypeMeta } from '@stonecrop/schema'
|
|
23
23
|
import { projects, tasks, type Project, type Task } from './data'
|
|
24
24
|
|
|
@@ -27,45 +27,22 @@ import { projects, tasks, type Project, type Task } from './data'
|
|
|
27
27
|
// response type defined in server/schema.graphql)
|
|
28
28
|
// ============================================================
|
|
29
29
|
|
|
30
|
-
function
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
required: field.required ?? false,
|
|
36
|
-
readOnly: field.readOnly ?? false,
|
|
37
|
-
options: field.options ?? null,
|
|
38
|
-
default: field.default ?? null,
|
|
39
|
-
width: field.width ?? null,
|
|
40
|
-
validation: field.validation ?? null,
|
|
41
|
-
component: field.component ?? null,
|
|
42
|
-
align: field.align ?? null,
|
|
43
|
-
edit: field.edit ?? null,
|
|
44
|
-
hidden: field.hidden ?? null,
|
|
45
|
-
mask: field.mask ?? null,
|
|
46
|
-
precision: field.precision ?? null,
|
|
47
|
-
scale: field.scale ?? null,
|
|
48
|
-
mode: field.mode ?? null,
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function formatDoctypeMeta(meta: DoctypeMeta) {
|
|
30
|
+
export function formatDoctypeMeta(meta: DoctypeMeta) {
|
|
31
|
+
// Fields and actions pass through verbatim — the SDL alone decides what is
|
|
32
|
+
// selectable. Enumerating keys here silently drops any field the schema gains
|
|
33
|
+
// later; the only computed addition is `name` (an action's key in the
|
|
34
|
+
// WorkflowMeta.actions record, flattened into the list the SDL declares).
|
|
53
35
|
const actions = meta.workflow?.actions
|
|
54
36
|
const actionList = actions
|
|
55
37
|
? Object.entries(actions as Record<string, Record<string, unknown>>).map(([name, action]) => ({
|
|
56
38
|
name,
|
|
57
|
-
|
|
58
|
-
handler: action.handler ?? null,
|
|
59
|
-
requiredFields: (action.requiredFields as string[]) ?? [],
|
|
60
|
-
allowedStates: (action.allowedStates as string[]) ?? [],
|
|
61
|
-
confirm: action.confirm ?? false,
|
|
62
|
-
args: action.args ?? null,
|
|
39
|
+
...action,
|
|
63
40
|
}))
|
|
64
41
|
: []
|
|
65
42
|
return {
|
|
66
43
|
name: meta.name,
|
|
67
44
|
slug: meta.slug ?? null,
|
|
68
|
-
fields: meta.fields
|
|
45
|
+
fields: meta.fields,
|
|
69
46
|
workflow: meta.workflow
|
|
70
47
|
? {
|
|
71
48
|
states: meta.workflow.states ?? null,
|
|
@@ -188,18 +165,46 @@ export const resolvers = {
|
|
|
188
165
|
const actionDef = meta.workflow?.actions?.[spec.action]
|
|
189
166
|
if (!actionDef) return { success: false, data: null, error: `Unknown action: ${spec.action}` }
|
|
190
167
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
// In a PostGraphile setup, context.pgClient would provide the database connection instead.
|
|
198
|
-
const actionContext: ActionContext = { doctype: meta }
|
|
168
|
+
// Record envelope: [{ id, data }] — the transition keys off the record id, and a
|
|
169
|
+
// self-transition persists the edited field `data` in place.
|
|
170
|
+
const argList = Array.isArray(spec.actionArgs) ? spec.actionArgs : []
|
|
171
|
+
const recordId = argList[0]?.id != null ? String(argList[0].id) : undefined
|
|
172
|
+
const recordData: Record<string, unknown> = argList[0]?.data ?? {}
|
|
173
|
+
const d = spec.doctype.toLowerCase()
|
|
199
174
|
|
|
200
175
|
try {
|
|
201
|
-
|
|
202
|
-
|
|
176
|
+
// The server owns the transition: read current state, guard against allowedStates,
|
|
177
|
+
// write nextState. Reads/writes go straight to the in-memory Maps; a PostGraphile
|
|
178
|
+
// setup swaps in pgClient SQL instead.
|
|
179
|
+
return await applyGuardedTransition(
|
|
180
|
+
actionDef,
|
|
181
|
+
{
|
|
182
|
+
readState: async () => {
|
|
183
|
+
if (recordId == null) return undefined
|
|
184
|
+
const record = getRecord(d, recordId)
|
|
185
|
+
return record?.status == null ? undefined : String(record.status)
|
|
186
|
+
},
|
|
187
|
+
writeState: async (nextState: string) => {
|
|
188
|
+
if (recordId == null) return
|
|
189
|
+
const existing = getRecord(d, recordId)
|
|
190
|
+
if (!existing) return
|
|
191
|
+
if (d === 'project') projects.set(recordId, { ...existing, status: nextState } as Project)
|
|
192
|
+
else if (d === 'task') tasks.set(recordId, { ...existing, status: nextState } as Task)
|
|
193
|
+
},
|
|
194
|
+
// Self-transition data write: merge the edited fields into the record (status
|
|
195
|
+
// untouched) and return the full record for the client writeback.
|
|
196
|
+
writeData: async (patch: Record<string, unknown>) => {
|
|
197
|
+
if (recordId == null) return {}
|
|
198
|
+
const existing = getRecord(d, recordId)
|
|
199
|
+
if (!existing) return {}
|
|
200
|
+
const updated = { ...existing, ...patch }
|
|
201
|
+
if (d === 'project') projects.set(recordId, updated as Project)
|
|
202
|
+
else if (d === 'task') tasks.set(recordId, updated as Task)
|
|
203
|
+
return updated as Record<string, unknown>
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
recordData
|
|
207
|
+
)
|
|
203
208
|
} catch (err) {
|
|
204
209
|
return { success: false, data: null, error: err instanceof Error ? err.message : String(err) }
|
|
205
210
|
}
|
package/templates/schema.graphql
CHANGED
|
@@ -11,36 +11,44 @@ scalar JSON
|
|
|
11
11
|
Field metadata from doctype schema
|
|
12
12
|
"""
|
|
13
13
|
type StonecropField {
|
|
14
|
+
kind: String!
|
|
14
15
|
fieldname: String!
|
|
15
|
-
|
|
16
|
+
component: String
|
|
17
|
+
primaryKey: Boolean
|
|
18
|
+
computed: Boolean
|
|
19
|
+
language: String
|
|
20
|
+
doctype: String
|
|
16
21
|
label: String
|
|
17
|
-
required: Boolean
|
|
18
|
-
readOnly: Boolean
|
|
19
|
-
options: JSON
|
|
20
|
-
default: JSON
|
|
21
22
|
width: String
|
|
22
|
-
validation: JSON
|
|
23
|
-
component: String
|
|
24
23
|
align: String
|
|
25
24
|
edit: Boolean
|
|
26
|
-
hidden: Boolean
|
|
27
25
|
mask: String
|
|
28
|
-
|
|
29
|
-
scale: Int
|
|
26
|
+
format: String
|
|
30
27
|
mode: String
|
|
28
|
+
options: JSON
|
|
29
|
+
required: Boolean
|
|
30
|
+
readOnly: Boolean
|
|
31
|
+
hidden: Boolean
|
|
32
|
+
default: JSON
|
|
33
|
+
validation: JSON
|
|
34
|
+
cardinality: String
|
|
35
|
+
source: String
|
|
31
36
|
}
|
|
32
37
|
|
|
33
38
|
"""
|
|
34
|
-
A single workflow action definition
|
|
39
|
+
A single workflow action definition (`name` is the action's key in
|
|
40
|
+
WorkflowMeta.actions; the other fields mirror ActionDefinition in
|
|
41
|
+
@stonecrop/schema)
|
|
35
42
|
"""
|
|
36
43
|
type WorkflowAction {
|
|
37
44
|
name: String!
|
|
38
|
-
label: String
|
|
39
|
-
handler: String
|
|
45
|
+
label: String!
|
|
40
46
|
requiredFields: [String!]
|
|
41
47
|
allowedStates: [String!]
|
|
42
|
-
|
|
43
|
-
|
|
48
|
+
nextState: String
|
|
49
|
+
stateless: Boolean
|
|
50
|
+
selfTransition: Boolean
|
|
51
|
+
clientHandler: String
|
|
44
52
|
}
|
|
45
53
|
|
|
46
54
|
"""
|
package/templates/stonecrop.ts
CHANGED
|
@@ -1,82 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Stonecrop Server Plugin
|
|
3
3
|
*
|
|
4
|
-
* Runs at server startup (Nitro plugin) to
|
|
5
|
-
*
|
|
6
|
-
* 2. Register built-in action handlers (validation, etc.)
|
|
7
|
-
* 3. Register custom workflow action handlers
|
|
8
|
-
*
|
|
9
|
-
* Action handlers receive (args, context) where args is the array sent by the
|
|
10
|
-
* client — the app's frontend (app/composables/useDoctypes.ts) sends a single
|
|
11
|
-
* `{ id, data? }` object per action — and context.doctype is the DoctypeMeta
|
|
12
|
-
* for the doctype being acted upon. For this in-memory setup, data is imported
|
|
13
|
-
* directly from server/data.ts. In production with PostGraphile, context.pgClient
|
|
14
|
-
* provides an active database connection instead — see:
|
|
15
|
-
* https://stonecrop.io/docs/guides/postgraphile
|
|
4
|
+
* Runs at server startup (Nitro plugin) to load doctype definitions from the
|
|
5
|
+
* doctypes/ directory into the middleware registry.
|
|
16
6
|
*/
|
|
17
7
|
|
|
18
8
|
import { resolve } from 'node:path'
|
|
19
|
-
import { clearRegistry, loadDoctypes
|
|
20
|
-
import { projects, tasks, type Project, type Task } from '../data'
|
|
21
|
-
|
|
22
|
-
/** Action argument shape sent by the frontend (see runDoctypeAction in useDoctypes.ts) */
|
|
23
|
-
type ActionArgs = [{ id: string; data?: Record<string, unknown> }]
|
|
9
|
+
import { clearRegistry, loadDoctypes } from '@stonecrop/graphql-middleware'
|
|
24
10
|
|
|
25
11
|
export default defineNitroPlugin(async () => {
|
|
26
12
|
clearRegistry()
|
|
27
13
|
|
|
28
14
|
const doctypesDir = resolve(process.cwd(), 'doctypes')
|
|
29
15
|
loadDoctypes(doctypesDir, { continueOnError: true })
|
|
30
|
-
|
|
31
|
-
registerBuiltinHandlers()
|
|
32
|
-
|
|
33
|
-
// Persist form edits to a Project
|
|
34
|
-
registerHandler('project:save', async (args: unknown[]) => {
|
|
35
|
-
const [{ id, data }] = args as ActionArgs
|
|
36
|
-
const project = projects.get(id)
|
|
37
|
-
if (!project) throw new Error(`Project not found: ${id}`)
|
|
38
|
-
const updated = { ...project, ...data, id } as Project
|
|
39
|
-
projects.set(id, updated)
|
|
40
|
-
return updated
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
// Persist form edits to a Task
|
|
44
|
-
registerHandler('task:save', async (args: unknown[]) => {
|
|
45
|
-
const [{ id, data }] = args as ActionArgs
|
|
46
|
-
const task = tasks.get(id)
|
|
47
|
-
if (!task) throw new Error(`Task not found: ${id}`)
|
|
48
|
-
const updated = { ...task, ...data, id } as Task
|
|
49
|
-
tasks.set(id, updated)
|
|
50
|
-
return updated
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
// Transition a Task from Todo → In Progress
|
|
54
|
-
registerHandler('start_task', async (args: unknown[]) => {
|
|
55
|
-
const [{ id }] = args as ActionArgs
|
|
56
|
-
const task = tasks.get(id)
|
|
57
|
-
if (!task) throw new Error(`Task not found: ${id}`)
|
|
58
|
-
const updated = { ...task, status: 'In Progress' as const }
|
|
59
|
-
tasks.set(id, updated)
|
|
60
|
-
return updated
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
// Transition a Task from In Progress → Done
|
|
64
|
-
registerHandler('complete_task', async (args: unknown[]) => {
|
|
65
|
-
const [{ id }] = args as ActionArgs
|
|
66
|
-
const task = tasks.get(id)
|
|
67
|
-
if (!task) throw new Error(`Task not found: ${id}`)
|
|
68
|
-
const updated = { ...task, status: 'Done' as const }
|
|
69
|
-
tasks.set(id, updated)
|
|
70
|
-
return updated
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
// Transition a Project from Active → Archived
|
|
74
|
-
registerHandler('archive_project', async (args: unknown[]) => {
|
|
75
|
-
const [{ id }] = args as ActionArgs
|
|
76
|
-
const project = projects.get(id)
|
|
77
|
-
if (!project) throw new Error(`Project not found: ${id}`)
|
|
78
|
-
const updated = { ...project, status: 'Archived' as const }
|
|
79
|
-
projects.set(id, updated)
|
|
80
|
-
return updated
|
|
81
|
-
})
|
|
82
16
|
})
|