keri-ts 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +70 -0
  3. package/esm/_dnt.polyfills.js +127 -0
  4. package/esm/_dnt.shims.js +61 -0
  5. package/esm/app/cli/agent.js +37 -0
  6. package/esm/app/cli/cli-node.js +9 -0
  7. package/esm/app/cli/cli.js +195 -0
  8. package/esm/app/cli/db-dump.js +68 -0
  9. package/esm/app/cli/init.js +75 -0
  10. package/esm/app/server.js +77 -0
  11. package/esm/core/bytes.js +39 -0
  12. package/esm/core/errors.js +26 -0
  13. package/esm/core/index.js +7 -0
  14. package/esm/db/basing.js +168 -0
  15. package/esm/db/core/db.js +19 -0
  16. package/esm/db/core/lmdber.js +474 -0
  17. package/esm/db/core/path-manager.js +450 -0
  18. package/esm/db/index.js +4 -0
  19. package/esm/npm/index.js +4 -0
  20. package/esm/package.json +3 -0
  21. package/package.json +57 -0
  22. package/types/_dnt.polyfills.d.ts +101 -0
  23. package/types/_dnt.polyfills.d.ts.map +1 -0
  24. package/types/_dnt.shims.d.ts +6 -0
  25. package/types/_dnt.shims.d.ts.map +1 -0
  26. package/types/app/cli/agent.d.ts +9 -0
  27. package/types/app/cli/agent.d.ts.map +1 -0
  28. package/types/app/cli/cli-node.d.ts +2 -0
  29. package/types/app/cli/cli-node.d.ts.map +1 -0
  30. package/types/app/cli/cli.d.ts +7 -0
  31. package/types/app/cli/cli.d.ts.map +1 -0
  32. package/types/app/cli/db-dump.d.ts +11 -0
  33. package/types/app/cli/db-dump.d.ts.map +1 -0
  34. package/types/app/cli/init.d.ts +3 -0
  35. package/types/app/cli/init.d.ts.map +1 -0
  36. package/types/app/server.d.ts +8 -0
  37. package/types/app/server.d.ts.map +1 -0
  38. package/types/core/bytes.d.ts +17 -0
  39. package/types/core/bytes.d.ts.map +1 -0
  40. package/types/core/errors.d.ts +19 -0
  41. package/types/core/errors.d.ts.map +1 -0
  42. package/types/core/index.d.ts +8 -0
  43. package/types/core/index.d.ts.map +1 -0
  44. package/types/db/basing.d.ts +80 -0
  45. package/types/db/basing.d.ts.map +1 -0
  46. package/types/db/core/db.d.ts +5 -0
  47. package/types/db/core/db.d.ts.map +1 -0
  48. package/types/db/core/lmdber.d.ts +135 -0
  49. package/types/db/core/lmdber.d.ts.map +1 -0
  50. package/types/db/core/path-manager.d.ts +92 -0
  51. package/types/db/core/path-manager.d.ts.map +1 -0
  52. package/types/db/index.d.ts +5 -0
  53. package/types/db/index.d.ts.map +1 -0
  54. package/types/npm/index.d.ts +5 -0
  55. package/types/npm/index.d.ts.map +1 -0
