chadstart 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +10 -0
- package/.env.example +46 -0
- package/.github/workflows/browser-test.yml +34 -0
- package/.github/workflows/docker-publish.yml +54 -0
- package/.github/workflows/docs.yml +31 -0
- package/.github/workflows/npm-chadstart.yml +27 -0
- package/.github/workflows/npm-sdk.yml +38 -0
- package/.github/workflows/test.yml +85 -0
- package/.weblate +9 -0
- package/Dockerfile +23 -0
- package/README.md +348 -0
- package/admin/index.html +2802 -0
- package/admin/login.html +207 -0
- package/chadstart.example.yml +416 -0
- package/chadstart.schema.json +367 -0
- package/chadstart.yaml +53 -0
- package/cli/cli.js +295 -0
- package/core/api-generator.js +606 -0
- package/core/auth.js +298 -0
- package/core/db.js +384 -0
- package/core/entity-engine.js +166 -0
- package/core/error-reporter.js +132 -0
- package/core/file-storage.js +97 -0
- package/core/functions-engine.js +353 -0
- package/core/openapi.js +171 -0
- package/core/plugin-loader.js +92 -0
- package/core/realtime.js +93 -0
- package/core/schema-validator.js +50 -0
- package/core/seeder.js +231 -0
- package/core/telemetry.js +119 -0
- package/core/upload.js +372 -0
- package/core/workers/php_worker.php +19 -0
- package/core/workers/python_worker.py +33 -0
- package/core/workers/ruby_worker.rb +21 -0
- package/core/yaml-loader.js +64 -0
- package/demo/chadstart.yaml +178 -0
- package/demo/docker-compose.yml +31 -0
- package/demo/functions/greet.go +39 -0
- package/demo/functions/hello.cpp +18 -0
- package/demo/functions/hello.py +13 -0
- package/demo/functions/hello.rb +10 -0
- package/demo/functions/onTodoCreated.js +13 -0
- package/demo/functions/ping.sh +13 -0
- package/demo/functions/stats.js +22 -0
- package/demo/public/index.html +522 -0
- package/docker-compose.yml +17 -0
- package/docs/access-policies.md +155 -0
- package/docs/admin-ui.md +29 -0
- package/docs/angular.md +69 -0
- package/docs/astro.md +71 -0
- package/docs/auth.md +160 -0
- package/docs/cli.md +56 -0
- package/docs/config.md +127 -0
- package/docs/crud.md +627 -0
- package/docs/deploy.md +113 -0
- package/docs/docker.md +59 -0
- package/docs/entities.md +385 -0
- package/docs/functions.md +196 -0
- package/docs/getting-started.md +79 -0
- package/docs/groups.md +85 -0
- package/docs/index.md +5 -0
- package/docs/llm-rules.md +81 -0
- package/docs/middlewares.md +78 -0
- package/docs/overrides/home.html +350 -0
- package/docs/plugins.md +59 -0
- package/docs/react.md +75 -0
- package/docs/realtime.md +43 -0
- package/docs/s3-storage.md +40 -0
- package/docs/security.md +23 -0
- package/docs/stylesheets/extra.css +375 -0
- package/docs/svelte.md +71 -0
- package/docs/telemetry.md +97 -0
- package/docs/upload.md +168 -0
- package/docs/validation.md +115 -0
- package/docs/vue.md +86 -0
- package/docs/webhooks.md +87 -0
- package/index.js +11 -0
- package/locales/en/admin.json +169 -0
- package/mkdocs.yml +82 -0
- package/package.json +65 -0
- package/playwright.config.js +24 -0
- package/public/.gitkeep +0 -0
- package/sdk/README.md +284 -0
- package/sdk/package.json +39 -0
- package/sdk/scripts/build.js +58 -0
- package/sdk/src/index.js +368 -0
- package/sdk/test/sdk.test.cjs +340 -0
- package/sdk/types/index.d.ts +217 -0
- package/server/express-server.js +734 -0
- package/test/access-policies.test.js +96 -0
- package/test/ai.test.js +81 -0
- package/test/api-keys.test.js +361 -0
- package/test/auth.test.js +122 -0
- package/test/browser/admin-ui.spec.js +127 -0
- package/test/browser/global-setup.js +71 -0
- package/test/browser/global-teardown.js +11 -0
- package/test/db.test.js +227 -0
- package/test/entity-engine.test.js +193 -0
- package/test/error-reporter.test.js +140 -0
- package/test/functions-engine.test.js +240 -0
- package/test/groups.test.js +212 -0
- package/test/hot-reload.test.js +153 -0
- package/test/i18n.test.js +173 -0
- package/test/middleware.test.js +76 -0
- package/test/openapi.test.js +67 -0
- package/test/schema-validator.test.js +83 -0
- package/test/sdk.test.js +90 -0
- package/test/seeder.test.js +279 -0
- package/test/settings.test.js +109 -0
- package/test/telemetry.test.js +254 -0
- package/test/test.js +17 -0
- package/test/upload.test.js +265 -0
- package/test/validation.test.js +96 -0
- package/test/yaml-loader.test.js +93 -0
- package/utils/logger.js +24 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
/* ============================================================
|
|
2
|
+
Landing page hero section
|
|
3
|
+
============================================================ */
|
|
4
|
+
|
|
5
|
+
.mdx-container {
|
|
6
|
+
padding-top: 1rem;
|
|
7
|
+
position: relative;
|
|
8
|
+
overflow: hidden;
|
|
9
|
+
background: linear-gradient(
|
|
10
|
+
to bottom,
|
|
11
|
+
var(--md-primary-fg-color),
|
|
12
|
+
hsla(var(--md-hue), 15%, 21%, 1) 99%,
|
|
13
|
+
var(--md-default-bg-color) 99%
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/* ── Tech grid background overlay ── */
|
|
18
|
+
.mdx-tech-bg {
|
|
19
|
+
position: absolute;
|
|
20
|
+
inset: 0;
|
|
21
|
+
z-index: 0;
|
|
22
|
+
pointer-events: none;
|
|
23
|
+
background-image:
|
|
24
|
+
linear-gradient(rgba(255, 255, 255, 0.04) 1px, transparent 1px),
|
|
25
|
+
linear-gradient(90deg, rgba(255, 255, 255, 0.04) 1px, transparent 1px);
|
|
26
|
+
background-size: 40px 40px;
|
|
27
|
+
mask-image: linear-gradient(
|
|
28
|
+
to bottom,
|
|
29
|
+
transparent 0%,
|
|
30
|
+
rgba(0, 0, 0, 0.6) 15%,
|
|
31
|
+
rgba(0, 0, 0, 0.6) 85%,
|
|
32
|
+
transparent 100%
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* ── Floating warrior mascots (parallax) ── */
|
|
37
|
+
.mdx-mascot {
|
|
38
|
+
position: absolute;
|
|
39
|
+
bottom: 0;
|
|
40
|
+
z-index: 1;
|
|
41
|
+
pointer-events: none;
|
|
42
|
+
user-select: none;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.mdx-mascot img {
|
|
46
|
+
width: 200px;
|
|
47
|
+
height: auto;
|
|
48
|
+
filter: drop-shadow(0 8px 24px rgba(0, 0, 0, 0.5));
|
|
49
|
+
animation: mdx-mascot-float 6s ease-in-out infinite;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.mdx-mascot--left {
|
|
53
|
+
left: 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.mdx-mascot--right {
|
|
57
|
+
right: 0;
|
|
58
|
+
animation-delay: -3s;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.mdx-mascot--right img {
|
|
62
|
+
animation-delay: -3s;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@keyframes mdx-mascot-float {
|
|
66
|
+
0%, 100% { transform: translateY(0px) rotate(-1.5deg); }
|
|
67
|
+
50% { transform: translateY(-18px) rotate(1.5deg); }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@media screen and (max-width: 96em) {
|
|
71
|
+
.mdx-mascot { display: none; }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.mdx-hero {
|
|
75
|
+
position: relative;
|
|
76
|
+
z-index: 2;
|
|
77
|
+
display: grid;
|
|
78
|
+
grid-template-columns: 1fr 1fr;
|
|
79
|
+
gap: 3rem;
|
|
80
|
+
align-items: center;
|
|
81
|
+
padding: 4rem 0 5rem;
|
|
82
|
+
color: var(--md-primary-bg-color);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
@media screen and (max-width: 76.25em) {
|
|
86
|
+
.mdx-hero {
|
|
87
|
+
grid-template-columns: 1fr;
|
|
88
|
+
text-align: center;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.mdx-hero__code {
|
|
92
|
+
display: none;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.mdx-hero h1 {
|
|
97
|
+
font-size: 2.8rem;
|
|
98
|
+
font-weight: 700;
|
|
99
|
+
line-height: 1.2;
|
|
100
|
+
color: var(--md-primary-bg-color);
|
|
101
|
+
margin-bottom: 1rem;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.mdx-hero__accent {
|
|
105
|
+
/* subtle fade on the second heading line for visual layering */
|
|
106
|
+
opacity: 0.85;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.mdx-hero__description {
|
|
110
|
+
font-size: 1.15rem;
|
|
111
|
+
line-height: 1.7;
|
|
112
|
+
opacity: 0.9;
|
|
113
|
+
margin-bottom: 2rem;
|
|
114
|
+
max-width: 480px;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@media screen and (max-width: 76.25em) {
|
|
118
|
+
.mdx-hero__description {
|
|
119
|
+
max-width: 100%;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/* Code block in hero */
|
|
124
|
+
.mdx-hero__code {
|
|
125
|
+
background-color: hsla(0, 0%, 0%, 0.4);
|
|
126
|
+
border-radius: 0.5rem;
|
|
127
|
+
padding: 1.5rem;
|
|
128
|
+
overflow: auto;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.mdx-hero__code pre {
|
|
132
|
+
margin: 0;
|
|
133
|
+
background: transparent;
|
|
134
|
+
padding: 0;
|
|
135
|
+
overflow: visible;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.mdx-hero__code code {
|
|
139
|
+
font-size: 0.85rem;
|
|
140
|
+
line-height: 1.7;
|
|
141
|
+
color: #e0e0e0;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/* YAML syntax highlighting colours in hero */
|
|
145
|
+
.mdx-hero__code .nc { color: #82b1ff; font-weight: 600; }
|
|
146
|
+
.mdx-hero__code .na { color: #80cbc4; }
|
|
147
|
+
.mdx-hero__code .s { color: #c3e88d; }
|
|
148
|
+
.mdx-hero__code .kc { color: #f78c6c; }
|
|
149
|
+
.mdx-hero__code .m { color: #f78c6c; }
|
|
150
|
+
|
|
151
|
+
/* ============================================================
|
|
152
|
+
CTA buttons — colourful spinning border animation
|
|
153
|
+
(inspired by coding2go.com/border-animation/)
|
|
154
|
+
============================================================ */
|
|
155
|
+
|
|
156
|
+
.mdx-cta-group {
|
|
157
|
+
display: flex;
|
|
158
|
+
flex-wrap: wrap;
|
|
159
|
+
gap: 1rem;
|
|
160
|
+
margin-top: 1.5rem;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
@media screen and (max-width: 76.25em) {
|
|
164
|
+
.mdx-cta-group {
|
|
165
|
+
justify-content: center;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.mdx-cta {
|
|
170
|
+
position: relative;
|
|
171
|
+
z-index: 0;
|
|
172
|
+
display: inline-flex;
|
|
173
|
+
align-items: center;
|
|
174
|
+
gap: 0.4em;
|
|
175
|
+
padding: 0.7em 1.7em;
|
|
176
|
+
border-radius: 0.4rem;
|
|
177
|
+
font-size: 0.9rem;
|
|
178
|
+
font-weight: 600;
|
|
179
|
+
text-decoration: none;
|
|
180
|
+
cursor: pointer;
|
|
181
|
+
overflow: hidden;
|
|
182
|
+
transition: transform 0.2s, opacity 0.2s;
|
|
183
|
+
white-space: nowrap;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.mdx-cta:hover {
|
|
187
|
+
transform: translateY(-2px);
|
|
188
|
+
opacity: 0.92;
|
|
189
|
+
text-decoration: none;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/* Spinning conic-gradient that forms the animated border */
|
|
193
|
+
.mdx-cta::before {
|
|
194
|
+
content: '';
|
|
195
|
+
position: absolute;
|
|
196
|
+
z-index: -2;
|
|
197
|
+
left: -50%;
|
|
198
|
+
top: -50%;
|
|
199
|
+
width: 200%;
|
|
200
|
+
height: 200%;
|
|
201
|
+
background: conic-gradient(
|
|
202
|
+
#ff0080,
|
|
203
|
+
#ff8c00,
|
|
204
|
+
#ffe600,
|
|
205
|
+
#00ff80,
|
|
206
|
+
#00cfff,
|
|
207
|
+
#7b2fff,
|
|
208
|
+
#ff0080
|
|
209
|
+
);
|
|
210
|
+
animation: mdx-cta-border-spin 3s linear infinite;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/* Inner fill — hides the gradient except at the border edges */
|
|
214
|
+
.mdx-cta::after {
|
|
215
|
+
content: '';
|
|
216
|
+
position: absolute;
|
|
217
|
+
z-index: -1;
|
|
218
|
+
inset: 3px;
|
|
219
|
+
border-radius: calc(0.4rem - 3px);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
@keyframes mdx-cta-border-spin {
|
|
223
|
+
to { transform: rotate(360deg); }
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/* Primary: white fill */
|
|
227
|
+
.mdx-cta--primary {
|
|
228
|
+
color: var(--md-primary-fg-color);
|
|
229
|
+
}
|
|
230
|
+
.mdx-cta--primary::after {
|
|
231
|
+
background: #fff;
|
|
232
|
+
}
|
|
233
|
+
.mdx-cta--primary span {
|
|
234
|
+
color: var(--md-primary-fg-color);
|
|
235
|
+
font-weight: 700;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/* Ghost: dark fill */
|
|
239
|
+
.mdx-cta--ghost {
|
|
240
|
+
color: var(--md-primary-bg-color);
|
|
241
|
+
}
|
|
242
|
+
.mdx-cta--ghost::after {
|
|
243
|
+
background: hsla(var(--md-hue), 25%, 18%, 0.95);
|
|
244
|
+
}
|
|
245
|
+
.mdx-cta--ghost span,
|
|
246
|
+
.mdx-cta--ghost svg {
|
|
247
|
+
color: var(--md-primary-bg-color);
|
|
248
|
+
fill: var(--md-primary-bg-color);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.mdx-cta svg {
|
|
252
|
+
width: 1em;
|
|
253
|
+
height: 1em;
|
|
254
|
+
fill: currentColor;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/* ============================================================
|
|
258
|
+
Getting started section
|
|
259
|
+
============================================================ */
|
|
260
|
+
|
|
261
|
+
.mdx-getting-started {
|
|
262
|
+
padding: 4rem 0;
|
|
263
|
+
background: var(--md-code-bg-color);
|
|
264
|
+
border-top: 1px solid var(--md-default-fg-color--lightest);
|
|
265
|
+
border-bottom: 1px solid var(--md-default-fg-color--lightest);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.mdx-section-title {
|
|
269
|
+
text-align: center;
|
|
270
|
+
font-size: 1.8rem;
|
|
271
|
+
font-weight: 700;
|
|
272
|
+
margin-bottom: 2.5rem;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.mdx-gs-steps {
|
|
276
|
+
display: grid;
|
|
277
|
+
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
|
278
|
+
gap: 2rem;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.mdx-gs-step {
|
|
282
|
+
display: flex;
|
|
283
|
+
gap: 1rem;
|
|
284
|
+
align-items: flex-start;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.mdx-gs-step__num {
|
|
288
|
+
flex-shrink: 0;
|
|
289
|
+
width: 2.2rem;
|
|
290
|
+
height: 2.2rem;
|
|
291
|
+
background: var(--md-primary-fg-color);
|
|
292
|
+
color: var(--md-primary-bg-color);
|
|
293
|
+
border-radius: 50%;
|
|
294
|
+
display: flex;
|
|
295
|
+
align-items: center;
|
|
296
|
+
justify-content: center;
|
|
297
|
+
font-weight: 700;
|
|
298
|
+
font-size: 1rem;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
.mdx-gs-step__label {
|
|
302
|
+
font-weight: 600;
|
|
303
|
+
margin: 0 0 0.5rem;
|
|
304
|
+
font-size: 0.95rem;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.mdx-gs-step__code pre {
|
|
308
|
+
margin: 0 0 0.5rem;
|
|
309
|
+
border-radius: 0.3rem;
|
|
310
|
+
font-size: 0.8rem;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.mdx-gs-step__note {
|
|
314
|
+
display: block;
|
|
315
|
+
font-size: 0.78rem;
|
|
316
|
+
color: var(--md-default-fg-color--light);
|
|
317
|
+
line-height: 1.5;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/* ============================================================
|
|
321
|
+
Features section
|
|
322
|
+
============================================================ */
|
|
323
|
+
|
|
324
|
+
.mdx-features {
|
|
325
|
+
padding: 4rem 0;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.mdx-features__grid {
|
|
329
|
+
display: grid;
|
|
330
|
+
grid-template-columns: repeat(3, 1fr);
|
|
331
|
+
gap: 1.5rem;
|
|
332
|
+
margin-bottom: 2rem;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
@media screen and (max-width: 76.25em) {
|
|
336
|
+
.mdx-features__grid {
|
|
337
|
+
grid-template-columns: repeat(2, 1fr);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
@media screen and (max-width: 44.9375em) {
|
|
342
|
+
.mdx-features__grid {
|
|
343
|
+
grid-template-columns: 1fr;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.mdx-feature-card {
|
|
348
|
+
border: 1px solid var(--md-default-fg-color--lightest);
|
|
349
|
+
border-radius: 0.5rem;
|
|
350
|
+
padding: 1.5rem;
|
|
351
|
+
transition: border-color 0.2s, box-shadow 0.2s;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
.mdx-feature-card:hover {
|
|
355
|
+
border-color: var(--md-accent-fg-color);
|
|
356
|
+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.mdx-feature-card__icon {
|
|
360
|
+
font-size: 2rem;
|
|
361
|
+
margin-bottom: 0.75rem;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
.mdx-feature-card h3 {
|
|
365
|
+
font-size: 1rem;
|
|
366
|
+
font-weight: 600;
|
|
367
|
+
margin: 0 0 0.5rem;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
.mdx-feature-card p {
|
|
371
|
+
font-size: 0.875rem;
|
|
372
|
+
line-height: 1.65;
|
|
373
|
+
color: var(--md-default-fg-color--light);
|
|
374
|
+
margin: 0;
|
|
375
|
+
}
|
package/docs/svelte.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: svelte
|
|
3
|
+
title: Create a Full-Stack app with Svelte and ChadStart
|
|
4
|
+
description: Quick start guide to create a full-stack app using Svelte as a frontend and ChadStart as a backend.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Quick start with Svelte
|
|
8
|
+
|
|
9
|
+
Give a proper backend to your Svelte app.
|
|
10
|
+
|
|
11
|
+
!!! warning
|
|
12
|
+
This quick start guide focuses exclusively on the **frontend**. To ensure the functionality of this code, your ChadStart backend must be [up and running](./getting-started.md#install-chadstart) at `http://localhost:3000`.
|
|
13
|
+
|
|
14
|
+
## 1. Create a Svelte app
|
|
15
|
+
|
|
16
|
+
If you already have a Svelte app running, you can skip this step.
|
|
17
|
+
|
|
18
|
+
There are several ways to do that. In our example we use [SvelteKit](https://kit.svelte.dev/) to generate a pre-configured Svelte app, you . You can replace `my-client` by the name of your front-end app.
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
npm create svelte@latest my-client
|
|
22
|
+
cd my-client
|
|
23
|
+
npm install
|
|
24
|
+
npm run dev -- --open
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## 2. Install ChadStart SDK
|
|
28
|
+
|
|
29
|
+
Install the JS SDK from the root of your Svelte app.
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
npm i @chadstart/sdk
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## 3. Use it in your app
|
|
36
|
+
|
|
37
|
+
In that example we are using a Cat entity [created previously](entities.md). Replace it by your own entity. This example uses TypeScript, you can remove the typing to have plain JS.
|
|
38
|
+
|
|
39
|
+
```js title="src/routes/+page.svelte"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
<script lang="ts">
|
|
43
|
+
import ChadStart from "@chadstart/sdk";
|
|
44
|
+
import { onMount } from "svelte";
|
|
45
|
+
|
|
46
|
+
interface Cat {
|
|
47
|
+
id: string;
|
|
48
|
+
name: string;
|
|
49
|
+
type: string;
|
|
50
|
+
image: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let cats: Cat[] = [];
|
|
54
|
+
|
|
55
|
+
onMount(async () => {
|
|
56
|
+
const chadstart = new ChadStart();
|
|
57
|
+
const result = await chadstart.from("cats").find<Cat>();
|
|
58
|
+
cats = result.data;
|
|
59
|
+
});
|
|
60
|
+
</script>
|
|
61
|
+
|
|
62
|
+
<div class="main">
|
|
63
|
+
<ul>
|
|
64
|
+
{#each cats as cat}
|
|
65
|
+
<li>{cat.name}</li>
|
|
66
|
+
{/each}
|
|
67
|
+
</ul>
|
|
68
|
+
</div>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Checkout the [SDK doc](./crud.md#using-the-javascript-sdk) to see more usages of the SDK: CRUD operations, file upload, authentication,
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: telemetry
|
|
3
|
+
title: Telemetry
|
|
4
|
+
description: Monitor your ChadStart backend with OpenTelemetry. Send traces and metrics to any OTLP-compatible backend like Grafana, Datadog, or Jaeger.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Telemetry
|
|
8
|
+
|
|
9
|
+
ChadStart includes built-in observability via **OpenTelemetry** (OTel). Enable it to export distributed traces to any OTLP-compatible collector such as [Grafana Tempo](https://grafana.com/oss/tempo/), [Datadog](https://www.datadoghq.com/), [Jaeger](https://www.jaegertracing.io/), or [Honeycomb](https://www.honeycomb.io/).
|
|
10
|
+
|
|
11
|
+
## Configuration
|
|
12
|
+
|
|
13
|
+
Add a `telemetry` block to your `chadstart.yaml`:
|
|
14
|
+
|
|
15
|
+
```yaml title="chadstart.yaml"
|
|
16
|
+
telemetry:
|
|
17
|
+
enabled: true
|
|
18
|
+
serviceName: my-app
|
|
19
|
+
endpoint: http://localhost:4318
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
| Option | Default | Description |
|
|
23
|
+
| --------------- | -------------------------- | --------------------------------------------------------- |
|
|
24
|
+
| **enabled** | `false` | Set to `true` to activate OpenTelemetry tracing |
|
|
25
|
+
| **serviceName** | `chadstart-app` | The service name reported to your collector |
|
|
26
|
+
| **endpoint** | `http://localhost:4318` | Base URL of your OTLP collector (HTTP protocol) |
|
|
27
|
+
|
|
28
|
+
!!! warning "Auth headers are secrets"
|
|
29
|
+
If your collector requires an API key or bearer token, **never** put it in `chadstart.yaml`. Use the `OTEL_EXPORTER_OTLP_HEADERS` environment variable instead (see below).
|
|
30
|
+
|
|
31
|
+
## Environment variables
|
|
32
|
+
|
|
33
|
+
All telemetry options can be set (or overridden) via environment variables. Environment variables always take precedence over YAML values.
|
|
34
|
+
|
|
35
|
+
| Variable | Description |
|
|
36
|
+
| ------------------------------- | ---------------------------------------------------------------------------------------- |
|
|
37
|
+
| `OTEL_ENABLED=true` | Enable tracing (overrides `telemetry.enabled`) |
|
|
38
|
+
| `OTEL_SERVICE_NAME` | Service name sent to the collector (overrides `telemetry.serviceName`) |
|
|
39
|
+
| `OTEL_EXPORTER_OTLP_ENDPOINT` | Collector base URL (overrides `telemetry.endpoint`) |
|
|
40
|
+
| `OTEL_EXPORTER_OTLP_HEADERS` | Comma-separated `key=value` auth headers, e.g. `authorization=Bearer <token>` **(secrets — env only)** |
|
|
41
|
+
|
|
42
|
+
Add these to your `.env` file:
|
|
43
|
+
|
|
44
|
+
```bash title=".env"
|
|
45
|
+
OTEL_ENABLED=true
|
|
46
|
+
OTEL_SERVICE_NAME=my-app
|
|
47
|
+
OTEL_EXPORTER_OTLP_ENDPOINT=https://otlp.example.com
|
|
48
|
+
OTEL_EXPORTER_OTLP_HEADERS=authorization=Bearer my-secret-token
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Example: Grafana Cloud
|
|
52
|
+
|
|
53
|
+
1. Create a free account at [grafana.com](https://grafana.com/auth/sign-up).
|
|
54
|
+
2. Go to **My Account → Grafana Cloud → OpenTelemetry**.
|
|
55
|
+
3. Copy your OTLP endpoint and instance token.
|
|
56
|
+
4. Add the following to your `.env`:
|
|
57
|
+
|
|
58
|
+
```bash title=".env"
|
|
59
|
+
OTEL_ENABLED=true
|
|
60
|
+
OTEL_SERVICE_NAME=my-app
|
|
61
|
+
OTEL_EXPORTER_OTLP_ENDPOINT=https://otlp-gateway-prod-eu-west-0.grafana.net/otlp
|
|
62
|
+
OTEL_EXPORTER_OTLP_HEADERS=authorization=Basic <base64-encoded-token>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Example: Datadog
|
|
66
|
+
|
|
67
|
+
```bash title=".env"
|
|
68
|
+
OTEL_ENABLED=true
|
|
69
|
+
OTEL_SERVICE_NAME=my-app
|
|
70
|
+
OTEL_EXPORTER_OTLP_ENDPOINT=https://api.datadoghq.com
|
|
71
|
+
OTEL_EXPORTER_OTLP_HEADERS=DD-API-KEY=<your-datadog-api-key>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Local development with Jaeger
|
|
75
|
+
|
|
76
|
+
Run a local [Jaeger](https://www.jaegertracing.io/) instance with Docker to visualise traces during development:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
docker run -d --name jaeger \
|
|
80
|
+
-p 16686:16686 \
|
|
81
|
+
-p 4318:4318 \
|
|
82
|
+
jaegertracing/all-in-one:latest
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Then in your `chadstart.yaml`:
|
|
86
|
+
|
|
87
|
+
```yaml title="chadstart.yaml"
|
|
88
|
+
telemetry:
|
|
89
|
+
enabled: true
|
|
90
|
+
serviceName: my-app
|
|
91
|
+
endpoint: http://localhost:4318
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Open [http://localhost:16686](http://localhost:16686) to explore traces.
|
|
95
|
+
|
|
96
|
+
!!! note
|
|
97
|
+
Telemetry uses the OTLP **HTTP** protocol on port `4318` (not the gRPC port `4317`). Make sure your collector is configured to accept HTTP.
|
package/docs/upload.md
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: upload
|
|
3
|
+
title: File and Image Uploads
|
|
4
|
+
description: Upload files and images with ChadStart built-in storage system to upload assets in the file storage or any S3-compatible storage.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Uploads
|
|
8
|
+
|
|
9
|
+
## Introduction
|
|
10
|
+
|
|
11
|
+
ChadStart comes with a **built-in storage system** to upload assets locally (default) or in a [S3 bucket](./s3-storage.md). You can use [file upload](#upload-a-file) to let your users update any kind of file, or [image upload](#upload-an-image) for image resizing.
|
|
12
|
+
|
|
13
|
+
A `public/storage` folder is automatically created when needed. Uploaded files and images will be renamed with a unique name and stored in a specific folder based on entity and property name, ending by a folder with the current month name to prevent having too many files in a single folder.
|
|
14
|
+
|
|
15
|
+
Example: _public/storage/project/contract/Nov24/8dab3936m1p54a66-contract.pdf_
|
|
16
|
+
|
|
17
|
+
!!! warning
|
|
18
|
+
If you want to set this file as an item's property, you need to upload the file first and then add the new uploaded file path as the property value creating or updating a record.
|
|
19
|
+
|
|
20
|
+
## Add a BASE_URL variable
|
|
21
|
+
|
|
22
|
+
ChadStart stores absolute paths for convenience.
|
|
23
|
+
|
|
24
|
+
By default the base url is set to `http://localhost:${port}` but you can change it using the `BASE_URL` environment variable in your `.env` file to adapt to your own base URL.
|
|
25
|
+
|
|
26
|
+
Example: `BASE_URL=https://example.com`.
|
|
27
|
+
|
|
28
|
+
!!! warning
|
|
29
|
+
Changing the `BASE_URL` will not change the path of images and files that are already stored but it will impact the new ones.
|
|
30
|
+
|
|
31
|
+
## Upload a file
|
|
32
|
+
|
|
33
|
+
A file should be related to a property with the [file property type](./entities.md#file).
|
|
34
|
+
|
|
35
|
+
=== "JS SDK"
|
|
36
|
+
```js
|
|
37
|
+
|
|
38
|
+
// Create a Blob, adapt this step to your use case.
|
|
39
|
+
const file = new Blob(['Hello, this is a test file!'], {
|
|
40
|
+
type: 'text/plain',
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
// Upload a file that will be used as a contract for an invoice.
|
|
44
|
+
const file = await chadstart.from('invoices').upload('contract', file)
|
|
45
|
+
|
|
46
|
+
console.log(file)
|
|
47
|
+
// Output: {"path":"http://localhost:3000/invoices/contract/Oct2024/8dabo9qm1q3swvu-my-contract.pdf"}
|
|
48
|
+
|
|
49
|
+
// Then you can store the path in the database.
|
|
50
|
+
const invoice = await chadstart.from('invoices').create({
|
|
51
|
+
name: 'Invoice ACME',
|
|
52
|
+
contract: file.path
|
|
53
|
+
})
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
=== "REST API"
|
|
57
|
+
```http
|
|
58
|
+
// Upload file.
|
|
59
|
+
POST /api/upload/file
|
|
60
|
+
Content-Type: multipart/form-data
|
|
61
|
+
{
|
|
62
|
+
file: (binary)
|
|
63
|
+
entity: invoices
|
|
64
|
+
property: contract
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Response.
|
|
68
|
+
{
|
|
69
|
+
"path":"http://localhost:3000/invoices/contract/Oct2024/8dabo9qm1q3swvu-my-contract.pdf"
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Upload an image
|
|
74
|
+
|
|
75
|
+
An image should be related to a property with the [image property type](./entities.md#image). ChadStart accepts **.PNG** and **.JPG** images only.
|
|
76
|
+
|
|
77
|
+
### Default behavior
|
|
78
|
+
|
|
79
|
+
By default, uploaded images are **compressed to JPEG** at quality 80. Resizing is **not applied** unless sizes are explicitly configured in the YAML — the image is stored as-is (after compression).
|
|
80
|
+
|
|
81
|
+
### Disable compression
|
|
82
|
+
|
|
83
|
+
Set `compress: false` on the image property to keep the original file untouched:
|
|
84
|
+
|
|
85
|
+
```yaml
|
|
86
|
+
entities:
|
|
87
|
+
Cat:
|
|
88
|
+
properties:
|
|
89
|
+
- name: avatar
|
|
90
|
+
type: image
|
|
91
|
+
options:
|
|
92
|
+
compress: false
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
You can also adjust the JPEG quality (1–100, default `80`):
|
|
96
|
+
|
|
97
|
+
```yaml
|
|
98
|
+
options:
|
|
99
|
+
quality: 60
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Enable resizing
|
|
103
|
+
|
|
104
|
+
Define `sizes` on the image property to resize the image into one or more named variants. Each variant is stored as a separate JPEG file and the response contains a URL for each:
|
|
105
|
+
|
|
106
|
+
```yaml
|
|
107
|
+
entities:
|
|
108
|
+
Cat:
|
|
109
|
+
properties:
|
|
110
|
+
- name: avatar
|
|
111
|
+
type: image
|
|
112
|
+
options:
|
|
113
|
+
sizes:
|
|
114
|
+
thumbnail: [80, 80]
|
|
115
|
+
medium: [160, 160]
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
When sizes are configured, compression also applies to each resized variant (set `compress: false` to use lossless quality instead).
|
|
119
|
+
|
|
120
|
+
=== "JS SDK"
|
|
121
|
+
```js
|
|
122
|
+
// Create a Blob from an image, adapt this step to your use case.
|
|
123
|
+
const base64Image =
|
|
124
|
+
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/wcAAwAB/eb7jLwAAAAASUVORK5CYII='
|
|
125
|
+
const imageBlob: Blob = base64ToBlob(base64Image, 'image/png')
|
|
126
|
+
|
|
127
|
+
// Upload the image (default: compressed to JPEG, no resize).
|
|
128
|
+
const image = await chadstart.from('cats').uploadImage('avatar', imageBlob)
|
|
129
|
+
|
|
130
|
+
console.log(image)
|
|
131
|
+
// Output (no sizes configured):
|
|
132
|
+
// { "path": "http://localhost:3000/cats/avatar/Oct2024/8dabo9qm1q4n1nk-avatar.jpg" }
|
|
133
|
+
|
|
134
|
+
// Output (sizes configured in YAML):
|
|
135
|
+
// {
|
|
136
|
+
// "medium": "http://localhost:3000/cats/avatar/Oct2024/8dabo9qm1q4n1nk-medium.jpg",
|
|
137
|
+
// "thumbnail": "http://localhost:3000/cats/avatar/Oct2024/8dabo9qm1q4n1nk-thumbnail.jpg"
|
|
138
|
+
// }
|
|
139
|
+
|
|
140
|
+
// Then you can store the path in the database.
|
|
141
|
+
const cat = await chadstart.from('cats').create({
|
|
142
|
+
name: 'Felix',
|
|
143
|
+
image: image
|
|
144
|
+
})
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
=== "REST API"
|
|
148
|
+
```http
|
|
149
|
+
// Upload image.
|
|
150
|
+
POST /api/upload/image
|
|
151
|
+
Content-Type: multipart/form-data
|
|
152
|
+
{
|
|
153
|
+
image: (binary)
|
|
154
|
+
entity: cats
|
|
155
|
+
property: avatar
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Response (no sizes configured — default):
|
|
159
|
+
{
|
|
160
|
+
"path": "http://localhost:3000/cats/avatar/Oct2024/8dabo9qm1q4n1nk-avatar.jpg"
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Response (sizes configured in YAML):
|
|
164
|
+
{
|
|
165
|
+
"medium": "http://localhost:3000/cats/avatar/Oct2024/8dabo9qm1q4n1nk-medium.jpg",
|
|
166
|
+
"thumbnail": "http://localhost:3000/cats/avatar/Oct2024/8dabo9qm1q4n1nk-thumbnail.jpg"
|
|
167
|
+
}
|
|
168
|
+
```
|