@orieljs/framework 0.1.0-alpha.5 → 0.1.0-alpha.7

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 (87) hide show
  1. package/NOTICE +7 -0
  2. package/README.md +10 -1
  3. package/TRADEMARKS.md +33 -0
  4. package/dist/{chunk-WNJP46B7.js → chunk-4MWG3B7G.js} +1785 -626
  5. package/dist/chunk-4MWG3B7G.js.map +1 -0
  6. package/dist/chunk-7ZQZYDOC.js +3253 -0
  7. package/dist/chunk-7ZQZYDOC.js.map +1 -0
  8. package/dist/{chunk-BSQKOLSK.js → chunk-GVNLZZTN.js} +1 -1
  9. package/dist/chunk-GVNLZZTN.js.map +1 -0
  10. package/dist/{chunk-A5XRLEWM.js → chunk-VOYW3R5J.js} +4 -4
  11. package/dist/chunk-VOYW3R5J.js.map +1 -0
  12. package/dist/contracts.d.ts +535 -17
  13. package/dist/contracts.js +1 -1
  14. package/dist/index.d-WQpfdIbG.d.ts +630 -0
  15. package/dist/index.d.ts +2202 -5952
  16. package/dist/index.js +4900 -944
  17. package/dist/index.js.map +1 -1
  18. package/dist/{jsx-runtime-WKTl3EEw.d.ts → jsx-runtime-6um4Mwfb.d.ts} +97 -51
  19. package/dist/jsx-runtime.d.ts +2 -2
  20. package/dist/jsx-runtime.js +3 -3
  21. package/dist/{testing-BErengYY.d.ts → testing-CKwfWJGn.d.ts} +313 -994
  22. package/dist/testing.d.ts +2 -2
  23. package/dist/testing.js +19 -5
  24. package/docs/authentication.md +487 -0
  25. package/docs/authorization.md +435 -0
  26. package/docs/cache.md +647 -0
  27. package/docs/collections.md +2270 -0
  28. package/docs/configuration.md +334 -0
  29. package/docs/console.md +651 -0
  30. package/docs/container.md +430 -0
  31. package/docs/controllers.md +406 -0
  32. package/docs/csrf.md +126 -0
  33. package/docs/database-testing.md +144 -0
  34. package/docs/database.md +236 -0
  35. package/docs/deployment.md +276 -0
  36. package/docs/eloquent-collections.md +218 -0
  37. package/docs/eloquent-factories.md +255 -0
  38. package/docs/eloquent-mutators.md +254 -0
  39. package/docs/eloquent-observers.md +145 -0
  40. package/docs/eloquent-relationships.md +557 -0
  41. package/docs/eloquent.md +494 -0
  42. package/docs/encryption.md +104 -0
  43. package/docs/errors.md +318 -0
  44. package/docs/events.md +336 -0
  45. package/docs/facades.md +278 -0
  46. package/docs/hashing.md +118 -0
  47. package/docs/helpers.md +1281 -0
  48. package/docs/http-tests.md +386 -0
  49. package/docs/inertia.md +485 -0
  50. package/docs/installation.md +209 -0
  51. package/docs/lifecycle.md +234 -0
  52. package/docs/live-tests.md +226 -0
  53. package/docs/live.md +910 -0
  54. package/docs/logging.md +255 -0
  55. package/docs/mail.md +619 -0
  56. package/docs/middleware.md +384 -0
  57. package/docs/migrations.md +416 -0
  58. package/docs/mocking.md +264 -0
  59. package/docs/navigation.json +273 -0
  60. package/docs/notifications.md +225 -0
  61. package/docs/pagination.md +247 -0
  62. package/docs/passwords.md +207 -0
  63. package/docs/providers.md +187 -0
  64. package/docs/queries.md +600 -0
  65. package/docs/queues.md +330 -0
  66. package/docs/requests.md +533 -0
  67. package/docs/responses.md +444 -0
  68. package/docs/routing.md +738 -0
  69. package/docs/scheduling.md +277 -0
  70. package/docs/seeding.md +122 -0
  71. package/docs/session.md +409 -0
  72. package/docs/starter-kits.md +396 -0
  73. package/docs/storage.md +192 -0
  74. package/docs/structure.md +264 -0
  75. package/docs/testing.md +165 -0
  76. package/docs/time-travel.md +138 -0
  77. package/docs/validation.md +1792 -0
  78. package/docs/verification.md +152 -0
  79. package/docs/views.md +566 -0
  80. package/docs/vite.md +322 -0
  81. package/package.json +34 -32
  82. package/dist/chunk-A5XRLEWM.js.map +0 -1
  83. package/dist/chunk-BSQKOLSK.js.map +0 -1
  84. package/dist/chunk-PM5YTG57.js +0 -880
  85. package/dist/chunk-PM5YTG57.js.map +0 -1
  86. package/dist/chunk-WNJP46B7.js.map +0 -1
  87. package/dist/index.d-DOlTeG-8.d.ts +0 -99
