Skip to content

Commit

Permalink
types: Improve types for store, storeState, effects (#36)
Browse files Browse the repository at this point in the history
* IfSubset fix

* add TSliceName to store and others

* embed types inside

* fixes
  • Loading branch information
kepta committed Oct 6, 2023
1 parent a0138c2 commit a773336
Show file tree
Hide file tree
Showing 33 changed files with 1,987 additions and 4,717 deletions.
2 changes: 1 addition & 1 deletion .github/pr-labeler.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'change/feat':
- '/^(feat|perf)/'
'change/fix':
- '/^(fix)/'
- '/^(fix|types)/'
'change/breaking':
- '/^breaking-change/'
'change/docs':
Expand Down
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ Before submitting a PR, make sure to test your changes locally. Here's how:
- `perf`: for performance improvements
- `ci`: for CI/CD related changes
- `build`: for build related changes
- `types`: for type related changes

> The above convention helps us produce a changelog automatically.
Expand Down
75 changes: 75 additions & 0 deletions misc/__test__/base.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { expect, test } from '@jest/globals';
import { createKey, Key, Slice } from '@nalanda/core';
import {
InferSliceDepNames,
InferSliceName,
expectType,
IfEquals,
} from '../type-helpers';

const dep1Key = createKey('dep1Key', []);
const dep1Slice = dep1Key.slice({
fields: {},
});

const dep2Key = createKey('dep2Key', []);
const dep2Slice = dep2Key.slice({
fields: {},
});

const key = createKey('myKey', [dep1Slice, dep2Slice]);

const field1 = key.field(1);

const mySlice = key.slice({
fields: {},
});

test('slice', () => {
type MySlice = typeof mySlice;

type MySliceName = InferSliceName<MySlice>;
type MySliceDepNames = InferSliceDepNames<MySlice>;

() => {
type Result = IfEquals<MySliceName, 'myKey', 'yes', 'no'>;
const result: Result = 'yes';
};

() => {
type Result = IfEquals<MySliceDepNames, 'myKey', 'yes', 'no'>;
let result1: Result = 'no';
};

() => {
type Result = IfEquals<MySliceDepNames, 'dep1Key' | 'dep2Key', 'yes', 'no'>;
let result1: Result = 'yes';
};

expect(1).toBe(1);
});

test('key', () => {
type KeyType = typeof key;

type InferName<T> = T extends Key<infer Name, any> ? Name : never;
type InferDepNames<T> = T extends Key<any, infer DepNames> ? DepNames : never;
type DepName = InferDepNames<KeyType>;

() => {
type Result = IfEquals<InferName<KeyType>, 'myKey', 'yes', 'no'>;
const result: Result = 'yes';
};

() => {
type Result = IfEquals<DepName, 'myKey', 'yes', 'no'>;
let result1: Result = 'no';
};

() => {
type Result = IfEquals<DepName, 'dep1Key' | 'dep2Key', 'yes', 'no'>;
let result1: Result = 'yes';
};

expect(1).toBe(1);
});
24 changes: 0 additions & 24 deletions misc/__test__/types.test.ts

This file was deleted.

3 changes: 2 additions & 1 deletion misc/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"extends": "tsconfig/base.json",
"compilerOptions": {
"types": ["node"]
"types": ["node"],
"skipLibCheck": false
},
"include": ["."],
"exclude": ["node_modules"]
Expand Down
19 changes: 19 additions & 0 deletions misc/type-helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Slice } from '@nalanda/core';

export type IfEquals<T, U, Y = unknown, N = never> = (<G>() => G extends T
? 1
: 2) extends <G>() => G extends U ? 1 : 2
? Y
: N;

export const expectType = <Expected, Actual>(
_actual: IfEquals<Actual, Expected, Actual>,
) => void 0;

export type InferSliceName<T> = T extends Slice<any, infer Name, any>
? Name
: never;

export type InferSliceDepNames<T> = T extends Slice<any, any, infer DepNames>
? DepNames
: never;
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"lint:tsc": "tsc --noEmit",
"lint:prettier": "prettier src --check",
"format": "prettier src --write",
"build": "tsup --config tsup.config.ts && pnpm run patch-core-types",
"build": "tsup --config tsup.config.ts",
"build:watch": "tsup --config tsup.config.ts --watch",
"prepack": "pnpm run build",
"patch-core-types": "tsx ./src/patch/patch-types.ts",
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/__tests__/actions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ afterEach(() => {
});

