@cyysummer/projector 0.0.8 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
- * @cyysummer/projector v0.0.8
2
+ * @cyysummer/projector v0.0.9
3
3
  * (c) 2021-PRESENT Chris Liu
4
4
  * @license MIT
5
5
  **/
6
- import b from"on-change";import{set as m}from"lodash-es";import{withEvents as I}from"@cyysummer/core";var T=class extends I(){constructor(e){super();this._paused=!1;this._shadow=structuredClone(e)}receive(e,r){if(this._paused)return;m(this._shadow,e,r);let o=[{path:e,value:r}];this.emit("record",{next:this._shadow,patches:o}),this._ensureProjector(),this._projector?.project(this._shadow,...o)}sendTo(e){typeof e=="function"?this._projectorFactory=e:this._projector=e}pause(){this._paused=!0}resume(){this._paused=!1}_ensureProjector(){!this._projector&&this._projectorFactory&&(this._projector=this._projectorFactory())}};import{get as l,keys as d}from"lodash-es";var y=s=>{},S=class{constructor(t){this._strategy=t}at(t){return(e,r)=>{let o=i(r);return this._strategy.execute(e,t,o)}}atWith(t,e){return(o,c)=>{let u=i(c),a=e(u);return this._strategy.execute(o,t,a)}}arrayAt(t,e){return(r,o)=>{let c=o.path?l(o.source,o.path):o.value;if(c&&Array.isArray(c)&&c.length>0){let u={keys:typeof c[0]=="object"?d(c[0]):[],resolveHeader:(a,n)=>String(a),...e};return this._strategy.executeArray(r,t,c,u)}}}arrayAtWith(t,e,r){return(o,c)=>{let u=c.path?l(c.source,c.path):c.value;if(u&&Array.isArray(u)&&u.length>0){let a=u.map(e),n={keys:typeof u[0]=="object"?d(u[0]):[],resolveHeader:(f,v)=>String(f),...r};return this._strategy.executeArray(o,t,a,n)}}}loop(t){return async(e,r)=>{let o=i(r);if(Array.isArray(o)){let c=o;for(let u=0;u<c.length;u++){let a=c[u],f=t(a,u)(e,{value:a});f instanceof Promise&&await f}}else throw new Error(`Effect: Value at path '${r.path}' is not an array.`)}}sourceWith(t,e){return(r,o)=>{let c=e(o.source);return this._strategy.execute(r,t,c)}}raw(t,e){return(r,o)=>this._strategy.execute(r,t,e)}sequence(t){return async(e,r)=>{let o=i(r);for(let c of t)await c(e,{value:o})}}sequenceWith(t,e){return async(o,c)=>{let u=i(c),a=e(u);for(let n of t)await n(o,{value:a})}}when(t,e,r=y){return(o,c)=>{let u=i(c);return(t(u)?e:r)(o,{value:u})}}whenFromSource(t,e,r=y){return(o,c)=>(t(c.source)?e:r)(o,c)}},i=s=>s.source&&s.path?l(s.source,s.path):s.value;var h=class{constructor(t){this._schema=t;if(!t)throw new Error("Projector: Schema is required.")}project(t,...e){for(let r of e)this._dispatch(t,r)}_dispatch(t,e){let{path:r,value:o}=e,c=this._resolveEffect(r);c&&(this._ensureScheduler(),this._scheduler.enqueue({path:r.join("."),effect:c,ctx:{source:t,path:r,value:o}}))}_ensureScheduler(){if(!this._scheduler)throw new Error("Projector: no scheduler.")}_resolveEffect(t){let e=this._schema;for(let r of t){if(!e)return null;e=e[r]}return typeof e=="function"?e:null}},_=class extends h{constructor(t,e){super(t),this._scheduler=e}},g=class extends h{scheduleWith(t){return typeof t=="function"?this._schedulerFactory=t:this._scheduler=t,this}_ensureScheduler(){if(!this._scheduler&&this._schedulerFactory&&(this._scheduler=this._schedulerFactory()),!this._scheduler)throw new Error("Projector: no scheduler.")}};var p=class{constructor(t){this._target=t}withTarget(t){return typeof t=="function"?this._targetFactory=t:this._target=t,this}checkTarget(){if(!this._target&&this._targetFactory&&(this._target=this._targetFactory()),!this._target)throw new Error("Scheduler: target is not set")}},E=class extends p{constructor(){super(...arguments);this._queue=new Map}enqueue(e){return this._queue.set(e.path,e),this}async flush(){this.checkTarget(),log.trace("BufferedScheduler: flushing",[...this._queue.keys()]);let e=[...this._queue.values()];this._queue.clear();for(let r of e){let{effect:o,ctx:c}=r,u=o(this._target,c);u instanceof Promise&&await u}}};function k(s){let t=new T(s);return[b(s,t.receive.bind(t),{pathAsArray:!0}),t]}export{E as BufferedScheduler,g as DynamicProjector,S as EffectFactory,_ as Projector,h as ProjectorBase,T as Recorder,p as Scheduler,k as track};
6
+ import w from"on-change";import{cloneDeep as m,set as x}from"lodash-es";import{withEvents as v}from"@cyysummer/core";var f=class extends v(){constructor(t){super();this._paused=!1;this._shadow=m(t)}receive(t,r){if(this._paused)return;x(this._shadow,t,r);let o=[{path:t,value:r}];this.emit("record",{next:this._shadow,patches:o}),this._ensureProjector(),this._projector?.project(this._shadow,...o)}sendTo(t){typeof t=="function"?this._projectorFactory=t:this._projector=t}pause(){this._paused=!0}resume(){this._paused=!1}_ensureProjector(){!this._projector&&this._projectorFactory&&(this._projector=this._projectorFactory())}};import{get as p,keys as l}from"lodash-es";var S=n=>{},d=class{at(e){return(t,r)=>{let o=i(r);return t.execute(e,o)}}atWith(e,t){return(o,c)=>{let a=i(c),u=t(a);return o.execute(e,u)}}arrayAt(e,t){return(r,o)=>{let c=o.path?p(o.source,o.path):o.value;if(c&&Array.isArray(c)&&c.length>0){let a={keys:typeof c[0]=="object"?l(c[0]):[],resolveHeader:(u,s)=>String(u),...t};return r.executeArray(e,c,a)}}}arrayAtWith(e,t,r){return(o,c)=>{let a=c.path?p(c.source,c.path):c.value;if(a&&Array.isArray(a)&&a.length>0){let u=a.map(t),s={keys:typeof a[0]=="object"?l(a[0]):[],resolveHeader:(T,j)=>String(T),...r};return o.executeArray(e,u,s)}}}loop(e){return async(t,r)=>{let o=i(r);if(Array.isArray(o)){let c=o;for(let a=0;a<c.length;a++){let u=c[a],T=e(u,a)(t,{value:u});T instanceof Promise&&await T}}else throw new Error(`Effect: Value at path '${r.path}' is not an array.`)}}sourceWith(e,t){return(r,o)=>{let c=t(o.source);return r.execute(e,c)}}raw(e,t){return(r,o)=>r.execute(e,t)}sequence(e){return async(t,r)=>{let o=i(r);for(let c of e)await c(t,{value:o})}}sequenceWith(e,t){return async(o,c)=>{let a=i(c),u=t(a);for(let s of e)await s(o,{value:u})}}when(e,t,r=S){return(o,c)=>{let a=i(c);return(e(a)?t:r)(o,{value:a})}}whenFromSource(e,t,r=S){return(o,c)=>(e(c.source)?t:r)(o,c)}},i=n=>n.source&&n.path?p(n.source,n.path):n.value;var h=class{constructor(e){this._schema=e;if(!e)throw new Error("Projector: Schema is required.")}project(e,...t){for(let r of t)this._dispatch(e,r)}_dispatch(e,t){let{path:r,value:o}=t,c=this._resolveEffect(r);c&&(this._ensureScheduler(),this._scheduler.enqueue({path:r.join("."),effect:c,ctx:{source:e,path:r,value:o}}))}_ensureScheduler(){if(!this._scheduler)throw new Error("Projector: no scheduler.")}_resolveEffect(e){let t=this._schema;for(let r of e){if(!t)return null;t=t[r]}return typeof t=="function"?t:null}},g=class extends h{constructor(e,t){super(e),this._scheduler=t}},E=class extends h{scheduleWith(e){return typeof e=="function"?this._schedulerFactory=e:this._scheduler=e,this}_ensureScheduler(){if(!this._scheduler&&this._schedulerFactory&&(this._scheduler=this._schedulerFactory()),!this._scheduler)throw new Error("Projector: no scheduler.")}};import{debounce as b}from"lodash-es";var y=class{constructor(e){this._strategy=e;this._queue=new Map}enqueue(e){return this._queue.set(e.path,e),this}flush(){return this.checkStrategy(),log.trace("Scheduler.flush()",[...this._queue.keys()]),this.flushCore()}withStrategy(e){return typeof e=="function"?this._strategyFactory=e:this._strategy=e,this}checkStrategy(){if(!this._strategy&&this._strategyFactory&&(this._strategy=this._strategyFactory()),!this._strategy)throw new Error("Scheduler: target is not set")}async run(e){for(let t of e){let{effect:r,ctx:o}=t,c=r(this._strategy,o);c instanceof Promise&&await c}}},I=class extends y{constructor(t,r=500){super(t);this.timeout=r;this.flushCore=b(async()=>{let t=[...this._queue.values()];this._queue.clear(),this.run(t)},this.timeout)}},_=class extends y{async flushCore(){this._strategy.reset(),this.run([...this._queue.values()])}};function O(n){let e=new f(n);return[w(n,(r,o)=>e.receive(r,o),{pathAsArray:!0}),e]}export{E as DynamicProjector,d as EffectFactory,I as LazyScheduler,g as Projector,h as ProjectorBase,f as Recorder,y as Scheduler,_ as TotalScheduler,O as track};
7
7
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyysummer/projector",
3
- "version": "0.0.8",
3
+ "version": "0.0.9",
4
4
  "description": "A powerful state projection library for creating anything.",
