@stonecrop/nuxt 0.16.4 → 0.16.6

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/dist/module.d.mts CHANGED
@@ -87,7 +87,8 @@ interface ModuleOptions {
87
87
  */
88
88
  theme?: string | false;
89
89
  }
90
+ declare const STONECROP_PACKAGES: string[];
90
91
  declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
91
92
 
92
- export { _default as default };
93
+ export { STONECROP_PACKAGES, _default as default };
93
94
  export type { ModuleOptions, ParsedDoctype, RouteStrategyFn };
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stonecrop/nuxt",
3
3
  "configKey": "stonecrop",
4
- "version": "0.16.4",
4
+ "version": "0.16.6",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "unknown"
package/dist/module.mjs CHANGED
@@ -7,6 +7,8 @@ const { resolve } = createResolver(import.meta.url);
7
7
  const STONECROP_PACKAGES = [
8
8
  "@stonecrop/aform",
9
9
  "@stonecrop/atable",
10
+ "@stonecrop/code-editor",
11
+ "@stonecrop/desktop",
10
12
  "@stonecrop/stonecrop",
11
13
  "@stonecrop/node-editor",
12
14
  "@stonecrop/utilities",
@@ -251,4 +253,4 @@ const module$1 = defineNuxtModule({
251
253
  }
252
254
  });
253
255
 
254
- export { module$1 as default };
256
+ export { STONECROP_PACKAGES, module$1 as default };
@@ -70,14 +70,14 @@
70
70
  <input
71
71
  type="text"
72
72
  :value="str(row.__field[p.key])"
73
- :disabled="!!p.identity && isLocked(row.__field)"
73
+ :disabled="isIdentity(p.key) && isLocked(row.__field)"
74
74
  @input="update(row.__realIndex, p.key, value($event) || void 0)" />
75
75
  </label>
76
76
  <label v-for="p in SELECT_PROPS" :key="p.key" class="field-prop">
77
77
  <span>{{ p.label }}</span>
78
78
  <select
79
79
  :value="str(row.__field[p.key])"
80
- :disabled="!!p.identity && isLocked(row.__field)"
80
+ :disabled="isIdentity(p.key) && isLocked(row.__field)"
81
81
  @change="update(row.__realIndex, p.key, value($event) || void 0)">
82
82
  <option value="">—</option>
83
83
  <option v-for="o in p.options" :key="o" :value="o">{{ o }}</option>
@@ -87,7 +87,7 @@
87
87
  <input
88
88
  type="checkbox"
89
89
  :checked="bool(row.__field[p.key])"
90
- :disabled="!!p.identity && isLocked(row.__field)"
90
+ :disabled="isIdentity(p.key) && isLocked(row.__field)"
91
91
  @change="update(row.__realIndex, p.key, checked($event) || void 0)" />
92
92
  <span>{{ p.label }}</span>
93
93
  </label>
@@ -96,7 +96,7 @@
96
96
  <input
97
97
  type="text"
98
98
  :value="jsonStr(row.__field[p.key])"
99
- :disabled="!!p.identity && isLocked(row.__field)"
99
+ :disabled="isIdentity(p.key) && isLocked(row.__field)"
100
100
  :class="{ 'json-invalid': jsonErrors[`${row.__realIndex}:${p.key}`] }"
101
101
  @change="updateJson(row.__realIndex, p.key, value($event))" />
102
102
  </label>
@@ -127,11 +127,13 @@
127
127
 
128
128
  <script setup>
129
129
  import { ATable, ARow } from "@stonecrop/atable";
130
- import { CANONICAL_COMPONENTS } from "@stonecrop/schema";
130
+ import { CANONICAL_COMPONENTS, INTROSPECTED_IDENTITY_PROPS } from "@stonecrop/schema";
131
131
  import { computed, nextTick, ref, useId } from "vue";
132
+ const IDENTITY_PROPS = new Set(INTROSPECTED_IDENTITY_PROPS);
133
+ const isIdentity = (key) => IDENTITY_PROPS.has(key);
132
134
  const componentListId = useId();
