@weiliang0121/capix-memory 0.0.0-stage → 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/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,71 @@
1
- # Temporary Holding Version
1
+ # @weiliang0121/capix-memory
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Independent, revisable summaries and controlled resource access for CAPiX. A collection does not require a Transcript, topic, message sources, or file receipts.
4
+
5
+ ES modules and TypeScript declarations. Requires Node.js 22.23.2 or newer.
6
+
7
+ ```sh
8
+ npm install @weiliang0121/capix-memory
9
+ ```
10
+
11
+ ## Public entry points
12
+
13
+ - `@weiliang0121/capix-memory`
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
+ ## 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
+
69
+ ## License
70
+
71
+ MIT. Copyright (c) 2026 wei.liang. See [LICENSE](LICENSE).
@@ -0,0 +1,31 @@
1
+ import type { MemoryMutation } from './change.js';
2
+ import type { MemoryBasis, MemoryContentContractIdentity } from './memory.js';
3
+ export interface MemoryFixedApply<Metadata = never> {
4
+ readonly contract: {
5
+ readonly key: 'memories.apply';
6
+ readonly version: 2;
7
+ };
8
+ readonly contentContract: MemoryContentContractIdentity;
9
+ readonly operation: {
10
+ readonly contextId: string;
11
+ };
12
+ readonly memoriesId: string;
13
+ readonly command: {
14
+ readonly position: string;
15
+ readonly changes: readonly MemoryMutation<Metadata>[];
16
+ };
17
+ readonly expected?: MemoryBasis;
18
+ }
19
+ export interface MemoryApplyReceipt {
20
+ readonly memoriesId: string;
21
+ readonly startSequence: number;
22
+ readonly sequence: number;
23
+ readonly changes: readonly {
24
+ readonly commandIndex: number;
25
+ readonly memoryId: string;
26
+ }[];
27
+ }
28
+ export interface MemoryApplyState<Metadata = never> {
29
+ readonly request: MemoryFixedApply<Metadata>;
30
+ readonly receipt: MemoryApplyReceipt;
31
+ }
package/dist/apply.js ADDED
@@ -0,0 +1 @@
1
+ /*! MIT License | Copyright (c) 2026 wei.liang | See LICENSE */
@@ -0,0 +1,10 @@
1
+ import type { MemoryContent } from './memory.js';
2
+ export type MemoryMutation<Metadata = never> = ({
3
+ readonly action: 'create';
4
+ } | {
5
+ readonly action: 'update';
6
+ readonly memoryId: string;
7
+ }) & {
8
+ readonly value: MemoryContent<Metadata>;
9
+ readonly reason?: string;
10
+ };
package/dist/change.js ADDED
@@ -0,0 +1 @@
1
+ /*! MIT License | Copyright (c) 2026 wei.liang | See LICENSE */
@@ -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};
@@ -0,0 +1,8 @@
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
+ export type { MemoryFixedApply, MemoryApplyReceipt, MemoryApplyState } from './apply.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 ADDED
@@ -0,0 +1,2 @@
1
+ /*! MIT License | Copyright (c) 2026 wei.liang | See LICENSE */
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};
@@ -0,0 +1,44 @@
1
+ import { type Stable } from '@weiliang0121/capix-contracts';
2
+ import { type ChangeProvenance, type EventConsumer, type ChangeCapture } from '@weiliang0121/capix-changes';
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
+ import type { MemoriesPersistence, MemoriesState } from './persistence.js';
7
+ import type { MemoryFixedApply, MemoryApplyReceipt } from './apply.js';
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>;
13
+ }
14
+ export interface MemoryApplyOptions<Metadata = never> extends ChangeProvenance<MemoryEvent<Metadata>> {
15
+ readonly expected?: MemoryBasis;
16
+ }
17
+ export declare class Memories<Metadata = never> {
18
+ #private;
19
+ constructor(options?: MemoriesOptions<Metadata>);
20
+ get id(): string;
21
+ get contentContract(): Stable<MemoryContentContractIdentity>;
22
+ get sequence(): number;
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;
37
+ private acceptFixed;
38
+ private confirmAccepted;
39
+ private acceptProvenance;
40
+ private applyPrepared;
41
+ private commit;
42
+ private acceptReceipt;
43
+ private validateTimeline;
44
+ }
@@ -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,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};
@@ -0,0 +1,32 @@
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;
32
+ }
package/dist/memory.js ADDED
@@ -0,0 +1 @@
1
+ /*! MIT License | Copyright (c) 2026 wei.liang | See LICENSE */
@@ -0,0 +1,22 @@
1
+ import type { Stable } from '@weiliang0121/capix-contracts';
2
+ import type { MemoryApplyState } from './apply.js';
3
+ import type { MemoryContentContractIdentity, MemoryItem } from './memory.js';
4
+ export interface MemoriesState<Metadata = never> {
5
+ readonly version: 2;
6
+ readonly id: string;
7
+ readonly contentContract: MemoryContentContractIdentity;
8
+ readonly sequence: number;
9
+ readonly items: readonly MemoryItem<Metadata>[];
10
+ readonly applies?: readonly MemoryApplyState<Metadata>[];
11
+ }
12
+ export interface MemoriesCommit<Metadata = never> {
13
+ readonly memoriesId: string;
14
+ readonly contentContract: MemoryContentContractIdentity;
15
+ readonly expectedSequence: number;
16
+ readonly sequence: number;
17
+ readonly upserts: readonly MemoryItem<Metadata>[];
18
+ readonly fixed?: MemoryApplyState<Metadata>;
19
+ }
20
+ export interface MemoriesPersistence<Metadata = never> {
21
+ commit(change: Stable<MemoriesCommit<Metadata>>): undefined;
22
+ }
@@ -0,0 +1 @@
1
+ /*! MIT License | Copyright (c) 2026 wei.liang | See LICENSE */
@@ -0,0 +1,59 @@
1
+ import { type Stable } from '@weiliang0121/capix-contracts';
2
+ import { ResourceOffer } from '@weiliang0121/capix-resources';
3
+ import type { Memories } from './memories.js';
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
+ };
51
+ applyFixed(input: {
52
+ readonly position: string;
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>;
59
+ }>;
@@ -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{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};
@@ -0,0 +1,5 @@
1
+ export declare function isRecord(value: unknown): value is Record<string, unknown>;
2
+ export declare function isNonBlankString(value: unknown): value is string;
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;
@@ -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 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 ADDED
@@ -0,0 +1,22 @@
1
+ import { type Stable } from '@weiliang0121/capix-contracts';
2
+ import type { SyncViewSource } from '@weiliang0121/capix-view';
3
+ import type { Memories } from './memories.js';
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> {
8
+ #private;
9
+ readonly id: string;
10
+ readonly scope: string;
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']>;
15
+ check(expected: SourceStamp): boolean;
16
+ private stamp;
17
+ private requireCurrent;
18
+ private acceptKeys;
19
+ private summary;
20
+ private requireMemory;
21
+ }
22
+ export {};
package/dist/view.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{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,47 @@
1
1
  {
2
2
  "name": "@weiliang0121/capix-memory",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.2.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
+ "@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"
18
+ },
19
+ "description": "Versioned memory entities and controlled resource access for CAPiX.",
20
+ "license": "MIT",
21
+ "author": "wei.liang (https://github.com/weiliang0121)",
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git+https://github.com/weiliang0121/capix.git",
25
+ "directory": "packages/memory"
26
+ },
27
+ "engines": {
28
+ "node": ">=22.23.2"
29
+ },
30
+ "main": "./dist/index.js",
31
+ "types": "./dist/index.d.ts",
32
+ "files": [
33
+ "dist/**/*.js",
34
+ "dist/**/*.d.ts",
35
+ "LICENSE",
36
+ "README.md"
37
+ ],
38
+ "publishConfig": {
39
+ "access": "public",
40
+ "registry": "https://registry.npmjs.org"
41
+ },
42
+ "scripts": {
43
+ "build": "tsc -p tsconfig.json",
44
+ "typecheck": "tsc -p tsconfig.json --noEmit",
45
+ "test": "node ../../scripts/test/run.mjs --package ."
46
+ }
6
47
  }