prisma-php 0.0.10 → 0.0.12

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.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Error Handling
3
- description: Learn how to handle expected errors and uncaught exceptions in Prisma PHP.
3
+ description: Learn how Prisma PHP handles expected errors and uncaught exceptions so AI agents can keep validation failures separate from crashes.
4
4
  related:
5
5
  title: API Reference
6
6
  description: Learn more about the features mentioned on this page by reading the Prisma PHP docs.
@@ -17,7 +17,19 @@ Errors in Prisma PHP can be thought of in two broad categories:
17
17
  1. **Expected errors** — validation failures, missing records, failed form submissions, or known request problems
18
18
  2. **Unexpected errors** — uncaught exceptions, fatal errors, parse errors, or framework-level failures
19
19
 
20
- Prisma PHP’s documented error model is centered around the `ErrorHandler` class, which acts as the application safety net. It captures exceptions, fatal errors, and parse errors, then converts them into structured responses depending on the request context. For AJAX-style requests it can return JSON; for browser rendering it can show a richer diagnostic UI. citeturn0view0
20
+ Prisma PHP’s documented error model is centered around the `ErrorHandler` class, which acts as the application safety net. It captures exceptions, fatal errors, and parse errors, then converts them into structured responses depending on the request context. For AJAX-style requests it can return JSON; for browser rendering it can show a richer diagnostic UI.
21
+
22
+ If a task involves expected validation failures, missing records, `Boom` responses, `error.php`, `not-found.php`, or uncaught exceptions, AI agents should read this page first and keep routine failures separate from true crashes.
23
+
24
+ ## AI rule: read the error-handling docs first
25
+
26
+ Before generating Prisma PHP error flows, use this order:
27
+
28
+ 1. Decide whether the failure is expected or unexpected.
29
+ 2. Use `PP\Validator` and structured return values for expected input problems.
30
+ 3. Use `Boom`, `error.php`, `not-found.php`, or `ErrorHandler` for request and app-level error behavior.
31
+ 4. Keep `route.php`, `pp.fetchFunction(...)`, and form responses explicit instead of turning routine validation into exceptions.
32
+ 5. Read `validator.md` and `route-handlers.md` when the error starts from request validation.
21
33
 
22
34
  ## Handling expected errors
23
35
 
@@ -197,7 +209,7 @@ The docs describe `ErrorHandler` as the safety net for the application. It captu
197
209
 
198
210
  - exceptions
199
211
  - fatal errors
200
- - parse errors citeturn0view0
212
+ - parse errors
201
213
 
202
214
  This makes it the Prisma PHP equivalent of a central application error boundary, but implemented at the PHP framework level rather than with React client components.
203
215
 
@@ -206,13 +218,13 @@ This makes it the Prisma PHP equivalent of a central application error boundary,
206
218
  Prisma PHP’s `ErrorHandler` automatically detects the request context and chooses the appropriate output format. The docs explicitly say it can return:
207
219
 
208
220
  - a JSON payload for AJAX requests
209
- - a rich diagnostic UI for browser requests citeturn0view0
221
+ - a rich diagnostic UI for browser requests
210
222
 
211
223
  This is one of the biggest differences from Next.js. Instead of defining client-side error boundaries with `error.js`, Prisma PHP handles failures centrally on the server and formats the response according to the request type.
212
224
 
213
225
  ## Rich diagnostics
214
226
 
215
- The Prisma PHP docs emphasize that `ErrorHandler` does more than show a generic PHP crash page. It can generate context-aware diagnostics for framework-specific failures. citeturn0view0
227
+ The Prisma PHP docs emphasize that `ErrorHandler` does more than show a generic PHP crash page. It can generate context-aware diagnostics for framework-specific failures.
216
228
 
217
229
  ### Component validation diagnostics
218
230
 
@@ -221,7 +233,7 @@ When a component receives invalid props, the docs say the error UI can show:
221
233
  - the specific component name
222
234
  - the invalid property
223
235
  - quick fixes such as adding a missing public property
224
- - a list of currently available props citeturn0view0
236
+ - a list of currently available props
225
237
 
226
238
  ### Template compilation diagnostics
227
239
 
@@ -229,7 +241,7 @@ When the template engine fails to parse a file, the docs say the error UI can:
229
241
 
