@supersoniks/concorde 4.2.1 → 4.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 (55) hide show
  1. package/README.md +163 -0
  2. package/build-infos.json +1 -1
  3. package/concorde-core.bundle.js +175 -171
  4. package/concorde-core.es.js +2490 -2246
  5. package/dist/concorde-core.bundle.js +175 -171
  6. package/dist/concorde-core.es.js +2490 -2246
  7. package/package.json +22 -1
  8. package/php/get-challenge.php +34 -0
  9. package/php/some-service.php +42 -0
  10. package/scripts/pre-build.mjs +4 -0
  11. package/src/core/_types/endpoint.ts +4 -0
  12. package/src/core/_types/key.ts +1 -0
  13. package/src/core/components/functional/example/example.ts +38 -6
  14. package/src/core/decorators/Subscriber.ts +2 -0
  15. package/src/core/decorators/api.spec.ts +150 -0
  16. package/src/core/decorators/api.ts +244 -0
  17. package/src/core/decorators/subscriber/bind.ts +57 -145
  18. package/src/core/decorators/subscriber/dynamicPath.ts +77 -0
  19. package/src/core/decorators/subscriber/dynamicPropertyWatch.ts +105 -0
  20. package/src/core/decorators/subscriber/onAssign.ts +11 -147
  21. package/src/core/decorators/subscriber/publish.spec.ts +21 -0
  22. package/src/core/decorators/subscriber/publish.ts +148 -0
  23. package/src/core/decorators/subscriber/publisherPath.ts +13 -0
  24. package/src/core/decorators/subscriber/subscribe.spec.ts +21 -0
  25. package/src/core/decorators/subscriber/subscribe.ts +32 -0
  26. package/src/core/decorators/subscriber/subscribe.type-test.ts +32 -0
  27. package/src/core/utils/api.ts +83 -15
  28. package/src/core/utils/dataProviderKey.spec.ts +34 -0
  29. package/src/core/utils/dataProviderKey.ts +86 -0
  30. package/src/core/utils/endpoint.spec.ts +41 -0
  31. package/src/core/utils/endpoint.ts +87 -0
  32. package/src/decorators.ts +14 -0
  33. package/src/docs/{_misc → _decorators}/ancestor-attribute.md +15 -31
  34. package/src/docs/_decorators/bind.md +164 -0
  35. package/src/docs/_decorators/get.md +65 -0
  36. package/src/docs/_decorators/publish.md +54 -0
  37. package/src/docs/_decorators/subscribe.md +36 -0
  38. package/src/docs/_misc/dataProviderKey.md +135 -0
  39. package/src/docs/_misc/endpoint.md +42 -0
  40. package/src/docs/example/decorators-demo-bind-demos.ts +210 -0
  41. package/src/docs/example/decorators-demo-geo.ts +45 -0
  42. package/src/docs/example/decorators-demo-init.ts +228 -0
  43. package/src/docs/example/decorators-demo-subscribe-publish-get-demos.ts +324 -0
  44. package/src/docs/example/decorators-demo.ts +12 -459
  45. package/src/docs/navigation/navigation.ts +27 -10
  46. package/src/docs/search/docs-search.json +1059 -609
  47. package/src/tsconfig-model.json +1 -1
  48. package/src/tsconfig.json +65 -1
  49. package/src/tsconfig.tsbuildinfo +1 -1
  50. package/src/utils.ts +8 -1
  51. package/vite.config.mts +11 -0
  52. package/src/docs/_misc/bind.md +0 -362
  53. /package/src/docs/{_misc → _decorators}/auto-subscribe.md +0 -0
  54. /package/src/docs/{_misc → _decorators}/on-assign.md +0 -0
  55. /package/src/docs/{_misc → _decorators}/wait-for-ancestors.md +0 -0