5
5
  "license": "MIT",
6
6
  "author": "Chris Liu",
@@ -18,7 +18,7 @@
18
18
  }
19
19
  },
20
20
  "dependencies": {
21
- "lodash-es": "^4.17.22",
21
+ "lodash-es": "4.17.22",
22
22
  "on-change": "^6.0.1",
23
23
  "@cyysummer/core": "^0.0.12"
24
24
  },
@@ -26,11 +26,11 @@
26
26
  "@cyysummer/core": "^0.0.12"
27
27
  },
28
28
  "devDependencies": {
29
- "@types/lodash-es": "^4.17.12",
30
- "tsup": "^8.5.0",
31
- "typescript": "^5.9.2",
32
- "vite": "^7.1.5",
33
- "vitest": "^3.2.4"
29
+ "@types/lodash-es": "4.17.12",
30
+ "tsup": "^8.5.1",
31
+ "typescript": "^5.9.3",
32
+ "vite": "^7.3.1",
33
+ "vitest": "^4.0.17"
34
34
  },
35
35
  "publishConfig": {
36
36
  "access": "public"
package/types/lib.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import * as lodash_es from 'lodash-es';
2
+
1
3
  /**
2
4
  * A Schema defines how to project changes from a source object to a target object.
3
5
  */
@@ -16,8 +18,8 @@ type Schema<TSource, T = TSource> = {
16
18
  /**
17
19
  * An effect defines how to project a value from source to target.
18
20
  */
19
- // todo: Solve this `any`
20
- type Effect<TSource, TValue> = (target: any, ctx: IEffectContext<TSource, TValue>) => MaybePromise<void>;
21
+ // todo: Solve ITargetExecutionStrategy<any, any>. How to obtain TTarget and TLocation?
22
+ type Effect<TSource, TValue> = (strategy: ITargetExecutionStrategy<any, any>, ctx: IEffectContext<TSource, TValue>) => MaybePromise<void>;
21
23
 
22
24
  /**
23
25
  * The context for an effect. It contains information about changes.
@@ -58,8 +60,9 @@ type ArrayEffectOptions = {
58
60
  * Strategy defines actual execution methods for projecting values to target.
59
61
  */
60
62
  interface ITargetExecutionStrategy<TTarget, TLocation = any> {
61
- execute<T extends any>(target: TTarget, location: TLocation, value: T): MaybePromise<void>;
62
- executeArray<T extends any>(target: TTarget, location: TLocation, rows: T[], options: ArrayEffectOptions): MaybePromise<void>;
63
+ execute<T extends any>(location: TLocation, value: T): MaybePromise<void>;
64
+ executeArray<T extends any>(location: TLocation, rows: T[], options: ArrayEffectOptions): MaybePromise<void>;
65
+ reset(): void;
63
66
  }
64
67
 
65
68
  interface IProjector<TSource> {
@@ -69,8 +72,8 @@ interface IProjector<TSource> {
69
72
  interface IScheduler<TTarget> {
70
73
  enqueue(effect: IScheduleItem): void;
71
74
  flush(): MaybePromise<void>;
72
- withTarget(target: TTarget): IScheduler<TTarget>;
73
- withTarget(target: Func<TTarget>): IScheduler<TTarget>;
75
+ withStrategy(target: ITargetExecutionStrategy<TTarget, unknown>): IScheduler<TTarget>;
76
+ withStrategy(target: Func<ITargetExecutionStrategy<TTarget, unknown>>): IScheduler<TTarget>;
74
77
  }
75
78
 
76
79
  interface IScheduleItem<TSource, TValue> {
@@ -124,32 +127,32 @@ declare class Recorder<TSource extends object> extends Recorder_base {
124
127
 
125
128
  /**
126
129
  * Build effects for a target.
130
+ * @template TSource The type of the source object.
131
+ * @template TLocation The type used to describe a location within an effect target. This is interpreted by the execution strategy and may represent a bookmark, anchor, or address.
127
132
  */
128
- declare class EffectFactory<TTarget, TSource extends object, TLocation = any> {
129
- protected _strategy: ITargetExecutionStrategy<TTarget, TLocation>;
130
- constructor(_strategy: ITargetExecutionStrategy<TTarget, TLocation>);
133
+ declare class EffectFactory<TSource extends object, TLocation = any> {
131
134
  /**
132
- * Create an effect that will be applied at the specified location.
133
- * @param location The location in `TTarget` where the effect is executed.
135
+ * Create an effect bound to a specific location on the target. Source value should be primitive type.
136
+ * @param location Identifies the location on the effect target where this effect will be executed.
134
137
  * @returns
135
138
  */
136
139
  at<T>(location: TLocation): Effect<TSource, T>;
137
140
  /**
138
- * Create an effect that will be applied at the specified location. Use `mapper` to transform the value.
139
- * @param location The location in `TTarget` where the effect is executed.
141
+ * Create an effect bound to a specific location on the target. When it executs, source value will be transformed with `mapper`. Source value should be primitive type.
142
+ * @param location Identifies the location on the effect target where this effect will be executed.
140
143
  * @param mapper Function to transform the value.
141
144
  * @returns
142
145
  */
143
146
  atWith<T, R>(location: TLocation, mapper: Func1<T, R>): Effect<TSource, R>;
144
147
  /**
145
- * Create an effect that will be applied at the specified location for array data.
146
- * @param location The location in `TTarget` where the effect is executed.
148
+ * Create an effect bound to a specific location on the target. Source value should be an array.
149
+ * @param location Identifies the location on the effect target where this effect will be executed.
147
150
  * @returns
148
151
  */
149
152
  arrayAt<T, U = UnwrapArray<T>>(location: TLocation, options?: Partial<ArrayEffectOptions>): Effect<TSource, T>;
150
153
  /**
151
- * Create an effect that will be applied at the specified location for array data.
152
- * @param location The location in `TTarget` where the effect is executed.
154
+ * Create an effect bound to a specific location on the target. When it executs, source value will be transformed with `mapper`. Source value should be an array.
155
+ * @param location Identifies the location on the effect target where this effect will be executed.
153
156
  * @param mapper Function to transform the element of the array.
154
157
  * @returns
155
158
  */
@@ -162,15 +165,15 @@ declare class EffectFactory<TTarget, TSource extends object, TLocation = any> {
162
165
  */
163
166
  loop<T, U = T extends Array<infer K> ? K : never>(each: Func2<U, number, Effect<TSource, U>>): Effect<TSource, T>;
164
167
  /**
165
- * Create an effect that will be applied at the specified location for `TSource` object. Use `mapper` to transform the `TSource` object.
166
- * @param location The location in `TTarget` where the effect is executed.
168
+ * Create an effect bound to a specific location on the target for `TSource` object. When it executs, `TSource` object will be transformed with `mapper`.
169
+ * @param location Identifies the location on the effect target where this effect will be executed.
167
170
  * @param mapper Function to transform the `TSource` object.
168
171
  * @returns
169
172
  */
170
173
  sourceWith<T>(location: TLocation, mapper: Func1<TSource, string>): Effect<TSource, T>;
171
174
  /**
172
- * Create an effect that will be applied at the specified location for raw value. This effect will not read from `TSource` object.
173
- * @param location The location in `TTarget` where the effect is executed.
175
+ * Create an effect bound to a specific location on the target for raw value. This effect will not read from `TSource` object.
176
+ * @param location Identifies the location on the effect target where this effect will be executed.
174
177
  * @param rawValue The value to use.
175
178
  * @returns
176
179
  */
@@ -182,7 +185,7 @@ declare class EffectFactory<TTarget, TSource extends object, TLocation = any> {
182
185
  */
183
186
  sequence<T>(effects: Effect<TSource, T>[]): Effect<TSource, T>;
184
187
  /**
185
- * Call multiple effects in sequence. Use `mapper` to transform the value.
188
+ * Call multiple effects in sequence. When it executs, source value will be transformed with `mapper` first, then pass to `effects`.
186
189
  * @param effects Effects to call.
187
190
  * @param mapper Function to transform the value.
188
191
  * @returns
@@ -229,22 +232,40 @@ declare class DynamicProjector<TSource> extends ProjectorBase<TSource> {
229
232
  * Scheduler abstract class, used to schedule projector effects.
230
233
  */
231
234
  declare abstract class Scheduler<TTarget> implements IScheduler<TTarget> {
232
- protected _target?: TTarget | undefined;
233
- protected _targetFactory?: Func<TTarget>;
234
- constructor(_target?: TTarget | undefined);
235
- abstract enqueue(item: IScheduleItem<TTarget, any>): IScheduler<TTarget>;
236
- abstract flush(): MaybePromise<void>;
237
- withTarget(target: TTarget): IScheduler<TTarget>;
238
- withTarget(target: Func<TTarget>): IScheduler<TTarget>;
239
- protected checkTarget(): void;
235
+ protected _strategy?: ITargetExecutionStrategy<TTarget, any> | undefined;
236
+ protected readonly _queue: Map<string, IScheduleItem<any, any>>;
237
+ protected _strategyFactory?: Func<ITargetExecutionStrategy<TTarget, any>>;
238
+ constructor(_strategy?: ITargetExecutionStrategy<TTarget, any> | undefined);
239
+ enqueue(item: IScheduleItem<any, any>): IScheduler<TTarget>;
240
+ flush(): MaybePromise<void>;
241
+ withStrategy(strategy: ITargetExecutionStrategy<TTarget, any>): IScheduler<TTarget>;
242
+ withStrategy(strategy: Func<ITargetExecutionStrategy<TTarget, any>>): IScheduler<TTarget>;
243
+ protected checkStrategy(): void;
244
+ protected abstract flushCore(): MaybePromise<void>;
245
+ protected run(list: IScheduleItem<any, any>[]): Promise<void>;
246
+ }
247
+ /**
248
+ * A scheduler that **delays and batches** effect executions using debounce.
249
+ *
250
+ * Effects are collected in a queue and only executed after the specified `timeout`
251
+ * has passed without any new effects being enqueued. This helps reduce redundant
252
+ * executions (e.g. during rapid successive state updates) and is especially useful
253
+ * when projecting to expensive targets like UI rendering, DOM updates, or network requests.
254
+ *
255
+ * In contrast to `TotalScheduler` which eagerly executes all effects on every projection,
256
+ * `LazyScheduler` is more conservative: it waits for a quiet period before flushing the accumulated
257
+ * effects in batch.
258
+ */
259
+ declare class LazyScheduler<TTarget> extends Scheduler<TTarget> {
260
+ protected timeout: number;
261
+ constructor(strategy?: ITargetExecutionStrategy<TTarget, any>, timeout?: number);
262
+ protected readonly flushCore: lodash_es.DebouncedFunc<() => Promise<void>>;
240
263
  }
241
264
  /**
242
- * Buffered scheduler, will buffer effects and execute them in batch.
265
+ * Run all queued effects at once. The queue will NOT be cleared. The strategy will `reset()` before running any effects.
243
266
  */
244
- declare class BufferedScheduler<TTarget> extends Scheduler<TTarget> {
245
- protected readonly _queue: Map<string, IScheduleItem<TTarget, any>>;
246
- enqueue(item: IScheduleItem<TTarget, any>): IScheduler<TTarget>;
247
- flush(): Promise<void>;
267
+ declare class TotalScheduler<TTarget> extends Scheduler<TTarget> {
268
+ protected flushCore(): Promise<void>;
248
269
  }
249
270
 
250
271
  /**
@@ -254,4 +275,4 @@ declare class BufferedScheduler<TTarget> extends Scheduler<TTarget> {
254
275
  */
255
276
  declare function track<TSource extends object>(initial: TSource): [TSource, Recorder<TSource>];
256
277
 
257
- export { BufferedScheduler, DynamicProjector, EffectFactory, Projector, ProjectorBase, Recorder, Scheduler, track };
278
+ export { DynamicProjector, EffectFactory, LazyScheduler, Projector, ProjectorBase, Recorder, Scheduler, TotalScheduler, track };
package/types/types.d.ts CHANGED
@@ -16,8 +16,8 @@ export type Schema<TSource, T = TSource> = {
16
16
  /**
17
17
  * An effect defines how to project a value from source to target.
18
18
  */
19
- // todo: Solve this `any`
20
- export type Effect<TSource, TValue> = (target: any, ctx: IEffectContext<TSource, TValue>) => MaybePromise<void>;
19
+ // todo: Solve ITargetExecutionStrategy<any, any>. How to obtain TTarget and TLocation?
20
+ export type Effect<TSource, TValue> = (strategy: ITargetExecutionStrategy<any, any>, ctx: IEffectContext<TSource, TValue>) => MaybePromise<void>;
21
21
 
22
22
  /**
23
23
  * The context for an effect. It contains information about changes.
@@ -58,8 +58,9 @@ export type ArrayEffectOptions = {
58
58
  * Strategy defines actual execution methods for projecting values to target.
59
59
  */
60
60
  export interface ITargetExecutionStrategy<TTarget, TLocation = any> {
61
- execute<T extends any>(target: TTarget, location: TLocation, value: T): MaybePromise<void>;
62
- executeArray<T extends any>(target: TTarget, location: TLocation, rows: T[], options: ArrayEffectOptions): MaybePromise<void>;
61
+ execute<T extends any>(location: TLocation, value: T): MaybePromise<void>;
62
+ executeArray<T extends any>(location: TLocation, rows: T[], options: ArrayEffectOptions): MaybePromise<void>;
63
+ reset(): void;
63
64
  }
64
65
 
65
66
  export interface IProjector<TSource> {
@@ -69,8 +70,8 @@ export interface IProjector<TSource> {
69
70
  export interface IScheduler<TTarget> {
70
71
  enqueue(effect: IScheduleItem): void;
71
72
  flush(): MaybePromise<void>;
72
- withTarget(target: TTarget): IScheduler<TTarget>;
73
- withTarget(target: Func<TTarget>): IScheduler<TTarget>;
73
+ withStrategy(target: ITargetExecutionStrategy<TTarget, unknown>): IScheduler<TTarget>;
74
+ withStrategy(target: Func<ITargetExecutionStrategy<TTarget, unknown>>): IScheduler<TTarget>;
74
75
  }
75
76
 
76
77
  export interface IScheduleItem<TSource, TValue> {