Skip to content

Commit 8ea1825

Browse files
committed
improve type definitions
1 parent 1c7df81 commit 8ea1825

4 files changed

Lines changed: 103 additions & 42 deletions

File tree

packages/shadow-objects/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ All notable changes to [@spearwolf/shadow-objects](https://github.com/spearwolf/
55
The format is loosely based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.26.3] - 2026-01-15
9+
10+
- fix type definitions for `provideContext()`, `provideGlobalContext()`, `useContext()`, `useParentContext()` and `useProperty()` when using the deprecated third argument as `isEqual` callback
11+
- improve type definitions for `createResource()`
12+
- resource is set to `undefined` after entity destruction
13+
814
## [0.26.2] - 2026-01-12
915

1016
- fix the check for when the deprecation warning is shown in `provideContext()` and `provideGlobalContext()`

packages/shadow-objects/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@spearwolf/shadow-objects",
33
"description": "a reactive entity-component framework that feels at home in the shadows",
4-
"version": "0.26.2",
4+
"version": "0.26.3",
55
"author": {
66
"name": "Wolfger Schramm",
77
"email": "wolfger@spearwolf.de",

packages/shadow-objects/src/in-the-dark/Kernel.ts

Lines changed: 75 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import type {
2020
ShadowObjectType,
2121
SyncEvent,
2222
Maybe,
23+
SignalValueOptions,
2324
} from '../types.js';
2425
import {ConsoleLogger} from '../utils/ConsoleLogger.js';
2526
import {toMaybe} from '../utils/toMaybe.js';
@@ -58,6 +59,10 @@ enum ShadowObjectAction {
5859
const getDisplayName = (construct: ShadowObjectConstructor) => construct.displayName || construct.name;
5960

6061
let provideContextOptionsDeprecatedShown = false;
62+
let provideGlobalContextOptionsDeprecatedShown = false;
63+
let useContextOptionsDeprecatedShown = false;
64+
let useParentContextOptionsDeprecatedShown = false;
65+
let usePropertyOptionsDeprecatedShown = false;
6166

6267
/**
6368
* The entity kernel manages the lifecycle of all entities and shadow-objects.
@@ -357,14 +362,28 @@ export class Kernel {
357362

358363
const propertyReaders = new Map<string, SignalReader<any>>();
359364

360-
const getUseProperty = <T = any>(name: string, compare?: CompareFunc<T>): SignalReader<T> => {
365+
const getUseProperty = <T = any>(
366+
name: string,
367+
options?: SignalValueOptions<T> | CompareFunc<T | undefined>,
368+
): SignalReader<T> => {
369+
if (!usePropertyOptionsDeprecatedShown && options != null && typeof options === 'function') {
370+
console.warn(
371+
'[shadow-objects] Deprecation Warning: The "isEqual" option of "useProperty()" is now passed as {compare} argument. Please update your code accordingly.',
372+
);
373+
usePropertyOptionsDeprecatedShown = true;
374+
}
375+
376+
const opts = typeof options === 'function' ? {compare: options} : options;
377+
361378
let propReader = propertyReaders.get(name);
379+
362380
if (propReader === undefined) {
363-
propReader = createSignal<any>(undefined, compare ? {compare} : undefined).get;
381+
propReader = createSignal<any>(undefined, opts).get;
364382
propertyReaders.set(name, propReader);
365383
const con = link(entry.entity.getPropertyReader(name), propReader);
366384
unsubscribeSecondary.add(con.destroy.bind(con));
367385
}
386+
368387
return propReader;
369388
};
370389

@@ -374,23 +393,23 @@ export class Kernel {
374393

375394
provideContext<T = unknown>(
376395
name: string | symbol,
377-
sourceOrInitialValue?: T | SignalReader<T>,
378-
options?: ProvideContextOptions<T> | CompareFunc<T>,
396+
sourceOrInitialValue?: T | SignalReader<T | undefined>,
397+
options?: ProvideContextOptions<T> | CompareFunc<T | undefined>,
379398
) {
380-
let ctxProvider = contextProviders.get(name);
381-
382399
if (!provideContextOptionsDeprecatedShown && options != null && typeof options === 'function') {
383400
console.warn(
384-
'[shadow-objects] Deprecation Warning: The "isEqual" option of "provideContext()" and "provideGlobalContext()" is now passed as {compare} argument. Please update your code accordingly.',
401+
'[shadow-objects] Deprecation Warning: The "isEqual" option of "provideContext()" is now passed as {compare} argument. Please update your code accordingly.',
385402
);
386403
provideContextOptionsDeprecatedShown = true;
387404
}
388405

389406
const opts = typeof options === 'function' ? {compare: options} : options;
390407

408+
let ctxProvider = contextProviders.get(name);
409+
391410
if (ctxProvider == null) {
392411
const isSig = isSignal(sourceOrInitialValue);
393-
const initialValue = isSig ? undefined : (sourceOrInitialValue as T);
412+
const initialValue = isSig ? undefined : toMaybe(sourceOrInitialValue as T);
394413

395414
ctxProvider = createSignal(initialValue, opts?.compare ? {compare: opts.compare} : undefined);
396415

@@ -415,23 +434,23 @@ export class Kernel {
415434

416435
provideGlobalContext<T = unknown>(
417436
name: string | symbol,
418-
sourceOrInitialValue?: T | SignalReader<T>,
419-
options?: ProvideContextOptions<T> | CompareFunc<T>,
437+
sourceOrInitialValue?: T | SignalReader<T | undefined>,
438+
options?: ProvideContextOptions<T> | CompareFunc<T | undefined>,
420439
) {
421-
let ctxProvider = contextRootProviders.get(name);
422-
423-
if (!provideContextOptionsDeprecatedShown && options != null && typeof options === 'function') {
440+
if (!provideGlobalContextOptionsDeprecatedShown && options != null && typeof options === 'function') {
424441
console.warn(
425-
'[shadow-objects] Deprecation Warning: The "isEqual" option of "provideContext()" and "provideGlobalContext()" is now passed as {compare} argument. Please update your code accordingly.',
442+
'[shadow-objects] Deprecation Warning: The "isEqual" option of "provideGlobalContext()" is now passed as {compare} argument. Please update your code accordingly.',
426443
);
427-
provideContextOptionsDeprecatedShown = true;
444+
provideGlobalContextOptionsDeprecatedShown = true;
428445
}
429446

430447
const opts = typeof options === 'function' ? {compare: options} : options;
431448

449+
let ctxProvider = contextRootProviders.get(name);
450+
432451
if (ctxProvider == null) {
433452
const isSig = isSignal(sourceOrInitialValue);
434-
const initialValue = isSig ? undefined : (sourceOrInitialValue as T);
453+
const initialValue = isSig ? undefined : toMaybe(sourceOrInitialValue as T);
435454

436455
ctxProvider = createSignal(initialValue, opts?.compare ? {compare: opts.compare} : undefined);
437456

@@ -454,25 +473,47 @@ export class Kernel {
454473
return ctxProvider;
455474
},
456475

457-
useContext(name: string | symbol, compare?: CompareFunc<any>) {
476+
useContext<T = unknown>(name: string | symbol, options?: SignalValueOptions<T> | CompareFunc<T | undefined>) {
477+
if (!useContextOptionsDeprecatedShown && options != null && typeof options === 'function') {
478+
console.warn(
479+
'[shadow-objects] Deprecation Warning: The "isEqual" option of "useContext()" is now passed as {compare} argument. Please update your code accordingly.',
480+
);
481+
useContextOptionsDeprecatedShown = true;
482+
}
483+
484+
const opts = typeof options === 'function' ? {compare: options} : options;
485+
458486
let ctxReader = contextReaders.get(name);
487+
459488
if (ctxReader === undefined) {
460-
ctxReader = createSignal<any>(undefined, compare ? {compare} : undefined).get;
489+
ctxReader = createSignal<any>(undefined, opts).get;
461490
contextReaders.set(name, ctxReader);
462-
const con = link(entry.entity.useContext(name), ctxReader);
463-
unsubscribeSecondary.add(con.destroy.bind(con));
491+
const ln = link(entry.entity.useContext(name), ctxReader);
492+
unsubscribeSecondary.add(ln.destroy.bind(ln));
464493
}
494+
465495
return ctxReader;
466496
},
467497

468-
useParentContext(name: string | symbol, compare?: CompareFunc<any>) {
498+
useParentContext<T = unknown>(name: string | symbol, options?: SignalValueOptions<T> | CompareFunc<T | undefined>) {
499+
if (!useParentContextOptionsDeprecatedShown && options != null && typeof options === 'function') {
500+
console.warn(
501+
'[shadow-objects] Deprecation Warning: The "isEqual" option of "useParentContext()" is now passed as {compare} argument. Please update your code accordingly.',
502+
);
503+
useParentContextOptionsDeprecatedShown = true;
504+
}
505+
506+
const opts = typeof options === 'function' ? {compare: options} : options;
507+
469508
let ctxReader = contextParentReaders.get(name);
509+
470510
if (ctxReader === undefined) {
471-
ctxReader = createSignal<any>(undefined, compare ? {compare} : undefined).get;
511+
ctxReader = createSignal<any>(undefined, opts).get;
472512
contextParentReaders.set(name, ctxReader);
473-
const con = link(entry.entity.useParentContext(name), ctxReader);
474-
unsubscribeSecondary.add(con.destroy.bind(con));
513+
const ln = link(entry.entity.useParentContext(name), ctxReader);
514+
unsubscribeSecondary.add(ln.destroy.bind(ln));
475515
}
516+
476517
return ctxReader;
477518
},
478519

@@ -488,14 +529,12 @@ export class Kernel {
488529
return result;
489530
},
490531

491-
createResource<T>(factory: () => T | undefined, cleanup?: (resource: NonNullable<T>) => any): Signal<Maybe<T>> {
532+
createResource<T = unknown>(
533+
factory: () => T | undefined,
534+
cleanup?: (resource: NonNullable<T>) => unknown,
535+
): Signal<Maybe<T>> {
492536
const resourceSignal = createSignal<Maybe<T>>();
493537

494-
unsubscribeSecondary.add(() => {
495-
resourceSignal.set(undefined);
496-
destroySignal(resourceSignal);
497-
});
498-
499538
const effect = createEffect(() => {
500539
const resource = toMaybe(factory());
501540
resourceSignal.set(resource);
@@ -506,12 +545,17 @@ export class Kernel {
506545
resourceSignal.set(undefined);
507546
};
508547
}
548+
509549
return () => {
510550
resourceSignal.set(undefined);
511551
};
512552
});
513553

514-
unsubscribeSecondary.add(effect.destroy);
554+
unsubscribeSecondary.add(() => {
555+
effect.destroy();
556+
resourceSignal.set(undefined);
557+
destroySignal(resourceSignal);
558+
});
515559

516560
return resourceSignal;
517561
},

packages/shadow-objects/src/types.ts

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,11 @@ export type EntityApi = Pick<
9393
traverse(callback: (entity: EntityApi) => any): void;
9494
};
9595

96-
export interface ProvideContextOptions<T> {
97-
compare?: CompareFunc<T>;
96+
export interface SignalValueOptions<T> {
97+
compare?: CompareFunc<T | undefined>;
98+
}
99+
100+
export interface ProvideContextOptions<T> extends SignalValueOptions<T> {
98101
clearOnDestroy?: boolean;
99102
}
100103

@@ -105,23 +108,31 @@ export interface ShadowObjectCreationAPI {
105108

106109
provideContext<T = unknown>(
107110
name: string | symbol,
108-
sourceOrInitialValue?: T | SignalReader<T>,
109-
options?: ProvideContextOptions<T> | CompareFunc<T>,
111+
sourceOrInitialValue?: T | SignalReader<T | undefined>,
112+
options?: ProvideContextOptions<T> | CompareFunc<T | undefined>,
110113
): Signal<T>;
111114

112115
provideGlobalContext<T = unknown>(
113116
name: string | symbol,
114-
sourceOrInitialValue?: T | SignalReader<T>,
115-
options?: ProvideContextOptions<T> | CompareFunc<T>,
117+
sourceOrInitialValue?: T | SignalReader<T | undefined>,
118+
options?: ProvideContextOptions<T> | CompareFunc<T | undefined>,
116119
): Signal<T>;
117120

118-
useContext<T = unknown>(name: string | symbol, isEqual?: CompareFunc<T>): SignalReader<Maybe<T>>;
119-
useParentContext<T = unknown>(name: string | symbol, isEqual?: CompareFunc<T>): SignalReader<Maybe<T>>;
121+
useContext<T = unknown>(
122+
name: string | symbol,
123+
options?: SignalValueOptions<T> | CompareFunc<T | undefined>,
124+
): SignalReader<Maybe<T>>;
125+
126+
useParentContext<T = unknown>(
127+
name: string | symbol,
128+
options?: SignalValueOptions<T> | CompareFunc<T | undefined>,
129+
): SignalReader<Maybe<T>>;
130+
131+
useProperty<T = unknown>(name: string, options?: SignalValueOptions<T> | CompareFunc<T | undefined>): SignalReader<Maybe<T>>;
120132

121-
useProperty<T = unknown>(name: string, isEqual?: CompareFunc<T>): SignalReader<Maybe<T>>;
122133
useProperties<K extends string>(props: Record<K, string>): Record<K, SignalReader<any>>;
123134

124-
createResource<T = unknown>(factory: () => T | undefined, cleanup?: (resource: NonNullable<T>) => any): Signal<Maybe<T>>;
135+
createResource<T = unknown>(factory: () => T | undefined, cleanup?: (resource: NonNullable<T>) => unknown): Signal<Maybe<T>>;
125136

126137
createEffect(...args: Parameters<typeof createEffect>): ReturnType<typeof createEffect>;
127138
createSignal<T = unknown>(...args: Parameters<typeof createSignal<T>>): ReturnType<typeof createSignal<T>>;

0 commit comments

Comments
 (0)