Skip to content
Draft
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ temp
*.md
coverage
.angular
tests
13 changes: 10 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,16 @@
"main": "./dist/package/index.cjs",
"module": "./dist/package/index.js",
"exports": {
"types": "./dist/package/index.d.ts",
"require": "./dist/package/index.cjs",
"default": "./dist/package/index.js"
".": {
"types": "./dist/package/index.d.ts",
"require": "./dist/package/index.cjs",
"default": "./dist/package/index.js"
},
"./polyfill": {
"types": "./dist/package/wrapper.d.ts",
"require": "./dist/package/wrapper.cjs",
"default": "./dist/package/wrapper.js"
}
},
"license": "MIT",
"repository": {
Expand Down
20 changes: 13 additions & 7 deletions rollup.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,20 @@ export default defineConfig({
output: [
{
format: 'cjs',
file: './dist/package/index.cjs',
dir: './dist/package',
entryFileNames: (chunk) => `${chunk.name}.cjs`,
},
{
format: 'es',
file: './dist/package/index.js',
dir: './dist/package',
entryFileNames: (chunk) => `${chunk.name}.js`,
},
],
input: './src/index.ts',
input: { index: './src/index.ts', wrapper: './src/wrapper.ts' },
plugins: [
typescript(),
typescript({
tsconfig: 'tsconfig.d.json',
}),
{
name: 'package',
async buildStart() {
Expand All @@ -36,9 +40,11 @@ export default defineConfig({
pkg.typings = removeDistPackage(pkg.typings);
pkg.main = removeDistPackage(pkg.main);
pkg.module = removeDistPackage(pkg.module);
pkg.exports.types = removeDistPackage(pkg.exports.types);
pkg.exports.require = removeDistPackage(pkg.exports.require);
pkg.exports.default = removeDistPackage(pkg.exports.default);
for (const entryPoint of Object.values(pkg.exports)) {
entryPoint.types = removeDistPackage(entryPoint.types);
entryPoint.require = removeDistPackage(entryPoint.require);
entryPoint.default = removeDistPackage(entryPoint.default);
}
this.emitFile({ type: 'asset', fileName: 'package.json', source: JSON.stringify(pkg) });
this.emitFile({
type: 'asset',
Expand Down
3 changes: 3 additions & 0 deletions src/internal/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { SignalStore, SubscribableStore } from '../types';

export interface Consumer {
markDirty(): void;
wrapper?: any;
}

export const enum RawStoreFlags {
Expand All @@ -13,6 +14,7 @@ export const enum RawStoreFlags {
// the following flags are used in RawStoreComputedOrDerived and derived classes
COMPUTING = 1 << 3,
DIRTY = 1 << 4,
COMPUTED_WITH_ONUSE = 1 << 5,
}

export interface BaseLink<T> {
Expand All @@ -30,6 +32,7 @@ export interface RawStore<T, Link extends BaseLink<T> = BaseLink<T>>
updateValue(): void;
isLinkUpToDate(link: Link): boolean;
updateLink(link: Link): T;
wrapper?: any;
}

export const updateLinkProducerValue = <T>(link: BaseLink<T>): void => {
Expand Down
16 changes: 11 additions & 5 deletions src/internal/storeComputed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export class RawStoreComputed<T>
implements Consumer, ActiveConsumer
{
private producerIndex = 0;
private producerLinks: BaseLink<any>[] = [];
public producerLinks: BaseLink<any>[] = [];
private epoch = -1;

constructor(private readonly computeFn: () => T) {
Expand Down Expand Up @@ -59,7 +59,10 @@ export class RawStoreComputed<T>
producerLinks[producerIndex] = link;
this.producerIndex = producerIndex + 1;
updateLinkProducerValue(link);
if (producer.flags & RawStoreFlags.HAS_VISIBLE_ONUSE) {
if (
!(this.flags & RawStoreFlags.COMPUTED_WITH_ONUSE) &&
producer.flags & RawStoreFlags.HAS_VISIBLE_ONUSE
) {
this.flags |= RawStoreFlags.HAS_VISIBLE_ONUSE;
}
return producer.updateLink(link);
Expand All @@ -72,6 +75,7 @@ export class RawStoreComputed<T>
link.producer.registerConsumer(link);
}
this.flags |= RawStoreFlags.DIRTY;
super.startUse();
}

override endUse(): void {
Expand All @@ -80,6 +84,7 @@ export class RawStoreComputed<T>
const link = producerLinks[i];
link.producer.unregisterConsumer(link);
}
super.endUse();
}

override areProducersUpToDate(): boolean {
Expand All @@ -103,9 +108,10 @@ export class RawStoreComputed<T>
const prevActiveConsumer = setActiveConsumer(this);
try {
this.producerIndex = 0;
this.flags &= ~RawStoreFlags.HAS_VISIBLE_ONUSE;
const computeFn = this.computeFn;
value = computeFn();
if (!(this.flags & RawStoreFlags.COMPUTED_WITH_ONUSE)) {
this.flags &= ~RawStoreFlags.HAS_VISIBLE_ONUSE;
}
value = this.computeFn.call(this.wrapper);
this.error = null;
} catch (error) {
value = COMPUTED_ERRORED;
Expand Down
2 changes: 2 additions & 0 deletions src/internal/storeDerived.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ abstract class RawStoreDerived<T, S extends StoresInput>
producer.registerConsumer(producer.newLink(this))
);
this.flags |= RawStoreFlags.DIRTY;
super.startUse();
}

override endUse(): void {
Expand All @@ -63,6 +64,7 @@ abstract class RawStoreDerived<T, S extends StoresInput>
link.producer.unregisterConsumer(link);
}
}
super.endUse();
}

override areProducersUpToDate(): boolean {
Expand Down
2 changes: 2 additions & 0 deletions src/internal/storeSubscribable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export class RawSubscribableWrapper<T> extends RawStoreTrackingUsage<T> {

override startUse(): void {
this.unsubscribe = normalizeUnsubscribe(this.subscribable.subscribe(this.subscriber));
super.startUse();
}

override endUse(): void {
Expand All @@ -37,5 +38,6 @@ export class RawSubscribableWrapper<T> extends RawStoreTrackingUsage<T> {
this.unsubscribe = null;
unsubscribe();
}
super.endUse();
}
}
25 changes: 21 additions & 4 deletions src/internal/storeTrackingUsage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,19 @@ export const flushUnused = (): void => {
}
};

export abstract class RawStoreTrackingUsage<T> extends RawStoreWritable<T> {
export class RawStoreTrackingUsage<T> extends RawStoreWritable<T> {
private extraUsages = 0;
abstract startUse(): void;
abstract endUse(): void;
startUseFn?: () => void;
endUseFn?: () => void;

override updateValue(): void {
startUse(): void {
this.startUseFn?.call(this.wrapper);
}
endUse(): void {
this.endUseFn?.call(this.wrapper);
}

private callOnUse(): boolean {
const flags = this.flags;
if (!(flags & RawStoreFlags.START_USE_CALLED)) {
// Ignoring coverage for the following lines because, unless there is a bug in tansu (which would have to be fixed!)
Expand All @@ -44,7 +51,17 @@ export abstract class RawStoreTrackingUsage<T> extends RawStoreWritable<T> {
}
this.flags |= RawStoreFlags.START_USE_CALLED;
untrack(() => this.startUse());
return true;
}
return false;
}

override updateValue(): void {
this.callOnUse();
}

isUsed(): boolean {
return this.extraUsages > 0 || (this.consumerLinks?.length ?? 0) > 0;
}

override checkUnused(): void {
Expand Down
2 changes: 2 additions & 0 deletions src/internal/storeWithOnUse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export class RawStoreWithOnUse<T> extends RawStoreTrackingUsage<T> {

override startUse(): void {
this.cleanUpFn = normalizeUnsubscribe(this.onUseFn());
super.startUse();
}

override endUse(): void {
Expand All @@ -24,5 +25,6 @@ export class RawStoreWithOnUse<T> extends RawStoreTrackingUsage<T> {
this.cleanUpFn = null;
cleanUpFn();
}
super.endUse();
}
}
4 changes: 2 additions & 2 deletions src/internal/storeWritable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export class RawStoreWritable<T> implements RawStore<T, ProducerConsumerLink<T>>
equalFn = equal<T>;
private equalCache: Record<number, boolean> | null = null;
consumerLinks: ProducerConsumerLink<T>[] = [];
wrapper?: any;

newLink(consumer: Consumer): ProducerConsumerLink<T> {
return {
Expand Down Expand Up @@ -102,8 +103,7 @@ export class RawStoreWritable<T> implements RawStore<T, ProducerConsumerLink<T>>
updateValue(): void {}

protected equal(a: T, b: T): boolean {
const equalFn = this.equalFn;
return equalFn(a, b);
return this.equalFn.call(this.wrapper, a, b);
}

protected increaseEpoch(): void {
Expand Down
1 change: 1 addition & 0 deletions src/internal/untrack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { BaseLink, RawStore } from './store';

export interface ActiveConsumer {
addProducer: <T, L extends BaseLink<T>>(store: RawStore<T, L>) => T;
wrapper?: any;
}

export let activeConsumer: ActiveConsumer | null = null;
Expand Down
44 changes: 44 additions & 0 deletions src/internal/watcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { updateLinkProducerValue, type BaseLink, type Consumer, type RawStore } from './store';

export class RawWatcher implements Consumer {
producerLinks: BaseLink<any>[] = [];
dirty = false;
wrapper?: any;

constructor(public notifyFn: () => void) {}

markDirty(): void {
if (!this.dirty) {
this.dirty = true;
this.notifyFn.call(this.wrapper);
}
}

update(): void {
try {
this.dirty = true;
this.producerLinks.forEach(updateLinkProducerValue);
} finally {
this.dirty = false;
}
}

addProducer(producer: RawStore<any>): void {
const link = producer.newLink(this);
this.producerLinks.push(link);
producer.registerConsumer(link);
}

removeProducer(producer: RawStore<any>): void {
const producerLinks = this.producerLinks;
const index = producerLinks.findIndex((link) => link.producer === producer);
if (index > -1) {
const link = producerLinks[index];
const lastItem = producerLinks.pop()!;
if (link !== lastItem) {
producerLinks[index] = lastItem;
}
producer.unregisterConsumer(link);
}
}
}
Loading
Loading