create-rudder-app 1.1.2 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/dist/cli-flags.d.ts +0 -9
  2. package/dist/cli-flags.d.ts.map +1 -1
  3. package/dist/cli-flags.js +0 -8
  4. package/dist/cli-flags.js.map +1 -1
  5. package/dist/index.js +2 -2
  6. package/dist/index.js.map +1 -1
  7. package/dist/templates/app/service-provider.d.ts.map +1 -1
  8. package/dist/templates/app/service-provider.js +0 -15
  9. package/dist/templates/app/service-provider.js.map +1 -1
  10. package/dist/templates/components/site-header.d.ts.map +1 -1
  11. package/dist/templates/components/site-header.js +2 -8
  12. package/dist/templates/components/site-header.js.map +1 -1
  13. package/dist/templates/package-json.d.ts.map +1 -1
  14. package/dist/templates/package-json.js +4 -5
  15. package/dist/templates/package-json.js.map +1 -1
  16. package/dist/templates/pages/index.d.ts +13 -0
  17. package/dist/templates/pages/index.d.ts.map +1 -1
  18. package/dist/templates/pages/index.js +43 -0
  19. package/dist/templates/pages/index.js.map +1 -1
  20. package/dist/templates/routes/api.d.ts.map +1 -1
  21. package/dist/templates/routes/api.js +0 -99
  22. package/dist/templates/routes/api.js.map +1 -1
  23. package/dist/templates/routes/console.d.ts.map +1 -1
  24. package/dist/templates/routes/console.js +3 -4
  25. package/dist/templates/routes/console.js.map +1 -1
  26. package/dist/templates/routes/web.d.ts.map +1 -1
  27. package/dist/templates/routes/web.js +10 -83
  28. package/dist/templates/routes/web.js.map +1 -1
  29. package/dist/templates/routes-manifest.d.ts +2 -2
  30. package/dist/templates/routes-manifest.d.ts.map +1 -1
  31. package/dist/templates/routes-manifest.js +19 -22
  32. package/dist/templates/routes-manifest.js.map +1 -1
  33. package/dist/templates/views/welcome.d.ts +13 -0
  34. package/dist/templates/views/welcome.d.ts.map +1 -1
  35. package/dist/templates/views/welcome.js +53 -0
  36. package/dist/templates/views/welcome.js.map +1 -1
  37. package/dist/templates.d.ts +0 -9
  38. package/dist/templates.d.ts.map +1 -1
  39. package/dist/templates.js +38 -120
  40. package/dist/templates.js.map +1 -1
  41. package/package.json +1 -5
  42. package/dist/templates/demos/avatar.d.ts +0 -3
  43. package/dist/templates/demos/avatar.d.ts.map +0 -1
  44. package/dist/templates/demos/avatar.js +0 -174
  45. package/dist/templates/demos/avatar.js.map +0 -1
  46. package/dist/templates/demos/cache.d.ts +0 -3
  47. package/dist/templates/demos/cache.d.ts.map +0 -1
  48. package/dist/templates/demos/cache.js +0 -91
  49. package/dist/templates/demos/cache.js.map +0 -1
  50. package/dist/templates/demos/contact.d.ts +0 -3
  51. package/dist/templates/demos/contact.d.ts.map +0 -1
  52. package/dist/templates/demos/contact.js +0 -99
  53. package/dist/templates/demos/contact.js.map +0 -1
  54. package/dist/templates/demos/fibonacci.d.ts +0 -7
  55. package/dist/templates/demos/fibonacci.d.ts.map +0 -1
  56. package/dist/templates/demos/fibonacci.js +0 -164
  57. package/dist/templates/demos/fibonacci.js.map +0 -1
  58. package/dist/templates/demos/http.d.ts +0 -3
  59. package/dist/templates/demos/http.d.ts.map +0 -1
  60. package/dist/templates/demos/http.js +0 -109
  61. package/dist/templates/demos/http.js.map +0 -1
  62. package/dist/templates/demos/index-view.d.ts +0 -3
  63. package/dist/templates/demos/index-view.d.ts.map +0 -1
  64. package/dist/templates/demos/index-view.js +0 -47
  65. package/dist/templates/demos/index-view.js.map +0 -1
  66. package/dist/templates/demos/localization.d.ts +0 -4
  67. package/dist/templates/demos/localization.d.ts.map +0 -1
  68. package/dist/templates/demos/localization.js +0 -122
  69. package/dist/templates/demos/localization.js.map +0 -1
  70. package/dist/templates/demos/mail.d.ts +0 -4
  71. package/dist/templates/demos/mail.d.ts.map +0 -1
  72. package/dist/templates/demos/mail.js +0 -119
  73. package/dist/templates/demos/mail.js.map +0 -1
  74. package/dist/templates/demos/notifications.d.ts +0 -4
  75. package/dist/templates/demos/notifications.d.ts.map +0 -1
  76. package/dist/templates/demos/notifications.js +0 -125
  77. package/dist/templates/demos/notifications.js.map +0 -1
  78. package/dist/templates/demos/pennant.d.ts +0 -8
  79. package/dist/templates/demos/pennant.d.ts.map +0 -1
  80. package/dist/templates/demos/pennant.js +0 -135
  81. package/dist/templates/demos/pennant.js.map +0 -1
  82. package/dist/templates/demos/polymorphic.d.ts +0 -19
  83. package/dist/templates/demos/polymorphic.d.ts.map +0 -1
  84. package/dist/templates/demos/polymorphic.js +0 -663
  85. package/dist/templates/demos/polymorphic.js.map +0 -1
  86. package/dist/templates/demos/queue.d.ts +0 -4
  87. package/dist/templates/demos/queue.d.ts.map +0 -1
  88. package/dist/templates/demos/queue.js +0 -99
  89. package/dist/templates/demos/queue.js.map +0 -1
  90. package/dist/templates/demos/registry.d.ts +0 -26
  91. package/dist/templates/demos/registry.d.ts.map +0 -1
  92. package/dist/templates/demos/registry.js +0 -140
  93. package/dist/templates/demos/registry.js.map +0 -1
  94. package/dist/templates/demos/rudder-socket.d.ts +0 -2
  95. package/dist/templates/demos/rudder-socket.d.ts.map +0 -1
  96. package/dist/templates/demos/rudder-socket.js +0 -95
  97. package/dist/templates/demos/rudder-socket.js.map +0 -1
  98. package/dist/templates/demos/sync.d.ts +0 -2
  99. package/dist/templates/demos/sync.d.ts.map +0 -1
  100. package/dist/templates/demos/sync.js +0 -90
  101. package/dist/templates/demos/sync.js.map +0 -1
  102. package/dist/templates/demos/system-info.d.ts +0 -3
  103. package/dist/templates/demos/system-info.d.ts.map +0 -1
  104. package/dist/templates/demos/system-info.js +0 -134
  105. package/dist/templates/demos/system-info.js.map +0 -1
  106. package/dist/templates/demos/todos.d.ts +0 -6
  107. package/dist/templates/demos/todos.d.ts.map +0 -1
  108. package/dist/templates/demos/todos.js +0 -238
  109. package/dist/templates/demos/todos.js.map +0 -1
  110. package/dist/templates/demos/ws.d.ts +0 -2
  111. package/dist/templates/demos/ws.d.ts.map +0 -1
  112. package/dist/templates/demos/ws.js +0 -99
  113. package/dist/templates/demos/ws.js.map +0 -1