133
135
  const TEXT_PROPS = [
134
- { key: "doctype", label: "Link target", identity: true },
136
+ { key: "doctype", label: "Link target" },
135
137
  { key: "width", label: "Width" },
136
138
  { key: "mask", label: "Mask" },
137
139
  { key: "format", label: "Format" },
@@ -143,19 +145,18 @@ const SELECT_PROPS = [
143
145
  {
144
146
  key: "cardinality",
145
147
  label: "Cardinality",
146
- options: ["atMostOne", "one", "noneOrMany", "atLeastOne"],
147
- identity: true
148
+ options: ["atMostOne", "one", "noneOrMany", "atLeastOne"]
148
149
  }
149
150
  ];
150
151
  const BOOL_PROPS = [
151
152
  { key: "readOnly", label: "Read only" },
152
153
  { key: "hidden", label: "Hidden" },
153
154
  { key: "edit", label: "Editable in table" },
154
- { key: "primaryKey", label: "Primary key", identity: true },
155
+ { key: "primaryKey", label: "Primary key" },
155
156
  { key: "computed", label: "Computed (no DB column)" }
156
157
  ];
157
158
  const JSON_PROPS = [
158
- { key: "options", label: "Options", identity: true },
159
+ { key: "options", label: "Options" },
159
160
  { key: "default", label: "Default" }
160
161
  ];
161
162
  const FIELD_COLUMNS = [
package/dist/types.d.mts CHANGED
@@ -1,3 +1,3 @@
1
- export { default } from './module.mjs'
1
+ export { type STONECROP_PACKAGES, default } from './module.mjs'
2
2
 
3
3
  export { type ModuleOptions, type ParsedDoctype, type RouteStrategyFn } from './module.mjs'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stonecrop/nuxt",
3
- "version": "0.16.4",
3
+ "version": "0.16.6",
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/atable": "0.16.4",
49
- "@stonecrop/aform": "0.16.4",
50
- "@stonecrop/casl-middleware": "0.16.4",
51
- "@stonecrop/code-editor": "0.16.4",
52
- "@stonecrop/desktop": "0.16.4",
53
- "@stonecrop/graphql-middleware": "0.16.4",
54
- "@stonecrop/nuxt-grafserv": "0.16.4",
55
- "@stonecrop/graphql-client": "0.16.4",
56
- "@stonecrop/schema": "0.16.4",
57
- "@stonecrop/stonecrop": "0.16.4",
58
- "@stonecrop/themes": "0.16.4",
59
- "@stonecrop/node-editor": "0.16.4"
48
+ "@stonecrop/aform": "0.16.6",
49
+ "@stonecrop/desktop": "0.16.6",
50
+ "@stonecrop/casl-middleware": "0.16.6",
51
+ "@stonecrop/graphql-client": "0.16.6",
52
+ "@stonecrop/code-editor": "0.16.6",
53
+ "@stonecrop/graphql-middleware": "0.16.6",
54
+ "@stonecrop/node-editor": "0.16.6",
55
+ "@stonecrop/schema": "0.16.6",
56
+ "@stonecrop/nuxt-grafserv": "0.16.6",
57
+ "@stonecrop/stonecrop": "0.16.6",
58
+ "@stonecrop/themes": "0.16.6",
59
+ "@stonecrop/atable": "0.16.6"
60
60
  },
61
61
  "devDependencies": {
62
62
  "@eslint/js": "^10.0.1",
@@ -103,7 +103,7 @@
103
103
  "prepublish": "rushx dev:prepare && nuxt-module-build build",
104
104
  "test": "vitest run",
105
105
  "test:coverage": "vitest run --coverage.enabled --coverage.provider=istanbul",
106
- "test:types": "vue-tsc --noEmit -p tsconfig.runtime.json && cd playground && vue-tsc --noEmit && cd ../fullstack && vue-tsc --noEmit",
106
+ "test:types": "vue-tsc --noEmit -p tsconfig.runtime.json && cd playground && vue-tsc --noEmit && cd ../fullstack && vue-tsc --noEmit && vue-tsc --noEmit -p server/tsconfig.json",
107
107
  "test:ui": "vitest --ui",
108
108
  "test:watch": "vitest watch"
109
109
  }
@@ -10,6 +10,7 @@ import consola from 'consola'
10
10
  import { addDependencies } from '../utils/package'
11
11
  import { updateNuxtConfig } from '../utils/config'
12
12
  import { loadTemplate } from '../utils/templates'
13
+ import { STONECROP_PACKAGES } from '../../module'
13
14
 
14
15
  export interface FrontendInstallerOptions {
15
16
  cwd: string
@@ -60,15 +61,11 @@ export async function installFrontend(options: FrontendInstallerOptions): Promis
60
61
  // This is required because the packages use vite-plugin-lib-inject-css which adds
61
62
  // CSS imports to the JavaScript bundles. Node.js ESM loader doesn't understand .css files,
62
63
  // so we need Nitro to bundle these packages (allowing Vite to process the CSS)
64
+ // Shares the module's list rather than restating it: the two previously disagreed
65
+ // (this one had `desktop` but not `themes`; the module's had the reverse), so a
66
+ // scaffolded app and the module inlined different sets.
63
67
  nitroConfig: {
64
- externalsInline: [
65
- '@stonecrop/aform',
66
- '@stonecrop/atable',
67
- '@stonecrop/stonecrop',
68
- '@stonecrop/node-editor',
69
- '@stonecrop/utilities',
70
- '@stonecrop/desktop',
71
- ],
68
+ externalsInline: [...STONECROP_PACKAGES],
72
69
  },
73
70
  css: ['@stonecrop/desktop/styles'],
74
71
  })
