Skip to content

Commit

Permalink
bump minor version to 0.1.1
Browse files Browse the repository at this point in the history
restructure directories so that `mod.ts` now exports all of the important stuff and works with `typedoc` documentation generator
port over signal test from `kitchensink_ts` as `./test/legacy_signal.test.ts`
move the test in `mod.ts` to `./test/simple_signal.test.ts`
add more `ts-ignore` so that `deno task test` does not fail
change dependance on `kitchensink_ts` directly from github repo HEAD to `kitchensink_ts v0.7.0` in `deno.land/x/`
remove JOJO reference in `signal.ts`... NANI!
repo is ready to go public and be released onto `deno.land/x/`
  • Loading branch information
omar-azmi committed Oct 4, 2023
1 parent 2482ef2 commit 0825124
Show file tree
Hide file tree
Showing 12 changed files with 308 additions and 105 deletions.
4 changes: 4 additions & 0 deletions build_npm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ const site_root = Deno.args[0] ?? "/"
const npm_dir = "./npm/"
const main_entrypoint: string = "./src/mod.ts"
const sub_entrypoints: string[] = [
"./src/context.ts",
"./src/signal.ts",
"./src/mapped_signal.ts",
"./src/funcdefs.ts",
"./src/typedefs.ts",
]
const tsconfig = {
"$schema": "https://json.schemastore.org/tsconfig",
Expand Down
2 changes: 1 addition & 1 deletion deno.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "tsignal_ts",
"version": "0.1.0",
"version": "0.1.1",
"description": "a topological order respecting signals library inspired by SolidJS",
"author": "Omar Azmi",
"license": "Lulz plz don't steal yet",
Expand Down
6 changes: 6 additions & 0 deletions src/context.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
/** a context is required for any signal to be functional
* @module
*/

import { DEBUG, bindMethodToSelfByName, bind_array_clear, bind_array_pop, bind_array_push, bind_map_clear, bind_map_get, bind_map_set, bind_set_add, bind_set_clear, bind_set_delete, bind_set_has } from "./deps.ts"
import { hash_ids } from "./funcdefs.ts"
import { EffectFn, MemoFn, SimpleSignalInstance } from "./signal.ts"
Expand Down Expand Up @@ -178,6 +182,7 @@ export class Context {
}
}
}
// @ts-ignore:
this.delEdge = undefined

