sygnal 5.3.7 → 5.4.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 (226) hide show
  1. package/README.md +53 -38
  2. package/dist/astro/client.cjs.js +18 -8106
  3. package/dist/astro/client.cjs.js.map +1 -0
  4. package/dist/astro/client.mjs +17 -8105
  5. package/dist/astro/client.mjs.map +1 -0
  6. package/dist/astro/index.cjs.js +768 -9
  7. package/dist/astro/index.cjs.js.map +1 -0
  8. package/dist/astro/index.mjs +768 -9
  9. package/dist/astro/index.mjs.map +1 -0
  10. package/dist/astro/server.cjs.js +4 -1
  11. package/dist/astro/server.cjs.js.map +1 -0
  12. package/dist/astro/server.mjs +4 -1
  13. package/dist/astro/server.mjs.map +1 -0
  14. package/dist/diagnostics.cjs.js +1297 -0
  15. package/dist/diagnostics.cjs.js.map +1 -0
  16. package/dist/diagnostics.esm.js +1267 -0
  17. package/dist/diagnostics.esm.js.map +1 -0
  18. package/dist/index.cjs.js +2981 -1386
  19. package/dist/index.cjs.js.map +1 -0
  20. package/dist/index.d.ts +848 -111
  21. package/dist/index.esm.js +2997 -1411
  22. package/dist/index.esm.js.map +1 -0
  23. package/dist/jsx-dev-runtime.cjs.js +15 -9
  24. package/dist/jsx-dev-runtime.cjs.js.map +1 -0
  25. package/dist/jsx-dev-runtime.esm.js +15 -9
  26. package/dist/jsx-dev-runtime.esm.js.map +1 -0
  27. package/dist/jsx-runtime.cjs.js +15 -9
  28. package/dist/jsx-runtime.cjs.js.map +1 -0
  29. package/dist/jsx-runtime.esm.js +15 -9
  30. package/dist/jsx-runtime.esm.js.map +1 -0
  31. package/dist/jsx.cjs.js +15 -9
  32. package/dist/jsx.cjs.js.map +1 -0
  33. package/dist/jsx.esm.js +15 -9
  34. package/dist/jsx.esm.js.map +1 -0
  35. package/dist/sygnal.min.js +2 -1
  36. package/dist/sygnal.min.js.map +1 -0
  37. package/dist/vike/+config.cjs.js +1 -0
  38. package/dist/vike/+config.cjs.js.map +1 -0
  39. package/dist/vike/+config.js +1 -0
  40. package/dist/vike/+config.js.map +1 -0
  41. package/dist/vike/ClientOnly.cjs.js +1 -0
  42. package/dist/vike/ClientOnly.cjs.js.map +1 -0
  43. package/dist/vike/ClientOnly.mjs +1 -0
  44. package/dist/vike/ClientOnly.mjs.map +1 -0
  45. package/dist/vike/onRenderClient.cjs.js +1 -0
  46. package/dist/vike/onRenderClient.cjs.js.map +1 -0
  47. package/dist/vike/onRenderClient.mjs +1 -0
  48. package/dist/vike/onRenderClient.mjs.map +1 -0
  49. package/dist/vike/onRenderHtml.cjs.js +1 -0
  50. package/dist/vike/onRenderHtml.cjs.js.map +1 -0
  51. package/dist/vike/onRenderHtml.mjs +1 -0
  52. package/dist/vike/onRenderHtml.mjs.map +1 -0
  53. package/dist/vite/plugin.cjs.js +670 -44
  54. package/dist/vite/plugin.cjs.js.map +1 -0
  55. package/dist/vite/plugin.mjs +670 -44
  56. package/dist/vite/plugin.mjs.map +1 -0
  57. package/llms.txt +250 -0
  58. package/package.json +15 -6
  59. package/src/astro/client.ts +13 -2
  60. package/src/astro/index.d.ts +24 -1
  61. package/src/astro/index.ts +40 -9
  62. package/src/astro/server.ts +3 -1
  63. package/src/collection.ts +5 -2
  64. package/src/component.ts +375 -330
  65. package/src/cycle/dom/MainDOMSource.ts +4 -0
  66. package/src/cycle/dom/classNameModule.ts +74 -0
  67. package/src/cycle/dom/controlledInputModule.ts +33 -0
  68. package/src/cycle/dom/enrichEventStream.ts +9 -3
  69. package/src/cycle/dom/makeDOMDriver.ts +1 -2
  70. package/src/cycle/dom/mockDOMSource.ts +62 -8
  71. package/src/cycle/dom/modules.ts +5 -1
  72. package/src/cycle/state/Collection.ts +13 -3
  73. package/src/cycle/state/StateSource.ts +1 -1
  74. package/src/cycle/state/pickCombine.ts +35 -1
  75. package/src/cycle/state/withState.ts +1 -1
  76. package/src/extra/devtools.ts +8 -0
  77. package/src/extra/diagnostics/checks/collections.ts +67 -0
  78. package/src/extra/diagnostics/checks/dom.ts +241 -0
  79. package/src/extra/diagnostics/checks/events.ts +68 -0
  80. package/src/extra/diagnostics/checks/index.ts +109 -0
  81. package/src/extra/diagnostics/checks/inspect.ts +319 -0
  82. package/src/extra/diagnostics/checks/props.ts +51 -0
  83. package/src/extra/diagnostics/checks/public.d.ts +211 -0
  84. package/src/extra/diagnostics/checks/rxjsHints.ts +88 -0
  85. package/src/extra/diagnostics/checks/shared.ts +119 -0
  86. package/src/extra/diagnostics/checks/state.ts +55 -0
  87. package/src/extra/diagnostics/checks/strict.ts +112 -0
  88. package/src/extra/diagnostics/checks/wiring.ts +65 -0
  89. package/src/extra/diagnostics/codes.ts +232 -0
  90. package/src/extra/diagnostics/index.ts +328 -0
  91. package/src/extra/diagnostics/legacy.ts +59 -0
  92. package/src/extra/driverFactories.ts +106 -58
  93. package/src/extra/eventDriver.ts +4 -0
  94. package/src/extra/flatten.ts +75 -0
  95. package/src/extra/reducers.ts +12 -5
  96. package/src/extra/run.ts +11 -0
  97. package/src/extra/testing.ts +890 -180
  98. package/src/extra/xstreamExtras.ts +269 -0
  99. package/src/index.d.ts +428 -17
  100. package/src/index.ts +7 -6
  101. package/src/pragma/index.ts +14 -9
  102. package/src/switchable.ts +10 -8
  103. package/src/vike/types.ts +21 -4
  104. package/src/vite/plugin.d.ts +95 -2
  105. package/src/vite/plugin.ts +720 -54
  106. package/dist/astro/astro/client.d.ts +0 -5
  107. package/dist/astro/astro/index.d.ts +0 -16
  108. package/dist/astro/astro/server.d.ts +0 -12
  109. package/dist/astro/client.d.ts +0 -5
  110. package/dist/astro/client.esm.js +0 -4665
  111. package/dist/astro/collection.d.ts +0 -12
  112. package/dist/astro/component.d.ts +0 -22
  113. package/dist/astro/cycle/dom/BodyDOMSource.d.ts +0 -10
  114. package/dist/astro/cycle/dom/DOMSource.d.ts +0 -11
  115. package/dist/astro/cycle/dom/DocumentDOMSource.d.ts +0 -12
  116. package/dist/astro/cycle/dom/ElementFinder.d.ts +0 -8
  117. package/dist/astro/cycle/dom/EventDelegator.d.ts +0 -34
  118. package/dist/astro/cycle/dom/IsolateModule.d.ts +0 -23
  119. package/dist/astro/cycle/dom/MainDOMSource.d.ts +0 -32
  120. package/dist/astro/cycle/dom/PriorityQueue.d.ts +0 -7
  121. package/dist/astro/cycle/dom/ScopeChecker.d.ts +0 -9
  122. package/dist/astro/cycle/dom/SymbolTree.d.ts +0 -9
  123. package/dist/astro/cycle/dom/VNodeWrapper.d.ts +0 -8
  124. package/dist/astro/cycle/dom/enrichEventStream.d.ts +0 -24
  125. package/dist/astro/cycle/dom/fromEvent.d.ts +0 -6
  126. package/dist/astro/cycle/dom/index.d.ts +0 -8
  127. package/dist/astro/cycle/dom/isolate.d.ts +0 -9
  128. package/dist/astro/cycle/dom/makeDOMDriver.d.ts +0 -11
  129. package/dist/astro/cycle/dom/mockDOMSource.d.ts +0 -17
  130. package/dist/astro/cycle/dom/modules.d.ts +0 -5
  131. package/dist/astro/cycle/dom/snabbdom.d.ts +0 -22
  132. package/dist/astro/cycle/dom/styleModule.d.ts +0 -13
  133. package/dist/astro/cycle/dom/thunk.d.ts +0 -11
  134. package/dist/astro/cycle/dom/utils.d.ts +0 -8
  135. package/dist/astro/cycle/isolate/index.d.ts +0 -42
  136. package/dist/astro/cycle/run/adapt.d.ts +0 -6
  137. package/dist/astro/cycle/run/index.d.ts +0 -36
  138. package/dist/astro/cycle/run/internals.d.ts +0 -8
  139. package/dist/astro/cycle/run/types.d.ts +0 -49
  140. package/dist/astro/cycle/state/Collection.d.ts +0 -29
  141. package/dist/astro/cycle/state/StateSource.d.ts +0 -20
  142. package/dist/astro/cycle/state/index.d.ts +0 -5
  143. package/dist/astro/cycle/state/pickCombine.d.ts +0 -3
  144. package/dist/astro/cycle/state/pickMerge.d.ts +0 -3
  145. package/dist/astro/cycle/state/types.d.ts +0 -18
  146. package/dist/astro/cycle/state/withState.d.ts +0 -17
  147. package/dist/astro/extra/classes.d.ts +0 -7
  148. package/dist/astro/extra/devtools.d.ts +0 -78
  149. package/dist/astro/extra/dragDriver.d.ts +0 -20
  150. package/dist/astro/extra/driverFactories.d.ts +0 -12
  151. package/dist/astro/extra/eventDriver.d.ts +0 -9
  152. package/dist/astro/extra/exactState.d.ts +0 -1
  153. package/dist/astro/extra/hmr.d.ts +0 -17
  154. package/dist/astro/extra/logDriver.d.ts +0 -2
  155. package/dist/astro/extra/processDrag.d.ts +0 -19
  156. package/dist/astro/extra/processForm.d.ts +0 -15
  157. package/dist/astro/extra/run.d.ts +0 -13
  158. package/dist/astro/extra/xstreamCompat.d.ts +0 -5
  159. package/dist/astro/index.d.ts +0 -19
  160. package/dist/astro/index.esm.js +0 -27
  161. package/dist/astro/jsx-dev-runtime.d.ts +0 -1
  162. package/dist/astro/jsx-runtime.d.ts +0 -4
  163. package/dist/astro/jsx.d.ts +0 -2
  164. package/dist/astro/pragma/fn.d.ts +0 -7
  165. package/dist/astro/pragma/index.d.ts +0 -7
  166. package/dist/astro/pragma/is.d.ts +0 -10
  167. package/dist/astro/server.d.ts +0 -12
  168. package/dist/astro/server.esm.js +0 -25
  169. package/dist/astro/switchable.d.ts +0 -7
  170. package/dist/collection.d.ts +0 -12
  171. package/dist/component.d.ts +0 -22
  172. package/dist/cycle/dom/BodyDOMSource.d.ts +0 -10
  173. package/dist/cycle/dom/DOMSource.d.ts +0 -11
  174. package/dist/cycle/dom/DocumentDOMSource.d.ts +0 -12
  175. package/dist/cycle/dom/ElementFinder.d.ts +0 -8
  176. package/dist/cycle/dom/EventDelegator.d.ts +0 -34
  177. package/dist/cycle/dom/IsolateModule.d.ts +0 -23
  178. package/dist/cycle/dom/MainDOMSource.d.ts +0 -32
  179. package/dist/cycle/dom/PriorityQueue.d.ts +0 -7
  180. package/dist/cycle/dom/ScopeChecker.d.ts +0 -9
  181. package/dist/cycle/dom/SymbolTree.d.ts +0 -9
  182. package/dist/cycle/dom/VNodeWrapper.d.ts +0 -8
  183. package/dist/cycle/dom/enrichEventStream.d.ts +0 -24
  184. package/dist/cycle/dom/fromEvent.d.ts +0 -6
  185. package/dist/cycle/dom/index.d.ts +0 -8
  186. package/dist/cycle/dom/isolate.d.ts +0 -9
  187. package/dist/cycle/dom/makeDOMDriver.d.ts +0 -11
  188. package/dist/cycle/dom/mockDOMSource.d.ts +0 -17
  189. package/dist/cycle/dom/modules.d.ts +0 -5
  190. package/dist/cycle/dom/snabbdom.d.ts +0 -22
  191. package/dist/cycle/dom/styleModule.d.ts +0 -13
  192. package/dist/cycle/dom/thunk.d.ts +0 -11
  193. package/dist/cycle/dom/utils.d.ts +0 -8
  194. package/dist/cycle/isolate/index.d.ts +0 -42
  195. package/dist/cycle/run/adapt.d.ts +0 -6
  196. package/dist/cycle/run/index.d.ts +0 -36
  197. package/dist/cycle/run/internals.d.ts +0 -8
  198. package/dist/cycle/run/types.d.ts +0 -49
  199. package/dist/cycle/state/Collection.d.ts +0 -29
  200. package/dist/cycle/state/StateSource.d.ts +0 -20
  201. package/dist/cycle/state/index.d.ts +0 -5
  202. package/dist/cycle/state/pickCombine.d.ts +0 -3
  203. package/dist/cycle/state/pickMerge.d.ts +0 -3
  204. package/dist/cycle/state/types.d.ts +0 -18
  205. package/dist/cycle/state/withState.d.ts +0 -17
  206. package/dist/extra/classes.d.ts +0 -7
  207. package/dist/extra/devtools.d.ts +0 -78
  208. package/dist/extra/dragDriver.d.ts +0 -20
  209. package/dist/extra/driverFactories.d.ts +0 -12
  210. package/dist/extra/eventDriver.d.ts +0 -9
  211. package/dist/extra/exactState.d.ts +0 -1
  212. package/dist/extra/hmr.d.ts +0 -17
  213. package/dist/extra/logDriver.d.ts +0 -2
  214. package/dist/extra/processDrag.d.ts +0 -19
  215. package/dist/extra/processForm.d.ts +0 -15
  216. package/dist/extra/run.d.ts +0 -13
  217. package/dist/extra/xstreamCompat.d.ts +0 -5
  218. package/dist/jsx-dev-runtime.d.ts +0 -1
  219. package/dist/jsx-dev-runtime.js +0 -224
  220. package/dist/jsx-runtime.d.ts +0 -4
  221. package/dist/jsx-runtime.js +0 -224
  222. package/dist/jsx.d.ts +0 -2
  223. package/dist/pragma/fn.d.ts +0 -7
  224. package/dist/pragma/index.d.ts +0 -7
  225. package/dist/pragma/is.d.ts +0 -10
  226. package/dist/switchable.d.ts +0 -7
