@weiliang0121/capix-interaction 0.0.0-stage → 0.1.0
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/LICENSE +21 -0
- package/README.md +18 -2
- package/dist/contract.d.ts +60 -0
- package/dist/contract.js +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +2 -0
- package/dist/interaction.d.ts +17 -0
- package/dist/interaction.js +2 -0
- package/dist/interactions.d.ts +36 -0
- package/dist/interactions.js +2 -0
- package/dist/observation.d.ts +3 -0
- package/dist/observation.js +2 -0
- package/dist/progress/budget.d.ts +10 -0
- package/dist/progress/budget.js +2 -0
- package/dist/progress/buffer.d.ts +47 -0
- package/dist/progress/buffer.js +2 -0
- package/dist/progress/types.d.ts +55 -0
- package/dist/progress/types.js +1 -0
- package/dist/value.d.ts +3 -0
- package/dist/value.js +2 -0
- package/package.json +42 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 wei.liang (https://github.com/weiliang0121)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @weiliang0121/capix-interaction
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Independent external interactions, persisted feedback, and bounded progress streams for CAPiX.
|
|
4
|
+
|
|
5
|
+
ES modules and TypeScript declarations. Requires Node.js 22.23.2 or newer.
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install @weiliang0121/capix-interaction
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Public entry points
|
|
12
|
+
|
|
13
|
+
- `@weiliang0121/capix-interaction`
|
|
14
|
+
|
|
15
|
+
This package is part of CAPiX. The npm distribution contains compiled JavaScript, type declarations, and this package's MIT license. Reference services and client applications are not included in this package or the initial npm release.
|
|
16
|
+
|
|
17
|
+
## License
|
|
18
|
+
|
|
19
|
+
MIT. Copyright (c) 2026 wei.liang. See [LICENSE](./LICENSE).
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { RuntimeErrorValue } from '@weiliang0121/capix-contracts';
|
|
2
|
+
import type { ProgressReceiver } from '@weiliang0121/capix-changes';
|
|
3
|
+
export interface InteractionContract<Request, Reply, Delta = never> {
|
|
4
|
+
readonly key: string;
|
|
5
|
+
request(value: unknown): Request;
|
|
6
|
+
reply(value: unknown): Reply;
|
|
7
|
+
progress?(value: unknown): Delta;
|
|
8
|
+
}
|
|
9
|
+
export interface InteractionFeedback<Reply> {
|
|
10
|
+
readonly id: string;
|
|
11
|
+
readonly payload: Readonly<Reply>;
|
|
12
|
+
}
|
|
13
|
+
export interface InteractionState<Request = unknown, Reply = unknown> {
|
|
14
|
+
readonly id: string;
|
|
15
|
+
readonly contract: string;
|
|
16
|
+
readonly revision: number;
|
|
17
|
+
readonly request: Readonly<Request>;
|
|
18
|
+
readonly delivery: 'prepared' | 'started';
|
|
19
|
+
readonly feedback?: InteractionFeedback<Reply>;
|
|
20
|
+
readonly failure?: RuntimeErrorValue;
|
|
21
|
+
}
|
|
22
|
+
export interface InteractionCommit<Request = unknown, Reply = unknown> {
|
|
23
|
+
readonly expectedRevision: number;
|
|
24
|
+
readonly state: InteractionState<Request, Reply>;
|
|
25
|
+
}
|
|
26
|
+
export interface InteractionPersistence<Request = unknown, Reply = unknown> {
|
|
27
|
+
create(state: InteractionState<Request, Reply>): undefined;
|
|
28
|
+
commit(change: InteractionCommit<Request, Reply>): undefined;
|
|
29
|
+
}
|
|
30
|
+
export interface InteractionOptions<Request, Reply> {
|
|
31
|
+
readonly contract: Pick<InteractionContract<Request, Reply>, 'key' | 'request' | 'reply'>;
|
|
32
|
+
readonly persistence?: InteractionPersistence<Request, Reply>;
|
|
33
|
+
readonly observation?: ProgressReceiver<InteractionFact<Request, Reply>>;
|
|
34
|
+
}
|
|
35
|
+
export type InteractionFact<Request = unknown, Reply = unknown> = {
|
|
36
|
+
readonly type: 'changed';
|
|
37
|
+
readonly state: InteractionState<Request, Reply>;
|
|
38
|
+
} | {
|
|
39
|
+
readonly type: 'unavailable';
|
|
40
|
+
readonly state: InteractionState<Request, Reply>;
|
|
41
|
+
readonly error: RuntimeErrorValue;
|
|
42
|
+
};
|
|
43
|
+
export type InteractionRead<Reply> = {
|
|
44
|
+
readonly kind: 'pending';
|
|
45
|
+
} | {
|
|
46
|
+
readonly kind: 'received';
|
|
47
|
+
readonly feedback: InteractionFeedback<Reply>;
|
|
48
|
+
} | {
|
|
49
|
+
readonly kind: 'unavailable';
|
|
50
|
+
readonly error: unknown;
|
|
51
|
+
};
|
|
52
|
+
export type InteractionWait<Reply> = Exclude<InteractionRead<Reply>, {
|
|
53
|
+
readonly kind: 'pending';
|
|
54
|
+
}> | {
|
|
55
|
+
readonly kind: 'wait-expired';
|
|
56
|
+
};
|
|
57
|
+
export interface InteractionWaitOptions {
|
|
58
|
+
readonly waitMs: number;
|
|
59
|
+
readonly signal?: AbortSignal;
|
|
60
|
+
}
|
package/dist/contract.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/*! MIT License | Copyright (c) 2026 wei.liang | See LICENSE */
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export * from './contract.js';
|
|
2
|
+
export { Interaction } from './interaction.js';
|
|
3
|
+
export { Interactions, type InteractionReference, type InteractionChannel, type InteractionRegistration } from './interactions.js';
|
|
4
|
+
export type { InteractionCursor, InteractionProgressRead, InteractionProgressReader, InteractionProgressLimits, InteractionProgressBudgetLimits, InteractionProgressFact, InteractionPublisher, InteractionPublication } from './progress/types.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { InteractionFeedback, InteractionOptions, InteractionRead, InteractionState, InteractionWait, InteractionWaitOptions } from './contract.js';
|
|
2
|
+
export declare class Interaction<Request, Reply> {
|
|
3
|
+
#private;
|
|
4
|
+
constructor(request: Request, options: InteractionOptions<Request, Reply>);
|
|
5
|
+
get id(): string;
|
|
6
|
+
exportState(): InteractionState<Request, Reply>;
|
|
7
|
+
static restore<Request, Reply>(state: InteractionState, options: InteractionOptions<Request, Reply>): Interaction<Request, Reply>;
|
|
8
|
+
start(dispatch: (request: Readonly<Request>) => Reply | Promise<Reply>): void;
|
|
9
|
+
receive(feedback: InteractionFeedback<Reply>): void;
|
|
10
|
+
read(): InteractionRead<Reply>;
|
|
11
|
+
wait(options: InteractionWaitOptions): Promise<InteractionWait<Reply>>;
|
|
12
|
+
settled(): Promise<void>;
|
|
13
|
+
private fail;
|
|
14
|
+
private requireMutable;
|
|
15
|
+
private commit;
|
|
16
|
+
private notify;
|
|
17
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/*! MIT License | Copyright (c) 2026 wei.liang | See LICENSE */
|
|
2
|
+
var __defProp=Object.defineProperty;var __name=(target,value)=>__defProp(target,"name",{value,configurable:!0});import{randomUUID}from"node:crypto";import{RuntimeError}from"@weiliang0121/capix-contracts";import{PersistenceError,RuntimeErrorHelper}from"@weiliang0121/capix-diagnostics";import{isNonBlankString}from"./value.js";import{captureInteractionValue,sameInteractionValue}from"./value.js";import{ProgressChannel}from"@weiliang0121/capix-changes";class Interaction{static{__name(this,"Interaction")}#state;#options;#observation;#listeners=new Set;#settled=Promise.resolve();#fault;#deliveryFailure;#mutating=!1;#restored=!1;constructor(request,options){if(!isNonBlankString(options.contract.key))throw new TypeError("Interaction contract key must be nonempty");this.#options=Object.freeze({...options,contract:Object.freeze({key:options.contract.key,request:options.contract.request.bind(options.contract),reply:options.contract.reply.bind(options.contract)})}),this.#observation=options.observation?new ProgressChannel(options.observation):void 0;const captured=captureInteractionValue(request);this.#state=Object.freeze({id:randomUUID(),contract:this.#options.contract.key,revision:0,request:captureInteractionValue(this.#options.contract.request(captured)),delivery:"prepared"});try{const committed=this.#options.persistence?.create(this.#state);if(committed!==void 0)throw Promise.resolve(committed).catch(()=>{}),new TypeError("Interaction persistence must be synchronous")}catch(cause){throw new PersistenceError("interaction.create",cause)}this.#observation?.publish(Object.freeze({type:"changed",state:this.#state}))}get id(){return this.#state.id}exportState(){return this.#state}static restore(state,options){const saved=captureInteractionValue(state),allowed=new Set(["id","contract","revision","request","delivery","feedback","failure"]);if(Object.keys(saved).some(key=>!allowed.has(key))||!Object.hasOwn(saved,"request")||Object.hasOwn(saved,"feedback")&&!saved.feedback||Object.hasOwn(saved,"failure")&&!saved.failure)throw new TypeError("Invalid persisted interaction fields");if(!isNonBlankString(saved.id)||saved.contract!==options.contract.key||!Number.isSafeInteger(saved.revision)||saved.revision<0||saved.delivery!=="prepared"&&saved.delivery!=="started")throw new TypeError("Invalid persisted interaction identity or delivery");const expectedRevision=(saved.delivery==="started"?1:0)+(saved.feedback?1:0)+(saved.failure?1:0);if(saved.revision!==expectedRevision||saved.delivery==="prepared"&&(saved.feedback||saved.failure))throw new TypeError("Invalid persisted interaction progress");if(saved.feedback&&!isNonBlankString(saved.feedback.id))throw new TypeError("Invalid persisted interaction feedback identity");if(saved.failure&&(!isNonBlankString(saved.failure.code)||typeof saved.failure.message!="string"))throw new TypeError("Invalid persisted interaction diagnostic");const interaction=new Interaction(saved.request,{contract:options.contract}),request=interaction.#state.request;if(!sameInteractionValue(request,saved.request))throw new TypeError("Persisted interaction request does not match its contract");const feedback=saved.feedback?Object.freeze({id:saved.feedback.id,payload:captureInteractionValue(options.contract.reply(saved.feedback.payload))}):void 0;if(feedback&&!sameInteractionValue(feedback.payload,saved.feedback.payload))throw new TypeError("Persisted interaction feedback does not match its contract");return interaction.#state=Object.freeze({id:saved.id,contract:saved.contract,revision:saved.revision,request,delivery:saved.delivery,...feedback?{feedback}:{},...saved.failure?{failure:saved.failure}:{}}),interaction.#restored=!0,saved.failure&&(interaction.#deliveryFailure={error:new RuntimeError(saved.failure.code,saved.failure.message,saved.failure)}),interaction}start(dispatch){if(this.requireMutable(),this.#state.delivery!=="prepared")throw new RuntimeError("INTERACTION_ALREADY_STARTED","This interaction has already started");this.commit(Object.freeze({...this.#state,revision:this.#state.revision+1,delivery:"started"})),this.#settled=Promise.resolve().then(()=>dispatch(this.#state.request)).then(payload=>{try{this.receive({id:randomUUID(),payload})}catch(error){if(this.#fault)throw error;this.fail(error)}},error=>this.fail(error)).catch(error=>{this.#state.feedback||(this.#fault={error}),this.notify()})}receive(feedback){if(this.requireMutable(),this.#state.delivery!=="started")throw new RuntimeError("INTERACTION_NOT_STARTED","An unstarted interaction cannot receive feedback");const captured=captureInteractionValue(feedback);if(!isNonBlankString(captured.id))throw new TypeError("Interaction feedback identity must be nonempty");const accepted=Object.freeze({id:captured.id,payload:captureInteractionValue(this.#options.contract.reply(captured.payload))});if(this.#state.feedback){if(this.#state.feedback.id===accepted.id&&sameInteractionValue(this.#state.feedback.payload,accepted.payload))return;throw new RuntimeError("INTERACTION_FEEDBACK_CONFLICT","Interaction feedback cannot replace a previously received reply")}this.commit(Object.freeze({...this.#state,revision:this.#state.revision+1,feedback:accepted})),this.notify()}read(){return this.#fault?{kind:"unavailable",error:this.#fault.error}:this.#state.feedback?{kind:"received",feedback:this.#state.feedback}:this.#state.failure?{kind:"unavailable",error:this.#deliveryFailure?.error??new RuntimeError(this.#state.failure.code,this.#state.failure.message)}:this.#restored?{kind:"unavailable",error:new RuntimeError("INTERACTION_RESTORED_PENDING","Saved interaction has no received reply and no active delivery owner")}:{kind:"pending"}}wait(options){if(!Number.isSafeInteger(options.waitMs)||options.waitMs<0||options.waitMs>2147483647)return Promise.reject(new RangeError("Interaction waitMs must be a nonnegative timer duration"));if(options.signal?.aborted)return Promise.reject(options.signal.reason);const current=this.read();return current.kind!=="pending"?Promise.resolve(current):new Promise((resolve,reject)=>{let finished=!1,timer;const cleanup=__name(()=>{timer!==void 0&&clearTimeout(timer),this.#listeners.delete(check),options.signal?.removeEventListener("abort",cancel)},"cleanup"),finish=__name(result=>{finished||(finished=!0,cleanup(),resolve(result))},"finish"),cancel=__name(()=>{finished||(finished=!0,cleanup(),reject(options.signal?.reason))},"cancel"),check=__name(()=>{const result=this.read();result.kind!=="pending"&&finish(result)},"check");this.#listeners.add(check),options.signal?.addEventListener("abort",cancel,{once:!0}),options.signal?.aborted&&cancel(),finished||check(),finished||(timer=setTimeout(()=>{check(),finished||finish({kind:"wait-expired"})},options.waitMs))})}settled(){return this.#settled}fail(error){if(this.#state.feedback)return;const failure=captureInteractionValue(RuntimeErrorHelper.format(error));this.commit(Object.freeze({...this.#state,revision:this.#state.revision+1,failure})),this.#deliveryFailure={error},this.notify()}requireMutable(){if(this.#restored)throw new RuntimeError("INTERACTION_RESTORED","Restored interactions are read-only and cannot be sent again");if(this.#fault)throw this.#fault.error;if(this.#mutating)throw new RuntimeError("INTERACTION_REENTRANT_MUTATION","Interaction mutation cannot reenter persistence")}commit(state){this.requireMutable(),this.#mutating=!0;try{try{const committed=this.#options.persistence?.commit(Object.freeze({expectedRevision:this.#state.revision,state}));if(committed!==void 0)throw Promise.resolve(committed).catch(()=>{}),new TypeError("Interaction persistence must be synchronous")}catch(cause){throw new PersistenceError("interaction.commit",cause)}this.#state=state,this.#observation?.publish(Object.freeze({type:"changed",state}))}catch(error){throw this.#fault={error},this.#observation?.publish(Object.freeze({type:"unavailable",state:this.#state,error:RuntimeErrorHelper.format(error)})),this.notify(),error}finally{this.#mutating=!1}}notify(){for(const listener of this.#listeners)listener()}}export{Interaction};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { InteractionContract, InteractionOptions, InteractionPersistence, InteractionRead, InteractionState, InteractionWait, InteractionWaitOptions } from './contract.js';
|
|
2
|
+
import type { ProgressReceiver } from '@weiliang0121/capix-changes';
|
|
3
|
+
import type { InteractionCursor, InteractionProgressBudgetLimits, InteractionProgressFact, InteractionProgressLimits, InteractionProgressReader, InteractionPublisher } from './progress/types.js';
|
|
4
|
+
export interface InteractionReference {
|
|
5
|
+
readonly id: string;
|
|
6
|
+
readonly contract: string;
|
|
7
|
+
}
|
|
8
|
+
export interface InteractionChannel<Request, Reply, Delta = never> {
|
|
9
|
+
start(request: Request): InteractionReference;
|
|
10
|
+
read(ref: InteractionReference): InteractionRead<Reply>;
|
|
11
|
+
wait(ref: InteractionReference, options: InteractionWaitOptions): Promise<InteractionWait<Reply>>;
|
|
12
|
+
openProgress(ref: InteractionReference, after?: InteractionCursor): InteractionProgressReader<Delta>;
|
|
13
|
+
}
|
|
14
|
+
export interface InteractionRegistration<Request, Reply, Delta = never> {
|
|
15
|
+
readonly send: (request: Readonly<Request>, progress: InteractionPublisher<Delta>) => Reply | Promise<Reply>;
|
|
16
|
+
readonly progress?: InteractionProgressLimits;
|
|
17
|
+
readonly progressObservation?: ProgressReceiver<InteractionProgressFact>;
|
|
18
|
+
readonly persistence?: InteractionPersistence<Request, Reply> & {
|
|
19
|
+
read?(id: string): InteractionState | undefined;
|
|
20
|
+
};
|
|
21
|
+
readonly observation?: InteractionOptions<Request, Reply>['observation'];
|
|
22
|
+
}
|
|
23
|
+
export declare class Interactions {
|
|
24
|
+
#private;
|
|
25
|
+
constructor(options?: {
|
|
26
|
+
readonly retained?: number;
|
|
27
|
+
readonly progress?: InteractionProgressBudgetLimits;
|
|
28
|
+
});
|
|
29
|
+
register<Request, Reply, Delta = never>(contract: InteractionContract<Request, Reply, Delta>, registration: InteractionRegistration<NoInfer<Request>, NoInfer<Reply>, NoInfer<Delta>>): this;
|
|
30
|
+
bind<Request, Reply, Delta = never>(contract: InteractionContract<Request, Reply, Delta>): InteractionChannel<Request, Reply, Delta>;
|
|
31
|
+
get active(): boolean;
|
|
32
|
+
get pendingCount(): number;
|
|
33
|
+
settled(): Promise<void>;
|
|
34
|
+
close(): void;
|
|
35
|
+
private requireOpen;
|
|
36
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/*! MIT License | Copyright (c) 2026 wei.liang | See LICENSE */
|
|
2
|
+
var __defProp=Object.defineProperty;var __name=(target,value)=>__defProp(target,"name",{value,configurable:!0});import{isPromise}from"node:util/types";import{RuntimeError}from"@weiliang0121/capix-contracts";import{PersistenceError}from"@weiliang0121/capix-diagnostics";import{isNonBlankString}from"./value.js";import{Interaction}from"./interaction.js";import{InteractionProgress,unavailableProgressReader,validateProgressLimits}from"./progress/buffer.js";import{ProgressBudget}from"./progress/budget.js";import{progressObservation}from"./observation.js";class Interactions{static{__name(this,"Interactions")}#registrations=new Map;#keys=new Set;#retained;#progressBudget;#closed=!1;constructor(options={}){if(this.#retained=options.retained??128,!Number.isSafeInteger(this.#retained)||this.#retained<1)throw new RangeError("Interaction retention must be a positive integer");this.#progressBudget=new ProgressBudget(options.progress)}register(contract,registration){if(this.requireOpen(),!isNonBlankString(contract.key)||typeof contract.request!="function"||typeof contract.reply!="function")throw new TypeError("Interaction registration requires a valid contract");if(this.#registrations.has(contract)||this.#keys.has(contract.key))throw new RuntimeError("INTERACTION_ALREADY_REGISTERED","Interaction identity and key can each be registered only once");const registered=new RegisteredInteractions(contract,registration,this.#retained,()=>this.requireOpen(),this.#progressBudget);return this.#registrations.set(contract,{key:contract.key,channel:registered.channel,active:__name(()=>registered.active,"active"),pendingCount:__name(()=>registered.pendingCount,"pendingCount"),settled:__name(()=>registered.settled(),"settled"),clear:__name(()=>registered.clear(),"clear")}),this.#keys.add(contract.key),this}bind(contract){this.requireOpen();const registration=this.#registrations.get(contract);if(!registration)throw new RuntimeError(this.#keys.has(contract.key)?"INTERACTION_CONTRACT_IDENTITY":"INTERACTION_NOT_REGISTERED","The exact interaction contract must be registered before it is bound");if(registration.key!==contract.key)throw new RuntimeError("INTERACTION_CONTRACT_CHANGED","A registered interaction contract key cannot change");return registration.channel}get active(){return[...this.#registrations.values()].some(registration=>registration.active())}get pendingCount(){let count=0;for(const registration of this.#registrations.values())count+=registration.pendingCount();return count}async settled(){for(;this.active;)await Promise.all([...this.#registrations.values()].map(registration=>registration.settled()))}close(){if(this.active)throw new RuntimeError("INTERACTIONS_ACTIVE","Cannot close interactions while exchanges are active");this.#closed=!0;for(const registration of this.#registrations.values())registration.clear();this.#registrations.clear(),this.#keys.clear()}requireOpen(){if(this.#closed)throw new RuntimeError("INTERACTIONS_CLOSED","Interactions are closed")}}class RegisteredInteractions{static{__name(this,"RegisteredInteractions")}retained;requireOpen;progressBudget;channel;#options;#send;#decodeProgress;#progressLimits;#progressObservation;#read;#exchanges=new Map;#active=new Set;#preparing=0;#pruning=!1;constructor(contract,registration,retained,requireOpen,progressBudget){if(this.retained=retained,this.requireOpen=requireOpen,this.progressBudget=progressBudget,typeof registration.send!="function")throw new TypeError("Interaction registration requires a sender");if(contract.progress!==void 0&&typeof contract.progress!="function")throw new TypeError("Interaction progress requires a synchronous decoder");if(registration.progress&&!contract.progress)throw new TypeError("Progress limits require an interaction progress contract");this.#decodeProgress=contract.progress?.bind(contract),this.#progressLimits=validateProgressLimits(registration.progress),this.#progressObservation=progressObservation(registration.progressObservation);const persistence=registration.persistence;if(persistence&&(typeof persistence.create!="function"||typeof persistence.commit!="function"||persistence.read!==void 0&&typeof persistence.read!="function"))throw new TypeError("Interaction registration requires synchronous persistence methods");this.#send=registration.send.bind(registration),this.#read=persistence?.read?.bind(persistence);const observation=registration.observation;this.#options=Object.freeze({contract:Object.freeze({key:contract.key,request:contract.request.bind(contract),reply:contract.reply.bind(contract)}),...persistence?{persistence:Object.freeze({create:persistence.create.bind(persistence),commit:persistence.commit.bind(persistence)})}:{},...observation?{observation:Object.freeze({receive:observation.receive.bind(observation),...observation.failed?{failed:observation.failed.bind(observation)}:{}})}:{}}),this.channel=Object.freeze({start:__name(request=>this.start(request),"start"),read:__name(ref=>this.get(ref).interaction.read(),"read"),wait:__name(async(ref,options)=>this.get(ref).interaction.wait(options),"wait"),openProgress:__name((ref,after)=>(this.requireReference(ref),this.#exchanges.get(ref.id)?.progress?.open(after)??unavailableProgressReader(ref.id,this.#decodeProgress?"not-retained":"not-enabled",after)),"openProgress")})}get active(){return this.#preparing>0||this.#active.size>0}get pendingCount(){return this.#active.size}async settled(){await Promise.all([...this.#active].map(interaction=>interaction.settled()))}clear(){for(const entry of this.#exchanges.values())entry.progress?.invalidate("closed");this.#exchanges.clear()}start(request){this.requireOpen(),this.#preparing+=1;let interaction,progress;try{interaction=new Interaction(request,this.#options),this.#decodeProgress&&(progress=new InteractionProgress(interaction.id,this.#decodeProgress,this.#progressLimits,this.progressBudget,this.#progressObservation)),this.#exchanges.set(interaction.id,{interaction,progress}),this.#active.add(interaction),progress?.installed();const output=progress,publisher=Object.freeze({publish:__name(value=>output?.publish(value)??Object.freeze({kind:"unavailable",reason:"not-enabled"}),"publish")});interaction.start(async request2=>{try{return await this.#send(request2,publisher)}finally{output?.seal()}});const started=interaction;return started.settled().then(()=>{this.#active.delete(started),this.prune()}),Object.freeze({id:interaction.id,contract:this.#options.contract.key})}catch(error){throw progress?.invalidate("start-failed"),interaction&&this.#active.delete(interaction),this.prune(),error}finally{this.#preparing-=1}}get(ref){this.requireReference(ref);let entry=this.#exchanges.get(ref.id);if(!entry){let saved;try{if(saved=this.#read?.(ref.id),isPromise(saved))throw saved.catch(()=>{}),new TypeError("Interaction persistence reads must be synchronous")}catch(cause){throw new PersistenceError("interaction.read",cause)}if(this.requireOpen(),saved!==void 0){try{const interaction=Interaction.restore(saved,this.#options);if(interaction.id!==ref.id)throw new RuntimeError("INTERACTION_IDENTITY","Saved interaction does not match the requested identity");entry={interaction}}catch(cause){throw new PersistenceError("interaction.restore",cause)}this.requireOpen(),this.#exchanges.set(entry.interaction.id,entry),this.prune()}}if(!entry)throw new RuntimeError("INTERACTION_UNKNOWN","Interaction is not retained or persisted; it cannot be sent again from a reference");return entry}requireReference(ref){if(this.requireOpen(),!ref||!isNonBlankString(ref.id)||ref.contract!==this.#options.contract.key)throw new RuntimeError("INTERACTION_CONTRACT_MISMATCH","Interaction reference must belong to this registered contract")}prune(){if(!this.#pruning){this.#pruning=!0;try{for(const[id,entry]of this.#exchanges){if(this.#exchanges.size-this.#active.size<=this.retained)return;this.#active.has(entry.interaction)||(this.#exchanges.delete(id),entry.progress?.invalidate("evicted"))}}finally{this.#pruning=!1}}}}export{Interactions};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { type ProgressReceiver } from '@weiliang0121/capix-changes';
|
|
2
|
+
import type { InteractionProgressFact } from './progress/types.js';
|
|
3
|
+
export declare function progressObservation(receiver?: ProgressReceiver<InteractionProgressFact>): ProgressReceiver<InteractionProgressFact> | undefined;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/*! MIT License | Copyright (c) 2026 wei.liang | See LICENSE */
|
|
2
|
+
var __defProp=Object.defineProperty;var __name=(target,value)=>__defProp(target,"name",{value,configurable:!0});import{ProgressChannel}from"@weiliang0121/capix-changes";function progressObservation(receiver){if(!receiver)return;const receive=receiver.receive.bind(receiver);let delivering=!1;const channel=new ProgressChannel({receive(fact){if(delivering)throw new TypeError("Interaction progress observation cannot recursively deliver facts");delivering=!0;try{return receive(fact)}finally{delivering=!1}},failed:receiver.failed?.bind(receiver)});return Object.freeze({receive:__name(fact=>{channel.publish(fact)},"receive")})}__name(progressObservation,"progressObservation");export{progressObservation};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { InteractionProgressBudgetLimits } from './types.js';
|
|
2
|
+
export declare function positiveLimit(value: number, name: string): number;
|
|
3
|
+
export declare class ProgressBudget {
|
|
4
|
+
#private;
|
|
5
|
+
constructor(limits?: InteractionProgressBudgetLimits);
|
|
6
|
+
reserveBytes(bytes: number): boolean;
|
|
7
|
+
releaseBytes(bytes: number): void;
|
|
8
|
+
reserveReader(): boolean;
|
|
9
|
+
releaseReader(): void;
|
|
10
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/*! MIT License | Copyright (c) 2026 wei.liang | See LICENSE */
|
|
2
|
+
var __defProp=Object.defineProperty;var __name=(target,value)=>__defProp(target,"name",{value,configurable:!0});function positiveLimit(value,name){if(!Number.isSafeInteger(value)||value<1)throw new RangeError(`${name} must be a positive safe integer`);return value}__name(positiveLimit,"positiveLimit");class ProgressBudget{static{__name(this,"ProgressBudget")}#maxBytes;#maxReaders;#bytes=0;#readers=0;constructor(limits={}){this.#maxBytes=positiveLimit(limits.maxBytes??32*1024*1024,"Progress budget maxBytes"),this.#maxReaders=positiveLimit(limits.maxReaders??256,"Progress budget maxReaders")}reserveBytes(bytes){return positiveLimit(bytes,"Reserved progress bytes"),bytes>this.#maxBytes-this.#bytes?!1:(this.#bytes+=bytes,!0)}releaseBytes(bytes){if(!Number.isSafeInteger(bytes)||bytes<0||bytes>this.#bytes)throw new RangeError("Invalid progress byte release");this.#bytes-=bytes}reserveReader(){return this.#readers>=this.#maxReaders?!1:(this.#readers+=1,!0)}releaseReader(){if(this.#readers===0)throw new RangeError("Invalid progress reader release");this.#readers-=1}}export{ProgressBudget,positiveLimit};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type ProgressReceiver } from '@weiliang0121/capix-changes';
|
|
2
|
+
import { ProgressBudget } from './budget.js';
|
|
3
|
+
import type { InteractionCursor, InteractionProgressFact, InteractionProgressLimits, InteractionProgressRead, InteractionProgressReader, InteractionPublication } from './types.js';
|
|
4
|
+
type Item<Delta> = Extract<InteractionProgressRead<Delta>, {
|
|
5
|
+
kind: 'item';
|
|
6
|
+
}>;
|
|
7
|
+
type Terminal = Extract<InteractionProgressRead<never>, {
|
|
8
|
+
kind: 'sealed' | 'unavailable';
|
|
9
|
+
}>;
|
|
10
|
+
type Inspection<Delta> = Item<Delta> | Terminal | {
|
|
11
|
+
readonly kind: 'pending';
|
|
12
|
+
};
|
|
13
|
+
export declare function validateProgressLimits(limits?: InteractionProgressLimits): Required<InteractionProgressLimits>;
|
|
14
|
+
export declare class InteractionProgress<Delta> {
|
|
15
|
+
#private;
|
|
16
|
+
readonly interactionId: string;
|
|
17
|
+
constructor(interactionId: string, decode: (value: unknown) => Delta, limits?: InteractionProgressLimits, budget?: ProgressBudget, observation?: ProgressReceiver<InteractionProgressFact>);
|
|
18
|
+
installed(): void;
|
|
19
|
+
get revision(): number;
|
|
20
|
+
publish(value: Delta): InteractionPublication;
|
|
21
|
+
seal(): void;
|
|
22
|
+
invalidate(reason: string): Extract<Terminal, {
|
|
23
|
+
kind: 'unavailable';
|
|
24
|
+
}>;
|
|
25
|
+
open(after?: InteractionCursor): InteractionProgressReader<Delta>;
|
|
26
|
+
inspectAfter(sequence: number): Inspection<Delta>;
|
|
27
|
+
listen(wake: () => void): () => void;
|
|
28
|
+
release(reader: ProgressReader<Delta>): void;
|
|
29
|
+
private observe;
|
|
30
|
+
private changed;
|
|
31
|
+
}
|
|
32
|
+
export declare function unavailableProgressReader<Delta>(interactionId: string, reason: string, after?: InteractionCursor): InteractionProgressReader<Delta>;
|
|
33
|
+
declare class ProgressReader<Delta> implements InteractionProgressReader<Delta> {
|
|
34
|
+
#private;
|
|
35
|
+
constructor(source: InteractionProgress<Delta> | undefined, cursor: InteractionCursor, terminal?: Terminal);
|
|
36
|
+
get position(): InteractionCursor;
|
|
37
|
+
next(options: {
|
|
38
|
+
readonly waitMs: number;
|
|
39
|
+
readonly signal?: AbortSignal;
|
|
40
|
+
}): Promise<InteractionProgressRead<Delta>>;
|
|
41
|
+
close(reason?: unknown): void;
|
|
42
|
+
finish(terminal: Terminal): void;
|
|
43
|
+
private detach;
|
|
44
|
+
private read;
|
|
45
|
+
private wait;
|
|
46
|
+
}
|
|
47
|
+
export {};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/*! MIT License | Copyright (c) 2026 wei.liang | See LICENSE */
|
|
2
|
+
var __defProp=Object.defineProperty;var __name=(target,value)=>__defProp(target,"name",{value,configurable:!0});import{isPromise}from"node:util/types";import{RuntimeError}from"@weiliang0121/capix-contracts";import{ProgressChannel}from"@weiliang0121/capix-changes";import{captureInteractionValue,isNonBlankString}from"../value.js";import{positiveLimit,ProgressBudget}from"./budget.js";function validateProgressLimits(limits={}){return Object.freeze({maxBytes:positiveLimit(limits.maxBytes??4*1024*1024,"Progress maxBytes"),maxEntries:positiveLimit(limits.maxEntries??16384,"Progress maxEntries"),maxReaders:positiveLimit(limits.maxReaders??64,"Progress maxReaders")})}__name(validateProgressLimits,"validateProgressLimits");function position(interactionId,sequence){return Object.freeze({interactionId,sequence})}__name(position,"position");function checkCursor(interactionId,after,head){if(after!==void 0&&(!after||typeof after!="object"))throw new RuntimeError("INTERACTION_PROGRESS_CURSOR","Progress cursor must be a position in this interaction");const sequence=after===void 0?0:after.sequence;if(after!==void 0&&after.interactionId!==interactionId||!Number.isSafeInteger(sequence)||sequence<0||head!==void 0&&sequence>head)throw new RuntimeError("INTERACTION_PROGRESS_CURSOR","Progress cursor must refer to an existing position in this interaction");return position(interactionId,sequence)}__name(checkCursor,"checkCursor");class InteractionProgress{static{__name(this,"InteractionProgress")}interactionId;#limits;#budget;#decode;#observation;#items=[];#readers=new Set;#waiters=new Set;#bytes=0;#head=0;#revision=0;#sealed=!1;#unavailable;#publishing=!1;#observing=!1;#installed=!1;constructor(interactionId,decode,limits={},budget=new ProgressBudget,observation){if(this.interactionId=interactionId,!isNonBlankString(interactionId)||typeof decode!="function")throw new TypeError("Progress requires an interaction identity and synchronous data contract");if(this.#decode=decode,this.#limits=validateProgressLimits(limits),this.#budget=budget,observation){const receive=observation.receive.bind(observation);this.#observation=new ProgressChannel({receive:__name(fact=>{if(this.#observing)throw new RuntimeError("INTERACTION_PROGRESS_OBSERVATION_REENTRY","Progress observation cannot recursively publish lifecycle changes");this.#observing=!0;try{return receive(fact)}finally{this.#observing=!1}},"receive"),failed:observation.failed?.bind(observation)})}}installed(){this.#installed||(this.#installed=!0,this.observe("created"))}get revision(){return this.#revision}publish(value){if(this.#unavailable)return this.#unavailable;if(this.#sealed)return Object.freeze({kind:"sealed"});if(this.#publishing)return this.invalidate("invalid-data");this.#publishing=!0;try{const decoded=this.#decode(captureInteractionValue(value));if(isPromise(decoded))throw decoded.catch(()=>{}),new TypeError("Progress data contract must be synchronous");const captured=captureInteractionValue(decoded);if(this.#unavailable)return this.#unavailable;if(this.#sealed)return Object.freeze({kind:"sealed"});const bytes=Buffer.byteLength(JSON.stringify(captured),"utf8");if(bytes>this.#limits.maxBytes-this.#bytes||this.#items.length>=this.#limits.maxEntries||!this.#budget.reserveBytes(bytes))return this.invalidate("capacity");this.#bytes+=bytes;const cursor=position(this.interactionId,++this.#head);return this.#items.push(Object.freeze({kind:"item",cursor,value:captured})),this.changed(),Object.freeze({kind:"published",cursor})}catch{return this.#unavailable?this.#unavailable:this.#sealed?Object.freeze({kind:"sealed"}):this.invalidate("invalid-data")}finally{this.#publishing=!1}}seal(){if(!this.#sealed){this.#sealed=!0,this.observe("sealed");for(const reader of[...this.#readers])this.#unavailable?reader.finish(this.#unavailable):reader.position.sequence===this.#head&&reader.finish(Object.freeze({kind:"sealed",lastSequence:this.#head}));this.changed()}}invalidate(reason){if(this.#unavailable)return this.#unavailable;this.#unavailable=Object.freeze({kind:"unavailable",reason}),this.#items.length=0,this.#budget.releaseBytes(this.#bytes),this.#bytes=0,this.observe("unavailable",reason);for(const reader of[...this.#readers])reader.finish(this.#unavailable);return this.changed(),this.#unavailable}open(after){const cursor=checkCursor(this.interactionId,after,this.#head),state=this.inspectAfter(cursor.sequence);if(state.kind==="unavailable"||state.kind==="sealed")return new ProgressReader(void 0,cursor,state);if(this.#readers.size>=this.#limits.maxReaders||!this.#budget.reserveReader())throw new RuntimeError("INTERACTION_PROGRESS_READERS","Progress reader capacity is exhausted");const reader=new ProgressReader(this,cursor);return this.#readers.add(reader),this.observe("reader-opened"),reader}inspectAfter(sequence){if(this.#unavailable)return this.#unavailable;const item=this.#items[sequence];return item||(this.#sealed?Object.freeze({kind:"sealed",lastSequence:this.#head}):{kind:"pending"})}listen(wake){return this.#waiters.add(wake),()=>this.#waiters.delete(wake)}release(reader){this.#readers.delete(reader)&&(this.#budget.releaseReader(),this.observe("reader-closed"))}observe(phase,reason){!this.#installed||!this.#observation||this.#observation.publish(Object.freeze({interactionId:this.interactionId,phase,lastSequence:this.#head,entries:this.#items.length,bytes:this.#bytes,readers:this.#readers.size,...reason?{reason}:{}}))}changed(){this.#revision+=1;for(const wake of[...this.#waiters])wake()}}function unavailableProgressReader(interactionId,reason,after){return new ProgressReader(void 0,checkCursor(interactionId,after),Object.freeze({kind:"unavailable",reason}))}__name(unavailableProgressReader,"unavailableProgressReader");class ProgressReader{static{__name(this,"ProgressReader")}#position;#source;#terminal;#closed;#busy=!1;#wake;constructor(source,cursor,terminal){this.#source=source,this.#position=cursor,this.#terminal=terminal}get position(){return this.#position}next(options){return this.#busy?Promise.reject(new RuntimeError("INTERACTION_PROGRESS_CONCURRENT_READ","A progress reader permits only one pending next operation")):(this.#busy=!0,this.read(options).finally(()=>{this.#busy=!1}))}close(reason=new RuntimeError("INTERACTION_PROGRESS_READER_CLOSED","Progress reader is closed")){this.#closed||(this.#closed={reason},this.detach(),this.#wake?.())}finish(terminal){this.#closed||this.#terminal||(this.#terminal=terminal,this.detach(),this.#wake?.())}detach(){const source=this.#source;this.#source=void 0,source?.release(this)}async read(options){const{waitMs,signal}=options;if(!Number.isSafeInteger(waitMs)||waitMs<0||waitMs>2147483647)throw new RangeError("Progress waitMs must be a nonnegative timer duration");const deadline=performance.now()+waitMs;for(;;){if(signal?.aborted)throw signal.reason;if(this.#closed)throw this.#closed.reason;if(this.#terminal)return this.#terminal;const source=this.#source,revision=source.revision,result=source.inspectAfter(this.#position.sequence);if(result.kind==="item")return this.#position=result.cursor,result;if(result.kind!=="pending")return this.finish(result),result;const remaining=deadline-performance.now();if(remaining<=0)return{kind:"wait-expired"};await this.wait(source,revision,remaining,signal)}}wait(source,revision,remaining,signal){return new Promise(resolve=>{let finished=!1,timer;const wake=__name(()=>{finished||(finished=!0,remove(),timer!==void 0&&clearTimeout(timer),signal?.removeEventListener("abort",wake),this.#wake=void 0,resolve())},"wake"),remove=source.listen(wake);this.#wake=wake,signal?.addEventListener("abort",wake,{once:!0}),(source.revision!==revision||this.#closed||this.#terminal||signal?.aborted)&&wake(),finished||(timer=setTimeout(wake,remaining))})}}export{InteractionProgress,unavailableProgressReader,validateProgressLimits};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export interface InteractionCursor {
|
|
2
|
+
readonly interactionId: string;
|
|
3
|
+
readonly sequence: number;
|
|
4
|
+
}
|
|
5
|
+
export interface InteractionProgressLimits {
|
|
6
|
+
readonly maxBytes?: number;
|
|
7
|
+
readonly maxEntries?: number;
|
|
8
|
+
readonly maxReaders?: number;
|
|
9
|
+
}
|
|
10
|
+
export interface InteractionProgressBudgetLimits {
|
|
11
|
+
readonly maxBytes?: number;
|
|
12
|
+
readonly maxReaders?: number;
|
|
13
|
+
}
|
|
14
|
+
export interface InteractionProgressFact {
|
|
15
|
+
readonly interactionId: string;
|
|
16
|
+
readonly phase: 'created' | 'sealed' | 'unavailable' | 'reader-opened' | 'reader-closed';
|
|
17
|
+
readonly lastSequence: number;
|
|
18
|
+
readonly entries: number;
|
|
19
|
+
readonly bytes: number;
|
|
20
|
+
readonly readers: number;
|
|
21
|
+
readonly reason?: string;
|
|
22
|
+
}
|
|
23
|
+
export type InteractionProgressRead<Delta> = {
|
|
24
|
+
readonly kind: 'item';
|
|
25
|
+
readonly cursor: InteractionCursor;
|
|
26
|
+
readonly value: Readonly<Delta>;
|
|
27
|
+
} | {
|
|
28
|
+
readonly kind: 'sealed';
|
|
29
|
+
readonly lastSequence: number;
|
|
30
|
+
} | {
|
|
31
|
+
readonly kind: 'unavailable';
|
|
32
|
+
readonly reason: string;
|
|
33
|
+
} | {
|
|
34
|
+
readonly kind: 'wait-expired';
|
|
35
|
+
};
|
|
36
|
+
export interface InteractionProgressReader<Delta> {
|
|
37
|
+
readonly position: InteractionCursor;
|
|
38
|
+
next(options: {
|
|
39
|
+
readonly waitMs: number;
|
|
40
|
+
readonly signal?: AbortSignal;
|
|
41
|
+
}): Promise<InteractionProgressRead<Delta>>;
|
|
42
|
+
close(reason?: unknown): void;
|
|
43
|
+
}
|
|
44
|
+
export type InteractionPublication = {
|
|
45
|
+
readonly kind: 'published';
|
|
46
|
+
readonly cursor: InteractionCursor;
|
|
47
|
+
} | {
|
|
48
|
+
readonly kind: 'unavailable';
|
|
49
|
+
readonly reason: string;
|
|
50
|
+
} | {
|
|
51
|
+
readonly kind: 'sealed';
|
|
52
|
+
};
|
|
53
|
+
export interface InteractionPublisher<Delta> {
|
|
54
|
+
publish(value: Delta): InteractionPublication;
|
|
55
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/*! MIT License | Copyright (c) 2026 wei.liang | See LICENSE */
|
package/dist/value.d.ts
ADDED
package/dist/value.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/*! MIT License | Copyright (c) 2026 wei.liang | See LICENSE */
|
|
2
|
+
var __defProp=Object.defineProperty;var __name=(target,value)=>__defProp(target,"name",{value,configurable:!0});import{isProxy}from"node:util/types";import{isArray}from"es-toolkit/compat";const limits=Object.freeze({depth:64,values:1e5,characters:16*1024*1024});function captureInteractionValue(value){let values=0,characters=0;const ancestors=new Set,visit=__name((current,depth)=>{if(values+=1,depth>limits.depth||values>limits.values)throw new TypeError("Interaction data exceeds its structural limit");if(current===null||typeof current=="boolean"||typeof current=="number"&&Number.isFinite(current))return current;if(typeof current=="string"){if(characters+=current.length,characters>limits.characters)throw new TypeError("Interaction data exceeds its text limit");return current}if(typeof current!="object"||isProxy(current))throw new TypeError("Interaction data must contain only JSON values");const prototype=Object.getPrototypeOf(current);if(!isArray(current)&&prototype!==Object.prototype&&prototype!==null)throw new TypeError("Interaction data must contain only JSON values");if(ancestors.has(current))throw new TypeError("Interaction data must not contain cycles");ancestors.add(current);try{const descriptors=Object.getOwnPropertyDescriptors(current);if(isArray(current)){const result2=[];if(Reflect.ownKeys(descriptors).length!==current.length+1)throw new TypeError("Interaction arrays must be dense and contain only indexed values");for(let index=0;index<current.length;index+=1){const descriptor=descriptors[String(index)];if(!descriptor||!("value"in descriptor)||!descriptor.enumerable)throw new TypeError("Interaction data must not contain accessors or missing array values");result2.push(visit(descriptor.value,depth+1))}return Object.freeze(result2)}const result={};for(const key of Reflect.ownKeys(descriptors)){const descriptor=descriptors[key];if(typeof key!="string"||!("value"in descriptor)||!descriptor.enumerable)throw new TypeError("Interaction data must contain only enumerable data properties");if(characters+=key.length,characters>limits.characters)throw new TypeError("Interaction data exceeds its text limit");Object.defineProperty(result,key,{value:visit(descriptor.value,depth+1),enumerable:!0})}return Object.freeze(result)}finally{ancestors.delete(current)}},"visit");return visit(value,0)}__name(captureInteractionValue,"captureInteractionValue");function sameInteractionValue(left,right){if(left===right)return!0;if(left===null||right===null||typeof left!="object"||typeof right!="object"||isArray(left)!==isArray(right))return!1;const leftKeys=Object.keys(left),rightKeys=Object.keys(right);return leftKeys.length===rightKeys.length&&leftKeys.every(key=>Object.hasOwn(right,key)&&sameInteractionValue(Reflect.get(left,key),Reflect.get(right,key)))}__name(sameInteractionValue,"sameInteractionValue");function isNonBlankString(value){return typeof value=="string"&&/\S/.test(value)}__name(isNonBlankString,"isNonBlankString");export{captureInteractionValue,isNonBlankString,sameInteractionValue};
|
package/package.json
CHANGED
|
@@ -1,6 +1,45 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weiliang0121/capix-interaction",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"import": "./dist/index.js"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"es-toolkit": "1.52.0",
|
|
14
|
+
"@weiliang0121/capix-contracts": "0.1.0",
|
|
15
|
+
"@weiliang0121/capix-diagnostics": "0.1.0",
|
|
16
|
+
"@weiliang0121/capix-changes": "0.1.0"
|
|
17
|
+
},
|
|
18
|
+
"description": "Independent external interactions, persisted feedback, and bounded progress streams for CAPiX.",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"author": "wei.liang (https://github.com/weiliang0121)",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/weiliang0121/capix.git",
|
|
24
|
+
"directory": "server/interaction"
|
|
25
|
+
},
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=22.23.2"
|
|
28
|
+
},
|
|
29
|
+
"main": "./dist/index.js",
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"files": [
|
|
32
|
+
"dist/**/*.js",
|
|
33
|
+
"dist/**/*.d.ts",
|
|
34
|
+
"LICENSE",
|
|
35
|
+
"README.md"
|
|
36
|
+
],
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public",
|
|
39
|
+
"registry": "https://registry.npmjs.org"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "tsc -p tsconfig.json",
|
|
43
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
44
|
+
}
|
|
6
45
|
}
|