describe('actions', () => {
const key = createKey('mySliceName');
const key = createKey('mySliceName', []);

const counter = key.field(0);
const counterNegative = key.field(-1);
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/__tests__/field.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ beforeEach(() => {

describe('internal fields', () => {
test('internal field should be updated', () => {
const key = createKey('mySliceName');
const key = createKey('mySliceName', []);
const counter = key.field(0);
const counterSlice = key.slice({
fields: {},
Expand All @@ -36,7 +36,7 @@ describe('internal fields', () => {

describe('mix of internal and external fields', () => {
const setup = () => {
const key = createKey('mySliceName');
const key = createKey('mySliceName', []);
const counter = key.field(0);
const myName = key.field('kj');
const callCount = {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/__tests__/store-state.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ describe('StoreState Slice and Transaction Operations', () => {
return myField.update(fixedState);
}

let storeStateInstances: StoreState[] = [];
let storeStateInstances: StoreState<any>[] = [];

function mutatingAction(inputNumber: number) {
const transaction = mySliceKey.transaction();
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/__tests__/store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
import { createKey, createStore } from '../index';
import { testCleanup } from '../helpers/test-cleanup';

const key = createKey('mySliceName');
const key = createKey('mySliceName', []);

const counter = key.field(0);
const counterNegative = key.field(-1);
Expand Down
42 changes: 42 additions & 0 deletions packages/core/src/__tests__/types.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/* eslint-disable @typescript-eslint/no-duplicate-type-constituents */
import { describe, test } from '@jest/globals';
import { IfSubset, expectType } from '../types';

describe('IfSubset', () => {
test('works', () => {
expectType<
IfSubset<'kushan', 'kushan' | 'joshi' | 'test', 'yes', 'no'>,
'yes'
>('yes');
expectType<IfSubset<'kushan', 'kushan' | 'joshi', 'yes', 'no'>, 'yes'>(
'yes',
);
expectType<IfSubset<'kushan', 'kushan', 'yes', 'no'>, 'yes'>('yes');

expectType<IfSubset<'kushan', '', 'yes', 'no'>, 'no'>('no');
expectType<
IfSubset<'kushan' | 'joshi', 'kushan' | 'c' | 'joshi' | 'c', 'yes', 'no'>,
'yes'
>('yes');

expectType<
IfSubset<
'kushan' | 'joshi' | 'k',
'kushan' | 'c' | 'joshi' | 'c',
'yes',
'no'
>,
'no'
>('no');

expectType<
IfSubset<
'kushan' | 'joshi' | 'k',
'kushan' | 'c' | 'joshi' | 'c' | 'k',
'yes',
'no'
>,
'yes'
>('yes');
});
});
4 changes: 2 additions & 2 deletions packages/core/src/base-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ export type Dispatch = (
) => void;

export abstract class BaseStore {
abstract readonly state: StoreState;
abstract readonly state: StoreState<any>;

// @internal
abstract _rootStore: Store;
abstract _rootStore: Store<any>;

abstract dispatch(txn: Transaction | Operation): void;
}
2 changes: 1 addition & 1 deletion packages/core/src/effect/effect-run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export class EffectRun {
}

constructor(
public readonly store: Store,
private readonly store: Store<any>,
public readonly name: string,
) {}

Expand Down
12 changes: 6 additions & 6 deletions packages/core/src/effect/effect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export class EffectStore extends BaseStore {
// @internal
constructor(
// @internal
public _rootStore: Store,
public _rootStore: Store<any>,
public readonly name: string,
/**
* @internal
Expand All @@ -52,7 +52,7 @@ export type EffectScheduler = (
) => void;

export type EffectCallback = (store: EffectStore) => void | Promise<void>;
export type EffectCreator = (store: Store) => Effect;
export type EffectCreator = (store: Store<any>) => Effect;

const DEFAULT_SCHEDULER: EffectScheduler = (cb, opts) => {
if (opts.deferred) {
Expand Down Expand Up @@ -88,7 +88,7 @@ export class Effect {
// @internal
private readonly effectCallback: EffectCallback,
// @internal
private readonly rootStore: Store,
private readonly rootStore: Store<any>,
public readonly opts: EffectOpts,
) {
this.name = opts.name || effectCallback.name || 'anonymous';
Expand Down Expand Up @@ -171,7 +171,7 @@ export class Effect {
return;
}

let fieldChanged: BaseField<unknown> | undefined;
let fieldChanged: BaseField | undefined;

// if runCount == 0, always run, to ensure the effect runs at least once
if (this.runCount != 0) {
Expand Down Expand Up @@ -201,15 +201,15 @@ export class Effect {
}
}

export function effect(
export function effect<TSliceName extends string>(
callback: EffectCallback,
{
deferred = true,
maxWait = DEFAULT_MAX_WAIT,
scheduler = DEFAULT_SCHEDULER,
}: Partial<EffectOpts> = {},
): EffectCreator {
return (store: Store) => {
return (store: Store<TSliceName>) => {
const newEffect = new Effect(callback, store, {
deferred,
maxWait,
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/effect/operation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ export class OperationStore extends BaseStore {
private cleanupRan = false;
private readonly _cleanupCallbacks: Set<CleanupCallback> = new Set();

_rootStore: Store;
_rootStore: Store<any>;
constructor(
private rootStore: Store,
private rootStore: Store<any>,
public readonly name: string,
private readonly opts: OperationOpts,
) {
Expand Down
7 changes: 4 additions & 3 deletions packages/core/src/effect/ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ export type RefObject<T> = {

export function ref<T>(
init: () => T,
): (store: Store | BaseStore) => RefObject<T> {
const cache = new WeakMap<Store, RefObject<T>>();
): (store: Store<any> | BaseStore) => RefObject<T> {
const cache = new WeakMap<Store<any>, RefObject<T>>();

return (store) => {
const rootStore: Store = store instanceof Store ? store : store._rootStore;
const rootStore: Store<any> =
store instanceof Store ? store : store._rootStore;

let existing = cache.get(rootStore);

Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,9 @@ export { cleanup } from './effect/cleanup';
export { createKey } from './slice/key';
export { createStore } from './store';
export { ref } from './effect/ref';
export type { IfSubset } from './types';
export type { Slice } from './slice/slice';
export type { Key } from './slice/key';
export type { StoreState } from './store-state';
export type { Store } from './store';
export type { Effect } from './effect/effect';
Loading

0 comments on commit a773336

Please sign in to comment.