@bitstillery/mithril 3.8.0 → 3.9.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.
package/index.ts CHANGED
@@ -12,7 +12,15 @@ import censor from './util/censor'
12
12
  import nextTick from './util/next_tick'
13
13
  import domFor from './render/domFor'
14
14
  import {signal, computed, effect, Signal, ComputedSignal, setSignalRedrawCallback, getSignalComponents} from './signal'
15
- import {state, watch, registerState, getRegisteredStates, clearStateRegistry, copyGlobalStatesToContext} from './state'
15
+ import {
16
+ state,
17
+ watch,
18
+ registerState,
19
+ getRegisteredStates,
20
+ clearStateRegistry,
21
+ copyGlobalStatesToContext,
22
+ allowComputed,
23
+ } from './state'
16
24
 
17
25
  import type {Vnode, Children, ComponentType} from './render/vnode'
18
26
  import type {Hyperscript} from './render/hyperscript'
@@ -101,8 +109,20 @@ setSignalRedrawCallback((sig: Signal<any>) => {
101
109
  })
102
110
 
103
111
  // Export signals API
104
- export {signal, computed, effect, Signal, ComputedSignal, state, watch, registerState, getRegisteredStates, clearStateRegistry}
105
- export type {State, StateArray, StateOptions, StateSignals, Unwatch} from './state'
112
+ export {
113
+ signal,
114
+ computed,
115
+ effect,
116
+ Signal,
117
+ ComputedSignal,
118
+ state,
119
+ watch,
120
+ registerState,
121
+ getRegisteredStates,
122
+ clearStateRegistry,
123
+ allowComputed,
124
+ }
125
+ export type {DeepPartial, State, StateArray, StateOptions, StateSignals, Unwatch} from './state'
106
126
 
107
127
  // Export Store class
108
128
  export {Store} from './store'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bitstillery/mithril",
3
- "version": "3.8.0",
3
+ "version": "3.9.0",
4
4
  "description": "Mithril + Signals, Store and SSR",
5
5
  "license": "MIT",
6
6
  "author": "Bitstillery",
@@ -52,7 +52,7 @@
52
52
  "lint:ts": "bun run lint:ts-format && bun run lint:ts-syntax && bun run lint:ts-types",
53
53
  "lint:ts-format": "oxfmt",
54
54
  "lint:ts-syntax": "oxlint -c .oxlintrc.json",
55
- "lint:ts-types": "tsgo --noEmit",
55
+ "lint:ts-types": "tsc --noEmit",
56
56
  "test": "bun test",
57
57
  "bench": "NODE_ENV=production bun run bench/index.ts",
58
58
  "bench:hyperscript": "NODE_ENV=production bun run bench/index.ts hyperscript",
@@ -78,7 +78,6 @@
78
78
  "@codemirror/theme-one-dark": "^6.1.2",
79
79
  "@codemirror/view": "^6.39.1",
80
80
  "@types/bun": "^1.3.9",
81
- "@typescript/native-preview": "^7.0.0-dev.20260120.1",
82
81
  "codemirror": "^6.0.1",
83
82
  "marked": "^15.0.4",
84
83
  "marked-gfm-heading-id": "^4.0.0",
@@ -86,6 +85,7 @@
86
85
  "oxfmt": "^0.32.0",
87
86
  "oxlint": "^1.30.0",
88
87
  "standard-version": "^9.5.0",
89
- "tippy.js": "^6.3.7"
88
+ "tippy.js": "^6.3.7",
89
+ "typescript": "^7.0.2"
90
90
  }
91
91
  }
package/render/vnode.ts CHANGED
@@ -55,7 +55,7 @@ export type ComponentType<Attrs = Record<string, any>, State = any> =
55
55
  */
