@rakun-kit/core 1.7.2 → 1.7.3

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.
Files changed (81) hide show
  1. package/README.md +1409 -1394
  2. package/dist/cjs/api/utils/authRateLimit.js +35 -2
  3. package/dist/cjs/api/utils/authRateLimit.js.map +1 -1
  4. package/dist/cjs/collaboration/index.js +7 -1
  5. package/dist/cjs/collaboration/index.js.map +1 -1
  6. package/dist/cjs/collaboration/memory.js +38 -1
  7. package/dist/cjs/collaboration/memory.js.map +1 -1
  8. package/dist/cjs/collaboration/service.js +99 -15
  9. package/dist/cjs/collaboration/service.js.map +1 -1
  10. package/dist/cjs/collaboration/types.js +2 -0
  11. package/dist/cjs/collaboration/types.js.map +1 -1
  12. package/dist/cjs/eventLog/index.js +6 -1
  13. package/dist/cjs/eventLog/index.js.map +1 -1
  14. package/dist/cjs/index.js +28 -6
  15. package/dist/cjs/index.js.map +1 -1
  16. package/dist/cjs/lib/Logger.js +23 -10
  17. package/dist/cjs/lib/Logger.js.map +1 -1
  18. package/dist/cjs/mail/index.js +6 -1
  19. package/dist/cjs/mail/index.js.map +1 -1
  20. package/dist/cjs/media/index.js +8 -3
  21. package/dist/cjs/media/index.js.map +1 -1
  22. package/dist/cjs/orm/database.js +22 -4
  23. package/dist/cjs/orm/database.js.map +1 -1
  24. package/dist/cjs/orm/index.js +5 -1
  25. package/dist/cjs/orm/index.js.map +1 -1
  26. package/dist/cjs/translation/index.js +6 -1
  27. package/dist/cjs/translation/index.js.map +1 -1
  28. package/dist/docs/index.md +577 -558
  29. package/dist/esm/api/utils/authRateLimit.d.ts +1 -0
  30. package/dist/esm/api/utils/authRateLimit.d.ts.map +1 -1
  31. package/dist/esm/api/utils/authRateLimit.js +33 -1
  32. package/dist/esm/api/utils/authRateLimit.js.map +1 -1
  33. package/dist/esm/collaboration/index.d.ts +1 -0
  34. package/dist/esm/collaboration/index.d.ts.map +1 -1
  35. package/dist/esm/collaboration/index.js +5 -0
  36. package/dist/esm/collaboration/index.js.map +1 -1
  37. package/dist/esm/collaboration/memory.d.ts +1 -1
  38. package/dist/esm/collaboration/memory.d.ts.map +1 -1
  39. package/dist/esm/collaboration/memory.js +38 -1
  40. package/dist/esm/collaboration/memory.js.map +1 -1
  41. package/dist/esm/collaboration/service.d.ts +2 -1
  42. package/dist/esm/collaboration/service.d.ts.map +1 -1
  43. package/dist/esm/collaboration/service.js +100 -16
  44. package/dist/esm/collaboration/service.js.map +1 -1
  45. package/dist/esm/collaboration/types.d.ts +4 -0
  46. package/dist/esm/collaboration/types.d.ts.map +1 -1
  47. package/dist/esm/collaboration/types.js +1 -1
  48. package/dist/esm/collaboration/types.js.map +1 -1
  49. package/dist/esm/eventLog/index.d.ts +1 -0
  50. package/dist/esm/eventLog/index.d.ts.map +1 -1
  51. package/dist/esm/eventLog/index.js +4 -0
  52. package/dist/esm/eventLog/index.js.map +1 -1
  53. package/dist/esm/index.d.ts +3 -1
  54. package/dist/esm/index.d.ts.map +1 -1
  55. package/dist/esm/index.js +32 -12
  56. package/dist/esm/index.js.map +1 -1
  57. package/dist/esm/lib/Logger.d.ts +1 -0
  58. package/dist/esm/lib/Logger.d.ts.map +1 -1
  59. package/dist/esm/lib/Logger.js +22 -10
  60. package/dist/esm/lib/Logger.js.map +1 -1
  61. package/dist/esm/mail/index.d.ts +1 -0
  62. package/dist/esm/mail/index.d.ts.map +1 -1
  63. package/dist/esm/mail/index.js +4 -0
  64. package/dist/esm/mail/index.js.map +1 -1
  65. package/dist/esm/media/index.d.ts +1 -0
  66. package/dist/esm/media/index.d.ts.map +1 -1
  67. package/dist/esm/media/index.js +6 -2
  68. package/dist/esm/media/index.js.map +1 -1
  69. package/dist/esm/orm/database.d.ts +3 -1
  70. package/dist/esm/orm/database.d.ts.map +1 -1
  71. package/dist/esm/orm/database.js +22 -4
  72. package/dist/esm/orm/database.js.map +1 -1
  73. package/dist/esm/orm/index.d.ts.map +1 -1
  74. package/dist/esm/orm/index.js +5 -1
  75. package/dist/esm/orm/index.js.map +1 -1
  76. package/dist/esm/translation/index.d.ts +1 -0
  77. package/dist/esm/translation/index.d.ts.map +1 -1
  78. package/dist/esm/translation/index.js +4 -0
  79. package/dist/esm/translation/index.js.map +1 -1
  80. package/dist/tsconfig.build.tsbuildinfo +1 -1
  81. package/package.json +1 -1