this.newId = () => {
Expand All @@ -188,6 +193,7 @@ export class Context {
}
this.getId = all_signals_get
this.setId = all_signals_set
// @ts-ignore:
this.delId = undefined
this.runId = (id: ID): boolean => {
const will_fire_immediately = batch_nestedness <= 0
Expand Down
23 changes: 7 additions & 16 deletions src/deps.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,23 @@
export {
bindMethodToSelfByName,
bind_array_clear,
bind_array_push,
bind_array_pop,
bind_array_push,
bind_map_clear,
bind_map_get,
bind_map_set,
bind_set_add,
bind_set_clear,
bind_set_delete,
bind_set_has
} from "https://raw.githubusercontent.com/omar-azmi/kitchensink_ts/main/src/binder.ts"
export { THROTTLE_REJECT, throttle } from "https://raw.githubusercontent.com/omar-azmi/kitchensink_ts/main/src/browser.ts"
export { object_assign, array_isArray } from "https://raw.githubusercontent.com/omar-azmi/kitchensink_ts/main/src/builtin_aliases_deps.ts"
export { prototypeOfClass } from "https://raw.githubusercontent.com/omar-azmi/kitchensink_ts/main/src/struct.ts"
export * as typedefs from "https://raw.githubusercontent.com/omar-azmi/kitchensink_ts/main/src/typedefs.ts"
export type { CallableFunctionsOf, ConstructorOf, MethodsOf } from "https://raw.githubusercontent.com/omar-azmi/kitchensink_ts/main/src/typedefs.ts"
} from "https://deno.land/x/kitchensink_ts@v0.7.0/binder.ts"
export { THROTTLE_REJECT, throttle } from "https://deno.land/x/kitchensink_ts@v0.7.0/browser.ts"
export { array_isArray, noop, object_assign, object_keys, object_values } from "https://deno.land/x/kitchensink_ts@v0.7.0/builtin_aliases_deps.ts"
export { prototypeOfClass } from "https://deno.land/x/kitchensink_ts@v0.7.0/struct.ts"
export * as typedefs from "https://deno.land/x/kitchensink_ts@v0.7.0/typedefs.ts"
export type { CallableFunctionsOf, ConstructorOf, MethodsOf, StaticImplements } from "https://deno.land/x/kitchensink_ts@v0.7.0/typedefs.ts"


export const enum DEBUG {
LOG = 0,
}

export const {
keys: object_keys,
values: object_values,
} = Object

export type StaticImplements<CONSTRUCTOR extends new (...args: any[]) => any, CLASS extends CONSTRUCTOR> = InstanceType<CONSTRUCTOR>

export type SubPropertyMapper<T, PROP extends keyof T[keyof T]> = { [K in keyof T]: T[K][PROP] }
9 changes: 6 additions & 3 deletions src/funcdefs.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { DEBUG, THROTTLE_REJECT, throttle } from "./deps.ts"
/** utility functions
* @module
*/

import { DEBUG, THROTTLE_REJECT, noop, throttle } from "./deps.ts"
import { EqualityCheck, EqualityFn, FROM_ID, HASHED_IDS, ID, Signal, TO_ID, UNTRACKED_ID } from "./typedefs.ts"

export const default_equality = (<T>(v1: T, v2: T) => (v1 === v2)) satisfies EqualityFn<any>
Expand Down Expand Up @@ -43,15 +47,14 @@ export const hash_ids = (ids: ID[]): HASHED_IDS => {
return ids.reduce((sum, id) => sum + id * (id + sqrt_len), 0)
}

export const noop = /* @__PURE__ */ () => { }

export const log_get_request = /* @__PURE__ */ DEBUG.LOG ? (all_signals_get: (id: ID) => Signal<any> | undefined, observed_id: FROM_ID, observer_id?: TO_ID | UNTRACKED_ID) => {
const
observed_signal = all_signals_get(observed_id)!,
observer_signal = observer_id ? all_signals_get(observer_id)! : { name: "untracked" }
console.log(
"GET:\t", observed_signal.name,
"\tby OBSERVER:\t", observer_signal.name,
// @ts-ignore:
"\twith VALUE\t", observed_signal.value,
)
} : noop
10 changes: 7 additions & 3 deletions src/mapped_signal.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
/** mapped signals <br>
* @module
*/

import { Context } from "./context.ts"
import { array_isArray, object_keys, object_values } from "./deps.ts"
import { SimpleSignal_Factory } from "./signal.ts"
Expand Down Expand Up @@ -39,7 +43,7 @@ export interface RecordMemoSignalConfig<K extends PropertyKey, V> extends Record
export const RecordSignal_Factory = (ctx: Context) => {
return class RecordSignal<K extends PropertyKey, V> extends ctx.getClass(SimpleSignal_Factory)<[record: Record<K, V>, ...changed_keys: K[]]> {
declare value: [record: Record<K, V>, ...changed_keys: K[]]
//@ts-ignore:
// @ts-ignore:
declare equals: EqualityFn<V>

constructor(
Expand All @@ -51,7 +55,7 @@ export const RecordSignal_Factory = (ctx: Context) => {
empty_instance_of_record = (record_is_array ? [] : {}) as Record<K, V>,
keys = record_is_array ? [...base_record.keys()] : object_keys(base_record),
values: V[] = record_is_array ? [...base_record.values()] : object_values(base_record)
//@ts-ignore: `RecordSignalConfig` is not a subtype of `SimpleSignalConfig`, but we don't care and just wish to assign `config.equals as EqualityFn<V>`
// @ts-ignore: `RecordSignalConfig` is not a subtype of `SimpleSignalConfig`, but we don't care and just wish to assign `config.equals as EqualityFn<V>`
super([empty_instance_of_record], config)
this.setItems(keys as K[], values, false)
}
Expand All @@ -75,7 +79,7 @@ export const RecordSignal_Factory = (ctx: Context) => {
delta_record.splice(1)
}

//@ts-ignore:
// @ts-ignore:
set(key: K, new_value: V | Updater<V>, ignore?: boolean): boolean {
return this.setItems([key], [new_value], ignore)
}
Expand Down
54 changes: 11 additions & 43 deletions src/mod.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,12 @@
/** */

import { Context } from "./context.ts"
import { EffectSignal_Factory, LazySignal_Factory, MemoSignal_Factory, StateSignal_Factory } from "./signal.ts"

const
ctx = new Context(),
createState = ctx.addClass(StateSignal_Factory),
createMemo = ctx.addClass(MemoSignal_Factory),
createLazy = ctx.addClass(LazySignal_Factory),
createEffect = ctx.addClass(EffectSignal_Factory),
setFn = ctx.dynamic.setFn

const
[, A, setA] = createState(1, { name: "A" }),
[, B, setB] = createState(2, { name: "B" }),
[, C, setC] = createState(3, { name: "C" }),
[, H] = createMemo((id) => (A(id) + G(id)), { name: "H" }),
[, G] = createMemo((id) => (- D(id) + E(id) + 100), { name: "G" }),
[, D] = createMemo((id) => (A(id) * 0 + 10), { name: "D" }),
[, E] = createMemo((id) => (B(id) + F(id) + D(id) + C(id)), { name: "E" }),
[, F] = createMemo((id) => (C(id) + 20), { name: "F" }),
[, I] = createMemo((id) => (F(id) - 100), { name: "I" }),
[idK, K, fireK] = createEffect((id) => { console.log("this is effect K, broadcasting", A(id), B(id), E(id)); J(id) }, { name: "K", dynamic: true }),
[, J, fireJ] = createEffect((id) => { console.log("this is effect J, broadcasting", H(id), D(id), E(id)) }, { name: "J" })

I()
H()
K()
fireK()
setFn(idK, () => { console.log("effect K is now free of all worldy dependencies, yet it is still fired when one of its previous dependiencies are updated. so saj...") })
fireK()

/*
let start = performance.now()
for (let A_value = 0; A_value < 100_000; A_value++) {
setA(A_value)
}
let end = performance.now()
console.log("time:\t", end - start, " ms") // takes 70ms to 130ms for updating signal `A` 100_000 times (with `DEBUG` off) (dfs nodes to update/visit are cached after first run)
setA(10)
setB(10)
/** a equal-calorie clone of the popular reactivity library [SolidJS](https://github.com/solidjs/solid). <br>
* @module
*/

export { Context } from "./context.ts"
export type { Context_Batch, Context_Dynamic } from "./context.ts"
export { default_equality, falsey_equality, throttlingEquals } from "./funcdefs.ts"
export type * from "./mapped_signal.ts"
export { RecordMemoSignal_Factory, RecordSignal_Factory, RecordStateSignal_Factory } from "./mapped_signal.ts"
export type * from "./signal.ts"
export { EffectSignal_Factory, LazySignal_Factory, MemoSignal_Factory, SimpleSignal_Factory, StateSignal_Factory } from "./signal.ts"
export type * from "./typedefs.ts"
11 changes: 5 additions & 6 deletions src/signal.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
/** LALI-HOO!! <br>
* _Yare Yare Daze_ ... Code wa doko ni? ehh? <br>
* Mou daijoubu! Naze tte? Code wa kita, hai dozo. <br>
* Maji desu ka? <br>
* Hai! unmei desu! <br>
* Yogatta! hontoni ureshii desu. <br>
/** a equal-calorie clone of the popular reactivity library [SolidJS](https://github.com/solidjs/solid). <br>
* @module
*/

import { Context } from "./context.ts"
import { DEBUG, StaticImplements, bindMethodToSelfByName } from "./deps.ts"
import { default_equality, falsey_equality, log_get_request } from "./funcdefs.ts"
import { Accessor, EqualityCheck, EqualityFn, ID, Setter, SignalClass, SignalUpdateStatus, TO_ID, UNTRACKED_ID, Updater } from "./typedefs.ts"

// TODO: add `SimpleSignalConfig.deps: ID[]` option to manually enforce dependance on certain signal ids. this can be useful when you want a
// signal to defer its first run, yet you also want that signal to react to any of its dependencies, before this signal ever gets run

export interface SimpleSignalConfig<T> {
/** give a name to the signal for debuging purposes */
name?: string
Expand Down
4 changes: 4 additions & 0 deletions src/typedefs.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
/** base type definitions <br>
* @module
*/

/** type definition for a value equality check function. */
export type EqualityFn<T> = (prev_value: T | undefined, new_value: T) => boolean

Expand Down
146 changes: 146 additions & 0 deletions test/legacy_signal.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { assert } from "https://deno.land/std/testing/asserts.ts"
import { Context } from "../src/context.ts"
import { EffectSignal_Factory, LazySignal_Factory, MemoSignal_Factory, StateSignal_Factory, } from "../src/signal.ts"

const
ctx = new Context(),
createState = ctx.addClass(StateSignal_Factory),
createMemo = ctx.addClass(MemoSignal_Factory),
createLazy = ctx.addClass(LazySignal_Factory),
createEffect = ctx.addClass(EffectSignal_Factory)

Deno.test("create a signal and test getter and setter functions", () => {
const [, getValue, setValue] = createState<number>(0)
assert(getValue() === 0)
setValue(42)
assert(getValue() === 42)
})

Deno.test("create a memo and test its memorization and reactivity with signals", () => {
let counter = 0
const [, memo1] = createMemo((id) => (counter++))
assert(memo1() === 0)
assert(memo1() === 0) // memoized value remains the same

const [, getCounter, setCounter] = createState<number>(0)
let times_memo_was_run: number = 0
const [, memo2] = createMemo((id) => {
times_memo_was_run++
let double = getCounter(id) * 2
return double
})
assert((memo2() === 0) && (times_memo_was_run === 1 as number))
setCounter(5)
assert((memo2() === 10) && (times_memo_was_run === 2 as number))
setCounter(5.0) // same value as before, therefore, signal should NOT notify its `memo2` observer to rerun
assert((memo2() === 10) && (times_memo_was_run === 2 as number))
})

Deno.test("create and execute an effect", () => {
// test option `{ defer?: false }`, to fire the effect right after initialization
let times_effect_was_run: number = 0
const [, getEffect1, fireEffect1] = createEffect((id) => {
times_effect_was_run++
}, { defer: false })
assert(times_effect_was_run === 1)

const [, getCounter, setCounter] = createState<number>(0)
let times_effect_was_run2: number = 0

// test option `{ defer?: true }` and dependance on another effect signal
const [, getEffect2, fireEffect2] = createEffect((id) => {
getEffect1(id) // here, we declare that we are depending on `effect1`, therefore `effect2` must rerun each time `effect1` is run
times_effect_was_run2++
console.log(times_effect_was_run2)
let double = getCounter(id) * 2
return undefined
})
assert(times_effect_was_run2 === 0 && times_effect_was_run === 1)
setCounter(2)
assert(times_effect_was_run2 === 0) // because `effect2` has never been run before (due to `{ defer: undefined }`), it must at least run once so as to capture all of its dependencies
fireEffect2() // dependencies of `effect2` have been captured now, and `effect2` will now react to any of its two dependencies
setCounter(5)
assert(times_effect_was_run2 === 1 as number)
setCounter(5.0) //same value as before, therefore, signal should NOT notify its `effect2` observer to rerun
assert(times_effect_was_run2 === 1 as number)
//TODO trigger cleanup and implement async testing
})

Deno.test("batch set", () => {
let counter = 0
const [, getCounter1, setCounter1] = createState<number>(0)
const [, getCounter2, setCounter2] = createState<number>(0)
const [, getCounter3, setCounter3] = createState<number>(0)

createEffect((id) => {
getCounter1(id)
getCounter2(id)
getCounter3(id)
counter++
}, { defer: false })
assert(counter === 1)
// begin batching. this prevents any update cycle from firing, until `endBatching` is called,
// upon which all fired events are ran all at once (of course, still respecting their topological ordering)
ctx.batch.startBatching()
setCounter1(1)
setCounter2(2)
setCounter3(3)
ctx.batch.endBatching()
assert((getCounter1() === 1) && (getCounter2() === 2) && (getCounter3() === 3))
assert(counter === 2 as number)
})

Deno.test("untrack reactive dependencies", () => {
let counter = 0
const [, getCounter1, setCounter1] = createState<number>(0)
const [, getCounter2, setCounter2] = createState<number>(0)
const [, getCounter3, setCounter3] = createState<number>(0)
createEffect((id) => {
// not passing an `id` to dependency signals (or passing `id = 0`) will result in the signal not becoming a dependency
getCounter1()
getCounter2()
getCounter3()
counter++
}, { defer: false })
assert(counter === 1)
setCounter1(1)
setCounter2(2)
setCounter3(3)
assert((getCounter1() === 1) && (getCounter2() === 2) && (getCounter3() === 3))
assert(counter === 1) // had `id` been passed to all 3 signals, this would have been: `counter === 1`
})

/*
Deno.test("evaluate function with explicit dependencies", () => {
let counter = 0
const [, getA, setA] = createState<number>(0)
const [, getB, setB] = createState<number>(0)
const [, getC, setC] = createState<number>(0)
const dependencyFn = createMemo(dependsOn([getA, getB, getC], () => {
counter++
return getA() + getB()
}))
setA(1)
setB(2)
setC(3)
assert(counter === 4)
assert(dependencyFn() === 3)
})
Deno.test("create effect with explicit dependencies", () => {
let counter = 0
const [getA, setA] = createState<number>(0)
const [getB, setB] = createState<number>(0)
const [getC, setC] = createState<number>(0)
createEffect(dependsOn([getA, getB, getC], () => {
counter++
}))
assert(counter === 1 as number)
setA(1)
assert(counter === 2 as number)
setB(2)
assert(counter === 3 as number)
setC(3)
assert(counter === 4 as number)
})
*/
Loading

0 comments on commit 0825124

Please sign in to comment.