56
56
  export abstract class MithrilComponent<Attrs = Record<string, any>> {
57
57
  /** Required for JSX attribute type-checking - do not use directly */
58
- private readonly __tsx_attrs!: (unknown extends Attrs ? Record<string, any> : Attrs) & {key?: string | number}
58
+ private readonly __tsx_attrs!: (unknown extends Attrs ? Record<string, any> : Attrs) & {key?: string | number | null}
59
59
 
60
60
  oninit?(vnode: ComponentVnode<Attrs>): void
61
61
  oncreate?(vnode: ComponentVnode<Attrs>): void
package/ssrContext.ts CHANGED
@@ -5,6 +5,8 @@
5
5
  * request's context. No globals, safe under concurrent requests.
6
6
  * In the browser, getSSRContext() returns undefined and runWithContext just runs fn.
7
7
  */
8
+ import type {Store} from './store'
9
+
8
10
  type StorageLike = {
9
11
  getStore(): SSRAccessContext | undefined
10
12
  run<T>(context: SSRAccessContext, fn: () => T): T
@@ -28,7 +30,7 @@ try {
28
30
  * that runs inside the same runWithContext() call.
29
31
  */
30
32
  export interface SSRAccessContext {
31
- store?: any
33
+ store?: Store
32
34
  /** Per-request state registry for serialization; fresh Map per request. */
33
35
  stateRegistry: Map<string, {state: any; initial: any}>
34
36
  sessionId?: string
package/state.ts CHANGED
@@ -871,11 +871,13 @@ export function state<T extends Record<string, any>>(initial: T, name?: string,
871
871
  * Mapped type that adds $prop for each key, returning the Signal for that property.
872
872
  * - Primitives: $prop => Signal<T[K]>
873
873
  * - Nested objects: $prop => Signal<State<T[K]>>
874
- * - Functions: $prop => ComputedSignal (computed from getter)
874
+ * - Functions: $prop => ComputedSignal of the getter's return type
875
875
  */
876
876
  export type StateSignals<T extends Record<string, any>> = {
877
- [K in keyof T as K extends string ? `$${K}` : never]: T[K] extends (...args: any[]) => any
878
- ? ComputedSignal<any>
877
+ // Only for declared keys: a record's index signature would otherwise gain a `$${string}` twin, and every
878
+ // lookup by a `string` key would read as `Value | Signal<Value>`.
879
+ [K in keyof T as K extends string ? (string extends K ? never : `$${K}`) : never]: T[K] extends (...args: any[]) => infer R
880
+ ? ComputedSignal<R>
879
881
  : T[K] extends object
880
882
  ? Signal<State<T[K]>>
881
883
  : Signal<T[K]>
@@ -895,6 +897,27 @@ export type State<T extends Record<string, any>> = T extends (infer Elem)[]
895
897
  [K in keyof T]: T[K] extends (...args: any[]) => infer R ? R : T[K] extends Record<string, any> ? State<T[K]> : T[K]
896
898
  } & StateSignals<T>
897
899
 
900
+ /**
901
+ * A partial of a state shape at every depth, for the persistence tiers of a Store: each tier fills in
902
+ * part of a nested object (`saved` some keys of `exact`, `temporary` the rest). Arrays and computed
903
+ * getters are replaced wholesale, never merged element-wise.
904
+ */
905
+ export type DeepPartial<T> = T extends (...args: any[]) => any
906
+ ? T
907
+ : T extends readonly unknown[]
908
+ ? T
909
+ : T extends object
910
+ ? {[K in keyof T]?: DeepPartial<T[K]>}
911
+ : T
912
+
913
+ /**
914
+ * Opens the deferred-computed gate of a state built with `deferComputed` (ADR-0013) and marks its
915
+ * computeds dirty. The gate is a proxy trap rather than a key of the state, so it isn't on `State<T>`.
916
+ */
917
+ export function allowComputed(stateInstance: State<any>): void {
918
+ ;(stateInstance as unknown as {allowComputed?: () => void}).allowComputed?.()
919
+ }
920
+
898
921
  /** Function returned by watch() to remove the watcher */
899
922
  export type Unwatch = () => void
900
923
 
package/store.ts CHANGED
@@ -1,4 +1,4 @@
1
- import {state, State, updateStateRegistry} from './state'
1
+ import {allowComputed, state, State, updateStateRegistry, type DeepPartial} from './state'
2
2
  import {serializeStore, deserializeStore} from './render/ssrState'
3
3
 
4
4
  // Helper function to restore computed properties (same as in ssrState.ts)
@@ -143,11 +143,11 @@ let storeInstanceCounter = 0
143
143
  export class Store<T extends Record<string, any> = Record<string, any>> {
144
144
  private stateInstance: State<T>
145
145
  private templates = {
146
- saved: {} as Partial<T>,
147
- temporary: {} as Partial<T>,
148
- tab: {} as Partial<T>,
149
- session: {} as Partial<T>,
150
- cookie: {} as Partial<T>,
146
+ saved: {} as DeepPartial<T>,
147
+ temporary: {} as DeepPartial<T>,
148
+ tab: {} as DeepPartial<T>,
149
+ session: {} as DeepPartial<T>,
150
+ cookie: {} as DeepPartial<T>,
151
151
  }
152
152
  private lookup_verify_interval: number | null = null
153
153
  private lookup_ttl: number
@@ -193,15 +193,15 @@ export class Store<T extends Record<string, any> = Record<string, any>> {
193
193
  * (e.g. after $s, context, or route are ready) so computeds that depend on them can run.
194
194
  */
195
195
  ready(): void {
196
- ;(this.stateInstance as any).allowComputed?.()
196
+ allowComputed(this.stateInstance)
197
197
  }
198
198
 
199
199
  /**
200
200
  * Merge deep on object `state`, but only the key/values in `blueprint`.
201
201
  */
202
- blueprint(state: T, blueprint: Partial<T>): Partial<T> {
202
+ blueprint(state: T, blueprint: DeepPartial<T>): DeepPartial<T> {
203
203
  if (state == null || typeof state !== 'object') {
204
- return {} as Partial<T>
204
+ return {} as DeepPartial<T>
205
205
  }
206
206
  const result: any = {}
207
207
  for (const key of Object.keys(blueprint)) {
@@ -227,7 +227,7 @@ export class Store<T extends Record<string, any> = Record<string, any>> {
227
227
  result[key] = stateValue
228
228
  }
229
229
  }
230
- return result as Partial<T>
230
+ return result as DeepPartial<T>
231
231
  }
232
232
 
233
233
  clean_lookup() {
@@ -297,11 +297,11 @@ export class Store<T extends Record<string, any> = Record<string, any>> {
297
297
  }
298
298
 
299
299
  load(
300
- saved: Partial<T>,
301
- temporary: Partial<T>,
302
- tab: Partial<T> = {} as Partial<T>,
303
- session: Partial<T> = {} as Partial<T>,
304
- cookie: Partial<T> = {} as Partial<T>,
300
+ saved: DeepPartial<T>,
301
+ temporary: DeepPartial<T>,
302
+ tab: DeepPartial<T> = {} as DeepPartial<T>,
303
+ session: DeepPartial<T> = {} as DeepPartial<T>,
304
+ cookie: DeepPartial<T> = {} as DeepPartial<T>,
305
305
  ) {
306
306
  const restored_state = {
307
307
  tab: this.get_tab_storage(this.tabStorageKey),