@arkstack/view 0.5.1 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # @arkstack/view
2
2
 
3
+ [![@arkstack/view](https://img.shields.io/npm/dt/@arkstack/view?style=flat-square&label=@arkstack/view&link=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2F@arkstack/view)](https://www.npmjs.com/package/@arkstack/view)
4
+
3
5
  View module for Arkstack, providing template rendering and view integration utilities.
4
6
 
5
7
  ```ts
@@ -1,5 +1,6 @@
1
1
  import { dirname, resolve } from "node:path";
2
2
  import { mkdir, writeFile } from "node:fs/promises";
3
+ import { Arkstack } from "@arkstack/contract";
3
4
  import { Command } from "@h3ravel/musket";
4
5
  //#region src/commands/MakeViewCommand.ts
5
6
  var MakeViewCommand = class extends Command {
@@ -21,7 +22,7 @@ var MakeViewCommand = class extends Command {
21
22
  }
22
23
  path(name) {
23
24
  const viewPath = name.replace(/\\/g, "/").replace(/\./g, "/").replace(/\.edge$/i, "");
24
- return resolve(process.cwd(), "src", "resources", "views", `${viewPath}.edge`);
25
+ return resolve(Arkstack.rootDir(), "src", "resources", "views", `${viewPath}.edge`);
25
26
  }
26
27
  stub(name) {
27
28
  const title = name.split(/[./\\]/).filter(Boolean).pop() ?? "view";
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  /// <reference path="./app.d.ts" />
2
+ import { t as clearRouterViewPlugin } from "./plugins-D3vyoc8i.js";
2
3
  import edge, { Edge, Edge as Edge$1 } from "edge.js";
4
+ import { ParserTagDefinitionContract } from "edge.js/types";
3
5
 
4
6
  //#region src/ViewInstance.d.ts
5
7
  declare class ViewInstance implements PromiseLike<string> {
@@ -25,6 +27,10 @@ declare class ViewInstance implements PromiseLike<string> {
25
27
  }
26
28
  //#endregion
27
29
  //#region src/types.d.ts
30
+ type ViewErrorValue = string | string[] | Error | {
31
+ message?: unknown;
32
+ } | unknown;
33
+ type ViewErrorRecord = Record<string, ViewErrorValue>;
28
34
  type ComposerRunner = (view: ViewInstance) => Promise<void>;
29
35
  type SyncComposerRunner = (view: ViewInstance) => void;
30
36
  type ViewData = Record<string, any>;
@@ -51,16 +57,109 @@ declare class ViewFactory {
51
57
  private mountedPackages;
52
58
  private packageViewsPath;
53
59
  constructor(options?: ViewFactoryOptions);
60
+ /**
61
+ * Create a new view instance for the given view name and data.
62
+ *
63
+ * @param name
64
+ * @param data
65
+ * @returns
66
+ */
54
67
  make(name: ViewName, data?: ViewData): ViewInstance;
68
+ /**
69
+ * Render the first view that exists from the given list of names.
70
+ *
71
+ * @param names
72
+ * @param data
73
+ * @returns
74
+ */
55
75
  first(names: ViewName[], data?: ViewData): ViewInstance;
56
- exists(name: ViewName): any;
76
+ /**
77
+ * Check if a view exists.
78
+ *
79
+ * @param name
80
+ * @returns
81
+ */
82
+ exists(name: ViewName): boolean;
83
+ /**
84
+ * Share data with all views.
85
+ * This data will be available in every view rendered by the factory.
86
+ *
87
+ * @param key
88
+ * @param value
89
+ */
57
90
  share(key: string, value: any): this;
58
91
  share(data: ViewData): this;
92
+ /**
93
+ * Register a view composer for the given view name(s).
94
+ * A view composer is a function or object that is called when a view is
95
+ * rendered, allowing you to modify the view's data or perform other actions.
96
+ *
97
+ * @param names
98
+ * @param composer
99
+ * @returns
100
+ */
59
101
  composer(names: ViewComposerName, composer: ViewComposer): this;
102
+ /**
103
+ * Mount a directory containing views.
104
+ * If only one argument is provided, it will be treated as the views directory.
105
+ * If two arguments are provided, the first will be treated as the disk name and
106
+ * the second as the views directory.
107
+ *
108
+ * @param viewsDirectory
109
+ */
60
110
  mount(viewsDirectory: string | URL): this;
61
111
  mount(diskName: string, viewsDirectory: string | URL): this;
112
+ /**
113
+ * Register a raw template with the given name and contents.
114
+ *
115
+ * @param name
116
+ * @param contents
117
+ * @returns
118
+ */
62
119
  raw(name: ViewName, contents: string): this;
120
+ /**
121
+ * Register a custom tag with the given name, block type, seekable type,
122
+ * and compiler function.
123
+ *
124
+ * @param tagName
125
+ * @param block
126
+ * @param seekable
127
+ * @param compiler
128
+ */
129
+ tag(
130
+ /**
131
+ * The tag name
132
+ */
133
+
134
+ tagName: string,
135
+ /**
136
+ * Tag accepts content within the opening and
137
+ * closing tags
138
+ */
139
+
140
+ block: boolean,
141
+ /**
142
+ * Tag accepts parameters
143
+ */
144
+
145
+ seekable: boolean,
146
+ /**
147
+ * The parser needs the `compile` method on every tag
148
+ */
149
+
150
+ compiler: ParserTagDefinitionContract['compile']): void;
151
+ /**
152
+ * Flush all shared data. This will remove all data that has been shared with all views.
153
+ *
154
+ * @returns
155
+ */
63
156
  flushShared(): this;
157
+ /**
158
+ * Flush all registered composers. This will remove all composers that have
159
+ * been registered for any view.
160
+ *
161
+ * @returns
162
+ */
64
163
  flushComposers(): this;
65
164
  private getComposers;
66
165
  private runComposers;
@@ -71,12 +170,16 @@ declare class ViewFactory {
71
170
  //#region src/View.d.ts
72
171
  declare class View {
73
172
  private static factory;
173
+ private static usesDefaultFactoryRoot;
174
+ /**
175
+ * Bootstrap the view service
176
+ */
74
177
  static boot(): void;
75
178
  static configure(options?: ViewFactoryOptions): ViewFactory;
76
179
  static factoryInstance(): ViewFactory;
77
180
  static make(name: ViewName, data?: ViewData): ViewInstance;
78
181
  static first(names: ViewName[], data?: ViewData): ViewInstance;
79
- static exists(name: ViewName): any;
182
+ static exists(name: ViewName): boolean;
80
183
  static share(key: string, value: any): typeof View;
81
184
  static share(data: ViewData): typeof View;
82
185
  static composer(names: ViewComposerName, composer: ViewComposer): typeof View;
@@ -85,6 +188,34 @@ declare class View {
85
188
  static raw(name: ViewName, contents: string): typeof View;
86
189
  }
87
190
  //#endregion
191
+ //#region src/ViewErrorBag.d.ts
192
+ declare class ViewErrorBag {
193
+ private bag;
194
+ constructor(errors?: ViewErrorRecord | ViewErrorBag | unknown);
195
+ add(field: string, message: ViewErrorValue): this;
196
+ merge(errors: ViewErrorRecord | ViewErrorBag | unknown): this;
197
+ keys(): string[];
198
+ get(field?: string): string[];
199
+ first(field?: string | null): string;
200
+ has(field?: string | string[] | null): boolean;
201
+ hasAny(fields: string | string[]): boolean;
202
+ missing(fields: string | string[]): boolean;
203
+ any(): boolean;
204
+ isEmpty(): boolean;
205
+ isNotEmpty(): boolean;
206
+ count(): number;
207
+ all(): string[];
208
+ unique(): string[];
209
+ clear(field?: string | string[]): this;
210
+ forget(field: string): this;
211
+ messagesRaw(): Record<string, string[]>;
212
+ getMessages(): Record<string, string[]>;
213
+ getMessageBag(): this;
214
+ toArray(): Record<string, string[]>;
215
+ toJSON(): Record<string, string[]>;
216
+ }
217
+ declare const normalizeViewErrors: (errors?: unknown) => Pick<ViewErrorBag, "all" | "first" | "get" | "has">;
218
+ //#endregion
88
219
  //#region src/helpers.d.ts
89
220
  declare function view(): ViewFactory;
90
221
  declare function view(name: ViewName, data?: ViewData): ViewInstance;
@@ -99,7 +230,46 @@ type PackageViewReference = {
99
230
  edgeName: string;
100
231
  };
101
232
  declare const parsePackageViewName: (name: string) => PackageViewReference | null;
102
- declare const resolvePackageViewsPath: (nodePackageName: string, viewPath?: string) => any;
233
+ declare const resolvePackageViewsPath: (nodePackageName: string, viewPath?: string) => string;
234
+ //#endregion
235
+ //#region src/vite.d.ts
236
+ interface ViteTagOptions {
237
+ /** Force development (Vite dev server) tags regardless of `NODE_ENV`. */
238
+ hot?: boolean;
239
+ /** Vite dev server URL. Defaults to `VITE_DEV_URL` or `http://localhost:5173`. */
240
+ devUrl?: string;
241
+ /** Path to the Vite build manifest. Defaults to `public/build/.vite/manifest.json`. */
242
+ manifest?: string;
243
+ /** Public URL prefix the built assets are served from. Defaults to `/build/`. */
244
+ buildDir?: string;
245
+ }
246
+ /**
247
+ * Resolve `<script>`/`<link>` tags for one or more Vite entries.
248
+ *
249
+ * In development (when `NODE_ENV` is not `production`, or `hot` is set) it points
250
+ * at the Vite dev server and includes the `@vite/client`. In production it reads
251
+ * the build manifest and emits the hashed asset tags (including any CSS a chunk
252
+ * imports). Backs the `@vite(...)` Edge tag.
253
+ *
254
+ * @param entries
255
+ * @param options
256
+ * @returns
257
+ */
258
+ declare const viteTags: (entries: string | string[], options?: ViteTagOptions) => string;
259
+ /**
260
+ * Register the `@vite(...)` tag (and its backing global) on a view factory so
261
+ * templates can emit Vite asset tags: `@vite('resources/js/app.ts')` or
262
+ * `@vite(['resources/css/app.css', 'resources/js/app.ts'])`.
263
+ *
264
+ * @param factory
265
+ */
266
+ declare const registerViteTag: (factory: ViewFactory) => void;
267
+ //#endregion
268
+ //#region src/viewContext.d.ts
269
+ declare const getViewData: () => ViewData;
270
+ declare const enterViewData: (data?: ViewData) => void;
271
+ declare const runWithViewData: <T>(data: ViewData, callback: () => T | Promise<T>) => Promise<T>;
272
+ declare const clearViewData: () => void;
273
+ declare const collectViewData: (context: Record<string, any>) => ViewData;
103
274
  //#endregion
104
- export { ComposerRunner, Edge, SyncComposerRunner, View, ViewComposer, ViewComposerClass, ViewComposerHandler, ViewComposerName, ViewComposerObject, ViewData, ViewFactory, ViewFactoryOptions, ViewInstance, ViewName, edge, parsePackageViewName, resolvePackageViewsPath, view };
105
- //# sourceMappingURL=index.d.ts.map
275
+ export { ComposerRunner, Edge, SyncComposerRunner, View, ViewComposer, ViewComposerClass, ViewComposerHandler, ViewComposerName, ViewComposerObject, ViewData, ViewErrorBag, ViewErrorRecord, ViewErrorValue, ViewFactory, ViewFactoryOptions, ViewInstance, ViewName, type ViteTagOptions, clearRouterViewPlugin, clearViewData, collectViewData, edge, enterViewData, getViewData, normalizeViewErrors, parsePackageViewName, registerViteTag, resolvePackageViewsPath, runWithViewData, view, viteTags };
package/dist/index.js CHANGED
@@ -1,247 +1,3 @@
1
- import { existsSync } from "node:fs";
2
- import { resolve } from "node:path";
3
- import edge, { Edge, Edge as Edge$1 } from "edge.js";
4
- //#region src/helpers.ts
5
- function view(name, data = {}) {
6
- if (name === void 0) return View.factoryInstance();
7
- return View.make(name, data);
8
- }
9
- const isClass = (target) => {
10
- return typeof target === "function" && /^class\s/.test(Function.prototype.toString.call(target));
11
- };
12
- const mergeData = (target, data) => {
13
- if (data.length === 0) return target;
14
- if (typeof data[0] === "string") {
15
- target[data[0]] = data[1];
16
- return target;
17
- }
18
- for (const value of data) if (value && typeof value === "object" && !Array.isArray(value)) Object.assign(target, value);
19
- return target;
20
- };
21
- const runComposerSync = (composer, view) => {
22
- const result = runComposer(composer, view);
23
- if (result && typeof result.then === "function") throw new Error("Async view composers cannot be used with renderSync.");
24
- };
25
- const runComposer = (composer, view) => {
26
- if (typeof composer === "function") {
27
- if (isClass(composer)) return new composer().compose(view);
28
- return composer(view);
29
- }
30
- return composer.compose(view);
31
- };
32
- //#endregion
33
- //#region src/packageViews.ts
34
- const parsePackageViewName = (name) => {
35
- if (!name.startsWith("~")) return null;
36
- const source = name.slice(1);
37
- const slashIndex = source.indexOf("/");
38
- const dotIndex = slashIndex === -1 ? source.indexOf(".") : source.indexOf(".", slashIndex);
39
- if (dotIndex <= 0) throw new Error(`Invalid package view name: ${name}`);
40
- const packageName = source.slice(0, dotIndex);
41
- const viewName = source.slice(dotIndex + 1);
42
- if (!viewName) throw new Error(`Invalid package view name: ${name}`);
43
- const nodePackageName = slashIndex === -1 ? packageName : `@${packageName}`;
44
- const diskName = `package_${packageName.replace(/[^a-zA-Z0-9_-]/g, "_")}`;
45
- return {
46
- source: name,
47
- packageName,
48
- nodePackageName,
49
- diskName,
50
- viewName,
51
- edgeName: `${diskName}::${viewName}`
52
- };
53
- };
54
- const resolvePackageViewsPath = (nodePackageName, viewPath = "resources/views") => {
55
- const viewsPath = resolve(resolve(process.cwd(), "node_modules", nodePackageName), viewPath);
56
- if (!existsSync(viewsPath)) throw new Error(`Package views directory not found: ${viewsPath}`);
57
- return viewsPath;
58
- };
59
- //#endregion
60
- //#region src/ViewInstance.ts
61
- var ViewInstance = class {
62
- name;
63
- renderer;
64
- runComposers;
65
- runComposersSync;
66
- renderName;
67
- payload;
68
- composersHaveRun = false;
69
- constructor(name, data = {}, renderer, runComposers, runComposersSync, renderName = name) {
70
- this.name = name;
71
- this.renderer = renderer;
72
- this.runComposers = runComposers;
73
- this.runComposersSync = runComposersSync;
74
- this.renderName = renderName;
75
- this.payload = { ...data };
76
- }
77
- get data() {
78
- return this.payload;
79
- }
80
- with(...data) {
81
- mergeData(this.payload, data);
82
- return this;
83
- }
84
- async render() {
85
- await this.compose();
86
- return await this.renderer.render(this.renderName, this.payload);
87
- }
88
- renderSync() {
89
- this.composeSync();
90
- return this.renderer.renderSync(this.renderName, this.payload);
91
- }
92
- then(onfulfilled, onrejected) {
93
- return this.render().then(onfulfilled, onrejected);
94
- }
95
- async compose() {
96
- if (this.composersHaveRun) return;
97
- this.composersHaveRun = true;
98
- await this.runComposers(this);
99
- }
100
- composeSync() {
101
- if (this.composersHaveRun) return;
102
- this.composersHaveRun = true;
103
- this.runComposersSync(this);
104
- }
105
- };
106
- //#endregion
107
- //#region src/ViewFactory.ts
108
- var ViewFactory = class {
109
- edge;
110
- sharedData = {};
111
- composers = /* @__PURE__ */ new Map();
112
- mountedPackages = /* @__PURE__ */ new Set();
113
- packageViewsPath;
114
- constructor(options = {}) {
115
- this.edge = options.edge ?? Edge$1.create({ cache: options.cache });
116
- this.packageViewsPath = options.packageViewsPath ?? "resources/views";
117
- this.mount(options.viewsPath ?? resolve(process.cwd(), "src", "resources", "views"));
118
- }
119
- make(name, data = {}) {
120
- const edgeName = this.resolveName(name);
121
- return new ViewInstance(name, {
122
- ...this.sharedData,
123
- ...data
124
- }, this.edge, async (view) => await this.runComposers(name, view), (view) => this.runComposersSync(name, view), edgeName);
125
- }
126
- first(names, data = {}) {
127
- const name = names.find((candidate) => this.exists(candidate));
128
- if (!name) throw new Error(`None of the given views exist: ${names.join(", ")}`);
129
- return this.make(name, data);
130
- }
131
- exists(name) {
132
- const edgeName = this.resolveName(name);
133
- if (this.edge.loader.templates[edgeName]) return true;
134
- try {
135
- return existsSync(this.edge.loader.makePath(edgeName));
136
- } catch {
137
- return false;
138
- }
139
- }
140
- share(...data) {
141
- mergeData(this.sharedData, data);
142
- return this;
143
- }
144
- composer(names, composer) {
145
- for (const name of Array.isArray(names) ? names : [names]) this.composers.set(name, [...this.composers.get(name) ?? [], composer]);
146
- return this;
147
- }
148
- mount(diskName, viewsDirectory) {
149
- if (viewsDirectory === void 0) {
150
- this.edge.mount(diskName);
151
- return this;
152
- }
153
- this.edge.mount(diskName, viewsDirectory);
154
- return this;
155
- }
156
- raw(name, contents) {
157
- this.edge.registerTemplate(name, { template: contents });
158
- return this;
159
- }
160
- flushShared() {
161
- this.sharedData = {};
162
- return this;
163
- }
164
- flushComposers() {
165
- this.composers.clear();
166
- return this;
167
- }
168
- getComposers(name) {
169
- const edgeName = this.resolveName(name);
170
- return [
171
- ...this.composers.get("*") ?? [],
172
- ...this.composers.get(edgeName) ?? [],
173
- ...this.composers.get(name) ?? []
174
- ];
175
- }
176
- async runComposers(name, view) {
177
- for (const composer of this.getComposers(name)) await runComposer(composer, view);
178
- }
179
- runComposersSync(name, view) {
180
- for (const composer of this.getComposers(name)) runComposerSync(composer, view);
181
- }
182
- resolveName(name) {
183
- const packageView = parsePackageViewName(name);
184
- if (!packageView) return name;
185
- if (!this.mountedPackages.has(packageView.diskName)) {
186
- this.mount(packageView.diskName, resolvePackageViewsPath(packageView.nodePackageName, this.packageViewsPath));
187
- this.mountedPackages.add(packageView.diskName);
188
- }
189
- return packageView.edgeName;
190
- }
191
- };
192
- //#endregion
193
- //#region src/View.ts
194
- var View = class View {
195
- static factory = new ViewFactory();
196
- /**
197
- * Bootstrap the view service
198
- */
199
- static boot() {
200
- Object.defineProperty(globalThis, "view", {
201
- value: (name, data = {}) => {
202
- if (name === void 0) return View.factoryInstance();
203
- return View.make(name, data);
204
- },
205
- configurable: true,
206
- writable: true
207
- });
208
- }
209
- static configure(options = {}) {
210
- this.factory = new ViewFactory(options);
211
- return this.factory;
212
- }
213
- static factoryInstance() {
214
- return this.factory;
215
- }
216
- static make(name, data = {}) {
217
- return this.factory.make(name, data);
218
- }
219
- static first(names, data = {}) {
220
- return this.factory.first(names, data);
221
- }
222
- static exists(name) {
223
- return this.factory.exists(name);
224
- }
225
- static share(...data) {
226
- if (typeof data[0] === "string") this.factory.share(data[0], data[1]);
227
- else this.factory.share(data[0] ?? {});
228
- return this;
229
- }
230
- static composer(names, composer) {
231
- this.factory.composer(names, composer);
232
- return this;
233
- }
234
- static mount(diskName, viewsDirectory) {
235
- if (viewsDirectory === void 0) this.factory.mount(diskName);
236
- else this.factory.mount(diskName, viewsDirectory);
237
- return this;
238
- }
239
- static raw(name, contents) {
240
- this.factory.raw(name, contents);
241
- return this;
242
- }
243
- };
244
- //#endregion
245
- export { Edge, View, ViewFactory, ViewInstance, edge, parsePackageViewName, resolvePackageViewsPath, view };
246
-
247
- //# sourceMappingURL=index.js.map
1
+ import { a as viteTags, c as enterViewData, d as ViewInstance, f as parsePackageViewName, g as normalizeViewErrors, h as ViewErrorBag, i as registerViteTag, l as getViewData, m as view, n as View, o as clearViewData, p as resolvePackageViewsPath, r as ViewFactory, s as collectViewData, t as clearRouterViewPlugin, u as runWithViewData } from "./plugins-BuRvAzjs.js";
2
+ import edge, { Edge } from "edge.js";
3
+ export { Edge, View, ViewErrorBag, ViewFactory, ViewInstance, clearRouterViewPlugin, clearViewData, collectViewData, edge, enterViewData, getViewData, normalizeViewErrors, parsePackageViewName, registerViteTag, resolvePackageViewsPath, runWithViewData, view, viteTags };
@@ -0,0 +1,591 @@
1
+ import { env, isClass, nodeEnv } from "@arkstack/common";
2
+ import { Arkstack } from "@arkstack/contract";
3
+ import { existsSync, readFileSync } from "node:fs";
4
+ import { join, resolve } from "node:path";
5
+ import { Edge } from "edge.js";
6
+ import { AsyncLocalStorage } from "node:async_hooks";
7
+ import { Hook } from "@arkstack/foundry";
8
+ import { definePlugin } from "clear-router/core";
9
+ //#region src/ViewErrorBag.ts
10
+ const defaultErrorKey = "_";
11
+ var ViewErrorBag = class ViewErrorBag {
12
+ bag = {};
13
+ constructor(errors) {
14
+ if (errors) this.merge(errors);
15
+ }
16
+ add(field, message) {
17
+ const key = field || defaultErrorKey;
18
+ const messages = toMessages(message);
19
+ if (!messages.length) return this;
20
+ this.bag[key] = [...this.bag[key] || [], ...messages];
21
+ return this;
22
+ }
23
+ merge(errors) {
24
+ const incoming = errors instanceof ViewErrorBag ? errors.toJSON() : getMessageRecord(errors) || (isRecord(errors) ? errors : void 0);
25
+ if (!incoming) return this;
26
+ for (const [field, messages] of Object.entries(incoming)) this.add(field, messages);
27
+ return this;
28
+ }
29
+ keys() {
30
+ return Object.keys(this.bag);
31
+ }
32
+ get(field = defaultErrorKey) {
33
+ return [...this.bag[field] || []];
34
+ }
35
+ first(field) {
36
+ if (field) return this.bag[field]?.[0] || "";
37
+ return this.all()[0] || "";
38
+ }
39
+ has(field) {
40
+ if (Array.isArray(field)) return field.every((key) => this.has(key));
41
+ if (field) return (this.bag[field]?.length || 0) > 0;
42
+ return this.any();
43
+ }
44
+ hasAny(fields) {
45
+ return (Array.isArray(fields) ? fields : [fields]).some((key) => this.has(key));
46
+ }
47
+ missing(fields) {
48
+ return (Array.isArray(fields) ? fields : [fields]).every((key) => !this.has(key));
49
+ }
50
+ any() {
51
+ return Object.values(this.bag).some((messages) => messages.length > 0);
52
+ }
53
+ isEmpty() {
54
+ return !this.any();
55
+ }
56
+ isNotEmpty() {
57
+ return this.any();
58
+ }
59
+ count() {
60
+ return Object.values(this.bag).reduce((total, messages) => total + messages.length, 0);
61
+ }
62
+ all() {
63
+ return Object.values(this.bag).flat();
64
+ }
65
+ unique() {
66
+ return [...new Set(this.all())];
67
+ }
68
+ clear(field) {
69
+ if (Array.isArray(field)) {
70
+ for (const key of field) delete this.bag[key];
71
+ return this;
72
+ }
73
+ if (field) {
74
+ delete this.bag[field];
75
+ return this;
76
+ }
77
+ this.bag = {};
78
+ return this;
79
+ }
80
+ forget(field) {
81
+ return this.clear(field);
82
+ }
83
+ messagesRaw() {
84
+ return this.toJSON();
85
+ }
86
+ getMessages() {
87
+ return this.messagesRaw();
88
+ }
89
+ getMessageBag() {
90
+ return this;
91
+ }
92
+ toArray() {
93
+ return this.toJSON();
94
+ }
95
+ toJSON() {
96
+ return Object.entries(this.bag).reduce((errors, [field, messages]) => {
97
+ errors[field] = [...messages];
98
+ return errors;
99
+ }, {});
100
+ }
101
+ };
102
+ const isViewErrorBag = (value) => {
103
+ return isRecord(value) && typeof value.all === "function" && typeof value.first === "function" && typeof value.get === "function" && typeof value.has === "function";
104
+ };
105
+ const normalizeViewErrors = (errors) => {
106
+ if (isViewErrorBag(errors)) return errors;
107
+ return new ViewErrorBag(errors);
108
+ };
109
+ //#endregion
110
+ //#region src/helpers.ts
111
+ function view(name, data = {}) {
112
+ if (name === void 0) return View.factoryInstance();
113
+ return View.make(name, data);
114
+ }
115
+ const currentHttpSession = () => {
116
+ try {
117
+ const session = globalThis.session?.();
118
+ return session && typeof session === "object" ? session : void 0;
119
+ } catch {
120
+ return;
121
+ }
122
+ };
123
+ const hasRenderableErrors = (errors) => {
124
+ if (!errors || typeof errors !== "object") return false;
125
+ const bag = errors;
126
+ if (typeof bag.any === "function") return Boolean(bag.any());
127
+ if (typeof bag.has === "function") return Boolean(bag.has());
128
+ if (typeof bag.isNotEmpty === "function") return Boolean(bag.isNotEmpty());
129
+ if (typeof bag.all === "function") {
130
+ const messages = bag.all();
131
+ if (Array.isArray(messages)) return messages.length > 0;
132
+ return !!messages && typeof messages === "object" && Object.keys(messages).length > 0;
133
+ }
134
+ return Object.keys(bag).length > 0;
135
+ };
136
+ const normalizeViewData = (data = {}) => {
137
+ const session = currentHttpSession();
138
+ const ownErrors = normalizeViewErrors(data.errors);
139
+ const sessionErrors = session?.errors;
140
+ const errors = hasRenderableErrors(ownErrors) || !sessionErrors ? ownErrors : normalizeViewErrors(sessionErrors);
141
+ return {
142
+ ...session && !("session" in data) ? { session } : {},
143
+ ...session && !("httpSession" in data) ? { httpSession: session } : {},
144
+ ...data,
145
+ errors
146
+ };
147
+ };
148
+ const mergeData = (target, data) => {
149
+ if (data.length === 0) return target;
150
+ if (typeof data[0] === "string") {
151
+ target[data[0]] = data[0] === "errors" ? normalizeViewErrors(data[1]) : data[1];
152
+ return target;
153
+ }
154
+ for (const value of data) if (value && typeof value === "object" && !Array.isArray(value)) Object.assign(target, normalizeViewData(value));
155
+ return target;
156
+ };
157
+ const runComposerSync = (composer, view) => {
158
+ const result = runComposer(composer, view);
159
+ if (result && typeof result.then === "function") throw new Error("Async view composers cannot be used with renderSync.");
160
+ };
161
+ const runComposer = (composer, view) => {
162
+ if (typeof composer === "function") {
163
+ if (isClass(composer)) return new composer().compose(view);
164
+ return composer(view);
165
+ }
166
+ return composer.compose(view);
167
+ };
168
+ const isRecord = (value) => {
169
+ return !!value && typeof value === "object" && !Array.isArray(value);
170
+ };
171
+ const toMessages = (value) => {
172
+ if (Array.isArray(value)) return value.flatMap((item) => toMessages(item));
173
+ if (value instanceof Error) return [value.message];
174
+ if (isRecord(value) && typeof value.message === "string") return [value.message];
175
+ if (value === null || typeof value === "undefined") return [];
176
+ return [String(value)];
177
+ };
178
+ const getMessageRecord = (source) => {
179
+ if (!isRecord(source)) return;
180
+ if (typeof source.getMessageBag === "function") {
181
+ const bag = source.getMessageBag();
182
+ if (bag && bag !== source) {
183
+ const messages = getMessageRecord(bag);
184
+ if (messages) return messages;
185
+ }
186
+ }
187
+ for (const method of [
188
+ "getMessages",
189
+ "messagesRaw",
190
+ "toArray"
191
+ ]) if (typeof source[method] === "function") {
192
+ const messages = source[method]();
193
+ if (isRecord(messages)) return messages;
194
+ }
195
+ if (typeof source.errors === "function") {
196
+ const errors = source.errors();
197
+ const messages = getMessageRecord(errors) || (isRecord(errors) ? errors : void 0);
198
+ if (messages) return messages;
199
+ }
200
+ return getMessageRecord(source.errors) || (isRecord(source.errors) ? source.errors : void 0);
201
+ };
202
+ //#endregion
203
+ //#region src/packageViews.ts
204
+ const parsePackageViewName = (name) => {
205
+ if (!name.startsWith("~")) return null;
206
+ const source = name.slice(1);
207
+ const slashIndex = source.indexOf("/");
208
+ const dotIndex = slashIndex === -1 ? source.indexOf(".") : source.indexOf(".", slashIndex);
209
+ if (dotIndex <= 0) throw new Error(`Invalid package view name: ${name}`);
210
+ const packageName = source.slice(0, dotIndex);
211
+ const viewName = source.slice(dotIndex + 1);
212
+ if (!viewName) throw new Error(`Invalid package view name: ${name}`);
213
+ const nodePackageName = slashIndex === -1 ? packageName : `@${packageName}`;
214
+ const diskName = `package_${packageName.replace(/[^a-zA-Z0-9_-]/g, "_")}`;
215
+ return {
216
+ source: name,
217
+ packageName,
218
+ nodePackageName,
219
+ diskName,
220
+ viewName,
221
+ edgeName: `${diskName}::${viewName}`
222
+ };
223
+ };
224
+ const resolvePackageViewsPath = (nodePackageName, viewPath = "resources/views") => {
225
+ const viewsPath = resolve(resolve(Arkstack.rootDir(), "node_modules", nodePackageName), viewPath);
226
+ if (!existsSync(viewsPath)) throw new Error(`Package views directory not found: ${viewsPath}`);
227
+ return viewsPath;
228
+ };
229
+ //#endregion
230
+ //#region src/ViewInstance.ts
231
+ var ViewInstance = class {
232
+ name;
233
+ renderer;
234
+ runComposers;
235
+ runComposersSync;
236
+ renderName;
237
+ payload;
238
+ composersHaveRun = false;
239
+ constructor(name, data = {}, renderer, runComposers, runComposersSync, renderName = name) {
240
+ this.name = name;
241
+ this.renderer = renderer;
242
+ this.runComposers = runComposers;
243
+ this.runComposersSync = runComposersSync;
244
+ this.renderName = renderName;
245
+ this.payload = normalizeViewData({ ...data });
246
+ }
247
+ get data() {
248
+ return this.payload;
249
+ }
250
+ with(...data) {
251
+ mergeData(this.payload, data);
252
+ return this;
253
+ }
254
+ async render() {
255
+ await this.compose();
256
+ return await this.renderer.render(this.renderName, this.payload);
257
+ }
258
+ renderSync() {
259
+ this.composeSync();
260
+ return this.renderer.renderSync(this.renderName, this.payload);
261
+ }
262
+ then(onfulfilled, onrejected) {
263
+ return this.render().then(onfulfilled, onrejected);
264
+ }
265
+ async compose() {
266
+ if (this.composersHaveRun) return;
267
+ this.composersHaveRun = true;
268
+ await this.runComposers(this);
269
+ }
270
+ composeSync() {
271
+ if (this.composersHaveRun) return;
272
+ this.composersHaveRun = true;
273
+ this.runComposersSync(this);
274
+ }
275
+ };
276
+ //#endregion
277
+ //#region src/viewContext.ts
278
+ const store = new AsyncLocalStorage();
279
+ const normalizeContextData = (data = {}) => normalizeViewData(data);
280
+ const getViewData = () => store.getStore() || {};
281
+ const enterViewData = (data = {}) => {
282
+ store.enterWith(normalizeContextData({
283
+ ...getViewData(),
284
+ ...data
285
+ }));
286
+ };
287
+ const runWithViewData = async (data, callback) => {
288
+ return await store.run(normalizeContextData(data), callback);
289
+ };
290
+ const clearViewData = () => {
291
+ store.disable();
292
+ };
293
+ const collectViewData = (context) => {
294
+ const ctx = isRecord(context.ctx) ? context.ctx : context;
295
+ const response = isRecord(context.response) ? context.response : void 0;
296
+ const locals = {
297
+ ...isRecord(ctx.res?.locals) ? ctx.res.locals : {},
298
+ ...isRecord(ctx.response?.source?.locals) ? ctx.response.source.locals : {},
299
+ ...isRecord(response?.source?.locals) ? response.source.locals : {}
300
+ };
301
+ return normalizeContextData({
302
+ ..."session" in ctx ? { session: ctx.session } : {},
303
+ ..."httpSession" in ctx ? { httpSession: ctx.httpSession } : {},
304
+ ..."errors" in ctx ? { errors: ctx.errors } : {},
305
+ ...locals
306
+ });
307
+ };
308
+ //#endregion
309
+ //#region src/vite.ts
310
+ const scriptTag = (src) => `<script type="module" src="${src}"><\/script>`;
311
+ const styleTag = (href) => `<link rel="stylesheet" href="${href}">`;
312
+ const isCss = (entry) => /\.(css|scss|sass|less|styl|pcss)$/.test(entry);
313
+ const trimTrailingSlash = (value) => value.replace(/\/+$/, "");
314
+ const withTrailingSlash = (value) => `${trimTrailingSlash(value)}/`;
315
+ const devTags = (entries, devUrl) => {
316
+ const base = trimTrailingSlash(devUrl);
317
+ return [scriptTag(`${base}/@vite/client`), ...entries.map((entry) => scriptTag(`${base}/${entry.replace(/^\/+/, "")}`))].join("\n");
318
+ };
319
+ const productionTags = (entries, manifestPath, buildDir) => {
320
+ let manifest;
321
+ try {
322
+ manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
323
+ } catch {
324
+ throw new Error(`Vite manifest not found at ${manifestPath}. Run \`vite build\` before serving in production.`);
325
+ }
326
+ const base = withTrailingSlash(buildDir);
327
+ const tags = [];
328
+ for (const entry of entries) {
329
+ const chunk = manifest[entry];
330
+ if (!chunk) throw new Error(`Vite manifest (${manifestPath}) has no entry for "${entry}".`);
331
+ for (const css of chunk.css ?? []) tags.push(styleTag(`${base}${css}`));
332
+ tags.push(isCss(entry) ? styleTag(`${base}${chunk.file}`) : scriptTag(`${base}${chunk.file}`));
333
+ }
334
+ return tags.join("\n");
335
+ };
336
+ /**
337
+ * Resolve `<script>`/`<link>` tags for one or more Vite entries.
338
+ *
339
+ * In development (when `NODE_ENV` is not `production`, or `hot` is set) it points
340
+ * at the Vite dev server and includes the `@vite/client`. In production it reads
341
+ * the build manifest and emits the hashed asset tags (including any CSS a chunk
342
+ * imports). Backs the `@vite(...)` Edge tag.
343
+ *
344
+ * @param entries
345
+ * @param options
346
+ * @returns
347
+ */
348
+ const viteTags = (entries, options = {}) => {
349
+ const list = (Array.isArray(entries) ? entries : [entries]).filter(Boolean);
350
+ if (options.hot ?? nodeEnv() !== "prod") return devTags(list, options.devUrl ?? env("VITE_DEV_URL", "http://localhost:5173"));
351
+ return productionTags(list, options.manifest ?? join(Arkstack.rootDir(), "public", "build", ".vite", "manifest.json"), options.buildDir ?? "/build/");
352
+ };
353
+ /**
354
+ * Register the `@vite(...)` tag (and its backing global) on a view factory so
355
+ * templates can emit Vite asset tags: `@vite('resources/js/app.ts')` or
356
+ * `@vite(['resources/css/app.css', 'resources/js/app.ts'])`.
357
+ *
358
+ * @param factory
359
+ */
360
+ const registerViteTag = (factory) => {
361
+ factory.edge.global("__arkViteTags", (entries, options) => viteTags(entries, options));
362
+ factory.tag("vite", false, true, (parser, buffer, token) => {
363
+ const expression = `__arkViteTags(${token.properties.jsArg})`;
364
+ const ast = parser.utils.transformAst(parser.utils.generateAST(expression, token.loc, token.filename), token.filename, parser);
365
+ buffer.outputExpression(parser.utils.stringify(ast), token.filename, token.loc.start.line, false);
366
+ });
367
+ };
368
+ //#endregion
369
+ //#region src/ViewFactory.ts
370
+ var ViewFactory = class {
371
+ edge;
372
+ sharedData = {};
373
+ composers = /* @__PURE__ */ new Map();
374
+ mountedPackages = /* @__PURE__ */ new Set();
375
+ packageViewsPath;
376
+ constructor(options = {}) {
377
+ this.edge = options.edge ?? Edge.create({ cache: options.cache });
378
+ this.packageViewsPath = options.packageViewsPath ?? "resources/views";
379
+ this.mount(options.viewsPath ?? resolve(Arkstack.rootDir(), "src", "resources", "views"));
380
+ registerViteTag(this);
381
+ }
382
+ /**
383
+ * Create a new view instance for the given view name and data.
384
+ *
385
+ * @param name
386
+ * @param data
387
+ * @returns
388
+ */
389
+ make(name, data = {}) {
390
+ const edgeName = this.resolveName(name);
391
+ return new ViewInstance(name, normalizeViewData({
392
+ ...this.sharedData,
393
+ ...getViewData(),
394
+ ...data
395
+ }), this.edge, async (view) => await this.runComposers(name, view), (view) => this.runComposersSync(name, view), edgeName);
396
+ }
397
+ /**
398
+ * Render the first view that exists from the given list of names.
399
+ *
400
+ * @param names
401
+ * @param data
402
+ * @returns
403
+ */
404
+ first(names, data = {}) {
405
+ const name = names.find((candidate) => this.exists(candidate));
406
+ if (!name) throw new Error(`None of the given views exist: ${names.join(", ")}`);
407
+ return this.make(name, data);
408
+ }
409
+ /**
410
+ * Check if a view exists.
411
+ *
412
+ * @param name
413
+ * @returns
414
+ */
415
+ exists(name) {
416
+ const edgeName = this.resolveName(name);
417
+ if (this.edge.loader.templates[edgeName]) return true;
418
+ try {
419
+ return existsSync(this.edge.loader.makePath(edgeName));
420
+ } catch {
421
+ return false;
422
+ }
423
+ }
424
+ share(...data) {
425
+ mergeData(this.sharedData, data);
426
+ return this;
427
+ }
428
+ /**
429
+ * Register a view composer for the given view name(s).
430
+ * A view composer is a function or object that is called when a view is
431
+ * rendered, allowing you to modify the view's data or perform other actions.
432
+ *
433
+ * @param names
434
+ * @param composer
435
+ * @returns
436
+ */
437
+ composer(names, composer) {
438
+ for (const name of Array.isArray(names) ? names : [names]) this.composers.set(name, [...this.composers.get(name) ?? [], composer]);
439
+ return this;
440
+ }
441
+ mount(diskName, viewsDirectory) {
442
+ if (viewsDirectory === void 0) {
443
+ this.edge.mount(diskName);
444
+ return this;
445
+ }
446
+ this.edge.mount(diskName, viewsDirectory);
447
+ return this;
448
+ }
449
+ /**
450
+ * Register a raw template with the given name and contents.
451
+ *
452
+ * @param name
453
+ * @param contents
454
+ * @returns
455
+ */
456
+ raw(name, contents) {
457
+ this.edge.registerTemplate(name, { template: contents });
458
+ return this;
459
+ }
460
+ /**
461
+ * Register a custom tag with the given name, block type, seekable type,
462
+ * and compiler function.
463
+ *
464
+ * @param tagName
465
+ * @param block
466
+ * @param seekable
467
+ * @param compiler
468
+ */
469
+ tag(tagName, block, seekable, compiler) {
470
+ const tag = {
471
+ block,
472
+ seekable,
473
+ tagName,
474
+ compile: compiler
475
+ };
476
+ this.edge.registerTag(tag);
477
+ }
478
+ /**
479
+ * Flush all shared data. This will remove all data that has been shared with all views.
480
+ *
481
+ * @returns
482
+ */
483
+ flushShared() {
484
+ this.sharedData = {};
485
+ return this;
486
+ }
487
+ /**
488
+ * Flush all registered composers. This will remove all composers that have
489
+ * been registered for any view.
490
+ *
491
+ * @returns
492
+ */
493
+ flushComposers() {
494
+ this.composers.clear();
495
+ return this;
496
+ }
497
+ getComposers(name) {
498
+ const edgeName = this.resolveName(name);
499
+ return [
500
+ ...this.composers.get("*") ?? [],
501
+ ...this.composers.get(edgeName) ?? [],
502
+ ...this.composers.get(name) ?? []
503
+ ];
504
+ }
505
+ async runComposers(name, view) {
506
+ for (const composer of this.getComposers(name)) await runComposer(composer, view);
507
+ }
508
+ runComposersSync(name, view) {
509
+ for (const composer of this.getComposers(name)) runComposerSync(composer, view);
510
+ }
511
+ resolveName(name) {
512
+ const packageView = parsePackageViewName(name);
513
+ if (!packageView) return name;
514
+ if (!this.mountedPackages.has(packageView.diskName)) {
515
+ this.mount(packageView.diskName, resolvePackageViewsPath(packageView.nodePackageName, this.packageViewsPath));
516
+ this.mountedPackages.add(packageView.diskName);
517
+ }
518
+ return packageView.edgeName;
519
+ }
520
+ };
521
+ //#endregion
522
+ //#region src/View.ts
523
+ var View = class View {
524
+ static factory = new ViewFactory();
525
+ static usesDefaultFactoryRoot = true;
526
+ static {
527
+ Hook.set("set:root-dir", { after: () => {
528
+ if (View.usesDefaultFactoryRoot) View.factory = new ViewFactory();
529
+ } });
530
+ }
531
+ /**
532
+ * Bootstrap the view service
533
+ */
534
+ static boot() {
535
+ Object.defineProperty(globalThis, "view", {
536
+ value: (name, data = {}) => {
537
+ if (name === void 0) return View.factoryInstance();
538
+ return View.make(name, data);
539
+ },
540
+ configurable: true,
541
+ writable: true
542
+ });
543
+ }
544
+ static configure(options = {}) {
545
+ this.factory = new ViewFactory(options);
546
+ this.usesDefaultFactoryRoot = options.viewsPath === void 0 && options.edge === void 0;
547
+ return this.factory;
548
+ }
549
+ static factoryInstance() {
550
+ return this.factory;
551
+ }
552
+ static make(name, data = {}) {
553
+ return this.factory.make(name, data);
554
+ }
555
+ static first(names, data = {}) {
556
+ return this.factory.first(names, data);
557
+ }
558
+ static exists(name) {
559
+ return this.factory.exists(name);
560
+ }
561
+ static share(...data) {
562
+ if (typeof data[0] === "string") this.factory.share(data[0], data[1]);
563
+ else this.factory.share(data[0] ?? {});
564
+ return this;
565
+ }
566
+ static composer(names, composer) {
567
+ this.factory.composer(names, composer);
568
+ return this;
569
+ }
570
+ static mount(diskName, viewsDirectory) {
571
+ if (viewsDirectory === void 0) this.factory.mount(diskName);
572
+ else this.factory.mount(diskName, viewsDirectory);
573
+ return this;
574
+ }
575
+ static raw(name, contents) {
576
+ this.factory.raw(name, contents);
577
+ return this;
578
+ }
579
+ };
580
+ //#endregion
581
+ //#region src/plugins.ts
582
+ const clearRouterViewPlugin = definePlugin({
583
+ name: "arkstack-view",
584
+ setup: ({ useHttpContext }) => {
585
+ useHttpContext((context) => {
586
+ enterViewData(collectViewData(context));
587
+ });
588
+ }
589
+ });
590
+ //#endregion
591
+ export { viteTags as a, enterViewData as c, ViewInstance as d, parsePackageViewName as f, normalizeViewErrors as g, ViewErrorBag as h, registerViteTag as i, getViewData as l, view as m, View as n, clearViewData as o, resolvePackageViewsPath as p, ViewFactory as r, collectViewData as s, clearRouterViewPlugin as t, runWithViewData as u };
@@ -0,0 +1,6 @@
1
+ import * as _$clear_router_core0 from "clear-router/core";
2
+
3
+ //#region src/plugins.d.ts
4
+ declare const clearRouterViewPlugin: _$clear_router_core0.ClearRouterPlugin<any, any>;
5
+ //#endregion
6
+ export { clearRouterViewPlugin as t };
@@ -0,0 +1,2 @@
1
+ import { t as clearRouterViewPlugin } from "./plugins-D3vyoc8i.js";
2
+ export { clearRouterViewPlugin };
package/dist/setup.js ADDED
@@ -0,0 +1,6 @@
1
+ import { t as clearRouterViewPlugin } from "./plugins-BuRvAzjs.js";
2
+ import { CoreRouter } from "clear-router/core";
3
+ //#region src/setup.ts
4
+ CoreRouter.use(clearRouterViewPlugin);
5
+ //#endregion
6
+ export { clearRouterViewPlugin };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arkstack/view",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "type": "module",
5
5
  "description": "View module for Arkstack, providing template rendering and view integration utilities.",
6
6
  "homepage": "https://arkstack.toneflix.net/guide/views",
@@ -16,6 +16,7 @@
16
16
  "exports": {
17
17
  ".": "./dist/index.js",
18
18
  "./MakeViewCommand": "./dist/commands/MakeViewCommand.js",
19
+ "./setup": "./dist/setup.js",
19
20
  "./package.json": "./package.json"
20
21
  },
21
22
  "keywords": [
@@ -31,15 +32,21 @@
31
32
  "resources"
32
33
  ],
33
34
  "dependencies": {
34
- "edge.js": "^6.5.0"
35
+ "edge.js": "^6.5.0",
36
+ "@arkstack/contract": "^0.5.3"
35
37
  },
36
38
  "peerDependencies": {
37
- "@h3ravel/musket": "^0.10.1",
38
- "@arkstack/common": "^0.5.1"
39
+ "@h3ravel/musket": "^2.2.1",
40
+ "clear-router": "^2.9.0",
41
+ "@arkstack/common": "^0.5.3",
42
+ "@arkstack/foundry": "^0.5.3"
39
43
  },
40
44
  "peerDependenciesMeta": {
41
45
  "@h3ravel/musket": {
42
46
  "optional": true
47
+ },
48
+ "clear-router": {
49
+ "optional": true
43
50
  }
44
51
  },
45
52
  "scripts": {
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","names":["Edge"],"sources":["../src/helpers.ts","../src/packageViews.ts","../src/ViewInstance.ts","../src/ViewFactory.ts","../src/View.ts"],"sourcesContent":["import type { ViewComposer, ViewComposerObject, ViewData, ViewName } from './types'\n\nimport { View } from './View'\nimport { ViewFactory } from './ViewFactory'\nimport { ViewInstance } from './ViewInstance'\n\nexport function view (): ViewFactory\nexport function view (name: ViewName, data?: ViewData): ViewInstance\nexport function view (name?: ViewName, data: ViewData = {}) {\n if (name === undefined) {\n return View.factoryInstance()\n }\n\n return View.make(name, data)\n}\n\nexport const isClass = <T = unknown> (\n target: unknown\n): target is new (...args: any[]) => T => {\n return typeof target === 'function'\n && /^class\\s/.test(Function.prototype.toString.call(target))\n}\n\nexport const mergeData = (target: ViewData, data: any[]) => {\n if (data.length === 0) {\n return target\n }\n\n if (typeof data[0] === 'string') {\n target[data[0]] = data[1]\n\n return target\n }\n\n for (const value of data) {\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n Object.assign(target, value)\n }\n }\n\n return target\n}\n\nexport const runComposerSync = (composer: ViewComposer, view: ViewInstance) => {\n const result = runComposer(composer, view)\n\n if (result && typeof result.then === 'function') {\n throw new Error('Async view composers cannot be used with renderSync.')\n }\n}\n\nexport const runComposer = (composer: ViewComposer, view: ViewInstance) => {\n if (typeof composer === 'function') {\n if (isClass<ViewComposerObject>(composer)) {\n return new composer().compose(view)\n }\n\n return composer(view)\n }\n\n return composer.compose(view)\n}\n","import { existsSync } from 'node:fs'\nimport { resolve } from 'node:path'\n\nexport type PackageViewReference = {\n source: string\n packageName: string\n nodePackageName: string\n diskName: string\n viewName: string\n edgeName: string\n}\n\nexport const parsePackageViewName = (name: string): PackageViewReference | null => {\n if (!name.startsWith('~')) {\n return null\n }\n\n const source = name.slice(1)\n const slashIndex = source.indexOf('/')\n const dotIndex = slashIndex === -1\n ? source.indexOf('.')\n : source.indexOf('.', slashIndex)\n\n if (dotIndex <= 0) {\n throw new Error(`Invalid package view name: ${name}`)\n }\n\n const packageName = source.slice(0, dotIndex)\n const viewName = source.slice(dotIndex + 1)\n\n if (!viewName) {\n throw new Error(`Invalid package view name: ${name}`)\n }\n\n const nodePackageName = slashIndex === -1\n ? packageName\n : `@${packageName}`\n const diskName = `package_${packageName.replace(/[^a-zA-Z0-9_-]/g, '_')}`\n\n return {\n source: name,\n packageName,\n nodePackageName,\n diskName,\n viewName,\n edgeName: `${diskName}::${viewName}`,\n }\n}\n\nexport const resolvePackageViewsPath = (\n nodePackageName: string,\n viewPath = 'resources/views',\n) => {\n const packageRoot = resolve(process.cwd(), 'node_modules', nodePackageName)\n const viewsPath = resolve(packageRoot, viewPath)\n\n if (!existsSync(viewsPath)) {\n throw new Error(`Package views directory not found: ${viewsPath}`)\n }\n\n return viewsPath\n}\n","import type { ComposerRunner, SyncComposerRunner, ViewData } from './types'\n\nimport { mergeData } from './helpers'\n\nexport class ViewInstance implements PromiseLike<string> {\n private payload: ViewData\n private composersHaveRun = false\n\n constructor(\n readonly name: string,\n data: ViewData = {},\n private renderer: {\n render: (name: string, data?: ViewData) => Promise<string>\n renderSync: (name: string, data?: ViewData) => string\n },\n private runComposers: ComposerRunner,\n private runComposersSync: SyncComposerRunner,\n private renderName = name,\n ) {\n this.payload = { ...data }\n }\n\n get data () {\n return this.payload\n }\n\n with (key: string, value: any): this\n with (data: ViewData): this\n with (...data: any[]): this {\n mergeData(this.payload, data)\n\n return this\n }\n\n async render () {\n await this.compose()\n\n return await this.renderer.render(this.renderName, this.payload)\n }\n\n renderSync () {\n this.composeSync()\n\n return this.renderer.renderSync(this.renderName, this.payload)\n }\n\n then<TResult1 = string, TResult2 = never> (\n onfulfilled?: ((value: string) => TResult1 | PromiseLike<TResult1>) | null,\n onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null,\n ): PromiseLike<TResult1 | TResult2> {\n return this.render().then(onfulfilled, onrejected)\n }\n\n private async compose () {\n if (this.composersHaveRun) {\n return\n }\n\n this.composersHaveRun = true\n await this.runComposers(this)\n }\n\n private composeSync () {\n if (this.composersHaveRun) {\n return\n }\n\n this.composersHaveRun = true\n this.runComposersSync(this)\n }\n}\n","import type { ViewComposer, ViewComposerName, ViewData, ViewFactoryOptions, ViewName } from './types'\nimport { mergeData, runComposer, runComposerSync } from './helpers'\nimport { parsePackageViewName, resolvePackageViewsPath } from './packageViews'\n\nimport { Edge } from 'edge.js'\nimport { ViewInstance } from './ViewInstance'\nimport { existsSync } from 'node:fs'\nimport { resolve } from 'node:path'\n\nexport class ViewFactory {\n readonly edge: Edge\n private sharedData: ViewData = {}\n private composers = new Map<ViewName, ViewComposer[]>()\n private mountedPackages = new Set<string>()\n private packageViewsPath: string\n\n constructor(options: ViewFactoryOptions = {}) {\n this.edge = options.edge ?? Edge.create({ cache: options.cache })\n this.packageViewsPath = options.packageViewsPath ?? 'resources/views'\n this.mount(options.viewsPath ?? resolve(process.cwd(), 'src', 'resources', 'views'))\n }\n\n make (name: ViewName, data: ViewData = {}) {\n const edgeName = this.resolveName(name)\n\n return new ViewInstance(\n name,\n { ...this.sharedData, ...data },\n this.edge,\n async view => await this.runComposers(name, view),\n view => this.runComposersSync(name, view),\n edgeName,\n )\n }\n\n first (names: ViewName[], data: ViewData = {}) {\n const name = names.find(candidate => this.exists(candidate))\n\n if (!name) {\n throw new Error(`None of the given views exist: ${names.join(', ')}`)\n }\n\n return this.make(name, data)\n }\n\n exists (name: ViewName) {\n const edgeName = this.resolveName(name)\n\n if (this.edge.loader.templates[edgeName]) {\n return true\n }\n\n try {\n return existsSync(this.edge.loader.makePath(edgeName))\n } catch {\n return false\n }\n }\n\n share (key: string, value: any): this\n share (data: ViewData): this\n share (...data: any[]): this {\n mergeData(this.sharedData, data)\n\n return this\n }\n\n composer (names: ViewComposerName, composer: ViewComposer): this {\n for (const name of Array.isArray(names) ? names : [names]) {\n this.composers.set(name, [\n ...(this.composers.get(name) ?? []),\n composer,\n ])\n }\n\n return this\n }\n\n mount (viewsDirectory: string | URL): this\n mount (diskName: string, viewsDirectory: string | URL): this\n mount (diskName: string | URL, viewsDirectory?: string | URL): this {\n if (viewsDirectory === undefined) {\n this.edge.mount(diskName)\n\n return this\n }\n\n this.edge.mount(diskName as string, viewsDirectory)\n\n return this\n }\n\n raw (name: ViewName, contents: string): this {\n this.edge.registerTemplate(name, { template: contents })\n\n return this\n }\n\n flushShared () {\n this.sharedData = {}\n\n return this\n }\n\n flushComposers () {\n this.composers.clear()\n\n return this\n }\n\n private getComposers (name: ViewName) {\n const edgeName = this.resolveName(name)\n\n return [\n ...(this.composers.get('*') ?? []),\n ...(this.composers.get(edgeName) ?? []),\n ...(this.composers.get(name) ?? []),\n ]\n }\n\n private async runComposers (name: ViewName, view: ViewInstance) {\n for (const composer of this.getComposers(name)) {\n await runComposer(composer, view)\n }\n }\n\n private runComposersSync (name: ViewName, view: ViewInstance) {\n for (const composer of this.getComposers(name)) {\n runComposerSync(composer, view)\n }\n }\n\n private resolveName (name: ViewName) {\n const packageView = parsePackageViewName(name)\n\n if (!packageView) {\n return name\n }\n\n if (!this.mountedPackages.has(packageView.diskName)) {\n this.mount(\n packageView.diskName,\n resolvePackageViewsPath(packageView.nodePackageName, this.packageViewsPath),\n )\n this.mountedPackages.add(packageView.diskName)\n }\n\n return packageView.edgeName\n }\n}\n","import type { ViewComposer, ViewComposerName, ViewData, ViewFactoryOptions, ViewName } from './types'\n\nimport { ViewFactory } from './ViewFactory'\nimport type { ViewInstance } from './ViewInstance'\n\nexport class View {\n private static factory = new ViewFactory()\n\n /**\n * Bootstrap the view service\n */\n static boot () {\n Object.defineProperty(globalThis, 'view', {\n value: (name?: ViewName, data: ViewData = {}) => {\n if (name === undefined) {\n return View.factoryInstance()\n }\n\n return View.make(name, data)\n },\n configurable: true,\n writable: true,\n })\n }\n\n static configure (options: ViewFactoryOptions = {}) {\n this.factory = new ViewFactory(options)\n\n return this.factory\n }\n\n static factoryInstance () {\n return this.factory\n }\n\n static make (name: ViewName, data: ViewData = {}): ViewInstance {\n return this.factory.make(name, data)\n }\n\n static first (names: ViewName[], data: ViewData = {}): ViewInstance {\n return this.factory.first(names, data)\n }\n\n static exists (name: ViewName) {\n return this.factory.exists(name)\n }\n\n static share (key: string, value: any): typeof View\n static share (data: ViewData): typeof View\n static share (...data: any[]) {\n if (typeof data[0] === 'string') {\n this.factory.share(data[0], data[1])\n } else {\n this.factory.share(data[0] ?? {})\n }\n\n return this\n }\n\n static composer (names: ViewComposerName, composer: ViewComposer): typeof View {\n this.factory.composer(names, composer)\n\n return this\n }\n\n static mount (viewsDirectory: string | URL): typeof View\n static mount (diskName: string, viewsDirectory: string | URL): typeof View\n static mount (diskName: string | URL, viewsDirectory?: string | URL) {\n if (viewsDirectory === undefined) {\n this.factory.mount(diskName)\n } else {\n this.factory.mount(diskName as string, viewsDirectory)\n }\n\n return this\n }\n\n static raw (name: ViewName, contents: string) {\n this.factory.raw(name, contents)\n\n return this\n }\n}\n"],"mappings":"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"}