@weiliang0121/capix-memory 0.1.0 → 0.2.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @weiliang0121/capix-memory
2
2
 
3
- Versioned memory entities and controlled resource access for CAPiX.
3
+ Independent, revisable summaries and controlled resource access for CAPiX. A collection does not require a Transcript, topic, message sources, or file receipts.
4
4
 
5
5
  ES modules and TypeScript declarations. Requires Node.js 22.23.2 or newer.
6
6
 
@@ -14,6 +14,58 @@ npm install @weiliang0121/capix-memory
14
14
 
15
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
16
 
17
+ ## Content and current reads
18
+
19
+ `Memories<Metadata = never>` is the only write owner. Each immutable `MemoryItem` has an independent UUID and a `value: { content: string; metadata?: Metadata }`. Updating an item preserves its ID and replaces the entire value, including optional metadata. Selection, extraction, source validation, and retention belong to the application.
20
+
21
+ ```ts
22
+ import { Memories, memoryResources } from '@weiliang0121/capix-memory';
23
+ import { Resource, Resources } from '@weiliang0121/capix-resources';
24
+
25
+ const memories = new Memories<{ topic?: string }>();
26
+ const created = memories.apply([{ action: 'create', value: { content: 'Prefer concise answers', metadata: { topic: 'preference' } } }]);
27
+ const first = memories.read(created.changed[0].id)!;
28
+ memories.apply([{ action: 'update', memoryId: first.id, value: { content: 'Explain complex questions in detail' }, reason: 'Clarified preference' }], { expected: first.basis });
29
+ // first remains the original fixed material; the current item has no metadata.
30
+
31
+ const memory = memoryResources<{ topic?: string }>();
32
+ const resource = new Resource(memories);
33
+ const resources = new Resources().register(resource, memory.read).register(resource, memory.apply);
34
+ // Share these Offer objects with authors: memory.read.request() / memory.apply.request().
35
+ ```
36
+
37
+ `read(id)` returns `{id,basis:{memoriesId,sequence},value}` or undefined. `values()` explicitly enumerates fixed items; `snapshot()` returns `{basis,items}`. `apply(changes, {expected?})` returns `{before,after,changed}`, containing only actual content changes. An omitted expected basis provides no stale-write protection. Optional reason describes the operation; changing only reason does not change content or advance sequence.
38
+
39
+ `memoryResources<Metadata>()` creates a typed pair of Offers. The read endpoint provides `read(id)` and explicit `snapshot()`; apply provides `apply(changes, expected?)` and `applyFixed({position,changes}, expected?)`. Binding does not capture the collection. `MemoryViewSource<Metadata>` supplies whole or fixed ordered subsets, checks collection sequence, returns fixed `{id,value}`, and uses undefined summaries. Entity updates and View membership remain separate operations.
40
+
41
+ ## Fixed effects and persistence
42
+
43
+ `applyFixed(request)` and `confirmFixed(request)` use `memories.apply` v2 requests with content contract, memoriesId, accurate `operation: {contextId}`, author `command: {position,changes}` with optional per-change reason, and original expected basis. The entire command and basis are fixed inside the entity write guard. Resource writes derive contextId from the actual calling operation; the application supplies position. The receipt contains `{memoriesId,startSequence,sequence,changes:[{commandIndex,memoryId}]}`. The same operation must match the entire original request, and confirmation returns the original receipt even after later content changes. First fixed no-ops persist confirmation without a content event.
44
+
45
+ ```ts
46
+ const request = {
47
+ contract: { key: 'memories.apply', version: 2 } as const,
48
+ contentContract: memories.contentContract,
49
+ memoriesId: memories.id,
50
+ operation: { contextId },
51
+ command: { position: 'remember-answer', changes },
52
+ expected,
53
+ };
54
+ const receipt = memories.applyFixed(request);
55
+ ```
56
+
57
+ `exportState()` and `Memories.restore(state, options?)` use current format v2. Restore is silent, preserves identities and confirmations, and rejects old formats. Optional `contentContract: {key,version,check?}` validates fixed content synchronously; TypeScript metadata types alone do not validate runtime input.
58
+
59
+ Exported state, commit payloads, events, and reads contain Stable values, not an automatic JSON representation. An adapter that supports undefined, negative zero, or null-prototype records must explicitly encode and decode those distinctions, including fixed requests and receipts. Check encoding and capacity before external writes. Checkpoint authors likewise supply faithful codecs; a display projection cannot replace recovery material.
60
+
61
+ `persistence.commit` synchronously commits incremental upserts and any fixed confirmation together. If it throws or violates the synchronous return contract, the instance treats the outcome as uncertain: reload from authoritative storage before current reads or further writes. Previously delivered material remains valid, and known exact receipts remain confirmable; an unknown receipt cannot establish that an operation did not execute. Publication failure occurs after installation and does not undo content or confirmation.
62
+
63
+ ## Domain events and display projections
64
+
65
+ `MemoryEvent<Metadata>` and `MemoryFacts` belong to this package. Each event identifies memoriesId, sequence, time, optional origin, and `{action,memoryId,value,reason?}`. Forwarding uses typed facts; an independent change without origin does not acquire an inferred calling identity.
66
+
67
+ Default records deliberately project only content and optional reason, omitting unknown metadata. The framework protocol package does not define Memory wire DTOs. Baseline and incremental display values use the same projection. Applications that need topic, source links, or other metadata must define their own projection and encoding. Applications own their persistence adapters and wire DTOs; display projections are not complete entity snapshots or recovery material.
68
+
17
69
  ## License
18
70
 