@@ -1,147 +1,23 @@
1
- import { Objects } from "@supersoniks/concorde/utils";
2
-
3
- import DataProvider, { PublisherManager } from "../../utils/PublisherProxy";
1
+ import type {
2
+ DataProviderKey,
3
+ DataProviderKeyHost,
4
+ } from "../../utils/dataProviderKey";
4
5
  import { ConnectedComponent, setSubscribable } from "./common";
5
-
6
- const dynamicWatcherStore = Symbol("__bindDynamicWatcherStore__");
7
- const dynamicWillUpdateHookedStore = Symbol("__bindDynamicWillUpdateHooked__");
8
-
9
- function registerDynamicWatcher(
10
- instance: any,
11
- propertyName: string,
12
- onChange: () => void
13
- ) {
14
- const key = String(propertyName);
15
- ensureWillUpdateHook(instance);
16
- if (!instance[dynamicWatcherStore]) {
17
- Object.defineProperty(instance, dynamicWatcherStore, {
18
- value: new Map<string, Set<() => void>>(),
19
- enumerable: false,
20
- configurable: false,
21
- writable: false,
22
- });
23
- }
24
- const watcherMap = instance[dynamicWatcherStore] as Map<
25
- string,
26
- Set<() => void>
27
- >;
28
- if (!watcherMap.has(key)) {
29
- watcherMap.set(key, new Set());
30
- }
31
- const watchers = watcherMap.get(key)!;
32
- watchers.add(onChange);
33
- return () => {
34
- watchers.delete(onChange);
35
- if (watchers.size === 0) {
36
- watcherMap.delete(key);
37
- }
38
- };
39
- }
40
-
41
- function ensureWillUpdateHook(instance: any) {
42
- const proto = Object.getPrototypeOf(instance);
43
- if (!proto || proto[dynamicWillUpdateHookedStore]) return;
44
- const originalWillUpdate = Object.prototype.hasOwnProperty.call(
45
- proto,
46
- "willUpdate"
47
- )
48
- ? proto.willUpdate
49
- : Object.getPrototypeOf(proto)?.willUpdate;
50
- proto.willUpdate = function (changedProperties?: Map<unknown, unknown>) {
51
- const handlers = this[dynamicWatcherStore] as
52
- | Map<string, Set<() => void>>
53
- | undefined;
54
- if (handlers && handlers.size > 0) {
55
- if (changedProperties && changedProperties.size > 0) {
56
- changedProperties.forEach((_value, dependency) => {
57
- const callbacks = handlers.get(String(dependency));
58
- if (callbacks) {
59
- callbacks.forEach((cb) => cb());
60
- }
61
- });
62
- } else {
63
- handlers.forEach((callbacks) => callbacks.forEach((cb) => cb()));
64
- }
65
- }
66
- originalWillUpdate?.call(this, changedProperties);
67
- };
68
- proto[dynamicWillUpdateHookedStore] = true;
69
- }
70
-
71
- function extractDynamicDependencies(path: string) {
72
- const patterns = [/\$\{([^}]+)\}/g, /\{\$([^}]+)\}/g];
73
- const deps = new Set<string>();
74
- for (const pattern of patterns) {
75
- let match;
76
- while ((match = pattern.exec(path)) !== null) {
77
- const cleaned = cleanPlaceholder(match[1]);
78
- if (!cleaned) continue;
79
- const [root] = cleaned.split(".");
80
- if (root) deps.add(root);
81
- }
82
- }
83
- return Array.from(deps);
84
- }
85
-
86
- function cleanPlaceholder(value: string) {
87
- return value.trim().replace(/^this\./, "");
88
- }
89
-
90
- function resolveDynamicPath(component: any, template: string) {
91
- let missing = false;
92
- const replaceValue = (_match: string, expression: string) => {
93
- const cleaned = cleanPlaceholder(expression);
94
- const resolved = getValueFromExpression(component, cleaned);
95
- if (resolved === undefined || resolved === null) {
96
- missing = true;
97
- return "";
98
- }
99
- return `${resolved}`;
100
- };
101
- const resolvedPath = template
102
- .replace(/\$\{([^}]+)\}/g, replaceValue)
103
- .replace(/\{\$([^}]+)\}/g, replaceValue)
104
- .trim();
105
- if (missing || !resolvedPath.length) {
106
- return { ready: false, path: null };
107
- }
108
- const segments = resolvedPath.split(".").filter(Boolean);
109
- if (segments.length === 0 || !segments[0]) {
110
- return { ready: false, path: null };
111
- }
112
- return { ready: true, path: resolvedPath };
113
- }
114
-
115
- function getValueFromExpression(component: any, expression: string) {
116
- if (!expression) return undefined;
117
- const segments = expression.split(".").filter(Boolean);
118
- if (segments.length === 0) return undefined;
119
- let current: unknown = component;
120
- for (const segment of segments) {
121
- if (
122
- current === undefined ||
123
- current === null ||
124
- typeof current !== "object"
125
- ) {
126
- return undefined;
127
- }
128
- current = (current as Record<string, unknown>)[segment];
129
- }
130
- return current;
131
- }
132
-
133
- function getPublisherFromPath(path: string) {
134
- const segments = path.split(".").filter((segment) => segment.length > 0);
135
- if (segments.length === 0) return null;
136
- const dataProvider = segments.shift() || "";
137
- if (!dataProvider) return null;
138
- let publisher = PublisherManager.get(dataProvider);
139
- if (!publisher) return null;
140
- publisher = Objects.traverse(publisher, segments);
141
- return publisher as DataProvider | null;
142
- }
143
-
144
- export function bind(path: string, options?: { reflect?: boolean }) {
6
+ import {
7
+ extractDynamicDependencies,
8
+ hasPath,
9
+ resolveDynamicPath,
10
+ } from "./dynamicPath";
11
+ import {
12
+ bindDynamicWatchKeys,
13
+ registerDynamicPropertyWatcher,
14
+ } from "./dynamicPropertyWatch";
15
+ import { getPublisherFromPath } from "./publisherPath";
16
+
17
+ function bindImpl(
18
+ path: string,
19
+ options?: { reflect?: boolean }
20
+ ): (target: unknown, propertyKey: string) => void {
145
21
  const reflect = options?.reflect ?? false;
146
22
  const dynamicDependencies = extractDynamicDependencies(path);
147
23
  const isDynamicPath = dynamicDependencies.length > 0;
@@ -276,8 +152,10 @@ export function bind(path: string, options?: { reflect?: boolean }) {
276
152
 
277
153
  if (isDynamicPath) {
278
154
  for (const dependency of dynamicDependencies) {
279
- const unsubscribe = registerDynamicWatcher(
280
- component as Record<string, unknown>,
155
+ const unsubscribe = registerDynamicPropertyWatcher(
156
+ bindDynamicWatchKeys.watcherStore,
157
+ bindDynamicWatchKeys.hooked,
158
+ component,
281
159
  dependency,
282
160
  () => refreshSubscription()
283
161
  );
@@ -303,3 +181,37 @@ export function bind(path: string, options?: { reflect?: boolean }) {
303
181
  };
304
182
  }
305
183
 
184
+ /**
185
+ * Bidirectional binding to a publisher path. Subscribes to changes and optionally reflects writes back.
186
+ * Accepts either a string path (legacy) or DataProviderKey&lt;T&gt; for type-safe binding.
187
+ * Supports dynamic paths: use placeholders like "users.${userIndex}" in the path or DataProviderKey.
188
+ *
189
+ * @example
190
+ * // String path (legacy):
191
+ * @bind("demoData.firstName")
192
+ * @state()
193
+ * firstName = "";
194
+ *
195
+ * @example
196
+ * // DataProviderKey with type validation:
197
+ * const dataKey = new DataProviderKey<Data>("data");
198
+ * @bind(dataKey.count, { reflect: true })
199
+ * @state()
200
+ * count: number = 0;
201
+ */
202
+ export function bind(path: string, options?: { reflect?: boolean }): (target: unknown, propertyKey: string) => void;
203
+ export function bind<T, U = any>(
204
+ key: DataProviderKey<T, U>,
205
+ options?: { reflect?: boolean },
206
+ ): <K extends string>(
207
+ target: DataProviderKeyHost<U> & { [P in K]?: T | null | undefined },
208
+ propertyKey: K,
209
+ ) => void;
210
+ export function bind(
211
+ pathOrKey: string | DataProviderKey<unknown, unknown>,
212
+ options?: { reflect?: boolean },
213
+ ): (target: unknown, propertyKey: string) => void {
214
+ const path = hasPath(pathOrKey) ? pathOrKey.path : pathOrKey;
215
+ return bindImpl(path, options);
216
+ }
217
+
@@ -0,0 +1,77 @@
1
+ /** Lit / décorateurs : chemins publisher avec `${prop}` ou `{$prop}`. */
2
+
3
+ export function cleanPlaceholder(value: string): string {
4
+ return value.trim().replace(/^this\./, "");
5
+ }
6
+
7
+ export function getValueFromExpression(
8
+ component: unknown,
9
+ expression: string,
10
+ ): unknown {
11
+ if (!expression) return undefined;
12
+ const segments = expression.split(".").filter(Boolean);
13
+ if (segments.length === 0) return undefined;
14
+ let current: unknown = component;
15
+ for (const segment of segments) {
16
+ if (
17
+ current === undefined ||
18
+ current === null ||
19
+ typeof current !== "object"
20
+ ) {
21
+ return undefined;
22
+ }
23
+ current = (current as Record<string, unknown>)[segment];
24
+ }
25
+ return current;
26
+ }
27
+
28
+ export function resolveDynamicPath(
29
+ component: unknown,
30
+ template: string,
31
+ ): { ready: boolean; path: string | null } {
32
+ let missing = false;
33
+ const replaceValue = (_match: string, expression: string) => {
34
+ const cleaned = cleanPlaceholder(expression);
35
+ const resolved = getValueFromExpression(component, cleaned);
36
+ if (resolved === undefined || resolved === null) {
37
+ missing = true;
38
+ return "";
39
+ }
40
+ return `${resolved}`;
41
+ };
42
+ const resolvedPath = template
43
+ .replace(/\$\{([^}]+)\}/g, replaceValue)
44
+ .replace(/\{\$([^}]+)\}/g, replaceValue)
45
+ .trim();
46
+ if (missing || !resolvedPath.length) {
47
+ return { ready: false, path: null };
48
+ }
49
+ const segments = resolvedPath.split(".").filter(Boolean);
50
+ if (segments.length === 0 || !segments[0]) {
51
+ return { ready: false, path: null };
52
+ }
53
+ return { ready: true, path: resolvedPath };
54
+ }
55
+
56
+ export function extractDynamicDependencies(path: string): string[] {
57
+ const patterns = [/\$\{([^}]+)\}/g, /\{\$([^}]+)\}/g];
58
+ const deps = new Set<string>();
59
+ for (const pattern of patterns) {
60
+ let match;
61
+ while ((match = pattern.exec(path)) !== null) {
62
+ const cleaned = (match[1] || "").trim().replace(/^this\./, "");
63
+ if (!cleaned) continue;
64
+ const [root] = cleaned.split(".");
65
+ if (root) deps.add(root);
66
+ }
67
+ }
68
+ return Array.from(deps);
69
+ }
70
+ export function hasPath(obj: unknown): obj is { path: string } {
71
+ return (
72
+ typeof obj === "object" &&
73
+ obj !== null &&
74
+ "path" in obj &&
75
+ typeof (obj as { path: unknown }).path === "string"
76
+ );
77
+ }
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Enregistre des callbacks sur des propriétés Lit (via `willUpdate`) pour
3
+ * réagir aux changements de segments dynamiques `${…}` dans les chemins.
4
+ * Chaque décorateur utilise des clés de stockage distinctes pour éviter les collisions.
5
+ */
6
+
7
+ type InstanceStores = Record<PropertyKey, unknown>;
8
+
9
+ export function registerDynamicPropertyWatcher(
10
+ watcherStoreKey: PropertyKey,
11
+ hookedStoreKey: PropertyKey,
12
+ instance: object,
13
+ propertyName: string,
14
+ onChange: () => void,
15
+ ): () => void {
16
+ const inst = instance as InstanceStores;
17
+ const key = String(propertyName);
18
+ ensureDynamicPropertiesWillUpdate(
19
+ watcherStoreKey,
20
+ hookedStoreKey,
21
+ instance,
22
+ );
23
+ if (!inst[watcherStoreKey]) {
24
+ Object.defineProperty(inst, watcherStoreKey, {
25
+ value: new Map<string, Set<() => void>>(),
26
+ enumerable: false,
27
+ configurable: false,
28
+ writable: false,
29
+ });
30
+ }
31
+ const watcherMap = inst[watcherStoreKey] as Map<string, Set<() => void>>;
32
+ if (!watcherMap.has(key)) {
33
+ watcherMap.set(key, new Set());
34
+ }
35
+ const watchers = watcherMap.get(key)!;
36
+ watchers.add(onChange);
37
+ return () => {
38
+ watchers.delete(onChange);
39
+ if (watchers.size === 0) {
40
+ watcherMap.delete(key);
41
+ }
42
+ };
43
+ }
44
+
45
+ export function ensureDynamicPropertiesWillUpdate(
46
+ watcherStoreKey: PropertyKey,
47
+ hookedStoreKey: PropertyKey,
48
+ instance: object,
49
+ ): void {
50
+ const proto = Object.getPrototypeOf(instance);
51
+ if (!proto || (proto as InstanceStores)[hookedStoreKey]) return;
52
+ const originalWillUpdate = Object.prototype.hasOwnProperty.call(
53
+ proto,
54
+ "willUpdate",
55
+ )
56
+ ? (proto as InstanceStores).willUpdate
57
+ : (Object.getPrototypeOf(proto) as InstanceStores)?.willUpdate;
58
+ (proto as InstanceStores).willUpdate = function (
59
+ changedProperties?: Map<unknown, unknown>,
60
+ ) {
61
+ const handlers = (this as InstanceStores)[watcherStoreKey] as
62
+ | Map<string, Set<() => void>>
63
+ | undefined;
64
+ if (handlers && handlers.size > 0) {
65
+ if (changedProperties && changedProperties.size > 0) {
66
+ changedProperties.forEach((_value, dependency) => {
67
+ const callbacks = handlers.get(String(dependency));
68
+ if (callbacks) {
69
+ callbacks.forEach((cb) => cb());
70
+ }
71
+ });
72
+ } else {
73
+ handlers.forEach((callbacks) => callbacks.forEach((cb) => cb()));
74
+ }
75
+ }
76
+ if (typeof originalWillUpdate === "function") {
77
+ originalWillUpdate.call(this, changedProperties);
78
+ }
79
+ };
80
+ (proto as InstanceStores)[hookedStoreKey] = true;
81
+ }
82
+
83
+ /** Clés utilisées par `@bind`. */
84
+ export const bindDynamicWatchKeys = {
85
+ watcherStore: Symbol("__bindDynamicWatcherStore__"),
86
+ hooked: Symbol("__bindDynamicWillUpdateHooked__"),
87
+ } as const;
88
+
89
+ /** Clés utilisées par `@publish`. */
90
+ export const publishDynamicWatchKeys = {
91
+ watcherStore: "__publishDynamicWatcherStore__",
92
+ hooked: "__publishDynamicWillUpdateHooked__",
93
+ } as const;
94
+
95
+ /** Clés utilisées par `@get`. */
96
+ export const getDynamicWatchKeys = {
97
+ watcherStore: "__getDynamicWatcherStore__",
98
+ hooked: "__getDynamicWillUpdateHooked__",
99
+ } as const;
100
+
101
+ /** Clés utilisées par `@onAssign`. */
102
+ export const onAssignDynamicWatchKeys = {
103
+ watcherStore: Symbol("__onAssignDynamicWatcherStore__"),
104
+ hooked: Symbol("__onAssignDynamicWillUpdateHooked__"),
105
+ } as const;
@@ -1,149 +1,11 @@
1
- import { Objects } from "@supersoniks/concorde/utils";
2
-
3
- import DataProvider, { PublisherManager } from "../../utils/PublisherProxy";
1
+ import DataProvider from "../../utils/PublisherProxy";
4
2
  import { ConnectedComponent, setSubscribable } from "./common";
5
-
6
- const dynamicWatcherStore = Symbol("__onAssignDynamicWatcherStore__");
7
- const dynamicWillUpdateHookedStore = Symbol(
8
- "__onAssignDynamicWillUpdateHooked__"
9
- );
10
-
11
- function registerDynamicWatcher(
12
- instance: any,
13
- propertyName: string,
14
- onChange: () => void
15
- ) {
16
- const key = String(propertyName);
17
- ensureWillUpdateHook(instance);
18
- if (!instance[dynamicWatcherStore]) {
19
- Object.defineProperty(instance, dynamicWatcherStore, {
20
- value: new Map<string, Set<() => void>>(),
21
- enumerable: false,
22
- configurable: false,
23
- writable: false,
24
- });
25
- }
26
- const watcherMap = instance[dynamicWatcherStore] as Map<
27
- string,
28
- Set<() => void>
29
- >;
30
- if (!watcherMap.has(key)) {
31
- watcherMap.set(key, new Set());
32
- }
33
- const watchers = watcherMap.get(key)!;
34
- watchers.add(onChange);
35
- return () => {
36
- watchers.delete(onChange);
37
- if (watchers.size === 0) {
38
- watcherMap.delete(key);
39
- }
40
- };
41
- }
42
-
43
- function ensureWillUpdateHook(instance: any) {
44
- const proto = Object.getPrototypeOf(instance);
45
- if (!proto || proto[dynamicWillUpdateHookedStore]) return;
46
-
47
- const originalWillUpdate = Object.prototype.hasOwnProperty.call(
48
- proto,
49
- "willUpdate"
50
- )
51
- ? proto.willUpdate
52
- : Object.getPrototypeOf(proto)?.willUpdate;
53
-
54
- proto.willUpdate = function (changedProperties?: Map<unknown, unknown>) {
55
- const handlers = this[dynamicWatcherStore] as
56
- | Map<string, Set<() => void>>
57
- | undefined;
58
- if (handlers && handlers.size > 0) {
59
- if (changedProperties && changedProperties.size > 0) {
60
- changedProperties.forEach((_value, dependency) => {
61
- const callbacks = handlers.get(String(dependency));
62
- if (callbacks) {
63
- callbacks.forEach((cb) => cb());
64
- }
65
- });
66
- } else {
67
- handlers.forEach((callbacks) => callbacks.forEach((cb) => cb()));
68
- }
69
- }
70
- originalWillUpdate?.call(this, changedProperties);
71
- };
72
- proto[dynamicWillUpdateHookedStore] = true;
73
- }
74
-
75
- function extractDynamicDependencies(path: string) {
76
- const patterns = [/\$\{([^}]+)\}/g, /\{\$([^}]+)\}/g];
77
- const deps = new Set<string>();
78
- for (const pattern of patterns) {
79
- let match;
80
- while ((match = pattern.exec(path)) !== null) {
81
- const cleaned = cleanPlaceholder(match[1]);
82
- if (!cleaned) continue;
83
- const [root] = cleaned.split(".");
84
- if (root) deps.add(root);
85
- }
86
- }
87
- return Array.from(deps);
88
- }
89
-
90
- function cleanPlaceholder(value: string) {
91
- return value.trim().replace(/^this\./, "");
92
- }
93
-
94
- function resolveDynamicPath(component: any, template: string) {
95
- let missing = false;
96
- const replaceValue = (_match: string, expression: string) => {
97
- const cleaned = cleanPlaceholder(expression);
98
- const resolved = getValueFromExpression(component, cleaned);
99
- if (resolved === undefined || resolved === null) {
100
- missing = true;
101
- return "";
102
- }
103
- return `${resolved}`;
104
- };
105
- const resolvedPath = template
106
- .replace(/\$\{([^}]+)\}/g, replaceValue)
107
- .replace(/\{\$([^}]+)\}/g, replaceValue)
108
- .trim();
109
- if (missing || !resolvedPath.length) {
110
- return { ready: false, path: null };
111
- }
112
- const segments = resolvedPath.split(".").filter(Boolean);
113
- if (segments.length === 0 || !segments[0]) {
114
- return { ready: false, path: null };
115
- }
116
- return { ready: true, path: resolvedPath };
117
- }
118
-
119
- function getValueFromExpression(component: any, expression: string) {
120
- if (!expression) return undefined;
121
- const segments = expression.split(".").filter(Boolean);
122
- if (segments.length === 0) return undefined;
123
- let current: unknown = component;
124
- for (const segment of segments) {
125
- if (
126
- current === undefined ||
127
- current === null ||
128
- typeof current !== "object"
129
- ) {
130
- return undefined;
131
- }
132
- current = (current as Record<string, unknown>)[segment];
133
- }
134
- return current;
135
- }
136
-
137
- function getPublisherFromPath(path: string) {
138
- const segments = path.split(".").filter((segment) => segment.length > 0);
139
- if (segments.length === 0) return null;
140
- const dataProvider = segments.shift() || "";
141
- if (!dataProvider) return null;
142
- let publisher = PublisherManager.get(dataProvider);
143
- if (!publisher) return null;
144
- publisher = Objects.traverse(publisher, segments);
145
- return publisher as DataProvider | null;
146
- }
3
+ import { extractDynamicDependencies, resolveDynamicPath } from "./dynamicPath";
4
+ import {
5
+ onAssignDynamicWatchKeys,
6
+ registerDynamicPropertyWatcher,
7
+ } from "./dynamicPropertyWatch";
8
+ import { getPublisherFromPath } from "./publisherPath";
147
9
 
