@heshammesalam/heshjs 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +19 -4
  2. package/cli.js +26 -0
  3. package/dist/architecture/diagnostics.d.ts +12 -0
  4. package/dist/architecture/diagnostics.js +8 -0
  5. package/dist/architecture/diagnostics.js.map +1 -0
  6. package/dist/architecture/html.d.ts +16 -0
  7. package/dist/architecture/html.js +132 -0
  8. package/dist/architecture/html.js.map +1 -0
  9. package/dist/architecture/index.d.ts +42 -0
  10. package/dist/architecture/index.js +418 -0
  11. package/dist/architecture/index.js.map +1 -0
  12. package/dist/compiler/index.js +2 -0
  13. package/dist/compiler/index.js.map +1 -1
  14. package/dist/destinations/index.d.ts +63 -0
  15. package/dist/destinations/index.js +185 -0
  16. package/dist/destinations/index.js.map +1 -0
  17. package/dist/dom/index.d.ts +2 -0
  18. package/dist/dom/index.js +5 -1
  19. package/dist/dom/index.js.map +1 -1
  20. package/dist/domains/index.d.ts +24 -0
  21. package/dist/domains/index.js +110 -0
  22. package/dist/domains/index.js.map +1 -0
  23. package/dist/hmvc/index.d.ts +35 -0
  24. package/dist/hmvc/index.js +49 -0
  25. package/dist/hmvc/index.js.map +1 -0
  26. package/dist/parser/index.d.ts +15 -1
  27. package/dist/parser/index.js +73 -3
  28. package/dist/parser/index.js.map +1 -1
  29. package/dist/presentation/index.d.ts +9 -0
  30. package/dist/presentation/index.js +38 -0
  31. package/dist/presentation/index.js.map +1 -0
  32. package/dist/router/index.d.ts +24 -0
  33. package/dist/router/index.js +258 -0
  34. package/dist/router/index.js.map +1 -0
  35. package/dist/vite/index.d.ts +4 -1
  36. package/dist/vite/index.js +37 -2
  37. package/dist/vite/index.js.map +1 -1
  38. package/docs/contracts.md +60 -0
  39. package/docs/domain-graph.md +205 -0
  40. package/docs/grammar.md +4 -0
  41. package/docs/hmvc.md +134 -0
  42. package/package.json +10 -3
  43. package/src/architecture/diagnostics.ts +11 -0
  44. package/src/architecture/html.ts +88 -0
  45. package/src/architecture/index.ts +283 -0
  46. package/src/compiler/index.ts +1 -0
  47. package/src/destinations/index.ts +146 -0
  48. package/src/dom/index.ts +6 -1
  49. package/src/domains/index.ts +97 -0
  50. package/src/hmvc/index.ts +71 -0
  51. package/src/parser/index.ts +55 -4
  52. package/src/presentation/index.ts +31 -0
  53. package/src/router/index.ts +171 -0
  54. package/src/vite/index.ts +25 -2
