@hexis-ai/engram-server 0.3.0 → 0.4.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/dist/adapters/memory-key-store.js +9 -11
- package/dist/adapters/memory.d.ts +1 -0
- package/dist/adapters/memory.js +11 -1
- package/dist/adapters/postgres-key-store.js +5 -11
- package/dist/adapters/postgres.d.ts +1 -0
- package/dist/adapters/postgres.js +15 -0
- package/dist/key-store.d.ts +29 -0
- package/dist/key-store.js +26 -0
- package/dist/openapi.js +187 -115
- package/dist/routes/sessions.d.ts +5 -4
- package/dist/routes/sessions.js +12 -4
- package/dist/server.js +1 -0
- package/dist/storage.d.ts +8 -0
- package/openapi.json +227 -116
- package/package.json +2 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { deriveKey, hashKey, mintKey, resolveWorkspaceId, } from "../key-store";
|
|
2
2
|
/**
|
|
3
3
|
* In-process KeyStore for tests and single-node dev. Volatile.
|
|
4
4
|
*/
|
|
@@ -7,9 +7,7 @@ export class InMemoryKeyStore {
|
|
|
7
7
|
keys = new Map();
|
|
8
8
|
byHash = new Map();
|
|
9
9
|
async createWorkspace(input) {
|
|
10
|
-
const id = input
|
|
11
|
-
if (!isValidWorkspaceId(id))
|
|
12
|
-
throw new Error("invalid_workspace_id");
|
|
10
|
+
const id = resolveWorkspaceId(input);
|
|
13
11
|
const existing = this.workspaces.get(id);
|
|
14
12
|
if (existing)
|
|
15
13
|
return existing;
|
|
@@ -40,12 +38,12 @@ export class InMemoryKeyStore {
|
|
|
40
38
|
async issueKey(workspaceId, opts = {}) {
|
|
41
39
|
if (!this.workspaces.has(workspaceId))
|
|
42
40
|
throw new Error("workspace_not_found");
|
|
43
|
-
const raw =
|
|
41
|
+
const { id, hash, prefix, raw } = mintKey();
|
|
44
42
|
const row = {
|
|
45
|
-
id
|
|
43
|
+
id,
|
|
46
44
|
workspaceId,
|
|
47
|
-
keyHash:
|
|
48
|
-
prefix
|
|
45
|
+
keyHash: hash,
|
|
46
|
+
prefix,
|
|
49
47
|
...(opts.name !== undefined ? { name: opts.name } : {}),
|
|
50
48
|
createdAt: new Date().toISOString(),
|
|
51
49
|
};
|
|
@@ -80,14 +78,14 @@ export class InMemoryKeyStore {
|
|
|
80
78
|
async registerKey(workspaceId, rawKey, name) {
|
|
81
79
|
if (!this.workspaces.has(workspaceId))
|
|
82
80
|
throw new Error("workspace_not_found");
|
|
83
|
-
const hash =
|
|
81
|
+
const { id, hash, prefix } = deriveKey(rawKey);
|
|
84
82
|
if (this.byHash.has(hash))
|
|
85
83
|
return;
|
|
86
84
|
const row = {
|
|
87
|
-
id
|
|
85
|
+
id,
|
|
88
86
|
workspaceId,
|
|
89
87
|
keyHash: hash,
|
|
90
|
-
prefix
|
|
88
|
+
prefix,
|
|
91
89
|
...(name !== undefined ? { name } : {}),
|
|
92
90
|
createdAt: new Date().toISOString(),
|
|
93
91
|
};
|
|
@@ -20,6 +20,7 @@ export declare class InMemoryAdapter implements StorageAdapter {
|
|
|
20
20
|
}): Promise<void>;
|
|
21
21
|
appendEvents(sessionId: string, events: SessionEvent[]): Promise<void>;
|
|
22
22
|
getSession(sessionId: string): Promise<Session | null>;
|
|
23
|
+
getSessionEvents(sessionId: string): Promise<SessionEvent[] | null>;
|
|
23
24
|
listSessions(opts: {
|
|
24
25
|
limit: number;
|
|
25
26
|
channel?: string;
|
package/dist/adapters/memory.js
CHANGED
|
@@ -62,6 +62,12 @@ export class InMemoryAdapter {
|
|
|
62
62
|
return null;
|
|
63
63
|
return foldEvents(s.row, [...s.events.values()], new Date());
|
|
64
64
|
}
|
|
65
|
+
async getSessionEvents(sessionId) {
|
|
66
|
+
const s = this.sessions.get(sessionId);
|
|
67
|
+
if (!s)
|
|
68
|
+
return null;
|
|
69
|
+
return [...s.events.values()].sort((a, b) => a.seq - b.seq);
|
|
70
|
+
}
|
|
65
71
|
async listSessions(opts) {
|
|
66
72
|
const now = new Date();
|
|
67
73
|
const all = [];
|
|
@@ -104,7 +110,11 @@ export class InMemoryAdapter {
|
|
|
104
110
|
const existing = this.persons.get(id);
|
|
105
111
|
const next = {
|
|
106
112
|
id,
|
|
107
|
-
|
|
113
|
+
// `PersonCreate` has no notion of "clear the name" — that is
|
|
114
|
+
// `updatePerson`'s job. A missing value (and, defensively, an
|
|
115
|
+
// explicit null that bypasses the schema) means "keep what's
|
|
116
|
+
// there", matching the Postgres adapter's `COALESCE(EXCLUDED.…)`.
|
|
117
|
+
display_name: input.display_name != null
|
|
108
118
|
? input.display_name
|
|
109
119
|
: existing?.display_name ?? null,
|
|
110
120
|
created_at: existing?.created_at ?? now,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { deriveKey, hashKey, mintKey, resolveWorkspaceId, } from "../key-store";
|
|
2
2
|
import { runMigrations } from "../migrator";
|
|
3
3
|
export class PostgresKeyStore {
|
|
4
4
|
sql;
|
|
@@ -13,9 +13,7 @@ export class PostgresKeyStore {
|
|
|
13
13
|
await runMigrations(this.sql);
|
|
14
14
|
}
|
|
15
15
|
async createWorkspace(input) {
|
|
16
|
-
const id = input
|
|
17
|
-
if (!isValidWorkspaceId(id))
|
|
18
|
-
throw new Error("invalid_workspace_id");
|
|
16
|
+
const id = resolveWorkspaceId(input);
|
|
19
17
|
await this.sql `
|
|
20
18
|
INSERT INTO engram_workspaces (id, name, metadata)
|
|
21
19
|
VALUES (${id}, ${input.name ?? null}, ${(input.metadata ?? {})})
|
|
@@ -49,10 +47,7 @@ export class PostgresKeyStore {
|
|
|
49
47
|
const ws = await this.getWorkspace(workspaceId);
|
|
50
48
|
if (!ws)
|
|
51
49
|
throw new Error("workspace_not_found");
|
|
52
|
-
const raw =
|
|
53
|
-
const id = crypto.randomUUID();
|
|
54
|
-
const hash = hashKey(raw);
|
|
55
|
-
const prefix = keyPrefix(raw);
|
|
50
|
+
const { id, hash, prefix, raw } = mintKey();
|
|
56
51
|
const rows = await this.sql `
|
|
57
52
|
INSERT INTO engram_api_keys (id, workspace_id, key_hash, prefix, name)
|
|
58
53
|
VALUES (${id}, ${workspaceId}, ${hash}, ${prefix}, ${opts.name ?? null})
|
|
@@ -110,11 +105,10 @@ export class PostgresKeyStore {
|
|
|
110
105
|
const ws = await this.getWorkspace(workspaceId);
|
|
111
106
|
if (!ws)
|
|
112
107
|
throw new Error("workspace_not_found");
|
|
113
|
-
const hash =
|
|
114
|
-
const id = crypto.randomUUID();
|
|
108
|
+
const { id, hash, prefix } = deriveKey(rawKey);
|
|
115
109
|
await this.sql `
|
|
116
110
|
INSERT INTO engram_api_keys (id, workspace_id, key_hash, prefix, name)
|
|
117
|
-
VALUES (${id}, ${workspaceId}, ${hash}, ${
|
|
111
|
+
VALUES (${id}, ${workspaceId}, ${hash}, ${prefix}, ${name ?? null})
|
|
118
112
|
ON CONFLICT (key_hash) DO NOTHING
|
|
119
113
|
`;
|
|
120
114
|
}
|
|
@@ -37,6 +37,7 @@ export declare class PostgresAdapter implements StorageAdapter {
|
|
|
37
37
|
}): Promise<void>;
|
|
38
38
|
appendEvents(sessionId: string, events: SessionEvent[]): Promise<void>;
|
|
39
39
|
getSession(sessionId: string): Promise<Session | null>;
|
|
40
|
+
getSessionEvents(sessionId: string): Promise<SessionEvent[] | null>;
|
|
40
41
|
listSessions(opts: {
|
|
41
42
|
limit: number;
|
|
42
43
|
channel?: string;
|
|
@@ -110,6 +110,21 @@ export class PostgresAdapter {
|
|
|
110
110
|
};
|
|
111
111
|
return foldEvents(row, events.map((e) => e.payload), new Date());
|
|
112
112
|
}
|
|
113
|
+
async getSessionEvents(sessionId) {
|
|
114
|
+
const rows = await this.sql `
|
|
115
|
+
SELECT id FROM engram_sessions
|
|
116
|
+
WHERE workspace_id = ${this.workspaceId} AND id = ${sessionId}
|
|
117
|
+
LIMIT 1
|
|
118
|
+
`;
|
|
119
|
+
if (rows.length === 0)
|
|
120
|
+
return null;
|
|
121
|
+
const events = await this.sql `
|
|
122
|
+
SELECT payload FROM engram_events
|
|
123
|
+
WHERE workspace_id = ${this.workspaceId} AND session_id = ${sessionId}
|
|
124
|
+
ORDER BY seq
|
|
125
|
+
`;
|
|
126
|
+
return events.map((e) => e.payload);
|
|
127
|
+
}
|
|
113
128
|
async listSessions(opts) {
|
|
114
129
|
const channelFilter = opts.channel ?? null;
|
|
115
130
|
const rows = await this.sql `
|
package/dist/key-store.d.ts
CHANGED
|
@@ -61,3 +61,32 @@ export declare function generateRawKey(): string;
|
|
|
61
61
|
export declare function hashKey(raw: string): string;
|
|
62
62
|
export declare function keyPrefix(raw: string): string;
|
|
63
63
|
export declare function isValidWorkspaceId(id: string): boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Resolve the workspace id for a `createWorkspace` call: fall back to a random
|
|
66
|
+
* UUID when none is supplied, then validate. Shared by every KeyStore so the
|
|
67
|
+
* id policy can't drift between the in-memory and Postgres adapters.
|
|
68
|
+
*/
|
|
69
|
+
export declare function resolveWorkspaceId(input: {
|
|
70
|
+
id?: string;
|
|
71
|
+
}): string;
|
|
72
|
+
/** Derived material for a single API key row. */
|
|
73
|
+
export interface KeyMaterial {
|
|
74
|
+
/** Row id (primary key). */
|
|
75
|
+
id: string;
|
|
76
|
+
/** SHA-256 hash of the raw key — the only form that gets persisted. */
|
|
77
|
+
hash: string;
|
|
78
|
+
/** Human-readable prefix shown in listings. */
|
|
79
|
+
prefix: string;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Mint a brand-new random key. Returns the persistable material plus the
|
|
83
|
+
* plaintext `raw` key, which the caller must return to the user exactly once.
|
|
84
|
+
*/
|
|
85
|
+
export declare function mintKey(): KeyMaterial & {
|
|
86
|
+
raw: string;
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Derive persistable material for a caller-supplied raw key (`registerKey`).
|
|
90
|
+
* Unlike `mintKey`, the raw key already exists and is not returned.
|
|
91
|
+
*/
|
|
92
|
+
export declare function deriveKey(raw: string): KeyMaterial;
|
package/dist/key-store.js
CHANGED
|
@@ -15,3 +15,29 @@ export function keyPrefix(raw) {
|
|
|
15
15
|
export function isValidWorkspaceId(id) {
|
|
16
16
|
return WORKSPACE_ID_RE.test(id);
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Resolve the workspace id for a `createWorkspace` call: fall back to a random
|
|
20
|
+
* UUID when none is supplied, then validate. Shared by every KeyStore so the
|
|
21
|
+
* id policy can't drift between the in-memory and Postgres adapters.
|
|
22
|
+
*/
|
|
23
|
+
export function resolveWorkspaceId(input) {
|
|
24
|
+
const id = input.id ?? crypto.randomUUID();
|
|
25
|
+
if (!isValidWorkspaceId(id))
|
|
26
|
+
throw new Error("invalid_workspace_id");
|
|
27
|
+
return id;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Mint a brand-new random key. Returns the persistable material plus the
|
|
31
|
+
* plaintext `raw` key, which the caller must return to the user exactly once.
|
|
32
|
+
*/
|
|
33
|
+
export function mintKey() {
|
|
34
|
+
const raw = generateRawKey();
|
|
35
|
+
return { id: crypto.randomUUID(), hash: hashKey(raw), prefix: keyPrefix(raw), raw };
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Derive persistable material for a caller-supplied raw key (`registerKey`).
|
|
39
|
+
* Unlike `mintKey`, the raw key already exists and is not returned.
|
|
40
|
+
*/
|
|
41
|
+
export function deriveKey(raw) {
|
|
42
|
+
return { id: crypto.randomUUID(), hash: hashKey(raw), prefix: keyPrefix(raw) };
|
|
43
|
+
}
|