@@ -4,6 +4,7 @@
4
4
  "fields": [
5
5
  {
6
6
  "fieldname": "id",
7
+ "primaryKey": true,
7
8
  "label": "ID",
8
9
  "component": "ATextInput",
9
10
  "mode": "display"
@@ -4,6 +4,7 @@
4
4
  "fields": [
5
5
  {
6
6
  "fieldname": "id",
7
+ "primaryKey": true,
7
8
  "label": "ID",
8
9
  "component": "ATextInput",
9
10
  "mode": "display"
@@ -19,6 +19,7 @@
19
19
 
20
20
  import { constant, lambda, loadOne, object } from 'grafast'
21
21
  import { getMeta, getAllMeta, applyGuardedTransition } from '@stonecrop/graphql-middleware'
22
+ import { getPrimaryKeyField } from '@stonecrop/schema'
22
23
  import type { DoctypeMeta } from '@stonecrop/schema'
23
24
  import { projects, tasks, type Project, type Task } from './data'
24
25
 
@@ -57,10 +58,36 @@ export function formatDoctypeMeta(meta: DoctypeMeta) {
57
58
  // Record helpers — read/write the in-memory Maps by doctype
58
59
  // ============================================================
59
60
 
60
- function getRecord(doctype: string, id: string): Project | Task | null {
61
+ /**
62
+ * The field an incoming `stonecropRecord(id:)` argument is matched against.
63
+ *
64
+ * Part of the adapter contract — see test/adapter-conformance.test.ts, which runs the same
65
+ * expectations against every host. A doctype that declares a `primaryKey` is keyed by that
66
+ * field; the client resolves the same field via `@stonecrop/schema`'s `getRecordIdentity`,
67
+ * so an adapter that ignores the declaration looks records up by a key the client never sent.
68
+ *
69
+ * The `id` fallback covers doctypes that declare no `primaryKey`. Every doctype in this repo now
70
+ * declares one, enforced by test/doctype-fixtures.test.ts, so in-repo the fallback is inert.
71
+ * It stays for consumer doctypes that have not adopted the rule: the Postgres adapter refuses
72
+ * those outright (`data: null`), and this host stays permissive. The conformance suite records
73
+ * that difference rather than asserting one answer.
74
+ */
75
+ export function recordLookupField(meta: DoctypeMeta): string {
76
+ return getPrimaryKeyField(meta.fields)?.fieldname ?? 'id'
77
+ }
78
+
79
+ function getRecord(doctype: string, id: string, lookupField = 'id'): Project | Task | null {
61
80
  const d = doctype.toLowerCase()
62
- if (d === 'project') return projects.get(id) ?? null
63
- if (d === 'task') return tasks.get(id) ?? null
81
+ const store = d === 'project' ? projects : d === 'task' ? tasks : null
82
+ if (!store) return null
83
+ if (lookupField === 'id') return store.get(id) ?? null
84
+ // A natural key is not the Map key, so the store is scanned. Values are compared as
85
+ // strings because the `id` argument arrives from GraphQL as `String!`. Only primitives
86
+ // are usable keys — stringifying an object would compare "[object Object]".
87
+ for (const record of store.values()) {
88
+ const value: unknown = Reflect.get(record, lookupField)
89
+ if ((typeof value === 'string' || typeof value === 'number') && String(value) === id) return record
90
+ }
64
91
  return null
65
92
  }
66
93
 
@@ -117,10 +144,13 @@ export const resolvers = {
117
144
 
118
145
  stonecropRecord(_: unknown, { $doctype, $id, $options }: any) {
119
146
  return loadOne(object({ doctype: $doctype, id: $id, options: $options }), async (specs: readonly any[]) => {
120
- return specs.map(spec => ({
121
- data: getRecord(spec.doctype, spec.id),
122
- doctype: spec.doctype,
123
- }))
147
+ return specs.map(spec => {
148
+ const meta = getMeta(spec.doctype)
149
+ return {
150
+ data: meta ? getRecord(spec.doctype, spec.id, recordLookupField(meta)) : null,
151
+ doctype: spec.doctype,
152
+ }
153
+ })
124
154
  })
125
155
  },
126
156