@dyrected/sdk 2.5.29 → 2.5.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -22,9 +22,7 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  DyrectedClient: () => DyrectedClient,
24
24
  DyrectedError: () => DyrectedError,
25
- createClient: () => createClient,
26
- generateAIPrompt: () => generateAIPrompt,
27
- generateFreshSetupPrompt: () => generateFreshSetupPrompt
25
+ createClient: () => createClient
28
26
  });
29
27
  module.exports = __toCommonJS(index_exports);
30
28
 
@@ -107,818 +105,6 @@ var QueryBuilder = class {
107
105
  }
108
106
  };
109
107
 
110
- // src/utils/setup-prompt.ts
111
- function buildEnvironmentSection(frameworkLabel, isSelfHosted, config) {
112
- const credentialLines = isSelfHosted ? "" : `- Site ID : ${config.siteId}
113
- - API Key : ${config.apiKey}`;
114
- return `
115
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
116
- 1. ENVIRONMENT
117
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
118
- - Framework : ${frameworkLabel || "Detect it"}
119
- - Host Type : ${isSelfHosted ? "Self-Hosted (Local/Private Server)" : "Managed (Dyrected Cloud)"}
120
- - API Base : ${config.baseUrl || "http://localhost:3000"}
121
- ${credentialLines}`;
122
- }
123
- function buildDiagnosticSection() {
124
- return `
125
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
126
- 2. PHASE 0 \u2014 DISCOVERY
127
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
128
- Before writing any code, you MUST ask the user these questions.
129
- Write them exactly as shown below \u2014 in plain language with examples.
130
- Wait for the user to answer ALL of them before proceeding.
131
-
132
- \u2500\u2500\u2500 QUESTION 1 \u2500\u2500\u2500
133
- Ask:
134
- "Do you already have a website built, or are we starting fresh?
135
-
136
- \u2192 Already built: share your project or describe what pages you have
137
- (e.g. Home, About, Services, Contact)
138
- \u2192 Starting fresh: just say 'new site' and describe what the site is for"
139
-
140
- \u2500\u2500\u2500 QUESTION 2 \u2500\u2500\u2500
141
- Ask:
142
- "What kind of content will your client need to update regularly?
143
-
144
- Here are some examples to help you think:
145
- \u2192 Blog posts or news articles
146
- \u2192 Team member profiles (name, photo, bio)
147
- \u2192 Services or product descriptions
148
- \u2192 Testimonials or reviews
149
- \u2192 Event listings
150
- \u2192 FAQs
151
- \u2192 Homepage text (headline, hero image, call-to-action button)
152
-
153
- Just list the ones that apply. You can say things like:
154
- 'They need to update blog posts and team members'"
155
-
156
- \u2500\u2500\u2500 QUESTION 3 \u2500\u2500\u2500
157
- Ask:
158
- "Are there any pages on the site that should NEVER change \u2014 like a
159
- custom-coded page you want to leave exactly as it is?
160
-
161
- Example answer: 'The homepage has a custom animation, leave that alone.
162
- Everything else can be managed from the CMS.'
163
-
164
- If everything should be manageable, just say 'all pages'"
165
-
166
- \u2500\u2500\u2500 QUESTION 4 \u2500\u2500\u2500
167
- Ask:
168
- "What is this website for? Pick the closest description:
169
-
170
- A) A business or agency marketing site (show services, get leads)
171
- B) A blog or content site (publish articles regularly)
172
- C) A SaaS or product site (landing page, pricing, features)
173
- D) A portfolio (show work, case studies)
174
- E) An e-commerce or product catalogue
175
- F) Something else \u2014 describe it in one sentence"
176
-
177
- \u2500\u2500\u2500 IF EXISTING SITE \u2500\u2500\u2500
178
- If the user confirms they have an existing site, also:
179
- - Scan the codebase for all hardcoded text that should be CMS-managed
180
- - Identify repeated data structures that should become collections
181
- - Propose saving current content to a migration/ folder as .json files
182
- BEFORE touching any code
183
- - Report your findings to the user and get confirmation before proceeding
184
-
185
- Do NOT write any implementation code until all questions are answered
186
- and the user has confirmed the content plan.`;
187
- }
188
- function buildConstraintsSection() {
189
- return `
190
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
191
- 3. ARCHITECTURE & CONSTRAINTS
192
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
193
- - API ACCESS : Use client.collection(slug) as the only entry point.
194
- - ZERO-STATE : Always use initialData in all data fetches so the site
195
- renders correctly on first load and never throws during render.
196
- - MARKETING FREEDOM : Use a dynamic pages collection with a catch-all route
197
- so marketing can create and manage pages without a developer.
198
- - BLOCKS DESIGN : Use blocks for flexible page builders. Store as
199
- [{ blockType: 'slug', ...fields }] and switch on blockType
200
- in the frontend renderer.
201
- - DATA SAFETY : Never overwrite or drop existing content or pages.
202
- Preserve everything before making changes.
203
- - RESILIENCE : If Dyrected backend is unreachable, fall back to
204
- initialData and show stale content \u2014 never an error page.
205
- All relationship fields must handle null gracefully.
206
- Every block renderer must have a default fallback case.`;
207
- }
208
- function buildSchemaRulesSection() {
209
- return `
210
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
211
- 4. SCHEMA EVOLUTION RULES
212
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
213
- - Never drop existing fields from the schema. Mark unused fields as deprecated only.
214
- - All new fields must have a defaultValue.
215
- - Never rename a field slug \u2014 add a new field and migrate data separately.
216
- - For Cloud deployments, run npx @dyrected/cli sync:schema after every config change. Self-hosted deployments sync automatically on startup.`;
217
- }
218
- function buildDoNotSection() {
219
- return `
220
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
221
- 5. DO NOT
222
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
223
- - Do NOT use client.collections \u2014 always use client.collection(slug).
224
- - Do NOT add custom auth middleware to the admin route.
225
- Dyrected handles admin authentication internally. Do not wrap,
226
- protect, or redirect the admin route yourself.
227
- - Do NOT use renderAdminUI in a Nuxt.js project. Use the DyrectedAdmin
228
- component which is auto-imported by @dyrected/nuxt.
229
- - Do NOT modify or overwrite existing pages without first preserving their data.
230
- - Do NOT drop, rename, or remove fields from an existing schema.
231
- - Do NOT integrate blog posts or testimonials unless explicitly requested.
232
- - Do NOT skip the diagnostic or discovery phase.`;
233
- }
234
- function buildTechnicalReferenceSection() {
235
- return `
236
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
237
- 6. TECHNICAL REFERENCE
238
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
239
- Use defineCollection, defineGlobal, and defineConfig from '@dyrected/core'.
240
-
241
- FIELD TYPES:
242
- - Primitive : text | textarea | richText | number | boolean | date | email | url | json | image
243
- - Choice : select | multiSelect (requires options: [{ label, value }])
244
- - Structural : array | object (requires nested fields: [...])
245
- - Relation : relationship (requires relationTo: '<slug>')
246
- - Media : relationship to an upload collection (e.g. 'media')
247
- - Blocks : blocks (requires blocks: [{ slug, labels, fields }])
248
-
249
- COLLECTION OPTIONS:
250
- - upload: true \u2014 media library with file upload support
251
- - auth: true \u2014 adds login/me endpoints; password field is auto-managed
252
- - audit: true \u2014 enables activity logging
253
- - admin.useAsTitle \u2014 field used as display title in admin list view
254
- - admin.group \u2014 groups collection under a sidebar heading
255
- - admin.hidden \u2014 hides collection from the sidebar (internal/system use)
256
-
257
- FIELD OPTIONS:
258
- - label \u2014 user-friendly display name (REQUIRED for all fields)
259
- - required \u2014 validation
260
- - unique \u2014 database-level uniqueness constraint
261
- - hasMany \u2014 allow multiple values (for relationship, select, image)
262
- - defaultValue \u2014 fallback value (required on all new fields added to existing schemas)
263
- - admin.condition \u2014 Jexl expression string to conditionally show/hide field
264
- e.g. "status == \\"published\\""
265
- - admin.readOnly \u2014 display only, not editable
266
- - admin.hidden \u2014 hidden from editor UI entirely
267
- - hooks.beforeChange \u2014 [async (value) => newValue] transform before save
268
- - hooks.afterRead \u2014 [async (value) => newValue] transform after read
269
-
270
- GLOBALS:
271
- Use defineGlobal for single-instance documents like site settings or navigation.
272
- Access via client.global(slug).get() and client.global(slug).update(data).
273
-
274
- BLOCKS:
275
- Blocks are stored as [{ blockType: '<slug>', ...fields }].
276
- The admin renders a drag-and-drop block editor automatically.
277
- On the frontend, iterate the array and switch on block.blockType.
278
- Always include a default case in your switch for unknown block types.
279
-
280
- COMPLETE SCHEMA EXAMPLE:
281
- \`\`\`ts
282
- import { defineCollection, defineGlobal, defineConfig } from '@dyrected/core'
283
- import { SqliteAdapter } from '@dyrected/db-sqlite'
284
-
285
- const media = defineCollection({
286
- slug: 'media',
287
- upload: true,
288
- fields: [
289
- { name: 'alt', label: 'Alt Text', type: 'text' },
290
- ],
291
- })
292
-
293
- const pages = defineCollection({
294
- slug: 'pages',
295
- admin: { useAsTitle: 'title', group: 'Content' },
296
- fields: [
297
- { name: 'title', label: 'Title', type: 'text', required: true },
298
- { name: 'slug', label: 'URL Slug', type: 'text', required: true, unique: true },
299
- { name: 'seo', label: 'SEO Metadata', type: 'object', fields: [
300
- { name: 'metaTitle', label: 'Meta Title', type: 'text' },
301
- { name: 'metaDescription', label: 'Meta Description', type: 'textarea' },
302
- { name: 'ogImage', label: 'OG Image', type: 'relationship', relationTo: 'media' },
303
- ]},
304
- {
305
- name: 'layout',
306
- label: 'Page Layout',
307
- type: 'blocks',
308
- blocks: [
309
- {
310
- slug: 'hero',
311
- labels: { singular: 'Hero', plural: 'Heroes' },
312
- fields: [
313
- { name: 'heading', label: 'Heading', type: 'text', required: true },
314
- { name: 'subheading', label: 'Subheading', type: 'textarea' },
315
- { name: 'image', label: 'Hero Image', type: 'relationship', relationTo: 'media' },
316
- { name: 'ctaLabel', label: 'Button Label', type: 'text' },
317
- { name: 'ctaLink', label: 'Button Link', type: 'url' },
318
- ],
319
- },
320
- {
321
- slug: 'richContent',
322
- labels: { singular: 'Rich Content', plural: 'Rich Content Blocks' },
323
- fields: [
324
- { name: 'content', label: 'Content', type: 'richText', required: true },
325
- ],
326
- },
327
- {
328
- slug: 'callToAction',
329
- labels: { singular: 'Call to Action', plural: 'Calls to Action' },
330
- fields: [
331
- { name: 'heading', label: 'Heading', type: 'text', required: true },
332
- { name: 'description', label: 'Description', type: 'textarea' },
333
- { name: 'buttonLabel', label: 'Button Text', type: 'text' },
334
- { name: 'buttonLink', label: 'Button Link', type: 'url' },
335
- { name: 'theme', label: 'Theme', type: 'select', options: [
336
- { label: 'Primary', value: 'primary' },
337
- { label: 'Secondary', value: 'secondary' },
338
- { label: 'Dark', value: 'dark' },
339
- ]},
340
- ],
341
- },
342
- ],
343
- },
344
- ],
345
- })
346
-
347
- const settings = defineGlobal({
348
- slug: 'settings',
349
- label: 'Site Settings',
350
- fields: [
351
- { name: 'siteName', label: 'Site Name', type: 'text' },
352
- { name: 'tagline', label: 'Site Tagline', type: 'text' },
353
- { name: 'logo', label: 'Site Logo', type: 'relationship', relationTo: 'media' },
354
- { name: 'footerText', label: 'Footer Text', type: 'textarea' },
355
- ],
356
- })
357
-
358
- export default defineConfig({
359
- collections: [media, pages],
360
- globals: [settings],
361
- db: new SqliteAdapter({ filename: './dyrected.db' }),
362
- })
363
- \`\`\``;
364
- }
365
- function buildDeliverablesSection() {
366
- return `
367
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
368
- 7. REQUIRED DELIVERABLES \u2014 IN THIS ORDER
369
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
370
- Return your response in exactly this order. Do not combine steps. Do not skip steps.
371
-
372
- 1. Diagnostic findings (what exists, what needs to become CMS-managed)
373
- 2. dyrected.config.ts \u2014 complete file
374
- 3. Admin route file \u2014 complete file
375
- 4. Catch-all frontend page route \u2014 complete file
376
- 5. Block components \u2014 names and fields only
377
- 6. Migration/fallback strategy \u2014 numbered steps
378
- 7. Schema sync command
379
-
380
- API Reference: https://docs.dyrected.com
381
- If you are unsure about any syntax or property, refer to the documentation above.`;
382
- }
383
- function buildFrameworkSection(activeTab, isSelfHosted, config) {
384
- const envPrefix = activeTab === "next" ? "NEXT_PUBLIC_" : activeTab === "nuxt" ? "NUXT_PUBLIC_" : "";
385
- const credentialEnvLines = isSelfHosted ? "" : `
386
- apiKey: process.env.${envPrefix}DYRECTED_API_KEY || '${config.apiKey}',
387
- siteId: process.env.${envPrefix}SITE_ID || '${config.siteId}',`;
388
- const baseUrlLine = `process.env.${envPrefix}DYRECTED_URL || '${config.baseUrl || "http://localhost:3000"}'`;
389
- const sections = {
390
- next: `
391
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
392
- 8. IMPLEMENTATION \u2014 Next.js
393
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
394
- 1. SDK Setup (lib/dyrected.ts):
395
- \`\`\`ts
396
- import { createClient } from '@dyrected/sdk'
397
-
398
- export const dyrected = createClient({
399
- baseUrl: ${baseUrlLine},${credentialEnvLines}
400
- })
401
- \`\`\`
402
-
403
- 2. Admin Route (app/cms/page.tsx):
404
- \`\`\`tsx
405
- import { DyrectedAdmin } from '@dyrected/next/admin'
406
-
407
- export default function AdminPage() {
408
- // DyrectedAdmin handles routing, auth, and CSS automatically.
409
- // Do NOT wrap this in custom auth middleware.
410
- return <DyrectedAdmin basename="/cms" />
411
- }
412
- \`\`\`
413
-
414
- 3. Catch-all Page Route (app/[...slug]/page.tsx):
415
- \`\`\`tsx
416
- import { dyrected } from '@/lib/dyrected'
417
-
418
- export default async function CmsPage({ params }: { params: { slug: string[] } }) {
419
- const slug = params.slug?.join('/') || 'home'
420
- const page = await dyrected.collection('pages').findOne({ where: { slug: { equals: slug } } })
421
-
422
- if (!page) return <div>Page not found</div>
423
-
424
- return (
425
- <main>
426
- {page.layout?.map((block: any, i: number) => (
427
- <BlockRenderer key={i} block={block} />
428
- ))}
429
- </main>
430
- )
431
- }
432
- \`\`\``,
433
- nuxt: `
434
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
435
- 8. IMPLEMENTATION \u2014 Nuxt.js
436
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
437
- 1. Nuxt.js Config (nuxt.config.ts):
438
- \`\`\`ts
439
- export default defineNuxtConfig({
440
- modules: ['@dyrected/nuxt'],
441
- dyrected: {
442
- baseUrl: process.env.${envPrefix}DYRECTED_URL || '${config.baseUrl || "http://localhost:3000"}',${isSelfHosted ? "" : `
443
- apiKey: process.env.${envPrefix}DYRECTED_API_KEY || '${config.apiKey}',
444
- siteId: process.env.${envPrefix}SITE_ID || '${config.siteId}',`}
445
- },
446
- })
447
- \`\`\`
448
-
449
- 2. Admin Route (pages/cms/index.vue):
450
- \`\`\`vue
451
- <script setup lang="ts">
452
- // DyrectedAdmin is auto-imported by @dyrected/nuxt.
453
- // Do NOT manually import it.
454
- // Do NOT use renderAdminUI here.
455
- // Do NOT add custom auth middleware \u2014 Dyrected handles authentication.
456
- definePageMeta({ layout: false })
457
- </script>
458
-
459
- <template>
460
- <ClientOnly>
461
- <DyrectedAdmin basename="/cms" />
462
- </ClientOnly>
463
- </template>
464
- \`\`\`
465
-
466
- 3. Catch-all Page Route (pages/[...slug].vue):
467
- \`\`\`vue
468
- <script setup lang="ts">
469
- const route = useRoute()
470
- const slug = computed(() =>
471
- Array.isArray(route.params.slug)
472
- ? route.params.slug.join('/')
473
- : route.params.slug || 'home'
474
- )
475
-
476
- const { data: page } = await useDyrected('pages').findOne({
477
- where: { slug: { equals: slug.value } },
478
- initialData: null,
479
- })
480
- </script>
481
-
482
- <template>
483
- <main v-if="page">
484
- <BlockRenderer
485
- v-for="(block, i) in page.layout"
486
- :key="i"
487
- :block="block"
488
- />
489
- </main>
490
- <div v-else>Page not found</div>
491
- </template>
492
- \`\`\``,
493
- react: `
494
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
495
- 8. IMPLEMENTATION \u2014 React
496
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
497
- 1. SDK Setup (lib/dyrected.ts):
498
- \`\`\`ts
499
- import { createClient } from '@dyrected/sdk'
500
-
501
- export const dyrected = createClient({
502
- baseUrl: '${config.baseUrl || "https://api.dyrected.cloud"}',${isSelfHosted ? "" : `
503
- apiKey: '${config.apiKey}',
504
- siteId: '${config.siteId}',`}
505
- })
506
- \`\`\`
507
-
508
- 2. Admin Route (pages/cms.tsx):
509
- \`\`\`tsx
510
- import { AdminUI } from '@dyrected/admin'
511
- import '@dyrected/admin/styles'
512
-
513
- export default function AdminPage() {
514
- return (
515
- <div style={{ height: '100vh' }}>
516
- <AdminUI
517
- baseUrl="${config.baseUrl || "https://api.dyrected.cloud"}"${isSelfHosted ? "" : `
518
- apiKey="${config.apiKey}"
519
- siteId="${config.siteId}"`}
520
- />
521
- </div>
522
- )
523
- }
524
- \`\`\``,
525
- vue: `
526
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
527
- 8. IMPLEMENTATION \u2014 Vue
528
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
529
- 1. SDK Setup (lib/dyrected.ts):
530
- \`\`\`ts
531
- import { createClient } from '@dyrected/sdk'
532
-
533
- export const dyrected = createClient({
534
- baseUrl: '${config.baseUrl || "https://api.dyrected.cloud"}',${isSelfHosted ? "" : `
535
- apiKey: '${config.apiKey}',
536
- siteId: '${config.siteId}',`}
537
- })
538
- \`\`\`
539
-
540
- 2. Admin Route (pages/cms.vue):
541
- \`\`\`vue
542
- <template>
543
- <div ref="container" style="height: 100vh" />
544
- </template>
545
-
546
- <script setup>
547
- import { ref, onMounted, onUnmounted } from 'vue'
548
- import { renderAdminUI } from '@dyrected/admin'
549
- import '@dyrected/admin/styles'
550
-
551
- const container = ref(null)
552
- let cleanup: (() => void) | undefined
553
-
554
- onMounted(() => {
555
- cleanup = renderAdminUI(container.value, {
556
- baseUrl: '${config.baseUrl || "https://api.dyrected.cloud"}',${isSelfHosted ? "" : `
557
- apiKey: '${config.apiKey}',
558
- siteId: '${config.siteId}',`}
559
- })
560
- })
561
-
562
- onUnmounted(() => cleanup?.())
563
- </script>
564
- \`\`\``
565
- };
566
- return sections[activeTab] || sections.next;
567
- }
568
- function generateFreshSetupPrompt(activeTab, config) {
569
- const frameworkLabel = activeTab === "next" ? "Next.js" : activeTab === "nuxt" ? "Nuxt.js" : activeTab ? activeTab.charAt(0).toUpperCase() + activeTab.slice(1) : "the project's detected framework";
570
- const isSelfHosted = config.isSelfHosted === true || !config.apiKey && !config.siteId;
571
- const frameworkSetup = {
572
- nuxt: `
573
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
574
- 3. INSTALLATION STEPS \u2014 Run these in order
575
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
576
- Tell the user to follow these steps exactly. Explain each one in plain
577
- language before showing the command. Do not skip any step.
578
-
579
- STEP 1 \u2014 Initialize Dyrected
580
- Tell the user:
581
- "Run this command in your terminal inside your Nuxt.js project folder.
582
- It will set everything up for you automatically."
583
-
584
- \`\`\`bash
585
- npx @dyrected/cli init
586
- \`\`\`
587
-
588
- When it asks questions, tell the user to:
589
- - Choose: Nuxt.js 3
590
- - Choose: SQLite (easiest option, no extra setup needed)
591
-
592
- The CLI will automatically:
593
- - Install all required packages
594
- - Create a dyrected.config.ts file
595
- - Mount the Admin UI at pages/cms/index.vue
596
- - Generate a .env.example file
597
-
598
- STEP 2 \u2014 Register the module
599
- Tell the user to open nuxt.config.ts and add '@dyrected/nuxt' to modules:
600
-
601
- \`\`\`ts
602
- export default defineNuxtConfig({
603
- modules: ['@dyrected/nuxt'],
604
- })
605
- \`\`\`
606
-
607
- STEP 3 \u2014 Set up environment variables
608
- Tell the user:
609
- "Find the file called .env.example in your project.
610
- Make a copy of it and rename the copy to .env
611
- Then open .env and fill in the values."
612
-
613
- STEP 4 \u2014 Start the project
614
- \`\`\`bash
615
- pnpm dev
616
- \`\`\`
617
-
618
- STEP 5 \u2014 Open the dashboard
619
- Tell the user to open their browser and go to:
620
- http://localhost:3000/cms
621
-
622
- If they see the Dyrected admin dashboard, the installation worked.
623
- Tell them: "You are ready. Now let's set up your content."`,
624
- next: `
625
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
626
- 3. INSTALLATION STEPS \u2014 Run these in order
627
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
628
- Tell the user to follow these steps exactly. Explain each one in plain
629
- language before showing the command. Do not skip any step.
630
-
631
- STEP 1 \u2014 Initialize Dyrected
632
- Tell the user:
633
- "Run this command in your terminal inside your Next.js project folder."
634
-
635
- \`\`\`bash
636
- npx @dyrected/cli init
637
- \`\`\`
638
-
639
- When it asks questions, tell the user to:
640
- - Choose: Next.js
641
- - Choose: SQLite (easiest option, no extra setup needed)
642
-
643
- The CLI will automatically:
644
- - Install all required packages
645
- - Create a dyrected.config.ts file
646
- - Mount the Admin UI at app/cms/page.tsx
647
- - Generate a .env.example file
648
-
649
- STEP 2 \u2014 Set up environment variables
650
- Tell the user:
651
- "Find the file called .env.example in your project.
652
- Make a copy of it and rename the copy to .env.local
653
- Then open .env.local and fill in the values."
654
-
655
- STEP 3 \u2014 Start the project
656
- \`\`\`bash
657
- pnpm dev
658
- \`\`\`
659
-
660
- STEP 4 \u2014 Open the dashboard
661
- Tell the user to open their browser and go to:
662
- http://localhost:3000/cms
663
-
664
- If they see the Dyrected admin dashboard, the installation worked.
665
- Tell them: "You are ready. Now let's set up your content."`,
666
- react: `
667
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
668
- 3. INSTALLATION STEPS \u2014 Run these in order
669
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
670
- STEP 1 \u2014 Install the SDK and admin packages
671
- \`\`\`bash
672
- npm install @dyrected/sdk @dyrected/admin
673
- \`\`\`
674
-
675
- STEP 2 \u2014 Set up the client (lib/dyrected.ts):
676
- \`\`\`ts
677
- import { createClient } from '@dyrected/sdk'
678
-
679
- export const dyrected = createClient({
680
- baseUrl: '${config.baseUrl || "https://api.dyrected.cloud"}',${isSelfHosted ? "" : `
681
- apiKey: '${config.apiKey}',
682
- siteId: '${config.siteId}',`}
683
- })
684
- \`\`\`
685
-
686
- STEP 3 \u2014 Mount the Admin UI (pages/cms.tsx):
687
- \`\`\`tsx
688
- import { AdminUI } from '@dyrected/admin'
689
- import '@dyrected/admin/styles'
690
-
691
- export default function AdminPage() {
692
- return (
693
- <div style={{ height: '100vh' }}>
694
- <AdminUI
695
- baseUrl="${config.baseUrl || "https://api.dyrected.cloud"}"${isSelfHosted ? "" : `
696
- apiKey="${config.apiKey}"
697
- siteId="${config.siteId}"`}
698
- />
699
- </div>
700
- )
701
- }
702
- \`\`\``,
703
- vue: `
704
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
705
- 3. INSTALLATION STEPS \u2014 Run these in order
706
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
707
- STEP 1 \u2014 Install the SDK and admin packages
708
- \`\`\`bash
709
- npm install @dyrected/sdk @dyrected/admin
710
- \`\`\`
711
-
712
- STEP 2 \u2014 Set up the client (lib/dyrected.ts):
713
- \`\`\`ts
714
- import { createClient } from '@dyrected/sdk'
715
-
716
- export const dyrected = createClient({
717
- baseUrl: '${config.baseUrl || "https://api.dyrected.cloud"}',${isSelfHosted ? "" : `
718
- apiKey: '${config.apiKey}',
719
- siteId: '${config.siteId}',`}
720
- })
721
- \`\`\`
722
-
723
- STEP 3 \u2014 Mount the Admin UI (pages/cms.vue):
724
- \`\`\`vue
725
- <template>
726
- <div ref="container" style="height: 100vh" />
727
- </template>
728
-
729
- <script setup>
730
- import { ref, onMounted, onUnmounted } from 'vue'
731
- import { renderAdminUI } from '@dyrected/admin'
732
- import '@dyrected/admin/styles'
733
-
734
- const container = ref(null)
735
- let cleanup
736
-
737
- onMounted(() => {
738
- cleanup = renderAdminUI(container.value, {
739
- baseUrl: '${config.baseUrl || "https://api.dyrected.cloud"}',${isSelfHosted ? "" : `
740
- apiKey: '${config.apiKey}',
741
- siteId: '${config.siteId}',`}
742
- })
743
- })
744
-
745
- onUnmounted(() => cleanup?.())
746
- </script>
747
- \`\`\``
748
- };
749
- return [
750
- `You are a friendly technical assistant helping someone set up Dyrected CMS for the very first time in a ${frameworkLabel} project. They have NOT installed anything yet.
751
-
752
- Your job is to:
753
- 1. Understand who you are talking to before giving any instructions
754
- 2. Ask simple questions about their project and content needs
755
- 3. Walk them through setup in a way that matches their technical level
756
- 4. Confirm each step worked before moving to the next
757
- 5. Help them design their content so their client can manage it
758
-
759
- Speak in plain language at all times. Never assume technical knowledge.
760
- Never show more than one step at a time.
761
- Never mention the terminal, command line, or any commands until you have
762
- confirmed the user is comfortable running them.`,
763
- `
764
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
765
- 1. ENVIRONMENT
766
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
767
- - Framework : ${frameworkLabel}
768
- - Host Type : ${isSelfHosted ? "Self-Hosted" : "Dyrected Cloud"}
769
- - API Base : ${config.baseUrl || "http://localhost:3000"}
770
- ${isSelfHosted ? "" : `- Site ID : ${config.siteId}
771
- - API Key : ${config.apiKey}`}`.trim(),
772
- `
773
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
774
- 2. PHASE 0 \u2014 UNDERSTAND THE USER FIRST
775
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
776
- Ask these questions one at a time. Wait for each answer before asking the next.
777
- Never ask more than one question at a time.
778
-
779
- \u2500\u2500\u2500 QUESTION 1 \u2014 TECH LEVEL \u2500\u2500\u2500
780
- This is the most important question. Ask it first.
781
-
782
- Ask exactly this:
783
- "Before we dive in \u2014 how would you describe yourself?
784
-
785
- A) I write code myself (I'm comfortable with the terminal and editing files)
786
- B) I use tools like Lovable, Bolt, or v0 to build with AI \u2014 I don't write much code myself
787
- C) I'm a designer or project manager \u2014 someone else usually handles the technical stuff
788
- D) Something else \u2014 just describe how you work"
789
-
790
- Use their answer to decide how to guide them for the rest of the setup:
791
- - If A \u2192 they are TECHNICAL. You may show terminal commands and code directly.
792
- - If B \u2192 they are SEMI-TECHNICAL. Explain what each step does before showing
793
- any code or commands. Ask before running anything in the terminal.
794
- For Lovable users specifically: guide them to use the built-in
795
- terminal or ask them to paste code into the right file in their editor.
796
- - If C \u2192 they are NON-TECHNICAL. Do not show terminal commands at all.
797
- Generate all the code and config for them. Walk them through
798
- copy-pasting into specific files by name. If a terminal step is
799
- unavoidable, warn them first and offer to write the exact command
800
- with a clear explanation of what it does and where to run it.
801
- - If D \u2192 ask one follow-up question to understand their workflow before
802
- deciding which path above fits best.
803
-
804
- \u2500\u2500\u2500 QUESTION 2 \u2014 PROJECT STATUS \u2500\u2500\u2500
805
- Ask after Q1 is answered:
806
-
807
- "Do you already have a ${frameworkLabel} project open,
808
- or are we starting from scratch?
809
-
810
- \u2192 Already have a project: tell me what the site is about or
811
- share the folder name
812
- \u2192 Starting fresh: just say 'new project' and I will help you
813
- create one first"
814
-
815
- \u2500\u2500\u2500 QUESTION 3 \u2014 SITE PURPOSE \u2500\u2500\u2500
816
- Ask after Q2 is answered:
817
-
818
- "What kind of website is this?
819
-
820
- A) A business or agency site (show services, get enquiries)
821
- B) A blog or news site (publish articles regularly)
822
- C) A SaaS or product landing page (features, pricing, sign up)
823
- D) A portfolio (show work and past projects)
824
- E) Something else \u2014 describe it in one sentence"
825
-
826
- \u2500\u2500\u2500 QUESTION 4 \u2014 CONTENT NEEDS \u2500\u2500\u2500
827
- Ask after Q3 is answered:
828
-
829
- "What will your client need to update themselves \u2014 without calling you?
830
-
831
- Some examples to help you think:
832
- \u2192 Blog posts or news articles
833
- \u2192 Team member profiles (name, photo, bio)
834
- \u2192 Services or product descriptions
835
- \u2192 Homepage text (headline, buttons, images)
836
- \u2192 Testimonials or reviews
837
- \u2192 FAQs
838
- \u2192 Event listings or announcements
839
-
840
- Just list the ones that apply. Or say 'not sure yet'
841
- and we will figure it out together."
842
-
843
- \u2500\u2500\u2500 AFTER ALL QUESTIONS \u2500\u2500\u2500
844
- Once all four questions are answered:
845
- 1. Summarise what you understood in plain English
846
- 2. Ask the user to confirm before touching any code
847
- 3. Then move to the installation steps using the correct path
848
- for their tech level from Question 1`,
849
- frameworkSetup[activeTab] || frameworkSetup.nuxt,
850
- `
851
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
852
- 4. AFTER INSTALLATION \u2014 CONTENT SETUP
853
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
854
- Once the dashboard is confirmed working, help the user design their
855
- content model in dyrected.config.ts based on what they told you in
856
- Phase 0.
857
-
858
- RULES for this phase:
859
- - Use defineCollection and defineConfig from '@dyrected/core'
860
- - Use client.collection(slug) only \u2014 never client.collections
861
- - Always use initialData in all data fetches
862
- - Use a catch-all pages collection for marketing-managed pages
863
- - Use blocks for flexible page layouts
864
- - Use type: 'relationship', relationTo: 'collectionSlug' for relations
865
- - Never throw during render \u2014 fall back to initialData on errors
866
- - All relationship fields must handle null gracefully
867
-
868
- CONTENT SETUP DELIVERABLES \u2014 in this order:
869
- 1. dyrected.config.ts \u2014 complete file based on their answers
870
- 2. Catch-all page route for CMS-managed pages
871
- 3. Block components list (names and fields only)
872
- 4. One example fetch showing how to load content on a page
873
-
874
- After delivering the config, tell the user to sync their schema.
875
- For TECHNICAL users show the command directly:
876
- \`\`\`bash
877
- npx @dyrected/cli sync:schema
878
- \`\`\`
879
- For SEMI-TECHNICAL or NON-TECHNICAL users explain it first:
880
- "This next step tells Dyrected to read your content setup file
881
- and prepare the database. Here is the command to run:" then show it.
882
-
883
- Then ask: "Do you want me to help you connect this content to your
884
- frontend pages now?"
885
-
886
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
887
- DO NOT
888
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
889
- - Do NOT show terminal commands before confirming the user's tech level
890
- - Do NOT assume the user knows what a terminal, CLI, or package manager is
891
- - Do NOT show all steps at once \u2014 one step at a time always
892
- - Do NOT use client.collections \u2014 use client.collection(slug)
893
- - Do NOT add custom auth middleware to the admin route
894
- - Do NOT use renderAdminUI in a Nuxt.js project
895
- - Do NOT skip the confirmation after each installation step
896
- - Do NOT use jargon without explaining it in plain English first
897
- - Do NOT assume the installation worked \u2014 ask the user to confirm
898
-
899
- API Reference: https://docs.dyrected.com
900
- If you are unsure about any syntax, refer to the official documentation above.`
901
- ].join("\n");
902
- }
903
- function generateAIPrompt(activeTab, config) {
904
- const frameworkLabel = activeTab === "next" ? "Next.js" : activeTab === "nuxt" ? "Nuxt.js" : activeTab ? activeTab.charAt(0).toUpperCase() + activeTab.slice(1) : "the project's detected framework";
905
- const isSelfHosted = config.isSelfHosted === true || !config.apiKey && !config.siteId;
906
- const existingSite = config.existingSite ?? false;
907
- const missionText = existingSite ? `You are a Senior Content Architect. Your mission is to integrate Dyrected CMS into an EXISTING ${frameworkLabel} project. Your absolute priority is DATA PRESERVATION and MIGRATION of existing hardcoded content into a flexible, blocks-based schema that empowers marketing teams to move independently.` : `You are a Senior Content Architect. Your mission is to integrate Dyrected CMS into a NEW ${frameworkLabel} project. Your priority is DATA PRESERVATION and creating a CMS that empowers marketing teams to move independently without raising tickets to engineering.`;
908
- const sections = [
909
- missionText,
910
- buildEnvironmentSection(frameworkLabel, isSelfHosted, config),
911
- buildDiagnosticSection(),
912
- buildConstraintsSection(),
913
- buildSchemaRulesSection(),
914
- buildDoNotSection(),
915
- buildTechnicalReferenceSection(),
916
- buildDeliverablesSection(),
917
- buildFrameworkSection(activeTab, isSelfHosted, config)
918
- ];
919
- return sections.join("\n");
920
- }
921
-
922
108
  // src/index.ts
