@toyz/loom 0.1.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 (127) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +108 -0
  3. package/dist/app.d.ts +70 -0
  4. package/dist/app.d.ts.map +1 -0
  5. package/dist/app.js +152 -0
  6. package/dist/app.js.map +1 -0
  7. package/dist/bus.d.ts +27 -0
  8. package/dist/bus.d.ts.map +1 -0
  9. package/dist/bus.js +41 -0
  10. package/dist/bus.js.map +1 -0
  11. package/dist/css.d.ts +25 -0
  12. package/dist/css.d.ts.map +1 -0
  13. package/dist/css.js +48 -0
  14. package/dist/css.js.map +1 -0
  15. package/dist/decorators/component.d.ts +11 -0
  16. package/dist/decorators/component.d.ts.map +1 -0
  17. package/dist/decorators/component.js +38 -0
  18. package/dist/decorators/component.js.map +1 -0
  19. package/dist/decorators/di.d.ts +37 -0
  20. package/dist/decorators/di.d.ts.map +1 -0
  21. package/dist/decorators/di.js +65 -0
  22. package/dist/decorators/di.js.map +1 -0
  23. package/dist/decorators/dom.d.ts +15 -0
  24. package/dist/decorators/dom.d.ts.map +1 -0
  25. package/dist/decorators/dom.js +33 -0
  26. package/dist/decorators/dom.js.map +1 -0
  27. package/dist/decorators/events.d.ts +46 -0
  28. package/dist/decorators/events.d.ts.map +1 -0
  29. package/dist/decorators/events.js +69 -0
  30. package/dist/decorators/events.js.map +1 -0
  31. package/dist/decorators/index.d.ts +18 -0
  32. package/dist/decorators/index.d.ts.map +1 -0
  33. package/dist/decorators/index.js +27 -0
  34. package/dist/decorators/index.js.map +1 -0
  35. package/dist/decorators/lifecycle.d.ts +49 -0
  36. package/dist/decorators/lifecycle.d.ts.map +1 -0
  37. package/dist/decorators/lifecycle.js +105 -0
  38. package/dist/decorators/lifecycle.js.map +1 -0
  39. package/dist/decorators/state.d.ts +41 -0
  40. package/dist/decorators/state.d.ts.map +1 -0
  41. package/dist/decorators/state.js +125 -0
  42. package/dist/decorators/state.js.map +1 -0
  43. package/dist/decorators/symbols.d.ts +13 -0
  44. package/dist/decorators/symbols.d.ts.map +1 -0
  45. package/dist/decorators/symbols.js +15 -0
  46. package/dist/decorators/symbols.js.map +1 -0
  47. package/dist/decorators/timing.d.ts +35 -0
  48. package/dist/decorators/timing.d.ts.map +1 -0
  49. package/dist/decorators/timing.js +57 -0
  50. package/dist/decorators/timing.js.map +1 -0
  51. package/dist/decorators/transform.d.ts +45 -0
  52. package/dist/decorators/transform.d.ts.map +1 -0
  53. package/dist/decorators/transform.js +48 -0
  54. package/dist/decorators/transform.js.map +1 -0
  55. package/dist/element.d.ts +62 -0
  56. package/dist/element.d.ts.map +1 -0
  57. package/dist/element.js +150 -0
  58. package/dist/element.js.map +1 -0
  59. package/dist/event.d.ts +24 -0
  60. package/dist/event.d.ts.map +1 -0
  61. package/dist/event.js +35 -0
  62. package/dist/event.js.map +1 -0
  63. package/dist/icon.d.ts +35 -0
  64. package/dist/icon.d.ts.map +1 -0
  65. package/dist/icon.js +119 -0
  66. package/dist/icon.js.map +1 -0
  67. package/dist/index.d.ts +29 -0
  68. package/dist/index.d.ts.map +1 -0
  69. package/dist/index.js +34 -0
  70. package/dist/index.js.map +1 -0
  71. package/dist/jsx-dev-runtime.d.ts +8 -0
  72. package/dist/jsx-dev-runtime.d.ts.map +1 -0
  73. package/dist/jsx-dev-runtime.js +8 -0
  74. package/dist/jsx-dev-runtime.js.map +1 -0
  75. package/dist/jsx-runtime.d.ts +13 -0
  76. package/dist/jsx-runtime.d.ts.map +1 -0
  77. package/dist/jsx-runtime.js +101 -0
  78. package/dist/jsx-runtime.js.map +1 -0
  79. package/dist/morph.d.ts +23 -0
  80. package/dist/morph.d.ts.map +1 -0
  81. package/dist/morph.js +212 -0
  82. package/dist/morph.js.map +1 -0
  83. package/dist/reactive.d.ts +75 -0
  84. package/dist/reactive.d.ts.map +1 -0
  85. package/dist/reactive.js +133 -0
  86. package/dist/reactive.js.map +1 -0
  87. package/dist/render-loop.d.ts +34 -0
  88. package/dist/render-loop.d.ts.map +1 -0
  89. package/dist/render-loop.js +70 -0
  90. package/dist/render-loop.js.map +1 -0
  91. package/dist/router/events.d.ts +12 -0
  92. package/dist/router/events.d.ts.map +1 -0
  93. package/dist/router/events.js +17 -0
  94. package/dist/router/events.js.map +1 -0
  95. package/dist/router/index.d.ts +14 -0
  96. package/dist/router/index.d.ts.map +1 -0
  97. package/dist/router/index.js +18 -0
  98. package/dist/router/index.js.map +1 -0
  99. package/dist/router/link.d.ts +18 -0
  100. package/dist/router/link.d.ts.map +1 -0
  101. package/dist/router/link.js +75 -0
  102. package/dist/router/link.js.map +1 -0
  103. package/dist/router/mode.d.ts +33 -0
  104. package/dist/router/mode.d.ts.map +1 -0
  105. package/dist/router/mode.js +48 -0
  106. package/dist/router/mode.js.map +1 -0
  107. package/dist/router/outlet.d.ts +40 -0
  108. package/dist/router/outlet.d.ts.map +1 -0
  109. package/dist/router/outlet.js +171 -0
  110. package/dist/router/outlet.js.map +1 -0
  111. package/dist/router/route.d.ts +76 -0
  112. package/dist/router/route.d.ts.map +1 -0
  113. package/dist/router/route.js +147 -0
  114. package/dist/router/route.js.map +1 -0
  115. package/dist/router/router.d.ts +50 -0
  116. package/dist/router/router.d.ts.map +1 -0
  117. package/dist/router/router.js +140 -0
  118. package/dist/router/router.js.map +1 -0
  119. package/dist/storage.d.ts +55 -0
  120. package/dist/storage.d.ts.map +1 -0
  121. package/dist/storage.js +90 -0
  122. package/dist/storage.js.map +1 -0
  123. package/dist/virtual.d.ts +69 -0
  124. package/dist/virtual.d.ts.map +1 -0
  125. package/dist/virtual.js +247 -0
  126. package/dist/virtual.js.map +1 -0
  127. package/package.json +58 -0
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Loom Router — <loom-outlet>
3
+ *
4
+ * Renders the component matched by the current route.
5
+ * Supports `inherit-styles` attribute to pass parent stylesheets
6
+ * into the routed component's shadow root.
7
+ *
8
+ * Resolves the initial route on connect so pages render even if
9
+ * RouteChanged fired before the outlet entered the DOM.
10
+ */
11
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
12
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
13
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
14
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
15
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
16
+ };
17
+ import { LoomElement } from "../element";
18
+ import { component, on, prop, ROUTE_PROPS, TRANSFORMS } from "../decorators";
19
+ import { matchRoute } from "./route";
20
+ import { RouteChanged } from "./events";
21
+ import { params as paramsSentinel, routeQuery as querySentinel } from "../decorators/state";
22
+ let LoomOutlet = class LoomOutlet extends LoomElement {
23
+ /** Stylesheets to pass down to routed components via adoptStyles() */
24
+ styles = [];
25
+ scrollToTop = true;
26
+ /** Optional callback invoked with the routed element after mount */
27
+ routeMount = null;
28
+ _currentTag = null;
29
+ _currentEl = null;
30
+ _initialResolved = false;
31
+ onRouteChanged(e) {
32
+ this._show(e.path, e.params);
33
+ }
34
+ /** Called after the first render — resolve whatever the current URL is */
35
+ firstUpdated() {
36
+ if (this._initialResolved)
37
+ return;
38
+ this._initialResolved = true;
39
+ const path = location.hash.slice(1) || "/";
40
+ const match = matchRoute(path);
41
+ if (match) {
42
+ this._show(path, match.params);
43
+ }
44
+ }
45
+ _show(path, params) {
46
+ const match = matchRoute(path);
47
+ const tag = match?.entry.tag ?? null;
48
+ if (!tag) {
49
+ this._clear();
50
+ return;
51
+ }
52
+ // Same component — just update data
53
+ if (tag === this._currentTag && this._currentEl) {
54
+ this._injectRouteData(this._currentEl, params);
55
+ return;
56
+ }
57
+ this._clear();
58
+ this._mount(tag, params);
59
+ if (this.scrollToTop)
60
+ this._scrollToTop();
61
+ }
62
+ _mount(tag, params) {
63
+ const el = document.createElement(tag);
64
+ this._injectRouteData(el, params);
65
+ // Pass explicit styles to the routed component
66
+ if (this.styles.length > 0) {
67
+ this._adoptParentStyles(el);
68
+ }
69
+ this.shadow.appendChild(el);
70
+ this._currentTag = tag;
71
+ this._currentEl = el;
72
+ // Notify consumer
73
+ if (this.routeMount) {
74
+ queueMicrotask(() => this.routeMount?.(el));
75
+ }
76
+ }
77
+ /**
78
+ * Inject route params and query params into the element.
79
+ * Uses ROUTE_PROPS metadata when available (typed properties),
80
+ * falls back to setAttribute for backward compat.
81
+ */
82
+ _injectRouteData(el, params) {
83
+ const ctor = el.constructor;
84
+ const routeBindings = ctor[ROUTE_PROPS] ?? [];
85
+ const transforms = ctor[TRANSFORMS];
86
+ const queryMap = this._parseQuery();
87
+ const boundParamKeys = new Set();
88
+ for (const binding of routeBindings) {
89
+ let value;
90
+ if (binding.params === paramsSentinel) {
91
+ // Full param decompose: @prop({params})
92
+ value = { ...params };
93
+ }
94
+ else if (typeof binding.param === "string") {
95
+ // Single param pick: @prop({ param: "id" })
96
+ value = params[binding.param] ?? "";
97
+ boundParamKeys.add(binding.param);
98
+ }
99
+ else if (binding.query === querySentinel) {
100
+ // Full query decompose: @prop({query: routeQuery})
101
+ value = Object.fromEntries(queryMap);
102
+ }
103
+ else if (typeof binding.query === "string") {
104
+ // Single query pick: @prop({ query: "tab" })
105
+ value = queryMap.get(binding.query) ?? "";
106
+ }
107
+ // Apply @transform if registered
108
+ if (transforms?.has(binding.propKey) && value !== undefined) {
109
+ value = transforms.get(binding.propKey)(value);
110
+ }
111
+ el[binding.propKey] = value;
112
+ }
113
+ // Backward compat: set unbound params as attributes
114
+ for (const [key, val] of Object.entries(params)) {
115
+ if (!boundParamKeys.has(key)) {
116
+ el.setAttribute(key, val);
117
+ }
118
+ }
119
+ }
120
+ /** Parse query params from the URL hash */
121
+ _parseQuery() {
122
+ const hash = location.hash;
123
+ const qIdx = hash.indexOf("?");
124
+ return new URLSearchParams(qIdx >= 0 ? hash.slice(qIdx + 1) : "");
125
+ }
126
+ _adoptParentStyles(el) {
127
+ if (typeof el.adoptStyles === "function") {
128
+ el.adoptStyles(this.styles);
129
+ }
130
+ }
131
+ _clear() {
132
+ if (this._currentEl) {
133
+ this._currentEl.remove();
134
+ this._currentEl = null;
135
+ this._currentTag = null;
136
+ }
137
+ }
138
+ _scrollToTop() {
139
+ // Walk up the DOM tree (including shadow host boundaries)
140
+ let node = this;
141
+ while (node) {
142
+ if (node instanceof Element && node.scrollTop > 0) {
143
+ node.scrollTop = 0;
144
+ }
145
+ // Cross shadow DOM boundary
146
+ const root = node.getRootNode();
147
+ if (root instanceof ShadowRoot) {
148
+ node = root.host;
149
+ }
150
+ else {
151
+ node = node.parentElement;
152
+ }
153
+ }
154
+ // Always scroll the window as failsafe
155
+ window.scrollTo(0, 0);
156
+ }
157
+ };
158
+ __decorate([
159
+ prop
160
+ ], LoomOutlet.prototype, "styles", void 0);
161
+ __decorate([
162
+ prop
163
+ ], LoomOutlet.prototype, "scrollToTop", void 0);
164
+ __decorate([
165
+ on(RouteChanged)
166
+ ], LoomOutlet.prototype, "onRouteChanged", null);
167
+ LoomOutlet = __decorate([
168
+ component("loom-outlet")
169
+ ], LoomOutlet);
170
+ export { LoomOutlet };
171
+ //# sourceMappingURL=outlet.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outlet.js","sourceRoot":"","sources":["../../src/router/outlet.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Loom Router — Route table and decorators
3
+ *
4
+ * @route(path, opts?) registers a component class as a route handler.
5
+ * @guard(name?) marks a method as a named route guard.
6
+ */
7
+ export declare const ROUTE_PATH: unique symbol;
8
+ export declare const GUARD_HANDLERS: unique symbol;
9
+ export interface RouteEntry {
10
+ pattern: string;
11
+ regex: RegExp;
12
+ paramNames: string[];
13
+ tag: string;
14
+ ctor: any;
15
+ /** Named guards to check before rendering this route */
16
+ guards: string[];
17
+ }
18
+ /** Global route table — populated by @route decorators */
19
+ export declare const routes: RouteEntry[];
20
+ /**
21
+ * Global guard registry — populated by @guard decorators.
22
+ * Maps guard name → { proto, key, injectMeta }
23
+ */
24
+ export interface GuardRegistration {
25
+ proto: any;
26
+ key: string;
27
+ }
28
+ export declare const guardRegistry: Map<string, GuardRegistration>;
29
+ /**
30
+ * Match a path against the route table.
31
+ * Returns the matched entry + extracted params, or null.
32
+ */
33
+ export declare function matchRoute(path: string): {
34
+ entry: RouteEntry;
35
+ params: Record<string, string>;
36
+ } | null;
37
+ interface RouteOptions {
38
+ /** Named guards to check before this route is rendered */
39
+ guards?: string[];
40
+ }
41
+ /**
42
+ * Register a class as a route handler.
43
+ *
44
+ * ```ts
45
+ * @route("/docs/:slug")
46
+ * @component("page-docs")
47
+ * class PageDocs extends LoomElement { ... }
48
+ *
49
+ * @route("/admin", { guards: ["auth", "role"] })
50
+ * @component("page-admin")
51
+ * class PageAdmin extends LoomElement { ... }
52
+ * ```
53
+ */
54
+ export declare function route(pattern: string, opts?: RouteOptions): (ctor: any) => void;
55
+ /**
56
+ * Mark a method as a named route guard.
57
+ *
58
+ * Return `true` to allow, `false` to block, or a `string` to redirect.
59
+ * Async guards are awaited. Supports @inject on parameters.
60
+ *
61
+ * ```ts
62
+ * @guard("auth")
63
+ * checkAuth(@inject(AuthService) auth: AuthService) {
64
+ * return auth.isLoggedIn ? true : "/login";
65
+ * }
66
+ *
67
+ * // Derived name from method name:
68
+ * @guard
69
+ * checkRole(@inject(UserStore) users: UserStore) {
70
+ * return users.current?.role === "admin" ? true : "/forbidden";
71
+ * }
72
+ * ```
73
+ */
74
+ export declare function guard(nameOrTarget: any, key?: string): any;
75
+ export {};
76
+ //# sourceMappingURL=route.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../src/router/route.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,eAAO,MAAM,UAAU,eAA4B,CAAC;AACpD,eAAO,MAAM,cAAc,eAA8B,CAAC;AAE1D,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,GAAG,CAAC;IACV,wDAAwD;IACxD,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,0DAA0D;AAC1D,eAAO,MAAM,MAAM,EAAE,UAAU,EAAO,CAAC;AAEvC;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,GAAG,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;CACb;AACD,eAAO,MAAM,aAAa,gCAAuC,CAAC;AA4BlE;;;GAGG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAAG,IAAI,CAYrG;AAID,UAAU,YAAY;IACpB,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,YAAY,IAChD,MAAM,GAAG,UA4BlB;AAeD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,KAAK,CAAC,YAAY,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,GAAG,CAa1D"}
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Loom Router — Route table and decorators
3
+ *
4
+ * @route(path, opts?) registers a component class as a route handler.
5
+ * @guard(name?) marks a method as a named route guard.
6
+ */
7
+ export const ROUTE_PATH = Symbol("loom:route:path");
8
+ export const GUARD_HANDLERS = Symbol("loom:route:guards");
9
+ /** Global route table — populated by @route decorators */
10
+ export const routes = [];
11
+ export const guardRegistry = new Map();
12
+ /**
13
+ * Compile a route pattern into a regex + param name list.
14
+ *
15
+ * `/docs/:slug` → { regex: /^\/docs\/([^/]+)$/, paramNames: ["slug"] }
16
+ * `*` → { regex: /^.*$/, paramNames: [] }
17
+ */
18
+ function compilePattern(pattern) {
19
+ if (pattern === "*") {
20
+ return { regex: /^.*$/, paramNames: [] };
21
+ }
22
+ const paramNames = [];
23
+ const regexStr = pattern
24
+ .split("/")
25
+ .map((seg) => {
26
+ if (seg.startsWith(":")) {
27
+ paramNames.push(seg.slice(1));
28
+ return "([^/]+)";
29
+ }
30
+ return seg.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
31
+ })
32
+ .join("\\/");
33
+ return { regex: new RegExp(`^${regexStr}$`), paramNames };
34
+ }
35
+ /**
36
+ * Match a path against the route table.
37
+ * Returns the matched entry + extracted params, or null.
38
+ */
39
+ export function matchRoute(path) {
40
+ for (const entry of routes) {
41
+ const m = path.match(entry.regex);
42
+ if (m) {
43
+ const params = {};
44
+ entry.paramNames.forEach((name, i) => {
45
+ params[name] = m[i + 1];
46
+ });
47
+ return { entry, params };
48
+ }
49
+ }
50
+ return null;
51
+ }
52
+ /**
53
+ * Register a class as a route handler.
54
+ *
55
+ * ```ts
56
+ * @route("/docs/:slug")
57
+ * @component("page-docs")
58
+ * class PageDocs extends LoomElement { ... }
59
+ *
60
+ * @route("/admin", { guards: ["auth", "role"] })
61
+ * @component("page-admin")
62
+ * class PageAdmin extends LoomElement { ... }
63
+ * ```
64
+ */
65
+ export function route(pattern, opts) {
66
+ return (ctor) => {
67
+ ctor[ROUTE_PATH] = pattern;
68
+ const { regex, paramNames } = compilePattern(pattern);
69
+ // Defer tag resolution — the @component decorator may not have run yet.
70
+ // We use a getter to lazily resolve the custom element tag.
71
+ const entry = {
72
+ pattern,
73
+ regex,
74
+ paramNames,
75
+ get tag() {
76
+ // Find the tag registered for this constructor
77
+ return _tagForCtor(ctor);
78
+ },
79
+ ctor,
80
+ guards: opts?.guards ?? [],
81
+ };
82
+ // Wildcard goes last
83
+ if (pattern === "*") {
84
+ routes.push(entry);
85
+ }
86
+ else {
87
+ // Insert before wildcards
88
+ const wildcardIdx = routes.findIndex((r) => r.pattern === "*");
89
+ if (wildcardIdx === -1)
90
+ routes.push(entry);
91
+ else
92
+ routes.splice(wildcardIdx, 0, entry);
93
+ }
94
+ };
95
+ }
96
+ /** Resolve the custom element tag for a constructor */
97
+ function _tagForCtor(ctor) {
98
+ // Search customElements registry
99
+ const name = customElements.getName?.(ctor);
100
+ if (name)
101
+ return name;
102
+ // Fallback: scan internal app registry (custom elements v1 doesn't have getName everywhere)
103
+ // We store the tag on the constructor via @component
104
+ return ctor.__loom_tag ?? ctor.name.toLowerCase();
105
+ }
106
+ // ── @guard decorator ──
107
+ /**
108
+ * Mark a method as a named route guard.
109
+ *
110
+ * Return `true` to allow, `false` to block, or a `string` to redirect.
111
+ * Async guards are awaited. Supports @inject on parameters.
112
+ *
113
+ * ```ts
114
+ * @guard("auth")
115
+ * checkAuth(@inject(AuthService) auth: AuthService) {
116
+ * return auth.isLoggedIn ? true : "/login";
117
+ * }
118
+ *
119
+ * // Derived name from method name:
120
+ * @guard
121
+ * checkRole(@inject(UserStore) users: UserStore) {
122
+ * return users.current?.role === "admin" ? true : "/forbidden";
123
+ * }
124
+ * ```
125
+ */
126
+ export function guard(nameOrTarget, key) {
127
+ if (typeof key === "string") {
128
+ // Bare @guard (no name) — derive name from method name
129
+ _registerGuard(nameOrTarget, key, key);
130
+ return;
131
+ }
132
+ if (typeof nameOrTarget === "string") {
133
+ // @guard("auth") — factory form with explicit name
134
+ return (target, methodKey) => {
135
+ _registerGuard(target, methodKey, nameOrTarget);
136
+ };
137
+ }
138
+ }
139
+ function _registerGuard(proto, key, name) {
140
+ // Legacy: store on prototype for backward compat
141
+ if (!proto[GUARD_HANDLERS])
142
+ proto[GUARD_HANDLERS] = [];
143
+ proto[GUARD_HANDLERS].push(key);
144
+ // New: register in global registry by name
145
+ guardRegistry.set(name, { proto, key });
146
+ }
147
+ //# sourceMappingURL=route.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route.js","sourceRoot":"","sources":["../../src/router/route.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Loom Router — LoomRouter service
3
+ *
4
+ * Dual-mode (hash/history) router with named guard support.
5
+ * Register via app.use(new LoomRouter({ mode: "hash" }))
6
+ */
7
+ import { type RouterMode } from "./mode";
8
+ export interface RouterOptions {
9
+ mode?: "hash" | "history";
10
+ }
11
+ export interface RouteInfo {
12
+ path: string;
13
+ params: Record<string, string>;
14
+ tag: string | null;
15
+ }
16
+ export declare class LoomRouter {
17
+ readonly mode: RouterMode;
18
+ private _current;
19
+ constructor(opts?: RouterOptions);
20
+ /** Current route info (read-only snapshot) */
21
+ get current(): Readonly<RouteInfo>;
22
+ /** Start listening for URL changes and resolve the initial route */
23
+ start(): () => void;
24
+ /** Navigate to a path */
25
+ go(path: string): Promise<void>;
26
+ /** Navigate without creating a history entry */
27
+ replace(path: string): Promise<void>;
28
+ /** Go back in history */
29
+ back(): void;
30
+ /** Go forward in history */
31
+ forward(): void;
32
+ /** Build an href for the current mode */
33
+ href(path: string): string;
34
+ /** Resolve the current URL against the route table and emit RouteChanged */
35
+ private _resolve;
36
+ /**
37
+ * Run guards for the target route.
38
+ *
39
+ * Checks named guards from @route({ guards: [...] }) first,
40
+ * then falls back to @guard methods on the component prototype.
41
+ * Resolves @inject params on guard methods from the DI container.
42
+ */
43
+ private _checkGuards;
44
+ /**
45
+ * Resolve @inject parameters for a method.
46
+ * Uses the same INJECT_PARAMS metadata as @factory.
47
+ */
48
+ private _resolveInjectParams;
49
+ }
50
+ //# sourceMappingURL=router.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/router/router.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,KAAK,UAAU,EAAyB,MAAM,QAAQ,CAAC;AAMhE,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3B;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CACpB;AAED,qBAAa,UAAU;IACrB,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,OAAO,CAAC,QAAQ,CAAmD;gBAEvD,IAAI,GAAE,aAAkB;IAIpC,8CAA8C;IAC9C,IAAI,OAAO,IAAI,QAAQ,CAAC,SAAS,CAAC,CAEjC;IAED,oEAAoE;IACpE,KAAK,IAAI,MAAM,IAAI;IAMnB,yBAAyB;IACnB,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWrC,gDAAgD;IAC1C,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAU1C,yBAAyB;IACzB,IAAI,IAAI,IAAI;IAIZ,4BAA4B;IAC5B,OAAO,IAAI,IAAI;IAIf,yCAAyC;IACzC,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAI1B,4EAA4E;IAC5E,OAAO,CAAC,QAAQ;IAchB;;;;;;OAMG;YACW,YAAY;IAwC1B;;;OAGG;IACH,OAAO,CAAC,oBAAoB;CAe7B"}
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Loom Router — LoomRouter service
3
+ *
4
+ * Dual-mode (hash/history) router with named guard support.
5
+ * Register via app.use(new LoomRouter({ mode: "hash" }))
6
+ */
7
+ import { bus } from "../bus";
8
+ import { HashMode, HistoryMode } from "./mode";
9
+ import { matchRoute, GUARD_HANDLERS, guardRegistry } from "./route";
10
+ import { RouteChanged } from "./events";
11
+ import { INJECT_PARAMS } from "../decorators/symbols";
12
+ import { app } from "../app";
13
+ export class LoomRouter {
14
+ mode;
15
+ _current = { path: "/", params: {}, tag: null };
16
+ constructor(opts = {}) {
17
+ this.mode = opts.mode === "history" ? new HistoryMode() : new HashMode();
18
+ }
19
+ /** Current route info (read-only snapshot) */
20
+ get current() {
21
+ return this._current;
22
+ }
23
+ /** Start listening for URL changes and resolve the initial route */
24
+ start() {
25
+ const cleanup = this.mode.listen(() => this._resolve());
26
+ this._resolve();
27
+ return cleanup;
28
+ }
29
+ /** Navigate to a path */
30
+ async go(path) {
31
+ const allowed = await this._checkGuards(path);
32
+ if (allowed === false)
33
+ return;
34
+ if (typeof allowed === "string") {
35
+ // Redirect
36
+ return this.go(allowed);
37
+ }
38
+ this.mode.write(path);
39
+ this._resolve();
40
+ }
41
+ /** Navigate without creating a history entry */
42
+ async replace(path) {
43
+ const allowed = await this._checkGuards(path);
44
+ if (allowed === false)
45
+ return;
46
+ if (typeof allowed === "string") {
47
+ return this.replace(allowed);
48
+ }
49
+ this.mode.replace(path);
50
+ this._resolve();
51
+ }
52
+ /** Go back in history */
53
+ back() {
54
+ history.back();
55
+ }
56
+ /** Go forward in history */
57
+ forward() {
58
+ history.forward();
59
+ }
60
+ /** Build an href for the current mode */
61
+ href(path) {
62
+ return this.mode.href(path);
63
+ }
64
+ /** Resolve the current URL against the route table and emit RouteChanged */
65
+ _resolve() {
66
+ const path = this.mode.read();
67
+ const match = matchRoute(path);
68
+ const previous = this._current.path;
69
+ this._current = {
70
+ path,
71
+ params: match?.params ?? {},
72
+ tag: match?.entry.tag ?? null,
73
+ };
74
+ bus.emit(new RouteChanged(path, this._current.params, previous));
75
+ }
76
+ /**
77
+ * Run guards for the target route.
78
+ *
79
+ * Checks named guards from @route({ guards: [...] }) first,
80
+ * then falls back to @guard methods on the component prototype.
81
+ * Resolves @inject params on guard methods from the DI container.
82
+ */
83
+ async _checkGuards(path) {
84
+ const match = matchRoute(path);
85
+ if (!match)
86
+ return true; // No route = no guards
87
+ // Collect guards to run: named guards from route entry + prototype guards
88
+ const namedGuards = match.entry.guards ?? [];
89
+ const protoGuards = match.entry.ctor.prototype?.[GUARD_HANDLERS] ?? [];
90
+ // No guards at all = allow
91
+ if (namedGuards.length === 0 && protoGuards.length === 0)
92
+ return true;
93
+ // 1. Run named guards from the global registry
94
+ for (const guardName of namedGuards) {
95
+ const reg = guardRegistry.get(guardName);
96
+ if (!reg) {
97
+ console.warn(`[LoomRouter] Guard "${guardName}" not found in registry`);
98
+ continue;
99
+ }
100
+ const args = this._resolveInjectParams(reg.proto, reg.key);
101
+ const result = await reg.proto[reg.key].apply(reg.proto, args);
102
+ if (result === false)
103
+ return false;
104
+ if (typeof result === "string")
105
+ return result;
106
+ }
107
+ // 2. Run legacy @guard methods on the component prototype
108
+ const proto = match.entry.ctor.prototype;
109
+ for (const key of protoGuards) {
110
+ // Skip if already run as a named guard
111
+ if (namedGuards.some((n) => guardRegistry.get(n)?.key === key))
112
+ continue;
113
+ const args = this._resolveInjectParams(proto, key);
114
+ const result = await proto[key].apply(proto, args);
115
+ if (result === false)
116
+ return false;
117
+ if (typeof result === "string")
118
+ return result;
119
+ }
120
+ return true;
121
+ }
122
+ /**
123
+ * Resolve @inject parameters for a method.
124
+ * Uses the same INJECT_PARAMS metadata as @factory.
125
+ */
126
+ _resolveInjectParams(proto, method) {
127
+ const injectMeta = proto[INJECT_PARAMS] ?? [];
128
+ const methodParams = injectMeta
129
+ .filter((m) => m.method === method)
130
+ .sort((a, b) => a.index - b.index);
131
+ if (methodParams.length === 0)
132
+ return [];
133
+ const args = [];
134
+ for (const param of methodParams) {
135
+ args[param.index] = app.get(param.key);
136
+ }
137
+ return args;
138
+ }
139
+ }
140
+ //# sourceMappingURL=router.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.js","sourceRoot":"","sources":["../../src/router/router.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Loom — Storage Medium
3
+ *
4
+ * Pluggable persistence for Reactive<T> and CollectionStore<T>.
5
+ * Three built-in implementations: Memory (default), Local, Session.
6
+ * Implement StorageMedium for custom backends (IndexedDB, NATS KV, REST, etc).
7
+ */
8
+ /** Sync read/write contract for a storage medium */
9
+ export interface StorageMedium {
10
+ get(key: string): string | null;
11
+ set(key: string, value: string): void;
12
+ remove(key: string): void;
13
+ }
14
+ /** Options bag for persistent Reactive/CollectionStore */
15
+ export interface PersistOptions {
16
+ /** Storage key — must be unique across your app */
17
+ key: string;
18
+ /** Storage medium — defaults to MemoryStorage (no persistence) */
19
+ storage: StorageMedium;
20
+ }
21
+ /**
22
+ * In-memory storage. No persistence — values lost on page reload.
23
+ * This is the implicit default when no storage is configured.
24
+ */
25
+ export declare class MemoryStorage implements StorageMedium {
26
+ private data;
27
+ get(key: string): string | null;
28
+ set(key: string, value: string): void;
29
+ remove(key: string): void;
30
+ }
31
+ /**
32
+ * localStorage wrapper — survives page reloads and browser restarts.
33
+ *
34
+ * ```ts
35
+ * const count = new Reactive(0, { key: "app:count", storage: new LocalMedium() });
36
+ * ```
37
+ */
38
+ export declare class LocalMedium implements StorageMedium {
39
+ get(key: string): string | null;
40
+ set(key: string, value: string): void;
41
+ remove(key: string): void;
42
+ }
43
+ /**
44
+ * sessionStorage wrapper — survives page reloads but not browser restarts.
45
+ *
46
+ * ```ts
47
+ * const temp = new Reactive("", { key: "app:temp", storage: new SessionMedium() });
48
+ * ```
49
+ */
50
+ export declare class SessionMedium implements StorageMedium {
51
+ get(key: string): string | null;
52
+ set(key: string, value: string): void;
53
+ remove(key: string): void;
54
+ }
55
+ //# sourceMappingURL=storage.d.ts.map