@@ -1,663 +0,0 @@
1
- // Polymorphic relations demo — every polymorphic relation type
2
- // (morphMany / morphTo / morphToMany / morphedByMany) via @rudderjs/orm.
3
- //
4
- // Scaffolds four Models (Post / Video / Comment / Tag), the React view, the
5
- // Prisma block (Comment with camelCase commentableId/commentableType + Tag
6
- // with the shared `taggable` pivot), the controller view, and a self-contained
7
- // API surface (state, create, comment, morphTo resolution, tag attach/detach,
8
- // and morphedByMany inverse fan-out).
9
- export function postModelTs() {
10
- return `import { Model } from '@rudderjs/orm'
11
- import { Comment } from './Comment.js'
12
- import { Tag } from './Tag.js'
13
-
14
- export class Post extends Model {
15
- static table = 'post'
16
- static fillable = ['title']
17
-
18
- static override relations = {
19
- comments: { type: 'morphMany' as const, model: () => Comment, morphName: 'commentable' },
20
- tags: { type: 'morphToMany' as const, model: () => Tag, pivotTable: 'taggable', morphName: 'taggable' },
21
- }
22
-
23
- id!: number
24
- title!: string
25
- createdAt!: Date
26
- }
27
- `;
28
- }
29
- export function videoModelTs() {
30
- return `import { Model } from '@rudderjs/orm'
31
- import { Comment } from './Comment.js'
32
- import { Tag } from './Tag.js'
33
-
34
- export class Video extends Model {
35
- static table = 'video'
36
- static fillable = ['url']
37
-
38
- static override relations = {
39
- comments: { type: 'morphMany' as const, model: () => Comment, morphName: 'commentable' },
40
- tags: { type: 'morphToMany' as const, model: () => Tag, pivotTable: 'taggable', morphName: 'taggable' },
41
- }
42
-
43
- id!: number
44
- url!: string
45
- createdAt!: Date
46
- }
47
- `;
48
- }
49
- export function commentModelTs() {
50
- return `import { Model } from '@rudderjs/orm'
51
- import { Post } from './Post.js'
52
- import { Video } from './Video.js'
53
-
54
- export class Comment extends Model {
55
- static table = 'comment'
56
- static fillable = ['body', 'commentableId', 'commentableType']
57
-
58
- static override relations = {
59
- commentable: { type: 'morphTo' as const, morphName: 'commentable', types: () => [Post, Video] },
60
- }
61
-
62
- id!: number
63
- body!: string
64
- commentableId!: number
65
- commentableType!: string
66
- createdAt!: Date
67
- }
68
- `;
69
- }
70
- export function tagModelTs() {
71
- return `import { Model } from '@rudderjs/orm'
72
- import { Post } from './Post.js'
73
- import { Video } from './Video.js'
74
-
75
- export class Tag extends Model {
76
- static table = 'tag'
77
- static fillable = ['name']
78
-
79
- static override relations = {
80
- posts: {
81
- type: 'morphedByMany' as const,
82
- model: () => Post,
83
- pivotTable: 'taggable',
84
- morphName: 'taggable',
85
- },
86
- videos: {
87
- type: 'morphedByMany' as const,
88
- model: () => Video,
89
- pivotTable: 'taggable',
90
- morphName: 'taggable',
91
- },
92
- }
93
-
94
- id!: number
95
- name!: string
96
- }
97
- `;
98
- }
99
- export function polymorphicPrismaBlock() {
100
- return `// module: Polymorphic demo (Post / Video / Comment / Tag + Taggable pivot)
101
- // commentableId/commentableType + taggableId/taggableType follow @rudderjs/orm's
102
- // camelCase convention.
103
- model Post {
104
- id Int @id @default(autoincrement())
105
- title String
106
- createdAt DateTime @default(now())
107
- }
108
-
109
- model Video {
110
- id Int @id @default(autoincrement())
111
- url String
112
- createdAt DateTime @default(now())
113
- }
114
-
115
- model Comment {
116
- id Int @id @default(autoincrement())
117
- body String
118
- commentableId Int
119
- commentableType String
120
- createdAt DateTime @default(now())
121
-
122
- @@index([commentableType, commentableId])
123
- }
124
-
125
- // Polymorphic many-to-many. One Tag table shared by both Post and Video
126
- // through a single Taggable pivot. The pivot carries tagId (strong side) +
127
- // taggableId/taggableType (polymorphic side).
128
- model Tag {
129
- id Int @id @default(autoincrement())
130
- name String @unique
131
- }
132
-
133
- model Taggable {
134
- tagId Int
135
- taggableId Int
136
- taggableType String
137
-
138
- @@id([tagId, taggableId, taggableType])
139
- @@index([taggableId, taggableType])
140
- }
141
- `;
142
- }
143
- export function demosPolymorphicView() {
144
- return `import '@/index.css'
145
- import { SiteHeader } from 'App/Components/SiteHeader.js'
146
- import { useState } from 'react'
147
-
148
- interface CommentDto {
149
- id: number
150
- body: string
151
- commentableId: number
152
- commentableType: string
153
- createdAt: string
154
- }
155
-
156
- interface TagDto {
157
- id: number
158
- name: string
159
- }
160
-
161
- interface PostDto {
162
- id: number
163
- title: string
164
- comments: CommentDto[]
165
- tags: TagDto[]
166
- }
167
-
168
- interface VideoDto {
169
- id: number
170
- url: string
171
- comments: CommentDto[]
172
- tags: TagDto[]
173
- }
174
-
175
- interface ResolvedParent {
176
- type: 'Post' | 'Video'
177
- id: number
178
- title: string
179
- }
180
-
181
- interface InverseFanOut {
182
- posts: Array<{ id: number; title: string }>
183
- videos: Array<{ id: number; url: string }>
184
- }
185
-
186
- interface PolymorphicDemoProps {
187
- posts: PostDto[]
188
- videos: VideoDto[]
189
- tags: TagDto[]
190
- }
191
-
192
- export default function PolymorphicDemo(props: PolymorphicDemoProps) {
193
- const [posts, setPosts] = useState<PostDto[]>(props.posts)
194
- const [videos, setVideos] = useState<VideoDto[]>(props.videos)
195
- const [tags, setTags] = useState<TagDto[]>(props.tags)
196
- const [resolved, setResolved] = useState<ResolvedParent | null>(null)
197
- const [inverse, setInverse] = useState<{ tag: TagDto; data: InverseFanOut } | null>(null)
198
- const [loading, setLoading] = useState(false)
199
-
200
- async function refresh() {
201
- const res = await fetch('/api/polymorphic/state')
202
- const json = await res.json() as { posts: PostDto[]; videos: VideoDto[]; tags: TagDto[] }
203
- setPosts(json.posts)
204
- setVideos(json.videos)
205
- setTags(json.tags)
206
- }
207
-
208
- async function addPost() {
209
- const title = prompt('Post title?')
210
- if (!title) return
211
- setLoading(true)
212
- try {
213
- await fetch('/api/polymorphic/posts', {
214
- method: 'POST',
215
- headers: { 'Content-Type': 'application/json' },
216
- body: JSON.stringify({ title }),
217
- })
218
- await refresh()
219
- } finally { setLoading(false) }
220
- }
221
-
222
- async function addVideo() {
223
- const url = prompt('Video URL?')
224
- if (!url) return
225
- setLoading(true)
226
- try {
227
- await fetch('/api/polymorphic/videos', {
228
- method: 'POST',
229
- headers: { 'Content-Type': 'application/json' },
230
- body: JSON.stringify({ url }),
231
- })
232
- await refresh()
233
- } finally { setLoading(false) }
234
- }
235
-
236
- async function addComment(type: 'post' | 'video', id: number) {
237
- const body = prompt(\`Comment on \${type} #\${id}?\`)
238
- if (!body) return
239
- setLoading(true)
240
- try {
241
- await fetch(\`/api/polymorphic/\${type}s/\${id}/comments\`, {
242
- method: 'POST',
243
- headers: { 'Content-Type': 'application/json' },
244
- body: JSON.stringify({ body }),
245
- })
246
- await refresh()
247
- } finally { setLoading(false) }
248
- }
249
-
250
- async function resolveParent(commentId: number) {
251
- setLoading(true)
252
- try {
253
- const res = await fetch(\`/api/polymorphic/comments/\${commentId}/parent\`)
254
- setResolved(await res.json() as ResolvedParent)
255
- } finally { setLoading(false) }
256
- }
257
-
258
- async function addTag() {
259
- const name = prompt('Tag name?')
260
- if (!name) return
261
- setLoading(true)
262
- try {
263
- await fetch('/api/polymorphic/tags', {
264
- method: 'POST',
265
- headers: { 'Content-Type': 'application/json' },
266
- body: JSON.stringify({ name }),
267
- })
268
- await refresh()
269
- } finally { setLoading(false) }
270
- }
271
-
272
- async function attachTag(type: 'post' | 'video', parentId: number) {
273
- if (tags.length === 0) { alert('Create a tag first.'); return }
274
- const tagName = prompt(\`Attach which tag? (\${tags.map(t => t.name).join(', ')})\`)
275
- if (!tagName) return
276
- const tag = tags.find(t => t.name === tagName)
277
- if (!tag) { alert('No tag with that name.'); return }
278
- setLoading(true)
279
- try {
280
- await fetch(\`/api/polymorphic/\${type}s/\${parentId}/tags\`, {
281
- method: 'POST',
282
- headers: { 'Content-Type': 'application/json' },
283
- body: JSON.stringify({ tagId: tag.id }),
284
- })
285
- await refresh()
286
- } finally { setLoading(false) }
287
- }
288
-
289
- async function detachTag(type: 'post' | 'video', parentId: number, tagId: number) {
290
- setLoading(true)
291
- try {
292
- await fetch(\`/api/polymorphic/\${type}s/\${parentId}/tags/\${tagId}\`, { method: 'DELETE' })
293
- await refresh()
294
- } finally { setLoading(false) }
295
- }
296
-
297
- async function resolveInverse(tag: TagDto) {
298
- setLoading(true)
299
- try {
300
- const res = await fetch(\`/api/polymorphic/tags/\${tag.id}/items\`)
301
- setInverse({ tag, data: await res.json() as InverseFanOut })
302
- } finally { setLoading(false) }
303
- }
304
-
305
- return (
306
- <div className="page">
307
- <SiteHeader />
308
-
309
- <section className="hero">
310
- <h1 className="hero-title">Polymorphic relations</h1>
311
- <p className="hero-lead">
312
- One <code className="inline-code">Comment</code> table belonging to either a <code className="inline-code">Post</code> or a <code className="inline-code">Video</code> via <code className="inline-code">commentableId</code> + <code className="inline-code">commentableType</code>. One <code className="inline-code">Tag</code> table shared by both Posts and Videos through a single <code className="inline-code">taggable</code> pivot — <code className="inline-code">morphToMany</code> on the owning side, <code className="inline-code">morphedByMany</code> on the inverse.
313
- </p>
314
- <p className="hero-meta">
315
- Models: <code className="inline-code">Post.morphMany('comments')</code> + <code className="inline-code">Post.morphToMany('tags')</code>, <code className="inline-code">Video.morphMany('comments')</code> + <code className="inline-code">Video.morphToMany('tags')</code>, <code className="inline-code">Comment.morphTo('commentable', [Post, Video])</code>, <code className="inline-code">Tag.morphedByMany('posts'|'videos')</code>. Writes use <code className="inline-code">Model.morph()</code> + <code className="inline-code">Model.morphToMany().attach()</code>.
316
- </p>
317
- </section>
318
-
319
- <section className="feature-section">
320
- <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem', maxWidth: '60rem', margin: '0 auto' }}>
321
- <div className="demo-card">
322
- <div className="demo-card-header" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
323
- <h2 className="demo-card-title">Posts</h2>
324
- <button className="button-primary" onClick={addPost} disabled={loading}>+ Post</button>
325
- </div>
326
- <div className="demo-card-body">
327
- {posts.length === 0 && <p className="empty-state">No posts yet.</p>}
328
- {posts.map(p => (
329
- <div key={p.id} style={{ borderBottom: '1px solid var(--border, #e5e7eb)', padding: '0.75rem 0' }}>
330
- <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
331
- <strong>#{p.id} — {p.title}</strong>
332
- <span style={{ display: 'flex', gap: '0.25rem' }}>
333
- <button onClick={() => addComment('post', p.id)} disabled={loading} style={{ fontSize: '0.75rem', padding: '0.25rem 0.5rem' }}>+ Comment</button>
334
- <button onClick={() => attachTag('post', p.id)} disabled={loading} style={{ fontSize: '0.75rem', padding: '0.25rem 0.5rem' }}>+ Tag</button>
335
- </span>
336
- </div>
337
- {p.tags.length > 0 && (
338
- <div style={{ marginTop: '0.4rem', display: 'flex', flexWrap: 'wrap', gap: '0.25rem' }}>
339
- {p.tags.map(t => (
340
- <button
341
- key={t.id}
342
- onClick={() => detachTag('post', p.id, t.id)}
343
- disabled={loading}
344
- title="Click to detach"
345
- style={{ fontSize: '0.7rem', padding: '0.15rem 0.5rem', borderRadius: '999px', background: '#dbeafe', color: '#1e40af', border: '1px solid #93c5fd', cursor: 'pointer' }}
346
- >
347
- {t.name} ×
348
- </button>
349
- ))}
350
- </div>
351
- )}
352
- <ul style={{ marginTop: '0.5rem', paddingLeft: '1rem', fontSize: '0.85rem' }}>
353
- {p.comments.map(c => (
354
- <li key={c.id} style={{ marginBottom: '0.25rem' }}>
355
- {c.body} <button onClick={() => resolveParent(c.id)} style={{ fontSize: '0.7rem', marginLeft: '0.5rem' }}>resolve</button>
356
- </li>
357
- ))}
358
- {p.comments.length === 0 && <li style={{ color: 'var(--text-muted, #888)' }}>(no comments)</li>}
359
- </ul>
360
- </div>
361
- ))}
362
- </div>
363
- </div>
364
-
365
- <div className="demo-card">
366
- <div className="demo-card-header" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
367
- <h2 className="demo-card-title">Videos</h2>
368
- <button className="button-primary" onClick={addVideo} disabled={loading}>+ Video</button>
369
- </div>
370
- <div className="demo-card-body">
371
- {videos.length === 0 && <p className="empty-state">No videos yet.</p>}
372
- {videos.map(v => (
373
- <div key={v.id} style={{ borderBottom: '1px solid var(--border, #e5e7eb)', padding: '0.75rem 0' }}>
374
- <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
375
- <strong>#{v.id} — {v.url}</strong>
376
- <span style={{ display: 'flex', gap: '0.25rem' }}>
377
- <button onClick={() => addComment('video', v.id)} disabled={loading} style={{ fontSize: '0.75rem', padding: '0.25rem 0.5rem' }}>+ Comment</button>
378
- <button onClick={() => attachTag('video', v.id)} disabled={loading} style={{ fontSize: '0.75rem', padding: '0.25rem 0.5rem' }}>+ Tag</button>
379
- </span>
380
- </div>
381
- {v.tags.length > 0 && (
382
- <div style={{ marginTop: '0.4rem', display: 'flex', flexWrap: 'wrap', gap: '0.25rem' }}>
383
- {v.tags.map(t => (
384
- <button
385
- key={t.id}
386
- onClick={() => detachTag('video', v.id, t.id)}
387
- disabled={loading}
388
- title="Click to detach"
389
- style={{ fontSize: '0.7rem', padding: '0.15rem 0.5rem', borderRadius: '999px', background: '#fce7f3', color: '#9d174d', border: '1px solid #f9a8d4', cursor: 'pointer' }}
390
- >
391
- {t.name} ×
392
- </button>
393
- ))}
394
- </div>
395
- )}
396
- <ul style={{ marginTop: '0.5rem', paddingLeft: '1rem', fontSize: '0.85rem' }}>
397
- {v.comments.map(c => (
398
- <li key={c.id} style={{ marginBottom: '0.25rem' }}>
399
- {c.body} <button onClick={() => resolveParent(c.id)} style={{ fontSize: '0.7rem', marginLeft: '0.5rem' }}>resolve</button>
400
- </li>
401
- ))}
402
- {v.comments.length === 0 && <li style={{ color: 'var(--text-muted, #888)' }}>(no comments)</li>}
403
- </ul>
404
- </div>
405
- ))}
406
- </div>
407
- </div>
408
- </div>
409
-
410
- <div className="demo-card" style={{ maxWidth: '60rem', margin: '1rem auto 0' }}>
411
- <div className="demo-card-header" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
412
- <h2 className="demo-card-title">Tags (shared)</h2>
413
- <button className="button-primary" onClick={addTag} disabled={loading}>+ Tag</button>
414
- </div>
415
- <div className="demo-card-body">
416
- {tags.length === 0 && <p className="empty-state">No tags yet.</p>}
417
- {tags.length > 0 && (
418
- <div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.4rem' }}>
419
- {tags.map(t => (
420
- <button
421
- key={t.id}
422
- onClick={() => resolveInverse(t)}
423
- disabled={loading}
424
- title="Show every Post + Video tagged with this"
425
- style={{ fontSize: '0.8rem', padding: '0.25rem 0.6rem', borderRadius: '999px', background: '#f3f4f6', border: '1px solid #d1d5db', cursor: 'pointer' }}
426
- >
427
- {t.name}
428
- </button>
429
- ))}
430
- </div>
431
- )}
432
- </div>
433
- </div>
434
-
435
- {resolved && (
436
- <div className="demo-card" style={{ maxWidth: '40rem', margin: '1rem auto 0' }}>
437
- <div className="demo-card-header"><h2 className="demo-card-title">morphTo resolved</h2></div>
438
- <div className="demo-card-body">
439
- <code className="inline-code">comment.related('commentable').first()</code> ⇒ <strong>{resolved.type}</strong> #{resolved.id} — {resolved.title}
440
- </div>
441
- </div>
442
- )}
443
-
444
- {inverse && (
445
- <div className="demo-card" style={{ maxWidth: '60rem', margin: '1rem auto 0' }}>
446
- <div className="demo-card-header"><h2 className="demo-card-title">morphedByMany resolved — tag "{inverse.tag.name}"</h2></div>
447
- <div className="demo-card-body">
448
- <p style={{ fontSize: '0.85rem', color: 'var(--text-muted, #888)' }}>
449
- <code className="inline-code">tag.related('posts').get()</code> + <code className="inline-code">tag.related('videos').get()</code> — one pivot, two scoped reads.
450
- </p>
451
- <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem', marginTop: '0.5rem' }}>
452
- <div>
453
- <strong style={{ fontSize: '0.85rem' }}>Posts ({inverse.data.posts.length})</strong>
454
- <ul style={{ paddingLeft: '1rem', fontSize: '0.85rem' }}>
455
- {inverse.data.posts.map(p => <li key={p.id}>#{p.id} — {p.title}</li>)}
456
- {inverse.data.posts.length === 0 && <li style={{ color: 'var(--text-muted, #888)' }}>(none)</li>}
457
- </ul>
458
- </div>
459
- <div>
460
- <strong style={{ fontSize: '0.85rem' }}>Videos ({inverse.data.videos.length})</strong>
461
- <ul style={{ paddingLeft: '1rem', fontSize: '0.85rem' }}>
462
- {inverse.data.videos.map(v => <li key={v.id}>#{v.id} — {v.url}</li>)}
463
- {inverse.data.videos.length === 0 && <li style={{ color: 'var(--text-muted, #888)' }}>(none)</li>}
464
- </ul>
465
- </div>
466
- </div>
467
- </div>
468
- </div>
469
- )}
470
- </section>
471
- </div>
472
- )
473
- }
474
- `;
475
- }
476
- /**
477
- * Inlined into routes/web.ts demos block. Loads parents + hydrates each parent's
478
- * comments + tags via the polymorphic relations. Returns plain objects (Vike
479
- * refuses to serialize Model instances across the SSR boundary).
480
- */
481
- export function demosPolymorphicWebBlock() {
482
- return `Route.get('/demos/polymorphic', async () => {
483
- const [posts, videos, tags] = await Promise.all([Post.all(), Video.all(), Tag.all()])
484
- type WithRelated = { related(n: string): { get(): Promise<unknown[]> } }
485
- const hydrate = async (parent: Post | Video) => {
486
- const r = parent as unknown as WithRelated
487
- const [comments, ptags] = await Promise.all([
488
- r.related('comments').get() as Promise<Comment[]>,
489
- r.related('tags').get() as Promise<Tag[]>,
490
- ])
491
- return {
492
- ...parent,
493
- comments: comments.map(c => ({ ...c })),
494
- tags: ptags.map(t => ({ ...t })),
495
- }
496
- }
497
- return view('demos.polymorphic', {
498
- posts: await Promise.all(posts.map(hydrate)),
499
- videos: await Promise.all(videos.map(hydrate)),
500
- tags: tags.map(t => ({ ...t })),
501
- })
502
- })`;
503
- }
504
- /**
505
- * Inlined into routes/api.ts demos block. Endpoints: state, create-post,
506
- * create-video, comment-on-post, comment-on-video, morphTo resolution,
507
- * create-tag, attach/detach tag (post + video), morphedByMany inverse.
508
- */
509
- export function demosPolymorphicApiBlock() {
510
- return `// ── /demos/polymorphic — every polymorphic relation type via @rudderjs/orm ──
511
-
512
- // GET /api/polymorphic/state — posts + videos with comments + tags + the flat tag list.
513
- router.get('/api/polymorphic/state', async (_req, res) => {
514
- const [posts, videos, tags] = await Promise.all([Post.all(), Video.all(), Tag.all()])
515
- const hydrate = async (parent: Post | Video) => {
516
- const r = parent as unknown as { related(n: string): { get(): Promise<unknown[]> } }
517
- const [comments, ptags] = await Promise.all([
518
- r.related('comments').get() as Promise<Comment[]>,
519
- r.related('tags').get() as Promise<Tag[]>,
520
- ])
521
- return {
522
- ...parent,
523
- comments: comments.map(c => ({ ...c })),
524
- tags: ptags.map(t => ({ ...t })),
525
- }
526
- }
527
- res.json({
528
- posts: await Promise.all(posts.map(hydrate)),
529
- videos: await Promise.all(videos.map(hydrate)),
530
- tags: tags.map(t => ({ ...t })),
531
- })
532
- })
533
-
534
- // POST /api/polymorphic/posts — create a post.
535
- router.post('/api/polymorphic/posts', async (req, res) => {
536
- const { title } = (req.body ?? {}) as { title?: string }
537
- if (!title) return res.status(400).json({ error: 'title required' })
538
- const post = await Post.create({ title })
539
- res.status(201).json({ ...post })
540
- })
541
-
542
- // POST /api/polymorphic/videos — create a video.
543
- router.post('/api/polymorphic/videos', async (req, res) => {
544
- const { url } = (req.body ?? {}) as { url?: string }
545
- if (!url) return res.status(400).json({ error: 'url required' })
546
- const video = await Video.create({ url })
547
- res.status(201).json({ ...video })
548
- })
549
-
550
- // POST /api/polymorphic/(posts|videos)/:id/comments — Model.morph() write.
551
- router.post('/api/polymorphic/posts/:id/comments', async (req, res) => {
552
- const idParam = req.params['id']
553
- if (!idParam) return res.status(400).json({ error: 'id required' })
554
- const post = await Post.find(Number(idParam))
555
- if (!post) return res.status(404).json({ error: 'post not found' })
556
- const { body } = (req.body ?? {}) as { body?: string }
557
- if (!body) return res.status(400).json({ error: 'body required' })
558
- const comment = await Comment.create({ body, ...Model.morph('commentable', post) })
559
- res.status(201).json({ ...comment })
560
- })
561
-
562
- router.post('/api/polymorphic/videos/:id/comments', async (req, res) => {
563
- const idParam = req.params['id']
564
- if (!idParam) return res.status(400).json({ error: 'id required' })
565
- const video = await Video.find(Number(idParam))
566
- if (!video) return res.status(404).json({ error: 'video not found' })
567
- const { body } = (req.body ?? {}) as { body?: string }
568
- if (!body) return res.status(400).json({ error: 'body required' })
569
- const comment = await Comment.create({ body, ...Model.morph('commentable', video) })
570
- res.status(201).json({ ...comment })
571
- })
572
-
573
- // GET /api/polymorphic/comments/:id/parent — morphTo resolution.
574
- router.get('/api/polymorphic/comments/:id/parent', async (req, res) => {
575
- const idParam = req.params['id']
576
- if (!idParam) return res.status(400).json({ error: 'id required' })
577
- const comment = await Comment.find(Number(idParam))
578
- if (!comment) return res.status(404).json({ error: 'comment not found' })
579
-
580
- const parent = await (comment as unknown as { related(n: string): { first(): Promise<Post | Video | null> } })
581
- .related('commentable').first()
582
- if (!parent) return res.status(404).json({ error: 'parent not found' })
583
-
584
- res.json({
585
- type: comment.commentableType,
586
- id: parent.id,
587
- title: 'title' in parent ? parent.title : parent.url,
588
- })
589
- })
590
-
591
- // ── morphToMany / morphedByMany — Tag endpoints ────────────────────────────
592
-
593
- // POST /api/polymorphic/tags — create a tag.
594
- router.post('/api/polymorphic/tags', async (req, res) => {
595
- const { name } = (req.body ?? {}) as { name?: string }
596
- if (!name) return res.status(400).json({ error: 'name required' })
597
- const tag = await Tag.create({ name })
598
- res.status(201).json({ ...tag })
599
- })
600
-
601
- // POST /api/polymorphic/posts/:id/tags — morphToMany attach. The pivot row
602
- // carries taggableType='Post' automatically.
603
- router.post('/api/polymorphic/posts/:id/tags', async (req, res) => {
604
- const idParam = req.params['id']
605
- if (!idParam) return res.status(400).json({ error: 'id required' })
606
- const post = await Post.find(Number(idParam))
607
- if (!post) return res.status(404).json({ error: 'post not found' })
608
- const { tagId } = (req.body ?? {}) as { tagId?: number }
609
- if (typeof tagId !== 'number') return res.status(400).json({ error: 'tagId required' })
610
- await Model.morphToMany(post, 'tags').attach([tagId])
611
- res.json({ ok: true })
612
- })
613
-
614
- router.post('/api/polymorphic/videos/:id/tags', async (req, res) => {
615
- const idParam = req.params['id']
616
- if (!idParam) return res.status(400).json({ error: 'id required' })
617
- const video = await Video.find(Number(idParam))
618
- if (!video) return res.status(404).json({ error: 'video not found' })
619
- const { tagId } = (req.body ?? {}) as { tagId?: number }
620
- if (typeof tagId !== 'number') return res.status(400).json({ error: 'tagId required' })
621
- await Model.morphToMany(video, 'tags').attach([tagId])
622
- res.json({ ok: true })
623
- })
624
-
625
- // DELETE /api/polymorphic/(posts|videos)/:id/tags/:tagId — morphToMany detach
626
- // scoped to the parent's discriminator (videos sharing the tag are untouched).
627
- router.delete('/api/polymorphic/posts/:id/tags/:tagId', async (req, res) => {
628
- const id = req.params['id']; const tagId = req.params['tagId']
629
- if (!id || !tagId) return res.status(400).json({ error: 'id/tagId required' })
630
- const post = await Post.find(Number(id))
631
- if (!post) return res.status(404).json({ error: 'post not found' })
632
- await Model.morphToMany(post, 'tags').detach([Number(tagId)])
633
- res.json({ ok: true })
634
- })
635
-
636
- router.delete('/api/polymorphic/videos/:id/tags/:tagId', async (req, res) => {
637
- const id = req.params['id']; const tagId = req.params['tagId']
638
- if (!id || !tagId) return res.status(400).json({ error: 'id/tagId required' })
639
- const video = await Video.find(Number(id))
640
- if (!video) return res.status(404).json({ error: 'video not found' })
641
- await Model.morphToMany(video, 'tags').detach([Number(tagId)])
642
- res.json({ ok: true })
643
- })
644
-
645
- // GET /api/polymorphic/tags/:id/items — morphedByMany inverse fan-out.
646
- // One pivot, two scoped reads (one per concrete inverse class).
647
- router.get('/api/polymorphic/tags/:id/items', async (req, res) => {
648
- const idParam = req.params['id']
649
- if (!idParam) return res.status(400).json({ error: 'id required' })
650
- const tag = await Tag.find(Number(idParam))
651
- if (!tag) return res.status(404).json({ error: 'tag not found' })
652
- const r = tag as unknown as { related(n: string): { get(): Promise<unknown[]> } }
653
- const [posts, videos] = await Promise.all([
654
- r.related('posts').get() as Promise<Post[]>,
655
- r.related('videos').get() as Promise<Video[]>,
656
- ])
657
- res.json({
658
- posts: posts.map(p => ({ ...p })),
659
- videos: videos.map(v => ({ ...v })),
660
- })
661
- })`;
662
- }
663
- //# sourceMappingURL=polymorphic.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"polymorphic.js","sourceRoot":"","sources":["../../../src/templates/demos/polymorphic.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,yEAAyE;AACzE,EAAE;AACF,4EAA4E;AAC5E,2EAA2E;AAC3E,+EAA+E;AAC/E,8EAA8E;AAC9E,sCAAsC;AAEtC,MAAM,UAAU,WAAW;IACzB,OAAO;;;;;;;;;;;;;;;;;CAiBR,CAAA;AACD,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,OAAO;;;;;;;;;;;;;;;;;CAiBR,CAAA;AACD,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,OAAO;;;;;;;;;;;;;;;;;;CAkBR,CAAA;AACD,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;CA0BR,CAAA;AACD,CAAC;AAED,MAAM,UAAU,sBAAsB;IACpC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyCR,CAAA;AACD,CAAC;AAED,MAAM,UAAU,oBAAoB;IAClC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0UR,CAAA;AACD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,wBAAwB;IACtC,OAAO;;;;;;;;;;;;;;;;;;;;GAoBN,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,wBAAwB;IACtC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuJN,CAAA;AACH,CAAC"}
@@ -1,4 +0,0 @@
1
- export declare function demosQueueView(): string;
2
- export declare function exampleJob(): string;
3
- export declare function demosQueueApiBlock(): string;
4
- //# sourceMappingURL=queue.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../../../src/templates/demos/queue.ts"],"names":[],"mappings":"AAGA,wBAAgB,cAAc,IAAI,MAAM,CA6DvC;AAED,wBAAgB,UAAU,IAAI,MAAM,CAyBnC;AAED,wBAAgB,kBAAkB,IAAI,MAAM,CAO3C"}