230
242
  - identify syntax errors in PulsePoint-specific tags
231
243
  - highlight the specific line in the view file
232
- - provide suggestions on how to correct the syntax citeturn0view0
244
+ - provide suggestions on how to correct the syntax
233
245
 
234
246
  This means Prisma PHP’s error page is intended to be an actionable developer tool, not only a fallback screen.
235
247
 
@@ -241,7 +253,7 @@ Prisma PHP allows you to override the default error output by creating:
241
253
  APP_PATH . '/error.php'
242
254
  ```
243
255
 
244
- The docs state that if this file exists, `ErrorHandler` will render it inside your main layout wrapper instead of using the default output. citeturn0view0
256
+ The docs state that if this file exists, `ErrorHandler` will render it inside your main layout wrapper instead of using the default output.
245
257
 
246
258
  That makes `error.php` the main file convention for customizing browser-visible application errors.
247
259
 
@@ -256,9 +268,9 @@ Example:
256
268
 
257
269
  ## Output buffering behavior
258
270
 
259
- The docs note an important implementation detail: when a fatal error occurs, the handler calls `ob_end_clean()` to remove any partial HTML that was generated before the crash. This prevents broken layouts or incomplete output from being rendered. citeturn0view0
271
+ The docs note an important implementation detail: when a fatal error occurs, the handler calls `ob_end_clean()` to remove any partial HTML that was generated before the crash. This prevents broken layouts or incomplete output from being rendered.
260
272
 
261
- The docs also mention that if you are debugging with `echo` or `var_dump` immediately before a crash, you may need to temporarily disable this buffer cleaning in `modifyOutputLayoutForError` in order to see that debug output. citeturn0view0
273
+ The docs also mention that if you are debugging with `echo` or `var_dump` immediately before a crash, you may need to temporarily disable this buffer cleaning in `modifyOutputLayoutForError` in order to see that debug output.
262
274
 
263
275
  ## Key methods
264
276
 
@@ -272,15 +284,15 @@ Registers:
272
284
  - `register_shutdown_function`
273
285
  - `set_error_handler`
274
286
 
275
- The docs say this is usually called in `bootstrap.php`. citeturn0view0
287
+ The docs say this is usually called in `bootstrap.php`.
276
288
 
277
289
  ### `checkFatalError(): void`
278
290
 
279
- Manually checks `error_get_last()`. The docs describe this as useful for catching shutdown-time errors that are not regular exceptions. citeturn0view0
291
+ Manually checks `error_get_last()`. The docs describe this as useful for catching shutdown-time errors that are not regular exceptions.
280
292
 
281
293
  ### `formatExceptionForDisplay(Throwable $e): string`
282
294
 
283
- Formats an exception into the rich HTML diagnostic output. The docs say it determines whether the error is a standard PHP exception or a specific PulsePoint framework error. citeturn0view0
295
+ Formats an exception into the rich HTML diagnostic output. The docs say it determines whether the error is a standard PHP exception or a specific PulsePoint framework error.
284
296
 
285
297
  ## Bootstrap registration
286
298
 
@@ -315,7 +327,7 @@ try {
315
327
  }
316
328
  ```
317
329
 
318
- This is useful when you want to catch an exception yourself but still use the framework’s standard rich diagnostic output. citeturn0view0
330
+ This is useful when you want to catch an exception yourself but still use the framework’s standard rich diagnostic output.
319
331
 
320
332
  ## `index.php` vs `route.php` error handling
321
333
 
@@ -323,7 +335,7 @@ Error handling in Prisma PHP still follows the route model:
323
335
 
324
336
  - in `index.php`, errors usually affect rendered page output
325
337
  - in `route.php`, errors usually affect JSON or direct handler responses
326
- - the `ErrorHandler` adapts the response format based on request context citeturn0view0
338
+ - the `ErrorHandler` adapts the response format based on request context
327
339
 
328
340
  For full-stack projects, expected user-facing problems should usually be handled gracefully in `index.php` or in direct function responses. For API-style routes, structured JSON error responses are usually the right choice for expected failures. Uncaught exceptions should be left to the framework-level `ErrorHandler`.
329
341
 
@@ -343,14 +355,14 @@ The main difference is that Prisma PHP’s documented error handling is server-d
343
355
 