923
109
  var DyrectedError = class extends Error {
924
110
  statusCode;
@@ -1002,12 +188,19 @@ var DyrectedClient = class {
1002
188
  normalizedArgs.where = JSON.stringify(normalizedArgs.where);
1003
189
  }
1004
190
  const query = stringifyQuery(normalizedArgs, { addQueryPrefix: true });
1005
- const res = await this.request(`/api/collections/${collection}${query}`);
191
+ const res = await this.request(
192
+ `/api/collections/${collection}${query}`
193
+ );
1006
194
  if (res.docs.length === 0 && initialData && initialData.length > 0) {
1007
195
  this.request(`/api/collections/${collection}/seed`, {
1008
196
  method: "POST",
1009
197
  body: JSON.stringify({ data: initialData })
1010
- }).catch((err) => console.error(`[dyrected/sdk] Failed to auto-seed collection "${collection}":`, err));
198
+ }).catch(
199
+ (err) => console.error(
200
+ `[dyrected/sdk] Failed to auto-seed collection "${collection}":`,
201
+ err
202
+ )
203
+ );
1011
204
  return {
1012
205
  docs: initialData,
1013
206
  total: initialData.length,
@@ -1026,7 +219,10 @@ var DyrectedClient = class {
1026
219
  collection(slug) {
1027
220
  return {
1028
221
  find: (args) => {
1029
- const qb = new QueryBuilder(slug, (c, a) => this.find(c, a));
222
+ const qb = new QueryBuilder(
223
+ slug,
224
+ (c, a) => this.find(c, a)
225
+ );
1030
226
  if (args) {
1031
227
  if (args.where) qb.where(args.where);
1032
228
  if (args.sort) qb.sort(args.sort);
@@ -1062,7 +258,9 @@ var DyrectedClient = class {
1062
258
  /** Return the currently authenticated user (requires a token via setToken). */
1063
259
  me: () => this.request(`/api/collections/${slug}/me`),
1064
260
  /** Issue a fresh token for the currently authenticated user. */
1065
- refreshToken: () => this.request(`/api/collections/${slug}/refresh-token`, { method: "POST" }),
261
+ refreshToken: () => this.request(`/api/collections/${slug}/refresh-token`, {
262
+ method: "POST"
263
+ }),
1066
264
  /** Check if this auth collection has any users (initialized). */
1067
265
  isInitialized: () => this.request(`/api/collections/${slug}/init`),
1068
266
  /** Register the very first user in an empty auth collection. */
@@ -1103,7 +301,39 @@ var DyrectedClient = class {
1103
301
  resetPassword: (token, password) => this.request(`/api/collections/${slug}/reset-password`, {
1104
302
  method: "POST",
1105
303
  body: JSON.stringify({ token, password })
1106
- })
304
+ }),
305
+ /**
306
+ * Perform a workflow transition on a single document.
307
+ *
308
+ * @param id - Document ID to transition.
309
+ * @param transitionName - The transition key (e.g. `'submit'`, `'publish'`, `'reject'`).
310
+ * @param opts - Optional `expectedRevision` (optimistic concurrency) and `comment`.
311
+ * @returns The updated document with refreshed `_workflow` metadata.
312
+ *
313
+ * @example
314
+ * // Publisher approves a submission
315
+ * const updated = await client.collection('posts').transition(id, 'publish')
316
+ *
317
+ * // Editor requests changes with a comment
318
+ * const updated = await client.collection('posts').transition(id, 'reject', {
319
+ * expectedRevision: doc._workflow.revision,
320
+ * comment: 'Please add more detail to section 2.',
321
+ * })
322
+ */
323
+ transition: (id, transitionName, opts) => this.transition(
324
+ slug,
325
+ id,
326
+ transitionName,
327
+ opts
328
+ ),
329
+ /**
330
+ * Fetch the workflow history for a single document — every transition that
331
+ * has ever been performed, newest first.
332
+ *
333
+ * @param id - Document ID.
334
+ * @param args - Optional `limit` (default 50, max 100).
335
+ */
336
+ workflowHistory: (id, args = {}) => this.workflowHistory(slug, id, args)
1107
337
  };
1108
338
  }
1109
339
  /**
@@ -1128,7 +358,10 @@ var DyrectedClient = class {
1128
358
  method: "POST",
1129
359
  body: JSON.stringify({ data: [{ id, ...initialData }] })
1130
360
  }).catch(
1131
- (err2) => console.error(`[dyrected/sdk] Failed to auto-seed document "${id}" in collection "${collection}":`, err2)
361
+ (err2) => console.error(
362
+ `[dyrected/sdk] Failed to auto-seed document "${id}" in collection "${collection}":`,
363
+ err2
364
+ )
1132
365
  );
1133
366
  return initialData;
1134
367
  }
@@ -1152,6 +385,44 @@ var DyrectedClient = class {
1152
385
  method: "DELETE"
1153
386
  });
1154
387
  }
388
+ /**
389
+ * Perform a workflow transition on a document.
390
+ *
391
+ * Sends `POST /api/collections/:collection/:id/transitions/:transition`.
392
+ * Requires the client to have a valid bearer token set via `setToken()`.
393
+ *
394
+ * @param collection - Collection slug.
395
+ * @param id - Document ID.
396
+ * @param transitionName - Transition key defined in `WorkflowConfig.transitions`.
397
+ * @param opts - Optional concurrency guard and comment.
398
+ *
399
+ * @example
400
+ * const updated = await client.transition('posts', postId, 'publish')
401
+ */
402
+ async transition(collection, id, transitionName, opts = {}) {
403
+ return this.request(
404
+ `/api/collections/${collection}/${id}/transitions/${encodeURIComponent(transitionName)}`,
405
+ {
406
+ method: "POST",
407
+ body: JSON.stringify(opts)
408
+ }
409
+ );
410
+ }
411
+ /**
412
+ * Fetch the workflow history for a document.
413
+ *
414
+ * Sends `GET /api/collections/:collection/:id/workflow-history`.
415
+ *
416
+ * @param collection - Collection slug.
417
+ * @param id - Document ID.
418
+ * @param args - Optional `limit` (max 100).
419
+ */
420
+ async workflowHistory(collection, id, args = {}) {
421
+ const query = args.limit ? `?limit=${args.limit}` : "";
422
+ return this.request(
423
+ `/api/collections/${collection}/${id}/workflow-history${query}`
424
+ );
425
+ }
1155
426
  async deleteMany(collection, ids) {
1156
427
  return this.request(`/api/collections/${collection}/delete-many`, {
1157
428
  method: "DELETE",
@@ -1167,7 +438,12 @@ var DyrectedClient = class {
1167
438
  this.request(`/api/globals/${slug}/seed`, {
1168
439
  method: "POST",
1169
440
  body: JSON.stringify({ data: initialData })
1170
- }).catch((err) => console.error(`[dyrected/sdk] Failed to auto-seed global "${slug}":`, err));
441
+ }).catch(
442
+ (err) => console.error(
443
+ `[dyrected/sdk] Failed to auto-seed global "${slug}":`,
444
+ err
445
+ )
446
+ );
1171
447
  return initialData;
1172
448
  }
1173
449
  return res;
@@ -1176,7 +452,12 @@ var DyrectedClient = class {
1176
452
  this.request(`/api/globals/${slug}/seed`, {
1177
453
  method: "POST",
1178
454
  body: JSON.stringify({ data: initialData })
1179
- }).catch((err2) => console.error(`[dyrected/sdk] Failed to auto-seed global "${slug}":`, err2));
455
+ }).catch(
456
+ (err2) => console.error(
457
+ `[dyrected/sdk] Failed to auto-seed global "${slug}":`,
458
+ err2
459
+ )
460
+ );
1180
461
  return initialData;
1181
462
  }
1182
463
  throw err;
@@ -1238,11 +519,17 @@ var DyrectedClient = class {
1238
519
  if (!res.ok) {
1239
520
  const body = await res.json().catch(() => ({ message: "Unknown error" }));
1240
521
  if (res.status === 429 && typeof window !== "undefined") {
1241
- window.dispatchEvent(new CustomEvent("dyrected:rate-limit", {
1242
- detail: { message: body.message, code: body.code }
1243
- }));
522
+ window.dispatchEvent(
523
+ new CustomEvent("dyrected:rate-limit", {
524
+ detail: { message: body.message, code: body.code }
525
+ })
526
+ );
1244
527
  }
1245
- throw new DyrectedError(body.message || `Request failed with status ${res.status}`, res.status, body.code);
528
+ throw new DyrectedError(
529
+ body.message || `Request failed with status ${res.status}`,
530
+ res.status,
531
+ body.code
532
+ );
1246
533
  }
1247
534
  return res.json();
1248
535
  }
@@ -1256,7 +543,5 @@ function createClient(config) {
1256
543
  0 && (module.exports = {
1257
544
  DyrectedClient,
1258
545
  DyrectedError,
1259
- createClient,
1260
- generateAIPrompt,
1261
- generateFreshSetupPrompt
546
+ createClient
1262
547
  });