@@ -0,0 +1,195 @@
1
+ import * as dntShim from "../../_dnt.shims.js";
2
+ import { Command } from "commander";
3
+ import { action } from "effection";
4
+ import { agentCommand } from "./agent.js";
5
+ import { dumpEvts } from "./db-dump.js";
6
+ import { initCommand } from "./init.js";
7
+ /**
8
+ * Promise to Structured Concurrency Helper: Convert Promise to Effection Operation
9
+ * This allows us to integrate promise-based APIs (like Commander) into Effection's structured concurrency
10
+ */
11
+ function* toOp(promise) {
12
+ return yield* action((resolve, reject) => {
13
+ promise.then(resolve, reject);
14
+ return () => { }; // Cleanup function (can add abort logic if needed)
15
+ });
16
+ }
17
+ /**
18
+ * Create the CLI program with action handlers that signal command execution
19
+ */
20
+ function createCLIProgram(context) {
21
+ const program = new Command();
22
+ program.name("kli").version("0.0.2").description("KERI TypeScript CLI");
23
+ // Prevent Commander from exiting automatically so we can run Effection operations
24
+ program.exitOverride();
25
+ program
26
+ .command("init")
27
+ .description("Create a database and keystore")
28
+ .option("-n, --name <name>", "Keystore name and file location of KERI keystore (required)")
29
+ .option("-b, --base <base>", "Additional optional prefix to file location of KERI keystore")
30
+ .option("-t, --temp", "Create a temporary keystore, used for testing")
31
+ .option("-s, --salt <salt>", "Qualified base64 salt for creating key pairs")
32
+ .option("-c, --config-dir <dir>", "Directory override for configuration data")
33
+ .option("--config-file <file>", "Configuration filename override")
34
+ .option("-p, --passcode <passcode>", "22 character encryption passcode for keystore (is not saved)")
35
+ .option("--nopasscode", "Create an unencrypted keystore")
36
+ .option("-a, --aeid <aeid>", "Qualified base64 of non-transferable identifier prefix for authentication and encryption of secrets in keystore")
37
+ .option("-e, --seed <seed>", "Qualified base64 private-signing key (seed) for the aeid from which the private decryption key may be derived")
38
+ .action((options) => {
39
+ // Store command info in context for execution within Effection
40
+ context.command = "init";
41
+ context.args = {
42
+ name: options.name,
43
+ base: options.base,
44
+ temp: options.temp || false,
45
+ salt: options.salt,
46
+ configDir: options.configDir,
47
+ configFile: options.configFile,
48
+ passcode: options.passcode,
49
+ nopasscode: options.nopasscode || false,
50
+ aeid: options.aeid,
51
+ seed: options.seed,
52
+ };
53
+ // Return immediately - actual execution happens in kli operation
54
+ return Promise.resolve();
55
+ });
56
+ program
57
+ .command("agent")
58
+ .description("Start the KERI agent server")
59
+ .option("-p, --port <port>", "Port number for the server (default: 8000)", "8000")
60
+ .action(function () {
61
+ const options = this.opts();
62
+ context.command = "agent";
63
+ context.args = {
64
+ port: options.port ? Number(options.port) : 8000,
65
+ };
66
+ return Promise.resolve();
67
+ });
68
+ program
69
+ .command("incept")
70
+ .description("Create a new identifier")
71
+ .action(() => {
72
+ context.command = "incept";
73
+ context.args = {};
74
+ return Promise.resolve();
75
+ });
76
+ program
77
+ .command("rotate")
78
+ .description("Rotate keys for an identifier")
79
+ .action(() => {
80
+ context.command = "rotate";
81
+ context.args = {};
82
+ return Promise.resolve();
83
+ });
84
+ program
85
+ .command("interact")
86
+ .description("Create an interaction event")
87
+ .action(() => {
88
+ context.command = "interact";
89
+ context.args = {};
90
+ return Promise.resolve();
91
+ });
92
+ program
93
+ .command("witness")
94
+ .description("Start a witness server")
95
+ .action(() => {
96
+ context.command = "witness";
97
+ context.args = {};
98
+ return Promise.resolve();
99
+ });
100
+ // Create db command with dump subcommand using chained .command() pattern
101
+ const dbCommand = program.command("db").description("Database operations");
102
+ dbCommand
103
+ .command("dump")
104
+ .description("Dump database contents")
105
+ .requiredOption("-n, --name <name>", "Database name")
106
+ .option("-b, --base <base>", "Additional optional prefix to database path")
107
+ .option("-t, --temp", "Use temporary database")
108
+ .action((options) => {
109
+ context.command = "db.dump";
110
+ context.args = {
111
+ name: options.name,
112
+ base: options.base,
113
+ temp: options.temp || false,
114
+ };
115
+ return Promise.resolve();
116
+ });
117
+ return program;
118
+ }
119
+ /**
120
+ * Stub command operations (to be implemented)
121
+ * These are placeholder operations that will be fully implemented later
122
+ */
123
+ // deno-lint-ignore require-yield
124
+ function* inceptCommand(_args) {
125
+ console.log("kli incept command - coming soon!");
126
+ }
127
+ // deno-lint-ignore require-yield
128
+ function* rotateCommand(_args) {
129
+ console.log("kli rotate command - coming soon!");
130
+ }
131
+ // deno-lint-ignore require-yield
132
+ function* interactCommand(_args) {
133
+ console.log("kli interact command - coming soon!");
134
+ }
135
+ // deno-lint-ignore require-yield
136
+ function* witnessCommand(_args) {
137
+ console.log("kli witness command - coming soon!");
138
+ }
139
+ /**
140
+ * Command handler registry - maps command names to Effection operations
141
+ */
142
+ const commandHandlers = new Map([
143
+ ["init", (args) => initCommand(args)],
144
+ ["agent", (args) => agentCommand(args)],
145
+ ["incept", (args) => inceptCommand(args)],
146
+ ["rotate", (args) => rotateCommand(args)],
147
+ ["interact", (args) => interactCommand(args)],
148
+ ["witness", (args) => witnessCommand(args)],
149
+ ["db.dump", (args) => dumpEvts(args)],
150
+ ]);
151
+ /**
152
+ * Main CLI operation - runs within Effection's structured concurrency
153
+ * This is the outermost runtime, not JavaScript's event loop
154
+ */
155
+ export function* kli(args = []) {
156
+ // Create a context for command execution
157
+ const context = {};
158
+ // Use Commander.js for all command parsing
159
+ const program = createCLIProgram(context);
160
+ try {
161
+ // Parse arguments - Commander expects full argv or args array
162
+ // In Deno, Deno.args gives us the arguments without the executable info
163
+ const argsToParse = args.length > 0 ? args : dntShim.Deno.args;
164
+ program.parse(argsToParse, { from: "user" });
165
+ }
166
+ catch (error) {
167
+ // Handle Commander-specific errors
168
+ if (error && typeof error === "object" && "code" in error) {
169
+ const commanderError = error;
170
+ // Help was requested - Commander already printed it, just return
171
+ if (commanderError.code === "commander.help" ||
172
+ commanderError.code === "commander.helpDisplayed") {
173
+ return;
174
+ }
175
+ // Unknown command or other parsing errors - Commander already printed the error
176
+ if (commanderError.code === "commander.unknownCommand" ||
177
+ commanderError.code === "commander.missingArgument") {
178
+ // Commander already printed the error message, just exit gracefully
179
+ return;
180
+ }
181
+ }
182
+ // For other errors, log and rethrow
183
+ const message = error instanceof Error ? error.message : String(error);
184
+ console.error(`Error: ${message}`);
185
+ throw error;
186
+ }
187
+ // Execute the appropriate command operation based on context
188
+ if (context.command && context.args) {
189
+ const handler = commandHandlers.get(context.command);
190
+ if (handler) {
191
+ // Execute the command operation within Effection's structured concurrency
192
+ yield* handler(context.args);
193
+ }
194
+ }
195
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Database dump command for CLI
3
+ *
4
+ * Dumps the contents of Baser.evts sub-database to console in prettified table format
5
+ */
6
+ import { displayStr } from "../../core/bytes.js";
7
+ import { createBaser } from "../../db/basing.js";
8
+ /**
9
+ * Dump the evts sub-database to console
10
+ */
11
+ export function* dumpEvts(args) {
12
+ const name = args.name;
13
+ const base = args.base;
14
+ const temp = args.temp;
15
+ const readonly = true; // Always open readonly for dump
16
+ if (!name) {
17
+ console.error("Error: --name is required");
18
+ return;
19
+ }
20
+ console.log(`Dumping database ${name} from ${base} in temp mode: ${temp}`);
21
+ const options = {
22
+ name,
23
+ base,
24
+ temp,
25
+ reopen: true,
26
+ readonly,
27
+ dupsort: false,
28
+ };
29
+ const baser = yield* createBaser(options);
30
+ try {
31
+ // get database version
32
+ const version = baser.getVer();
33
+ console.log(`Database version: ${version}`);
34
+ // Get count
35
+ const count = baser.cntEvts();
36
+ console.log(`\nBaser.evts sub-database dump (${count} entries)\n`);
37
+ console.log("=".repeat(137));
38
+ // Print header
39
+ console.log(`${"Key".padEnd(89)} | ${"Value (UTF-8)".padEnd(45)}`);
40
+ console.log("-".repeat(137));
41
+ // Iterate and print entries (empty top = all items)
42
+ const iter = baser.getAllEvtsIter(new Uint8Array(0));
43
+ let entryCount = 0;
44
+ let keyColSize = 50;
45
+ let valColSize = 50;
46
+ for (const [keyBytes, valBytes] of iter) {
47
+ entryCount++;
48
+ const keyStr = displayStr(keyBytes);
49
+ if (keyStr.length > keyColSize) {
50
+ keyColSize = keyStr.length;
51
+ }
52
+ const valStr = displayStr(valBytes, 45);
53
+ if (valStr.length > valColSize) {
54
+ valColSize = valStr.length;
55
+ }
56
+ console.log(`${keyStr.padEnd(50)} | ${valStr.padEnd(45)}`);
57
+ }
58
+ console.log("=".repeat(137));
59
+ console.log(`\nTotal entries: ${entryCount}`);
60
+ }
61
+ catch (error) {
62
+ console.error(`Error dumping database: ${error}`);
63
+ throw error;
64
+ }
65
+ finally {
66
+ yield* baser.close();
67
+ }
68
+ }
@@ -0,0 +1,75 @@
1
+ function printInitHelp() {
2
+ console.log(`
3
+ kli init - Create a database and keystore
4
+
5
+ Usage: kli init [options]
6
+
7
+ Options:
8
+ --name, -n <name> Keystore name and file location of KERI keystore (required)
9
+ --base, -b <base> Additional optional prefix to file location of KERI keystore
10
+ --temp, -t Create a temporary keystore, used for testing
11
+ --salt, -s <salt> Qualified base64 salt for creating key pairs
12
+ --config-dir, -c <dir> Directory override for configuration data
13
+ --config-file <file> Configuration filename override
14
+ --passcode, -p <passcode> 22 character encryption passcode for keystore (is not saved)
15
+ --nopasscode Create an unencrypted keystore
16
+ --aeid, -a <aeid> Qualified base64 of non-transferable identifier prefix for authentication and encryption of secrets in keystore
17
+ --seed, -e <seed> Qualified base64 private-signing key (seed) for the aeid from which the private decryption key may be derived
18
+ --help, -h Show this help message
19
+ `);
20
+ }
21
+ // TODO remove this ignore once init is finished
22
+ // deno-lint-ignore require-yield
23
+ export function* initCommand(args) {
24
+ // Check for help flag
25
+ if (args.help || args.h) {
26
+ printInitHelp();
27
+ return;
28
+ }
29
+ // Extract values from args (already parsed by Cliffy or test mocks)
30
+ const initArgs = {
31
+ name: args.name,
32
+ base: args.base,
33
+ temp: args.temp,
34
+ salt: args.salt,
35
+ configDir: args.configDir,
36
+ configFile: args.configFile,
37
+ passcode: args.passcode,
38
+ nopasscode: args.nopasscode,
39
+ aeid: args.aeid,
40
+ seed: args.seed,
41
+ };
42
+ // Validate required name
43
+ const name = initArgs.name;
44
+ if (!name || name === "") {
45
+ throw new Error("Name is required and cannot be empty");
46
+ }
47
+ const base = initArgs.base || "";
48
+ const temp = initArgs.temp || false;
49
+ let bran = initArgs.passcode;
50
+ const configFile = initArgs.configFile;
51
+ const configDir = initArgs.configDir;
52
+ const nopasscode = initArgs.nopasscode || false;
53
+ // Handle passcode input if not provided and not using nopasscode
54
+ if (!nopasscode && !bran) {
55
+ console.log("Creating encrypted keystore, please enter your 22 character passcode:");
56
+ // For now, we'll use a simple prompt since Deno doesn't have getpass equivalent
57
+ // In a real implementation, you'd want to use a proper password input library
58
+ const passcode = prompt("Passcode: ");
59
+ const retry = prompt("Re-enter passcode: ");
60
+ if (passcode !== retry) {
61
+ throw new Error("Passcodes do not match");
62
+ }
63
+ bran = passcode || undefined;
64
+ }
65
+ // TODO: Implement actual keystore and database creation
66
+ // This is a stub implementation that mirrors the KERIpy structure
67
+ console.log("KERI Keystore created at: [stub - keystore path]");
68
+ console.log("KERI Database created at: [stub - database path]");
69
+ console.log("KERI Credential Store created at: [stub - credential store path]");
70
+ if (initArgs.aeid) {
71
+ console.log("\taeid:", initArgs.aeid);
72
+ }
73
+ console.log("\nInitialization complete!");
74
+ console.log("Note: This is a stub implementation. Full keystore and database creation will be implemented in future versions.");
75
+ }
@@ -0,0 +1,77 @@
1
+ import * as dntShim from "../_dnt.shims.js";
2
+ import { action } from "effection";
3
+ import { openDB, readValue, writeValue } from "../db/core/db.js";
4
+ /**
5
+ * Start HTTP server with Effection as the outermost runtime.
6
+ * Each request is spawned as a separate Effection task, ensuring
7
+ * proper structured concurrency and cleanup.
8
+ */
9
+ export function* startServer(port = 8000) {
10
+ // openDB is synchronous, so call it directly
11
+ let db;
12
+ try {
13
+ db = openDB();
14
+ }
15
+ catch (error) {
16
+ console.error("Error opening database:", error);
17
+ throw error;
18
+ }
19
+ // Use Deno.serve
20
+ return yield* action((resolve, reject) => {
21
+ const controller = new AbortController();
22
+ const { signal } = controller;
23
+ const server = dntShim.Deno.serve({
24
+ port,
25
+ hostname: "127.0.0.1",
26
+ signal,
27
+ onListen: ({ port }) => console.log(`Server running on http://localhost:${port}`),
28
+ onError: (error) => {
29
+ console.error("Server error:", error);
30
+ return new Response("Internal Server Error", { status: 500 });
31
+ },
32
+ }, (req) => {
33
+ try {
34
+ const url = new URL(req.url);
35
+ if (url.pathname.startsWith("/echo/")) {
36
+ // Extract value from path (everything after "/echo/")
37
+ let val = url.pathname.slice(6); // Trim '/echo/'
38
+ // If no value in path, read from database
39
+ if (!val) {
40
+ const oldVal = readValue(db, "echo");
41
+ val = oldVal ? oldVal : "initial echo";
42
+ }
43
+ // Write the value to database (persist it)
44
+ writeValue(db, "echo", val);
45
+ return new Response(val, {
46
+ status: 200,
47
+ headers: { "Content-Type": "text/plain" },
48
+ });
49
+ }
50
+ else {
51
+ return new Response("Not Found", {
52
+ status: 404,
53
+ headers: { "Content-Type": "text/plain" },
54
+ });
55
+ }
56
+ }
57
+ catch (error) {
58
+ return new Response(String(error), { status: 500 });
59
+ }
60
+ });
61
+ // Wait for server to finish
62
+ server.finished.then(resolve).catch(reject);
63
+ // Graceful shutdown logic using signals
64
+ const shutdown = () => {
65
+ console.log("Shutting down server...");
66
+ controller.abort();
67
+ };
68
+ // Deno signal listeners
69
+ dntShim.Deno.addSignalListener("SIGINT", shutdown);
70
+ dntShim.Deno.addSignalListener("SIGTERM", shutdown);
71
+ return () => {
72
+ dntShim.Deno.removeSignalListener("SIGINT", shutdown);
73
+ dntShim.Deno.removeSignalListener("SIGTERM", shutdown);
74
+ controller.abort();
75
+ };
76
+ });
77
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Check if keyBytes starts with prefixBytes
3
+ */
4
+ export function startsWith(keyBytes, prefixBytes) {
5
+ if (prefixBytes.length === 0) {
6
+ return true; // Empty prefix matches everything
7
+ }
8
+ if (keyBytes.length < prefixBytes.length) {
9
+ return false;
10
+ }
11
+ return prefixBytes.every((byte, i) => keyBytes[i] === byte);
12
+ }
13
+ /**
14
+ * Convert bytes to a displayable string for terminal output.
15
+ *
16
+ * Decodes bytes as UTF-8 and replaces non-printable control characters
17
+ * with '.' for readable terminal display. UTF-8 can represent control
18
+ * characters (like \x00, \x09, \x0A) which are valid but not printable.
19
+ *
20
+ * @param bytes - Bytes to convert to display string
21
+ * @param maxLength - Optional maximum length; if exceeded, truncates and adds "..."
22
+ * @returns Displayable string with control characters replaced
23
+ */
24
+ export function displayStr(bytes, maxLength) {
25
+ try {
26
+ const decoder = new TextDecoder("utf-8", { fatal: false });
27
+ let str = decoder.decode(bytes);
28
+ // Replace control characters (non-printable) for readable terminal output
29
+ str = str.replace(/[\x00-\x1F\x7F-\x9F]/g, ".");
30
+ // Truncate if maxLength is specified
31
+ if (maxLength !== undefined && str.length > maxLength) {
32
+ str = str.substring(0, maxLength - 3) + "...";
33
+ }
34
+ return str;
35
+ }
36
+ catch {
37
+ return `[${bytes.length} bytes]`;
38
+ }
39
+ }
@@ -0,0 +1,26 @@
1
+ export class AppError extends Error {
2
+ constructor(message, context) {
3
+ super(message);
4
+ Object.defineProperty(this, "context", {
5
+ enumerable: true,
6
+ configurable: true,
7
+ writable: true,
8
+ value: context
9
+ });
10
+ this.name = globalThis[Symbol.for("import-meta-ponyfill-esmodule")](import.meta).name;
11
+ }
12
+ }
13
+ export class ValidationError extends AppError {
14
+ }
15
+ export class PathError extends AppError {
16
+ }
17
+ export class InvalidPathNameError extends ValidationError {
18
+ }
19
+ export class DatabaseError extends AppError {
20
+ }
21
+ export class DatabaseNotOpenError extends DatabaseError {
22
+ }
23
+ export class DatabaseKeyError extends DatabaseError {
24
+ }
25
+ export class DatabaseOperationError extends DatabaseError {
26
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Core module - public API
3
+ *
4
+ * This module will contain core KERI types (future).
5
+ */
6
+ export * from "./bytes.js";
7
+ export * from "./errors.js";
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Baser - KERI Event Log Database
3
+ *
4
+ * Manages KEL events and related data using composition with LMDBer.
5
+ * Sets up named sub-databases for key event logs.
6
+ */
7
+ import { DatabaseNotOpenError, DatabaseOperationError, } from "../core/errors.js";
8
+ import { LMDBer } from "./core/lmdber.js";
9
+ /**
10
+ * Baser manages KERI event logs
11
+ * Uses composition with LMDBer instead of inheritance
12
+ */
13
+ export class Baser {
14
+ constructor(options = {}) {
15
+ Object.defineProperty(this, "lmdber", {
16
+ enumerable: true,
17
+ configurable: true,
18
+ writable: true,
19
+ value: void 0
20
+ });
21
+ // Named sub-databases
22
+ Object.defineProperty(this, "evts", {
23
+ enumerable: true,
24
+ configurable: true,
25
+ writable: true,
26
+ value: void 0
27
+ }); // Events sub-database (dgKey: serialized KEL events)
28
+ // Create LMDBer with composition
29
+ this.lmdber = new LMDBer(options, {
30
+ tailDirPath: Baser.TailDirPath,
31
+ altTailDirPath: Baser.AltTailDirPath,
32
+ tempPrefix: Baser.TempPrefix,
33
+ maxNamedDBs: Baser.MaxNamedDBs,
34
+ });
35
+ }
36
+ get name() {
37
+ return this.lmdber.name;
38
+ }
39
+ get base() {
40
+ return this.lmdber.base;
41
+ }
42
+ get opened() {
43
+ return this.lmdber.opened;
44
+ }
45
+ get temp() {
46
+ return this.lmdber.temp;
47
+ }
48
+ get path() {
49
+ return this.lmdber.path;
50
+ }
51
+ get env() {
52
+ return this.lmdber.env;
53
+ }
54
+ /**
55
+ * Reopen the database and initialize sub-databases
56
+ */
57
+ *reopen(options = {}) {
58
+ const opened = yield* this.lmdber.reopen(options);
59
+ if (!opened) {
60
+ return false;
61
+ }
62
+ // Open named sub-databases
63
+ // Names end with "." to avoid namespace collisions with Base64 identifier prefixes
64
+ try {
65
+ this.evts = this.lmdber.openDB("evts.", false);
66
+ return this.opened;
67
+ }
68
+ catch (error) {
69
+ console.error(`Failed to open Baser sub-databases: ${error}`);
70
+ throw new DatabaseOperationError("Failed to open Baser sub-databases", { cause: error instanceof Error ? error.message : String(error) });
71
+ }
72
+ }
73
+ /**
74
+ * Close the database
75
+ */
76
+ *close(clear = false) {
77
+ return yield* this.lmdber.close(clear);
78
+ }
79
+ /**
80
+ * Get version
81
+ */
82
+ getVer() {
83
+ return this.lmdber.getVer();
84
+ }
85
+ /**
86
+ * Set version
87
+ */
88
+ setVer(val) {
89
+ this.lmdber.setVer(val);
90
+ }
91
+ /**
92
+ * Count entries in evts sub-database
93
+ */
94
+ cntEvts() {
95
+ return this.lmdber.cnt(this.evts);
96
+ }
97
+ /**
98
+ * Put value in evts sub-database
99
+ */
100
+ putEvt(key, val) {
101
+ return this.lmdber.putVal(this.evts, key, val);
102
+ }
103
+ /**
104
+ * Set value in evts sub-database
105
+ */
106
+ setEvt(key, val) {
107
+ return this.lmdber.setVal(this.evts, key, val);
108
+ }
109
+ /**
110
+ * Get value from evts sub-database
111
+ */
112
+ getEvt(key) {
113
+ return this.lmdber.getVal(this.evts, key);
114
+ }
115
+ /**
116
+ * Delete value from evts sub-database
117
+ */
118
+ delEvt(key) {
119
+ return this.lmdber.delVal(this.evts, key);
120
+ }
121
+ /**
122
+ * Get iterator over items in evts sub-database
123
+ *
124
+ * @param top - Key prefix to filter by (empty to get all items)
125
+ * @returns Generator yielding (key, val) tuples
126
+ */
127
+ *getAllEvtsIter(top = new Uint8Array(0)) {
128
+ yield* this.lmdber.getTopItemIter(this.evts, top);
129
+ }
130
+ }
131
+ // Class constants
132
+ Object.defineProperty(Baser, "TailDirPath", {
133
+ enumerable: true,
134
+ configurable: true,
135
+ writable: true,
136
+ value: "keri/db"
137
+ });
138
+ Object.defineProperty(Baser, "AltTailDirPath", {
139
+ enumerable: true,
140
+ configurable: true,
141
+ writable: true,
142
+ value: ".keri/db"
143
+ });
144
+ Object.defineProperty(Baser, "TempPrefix", {
145
+ enumerable: true,
146
+ configurable: true,
147
+ writable: true,
148
+ value: "keri_db_"
149
+ });
150
+ Object.defineProperty(Baser, "MaxNamedDBs", {
151
+ enumerable: true,
152
+ configurable: true,
153
+ writable: true,
154
+ value: 96
155
+ });
156
+ /**
157
+ * Create and open a Baser instance.
158
+ *
159
+ * Constructors cannot be async, so call this factory where an opened Baser is required.
160
+ */
161
+ export function* createBaser(options = {}) {
162
+ const baser = new Baser(options);
163
+ const opened = yield* baser.reopen(options);
164
+ if (!opened) {
165
+ throw new DatabaseNotOpenError("Failed to open Baser");
166
+ }
167
+ return baser;
168
+ }
@@ -0,0 +1,19 @@
1
+ import { open } from "lmdb";
2
+ // Singleton db-lazy open - not thread safe
3
+ let db = null;
4
+ export function openDB(path = "./data.mdb") {
5
+ if (!db) {
6
+ db = open({ path, mapSize: 2e9 }); // 2GB map; tune for KERI events.
7
+ }
8
+ return db;
9
+ }
10
+ export function readValue(db, key) {
11
+ // sync get
12
+ return db.get(key) ?? null;
13
+ }
14
+ export function writeValue(db, key, value) {
15
+ // sync write
16
+ db.transactionSync(() => {
17
+ db.putSync(key, value);
18
+ });
19
+ }