@hexis-ai/engram-server 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 hexis ltd.
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 ADDED
@@ -0,0 +1,79 @@
1
+ # @hexis-ai/engram-server
2
+
3
+ Hono-based HTTP server for engram with pluggable storage.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm i @hexis-ai/engram-server
9
+ # optional, for the postgres adapter:
10
+ npm i postgres
11
+ ```
12
+
13
+ ## Quick start
14
+
15
+ ```ts
16
+ import { createServer, InMemoryAdapter } from "@hexis-ai/engram-server";
17
+
18
+ const adapter = new InMemoryAdapter();
19
+ const app = createServer({
20
+ auth: (apiKey) => apiKey === process.env.ENGRAM_API_KEY
21
+ ? { workspaceId: "default", storage: adapter }
22
+ : null,
23
+ });
24
+
25
+ export default { port: 3100, fetch: app.fetch };
26
+ ```
27
+
28
+ ## With Postgres
29
+
30
+ ```ts
31
+ import postgres from "postgres";
32
+ import { createServer, PostgresAdapter } from "@hexis-ai/engram-server";
33
+
34
+ const sql = postgres(process.env.DATABASE_URL!);
35
+
36
+ // Multi-tenant: one adapter instance per workspace, scoped by workspaceId.
37
+ function adapterFor(workspaceId: string) {
38
+ return new PostgresAdapter({ workspaceId, sql });
39
+ }
40
+
41
+ await new PostgresAdapter({ workspaceId: "_init", sql }).ensureSchema();
42
+
43
+ const app = createServer({
44
+ auth: async (apiKey) => {
45
+ const ws = await resolveApiKey(apiKey); // your own keying
46
+ if (!ws) return null;
47
+ return { workspaceId: ws, storage: adapterFor(ws) };
48
+ },
49
+ });
50
+ ```
51
+
52
+ ## Routes
53
+
54
+ ```
55
+ POST /v1/sessions { id?, title?, channel?, participants? } → { id }
56
+ POST /v1/sessions/:id/events { events: SessionEvent[] } → 204
57
+ GET /v1/sessions/:id → Session
58
+ GET /v1/sessions?limit&channel → Session[]
59
+ POST /v1/search { query, options? } → { results }
60
+ ```
61
+
62
+ ## Storage adapter contract
63
+
64
+ Implement `StorageAdapter` to plug in your own backend (SQLite, Redis, Dynamo, etc.):
65
+
66
+ ```ts
67
+ interface StorageAdapter {
68
+ createSession(init): Promise<void>;
69
+ appendEvents(sessionId, events): Promise<void>;
70
+ getSession(sessionId): Promise<Session | null>;
71
+ listSessions({ limit, channel? }): Promise<Session[]>;
72
+ }
73
+ ```
74
+
75
+ Events are append-only and idempotent on `(sessionId, seq)`. Sessions materialize via the `foldEvents()` helper.
76
+
77
+ ## License
78
+
79
+ MIT
@@ -0,0 +1,20 @@
1
+ import type { Session } from "@hexis-ai/engram-core";
2
+ import type { SessionEvent, SessionInit } from "@hexis-ai/engram-sdk";
3
+ import { type StorageAdapter } from "../storage";
4
+ /**
5
+ * In-process storage adapter for tests, dev, and small single-node deploys.
6
+ * Idempotency: events keyed by (sessionId, seq) — duplicates overwrite by seq.
7
+ */
8
+ export declare class InMemoryAdapter implements StorageAdapter {
9
+ private readonly sessions;
10
+ createSession(init: SessionInit & {
11
+ id: string;
12
+ createdAt: string;
13
+ }): Promise<void>;
14
+ appendEvents(sessionId: string, events: SessionEvent[]): Promise<void>;
15
+ getSession(sessionId: string): Promise<Session | null>;
16
+ listSessions(opts: {
17
+ limit: number;
18
+ channel?: string;
19
+ }): Promise<Session[]>;
20
+ }
@@ -0,0 +1,49 @@
1
+ import { foldEvents } from "../storage";
2
+ /**
3
+ * In-process storage adapter for tests, dev, and small single-node deploys.
4
+ * Idempotency: events keyed by (sessionId, seq) — duplicates overwrite by seq.
5
+ */
6
+ export class InMemoryAdapter {
7
+ sessions = new Map();
8
+ async createSession(init) {
9
+ if (this.sessions.has(init.id))
10
+ return;
11
+ this.sessions.set(init.id, {
12
+ row: {
13
+ id: init.id,
14
+ ...(init.title ? { title: init.title } : {}),
15
+ ...(init.channel ? { channel: init.channel } : {}),
16
+ participants: init.participants ?? [],
17
+ createdAt: init.createdAt,
18
+ },
19
+ events: new Map(),
20
+ });
21
+ }
22
+ async appendEvents(sessionId, events) {
23
+ const s = this.sessions.get(sessionId);
24
+ if (!s)
25
+ throw new Error(`session not found: ${sessionId}`);
26
+ for (const ev of events)
27
+ s.events.set(ev.seq, ev);
28
+ }
29
+ async getSession(sessionId) {
30
+ const s = this.sessions.get(sessionId);
31
+ if (!s)
32
+ return null;
33
+ return foldEvents(s.row, [...s.events.values()], new Date());
34
+ }
35
+ async listSessions(opts) {
36
+ const now = new Date();
37
+ const all = [];
38
+ for (const stored of this.sessions.values()) {
39
+ if (opts.channel && stored.row.channel !== opts.channel)
40
+ continue;
41
+ all.push({
42
+ s: foldEvents(stored.row, [...stored.events.values()], now),
43
+ createdAt: stored.row.createdAt,
44
+ });
45
+ }
46
+ all.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
47
+ return all.slice(0, opts.limit).map((x) => x.s);
48
+ }
49
+ }
@@ -0,0 +1,38 @@
1
+ import type { Session } from "@hexis-ai/engram-core";
2
+ import type { SessionEvent, SessionInit } from "@hexis-ai/engram-sdk";
3
+ import { type StorageAdapter } from "../storage";
4
+ /**
5
+ * Minimal subset of `postgres` driver's tagged-template surface that this
6
+ * adapter relies on. Declared structurally so the package does not hard-import
7
+ * the `postgres` module (it is a peer dep) and tests can supply a fake.
8
+ */
9
+ export interface SqlClient {
10
+ <T = Record<string, unknown>>(strings: TemplateStringsArray, ...values: unknown[]): Promise<T[]>;
11
+ }
12
+ export interface PostgresAdapterOptions {
13
+ /** Workspace scope baked into every row. Required for multi-tenant isolation. */
14
+ workspaceId: string;
15
+ sql: SqlClient;
16
+ }
17
+ export declare class PostgresAdapter implements StorageAdapter {
18
+ private readonly workspaceId;
19
+ private readonly sql;
20
+ constructor(opts: PostgresAdapterOptions);
21
+ /**
22
+ * Create the schema. Call once at boot. Safe to invoke repeatedly.
23
+ * Postgres pg-tag template clients accept multi-statement strings via
24
+ * `sql.unsafe()` in the real driver; for portability we issue them
25
+ * separately so any tagged-template impl works.
26
+ */
27
+ ensureSchema(): Promise<void>;
28
+ createSession(init: SessionInit & {
29
+ id: string;
30
+ createdAt: string;
31
+ }): Promise<void>;
32
+ appendEvents(sessionId: string, events: SessionEvent[]): Promise<void>;
33
+ getSession(sessionId: string): Promise<Session | null>;
34
+ listSessions(opts: {
35
+ limit: number;
36
+ channel?: string;
37
+ }): Promise<Session[]>;
38
+ }
@@ -0,0 +1,116 @@
1
+ import { foldEvents } from "../storage";
2
+ const SCHEMA_SQL = `
3
+ CREATE TABLE IF NOT EXISTS engram_sessions (
4
+ workspace_id TEXT NOT NULL,
5
+ id TEXT NOT NULL,
6
+ title TEXT,
7
+ channel TEXT,
8
+ participants TEXT[] NOT NULL DEFAULT '{}',
9
+ created_at TIMESTAMPTZ NOT NULL,
10
+ PRIMARY KEY (workspace_id, id)
11
+ );
12
+
13
+ CREATE TABLE IF NOT EXISTS engram_events (
14
+ workspace_id TEXT NOT NULL,
15
+ session_id TEXT NOT NULL,
16
+ seq INTEGER NOT NULL,
17
+ type TEXT NOT NULL,
18
+ at TIMESTAMPTZ NOT NULL,
19
+ payload JSONB NOT NULL,
20
+ PRIMARY KEY (workspace_id, session_id, seq),
21
+ FOREIGN KEY (workspace_id, session_id)
22
+ REFERENCES engram_sessions(workspace_id, id) ON DELETE CASCADE
23
+ );
24
+
25
+ CREATE INDEX IF NOT EXISTS idx_engram_sessions_workspace_created
26
+ ON engram_sessions (workspace_id, created_at DESC);
27
+ `;
28
+ export class PostgresAdapter {
29
+ workspaceId;
30
+ sql;
31
+ constructor(opts) {
32
+ this.workspaceId = opts.workspaceId;
33
+ this.sql = opts.sql;
34
+ }
35
+ /**
36
+ * Create the schema. Call once at boot. Safe to invoke repeatedly.
37
+ * Postgres pg-tag template clients accept multi-statement strings via
38
+ * `sql.unsafe()` in the real driver; for portability we issue them
39
+ * separately so any tagged-template impl works.
40
+ */
41
+ async ensureSchema() {
42
+ const stmts = SCHEMA_SQL.split(/;\s*\n/).map((s) => s.trim()).filter(Boolean);
43
+ for (const stmt of stmts) {
44
+ // Tagged-template invocation with no interpolations.
45
+ await this.sql([stmt + ";"]);
46
+ }
47
+ }
48
+ async createSession(init) {
49
+ await this.sql `
50
+ INSERT INTO engram_sessions (workspace_id, id, title, channel, participants, created_at)
51
+ VALUES (
52
+ ${this.workspaceId},
53
+ ${init.id},
54
+ ${init.title ?? null},
55
+ ${init.channel ?? null},
56
+ ${init.participants ?? []},
57
+ ${init.createdAt}
58
+ )
59
+ ON CONFLICT (workspace_id, id) DO NOTHING
60
+ `;
61
+ }
62
+ async appendEvents(sessionId, events) {
63
+ if (events.length === 0)
64
+ return;
65
+ for (const ev of events) {
66
+ await this.sql `
67
+ INSERT INTO engram_events (workspace_id, session_id, seq, type, at, payload)
68
+ VALUES (
69
+ ${this.workspaceId},
70
+ ${sessionId},
71
+ ${ev.seq},
72
+ ${ev.type},
73
+ ${ev.at},
74
+ ${JSON.stringify(ev)}::jsonb
75
+ )
76
+ ON CONFLICT (workspace_id, session_id, seq) DO NOTHING
77
+ `;
78
+ }
79
+ }
80
+ async getSession(sessionId) {
81
+ const rows = await this.sql `
82
+ SELECT id, title, channel, participants, created_at
83
+ FROM engram_sessions
84
+ WHERE workspace_id = ${this.workspaceId} AND id = ${sessionId}
85
+ LIMIT 1
86
+ `;
87
+ if (rows.length === 0)
88
+ return null;
89
+ const r = rows[0];
90
+ const events = await this.sql `
91
+ SELECT payload FROM engram_events
92
+ WHERE workspace_id = ${this.workspaceId} AND session_id = ${sessionId}
93
+ ORDER BY seq
94
+ `;
95
+ const row = {
96
+ id: r.id,
97
+ ...(r.title ? { title: r.title } : {}),
98
+ ...(r.channel ? { channel: r.channel } : {}),
99
+ participants: r.participants,
100
+ createdAt: typeof r.created_at === "string" ? r.created_at : r.created_at.toISOString(),
101
+ };
102
+ return foldEvents(row, events.map((e) => e.payload), new Date());
103
+ }
104
+ async listSessions(opts) {
105
+ const channelFilter = opts.channel ?? null;
106
+ const rows = await this.sql `
107
+ SELECT id FROM engram_sessions
108
+ WHERE workspace_id = ${this.workspaceId}
109
+ AND (${channelFilter}::text IS NULL OR channel = ${channelFilter}::text)
110
+ ORDER BY created_at DESC
111
+ LIMIT ${opts.limit}
112
+ `;
113
+ const sessions = await Promise.all(rows.map((r) => this.getSession(r.id)));
114
+ return sessions.filter((s) => s !== null);
115
+ }
116
+ }
@@ -0,0 +1,4 @@
1
+ export { createServer, type CreateServerOptions, type AuthResolver, type WorkspaceContext, } from "./server";
2
+ export { foldEvents, type StorageAdapter, type SessionRow, } from "./storage";
3
+ export { InMemoryAdapter } from "./adapters/memory";
4
+ export { PostgresAdapter, type PostgresAdapterOptions, type SqlClient, } from "./adapters/postgres";
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { createServer, } from "./server";
2
+ export { foldEvents, } from "./storage";
3
+ export { InMemoryAdapter } from "./adapters/memory";
4
+ export { PostgresAdapter, } from "./adapters/postgres";
@@ -0,0 +1,35 @@
1
+ import { Hono } from "hono";
2
+ import type { StorageAdapter } from "./storage";
3
+ /**
4
+ * Resolve an API key (raw `Authorization: Bearer <key>` token) into a
5
+ * workspace context. Throwing or returning null short-circuits to 401.
6
+ */
7
+ export interface AuthResolver {
8
+ (apiKey: string): Promise<WorkspaceContext | null> | WorkspaceContext | null;
9
+ }
10
+ export interface WorkspaceContext {
11
+ workspaceId: string;
12
+ /** The storage adapter scoped to this workspace. */
13
+ storage: StorageAdapter;
14
+ }
15
+ export interface CreateServerOptions {
16
+ /** Resolves Bearer tokens into workspace contexts. */
17
+ auth: AuthResolver;
18
+ /**
19
+ * Generates session ids. Defaults to `crypto.randomUUID()`.
20
+ * Provide a custom function for deterministic ids in tests.
21
+ */
22
+ newSessionId?: () => string;
23
+ /**
24
+ * Clamps for `GET /v1/sessions?limit=…`. Defaults: 100 / 500.
25
+ */
26
+ defaultListLimit?: number;
27
+ maxListLimit?: number;
28
+ }
29
+ interface Env {
30
+ Variables: {
31
+ ctx: WorkspaceContext;
32
+ };
33
+ }
34
+ export declare function createServer(opts: CreateServerOptions): Hono<Env>;
35
+ export {};
package/dist/server.js ADDED
@@ -0,0 +1,93 @@
1
+ import { Hono } from "hono";
2
+ import { buildIndex, search, } from "@hexis-ai/engram-core";
3
+ export function createServer(opts) {
4
+ const newId = opts.newSessionId ?? (() => crypto.randomUUID());
5
+ const defaultLimit = opts.defaultListLimit ?? 100;
6
+ const maxLimit = opts.maxListLimit ?? 500;
7
+ const app = new Hono();
8
+ app.use("/v1/*", async (c, next) => {
9
+ const auth = c.req.header("authorization");
10
+ const m = auth?.match(/^Bearer\s+(.+)$/i);
11
+ if (!m)
12
+ return c.json({ error: "unauthorized" }, 401);
13
+ const ctx = await opts.auth(m[1]);
14
+ if (!ctx)
15
+ return c.json({ error: "unauthorized" }, 401);
16
+ c.set("ctx", ctx);
17
+ await next();
18
+ });
19
+ app.post("/v1/sessions", async (c) => {
20
+ const body = (await c.req.json().catch(() => null));
21
+ if (body === null)
22
+ return c.json({ error: "invalid_json" }, 400);
23
+ const id = body.id ?? newId();
24
+ const createdAt = new Date().toISOString();
25
+ await c.var.ctx.storage.createSession({
26
+ ...body,
27
+ id,
28
+ createdAt,
29
+ });
30
+ return c.json({ id });
31
+ });
32
+ app.post("/v1/sessions/:id/events", async (c) => {
33
+ const id = c.req.param("id");
34
+ const body = (await c.req.json().catch(() => null));
35
+ if (!body || !Array.isArray(body.events)) {
36
+ return c.json({ error: "events_array_required" }, 400);
37
+ }
38
+ try {
39
+ await c.var.ctx.storage.appendEvents(id, body.events);
40
+ }
41
+ catch (e) {
42
+ return c.json({ error: "session_not_found", message: e.message }, 404);
43
+ }
44
+ return c.body(null, 204);
45
+ });
46
+ app.get("/v1/sessions/:id", async (c) => {
47
+ const id = c.req.param("id");
48
+ const s = await c.var.ctx.storage.getSession(id);
49
+ if (!s)
50
+ return c.json({ error: "session_not_found" }, 404);
51
+ return c.json(s);
52
+ });
53
+ app.get("/v1/sessions", async (c) => {
54
+ const limit = clampLimit(c, defaultLimit, maxLimit);
55
+ const channel = c.req.query("channel") || undefined;
56
+ const sessions = await c.var.ctx.storage.listSessions({ limit, channel });
57
+ return c.json(sessions);
58
+ });
59
+ app.post("/v1/search", async (c) => {
60
+ const body = (await c.req.json().catch(() => null));
61
+ if (!body || !body.query)
62
+ return c.json({ error: "query_required" }, 400);
63
+ const corpus = await c.var.ctx.storage.listSessions({ limit: maxLimit });
64
+ let queryQuery;
65
+ if ("sessionId" in body.query) {
66
+ const q = await c.var.ctx.storage.getSession(body.query.sessionId);
67
+ if (!q)
68
+ return c.json({ error: "query_session_not_found" }, 404);
69
+ queryQuery = q;
70
+ }
71
+ else {
72
+ queryQuery = body.query.session;
73
+ }
74
+ const index = buildIndex(corpus);
75
+ const opts = body.options ?? {};
76
+ const queryParticipants = opts.queryParticipants ?? queryQuery.participants ?? [];
77
+ const results = search(queryQuery.steps, index, {
78
+ ...opts,
79
+ queryParticipants,
80
+ });
81
+ return c.json({ results });
82
+ });
83
+ return app;
84
+ }
85
+ function clampLimit(c, def, max) {
86
+ const raw = c.req.query("limit");
87
+ if (!raw)
88
+ return def;
89
+ const n = parseInt(raw, 10);
90
+ if (isNaN(n) || n < 1)
91
+ return def;
92
+ return Math.min(n, max);
93
+ }
@@ -0,0 +1,35 @@
1
+ import type { Session } from "@hexis-ai/engram-core";
2
+ import type { SessionEvent, SessionInit } from "@hexis-ai/engram-sdk";
3
+ /**
4
+ * Storage adapter interface. Each implementation owns persistence for
5
+ * a single workspace's sessions. Multi-tenancy is the host's concern: the
6
+ * server keys adapters by workspace id (derived from API key).
7
+ */
8
+ export interface StorageAdapter {
9
+ /** Create a session row. Returns the assigned id. */
10
+ createSession(init: SessionInit & {
11
+ id: string;
12
+ createdAt: string;
13
+ }): Promise<void>;
14
+ /** Append events for a session in batch. Idempotent on (sessionId, seq). */
15
+ appendEvents(sessionId: string, events: SessionEvent[]): Promise<void>;
16
+ /** Materialize a session (events folded into Session shape). */
17
+ getSession(sessionId: string): Promise<Session | null>;
18
+ /** List recent sessions. */
19
+ listSessions(opts: {
20
+ limit: number;
21
+ channel?: string;
22
+ }): Promise<Session[]>;
23
+ }
24
+ /**
25
+ * Pure fold of an event log into the parts a Session needs. Used by adapters
26
+ * after they retrieve raw events to assemble the materialized view.
27
+ */
28
+ export interface SessionRow {
29
+ id: string;
30
+ title?: string;
31
+ channel?: string;
32
+ participants: string[];
33
+ createdAt: string;
34
+ }
35
+ export declare function foldEvents(row: SessionRow, events: SessionEvent[], now: Date): Session;
@@ -0,0 +1,26 @@
1
+ export function foldEvents(row, events, now) {
2
+ const sorted = [...events].sort((a, b) => a.seq - b.seq);
3
+ const steps = [];
4
+ const participants = new Set(row.participants);
5
+ let title = row.title;
6
+ for (const ev of sorted) {
7
+ if (ev.type === "step") {
8
+ steps.push({ tool: ev.tool, resources: [...ev.resources] });
9
+ }
10
+ else if (ev.type === "participant") {
11
+ participants.add(ev.identityRef);
12
+ }
13
+ else if (ev.type === "title") {
14
+ title = ev.title;
15
+ }
16
+ }
17
+ const created = new Date(row.createdAt).getTime();
18
+ const daysAgo = Math.max(0, (now.getTime() - created) / 86_400_000);
19
+ return {
20
+ id: row.id,
21
+ ...(title ? { title } : {}),
22
+ steps,
23
+ daysAgo,
24
+ ...(participants.size > 0 ? { participants: [...participants] } : {}),
25
+ };
26
+ }
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@hexis-ai/engram-server",
3
+ "version": "0.1.0",
4
+ "description": "Engram server: ingest agent session events, persist via a pluggable adapter, expose search.",
5
+ "keywords": ["engram", "agents", "search", "hono", "postgres", "server"],
6
+ "homepage": "https://github.com/hexis-ai/engram#readme",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/hexis-ai/engram.git",
10
+ "directory": "packages/server"
11
+ },
12
+ "bugs": "https://github.com/hexis-ai/engram/issues",
13
+ "author": "hexis ltd.",
14
+ "license": "MIT",
15
+ "type": "module",
16
+ "main": "dist/index.js",
17
+ "types": "dist/index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "default": "./dist/index.js"
22
+ },
23
+ "./adapters/memory": {
24
+ "types": "./dist/adapters/memory.d.ts",
25
+ "default": "./dist/adapters/memory.js"
26
+ },
27
+ "./adapters/postgres": {
28
+ "types": "./dist/adapters/postgres.d.ts",
29
+ "default": "./dist/adapters/postgres.js"
30
+ }
31
+ },
32
+ "files": [
33
+ "dist"
34
+ ],
35
+ "scripts": {
36
+ "build": "rm -rf dist && tsc -p tsconfig.build.json",
37
+ "prepack": "bun run build",
38
+ "pack": "bun run build && bun pm pack",
39
+ "test": "bun test",
40
+ "type-check": "tsc --noEmit",
41
+ "dev": "bun --hot src/dev.ts"
42
+ },
43
+ "dependencies": {
44
+ "@hexis-ai/engram-core": "workspace:*",
45
+ "@hexis-ai/engram-sdk": "workspace:*",
46
+ "hono": "^4.6.0"
47
+ },
48
+ "peerDependencies": {
49
+ "postgres": "^3.4.0"
50
+ },
51
+ "peerDependenciesMeta": {
52
+ "postgres": { "optional": true }
53
+ },
54
+ "publishConfig": {
55
+ "access": "public"
56
+ }
57
+ }