@stonecrop/nuxt 0.25.0 → 0.26.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 +15 -7
- package/dist/module.d.mts +5 -3
- package/dist/module.json +1 -1
- package/dist/module.mjs +26 -9
- package/dist/runtime/app/components/DocBuilderFieldsPanel.vue +1 -5
- package/dist/runtime/app/components/docbuilderFields.d.ts +13 -0
- package/dist/runtime/app/components/docbuilderFields.js +4 -0
- package/dist/runtime/server/api/docbuilder/mergeDoctype.js +5 -1
- package/package.json +13 -13
package/README.md
CHANGED
|
@@ -211,7 +211,7 @@ export default defineNuxtConfig({
|
|
|
211
211
|
modules: ['@stonecrop/nuxt'],
|
|
212
212
|
|
|
213
213
|
stonecrop: {
|
|
214
|
-
// Point to your own page component
|
|
214
|
+
// Point to your own page component. Doctypes that declare a `route` get a page.
|
|
215
215
|
pageComponent: 'pages/StonecropPage.vue',
|
|
216
216
|
|
|
217
217
|
// Or supply a custom strategy for full control
|
|
@@ -231,7 +231,7 @@ export default defineNuxtConfig({
|
|
|
231
231
|
|
|
232
232
|
| Option | Type | Description |
|
|
233
233
|
|--------|------|-------------|
|
|
234
|
-
| `pageComponent` | `string` | Path (relative to `srcDir`) to your page component. The module registers
|
|
234
|
+
| `pageComponent` | `string` | Path (relative to `srcDir`) to your page component. The module registers a route for each doctype that declares one, at exactly its `route` path, passing `schema` and `doctype` in `route.meta`. |
|
|
235
235
|
| `routeStrategy` | `RouteStrategyFn` | Custom function receiving all parsed doctypes; returns a `NuxtPage[]`. Takes priority over `pageComponent`. |
|
|
236
236
|
| `docbuilder` | `boolean` | Enable the DocBuilder feature at `/docbuilder`. Defaults to `false`. |
|
|
237
237
|
| `doctypesDir` | `string` | Override the doctypes directory path. Defaults to `doctypes/` inside `srcDir`. |
|
|
@@ -241,17 +241,25 @@ If neither `pageComponent` nor `routeStrategy` is configured the module logs a w
|
|
|
241
241
|
|
|
242
242
|
## Route Generation
|
|
243
243
|
|
|
244
|
-
### Default:
|
|
244
|
+
### Default: routes the doctypes declare
|
|
245
245
|
|
|
246
|
-
The module scans your `doctypes/` folder and registers
|
|
246
|
+
The module scans your `doctypes/` folder and registers a route for each doctype carrying a `route`
|
|
247
|
+
key, at exactly that path. A doctype without one gets no page.
|
|
247
248
|
|
|
248
249
|
```
|
|
249
250
|
doctypes/
|
|
250
|
-
├── task.json
|
|
251
|
-
├──
|
|
252
|
-
└──
|
|
251
|
+
├── task.json { "route": "/task/:id" } → /task/:id the record
|
|
252
|
+
├── tasks.json { "route": "/task" } → /task the collection
|
|
253
|
+
└── task-comment.json no route → no page
|
|
253
254
|
```
|
|
254
255
|
|
|
256
|
+
Most doctypes are not meant to be visited. A child table's rows are edited inside their parent and
|
|
257
|
+
a link target is reached through the record pointing at it, so neither needs a URL of its own.
|
|
258
|
+
|
|
259
|
+
`stonecrop-schema generate` writes these keys for you: an entity and its aggregate share one URL
|
|
260
|
+
segment, and a doctype the schema shows as rows owned by another gets none. The generated file is
|
|
261
|
+
yours after that — add, change or delete a `route` and regeneration leaves it alone.
|
|
262
|
+
|
|
255
263
|
Each route's `meta` contains the parsed doctype:
|
|
256
264
|
|
|
257
265
|
```typescript
|
package/dist/module.d.mts
CHANGED
|
@@ -51,9 +51,11 @@ interface ModuleOptions {
|
|
|
51
51
|
/** Path to doctypes folder relative to the project root (defaults to 'doctypes') */
|
|
52
52
|
doctypesDir?: string;
|
|
53
53
|
/**
|
|
54
|
-
* Path to the page component
|
|
55
|
-
*
|
|
56
|
-
*
|
|
54
|
+
* Path to the page component every doctype route renders.
|
|
55
|
+
*
|
|
56
|
+
* When `routeStrategy` is not set, one route is registered per doctype that **declares** a
|
|
57
|
+
* `route`, at exactly that path — `/order` for a collection, `/order/:id` for a record. A
|
|
58
|
+
* doctype without one gets no page: most doctypes are not meant to be visited directly.
|
|
57
59
|
*
|
|
58
60
|
* The path is resolved relative to the application's `srcDir`.
|
|
59
61
|
*
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -3,6 +3,25 @@ import { readdir, readFile } from 'node:fs/promises';
|
|
|
3
3
|
import { dirname, extname } from 'node:path';
|
|
4
4
|
import { createResolver, defineNuxtModule, useLogger, resolvePath, addLayout, extendPages, addServerHandler, addComponent, addPlugin, addImportsDir } from '@nuxt/kit';
|
|
5
5
|
|
|
6
|
+
function declaredRoutePages(doctypes, componentPath) {
|
|
7
|
+
const pages = [];
|
|
8
|
+
const skipped = [];
|
|
9
|
+
for (const { fileName, data, fields } of doctypes) {
|
|
10
|
+
const route = data.route;
|
|
11
|
+
if (typeof route !== "string" || !route.startsWith("/")) {
|
|
12
|
+
skipped.push(fileName);
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
pages.push({
|
|
16
|
+
name: `stonecrop-${fileName}`,
|
|
17
|
+
path: route,
|
|
18
|
+
file: componentPath,
|
|
19
|
+
meta: { schema: fields, doctype: data }
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
return { pages, skipped };
|
|
23
|
+
}
|
|
24
|
+
|
|
6
25
|
const { resolve } = createResolver(import.meta.url);
|
|
7
26
|
const STONECROP_PACKAGES = [
|
|
8
27
|
"@stonecrop/aform",
|
|
@@ -144,15 +163,13 @@ const module$1 = defineNuxtModule({
|
|
|
144
163
|
generatedPages = options.routeStrategy(doctypes);
|
|
145
164
|
} else if (options.pageComponent) {
|
|
146
165
|
const componentPath = resolve(appDir, options.pageComponent);
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
};
|
|
155
|
-
});
|
|
166
|
+
const declared = declaredRoutePages(doctypes, componentPath);
|
|
167
|
+
generatedPages = declared.pages;
|
|
168
|
+
if (declared.skipped.length > 0) {
|
|
169
|
+
logger.info(
|
|
170
|
+
`No route declared, so no page registered for: ${declared.skipped.join(", ")}. Add a "route" to a doctype to give it one.`
|
|
171
|
+
);
|
|
172
|
+
}
|
|
156
173
|
} else {
|
|
157
174
|
logger.warn(
|
|
158
175
|
"No routeStrategy or pageComponent configured \u2014 doctype routes will not be registered. Set pageComponent to a page path or provide a routeStrategy function."
|
|
@@ -129,7 +129,7 @@
|
|
|
129
129
|
import { ATable, ARow } from "@stonecrop/atable";
|
|
130
130
|
import { CANONICAL_COMPONENTS, INTROSPECTED_IDENTITY_PROPS } from "@stonecrop/schema";
|
|
131
131
|
import { computed, nextTick, ref, useId } from "vue";
|
|
132
|
-
import { updateFieldAt } from "./docbuilderFields";
|
|
132
|
+
import { isValueField, updateFieldAt } from "./docbuilderFields";
|
|
133
133
|
const IDENTITY_PROPS = new Set(INTROSPECTED_IDENTITY_PROPS);
|
|
134
134
|
const isIdentity = (key) => IDENTITY_PROPS.has(key);
|
|
135
135
|
const componentListId = useId();
|
|
@@ -196,10 +196,6 @@ const props = defineProps({
|
|
|
196
196
|
modelValue: { type: Array, required: true }
|
|
197
197
|
});
|
|
198
198
|
const emit = defineEmits(["update:modelValue"]);
|
|
199
|
-
function isValueField(f) {
|
|
200
|
-
if (typeof f.kind === "string") return f.kind === "field";
|
|
201
|
-
return !("schema" in f) && !("columns" in f);
|
|
202
|
-
}
|
|
203
199
|
function isLocked(f) {
|
|
204
200
|
return f.source === "introspected";
|
|
205
201
|
}
|
|
@@ -15,6 +15,19 @@
|
|
|
15
15
|
*/
|
|
16
16
|
/** A doctype field as authored on disk: known keys plus anything the builder does not model. */
|
|
17
17
|
export type Field = Record<string, unknown>;
|
|
18
|
+
/**
|
|
19
|
+
* Whether an entry is a value field — the only kind the panel renders as a row. Fieldsets and
|
|
20
|
+
* inline tables are preserved untouched.
|
|
21
|
+
*
|
|
22
|
+
* Classified by shape through `@stonecrop/schema`'s `inferFieldKind`, never by reading `kind`.
|
|
23
|
+
* `kind` is Stonecrop's own discriminant — the parser synthesizes it and nothing writes it to disk
|
|
24
|
+
* — so the builder, which reads raw JSON and never parses a doctype, has no business consulting it.
|
|
25
|
+
* It used to, only because the generator and this very save path were leaking it into the files.
|
|
26
|
+
*
|
|
27
|
+
* The rule itself is imported rather than restated: getting it wrong re-types a field silently, and
|
|
28
|
+
* a fieldset read as a value field renders as an editable row and loses its children on save.
|
|
29
|
+
*/
|
|
30
|
+
export declare function isValueField(field: Field): boolean;
|
|
18
31
|
/**
|
|
19
32
|
* Set `key` on `field`, or remove it when `val` is `undefined`.
|
|
20
33
|
*
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { stripFieldKind } from "@stonecrop/schema";
|
|
1
2
|
export function orderKeysByReference(next, reference) {
|
|
2
3
|
if (!next || !reference) return next;
|
|
3
4
|
const result = {};
|
|
@@ -13,7 +14,10 @@ export function orderKeysByReference(next, reference) {
|
|
|
13
14
|
export function mergeSavedDoctype(existing, body, requestedName) {
|
|
14
15
|
const doctypeData = {
|
|
15
16
|
...existing,
|
|
16
|
-
fields
|
|
17
|
+
// `kind` is the parser's discriminant, not authored data — the builder holds fields that
|
|
18
|
+
// went through `normalizeFieldKind`, so it submits one on every field. Writing it back put
|
|
19
|
+
// a key on disk that no author typed and that `injectKind` re-derives on every read.
|
|
20
|
+
fields: body.fields.map(stripFieldKind)
|
|
17
21
|
};
|
|
18
22
|
if (body.workflow !== void 0 && body.workflow !== null) {
|
|
19
23
|
const existingActions = existing.workflow?.actions;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stonecrop/nuxt",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"description": "Nuxt module for Stonecrop",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -45,18 +45,18 @@
|
|
|
45
45
|
"pathe": "^2.0.3",
|
|
46
46
|
"pinia": "^3.0.4",
|
|
47
47
|
"prompts": "^2.4.2",
|
|
48
|
-
"@stonecrop/
|
|
49
|
-
"@stonecrop/
|
|
50
|
-
"@stonecrop/
|
|
51
|
-
"@stonecrop/casl-middleware": "0.
|
|
52
|
-
"@stonecrop/
|
|
53
|
-
"@stonecrop/graphql-client": "0.
|
|
54
|
-
"@stonecrop/graphql-middleware": "0.
|
|
55
|
-
"@stonecrop/
|
|
56
|
-
"@stonecrop/nuxt-grafserv": "0.
|
|
57
|
-
"@stonecrop/
|
|
58
|
-
"@stonecrop/
|
|
59
|
-
"@stonecrop/
|
|
48
|
+
"@stonecrop/atable": "0.26.0",
|
|
49
|
+
"@stonecrop/code-editor": "0.26.0",
|
|
50
|
+
"@stonecrop/desktop": "0.26.0",
|
|
51
|
+
"@stonecrop/casl-middleware": "0.26.0",
|
|
52
|
+
"@stonecrop/aform": "0.26.0",
|
|
53
|
+
"@stonecrop/graphql-client": "0.26.0",
|
|
54
|
+
"@stonecrop/graphql-middleware": "0.26.0",
|
|
55
|
+
"@stonecrop/node-editor": "0.26.0",
|
|
56
|
+
"@stonecrop/nuxt-grafserv": "0.26.0",
|
|
57
|
+
"@stonecrop/schema": "0.26.0",
|
|
58
|
+
"@stonecrop/stonecrop": "0.26.0",
|
|
59
|
+
"@stonecrop/themes": "0.26.0"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
62
|
"@eslint/js": "^10.0.1",
|