@@ -0,0 +1,319 @@
1
+ /**
2
+ * inspect() — the machine-readable app graph at runtime (PLAN-1 workstream 2B).
3
+ *
4
+ * Built only from what the diagnostics hooks already see, keyed by component
5
+ * instance (so diagnostics must be on while the app runs):
6
+ * onIntent registers the instance (name, parent, kind) and its intent
7
+ * action names; claims the EVENTS.select() types seen since the
8
+ * previous onIntent (intent runs synchronously right before it)
9
+ * onModel action -> sinks; and wraps the component's own EVENTS model
10
+ * stream (component.model$.EVENTS, before initSinks merges it)
11
+ * in an identity map that records the emitted types. Since
12
+ * B-023 the driver-side emitter name (onBusEmit) is the
13
+ * emitting component's, but it is only a name, and this
14
+ * graph is per instance, so the per-instance tap stays.
15
+ * onReducer which model-only actions actually ran (=> dispatched by next())
16
+ * onBusEmit EVENTS types seen on the bus
17
+ * onBusSelect EVENTS types selected (see onIntent)
18
+ * onDispose prunes the instance
19
+ * Selectors come from the DOM check (real DOM) or from renderComponent (mock
20
+ * DOM, passed in as options.selectors). The same shape is produced statically
21
+ * by `sygnal-check --graph`; the JSON Schema is
22
+ * sygnal-check/schema/inspect.schema.json, the types are in ./public.d.ts.
23
+ *
24
+ * This lives in the 'sygnal/diagnostics' dev entry only (D29: zero bytes in
25
+ * app bundles). installInspect() publishes inspect() on the core bridge
26
+ * (renderComponent's t.inspect() reads it there) and on the devtools object
27
+ * (window.__SYGNAL_DEVTOOLS__, which is getDevTools()) once it exists.
28
+ */
29
+ import type {DiagnosticCheck} from '../index'
30
+ import type {InspectGraph, InspectComponent, InspectOptions, InspectSelector, InspectDiagnostic, InspectChild} from './public'
31
+ import {bridge, onReset, nameOf, isPlainObject, BUILTIN_ACTIONS} from './shared'
32
+ import {checkEventBus} from './events'
33
+ import {selectorStatus} from './dom'
34
+
35
+ type Kind = InspectComponent['kind']
36
+
37
+ interface Rec {
38
+ instance: any
39
+ id: string
40
+ name: string
41
+ parentId: string | null
42
+ kind: Kind
43
+ /** null: single-stream intent (no action names) */
44
+ intentActions: string[] | null
45
+ injected: Set<string>
46
+ modelMap: Record<string, string[]>
47
+ fired: Set<string>
48
+ eventsSelected: Set<string>
49
+ eventsEmitted: Set<string>
50
+ }
51
+
52
+ let records = new Map<any, Rec>()
53
+ let pendingSelects: string[] = []
54
+ /** every EVENTS type seen on the bus */
55
+ let busTypes = new Set<string>()
56
+
57
+ onReset(() => {
58
+ records = new Map()
59
+ pendingSelects = []
60
+ busTypes = new Set()
61
+ })
62
+ const uniq = <T>(list: Iterable<T>): T[] => [...new Set(list)]
63
+
64
+ function kindOf(c: any): Kind {
65
+ const s = (c && c.sources) || {}
66
+ if (typeof s.__parentComponentNumber !== 'number') return 'root'
67
+ // Collection items get PARENT: null (src/component.ts instantiateCollection)
68
+ if ('PARENT' in s && s.PARENT === null) return 'collection-item'
69
+ // switchable() adds a lower-case `state` source next to the component's own (src/switchable.ts)
70
+ if (c.stateSourceName !== 'state' && 'state' in s && s.state !== s[c.stateSourceName]) return 'switchable'
71
+ return 'child'
72
+ }
73
+
74
+ function ensure(c: any): Rec {
75
+ let r = records.get(c)
76
+ if (!r) {
77
+ const parent = c && c.sources && c.sources.__parentComponentNumber
78
+ r = {
79
+ instance: c,
80
+ id: String(c && c._componentNumber),
81
+ name: nameOf(c),
82
+ parentId: typeof parent === 'number' ? String(parent) : null,
83
+ kind: kindOf(c),
84
+ intentActions: [],
85
+ injected: new Set(),
86
+ modelMap: {},
87
+ fired: new Set(),
88
+ eventsSelected: new Set(),
89
+ eventsEmitted: new Set(),
90
+ }
91
+ records.set(c, r)
92
+ }
93
+ return r
94
+ }
95
+
96
+ let devtools: any
97
+ function attachDevtools(): void {
98
+ const dt = (globalThis as any).__SYGNAL_DEVTOOLS__
99
+ if (!dt || typeof dt !== 'object') return
100
+ devtools = dt
101
+ if (dt.inspect !== inspect) {
102
+ try { dt.inspect = inspect } catch (_) { /* frozen: ignore */ }
103
+ }
104
+ }
105
+
106
+ export const inspectCheck: DiagnosticCheck = {
107
+ id: 'inspect',
108
+
109
+ onBusSelect(type) {
110
+ if (type === undefined || type === null || type === '') pendingSelects.push('*')
111
+ else for (const t of ([] as any[]).concat(type)) if (typeof t === 'string') pendingSelects.push(t)
112
+ if (pendingSelects.length > 1000) pendingSelects.shift()
113
+ },
114
+
115
+ onIntent(component, actionNames) {
116
+ attachDevtools()
117
+ const r = ensure(component)
118
+ const intent$ = component && component.intent$
119
+ r.intentActions = intent$ && typeof intent$.addListener === 'function' ? null : (actionNames || [])
120
+ r.injected = new Set((intent$ && typeof intent$ === 'object' && intent$.__sygnalTestActions) || [])
121
+ for (const t of pendingSelects) r.eventsSelected.add(t)
122
+ pendingSelects = []
123
+ },
124
+
125
+ onModel(component, modelMap) {
126
+ const r = ensure(component)
127
+ r.modelMap = modelMap || {}
128
+ const model$ = component && component.model$
129
+ const events$ = model$ && model$.EVENTS
130
+ if (events$ && typeof events$.map === 'function' && !events$.__sygnalInspect) {
131
+ const tapped = events$.map((ev: any) => {
132
+ if (ev && typeof ev.type === 'string') r.eventsEmitted.add(ev.type)
133
+ return ev
134
+ })
135
+ tapped.__sygnalInspect = true
136
+ model$.EVENTS = tapped
137
+ }
138
+ },
139
+
140
+ onReducer(component, action) {
141
+ const r = records.get(component)
142
+ if (r && typeof action === 'string') r.fired.add(action)
143
+ },
144
+
145
+ onBusEmit(type) {
146
+ if (typeof type === 'string') busTypes.add(type)
147
+ },
148
+
149
+ onDispose(component) {
150
+ records.delete(component)
151
+ },
152
+ }
153
+
154
+ const slim = (d: any): InspectDiagnostic => {
155
+ const out: InspectDiagnostic = {code: d.code, severity: d.severity, message: d.message}
156
+ if (d.component) out.component = d.component
157
+ if (d.fix) out.fix = d.fix
158
+ if (d.docsUrl) out.docsUrl = d.docsUrl
159
+ if (d.data !== undefined) {
160
+ // keep it JSON-safe (data can hold errors, vnodes, ...)
161
+ try { out.data = JSON.parse(JSON.stringify(d.data)) } catch (_) { /* drop */ }
162
+ }
163
+ return out
164
+ }
165
+
166
+ function actionsOf(r: Rec): InspectComponent['actions'] {
167
+ const intent = (r.intentActions || []).filter(a => !r.injected.has(a))
168
+ const names = uniq([...intent, ...Object.keys(r.modelMap)]).filter(a => typeof a === 'string' && !a.startsWith('__'))
169
+ return names.map(name => ({
170
+ name,
171
+ trigger: BUILTIN_ACTIONS.has(name) ? 'builtin'
172
+ : intent.includes(name) ? 'intent'
173
+ : !r.injected.has(name) && r.fired.has(name) ? 'next'
174
+ : 'unknown',
175
+ sinks: [...(r.modelMap[name] || [])],
176
+ }))
177
+ }
178
+
179
+ function stateKeysOf(c: any, calculated: string[]): string[] {
180
+ const s = c && c.currentState
181
+ return isPlainObject(s) ? Object.keys(s).filter(k => !calculated.includes(k)) : []
182
+ }
183
+
184
+ /**
185
+ * renderComponent's mock DOM (src/extra/testing.ts passes its listener registry):
186
+ * listener paths are the select() chain with isolation scopes as '.___<scope>' entries;
187
+ * probe(selectors, scope) matches one against the latest vtree: own = in the component's
188
+ * own scope, child = the scope of the first (isolated) child component it matches inside.
189
+ */
190
+ interface MockDom {
191
+ listeners: Map<string, string[]>
192
+ evTypes: Record<string, string[]>
193
+ owners: Map<string, string>
194
+ scopeIds: Map<string, number>
195
+ probe: (selectors: string[], scope?: string) => {own: boolean; child?: string}
196
+ vtree: any
197
+ }
198
+
199
+ const isScope = (s: string) => s.startsWith('.___')
200
+
201
+ function mockSelectors(mock: MockDom): Record<string, InspectSelector[]> {
202
+ const out: Record<string, InspectSelector[]> = {}
203
+ mock.listeners.forEach((path, key) => {
204
+ const scope = path.filter(isScope).pop()
205
+ const id = mock.scopeIds.get(scope || '')
206
+ if (id === undefined) return
207
+ const sels = path.filter(s => !isScope(s)).join(' ').split(/[\s>]+/).filter(Boolean)
208
+ const page = !sels.length || /^(document|body)$/.test(sels[0])
209
+ const {own, child} = mock.vtree && !page ? mock.probe(sels, scope) : {own: false, child: undefined}
210
+ ;(out[id] = out[id] || []).push({
211
+ selector: sels.join(' '),
212
+ events: uniq(mock.evTypes[key] || []),
213
+ matched: mock.vtree && !page ? own : null,
214
+ isolationHit: !own && child ? mock.owners.get(child) || 'a child component' : null,
215
+ })
216
+ })
217
+ return out
218
+ }
219
+
220
+ function selectorsOf(r: Rec, options: InspectOptions, diags: any[]): InspectSelector[] {
221
+ const given = options.selectors && options.selectors[r.id]
222
+ if (given) return given.map(s => ({...s}))
223
+ return (selectorStatus(r.instance) || []).map(({selector, matched, crossed}) => {
224
+ const hit = diags.find(d => d.code === 'SYG104' && d.component === r.name && d.data && d.data.selector === selector)
225
+ const missed = diags.some(d => d.code === 'SYG103' && d.component === r.name && d.data && d.data.selector === selector)
226
+ return {
227
+ selector,
228
+ events: null,
229
+ matched: matched === null && missed ? false : matched,
230
+ isolationHit: crossed ? ((hit && hit.data.child) || 'a child component') : null,
231
+ }
232
+ })
233
+ }
234
+
235
+ function childrenOf(r: Rec, all: Rec[]): InspectChild[] {
236
+ const via: Record<Kind, InspectChild['via']> = {root: 'tag', child: 'tag', 'collection-item': 'collection', switchable: 'switchable'}
237
+ const out = new Map<string, InspectChild>()
238
+ for (const c of all) {
239
+ if (c.parentId !== r.id) continue
240
+ const key = `${c.name}\u0000${via[c.kind]}`
241
+ const entry = out.get(key)
242
+ if (entry) entry.count = (entry.count || 1) + 1
243
+ else out.set(key, {name: c.name, via: via[c.kind], count: 1})
244
+ }
245
+ return [...out.values()]
246
+ }
247
+
248
+ /** The app graph of the live component instances (see ./public.d.ts). */
249
+ export function inspect(options: InspectOptions = {}): InspectGraph {
250
+ // report SYG105 (info) for EVENTS types selected but never emitted so far
251
+ try { checkEventBus() } catch (_) { /* 'error' mode: info never throws; be safe */ }
252
+
253
+ let diags: any[] = options.diagnostics as any[] || []
254
+ if (!options.diagnostics) {
255
+ attachDevtools()
256
+ try { diags = (devtools && typeof devtools.getDiagnostics === 'function' && devtools.getDiagnostics()) || [] } catch (_) { diags = [] }
257
+ }
258
+
259
+ const mock: MockDom | undefined = (options as any).mock
260
+ if (mock) options = {...options, selectors: {...mockSelectors(mock), ...options.selectors}}
261
+
262
+ const all = [...records.values()]
263
+ const ids = options.ids && new Set(options.ids.map(String))
264
+ const recs = ids ? all.filter(r => ids.has(r.id)) : all
265
+ const names = new Set(recs.map(r => r.name))
266
+
267
+ const components: InspectComponent[] = recs.map(r => {
268
+ const c = r.instance
269
+ const calculated = c && c.calculated && typeof c.calculated === 'object' ? Object.keys(c.calculated) : []
270
+ return {
271
+ name: r.name,
272
+ id: r.id,
273
+ parentId: r.parentId,
274
+ kind: r.kind,
275
+ actions: actionsOf(r),
276
+ stateKeys: stateKeysOf(c, calculated),
277
+ calculated,
278
+ contextProvides: c && isPlainObject(c.context) ? Object.keys(c.context) : [],
279
+ contextConsumes: null,
280
+ eventsEmitted: [...r.eventsEmitted],
281
+ eventsSelected: [...r.eventsSelected],
282
+ children: childrenOf(r, all),
283
+ selectors: selectorsOf(r, options, diags),
284
+ diagnostics: diags.filter(d => d && d.component === r.name).map(slim),
285
+ }
286
+ })
287
+
288
+ const events: InspectGraph['events'] = {}
289
+ const ev = (type: string) => events[type] || (events[type] = {emitters: [], selectors: []})
290
+ const add = (list: string[], name: string) => { if (!list.includes(name)) list.push(name) }
291
+ for (const c of components) {
292
+ for (const t of c.eventsEmitted) add(ev(t).emitters, c.name)
293
+ for (const t of c.eventsSelected) add(ev(t).selectors, c.name)
294
+ }
295
+ // emitted on the bus by something that isn't a listed component (e.g. a driver, a pruned component)
296
+ if (!ids) for (const t of busTypes) ev(t)
297
+
298
+ return {
299
+ version: 1,
300
+ source: 'runtime',
301
+ components,
302
+ events,
303
+ diagnostics: diags.filter(d => d && (!d.component || !names.has(d.component))).map(slim),
304
+ }
305
+ }
306
+
307
+ /** Publish inspect() on the core bridge and the devtools object. Returns an uninstall function. */
308
+ export function installInspect(): () => void {
309
+ const core = bridge()
310
+ if (core) core.inspect = inspect
311
+ attachDevtools()
312
+ return () => {
313
+ if (core && core.inspect === inspect) core.inspect = undefined
314
+ const dt = (globalThis as any).__SYGNAL_DEVTOOLS__
315
+ if (dt && dt.inspect === inspect) {
316
+ try { delete dt.inspect } catch (_) { /* ignore */ }
317
+ }
318
+ }
319
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * SYG106 — a prop passed by the parent is silently overwritten (warn).
3
+ *
4
+ * The view is called with { ...props, state, children, slots, context, peers },
5
+ * so a parent prop with one of those names never reaches the child. (Tracker
6
+ * G-007 item 2.) `state` is special: Sygnal reads it as the child's state
7
+ * lens (a state field name or a { get, set } object), so it is only reported
8
+ * when the value can't be a lens.
9
+ *
10
+ * Mechanism: onRender reads the component's current (sanitized) props.
11
+ * Reported once per component name and prop.
12
+ */
13
+ import type {DiagnosticCheck} from '../index'
14
+ import {report, once, nameOf} from './shared'
15
+
16
+ const RESERVED = ['children', 'slots', 'context', 'peers']
17
+
18
+ export const propsCheck: DiagnosticCheck = {
19
+ id: 'props',
20
+
21
+ onRender(component) {
22
+ const props = component && component.currentProps
23
+ if (!props || typeof props !== 'object') return
24
+ const name = nameOf(component)
25
+
26
+ for (const key of RESERVED) {
27
+ if (!(key in props) || !once(`SYG106:${name}:${key}`)) continue
28
+ report('SYG106', {
29
+ component,
30
+ severity: 'warn',
31
+ message: `The prop '${key}' passed to ${name} is overwritten: '${key}' is a reserved view argument, so the child never sees the parent's value`,
32
+ fix: `Rename the prop (for example '${key}Value'), in the parent and in ${name}`,
33
+ data: {prop: key},
34
+ })
35
+ }
36
+
37
+ if ('state' in props) {
38
+ const value = props.state
39
+ const isLens = typeof value === 'string' || (value && typeof value === 'object' && typeof value.get === 'function')
40
+ if (!isLens && value !== undefined && once(`SYG106:${name}:state`)) {
41
+ report('SYG106', {
42
+ component,
43
+ severity: 'warn',
44
+ message: `The prop 'state' passed to ${name} is not data: Sygnal reads it as the child's state lens (a state field name or a { get, set } object), and the view's 'state' argument is the child's own state`,
45
+ fix: `To give ${name} a slice of the parent state use state="fieldName"; to pass data, use another prop name (for example 'item')`,
46
+ data: {prop: 'state', valueType: typeof value},
47
+ })
48
+ }
49
+ }
50
+ },
51
+ }
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Types for the 'sygnal/diagnostics' entry (runtime consistency checks).
3
+ *
4
+ * import 'sygnal/diagnostics' // registers all checks (side effect)
5
+ *
6
+ * Checks only run while diagnostics are on: run(App, drivers, { diagnostics: 'warn' }),
7
+ * or globalThis.__SYGNAL_DEV__ (set by the Vite plugin in dev).
8
+ */
9
+
10
+ export type DiagnosticSeverity = 'error' | 'warn' | 'info'
11
+
12
+ export interface DiagnosticCodeInfo {
13
+ code: string
14
+ severity: DiagnosticSeverity
15
+ title: string
16
+ /** anchor on https://sygnal.js.org/reference/errors, e.g. 'syg101' */
17
+ docsSlug: string
18
+ }
19
+
20
+ /** A runtime check, as registered with the diagnostics core. */
21
+ export interface DiagnosticCheck {
22
+ id: string
23
+ onIntent?: (component: any, actionNames: string[], selectorsUsed: string[] | undefined) => void
24
+ onModel?: (component: any, modelMap: Record<string, string[]>) => void
25
+ onRender?: (component: any, rootVnode: any) => void
26
+ onReducer?: (component: any, action: string, prevState: any, nextState: any, sinkName: string) => void
27
+ onDispose?: (component: any) => void
28
+ onSelector?: (domSource: any, selector: string) => void
29
+ onBusEmit?: (type: string, emitterName?: string) => void
30
+ onBusSelect?: (type: string | string[] | undefined) => void
31
+ }
32
+
33
+ /**
34
+ * Strict mode (canonical forms, SYG5xx): turn the runtime strict checks on
35
+ * (true), off (false), or back to the default (undefined → on only when
36
+ * globalThis.__SYGNAL_STRICT__ === true). Off by default. Diagnostics must
37
+ * also be on. Runtime rules: SYG501 (positional view args), SYG502 (reducer
38
+ * returned the unchanged state instead of ABORT), SYG504 ('ACTION | SINK'
39
+ * keys); `sygnal-check --strict` checks all of SYG501-507 statically.
40
+ * In tests: renderComponent(C, { strict: true }).
41
+ */
42
+ export function configureStrict(on?: boolean): void
43
+
44
+ /** Whether the runtime strict checks are on. */
45
+ export function isStrictEnabled(): boolean
46
+
47
+ /** Every runtime check in this entry. */
48
+ export const checks: DiagnosticCheck[]
49
+
50
+ /** Re-register all checks and the RxJS hints (done automatically on import). Returns an uninstall function. */
51
+ export function installChecks(): () => void
52
+
53
+ export interface ChecksOptions {
54
+ /** delay after a render before the DOM checks (SYG103/104) run; default 50 ms */
55
+ settleMs?: number
56
+ /** render-idle time before SYG103 escalates to warn; default 2000 ms */
57
+ idleMs?: number
58
+ /** renders required before SYG103 escalates to warn; default 3 */
59
+ minRenders?: number
60
+ }
61
+
62
+ /** Tune the timing of the DOM checks (mostly for tests). */
63
+ export function configureChecks(options?: ChecksOptions): void
64
+
65
+ /** Forget what the checks have seen and reported (dedupe state, registries). For tests. */
66
+ export function resetChecks(): void
67
+
68
+ export interface EventBusSummary {
69
+ selected: string[]
70
+ emitted: string[]
71
+ selectedNeverEmitted: string[]
72
+ emittedNeverSelected: string[]
73
+ }
74
+
75
+ /**
76
+ * Report SYG105 (info) for every EVENTS type selected but never emitted so far,
77
+ * and return the EVENTS bus registry.
78
+ */
79
+ export function checkEventBus(): EventBusSummary
80
+
81
+ /** RxJS operator name -> xstream equivalent (used by SYG301). */
82
+ export const RXJS_HINTS: Record<string, string>
83
+
84
+ /** Metadata for every registered diagnostic code. */
85
+ export function listCodes(): DiagnosticCodeInfo[]
86
+
87
+ /** Metadata for one diagnostic code, or undefined if unknown. */
88
+ export function getCodeInfo(code: string): DiagnosticCodeInfo | undefined
89
+
90
+ // ---------------------------------------------------------------------------
91
+ // inspect (PLAN-1 workstream 2B): a machine-readable app graph. The same shape
92
+ // is produced at runtime (inspect(), getDevTools().inspect(), renderComponent's
93
+ // t.inspect()) and statically (`sygnal-check --graph --json`); JSON Schema:
94
+ // sygnal-check/schema/inspect.schema.json. Fields one side cannot know are
95
+ // null (or omitted).
96
+ // ---------------------------------------------------------------------------
97
+
98
+ /** How an action is dispatched. */
99
+ export type InspectActionTrigger = 'intent' | 'next' | 'builtin' | 'unknown'
100
+
101
+ export interface InspectAction {
102
+ name: string
103
+ /**
104
+ * 'intent': returned by the component's intent. 'builtin': BOOTSTRAP,
105
+ * INITIALIZE, HYDRATE, DISPOSE or READY. 'next': dispatched with next()
106
+ * (statically: a next('NAME') literal; at runtime: a model-only action whose
107
+ * STATE reducer was seen running). 'unknown': none of these is known.
108
+ */
109
+ trigger: InspectActionTrigger
110
+ /** sinks of the model entry (STATE, EVENTS, EFFECT, PARENT, custom drivers); [] without a model entry */
111
+ sinks: string[]
112
+ }
113
+
114
+ export interface InspectChild {
115
+ name: string
116
+ via: 'tag' | 'collection' | 'switchable' | 'slot'
117
+ /** Collection: the `from` state field, when known */
118
+ from?: string | null
119
+ /** runtime: number of live instances behind this entry (e.g. Collection items) */
120
+ count?: number
121
+ }
122
+
123
+ export interface InspectSelector {
124
+ /** CSS selector passed to DOM.select() (chained selects joined with a space) */
125
+ selector: string
126
+ /** event types listened to; null when unknown (runtime, real DOM) */
127
+ events: string[] | null
128
+ /**
129
+ * whether it matches an element the component itself renders; null when unknown.
130
+ * static: the class/id is in the view source. renderComponent: an element of the latest
131
+ * render matches (a conditionally rendered element that is hidden right now gives false).
132
+ * real DOM: true once a DOM check saw it match, false after SYG103/SYG104, else null.
133
+ */
134
+ matched: boolean | null
135
+ /** the child component whose (isolated) elements it matches instead, if any */
136
+ isolationHit: string | null
137
+ }
138
+
139
+ export interface InspectDiagnostic {
140
+ code: string
141
+ severity: DiagnosticSeverity
142
+ component?: string
143
+ message: string
144
+ fix?: string
145
+ docsUrl?: string
146
+ data?: any
147
+ /** static only */
148
+ file?: string
149
+ line?: number
150
+ column?: number
151
+ }
152
+
153
+ export interface InspectComponent {
154
+ name: string
155
+ /** runtime: instance number (as a string); static: 'file:line' of the definition */
156
+ id: string
157
+ /** runtime: the parent instance's id; static: null (see the parents' `children`) */
158
+ parentId: string | null
159
+ /** static only: source file, relative to the working directory */
160
+ file?: string
161
+ /**
162
+ * runtime: how the instance was created; static: 'root' when no scanned
163
+ * component renders it, else how it is first rendered
164
+ */
165
+ kind: 'root' | 'child' | 'collection-item' | 'switchable'
166
+ actions: InspectAction[]
167
+ /** state keys (runtime: current state; static: initialState) */
168
+ stateKeys: string[]
169
+ /** calculated field names */
170
+ calculated: string[]
171
+ /** context fields this component provides (Component.context) */
172
+ contextProvides: string[]
173
+ /** context fields its view reads (static only; null at runtime) */
174
+ contextConsumes?: string[] | null
175
+ /** EVENTS types this component emits */
176
+ eventsEmitted: string[]
177
+ /** EVENTS types this component selects ('*' = EVENTS.select() without a type) */
178
+ eventsSelected: string[]
179
+ children: InspectChild[]
180
+ selectors: InspectSelector[]
181
+ diagnostics: InspectDiagnostic[]
182
+ }
183
+
184
+ export interface InspectGraph {
185
+ version: 1
186
+ /** which implementation produced the graph */
187
+ source?: 'runtime' | 'static'
188
+ components: InspectComponent[]
189
+ /** EVENTS bus: type -> names of the components that emit / select it */
190
+ events: Record<string, { emitters: string[]; selectors: string[] }>
191
+ /** diagnostics not tied to a listed component */
192
+ diagnostics: InspectDiagnostic[]
193
+ }
194
+
195
+ export interface InspectOptions {
196
+ /** only these component instances (ids as in InspectComponent.id) */
197
+ ids?: Array<string | number>
198
+ /** selector details by component id (renderComponent passes its mock-DOM view) */
199
+ selectors?: Record<string, InspectSelector[]>
200
+ /** diagnostics to attach (default: the devtools' collected diagnostics, when available) */
201
+ diagnostics?: Array<{ code: string; severity: DiagnosticSeverity; component?: string; message: string; [key: string]: any }>
202
+ }
203
+
204
+ /**
205
+ * The app graph of the live component instances, built from what the
206
+ * diagnostics hooks have seen (so diagnostics must be on while the app runs).
207
+ * Disposed components are pruned. Also available as `getDevTools().inspect()`
208
+ * / `window.__SYGNAL_DEVTOOLS__.inspect()` once the devtools bridge exists
209
+ * (run() in a browser), and as renderComponent's `t.inspect()`.
210
+ */
211
+ export function inspect(options?: InspectOptions): InspectGraph
@@ -0,0 +1,88 @@
1
+ /**
2
+ * SYG301 — an RxJS operator was called on an xstream stream (error).
3
+ *
4
+ * Without help this fails with a bare "x.switchMap is not a function" inside
5
+ * the intent function. Mechanism: when 'sygnal/diagnostics' is loaded, every
6
+ * RxJS operator name below that xstream's Stream does NOT already have is
7
+ * defined on Stream.prototype (non-enumerable) as a method that throws an
8
+ * enriched TypeError: same "stream.<op> is not a function" shape, plus the
9
+ * xstream equivalent and the SYG301 docs link. The error is also reported
10
+ * (collected / printed per the diagnostics mode; in 'error' mode report()
11
+ * throws the DiagnosticError itself). It works anywhere a stream is used
12
+ * (intent, peers, drivers), with no hook in component.ts.
13
+ *
14
+ * Caveat: `typeof stream.pipe === 'function'` is true while this entry is
15
+ * loaded (dev only).
16
+ */
17
+ import {report} from './shared'
18
+ import {docsUrlFor} from '../codes'
19
+
20
+ /** RxJS operator -> xstream equivalent. */
21
+ export const RXJS_HINTS: Record<string, string> = {
22
+ pipe: `chain xstream operators directly (.map(...).filter(...)), or use .compose(operator) for extra operators`,
23
+ switchMap: `.map(x => inner$).flatten()`,
24
+ mergeMap: `.map(x => inner$).compose(flattenConcurrently) (import { flattenConcurrently } from 'sygnal')`,
25
+ flatMap: `.map(x => inner$).compose(flattenConcurrently) (import { flattenConcurrently } from 'sygnal')`,
26
+ concatMap: `.map(x => inner$).compose(flattenSequentially) (import { flattenSequentially } from 'sygnal')`,
27
+ exhaustMap: `.map(x => inner$).flatten() (xstream has no exhaustMap; ignore new values while busy with .filter())`,
28
+ switchAll: `.flatten()`,
29
+ mergeAll: `.compose(flattenConcurrently) (import { flattenConcurrently } from 'sygnal')`,
30
+ concatAll: `.compose(flattenSequentially) (import { flattenSequentially } from 'sygnal')`,
31
+ debounceTime: `.compose(debounce(ms)) (import { debounce } from 'sygnal')`,
32
+ debounce: `.compose(debounce(ms)) (import { debounce } from 'sygnal')`,
33
+ throttleTime: `.compose(throttle(ms)) (import { throttle } from 'sygnal')`,
34
+ throttle: `.compose(throttle(ms)) (import { throttle } from 'sygnal')`,
35
+ auditTime: `.compose(throttle(ms)) (import { throttle } from 'sygnal')`,
36
+ delay: `.compose(delay(ms)) (import { delay } from 'sygnal')`,
37
+ distinctUntilChanged: `.compose(dropRepeats()) (import { dropRepeats } from 'sygnal')`,
38
+ withLatestFrom: `.compose(sampleCombine(other$)) (import { sampleCombine } from 'sygnal')`,
39
+ combineLatest: `xs.combine(a$, b$) (import { xs } from 'sygnal')`,
40
+ combineLatestWith: `xs.combine(a$, b$) (import { xs } from 'sygnal')`,
41
+ merge: `xs.merge(a$, b$) (import { xs } from 'sygnal')`,
42
+ mergeWith: `xs.merge(a$, b$) (import { xs } from 'sygnal')`,
43
+ concat: `concat(a$, b$) (import { concat } from 'sygnal')`,
44
+ scan: `.fold((acc, x) => next, seed)`,
45
+ reduce: `.fold((acc, x) => next, seed).last()`,
46
+ skip: `.drop(n)`,
47
+ first: `.take(1)`,
48
+ takeUntil: `.endWhen(other$)`,
49
+ tap: `.debug(fn)`,
50
+ do: `.debug(fn)`,
51
+ catchError: `.replaceError(err => fallback$)`,
52
+ catch: `.replaceError(err => fallback$)`,
53
+ pluck: `.map(x => x.field)`,
54
+ share: `xstream streams are already shared (hot); use .remember() to replay the latest value`,
55
+ shareReplay: `.remember()`,
56
+ publishReplay: `.remember()`,
57
+ finalize: `.debug({ complete: fn }) or handle completion in the subscriber`,
58
+ }
59
+
60
+ const SYG301_URL = docsUrlFor('SYG301')
61
+
62
+ function rxjsError(op: string): TypeError {
63
+ const hint = RXJS_HINTS[op]
64
+ const message = `stream.${op} is not a function: '${op}' is an RxJS operator, but Sygnal streams are xstream streams`
65
+ const fix = `Use ${hint}`
66
+ // In 'error' mode report() throws the DiagnosticError itself (we are about to throw anyway).
67
+ const d = report('SYG301', {message, fix, data: {operator: op, hint}})
68
+ const err: any = new TypeError(d ? d.text : `[Sygnal SYG301] ${message}. ${fix}. ${SYG301_URL}`)
69
+ err.diagnostic = d
70
+ return err
71
+ }
72
+
73
+ /** Install the hints on xstream's Stream.prototype. Returns an uninstall function. */
74
+ export function installRxjsHints(proto: any): () => void {
75
+ const added: string[] = []
76
+ if (!proto) return () => {}
77
+ for (const op of Object.keys(RXJS_HINTS)) {
78
+ if (op in proto) continue
79
+ Object.defineProperty(proto, op, {
80
+ configurable: true,
81
+ writable: true,
82
+ enumerable: false,
83
+ value: function rxjsOperatorHint() { throw rxjsError(op) },
84
+ })
85
+ added.push(op)
86
+ }
87
+ return () => { for (const op of added) delete proto[op] }
88
+ }