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 +59 -31
- package/dist/cli/index.mjs +138 -18
- package/dist/module.d.mts +35 -0
- package/dist/module.d.ts +35 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +12 -3
- package/dist/runtime/composables/files.js +1 -1
- package/dist/runtime/server/services/directus.d.ts +1 -1
- package/dist/runtime/server/services/directus.js +1 -1
- package/dist/runtime/types/generate.d.ts +47 -2
- package/dist/runtime/types/generate.js +116 -13
- package/package.json +18 -2
- package/dist/runtime/components/directus-add-button.d.vue.ts +0 -22
- package/dist/runtime/components/directus-add-button.vue +0 -64
- package/dist/runtime/components/directus-add-button.vue.d.ts +0 -22
- package/dist/runtime/components/directus-edit-button.d.vue.ts +0 -25
- package/dist/runtime/components/directus-edit-button.vue +0 -64
- package/dist/runtime/components/directus-edit-button.vue.d.ts +0 -25
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
|
|
10
|
+
> A Nuxt module for Directus with built-in authentication, realtime, file management, type generation, and visual editor support.
|
|
9
11
|
|
|
10
|
-
- [✨ Release Notes](/
|
|
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
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
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
|
-
```
|
|
50
|
+
```ts
|
|
43
51
|
export default defineNuxtConfig({
|
|
44
|
-
modules: [
|
|
45
|
-
'nuxt-directus-sdk'
|
|
46
|
-
],
|
|
52
|
+
modules: ['nuxt-directus-sdk'],
|
|
47
53
|
directus: {
|
|
48
|
-
|
|
49
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
102
|
+
pnpm run dev:prepare
|
|
85
103
|
|
|
86
104
|
# Develop with the playground
|
|
87
|
-
|
|
105
|
+
pnpm run dev
|
|
88
106
|
|
|
89
107
|
# Build the playground
|
|
90
|
-
|
|
108
|
+
pnpm run dev:build
|
|
91
109
|
|
|
92
110
|
# Run ESLint
|
|
93
|
-
|
|
111
|
+
pnpm run lint
|
|
94
112
|
|
|
95
113
|
# Run Vitest
|
|
96
|
-
|
|
97
|
-
|
|
114
|
+
pnpm run test
|
|
115
|
+
pnpm run test:watch
|
|
98
116
|
|
|
99
|
-
# Release new version
|
|
100
|
-
|
|
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
|
|
package/dist/cli/index.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
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.
|
|
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
|
-
"
|
|
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`,
|
|
@@ -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
|
|
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
|
|
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
|
|
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[]
|
|
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
|
|
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
|
-
|
|
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
|
|
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.
|
|
4
|
+
"version": "6.0.0-beta.1",
|
|
5
5
|
"packageManager": "pnpm@10.32.1",
|
|
6
|
-
"description": "A Directus
|
|
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
|
-
}) & {};
|