19
- MIT. Copyright (c) 2026 wei.liang. See [LICENSE](./LICENSE).
71
+ MIT. Copyright (c) 2026 wei.liang. See [LICENSE](LICENSE).
package/dist/apply.d.ts CHANGED
@@ -1,23 +1,31 @@
1
- import type { MemoryChange } from './change.js';
2
- export interface MemoryFixedApply {
1
+ import type { MemoryMutation } from './change.js';
2
+ import type { MemoryBasis, MemoryContentContractIdentity } from './memory.js';
3
+ export interface MemoryFixedApply<Metadata = never> {
3
4
  readonly contract: {
4
5
  readonly key: 'memories.apply';
5
- readonly version: 1;
6
+ readonly version: 2;
6
7
  };
8
+ readonly contentContract: MemoryContentContractIdentity;
7
9
  readonly operation: {
8
10
  readonly contextId: string;
9
- readonly position: string;
10
11
  };
11
12
  readonly memoriesId: string;
12
- readonly changes: readonly MemoryChange[];
13
+ readonly command: {
14
+ readonly position: string;
15
+ readonly changes: readonly MemoryMutation<Metadata>[];
16
+ };
17
+ readonly expected?: MemoryBasis;
13
18
  }
14
19
  export interface MemoryApplyReceipt {
15
20
  readonly memoriesId: string;
16
21
  readonly startSequence: number;
17
22
  readonly sequence: number;
18
- readonly memoryIds: readonly string[];
23
+ readonly changes: readonly {
24
+ readonly commandIndex: number;
25
+ readonly memoryId: string;
26
+ }[];
19
27
  }
20
- export interface MemoryApplyState {
21
- readonly request: MemoryFixedApply;
28
+ export interface MemoryApplyState<Metadata = never> {
29
+ readonly request: MemoryFixedApply<Metadata>;
22
30
  readonly receipt: MemoryApplyReceipt;
23
31
  }
package/dist/change.d.ts CHANGED
@@ -1,15 +1,10 @@
1
- import type { MemoryContent, MemoryResultSource } from '@weiliang0121/capix-protocol';
2
- import type { Transcript } from '@weiliang0121/capix-transcript';
3
- export type MemoryChange = {
4
- readonly change: string;
5
- readonly sourceIndexes: readonly number[];
6
- readonly sourceResults?: readonly MemoryResultSource[];
7
- readonly result: MemoryContent;
8
- } & ({
1
+ import type { MemoryContent } from './memory.js';
2
+ export type MemoryMutation<Metadata = never> = ({
9
3
  readonly action: 'create';
10
- readonly memoryId: null;
11
4
  } | {
12
5
  readonly action: 'update';
13
6
  readonly memoryId: string;
14
- });
15
- export declare function prepareMemoryChanges(transcript: Transcript, changes: readonly MemoryChange[]): readonly MemoryChange[];
7
+ }) & {
8
+ readonly value: MemoryContent<Metadata>;
9
+ readonly reason?: string;
10
+ };
package/dist/change.js CHANGED
@@ -1,2 +1 @@
1
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{isEqual}from"es-toolkit/predicate";import{isArray}from"es-toolkit/compat";import{Memory}from"./memory.js";import{prepareMemoryResultSources}from"./source.js";import{hasUniqueItems,isNonBlankString,isNonEmptyArray,isObject}from"./validation.js";function prepareMemoryChanges(transcript,changes){if(!isArray(changes))throw new TypeError("Memory changes must be an array");return Object.freeze(Array.from(changes,change=>prepareMemoryChange(transcript,change)))}__name(prepareMemoryChanges,"prepareMemoryChanges");function prepareMemoryChange(transcript,value){if(!isObject(value))throw new TypeError("Memory change requires an explanation and unique evidence");const action=value.action,memoryId=value.memoryId,change=value.change,evidence=value.sourceIndexes;if(!isNonBlankString(change)||!isNonEmptyArray(evidence))throw new TypeError("Memory change requires an explanation and unique evidence");const sourceIndexes=Object.freeze([...evidence]);if(!isNonEmptyArray(sourceIndexes)||!hasUniqueItems(sourceIndexes))throw new TypeError("Memory change requires an explanation and unique evidence");for(const index of sourceIndexes)transcript.ref(index);const sourceResults=prepareMemoryResultSources(transcript,value.sourceResults),result=Memory.prepare(transcript,value.result);if(!sourceIndexes.every(index=>result.sourceIndexes.includes(index)))throw new Error("Memory result must include change evidence");if(sourceResults?.some(source=>!result.sourceResults?.some(candidate=>isEqual(source,candidate))))throw new Error("Memory result must include the unchanged result sources of this change");if(action==="create"){if(memoryId!==null)throw new Error("Creating memory cannot specify an existing identity");return Object.freeze({action,memoryId,change,sourceIndexes,...sourceResults?{sourceResults}:{},result})}if(action!=="update")throw new Error("Unknown memory change action");if(!isNonBlankString(memoryId))throw new TypeError("Memory update requires an identity");return Object.freeze({action,memoryId,change,sourceIndexes,...sourceResults?{sourceResults}:{},result})}__name(prepareMemoryChange,"prepareMemoryChange");export{prepareMemoryChanges};
@@ -0,0 +1,17 @@
1
+ import { FactType } from '@weiliang0121/capix-changes';
2
+ import type { InvocationOrigin, Stable } from '@weiliang0121/capix-contracts';
3
+ import type { MemoryContent } from './memory.js';
4
+ export interface MemoryEvent<Metadata = never> {
5
+ readonly type: 'memory';
6
+ readonly memoriesId: string;
7
+ readonly sequence: number;
8
+ readonly occurredAt: string;
9
+ readonly origin?: InvocationOrigin;
10
+ readonly change: {
11
+ readonly action: 'create' | 'update';
12
+ readonly memoryId: string;
13
+ readonly value: Stable<MemoryContent<Metadata>>;
14
+ readonly reason?: string;
15
+ };
16
+ }
17
+ export declare const MemoryFacts: FactType<MemoryEvent<unknown>>;
package/dist/event.js ADDED
@@ -0,0 +1,2 @@
1
+ /*! MIT License | Copyright (c) 2026 wei.liang | See LICENSE */
2
+ import{FactType}from"@weiliang0121/capix-changes";const MemoryFacts=new FactType("memory");export{MemoryFacts};
package/dist/index.d.ts CHANGED
@@ -1,7 +1,8 @@
1
- export { Memory } from './memory.js';
2
- export { Memories, type MemoriesOptions } from './memories.js';
3
- export type { MemoryChange } from './change.js';
1
+ export { Memories, type MemoriesOptions, type MemoryApplyOptions } from './memories.js';
2
+ export type { MemoryContent, MemoryItem, MemoryBasis, MemoryRead, MemorySnapshot, MemoryApplication, MemoryContentContract, MemoryContentContractIdentity } from './memory.js';
3
+ export type { MemoryMutation } from './change.js';
4
4
  export type { MemoryFixedApply, MemoryApplyReceipt, MemoryApplyState } from './apply.js';
5
- export type { MemoryState, MemoriesState, MemoriesCommit, MemoriesPersistence } from './persistence.js';
6
- export { MemoryResource, type CapturedMemories, type MemoryBasis, type MemoryApplication } from './resource.js';
7
- export { MemoryViewSource, type MemoryViewSummary } from './view.js';
5
+ export type { MemoriesState, MemoriesCommit, MemoriesPersistence } from './persistence.js';
6
+ export { memoryResources } from './resource.js';
7
+ export { MemoryViewSource } from './view.js';
8
+ export { MemoryFacts, type MemoryEvent } from './event.js';
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
1
  /*! MIT License | Copyright (c) 2026 wei.liang | See LICENSE */
2
- import{Memory}from"./memory.js";import{Memories}from"./memories.js";import{MemoryResource}from"./resource.js";import{MemoryViewSource}from"./view.js";export{Memories,Memory,MemoryResource,MemoryViewSource};
2
+ import{Memories}from"./memories.js";import{memoryResources}from"./resource.js";import{MemoryViewSource}from"./view.js";import{MemoryFacts}from"./event.js";export{Memories,MemoryFacts,MemoryViewSource,memoryResources};
@@ -1,40 +1,44 @@
1
+ import { type Stable } from '@weiliang0121/capix-contracts';
1
2
  import { type ChangeProvenance, type EventConsumer, type ChangeCapture } from '@weiliang0121/capix-changes';
2
- import type { MemoryEvent } from '@weiliang0121/capix-protocol';
3
- import { Memory } from './memory.js';
4
- import { type MemoryChange } from './change.js';
5
- import type { Transcript } from '@weiliang0121/capix-transcript';
3
+ import type { MemoryEvent } from './event.js';
4
+ import type { MemoryMutation } from './change.js';
5
+ import type { MemoryApplication, MemoryBasis, MemoryContentContract, MemoryContentContractIdentity, MemoryItem, MemoryRead, MemorySnapshot } from './memory.js';
6
6
  import type { MemoriesPersistence, MemoriesState } from './persistence.js';
7
7
  import type { MemoryFixedApply, MemoryApplyReceipt } from './apply.js';
8
- export interface MemoriesOptions {
9
- observation?: EventConsumer<MemoryEvent>;
10
- changes?: ChangeCapture<MemoryEvent>;
11
- persistence?: MemoriesPersistence;
8
+ export interface MemoriesOptions<Metadata = never> {
9
+ readonly contentContract?: MemoryContentContract<Metadata>;
10
+ readonly observation?: EventConsumer<MemoryEvent<Metadata>>;
11
+ readonly changes?: ChangeCapture<MemoryEvent<Metadata>>;
12
+ readonly persistence?: MemoriesPersistence<Metadata>;
12
13
  }
13
- export declare class Memories {
14
+ export interface MemoryApplyOptions<Metadata = never> extends ChangeProvenance<MemoryEvent<Metadata>> {
15
+ readonly expected?: MemoryBasis;
16
+ }
17
+ export declare class Memories<Metadata = never> {
14
18
  #private;
15
- readonly transcript: Transcript;
19
+ constructor(options?: MemoriesOptions<Metadata>);
16
20
  get id(): string;
21
+ get contentContract(): Stable<MemoryContentContractIdentity>;
17
22
  get sequence(): number;
18
- private readonly observation?;
19
- private readonly changes?;
20
- private readonly persistence?;
21
- constructor(transcript: Transcript, options?: MemoriesOptions);
22
- static restore(state: MemoriesState, transcript: Transcript, options?: MemoriesOptions): Memories;
23
- exportState(): MemoriesState;
24
- get(id: string): Memory | undefined;
25
- values(): readonly Memory[];
26
- snapshot(): {
27
- topic: string;
28
- content: string;
29
- sourceIndexes: readonly number[];
30
- sourceResults?: readonly import("@weiliang0121/capix-protocol").MemoryResultSource[];
31
- id: string;
32
- }[];
33
- apply(changes: readonly MemoryChange[], provenance?: ChangeProvenance<MemoryEvent>): readonly Memory[];
34
- confirmFixed(value: MemoryFixedApply): MemoryApplyReceipt | undefined;
35
- applyFixed(value: MemoryFixedApply, provenance?: ChangeProvenance<MemoryEvent>): MemoryApplyReceipt;
23
+ read(id: string): MemoryRead<Metadata> | undefined;
24
+ values(): Stable<readonly MemoryItem<Metadata>[]>;
25
+ snapshot(): Stable<MemorySnapshot<Metadata>>;
26
+ exportState(): Stable<MemoriesState<Metadata>>;
27
+ static restore<Metadata = never>(value: MemoriesState<Metadata> | Stable<MemoriesState<Metadata>>, options?: MemoriesOptions<Metadata>): Memories<Metadata>;
28
+ apply(changes: readonly MemoryMutation<Metadata>[] | Stable<readonly MemoryMutation<Metadata>[]>, options?: MemoryApplyOptions<Metadata>): Stable<MemoryApplication<Metadata>>;
29
+ confirmFixed(value: MemoryFixedApply<Metadata> | Stable<MemoryFixedApply<Metadata>>): Stable<MemoryApplyReceipt> | undefined;
30
+ applyFixed(value: MemoryFixedApply<Metadata> | Stable<MemoryFixedApply<Metadata>>, provenance?: ChangeProvenance<MemoryEvent<Metadata>>): Stable<MemoryApplyReceipt>;
31
+ private protect;
32
+ private requireCertain;
33
+ private basis;
34
+ private validateBasis;
35
+ private validateContent;
36
+ private acceptChanges;
36
37
  private acceptFixed;
37
38
  private confirmAccepted;
39
+ private acceptProvenance;
38
40
  private applyPrepared;
39
41
  private commit;
42
+ private acceptReceipt;
43
+ private validateTimeline;
40
44
  }
package/dist/memories.js CHANGED
@@ -1,2 +1,2 @@
1
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{publishFact,combineFaults}from"@weiliang0121/capix-changes";import{randomUUID}from"node:crypto";import{isEqual}from"es-toolkit/predicate";import{Memory,restoreMemory,updateMemory}from"./memory.js";import{prepareMemoryChanges}from"./change.js";import{isNonBlankString,hasUniqueItems,isObject}from"./validation.js";import{isArray,isSafeInteger}from"es-toolkit/compat";import{RuntimeError}from"@weiliang0121/capix-contracts";import{PersistenceError}from"@weiliang0121/capix-diagnostics";class Memories{static{__name(this,"Memories")}transcript;#id=randomUUID();#sequence=0;#mutating=!1;get id(){return this.#id}get sequence(){return this.#sequence}#items=new Map;#applies=new Map;observation;changes;persistence;constructor(transcript,options={}){this.transcript=transcript,this.observation=options.observation,this.changes=options.changes,this.persistence=options.persistence}static restore(state,transcript,options={}){if(!isObject(state)||!isNonBlankString(state.id)||state.transcriptId!==transcript.id||!isArray(state.items))throw new TypeError("Invalid persisted memory collection");if(!isSafeInteger(state.sequence)||state.sequence<state.items.length||!state.items.length&&state.sequence!==0)throw new TypeError("Invalid persisted memory sequence");const restored=new Memories(transcript,options);for(const item of state.items){const memory=restoreMemory(transcript,item);if(restored.#items.has(memory.id))throw new TypeError("Duplicate persisted memory identity");restored.#items.set(memory.id,memory)}if(restored.#id=state.id,restored.#sequence=state.sequence,state.applies!==void 0){if(!isArray(state.applies))throw new TypeError("Invalid persisted memory applies");let previousSequence=0;for(const saved of state.applies){const request=restored.acceptFixed(saved.request),receipt=saved.receipt;if(!isObject(receipt)||receipt.memoriesId!==restored.id||!isSafeInteger(receipt.startSequence)||receipt.startSequence<previousSequence||!isSafeInteger(receipt.sequence)||receipt.sequence<receipt.startSequence||receipt.sequence>state.sequence||!isArray(receipt.memoryIds)||!hasUniqueItems(receipt.memoryIds)||receipt.memoryIds.length!==receipt.sequence-receipt.startSequence||receipt.memoryIds.some(id=>!restored.#items.has(id)))throw new TypeError("Invalid persisted memory apply receipt");const key=applyKey(request);if(restored.#applies.has(key))throw new TypeError("Duplicate persisted memory apply operation");restored.#applies.set(key,Object.freeze({request,receipt:Object.freeze({memoriesId:restored.id,startSequence:receipt.startSequence,sequence:receipt.sequence,memoryIds:Object.freeze([...receipt.memoryIds])})})),previousSequence=receipt.sequence}}return restored}exportState(){return Object.freeze({id:this.id,transcriptId:this.transcript.id,sequence:this.#sequence,items:Object.freeze(this.values().map(memory=>Object.freeze(memory.toJSON()))),...this.#applies.size?{applies:Object.freeze([...this.#applies.values()])}:{}})}get(id){return this.#items.get(id)}values(){return[...this.#items.values()]}snapshot(){return this.values().map(memory=>memory.toJSON())}apply(changes,provenance={}){if(this.#mutating)throw new Error("Memory persistence or change publication cannot reenter mutation");this.#mutating=!0;try{return this.applyPrepared(prepareMemoryChanges(this.transcript,changes),provenance)}finally{this.#mutating=!1}}confirmFixed(value){return this.confirmAccepted(this.acceptFixed(value))}applyFixed(value,provenance={}){if(this.#mutating)throw new Error("Memory persistence or change publication cannot reenter mutation");this.#mutating=!0;try{const request=this.acceptFixed(value),receipt=this.confirmAccepted(request);return receipt||(this.applyPrepared(request.changes,provenance,request),this.#applies.get(applyKey(request)).receipt)}finally{this.#mutating=!1}}acceptFixed(value){if(!isObject(value))throw new TypeError("Invalid fixed memory apply request");const contract=value.contract,origin=value.operation;if(contract?.key!=="memories.apply"||contract.version!==1||value.memoriesId!==this.id||!isObject(origin))throw new TypeError("Invalid fixed memory apply request");const operation=Object.freeze({contextId:origin.contextId,position:origin.position});if(!isNonBlankString(operation.contextId)||!isNonBlankString(operation.position))throw new TypeError("Invalid fixed memory apply request");const changes=prepareMemoryChanges(this.transcript,value.changes);return Object.freeze({contract:Object.freeze({key:"memories.apply",version:1}),operation,memoriesId:this.id,changes})}confirmAccepted(request){const saved=this.#applies.get(applyKey(request));if(saved&&!isEqual(saved.request,request))throw new RuntimeError("MEMORY_APPLY_CONFLICT","\u56FA\u5B9A\u8BB0\u5FC6\u6279\u6B21\u4E0E\u539F\u64CD\u4F5C\u4E0D\u4E00\u81F4\u3002");return saved?.receipt}applyPrepared(changes,provenance,request){const targets=new Set,prepared=changes.map(change=>{const result=change.result,sourceResults=change.sourceResults;if(change.action==="create")return{change,result,sourceResults,memory:void 0};const memory=this.#items.get(change.memoryId);if(!memory)throw new Error("Memory update target does not exist in this Runtime");for(const source of result.sourceResults??[]){const previous=memory.sourceResults?.find(candidate=>candidate.id===source.id);if(previous&&!isEqual(previous,source))throw new Error("Memory result source identity cannot change its receipt")}if(targets.has(memory.id))throw new Error("A batch cannot update the same memory twice");return targets.add(memory.id),{change,result,sourceResults,memory}}),mutations=[];for(const{change,result,sourceResults,memory:previous}of prepared){if(previous&&isEqual(previous.snapshot(),result))continue;const memory=previous??new Memory(this.transcript,result),sequence2=this.#sequence+mutations.length+1;if(!isSafeInteger(sequence2))throw new RangeError("Memory sequence is exhausted");const event=Object.freeze({type:"memory",memoriesId:this.id,sequence:sequence2,occurredAt:new Date().toISOString(),...provenance.origin?{origin:Object.freeze({...provenance.origin})}:{},change:Object.freeze({action:change.action,memoryId:memory.id,explanation:change.change,sourceIndexes:Object.freeze([...change.sourceIndexes]),...sourceResults?{sourceResults}:{},result})});mutations.push({memory,event,created:!previous})}if(!mutations.length&&!request)return[];const sequence=mutations.at(-1)?.event.sequence??this.#sequence,upserts=Object.freeze(mutations.map(({memory,event})=>Object.freeze({id:memory.id,...event.change.result}))),fixed=request?Object.freeze({request,receipt:Object.freeze({memoriesId:this.id,startSequence:this.#sequence,sequence,memoryIds:Object.freeze(upserts.map(memory=>memory.id))})}):void 0;this.commit(Object.freeze({memoriesId:this.id,transcriptId:this.transcript.id,expectedSequence:this.#sequence,sequence,upserts,...fixed?{fixed}:{}}));for(const{memory,event,created}of mutations)created?this.#items.set(memory.id,memory):updateMemory(memory,event.change.result);this.#sequence=sequence,fixed&&this.#applies.set(applyKey(fixed.request),fixed);const faults=[];for(const{event}of mutations)try{publishFact(event,this.changes,provenance.observation??this.observation)}catch(error){faults.push(error)}const fault=combineFaults(faults);if(fault!==void 0)throw fault;return mutations.map(({memory})=>memory)}commit(change){try{const result=this.persistence?.commit(change);if(result!==void 0)throw Promise.resolve(result).catch(()=>{}),new TypeError("Memory persistence must complete synchronously and return undefined")}catch(cause){throw cause instanceof PersistenceError?cause:new PersistenceError("memories.commit",cause)}}}function applyKey(request){return JSON.stringify([request.operation.contextId,request.operation.position])}__name(applyKey,"applyKey");export{Memories};
2
+ var __defProp=Object.defineProperty;var __name=(target,value)=>__defProp(target,"name",{value,configurable:!0});import{randomUUID}from"node:crypto";import{RuntimeError,stableValue}from"@weiliang0121/capix-contracts";import{publishFact,combineFaults}from"@weiliang0121/capix-changes";import{PersistenceError}from"@weiliang0121/capix-diagnostics";import{equalStable,hasOnlyKeys,isNonBlankString,isRecord,isUuid}from"./validation.js";class Memories{static{__name(this,"Memories")}#id=randomUUID();#sequence=0;#mutating=!1;#uncertain;#items=new Map;#applies=new Map;#contentContract;#check;#observation;#changes;#persistence;constructor(options={}){const fields=dataOptions(options,["contentContract","observation","changes","persistence"]),contract=fields.contentContract===void 0?{key:"memory.content",version:1}:dataOptions(fields.contentContract,["key","version","check"]);if(this.#contentContract=stableValue({key:contract.key,version:contract.version}),validateContract(this.#contentContract),contract.check!==void 0&&typeof contract.check!="function")throw new TypeError("Memory content check must be a function");this.#check=contract.check,this.#observation=fields.observation,this.#changes=fields.changes,this.#persistence=fields.persistence}get id(){return this.#id}get contentContract(){return this.#contentContract}get sequence(){return this.requireCertain(),this.#sequence}read(id){this.requireCertain();const item=this.#items.get(id);return item===void 0?void 0:stableValue({id:item.id,basis:this.basis(),value:item.value})}values(){return this.requireCertain(),stableValue([...this.#items.values()])}snapshot(){return this.requireCertain(),stableValue({basis:this.basis(),items:this.values()})}exportState(){return this.requireCertain(),stableValue({version:2,id:this.id,contentContract:this.contentContract,sequence:this.#sequence,items:this.values(),...this.#applies.size?{applies:[...this.#applies.values()]}:{}})}static restore(value,options={}){const restored=new Memories(options);restored.#mutating=!0;try{const state=stableValue(value);if(!isRecord(state)||!hasOnlyKeys(state,["version","id","contentContract","sequence","items"],["applies"])||state.version!==2||!isUuid(state.id)||!Array.isArray(state.items)||!Number.isSafeInteger(state.sequence)||state.sequence<state.items.length||state.items.length===0&&state.sequence!==0)throw new TypeError("Invalid current memory state");if(validateContract(state.contentContract),!equalStable(state.contentContract,restored.contentContract))throw new TypeError("Memory content contract does not match persisted material");restored.#id=state.id,restored.#sequence=state.sequence;for(const item of state.items){if(!isRecord(item)||!hasOnlyKeys(item,["id","value"])||!isUuid(item.id)||restored.#items.has(item.id))throw new TypeError("Invalid or duplicate persisted memory identity");restored.validateContent(item.value),restored.#items.set(item.id,item)}const births=new Map,deadlines=new Map,reserved=new Set;if(state.applies!==void 0){if(!Array.isArray(state.applies))throw new TypeError("Invalid persisted memory applies");let previousSequence=0;for(const saved of state.applies){if(!isRecord(saved)||!hasOnlyKeys(saved,["request","receipt"]))throw new TypeError("Invalid persisted memory apply");const request=restored.acceptFixed(saved.request),receipt=restored.acceptReceipt(saved.receipt,request,previousSequence,births,deadlines,reserved),key=applyKey(request);if(restored.#applies.has(key))throw new TypeError("Duplicate persisted memory apply operation");restored.#applies.set(key,stableValue({request,receipt})),previousSequence=receipt.sequence}}return restored.validateTimeline(births,deadlines,reserved),restored}finally{restored.#mutating=!1}}apply(changes,options={}){return this.protect(()=>{this.requireCertain();const fields=dataOptions(options,["expected","origin","observation"]),input=stableValue({changes,...Object.hasOwn(fields,"expected")?{expected:fields.expected}:{}}),accepted=this.acceptChanges(input.changes);input.expected!==void 0&&this.validateBasis(input.expected);const provenance=this.acceptProvenance(fields);return this.applyPrepared(accepted,input.expected,provenance)})}confirmFixed(value){return this.protect(()=>this.confirmAccepted(this.acceptFixed(value)))}applyFixed(value,provenance={}){return this.protect(()=>{const request=this.acceptFixed(value),receipt=this.confirmAccepted(request);if(receipt!==void 0)return receipt;const acceptedProvenance=this.acceptProvenance(dataOptions(provenance,["origin","observation"]));return this.applyPrepared(request.command.changes,request.expected,acceptedProvenance,request),this.#applies.get(applyKey(request)).receipt})}protect(operation){if(this.#mutating)throw new Error("Memory validation, persistence or publication cannot reenter mutation");this.#mutating=!0;try{return operation()}finally{this.#mutating=!1}}requireCertain(){if(this.#uncertain!==void 0)throw this.#uncertain}basis(){return stableValue({memoriesId:this.id,sequence:this.#sequence})}validateBasis(value){if(!isRecord(value)||!hasOnlyKeys(value,["memoriesId","sequence"])||!isUuid(value.memoriesId)||!Number.isSafeInteger(value.sequence)||value.sequence<0)throw new TypeError("Invalid memory application basis")}validateContent(value){if(!isRecord(value)||!hasOnlyKeys(value,["content"],["metadata"])||!isNonBlankString(value.content))throw new TypeError("Memory content requires a nonblank summary");const result=this.#check?.(value);if(result!==null&&(typeof result=="object"||typeof result=="function")&&typeof result.then=="function")throw Promise.resolve(result).catch(()=>{}),new TypeError("Memory content check must complete synchronously")}acceptChanges(value){if(!Array.isArray(value))throw new TypeError("Memory mutations must be an array");const targets=new Set;for(const mutation of value){if(!isRecord(mutation)||mutation.action!=="create"&&mutation.action!=="update"||!hasOnlyKeys(mutation,mutation.action==="create"?["action","value"]:["action","memoryId","value"],["reason"])||mutation.reason!==void 0&&typeof mutation.reason!="string")throw new TypeError("Invalid memory mutation");if(mutation.action==="update"){if(!isUuid(mutation.memoryId))throw new TypeError("Memory update requires a UUID");if(targets.has(mutation.memoryId))throw new TypeError("A batch cannot update the same memory twice");targets.add(mutation.memoryId)}this.validateContent(mutation.value)}return value}acceptFixed(value){const request=stableValue(value);if(!isRecord(request)||!hasOnlyKeys(request,["contract","contentContract","operation","memoriesId","command"],["expected"])||!isRecord(request.contract)||!hasOnlyKeys(request.contract,["key","version"])||request.contract.key!=="memories.apply"||request.contract.version!==2||request.memoriesId!==this.id||!isRecord(request.operation)||!hasOnlyKeys(request.operation,["contextId"])||!isNonBlankString(request.operation.contextId)||!isRecord(request.command)||!hasOnlyKeys(request.command,["position","changes"])||!isNonBlankString(request.command.position))throw new TypeError("Invalid current fixed memory apply request");if(validateContract(request.contentContract),!equalStable(request.contentContract,this.contentContract))throw new TypeError("Memory content contract does not match fixed apply");return this.acceptChanges(request.command.changes),request.expected!==void 0&&this.validateBasis(request.expected),request}confirmAccepted(request){const saved=this.#applies.get(applyKey(request));if(saved!==void 0&&!equalStable(saved.request,request))throw new RuntimeError("MEMORY_APPLY_CONFLICT","Fixed memory apply differs from its original request");if(saved!==void 0)return saved.receipt;this.requireCertain()}acceptProvenance(fields){const origin=stableValue(fields.origin);if(origin!==void 0&&(!isRecord(origin)||!hasOnlyKeys(origin,["contextId"])||!isNonBlankString(origin.contextId)))throw new TypeError("Invalid memory change origin");return{origin,observation:fields.observation}}applyPrepared(changes,expected,provenance,request){this.requireCertain();const before=this.basis();if(expected!==void 0&&(expected.memoriesId!==this.id||expected.sequence!==this.#sequence))throw new RuntimeError("MEMORY_BASIS_CONFLICT","Memory application basis does not match the current collection");for(const mutation of changes)if(mutation.action==="update"&&!this.#items.has(mutation.memoryId))throw new RangeError("Memory update target does not exist");const changed=[],events=[],correspondence=[],newIds=new Set;for(const[commandIndex,mutation]of changes.entries()){const previous=mutation.action==="update"?this.#items.get(mutation.memoryId):void 0;if(previous!==void 0&&equalStable(previous.value,mutation.value))continue;const sequence2=this.#sequence+changed.length+1;if(!Number.isSafeInteger(sequence2))throw new RangeError("Memory sequence is exhausted");const id=previous?.id??randomUUID();if(previous===void 0&&(this.#items.has(id)||newIds.has(id)))throw new Error("Memory identity collision");previous===void 0&&newIds.add(id);const item=stableValue({id,value:mutation.value}),event=stableValue({type:"memory",memoriesId:this.id,sequence:sequence2,occurredAt:new Date().toISOString(),...provenance.origin!==void 0?{origin:provenance.origin}:{},change:{action:mutation.action,memoryId:id,value:mutation.value,...Object.hasOwn(mutation,"reason")?{reason:mutation.reason}:{}}});changed.push(item),events.push(event),correspondence.push({commandIndex,memoryId:id})}const sequence=this.#sequence+changed.length,result=stableValue({before,after:{memoriesId:this.id,sequence},changed});if(changed.length===0&&request===void 0)return result;const fixed=request===void 0?void 0:stableValue({request,receipt:{memoriesId:this.id,startSequence:this.#sequence,sequence,changes:correspondence}}),commit=stableValue({memoriesId:this.id,contentContract:this.contentContract,expectedSequence:this.#sequence,sequence,upserts:changed,...fixed!==void 0?{fixed}:{}});this.commit(commit);for(const item of changed)this.#items.set(item.id,item);this.#sequence=sequence,fixed!==void 0&&this.#applies.set(applyKey(fixed.request),fixed);const faults=[];for(const event of events)try{publishFact(event,this.#changes,provenance.observation??this.#observation)}catch(error){faults.push(error)}const fault=combineFaults(faults);if(fault!==void 0)throw fault;return result}commit(change){try{const result=this.#persistence?.commit(change);if(result!==void 0)throw Promise.resolve(result).catch(()=>{}),new TypeError("Memory persistence must complete synchronously and return undefined")}catch(cause){let persistenceFailure=!1;try{persistenceFailure=cause instanceof PersistenceError}catch{}throw this.#uncertain=persistenceFailure?cause:new PersistenceError("memories.commit",cause),this.#uncertain}}acceptReceipt(value,request,previousSequence,births,deadlines,reserved){if(!isRecord(value)||!hasOnlyKeys(value,["memoriesId","startSequence","sequence","changes"])||value.memoriesId!==this.id||!Number.isSafeInteger(value.startSequence)||value.startSequence<previousSequence||!Number.isSafeInteger(value.sequence)||value.sequence<value.startSequence||value.sequence>this.#sequence||!Array.isArray(value.changes)||value.changes.length!==value.sequence-value.startSequence)throw new TypeError("Invalid persisted memory apply receipt");const start=value.startSequence;if(request.expected!==void 0&&(request.expected.memoriesId!==this.id||request.expected.sequence!==start))throw new TypeError("Persisted fixed apply has an impossible basis");const seen=new Set,commands=new Set;let previousIndex=-1;for(const[index,entry]of value.changes.entries()){if(!isRecord(entry)||!hasOnlyKeys(entry,["commandIndex","memoryId"])||!Number.isSafeInteger(entry.commandIndex)||entry.commandIndex<=previousIndex||entry.commandIndex>=request.command.changes.length||!isUuid(entry.memoryId)||seen.has(entry.memoryId)||!this.#items.has(entry.memoryId))throw new TypeError("Invalid persisted memory apply correspondence");const commandIndex=entry.commandIndex,mutation=request.command.changes[commandIndex];if(mutation.action==="update"&&mutation.memoryId!==entry.memoryId)throw new TypeError("Persisted update receipt identifies another memory");const sequence=start+index+1;if(mutation.action==="create"){if(births.has(entry.memoryId))throw new TypeError("A memory cannot be created by multiple fixed applies");births.set(entry.memoryId,sequence)}reserved.add(sequence),seen.add(entry.memoryId),commands.add(commandIndex),previousIndex=commandIndex}for(const[index,mutation]of request.command.changes.entries())if(mutation.action==="create"){if(!commands.has(index))throw new TypeError("Persisted create has no corresponding memory")}else{if(!this.#items.has(mutation.memoryId))throw new TypeError("Persisted update target does not exist");deadlines.set(mutation.memoryId,Math.min(deadlines.get(mutation.memoryId)??start,start))}return value}validateTimeline(births,deadlines,reserved){let previousBirth=0;for(const id of this.#items.keys()){const known=births.get(id);let birth=known??previousBirth+1;if(known===void 0)for(;reserved.has(birth);birth++);if(birth<=previousBirth||birth>this.#sequence||previousBirth===0&&birth!==1||birth>(deadlines.get(id)??this.#sequence))throw new TypeError("Persisted memory apply history is impossible");previousBirth=birth}}}function validateContract(value){if(!isRecord(value)||!hasOnlyKeys(value,["key","version"])||!isNonBlankString(value.key)||!Number.isSafeInteger(value.version)||value.version<1)throw new TypeError("Invalid memory content contract")}__name(validateContract,"validateContract");function dataOptions(value,allowed){if(!isRecord(value)||Object.getPrototypeOf(value)!==Object.prototype&&Object.getPrototypeOf(value)!==null)throw new TypeError("Memory options require an ordinary data record");const fields=Object.create(null);for(const key of Reflect.ownKeys(value)){const descriptor=Object.getOwnPropertyDescriptor(value,key);if(typeof key!="string"||!allowed.includes(key)||descriptor===void 0||!descriptor.enumerable||!Object.hasOwn(descriptor,"value"))throw new TypeError("Invalid memory options");fields[key]=descriptor.value}return fields}__name(dataOptions,"dataOptions");function applyKey(request){return JSON.stringify([request.operation.contextId,request.command.position])}__name(applyKey,"applyKey");export{Memories};
package/dist/memory.d.ts CHANGED
@@ -1,23 +1,32 @@
1
- import type { MemoryContent } from '@weiliang0121/capix-protocol';
2
- import type { Transcript } from '@weiliang0121/capix-transcript';
3
- import type { MemoryState } from './persistence.js';
4
- export declare class Memory {
5
- readonly transcript: Transcript;
6
- constructor(transcript: Transcript, content: MemoryContent);
7
- get id(): string;
8
- get topic(): string;
9
- get content(): string;
10
- get sourceIndexes(): readonly number[];
11
- get sourceResults(): readonly import("@weiliang0121/capix-protocol").MemoryResultSource[] | undefined;
12
- snapshot(): MemoryContent;
13
- toJSON(): {
14
- topic: string;
15
- content: string;
16
- sourceIndexes: readonly number[];
17
- sourceResults?: readonly import("@weiliang0121/capix-protocol").MemoryResultSource[];
18
- id: string;
19
- };
20
- static prepare(transcript: Transcript, content: MemoryContent): MemoryContent;
1
+ import type { Stable } from '@weiliang0121/capix-contracts';
2
+ export interface MemoryContent<Metadata = never> {
3
+ readonly content: string;
4
+ readonly metadata?: Metadata;
5
+ }
6
+ export interface MemoryItem<Metadata = never> {
7
+ readonly id: string;
8
+ readonly value: MemoryContent<Metadata>;
9
+ }
10
+ export interface MemoryBasis {
11
+ readonly memoriesId: string;
12
+ readonly sequence: number;
13
+ }
14
+ export type MemoryRead<Metadata = never> = Stable<MemoryItem<Metadata> & {
15
+ readonly basis: MemoryBasis;
16
+ }>;
17
+ export interface MemorySnapshot<Metadata = never> {
18
+ readonly basis: MemoryBasis;
19
+ readonly items: readonly MemoryItem<Metadata>[];
20
+ }
21
+ export interface MemoryApplication<Metadata = never> {
22
+ readonly before: MemoryBasis;
23
+ readonly after: MemoryBasis;
24
+ readonly changed: readonly MemoryItem<Metadata>[];
25
+ }
26
+ export interface MemoryContentContractIdentity {
27
+ readonly key: string;
28
+ readonly version: number;
29
+ }
30
+ export interface MemoryContentContract<Metadata = never> extends MemoryContentContractIdentity {
31
+ readonly check?: (value: Stable<MemoryContent<Metadata>>) => void;
21
32
  }
22
- export declare function updateMemory(memory: Memory, content: MemoryContent): void;
23
- export declare function restoreMemory(transcript: Transcript, state: MemoryState): Memory;
package/dist/memory.js CHANGED
@@ -1,2 +1 @@
1
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{isNonBlankString,isNonEmptyArray,hasUniqueItems,isObject}from"./validation.js";import{prepareMemoryResultSources}from"./source.js";const contents=new WeakMap;class Memory{static{__name(this,"Memory")}transcript;constructor(transcript,content){this.transcript=transcript,contents.set(this,{id:randomUUID(),content:Memory.prepare(transcript,content)}),Object.freeze(this)}get id(){return contents.get(this).id}get topic(){return contents.get(this).content.topic}get content(){return contents.get(this).content.content}get sourceIndexes(){return contents.get(this).content.sourceIndexes}get sourceResults(){return contents.get(this).content.sourceResults}snapshot(){return contents.get(this).content}toJSON(){return{id:this.id,...contents.get(this).content}}static prepare(transcript,content){if(!isObject(content))throw new TypeError("Memory requires topic, content and unique source indexes");const topic=content.topic,text=content.content,evidence=content.sourceIndexes;if(!isNonBlankString(topic)||!isNonBlankString(text)||!isNonEmptyArray(evidence))throw new TypeError("Memory requires topic, content and unique source indexes");const sourceIndexes=Object.freeze([...evidence]);if(!isNonEmptyArray(sourceIndexes)||!hasUniqueItems(sourceIndexes))throw new TypeError("Memory requires topic, content and unique source indexes");for(const index of sourceIndexes)transcript.ref(index);const sourceResults=prepareMemoryResultSources(transcript,content.sourceResults);return Object.freeze({topic,content:text,sourceIndexes,...sourceResults?{sourceResults}:{}})}}function updateMemory(memory,content){contents.get(memory).content=content}__name(updateMemory,"updateMemory");function restoreMemory(transcript,state){if(!isObject(state)||!isNonBlankString(state.id))throw new TypeError("Invalid persisted memory identity");const memory=new Memory(transcript,state);return contents.set(memory,{id:state.id,content:memory.snapshot()}),memory}__name(restoreMemory,"restoreMemory");export{Memory,restoreMemory,updateMemory};
@@ -1,23 +1,22 @@
1
- import type { MemoryContent } from '@weiliang0121/capix-protocol';
1
+ import type { Stable } from '@weiliang0121/capix-contracts';
2
2
  import type { MemoryApplyState } from './apply.js';
3
- export interface MemoryState extends MemoryContent {
3
+ import type { MemoryContentContractIdentity, MemoryItem } from './memory.js';
4
+ export interface MemoriesState<Metadata = never> {
5
+ readonly version: 2;
4
6
  readonly id: string;
5
- }
6
- export interface MemoriesState {
7
- readonly id: string;
8
- readonly transcriptId: string;
7
+ readonly contentContract: MemoryContentContractIdentity;
9
8
  readonly sequence: number;
10
- readonly items: readonly MemoryState[];
11
- readonly applies?: readonly MemoryApplyState[];
9
+ readonly items: readonly MemoryItem<Metadata>[];
10
+ readonly applies?: readonly MemoryApplyState<Metadata>[];
12
11
  }
13
- export interface MemoriesCommit {
12
+ export interface MemoriesCommit<Metadata = never> {
14
13
  readonly memoriesId: string;
15
- readonly transcriptId: string;
14
+ readonly contentContract: MemoryContentContractIdentity;
16
15
  readonly expectedSequence: number;
17
16
  readonly sequence: number;
18
- readonly upserts: readonly MemoryState[];
19
- readonly fixed?: MemoryApplyState;
17
+ readonly upserts: readonly MemoryItem<Metadata>[];
18
+ readonly fixed?: MemoryApplyState<Metadata>;
20
19
  }
21
- export interface MemoriesPersistence {
22
- commit(change: MemoriesCommit): undefined;
20
+ export interface MemoriesPersistence<Metadata = never> {
21
+ commit(change: Stable<MemoriesCommit<Metadata>>): undefined;
23
22
  }
@@ -1,36 +1,59 @@
1
+ import { type Stable } from '@weiliang0121/capix-contracts';
1
2
  import { ResourceOffer } from '@weiliang0121/capix-resources';
2
3
  import type { Memories } from './memories.js';
3
- import type { MemoryChange } from './change.js';
4
- import type { MemoryState } from './persistence.js';
5
- type MemorySnapshot = ReturnType<Memories['snapshot']>;
6
- export interface CapturedMemories {
7
- readonly memoriesId: string;
8
- readonly sequence: number;
9
- readonly items: Readonly<MemorySnapshot>;
10
- }
11
- export interface MemoryBasis {
12
- readonly memoriesId: string;
13
- readonly transcriptId: string;
14
- readonly sequence: number;
15
- }
16
- export interface MemoryApplication extends MemoryBasis {
17
- readonly startSequence: number;
18
- readonly items: readonly Readonly<MemoryState>[];
19
- }
20
- export declare const MemoryResource: Readonly<{
21
- snapshot: ResourceOffer<Memories | CapturedMemories, readonly {
22
- topic: string;
23
- content: string;
24
- sourceIndexes: readonly number[];
25
- sourceResults?: readonly import("@weiliang0121/capix-protocol").MemoryResultSource[];
26
- id: string;
27
- }[], CapturedMemories, never>;
28
- apply: ResourceOffer<Memories, Readonly<{
29
- apply(changes: readonly MemoryChange[], expected?: MemoryBasis): MemoryApplication;
4
+ import type { MemoryMutation } from './change.js';
5
+ import type { MemoryBasis } from './memory.js';
6
+ export declare function memoryResources<Metadata = never>(): Readonly<{
7
+ read: ResourceOffer<Memories<Metadata>, Readonly<{
8
+ read(id: string): {
9
+ readonly id: string;
10
+ readonly value: {
11
+ readonly content: string;
12
+ readonly metadata?: Stable<Metadata> | undefined;
13
+ };
14
+ readonly basis: {
15
+ readonly memoriesId: string;
16
+ readonly sequence: number;
17
+ };
18
+ } | undefined;
19
+ snapshot(): {
20
+ readonly basis: {
21
+ readonly memoriesId: string;
22
+ readonly sequence: number;
23
+ };
24
+ readonly items: import("@weiliang0121/capix-contracts").StableArray<{
25
+ readonly id: string;
26
+ readonly value: {
27
+ readonly content: string;
28
+ readonly metadata?: Stable<Metadata> | undefined;
29
+ };
30
+ }>;
31
+ };
32
+ }>, Memories<Metadata>, never>;
33
+ apply: ResourceOffer<Memories<Metadata>, Readonly<{
34
+ apply(changes: readonly MemoryMutation<Metadata>[] | Stable<readonly MemoryMutation<Metadata>[]>, expected?: MemoryBasis): {
35
+ readonly before: {
36
+ readonly memoriesId: string;
37
+ readonly sequence: number;
38
+ };
39
+ readonly after: {
40
+ readonly memoriesId: string;
41
+ readonly sequence: number;
42
+ };
43
+ readonly changed: import("@weiliang0121/capix-contracts").StableArray<{
44
+ readonly id: string;
45
+ readonly value: {
46
+ readonly content: string;
47
+ readonly metadata?: Stable<Metadata> | undefined;
48
+ };
49
+ }>;
50
+ };
30
51
  applyFixed(input: {
31
52
  readonly position: string;
32
- readonly changes: readonly MemoryChange[];
33
- }, expected?: MemoryBasis): import("./apply.js").MemoryApplyReceipt;
34
- }>, Memories, never>;
53
+ readonly changes: readonly MemoryMutation<Metadata>[];
54
+ } | Stable<{
55
+ readonly position: string;
56
+ readonly changes: readonly MemoryMutation<Metadata>[];
57
+ }>, expected?: MemoryBasis): unknown;
58
+ }>, Memories<Metadata>, never>;
35
59
  }>;
36
- export {};
package/dist/resource.js CHANGED
@@ -1,2 +1,2 @@
1
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{ResourceOffer}from"@weiliang0121/capix-resources";import{RuntimeError}from"@weiliang0121/capix-contracts";import{isNonBlankString,isObject}from"./validation.js";function checkBasis(memories,expected,sequence){if(!isObject(expected))throw new TypeError("Invalid memory application basis");if(!isNonBlankString(expected.memoriesId)||!isNonBlankString(expected.transcriptId)||!Number.isSafeInteger(expected.sequence)||expected.sequence<0)throw new TypeError("Invalid memory application basis");if(expected.memoriesId!==memories.id||expected.transcriptId!==memories.transcript.id||expected.sequence!==sequence)throw new RuntimeError("MEMORY_BASIS_CONFLICT","Memory application basis does not match the fixed collection version")}__name(checkBasis,"checkBasis");const MemoryResource=Object.freeze({snapshot:new ResourceOffer({name:"memories.snapshot",expose:__name(captured=>captured.items,"expose"),timing:"binding",capture:__name(memories=>"items"in memories?memories:{memoriesId:memories.id,sequence:memories.sequence,items:Object.freeze(memories.snapshot().map(memory=>Object.freeze(memory)))},"capture"),describe:__name(captured=>({memoriesId:captured.memoriesId,sequence:captured.sequence,memoryIds:captured.items.map(memory=>memory.id)}),"describe")}),apply:new ResourceOffer({name:"memories.apply",expose:__name((memories,activity)=>Object.freeze({apply(changes,expected){return activity.runSync("apply",{input:__name(()=>({count:changes.length}),"input"),output:__name(result=>({memoriesId:result.memoriesId,startSequence:result.startSequence,endSequence:result.sequence,memoryIds:result.items.map(memory=>memory.id)}),"output")},()=>{expected!==void 0&&checkBasis(memories,expected,memories.sequence);const startSequence=memories.sequence,applied=memories.apply(changes,{origin:activity.origin,observation:activity.memoryObservation});return Object.freeze({memoriesId:memories.id,transcriptId:memories.transcript.id,startSequence,sequence:memories.sequence,items:Object.freeze(applied.map(memory=>Object.freeze(memory.toJSON())))})})},applyFixed(input,expected){return activity.runSync("applyFixed",{input:__name(()=>({position:input.position,count:input.changes.length}),"input"),output:__name(receipt=>receipt,"output")},()=>{if(!activity.origin)throw new Error("Fixed memory application requires a calling identity");const request={contract:{key:"memories.apply",version:1},operation:{contextId:activity.origin.contextId,position:input.position},memoriesId:memories.id,changes:input.changes},confirmed=memories.confirmFixed(request);return expected!==void 0&&checkBasis(memories,expected,confirmed?.startSequence??memories.sequence),confirmed??memories.applyFixed(request,{origin:activity.origin,observation:activity.memoryObservation})})}}),"expose"),timing:"access",describe:__name(memories=>({memoriesId:memories.id}),"describe")})});export{MemoryResource};
2
+ var __defProp=Object.defineProperty;var __name=(target,value)=>__defProp(target,"name",{value,configurable:!0});import{MemoryFacts}from"./event.js";import"@weiliang0121/capix-contracts";import{ResourceOffer}from"@weiliang0121/capix-resources";function memoryResources(){return Object.freeze({read:new ResourceOffer({name:"memories.read",expose:__name((memories,activity)=>Object.freeze({read(id){return activity.runSync("read",{input:__name(()=>({memoryId:id}),"input"),output:__name(result=>({memoriesId:memories.id,memoryId:id,found:result!==void 0}),"output")},()=>memories.read(id))},snapshot(){return activity.runSync("snapshot",{output:__name(result=>({memoriesId:result.basis.memoriesId,sequence:result.basis.sequence,count:result.items.length}),"output")},()=>memories.snapshot())}}),"expose"),timing:"access",describe:__name(memories=>({memoriesId:memories.id}),"describe")}),apply:new ResourceOffer({name:"memories.apply",expose:__name((memories,activity)=>Object.freeze({apply(changes,expected){return activity.runSync("apply",{output:__name(result=>({memoriesId:result.after.memoriesId,startSequence:result.before.sequence,endSequence:result.after.sequence,memoryIds:result.changed.map(item=>item.id)}),"output")},operation=>memories.apply(changes,{expected,origin:operation.origin,observation:operation.facts(MemoryFacts)}))},applyFixed(input,expected){return activity.runSync("applyFixed",{output:__name(receipt=>receipt,"output")},operation=>{if(!activity.origin)throw new Error("Fixed memory application requires a calling identity");const request={contract:{key:"memories.apply",version:2},contentContract:memories.contentContract,operation:{contextId:activity.origin.contextId},memoriesId:memories.id,command:input,...expected===void 0?{}:{expected}};return memories.applyFixed(request,{origin:operation.origin,observation:operation.facts(MemoryFacts)})})}}),"expose"),timing:"access",describe:__name(memories=>({memoriesId:memories.id}),"describe")})})}__name(memoryResources,"memoryResources");export{memoryResources};
@@ -1,4 +1,5 @@
1
+ export declare function isRecord(value: unknown): value is Record<string, unknown>;
1
2
  export declare function isNonBlankString(value: unknown): value is string;
2
- export declare function isNonEmptyArray(value: unknown): value is [unknown, ...unknown[]];
3
- export declare function hasUniqueItems(values: readonly unknown[]): boolean;
4
- export declare function isObject(value: unknown): value is Record<string, unknown>;
3
+ export declare function isUuid(value: unknown): value is string;
4
+ export declare function hasOnlyKeys(value: Record<string, unknown>, required: readonly string[], optional?: readonly string[]): boolean;
5
+ export declare function equalStable(left: unknown, right: unknown): boolean;
@@ -1,2 +1,2 @@
1
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{isString}from"es-toolkit/predicate";import{isArray}from"es-toolkit/compat";function isNonBlankString(value){return isString(value)&&/\S/.test(value)}__name(isNonBlankString,"isNonBlankString");function isNonEmptyArray(value){return isArray(value)&&value.length>0}__name(isNonEmptyArray,"isNonEmptyArray");function hasUniqueItems(values){return new Set(values).size===values.length}__name(hasUniqueItems,"hasUniqueItems");function isObject(value){return typeof value=="object"&&value!==null&&!isArray(value)}__name(isObject,"isObject");export{hasUniqueItems,isNonBlankString,isNonEmptyArray,isObject};
2
+ var __defProp=Object.defineProperty;var __name=(target,value)=>__defProp(target,"name",{value,configurable:!0});function isRecord(value){return value!==null&&typeof value=="object"&&!Array.isArray(value)}__name(isRecord,"isRecord");function isNonBlankString(value){return typeof value=="string"&&/\S/u.test(value)}__name(isNonBlankString,"isNonBlankString");function isUuid(value){return typeof value=="string"&&/^[\da-f]{8}-[\da-f]{4}-[1-8][\da-f]{3}-[89ab][\da-f]{3}-[\da-f]{12}$/iu.test(value)}__name(isUuid,"isUuid");function hasOnlyKeys(value,required,optional=[]){return required.every(key=>Object.hasOwn(value,key))&&Object.keys(value).every(key=>required.includes(key)||optional.includes(key))}__name(hasOnlyKeys,"hasOnlyKeys");function equalStable(left,right){if(Object.is(left,right))return!0;if(left===null||right===null||typeof left!="object"||typeof right!="object"||Object.getPrototypeOf(left)!==Object.getPrototypeOf(right))return!1;if(Array.isArray(left))return!Array.isArray(right)||left.length!==right.length?!1:left.every((value,index)=>equalStable(value,right[index]));const keys=Object.keys(left);return keys.length!==Object.keys(right).length?!1:keys.every(key=>Object.hasOwn(right,key)&&equalStable(left[key],right[key]))}__name(equalStable,"equalStable");export{equalStable,hasOnlyKeys,isNonBlankString,isRecord,isUuid};
package/dist/view.d.ts CHANGED
@@ -1,23 +1,22 @@
1
+ import { type Stable } from '@weiliang0121/capix-contracts';
1
2
  import type { SyncViewSource } from '@weiliang0121/capix-view';
2
3
  import type { Memories } from './memories.js';
3
- import type { MemoryState } from './persistence.js';
4
- export interface MemoryViewSummary {
5
- readonly topic: string;
6
- }
7
- type MemoryViewContract = SyncViewSource<string, MemoryViewSummary, MemoryState>;
8
- type SourceStamp = Parameters<MemoryViewContract['read']>[1];
9
- export declare class MemoryViewSource implements MemoryViewContract {
4
+ import type { MemoryItem } from './memory.js';
5
+ type MemoryViewContract<Metadata> = SyncViewSource<string, undefined, Stable<MemoryItem<Metadata>>>;
6
+ type SourceStamp = Parameters<MemoryViewContract<never>['read']>[1];
7
+ export declare class MemoryViewSource<Metadata = never> implements MemoryViewContract<Metadata> {
10
8
  #private;
11
9
  readonly id: string;
12
10
  readonly scope: string;
13
- constructor(memories: Memories, keys?: readonly string[]);
14
- index(): ReturnType<MemoryViewContract['index']>;
15
- inspect(keys: readonly string[]): ReturnType<MemoryViewContract['inspect']>;
16
- read(keys: readonly string[], expected: SourceStamp): ReturnType<MemoryViewContract['read']>;
11
+ constructor(memories: Memories<Metadata>, keys?: readonly string[]);
12
+ index(): ReturnType<MemoryViewContract<Metadata>['index']>;
13
+ inspect(keys: readonly string[]): ReturnType<MemoryViewContract<Metadata>['inspect']>;
14
+ read(keys: readonly string[], expected: SourceStamp): ReturnType<MemoryViewContract<Metadata>['read']>;
17
15
  check(expected: SourceStamp): boolean;
18
16
  private stamp;
19
17
  private requireCurrent;
20
18
  private acceptKeys;
19
+ private summary;
21
20
  private requireMemory;
22
21
  }
23
22
  export {};
package/dist/view.js CHANGED
@@ -1,2 +1,2 @@
1
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{RuntimeError}from"@weiliang0121/capix-contracts";class MemoryViewSource{static{__name(this,"MemoryViewSource")}id;scope;#memories;#keys;#allowed;constructor(memories,keys){if(this.#memories=memories,this.id=memories.id,keys!==void 0){this.#keys=this.acceptKeys(keys);for(const key of this.#keys)this.requireMemory(key);this.#allowed=new Set(this.#keys)}this.scope=this.#keys===void 0?"whole":`keys:${JSON.stringify(this.#keys)}`,Object.freeze(this)}index(){const stamp=this.stamp(),keys=this.#keys??this.#memories.values().map(memory=>memory.id),entries=Object.freeze(keys.map(key=>Object.freeze({key,summary:Object.freeze({topic:this.requireMemory(key).topic})})));return this.requireCurrent(stamp),Object.freeze({stamp,entries})}inspect(keys){const stamp=this.stamp(),selected=this.acceptKeys(keys),entries=Object.freeze(selected.map(key=>{if(this.#allowed&&!this.#allowed.has(key))throw new RangeError("Memory is outside the view source scope");return Object.freeze({key,summary:Object.freeze({topic:this.requireMemory(key).topic})})}));return this.requireCurrent(stamp),Object.freeze({stamp,entries})}read(keys,expected){const basis=Object.freeze({id:expected.id,scope:expected.scope,version:expected.version});this.requireCurrent(basis);const selected=this.acceptKeys(keys),entries=Object.freeze(selected.map(key=>{if(this.#allowed&&!this.#allowed.has(key))throw new RangeError("Memory is outside the view source scope");return Object.freeze({key,value:Object.freeze(this.requireMemory(key).toJSON())})}));return this.requireCurrent(basis),Object.freeze({stamp:this.stamp(),entries})}check(expected){return expected?.id===this.id&&expected.scope===this.scope&&expected.version===String(this.#memories.sequence)}stamp(){return Object.freeze({id:this.id,scope:this.scope,version:String(this.#memories.sequence)})}requireCurrent(expected){if(!this.check(expected))throw new RuntimeError("VIEW_SOURCE_CONFLICT","Memory view source has changed")}acceptKeys(keys){if(!Array.isArray(keys))throw new TypeError("Memory view keys must be an array");const accepted=Array.from(keys);if(accepted.some(key=>typeof key!="string"||!key)||new Set(accepted).size!==accepted.length)throw new TypeError("Memory view requires unique nonempty string keys");return Object.freeze(accepted)}requireMemory(key){const memory=this.#memories.get(key);if(!memory)throw new RangeError("Memory view member does not exist");return memory}}export{MemoryViewSource};
2
+ var __defProp=Object.defineProperty;var __name=(target,value)=>__defProp(target,"name",{value,configurable:!0});import{RuntimeError}from"@weiliang0121/capix-contracts";class MemoryViewSource{static{__name(this,"MemoryViewSource")}id;scope;#memories;#keys;#allowed;constructor(memories,keys){if(this.#memories=memories,this.id=memories.id,keys!==void 0){this.#keys=this.acceptKeys(keys);for(const key of this.#keys)this.requireMemory(key);this.#allowed=new Set(this.#keys)}this.scope=this.#keys===void 0?"whole":`keys:${JSON.stringify(this.#keys)}`,Object.freeze(this)}index(){const stamp=this.stamp(),keys=this.#keys??this.#memories.values().map(memory=>memory.id),entries=Object.freeze(keys.map(key=>Object.freeze({key,summary:this.summary(key)})));return this.requireCurrent(stamp),Object.freeze({stamp,entries})}inspect(keys){const stamp=this.stamp(),selected=this.acceptKeys(keys),entries=Object.freeze(selected.map(key=>{if(this.#allowed&&!this.#allowed.has(key))throw new RangeError("Memory is outside the view source scope");return Object.freeze({key,summary:this.summary(key)})}));return this.requireCurrent(stamp),Object.freeze({stamp,entries})}read(keys,expected){const basis=Object.freeze({id:expected.id,scope:expected.scope,version:expected.version});this.requireCurrent(basis);const selected=this.acceptKeys(keys),entries=Object.freeze(selected.map(key=>{if(this.#allowed&&!this.#allowed.has(key))throw new RangeError("Memory is outside the view source scope");return Object.freeze({key,value:this.requireMemory(key)})}));return this.requireCurrent(basis),Object.freeze({stamp:this.stamp(),entries})}check(expected){return expected?.id===this.id&&expected.scope===this.scope&&expected.version===String(this.#memories.sequence)}stamp(){return Object.freeze({id:this.id,scope:this.scope,version:String(this.#memories.sequence)})}requireCurrent(expected){if(!this.check(expected))throw new RuntimeError("VIEW_SOURCE_CONFLICT","Memory view source has changed")}acceptKeys(keys){if(!Array.isArray(keys))throw new TypeError("Memory view keys must be an array");const accepted=Array.from(keys);if(accepted.some(key=>typeof key!="string"||!key)||new Set(accepted).size!==accepted.length)throw new TypeError("Memory view requires unique nonempty string keys");return Object.freeze(accepted)}summary(key){this.requireMemory(key)}requireMemory(key){const memory=this.#memories.read(key);if(!memory)throw new RangeError("Memory view member does not exist");return Object.freeze({id:memory.id,value:memory.value})}}export{MemoryViewSource};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weiliang0121/capix-memory",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "exports": {
@@ -10,14 +10,11 @@
10
10
  }
11
11
  },
12
12
  "dependencies": {
13
- "es-toolkit": "1.52.0",
14
- "@weiliang0121/capix-transcript": "0.1.0",
15
- "@weiliang0121/capix-changes": "0.1.0",
16
- "@weiliang0121/capix-resources": "0.1.0",
17
- "@weiliang0121/capix-view": "0.1.0",
18
- "@weiliang0121/capix-protocol": "0.1.0",
19
- "@weiliang0121/capix-contracts": "0.1.0",
20
- "@weiliang0121/capix-diagnostics": "0.1.0"
13
+ "@weiliang0121/capix-contracts": "0.2.0",
14
+ "@weiliang0121/capix-diagnostics": "0.2.0",
15
+ "@weiliang0121/capix-changes": "0.2.0",
16
+ "@weiliang0121/capix-resources": "0.2.0",
17
+ "@weiliang0121/capix-view": "0.2.0"
21
18
  },
22
19
  "description": "Versioned memory entities and controlled resource access for CAPiX.",
23
20
  "license": "MIT",
@@ -25,7 +22,7 @@
25
22
  "repository": {
26
23
  "type": "git",
27
24
  "url": "git+https://github.com/weiliang0121/capix.git",
28
- "directory": "server/memory"
25
+ "directory": "packages/memory"
29
26
  },
30
27
  "engines": {
31
28
  "node": ">=22.23.2"
@@ -44,6 +41,7 @@
44
41
  },
45
42
  "scripts": {
46
43
  "build": "tsc -p tsconfig.json",
47
- "typecheck": "tsc -p tsconfig.json --noEmit"
44
+ "typecheck": "tsc -p tsconfig.json --noEmit",
45
+ "test": "node ../../scripts/test/run.mjs --package ."
48
46
  }
49
47
  }
package/dist/source.d.ts DELETED
@@ -1,3 +0,0 @@
1
- import type { MemoryResultSource } from '@weiliang0121/capix-protocol';
2
- import type { Transcript } from '@weiliang0121/capix-transcript';
3
- export declare function prepareMemoryResultSources(transcript: Transcript, sources: readonly MemoryResultSource[] | undefined): readonly MemoryResultSource[] | undefined;
package/dist/source.js DELETED
@@ -1,2 +0,0 @@
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{isPlainObject}from"es-toolkit/predicate";import{isArray}from"es-toolkit/compat";import{isNonBlankString}from"./validation.js";function prepareMemoryResultSources(transcript,sources){if(sources===void 0)return;if(!isArray(sources))throw new TypeError("Memory result sources must be an array");const ids=new Set,prepared=Array.from(sources,source=>{if(!isPlainObject(source))throw new TypeError("Memory result source requires a known kind and unique identity");const id=source.id,kind=source.kind,answerIndex=source.answerIndex;if(kind!=="file-created"||!isNonBlankString(id)||ids.has(id))throw new TypeError("Memory result source requires a known kind and unique identity");if(ids.add(id),transcript.ref(answerIndex).read().role!=="assistant")throw new TypeError("Memory result source must locate an assistant answer");const receipt=source.receipt;if(!isPlainObject(receipt))throw new TypeError("Memory result source requires a valid file creation receipt");const path=receipt.path,bytes=receipt.bytes,digest=receipt.digest;if(!isNonBlankString(path)||!Number.isSafeInteger(bytes)||bytes<0||typeof digest!="string"||!/^[a-f0-9]{64}$/u.test(digest))throw new TypeError("Memory result source requires a valid file creation receipt");return Object.freeze({id,kind,answerIndex,receipt:Object.freeze({path,bytes,digest})})});return prepared.length?Object.freeze(prepared):void 0}__name(prepareMemoryResultSources,"prepareMemoryResultSources");export{prepareMemoryResultSources};