diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 0166fc7..109b03f 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -1,6 +1,6 @@ version: 0.1 cli: - version: 1.15.0 + version: 1.16.0 plugins: sources: - id: trunk diff --git a/package.json b/package.json index c410c3f..e5cf5d8 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "license": "MIT", "private": true, "scripts": { - "postinstall": "test -n \"$NO_YARN_POSTINSTALL\" || yarn run build", + "postinstall": "test -n \"$NO_YARN_POSTINSTALL\" || bun run build", "core": "bun workspace @plexusjs/core", "react": "bun workspace @plexusjs/react", "next": "bun workspace @plexusjs/next", @@ -18,9 +18,9 @@ "docs:generate:ref": "bun write-documentation.ts", "postdocs:generate:ref": "bun format --all", "bootstrap": "lerna bootstrap --use-workspaces", - "build": "bunx --bun lerna run build", + "build": "bunx lerna run build", "postbuild": "bun docs:generate:ref", - "dev": "bunx --bun lerna run build && bunx --bun lerna watch -- lerna run build --scope=$LERNA_PACKAGE_NAME", + "dev": "bun --watch run lerna run build", "prereleaseOnly": "pinst --disable && echo '🚀 Publishing...'", "release-canary": "lerna run build && lerna publish --preid canary --no-private --yes --force-publish --canary", "release-stable": "lerna run build && lerna publish --preid canary --no-private --yes --force-publish", diff --git a/packages/plexus-api/src/api.ts b/packages/plexus-api/src/api.ts index aaa4660..3b6c25c 100644 --- a/packages/plexus-api/src/api.ts +++ b/packages/plexus-api/src/api.ts @@ -272,7 +272,7 @@ export class ApiInstance { this._internalStore.options = deepMerge( this._internalStore.options, options - ) as RequestInit & { headers: Record } + ) options.headers && this.setHeaders(options.headers) return this diff --git a/packages/plexus-core/src/collection/collection.ts b/packages/plexus-core/src/collection/collection.ts index 478c72a..3bda9be 100644 --- a/packages/plexus-core/src/collection/collection.ts +++ b/packages/plexus-core/src/collection/collection.ts @@ -1,6 +1,10 @@ import { PlexusWatchableValueInterpreter } from '@plexusjs/utils' import { PlexusInstance, instance } from '../instance/instance' -import { PlexusInternalWatcher } from '../types' +import { + CollectionFetcher, + CollectionSorter, + PlexusInternalWatcher, +} from '../types' import { _data, PlexusDataInstance, DataKey, CollectionData } from './data' import { @@ -80,15 +84,31 @@ export interface PlexusCollectionConfig { */ decay?: number - sort?: (a: DataType, b: DataType) => number + /** + * Sort function to use for the collection + * @param a The first item to compare + * @param b The second item to compare + * @returns -1 if a should be before b, 1 if a should be after b, 0 if they are equal + */ + sort?: CollectionSorter + + /** + * A function used to fetch data for a given collection key. If a call is made to use an item that doesn't exist in the collection (getItem, getItemValue, etc.), this function will be called to fetch the data. If the function returns a promise, the promise will be awaited before returning the data. + * @param key The key of the item to fetch + * @returns The data for the item + */ + dataFetcher?: CollectionFetcher } interface PlexusCollectionStore> { _internalId: string _lastChanged: string _lookup: Map _key: string - _data: Map> - _groups: GroupMap + _data: Map< + string, + PlexusDataInstance> + > + _groups: GroupMap> _selectors: SelectorMap _name: string _externalName: string @@ -99,13 +119,18 @@ interface PlexusCollectionStore> { _computeFn?: ( data: PlexusWatchableValueInterpreter ) => PlexusWatchableValueInterpreter - sort?: (a: DataType, b: DataType) => number + sort?: CollectionSorter + dataFetcher?: CollectionFetcher } export type PlexusCollectionInstance< DataType extends Record = Record, - Groups extends GroupMap = GroupMap, - Selectors extends SelectorMap = SelectorMap, + Groups extends GroupMap> = GroupMap< + PlexusWatchableValueInterpreter + >, + Selectors extends SelectorMap< + PlexusWatchableValueInterpreter + > = SelectorMap> > = CollectionInstance /** * A Collection Instance @@ -113,8 +138,8 @@ export type PlexusCollectionInstance< */ export class CollectionInstance< DataTypeInput extends Record, - Groups extends GroupMap, - Selectors extends SelectorMap, + Groups extends GroupMap>, + Selectors extends SelectorMap> // ForeignRefs extends boolean = this['config']['foreignKeys'] extends {} ? true : false > { private _internalStore: PlexusCollectionStore @@ -160,10 +185,13 @@ export class CollectionInstance< _lookup: new Map(), _lastChanged: '', _key: config?.primaryKey || 'id', - _data: new Map>(), + _data: new Map< + string, + PlexusDataInstance> + >(), _groups: new Map< GroupName, - PlexusCollectionGroup + PlexusCollectionGroup> >() as Groups, _selectors: new Map< SelectorName, @@ -180,6 +208,7 @@ export class CollectionInstance< this._persist = value }, sort: config.sort, + dataFetcher: config.dataFetcher, } this.mount() @@ -591,13 +620,17 @@ export class CollectionInstance< * @param {string} name The Group Name to search for * @returns {this} The new Collection Instance */ - getGroup(name: GroupName): PlexusCollectionGroup - getGroup(name: KeyOfMap): PlexusCollectionGroup + getGroup( + name: GroupName + ): PlexusCollectionGroup> + getGroup( + name: KeyOfMap + ): PlexusCollectionGroup> getGroup(name: KeyOfMap | GroupName) { if (this.isCreatedGroup(name)) { const group = this._internalStore._groups.get( name - ) as PlexusCollectionGroup + ) as PlexusCollectionGroup> return group } else { @@ -743,12 +776,21 @@ export class CollectionInstance< */ watchGroup( name: KeyOfMap, - callback: PlexusInternalWatcher + callback: PlexusInternalWatcher< + PlexusWatchableValueInterpreter[] + > + ) + watchGroup( + name: string, + callback: PlexusInternalWatcher< + PlexusWatchableValueInterpreter[] + > ) - watchGroup(name: string, callback: PlexusInternalWatcher) watchGroup( name: KeyOfMap | string, - callback: PlexusInternalWatcher + callback: PlexusInternalWatcher< + PlexusWatchableValueInterpreter[] + > ) { const group = this.getGroup(name) if (this.isCreatedGroup(name) && group) { @@ -923,9 +965,13 @@ export class CollectionInstance< * Get all of the collection data values as an array * @type {DataTypeInput[]} */ - get value(): (DataTypeInput & { [key: string]: any })[] { + get value(): (PlexusWatchableValueInterpreter & { + [key: string]: any + })[] { this.mount() - const values: (DataTypeInput & { [key: string]: any })[] = [] + const values: (PlexusWatchableValueInterpreter & { + [key: string]: any + })[] = [] for (let item of this._internalStore._data.values()) { if (!item.provisional) { values.push(item.value) @@ -954,10 +1000,10 @@ export class CollectionInstance< * @type {Record} */ get groups() { - const groups: Record< + const groups = {} as Record< KeyOfMap, - PlexusCollectionGroup - > = {} as Record, PlexusCollectionGroup> + PlexusCollectionGroup> + > for (let group of this._internalStore._groups.entries()) { groups[group[0] as KeyOfMap] = group[1] } @@ -1058,7 +1104,7 @@ export class CollectionInstance< export function _collection< DataType extends { [key: string]: any }, Groups extends GroupMap = GroupMap, - Selectors extends SelectorMap = SelectorMap, + Selectors extends SelectorMap = SelectorMap >( instance: () => PlexusInstance, _config: PlexusCollectionConfig = { primaryKey: 'id' } as const diff --git a/packages/plexus-core/src/collection/data.ts b/packages/plexus-core/src/collection/data.ts index 7f90341..57ce87d 100644 --- a/packages/plexus-core/src/collection/data.ts +++ b/packages/plexus-core/src/collection/data.ts @@ -25,7 +25,7 @@ interface PlexusDataStore { } export type PlexusDataInstance< - DataType extends Record = Record, + DataType extends Record = Record > = CollectionData export type DataKey = string @@ -38,7 +38,7 @@ type DataObjectType = Record & { */ export class CollectionData< DataType extends DataObjectType = any, - PK extends string = string, + PK extends string = string > extends WatchableMutable { private primaryKey: PK readonly key: string @@ -67,6 +67,25 @@ export class CollectionData< _wDestroyers: new Set<() => void>(), config: config, } + let dataFetcher = this.collection().config.dataFetcher + if (this.provisional && dataFetcher) { + this.set(dataFetcher(this.key)) + // const data = + // if (data instanceof Promise) { + // // this.loading = true + // // data.then((data) => { + // // if (data) { + // // this.set(data) + // // this.loading = false + // // } + // // }) + // this.set(async () => { + // return await data + // }) + // } else { + // this.set(data) + // } + } if (!this.provisional) { this.mount() this.syncForeignKeyData(true) @@ -253,7 +272,9 @@ export class CollectionData< * @param {DataType} value The value to set * @returns {this} The data instance */ - set(value?: Partial>): this { + set( + value?: PlexusWatchableValueInterpreter> | DataType + ): this { if (!value) return this // if this is provisional, mount to the collection & instance @@ -316,7 +337,7 @@ export class CollectionData< * @param {DataType} value A value of the state to merge with the current value * @returns {this} The data instance */ - patch(value: Partial>): this { + patch(value: PlexusWatchableValueInterpreter>): this { this.set(deepMerge(this._watchableStore._value, value, true)) this.collection().lastUpdatedKey = this.key diff --git a/packages/plexus-core/src/collection/group.ts b/packages/plexus-core/src/collection/group.ts index 6827976..088b79d 100644 --- a/packages/plexus-core/src/collection/group.ts +++ b/packages/plexus-core/src/collection/group.ts @@ -1,14 +1,14 @@ import { PlexusWatchableValueInterpreter } from '@plexusjs/utils' import { PlexusCollectionInstance } from '..' import { PlexusInstance } from '../instance/instance' -import { PlexusInternalWatcher } from '../types' +import { CollectionSorter, PlexusInternalWatcher } from '../types' import { Watchable } from '../watchable' import { DataKey, PlexusDataInstance } from './data' export interface PlexusCollectionGroupConfig { addWhen?: (item: PlexusWatchableValueInterpreter) => boolean - sort?: (a: DataType, b: DataType) => number + sort?: CollectionSorter } export type GroupName = string @@ -21,15 +21,15 @@ interface CollectionGroupStore { _collectionId: string _includedKeys: Set _dataWatcherDestroyers: Set<() => void> - sort?: (a: DataType, b: DataType) => number + sort?: CollectionSorter } /** * A group of data */ export class CollectionGroup< - DataType extends Record = any, -> extends Watchable { + DataType extends Record = any +> extends Watchable[]> { private _internalStore: CollectionGroupStore private collection: () => PlexusCollectionInstance // private instance: () => PlexusInstance @@ -80,7 +80,7 @@ export class CollectionGroup< // memoization: this updates the groups stored value! This reduces computation as the state of the group is only updated when the data changes this._watchableStore._publicValue = keys .map((key) => this.collection().getItemValue(key)) - .filter(Boolean) as DataType[] + .filter(Boolean) as PlexusWatchableValueInterpreter[] this.instance().runtime.broadcast(this.id, this.value) } diff --git a/packages/plexus-core/src/instance/engine.ts b/packages/plexus-core/src/instance/engine.ts index 6264294..0951683 100644 --- a/packages/plexus-core/src/instance/engine.ts +++ b/packages/plexus-core/src/instance/engine.ts @@ -113,7 +113,7 @@ export class EventEngine { const pendingPayload = this.pendingEventPayloads.get(eventId) const eventPayload = pendingPayload - ? deepMerge(pendingPayload, args) + ? deepMerge(pendingPayload, args) : args this.pendingEventPayloads.set(eventId, eventPayload) return diff --git a/packages/plexus-core/src/types.ts b/packages/plexus-core/src/types.ts index 5d19419..53415fb 100644 --- a/packages/plexus-core/src/types.ts +++ b/packages/plexus-core/src/types.ts @@ -1,4 +1,7 @@ -import { AlmostAnything } from '@plexusjs/utils' +import { + AlmostAnything, + PlexusWatchableValueInterpreter, +} from '@plexusjs/utils' import { _event } from './event' import { _runtime } from './instance/runtime' @@ -12,4 +15,15 @@ export type PlexusInternalWatcher = ( ) => void export type PlexusValidStateTypes = NonNullable -export type Fetcher = () => Value +export type Fetcher = () => Value | Promise + +export type CollectionSorter = ( + a: PlexusWatchableValueInterpreter, + b: PlexusWatchableValueInterpreter +) => number + +export type CollectionFetcher = ( + key: string +) => + | Promise> + | PlexusWatchableValueInterpreter diff --git a/packages/plexus-core/src/watchable.ts b/packages/plexus-core/src/watchable.ts index f5bb340..4a4f51a 100644 --- a/packages/plexus-core/src/watchable.ts +++ b/packages/plexus-core/src/watchable.ts @@ -83,9 +83,7 @@ export class Watchable { */ get value(): PlexusWatchableValueInterpreter { const value = this._watchableStore._publicValue - if (value === undefined && this._watchableStore._dataFetcher) { - return this._watchableStore._dataFetcher() - } + return value } @@ -114,7 +112,9 @@ export class WatchableMutable extends Watchable { * @param newValue The new value of this state * @returns {this} The state instance */ - set(newValue?: PlexusWatchableValueInterpreter): this { + set( + newValue?: ValueType | Fetcher> + ): this { if (this.instance().runtime.isBatching) { // this.instance().runtime.batchedCalls.push(() => this.set(newValue)) this.instance().runtime.batch(() => this.set(newValue)) @@ -202,7 +202,7 @@ export class WatchableMutable extends Watchable { * Reset the state to the initial value */ reset() { - this.set(this._watchableStore._initialValue) + this.set(this._watchableStore._initialValue as any) // disable history if enabled this.history(0) return this @@ -233,17 +233,11 @@ export class WatchableMutable extends Watchable { // no history, so just try to reset to last value; if null, reset to initial value else { if (this._watchableStore._lastValue !== null) { - this.set( - this._watchableStore - ._lastValue as PlexusWatchableValueInterpreter - ) + this.set(this._watchableStore._lastValue as any) // last value should now be the current value BEFORE the undo, so set this to next value this._watchableStore._nextValue = this._watchableStore._lastValue } else { - this.set( - this._watchableStore - ._initialValue as PlexusWatchableValueInterpreter - ) + this.set(this._watchableStore._initialValue as any) } this._watchableStore._lastValue = null } @@ -330,7 +324,7 @@ export class WatchableMutable extends Watchable { fetch(): this { if (this._watchableStore._dataFetcher) { this.loading = true - this.set(this._watchableStore._dataFetcher()) + this.set(this._watchableStore._dataFetcher) this.loading = false } return this diff --git a/packages/plexus-core/tsconfig.json b/packages/plexus-core/tsconfig.json index e868a3b..9526596 100644 --- a/packages/plexus-core/tsconfig.json +++ b/packages/plexus-core/tsconfig.json @@ -4,8 +4,7 @@ "lib": ["dom"], "types": ["bun-types"], "rootDir": "./src", - "outDir": "./dist", - "emitDeclarationOnly": true + "outDir": "./dist" }, "include": ["./src/**/*"] } diff --git a/packages/plexus-utils/src/shared/itemManipulation.ts b/packages/plexus-utils/src/shared/itemManipulation.ts index 6179f7d..b99d4c2 100644 --- a/packages/plexus-utils/src/shared/itemManipulation.ts +++ b/packages/plexus-utils/src/shared/itemManipulation.ts @@ -62,12 +62,18 @@ export const convertStringToThing = (inp: string) => { } } // A function to deeply merge two things (objects or arrays). -export function deepMerge( - target: Thing, - source: Thing, - override = false -): Thing { - let output: Thing = Object.assign({}, target) +export function deepMerge< + Thing1 extends Record = Record< + string | number | symbol, + any + >, + Thing2 extends Record = Record< + string | number | symbol, + any + >, + Thing extends Thing1 & Thing2 = Thing1 & Thing2 +>(target: Thing1, source: Thing2, override = false): Thing1 & Thing2 { + let output = Object.assign({} as Thing2, target) if ( (isObject(target) && isObject(source)) || (Array.isArray(target) && Array.isArray(source)) @@ -82,7 +88,7 @@ export function deepMerge( } else { output[key] = deepMerge( target[key] as any, - source[key], + source[key] as any, override ) as any } @@ -104,7 +110,7 @@ export function deepMerge( return Object.values(output) as any as Thing } - return output + return output as Thing } // a deep clone of anything diff --git a/packages/plexus-utils/src/types.ts b/packages/plexus-utils/src/types.ts index d391311..1a0685f 100644 --- a/packages/plexus-utils/src/types.ts +++ b/packages/plexus-utils/src/types.ts @@ -34,10 +34,18 @@ export type UnionToIntersection = ( export type IsUnion = [T] extends [UnionToIntersection] ? false : true +/** + * A type that calculates... + * - If input is of type sync function: The return type + * - If input is of type async function: The return type after waiting: PromiseType> -> `returns T` + * - If input is not a function: The input type + */ export type PlexusWatchableValueInterpreter = Value extends ( ...args: any -) => any - ? ReturnType +) => infer RT + ? RT extends Promise + ? PRT + : RT : Value export declare type AsyncState = @@ -71,9 +79,9 @@ export declare type PromiseType

> = P extends Promise< : never declare type StateFromFunctionReturningPromise< - T extends FunctionReturningPromise, + T extends FunctionReturningPromise > = AsyncState>> export declare type AsyncFnReturn< - T extends FunctionReturningPromise = FunctionReturningPromise, + T extends FunctionReturningPromise = FunctionReturningPromise > = [StateFromFunctionReturningPromise, T]