@dyrected/core 2.0.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import {
2
+ createDyrectedApp,
2
3
  normalizeConfig
3
- } from "./chunk-GM4WW6IE.js";
4
+ } from "./chunk-GZODLJ3C.js";
4
5
 
5
6
  // src/utils/setup-prompt.ts
6
7
  function buildEnvironmentSection(frameworkLabel, isSelfHosted, config) {
@@ -10,41 +11,75 @@ function buildEnvironmentSection(frameworkLabel, isSelfHosted, config) {
10
11
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
11
12
  1. ENVIRONMENT
12
13
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
13
- - Framework : ${frameworkLabel}
14
+ - Framework : ${frameworkLabel || "Detect it"}
14
15
  - Host Type : ${isSelfHosted ? "Self-Hosted (Local/Private Server)" : "Managed (Dyrected Cloud)"}
15
16
  - API Base : ${config.baseUrl || "http://localhost:3000"}
16
17
  ${credentialLines}`;
17
18
  }
18
- function buildDiagnosticSection(existingSite) {
19
- if (existingSite) {
20
- return `
21
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
22
- 2. PHASE 0 \u2014 DIAGNOSTIC (EXISTING SITE)
23
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
24
- Before writing any code, scan the existing codebase and report:
25
- - All hardcoded text strings that should be CMS-managed
26
- - All repeated data structures that should become collections
27
- - All static pages that marketing will want to edit independently
28
- - Any existing fetch or API calls that overlap with Dyrected
29
-
30
- Then propose a backup plan: extract all current content into a
31
- migration/ folder as structured .md or .json files BEFORE
32
- modifying any code.
33
-
34
- Do NOT write any implementation code until you have reported
35
- your findings and the user has confirmed the content plan.`;
36
- }
19
+ function buildDiagnosticSection() {
37
20
  return `
38
21
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
39
- 2. PHASE 0 \u2014 DISCOVERY (NEW SITE)
22
+ 2. PHASE 0 \u2014 DISCOVERY
40
23
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
41
- This is a new project. Before designing the content model, ask:
42
- - "What are your core content types? (e.g. Services, Team, Blog, Projects)"
43
- - "Which pages should marketing manage independently with blocks?"
44
- - "Are there any pages that must remain static and never become CMS-managed?"
45
- - "What is the primary goal of the site \u2014 marketing, e-commerce, portfolio, SaaS?"
46
-
47
- Do NOT write any code until the user has answered these questions.`;
24
+ Before writing any code, you MUST ask the user these questions.
25
+ Write them exactly as shown below \u2014 in plain language with examples.
26
+ Wait for the user to answer ALL of them before proceeding.
27
+
28
+ \u2500\u2500\u2500 QUESTION 1 \u2500\u2500\u2500
29
+ Ask:
30
+ "Do you already have a website built, or are we starting fresh?
31
+
32
+ \u2192 Already built: share your project or describe what pages you have
33
+ (e.g. Home, About, Services, Contact)
34
+ \u2192 Starting fresh: just say 'new site' and describe what the site is for"
35
+
36
+ \u2500\u2500\u2500 QUESTION 2 \u2500\u2500\u2500
37
+ Ask:
38
+ "What kind of content will your client need to update regularly?
39
+
40
+ Here are some examples to help you think:
41
+ \u2192 Blog posts or news articles
42
+ \u2192 Team member profiles (name, photo, bio)
43
+ \u2192 Services or product descriptions
44
+ \u2192 Testimonials or reviews
45
+ \u2192 Event listings
46
+ \u2192 FAQs
47
+ \u2192 Homepage text (headline, hero image, call-to-action button)
48
+
49
+ Just list the ones that apply. You can say things like:
50
+ 'They need to update blog posts and team members'"
51
+
52
+ \u2500\u2500\u2500 QUESTION 3 \u2500\u2500\u2500
53
+ Ask:
54
+ "Are there any pages on the site that should NEVER change \u2014 like a
55
+ custom-coded page you want to leave exactly as it is?
56
+
57
+ Example answer: 'The homepage has a custom animation, leave that alone.
58
+ Everything else can be managed from the CMS.'
59
+
60
+ If everything should be manageable, just say 'all pages'"
61
+
62
+ \u2500\u2500\u2500 QUESTION 4 \u2500\u2500\u2500
63
+ Ask:
64
+ "What is this website for? Pick the closest description:
65
+
66
+ A) A business or agency marketing site (show services, get leads)
67
+ B) A blog or content site (publish articles regularly)
68
+ C) A SaaS or product site (landing page, pricing, features)
69
+ D) A portfolio (show work, case studies)
70
+ E) An e-commerce or product catalogue
71
+ F) Something else \u2014 describe it in one sentence"
72
+
73
+ \u2500\u2500\u2500 IF EXISTING SITE \u2500\u2500\u2500
74
+ If the user confirms they have an existing site, also:
75
+ - Scan the codebase for all hardcoded text that should be CMS-managed
76
+ - Identify repeated data structures that should become collections
77
+ - Propose saving current content to a migration/ folder as .json files
78
+ BEFORE touching any code
79
+ - Report your findings to the user and get confirmation before proceeding
80
+
81
+ Do NOT write any implementation code until all questions are answered
82
+ and the user has confirmed the content plan.`;
48
83
  }
49
84
  function buildConstraintsSection() {
50
85
  return `
@@ -64,7 +99,10 @@ function buildConstraintsSection() {
64
99
  - RESILIENCE : If Dyrected backend is unreachable, fall back to
65
100
  initialData and show stale content \u2014 never an error page.
66
101
  All relationship fields must handle null gracefully.
67
- Every block renderer must have a default fallback case.`;
102
+ Every block renderer must have a default fallback case.
103
+ - AUTO-SEEDING : Use initialData: [...] in CollectionConfig or GlobalConfig
104
+ to automatically populate the database on first run.
105
+ This is great for demo content or default settings.`;
68
106
  }
69
107
  function buildSchemaRulesSection() {
70
108
  return `
@@ -73,8 +111,10 @@ function buildSchemaRulesSection() {
73
111
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
74
112
  - Never drop existing fields from the schema. Mark unused fields as deprecated only.
75
113
  - All new fields must have a defaultValue.
76
- - Never rename a field slug \u2014 add a new field and migrate data separately.
77
- - Run npx @dyrected/cli sync:schema after every config change.`;
114
+ - Use renameTo: 'oldName' to lazily migrate data when renaming fields.
115
+ - Use promoted: true for fields that need high-performance SQL indexing or unique constraints.
116
+ - For Cloud deployments, run npx @dyrected/cli sync:schema after every config change. Self-hosted deployments sync automatically on startup.
117
+ `;
78
118
  }
79
119
  function buildDoNotSection() {
80
120
  return `
@@ -85,7 +125,7 @@ function buildDoNotSection() {
85
125
  - Do NOT add custom auth middleware to the admin route.
86
126
  Dyrected handles admin authentication internally. Do not wrap,
87
127
  protect, or redirect the admin route yourself.
88
- - Do NOT use renderAdminUI in a Nuxt project. Use the DyrectedAdmin
128
+ - Do NOT use renderAdminUI in a Nuxt.js project. Use the DyrectedAdmin
89
129
  component which is auto-imported by @dyrected/nuxt.
90
130
  - Do NOT modify or overwrite existing pages without first preserving their data.
91
131
  - Do NOT drop, rename, or remove fields from an existing schema.
@@ -100,24 +140,29 @@ function buildTechnicalReferenceSection() {
100
140
  Use defineCollection, defineGlobal, and defineConfig from '@dyrected/core'.
101
141
 
102
142
  FIELD TYPES:
103
- - Primitive : text | textarea | richText | number | boolean | date | email | url | json
143
+ - Primitive : text | textarea | richText | number | boolean | date | email | url | json | image
104
144
  - Choice : select | multiSelect (requires options: [{ label, value }])
105
145
  - Structural : array | object (requires nested fields: [...])
106
- - Relation : relationship (requires collection: '<slug>')
146
+ - Relation : relationship (requires relationTo: '<slug>')
107
147
  - Media : relationship to an upload collection (e.g. 'media')
108
148
  - Blocks : blocks (requires blocks: [{ slug, labels, fields }])
109
149
 
110
150
  COLLECTION OPTIONS:
111
151
  - upload: true \u2014 media library with file upload support
112
152
  - auth: true \u2014 adds login/me endpoints; password field is auto-managed
153
+ - audit: true \u2014 enables activity logging
113
154
  - admin.useAsTitle \u2014 field used as display title in admin list view
114
155
  - admin.group \u2014 groups collection under a sidebar heading
115
156
  - admin.hidden \u2014 hides collection from the sidebar (internal/system use)
116
157
 
117
158
  FIELD OPTIONS:
159
+ - label \u2014 user-friendly display name (REQUIRED for all fields)
118
160
  - required \u2014 validation
119
161
  - unique \u2014 database-level uniqueness constraint
162
+ - hasMany \u2014 allow multiple values (for relationship, select, image)
120
163
  - defaultValue \u2014 fallback value (required on all new fields added to existing schemas)
164
+ - promoted \u2014 extracts field to a real SQL column for native indexing (SQL adapters only)
165
+ - renameTo \u2014 name of the old field key to migrate data from (lazy migration)
121
166
  - admin.condition \u2014 Jexl expression string to conditionally show/hide field
122
167
  e.g. "status == \\"published\\""
123
168
  - admin.readOnly \u2014 display only, not editable
@@ -138,14 +183,13 @@ Always include a default case in your switch for unknown block types.
138
183
  COMPLETE SCHEMA EXAMPLE:
139
184
  \`\`\`ts
140
185
  import { defineCollection, defineGlobal, defineConfig } from '@dyrected/core'
141
- import { MongoAdapter } from '@dyrected/db-mongodb'
142
- import { S3StorageAdapter } from '@dyrected/storage-s3'
186
+ import { SqliteAdapter } from '@dyrected/db-sqlite'
143
187
 
144
188
  const media = defineCollection({
145
189
  slug: 'media',
146
190
  upload: true,
147
191
  fields: [
148
- { name: 'alt', type: 'text', label: 'Alt Text' },
192
+ { name: 'alt', label: 'Alt Text', type: 'text' },
149
193
  ],
150
194
  })
151
195
 
@@ -153,44 +197,45 @@ const pages = defineCollection({
153
197
  slug: 'pages',
154
198
  admin: { useAsTitle: 'title', group: 'Content' },
155
199
  fields: [
156
- { name: 'title', type: 'text', required: true },
157
- { name: 'slug', type: 'text', required: true, unique: true },
158
- { name: 'seo', type: 'object', fields: [
159
- { name: 'metaTitle', type: 'text' },
160
- { name: 'metaDescription', type: 'textarea' },
161
- { name: 'ogImage', type: 'relationship', collection: 'media' },
200
+ { name: 'title', label: 'Title', type: 'text', required: true },
201
+ { name: 'slug', label: 'URL Slug', type: 'text', required: true, unique: true },
202
+ { name: 'seo', label: 'SEO Metadata', type: 'object', fields: [
203
+ { name: 'metaTitle', label: 'Meta Title', type: 'text' },
204
+ { name: 'metaDescription', label: 'Meta Description', type: 'textarea' },
205
+ { name: 'ogImage', label: 'OG Image', type: 'relationship', relationTo: 'media' },
162
206
  ]},
163
207
  {
164
208
  name: 'layout',
209
+ label: 'Page Layout',
165
210
  type: 'blocks',
166
211
  blocks: [
167
212
  {
168
213
  slug: 'hero',
169
214
  labels: { singular: 'Hero', plural: 'Heroes' },
170
215
  fields: [
171
- { name: 'heading', type: 'text', required: true },
172
- { name: 'subheading', type: 'textarea' },
173
- { name: 'image', type: 'relationship', collection: 'media' },
174
- { name: 'ctaLabel', type: 'text' },
175
- { name: 'ctaLink', type: 'url' },
216
+ { name: 'heading', label: 'Heading', type: 'text', required: true },
217
+ { name: 'subheading', label: 'Subheading', type: 'textarea' },
218
+ { name: 'image', label: 'Hero Image', type: 'relationship', relationTo: 'media' },
219
+ { name: 'ctaLabel', label: 'Button Label', type: 'text' },
220
+ { name: 'ctaLink', label: 'Button Link', type: 'url' },
176
221
  ],
177
222
  },
178
223
  {
179
224
  slug: 'richContent',
180
225
  labels: { singular: 'Rich Content', plural: 'Rich Content Blocks' },
181
226
  fields: [
182
- { name: 'content', type: 'richText', required: true },
227
+ { name: 'content', label: 'Content', type: 'richText', required: true },
183
228
  ],
184
229
  },
185
230
  {
186
231
  slug: 'callToAction',
187
232
  labels: { singular: 'Call to Action', plural: 'Calls to Action' },
188
233
  fields: [
189
- { name: 'heading', type: 'text', required: true },
190
- { name: 'description', type: 'textarea' },
191
- { name: 'buttonLabel', type: 'text' },
192
- { name: 'buttonLink', type: 'url' },
193
- { name: 'theme', type: 'select', options: [
234
+ { name: 'heading', label: 'Heading', type: 'text', required: true },
235
+ { name: 'description', label: 'Description', type: 'textarea' },
236
+ { name: 'buttonLabel', label: 'Button Text', type: 'text' },
237
+ { name: 'buttonLink', label: 'Button Link', type: 'url' },
238
+ { name: 'theme', label: 'Theme', type: 'select', options: [
194
239
  { label: 'Primary', value: 'primary' },
195
240
  { label: 'Secondary', value: 'secondary' },
196
241
  { label: 'Dark', value: 'dark' },
@@ -206,28 +251,18 @@ const settings = defineGlobal({
206
251
  slug: 'settings',
207
252
  label: 'Site Settings',
208
253
  fields: [
209
- { name: 'siteName', type: 'text' },
210
- { name: 'tagline', type: 'text' },
211
- { name: 'logo', type: 'relationship', collection: 'media' },
212
- { name: 'footerText', type: 'textarea' },
254
+ { name: 'siteName', label: 'Site Name', type: 'text' },
255
+ { name: 'tagline', label: 'Site Tagline', type: 'text' },
256
+ { name: 'logo', label: 'Site Logo', type: 'relationship', relationTo: 'media' },
257
+ { name: 'footerText', label: 'Footer Text', type: 'textarea' },
213
258
  ],
214
259
  })
215
260
 
216
261
  export default defineConfig({
217
262
  collections: [media, pages],
218
- globals: [settings],
219
- db: new MongoAdapter({
220
- url: process.env.DATABASE_URL!,
221
- dbName: 'dyrected_cms',
222
- }),
223
- storage: new S3StorageAdapter({
224
- bucket: process.env.S3_BUCKET!,
225
- region: process.env.S3_REGION!,
226
- credentials: {
227
- accessKeyId: process.env.S3_ACCESS_KEY_ID!,
228
- secretAccessKey: process.env.S3_SECRET_ACCESS_KEY!,
229
- },
230
- }),
263
+ globals: [settings],
264
+ // Use SqliteAdapter for local, PostgresAdapter or MySqlAdapter for production
265
+ db: new SqliteAdapter({ filename: './dyrected.db' }),
231
266
  })
232
267
  \`\`\``;
233
268
  }
@@ -246,7 +281,8 @@ Return your response in exactly this order. Do not combine steps. Do not skip st
246
281
  6. Migration/fallback strategy \u2014 numbered steps
247
282
  7. Schema sync command
248
283
 
249
- API Reference: https://docs.dyrected.com`;
284
+ API Reference: https://docs.dyrected.com
285
+ If you are unsure about any syntax or property, refer to the documentation above.`;
250
286
  }
251
287
  function buildFrameworkSection(activeTab, isSelfHosted, config) {
252
288
  const envPrefix = activeTab === "next" ? "NEXT_PUBLIC_" : activeTab === "nuxt" ? "NUXT_PUBLIC_" : "";
@@ -268,14 +304,14 @@ export const dyrected = createClient({
268
304
  })
269
305
  \`\`\`
270
306
 
271
- 2. Admin Route (app/admin/[[...slug]]/page.tsx):
307
+ 2. Admin Route (app/cms/page.tsx):
272
308
  \`\`\`tsx
273
309
  import { DyrectedAdmin } from '@dyrected/next/admin'
274
310
 
275
311
  export default function AdminPage() {
276
312
  // DyrectedAdmin handles routing, auth, and CSS automatically.
277
313
  // Do NOT wrap this in custom auth middleware.
278
- return <DyrectedAdmin basename="/admin" />
314
+ return <DyrectedAdmin basename="/cms" />
279
315
  }
280
316
  \`\`\`
281
317
 
@@ -300,9 +336,9 @@ export default async function CmsPage({ params }: { params: { slug: string[] } }
300
336
  \`\`\``,
301
337
  nuxt: `
302
338
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
303
- 8. IMPLEMENTATION \u2014 Nuxt
339
+ 8. IMPLEMENTATION \u2014 Nuxt.js
304
340
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
305
- 1. Nuxt Config (nuxt.config.ts):
341
+ 1. Nuxt.js Config (nuxt.config.ts):
306
342
  \`\`\`ts
307
343
  export default defineNuxtConfig({
308
344
  modules: ['@dyrected/nuxt'],
@@ -314,7 +350,7 @@ export default defineNuxtConfig({
314
350
  })
315
351
  \`\`\`
316
352
 
317
- 2. Admin Route (pages/admin.vue):
353
+ 2. Admin Route (pages/cms/index.vue):
318
354
  \`\`\`vue
319
355
  <script setup lang="ts">
320
356
  // DyrectedAdmin is auto-imported by @dyrected/nuxt.
@@ -326,7 +362,7 @@ definePageMeta({ layout: false })
326
362
 
327
363
  <template>
328
364
  <ClientOnly>
329
- <DyrectedAdmin basename="/admin" />
365
+ <DyrectedAdmin basename="/cms" />
330
366
  </ClientOnly>
331
367
  </template>
332
368
  \`\`\`
@@ -373,7 +409,7 @@ export const dyrected = createClient({
373
409
  })
374
410
  \`\`\`
375
411
 
376
- 2. Admin Route (pages/admin.tsx):
412
+ 2. Admin Route (pages/cms.tsx):
377
413
  \`\`\`tsx
378
414
  import { AdminUI } from '@dyrected/admin'
379
415
  import '@dyrected/admin/styles'
@@ -405,7 +441,7 @@ export const dyrected = createClient({
405
441
  })
406
442
  \`\`\`
407
443
 
408
- 2. Admin Route (pages/admin.vue):
444
+ 2. Admin Route (pages/cms.vue):
409
445
  \`\`\`vue
410
446
  <template>
411
447
  <div ref="container" style="height: 100vh" />
@@ -433,14 +469,350 @@ onUnmounted(() => cleanup?.())
433
469
  };
434
470
  return sections[activeTab] || sections.next;
435
471
  }
472
+ function generateFreshSetupPrompt(activeTab, config) {
473
+ const frameworkLabel = activeTab === "next" ? "Next.js" : activeTab === "nuxt" ? "Nuxt.js" : activeTab ? activeTab.charAt(0).toUpperCase() + activeTab.slice(1) : "the project's detected framework";
474
+ const isSelfHosted = config.isSelfHosted === true || !config.apiKey && !config.siteId;
475
+ const frameworkSetup = {
476
+ nuxt: `
477
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
478
+ 3. INSTALLATION STEPS \u2014 Run these in order
479
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
480
+ Tell the user to follow these steps exactly. Explain each one in plain
481
+ language before showing the command. Do not skip any step.
482
+
483
+ STEP 1 \u2014 Initialize Dyrected
484
+ Tell the user:
485
+ "Run this command in your terminal inside your Nuxt.js project folder.
486
+ It will set everything up for you automatically."
487
+
488
+ \`\`\`bash
489
+ npx @dyrected/cli init
490
+ \`\`\`
491
+
492
+ When it asks questions, tell the user to:
493
+ - Choose: Nuxt.js 3
494
+ - Choose: SQLite (easiest option, no extra setup needed)
495
+
496
+ The CLI will automatically:
497
+ - Install all required packages
498
+ - Create a dyrected.config.ts file
499
+ - Mount the Admin UI at pages/cms/index.vue
500
+ - Generate a .env.example file
501
+
502
+ STEP 2 \u2014 Register the module
503
+ Tell the user to open nuxt.config.ts and add '@dyrected/nuxt' to modules:
504
+
505
+ \`\`\`ts
506
+ export default defineNuxtConfig({
507
+ modules: ['@dyrected/nuxt'],
508
+ })
509
+ \`\`\`
510
+
511
+ STEP 3 \u2014 Set up environment variables
512
+ Tell the user:
513
+ "Find the file called .env.example in your project.
514
+ Make a copy of it and rename the copy to .env
515
+ Then open .env and fill in the values."
516
+
517
+ STEP 4 \u2014 Start the project
518
+ \`\`\`bash
519
+ pnpm dev
520
+ \`\`\`
521
+
522
+ STEP 5 \u2014 Open the dashboard
523
+ Tell the user to open their browser and go to:
524
+ http://localhost:3000/cms
525
+
526
+ If they see the Dyrected admin dashboard, the installation worked.
527
+ Tell them: "You are ready. Now let's set up your content."`,
528
+ next: `
529
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
530
+ 3. INSTALLATION STEPS \u2014 Run these in order
531
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
532
+ Tell the user to follow these steps exactly. Explain each one in plain
533
+ language before showing the command. Do not skip any step.
534
+
535
+ STEP 1 \u2014 Initialize Dyrected
536
+ Tell the user:
537
+ "Run this command in your terminal inside your Next.js project folder."
538
+
539
+ \`\`\`bash
540
+ npx @dyrected/cli init
541
+ \`\`\`
542
+
543
+ When it asks questions, tell the user to:
544
+ - Choose: Next.js
545
+ - Choose: SQLite (easiest option, no extra setup needed)
546
+
547
+ The CLI will automatically:
548
+ - Install all required packages
549
+ - Create a dyrected.config.ts file
550
+ - Mount the Admin UI at app/cms/page.tsx
551
+ - Generate a .env.example file
552
+
553
+ STEP 2 \u2014 Set up environment variables
554
+ Tell the user:
555
+ "Find the file called .env.example in your project.
556
+ Make a copy of it and rename the copy to .env.local
557
+ Then open .env.local and fill in the values."
558
+
559
+ STEP 3 \u2014 Start the project
560
+ \`\`\`bash
561
+ pnpm dev
562
+ \`\`\`
563
+
564
+ STEP 4 \u2014 Open the dashboard
565
+ Tell the user to open their browser and go to:
566
+ http://localhost:3000/cms
567
+
568
+ If they see the Dyrected admin dashboard, the installation worked.
569
+ Tell them: "You are ready. Now let's set up your content."`,
570
+ react: `
571
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
572
+ 3. INSTALLATION STEPS \u2014 Run these in order
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
+ STEP 1 \u2014 Install the SDK and admin packages
575
+ \`\`\`bash
576
+ npm install @dyrected/sdk @dyrected/admin
577
+ \`\`\`
578
+
579
+ STEP 2 \u2014 Set up the client (lib/dyrected.ts):
580
+ \`\`\`ts
581
+ import { createClient } from '@dyrected/sdk'
582
+
583
+ export const dyrected = createClient({
584
+ baseUrl: '${config.baseUrl || "https://api.dyrected.cloud"}',${isSelfHosted ? "" : `
585
+ apiKey: '${config.apiKey}',
586
+ siteId: '${config.siteId}',`}
587
+ })
588
+ \`\`\`
589
+
590
+ STEP 3 \u2014 Mount the Admin UI (pages/cms.tsx):
591
+ \`\`\`tsx
592
+ import { AdminUI } from '@dyrected/admin'
593
+ import '@dyrected/admin/styles'
594
+
595
+ export default function AdminPage() {
596
+ return (
597
+ <div style={{ height: '100vh' }}>
598
+ <AdminUI
599
+ baseUrl="${config.baseUrl || "https://api.dyrected.cloud"}"${isSelfHosted ? "" : `
600
+ apiKey="${config.apiKey}"
601
+ siteId="${config.siteId}"`}
602
+ />
603
+ </div>
604
+ )
605
+ }
606
+ \`\`\``,
607
+ vue: `
608
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
609
+ 3. INSTALLATION STEPS \u2014 Run these in order
610
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
611
+ STEP 1 \u2014 Install the SDK and admin packages
612
+ \`\`\`bash
613
+ npm install @dyrected/sdk @dyrected/admin
614
+ \`\`\`
615
+
616
+ STEP 2 \u2014 Set up the client (lib/dyrected.ts):
617
+ \`\`\`ts
618
+ import { createClient } from '@dyrected/sdk'
619
+
620
+ export const dyrected = createClient({
621
+ baseUrl: '${config.baseUrl || "https://api.dyrected.cloud"}',${isSelfHosted ? "" : `
622
+ apiKey: '${config.apiKey}',
623
+ siteId: '${config.siteId}',`}
624
+ })
625
+ \`\`\`
626
+
627
+ STEP 3 \u2014 Mount the Admin UI (pages/cms.vue):
628
+ \`\`\`vue
629
+ <template>
630
+ <div ref="container" style="height: 100vh" />
631
+ </template>
632
+
633
+ <script setup>
634
+ import { ref, onMounted, onUnmounted } from 'vue'
635
+ import { renderAdminUI } from '@dyrected/admin'
636
+ import '@dyrected/admin/styles'
637
+
638
+ const container = ref(null)
639
+ let cleanup
640
+
641
+ onMounted(() => {
642
+ cleanup = renderAdminUI(container.value, {
643
+ baseUrl: '${config.baseUrl || "https://api.dyrected.cloud"}',${isSelfHosted ? "" : `
644
+ apiKey: '${config.apiKey}',
645
+ siteId: '${config.siteId}',`}
646
+ })
647
+ })
648
+
649
+ onUnmounted(() => cleanup?.())
650
+ </script>
651
+ \`\`\``
652
+ };
653
+ return [
654
+ `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.
655
+
656
+ Your job is to:
657
+ 1. Understand who you are talking to before giving any instructions
658
+ 2. Ask simple questions about their project and content needs
659
+ 3. Walk them through setup in a way that matches their technical level
660
+ 4. Confirm each step worked before moving to the next
661
+ 5. Help them design their content so their client can manage it
662
+
663
+ Speak in plain language at all times. Never assume technical knowledge.
664
+ Never show more than one step at a time.
665
+ Never mention the terminal, command line, or any commands until you have
666
+ confirmed the user is comfortable running them.`,
667
+ `
668
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
669
+ 1. ENVIRONMENT
670
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
671
+ - Framework : ${frameworkLabel}
672
+ - Host Type : ${isSelfHosted ? "Self-Hosted" : "Dyrected Cloud"}
673
+ - API Base : ${config.baseUrl || "http://localhost:3000"}
674
+ ${isSelfHosted ? "" : `- Site ID : ${config.siteId}
675
+ - API Key : ${config.apiKey}`}`.trim(),
676
+ `
677
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
678
+ 2. PHASE 0 \u2014 UNDERSTAND THE USER FIRST
679
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
680
+ Ask these questions one at a time. Wait for each answer before asking the next.
681
+ Never ask more than one question at a time.
682
+
683
+ \u2500\u2500\u2500 QUESTION 1 \u2014 TECH LEVEL \u2500\u2500\u2500
684
+ This is the most important question. Ask it first.
685
+
686
+ Ask exactly this:
687
+ "Before we dive in \u2014 how would you describe yourself?
688
+
689
+ A) I write code myself (I'm comfortable with the terminal and editing files)
690
+ B) I use tools like Lovable, Bolt, or v0 to build with AI \u2014 I don't write much code myself
691
+ C) I'm a designer or project manager \u2014 someone else usually handles the technical stuff
692
+ D) Something else \u2014 just describe how you work"
693
+
694
+ Use their answer to decide how to guide them for the rest of the setup:
695
+ - If A \u2192 they are TECHNICAL. You may show terminal commands and code directly.
696
+ - If B \u2192 they are SEMI-TECHNICAL. Explain what each step does before showing
697
+ any code or commands. Ask before running anything in the terminal.
698
+ For Lovable users specifically: guide them to use the built-in
699
+ terminal or ask them to paste code into the right file in their editor.
700
+ - If C \u2192 they are NON-TECHNICAL. Do not show terminal commands at all.
701
+ Generate all the code and config for them. Walk them through
702
+ copy-pasting into specific files by name. If a terminal step is
703
+ unavoidable, warn them first and offer to write the exact command
704
+ with a clear explanation of what it does and where to run it.
705
+ - If D \u2192 ask one follow-up question to understand their workflow before
706
+ deciding which path above fits best.
707
+
708
+ \u2500\u2500\u2500 QUESTION 2 \u2014 PROJECT STATUS \u2500\u2500\u2500
709
+ Ask after Q1 is answered:
710
+
711
+ "Do you already have a ${frameworkLabel} project open,
712
+ or are we starting from scratch?
713
+
714
+ \u2192 Already have a project: tell me what the site is about or
715
+ share the folder name
716
+ \u2192 Starting fresh: just say 'new project' and I will help you
717
+ create one first"
718
+
719
+ \u2500\u2500\u2500 QUESTION 3 \u2014 SITE PURPOSE \u2500\u2500\u2500
720
+ Ask after Q2 is answered:
721
+
722
+ "What kind of website is this?
723
+
724
+ A) A business or agency site (show services, get enquiries)
725
+ B) A blog or news site (publish articles regularly)
726
+ C) A SaaS or product landing page (features, pricing, sign up)
727
+ D) A portfolio (show work and past projects)
728
+ E) Something else \u2014 describe it in one sentence"
729
+
730
+ \u2500\u2500\u2500 QUESTION 4 \u2014 CONTENT NEEDS \u2500\u2500\u2500
731
+ Ask after Q3 is answered:
732
+
733
+ "What will your client need to update themselves \u2014 without calling you?
734
+
735
+ Some examples to help you think:
736
+ \u2192 Blog posts or news articles
737
+ \u2192 Team member profiles (name, photo, bio)
738
+ \u2192 Services or product descriptions
739
+ \u2192 Homepage text (headline, buttons, images)
740
+ \u2192 Testimonials or reviews
741
+ \u2192 FAQs
742
+ \u2192 Event listings or announcements
743
+
744
+ Just list the ones that apply. Or say 'not sure yet'
745
+ and we will figure it out together."
746
+
747
+ \u2500\u2500\u2500 AFTER ALL QUESTIONS \u2500\u2500\u2500
748
+ Once all four questions are answered:
749
+ 1. Summarise what you understood in plain English
750
+ 2. Ask the user to confirm before touching any code
751
+ 3. Then move to the installation steps using the correct path
752
+ for their tech level from Question 1`,
753
+ frameworkSetup[activeTab] || frameworkSetup.nuxt,
754
+ `
755
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
756
+ 4. AFTER INSTALLATION \u2014 CONTENT SETUP
757
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
758
+ Once the dashboard is confirmed working, help the user design their
759
+ content model in dyrected.config.ts based on what they told you in
760
+ Phase 0.
761
+
762
+ RULES for this phase:
763
+ - Use defineCollection and defineConfig from '@dyrected/core'
764
+ - Use client.collection(slug) only \u2014 never client.collections
765
+ - Always use initialData in all data fetches
766
+ - Use a catch-all pages collection for marketing-managed pages
767
+ - Use blocks for flexible page layouts
768
+ - Use type: 'relationship', relationTo: 'collectionSlug' for relations
769
+ - Never throw during render \u2014 fall back to initialData on errors
770
+ - All relationship fields must handle null gracefully
771
+
772
+ CONTENT SETUP DELIVERABLES \u2014 in this order:
773
+ 1. dyrected.config.ts \u2014 complete file based on their answers
774
+ 2. Catch-all page route for CMS-managed pages
775
+ 3. Block components list (names and fields only)
776
+ 4. One example fetch showing how to load content on a page
777
+
778
+ After delivering the config, tell the user to sync their schema.
779
+ For TECHNICAL users show the command directly:
780
+ \`\`\`bash
781
+ npx @dyrected/cli sync:schema
782
+ \`\`\`
783
+ For SEMI-TECHNICAL or NON-TECHNICAL users explain it first:
784
+ "This next step tells Dyrected to read your content setup file
785
+ and prepare the database. Here is the command to run:" then show it.
786
+
787
+ Then ask: "Do you want me to help you connect this content to your
788
+ frontend pages now?"
789
+
790
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
791
+ DO NOT
792
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
793
+ - Do NOT show terminal commands before confirming the user's tech level
794
+ - Do NOT assume the user knows what a terminal, CLI, or package manager is
795
+ - Do NOT show all steps at once \u2014 one step at a time always
796
+ - Do NOT use client.collections \u2014 use client.collection(slug)
797
+ - Do NOT add custom auth middleware to the admin route
798
+ - Do NOT use renderAdminUI in a Nuxt.js project
799
+ - Do NOT skip the confirmation after each installation step
800
+ - Do NOT use jargon without explaining it in plain English first
801
+ - Do NOT assume the installation worked \u2014 ask the user to confirm
802
+
803
+ API Reference: https://docs.dyrected.com
804
+ If you are unsure about any syntax, refer to the official documentation above.`
805
+ ].join("\n");
806
+ }
436
807
  function generateAIPrompt(activeTab, config) {
437
- const frameworkLabel = activeTab === "next" ? "Next.js" : activeTab === "nuxt" ? "Nuxt" : activeTab.charAt(0).toUpperCase() + activeTab.slice(1);
808
+ const frameworkLabel = activeTab === "next" ? "Next.js" : activeTab === "nuxt" ? "Nuxt.js" : activeTab ? activeTab.charAt(0).toUpperCase() + activeTab.slice(1) : "the project's detected framework";
438
809
  const isSelfHosted = config.isSelfHosted === true || !config.apiKey && !config.siteId;
439
810
  const existingSite = config.existingSite ?? false;
811
+ 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.`;
440
812
  const sections = [
441
- `You are a Senior Content Architect. Your mission is to integrate Dyrected CMS into a ${frameworkLabel} project. Your priority is DATA PRESERVATION and creating a CMS that empowers marketing teams to move independently without raising tickets to engineering.`,
813
+ missionText,
442
814
  buildEnvironmentSection(frameworkLabel, isSelfHosted, config),
443
- buildDiagnosticSection(existingSite),
815
+ buildDiagnosticSection(),
444
816
  buildConstraintsSection(),
445
817
  buildSchemaRulesSection(),
446
818
  buildDoNotSection(),
@@ -451,6 +823,162 @@ function generateAIPrompt(activeTab, config) {
451
823
  return sections.join("\n");
452
824
  }
453
825
 
826
+ // src/utils/parse-where.ts
827
+ function assertNever(op, context) {
828
+ throw new Error(`[dyrected/core] Unhandled where operator "${op}" in ${context}`);
829
+ }
830
+ function parseSqlWhere(where, getJsonField, placeholder = "?") {
831
+ const params = [];
832
+ let pgIndex = 1;
833
+ function next() {
834
+ return placeholder === "pg" ? `$${pgIndex++}` : "?";
835
+ }
836
+ function col(field) {
837
+ return field === "id" ? "id" : getJsonField(field);
838
+ }
839
+ function buildOperator(field, value) {
840
+ const c = col(field);
841
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
842
+ params.push(value);
843
+ return `${c} = ${next()}`;
844
+ }
845
+ const entries = Object.entries(value);
846
+ if (entries.length !== 1) {
847
+ return entries.map(([op2, operand2]) => buildSingleOp(c, op2, operand2)).join(" AND ");
848
+ }
849
+ const [op, operand] = entries[0];
850
+ return buildSingleOp(c, op, operand);
851
+ }
852
+ function buildSingleOp(c, op, operand) {
853
+ switch (op) {
854
+ case "equals":
855
+ params.push(operand);
856
+ return `${c} = ${next()}`;
857
+ case "not_equals":
858
+ params.push(operand);
859
+ return `${c} != ${next()}`;
860
+ case "in": {
861
+ const vals = Array.isArray(operand) ? operand : [operand];
862
+ if (vals.length === 0) return "1=0";
863
+ const placeholders = vals.map((v) => {
864
+ params.push(v);
865
+ return next();
866
+ });
867
+ return `${c} IN (${placeholders.join(", ")})`;
868
+ }
869
+ case "not_in": {
870
+ const vals = Array.isArray(operand) ? operand : [operand];
871
+ if (vals.length === 0) return "1=1";
872
+ const placeholders = vals.map((v) => {
873
+ params.push(v);
874
+ return next();
875
+ });
876
+ return `${c} NOT IN (${placeholders.join(", ")})`;
877
+ }
878
+ case "gt":
879
+ params.push(operand);
880
+ return `${c} > ${next()}`;
881
+ case "gte":
882
+ params.push(operand);
883
+ return `${c} >= ${next()}`;
884
+ case "lt":
885
+ params.push(operand);
886
+ return `${c} < ${next()}`;
887
+ case "lte":
888
+ params.push(operand);
889
+ return `${c} <= ${next()}`;
890
+ case "contains":
891
+ params.push(`%${operand}%`);
892
+ return `${c} LIKE ${next()}`;
893
+ case "starts_with":
894
+ params.push(`${operand}%`);
895
+ return `${c} LIKE ${next()}`;
896
+ case "exists":
897
+ return operand ? `${c} IS NOT NULL` : `${c} IS NULL`;
898
+ default:
899
+ return assertNever(op, "parseSqlWhere");
900
+ }
901
+ }
902
+ function buildClause(w) {
903
+ const parts = [];
904
+ for (const [field, value] of Object.entries(w)) {
905
+ if (field === "OR" && Array.isArray(value)) {
906
+ const sub = value.map((v) => `(${buildClause(v)})`).join(" OR ");
907
+ parts.push(`(${sub})`);
908
+ } else if (field === "AND" && Array.isArray(value)) {
909
+ const sub = value.map((v) => `(${buildClause(v)})`).join(" AND ");
910
+ parts.push(`(${sub})`);
911
+ } else {
912
+ parts.push(buildOperator(field, value));
913
+ }
914
+ }
915
+ return parts.length ? parts.join(" AND ") : "1=1";
916
+ }
917
+ const sql = buildClause(where);
918
+ return { sql, params };
919
+ }
920
+ function parseMongoWhere(where) {
921
+ function buildOperator(field, value) {
922
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
923
+ return { [field]: { $eq: value } };
924
+ }
925
+ const entries = Object.entries(value);
926
+ if (entries.length !== 1) {
927
+ const merged = {};
928
+ for (const [op2, operand2] of entries) {
929
+ Object.assign(merged, buildSingleOp(field, op2, operand2)[field]);
930
+ }
931
+ return { [field]: merged };
932
+ }
933
+ const [op, operand] = entries[0];
934
+ return buildSingleOp(field, op, operand);
935
+ }
936
+ function buildSingleOp(field, op, operand) {
937
+ switch (op) {
938
+ case "equals":
939
+ return { [field]: { $eq: operand } };
940
+ case "not_equals":
941
+ return { [field]: { $ne: operand } };
942
+ case "in":
943
+ return { [field]: { $in: Array.isArray(operand) ? operand : [operand] } };
944
+ case "not_in":
945
+ return { [field]: { $nin: Array.isArray(operand) ? operand : [operand] } };
946
+ case "gt":
947
+ return { [field]: { $gt: operand } };
948
+ case "gte":
949
+ return { [field]: { $gte: operand } };
950
+ case "lt":
951
+ return { [field]: { $lt: operand } };
952
+ case "lte":
953
+ return { [field]: { $lte: operand } };
954
+ case "contains":
955
+ return { [field]: { $regex: operand, $options: "i" } };
956
+ case "starts_with":
957
+ return { [field]: { $regex: `^${operand}`, $options: "i" } };
958
+ case "exists":
959
+ return { [field]: { $exists: operand } };
960
+ default:
961
+ return assertNever(op, "parseMongoWhere");
962
+ }
963
+ }
964
+ function buildClause(w) {
965
+ const conditions = [];
966
+ for (const [field, value] of Object.entries(w)) {
967
+ if (field === "OR" && Array.isArray(value)) {
968
+ conditions.push({ $or: value.map(buildClause) });
969
+ } else if (field === "AND" && Array.isArray(value)) {
970
+ conditions.push({ $and: value.map(buildClause) });
971
+ } else {
972
+ conditions.push(buildOperator(field, value));
973
+ }
974
+ }
975
+ if (conditions.length === 0) return {};
976
+ if (conditions.length === 1) return conditions[0];
977
+ return { $and: conditions };
978
+ }
979
+ return buildClause(where);
980
+ }
981
+
454
982
  // src/index.ts
455
983
  function defineCollection(config) {
456
984
  return config;
@@ -462,9 +990,13 @@ function defineConfig(config) {
462
990
  return config;
463
991
  }
464
992
  export {
993
+ createDyrectedApp,
465
994
  defineCollection,
466
995
  defineConfig,
467
996
  defineGlobal,
468
997
  generateAIPrompt,
469
- normalizeConfig
998
+ generateFreshSetupPrompt,
999
+ normalizeConfig,
1000
+ parseMongoWhere,
1001
+ parseSqlWhere
470
1002
  };