@stonecrop/nuxt 0.13.8 → 0.13.10

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,82 @@
1
+ /**
2
+ * Stonecrop Server Plugin
3
+ *
4
+ * Runs at server startup (Nitro plugin) to:
5
+ * 1. Load doctype definitions from the doctypes/ directory
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
16
+ */
17
+
18
+ import { resolve } from 'node:path'
19
+ import { clearRegistry, loadDoctypes, registerBuiltinHandlers, registerHandler } from '@stonecrop/graphql-middleware'
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> }]
24
+
25
+ export default defineNitroPlugin(async () => {
26
+ clearRegistry()
27
+
28
+ const doctypesDir = resolve(process.cwd(), 'doctypes')
29
+ 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
+ })
@@ -0,0 +1,53 @@
1
+ import type { DoctypeRef } from '@stonecrop/schema'
2
+ import type { DoctypeConfig } from '@stonecrop/stonecrop'
3
+ import { useNuxtApp } from 'nuxt/app'
4
+
5
+ const modules = import.meta.glob<DoctypeConfig>('../../doctypes/*.json', {
6
+ eager: true,
7
+ import: 'default',
8
+ })
9
+
10
+ export const doctypeMap = new Map<string, DoctypeConfig>()
11
+
12
+ for (const [path, doctype] of Object.entries(modules)) {
13
+ const filename = path.split('/').pop()!.replace('.json', '')
14
+ const slug = filename
15
+ .replace(/([a-z])([A-Z])/g, '$1-$2')
16
+ .replace(/[\s_]+/g, '-')
17
+ .toLowerCase()
18
+ doctypeMap.set(slug, doctype)
19
+ }
20
+
21
+ export function useDoctypeConfig(slug: string): DoctypeConfig | undefined {
22
+ return doctypeMap.get(slug)
23
+ }
24
+
25
+ export async function fetchDoctypeRecords(doctype: DoctypeRef, limit = 200): Promise<{ data: any[]; count: number }> {
26
+ const { $stonecropClient } = useNuxtApp()
27
+ const data = (await $stonecropClient.getRecords({ name: doctype.name }, { limit })) as any[]
28
+ return { data, count: data.length }
29
+ }
30
+
31
+ export async function fetchDoctypeRecord(
32
+ doctype: DoctypeRef,
33
+ recordId: string
34
+ ): Promise<Record<string, unknown> | null> {
35
+ const { $stonecropClient } = useNuxtApp()
36
+ const result = await $stonecropClient.getRecord(doctype, recordId)
37
+ return result.record
38
+ }
39
+
40
+ export interface ActionResult {
41
+ success: boolean
42
+ data?: unknown
43
+ error?: string | null
44
+ }
45
+
46
+ export async function runDoctypeAction(
47
+ doctype: DoctypeConfig,
48
+ action: string,
49
+ args: { id: string; data?: Record<string, unknown> }
50
+ ): Promise<ActionResult> {
51
+ const { $stonecropClient } = useNuxtApp()
52
+ return $stonecropClient.runAction({ name: doctype.name }, action, [args])
53
+ }
@@ -0,0 +1,36 @@
1
+ import type { NavigationTarget, RouteAdapter } from '@stonecrop/desktop'
2
+ import { navigateTo, useRoute } from 'nuxt/app'
3
+
4
+ export function useRouteAdapter(): RouteAdapter {
5
+ const route = useRoute()
6
+
7
+ const getCurrentDoctype = (): string => {
8
+ const pathMatch = route.params.pathMatch as string[] | undefined
9
+ if (pathMatch && pathMatch.length > 0) return pathMatch[0] ?? ''
10
+ return ''
11
+ }
12
+
13
+ const getCurrentRecordId = (): string => {
14
+ const pathMatch = route.params.pathMatch as string[] | undefined
15
+ if (pathMatch && pathMatch.length > 1) return pathMatch[1] ?? ''
16
+ return ''
17
+ }
18
+
19
+ const getCurrentView = (): 'doctypes' | 'records' | 'record' => {
20
+ if (!getCurrentDoctype()) return 'doctypes'
21
+ if (getCurrentRecordId()) return 'record'
22
+ return 'records'
23
+ }
24
+
25
+ const navigate = async (target: NavigationTarget): Promise<void> => {
26
+ if (target.view === 'doctypes') {
27
+ await navigateTo('/')
28
+ } else if (target.view === 'records' && target.doctype) {
29
+ await navigateTo(`/${target.doctype}`)
30
+ } else if (target.view === 'record' && target.doctype && target.recordId) {
31
+ await navigateTo(`/${target.doctype}/${target.recordId}`)
32
+ }
33
+ }
34
+
35
+ return { getCurrentDoctype, getCurrentRecordId, getCurrentView, navigate }
36
+ }
@@ -1,84 +0,0 @@
1
- {
2
- "name": "Example",
3
- "slug": "example/:id",
4
- "fields": [
5
- {
6
- "fieldname": "id",
7
- "fieldtype": "Data",
8
- "label": "ID",
9
- "readOnly": true
10
- },
11
- {
12
- "fieldname": "title",
13
- "fieldtype": "Data",
14
- "label": "Title",
15
- "required": true,
16
- "width": "30ch"
17
- },
18
- {
19
- "fieldname": "description",
20
- "fieldtype": "Text",
21
- "label": "Description"
22
- },
23
- {
24
- "fieldname": "status",
25
- "fieldtype": "Select",
26
- "label": "Status",
27
- "options": ["Draft", "Active", "Archived"],
28
- "default": "Draft"
29
- },
30
- {
31
- "fieldname": "priority",
32
- "fieldtype": "Select",
33
- "label": "Priority",
34
- "options": ["Low", "Medium", "High"],
35
- "default": "Medium"
36
- },
37
- {
38
- "fieldname": "assignee",
39
- "fieldtype": "Link",
40
- "label": "Assignee",
41
- "options": "User"
42
- },
43
- {
44
- "fieldname": "dueDate",
45
- "fieldtype": "Date",
46
- "label": "Due Date"
47
- },
48
- {
49
- "fieldname": "createdAt",
50
- "fieldtype": "Datetime",
51
- "label": "Created At",
52
- "readOnly": true
53
- },
54
- {
55
- "fieldname": "updatedAt",
56
- "fieldtype": "Datetime",
57
- "label": "Updated At",
58
- "readOnly": true
59
- }
60
- ],
61
- "workflow": {
62
- "states": ["Draft", "Active", "Archived"],
63
- "actions": {
64
- "activate": {
65
- "label": "Activate",
66
- "handler": "activate_example",
67
- "allowedStates": ["Draft"],
68
- "confirm": true
69
- },
70
- "archive": {
71
- "label": "Archive",
72
- "handler": "archive_example",
73
- "allowedStates": ["Active"],
74
- "confirm": true
75
- },
76
- "reopen": {
77
- "label": "Reopen",
78
- "handler": "reopen_example",
79
- "allowedStates": ["Archived"],
80
- "confirm": false
81
- }
82
- }
83
- }
84
- }
@@ -1,58 +0,0 @@
1
- {
2
- "name": "Example",
3
- "slug": "example",
4
- "schema": [
5
- {
6
- "component": "ATable",
7
- "columns": [
8
- {
9
- "name": "id",
10
- "label": "ID",
11
- "fieldtype": "Data",
12
- "width": "8ch"
13
- },
14
- {
15
- "name": "title",
16
- "label": "Title",
17
- "fieldtype": "Data",
18
- "width": "25ch"
19
- },
20
- {
21
- "name": "status",
22
- "label": "Status",
23
- "fieldtype": "Data",
24
- "width": "10ch"
25
- },
26
- {
27
- "name": "priority",
28
- "label": "Priority",
29
- "fieldtype": "Data",
30
- "width": "10ch"
31
- },
32
- {
33
- "name": "assignee",
34
- "label": "Assignee",
35
- "fieldtype": "Data",
36
- "width": "15ch"
37
- },
38
- {
39
- "name": "dueDate",
40
- "label": "Due Date",
41
- "fieldtype": "Date",
42
- "width": "12ch"
43
- },
44
- {
45
- "name": "createdAt",
46
- "label": "Created",
47
- "fieldtype": "Datetime",
48
- "width": "18ch"
49
- }
50
- ],
51
- "config": {
52
- "view": "list",
53
- "sortable": true,
54
- "filterable": true
55
- }
56
- }
57
- ]
58
- }