@@ -1,559 +1,578 @@
1
- # Rakun AI manual and package index
2
-
3
- This is the entry point for coding agents working on an application that uses
4
- Rakun. It describes the package map, the normal architecture, and the public
5
- surface of `@rakun-kit/core`. Read the manual for every adapter used by the
6
- application before editing its integration.
7
-
8
- In an installed project this file is located at
9
- `node_modules/@rakun-kit/core/dist/docs/index.md`. Other package manuals use the
10
- same `dist/docs/index.md` location.
11
-
12
- ## Package map
13
-
14
- | Package | Use it for | Installed manual |
15
- | ------------------------------- | ----------------------------------------------------------------------------------- | --------------------------------------------------------------- |
16
- | `@rakun-kit/core` | Domain model, bootstrap, operations, auth, routes, media, mail and shared contracts | This file |
17
- | `@rakun-kit/next` | Next.js App Router API, manager, media and web rendering | `node_modules/@rakun-kit/next/dist/docs/index.md` |
18
- | `@rakun-kit/express` | Express API and local-media adapter | `node_modules/@rakun-kit/express/dist/docs/index.md` |
19
- | `@rakun-kit/bun` | Bun server, manager, filesystem modules, SSR and static route generation | `node_modules/@rakun-kit/bun/dist/docs/index.md` |
20
- | `@rakun-kit/trpc` | Typed tRPC router for manager and web operations | `node_modules/@rakun-kit/trpc/dist/docs/index.md` |
21
- | `@rakun-kit/react` | Framework-neutral React module rendering | `node_modules/@rakun-kit/react/dist/docs/index.md` |
22
- | `@rakun-kit/manager-react` | Manager UI, clients, navigation and plugin runtime | `node_modules/@rakun-kit/manager-react/dist/docs/index.md` |
23
- | `@rakun-kit/manager-locales` | Optional manager UI locale packs | `node_modules/@rakun-kit/manager-locales/dist/docs/index.md` |
24
- | `@rakun-kit/s3` | S3 and S3-compatible media storage | `node_modules/@rakun-kit/s3/dist/docs/index.md` |
25
- | `@rakun-kit/smtp` | SMTP mail delivery | `node_modules/@rakun-kit/smtp/dist/docs/index.md` |
26
- | `@rakun-kit/resend` | Resend mail delivery | `node_modules/@rakun-kit/resend/dist/docs/index.md` |
27
- | `@rakun-kit/jsx-email` | Typed JSX Email template rendering | `node_modules/@rakun-kit/jsx-email/dist/docs/index.md` |
28
- | `@rakun-kit/openai` | OpenAI-backed automatic content translation | `node_modules/@rakun-kit/openai/dist/docs/index.md` |
29
- | `@rakun-kit/plugin-code-editor` | Code blocks in manager RichText fields | `node_modules/@rakun-kit/plugin-code-editor/dist/docs/index.md` |
30
- | `create-rakun-app` | Official project generator and framework starter templates | `node_modules/create-rakun-app/dist/docs/index.md` |
31
-
32
- Choose one server adapter (`next`, `express`, or `bun`). Add `trpc` only if the host
33
- wants tRPC, one media adapter if uploads are required, and one mail adapter if
34
- outbound mail is required. `manager-react` is normally consumed through the Bun
35
- or Next adapter; use it directly for custom hosts.
36
-
37
- `preview`, `preview-bun`, and `preview-next` are private example applications in the Rakun
38
- repository, not installable packages, so they do not have installed manuals.
39
-
40
- ## Typical architecture
41
-
42
- 1. Define literals, content types, routes and any custom operations in
43
- framework-neutral server modules.
44
- 2. Call `rakunBootstrap` once, or pass the same options to an adapter that calls
45
- `ensureRakunBootstrap`.
46
- 3. Mount the API with `@rakun-kit/next` or `@rakun-kit/express`.
47
- 4. Render the manager and web modules with the corresponding React integration.
48
- 5. Keep browser code away from server secrets and server-only package entries.
49
-
50
- ## Install and peer requirements
51
-
52
- ```sh
53
- bun add @rakun-kit/core mongodb ffmpeg-static zod
54
- # Node.js image optimization only
55
- bun add sharp
56
- ```
57
-
58
- `mongodb` and `ffmpeg-static` are peer dependencies. `bcrypt` is only needed by
59
- Node runtimes; Bun uses its native password implementation. `sharp` is an
60
- optional peer used by the default Node.js image processor and as the fallback
61
- for Bun versions without `Bun.Image`. Adapters and optional services are
62
- separate packages.
63
-
64
- ## Bootstrap
65
-
66
- ```ts
67
- import { ContentType, f, rakunBootstrap } from '@rakun-kit/core'
68
-
69
- const Article = new ContentType({
70
- name: 'Article',
71
- menu: { title: 'Articles', icon: 'newspaper', category: 'Content' },
72
- fields: {
73
- title: f.string(),
74
- slug: f.string().type('Slug'),
75
- body: f.string().type('RichText').optional(),
76
- },
77
- uniques: [['slug']],
78
- listFields: ['title', 'slug'],
79
- })
80
-
81
- rakunBootstrap({
82
- literals,
83
- contentTypes: [Article],
84
- routes,
85
- mongo: {
86
- MONGO_URI: process.env.MONGO_URI!,
87
- ENVIRONMENT: process.env.NODE_ENV === 'test' ? 'test' : 'production',
88
- },
89
- })
90
- ```
91
-
92
- Important bootstrap options are `literals`, `contentTypes`,
93
- `internalContentTypes`, `routes`, `apiOperations`, `plugins`, `mongo`, `media`,
94
- `mail`, `translation`, `collaboration`, `managerLanguages`, `login`,
95
- `accountRecovery`, `logger` and `syncRoutes`.
96
-
97
- - `rakunBootstrap(options)` registers configuration synchronously.
98
- - `ensureRakunBootstrap(options)` registers it only if needed. Framework
99
- adapters use this to tolerate development reloads.
100
- - `ensureRakunInitialized()` initializes logger, MongoDB, media and route sync.
101
- It is concurrency-safe and retries after a failed initialization.
102
-
103
- Do not import React, Next, Express, Vite or tRPC into core configuration modules.
104
-
105
- ## Runtime platform
106
-
107
- Core domain code consumes a resolved `Platform` instead of importing runtime
108
- image, crypto, filesystem, compression, or worker APIs. Omit `platform` to
109
- detect Node.js or Bun and apply standalone defaults, or compose capabilities:
110
-
111
- ```ts
112
- import { createPlatform, pollingRealtime, sharpImage } from '@rakun-kit/core'
113
-
114
- rakunBootstrap({
115
- // ...normal Rakun options
116
- platform: createPlatform({
117
- deployment: 'serverless',
118
- image: sharpImage(),
119
- realtime: pollingRealtime({ intervalMs: 5_000 }),
120
- }),
121
- })
122
- ```
123
-
124
- Runtime, framework, and deployment remain independent dimensions. Runtime is
125
- detected from `process.versions.bun`. Bun uses native `Bun.Image` when present;
126
- Node.js and older Bun versions use `sharp`. The Bun adapter also falls back to
127
- `sharp` when an OS-dependent codec cannot decode or encode the requested
128
- format, so install the optional peer when formats such as AVIF must work across
129
- all Bun hosts. `createPlatform` supplies every other default, so overrides
130
- never require listing unrelated capabilities.
131
-
132
- Every custom `ImageProcessor` implements `metadata`, `transform`, and
133
- `placeholder`. `placeholder` returns `{ dataUrl, mime }` for direct storage as
134
- an inline LQIP. The Bun adapter uses the native `Bun.Image.placeholder()` data
135
- URL, while the Sharp adapter applies the requested reduced transform and
136
- encodes its result as a data URL. Bun falls back to that Sharp path when its
137
- runtime lacks the placeholder method or its native codec reports an unsupported
138
- format or encode failure.
139
-
140
- Realtime synchronization uses `pollingRealtime` or `sseRealtime`, both of
141
- which expose transport metadata and a topic subscription contract. Polling is
142
- the conservative default. `@rakun-kit/next` and `@rakun-kit/express`
143
- automatically serve a configured SSE endpoint.
144
- `sseRealtime()` defaults to `/realtime` relative to the adapter's API mount;
145
- set `endpoint` only for a custom public route.
146
-
147
- Custom server adapters can compose the framework-neutral helpers exported by
148
- core: `parseRealtimeTopics`, `isRealtimeEndpointRequest`,
149
- `createRealtimeSseStream`, and `authorizeRealtimeSubscription`. The host
150
- remains responsible only for mapping its request/response APIs to those
151
- primitives.
152
-
153
- The built-in SSE provider keeps topic listeners in the current process.
154
- Replicated deployments must supply a shared-broker `RealtimeProvider` that
155
- implements the same `metadata`, `subscribe`, and `publish` contract.
156
-
157
- `manager.uiLocales` exposes the provider's safe transport metadata so the
158
- manager selects the same transport automatically. Core publishes stable
159
- invalidation topics after collaboration updates and mutations affecting locale
160
- variants, versions, comments, or notifications. Topic builders are exported
161
- from `@rakun-kit/core/client`; use those helpers on both sides of custom
162
- integrations instead of duplicating topic strings.
163
-
164
- ## Collaborative content working state
165
-
166
- Saved content documents have a Yjs working document identified by content type
167
- and document `_id`. This includes published documents and every independent
168
- draft/version. Incremental edits converge in that working document; they do not
169
- update the normal MongoDB content record, rebuild routes, or run `revalidate`.
170
- The public web API therefore continues to read the last saved JSON snapshot.
171
-
172
- `manager.contentCollaboration.sync` exchanges state vectors and incremental
173
- updates over the normal authenticated HTTP operation transport. It is
174
- idempotent protocol traffic and is not a business mutation.
175
- `manager.contentCollaboration.save` is the explicit commit boundary: core
176
- materializes the server-side `Y.Doc`, validates it with the normal
177
- `ContentType`, persists the snapshot through the existing update flow, and then
178
- runs revalidation. Promoting a draft remains a separate operation and consumes
179
- its saved snapshot.
180
-
181
- `manager.contentCollaboration.discard` replaces the shared working document
182
- with the last persisted content snapshot and advances its saved state vector,
183
- so every connected editor converges on the restored state without writing a
184
- new content revision. It is a mutation because it intentionally removes shared
185
- working changes.
186
-
187
- Shared content templates use the same protocol through
188
- `manager.templateCollaboration.sync` and
189
- `manager.templateCollaboration.save`. Their room is keyed only by content type,
190
- so editors working from different documents still see the same template. The
191
- save operation validates and persists it through the normal template update
192
- flow. `manager.templateCollaboration.discard` provides the equivalent reset to
193
- the last persisted shared template. Template state never becomes part of an
194
- individual content document's room.
195
-
196
- The React manager combines this server protocol with per-user IndexedDB
197
- persistence. Cached Yjs rooms can be opened and edited during a temporary API
198
- outage and upload their missing updates after reconnection. This local cache is
199
- working state only: the authenticated server Save remains the persistence
200
- boundary for MongoDB and public reads.
201
-
202
- Core uses a process-memory collaboration adapter by default. This preserves
203
- unsaved changes across manager navigation while that API process lives. A
204
- multi-process or restart-durable deployment must pass a shared adapter through
205
- `rakunBootstrap({ collaboration: { adapter } })`. An adapter stores opaque Yjs
206
- updates and saved state vectors; it must not write them into the public content
207
- document. `createMemoryCollaborationAdapter` is exported as the reference
208
- implementation.
209
-
210
- Collaboration sync also carries ephemeral presence for each open browser tab.
211
- Presence reports the authenticated manager user and the currently focused
212
- field, expires automatically, and is never included in the saved Yjs snapshot
213
- or persistent event log. Custom shared adapters may implement the optional
214
- `loadPresence` and `savePresence` methods to share this state across replicated
215
- API processes; adapters without them keep presence local to one process.
216
- On SSE deployments, core authorizes the collaboration room independently,
217
- binds the tab presence to that stream, renews it from server heartbeats, and
218
- removes it when the tab's last stream closes. This avoids client-side periodic
219
- sync calls whose only purpose is presence. Polling uses its existing sync cycle.
220
-
221
- This feature is scoped to saved content edit screens and their shared Template
222
- editor. Create-form content becomes collaborative after its first save; its
223
- shared Template is already collaborative because it exists independently of
224
- that new document. Settings, route-layout overrides, literals, users, and other
225
- administrative forms keep their existing local form state. Automatic content
226
- translation updates the shared working document and still requires Save before
227
- it changes the stored snapshot.
228
-
229
- ## Content types and fields
230
-
231
- `ContentType` describes a collection and derives its validation and TypeScript
232
- shapes. Use `f` factories rather than handwritten storage schemas.
233
- `Fields` remains available as a backward-compatible alias and references the
234
- same object, but new code and examples should prefer `f`.
235
-
236
- Common factories include string, number, boolean, date, object, relation, file,
237
- blocks and array/list forms. Fields are required by default; chain `.optional()`
238
- when a value may be omitted. `.required()` remains available as an
239
- explicit, backward-compatible modifier. Other modifiers include `.multiple()`,
240
- `.managerOnly()`, `.apiOnly()` and `.noDynamic()` when applicable.
241
- Special manager editors are selected with `.type(...)`, for example `Slug`,
242
- `RichText`, `Email`, `Password` and `Image`.
243
-
244
- Plugin field factories created with `createPluginField` must declare
245
- serializable `meta.capabilities`. `valueKind` (`string`, `richText`, `number`,
246
- `boolean`, `date`, `object`, `array`, or `unknown`) drives Dynamic Data type
247
- compatibility. Under `dynamic`, `properties` exposes named leaf paths,
248
- `mapProperties` allows those paths to be mapped independently, `relation`
249
- enables content-type traversal, and `collection` enables homogeneous or
250
- heterogeneous per-item mapping. This lets custom fields participate without
251
- adding their names to manager or core type switches.
252
-
253
- Custom server resolution belongs in the optional `runtime.populate` callback.
254
- It receives `{ db, populate, populateLink }`, so a composite field can recurse
255
- through nested fields or reuse Rakun's localized link resolver. Runtime hooks
256
- are not sent to the manager. The generic web-output phase is exported as
257
- `populateFields`; the former `populateLinks` name remains as a deprecated alias.
258
-
259
- Homogeneous `f.array(...)` fields accept `.min(count)` and `.max(count)` item
260
- limits. These methods are also available after `.multiple()` on relation, file,
261
- select, and content-reference fields, for example
262
- `f.file().multiple().min(1).max(4)`. Core enforces them on input, stored data and
263
- output schemas, and exposes `minItems`/`maxItems` to the manager.
264
-
265
- Routeable content types can initialize SEO string fields from the document being
266
- created. Add `.seo('<seoField>')` to a source string field; the argument is
267
- restricted to string properties of Rakun's built-in SEO model. For example,
268
- `title: f.string().required().seo('title')` initializes `seo.title` as a dynamic
269
- data binding to the document's `title`. This only initializes create forms: it
270
- does not modify existing documents, and editors can change or clear the binding
271
- before saving.
272
-
273
- Date fields support the `Date`, `DateTime` and `Time` manager modes. `Date` and
274
- `DateTime` persist JavaScript `Date` values; their write schema also accepts the
275
- ISO datetime strings produced when those values cross JSON and normalizes them
276
- back to `Date`. `Time` stores an ISO time string.
277
-
278
- The `f.link()` manager editor stores a direct `{ href, title }` value or an
279
- internal `{ routeId, contentTypeId, title }` reference. Web output for these
280
- values is `{ href, title }`, with internal `href` values localized by route.
281
- `DataInput` and `DataFront` use those object shapes. Persisted legacy URL strings
282
- remain readable, then receive an empty `title` when loaded in the manager or
283
- normalized for web output. Dynamic data exposes link source properties as
284
- `<field>.href` and `<field>.title`; list mapping targets expose those same two
285
- paths independently and reconstruct the link object from their mapped values.
286
-
287
- Use `f.menu()` for header and navigation trees. Its value is an ordered array
288
- of link nodes with recursive `children`: direct nodes persist
289
- `{ href, title, children }`, while internal nodes persist
290
- `{ routeId, contentTypeId, title, children }`. Core validates every level and
291
- recursively resolves internal nodes for web output as
292
- `{ href, title, children }`. The manager can reorder and nest nodes with drag
293
- and drop; there is no fixed nesting-depth limit.
294
-
295
- `f.breadcrums()` declares a computed, API-only field intended for modules such
296
- as heroes. When the module is rendered in a routable page, the field returns
297
- `{ label, href }[]` from the highest route ancestor through the current page,
298
- using localized route labels and paths. It returns `null` outside routable page
299
- output. The field is part of `DataFront`, but not `DataInput` or `DBOutput`, and
300
- does not appear in the manager.
301
-
302
- Use `iterator` for ordered page modules. Rakun stores it as the reserved
303
- `_iterator` field and edits it in the manager's Content tab. When the content
304
- type has a `hasPage: true` route, Rakun automatically enables a separate shared
305
- Template composition using the same module list as `iterator`. The Template
306
- editor makes a special Content slot available at the root and inside
307
- `f.blocks(...)`. A valid template contains exactly one such slot; web and
308
- preview output replace it with the current document's iterator.
309
-
310
- ```ts
311
- import { StructuredData } from '@rakun-kit/core/internal-content-types'
312
-
313
- const Page = new ContentType({
314
- name: 'Page',
315
- fields: {
316
- title: f.string().required(),
317
- slug: f.string().type('Slug').required(),
318
- },
319
- iterator: [
320
- { contentType: UseCaseSection, type: 'new' },
321
- { contentType: Hero, type: 'new' },
322
- { contentType: LayoutWithInfo, type: 'new' },
323
- { contentType: Newsletter, type: 'new' },
324
- { contentType: StructuredData, type: 'new' },
325
- ],
326
- })
327
- ```
328
-
329
- `StructuredData` is Rakun's native JSON-LD module. Add it to an iterator to make
330
- it available in Content and shared Templates. Its Product, Article,
331
- Organization, WebSite, and BreadcrumbList variants expose typed fields; Custom
332
- accepts an arbitrary JSON object. Template fields can use the normal dynamic
333
- data mappings, so one module can describe every document of a routeable content
334
- type. Official React and Next renderers emit the corresponding safe
335
- `application/ld+json` script without an application module file.
336
-
337
- Call `.help(text)` on a field to expose optional guidance from a compact help
338
- icon in the manager. The metadata survives other field modifiers and nested
339
- relation encoding. Pass either direct copy or a manager translation key. Use
340
- `.description(...)` for short guidance that should remain visible below the
341
- field label.
342
-
343
- For example, place Content inside an otherwise empty
344
- `LayoutWithInfo.fields.blocks` list to render the document-specific sections
345
- inside that shared wrapper. Route `layout` modules such as header and footer
346
- remain outside the assembled template.
347
-
348
- Content type names, field names and reserved fields are persisted API. Do not
349
- rename them without a data migration and a review of manager and web consumers.
350
- Use `.withHooks(...)` for lifecycle behavior; keep mutations and secrets out of
351
- `onGet` transformations unless that behavior is explicitly intended.
352
-
353
- Dynamic data list mappings may map a target `blocks`, link-array, or
354
- relation-array field recursively. A map entry with `kind: 'list'` contains the
355
- same `contentType`, optional `source` or `query`, `itemName`, and `map` shape as
356
- a top-level list binding. In its query,
357
- `{ $current: 'path' }` resolves against the parent source item and
358
- `{ $document: 'path' }` resolves against the root document. The manager exposes
359
- these values as `Current item` and `Current document`, respectively, supporting
360
- flows such as Category -> gallery item -> related Project -> nested image card.
361
- Source and target dynamic-field rules are enforced at every level.
362
-
363
- Homogeneous link and relation arrays also accept per-item list mappings, at the
364
- top level or recursively. This includes `f.relation(...).multiple()`, which is
365
- the relation-array shorthand. Link items map `title` and `href`; relation items
366
- map fields from their target content type. These arrays resolve to flat items,
367
- while `f.blocks(...)` remains heterogeneous and resolves each item as
368
- `{ name, value }`. A structured array may still use a direct whole-field binding.
369
-
370
- ## Routes and web output
371
-
372
- Routes connect a content type field to a public URL. A page route may also have
373
- a fixed `layout` with module slots and a `{ type: 'content' }` insertion point.
374
- When a content type has a `hasPage: true` route, core adds optional `_seo`
375
- metadata and can expose generated `href` values to dynamic data.
376
-
377
- The `web.page` and `web.previewPage` responses expose modules only through their
378
- required `layout` array. Document modules are in the `modules` property of the
379
- `{ type: 'content' }` item; there is no duplicate top-level `modules` property.
380
- Not-found responses use the same shape, while redirects return an empty layout.
381
-
382
- The optional `info` object contains normal resolved page fields and route context
383
- such as `locale` and `variantGroupId`. Translated website literals live in the
384
- separate top-level `literals` property. Core removes the composition-only
385
- `_iterator`, `_seo`, and legacy `_iteratorUnlinked` fields recursively,
386
- including from populated child relations, so nested page modules and SEO data
387
- are not duplicated in `info`.
388
-
389
- Use `@rakun-kit/core/web` for framework-neutral web types/utilities. Use the
390
- Next or React manual for actual rendering. Route changes can affect configured
391
- redirects, sitemap, robots output, preview, and adapter behavior.
392
-
393
- The built-in `LlmsSettings` record backs an optional site-level `llms.txt`.
394
- Editors curate a title, summary, free-form guidance, ordered sections, and
395
- internal or external links in Manager Settings. Core renders the Markdown via
396
- the public `web.llms` query; `getRakunWebLlmsTxt` provides the equivalent
397
- server-only call. It resolves internal links for the requested language, uses
398
- SEO `siteUrl` to make links absolute, and falls back to SEO `siteName` and the
399
- default SEO description when title or summary is omitted. Disabled or unusable
400
- configuration returns `null`. Only explicitly selected resources are included;
401
- internal links without a public route are omitted while their section heading
402
- remains. In Manager, routeable content types with document
403
- visibility expose only published documents in this picker. Entries marked
404
- optional are grouped under the conventional `## Optional` heading and retain
405
- their section titles as third-level headings.
406
-
407
- The authenticated `manager.localeVariants.list` result exposes `path` on a
408
- language assignment only when its exact document is published and has a route
409
- map. This is the supported manager-facing way to build a contextual View page
410
- action; do not query hidden `RouteMap` records from manager UI code.
411
-
412
- For routeable content types with document visibility, only explicit
413
- `published` and `hidden` values are eligible for the route map or public page
414
- resolution. A legacy document with no `_visibility` is treated as a draft;
415
- this fail-closed check also prevents an obsolete `RouteMap` entry from exposing
416
- its content.
417
-
418
- The public `web.staticPaths` query returns `{ path, ttl }` entries only for
419
- route-map records whose configured page route has `dynamic: false`. Framework
420
- adapters use it to generate static params and choose an ISR lifetime. A
421
- monolithic server adapter may instead call the server-only
422
- `getRakunWebStaticPaths`, `getRakunWebPage`, and `getRakunWebPreviewPage`
423
- exports after bootstrap and initialization, avoiding an HTTP request to itself
424
- during a framework build.
425
-
426
- Set `revalidate: { url, token }` in bootstrap to notify the web host after a
427
- manager mutation changes a public path. Core sends an authenticated `POST`
428
- containing `{ path }`; the host adapter is responsible for invalidating its
429
- page and data caches.
430
-
431
- ## Custom operations
432
-
433
- Define public API behavior with the operation helpers and Zod input/output
434
- schemas. Register the map through `apiOperations` so adapters and typed clients
435
- share one contract.
436
-
437
- ```ts
438
- import { defineOperation } from '@rakun-kit/core'
439
- import { z } from 'zod'
440
-
441
- export const apiOperations = {
442
- 'demo.hello': defineOperation({
443
- access: 'public',
444
- kind: 'query',
445
- method: 'get',
446
- input: z.object({ name: z.string() }),
447
- output: z.object({ message: z.string() }),
448
- resolve: ({ input }) => ({ message: `Hello ${input.name}` }),
449
- }),
450
- }
451
- ```
452
-
453
- Keep operations in server modules. Client code should import the operation map
454
- with `import type` and use the typed client supplied by its adapter.
455
-
456
- Every API error, including expected 4xx errors, must reach Rakun's persistent
457
- event log without persisting payloads, credentials, sensitive causes or raw
458
- internal error messages. Successful mutations already emit
459
- `<operation>.succeeded`; add explicit domain events for meaningful phases,
460
- external effects or partial outcomes. Queries are not logged by default.
461
-
462
- ## Request context, auth and permissions
463
-
464
- Adapters create a Rakun request context from headers, cookies and their response
465
- abstraction. Prefer existing helpers such as `getUser`, `checkPermissions` and
466
- `checkOwnership`; do not duplicate authorization rules in application code.
467
-
468
- External manager login adapters are configured under `login.adapters`.
469
- Provider emails must be verified and match an existing manager user; Rakun does
470
- not provision users automatically. Password login can use persistent IP
471
- blocking. Account recovery requires both a mail service and
472
- `accountRecovery.passwordReset` configuration.
473
-
474
- ## Media, mail and translation
475
-
476
- Core defines service contracts; concrete integrations are separate:
477
-
478
- - Media: use `@rakun-kit/s3`, or local media from the selected framework
479
- adapter. Configure the result as `media` in bootstrap.
480
- - Storage adapters may expose `getPublicObject`; Next.js, Express and Bun then
1
+ # Rakun AI manual and package index
2
+
3
+ This is the entry point for coding agents working on an application that uses
4
+ Rakun. It describes the package map, the normal architecture, and the public
5
+ surface of `@rakun-kit/core`. Read the manual for every adapter used by the
6
+ application before editing its integration.
7
+
8
+ In an installed project this file is located at
9
+ `node_modules/@rakun-kit/core/dist/docs/index.md`. Other package manuals use the
10
+ same `dist/docs/index.md` location.
11
+
12
+ ## Package map
13
+
14
+ | Package | Use it for | Installed manual |
15
+ | ------------------------------- | ----------------------------------------------------------------------------------- | --------------------------------------------------------------- |
16
+ | `@rakun-kit/core` | Domain model, bootstrap, operations, auth, routes, media, mail and shared contracts | This file |
17
+ | `@rakun-kit/next` | Next.js App Router API, manager, media and web rendering | `node_modules/@rakun-kit/next/dist/docs/index.md` |
18
+ | `@rakun-kit/express` | Express API and local-media adapter | `node_modules/@rakun-kit/express/dist/docs/index.md` |
19
+ | `@rakun-kit/bun` | Bun server, manager, filesystem modules, SSR and static route generation | `node_modules/@rakun-kit/bun/dist/docs/index.md` |
20
+ | `@rakun-kit/trpc` | Typed tRPC router for manager and web operations | `node_modules/@rakun-kit/trpc/dist/docs/index.md` |
21
+ | `@rakun-kit/react` | Framework-neutral React module rendering and shared development UI | `node_modules/@rakun-kit/react/dist/docs/index.md` |
22
+ | `@rakun-kit/manager-react` | Manager UI, clients, navigation and plugin runtime | `node_modules/@rakun-kit/manager-react/dist/docs/index.md` |
23
+ | `@rakun-kit/manager-locales` | Optional manager UI locale packs | `node_modules/@rakun-kit/manager-locales/dist/docs/index.md` |
24
+ | `@rakun-kit/s3` | S3 and S3-compatible media storage | `node_modules/@rakun-kit/s3/dist/docs/index.md` |
25
+ | `@rakun-kit/smtp` | SMTP mail delivery | `node_modules/@rakun-kit/smtp/dist/docs/index.md` |
26
+ | `@rakun-kit/resend` | Resend mail delivery | `node_modules/@rakun-kit/resend/dist/docs/index.md` |
27
+ | `@rakun-kit/jsx-email` | Typed JSX Email template rendering | `node_modules/@rakun-kit/jsx-email/dist/docs/index.md` |
28
+ | `@rakun-kit/openai` | OpenAI-backed automatic content translation | `node_modules/@rakun-kit/openai/dist/docs/index.md` |
29
+ | `@rakun-kit/plugin-code-editor` | Code blocks in manager RichText fields | `node_modules/@rakun-kit/plugin-code-editor/dist/docs/index.md` |
30
+ | `create-rakun-app` | Official project generator and framework starter templates | `node_modules/create-rakun-app/dist/docs/index.md` |
31
+
32
+ Choose one server adapter (`next`, `express`, or `bun`). Add `trpc` only if the host
33
+ wants tRPC, one media adapter if uploads are required, and one mail adapter if
34
+ outbound mail is required. `manager-react` is normally consumed through the Bun
35
+ or Next adapter; use it directly for custom hosts.
36
+
37
+ `preview`, `preview-bun`, and `preview-next` are private example applications in the Rakun
38
+ repository, not installable packages, so they do not have installed manuals.
39
+
40
+ ## Typical architecture
41
+
42
+ 1. Define literals, content types, routes and any custom operations in
43
+ framework-neutral server modules.
44
+ 2. Call `rakunBootstrap` once, or pass the same options to an adapter that calls
45
+ `ensureRakunBootstrap`.
46
+ 3. Mount the API with `@rakun-kit/next` or `@rakun-kit/express`.
47
+ 4. Render the manager and web modules with the corresponding React integration.
48
+ 5. Keep browser code away from server secrets and server-only package entries.
49
+
50
+ ## Install and peer requirements
51
+
52
+ ```sh
53
+ bun add @rakun-kit/core mongodb ffmpeg-static zod
54
+ # Node.js image optimization only
55
+ bun add sharp
56
+ ```
57
+
58
+ `mongodb` and `ffmpeg-static` are peer dependencies. `bcrypt` is only needed by
59
+ Node runtimes; Bun uses its native password implementation. `sharp` is an
60
+ optional peer used by the default Node.js image processor and as the fallback
61
+ for Bun versions without `Bun.Image`. Adapters and optional services are
62
+ separate packages.
63
+
64
+ ## Bootstrap
65
+
66
+ ```ts
67
+ import { ContentType, f, rakunBootstrap } from '@rakun-kit/core'
68
+
69
+ const Article = new ContentType({
70
+ name: 'Article',
71
+ menu: { title: 'Articles', icon: 'newspaper', category: 'Content' },
72
+ fields: {
73
+ title: f.string(),
74
+ slug: f.string().type('Slug'),
75
+ body: f.string().type('RichText').optional(),
76
+ },
77
+ uniques: [['slug']],
78
+ listFields: ['title', 'slug'],
79
+ })
80
+
81
+ rakunBootstrap({
82
+ literals,
83
+ contentTypes: [Article],
84
+ routes,
85
+ mongo: {
86
+ MONGO_URI: process.env.MONGO_URI!,
87
+ ENVIRONMENT: process.env.NODE_ENV === 'test' ? 'test' : 'production',
88
+ },
89
+ })
90
+ ```
91
+
92
+ Important bootstrap options are `literals`, `contentTypes`,
93
+ `internalContentTypes`, `routes`, `apiOperations`, `plugins`, `mongo`, `media`,
94
+ `mail`, `translation`, `collaboration`, `managerLanguages`, `login`,
95
+ `accountRecovery`, `logger` and `syncRoutes`.
96
+
97
+ - `rakunBootstrap(options)` registers configuration synchronously.
98
+ - `ensureRakunBootstrap(options)` registers it only if needed. Framework
99
+ adapters use this to tolerate development reloads.
100
+ - `ensureRakunInitialized()` initializes logger, MongoDB, media and route sync.
101
+ It is concurrency-safe, releases a partially opened MongoDB client after a
102
+ failed initialization, and retries on the next call.
103
+ - `shutdownRakun()` closes every MongoDB client, releases hydrated
104
+ collaboration documents, and clears transient authentication state. Server
105
+ adapters should await it during graceful shutdown.
106
+
107
+ `mongo.clientOptions` forwards options to the official MongoDB driver. Rakun
108
+ defaults `maxIdleTimeMS` to 60 seconds so unused pooled sockets are released;
109
+ set it explicitly to override that value. The driver's topology-monitoring
110
+ connection remains active until `shutdownRakun()` because it is required to
111
+ detect server availability. On Railway, use the database's private `MONGO_URL`
112
+ for service-to-service traffic rather than its public TCP proxy URL.
113
+
114
+ Do not import React, Next, Express, Vite or tRPC into core configuration modules.
115
+
116
+ ## Runtime platform
117
+
118
+ Core domain code consumes a resolved `Platform` instead of importing runtime
119
+ image, crypto, filesystem, compression, or worker APIs. Omit `platform` to
120
+ detect Node.js or Bun and apply standalone defaults, or compose capabilities:
121
+
122
+ ```ts
123
+ import { createPlatform, pollingRealtime, sharpImage } from '@rakun-kit/core'
124
+
125
+ rakunBootstrap({
126
+ // ...normal Rakun options
127
+ platform: createPlatform({
128
+ deployment: 'serverless',
129
+ image: sharpImage(),
130
+ realtime: pollingRealtime({ intervalMs: 5_000 }),
131
+ }),
132
+ })
133
+ ```
134
+
135
+ Runtime, framework, and deployment remain independent dimensions. Runtime is
136
+ detected from `process.versions.bun`. Bun uses native `Bun.Image` when present;
137
+ Node.js and older Bun versions use `sharp`. The Bun adapter also falls back to
138
+ `sharp` when an OS-dependent codec cannot decode or encode the requested
139
+ format, so install the optional peer when formats such as AVIF must work across
140
+ all Bun hosts. `createPlatform` supplies every other default, so overrides
141
+ never require listing unrelated capabilities.
142
+
143
+ Every custom `ImageProcessor` implements `metadata`, `transform`, and
144
+ `placeholder`. `placeholder` returns `{ dataUrl, mime }` for direct storage as
145
+ an inline LQIP. The Bun adapter uses the native `Bun.Image.placeholder()` data
146
+ URL, while the Sharp adapter applies the requested reduced transform and
147
+ encodes its result as a data URL. Bun falls back to that Sharp path when its
148
+ runtime lacks the placeholder method or its native codec reports an unsupported
149
+ format or encode failure.
150
+
151
+ Realtime synchronization uses `pollingRealtime` or `sseRealtime`, both of
152
+ which expose transport metadata and a topic subscription contract. Polling is
153
+ the conservative default. `@rakun-kit/next` and `@rakun-kit/express`
154
+ automatically serve a configured SSE endpoint.
155
+ `sseRealtime()` defaults to `/realtime` relative to the adapter's API mount;
156
+ set `endpoint` only for a custom public route.
157
+
158
+ Custom server adapters can compose the framework-neutral helpers exported by
159
+ core: `parseRealtimeTopics`, `isRealtimeEndpointRequest`,
160
+ `createRealtimeSseStream`, and `authorizeRealtimeSubscription`. The host
161
+ remains responsible only for mapping its request/response APIs to those
162
+ primitives.
163
+
164
+ The built-in SSE provider keeps topic listeners in the current process.
165
+ Replicated deployments must supply a shared-broker `RealtimeProvider` that
166
+ implements the same `metadata`, `subscribe`, and `publish` contract.
167
+
168
+ `manager.uiLocales` exposes the provider's safe transport metadata so the
169
+ manager selects the same transport automatically. Core publishes stable
170
+ invalidation topics after collaboration updates and mutations affecting locale
171
+ variants, versions, comments, or notifications. Topic builders are exported
172
+ from `@rakun-kit/core/client`; use those helpers on both sides of custom
173
+ integrations instead of duplicating topic strings.
174
+
175
+ ## Collaborative content working state
176
+
177
+ Saved content documents have a Yjs working document identified by content type
178
+ and document `_id`. This includes published documents and every independent
179
+ draft/version. Incremental edits converge in that working document; they do not
180
+ update the normal MongoDB content record, rebuild routes, or run `revalidate`.
181
+ The public web API therefore continues to read the last saved JSON snapshot.
182
+
183
+ `manager.contentCollaboration.sync` exchanges state vectors and incremental
184
+ updates over the normal authenticated HTTP operation transport. It is
185
+ idempotent protocol traffic and is not a business mutation.
186
+ `manager.contentCollaboration.save` is the explicit commit boundary: core
187
+ materializes the server-side `Y.Doc`, validates it with the normal
188
+ `ContentType`, persists the snapshot through the existing update flow, and then
189
+ runs revalidation. Promoting a draft remains a separate operation and consumes
190
+ its saved snapshot.
191
+
192
+ `manager.contentCollaboration.discard` replaces the shared working document
193
+ with the last persisted content snapshot and advances its saved state vector,
194
+ so every connected editor converges on the restored state without writing a
195
+ new content revision. It is a mutation because it intentionally removes shared
196
+ working changes.
197
+
198
+ Shared content templates use the same protocol through
199
+ `manager.templateCollaboration.sync` and
200
+ `manager.templateCollaboration.save`. Their room is keyed only by content type,
201
+ so editors working from different documents still see the same template. The
202
+ save operation validates and persists it through the normal template update
203
+ flow. `manager.templateCollaboration.discard` provides the equivalent reset to
204
+ the last persisted shared template. Template state never becomes part of an
205
+ individual content document's room.
206
+
207
+ The React manager combines this server protocol with per-user IndexedDB
208
+ persistence. Cached Yjs rooms can be opened and edited during a temporary API
209
+ outage and upload their missing updates after reconnection. This local cache is
210
+ working state only: the authenticated server Save remains the persistence
211
+ boundary for MongoDB and public reads.
212
+
213
+ Core uses a process-memory collaboration adapter by default. This preserves
214
+ unsaved changes across manager navigation while that API process lives. A
215
+ multi-process or restart-durable deployment must pass a shared adapter through
216
+ `rakunBootstrap({ collaboration: { adapter } })`. An adapter stores opaque Yjs
217
+ updates and saved state vectors; it must not write them into the public content
218
+ document. `createMemoryCollaborationAdapter` is exported as the reference
219
+ implementation.
220
+
221
+ The service releases each hydrated `Y.Doc` after five minutes without access
222
+ and reloads it from the adapter when needed. Set
223
+ `collaboration.roomIdleTimeoutMs` to tune this process-memory cache, or `0` to
224
+ disable idle eviction. Adapter state is never discarded by this purge, so
225
+ unsaved changes keep the durability guarantees of the selected adapter. A
226
+ custom adapter may implement synchronous `dispose()` to release its own
227
+ process resources during `shutdownRakun()` or service replacement.
228
+
229
+ Collaboration sync also carries ephemeral presence for each open browser tab.
230
+ Presence reports the authenticated manager user and the currently focused
231
+ field, expires automatically, and is never included in the saved Yjs snapshot
232
+ or persistent event log. Custom shared adapters may implement the optional
233
+ `loadPresence` and `savePresence` methods to share this state across replicated
234
+ API processes; adapters without them keep presence local to one process.
235
+ On SSE deployments, core authorizes the collaboration room independently,
236
+ binds the tab presence to that stream, renews it from server heartbeats, and
237
+ removes it when the tab's last stream closes. This avoids client-side periodic
238
+ sync calls whose only purpose is presence. Polling uses its existing sync cycle.
239
+
240
+ This feature is scoped to saved content edit screens and their shared Template
241
+ editor. Create-form content becomes collaborative after its first save; its
242
+ shared Template is already collaborative because it exists independently of
243
+ that new document. Settings, route-layout overrides, literals, users, and other
244
+ administrative forms keep their existing local form state. Automatic content
245
+ translation updates the shared working document and still requires Save before
246
+ it changes the stored snapshot.
247
+
248
+ ## Content types and fields
249
+
250
+ `ContentType` describes a collection and derives its validation and TypeScript
251
+ shapes. Use `f` factories rather than handwritten storage schemas.
252
+ `Fields` remains available as a backward-compatible alias and references the
253
+ same object, but new code and examples should prefer `f`.
254
+
255
+ Common factories include string, number, boolean, date, object, relation, file,
256
+ blocks and array/list forms. Fields are required by default; chain `.optional()`
257
+ when a value may be omitted. `.required()` remains available as an
258
+ explicit, backward-compatible modifier. Other modifiers include `.multiple()`,
259
+ `.managerOnly()`, `.apiOnly()` and `.noDynamic()` when applicable.
260
+ Special manager editors are selected with `.type(...)`, for example `Slug`,
261
+ `RichText`, `Email`, `Password` and `Image`.
262
+
263
+ Plugin field factories created with `createPluginField` must declare
264
+ serializable `meta.capabilities`. `valueKind` (`string`, `richText`, `number`,
265
+ `boolean`, `date`, `object`, `array`, or `unknown`) drives Dynamic Data type
266
+ compatibility. Under `dynamic`, `properties` exposes named leaf paths,
267
+ `mapProperties` allows those paths to be mapped independently, `relation`
268
+ enables content-type traversal, and `collection` enables homogeneous or
269
+ heterogeneous per-item mapping. This lets custom fields participate without
270
+ adding their names to manager or core type switches.
271
+
272
+ Custom server resolution belongs in the optional `runtime.populate` callback.
273
+ It receives `{ db, populate, populateLink }`, so a composite field can recurse
274
+ through nested fields or reuse Rakun's localized link resolver. Runtime hooks
275
+ are not sent to the manager. The generic web-output phase is exported as
276
+ `populateFields`; the former `populateLinks` name remains as a deprecated alias.
277
+
278
+ Homogeneous `f.array(...)` fields accept `.min(count)` and `.max(count)` item
279
+ limits. These methods are also available after `.multiple()` on relation, file,
280
+ select, and content-reference fields, for example
281
+ `f.file().multiple().min(1).max(4)`. Core enforces them on input, stored data and
282
+ output schemas, and exposes `minItems`/`maxItems` to the manager.
283
+
284
+ Routeable content types can initialize SEO string fields from the document being
285
+ created. Add `.seo('<seoField>')` to a source string field; the argument is
286
+ restricted to string properties of Rakun's built-in SEO model. For example,
287
+ `title: f.string().required().seo('title')` initializes `seo.title` as a dynamic
288
+ data binding to the document's `title`. This only initializes create forms: it
289
+ does not modify existing documents, and editors can change or clear the binding
290
+ before saving.
291
+
292
+ Date fields support the `Date`, `DateTime` and `Time` manager modes. `Date` and
293
+ `DateTime` persist JavaScript `Date` values; their write schema also accepts the
294
+ ISO datetime strings produced when those values cross JSON and normalizes them
295
+ back to `Date`. `Time` stores an ISO time string.
296
+
297
+ The `f.link()` manager editor stores a direct `{ href, title }` value or an
298
+ internal `{ routeId, contentTypeId, title }` reference. Web output for these
299
+ values is `{ href, title }`, with internal `href` values localized by route.
300
+ `DataInput` and `DataFront` use those object shapes. Persisted legacy URL strings
301
+ remain readable, then receive an empty `title` when loaded in the manager or
302
+ normalized for web output. Dynamic data exposes link source properties as
303
+ `<field>.href` and `<field>.title`; list mapping targets expose those same two
304
+ paths independently and reconstruct the link object from their mapped values.
305
+
306
+ Use `f.menu()` for header and navigation trees. Its value is an ordered array
307
+ of link nodes with recursive `children`: direct nodes persist
308
+ `{ href, title, children }`, while internal nodes persist
309
+ `{ routeId, contentTypeId, title, children }`. Core validates every level and
310
+ recursively resolves internal nodes for web output as
311
+ `{ href, title, children }`. The manager can reorder and nest nodes with drag
312
+ and drop; there is no fixed nesting-depth limit.
313
+
314
+ `f.breadcrums()` declares a computed, API-only field intended for modules such
315
+ as heroes. When the module is rendered in a routable page, the field returns
316
+ `{ label, href }[]` from the highest route ancestor through the current page,
317
+ using localized route labels and paths. It returns `null` outside routable page
318
+ output. The field is part of `DataFront`, but not `DataInput` or `DBOutput`, and
319
+ does not appear in the manager.
320
+
321
+ Use `iterator` for ordered page modules. Rakun stores it as the reserved
322
+ `_iterator` field and edits it in the manager's Content tab. When the content
323
+ type has a `hasPage: true` route, Rakun automatically enables a separate shared
324
+ Template composition using the same module list as `iterator`. The Template
325
+ editor makes a special Content slot available at the root and inside
326
+ `f.blocks(...)`. A valid template contains exactly one such slot; web and
327
+ preview output replace it with the current document's iterator.
328
+
329
+ ```ts
330
+ import { StructuredData } from '@rakun-kit/core/internal-content-types'
331
+
332
+ const Page = new ContentType({
333
+ name: 'Page',
334
+ fields: {
335
+ title: f.string().required(),
336
+ slug: f.string().type('Slug').required(),
337
+ },
338
+ iterator: [
339
+ { contentType: UseCaseSection, type: 'new' },
340
+ { contentType: Hero, type: 'new' },
341
+ { contentType: LayoutWithInfo, type: 'new' },
342
+ { contentType: Newsletter, type: 'new' },
343
+ { contentType: StructuredData, type: 'new' },
344
+ ],
345
+ })
346
+ ```
347
+
348
+ `StructuredData` is Rakun's native JSON-LD module. Add it to an iterator to make
349
+ it available in Content and shared Templates. Its Product, Article,
350
+ Organization, WebSite, and BreadcrumbList variants expose typed fields; Custom
351
+ accepts an arbitrary JSON object. Template fields can use the normal dynamic
352
+ data mappings, so one module can describe every document of a routeable content
353
+ type. Official React and Next renderers emit the corresponding safe
354
+ `application/ld+json` script without an application module file.
355
+
356
+ Call `.help(text)` on a field to expose optional guidance from a compact help
357
+ icon in the manager. The metadata survives other field modifiers and nested
358
+ relation encoding. Pass either direct copy or a manager translation key. Use
359
+ `.description(...)` for short guidance that should remain visible below the
360
+ field label.
361
+
362
+ For example, place Content inside an otherwise empty
363
+ `LayoutWithInfo.fields.blocks` list to render the document-specific sections
364
+ inside that shared wrapper. Route `layout` modules such as header and footer
365
+ remain outside the assembled template.
366
+
367
+ Content type names, field names and reserved fields are persisted API. Do not
368
+ rename them without a data migration and a review of manager and web consumers.
369
+ Use `.withHooks(...)` for lifecycle behavior; keep mutations and secrets out of
370
+ `onGet` transformations unless that behavior is explicitly intended.
371
+
372
+ Dynamic data list mappings may map a target `blocks`, link-array, or
373
+ relation-array field recursively. A map entry with `kind: 'list'` contains the
374
+ same `contentType`, optional `source` or `query`, `itemName`, and `map` shape as
375
+ a top-level list binding. In its query,
376
+ `{ $current: 'path' }` resolves against the parent source item and
377
+ `{ $document: 'path' }` resolves against the root document. The manager exposes
378
+ these values as `Current item` and `Current document`, respectively, supporting
379
+ flows such as Category -> gallery item -> related Project -> nested image card.
380
+ Source and target dynamic-field rules are enforced at every level.
381
+
382
+ Homogeneous link and relation arrays also accept per-item list mappings, at the
383
+ top level or recursively. This includes `f.relation(...).multiple()`, which is
384
+ the relation-array shorthand. Link items map `title` and `href`; relation items
385
+ map fields from their target content type. These arrays resolve to flat items,
386
+ while `f.blocks(...)` remains heterogeneous and resolves each item as
387
+ `{ name, value }`. A structured array may still use a direct whole-field binding.
388
+
389
+ ## Routes and web output
390
+
391
+ Routes connect a content type field to a public URL. A page route may also have
392
+ a fixed `layout` with module slots and a `{ type: 'content' }` insertion point.
393
+ When a content type has a `hasPage: true` route, core adds optional `_seo`
394
+ metadata and can expose generated `href` values to dynamic data.
395
+
396
+ The `web.page` and `web.previewPage` responses expose modules only through their
397
+ required `layout` array. Document modules are in the `modules` property of the
398
+ `{ type: 'content' }` item; there is no duplicate top-level `modules` property.
399
+ Not-found responses use the same shape, while redirects return an empty layout.
400
+
401
+ The optional `info` object contains normal resolved page fields and route context
402
+ such as `locale` and `variantGroupId`. Translated website literals live in the
403
+ separate top-level `literals` property. Core removes the composition-only
404
+ `_iterator`, `_seo`, and legacy `_iteratorUnlinked` fields recursively,
405
+ including from populated child relations, so nested page modules and SEO data
406
+ are not duplicated in `info`.
407
+
408
+ Use `@rakun-kit/core/web` for framework-neutral web types/utilities. Use the
409
+ Next or React manual for actual rendering. Route changes can affect configured
410
+ redirects, sitemap, robots output, preview, and adapter behavior.
411
+
412
+ The built-in `LlmsSettings` record backs an optional site-level `llms.txt`.
413
+ Editors curate a title, summary, free-form guidance, ordered sections, and
414
+ internal or external links in Manager Settings. Core renders the Markdown via
415
+ the public `web.llms` query; `getRakunWebLlmsTxt` provides the equivalent
416
+ server-only call. It resolves internal links for the requested language, uses
417
+ SEO `siteUrl` to make links absolute, and falls back to SEO `siteName` and the
418
+ default SEO description when title or summary is omitted. Disabled or unusable
419
+ configuration returns `null`. Only explicitly selected resources are included;
420
+ internal links without a public route are omitted while their section heading
421
+ remains. In Manager, routeable content types with document
422
+ visibility expose only published documents in this picker. Entries marked
423
+ optional are grouped under the conventional `## Optional` heading and retain
424
+ their section titles as third-level headings.
425
+
426
+ The authenticated `manager.localeVariants.list` result exposes `path` on a
427
+ language assignment only when its exact document is published and has a route
428
+ map. This is the supported manager-facing way to build a contextual View page
429
+ action; do not query hidden `RouteMap` records from manager UI code.
430
+
431
+ For routeable content types with document visibility, only explicit
432
+ `published` and `hidden` values are eligible for the route map or public page
433
+ resolution. A legacy document with no `_visibility` is treated as a draft;
434
+ this fail-closed check also prevents an obsolete `RouteMap` entry from exposing
435
+ its content.
436
+
437
+ The public `web.staticPaths` query returns `{ path, ttl }` entries only for
438
+ route-map records whose configured page route has `dynamic: false`. Framework
439
+ adapters use it to generate static params and choose an ISR lifetime. A
440
+ monolithic server adapter may instead call the server-only
441
+ `getRakunWebStaticPaths`, `getRakunWebPage`, and `getRakunWebPreviewPage`
442
+ exports after bootstrap and initialization, avoiding an HTTP request to itself
443
+ during a framework build.
444
+
445
+ Set `revalidate: { url, token }` in bootstrap to notify the web host after a
446
+ manager mutation changes a public path. Core sends an authenticated `POST`
447
+ containing `{ path }`; the host adapter is responsible for invalidating its
448
+ page and data caches.
449
+
450
+ ## Custom operations
451
+
452
+ Define public API behavior with the operation helpers and Zod input/output
453
+ schemas. Register the map through `apiOperations` so adapters and typed clients
454
+ share one contract.
455
+
456
+ ```ts
457
+ import { defineOperation } from '@rakun-kit/core'
458
+ import { z } from 'zod'
459
+
460
+ export const apiOperations = {
461
+ 'demo.hello': defineOperation({
462
+ access: 'public',
463
+ kind: 'query',
464
+ method: 'get',
465
+ input: z.object({ name: z.string() }),
466
+ output: z.object({ message: z.string() }),
467
+ resolve: ({ input }) => ({ message: `Hello ${input.name}` }),
468
+ }),
469
+ }
470
+ ```
471
+
472
+ Keep operations in server modules. Client code should import the operation map
473
+ with `import type` and use the typed client supplied by its adapter.
474
+
475
+ Every API error, including expected 4xx errors, must reach Rakun's persistent
476
+ event log without persisting payloads, credentials, sensitive causes or raw
477
+ internal error messages. Successful mutations already emit
478
+ `<operation>.succeeded`; add explicit domain events for meaningful phases,
479
+ external effects or partial outcomes. Queries are not logged by default.
480
+
481
+ ## Request context, auth and permissions
482
+
483
+ Adapters create a Rakun request context from headers, cookies and their response
484
+ abstraction. Prefer existing helpers such as `getUser`, `checkPermissions` and
485
+ `checkOwnership`; do not duplicate authorization rules in application code.
486
+
487
+ External manager login adapters are configured under `login.adapters`.
488
+ Provider emails must be verified and match an existing manager user; Rakun does
489
+ not provision users automatically. Password login can use persistent IP
490
+ blocking. Account recovery requires both a mail service and
491
+ `accountRecovery.passwordReset` configuration.
492
+
493
+ ## Media, mail and translation
494
+
495
+ Core defines service contracts; concrete integrations are separate:
496
+
497
+ - Media: use `@rakun-kit/s3`, or local media from the selected framework
498
+ adapter. Configure the result as `media` in bootstrap.
499
+ - Storage adapters may expose `getPublicObject`; Next.js, Express and Bun then
481
500
  serve public media through their normal `/media/<key>` API route. This
