@ternent/concord 0.2.2

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 ADDED
@@ -0,0 +1,46 @@
1
+ # `@ternent/concord`
2
+
3
+ Command-driven developer runtime for building non-custodial apps on top of `@ternent/ledger`.
4
+
5
+ ```ts
6
+ import { createConcordApp } from "@ternent/concord";
7
+
8
+ const app = await createConcordApp({
9
+ identity,
10
+ storage,
11
+ plugins: [createTodoPlugin()]
12
+ });
13
+
14
+ await app.load();
15
+
16
+ await app.command("todo.create-item", {
17
+ id: crypto.randomUUID(),
18
+ title: "Buy milk"
19
+ });
20
+
21
+ await app.command("todo.rename-item", {
22
+ id: "todo_123",
23
+ title: "Buy oat milk"
24
+ });
25
+
26
+ await app.commit({
27
+ metadata: {
28
+ message: "Create and refine first todo"
29
+ }
30
+ });
31
+
32
+ const todoState = app.getPluginState("todo");
33
+ ```
34
+
35
+ Concord keeps Ledger as the truth engine. Plugins own command ergonomics and projection logic, while commits remain explicit authored history boundaries.
36
+
37
+ Concord treats committed history as atomic truth. If any committed byte in reachable history is invalid, the document is globally invalid for normal runtime use, even though verification still reports the precise broken commits or entries.
38
+
39
+ The core lifecycle is:
40
+
41
+ 1. `command(...)` stages one or more entries
42
+ 2. local replay reflects committed truth plus staged truth
43
+ 3. `commit(...)` groups staged entries into a signed commit with contextual metadata
44
+ 4. replay rebuilds app state from that history
45
+
46
+ Entries carry domain meaning. Signed commits carry chain integrity.
package/SPEC.md ADDED
@@ -0,0 +1,189 @@
1
+ # `@ternent/concord` Specification
2
+
3
+ ## 1. Purpose
4
+
5
+ `@ternent/concord` is the developer-facing runtime for building non-custodial applications on top of `@ternent/ledger`.
6
+
7
+ Concord is command-first, but it is not auto-commit-first.
8
+
9
+ It exists to make this normal:
10
+
11
+ ```ts
12
+ const app = await createConcordApp({
13
+ identity,
14
+ storage,
15
+ plugins: [createTodoPlugin()],
16
+ });
17
+
18
+ await app.load();
19
+
20
+ await app.command("todo.create-item", {
21
+ id: crypto.randomUUID(),
22
+ title: "Buy milk",
23
+ });
24
+
25
+ await app.command("todo.rename-item", {
26
+ id: "todo_123",
27
+ title: "Buy oat milk",
28
+ });
29
+
30
+ await app.commit({
31
+ metadata: {
32
+ message: "Create and refine first todo",
33
+ },
34
+ });
35
+ ```
36
+
37
+ Commands stage domain meaning. Commits author signed history boundaries.
38
+
39
+ ## 2. Layering
40
+
41
+ - `@ternent/concord-protocol` provides deterministic protocol primitives
42
+ - `@ternent/ledger` owns append-only truth, signed commits, replay, and verification
43
+ - `@ternent/concord` owns command dispatch, plugin projection, and runtime composition
44
+ - framework adapters belong above Concord
45
+
46
+ Concord must never reimplement ledger truth mechanics.
47
+
48
+ ## 3. Core model
49
+
50
+ ### 3.1 Truth vs runtime state
51
+
52
+ - entries are units of meaning
53
+ - signed commits are the primary authored integrity boundary
54
+ - plugin state is derived from replay
55
+ - projection targets are derived from replay
56
+ - storage is persistence only
57
+
58
+ ### 3.2 Command and commit lifecycle
59
+
60
+ The normal Concord write lifecycle is:
61
+
62
+ 1. dispatch one or more commands
63
+ 2. stage one or more ledger entries
64
+ 3. replay committed truth plus staged truth into projected app state
65
+ 4. explicitly commit staged entries into a signed commit
66
+ 5. replay the updated committed history
67
+
68
+ Concord may support optional auto-commit hooks, but explicit commits are the default and primary model.
69
+
70
+ ## 4. Public API
71
+
72
+ ```ts
73
+ type ConcordApp = {
74
+ create(params?: ConcordCreateParams): Promise<void>;
75
+ load(): Promise<void>;
76
+
77
+ command<TInput = unknown>(
78
+ type: string,
79
+ input: TInput
80
+ ): Promise<ConcordCommandResult>;
81
+
82
+ commit(input?: ConcordCommitInput): Promise<ConcordCommitResult>;
83
+
84
+ replay(options?: ConcordReplayOptions): Promise<void>;
85
+ recompute(): Promise<void>;
86
+
87
+ verify(): Promise<LedgerVerificationResult>;
88
+
89
+ exportLedger(): Promise<LedgerContainer>;
90
+ importLedger(container: LedgerContainer): Promise<void>;
91
+
92
+ getState(): Readonly<ConcordState>;
93
+ getPluginState<T = unknown>(pluginId: string): T;
94
+
95
+ subscribe(listener: (state: Readonly<ConcordState>) => void): () => void;
96
+ destroy(): Promise<void>;
97
+ };
98
+ ```
99
+
100
+ ## 5. State model
101
+
102
+ ```ts
103
+ type ConcordState = {
104
+ ready: boolean;
105
+ integrityValid: boolean;
106
+ stagedCount: number;
107
+ plugins: Record<string, unknown>;
108
+ verification: LedgerVerificationResult | null;
109
+ };
110
+ ```
111
+
112
+ Rules:
113
+
114
+ - `plugins` is replay-derived app state
115
+ - `integrityValid` indicates whether the currently loaded committed history passed strict ledger verification
116
+ - `stagedCount` exposes local working-state depth without leaking raw ledger internals as the primary surface
117
+ - verification applies to the ledger artifact and current staged state, not to plugin state as authority
118
+ - `ready` means the app has a trustworthy projected runtime state available for normal use
119
+
120
+ ## 6. Plugin model
121
+
122
+ ```ts
123
+ type ConcordPlugin<PState = unknown> = {
124
+ id: string;
125
+ initialState(): PState;
126
+ commands?: Record<
127
+ string,
128
+ (
129
+ ctx: ConcordCommandContext,
130
+ input: unknown
131
+ ) => Promise<LedgerAppendInput | LedgerAppendInput[]> | LedgerAppendInput | LedgerAppendInput[]
132
+ >;
133
+ project(
134
+ state: PState,
135
+ entry: LedgerReplayEntry,
136
+ ctx: ConcordProjectionContext
137
+ ): PState;
138
+ selectors?: Record<string, (state: PState) => unknown>;
139
+ };
140
+ ```
141
+
142
+ Rules:
143
+
144
+ - commands return ledger append inputs
145
+ - commands stage entries; they do not implicitly define commit boundaries
146
+ - `project()` is a pure replay consumer over the plugin state slice and replay entry
147
+ - plugins define domain meaning, not truth mechanics
148
+
149
+ ## 7. Projection targets
150
+
151
+ Projection targets consume replay output after plugin state is rebuilt for the current entry.
152
+
153
+ They:
154
+
155
+ - consume replayed truth
156
+ - may materialize query stores
157
+ - do not author truth
158
+ - do not replace plugin projection
159
+
160
+ Projection target failures fail the Concord operation. They are not swallowed.
161
+
162
+ ## 8. Runtime rules
163
+
164
+ - Concord defaults to `autoCommit: false`
165
+ - `command()` stages entries and rebuilds local projected state from committed plus staged replay
166
+ - `commit()` creates a signed commit via Ledger and clears staged entries only after successful commit creation
167
+ - `verify()` validates the ledger artifact and current staged state; it does not itself replay
168
+ - `exportLedger()` exposes committed truth only
169
+ - staged truth remains visible through replayed app state until explicitly committed or cleared below Concord
170
+
171
+ Concord treats committed history as atomic truth.
172
+ If any committed byte in reachable history is invalid, Concord must not present projected state as trustworthy runtime state.
173
+
174
+ That means:
175
+
176
+ - `load()` and `importLedger()` may still succeed for diagnostic inspection
177
+ - invalid committed history forces `ready: false`
178
+ - invalid committed history forces `integrityValid: false`
179
+ - commands and commits are blocked until trustworthy runtime state exists again
180
+
181
+ ## 9. Acceptance criteria
182
+
183
+ Concord is correct when:
184
+
185
+ - multiple commands can stage multiple entries before one explicit commit
186
+ - staged state is visible through replay before commit
187
+ - committed history is advanced only by explicit commit unless auto-commit is intentionally configured
188
+ - exported ledgers show signed commit records as the authored integrity boundary
189
+ - docs and examples teach `command -> command -> commit`, not `command = commit`
@@ -0,0 +1,3 @@
1
+ import type { ConcordApp, CreateConcordAppInput } from "./types.js";
2
+ export declare function createConcordApp(input: CreateConcordAppInput): Promise<ConcordApp>;
3
+ //# sourceMappingURL=app.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["app.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EACV,UAAU,EAUV,qBAAqB,EACtB,MAAM,YAAY,CAAC;AA0GpB,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,qBAAqB,GAC3B,OAAO,CAAC,UAAU,CAAC,CA+arB"}
@@ -0,0 +1,5 @@
1
+ export declare class ConcordBoundaryError extends Error {
2
+ code: string;
3
+ constructor(code: string, message: string);
4
+ }
5
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["errors.ts"],"names":[],"mappings":"AAAA,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,IAAI,EAAE,MAAM,CAAC;gBAED,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAK1C"}
@@ -0,0 +1,4 @@
1
+ export { createConcordApp } from "./app.js";
2
+ export { ConcordBoundaryError } from "./errors.js";
3
+ export type { ConcordApp, ConcordCommandContext, ConcordCommitInput, ConcordCommitResult, ConcordCommandResult, ConcordCreateParams, ConcordIdentityContext, ConcordPlugin, ConcordProjectionContext, ConcordProjectionReplayContext, ConcordProjectionTarget, ConcordReplayAppView, ConcordReplayOptions, ConcordRuntimePolicy, ConcordState, CreateConcordAppInput } from "./types.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,YAAY,EACV,UAAU,EACV,qBAAqB,EACrB,kBAAkB,EAClB,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,EACnB,sBAAsB,EACtB,aAAa,EACb,wBAAwB,EACxB,8BAA8B,EAC9B,uBAAuB,EACvB,oBAAoB,EACpB,oBAAoB,EACpB,oBAAoB,EACpB,YAAY,EACZ,qBAAqB,EACtB,MAAM,YAAY,CAAC"}
@@ -0,0 +1,93 @@
1
+ import type { LedgerAppendInput, LedgerArmourContract, LedgerContainer, LedgerInstance, LedgerProtocolContract, LedgerReplayEntry, LedgerSealContract, LedgerStorageAdapter, LedgerVerificationResult } from '../../ledger-v2/src/index.ts';
2
+ export type ConcordCreateParams = {
3
+ metadata?: Record<string, unknown>;
4
+ };
5
+ export type ConcordReplayOptions = {
6
+ verify?: boolean;
7
+ decrypt?: boolean;
8
+ };
9
+ export type ConcordState = {
10
+ ready: boolean;
11
+ integrityValid: boolean;
12
+ stagedCount: number;
13
+ plugins: Record<string, unknown>;
14
+ verification: LedgerVerificationResult | null;
15
+ };
16
+ export type ConcordCommandResult = {
17
+ commitId?: string;
18
+ entryIds: string[];
19
+ stagedCount: number;
20
+ };
21
+ export type ConcordCommitInput = {
22
+ metadata?: Record<string, unknown>;
23
+ };
24
+ export type ConcordCommitResult = {
25
+ commitId: string;
26
+ entryIds: string[];
27
+ };
28
+ export type ConcordRuntimePolicy = {
29
+ autoCommit?: boolean;
30
+ };
31
+ export type ConcordIdentityContext = {
32
+ author: string;
33
+ signer?: unknown;
34
+ decryptor?: unknown;
35
+ };
36
+ export type ConcordCommandContext = {
37
+ now(): string;
38
+ identity: ConcordIdentityContext;
39
+ getPluginState<T = unknown>(pluginId: string): T;
40
+ };
41
+ export type ConcordProjectionContext = {
42
+ decryptAvailable: boolean;
43
+ };
44
+ export type ConcordPlugin<PState = unknown> = {
45
+ id: string;
46
+ initialState(): PState;
47
+ commands?: Record<string, (ctx: ConcordCommandContext, input: unknown) => Promise<LedgerAppendInput | LedgerAppendInput[]> | LedgerAppendInput | LedgerAppendInput[]>;
48
+ project(state: PState, entry: LedgerReplayEntry, ctx: ConcordProjectionContext): PState;
49
+ selectors?: Record<string, (state: PState) => unknown>;
50
+ };
51
+ export type ConcordReplayAppView = {
52
+ getState(): Readonly<ConcordState>;
53
+ getPluginState<T = unknown>(pluginId: string): T;
54
+ };
55
+ export type ConcordProjectionReplayContext = {
56
+ app: ConcordReplayAppView;
57
+ };
58
+ export type ConcordProjectionTarget = {
59
+ name: string;
60
+ reset(): Promise<void> | void;
61
+ beginReplay?(ctx: ConcordProjectionReplayContext): Promise<void> | void;
62
+ applyEntry(entry: LedgerReplayEntry, ctx: ConcordProjectionReplayContext): Promise<void> | void;
63
+ endReplay?(ctx: ConcordProjectionReplayContext): Promise<void> | void;
64
+ destroy?(): Promise<void> | void;
65
+ };
66
+ export type CreateConcordAppInput = {
67
+ identity: ConcordIdentityContext;
68
+ storage: LedgerStorageAdapter;
69
+ plugins: ConcordPlugin[];
70
+ now?: () => string;
71
+ protocol?: LedgerProtocolContract;
72
+ seal?: LedgerSealContract;
73
+ armour?: LedgerArmourContract;
74
+ ledger?: LedgerInstance<unknown>;
75
+ projectionTargets?: ConcordProjectionTarget[];
76
+ policy?: ConcordRuntimePolicy;
77
+ };
78
+ export type ConcordApp = {
79
+ create(params?: ConcordCreateParams): Promise<void>;
80
+ load(): Promise<void>;
81
+ command<TInput = unknown>(type: string, input: TInput): Promise<ConcordCommandResult>;
82
+ commit(input?: ConcordCommitInput): Promise<ConcordCommitResult>;
83
+ replay(options?: ConcordReplayOptions): Promise<void>;
84
+ recompute(): Promise<void>;
85
+ verify(): Promise<LedgerVerificationResult>;
86
+ exportLedger(): Promise<LedgerContainer>;
87
+ importLedger(container: LedgerContainer): Promise<void>;
88
+ getState(): Readonly<ConcordState>;
89
+ getPluginState<T = unknown>(pluginId: string): T;
90
+ subscribe(listener: (state: Readonly<ConcordState>) => void): () => void;
91
+ destroy(): Promise<void>;
92
+ };
93
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,430 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => {
4
+ __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
5
+ return value;
6
+ };
7
+ import { createLedger } from "@ternent/ledger";
8
+ class ConcordBoundaryError extends Error {
9
+ constructor(code, message) {
10
+ super(message);
11
+ __publicField(this, "code");
12
+ this.name = "ConcordBoundaryError";
13
+ this.code = code;
14
+ }
15
+ }
16
+ function createDefaultNow() {
17
+ return (/* @__PURE__ */ new Date()).toISOString();
18
+ }
19
+ function isObject(value) {
20
+ return typeof value === "object" && value !== null;
21
+ }
22
+ function isLedgerReplayEntry(value) {
23
+ if (!isObject(value)) {
24
+ return false;
25
+ }
26
+ if (typeof value.entryId !== "string" || typeof value.kind !== "string" || typeof value.author !== "string" || typeof value.authoredAt !== "string") {
27
+ return false;
28
+ }
29
+ if (!(value.meta === null || isObject(value.meta))) {
30
+ return false;
31
+ }
32
+ if (!isObject(value.payload) || typeof value.payload.type !== "string") {
33
+ return false;
34
+ }
35
+ return value.payload.type === "plain" || value.payload.type === "encrypted" || value.payload.type === "decrypted";
36
+ }
37
+ function assertReplayEntries(value) {
38
+ if (Array.isArray(value) && value.every(isLedgerReplayEntry)) {
39
+ return value;
40
+ }
41
+ throw new ConcordBoundaryError(
42
+ "INVALID_LEDGER_PROJECTION",
43
+ "Concord requires ledger replay output to be LedgerReplayEntry[]."
44
+ );
45
+ }
46
+ function createInitialPluginState(plugins) {
47
+ return Object.fromEntries(plugins.map((plugin) => [plugin.id, plugin.initialState()]));
48
+ }
49
+ function assertKnownPlugin(pluginsById, pluginId) {
50
+ if (!pluginsById.has(pluginId)) {
51
+ throw new ConcordBoundaryError(
52
+ "UNKNOWN_PLUGIN",
53
+ `Unknown Concord plugin: ${pluginId}`
54
+ );
55
+ }
56
+ }
57
+ function normalizeAppendInputs(value) {
58
+ const inputs = Array.isArray(value) ? value : [value];
59
+ if (inputs.length === 0) {
60
+ throw new ConcordBoundaryError(
61
+ "COMMAND_PRODUCED_NO_ENTRIES",
62
+ "Concord command handlers must return at least one LedgerAppendInput."
63
+ );
64
+ }
65
+ return inputs;
66
+ }
67
+ function assertInternalLedgerRequirements(input) {
68
+ if (typeof input.identity.author !== "string" || input.identity.author.length === 0) {
69
+ throw new ConcordBoundaryError(
70
+ "INVALID_IDENTITY",
71
+ "Concord requires identity.author when creating an internal ledger."
72
+ );
73
+ }
74
+ if (!input.identity.signer) {
75
+ throw new ConcordBoundaryError(
76
+ "INVALID_IDENTITY",
77
+ "Concord requires identity.signer when creating an internal ledger."
78
+ );
79
+ }
80
+ }
81
+ async function createConcordApp(input) {
82
+ const plugins = [...input.plugins];
83
+ const projectionTargets = [...input.projectionTargets ?? []];
84
+ const now = input.now ?? createDefaultNow;
85
+ const policy = {
86
+ autoCommit: input.policy?.autoCommit ?? false
87
+ };
88
+ const pluginsById = /* @__PURE__ */ new Map();
89
+ const commands = /* @__PURE__ */ new Map();
90
+ const projectionTargetNames = /* @__PURE__ */ new Set();
91
+ for (const plugin of plugins) {
92
+ if (pluginsById.has(plugin.id)) {
93
+ throw new ConcordBoundaryError(
94
+ "DUPLICATE_PLUGIN_ID",
95
+ `Duplicate Concord plugin id: ${plugin.id}`
96
+ );
97
+ }
98
+ pluginsById.set(plugin.id, plugin);
99
+ for (const [commandType, handler] of Object.entries(plugin.commands ?? {})) {
100
+ if (commands.has(commandType)) {
101
+ throw new ConcordBoundaryError(
102
+ "DUPLICATE_COMMAND_TYPE",
103
+ `Duplicate Concord command type: ${commandType}`
104
+ );
105
+ }
106
+ commands.set(commandType, { plugin, handler });
107
+ }
108
+ }
109
+ for (const target of projectionTargets) {
110
+ if (projectionTargetNames.has(target.name)) {
111
+ throw new ConcordBoundaryError(
112
+ "DUPLICATE_PROJECTION_TARGET_NAME",
113
+ `Duplicate Concord projection target name: ${target.name}`
114
+ );
115
+ }
116
+ projectionTargetNames.add(target.name);
117
+ }
118
+ if (!input.ledger) {
119
+ assertInternalLedgerRequirements(input);
120
+ }
121
+ const ledger = input.ledger ?? await createLedger({
122
+ identity: {
123
+ signer: input.identity.signer,
124
+ authorResolver: () => input.identity.author,
125
+ decryptor: input.identity.decryptor
126
+ },
127
+ initialProjection: [],
128
+ projector: (entries, entry) => [
129
+ ...entries,
130
+ entry
131
+ ],
132
+ storage: input.storage,
133
+ now,
134
+ protocol: input.protocol,
135
+ seal: input.seal,
136
+ armour: input.armour,
137
+ autoCommit: false,
138
+ replayPolicy: {
139
+ verify: false,
140
+ decrypt: true
141
+ }
142
+ });
143
+ let state = {
144
+ ready: false,
145
+ integrityValid: false,
146
+ stagedCount: 0,
147
+ plugins: createInitialPluginState(plugins),
148
+ verification: null
149
+ };
150
+ const listeners = /* @__PURE__ */ new Set();
151
+ function getPluginState(pluginId, source = state) {
152
+ assertKnownPlugin(pluginsById, pluginId);
153
+ return source.plugins[pluginId];
154
+ }
155
+ function publish(nextState) {
156
+ state = nextState;
157
+ for (const listener of listeners) {
158
+ listener(state);
159
+ }
160
+ }
161
+ async function rebuildFromEntries(entriesValue, options) {
162
+ const entries = assertReplayEntries(entriesValue);
163
+ const nextState = {
164
+ ready: options.ready,
165
+ integrityValid: options.integrityValid,
166
+ stagedCount: ledger.getState().staged.length,
167
+ plugins: createInitialPluginState(plugins),
168
+ verification: options.verification
169
+ };
170
+ const replayView = {
171
+ getState() {
172
+ return nextState;
173
+ },
174
+ getPluginState(pluginId) {
175
+ return getPluginState(pluginId, nextState);
176
+ }
177
+ };
178
+ const replayContext = {
179
+ app: replayView
180
+ };
181
+ for (const target of projectionTargets) {
182
+ await target.reset();
183
+ }
184
+ for (const target of projectionTargets) {
185
+ await target.beginReplay?.(replayContext);
186
+ }
187
+ for (const entry of entries) {
188
+ for (const plugin of plugins) {
189
+ nextState.plugins[plugin.id] = plugin.project(
190
+ nextState.plugins[plugin.id],
191
+ entry,
192
+ { decryptAvailable: Boolean(input.identity.decryptor) }
193
+ );
194
+ }
195
+ for (const target of projectionTargets) {
196
+ await target.applyEntry(entry, replayContext);
197
+ }
198
+ }
199
+ for (const target of projectionTargets) {
200
+ await target.endReplay?.(replayContext);
201
+ }
202
+ publish(nextState);
203
+ }
204
+ async function rebuildFromReplay(replayEntries, options) {
205
+ await rebuildFromEntries(replayEntries, options);
206
+ }
207
+ function requireReady() {
208
+ if (!state.ready) {
209
+ throw new ConcordBoundaryError(
210
+ "APP_NOT_READY",
211
+ "Concord app must be loaded or created before commands can run."
212
+ );
213
+ }
214
+ }
215
+ function createCommandContext() {
216
+ return {
217
+ now,
218
+ identity: input.identity,
219
+ getPluginState(pluginId) {
220
+ return getPluginState(pluginId);
221
+ }
222
+ };
223
+ }
224
+ function publishIntegrityFailure(verification, resetPlugins) {
225
+ publish({
226
+ ready: false,
227
+ integrityValid: false,
228
+ stagedCount: ledger.getState().staged.length,
229
+ plugins: resetPlugins ? createInitialPluginState(plugins) : state.plugins,
230
+ verification
231
+ });
232
+ }
233
+ async function ensureCommittedHistoryUsable(options) {
234
+ const verification = await ledger.verify();
235
+ if (verification.committedHistoryValid) {
236
+ return true;
237
+ }
238
+ publishIntegrityFailure(
239
+ verification,
240
+ options?.resetPluginsOnFailure ?? true
241
+ );
242
+ if (options?.allowInspectionOnly) {
243
+ return false;
244
+ }
245
+ throw new ConcordBoundaryError(
246
+ "INVALID_COMMITTED_HISTORY",
247
+ "Concord cannot project runtime state from invalid committed history."
248
+ );
249
+ }
250
+ async function replay(options) {
251
+ if (!await ensureCommittedHistoryUsable()) {
252
+ return;
253
+ }
254
+ const replayEntries = await ledger.replay(options);
255
+ await rebuildFromReplay(replayEntries, {
256
+ ready: state.ready,
257
+ integrityValid: true,
258
+ verification: state.verification
259
+ });
260
+ }
261
+ async function create(params) {
262
+ await ledger.create(params);
263
+ if (!await ensureCommittedHistoryUsable()) {
264
+ return;
265
+ }
266
+ await rebuildFromReplay(await ledger.replay(), {
267
+ ready: true,
268
+ integrityValid: true,
269
+ verification: null
270
+ });
271
+ }
272
+ async function load() {
273
+ const loaded = await ledger.loadFromStorage();
274
+ if (!loaded) {
275
+ await ledger.create();
276
+ }
277
+ if (!await ensureCommittedHistoryUsable({
278
+ allowInspectionOnly: true,
279
+ resetPluginsOnFailure: true
280
+ })) {
281
+ return;
282
+ }
283
+ await rebuildFromReplay(await ledger.replay(), {
284
+ ready: true,
285
+ integrityValid: true,
286
+ verification: null
287
+ });
288
+ }
289
+ async function command(type, inputValue) {
290
+ requireReady();
291
+ const registration = commands.get(type);
292
+ if (!registration) {
293
+ throw new ConcordBoundaryError(
294
+ "UNKNOWN_COMMAND",
295
+ `Unknown Concord command type: ${type}`
296
+ );
297
+ }
298
+ const appendInputs = normalizeAppendInputs(
299
+ await registration.handler(createCommandContext(), inputValue)
300
+ );
301
+ const appendResults = await ledger.appendMany(appendInputs);
302
+ let commitResult;
303
+ if (policy.autoCommit) {
304
+ commitResult = await ledger.commit();
305
+ }
306
+ if (!await ensureCommittedHistoryUsable()) {
307
+ return {
308
+ commitId: commitResult?.commit.commitId,
309
+ entryIds: appendResults.map(
310
+ (result) => result.entry.entryId
311
+ ),
312
+ stagedCount: ledger.getState().staged.length
313
+ };
314
+ }
315
+ await rebuildFromReplay(await ledger.replay(), {
316
+ ready: true,
317
+ integrityValid: true,
318
+ verification: null
319
+ });
320
+ return {
321
+ commitId: commitResult?.commit.commitId,
322
+ entryIds: appendResults.map(
323
+ (result) => result.entry.entryId
324
+ ),
325
+ stagedCount: state.stagedCount
326
+ };
327
+ }
328
+ async function commit(input2) {
329
+ requireReady();
330
+ const result = await ledger.commit(input2);
331
+ if (!await ensureCommittedHistoryUsable()) {
332
+ return {
333
+ commitId: result.commit.commitId,
334
+ entryIds: result.committedEntryIds
335
+ };
336
+ }
337
+ await rebuildFromReplay(await ledger.replay(), {
338
+ ready: true,
339
+ integrityValid: true,
340
+ verification: null
341
+ });
342
+ return {
343
+ commitId: result.commit.commitId,
344
+ entryIds: result.committedEntryIds
345
+ };
346
+ }
347
+ async function recompute() {
348
+ if (!await ensureCommittedHistoryUsable()) {
349
+ return;
350
+ }
351
+ const replayEntries = await ledger.recompute();
352
+ await rebuildFromReplay(replayEntries, {
353
+ ready: state.ready,
354
+ integrityValid: true,
355
+ verification: state.verification
356
+ });
357
+ }
358
+ async function verify() {
359
+ const verification = await ledger.verify();
360
+ publish({
361
+ ready: state.ready && verification.committedHistoryValid,
362
+ integrityValid: verification.committedHistoryValid,
363
+ stagedCount: state.stagedCount,
364
+ plugins: state.plugins,
365
+ verification
366
+ });
367
+ return verification;
368
+ }
369
+ async function exportLedger() {
370
+ return ledger.export();
371
+ }
372
+ async function importLedger(container) {
373
+ await ledger.import(container);
374
+ if (!await ensureCommittedHistoryUsable({
375
+ allowInspectionOnly: true,
376
+ resetPluginsOnFailure: true
377
+ })) {
378
+ return;
379
+ }
380
+ await rebuildFromReplay(await ledger.replay(), {
381
+ ready: true,
382
+ integrityValid: true,
383
+ verification: null
384
+ });
385
+ }
386
+ function subscribe(listener) {
387
+ listeners.add(listener);
388
+ return () => {
389
+ listeners.delete(listener);
390
+ };
391
+ }
392
+ async function destroy() {
393
+ for (const target of projectionTargets) {
394
+ await target.destroy?.();
395
+ }
396
+ await ledger.destroy();
397
+ publish({
398
+ ready: false,
399
+ integrityValid: false,
400
+ stagedCount: 0,
401
+ plugins: createInitialPluginState(plugins),
402
+ verification: null
403
+ });
404
+ listeners.clear();
405
+ }
406
+ return {
407
+ create,
408
+ load,
409
+ command,
410
+ commit,
411
+ replay,
412
+ recompute,
413
+ verify,
414
+ exportLedger,
415
+ importLedger,
416
+ getState() {
417
+ return state;
418
+ },
419
+ getPluginState(pluginId) {
420
+ return getPluginState(pluginId);
421
+ },
422
+ subscribe,
423
+ destroy
424
+ };
425
+ }
426
+ export {
427
+ ConcordBoundaryError,
428
+ createConcordApp
429
+ };
430
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/errors.ts","../src/app.ts"],"sourcesContent":["export class ConcordBoundaryError extends Error {\n code: string;\n\n constructor(code: string, message: string) {\n super(message);\n this.name = \"ConcordBoundaryError\";\n this.code = code;\n }\n}\n","import {\n createLedger,\n type LedgerAppendInput,\n type LedgerCommitResult,\n type LedgerContainer,\n type LedgerDecryptor,\n type LedgerIdentityContext,\n type LedgerInstance,\n type LedgerReplayEntry,\n type LedgerVerificationResult\n} from \"@ternent/ledger\";\nimport { ConcordBoundaryError } from \"./errors.js\";\nimport type {\n ConcordApp,\n ConcordCommandContext,\n ConcordCommandResult,\n ConcordCommitInput,\n ConcordCommitResult,\n ConcordCreateParams,\n ConcordPlugin,\n ConcordProjectionReplayContext,\n ConcordReplayOptions,\n ConcordState,\n CreateConcordAppInput\n} from \"./types.js\";\n\ntype CommandRegistration = {\n plugin: ConcordPlugin;\n handler: NonNullable<ConcordPlugin[\"commands\"]>[string];\n};\n\ntype RebuildStateOptions = {\n ready: boolean;\n integrityValid: boolean;\n verification: LedgerVerificationResult | null;\n};\n\nfunction createDefaultNow() {\n return new Date().toISOString();\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\nfunction isLedgerReplayEntry(value: unknown): value is LedgerReplayEntry {\n if (!isObject(value)) {\n return false;\n }\n\n if (\n typeof value.entryId !== \"string\" ||\n typeof value.kind !== \"string\" ||\n typeof value.author !== \"string\" ||\n typeof value.authoredAt !== \"string\"\n ) {\n return false;\n }\n\n if (!(value.meta === null || isObject(value.meta))) {\n return false;\n }\n\n if (!isObject(value.payload) || typeof value.payload.type !== \"string\") {\n return false;\n }\n\n return (\n value.payload.type === \"plain\" ||\n value.payload.type === \"encrypted\" ||\n value.payload.type === \"decrypted\"\n );\n}\n\nfunction assertReplayEntries(value: unknown): LedgerReplayEntry[] {\n if (Array.isArray(value) && value.every(isLedgerReplayEntry)) {\n return value;\n }\n\n throw new ConcordBoundaryError(\n \"INVALID_LEDGER_PROJECTION\",\n \"Concord requires ledger replay output to be LedgerReplayEntry[].\"\n );\n}\n\nfunction createInitialPluginState(plugins: ConcordPlugin[]): Record<string, unknown> {\n return Object.fromEntries(plugins.map((plugin) => [plugin.id, plugin.initialState()]));\n}\n\nfunction assertKnownPlugin(\n pluginsById: Map<string, ConcordPlugin>,\n pluginId: string\n): void {\n if (!pluginsById.has(pluginId)) {\n throw new ConcordBoundaryError(\n \"UNKNOWN_PLUGIN\",\n `Unknown Concord plugin: ${pluginId}`\n );\n }\n}\n\nfunction normalizeAppendInputs(\n value: LedgerAppendInput | LedgerAppendInput[]\n): LedgerAppendInput[] {\n const inputs = Array.isArray(value) ? value : [value];\n if (inputs.length === 0) {\n throw new ConcordBoundaryError(\n \"COMMAND_PRODUCED_NO_ENTRIES\",\n \"Concord command handlers must return at least one LedgerAppendInput.\"\n );\n }\n return inputs;\n}\n\nfunction assertInternalLedgerRequirements(input: CreateConcordAppInput): void {\n if (typeof input.identity.author !== \"string\" || input.identity.author.length === 0) {\n throw new ConcordBoundaryError(\n \"INVALID_IDENTITY\",\n \"Concord requires identity.author when creating an internal ledger.\"\n );\n }\n\n if (!input.identity.signer) {\n throw new ConcordBoundaryError(\n \"INVALID_IDENTITY\",\n \"Concord requires identity.signer when creating an internal ledger.\"\n );\n }\n}\n\nexport async function createConcordApp(\n input: CreateConcordAppInput\n): Promise<ConcordApp> {\n const plugins = [...input.plugins];\n const projectionTargets = [...(input.projectionTargets ?? [])];\n const now = input.now ?? createDefaultNow;\n const policy = {\n autoCommit: input.policy?.autoCommit ?? false\n };\n\n const pluginsById = new Map<string, ConcordPlugin>();\n const commands = new Map<string, CommandRegistration>();\n const projectionTargetNames = new Set<string>();\n\n for (const plugin of plugins) {\n if (pluginsById.has(plugin.id)) {\n throw new ConcordBoundaryError(\n \"DUPLICATE_PLUGIN_ID\",\n `Duplicate Concord plugin id: ${plugin.id}`\n );\n }\n\n pluginsById.set(plugin.id, plugin);\n\n for (const [commandType, handler] of Object.entries(plugin.commands ?? {})) {\n if (commands.has(commandType)) {\n throw new ConcordBoundaryError(\n \"DUPLICATE_COMMAND_TYPE\",\n `Duplicate Concord command type: ${commandType}`\n );\n }\n\n commands.set(commandType, { plugin, handler });\n }\n }\n\n for (const target of projectionTargets) {\n if (projectionTargetNames.has(target.name)) {\n throw new ConcordBoundaryError(\n \"DUPLICATE_PROJECTION_TARGET_NAME\",\n `Duplicate Concord projection target name: ${target.name}`\n );\n }\n\n projectionTargetNames.add(target.name);\n }\n\n if (!input.ledger) {\n assertInternalLedgerRequirements(input);\n }\n\n const ledger =\n input.ledger ??\n (await createLedger<LedgerReplayEntry[]>({\n identity: {\n signer: input.identity.signer as LedgerIdentityContext[\"signer\"],\n authorResolver: () => input.identity.author,\n decryptor: input.identity.decryptor as LedgerDecryptor | undefined\n },\n initialProjection: [],\n projector: (entries: LedgerReplayEntry[], entry: LedgerReplayEntry) => [\n ...entries,\n entry\n ],\n storage: input.storage,\n now,\n protocol: input.protocol,\n seal: input.seal,\n armour: input.armour,\n autoCommit: false,\n replayPolicy: {\n verify: false,\n decrypt: true\n }\n }));\n\n let state: ConcordState = {\n ready: false,\n integrityValid: false,\n stagedCount: 0,\n plugins: createInitialPluginState(plugins),\n verification: null\n };\n\n const listeners = new Set<(value: Readonly<ConcordState>) => void>();\n\n function getPluginState<T = unknown>(pluginId: string, source = state): T {\n assertKnownPlugin(pluginsById, pluginId);\n return source.plugins[pluginId] as T;\n }\n\n function publish(nextState: ConcordState): void {\n state = nextState;\n for (const listener of listeners) {\n listener(state);\n }\n }\n\n async function rebuildFromEntries(\n entriesValue: unknown,\n options: RebuildStateOptions\n ): Promise<void> {\n const entries = assertReplayEntries(entriesValue);\n const nextState: ConcordState = {\n ready: options.ready,\n integrityValid: options.integrityValid,\n stagedCount: ledger.getState().staged.length,\n plugins: createInitialPluginState(plugins),\n verification: options.verification\n };\n\n const replayView = {\n getState(): Readonly<ConcordState> {\n return nextState;\n },\n getPluginState<T = unknown>(pluginId: string): T {\n return getPluginState<T>(pluginId, nextState);\n }\n };\n\n const replayContext: ConcordProjectionReplayContext = {\n app: replayView\n };\n\n for (const target of projectionTargets) {\n await target.reset();\n }\n\n for (const target of projectionTargets) {\n await target.beginReplay?.(replayContext);\n }\n\n for (const entry of entries) {\n for (const plugin of plugins) {\n nextState.plugins[plugin.id] = plugin.project(\n nextState.plugins[plugin.id],\n entry,\n { decryptAvailable: Boolean(input.identity.decryptor) }\n );\n }\n\n for (const target of projectionTargets) {\n await target.applyEntry(entry, replayContext);\n }\n }\n\n for (const target of projectionTargets) {\n await target.endReplay?.(replayContext);\n }\n\n publish(nextState);\n }\n\n async function rebuildFromReplay(\n replayEntries: unknown,\n options: RebuildStateOptions\n ): Promise<void> {\n await rebuildFromEntries(replayEntries, options);\n }\n\n function requireReady(): void {\n if (!state.ready) {\n throw new ConcordBoundaryError(\n \"APP_NOT_READY\",\n \"Concord app must be loaded or created before commands can run.\"\n );\n }\n }\n\n function createCommandContext(): ConcordCommandContext {\n return {\n now,\n identity: input.identity,\n getPluginState<T = unknown>(pluginId: string): T {\n return getPluginState<T>(pluginId);\n }\n };\n }\n\n function publishIntegrityFailure(\n verification: LedgerVerificationResult,\n resetPlugins: boolean\n ): void {\n publish({\n ready: false,\n integrityValid: false,\n stagedCount: ledger.getState().staged.length,\n plugins: resetPlugins ? createInitialPluginState(plugins) : state.plugins,\n verification\n });\n }\n\n async function ensureCommittedHistoryUsable(options?: {\n allowInspectionOnly?: boolean;\n resetPluginsOnFailure?: boolean;\n }): Promise<boolean> {\n const verification = await ledger.verify();\n if (verification.committedHistoryValid) {\n return true;\n }\n\n publishIntegrityFailure(\n verification,\n options?.resetPluginsOnFailure ?? true\n );\n\n if (options?.allowInspectionOnly) {\n return false;\n }\n\n throw new ConcordBoundaryError(\n \"INVALID_COMMITTED_HISTORY\",\n \"Concord cannot project runtime state from invalid committed history.\"\n );\n }\n\n async function replay(options?: ConcordReplayOptions): Promise<void> {\n if (!(await ensureCommittedHistoryUsable())) {\n return;\n }\n\n const replayEntries = await ledger.replay(options);\n await rebuildFromReplay(replayEntries, {\n ready: state.ready,\n integrityValid: true,\n verification: state.verification\n });\n }\n\n async function create(params?: ConcordCreateParams): Promise<void> {\n await ledger.create(params);\n\n if (!(await ensureCommittedHistoryUsable())) {\n return;\n }\n\n await rebuildFromReplay(await ledger.replay(), {\n ready: true,\n integrityValid: true,\n verification: null\n });\n }\n\n async function load(): Promise<void> {\n const loaded = await ledger.loadFromStorage();\n if (!loaded) {\n await ledger.create();\n }\n\n if (\n !(await ensureCommittedHistoryUsable({\n allowInspectionOnly: true,\n resetPluginsOnFailure: true\n }))\n ) {\n return;\n }\n\n await rebuildFromReplay(await ledger.replay(), {\n ready: true,\n integrityValid: true,\n verification: null\n });\n }\n\n async function command<TInput = unknown>(\n type: string,\n inputValue: TInput\n ): Promise<ConcordCommandResult> {\n requireReady();\n\n const registration = commands.get(type);\n if (!registration) {\n throw new ConcordBoundaryError(\n \"UNKNOWN_COMMAND\",\n `Unknown Concord command type: ${type}`\n );\n }\n\n const appendInputs = normalizeAppendInputs(\n await registration.handler(createCommandContext(), inputValue)\n );\n\n const appendResults = await ledger.appendMany(appendInputs);\n let commitResult: LedgerCommitResult | undefined;\n\n if (policy.autoCommit) {\n commitResult = await ledger.commit();\n }\n\n if (!(await ensureCommittedHistoryUsable())) {\n return {\n commitId: commitResult?.commit.commitId,\n entryIds: appendResults.map(\n (result: { entry: { entryId: string } }) => result.entry.entryId\n ),\n stagedCount: ledger.getState().staged.length\n };\n }\n\n await rebuildFromReplay(await ledger.replay(), {\n ready: true,\n integrityValid: true,\n verification: null\n });\n\n return {\n commitId: commitResult?.commit.commitId,\n entryIds: appendResults.map(\n (result: { entry: { entryId: string } }) => result.entry.entryId\n ),\n stagedCount: state.stagedCount\n };\n }\n\n async function commit(\n input?: ConcordCommitInput\n ): Promise<ConcordCommitResult> {\n requireReady();\n\n const result = await ledger.commit(input);\n\n if (!(await ensureCommittedHistoryUsable())) {\n return {\n commitId: result.commit.commitId,\n entryIds: result.committedEntryIds\n };\n }\n\n await rebuildFromReplay(await ledger.replay(), {\n ready: true,\n integrityValid: true,\n verification: null\n });\n\n return {\n commitId: result.commit.commitId,\n entryIds: result.committedEntryIds\n };\n }\n\n async function recompute(): Promise<void> {\n if (!(await ensureCommittedHistoryUsable())) {\n return;\n }\n\n const replayEntries = await ledger.recompute();\n await rebuildFromReplay(replayEntries, {\n ready: state.ready,\n integrityValid: true,\n verification: state.verification\n });\n }\n\n async function verify(): Promise<LedgerVerificationResult> {\n const verification = await ledger.verify();\n publish({\n ready: state.ready && verification.committedHistoryValid,\n integrityValid: verification.committedHistoryValid,\n stagedCount: state.stagedCount,\n plugins: state.plugins,\n verification\n });\n return verification;\n }\n\n async function exportLedger(): Promise<LedgerContainer> {\n return ledger.export();\n }\n\n async function importLedger(container: LedgerContainer): Promise<void> {\n await ledger.import(container);\n\n if (\n !(await ensureCommittedHistoryUsable({\n allowInspectionOnly: true,\n resetPluginsOnFailure: true\n }))\n ) {\n return;\n }\n\n await rebuildFromReplay(await ledger.replay(), {\n ready: true,\n integrityValid: true,\n verification: null\n });\n }\n\n function subscribe(\n listener: (value: Readonly<ConcordState>) => void\n ): () => void {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n }\n\n async function destroy(): Promise<void> {\n for (const target of projectionTargets) {\n await target.destroy?.();\n }\n\n await ledger.destroy();\n publish({\n ready: false,\n integrityValid: false,\n stagedCount: 0,\n plugins: createInitialPluginState(plugins),\n verification: null\n });\n listeners.clear();\n }\n\n return {\n create,\n load,\n command,\n commit,\n replay,\n recompute,\n verify,\n exportLedger,\n importLedger,\n getState() {\n return state;\n },\n getPluginState<T = unknown>(pluginId: string): T {\n return getPluginState<T>(pluginId);\n },\n subscribe,\n destroy\n };\n}\n"],"names":["input"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@ternent/concord",
3
+ "version": "0.2.2",
4
+ "description": "Command-driven developer runtime for building non-custodial apps on @ternent/ledger",
5
+ "license": "ISC",
6
+ "type": "module",
7
+ "files": [
8
+ "dist",
9
+ "README.md",
10
+ "SPEC.md"
11
+ ],
12
+ "publishConfig": {
13
+ "access": "public"
14
+ },
15
+ "main": "dist/index.js",
16
+ "module": "dist/index.js",
17
+ "types": "dist/index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.js",
22
+ "default": "./dist/index.js"
23
+ }
24
+ },
25
+ "dependencies": {
26
+ "@ternent/ledger": "0.1.3"
27
+ },
28
+ "devDependencies": {
29
+ "@types/node": "^24.3.0",
30
+ "typescript": "^5.6.3",
31
+ "vite": "^4.5.3",
32
+ "vite-plugin-dts": "^1.7.3",
33
+ "vitest": "^1.6.0",
34
+ "@ternent/armour": "0.1.3",
35
+ "@ternent/identity": "0.3.0",
36
+ "@ternent/rage": "0.1.2",
37
+ "@ternent/seal-cli": "0.3.5"
38
+ },
39
+ "scripts": {
40
+ "build": "pnpm --filter @ternent/rage build && vite build",
41
+ "test": "pnpm --filter @ternent/rage build && vitest run"
42
+ }
43
+ }