344
356
  ## Good to know
345
357
 
346
- - Prisma PHP’s documented error system is `ErrorHandler`. citeturn0view0
347
- - It captures exceptions, fatal errors, and parse errors. citeturn0view0
348
- - It automatically switches output based on request context. citeturn0view0
349
- - AJAX-style requests can receive JSON errors. citeturn0view0
350
- - Browser requests can receive a rich diagnostic UI. citeturn0view0
351
- - You can override the default error view with `APP_PATH . '/error.php'`. citeturn0view0
352
- - Fatal-error handling clears partial output with `ob_end_clean()`. citeturn0view0
353
- - `registerHandlers()` is usually called in `bootstrap.php`. citeturn0view0
358
+ - Prisma PHP’s documented error system is `ErrorHandler`.
359
+ - It captures exceptions, fatal errors, and parse errors.
360
+ - It automatically switches output based on request context.
361
+ - AJAX-style requests can receive JSON errors.
362
+ - Browser requests can receive a rich diagnostic UI.
363
+ - You can override the default error view with `APP_PATH . '/error.php'`.
364
+ - Fatal-error handling clears partial output with `ob_end_clean()`.
365
+ - `registerHandlers()` is usually called in `bootstrap.php`.
354
366
 
355
367
  ## Validation vs unexpected failures
356
368
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Fetching Data
3
- description: Learn how to fetch and stream data in Prisma PHP using `pp.fetchFunction`, `#[Exposed]`, page handlers, SSE responses, and route handlers.
3
+ description: Learn how AI agents should fetch and stream data in Prisma PHP using `pp.fetchFunction`, `#[Exposed]`, page handlers, SSE responses, and route handlers.
4
4
  related:
5
5
  title: API Reference
6
6
  description: Learn more about the features mentioned in this page by reading the Prisma PHP docs.
@@ -14,6 +14,8 @@ related:
14
14
 
15
15
  This page will walk you through how you can fetch and stream data in Prisma PHP, when to use `pp.fetchFunction(...)`, when to use `index.php`, when to use `route.php`, and how to validate incoming data correctly on the PHP side.
16
16
 
17
+ If a task involves page-local interactivity, exposed PHP functions, streamed responses, or deciding between `index.php` and `route.php`, AI agents should read this page first and keep the interaction model aligned with Prisma PHP.
18
+
17
19
  Prisma PHP has a different mental model from Next.js. Instead of focusing on React Server Components, Suspense-driven HTML streaming, and client data libraries, Prisma PHP gives you direct function invocation from the frontend to PHP, plus file-based routing for page UI and direct route handlers.
18
20
 
19
21
  When using `pp.fetchFunction(...)`, the PHP function must be explicitly exposed with `#[Exposed]`. Functions are private by default, so a non-exposed function cannot be called from the client.