@@ -0,0 +1,258 @@
1
+ import { signal, batch } from '../reactivity/index.js';
2
+ function segments(path) { return path.split('/').filter(Boolean); }
3
+ function validate(routes, ancestors = new Set()) {
4
+ const siblings = new Set();
5
+ for (const route of routes) {
6
+ if (/[?#]/.test(route.path))
7
+ throw new Error(`Route paths cannot contain query/hash: ${route.path}`);
8
+ const parts = segments(route.path);
9
+ const signature = parts.map(p => p.startsWith(':') ? ':' : p).join('/');
10
+ if (siblings.has(signature))
11
+ throw new Error(`Duplicate sibling route: ${route.path}`);
12
+ siblings.add(signature);
13
+ const names = new Set(ancestors);
14
+ parts.forEach((p, i) => {
15
+ if (p === '*' && (i !== parts.length - 1 || route.children?.length))
16
+ throw new Error('Wildcard must end a leaf route');
17
+ if (p.startsWith(':')) {
18
+ const name = p.slice(1);
19
+ if (!/^[a-zA-Z_][a-zA-Z\d_]*$/.test(name) || names.has(name) || ['__proto__', 'prototype', 'constructor'].includes(name))
20
+ throw new Error(`Invalid or duplicate route param: ${name}`);
21
+ names.add(name);
22
+ }
23
+ });
24
+ validate(route.children ?? [], names);
25
+ }
26
+ }
27
+ function find(routes, parts, at = 0, params = {}) {
28
+ const ordered = [...routes].sort((a, b) => {
29
+ const aa = segments(a.path), bb = segments(b.path);
30
+ for (let i = 0; i < Math.max(aa.length, bb.length); i++) {
31
+ const rank = (p) => p === '*' ? 0 : p?.startsWith(':') ? 1 : p ? 2 : 0.5;
32
+ const diff = rank(bb[i]) - rank(aa[i]);
33
+ if (diff)
34
+ return diff;
35
+ }
36
+ return 0;
37
+ });
38
+ for (const record of ordered) {
39
+ const own = { ...params };
40
+ let cursor = at, matched = true;
41
+ for (const part of segments(record.path)) {
42
+ if (part === '*') {
43
+ own['wildcard'] = parts.slice(cursor).join('/');
44
+ cursor = parts.length;
45
+ break;
46
+ }
47
+ const actual = parts[cursor++];
48
+ if (actual === undefined) {
49
+ matched = false;
50
+ break;
51
+ }
52
+ if (part.startsWith(':'))
53
+ own[part.slice(1)] = actual;
54
+ else if (part !== actual) {
55
+ matched = false;
56
+ break;
57
+ }
58
+ }
59
+ if (!matched)
60
+ continue;
61
+ const entry = { record, params: own };
62
+ const children = find(record.children ?? [], parts, cursor, own);
63
+ if (children)
64
+ return [entry, ...children];
65
+ if (cursor === parts.length)
66
+ return [entry];
67
+ }
68
+ return undefined;
69
+ }
70
+ export function createRouter(options) {
71
+ validate(options.routes);
72
+ const browser = options.window ?? (typeof window !== 'undefined' ? window : undefined);
73
+ if (!browser)
74
+ throw new Error('createRouter requires a browser Window');
75
+ const report = options.onError ?? ((error) => console.error(error));
76
+ const location = signal(makeLocation(new URL(browser.location.href), {}));
77
+ let root, active = [], fallback;
78
+ let transitioning = false;
79
+ let generation = 0;
80
+ const notFoundRecord = options.notFound ? { path: '*', module: options.notFound } : undefined;
81
+ function makeLocation(url, params) {
82
+ return Object.freeze({ url: new URL(url), pathname: url.pathname, params: Object.freeze({ ...params }), query: new URLSearchParams(url.search), hash: url.hash });
83
+ }
84
+ function render(url) {
85
+ if (!root)
86
+ throw new Error('Start the router before navigating');
87
+ if (transitioning)
88
+ throw new Error('Navigation during module mounting is not supported');
89
+ transitioning = true;
90
+ try {
91
+ let entries;
92
+ try {
93
+ entries = find(options.routes, segments(url.pathname).map(decodeURIComponent));
94
+ }
95
+ catch (error) {
96
+ if (!(error instanceof URIError))
97
+ throw error;
98
+ }
99
+ if (!entries && notFoundRecord)
100
+ entries = [{ record: notFoundRecord, params: {} }];
101
+ entries ??= [];
102
+ let keep = 0;
103
+ while (keep < active.length && keep < entries.length && active[keep].record === entries[keep].record && JSON.stringify(active[keep].params) === JSON.stringify(entries[keep].params))
104
+ keep++;
105
+ const removed = active.splice(keep);
106
+ const errors = [];
107
+ for (const item of removed.reverse()) {
108
+ try {
109
+ item.instance.dispose();
110
+ }
111
+ catch (error) {
112
+ errors.push(error);
113
+ }
114
+ }
115
+ fallback?.remove();
116
+ fallback = undefined;
117
+ if (errors.length)
118
+ throw new AggregateError(errors, 'Route cleanup failed');
119
+ const added = [];
120
+ try {
121
+ for (let i = keep; i < entries.length; i++) {
122
+ const entry = entries[i];
123
+ const parent = active.at(-1);
124
+ const outlet = parent ? parent.instance.outlet : root;
125
+ if (!outlet)
126
+ throw new Error(`Parent module ${parent.record.path} must render Outlet()`);
127
+ const instance = entry.record.module.attach(outlet, makeLocation(url, entry.params), parent?.instance.context, { onError: report });
128
+ const item = { ...entry, instance };
129
+ active.push(item);
130
+ added.push(item);
131
+ }
132
+ }
133
+ catch (error) {
134
+ active.splice(keep);
135
+ for (const item of added.reverse()) {
136
+ try {
137
+ item.instance.dispose();
138
+ }
139
+ catch (cleanupError) {
140
+ report(cleanupError);
141
+ }
142
+ }
143
+ throw error;
144
+ }
145
+ if (!entries.length) {
146
+ fallback = root.ownerDocument.createElement('p');
147
+ fallback.setAttribute('role', 'status');
148
+ fallback.textContent = 'Page not found';
149
+ root.appendChild(fallback);
150
+ }
151
+ batch(() => {
152
+ for (const item of active)
153
+ item.instance.update(makeLocation(url, item.params));
154
+ location.set(makeLocation(url, entries.at(-1)?.params ?? {}));
155
+ });
156
+ }
157
+ finally {
158
+ transitioning = false;
159
+ }
160
+ }
161
+ function navigate(to, navigation = {}) {
162
+ if (!root)
163
+ throw new Error('Start the router before navigating');
164
+ const url = new URL(to, browser.location.href);
165
+ if (!['http:', 'https:'].includes(url.protocol) || url.origin !== browser.location.origin)
166
+ throw new Error('Router navigation requires a same-origin HTTP(S) URL');
167
+ if (url.href === browser.location.href)
168
+ return;
169
+ render(url);
170
+ browser.history[navigation.replace ? 'replaceState' : 'pushState'](null, '', url.href);
171
+ }
172
+ const popstate = () => {
173
+ try {
174
+ if (location.get().url.href !== browser.location.href)
175
+ render(new URL(browser.location.href));
176
+ }
177
+ catch (error) {
178
+ report(error);
179
+ }
180
+ };
181
+ const click = (event) => {
182
+ if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.altKey || event.shiftKey)
183
+ return;
184
+ const target = event.target;
185
+ const anchor = target?.closest?.('a[href]');
186
+ if (!anchor || anchor.hasAttribute('download') || anchor.hasAttribute('data-hesh-reload') || (anchor.getAttribute('target') && anchor.getAttribute('target').toLowerCase() !== '_self'))
187
+ return;
188
+ const href = anchor.getAttribute('href');
189
+ if (href.startsWith('#'))
190
+ return;
191
+ let url;
192
+ try {
193
+ url = new URL(href, browser.document.baseURI);
194
+ }
195
+ catch {
196
+ return;
197
+ }
198
+ if (!['http:', 'https:'].includes(url.protocol) || url.origin !== browser.location.origin)
199
+ return;
200
+ if (url.pathname === browser.location.pathname && url.search === browser.location.search && url.hash)
201
+ return;
202
+ event.preventDefault();
203
+ try {
204
+ navigate(url.href);
205
+ }
206
+ catch (error) {
207
+ report(error);
208
+ }
209
+ };
210
+ function stop() {
211
+ if (transitioning)
212
+ throw new Error('Cannot stop the router during module mounting');
213
+ browser.document.removeEventListener('click', click);
214
+ browser.removeEventListener('popstate', popstate);
215
+ browser.removeEventListener('hashchange', popstate);
216
+ const old = active;
217
+ active = [];
218
+ root = undefined;
219
+ generation++;
220
+ fallback?.remove();
221
+ fallback = undefined;
222
+ const errors = [];
223
+ for (const item of old.reverse()) {
224
+ try {
225
+ item.instance.dispose();
226
+ }
227
+ catch (error) {
228
+ errors.push(error);
229
+ }
230
+ }
231
+ if (errors.length)
232
+ throw new AggregateError(errors, 'Router cleanup failed');
233
+ }
234
+ return {
235
+ location: { get: location.get }, navigate, stop,
236
+ start(parent) {
237
+ if (root)
238
+ throw new Error('Router is already started');
239
+ if (parent.ownerDocument !== browser.document)
240
+ throw new Error('Router root must belong to its Window');
241
+ const started = ++generation;
242
+ root = parent;
243
+ try {
244
+ render(new URL(browser.location.href));
245
+ }
246
+ catch (error) {
247
+ stop();
248
+ throw error;
249
+ }
250
+ browser.document.addEventListener('click', click);
251
+ browser.addEventListener('popstate', popstate);
252
+ browser.addEventListener('hashchange', popstate);
253
+ return () => { if (generation === started)
254
+ stop(); };
255
+ }
256
+ };
257
+ }
258
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/router/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { signal, batch, type Disposer } from '../reactivity/index.js';\nimport type { AppModule, MountedModule, RouteLocation } from '../hmvc/index.js';\nexport type { RouteLocation } from '../hmvc/index.js';\nexport interface RouteRecord { path: string; module: AppModule; children?: readonly RouteRecord[] }\nexport interface RouterOptions {\n routes: readonly RouteRecord[];\n window?: Window;\n notFound?: AppModule;\n onError?: (error: unknown) => void;\n}\ninterface Entry { record: RouteRecord; params: Record<string, string> }\ninterface Active extends Entry { instance: MountedModule }\n\nfunction segments(path: string): string[] { return path.split('/').filter(Boolean); }\nfunction validate(routes: readonly RouteRecord[], ancestors = new Set<string>()): void {\n const siblings = new Set<string>();\n for (const route of routes) {\n if (/[?#]/.test(route.path)) throw new Error(`Route paths cannot contain query/hash: ${route.path}`);\n const parts = segments(route.path);\n const signature = parts.map(p => p.startsWith(':') ? ':' : p).join('/');\n if (siblings.has(signature)) throw new Error(`Duplicate sibling route: ${route.path}`);\n siblings.add(signature);\n const names = new Set(ancestors);\n parts.forEach((p, i) => {\n if (p === '*' && (i !== parts.length - 1 || route.children?.length)) throw new Error('Wildcard must end a leaf route');\n if (p.startsWith(':')) {\n const name = p.slice(1);\n if (!/^[a-zA-Z_][a-zA-Z\\d_]*$/.test(name) || names.has(name) || ['__proto__', 'prototype', 'constructor'].includes(name)) throw new Error(`Invalid or duplicate route param: ${name}`);\n names.add(name);\n }\n });\n validate(route.children ?? [], names);\n }\n}\nfunction find(routes: readonly RouteRecord[], parts: string[], at = 0, params: Record<string, string> = {}): Entry[] | undefined {\n const ordered = [...routes].sort((a, b) => {\n const aa = segments(a.path), bb = segments(b.path);\n for (let i = 0; i < Math.max(aa.length, bb.length); i++) {\n const rank = (p?: string) => p === '*' ? 0 : p?.startsWith(':') ? 1 : p ? 2 : 0.5;\n const diff = rank(bb[i]) - rank(aa[i]); if (diff) return diff;\n }\n return 0;\n });\n for (const record of ordered) {\n const own = { ...params }; let cursor = at, matched = true;\n for (const part of segments(record.path)) {\n if (part === '*') { own['wildcard'] = parts.slice(cursor).join('/'); cursor = parts.length; break; }\n const actual = parts[cursor++];\n if (actual === undefined) { matched = false; break; }\n if (part.startsWith(':')) own[part.slice(1)] = actual;\n else if (part !== actual) { matched = false; break; }\n }\n if (!matched) continue;\n const entry = { record, params: own };\n const children = find(record.children ?? [], parts, cursor, own);\n if (children) return [entry, ...children];\n if (cursor === parts.length) return [entry];\n }\n return undefined;\n}\n\nexport function createRouter(options: RouterOptions) {\n validate(options.routes);\n const browser = options.window ?? (typeof window !== 'undefined' ? window : undefined);\n if (!browser) throw new Error('createRouter requires a browser Window');\n const report = options.onError ?? ((error: unknown) => console.error(error));\n const location = signal<RouteLocation>(makeLocation(new URL(browser.location.href), {}));\n let root: Node | undefined, active: Active[] = [], fallback: HTMLElement | undefined;\n let transitioning = false;\n let generation = 0;\n const notFoundRecord: RouteRecord | undefined = options.notFound ? { path: '*', module: options.notFound } : undefined;\n function makeLocation(url: URL, params: Record<string, string>): RouteLocation {\n return Object.freeze({ url: new URL(url), pathname: url.pathname, params: Object.freeze({ ...params }), query: new URLSearchParams(url.search), hash: url.hash });\n }\n function render(url: URL): void {\n if (!root) throw new Error('Start the router before navigating');\n if (transitioning) throw new Error('Navigation during module mounting is not supported');\n transitioning = true;\n try {\n let entries: Entry[] | undefined;\n try { entries = find(options.routes, segments(url.pathname).map(decodeURIComponent)); }\n catch (error) { if (!(error instanceof URIError)) throw error; }\n if (!entries && notFoundRecord) entries = [{ record: notFoundRecord, params: {} }];\n entries ??= [];\n let keep = 0;\n while (keep < active.length && keep < entries.length && active[keep]!.record === entries[keep]!.record && JSON.stringify(active[keep]!.params) === JSON.stringify(entries[keep]!.params)) keep++;\n const removed = active.splice(keep);\n const errors: unknown[] = [];\n for (const item of removed.reverse()) { try { item.instance.dispose(); } catch (error) { errors.push(error); } }\n fallback?.remove(); fallback = undefined;\n if (errors.length) throw new AggregateError(errors, 'Route cleanup failed');\n const added: Active[] = [];\n try {\n for (let i = keep; i < entries.length; i++) {\n const entry = entries[i]!;\n const parent = active.at(-1);\n const outlet = parent ? parent.instance.outlet : root;\n if (!outlet) throw new Error(`Parent module ${parent!.record.path} must render Outlet()`);\n const instance = entry.record.module.attach(outlet, makeLocation(url, entry.params), parent?.instance.context, { onError: report });\n const item = { ...entry, instance };\n active.push(item); added.push(item);\n }\n } catch (error) {\n active.splice(keep);\n for (const item of added.reverse()) { try { item.instance.dispose(); } catch (cleanupError) { report(cleanupError); } }\n throw error;\n }\n if (!entries.length) {\n fallback = root.ownerDocument!.createElement('p');\n fallback.setAttribute('role', 'status'); fallback.textContent = 'Page not found'; root.appendChild(fallback);\n }\n batch(() => {\n for (const item of active) item.instance.update(makeLocation(url, item.params));\n location.set(makeLocation(url, entries.at(-1)?.params ?? {}));\n });\n } finally { transitioning = false; }\n }\n function navigate(to: string, navigation: { replace?: boolean } = {}): void {\n if (!root) throw new Error('Start the router before navigating');\n const url = new URL(to, browser!.location.href);\n if (!['http:', 'https:'].includes(url.protocol) || url.origin !== browser!.location.origin) throw new Error('Router navigation requires a same-origin HTTP(S) URL');\n if (url.href === browser!.location.href) return;\n render(url);\n browser!.history[navigation.replace ? 'replaceState' : 'pushState'](null, '', url.href);\n }\n const popstate = () => {\n try { if (location.get().url.href !== browser!.location.href) render(new URL(browser!.location.href)); }\n catch (error) { report(error); }\n };\n const click = (event: MouseEvent) => {\n if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.altKey || event.shiftKey) return;\n const target = event.target as Element | null;\n const anchor = target?.closest?.('a[href]');\n if (!anchor || anchor.hasAttribute('download') || anchor.hasAttribute('data-hesh-reload') || (anchor.getAttribute('target') && anchor.getAttribute('target')!.toLowerCase() !== '_self')) return;\n const href = anchor.getAttribute('href')!;\n if (href.startsWith('#')) return;\n let url: URL;\n try { url = new URL(href, browser!.document.baseURI); } catch { return; }\n if (!['http:', 'https:'].includes(url.protocol) || url.origin !== browser!.location.origin) return;\n if (url.pathname === browser!.location.pathname && url.search === browser!.location.search && url.hash) return;\n event.preventDefault();\n try { navigate(url.href); } catch (error) { report(error); }\n };\n function stop(): void {\n if (transitioning) throw new Error('Cannot stop the router during module mounting');\n browser!.document.removeEventListener('click', click);\n browser!.removeEventListener('popstate', popstate);\n browser!.removeEventListener('hashchange', popstate);\n const old = active; active = []; root = undefined;\n generation++;\n fallback?.remove(); fallback = undefined;\n const errors: unknown[] = [];\n for (const item of old.reverse()) { try { item.instance.dispose(); } catch (error) { errors.push(error); } }\n if (errors.length) throw new AggregateError(errors, 'Router cleanup failed');\n }\n return {\n location: { get: location.get }, navigate, stop,\n start(parent: Node): Disposer {\n if (root) throw new Error('Router is already started');\n if (parent.ownerDocument !== browser!.document) throw new Error('Router root must belong to its Window');\n const started = ++generation;\n root = parent;\n try { render(new URL(browser!.location.href)); }\n catch (error) { stop(); throw error; }\n browser!.document.addEventListener('click', click);\n browser!.addEventListener('popstate', popstate);\n browser!.addEventListener('hashchange', popstate);\n return () => { if (generation === started) stop(); };\n }\n };\n}\n"]}
@@ -1,2 +1,5 @@
1
1
  import type { Plugin } from 'vite';
2
- export declare function hesh(): Plugin;
2
+ import { type ProjectOptions } from '../architecture/index.js';
3
+ export declare function hesh(options?: {
4
+ architecture?: ProjectOptions;
5
+ }): Plugin;
@@ -1,16 +1,51 @@
1
1
  import { compile } from '../compiler/index.js';
2
- export function hesh() {
2
+ import { createArchitectureCompiler, writeProject, formatDiagnostic } from '../architecture/index.js';
3
+ import { resolve } from 'node:path';
4
+ export function hesh(options = {}) {
3
5
  let production = false;
6
+ const architecture = createArchitectureCompiler();
7
+ let project;
8
+ let watched = [];
9
+ let warn;
10
+ const check = () => {
11
+ if (!project)
12
+ return;
13
+ const result = architecture.check(project);
14
+ if (result.diagnostics.some(item => item.severity === 'error'))
15
+ throw new Error(result.diagnostics.map(formatDiagnostic).join('\n'));
16
+ for (const item of result.diagnostics)
17
+ if (item.severity === 'warning')
18
+ warn?.(formatDiagnostic(item));
19
+ writeProject(result);
20
+ watched = result.files;
21
+ };
4
22
  return {
5
23
  name: 'heshjs', enforce: 'pre',
6
- configResolved(config) { production = config.command === 'build'; },
24
+ config() { return { appType: 'spa' }; },
25
+ configResolved(config) {
26
+ production = config.command === 'build';
27
+ warn = message => config.logger.warn(message);
28
+ if (options.architecture) {
29
+ project = { ...options.architecture, root: resolve(config.root, options.architecture.root) };
30
+ check();
31
+ }
32
+ },
33
+ buildStart() { for (const file of watched)
34
+ this.addWatchFile(file); },
7
35
  transform(source, id) {
36
+ if (project && /(?:^|\/)server(?:\/|$)|\.server\.[cm]?[jt]s$/.test(id.replace(/\\/g, '/')))
37
+ throw new Error(`HESH-SEC-001 Server-only module cannot enter the browser bundle: ${id}`);
8
38
  if (!id.endsWith('.hesh'))
9
39
  return;
10
40
  const result = compile(source, { filename: id, development: !production });
11
41
  return { code: result.code, map: null };
12
42
  },
13
43
  handleHotUpdate(context) {
44
+ if (project && (watched.includes(context.file.replace(/\\/g, '/').toLowerCase()) || watched.includes(context.file.replace(/\\/g, '/')))) {
45
+ check();
46
+ context.server.ws.send({ type: 'full-reload' });
47
+ return [];
48
+ }
14
49
  if (context.file.endsWith('.hesh')) {
15
50
  context.server.ws.send({ type: 'full-reload' });
16
51
  return [];
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/vite/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,MAAM,UAAU,IAAI;IAClB,IAAI,UAAU,GAAG,KAAK,CAAC;IACvB,OAAO;QACL,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK;QAC9B,cAAc,CAAC,MAAM,IAAI,UAAU,GAAG,MAAM,CAAC,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC;QACnE,SAAS,CAAC,MAAM,EAAE,EAAE;YAClB,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC;gBAAE,OAAO;YAClC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,WAAW,EAAE,CAAC,UAAU,EAAE,CAAC,CAAC;YAC3E,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;QAC1C,CAAC;QACD,eAAe,CAAC,OAAO;YACrB,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;gBAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC,CAAC;gBAAC,OAAO,EAAE,CAAC;YAAC,CAAC;QACrG,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import type { Plugin } from 'vite';\nimport { compile } from '../compiler/index.js';\nexport function hesh(): Plugin {\n let production = false;\n return {\n name: 'heshjs', enforce: 'pre',\n configResolved(config) { production = config.command === 'build'; },\n transform(source, id) {\n if (!id.endsWith('.hesh')) return;\n const result = compile(source, { filename: id, development: !production });\n return { code: result.code, map: null };\n },\n handleHotUpdate(context) {\n if (context.file.endsWith('.hesh')) { context.server.ws.send({ type: 'full-reload' }); return []; }\n }\n };\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/vite/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Plugin } from 'vite';\nimport { compile } from '../compiler/index.js';\nimport { createArchitectureCompiler, writeProject, formatDiagnostic, type ProjectOptions } from '../architecture/index.js';\nimport { resolve } from 'node:path';\nexport function hesh(options: { architecture?: ProjectOptions } = {}): Plugin {\n let production = false;\n const architecture = createArchitectureCompiler();\n let project: ProjectOptions | undefined;\n let watched: string[] = [];\n let warn: ((message: string) => void) | undefined;\n const check = () => {\n if (!project) return;\n const result = architecture.check(project);\n if (result.diagnostics.some(item => item.severity === 'error')) throw new Error(result.diagnostics.map(formatDiagnostic).join('\\n'));\n for (const item of result.diagnostics) if (item.severity === 'warning') warn?.(formatDiagnostic(item));\n writeProject(result); watched = result.files;\n };\n return {\n name: 'heshjs', enforce: 'pre',\n config() { return { appType: 'spa' }; },\n configResolved(config) {\n production = config.command === 'build';\n warn = message => config.logger.warn(message);\n if (options.architecture) { project = { ...options.architecture, root: resolve(config.root, options.architecture.root) }; check(); }\n },\n buildStart() { for (const file of watched) this.addWatchFile(file); },\n transform(source, id) {\n if (project && /(?:^|\\/)server(?:\\/|$)|\\.server\\.[cm]?[jt]s$/.test(id.replace(/\\\\/g, '/'))) throw new Error(`HESH-SEC-001 Server-only module cannot enter the browser bundle: ${id}`);\n if (!id.endsWith('.hesh')) return;\n const result = compile(source, { filename: id, development: !production });\n return { code: result.code, map: null };\n },\n handleHotUpdate(context) {\n if (project && (watched.includes(context.file.replace(/\\\\/g, '/').toLowerCase()) || watched.includes(context.file.replace(/\\\\/g, '/')))) {\n check(); context.server.ws.send({ type: 'full-reload' }); return [];\n }\n if (context.file.endsWith('.hesh')) { context.server.ws.send({ type: 'full-reload' }); return []; }\n }\n };\n}\n"]}
@@ -0,0 +1,60 @@
1
+ # Architecture declaration grammar (experimental)
2
+
3
+ These declarations extend the existing Hesh lexer/parser. A file may contain
4
+ views or architecture declarations; project compilation resolves architecture.
5
+ Imports in architecture files reference other .hesh descriptor files explicitly.
6
+
7
+ ```ebnf
8
+ domain = "domain", Identifier, "{",
9
+ "implementation", ":", String, ";",
10
+ [ "owns", "{", { Identifier, ";" }, "}" ],
11
+ "exposes", "{", { Path, ";" }, "}",
12
+ [ "consumes", "{", { Path, ";" }, "}" ],
13
+ [ "access", "{", { Path, ":", String, ";" }, "}" ], "}" ;
14
+ presentation = "presentation", Identifier, "{",
15
+ "html", ":", String, ";", "logic", ":", String, ";",
16
+ [ "styles", ":", String, ";" ],
17
+ "bindings", "{", { Identifier, ";" }, "}",
18
+ "actions", "{", { Identifier, ";" }, "}", "}" ;
19
+ destination = "destination", Identifier, "{",
20
+ "url", ":", String, ";",
21
+ "input", "{", { Identifier, ":", ("string" | "number" | "boolean"), ";" }, "}",
22
+ "presentation", ":", Identifier, ";", [ "access", ":", String, ";" ], "}" ;
23
+ application = "application", Identifier, "{",
24
+ "domains", "{", { Identifier, ";" }, "}",
25
+ "destinations", "{", { Identifier, ";" }, "}", "}" ;
26
+ Path = Identifier, { ".", Identifier } ;
27
+ ```
28
+
29
+ Fields/blocks can appear in any order, once each. Unknown/duplicate fields are
30
+ errors. Strings use ordinary JS string escaping. Identifiers cannot be prototype
31
+ mutation names. Locations are retained for diagnostics. Semicolons are required.
32
+
33
+ `implementation` and `logic` have form `./module.js#exportName` (or .ts/.mjs).
34
+ A domain factory takes a dependencies object and returns its actual API. Every
35
+ exposed dotted operation must exist and be callable according to the TS checker;
36
+ every consumed path is Domain.operation and must be publicly exposed. An access
37
+ entry must name an exposed operation. `owns` is ownership metadata, not a generated
38
+ entity/validation/database implementation. The runtime passes only declared
39
+ consumed operations and exposes only the validated public facade.
40
+
41
+ A presentation factory receives `{ domains, input, location, signal }` and returns
42
+ `{ bindings, actions }`. bindings values may be signals/readers/ordinary values;
43
+ actions are callable. Both declared names and nested template paths are checked
44
+ against its inferred JS/TS return shape. HTML text `{product.name}` is escaped via
45
+ text nodes. `h-bind="product.name"` binds textContent (cannot have child content),
46
+ `h-action="reserve"` listens to click (submit on a form), `h-value="quantity"`
47
+ binds a writable signal to an input/textarea/select, and
48
+ `h-destination="ProductDetails" h-input-id="product.id"` binds a native anchor to
49
+ a validated destination. Other normal HTML attributes/semantics are preserved.
50
+ No arbitrary JS expressions, raw HTML, inline event attributes, script elements,
51
+ h-each, h-key or unknown h-* directives are accepted in this first milestone.
52
+ Use normal CSS files; template scoping and keyed template lists remain deferred.
53
+
54
+ Destination URL params must exactly match input names. Values are encoded/decoded
55
+ once; number inputs must be finite and booleans serialize as true/false. Duplicate
56
+ or structurally conflicting URL destinations are errors. Generated declarations
57
+ provide TS input types and JS IntelliSense. Runtime validation also rejects missing,
58
+ extra or invalid fields in JavaScript. Destination policy names are client metadata;
59
+ they cannot enforce server-side authorization. A policy without an authorize
60
+ callback is denied rather than silently accepted.
@@ -0,0 +1,205 @@
1
+ # Domain Graph first vertical slice (0.3)
2
+
3
+ This opt-in architecture builds on the existing Hesh compiler, shallow reactivity,
4
+ DOM renderer, router and HMVC. Existing components and `createRouter` remain valid.
5
+ This is the first architecture milestone, not an enterprise-ready framework.
6
+ These APIs require 0.3 or later. Install `@heshammesalam/heshjs@0.3.0` in an
7
+ existing Vite application and enable the architecture option described below.
8
+ The npm creator's default template remains HMVC; it does not scaffold domains.
9
+ The following commands run the reference examples from the framework repository:
10
+
11
+ ```sh
12
+ npm install
13
+ npm run check:architecture
14
+ npm run dev:domains # JS Identity/Inventory/Finance demo, localhost:5180
15
+ npm run dev:domains:ts # strict TS alternative, localhost:5181
16
+ ```
17
+
18
+ The JS demo uses mock identity, an abortable timer-backed mock product API and a
19
+ mock tax service. No HTTP server, credentials, payments or real authorization are
20
+ implemented. Browser refresh restores destination/path input, not in-memory demo
21
+ login state. Journeys are deliberately not simulated.
22
+
23
+ ## Descriptors and real implementations
24
+
25
+ ```hesh
26
+ domain Inventory {
27
+ implementation: "./inventory.js#createInventory";
28
+ owns { Product; }
29
+ exposes { products.get; stock.reserve; }
30
+ consumes { Identity.session; }
31
+ access { stock.reserve: "inventory.reserve"; }
32
+ }
33
+ presentation ProductScreen {
34
+ html: "./ui/product.html";
35
+ logic: "./ui/product.js#createProduct";
36
+ styles: "./ui/product.css";
37
+ bindings { title; }
38
+ actions { reserve; }
39
+ }
40
+ destination ProductDetails {
41
+ url: "/products/:id";
42
+ input { id: string; }
43
+ presentation: ProductScreen;
44
+ access: "inventory.read";
45
+ }
46
+ ```
47
+
48
+ ```js
49
+ // inventory.js: private implementation, no imports from another domain's files.
50
+ export function createInventory(dependencies) {
51
+ return {
52
+ products: { get: id => ({ id, title: 'Mock product' }) },
53
+ stock: { reserve: id => dependencies.Identity.session() + ': ' + id }
54
+ };
55
+ }
56
+ ```
57
+
58
+ `owns` is metadata, not a generated entity/database/validation system. Exposes are
59
+ checked against the real factory return type. Consumers receive only the listed
60
+ public operations, not the whole implementation object. Factories execute once
61
+ per application domain graph in dependency order. Cycles and missing contracts
62
+ are errors. Dependencies and public facades are frozen; disposed graphs reject
63
+ further operation calls. Factories may use `onCleanup` for owned resources.
64
+
65
+ The application descriptor imports domain files explicitly and selects names:
66
+
67
+ ```hesh
68
+ import "./domains/identity/domain.hesh";
69
+ import "./domains/inventory/domain.hesh";
70
+ application Shop {
71
+ domains { Identity; Inventory; }
72
+ destinations { ProductDetails; }
73
+ }
74
+ ```
75
+
76
+ Every selected destination/presentation belongs to one selected domain directory.
77
+ Each domain has its own directory boundary. Shared code can be imported by domains;
78
+ shared code cannot re-export a private domain implementation as an escape hatch.
79
+
80
+ ## Standard HTML, CSS and explicit bindings
81
+
82
+ ```html
83
+ <section aria-labelledby="title">
84
+ <h1 id="title">{title}</h1>
85
+ <button h-action="reserve">Reserve</button>
86
+ </section>
87
+ ```
88
+
89
+ ```js
90
+ import { signal } from '@heshammesalam/heshjs';
91
+ export function createProduct({ domains, input, location, signal: cancellation }) {
92
+ const title = signal(domains.Inventory.products.get(input.id).title);
93
+ return {
94
+ bindings: { title },
95
+ actions: { reserve() { domains.Inventory.stock.reserve(input.id); } }
96
+ };
97
+ }
98
+ ```
99
+
100
+ Bindings are explicit declared data paths, not arbitrary evaluated JS strings.
101
+ `{product.name}` and `h-bind="product.name"` use text nodes. `\{`/`\}` produce
102
+ literal braces. A whole attribute binding like `disabled="{disabled}"` preserves
103
+ boolean values; mixed attribute text is a string. Standard native HTML/custom tags
104
+ are preserved without Hesh element aliases. Browser forms, labels, ARIA and native
105
+ validation remain available. `h-action` means click, or submit on a form (preventing
106
+ full submission). Events are passed to actions and async errors use onError.
107
+ `h-value="email"` is two-way binding to a root writable signal on text controls.
108
+
109
+ `h-destination="ProductDetails" h-input-id="productId"` on a native anchor builds
110
+ a real href from the destination contract. Input attributes follow HTML's case
111
+ insensitivity; destination input names must therefore be unique ignoring case.
112
+ Missing/extra/wrongly typed inputs, missing bindings/actions and unsafe directives
113
+ produce located diagnostics. Dynamic any-typed data emits warnings when the shape
114
+ cannot be proven. JS receives inferred checks/JSDoc where present and runtime
115
+ input validation; this does not make arbitrary JavaScript fully type-safe.
116
+
117
+ HTML is parsed with parse5 at build time and emitted as DOM instructions. No
118
+ innerHTML/eval/Function constructor is used. Script/embedding/foreign/template
119
+ elements, inline event attributes and unknown h-* directives are rejected. SVG,
120
+ keyed h-each, raw HTML and scoped CSS are not implemented in this slice. Standard
121
+ CSS is imported unchanged through Vite, including variables, modern selectors,
122
+ media/container queries, animations and global themes. CSS is global in this release.
123
+
124
+ ## Generated typed destinations
125
+
126
+ Configure Vite using `hesh({ architecture: { root: 'src' } })`. For a tsconfig
127
+ outside src add `tsconfig: '../tsconfig.json'`. The plugin validates before build
128
+ and generates `src/.hesh-generated/app.js`, app.d.ts and compiled presentation
129
+ modules. Do not hand-edit generated files. Invalid checks block build; last valid
130
+ files are not deleted, so bypassing the check pipeline can use stale output.
131
+
132
+ ```js
133
+ import { createApplication } from './.hesh-generated/app.js';
134
+ const app = createApplication({
135
+ authorize: policy => policy === 'inventory.read', // CLIENT ONLY
136
+ onError: error => console.error(error),
137
+ onDiagnostic: event => console.debug(event.type, event.destination, event.reason)
138
+ });
139
+ const dispose = app.start(document.querySelector('#app'));
140
+ app.destinations.ProductDetails.open({ id: '15' });
141
+ app.destinations.ProductDetails.replace({ id: '20' }, { query: { tab: 'stock' } });
142
+ await app.destinations.ProductDetails.prefetch({ id: '30' });
143
+ ```
144
+
145
+ Generated declarations give each destination its exact required input fields and
146
+ primitive types. Empty-input destinations use `Record<string, never>`. Runtime
147
+ validation also rejects unknown/missing fields, non-finite numbers, empty strings
148
+ and URL dot segments. Inputs are encoded once and decoded once; URL parameter
149
+ names currently use the existing router's ASCII letter/digit/underscore rules.
150
+ Overlapping destination patterns are rejected so an input cannot silently open a
151
+ different static destination. This restriction applies only to the new contracts;
152
+ the existing router retains its static-priority behavior.
153
+
154
+ Page is the only supported presentation mode. Destinations load their own compiled
155
+ presentation and logic resources lazily. Prefetch deduplicates those code loads;
156
+ it does not fetch business data or authorize operations. Navigation disposes the
157
+ previous presentation, aborts its signal and prevents a late loader from mounting
158
+ stale UI. Dynamic module downloads themselves cannot be canceled. Factories are
159
+ synchronous and start their own asynchronous work using the supplied signal.
160
+ Guard against late results when using non-abortable services. Query/hash changes
161
+ create a fresh presentation instance. Successful mounting focuses a heading or
162
+ `data-hesh-focus`; scroll restoration and modal focus trapping are deferred.
163
+
164
+ `app.status.get()` is reactive loading/ready/error state. Optional diagnostic
165
+ events identify loading, ready, blocked, errors, cancellation and disposal. They
166
+ do not infer business intent or explain arbitrary reactive dependency chains.
167
+ Client policies default to deny when declared without an authorize callback;
168
+ server-side enforcement is still necessary for every protected operation.
169
+
170
+ ## Architecture validation and graph tooling
171
+
172
+ ```sh
173
+ hesh check ./src --write
174
+ hesh check ./src --tsconfig ../tsconfig.json --write
175
+ hesh graph ./src
176
+ ```
177
+
178
+ From the repository use `node packages/heshjs/cli.js ...`. Root means the directory
179
+ containing app.hesh, typically src. The TS resolver handles aliases, relative paths,
180
+ type imports and re-exports. Canonical paths account for filesystem aliases and
181
+ symlinks. Physical cross-domain imports of implementations are private, even if
182
+ the target JS module exports a function; use declared injection instead. Node and
183
+ server-only modules are blocked in client code. Computed imports in application
184
+ source are rejected because their target cannot be validated. This is build-time
185
+ architecture enforcement for configured projects, not a sandbox against malicious
186
+ application code, reflection or uninspected third-party packages.
187
+
188
+ Diagnostics include code, severity, path, offset/line/column, explanation and a
189
+ suggestion. HTML locations identify their source node/attribute; entity decoding
190
+ can make the exact inner text offset approximate. Graph JSON contains domain
191
+ dependencies, destination ownership/input schemas and compiled navigation links.
192
+ Descriptor ASTs are reused when unchanged and output writes are content-based.
193
+ Semantic analysis currently rechecks the whole graph; precise dependency-aware
194
+ typecheck invalidation and full HTML/Hesh source maps are deferred. The Vite workflow
195
+ uses full reload rather than state-preserving HMR.
196
+
197
+ ## Deliberate limits
198
+
199
+ Journeys, modal/panel/nested destination modes, redirects, scroll restoration,
200
+ file routing, generic async resources/caching, deep collections, scoped CSS,
201
+ server authorization, SSR/hydration, advanced scaffolding commands and a language
202
+ server are not implemented. Existing HMVC nested routes remain usable separately.
203
+ Production hosts still need SPA fallback to index.html for direct URLs. No
204
+ performance superiority, globally unique architecture or enterprise-readiness
205
+ claim is made. See architecture/domain-graph-audit.md for migration order.