nuxt-directus-sdk 6.0.0-beta.0 → 6.0.0-beta.1

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 CHANGED
@@ -2,22 +2,30 @@
2
2
 
3
3
  [![npm version][npm-version-src]][npm-version-href]
4
4
  [![npm downloads][npm-downloads-src]][npm-downloads-href]
5
+ [![CI][ci-src]][ci-href]
6
+ [![Install size][install-size-src]][install-size-href]
5
7
  [![License][license-src]][license-href]
6
8
  [![Nuxt][nuxt-src]][nuxt-href]
7
9
 
8
- > A Nuxt 4 Directus module that uses the Directus SDK to enhance your Nuxt application
10
+ > A Nuxt module for Directus with built-in authentication, realtime, file management, type generation, and visual editor support.
9
11
 
10
- - [✨  Release Notes](/CHANGELOG.md)
12
+ - [✨  Release Notes](https://github.com/rolleyio/nuxt-directus-sdk/releases)
11
13
  - [📚  Documentation](https://nuxt-directus-sdk.rolley.io)
12
14
 
13
15
  ## Features
14
16
 
15
17
  - 🔒  **Session-based authentication** with cross-domain support
16
- -  Authentication out of the box
17
- - 🚠  Type generation based on Directus collections
18
- - 🔥  Typesafe Client Websockets enabled
19
- - 🌉  Automatically configures Nuxt Image for directus
20
- - 🗂️  Directus Admin panel added to Devtools
18
+ -  **Realtime** via typed WebSocket subscriptions
19
+ - 📁  **File management** with `@nuxt/image` integration
20
+ - ✏️  **Visual editor** support for `@directus/visual-editing`
21
+ - 🧩  **Auto-generated types** from your Directus schema, plus a standalone CLI
22
+ - 📐  **Rules DSL** for defining and syncing permissions in code
23
+ - 🗂️  Directus admin panel embedded in Nuxt Devtools
24
+
25
+ ## Requirements
26
+
27
+ - **Nuxt 4.0+**
28
+ - **Directus v11.16.0+** (required by the bundled `@directus/visual-editing` v2 and `@directus/sdk` v21)
21
29
 
22
30
  ## Quick Setup
23
31
 
@@ -39,19 +47,12 @@ bun install --save-dev nuxt-directus-sdk
39
47
 
40
48
  2. Add `nuxt-directus-sdk` to the `modules` section of `nuxt.config.ts`
41
49
 
42
- ```js
50
+ ```ts
43
51
  export default defineNuxtConfig({
44
- modules: [
45
- 'nuxt-directus-sdk'
46
- ],
52
+ modules: ['nuxt-directus-sdk'],
47
53
  directus: {
48
- // Optional: customize authentication (defaults shown)
49
- auth: {
50
- autoRefresh: true,
51
- credentials: 'include', // Required for cross-domain
52
- realtimeAuthMode: 'public',
53
- }
54
- }
54
+ url: process.env.DIRECTUS_URL,
55
+ },
55
56
  })
56
57
  ```
57
58
 
@@ -59,47 +60,68 @@ export default defineNuxtConfig({
59
60
 
60
61
  ```dotenv
61
62
  DIRECTUS_URL=https://your-directus-url.com
62
- DIRECTUS_ADMIN_TOKEN=your_admin_token # Optional: for type generation
63
+ DIRECTUS_ADMIN_TOKEN=your_admin_token # Optional: required for type generation
63
64
  ```
64
65
 
65
- 4. **Configure your Directus backend** for cross-domain authentication (see [Authentication Guide](https://nuxt-directus-sdk.rolley.io/guide/authentication.html))
66
+ 4. **Configure your Directus backend** for cross-domain authentication (see the [Authentication Guide](https://nuxt-directus-sdk.rolley.io/guide/authentication.html))
66
67
 
67
68
  That's it! You can now use Directus within your Nuxt app ✨
68
69
 
69
- For cross-domain setups (e.g., `app.example.com` `api.example.com`), see the [Authentication Guide](https://nuxt-directus-sdk.rolley.io/guide/authentication.html).
70
+ For cross-domain setups (e.g. `app.example.com` and `api.example.com`), see the [Authentication Guide](https://nuxt-directus-sdk.rolley.io/guide/authentication.html).
71
+
72
+ ## CLI
73
+
74
+ The module ships with a CLI for type generation and permissions/rules sync that doesn't require a running Nuxt instance. Useful in CI, pre-commit hooks, or quick regeneration during development.
75
+
76
+ ```bash
77
+ # Generate TypeScript types from a Directus schema
78
+ npx nuxt-directus-sdk generate-types --prefix App -o types/directus.d.ts
79
+
80
+ # Pull permissions/rules to a JSON file
81
+ npx nuxt-directus-sdk rules:pull -o rules.json
82
+
83
+ # See all commands
84
+ npx nuxt-directus-sdk --help
85
+ ```
86
+
87
+ See the [CLI documentation](https://nuxt-directus-sdk.rolley.io/api/configuration/module#types) for flags and examples.
70
88
 
71
89
  ## Development
72
90
 
73
- > [!IMPORTANT] The playground uses [directus-template-cli](https://github.com/directus-labs/directus-template-cli?tab=readme-ov-file#applying-a-template) `cms` template.
91
+ > [!IMPORTANT] The playground uses the [directus-template-cli](https://github.com/directus-labs/directus-template-cli?tab=readme-ov-file#applying-a-template) `cms` template.
74
92
  > Apply the template with `npx directus-template-cli@latest apply` and follow the interactive prompts.
75
93
 
76
94
  ```bash
77
95
  # Install dependencies
78
- bun install
96
+ pnpm install
79
97
 
80
98
  # Add DIRECTUS_ADMIN_TOKEN to playground .env (don't forget to update your token)
81
99
  cp ./playground/.env.example ./playground/.env
82
100
 
83
101
  # Generate type stubs
84
- bun run dev:prepare
102
+ pnpm run dev:prepare
85
103
 
86
104
  # Develop with the playground
87
- bun run dev
105
+ pnpm run dev
88
106
 
89
107
  # Build the playground
90
- bun run dev:build
108
+ pnpm run dev:build
91
109
 
92
110
  # Run ESLint
93
- bun run lint
111
+ pnpm run lint
94
112
 
95
113
  # Run Vitest
96
- bun run test
97
- bun run test:watch
114
+ pnpm run test
115
+ pnpm run test:watch
98
116
 
99
- # Release new version
100
- bun run release
117
+ # Release new version (see RELEASING.md)
118
+ pnpm run release
101
119
  ```
102
120
 
121
+ ## Contributing
122
+
123
+ Contributions are welcome. Please target the `next` branch for new features and fixes; `main` is reserved for stable releases and hotfixes. See [RELEASING.md](./RELEASING.md) for the release process.
124
+
103
125
  <!-- Badges -->
104
126
  [npm-version-src]: https://img.shields.io/npm/v/nuxt-directus-sdk/latest.svg?style=flat&colorA=18181B&colorB=28CF8D
105
127
  [npm-version-href]: https://npmjs.com/package/nuxt-directus-sdk
@@ -107,6 +129,12 @@ bun run release
107
129
  [npm-downloads-src]: https://img.shields.io/npm/dm/nuxt-directus-sdk.svg?style=flat&colorA=18181B&colorB=28CF8D
108
130
  [npm-downloads-href]: https://npmjs.com/package/nuxt-directus-sdk
109
131
 
132
+ [ci-src]: https://github.com/rolleyio/nuxt-directus-sdk/actions/workflows/ci.yml/badge.svg?branch=main
133
+ [ci-href]: https://github.com/rolleyio/nuxt-directus-sdk/actions/workflows/ci.yml
134
+
135
+ [install-size-src]: https://packagephobia.com/badge?p=nuxt-directus-sdk
136
+ [install-size-href]: https://packagephobia.com/result?p=nuxt-directus-sdk
137
+
110
138
  [license-src]: https://img.shields.io/npm/l/nuxt-directus-sdk.svg?style=flat&colorA=18181B&colorB=28CF8D
111
139
  [license-href]: https://npmjs.com/package/nuxt-directus-sdk
112
140
 
@@ -1,9 +1,23 @@
1
1
  #!/usr/bin/env node
2
- import { existsSync, readFileSync, writeFileSync } from 'node:fs';
3
- import { resolve } from 'node:path';
2
+ import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs';
3
+ import { resolve, dirname } from 'node:path';
4
4
  import { parseArgs } from 'node:util';
5
5
  import { createDirectus, staticToken, rest } from '@directus/sdk';
6
6
  import { d as diffRemoteRules, e as formatDiff, c as compareRulesPayloads, f as fetchRemoteRules, k as loadRulesFromPayload, o as pushRules, g as formatPushResult, b as fetchRemoteRulesAsJson } from '../shared/nuxt-directus-sdk.1qEbZAZ_.mjs';
7
+ import { generateTypesFromDirectus } from '../../dist/runtime/types/generate.js';
8
+
9
+ function parseCsv(raw) {
10
+ if (!raw)
11
+ return [];
12
+ return raw.split(",").map((s) => s.trim()).filter(Boolean);
13
+ }
14
+ function resolveNegatableBoolean(positive, negative, fallback) {
15
+ if (negative)
16
+ return false;
17
+ if (positive !== void 0)
18
+ return positive;
19
+ return fallback;
20
+ }
7
21
 
8
22
  function loadEnv() {
9
23
  const envPath = resolve(process.cwd(), ".env");
@@ -28,12 +42,14 @@ function getConnectionConfig(urlFlag, tokenFlag, label) {
28
42
  const token = tokenFlag ?? process.env.DIRECTUS_ADMIN_TOKEN;
29
43
  if (!url) {
30
44
  console.error(`Error: ${label} URL is required`);
31
- console.error(`Provide --${label.toLowerCase()}-url or set DIRECTUS_URL in your .env file`);
45
+ const flagHint = label === "Source" ? "--url (or --source-url)" : `--${label.toLowerCase()}-url`;
46
+ console.error(`Provide ${flagHint} or set DIRECTUS_URL in your .env file`);
32
47
  process.exit(1);
33
48
  }
34
49
  if (!token) {
35
50
  console.error(`Error: ${label} token is required`);
36
- console.error(`Provide --${label.toLowerCase()}-token or set DIRECTUS_ADMIN_TOKEN in your .env file`);
51
+ const flagHint = label === "Source" ? "--token (or --source-token)" : `--${label.toLowerCase()}-token`;
52
+ console.error(`Provide ${flagHint} or set DIRECTUS_ADMIN_TOKEN in your .env file`);
37
53
  process.exit(1);
38
54
  }
39
55
  return { url, token };
@@ -43,7 +59,7 @@ function createClient(url, token) {
43
59
  }
44
60
  function printHelp() {
45
61
  console.log(`
46
- Directus Rules CLI
62
+ nuxt-directus-sdk CLI
47
63
 
48
64
  Usage:
49
65
  npx nuxt-directus-sdk <command> [options]
@@ -54,24 +70,40 @@ Commands:
54
70
  rules:diff <file> Compare local JSON file with remote Directus
55
71
  rules:diff-files <a> <b> Compare two local JSON files
56
72
  rules:diff-remote Compare two remote Directus instances
73
+ generate-types Generate TypeScript types from a Directus schema
57
74
 
58
75
  Options:
59
76
  -h, --help Show this help message
60
- -o, --output <file> Output file path (default: rules.json)
77
+ -o, --output <file> Output file path (default: stdout for generate-types, rules.json for rules:pull)
61
78
  --compact Output compact JSON (no pretty-print)
62
79
  --dry-run Show what would be changed without making changes (rules:push)
63
80
  --add-only Only add new items, don't modify or delete existing (rules:push)
64
81
  --skip-deletes Skip deleting items that exist remotely but not locally (rules:push)
82
+ --prefix <prefix> Prefix for custom collection type names (generate-types)
83
+ --include <names> Comma-separated collection names to include (generate-types).
84
+ When set, only these collections (plus any they reference
85
+ via --expand-references, default on) are emitted.
86
+ Takes precedence over --exclude if both are set.
87
+ --no-expand-references When --include is set, do NOT follow references to other
88
+ collections. Strict include mode \u2014 references to collections
89
+ not in the list collapse to \`string\`. (generate-types)
90
+ --exclude <names> Comma-separated collection names to exclude (generate-types).
91
+ References to excluded types are rewritten to \`string\`.
92
+ --verbose Show per-target warnings listing every field whose reference
93
+ was collapsed to \`string\` (generate-types).
94
+ --no-declare-global Emit types without the \`declare global\` wrapper (generate-types)
65
95
 
66
96
  Connection options (override DIRECTUS_URL / DIRECTUS_ADMIN_TOKEN):
97
+ --url <url> Directus URL (alias of --source-url)
98
+ --token <token> Admin token (alias of --source-token)
67
99
  --source-url <url> Source Directus URL
68
100
  --source-token <token> Source admin token
69
101
  --target-url <url> Target Directus URL (for rules:diff-remote)
70
102
  --target-token <token> Target admin token (for rules:diff-remote)
71
103
 
72
104
  Environment Variables:
73
- DIRECTUS_URL Default Directus URL (used if --source-url not provided)
74
- DIRECTUS_ADMIN_TOKEN Default admin token (used if --source-token not provided)
105
+ DIRECTUS_URL Default Directus URL (used if --url / --source-url not provided)
106
+ DIRECTUS_ADMIN_TOKEN Default admin token (used if --token / --source-token not provided)
75
107
 
76
108
  Examples:
77
109
  # Pull rules from Directus (uses env vars)
@@ -99,6 +131,27 @@ Examples:
99
131
  npx nuxt-directus-sdk rules:diff-remote \\
100
132
  --source-url https://staging.example.com --source-token staging-token \\
101
133
  --target-url https://production.example.com --target-token production-token
134
+
135
+ # Generate TypeScript types, pipe to a file
136
+ npx nuxt-directus-sdk generate-types > types/directus.d.ts
137
+
138
+ # Generate types with a prefix, write directly to a file
139
+ npx nuxt-directus-sdk generate-types --prefix App -o types/directus.d.ts
140
+
141
+ # Generate types from a specific instance
142
+ npx nuxt-directus-sdk generate-types --url https://my-directus.com --token my-token
143
+
144
+ # Exclude specific collections \u2014 references to them become \`string\`
145
+ npx nuxt-directus-sdk generate-types --exclude directus_activity,directus_revisions
146
+
147
+ # Include only specific collections (referenced collections auto-included)
148
+ npx nuxt-directus-sdk generate-types --include posts
149
+
150
+ # Strict include \u2014 only the listed collections, references collapse to \`string\`
151
+ npx nuxt-directus-sdk generate-types --include posts --no-expand-references
152
+
153
+ # Verbose \u2014 show every field whose reference was collapsed, grouped by target
154
+ npx nuxt-directus-sdk generate-types --exclude directus_users --verbose
102
155
  `);