@@ -0,0 +1,482 @@
1
+ ---
2
+ title: AI Integration
3
+ description: Build chat, assistant, and streamed AI features in Prisma PHP with PulsePoint, pp.fetchFunction(...), PP\Validator, and MCP when you need agent-facing tools.
4
+ related:
5
+ title: Related docs
6
+ description: Read the core Prisma PHP docs that support provider-backed AI UIs, streaming, and agent tooling.
7
+ links:
8
+ - /docs/env
9
+ - /docs/fetch-function
10
+ - /docs/php-validator
11
+ - /docs/pulsepoint
12
+ - /docs/prisma-php-ai-mcp
13
+ - /docs/websocket-chat-app
14
+ ---
15
+
16
+ Prisma PHP is a good fit for AI features, but the right Prisma PHP pattern depends on what you are actually building.
17
+
18
+ If a task involves provider-backed chat, prompt forms, streamed output, assistant panels, or deciding between page-local AI, websocket, and MCP, AI agents should read this page first and keep the implementation aligned with the installed Prisma PHP docs.
19
+
20
+ For a normal user-facing chat box, assistant panel, prompt form, streamed summary, or similar page-local AI feature, the recommended Prisma PHP approach is:
21
+
22
+ 1. render the UI in `index.php`
23
+ 2. keep browser-side interactivity in **PulsePoint**
24
+ 3. call backend PHP with **`pp.fetchFunction(...)`**
25
+ 4. mark callable PHP functions with **`#[Exposed]`**
26
+ 5. validate and normalize incoming data with **`PP\Validator`**
27
+ 6. stream incremental output by **yielding** from PHP when the provider supports streaming
28
+
29
+ That keeps the **frontend reactive** while keeping the **AI call, validation, secrets, and business logic on the PHP side**.
30
+
31
+ Do **not** default to `route.php` for page-local chat or assistant UI just because AI is involved. In Prisma PHP, `route.php` is still useful, but it is the better fit for standalone APIs, webhooks, third-party integrations, or endpoints that must exist independently of the current page.
32
+
33
+ If the real goal is to expose **agent-callable tools** instead of a user chat interface, read `mcp.md` and the official `prisma-php-ai-mcp` docs. MCP is about serving tools to agents; it is not the default answer for a page-local chat box.
34
+
35
+ ## The default AI architecture in Prisma PHP
36
+
37
+ When the user is building a normal AI feature inside a Prisma PHP app, AI should usually prefer this stack:
38
+
39
+ 1. `index.php` for the route UI
40
+ 2. PulsePoint for browser-side state
41
+ 3. `pp.fetchFunction(...)` for frontend-to-PHP RPC
42
+ 4. `#[Exposed]` for callable PHP functions
43
+ 5. `PP\Validator` for server-side validation
44
+ 6. streamed `yield` responses for incremental assistant output
45
+
46
+ This is the right default for:
47
+
48
+ - single-user chat pages
49
+ - assistant sidebars
50
+ - prompt forms
51
+ - streamed summaries
52
+ - content generation tools
53
+ - classification or extraction actions
54
+ - AI-backed search or suggestion panels
55
+ - route-local progress logs
56
+
57
+ Reserve `route.php` for cases where the endpoint must be called without the page, and reserve websocket-based transport for cases where you need true server push, multi-user realtime chat, or presence-style features.
58
+
59
+ ## Pick the right AI surface
60
+
61
+ Prisma PHP has three different AI-related surfaces, and they should not be mixed up.
62
+
63
+ ### 1. User-facing AI in a page
64
+
65
+ Use this when a human is interacting with a Prisma PHP page.
66
+
67
+ Default stack:
68
+
69
+ - `index.php`
70
+ - PulsePoint
71
+ - `pp.fetchFunction(...)`
72
+ - `#[Exposed]`
73
+ - `PP\Validator`
74
+
75
+ This is the default for chat, prompt forms, streamed output, inline assistants, or route-local AI actions.
76
+
77
+ ### 2. Standalone AI endpoint
78
+
79
+ Use `route.php` when the request should exist independently of the rendered page.
80
+
81
+ Examples:
82
+
83
+ - webhooks
84
+ - provider callbacks
85
+ - public JSON APIs
86
+ - integration endpoints
87
+ - non-UI automation calls
88
+
89
+ ### 3. Agent-facing tools
90
+
91
+ Use MCP when an external agent should call business actions in your app.
92
+
93
+ Read:
94
+
95
+ - `mcp.md`
96
+ - official `prisma-php-ai-mcp`
97
+ - official `ai-tools`
98
+
99
+ ### Quick rule of thumb
100
+
101
+ - user chat box on a page -> `index.php` + PulsePoint + `pp.fetchFunction(...)`
102
+ - endpoint with no page dependency -> `route.php`
103
+ - tools for agents -> MCP
104
+
105
+ ## Install an AI client
106
+
107
+ Prisma PHP is intentionally unopinionated about AI providers. You can use OpenAI, Anthropic, Groq, or another PHP SDK that fits your application.
108
+
109
+ For OpenAI-compatible providers, a common starting point is:
110
+
111
+ ```bash package="composer"
112
+ composer require openai-php/client
113
+ ```
114
+
115
+ Use the provider package that matches your model host, but keep the Prisma PHP architecture the same: the browser calls PHP, and PHP talks to the model provider.
116
+
117
+ ## Configure secrets in `.env`
118
+
119
+ Keep provider secrets on the server side.
120
+
121
+ Example:
122
+
123
+ ```dotenv
124
+ OPENAI_API_KEY="sk-..."
125
+ OPENAI_MODEL="gpt-4o-mini"
126
+ ```
127
+
128
+ Do **not** push provider API keys into PulsePoint state or browser-side JavaScript.
129
+ When reading these values in PHP, prefer `PP\Env` over raw `getenv()` so defaults and typed access stay consistent with Prisma PHP's env helper.
130
+
131
+ ## Recommended non-streaming chat pattern
132
+
133
+ For a simple AI response, keep the entire route in `index.php`, expose one PHP function, validate the prompt on the PHP side, and call the provider there.
134
+
135
+ ```php filename="src/app/chat/index.php"
136
+ <?php
137
+
138
+ use PP\Attributes\Exposed;
139
+ use PP\Env;
140
+ use PP\MainLayout;
141
+ use PP\Rule;
142
+ use PP\Validator;
143
+
144
+ MainLayout::$title = 'AI Chat';
145
+ MainLayout::$description = 'Chat with a provider from Prisma PHP using PulsePoint and pp.fetchFunction(...).';
146
+
147
+ #[Exposed]
148
+ function sendChatMessage($data)
149
+ {
150
+ $message = Validator::string($data->message ?? '');
151
+ $result = Validator::withRules(
152
+ $message,
153
+ Rule::required()->min(1)->max(4000)
154
+ );
155
+
156
+ if ($result !== true) {
157
+ return [
158
+ 'success' => false,
159
+ 'errors' => [
160
+ 'message' => $result,
161
+ ],
162
+ ];
163
+ }
164
+
165
+ $client = \OpenAI::client(Env::string('OPENAI_API_KEY', ''));
166
+ $model = Env::string('OPENAI_MODEL', 'gpt-4o-mini');
167
+
168
+ $response = $client->chat()->create([
169
+ 'model' => $model,
170
+ 'messages' => [
171
+ ['role' => 'system', 'content' => 'You are a helpful Prisma PHP assistant.'],
172
+ ['role' => 'user', 'content' => $message],
173
+ ],
174
+ ]);
175
+
176
+ return [
177
+ 'success' => true,
178
+ 'errors' => [],
179
+ 'assistant' => [
180
+ 'role' => 'assistant',
181
+ 'content' => trim($response->choices[0]->message->content ?? ''),
182
+ ],
183
+ ];
184
+ }
185
+ ?>
186
+
187
+ <section pp-component="chat-page">
188
+ <header>
189
+ <h1>AI Chat</h1>
190
+ <p>Send a prompt from PulsePoint and keep the provider call in PHP.</p>
191
+ </header>
192
+
193
+ <div>
194
+ <template pp-for="(item, index) in messages">
195
+ <article key="{`${item.role}-${index}`}">
196
+ <strong>{item.role}</strong>
197
+ <p>{item.content}</p>
198
+ </article>
199
+ </template>
200
+ </div>
201
+
202
+ <form onsubmit="event.preventDefault(); submitMessage()">
203
+ <textarea value="{draft}" oninput="setDraft(event.target.value)"></textarea>
204
+ <p hidden="{!error}">{error}</p>
205
+ <button type="submit" disabled="{loading}">Send</button>
206
+ </form>
207
+
208
+ <script>
209
+ const [draft, setDraft] = pp.state('');
210
+ const [messages, setMessages] = pp.state([]);
211
+ const [error, setError] = pp.state('');
212
+ const [loading, setLoading] = pp.state(false);
213
+
214
+ async function submitMessage() {
215
+ const text = draft.trim();
216
+
217
+ if (!text || loading) {
218
+ return;
219
+ }
220
+
221
+ setError('');
222
+ setDraft('');
223
+ setMessages((current) => [...current, { role: 'user', content: text }]);
224
+ setLoading(true);
225
+
226
+ try {
227
+ const response = await pp.fetchFunction('sendChatMessage', { message: text });
228
+
229
+ if (!response.success) {
230
+ setError(response.errors?.message ?? 'Message failed.');
231
+ return;
232
+ }
233
+
234
+ setMessages((current) => [...current, response.assistant]);
235
+ } catch (error) {
236
+ setError(error.message || 'Request failed.');
237
+ } finally {
238
+ setLoading(false);
239
+ }
240
+ }
241
+ </script>
242
+ </section>
243
+ ```
244
+
245
+ Why this pattern is preferred:
246
+
247
+ - the route UI stays in `index.php`
248
+ - PulsePoint owns the browser state
249
+ - the provider call stays in PHP
250
+ - secrets stay on the server
251
+ - `Validator` remains the authoritative validation layer
252
+ - the route avoids extra endpoint boilerplate when the action belongs to the page
253
+
254
+ ## Recommended streaming chat pattern
255
+
256
+ For streamed assistant output, keep the same architecture and change only the backend response shape: make the exposed PHP function **yield** chunks.
257
+
258
+ That gives you incremental SSE delivery through `pp.fetchFunction(...)` without hand-writing a separate transport layer for normal assistant output.
259
+
260
+ ```php filename="src/app/chat/index.php"
261
+ <?php
262
+
263
+ use PP\Attributes\Exposed;
264
+ use PP\Env;
265
+ use PP\Rule;
266
+ use PP\Validator;
267
+
268
+ #[Exposed]
269
+ function streamChatMessage($data)
270
+ {
271
+ $message = Validator::string($data->message ?? '');
272
+ $result = Validator::withRules(
273
+ $message,
274
+ Rule::required()->min(1)->max(4000)
275
+ );
276
+
277
+ if ($result !== true) {
278
+ yield [
279
+ 'type' => 'error',
280
+ 'message' => $result,
281
+ ];
282
+ return;
283
+ }
284
+
285
+ $client = \OpenAI::client(Env::string('OPENAI_API_KEY', ''));
286
+ $model = Env::string('OPENAI_MODEL', 'gpt-4o-mini');
287
+
288
+ $stream = $client->chat()->createStreamed([
289
+ 'model' => $model,
290
+ 'messages' => [
291
+ ['role' => 'system', 'content' => 'You are a helpful Prisma PHP assistant.'],
292
+ ['role' => 'user', 'content' => $message],
293
+ ],
294
+ ]);
295
+
296
+ foreach ($stream as $response) {
297
+ $delta = $response->choices[0]->delta->content ?? '';
298
+
299
+ if ($delta !== '') {
300
+ yield [
301
+ 'type' => 'chunk',
302
+ 'chunk' => $delta,
303
+ ];
304
+ }
305
+ }
306
+ }
307
+ ```
308
+
309
+ If your provider uses a different streaming API, keep the Prisma PHP shape the same:
310
+
311
+ 1. validate in PHP first
312
+ 2. start the provider stream in PHP
313
+ 3. loop over provider deltas
314
+ 4. yield strings or arrays back to the client
315
+
316
+ ### Client-side streaming with PulsePoint
317
+
318
+ ```html
319
+ <script>
320
+ const [draft, setDraft] = pp.state('');
321
+ const [messages, setMessages] = pp.state([]);
322
+ const [error, setError] = pp.state('');
323
+ const [streaming, setStreaming] = pp.state(false);
324
+
325
+ async function sendStream() {
326
+ const text = draft.trim();
327
+
328
+ if (!text || streaming) {
329
+ return;
330
+ }
331
+
332
+ setError('');
333
+ setDraft('');
334
+ setStreaming(true);
335
+ setMessages((current) => [
336
+ ...current,
337
+ { role: 'user', content: text },
338
+ { role: 'assistant', content: '' },
339
+ ]);
340
+
341
+ try {
342
+ await pp.fetchFunction(
343
+ 'streamChatMessage',
344
+ { message: text },
345
+ {
346
+ onStream(data) {
347
+ if (data.type === 'error') {
348
+ setError(data.message || 'Invalid message.');
349
+ return;
350
+ }
351
+
352
+ const delta = data.chunk || '';
353
+
354
+ if (delta === '') {
355
+ return;
356
+ }
357
+
358
+ setMessages((current) => {
359
+ if (current.length === 0) {
360
+ return current;
361
+ }
362
+
363
+ const next = current.slice();
364
+ const last = next[next.length - 1];
365
+
366
+ next[next.length - 1] = {
367
+ ...last,
368
+ content: `${last.content}${delta}`,
369
+ };
370
+
371
+ return next;
372
+ });
373
+ },
374
+ onStreamError(error) {
375
+ setError(error.message || 'Stream failed.');
376
+ },
377
+ }
378
+ );
379
+ } finally {
380
+ setStreaming(false);
381
+ }
382
+ }
383
+ </script>
384
+ ```
385
+
386
+ Important streaming rule:
387
+
388
+ - do not wait for one final JSON object to render the reply
389
+ - update the UI from `onStream`
390
+ - use `onStreamError` and `onStreamComplete` for stream lifecycle handling
391
+
392
+ For the current SSE behavior and low-level streaming helpers, read `fetching-data.md`.
393
+
394
+ ## Validate everything on the PHP side
395
+
396
+ When building AI features, do not trust browser-side checks as the final source of truth.
397
+
398
+ Use this split:
399
+
400
+ - PulsePoint for local UX state
401
+ - `pp.fetchFunction(...)` for calling PHP
402
+ - `PP\Validator` for sanitization and casting
403
+ - `Validator::withRules(...)` for business rules
404
+ - structured return values for expected validation failures
405
+
406
+ Good server-side validation targets include:
407
+
408
+ - prompt text
409
+ - prompt length
410
+ - selected model name
411
+ - message arrays or prior history
412
+ - temperature or option values
413
+ - tool selection input
414
+ - database IDs tied to retrieval or conversation threads
415
+
416
+ If you are sending prior messages or advanced options back to PHP, validate those values before they are stored, queried against the database, or forwarded to the provider.
417
+
418
+ ## Conversation state and persistence
419
+
420
+ For small page-local chat flows, PulsePoint can hold the visible message list while PHP handles the provider call.
421
+
422
+ When you need durable chat history or retrieval-backed context:
423
+
424
+ - load and store conversation records on the PHP side
425
+ - use Prisma ORM from PHP, not from the browser
426
+ - validate inbound message payloads before persistence
427
+ - shape the final provider request in PHP, not in client-side JavaScript
428
+
429
+ That keeps the conversation contract authoritative on the server.
430
+
431
+ ## SSE vs websocket vs MCP
432
+
433
+ These three features solve different problems.
434
+
435
+ ### Use streamed `pp.fetchFunction(...)` when
436
+
437
+ - the user triggered a request from the current page
438
+ - the assistant response should appear incrementally
439
+ - the page owns the request lifecycle
440
+ - normal request-response semantics are still correct
441
+
442
+ This is the default for AI text streaming.
443
+
444
+ ### Use websocket when
445
+
446
+ - the server must push updates without a fresh page request
447
+ - multiple users must see shared realtime changes
448
+ - you need presence, rooms, or bidirectional messaging
449
+
450
+ Read `websocket.md` when that is the actual requirement.
451
+
452
+ ### Use MCP when
453
+
454
+ - an external agent needs callable tools
455
+ - the goal is tool exposure, not page-local chat UI
456
+ - you want agent workflows to query your app or database
457
+
458
+ Read `mcp.md` when that is the actual requirement.
459
+
460
+ ## When `route.php` is still correct
461
+
462
+ `route.php` still makes sense for AI-related work when:
463
+
464
+ - the endpoint must be called from outside the current page
465
+ - the request is an integration or webhook
466
+ - you are exposing a standalone API
467
+ - the response is not part of a page-local interactive workflow
468
+
469
+ For example, provider webhooks, background callbacks, or public machine-facing AI endpoints are reasonable `route.php` use cases.
470
+
471
+ What should change is the **default assumption**: a chat screen inside a Prisma PHP page should usually start with `index.php` + PulsePoint + `pp.fetchFunction(...)`, not with a separate endpoint first.
472
+
473
+ ## Good to know
474
+
475
+ - Prisma PHP is unopinionated about AI SDK choice.
476
+ - For page-local AI UI, keep the browser reactive and keep the provider call in PHP.
477
+ - Use `#[Exposed]` for any PHP function called from `pp.fetchFunction(...)`.
478
+ - Use `PP\Validator` as the default validation layer for AI request payloads.
479
+ - Stream assistant replies by yielding from PHP and consuming chunks with `onStream`.
480
+ - Prefer `route.php` only when the endpoint must exist independently of the page.
481
+ - Use websocket for true server push or multi-user realtime behavior.
482
+ - Use MCP for agent-facing tools, not as the default transport for a page-local chat box.