148
10
  type Callback = (...values: unknown[]) => void;
149
11
  type PathConfiguration = {
@@ -279,8 +141,10 @@ export function onAssign(...values: Array<string>) {
279
141
  for (const conf of confs) {
280
142
  if (conf.pathConfig.isDynamic) {
281
143
  for (const dependency of conf.pathConfig.dynamicDependencies) {
282
- const unsubscribe = registerDynamicWatcher(
283
- component as Record<string, unknown>,
144
+ const unsubscribe = registerDynamicPropertyWatcher(
145
+ onAssignDynamicWatchKeys.watcherStore,
146
+ onAssignDynamicWatchKeys.hooked,
147
+ component,
284
148
  dependency,
285
149
  () => refreshSubscriptions()
286
150
  );
@@ -0,0 +1,21 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { DataProviderKey } from "../../utils/dataProviderKey";
3
+ import { publish } from "./publish";
4
+
5
+ type FormData = {
6
+ email: string;
7
+ };
8
+
9
+ const formKey = new DataProviderKey<FormData>("formData");
10
+
11
+ describe("publish", () => {
12
+ it("decorates property and allows get/set", () => {
13
+ class TestClass {
14
+ @publish(formKey.email)
15
+ email = "";
16
+ }
17
+ const instance = new TestClass();
18
+ instance.email = "test@example.com";
19
+ expect(instance.email).toBe("test@example.com");
20
+ });
21
+ });