package/docs/vite.md ADDED
@@ -0,0 +1,322 @@
1
+ # Asset bundling (Vite)
2
+
3
+ Vite bundles your pages' scripts and styles. While you work, it serves them and updates them in the browser as you
4
+ edit them; for production, it builds them into `public/build`.
5
+
6
+ ## Introduction
7
+
8
+ Oriel's Vite plugin, `oriel()` from `@orieljs/vite`, connects Vite and your app, as Laravel's `laravel-vite-plugin`
9
+ does: your views load the right files with `vite()`, from the dev server while it runs and from the build otherwise.
10
+
11
+ Every starter kit except the API kit has Vite set up. The API kit has no pages, so it has no `vite.config.ts` and no
12
+ `dev` or `build` script.
13
+
14
+ ## Installation and setup
15
+
16
+ ### Installing Node
17
+
18
+ Vite runs on Node 22 or later, or Bun, as your app does.
19
+
20
+ ### Installing Vite and the Oriel plugin
21
+
22
+ Every new app lists `vite` and `@orieljs/vite` in `package.json`, the API kit's too: its tests use them. To give an API
23
+ app pages, add a `vite.config.ts` as below, and `"dev": "vite"` and `"build": "vite build"` to its scripts.
24
+
25
+ ### Configuring Vite
26
+
27
+ `vite.config.ts`, in your app's root, lists your entry points, the files your pages load:
28
+
29
+ ```ts title="vite.config.ts"
30
+ import { oriel } from '@orieljs/vite'
31
+ import { defineConfig } from 'vite'
32
+
33
+ export default defineConfig({
34
+ plugins: [oriel({ input: ['resources/css/app.css', 'resources/js/app.ts'] })],
35
+ })
36
+ ```
37
+
38
+ An Inertia app names its framework, which loads that framework's Vite plugin (`@vitejs/plugin-react` for React), and
39
+ its server entry for SSR. The React kit's:
40
+
41
+ ```ts title="vite.config.ts"
42
+ import { oriel } from '@orieljs/vite'
43
+ import tailwindcss from '@tailwindcss/vite'
44
+ import { defineConfig } from 'vite'
45
+
46
+ export default defineConfig({
47
+ plugins: [
48
+ tailwindcss(),
49
+ oriel({ framework: 'react', input: ['resources/css/app.css', 'resources/js/app.tsx'], ssr: 'resources/js/ssr.tsx' }),
50
+ ],
51
+ })
52
+ ```
53
+
54
+ `framework` is `'react'`, `'vue'`, `'svelte'`, `'solid'` or `'angular'`. The Live kit names none: its components render
55
+ on the server.
56
+
57
+ | Option | What it is |
58
+ | --- | --- |
59
+ | `input` | The entry points |
60
+ | `framework` | The Inertia framework, whose Vite plugin it loads |
61
+ | `frameworkOptions` | Options for that plugin |
62
+ | `ssr` | The SSR entry, found by itself (`resources/js/ssr.tsx`) unless you name it; `false` turns SSR off |
63
+ | `inertia` | Options for `@inertiajs/vite`, or `false` to leave it out |
64
+ | `refresh` | The server files that reload the page when they change: `true` for the defaults, `false`, or your own globs |
65
+ | `types` | The generated page and route types: `true`, `false`, or `{ pages, routes, command }` |
66
+ | `publicDirectory`, `buildDirectory` | Where the build goes: `public` and `build` |
67
+ | `hotFile` | The file that tells your app the dev server is running: `public/hot` |
68
+ | `ssrOutputDirectory` | Where the SSR build goes: `bootstrap/ssr` |
69
+
70
+ ### Loading your scripts and styles
71
+
72
+ `vite()` in a view writes the tags for your entry points:
73
+
74
+ ```tsx title="resources/views/app.tsx"
75
+ import { vite } from '@orieljs/framework'
76
+
77
+ export default function App() {
78
+ return (
79
+ <html lang="en">
80
+ <head>
81
+ <meta charset="utf-8" />
82
+ {vite(['resources/css/app.css', 'resources/js/app.ts'])}
83
+ </head>
84
+ <body>...</body>
85
+ </html>
86
+ )
87
+ }
88
+ ```
89
+
90
+ While the dev server runs, the tags point at it. Otherwise they point at the built files, with the scripts and styles
91
+ they import preloaded. Each script and stylesheet tag carries `data-navigate-track`, so Live's `o:navigate` loads a
92
+ page in full when it names a new build's files; `useScriptTagAttributes({ 'data-navigate-track': false })` leaves it
93
+ out. A file that isn't in the build throws a `ViteError`: "Unable to locate file in Vite manifest:
94
+ resources/js/app.ts.".
95
+
96
+ A React app also needs `viteReactRefresh()` before `vite()`, so that React's fast refresh works while you work:
97
+
98
+ ```tsx
99
+ const head = (
100
+ <head>
101
+ {viteReactRefresh()}
102
+ {vite(['resources/css/app.css', 'resources/js/app.tsx'])}
103
+ </head>
104
+ )
105
+ ```
106
+
107
+ ## Running Vite
108
+
109
+ `npm run dev` starts the dev server, and `npm run build` builds your assets for production:
110
+
111
+ ```sh
112
+ npm run dev
113
+
114
+ npm run build
115
+ ```
116
+
117
+ Run your app's server next to the dev server, in a second terminal: `node oriel serve --watch`.
118
+ [Running your application](installation.md#running-your-application) explains how the two work together.
119
+
120
+ The kits with SSR build twice, `vite build && vite build --ssr`: the browser's bundle into `public/build`, and the
121
+ server's into `bootstrap/ssr`.
122
+
123
+ While the dev server runs, it writes its URL to `public/hot`, and deletes the file when it stops. Don't deploy that
124
+ file: while it exists, your pages load their assets from a dev server that isn't there.
125
+
126
+ ## Working with JavaScript
127
+
128
+ ### Aliases
129
+
130
+ `@` is `resources/js`, so you can import `@/components/Button` from anywhere in your scripts.
131
+
132
+ ### Inertia
133
+
134
+ The plugin loads `@inertiajs/vite`, which finds your pages in `resources/js/pages` and builds the SSR bundle. The
135
+ [Inertia](inertia.md) page covers the rest.
136
+
137
+ ### Generated types
138
+
139
+ In an Inertia app, the plugin writes your pages' prop types to `resources/js/oriel/pages.d.ts` and runs
140
+ `oriel route:types`, which writes the route table behind `route()` in your pages. It runs both when Vite starts, and
141
+ again while `npm run dev` runs: the page types when a file in `app` or `routes` changes, the route types when one in
142
+ `routes` or `app/Http/Controllers` does. `types: false` turns it off, and `types: { routes: false }` keeps the page
143
+ types only.
144
+
145
+ ### Environment variables
146
+
147
+ Vite gives your scripts the variables in `.env` that start with `VITE_`, and only those:
148
+
149
+ ```sh
150
+ VITE_SENTRY_DSN_PUBLIC=https://example.com
151
+ ```
152
+
153
+ ```ts nocheck
154
+ const dsn = import.meta.env.VITE_SENTRY_DSN_PUBLIC
155
+ ```
156
+
157
+ > [!WARNING]
158
+ > Every `VITE_` variable ends up in the scripts anyone can download. Keep your secrets in other variables.
159
+
160
+ ## Working with stylesheets
161
+
162
+ Give your CSS file to `input`, or import it from a script, and Vite processes it: the kits' `resources/css/app.css` is
163
+ built with Tailwind CSS, through `@tailwindcss/vite`. Vite's own CSS features, such as PostCSS and CSS modules, work as
164
+ its documentation describes.
165
+
166
+ ## Working with views and routes
167
+
168
+ ### Processing static assets with Vite
169
+
170
+ An image your script imports is in the build, with a hashed name. `vite().asset()` gives its URL in a view; to have
171
+ images that no script imports built too, import them as a glob in your entry point:
172
+
173
+ ```ts title="resources/js/app.ts"
174
+ import.meta.glob(['../images/**'])
175
+ ```
176
+
177
+ ```tsx
178
+ const logo = <img src={vite().asset('resources/images/logo.png')} alt="Logo" />
179
+ ```
180
+
181
+ `vite().content('resources/css/mail.css')` gives a built file's contents, such as styles to put in a mail.
182
+
183
+ ### Refreshing on save
184
+
185
+ When a view, a route file, a translation or a Live component changes, Vite reloads the page in the browser:
186
+
187
+ | Folder | Reloads |
188
+ | --- | --- |
189
+ | `resources/views/**` | Views |
190
+ | `routes/**` | Route files |
191
+ | `app/Live/**` | Live components |
192
+ | `lang/**`, `resources/lang/**` | Translations |
193
+
194
+ `refresh` takes your own list instead: `refresh: ['resources/views/**', 'app/Http/Controllers/**']`.
195
+
196
+ > [!NOTE]
197
+ > Vite reloads the page as soon as the file changes, often before `serve --watch` has restarted the server, and it
198
+ > doesn't reload again once the new server is up. After "Restarting server...", reload the page once more.
199
+
200
+ ## Custom base URLs
201
+
202
+ To serve your built assets from another host, such as a CDN, set `ASSET_URL` in `.env`. The build reads it, so the
203
+ URLs inside your scripts and styles start with it:
204
+
205
+ ```sh
206
+ ASSET_URL=https://cdn.example.com
207
+ ```
208
+
209
+ The tags `vite()` writes, and `asset()`, use `app.asset_url`. A new app's `config/app.ts` doesn't have it, so add it:
210
+
211
+ ```ts title="config/app.ts"
212
+ import { env } from '@orieljs/framework'
213
+
214
+ export default {
215
+ // ...
216
+ asset_url: env('ASSET_URL'),
217
+ }
218
+ ```
219
+
220
+ ## Disabling Vite in tests
221
+
222
+ A test that renders a page needs the build's manifest, unless Vite is off. `withoutVite()` makes `vite()` and
223
+ `viteReactRefresh()` write nothing for the rest of the test. The `TestCase` of the starter kits with pages calls it
224
+ for every test:
225
+
226
+ ```ts title="tests/TestCase.ts"
227
+ import { TestCase as BaseTestCase } from '@orieljs/framework/testing'
228
+ import type { Application } from '@orieljs/framework'
229
+ import { afterEach, beforeEach } from 'vitest'
230
+ import { createApplication } from '../bootstrap/app'
231
+
232
+ export class TestCase extends BaseTestCase {
233
+ static override hooks = { beforeEach, afterEach }
234
+
235
+ override createApplication(): Promise<Application> {
236
+ return createApplication()
237
+ }
238
+
239
+ override async setUp(): Promise<void> {
240
+ await super.setUp()
241
+ this.withoutVite()
242
+ }
243
+ }
244
+ ```
245
+
246
+ ### Decorators in tests
247
+
248
+ Vitest transforms your TypeScript with Oxc, which leaves TC39 decorators, such as Live's `@On`, in place, and Node
249
+ can't run them yet. The `decorators()` plugin lowers them; a new app's `vitest.config.ts` has it:
250
+
251
+ ```ts title="vitest.config.ts"
252
+ import { decorators } from '@orieljs/vite'
253
+ import { defineConfig } from 'vitest/config'
254
+
255
+ export default defineConfig({
256
+ plugins: [decorators()],
257
+ })
258
+ ```
259
+
260
+ ## Server-side rendering (SSR)
261
+
262
+ `ssr` names the server entry of an Inertia app, and `vite build --ssr` builds it into `bootstrap/ssr`. While the dev
263
+ server runs, it renders the pages on the server itself. The Angular kit has no SSR. [Inertia
264
+ SSR](starter-kits.md#inertia-ssr) covers running the SSR server.
265
+
266
+ ## Script and style tag attributes
267
+
268
+ ### Content Security Policy (CSP) nonce
269
+
270
+ `vite().useCspNonce()` makes a nonce for the request, and the tags `vite()` writes carry it. Call it in a middleware,
271
+ and send the same nonce in your policy:
272
+
273
+ ```ts title="app/Http/Middleware/AddContentSecurityPolicyHeaders.ts"
274
+ import { vite } from '@orieljs/framework'
275
+ import type { HttpRequest, Middleware, Next } from '@orieljs/framework/contracts'
276
+
277
+ export class AddContentSecurityPolicyHeaders implements Middleware {
278
+ async handle(request: HttpRequest, next: Next): Promise<Response> {
279
+ const nonce = vite().useCspNonce()
280
+
281
+ const response = await next(request)
282
+
283
+ response.headers.set('Content-Security-Policy', `script-src 'nonce-${nonce}'`)
284
+
285
+ return response
286
+ }
287
+ }
288
+ ```
289
+
290
+ `vite().cspNonce()` gives the nonce, for your own inline scripts: `<script nonce={vite().cspNonce()}>`. The starter
291
+ kits' appearance script and welcome page use it. Each request has its own `vite()`, so the nonce stays in its request.
292
+
293
+ ### Subresource Integrity (SRI)
294
+
295
+ When the manifest gives each file an `integrity` hash, such as one a plugin like `vite-plugin-manifest-sri` adds, the
296
+ tags carry it. `vite().useIntegrityKey('hash')` reads another key, and `useIntegrityKey(false)` leaves it out.
297
+
298
+ ### Arbitrary attributes
299
+
300
+ `useScriptTagAttributes()` and `useStyleTagAttributes()` add attributes to the tags, as an object or a function of the
301
+ file:
302
+
303
+ ```ts
304
+ vite().useScriptTagAttributes({ 'data-turbo-track': 'reload', async: true })
305
+
306
+ vite().useStyleTagAttributes((src) => (src === 'resources/css/app.css' ? { 'data-theme': 'main' } : {}))
307
+ ```
308
+
309
+ A value of `true` writes a bare attribute, and `false` leaves the attribute out. `usePreloadTagAttributes(false)` drops
310
+ the preload tags.
311
+
312
+ ## Advanced customization
313
+
314
+ `vite()` also has `useHotFile()`, `useBuildDirectory()`, `useManifestFilename()` and `createAssetPathsUsing()`, for a
315
+ build that isn't in the usual place. Set them in a middleware, as the nonce is, or pass the build folder to `vite()`:
316
+ `vite(['resources/js/admin.ts'], 'admin-build')`.
317
+
318
+ ### Dev server CORS
319
+
320
+ Your pages, on your app's URL, load scripts from the dev server, on another port. The dev server lets your `APP_URL`,
321
+ `localhost` and any `.test` host load them. For another origin, set `server.cors` in `vite.config.ts`, as Vite's
322
+ documentation describes.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orieljs/framework",
3
- "version": "0.1.0-alpha.5",
3
+ "version": "0.1.0-alpha.7",
4
4
  "description": "Oriel: a Laravel-style, full-stack TypeScript framework for Node.js and Bun.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -38,8 +38,10 @@
38
38
  },
39
39
  "files": [
40
40
  "dist",
41
+ "docs",
41
42
  "LICENSE",
42
- "NOTICE"
43
+ "NOTICE",
44
+ "TRADEMARKS.md"
43
45
  ],
44
46
  "dependencies": {
45
47
  "@aws-sdk/client-s3": "3.1139.0",
@@ -56,35 +58,35 @@
56
58
  "pluralize": "8.0.0"
57
59
  },
58
60
  "devDependencies": {
59
- "@orieljs/bus": "0.1.0-alpha.5",
60
- "@orieljs/auth": "0.1.0-alpha.5",
61
- "@orieljs/cache": "0.1.0-alpha.5",
62
- "@orieljs/config": "0.1.0-alpha.5",
63
- "@orieljs/collections": "0.1.0-alpha.5",
64
- "@orieljs/console": "0.1.0-alpha.5",
65
- "@orieljs/container": "0.1.0-alpha.5",
66
- "@orieljs/contracts": "0.1.0-alpha.5",
67
- "@orieljs/cookie": "0.1.0-alpha.5",
68
- "@orieljs/encryption": "0.1.0-alpha.5",
69
- "@orieljs/events": "0.1.0-alpha.5",
70
- "@orieljs/foundation": "0.1.0-alpha.5",
71
- "@orieljs/database": "0.1.0-alpha.5",
72
- "@orieljs/hashing": "0.1.0-alpha.5",
73
- "@orieljs/filesystem": "0.1.0-alpha.5",
74
- "@orieljs/http": "0.1.0-alpha.5",
75
- "@orieljs/inertia": "0.1.0-alpha.5",
76
- "@orieljs/log": "0.1.0-alpha.5",
77
- "@orieljs/notifications": "0.1.0-alpha.5",
78
- "@orieljs/pipeline": "0.1.0-alpha.5",
79
- "@orieljs/live": "0.1.0-alpha.5",
80
- "@orieljs/queue": "0.1.0-alpha.5",
81
- "@orieljs/routing": "0.1.0-alpha.5",
82
- "@orieljs/session": "0.1.0-alpha.5",
83
- "@orieljs/mail": "0.1.0-alpha.5",
84
- "@orieljs/support": "0.1.0-alpha.5",
85
- "@orieljs/testing": "0.1.0-alpha.5",
86
- "@orieljs/validation": "0.1.0-alpha.5",
87
- "@orieljs/view": "0.1.0-alpha.5"
61
+ "@orieljs/auth": "0.1.0-alpha.7",
62
+ "@orieljs/bus": "0.1.0-alpha.7",
63
+ "@orieljs/collections": "0.1.0-alpha.7",
64
+ "@orieljs/console": "0.1.0-alpha.7",
65
+ "@orieljs/config": "0.1.0-alpha.7",
66
+ "@orieljs/cache": "0.1.0-alpha.7",
67
+ "@orieljs/container": "0.1.0-alpha.7",
68
+ "@orieljs/contracts": "0.1.0-alpha.7",
69
+ "@orieljs/cookie": "0.1.0-alpha.7",
70
+ "@orieljs/database": "0.1.0-alpha.7",
71
+ "@orieljs/events": "0.1.0-alpha.7",
72
+ "@orieljs/encryption": "0.1.0-alpha.7",
73
+ "@orieljs/filesystem": "0.1.0-alpha.7",
74
+ "@orieljs/http": "0.1.0-alpha.7",
75
+ "@orieljs/inertia": "0.1.0-alpha.7",
76
+ "@orieljs/live": "0.1.0-alpha.7",
77
+ "@orieljs/hashing": "0.1.0-alpha.7",
78
+ "@orieljs/mail": "0.1.0-alpha.7",
79
+ "@orieljs/foundation": "0.1.0-alpha.7",
80
+ "@orieljs/notifications": "0.1.0-alpha.7",
81
+ "@orieljs/log": "0.1.0-alpha.7",
82
+ "@orieljs/pipeline": "0.1.0-alpha.7",
83
+ "@orieljs/routing": "0.1.0-alpha.7",
84
+ "@orieljs/queue": "0.1.0-alpha.7",
85
+ "@orieljs/session": "0.1.0-alpha.7",
86
+ "@orieljs/testing": "0.1.0-alpha.7",
87
+ "@orieljs/support": "0.1.0-alpha.7",
88
+ "@orieljs/validation": "0.1.0-alpha.7",
89
+ "@orieljs/view": "0.1.0-alpha.7"
88
90
  },
89
91
  "peerDependencies": {
90
92
  "pg": "^8.23.0",
@@ -99,6 +101,6 @@
99
101
  }
100
102
  },