482
- gives public Rakun media a stable application URL while the backing object
483
- store remains private or uses expiring storage URLs.
484
- - Mail: use `@rakun-kit/smtp` or `@rakun-kit/resend`; optionally render typed
485
- templates with `@rakun-kit/jsx-email`. Configure delivery as `mail`.
486
- - Translation: use `@rakun-kit/openai` or implement the core translation
487
- adapter contract. Configure it as `translation`.
488
-
489
- Never expose adapter credentials in client modules. Public and private media
490
- have different URL semantics; do not replace stored media objects with raw URLs.
491
- The built-in manager upload protocol preserves Unicode original file names,
492
- including accents, non-Latin scripts, and emoji, while storage object keys stay
493
- adapter-safe.
494
-
495
- The media manager can reimport an existing image or video with its currently
496
- selected optimization settings. Rakun writes the replacement and its variants
497
- under new storage keys, updates the existing `Media` record in place, and only
498
- then removes the previous objects, so content relations keep the same media ID.
499
- The `manager.media.replace` mutation applies the same safe storage sequence to
500
- a newly uploaded image. It preserves the existing media ID, editorial details,
501
- folder and relations while replacing its file metadata and generated variants,
502
- then revalidates documents that reference it when route revalidation is configured.
503
- When `generatePreview` is enabled, optimization stores a tiny `data:image/...`
504
- LQIP string on `previewUrl` instead of uploading a separate preview object.
505
- Its exact MIME may be selected by the runtime image processor; native Bun
506
- placeholders are PNG data URLs, while Sharp uses the configured output format.
507
- Older media that still have a `previewKey` continue to resolve normally.
508
-
509
- Optimized video uploads use the `ffmpeg-static` peer dependency. Rakun keeps
510
- MP4 as the primary object and exposes both MP4 and WebM entries through the
511
- media `sources` array (`key`, resolved `url`, `mime`, and `size`). Existing
512
- image-only `FileOptimizeOptions` remain valid; video quality defaults to 80 and
513
- can be set with `video: { quality }`.
514
-
515
- ## Manager languages and user-facing text
516
-
517
- Web/content-facing text belongs in Rakun literals. Manager UI text belongs in
518
- the manager catalog. English manager copy is built into `manager-react`; add
519
- optional packs such as Spanish through `@rakun-kit/manager-locales/es` and
520
- `managerLanguages`.
521
-
522
- Project-defined manager labels may use arbitrary keys. Field labels follow
523
- `field.<fieldName>` and layout slots follow `layoutModule.<layoutKey>`.
524
- The public `manager.uiLocales` response also exposes a validated HTTP(S) SEO
525
- `siteUrl` for manager chrome and the configured home-page variant group for SEO
526
- audits; no other SEO settings are included.
527
-
528
- SEO audit snapshots are stored in the hidden internal `SeoAudit` content type.
529
- It shares the `SeoSettings` permission resource: `own` permits creating dated
530
- site or page reports, while `readAny` permits viewing reports created by other
531
- editors. The manager uses normal content operations for this persistence, so
532
- successful report creation is covered by the standard mutation event log.
533
-
534
- ## Public entrypoints
535
-
536
- - `@rakun-kit/core`: bootstrap and the main domain/runtime API.
537
- - The main server entry also exports `getRakunWebPage`,
538
- `getRakunWebPreviewPage`, and `getRakunWebStaticPaths` for server adapters
539
- that share Rakun's process and database.
540
- - `@rakun-kit/core/contracts`: shared schemas, operation and locale contracts.
541
- - `@rakun-kit/core/client`: browser-safe client types and utilities.
542
- - `@rakun-kit/core/manager`: manager operation metadata and types.
543
- - `@rakun-kit/core/web`: web output types and utilities.
544
- - `@rakun-kit/core/types`: content-type-derived helpers.
545
- - `@rakun-kit/core/logger`, `/errors`, `/literals`, `/api-utils`, `/plugins`,
546
- `/event-log` and `/internal-content-types`: focused public surfaces.
547
-
548
- Import only declared package entrypoints. Do not reach into `dist/esm`,
549
- `dist/cjs` or source paths.
550
-
551
- ## Agent checklist
552
-
553
- - Identify the host adapter and read its installed manual.
554
- - Confirm an API is exported before importing it.
555
- - Preserve the server/browser boundary and use `import type` for contracts.
556
- - Reuse core validation, auth, operation, logging and service abstractions.
557
- - Treat persisted names and public schemas as compatibility boundaries.
558
- - Update application tests when content, routes, permissions or operation
559
- behavior changes.
501
+ gives public Rakun media a stable application URL while the backing object
502
+ store remains private or uses expiring storage URLs.
503
+ - Mail: use `@rakun-kit/smtp` or `@rakun-kit/resend`; optionally render typed
504
+ templates with `@rakun-kit/jsx-email`. Configure delivery as `mail`.
505
+ - Translation: use `@rakun-kit/openai` or implement the core translation
506
+ adapter contract. Configure it as `translation`.
507
+
508
+ Never expose adapter credentials in client modules. Public and private media
509
+ have different URL semantics; do not replace stored media objects with raw URLs.
510
+ The built-in manager upload protocol preserves Unicode original file names,
511
+ including accents, non-Latin scripts, and emoji, while storage object keys stay
512
+ adapter-safe.
513
+
514
+ The media manager can reimport an existing image or video with its currently
515
+ selected optimization settings. Rakun writes the replacement and its variants
516
+ under new storage keys, updates the existing `Media` record in place, and only
517
+ then removes the previous objects, so content relations keep the same media ID.
518
+ The `manager.media.replace` mutation applies the same safe storage sequence to
519
+ a newly uploaded image. It preserves the existing media ID, editorial details,
520
+ folder and relations while replacing its file metadata and generated variants,
521
+ then revalidates documents that reference it when route revalidation is configured.
522
+ When `generatePreview` is enabled, optimization stores a tiny `data:image/...`
523
+ LQIP string on `previewUrl` instead of uploading a separate preview object.
524
+ Its exact MIME may be selected by the runtime image processor; native Bun
525
+ placeholders are PNG data URLs, while Sharp uses the configured output format.
526
+ Older media that still have a `previewKey` continue to resolve normally.
527
+
528
+ Optimized video uploads use the `ffmpeg-static` peer dependency. Rakun keeps
529
+ MP4 as the primary object and exposes both MP4 and WebM entries through the
530
+ media `sources` array (`key`, resolved `url`, `mime`, and `size`). Existing
531
+ image-only `FileOptimizeOptions` remain valid; video quality defaults to 80 and
532
+ can be set with `video: { quality }`.
533
+
534
+ ## Manager languages and user-facing text
535
+
536
+ Web/content-facing text belongs in Rakun literals. Manager UI text belongs in
537
+ the manager catalog. English manager copy is built into `manager-react`; add
538
+ optional packs such as Spanish through `@rakun-kit/manager-locales/es` and
539
+ `managerLanguages`.
540
+
541
+ Project-defined manager labels may use arbitrary keys. Field labels follow
542
+ `field.<fieldName>` and layout slots follow `layoutModule.<layoutKey>`.
543
+ The public `manager.uiLocales` response also exposes a validated HTTP(S) SEO
544
+ `siteUrl` for manager chrome and the configured home-page variant group for SEO
545
+ audits; no other SEO settings are included.
546
+
547
+ SEO audit snapshots are stored in the hidden internal `SeoAudit` content type.
548
+ It shares the `SeoSettings` permission resource: `own` permits creating dated
549
+ site or page reports, while `readAny` permits viewing reports created by other
550
+ editors. The manager uses normal content operations for this persistence, so
551
+ successful report creation is covered by the standard mutation event log.
552
+
553
+ ## Public entrypoints
554
+
555
+ - `@rakun-kit/core`: bootstrap and the main domain/runtime API.
556
+ - The main server entry also exports `getRakunWebPage`,
557
+ `getRakunWebPreviewPage`, and `getRakunWebStaticPaths` for server adapters
558
+ that share Rakun's process and database.
559
+ - `@rakun-kit/core/contracts`: shared schemas, operation and locale contracts.
560
+ - `@rakun-kit/core/client`: browser-safe client types and utilities.
561
+ - `@rakun-kit/core/manager`: manager operation metadata and types.
562
+ - `@rakun-kit/core/web`: web output types and utilities.
563
+ - `@rakun-kit/core/types`: content-type-derived helpers.
564
+ - `@rakun-kit/core/logger`, `/errors`, `/literals`, `/api-utils`, `/plugins`,
565
+ `/event-log` and `/internal-content-types`: focused public surfaces.
566
+
567
+ Import only declared package entrypoints. Do not reach into `dist/esm`,
568
+ `dist/cjs` or source paths.
569
+
570
+ ## Agent checklist
571
+
572
+ - Identify the host adapter and read its installed manual.
573
+ - Confirm an API is exported before importing it.
574
+ - Preserve the server/browser boundary and use `import type` for contracts.
575
+ - Reuse core validation, auth, operation, logging and service abstractions.
576
+ - Treat persisted names and public schemas as compatibility boundaries.
577
+ - Update application tests when content, routes, permissions or operation
578
+ behavior changes.