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.
Files changed (115) hide show
  1. package/.dockerignore +10 -0
  2. package/.env.example +46 -0
  3. package/.github/workflows/browser-test.yml +34 -0
  4. package/.github/workflows/docker-publish.yml +54 -0
  5. package/.github/workflows/docs.yml +31 -0
  6. package/.github/workflows/npm-chadstart.yml +27 -0
  7. package/.github/workflows/npm-sdk.yml +38 -0
  8. package/.github/workflows/test.yml +85 -0
  9. package/.weblate +9 -0
  10. package/Dockerfile +23 -0
  11. package/README.md +348 -0
  12. package/admin/index.html +2802 -0
  13. package/admin/login.html +207 -0
  14. package/chadstart.example.yml +416 -0
  15. package/chadstart.schema.json +367 -0
  16. package/chadstart.yaml +53 -0
  17. package/cli/cli.js +295 -0
  18. package/core/api-generator.js +606 -0
  19. package/core/auth.js +298 -0
  20. package/core/db.js +384 -0
  21. package/core/entity-engine.js +166 -0
  22. package/core/error-reporter.js +132 -0
  23. package/core/file-storage.js +97 -0
  24. package/core/functions-engine.js +353 -0
  25. package/core/openapi.js +171 -0
  26. package/core/plugin-loader.js +92 -0
  27. package/core/realtime.js +93 -0
  28. package/core/schema-validator.js +50 -0
  29. package/core/seeder.js +231 -0
  30. package/core/telemetry.js +119 -0
  31. package/core/upload.js +372 -0
  32. package/core/workers/php_worker.php +19 -0
  33. package/core/workers/python_worker.py +33 -0
  34. package/core/workers/ruby_worker.rb +21 -0
  35. package/core/yaml-loader.js +64 -0
  36. package/demo/chadstart.yaml +178 -0
  37. package/demo/docker-compose.yml +31 -0
  38. package/demo/functions/greet.go +39 -0
  39. package/demo/functions/hello.cpp +18 -0
  40. package/demo/functions/hello.py +13 -0
  41. package/demo/functions/hello.rb +10 -0
  42. package/demo/functions/onTodoCreated.js +13 -0
  43. package/demo/functions/ping.sh +13 -0
  44. package/demo/functions/stats.js +22 -0
  45. package/demo/public/index.html +522 -0
  46. package/docker-compose.yml +17 -0
  47. package/docs/access-policies.md +155 -0
  48. package/docs/admin-ui.md +29 -0
  49. package/docs/angular.md +69 -0
  50. package/docs/astro.md +71 -0
  51. package/docs/auth.md +160 -0
  52. package/docs/cli.md +56 -0
  53. package/docs/config.md +127 -0
  54. package/docs/crud.md +627 -0
  55. package/docs/deploy.md +113 -0
  56. package/docs/docker.md +59 -0
  57. package/docs/entities.md +385 -0
  58. package/docs/functions.md +196 -0
  59. package/docs/getting-started.md +79 -0
  60. package/docs/groups.md +85 -0
  61. package/docs/index.md +5 -0
  62. package/docs/llm-rules.md +81 -0
  63. package/docs/middlewares.md +78 -0
  64. package/docs/overrides/home.html +350 -0
  65. package/docs/plugins.md +59 -0
  66. package/docs/react.md +75 -0
  67. package/docs/realtime.md +43 -0
  68. package/docs/s3-storage.md +40 -0
  69. package/docs/security.md +23 -0
  70. package/docs/stylesheets/extra.css +375 -0
  71. package/docs/svelte.md +71 -0
  72. package/docs/telemetry.md +97 -0
  73. package/docs/upload.md +168 -0
  74. package/docs/validation.md +115 -0
  75. package/docs/vue.md +86 -0
  76. package/docs/webhooks.md +87 -0
  77. package/index.js +11 -0
  78. package/locales/en/admin.json +169 -0
  79. package/mkdocs.yml +82 -0
  80. package/package.json +65 -0
  81. package/playwright.config.js +24 -0
  82. package/public/.gitkeep +0 -0
  83. package/sdk/README.md +284 -0
  84. package/sdk/package.json +39 -0
  85. package/sdk/scripts/build.js +58 -0
  86. package/sdk/src/index.js +368 -0
  87. package/sdk/test/sdk.test.cjs +340 -0
  88. package/sdk/types/index.d.ts +217 -0
  89. package/server/express-server.js +734 -0
  90. package/test/access-policies.test.js +96 -0
  91. package/test/ai.test.js +81 -0
  92. package/test/api-keys.test.js +361 -0
  93. package/test/auth.test.js +122 -0
  94. package/test/browser/admin-ui.spec.js +127 -0
  95. package/test/browser/global-setup.js +71 -0
  96. package/test/browser/global-teardown.js +11 -0
  97. package/test/db.test.js +227 -0
  98. package/test/entity-engine.test.js +193 -0
  99. package/test/error-reporter.test.js +140 -0
  100. package/test/functions-engine.test.js +240 -0
  101. package/test/groups.test.js +212 -0
  102. package/test/hot-reload.test.js +153 -0
  103. package/test/i18n.test.js +173 -0
  104. package/test/middleware.test.js +76 -0
  105. package/test/openapi.test.js +67 -0
  106. package/test/schema-validator.test.js +83 -0
  107. package/test/sdk.test.js +90 -0
  108. package/test/seeder.test.js +279 -0
  109. package/test/settings.test.js +109 -0
  110. package/test/telemetry.test.js +254 -0
  111. package/test/test.js +17 -0
  112. package/test/upload.test.js +265 -0
  113. package/test/validation.test.js +96 -0
  114. package/test/yaml-loader.test.js +93 -0
  115. 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
+ ```