103
156
  }
104
157
  function loadJsonFile(filePath) {
@@ -191,17 +244,67 @@ async function commandPush(localFile, connection, options) {
191
244
  process.exit(1);
192
245
  }
193
246
  }
247
+ async function commandGenerateTypes(connection, options) {
248
+ console.error(`Generating types from ${connection.url}...`);
249
+ if (options.include.length > 0) {
250
+ console.error(`Including collections: ${options.include.join(", ")}`);
251
+ }
252
+ if (options.exclude.length > 0) {
253
+ console.error(`Excluding collections: ${options.exclude.join(", ")}`);
254
+ }
255
+ const { typeString, logs } = await generateTypesFromDirectus(
256
+ connection.url,
257
+ connection.token,
258
+ options.prefix,
259
+ {
260
+ include: options.include,
261
+ exclude: options.exclude,
262
+ expandReferences: options.expandReferences,
263
+ verbose: options.verbose
264
+ }
265
+ );
266
+ for (const line of logs) {
267
+ console.error(line);
268
+ }
269
+ if (!typeString) {
270
+ console.error("Error: Type generation returned empty output.");
271
+ process.exit(1);
272
+ }
273
+ const output = options.declareGlobal ? typeString : typeString.replace(/^declare global \{\n\n/, "").replace(/\n\}\n\nexport \{\};?\n?$/, "\n");
274
+ if (options.output) {
275
+ const outputPath = resolve(process.cwd(), options.output);
276
+ mkdirSync(dirname(outputPath), { recursive: true });
277
+ writeFileSync(outputPath, output, "utf-8");
278
+ console.error(`Types written to ${outputPath}`);
279
+ } else {
280
+ process.stdout.write(output);
281
+ }
282
+ }
194
283
  async function main() {
195
284
  loadEnv();
196
285
  const { values, positionals } = parseArgs({
197
286
  allowPositionals: true,
198
287
  options: {
199
288
  "help": { type: "boolean", short: "h" },
200
- "output": { type: "string", short: "o", default: "rules.json" },
289
+ // `output` has no default here each command applies its own fallback
290
+ // (rules:pull defaults to rules.json, generate-types defaults to stdout).
291
+ "output": { type: "string", short: "o" },
201
292
  "compact": { type: "boolean", default: false },
202
293
  "dry-run": { type: "boolean", default: false },
203
294
  "add-only": { type: "boolean", default: false },
204
295
  "skip-deletes": { type: "boolean", default: false },
296
+ "prefix": { type: "string", default: "" },
297
+ "include": { type: "string" },
298
+ "exclude": { type: "string" },
299
+ // Node's parseArgs doesn't support `--no-X` syntax natively, so we
300
+ // register both forms. Negation wins if both are passed.
301
+ "expand-references": { type: "boolean", default: true },
302
+ "no-expand-references": { type: "boolean" },
303
+ "verbose": { type: "boolean", default: false },
304
+ "declare-global": { type: "boolean", default: true },
305
+ "no-declare-global": { type: "boolean" },
306
+ "url": { type: "string" },
307
+ "token": { type: "string" },
205
308
  "source-url": { type: "string" },
206
309
  "source-token": { type: "string" },
207
310
  "target-url": { type: "string" },
@@ -217,12 +320,12 @@ async function main() {
217
320
  switch (command) {
218
321
  case "rules:pull": {
219
322
  const connection = getConnectionConfig(
220
- values["source-url"],
221
- values["source-token"],
323
+ values["source-url"] ?? values.url,
324
+ values["source-token"] ?? values.token,
222
325
  "Source"
223
326
  );
224
327
  await commandPull({
225
- output: values.output,
328
+ output: values.output ?? "rules.json",
226
329
  compact: values.compact
227
330
  }, connection);
228
331
  break;
@@ -234,8 +337,8 @@ async function main() {
234
337
  process.exit(1);
235
338
  }
236
339
  const connection = getConnectionConfig(
237
- values["source-url"],
238
- values["source-token"],
340
+ values["source-url"] ?? values.url,
341
+ values["source-token"] ?? values.token,
239
342
  "Source"
240
343
  );
241
344
  await commandPush(positionals[1], connection, {
@@ -252,8 +355,8 @@ async function main() {
252
355
  process.exit(1);
253
356
  }
254
357
  const connection = getConnectionConfig(
255
- values["source-url"],
256
- values["source-token"],
358
+ values["source-url"] ?? values.url,
359
+ values["source-token"] ?? values.token,
257
360
  "Source"
258
361
  );
259
362
  await commandDiff(positionals[1], connection);
@@ -269,8 +372,8 @@ async function main() {
269
372
  break;
270
373
  case "rules:diff-remote": {
271
374
  const source = getConnectionConfig(
272
- values["source-url"],
273
- values["source-token"],
375
+ values["source-url"] ?? values.url,
376
+ values["source-token"] ?? values.token,
274
377
  "Source"
275
378
  );
276
379
  const target = getConnectionConfig(
@@ -281,6 +384,23 @@ async function main() {
281
384
  await commandDiffRemote(source, target);
282
385
  break;
283
386
  }
387
+ case "generate-types": {
388
+ const connection = getConnectionConfig(
389
+ values.url ?? values["source-url"],
390
+ values.token ?? values["source-token"],
391
+ "Source"
392
+ );
393
+ await commandGenerateTypes(connection, {
394
+ prefix: values.prefix ?? "",
395
+ output: values.output,
396
+ declareGlobal: resolveNegatableBoolean(values["declare-global"], values["no-declare-global"], true),
397
+ include: parseCsv(values.include),
398
+ exclude: parseCsv(values.exclude),
399
+ expandReferences: resolveNegatableBoolean(values["expand-references"], values["no-expand-references"], true),
400
+ verbose: values.verbose
401
+ });
402
+ break;
403
+ }
284
404
  default:
285
405
  console.error(`Unknown command: ${command}`);
286
406
  printHelp();
package/dist/module.d.mts CHANGED
@@ -153,6 +153,41 @@ interface ModuleOptions {
153
153
  * @default ''
154
154
  */
155
155
  prefix?: string;
156
+ /**
157
+ * Collection names to include in the generated types. When non-empty,
158
+ * only these collections (plus any they reference — see
159
+ * `expandReferences`) are emitted. References to collections not in
160
+ * the resolved set collapse to `string` (M2O) or `string[]` (O2M).
161
+ *
162
+ * Takes precedence over `exclude` if both are set.
163
+ * @type string[]
164
+ * @default []
165
+ */
166
+ include?: string[];
167
+ /**
168
+ * When `include` is set, also pull in any collections referenced by
169
+ * the included collections (transitively). Follows M2O, O2M, and M2A.
170
+ * No-op when `include` is empty.
171
+ * @type boolean
172
+ * @default true
173
+ */
174
+ expandReferences?: boolean;
175
+ /**
176
+ * Collection names to exclude from generated types.
177
+ * References to excluded collections are rewritten to `string` (M2O) or
178
+ * `string[]` (O2M) so the generated types stay resolvable.
179
+ * @type string[]
180
+ * @default []
181
+ */
182
+ exclude?: string[];
183
+ /**
184
+ * When true, emit per-target warnings listing every field whose
185
+ * reference was collapsed to `string`/`string[]`. Field lists are
186
+ * capped at 5 per collection.
187
+ * @type boolean
188
+ * @default false
189
+ */
190
+ verbose?: boolean;
156
191
  };
157
192
  }
158
193
  declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
package/dist/module.d.ts CHANGED
@@ -153,6 +153,41 @@ interface ModuleOptions {
153
153
  * @default ''
154
154
  */
155
155
  prefix?: string;
156
+ /**
157
+ * Collection names to include in the generated types. When non-empty,
158
+ * only these collections (plus any they reference — see
159
+ * `expandReferences`) are emitted. References to collections not in
160
+ * the resolved set collapse to `string` (M2O) or `string[]` (O2M).
161
+ *
162
+ * Takes precedence over `exclude` if both are set.
163
+ * @type string[]
164
+ * @default []
165
+ */
166
+ include?: string[];
167
+ /**
168
+ * When `include` is set, also pull in any collections referenced by
169
+ * the included collections (transitively). Follows M2O, O2M, and M2A.
170
+ * No-op when `include` is empty.
171
+ * @type boolean
172
+ * @default true
173
+ */
174
+ expandReferences?: boolean;
175
+ /**
176
+ * Collection names to exclude from generated types.
177
+ * References to excluded collections are rewritten to `string` (M2O) or
178
+ * `string[]` (O2M) so the generated types stay resolvable.
179
+ * @type string[]
180
+ * @default []
181
+ */
182
+ exclude?: string[];
183
+ /**
184
+ * When true, emit per-target warnings listing every field whose
185
+ * reference was collapsed to `string`/`string[]`. Field lists are
186
+ * capped at 5 per collection.
187
+ * @type boolean
188
+ * @default false
189
+ */
190
+ verbose?: boolean;
156
191
  };
157
192
  }
158
193
  declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
package/dist/module.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-directus-sdk",
3
- "version": "6.0.0-beta.0",
3
+ "version": "6.0.0-beta.1",
4
4
  "configKey": "directus",
5
5
  "compatibility": {
6
6
  "nuxt": "^4.0.0"
package/dist/module.mjs CHANGED
@@ -6,7 +6,7 @@ import { generateTypesFromDirectus } from '../dist/runtime/types/index.js';
6
6
  import { useUrl } from '../dist/runtime/utils/index.js';
7
7
 
8
8
  const name = "nuxt-directus-sdk";
9
- const version = "6.0.0-beta.0";
9
+ const version = "6.0.0-beta.1";
10
10
 
11
11
  const configKey = "directus";
12
12
  const logger = useLogger("nuxt-directus-sdk");
@@ -248,7 +248,7 @@ const module$1 = defineNuxtModule({
248
248
  imports: [
249
249
  "getDirectusSessionToken",
250
250
  "useAdminDirectus",
251
- "useServerDirectus",
251
+ "useSessionDirectus",
252
252
  "useDirectusUrl",
253
253
  "useTokenDirectus"
254
254
  ]
@@ -270,13 +270,22 @@ const module$1 = defineNuxtModule({
270
270
  }
271
271
  const typesEnabled = typeof options.types === "boolean" && options.types || options.types && options.types.enabled === true;
272
272
  const typesPrefix = typeof options.types === "object" ? options.types.prefix ?? "" : "";
273
+ const typesInclude = typeof options.types === "object" ? options.types.include ?? [] : [];
274
+ const typesExpandReferences = typeof options.types === "object" ? options.types.expandReferences ?? true : true;
275
+ const typesExclude = typeof options.types === "object" ? options.types.exclude ?? [] : [];
276
+ const typesVerbose = typeof options.types === "object" ? options.types.verbose ?? false : false;
273
277
  if (typesEnabled) {
274
278
  loggerMessage.push("\u{1F4CB} Directus Type Generator Enabled");
275
279
  if (!options.adminToken) {
276
280
  loggerMessage.push(` ${colors.bgRedBright(`${colors.red("\u2691 ERROR:")} Unable to generate Types`)}`, ` Fix: Set adminToken in config or DIRECTUS_ADMIN_TOKEN in .env`);
277
281
  } else {
278
282
  try {
279
- const { typeString, logs } = await generateTypesFromDirectus(directusUrl, options.adminToken, typesPrefix);
283
+ const { typeString, logs } = await generateTypesFromDirectus(directusUrl, options.adminToken, typesPrefix, {
284
+ include: typesInclude,
285
+ expandReferences: typesExpandReferences,
286
+ exclude: typesExclude,
287
+ verbose: typesVerbose
288
+ });
280
289
  loggerMessage.push(...logs);
281
290
  addTypeTemplate({
282
291
  filename: `types/${configKey}.d.ts`,
@@ -13,7 +13,7 @@ export async function uploadDirectusFiles(files, query) {
13
13
  formData.set(key, value);
14
14
  });
15
15
  }
16
- formData.set("file", file);
16
+ formData.append("file", file);
17
17
  });
18
18
  return directus.request(uploadFiles(formData, query));
19
19
  }
@@ -2,5 +2,5 @@ import type { H3Event } from 'h3';
2
2
  export declare function getDirectusSessionToken(event: H3Event): string | undefined;
3
3
  export declare function useDirectusUrl(path?: string): string;
4
4
  export declare function useTokenDirectus(token?: string): import("@directus/sdk").DirectusClient<DirectusSchema> & import("@directus/sdk").AuthenticationClient<DirectusSchema> & import("@directus/sdk").RestClient<DirectusSchema>;
5
- export declare function useServerDirectus(event: H3Event): import("@directus/sdk").DirectusClient<DirectusSchema> & import("@directus/sdk").AuthenticationClient<DirectusSchema> & import("@directus/sdk").RestClient<DirectusSchema>;
5
+ export declare function useSessionDirectus(event: H3Event): import("@directus/sdk").DirectusClient<DirectusSchema> & import("@directus/sdk").AuthenticationClient<DirectusSchema> & import("@directus/sdk").RestClient<DirectusSchema>;
6
6
  export declare function useAdminDirectus(): import("@directus/sdk").DirectusClient<DirectusSchema> & import("@directus/sdk").AuthenticationClient<DirectusSchema> & import("@directus/sdk").RestClient<DirectusSchema>;
@@ -18,7 +18,7 @@ export function useTokenDirectus(token) {
18
18
  directus.setToken(token);
19
19
  return directus;
20
20
  }
21
- export function useServerDirectus(event) {
21
+ export function useSessionDirectus(event) {
22
22
  return useTokenDirectus(getDirectusSessionToken(event));
23
23
  }
24
24
  export function useAdminDirectus() {
@@ -1,6 +1,11 @@
1
1
  import type { SnapshotCollection, SnapshotField, SnapshotRelation } from '@directus/types';
2
2
  import type { TypegenExtension } from './extensions/index.js';
3
3
  export declare const FALLBACK_TYPE_STRING = "declare global {\n\ninterface DirectusFile {\n\tid: string;\n}\ninterface DirectusUser {\n\tid: string;\n}\ninterface DirectusSchema { }\n}\n\nexport {};";
4
+ interface RewriteRecord {
5
+ fromCollection: string;
6
+ fromField: string;
7
+ target: string;
8
+ }
4
9
  /**
5
10
  * Fetches collections, fields, and relations from a live Directus instance and
6
11
  * generates a TypeScript declaration string.
@@ -10,7 +15,39 @@ export declare const FALLBACK_TYPE_STRING = "declare global {\n\ninterface Direc
10
15
  * @param prefix - Prefix used when generating interface names.
11
16
  * @returns The generated TypeScript string and an array of log messages.
12
17
  */
13
- export declare function generateTypesFromDirectus(url: string, token: string, prefix: string): Promise<{
18
+ export interface GenerateTypesOptions {
19
+ /**
20
+ * Collection names to include. When non-empty, only these collections
21
+ * (plus any collections they reference — see `expandReferences`) are
22
+ * emitted. References to collections not in the resolved set collapse
23
+ * to `string` / `string[]`.
24
+ */
25
+ include?: string[];
26
+ /**
27
+ * When `include` is set, also pull in any collections referenced by
28
+ * the included collections (transitively). Follows M2O, O2M, M2A, and
29
+ * translations relations. No-op when `include` is empty.
30
+ * @default true
31
+ */
32
+ expandReferences?: boolean;
33
+ /**
34
+ * Collection names to exclude from the generated types. References to
35
+ * excluded collections are rewritten to `string` (M2O), `string[]` (O2M),
36
+ * or a filtered union (M2A).
37
+ *
38
+ * When both `include` and `exclude` are set, `include` wins and `exclude`
39
+ * is ignored with a warning.
40
+ */
41
+ exclude?: string[];
42
+ /**
43
+ * When true, emit per-reference warnings for every field whose target
44
+ * collection was collapsed to `string` / `string[]`. Grouped by target
45
+ * collection to keep the output readable.
46
+ * @default false
47
+ */
48
+ verbose?: boolean;
49
+ }
50
+ export declare function generateTypesFromDirectus(url: string, token: string, prefix: string, options?: GenerateTypesOptions): Promise<{
14
51
  typeString: string;
15
52
  logs: string[];
16
53
  }>;
@@ -31,4 +68,12 @@ export declare function generateTypesFromDirectus(url: string, token: string, pr
31
68
  * @param extensions - Optional type generation extensions.
32
69
  * @returns A TypeScript declaration file as a string.
33
70
  */
34
- export declare function transformSnapshotToTypeString(collections: SnapshotCollection[], fields: SnapshotField[], relations: SnapshotRelation[], prefix: string, extensions?: TypegenExtension[]): string;
71
+ export declare function transformSnapshotToTypeString(collections: SnapshotCollection[], fields: SnapshotField[], relations: SnapshotRelation[], prefix: string, extensions?: TypegenExtension[], filter?: {
72
+ include?: string[];
73
+ exclude?: string[];
74
+ }): {
75
+ typeString: string;
76
+ rewrites: RewriteRecord[];
77
+ emittedCount: number;
78
+ };
79
+ export {};
@@ -1,7 +1,7 @@
1
1
  import { createDirectus, isDirectusError, readCollections, readFields, readRelations, rest, staticToken } from "@directus/sdk";
2
2
  import { typegenExtensions } from "./extensions/index.js";
3
3
  export const FALLBACK_TYPE_STRING = "declare global {\n\ninterface DirectusFile {\n id: string;\n}\ninterface DirectusUser {\n id: string;\n}\ninterface DirectusSchema { }\n}\n\nexport {};";
4
- export async function generateTypesFromDirectus(url, token, prefix) {
4
+ export async function generateTypesFromDirectus(url, token, prefix, options = {}) {
5
5
  const logs = [];
6
6
  const client = createDirectus(url).with(rest()).with(staticToken(token));
7
7
  let result = null;
@@ -28,13 +28,69 @@ export async function generateTypesFromDirectus(url, token, prefix) {
28
28
  }
29
29
  return { typeString: FALLBACK_TYPE_STRING, logs };
30
30
  }
31
- const typeString = transformSnapshotToTypeString(...result, prefix);
31
+ const resolvedOptions = {
32
+ include: options.include ?? [],
33
+ exclude: options.exclude ?? [],
34
+ verbose: options.verbose ?? false
35
+ };
36
+ if (resolvedOptions.include.length > 0 && resolvedOptions.exclude.length > 0) {
37
+ logs.push(" - Warning: both include and exclude were set; exclude is ignored because include takes precedence");
38
+ resolvedOptions.exclude = [];
39
+ }
40
+ const expandReferences = options.expandReferences ?? true;
41
+ if (expandReferences && resolvedOptions.include.length > 0) {
42
+ const originalSize = resolvedOptions.include.length;
43
+ const expanded = expandIncludeViaReferences(resolvedOptions.include, result[2]);
44
+ if (expanded.size > originalSize) {
45
+ resolvedOptions.include = Array.from(expanded);
46
+ const added = expanded.size - originalSize;
47
+ logs.push(` - Expanded include from ${originalSize} \u2192 ${expanded.size} collection${expanded.size === 1 ? "" : "s"} (+${added} via references)`);
48
+ }
49
+ }
50
+ const { typeString, rewrites, emittedCount } = transformSnapshotToTypeString(...result, prefix, void 0, resolvedOptions);
51
+ const filterActive = resolvedOptions.include.length > 0 || resolvedOptions.exclude.length > 0;
52
+ if (filterActive) {
53
+ const fetchedCount = result[0].length;
54
+ const filteredOut = fetchedCount - emittedCount;
55
+ logs.push(` - Emitting ${emittedCount} collection${emittedCount === 1 ? "" : "s"} (${filteredOut} filtered out)`);
56
+ }
57
+ if (rewrites.length > 0) {
58
+ const totalFields = rewrites.length;
59
+ const byTarget = /* @__PURE__ */ new Map();
60
+ for (const rec of rewrites) {
61
+ const list = byTarget.get(rec.target) ?? [];
62
+ list.push(rec);
63
+ byTarget.set(rec.target, list);
64
+ }
65
+ const reason = resolvedOptions.include.length > 0 ? "not in include list" : "excluded";
66
+ logs.push(` - ${totalFields} field reference${totalFields === 1 ? "" : "s"} across ${byTarget.size} target${byTarget.size === 1 ? "" : "s"} collapsed to string (${reason})`);
67
+ if (resolvedOptions.verbose) {
68
+ for (const [target, recs] of byTarget) {
69
+ const collectionCount = new Set(recs.map((r) => r.fromCollection)).size;
70
+ logs.push(` \xB7 ${target} (${reason}) \u2014 referenced by ${recs.length} field${recs.length === 1 ? "" : "s"} across ${collectionCount} collection${collectionCount === 1 ? "" : "s"}`);
71
+ const preview = recs.slice(0, 5).map((r) => `${r.fromCollection}.${r.fromField}`);
72
+ const suffix = recs.length > 5 ? `, \u2026and ${recs.length - 5} more` : "";
73
+ logs.push(` ${preview.join(", ")}${suffix}`);
74
+ }
75
+ }
76
+ }
32
77
  return { typeString, logs };
33
78
  }
34
- export function transformSnapshotToTypeString(collections, fields, relations, prefix, extensions = typegenExtensions) {
79
+ export function transformSnapshotToTypeString(collections, fields, relations, prefix, extensions = typegenExtensions, filter = {}) {
80
+ const includeList = filter.include ?? [];
81
+ const excludeList = filter.exclude ?? [];
82
+ const useInclude = includeList.length > 0;
83
+ const includeSet = new Set(includeList);
84
+ const excludeSet = new Set(excludeList);
85
+ const isCollectionAllowed = (name) => {
86
+ if (useInclude)
87
+ return includeSet.has(name);
88
+ return !excludeSet.has(name);
89
+ };
90
+ const rewrites = [];
35
91
  const relationMap = buildRelationMapFromSnapshot(relations);
36
92
  const collectionsWithDatabaseTables = collections.filter(
37
- (c) => c.schema !== null
93
+ (c) => c.schema !== null && isCollectionAllowed(c.collection)
38
94
  );
39
95
  const customCollections = collectionsWithDatabaseTables.filter(
40
96
  (c) => !collectionIsDirectusSystem(c.collection)
@@ -48,7 +104,7 @@ export function transformSnapshotToTypeString(collections, fields, relations, pr
48
104
  const impliedSystemCollections = [
49
105
  ...new Set(
50
106
  relations.flatMap((r) => [r.collection, r.related_collection ?? ""]).filter(
51
- (name) => collectionIsDirectusSystem(name) && !systemCollectionNamesAlreadyPresent.has(name)
107
+ (name) => collectionIsDirectusSystem(name) && !systemCollectionNamesAlreadyPresent.has(name) && isCollectionAllowed(name)
52
108
  )
53
109
  )
54
110
  ].map((name) => ({ collection: name, schema: { name }, meta: null }));
@@ -56,6 +112,8 @@ export function transformSnapshotToTypeString(collections, fields, relations, pr
56
112
  ...collectionsWithDatabaseTables,
57
113
  ...impliedSystemCollections
58
114
  ];
115
+ const allCollectionNamesInSchema = new Set(allCollectionsForSchema.map((c) => c.collection));
116
+ const isMissing = (name) => !allCollectionNamesInSchema.has(name);
59
117
  const singletonCollectionNames = new Set(
60
118
  allCollectionsForSchema.filter((c) => c.meta?.singleton === true).map((c) => c.collection)
61
119
  );
@@ -63,7 +121,7 @@ export function transformSnapshotToTypeString(collections, fields, relations, pr
63
121
  ...customCollections,
64
122
  ...systemCollectionsFromSnapshot
65
123
  ].map(
66
- (collection) => generateInterfaceForCollection(collection, fields, relationMap, prefix, extensions, singletonCollectionNames)
124
+ (collection) => generateInterfaceForCollection(collection, fields, relationMap, prefix, extensions, singletonCollectionNames, isMissing, rewrites)
67
125
  );
68
126
  const seenExtensionOutputs = /* @__PURE__ */ new Set();
69
127
  const uniqueExtensionOutputs = generatedCollections.flatMap((g) => g.extensionOutputs).filter((output) => {
@@ -82,7 +140,7 @@ export function transformSnapshotToTypeString(collections, fields, relations, pr
82
140
  directusSchemaBlock,
83
141
  enumBlock
84
142
  ];
85
- return [
143
+ const typeString = [
86
144
  "declare global {",
87
145
  "",
88
146
  bodyParts.join("\n\n"),
@@ -90,6 +148,7 @@ export function transformSnapshotToTypeString(collections, fields, relations, pr
90
148
  "",
91
149
  "export {};"
92
150
  ].join("\n");
151
+ return { typeString, rewrites, emittedCount: collectionsWithDatabaseTables.length };
93
152
  }
94
153
  function collectionIsDirectusSystem(collectionName) {
95
154
  return collectionName.startsWith("directus_");
@@ -111,6 +170,35 @@ function resolveExtensionForField(field, prefix, extensions) {
111
170
  output: match.output(prefix)
112
171
  };
113
172
  }
173
+ function expandIncludeViaReferences(seeds, relations) {
174
+ const relationMap = buildRelationMapFromSnapshot(relations);
175
+ const visited = /* @__PURE__ */ new Set();
176
+ const queue = [...seeds];
177
+ while (queue.length > 0) {
178
+ const current = queue.shift();
179
+ if (visited.has(current))
180
+ continue;
181
+ visited.add(current);
182
+ const rels = relationMap.get(current);
183
+ if (!rels)
184
+ continue;
185
+ for (const { relatedCollection } of rels.m2o.values()) {
186
+ if (!visited.has(relatedCollection))
187
+ queue.push(relatedCollection);
188
+ }
189
+ for (const { relatedCollection } of rels.o2m.values()) {
190
+ if (!visited.has(relatedCollection))
191
+ queue.push(relatedCollection);
192
+ }
193
+ for (const allowed of rels.m2a.values()) {
194
+ for (const name of allowed) {
195
+ if (!visited.has(name))
196
+ queue.push(name);
197
+ }
198
+ }
199
+ }
200
+ return visited;
201
+ }
114
202
  function buildRelationMapFromSnapshot(relations) {
115
203
  const relationMap = /* @__PURE__ */ new Map();
116
204
  const getOrCreateCollectionRelations = (collection) => {
@@ -135,18 +223,33 @@ function buildRelationMapFromSnapshot(relations) {
135
223
  }
136
224
  return relationMap;
137
225
  }
138
- function resolveFieldTypeString(snapshotField, collectionRelations, prefix, extensions, singletons = /* @__PURE__ */ new Set()) {
226
+ function resolveFieldTypeString(snapshotField, collectionRelations, prefix, extensions, singletons = /* @__PURE__ */ new Set(), isMissing = () => false, rewrites = []) {
139
227
  if (collectionRelations?.m2a.has(snapshotField.field)) {
140
228
  const allowedCollections = collectionRelations.m2a.get(snapshotField.field);
141
- const unionTypes = allowedCollections.map((c) => collectionNameToInterfaceName(c, prefix, singletons)).join(" | ");
229
+ const included = allowedCollections.filter((c) => !isMissing(c));
230
+ for (const missing of allowedCollections.filter((c) => isMissing(c))) {
231
+ rewrites.push({ fromCollection: snapshotField.collection, fromField: snapshotField.field, target: missing });
232
+ }
233
+ if (included.length === 0) {
234
+ return { tsType: "string", extensionOutput: null };
235
+ }
236
+ const unionTypes = included.map((c) => collectionNameToInterfaceName(c, prefix, singletons)).join(" | ");
142
237
  return { tsType: `${unionTypes} | string`, extensionOutput: null };
143
238
  }
144
239
  if (collectionRelations?.m2o.has(snapshotField.field)) {
145
240
  const related = collectionRelations.m2o.get(snapshotField.field);
241
+ if (isMissing(related.relatedCollection)) {
242
+ rewrites.push({ fromCollection: snapshotField.collection, fromField: snapshotField.field, target: related.relatedCollection });
243
+ return { tsType: "string", extensionOutput: null };
244
+ }
146
245
  return { tsType: `${collectionNameToInterfaceName(related.relatedCollection, prefix, singletons)} | string`, extensionOutput: null };
147
246
  }
148
247
  if (collectionRelations?.o2m.has(snapshotField.field)) {
149
248
  const related = collectionRelations.o2m.get(snapshotField.field);
249
+ if (isMissing(related.relatedCollection)) {
250
+ rewrites.push({ fromCollection: snapshotField.collection, fromField: snapshotField.field, target: related.relatedCollection });
251
+ return { tsType: "string[]", extensionOutput: null };
252
+ }
150
253
  return { tsType: `${collectionNameToInterfaceName(related.relatedCollection, prefix, singletons)}[] | string[]`, extensionOutput: null };
151
254
  }
152
255
  const extensionMatch = resolveExtensionForField(snapshotField, prefix, extensions);
@@ -160,13 +263,13 @@ function fieldIsUiOnlyAlias(field) {
160
263
  const special = field.meta?.special ?? [];
161
264
  return field.type === "alias" && special.some((s) => ALIAS_SPECIAL_TYPES.has(s)) && !special.includes("o2m") && !special.includes("m2o") && !special.includes("m2a") && !special.includes("files") && !special.includes("file");
162
265
  }
163
- function buildInterfaceField(snapshotField, collectionRelations, prefix, extensions, singletons = /* @__PURE__ */ new Set()) {
266
+ function buildInterfaceField(snapshotField, collectionRelations, prefix, extensions, singletons = /* @__PURE__ */ new Set(), isMissing = () => false, rewrites = []) {
164
267
  if (fieldIsUiOnlyAlias(snapshotField))
165
268
  return null;
166
269
  const isPrimaryKey = snapshotField.schema?.is_primary_key === true;
167
270
  const isRequired = snapshotField.meta?.required === true;
168
271
  const isNullable = snapshotField.schema?.is_nullable !== false;
169
- const { tsType, extensionOutput } = resolveFieldTypeString(snapshotField, collectionRelations, prefix, extensions, singletons);
272
+ const { tsType, extensionOutput } = resolveFieldTypeString(snapshotField, collectionRelations, prefix, extensions, singletons, isMissing, rewrites);
170
273
  const shouldAppendNull = isNullable && !isRequired && !isPrimaryKey;
171
274
  const finalType = shouldAppendNull ? `${tsType} | null` : tsType;
172
275
  return {
@@ -180,11 +283,11 @@ function buildInterfaceField(snapshotField, collectionRelations, prefix, extensi
180
283
  extensionOutput
181
284
  };
182
285
  }
183
- function generateInterfaceForCollection(collection, allFields, relationMap, prefix, extensions, singletons = /* @__PURE__ */ new Set()) {
286
+ function generateInterfaceForCollection(collection, allFields, relationMap, prefix, extensions, singletons = /* @__PURE__ */ new Set(), isMissing = () => false, rewrites = []) {
184
287
  const collectionName = collection.collection;
185
288
  const interfaceName = collectionNameToInterfaceName(collectionName, prefix, singletons);
186
289
  const collectionRelations = relationMap.get(collectionName);
187
- const builtFields = allFields.filter((f) => f.collection === collectionName).map((f) => buildInterfaceField(f, collectionRelations, prefix, extensions, singletons)).filter((f) => f !== null).sort((a, b) => a.interfaceField.sortOrder - b.interfaceField.sortOrder);
290
+ const builtFields = allFields.filter((f) => f.collection === collectionName).map((f) => buildInterfaceField(f, collectionRelations, prefix, extensions, singletons, isMissing, rewrites)).filter((f) => f !== null).sort((a, b) => a.interfaceField.sortOrder - b.interfaceField.sortOrder);
188
291
  const extensionOutputs = builtFields.map((f) => f.extensionOutput).filter((o) => o !== null);
189
292
  const fieldLines = builtFields.map(({ interfaceField }) => {
190
293
  const jsDoc = generateJSDocComment(interfaceField.snapshotField);
package/package.json CHANGED
@@ -1,15 +1,31 @@
1
1
  {
2
2
  "name": "nuxt-directus-sdk",
3
3
  "type": "module",
4
- "version": "6.0.0-beta.0",
4
+ "version": "6.0.0-beta.1",
5
5
  "packageManager": "pnpm@10.32.1",
6
- "description": "A Directus nuxt module that uses the Directus SDK",
6
+ "description": "A Nuxt module for Directus with built-in authentication, realtime, file management, type generation, and visual editor support.",
7
7
  "author": "Matthew Rollinson <matt@rolley.io>",
8
8
  "license": "MIT",
9
+ "homepage": "https://nuxt-directus-sdk.rolley.io",
9
10
  "repository": {
10
11
  "type": "git",
11
12
  "url": "https://github.com/rolleyio/nuxt-directus-sdk"
12
13
  },
14
+ "bugs": {
15
+ "url": "https://github.com/rolleyio/nuxt-directus-sdk/issues"
16
+ },
17
+ "keywords": [
18
+ "nuxt",
19
+ "nuxt-module",
20
+ "directus",
21
+ "cms",
22
+ "headless-cms",
23
+ "sdk",
24
+ "ssr",
25
+ "realtime",
26
+ "authentication",
27
+ "visual-editor"
28
+ ],
13
29
  "exports": {
14
30
  ".": {
15
31
  "types": "./dist/module.d.mts",
@@ -1,22 +0,0 @@
1
- import type { PrimaryKey } from '@directus/types';
2
- type __VLS_Props = {
3
- /** The parent collection that contains the repeater field */
4
- collection: string;
5
- /** The parent item ID */
6
- item: PrimaryKey;
7
- /** The field name of the repeater on the parent (e.g., 'blocks') */
8
- field: string;
9
- };
10
- declare var __VLS_1: {};
11
- type __VLS_Slots = {} & {
12
- default?: (props: typeof __VLS_1) => any;
13
- };
14
- declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
15
- declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
16
- declare const _default: typeof __VLS_export;
17
- export default _default;
18
- type __VLS_WithSlots<T, S> = T & {
19
- new (): {
20
- $slots: S;
21
- };
22
- };
@@ -1,64 +0,0 @@
1
- <script setup>
2
- import { computed, useRuntimeConfig } from "#imports";
3
- import { useDirectusOriginUrl, useDirectusVisualEditor } from "../composables/directus";
4
- const props = defineProps({
5
- collection: { type: String, required: true },
6
- item: { type: [String, Number], required: true },
7
- field: { type: String, required: true }
8
- });
9
- const config = useRuntimeConfig();
10
- const directusVisualEditing = useDirectusVisualEditor();
11
- const showButton = computed(() => config.public.directus.visualEditor && directusVisualEditing.value);
12
- function triggerAdd() {
13
- const directusUrl = useDirectusOriginUrl();
14
- const editConfig = {
15
- collection: props.collection,
16
- item: props.item,
17
- fields: [props.field],
18
- mode: "drawer"
19
- };
20
- try {
21
- window.parent.postMessage({
22
- action: "edit",
23
- data: {
24
- key: crypto.randomUUID(),
25
- editConfig,
26
- rect: { top: 0, left: 0, width: 0, height: 0 }
27
- }
28
- }, directusUrl);
29
- } catch (error) {
30
- console.error("[DirectusAddButton] Error triggering add:", error);
31
- }
32
- }
33
- </script>
34
-
35
- <template>
36
- <button
37
- v-if="showButton"
38
- type="button"
39
- class="directus-add-button"
40
- title="Add new item"
41
- @click="triggerAdd"
42
- >
43
- <slot>
44
- <svg
45
- xmlns="http://www.w3.org/2000/svg"
46
- width="20"
47
- height="20"
48
- viewBox="0 0 24 24"
49
- fill="none"
50
- stroke="currentColor"
51
- stroke-width="2"
52
- stroke-linecap="round"
53
- stroke-linejoin="round"
54
- >
55
- <line x1="12" y1="5" x2="12" y2="19" />
56
- <line x1="5" y1="12" x2="19" y2="12" />
57
- </svg>
58
- </slot>
59
- </button>
60
- </template>
61
-
62
- <style scoped>
63
- .directus-add-button{align-items:center;background:transparent;border:2px dashed #64f;border-radius:8px;color:#64f;cursor:pointer;display:flex;justify-content:center;margin:8px 0;opacity:.6;padding:8px;transition:background .2s,color .2s;width:100%}.directus-add-button:hover{background:rgba(102,68,255,.1);opacity:1}.directus-add-button:active{background:rgba(102,68,255,.2)}
64
- </style>
@@ -1,22 +0,0 @@
1
- import type { PrimaryKey } from '@directus/types';
2
- type __VLS_Props = {
3
- /** The parent collection that contains the repeater field */
4
- collection: string;
5
- /** The parent item ID */
6
- item: PrimaryKey;
7
- /** The field name of the repeater on the parent (e.g., 'blocks') */
8
- field: string;
9
- };
10
- declare var __VLS_1: {};
11
- type __VLS_Slots = {} & {
12
- default?: (props: typeof __VLS_1) => any;
13
- };
14
- declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
15
- declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
16
- declare const _default: typeof __VLS_export;
17
- export default _default;
18
- type __VLS_WithSlots<T, S> = T & {
19
- new (): {
20
- $slots: S;
21
- };
22
- };
@@ -1,25 +0,0 @@
1
- import type { PrimaryKey } from '@directus/types';
2
- declare const __VLS_export: <T extends keyof DirectusSchema>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_exposed?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
3
- props: import("vue").PublicProps & __VLS_PrettifyLocal<{
4
- collection: T;
5
- item: PrimaryKey;
6
- mode?: "drawer" | "modal" | "popover";
7
- }> & (typeof globalThis extends {
8
- __VLS_PROPS_FALLBACK: infer P;
9
- } ? P : {});
10
- expose: (exposed: {}) => void;
11
- attrs: any;
12
- slots: {
13
- default?: (props: {}) => any;
14
- };
15
- emit: {};
16
- }>) => import("vue").VNode & {
17
- __ctx?: Awaited<typeof __VLS_setup>;
18
- };
19
- declare const _default: typeof __VLS_export;
20
- export default _default;
21
- type __VLS_PrettifyLocal<T> = (T extends any ? {
22
- [K in keyof T]: T[K];
23
- } : {
24
- [K in keyof T as K]: T[K];
25
- }) & {};
@@ -1,64 +0,0 @@
1
- <script setup>
2
- import { computed, useRuntimeConfig } from "#imports";
3
- import { useDirectusOriginUrl, useDirectusVisualEditor } from "../composables/directus";
4
- const props = defineProps({
5
- collection: { type: null, required: true },
6
- item: { type: [String, Number], required: true },
7
- mode: { type: String, required: false }
8
- });
9
- const config = useRuntimeConfig();
10
- const directusVisualEditing = useDirectusVisualEditor();
11
- const showButton = computed(() => config.public.directus.visualEditor && directusVisualEditing.value);
12
- function triggerEdit() {
13
- const directusUrl = useDirectusOriginUrl();
14
- const editConfig = {
15
- collection: props.collection,
16
- item: props.item,
17
- mode: props.mode ?? "drawer"
18
- };
19
- try {
20
- window.parent.postMessage({
21
- action: "edit",
22
- data: {
23
- key: crypto.randomUUID(),
24
- editConfig,
25
- rect: { top: 0, left: 0, width: 0, height: 0 }
26
- }
27
- }, directusUrl);
28
- } catch (error) {
29
- console.error("[DirectusEditButton] Error triggering edit:", error);
30
- }
31
- }
32
- </script>
33
-
34
- <template>
35
- <button
36
- v-if="showButton"
37
- type="button"
38
- class="directus-edit-button"
39
- title="Edit in Directus"
40
- @click="triggerEdit"
41
- >
42
- <slot>
43
- <svg
44
- xmlns="http://www.w3.org/2000/svg"
45
- width="24"
46
- height="24"
47
- viewBox="0 0 24 24"
48
- fill="none"
49
- stroke="currentColor"
50
- stroke-width="2"
51
- stroke-linecap="round"
52
- stroke-linejoin="round"
53
- >
54
- <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
55
- <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" />
56
- </svg>
57
- <span>Edit Page</span>
58
- </slot>
59
- </button>
60
- </template>
61
-
62
- <style scoped>
63
- .directus-edit-button{align-items:center;background:#64f;border:none;border-radius:8px;bottom:24px;box-shadow:0 4px 12px rgba(102,68,255,.4);color:#fff;cursor:pointer;display:inline-flex;font-size:16px;font-weight:500;gap:8px;padding:12px 20px;position:fixed;right:24px;transition:background .2s,transform .2s,box-shadow .2s;z-index:2147483647}.directus-edit-button:hover{background:#53d;box-shadow:0 6px 16px rgba(102,68,255,.5);transform:translateY(-2px)}.directus-edit-button:active{transform:translateY(0)}
64
- </style>
@@ -1,25 +0,0 @@
1
- import type { PrimaryKey } from '@directus/types';
2
- declare const __VLS_export: <T extends keyof DirectusSchema>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_exposed?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
3
- props: import("vue").PublicProps & __VLS_PrettifyLocal<{
4
- collection: T;
5
- item: PrimaryKey;
6
- mode?: "drawer" | "modal" | "popover";
7
- }> & (typeof globalThis extends {
8
- __VLS_PROPS_FALLBACK: infer P;
9
- } ? P : {});
10
- expose: (exposed: {}) => void;
11
- attrs: any;
12
- slots: {
13
- default?: (props: {}) => any;
14
- };
15
- emit: {};
16
- }>) => import("vue").VNode & {
17
- __ctx?: Awaited<typeof __VLS_setup>;
18
- };
19
- declare const _default: typeof __VLS_export;
20
- export default _default;
21
- type __VLS_PrettifyLocal<T> = (T extends any ? {
22
- [K in keyof T]: T[K];
23
- } : {
24
- [K in keyof T as K]: T[K];
25
- }) & {};