-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
45 changed files
with
145 additions
and
745 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,50 +1,21 @@ | ||
import { expect, test, beforeEach } from "bun:test"; | ||
import { model } from "../model"; | ||
import { getModel } from "../get-model"; | ||
import { reset } from "../state"; | ||
import { Null } from "../null"; | ||
|
||
let count = 0; | ||
|
||
const Counter = model({ | ||
type: "Counter", | ||
refresh: { seconds: 60 }, | ||
async get() { | ||
return ++count; | ||
}, | ||
import { expect, test } from "bun:test"; | ||
import { model, getModel, request, compute } from "../index"; | ||
import { delay } from "./util/delay"; | ||
|
||
const Echo = model((value: string) => { | ||
return request(async () => { | ||
await delay(100); | ||
return value; | ||
}); | ||
}); | ||
|
||
beforeEach(() => { | ||
count = 0; | ||
reset(Counter()); | ||
const UppercaseEcho = model((value: string) => { | ||
return compute((get) => { | ||
return get(Echo(value)).value.toUpperCase(); | ||
}); | ||
}); | ||
|
||
test("getModel", async () => { | ||
const [count] = await getModel(Counter()); | ||
expect(count).toBe(1); | ||
}); | ||
|
||
test("getModel uses cached value", async () => { | ||
await getModel(Counter()); | ||
const [count] = await getModel(Counter()); | ||
expect(count).toBe(1); | ||
}); | ||
|
||
test("force model to update", async () => { | ||
await getModel(Counter()); | ||
reset(Counter()); | ||
const [count] = await getModel(Counter()); | ||
expect(count).toBe(2); | ||
}); | ||
|
||
test("get null model", async () => { | ||
const enabled = false; | ||
const [value] = await getModel(enabled ? Counter() : Null()); | ||
expect(value).toBe(null); | ||
}); | ||
|
||
test("get expiry", async () => { | ||
const now = Date.now(); | ||
const [_, { expiry }] = await getModel(Counter()); | ||
expect(expiry).toBe(now + 60 * 1000); | ||
const uppercaseEcho = await getModel(UppercaseEcho("test")); | ||
expect(uppercaseEcho.value).toBe("TEST"); | ||
}); |
File renamed without changes.
File renamed without changes.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,30 @@ | ||
import type { ModelResult, Query, QueryType } from "./core-types"; | ||
import { getAtom, getPromise } from "./state"; | ||
import type { Spec, State } from "./core"; | ||
import { getPromise, initSpace } from "./cosmos"; | ||
import type { Ready } from "./later"; | ||
|
||
/** | ||
* Resolve a model query as a promise. | ||
*/ | ||
export function getModel<Q extends Query>(query: Q) { | ||
return getPromise(query).then((atom) => { | ||
return [atom.value, atom, query] as ModelResult<QueryType<Q>>; | ||
}); | ||
export type GetModelResult<T> = State<T> & PromiseShape<State<Ready<T>>>; | ||
|
||
type PromiseShape<T> = { | ||
then: Promise<T>["then"]; | ||
catch: Promise<T>["catch"]; | ||
finally: Promise<T>["finally"]; | ||
}; | ||
|
||
export function getModel<T>(spec: Spec<T>): GetModelResult<T> { | ||
const space = initSpace(spec); | ||
return { | ||
...space.state, | ||
get then() { | ||
const promise = getPromise(spec); | ||
return promise.then.bind(promise); | ||
}, | ||
get catch() { | ||
const promise = getPromise(spec); | ||
return promise.catch.bind(promise); | ||
}, | ||
get finally() { | ||
const promise = getPromise(spec); | ||
return promise.finally.bind(promise); | ||
}, | ||
}; | ||
} |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,11 @@ | ||
export * from "./later"; | ||
export * from "./core"; | ||
export * from "./model"; | ||
export * from "./use-model"; | ||
export * from "./set-model"; | ||
export * from "./get-model"; | ||
export * from "./null"; | ||
export * from "./wait-for"; | ||
export { getAtom, checkAtom } from "./state"; | ||
export * from "./snapshot"; | ||
export * from "./value"; | ||
export * from "./compute"; | ||
export * from "./request"; | ||
export * from "./persist"; |
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,39 +1,54 @@ | ||
import stableStringify from "safe-stable-stringify"; | ||
import type { Query } from "./core-types"; | ||
import type { GetterModel } from "./templates/getter-model"; | ||
import { fromGetterModel, isGetterModel } from "./templates/getter-model"; | ||
import type { DerivedModel } from "./templates/derived-model"; | ||
import { fromDerivedModel, isDerivedModel } from "./templates/derived-model"; | ||
import { | ||
fromEmitterModel, | ||
isEmitterModel, | ||
type EmitterModel, | ||
} from "./templates/emitter-model"; | ||
|
||
export function model<T, P extends object | void = void>( | ||
m: GetterModel<T, P> | EmitterModel<T, P> | DerivedModel<T, P> | ||
) { | ||
// TODO make it easier to add new model templates | ||
const genericModel = isGetterModel<T, P>(m) | ||
? fromGetterModel(m) | ||
: isEmitterModel<T, P>(m) | ||
? fromEmitterModel(m) | ||
: isDerivedModel<T, P>(m) | ||
? fromDerivedModel(m) | ||
: (null as never); | ||
|
||
return function buildQuery(params: P): Query<T, P> { | ||
import type { Behavior, Model } from "./core"; | ||
import { combineBehavior, type Traits } from "./combine-behavior"; | ||
|
||
export function model<A extends any[], V>(resolve: Resolve<A, V>): Model<A, V>; | ||
|
||
export function model<A extends any[], V>( | ||
identity: string | Identity<A>, | ||
resolve: Resolve<A, V> | ||
): Model<A, V>; | ||
|
||
export function model<A extends any[], V>( | ||
identity: string | Identity<A> | Resolve<A, V>, | ||
resolve?: Resolve<A, V> | ||
): Model<A, V> { | ||
// Create a default identity if one is not provided | ||
if (typeof identity === "function") { | ||
return model(toIdentity({}), identity); | ||
} | ||
|
||
const _identity = toIdentity(identity); | ||
const _resolve = resolve!; | ||
|
||
return (...args) => { | ||
return { | ||
$key: `${genericModel.type}(${serializeParams(params)})`, | ||
model: genericModel, | ||
params, | ||
name: _identity.name, | ||
args: _identity.args(...args), | ||
resolve: () => combineBehavior(_resolve(...args)), | ||
}; | ||
}; | ||
} | ||
|
||
function serializeParams<P extends object | void>(params: P) { | ||
if (params == null) { | ||
return ""; | ||
type Identity<A extends any[]> = { | ||
name: string; | ||
args: (...args: A) => unknown[]; | ||
}; | ||
|
||
type Resolve<A extends any[], V> = (...args: A) => Behavior<V> | Traits<V>; | ||
|
||
let nextModelId = 0; | ||
|
||
function toIdentity<A extends any[]>( | ||
identity: string | Partial<Identity<A>> | ||
): Identity<A> { | ||
if (typeof identity === "string") { | ||
return { | ||
name: identity, | ||
args: (...args) => args, | ||
}; | ||
} | ||
return stableStringify(params); | ||
return { | ||
name: identity.name ?? `MODEL-${nextModelId++}`, | ||
args: identity.args ?? ((...args) => args), | ||
}; | ||
} |
This file was deleted.
Oops, something went wrong.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes.
File renamed without changes.
Oops, something went wrong.