101
103
  "scripts": {
102
- "build": "tsup && node ../../../scripts/copy-license.mjs"
104
+ "build": "tsup && node ../../../scripts/copy-license.mjs && node ../../../scripts/copy-docs.mjs"
103
105
  }
104
106
  }
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../View/Element.ts","../../View/jsx-runtime.ts","../../View/Html.ts","../../View/errors.ts","../../View/attributes.ts","../../View/render.ts","../../View/FileViewFinder.ts","../../View/TsxView.ts","../../View/Factory.ts","../../View/helpers.ts","../../View/facade.ts","../../View/ViewServiceProvider.ts"],"sourcesContent":["import type { Htmlable } from '@orieljs/contracts'\n\n/** Marks the objects jsx() returns (Symbol.for, so two copies of the package agree). */\nexport const ELEMENT: unique symbol = Symbol.for('oriel:view.element')\n\nexport type Props = Record<string, unknown>\n\n/** Anything that can appear as a child: text is escaped, Htmlable is emitted as-is, promises are awaited. */\nexport type Renderable =\n | OrielElement\n | string\n | number\n | bigint\n | boolean\n | null\n | undefined\n | Htmlable\n | Promise<Renderable>\n | Iterable<Renderable>\n\n/** A function component. It may be async: the renderer awaits it. */\nexport type Component<P = Props> = (props: P) => Renderable | Promise<Renderable>\n\n/**\n * `<>…</>` and `<Fragment>`: renders its children only. A symbol at runtime; typed as a component so that\n * `<Fragment>` type-checks as a JSX tag.\n */\nexport const Fragment = Symbol.for('oriel:view.fragment') as unknown as (props: { children?: Renderable }) => Renderable\n\nexport type ElementType = string | Component<never>\n\n/** What `<div />` evaluates to: an inert description, rendered later by renderToString()/renderToStream(). */\nexport interface OrielElement {\n readonly [ELEMENT]: true\n readonly type: ElementType\n readonly props: Props\n readonly key: string | number | undefined\n}\n\nexport function createElement(type: ElementType, props: Props | null | undefined, key?: string | number): OrielElement {\n return { [ELEMENT]: true, type, props: props ?? {}, key }\n}\n\nexport function isElement(value: unknown): value is OrielElement {\n return typeof value === 'object' && value !== null && (value as Record<symbol, unknown>)[ELEMENT] === true\n}\n","/**\n * The automatic JSX runtime (`jsxImportSource: \"@orieljs/view\"`). jsx() only describes elements; rendering\n * (escaping, async components) happens in renderToString()/renderToStream().\n */\nimport type { ClassValue, StyleObject } from './attributes'\nimport { createElement, Fragment, type Component, type ElementType, type OrielElement, type Props, type Renderable } from './Element'\n\nexport { Fragment }\n\nexport function jsx(type: ElementType, props: Props | null, key?: string | number): OrielElement {\n return createElement(type, props, key)\n}\n\n/** Same as jsx(); the compiler uses it when there are several static children. */\nexport const jsxs = jsx\n\n/** Attributes every intrinsic element accepts. Unknown attributes (hx-*, x-*, o:click, …) are allowed. */\nexport interface HtmlAttributes {\n children?: Renderable\n class?: ClassValue\n className?: ClassValue\n style?: string | StyleObject\n dangerouslySetInnerHTML?: { __html: string }\n [attribute: string]: unknown\n}\n\nexport declare namespace JSX {\n type Element = OrielElement\n /** Lets components be async: `async function Posts() { … }` is a valid element type. */\n type ElementType = string | Component<never>\n interface ElementChildrenAttribute {\n children: unknown\n }\n interface IntrinsicAttributes {\n key?: string | number\n }\n interface IntrinsicElements {\n [element: string]: HtmlAttributes\n }\n}\n","import type { Htmlable } from '@orieljs/contracts'\n\nconst ESCAPES: Record<string, string> = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#039;' }\nconst UNSAFE = /[&<>\"']/g\n\n/** Markup that must not be escaped again (Laravel's Illuminate\\Support\\HtmlString). */\nexport class HtmlString implements Htmlable {\n constructor(private readonly html: string = '') {}\n\n toHtml(): string {\n return this.html\n }\n\n toString(): string {\n return this.html\n }\n\n isEmpty(): boolean {\n return this.html === ''\n }\n}\n\n/** Marks trusted markup so the renderer emits it unescaped: {raw(markdownHtml)}. Never pass user input. */\nexport function raw(html: string): HtmlString {\n return new HtmlString(html)\n}\n\nexport function isHtmlable(value: unknown): value is Htmlable {\n return typeof value === 'object' && value !== null && typeof (value as Htmlable).toHtml === 'function'\n}\n\n/** Laravel's e(): htmlspecialchars(ENT_QUOTES, double-encode). Htmlable values pass through; null is ''. */\nexport function e(value: unknown): string {\n if (value === null || value === undefined) return ''\n if (isHtmlable(value)) return value.toHtml()\n return String(value).replace(UNSAFE, (char) => ESCAPES[char]!)\n}\n","import type { ErrorKind, OrielError } from '@orieljs/contracts'\n\n/**\n * Programmer errors (Laravel throws InvalidArgumentException for these). Not an HTTP condition, so kind 'Server':\n * the exception handler reports them and renders a 500 whose message is hidden unless app.debug is on.\n */\nexport class ViewRenderError extends Error implements OrielError {\n override readonly name: string = 'ViewRenderError'\n readonly kind: ErrorKind = 'Server'\n readonly status = 500\n}\n\n/** Laravel's \"View [x] not found.\" (InvalidArgumentException). */\nexport class ViewNotFoundError extends Error implements OrielError {\n override readonly name: string = 'ViewNotFoundError'\n readonly kind: ErrorKind = 'Server'\n readonly status = 500\n constructor(readonly view: string, readonly searched: string[] = []) {\n super(`View [${view}] not found.`)\n }\n}\n\n/** A view file that doesn't default-export a component. */\nexport class InvalidViewError extends Error implements OrielError {\n override readonly name: string = 'InvalidViewError'\n readonly kind: ErrorKind = 'Server'\n readonly status = 500\n}\n","import { e, isHtmlable } from './Html'\nimport { ViewRenderError } from './errors'\n\n/** Props that are never attributes. */\nconst SKIPPED = new Set(['children', 'dangerouslySetInnerHTML', 'key', 'ref'])\n/** React-style prop names that map to a different HTML attribute. */\nconst RENAMED: Record<string, string> = { className: 'class', htmlFor: 'for' }\n/** HTML attribute names: no whitespace, quotes, `<`, `>`, `/`, `=` or control characters (WHATWG). */\nconst VALID_NAME = /^[^\\s\"'<>/=\\u0000-\\u001f\\u007f]+$/\n/** CSS properties whose numbers get no `px` (React's list, trimmed to what HTML pages use). */\nconst UNITLESS = new Set([\n 'animation-iteration-count', 'aspect-ratio', 'border-image-outset', 'border-image-slice', 'border-image-width',\n 'column-count', 'columns', 'fill-opacity', 'flex', 'flex-grow', 'flex-shrink', 'flex-order', 'font-weight',\n 'grid-area', 'grid-column', 'grid-column-end', 'grid-column-start', 'grid-row', 'grid-row-end', 'grid-row-start',\n 'line-clamp', 'line-height', 'opacity', 'order', 'orphans', 'scale', 'stop-opacity', 'stroke-dashoffset',\n 'stroke-opacity', 'stroke-width', 'tab-size', 'widows', 'z-index', 'zoom',\n])\n\nexport type ClassValue = string | number | null | undefined | false | ClassValue[] | Record<string, unknown>\nexport type StyleObject = Record<string, string | number | null | undefined | false>\n\n/** `class` accepts strings, arrays and `{ name: condition }` objects, like Blade's @class. */\nexport function classNames(value: ClassValue): string {\n if (value === null || value === undefined || value === false || value === '') return ''\n if (typeof value === 'string' || typeof value === 'number') return String(value).trim()\n if (Array.isArray(value)) return value.map(classNames).filter((name) => name !== '').join(' ')\n return Object.entries(value).filter(([, on]) => Boolean(on)).map(([name]) => name).join(' ')\n}\n\nconst kebab = (property: string) =>\n property.startsWith('--') ? property : property.replace(/^ms([A-Z])/, '-ms-$1').replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`)\n\n/** `{ marginTop: 4, '--gap': '1rem' }` → `margin-top:4px;--gap:1rem`. */\nexport function styleString(style: StyleObject): string {\n const parts: string[] = []\n for (const [property, value] of Object.entries(style)) {\n if (value === null || value === undefined || value === false || value === '') continue\n const name = kebab(property)\n const text = typeof value === 'number' && value !== 0 && !UNITLESS.has(name) && !name.startsWith('--') ? `${value}px` : String(value)\n parts.push(`${name}:${text}`)\n }\n return parts.join(';')\n}\n\nexport function assertValidName(name: string, what: 'attribute' | 'tag'): void {\n if (!VALID_NAME.test(name)) throw new ViewRenderError(`Invalid ${what} name [${name}].`)\n}\n\n/** Serializes props to ` name=\"value\"` pairs. Every value is escaped (HtmlString too); names are validated, never escaped. */\nexport function renderAttributes(props: Record<string, unknown>): string {\n let out = ''\n let classes = ''\n for (const [prop, value] of Object.entries(props)) {\n if (SKIPPED.has(prop)) continue\n if (value === undefined || value === null) continue\n if (typeof value === 'function' || typeof value === 'symbol') continue // event handlers do nothing on the server\n const name = RENAMED[prop] ?? prop\n assertValidName(name, 'attribute')\n // aria-*/data-* are string attributes: booleans are written out, like React.\n if (typeof value === 'boolean' && (name.startsWith('aria-') || name.startsWith('data-'))) {\n out += ` ${name}=\"${value}\"`\n continue\n }\n if (value === false) continue\n if (name === 'class') {\n const joined = classNames(value as ClassValue)\n classes = classes === '' ? joined : joined === '' ? classes : `${classes} ${joined}`\n continue\n }\n // Boolean attributes (disabled, checked, …) are present or absent.\n if (value === true) {\n out += ` ${name}`\n continue\n }\n const text = name === 'style' && typeof value === 'object' ? styleString(value as StyleObject) : attributeText(value)\n out += ` ${name}=\"${e(text)}\"`\n }\n return classes === '' ? out : ` class=\"${e(classes)}\"${out}`\n}\n\n/** Attribute values are text: objects become JSON (x-data, data-*), Htmlable its markup; the caller escapes all of it. */\nfunction attributeText(value: unknown): string {\n if (typeof value !== 'object' || value === null) return String(value)\n if (isHtmlable(value)) return value.toHtml()\n if (value instanceof Date) return value.toISOString()\n return JSON.stringify(value)\n}\n","import { Fragment, isElement, type OrielElement, type Renderable } from './Element'\nimport { e, isHtmlable } from './Html'\nimport { assertValidName, renderAttributes } from './attributes'\nimport { ViewRenderError } from './errors'\n\n/** Elements that never have children or a closing tag. */\nconst VOID = new Set(['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'source', 'track', 'wbr'])\n\n/** Where rendered HTML goes. flush() is called before every await, so a stream sends what is ready. */\ninterface Sink {\n write(html: string): void\n flush(): void\n}\n\ninterface Renderer {\n render(): Promise<string>\n}\n\nconst isThenable = (value: unknown): value is PromiseLike<unknown> =>\n typeof value === 'object' && value !== null && typeof (value as PromiseLike<unknown>).then === 'function'\n\n/** A View (or anything with an async render() and toHtml()) used as a child, like Blade's @include. */\nconst isRenderer = (value: object): value is Renderer =>\n typeof (value as Partial<Renderer>).render === 'function' && isHtmlable(value)\n\nasync function write(node: Renderable, sink: Sink): Promise<void> {\n if (node === null || node === undefined || typeof node === 'boolean') return\n if (typeof node === 'string') return sink.write(e(node))\n if (typeof node === 'number' || typeof node === 'bigint') return sink.write(String(node))\n if (isElement(node)) return writeElement(node, sink)\n if (isThenable(node)) {\n sink.flush()\n const resolved: unknown = await (node as PromiseLike<unknown>)\n return write(resolved as Renderable, sink)\n }\n if (isRenderer(node)) {\n sink.flush()\n return sink.write(await node.render())\n }\n if (isHtmlable(node)) return sink.write(node.toHtml())\n if (typeof (node as Iterable<Renderable>)[Symbol.iterator] === 'function') {\n for (const child of node as Iterable<Renderable>) await write(child, sink)\n return\n }\n throw new ViewRenderError(`Objects are not valid as a child (found: object with keys {${Object.keys(node).join(', ')}}).`)\n}\n\nasync function writeElement(element: OrielElement, sink: Sink): Promise<void> {\n const { type, props } = element\n if (type === Fragment) return write(props.children as Renderable, sink)\n if (typeof type === 'function') {\n const result = (type as (props: unknown) => unknown)(props)\n if (isThenable(result)) sink.flush()\n const resolved: unknown = await result\n return write(resolved as Renderable, sink)\n }\n if (typeof type !== 'string') throw new ViewRenderError(`Invalid element type [${String(type)}].`)\n assertValidName(type, 'tag')\n\n const inner = props.dangerouslySetInnerHTML as { __html?: unknown } | undefined\n const hasChildren = props.children !== undefined && props.children !== null\n if (inner !== undefined && hasChildren) {\n throw new ViewRenderError(`<${type}> can't have both children and dangerouslySetInnerHTML.`)\n }\n // A document root gets its doctype: TSX has no syntax for <!DOCTYPE html>.\n if (type === 'html') sink.write('<!DOCTYPE html>')\n sink.write(`<${type}${renderAttributes(props)}>`)\n if (VOID.has(type.toLowerCase())) {\n if (hasChildren || inner !== undefined) throw new ViewRenderError(`<${type}> is a void element and can't have children.`)\n return\n }\n if (inner !== undefined) sink.write(String(inner.__html ?? ''))\n else await write(props.children as Renderable, sink)\n sink.write(`</${type}>`)\n}\n\n/** Renders JSX (or any Renderable) to an HTML string. Async components and promises are awaited in order. */\nexport async function renderToString(node: Renderable): Promise<string> {\n const parts: string[] = []\n await write(node, { write: (html) => void parts.push(html), flush: () => {} })\n return parts.join('')\n}\n\n/** Renders to a UTF-8 byte stream, sending everything rendered so far each time rendering waits on a promise. */\nexport function renderToStream(node: Renderable): ReadableStream<Uint8Array> {\n const encoder = new TextEncoder()\n return new ReadableStream<Uint8Array>({\n async start(controller) {\n let buffer = ''\n const sink: Sink = {\n write: (html) => void (buffer += html),\n flush: () => {\n if (buffer === '') return\n controller.enqueue(encoder.encode(buffer))\n buffer = ''\n },\n }\n try {\n await write(node, sink)\n sink.flush()\n controller.close()\n } catch (error) {\n controller.error(error)\n }\n },\n })\n}\n","import { existsSync } from 'node:fs'\nimport { isAbsolute, join, resolve } from 'node:path'\nimport { ViewNotFoundError } from './errors'\n\n/** `mail::message` → namespace `mail`, view `message` (Laravel's hint path delimiter). */\nconst HINT = '::'\n\n/**\n * Laravel's FileViewFinder: 'posts.index' → <location>/posts/index.<ext> for the first location and extension\n * that exist. Found paths are cached; misses are not, so a view created later is found.\n */\nexport class FileViewFinder {\n static readonly defaultExtensions = ['tsx', 'ts', 'jsx', 'js', 'mjs']\n\n private readonly locations: string[]\n private readonly hints = new Map<string, string[]>()\n private readonly views = new Map<string, string>()\n\n constructor(paths: string[], private readonly extensions: string[] = FileViewFinder.defaultExtensions) {\n this.locations = paths.map((path) => resolve(path))\n }\n\n /** The absolute path of the view file. Throws ViewNotFoundError. */\n find(name: string): string {\n const cached = this.views.get(name)\n if (cached !== undefined) return cached\n const [locations, view] = this.parse(name)\n const searched: string[] = []\n for (const location of locations) {\n for (const extension of this.extensions) {\n const file = join(location, `${view}.${extension}`)\n searched.push(file)\n if (existsSync(file)) {\n this.views.set(name, file)\n return file\n }\n }\n }\n throw new ViewNotFoundError(name, searched)\n }\n\n addLocation(path: string): void {\n this.locations.push(resolve(path))\n }\n\n prependLocation(path: string): void {\n this.locations.unshift(resolve(path))\n }\n\n /** Views under a namespace: addNamespace('mail', dir) makes 'mail::message' resolve to <dir>/message.tsx. */\n addNamespace(namespace: string, hints: string | string[]): void {\n const existing = this.hints.get(namespace) ?? []\n this.hints.set(namespace, [...existing, ...[hints].flat().map((path) => resolve(path))])\n }\n\n replaceNamespace(namespace: string, hints: string | string[]): void {\n this.hints.set(namespace, [hints].flat().map((path) => resolve(path)))\n this.flush()\n }\n\n getPaths(): string[] {\n return [...this.locations]\n }\n\n getHints(): Record<string, string[]> {\n return Object.fromEntries(this.hints)\n }\n\n /** Forget cached lookups. */\n flush(): void {\n this.views.clear()\n }\n\n /** [locations to search, relative file stem]. Dots become directories, so '..' can't climb out of a location. */\n private parse(name: string): [string[], string] {\n let locations = this.locations\n let view = name\n const at = name.indexOf(HINT)\n if (at !== -1) {\n const namespace = name.slice(0, at)\n view = name.slice(at + HINT.length)\n const hints = this.hints.get(namespace)\n if (hints === undefined) throw new ViewNotFoundError(name)\n locations = hints\n }\n const stem = view.replaceAll('.', '/')\n if (view === '' || stem.includes('\\0') || isAbsolute(stem) || stem.split(/[\\\\/]/).some((segment) => segment === '')) {\n throw new ViewNotFoundError(name)\n }\n return [locations, stem]\n }\n}\n","import type { View } from '@orieljs/contracts'\nimport { createElement, type Component } from './Element'\nimport { ViewRenderError } from './errors'\nimport { renderToString } from './render'\n\n/** What TsxView needs from its factory. */\nexport interface ViewEnvironment {\n getShared(): Record<string, unknown>\n callComposer(view: View): Promise<void>\n load(path: string, name: string): Promise<Component>\n}\n\n/**\n * One view: its name, file and data. render() runs the composers, imports the file and renders its default\n * export with { ...shared, ...data } as props. toHtml() returns the HTML of the last render().\n */\nexport class TsxView implements View {\n private data: Record<string, unknown>\n private html: string | undefined\n\n constructor(\n private readonly environment: ViewEnvironment,\n readonly name: string,\n readonly path: string,\n data: Record<string, unknown> = {},\n ) {\n this.data = { ...data }\n }\n\n with(key: string | Record<string, unknown>, value?: unknown): this {\n if (typeof key === 'string') this.data[key] = value\n else Object.assign(this.data, key)\n return this\n }\n\n /** The view's own data (Laravel's getData()). */\n getData(): Record<string, unknown> {\n return { ...this.data }\n }\n\n /** Shared data overlaid with the view's data: exactly the props the component receives. */\n gatherData(): Record<string, unknown> {\n return { ...this.environment.getShared(), ...this.data }\n }\n\n async render(): Promise<string> {\n await this.environment.callComposer(this)\n const component = await this.environment.load(this.path, this.name)\n this.html = await renderToString(createElement(component as Component<never>, this.gatherData()))\n return this.html\n }\n\n toHtml(): string {\n if (this.html === undefined) {\n throw new ViewRenderError(`View [${this.name}] has not been rendered: await view.render() before toHtml().`)\n }\n return this.html\n }\n}\n","import { pathToFileURL } from 'node:url'\nimport type { View, ViewFactory } from '@orieljs/contracts'\nimport { Str } from '@orieljs/support'\nimport type { Component, Renderable } from './Element'\nimport { InvalidViewError, ViewNotFoundError } from './errors'\nimport type { FileViewFinder } from './FileViewFinder'\nimport { renderToString } from './render'\nimport { TsxView, type ViewEnvironment } from './TsxView'\n\ntype Composer = (view: View) => void | Promise<void>\n\n/** Laravel's view Factory: finds views by dotted name, shares data app-wide and runs view composers. */\nexport class Factory implements ViewFactory, ViewEnvironment {\n private readonly shared: Record<string, unknown> = {}\n private readonly composers: Array<{ patterns: string[]; composer: Composer }> = []\n\n constructor(private readonly finder: FileViewFinder) {}\n\n /** Throws ViewNotFoundError (\"View [x] not found.\") when no file matches. */\n make(name: string, data: Record<string, unknown> = {}): TsxView {\n return new TsxView(this, name, this.finder.find(name), data)\n }\n\n /** The first view that exists (Laravel's View::first()). */\n first(names: string[], data: Record<string, unknown> = {}): TsxView {\n const name = names.find((candidate) => this.exists(candidate))\n if (name === undefined) throw new ViewNotFoundError(names.join(', '))\n return this.make(name, data)\n }\n\n exists(name: string): boolean {\n try {\n this.finder.find(name)\n return true\n } catch (error) {\n if (error instanceof ViewNotFoundError) return false\n throw error\n }\n }\n\n /** App-wide data every view receives as props (view data wins on conflicts). */\n share(key: string | Record<string, unknown>, value?: unknown): void {\n if (typeof key === 'string') this.shared[key] = value\n else Object.assign(this.shared, key)\n }\n\n getShared(): Record<string, unknown> {\n return { ...this.shared }\n }\n\n /** Runs before matching views render. Patterns use `*` wildcards: 'posts.*', '*'. */\n composer(views: string | string[], composer: Composer): void {\n this.composers.push({ patterns: [views].flat(), composer })\n }\n\n async callComposer(view: View): Promise<void> {\n for (const { patterns, composer } of this.composers) {\n if (patterns.some((pattern) => Str.is(pattern, view.name))) await composer(view)\n }\n }\n\n /** Renders TSX, a string, an Htmlable or a View (or promises of them) to HTML, like Laravel's Blade::render(). */\n renderToString(node: unknown): Promise<string> {\n return renderToString(node as Renderable)\n }\n\n /** Imports the view file (Node's module cache keeps it) and returns its default-export component. */\n async load(path: string, name: string): Promise<Component> {\n const module = (await import(pathToFileURL(path).href)) as { default?: unknown }\n if (typeof module.default !== 'function') {\n throw new InvalidViewError(`View [${name}] (${path}) must default-export a component function.`)\n }\n return module.default as Component\n }\n\n addLocation(path: string): void {\n this.finder.addLocation(path)\n }\n\n addNamespace(namespace: string, hints: string | string[]): this {\n this.finder.addNamespace(namespace, hints)\n return this\n }\n\n getFinder(): FileViewFinder {\n return this.finder\n }\n}\n","import { Session, ViewFactory, type View } from '@orieljs/contracts'\nimport { inject } from '@orieljs/container'\nimport { e, HtmlString } from './Html'\n\n/** view() → the ViewFactory; view('posts.index', data) → a View to return from a route or render. */\nexport function view(): ViewFactory\nexport function view(name: string, data?: Record<string, unknown>): View\nexport function view(name?: string, data: Record<string, unknown> = {}): ViewFactory | View {\n const factory = inject(ViewFactory)\n return name === undefined ? factory : factory.make(name, data)\n}\n\n/** Blade's @csrf / csrf_field(): the hidden _token input for the current request's session. */\nexport function csrfField(): HtmlString {\n return new HtmlString(`<input type=\"hidden\" name=\"_token\" value=\"${e(inject(Session).token())}\" autocomplete=\"off\">`)\n}\n\n/** Blade's @method('PUT') / method_field(): spoofs PUT/PATCH/DELETE from an HTML form. */\nexport function methodField(method: string): HtmlString {\n return new HtmlString(`<input type=\"hidden\" name=\"_method\" value=\"${e(method.toUpperCase())}\">`)\n}\n","import { ViewFactory, type Token } from '@orieljs/contracts'\nimport { facade } from '@orieljs/support'\nimport type { Factory } from './Factory'\n\n/** The View facade: View.make(), View.exists(), View.share(), View.composer(), View.first(), View.addNamespace(). */\nexport const View = facade(ViewFactory as unknown as Token<Factory>)\n","import { ConfigRepository, ViewFactory } from '@orieljs/contracts'\nimport { ServiceProvider } from '@orieljs/support'\nimport { Factory } from './Factory'\nimport { FileViewFinder } from './FileViewFinder'\n\n/** Binds the ViewFactory (resources/views by default) and merges the `view` config, like Laravel's config/view.php. */\nexport class ViewServiceProvider extends ServiceProvider {\n override register(): void {\n this.mergeConfigFrom({ paths: [this.app.resourcePath('views')] }, 'view')\n this.app.singleton(ViewFactory, () => {\n const paths = this.app.make(ConfigRepository).get<string[]>('view.paths', [this.app.resourcePath('views')])\n return new Factory(new FileViewFinder(paths))\n })\n }\n}\n"],"mappings":"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","names":["view","ViewFactory"]}