@coffer-org/server 1.7.1 → 1.9.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 (61) hide show
  1. package/dist/auth-api.d.ts +1 -0
  2. package/dist/auth-api.js +20 -10
  3. package/dist/background-scheduler.d.ts +22 -0
  4. package/dist/background-scheduler.js +101 -0
  5. package/dist/collection-io.d.ts +7 -7
  6. package/dist/collection-io.js +2 -2
  7. package/dist/connector-identity.d.ts +5 -0
  8. package/dist/connector-identity.js +32 -0
  9. package/dist/embed-openai.d.ts +10 -0
  10. package/dist/embed-openai.js +28 -0
  11. package/dist/entity-schema.d.ts +9 -5
  12. package/dist/entity-schema.js +64 -10
  13. package/dist/extend-io.d.ts +5 -3
  14. package/dist/extend-io.js +17 -10
  15. package/dist/extend-table.d.ts +4 -3
  16. package/dist/extend-table.js +10 -18
  17. package/dist/field-masking.d.ts +7 -0
  18. package/dist/field-masking.js +60 -0
  19. package/dist/index-signal.d.ts +3 -0
  20. package/dist/index-signal.js +14 -0
  21. package/dist/index.js +68 -160
  22. package/dist/local-api.d.ts +3 -3
  23. package/dist/local-api.js +6 -6
  24. package/dist/mcp-http.d.ts +5 -0
  25. package/dist/mcp-http.js +37 -0
  26. package/dist/mcp-http.test-helpers.d.ts +17 -0
  27. package/dist/mcp-http.test-helpers.js +117 -0
  28. package/dist/mcp-local.d.ts +10 -0
  29. package/dist/mcp-local.js +57 -0
  30. package/dist/mcp-tools.d.ts +61 -0
  31. package/dist/mcp-tools.js +225 -0
  32. package/dist/msg-log.d.ts +0 -1
  33. package/dist/msg-log.js +2 -2
  34. package/dist/mutate.d.ts +7 -5
  35. package/dist/mutate.js +20 -8
  36. package/dist/oauth-api.d.ts +2 -0
  37. package/dist/oauth-api.js +281 -0
  38. package/dist/oauth-store.d.ts +56 -0
  39. package/dist/oauth-store.js +159 -0
  40. package/dist/plugin-hooks.d.ts +10 -0
  41. package/dist/plugin-hooks.js +8 -0
  42. package/dist/plugin-runtime.d.ts +1 -0
  43. package/dist/plugin-runtime.js +30 -6
  44. package/dist/plugins-api.d.ts +1 -1
  45. package/dist/plugins-api.js +21 -11
  46. package/dist/public-url.d.ts +4 -0
  47. package/dist/public-url.js +35 -0
  48. package/dist/records-api.d.ts +8 -8
  49. package/dist/records-api.js +35 -32
  50. package/dist/registry-context.d.ts +1 -1
  51. package/dist/registry-context.js +2 -2
  52. package/dist/schema-api.js +5 -5
  53. package/dist/settings-write.d.ts +19 -0
  54. package/dist/settings-write.js +63 -0
  55. package/dist/temporal.d.ts +3 -3
  56. package/dist/temporal.js +1 -1
  57. package/dist/thread-store.d.ts +20 -0
  58. package/dist/thread-store.js +27 -0
  59. package/dist/uploads.d.ts +1 -0
  60. package/dist/uploads.js +4 -0
  61. package/package.json +6 -6
@@ -6,6 +6,7 @@ declare module 'fastify' {
6
6
  }
7
7
  }
8
8
  export declare const PUBLIC_API_PATHS: string[];
9
+ export declare function startPasswordSession(reply: FastifyReply, login: string, password: string): Promise<AuthUser | null>;
9
10
  export declare function resolveRequestUser(req: FastifyRequest): Promise<AuthUser | null>;
10
11
  export declare function requireAdmin(req: FastifyRequest, reply: FastifyReply): boolean;
11
12
  export declare function registerAuthApi(app: FastifyInstance): Promise<void>;
package/dist/auth-api.js CHANGED
@@ -1,10 +1,12 @@
1
1
  import { field } from '@coffer-org/sdk/fields';
2
2
  import { countUsers, createUser, findUserByLogin, findUserById, getPasswordHash, listUsers, updateUser, deleteUser, countAdmins, createSession, resolveSession, deleteSession, createApiToken, resolveApiToken, listApiTokens, revokeApiToken, } from "./auth-store.js";
3
+ import { resolveAccessToken } from "./oauth-store.js";
4
+ import { mcpResource } from "./public-url.js";
3
5
  import { verifyPassword } from "./auth-crypto.js";
4
- import { maskSecrets, preserveSecrets } from "./secrets.js";
6
+ import { maskSecrets, preserveSecrets } from "./field-masking.js";
5
7
  import { rowMatch } from "./records-api.js";
6
8
  import { tokenize } from '@coffer-org/core/search';
7
- import { USERS_MODULE } from '@coffer-org/sdk/users-module';
9
+ import { USERS_SHELF } from '@coffer-org/sdk/users-shelf';
8
10
  const PASSWORD_FIELDS = [field.password({ key: 'password' })];
9
11
  const SESSION_TTL_MS = 30 * 24 * 60 * 60 * 1000;
10
12
  const COOKIE_NAME = 'sid';
@@ -24,10 +26,21 @@ function setCookie(reply, value, maxAgeSec) {
24
26
  const secure = process.env['NODE_ENV'] === 'production' ? '; Secure' : '';
25
27
  reply.header('set-cookie', `${COOKIE_NAME}=${value}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${maxAgeSec}${secure}`);
26
28
  }
29
+ export async function startPasswordSession(reply, login, password) {
30
+ if (!login || !password)
31
+ return null;
32
+ const row = await findUserByLogin(login);
33
+ if (!row || row.disabled || !(await verifyPassword(password, row.passwordHash)))
34
+ return null;
35
+ const { raw } = await createSession(row.id, SESSION_TTL_MS);
36
+ setCookie(reply, raw, SESSION_TTL_MS / 1000);
37
+ return row;
38
+ }
27
39
  export async function resolveRequestUser(req) {
28
40
  const auth = req.headers.authorization;
29
41
  if (auth?.startsWith('Bearer ')) {
30
- return resolveApiToken(auth.slice('Bearer '.length));
42
+ const raw = auth.slice('Bearer '.length);
43
+ return (await resolveApiToken(raw)) ?? (await resolveAccessToken(raw, await mcpResource(req)));
31
44
  }
32
45
  const sid = readCookie(req, COOKIE_NAME);
33
46
  if (!sid)
@@ -73,13 +86,10 @@ export async function registerAuthApi(app) {
73
86
  const body = (req.body ?? {});
74
87
  if (!body.login || !body.password)
75
88
  return reply.code(422).send({ issues: [{ field: 'login', code: 'required' }] });
76
- const row = await findUserByLogin(body.login);
77
- if (!row || row.disabled || !(await verifyPassword(body.password, row.passwordHash))) {
89
+ const user = await startPasswordSession(reply, body.login, body.password);
90
+ if (!user)
78
91
  return reply.code(401).send({ error: 'invalid_credentials' });
79
- }
80
- const { raw } = await createSession(row.id, SESSION_TTL_MS);
81
- setCookie(reply, raw, SESSION_TTL_MS / 1000);
82
- return publicUser(row);
92
+ return publicUser(user);
83
93
  });
84
94
  app.post('/api/auth/logout', async (req, reply) => {
85
95
  const sid = readCookie(req, COOKIE_NAME);
@@ -108,7 +118,7 @@ export async function registerAuthApi(app) {
108
118
  const tokens = q ? tokenize(q) : [];
109
119
  let users = await listUsers();
110
120
  if (tokens.length > 0) {
111
- users = users.filter((u) => rowMatch(USERS_MODULE, u, tokens) !== null);
121
+ users = users.filter((u) => rowMatch(USERS_SHELF, u, tokens) !== null);
112
122
  }
113
123
  return users.map(withMaskedPassword);
114
124
  });
@@ -0,0 +1,22 @@
1
+ export type TimerHandle = unknown;
2
+ export interface BackgroundTask {
3
+ name: string;
4
+ intervalMs: number;
5
+ run: () => Promise<void>;
6
+ timeoutMs?: number;
7
+ }
8
+ export interface SchedulerOpts {
9
+ startupDelayMs?: number;
10
+ gapMs?: number;
11
+ taskTimeoutMs?: number;
12
+ setTimer?: (cb: () => void, ms: number) => TimerHandle;
13
+ clearTimer?: (h: TimerHandle) => void;
14
+ }
15
+ export interface BackgroundScheduler {
16
+ register(task: BackgroundTask): void;
17
+ start(): void;
18
+ stop(): void;
19
+ }
20
+ export declare function makeScheduler(opts?: SchedulerOpts): BackgroundScheduler;
21
+ export declare function startScheduler(tasks: BackgroundTask[], opts?: SchedulerOpts): void;
22
+ export declare function stopScheduler(): void;
@@ -0,0 +1,101 @@
1
+ import { getLogger } from '@coffer-org/sdk/logger';
2
+ const log = getLogger('scheduler');
3
+ const defaultSetTimer = (cb, ms) => {
4
+ const t = setTimeout(cb, ms);
5
+ if (typeof t.unref === 'function')
6
+ t.unref();
7
+ return t;
8
+ };
9
+ export function makeScheduler(opts = {}) {
10
+ const startupDelayMs = opts.startupDelayMs ?? (Number(process.env['BG_STARTUP_DELAY_MS']) || 600_000);
11
+ const gapMs = opts.gapMs ?? (Number(process.env['BG_GAP_MS']) || 30_000);
12
+ const defaultTimeoutMs = opts.taskTimeoutMs ?? (Number(process.env['BG_TASK_TIMEOUT_MS']) || 600_000);
13
+ const setTimer = opts.setTimer ?? defaultSetTimer;
14
+ const clearTimer = opts.clearTimer ?? ((h) => clearTimeout(h));
15
+ const tasks = [];
16
+ const queue = [];
17
+ const queued = new Set();
18
+ const timers = new Set();
19
+ let running = false;
20
+ let stopped = false;
21
+ let started = false;
22
+ function arm(cb, ms) {
23
+ if (stopped)
24
+ return;
25
+ const h = setTimer(() => { timers.delete(h); cb(); }, ms);
26
+ timers.add(h);
27
+ }
28
+ function enqueue(task) {
29
+ if (stopped || queued.has(task.name))
30
+ return;
31
+ queued.add(task.name);
32
+ queue.push(task);
33
+ pump();
34
+ }
35
+ function pump() {
36
+ if (stopped || running)
37
+ return;
38
+ const task = queue.shift();
39
+ if (!task)
40
+ return;
41
+ queued.delete(task.name);
42
+ running = true;
43
+ void runOne(task).finally(() => {
44
+ running = false;
45
+ arm(() => enqueue(task), task.intervalMs);
46
+ if (queue.length)
47
+ arm(pump, gapMs);
48
+ });
49
+ }
50
+ async function runOne(task) {
51
+ const timeoutMs = task.timeoutMs ?? defaultTimeoutMs;
52
+ let to;
53
+ const timeout = new Promise((resolve) => {
54
+ to = setTimer(() => { timers.delete(to); log.warn(`${task.name}: timed out after ${timeoutMs}ms`); resolve(); }, timeoutMs);
55
+ timers.add(to);
56
+ });
57
+ try {
58
+ let runPromise;
59
+ try {
60
+ runPromise = task.run();
61
+ }
62
+ catch (e) {
63
+ runPromise = Promise.reject(e);
64
+ }
65
+ await Promise.race([runPromise.catch((e) => log.warn(`${task.name}: ${e.message}`)), timeout]);
66
+ }
67
+ finally {
68
+ clearTimer(to);
69
+ timers.delete(to);
70
+ }
71
+ }
72
+ return {
73
+ register(task) { tasks.push(task); },
74
+ start() {
75
+ if (started || stopped)
76
+ return;
77
+ started = true;
78
+ arm(() => { for (const t of tasks)
79
+ enqueue(t); }, startupDelayMs);
80
+ },
81
+ stop() {
82
+ stopped = true;
83
+ for (const h of timers)
84
+ clearTimer(h);
85
+ timers.clear();
86
+ queue.length = 0;
87
+ queued.clear();
88
+ },
89
+ };
90
+ }
91
+ let current;
92
+ export function startScheduler(tasks, opts = {}) {
93
+ current = makeScheduler(opts);
94
+ for (const t of tasks)
95
+ current.register(t);
96
+ current.start();
97
+ }
98
+ export function stopScheduler() {
99
+ current?.stop();
100
+ current = undefined;
101
+ }
@@ -1,5 +1,5 @@
1
1
  import type { EntityManager } from '@mikro-orm/core';
2
- import type { ModuleDef } from '@coffer-org/sdk/module';
2
+ import type { ShelfDef } from '@coffer-org/sdk/shelf';
3
3
  import type { LayoutEl } from '@coffer-org/sdk/fields';
4
4
  type Row = Record<string, unknown>;
5
5
  export declare function scalarKeys(fields: LayoutEl[]): string[];
@@ -10,15 +10,15 @@ export declare function splitAt(fields: LayoutEl[], input: Row): {
10
10
  export declare function writeAt(tx: EntityManager, prefix: string, fields: LayoutEl[], parentId: number, collections: Record<string, Row[]>): Promise<void>;
11
11
  export declare function readAt(em: EntityManager, prefix: string, fields: LayoutEl[], parentId: number): Promise<Record<string, Row[]>>;
12
12
  export declare function deleteAt(tx: EntityManager, prefix: string, fields: LayoutEl[], parentId: number): Promise<void>;
13
- export declare const splitCollections: (m: ModuleDef, input: Row) => {
13
+ export declare const splitCollections: (m: ShelfDef, input: Row) => {
14
14
  base: Row;
15
15
  collections: Record<string, Row[]>;
16
16
  };
17
- export declare const writeCollections: (tx: EntityManager, m: ModuleDef, parentId: number, collections: Record<string, Row[]>) => Promise<void>;
18
- export declare const readCollections: (em: EntityManager, m: ModuleDef, parentId: number) => Promise<Record<string, Row[]>>;
19
- export declare const deleteCollections: (tx: EntityManager, m: ModuleDef, parentId: number) => Promise<void>;
17
+ export declare const writeCollections: (tx: EntityManager, m: ShelfDef, parentId: number, collections: Record<string, Row[]>) => Promise<void>;
18
+ export declare const readCollections: (em: EntityManager, m: ShelfDef, parentId: number) => Promise<Record<string, Row[]>>;
19
+ export declare const deleteCollections: (tx: EntityManager, m: ShelfDef, parentId: number) => Promise<void>;
20
20
  export declare function flattenEmbeddedAt(fields: LayoutEl[], input: Row): Row;
21
21
  export declare function nestEmbeddedAt(fields: LayoutEl[], row: Row): Row;
22
- export declare const flattenEmbedded: (m: ModuleDef, input: Row) => Row;
23
- export declare const nestEmbedded: (m: ModuleDef, row: Row) => Row;
22
+ export declare const flattenEmbedded: (m: ShelfDef, input: Row) => Row;
23
+ export declare const nestEmbedded: (m: ShelfDef, row: Row) => Row;
24
24
  export {};
@@ -1,5 +1,5 @@
1
1
  import { serialize } from '@mikro-orm/core';
2
- import { collectionGroups, fieldEntries, storageColumns } from '@coffer-org/sdk/module';
2
+ import { collectionGroups, fieldEntries, storageColumns } from '@coffer-org/sdk/shelf';
3
3
  import { isGroup, isEmbeddedGroup, isJsonStored } from '@coffer-org/sdk/fields';
4
4
  function flattenLayout(fields) {
5
5
  const out = [];
@@ -88,7 +88,7 @@ export async function deleteAt(tx, prefix, fields, parentId) {
88
88
  await tx.nativeDelete(table, { parent_id: parentId });
89
89
  }
90
90
  }
91
- const modPrefix = (m) => `${m.vault}__${m.module}`;
91
+ const modPrefix = (m) => `${m.library}__${m.shelf}`;
92
92
  export const splitCollections = (m, input) => splitAt(m.fields, input);
93
93
  export const writeCollections = (tx, m, parentId, collections) => writeAt(tx, modPrefix(m), m.fields, parentId, collections);
94
94
  export const readCollections = (em, m, parentId) => readAt(em, modPrefix(m), m.fields, parentId);
@@ -0,0 +1,5 @@
1
+ import type { AuthUser } from './auth-store.ts';
2
+ export declare function linkConnectorIdentity(connector: string, connectorUserId: string, userId: number): Promise<void>;
3
+ export declare function resolveConnectorUser(connector: string, connectorUserId: string): Promise<AuthUser | null>;
4
+ export declare function firstAdmin(): Promise<AuthUser | null>;
5
+ export declare function linkToFirstAdmin(connector: string, connectorUserId: string): Promise<void>;
@@ -0,0 +1,32 @@
1
+ import { getEm } from "./db.js";
2
+ import { findUserById, listUsers } from "./auth-store.js";
3
+ export async function linkConnectorIdentity(connector, connectorUserId, userId) {
4
+ const em = getEm().fork();
5
+ await em.upsert('_ConnectorIdentity', {
6
+ connector,
7
+ connector_user_id: connectorUserId,
8
+ user_id: userId,
9
+ });
10
+ await em.flush();
11
+ }
12
+ export async function resolveConnectorUser(connector, connectorUserId) {
13
+ const em = getEm().fork();
14
+ const row = (await em.findOne('_ConnectorIdentity', {
15
+ connector,
16
+ connector_user_id: connectorUserId,
17
+ }));
18
+ if (!row)
19
+ return null;
20
+ return findUserById(row.user_id);
21
+ }
22
+ export async function firstAdmin() {
23
+ const users = await listUsers();
24
+ return users.find((u) => u.role === 'admin' && !u.disabled) ?? null;
25
+ }
26
+ export async function linkToFirstAdmin(connector, connectorUserId) {
27
+ if (await resolveConnectorUser(connector, connectorUserId))
28
+ return;
29
+ const admin = await firstAdmin();
30
+ if (admin)
31
+ await linkConnectorIdentity(connector, connectorUserId, admin.id);
32
+ }
@@ -0,0 +1,10 @@
1
+ export declare const EMBED_MODEL = "text-embedding-3-small";
2
+ export declare const EMBED_DIM = 1024;
3
+ export declare function embedBatch(texts: string[], apiKey: string, fetchImpl?: typeof fetch): Promise<{
4
+ vectors: number[][];
5
+ tokens: number;
6
+ }>;
7
+ export declare function embedOne(text: string, apiKey: string, fetchImpl?: typeof fetch): Promise<{
8
+ vector: number[];
9
+ tokens: number;
10
+ }>;
@@ -0,0 +1,28 @@
1
+ const OPENAI_URL = 'https://api.openai.com/v1/embeddings';
2
+ export const EMBED_MODEL = 'text-embedding-3-small';
3
+ export const EMBED_DIM = 1024;
4
+ export async function embedBatch(texts, apiKey, fetchImpl = fetch) {
5
+ if (texts.length === 0)
6
+ return { vectors: [], tokens: 0 };
7
+ if (!apiKey)
8
+ throw new Error('embeddings: missing API key (OPENAI_API_KEY)');
9
+ const res = await fetchImpl(OPENAI_URL, {
10
+ method: 'POST',
11
+ headers: { 'content-type': 'application/json', authorization: `Bearer ${apiKey}` },
12
+ body: JSON.stringify({ model: EMBED_MODEL, input: texts, dimensions: EMBED_DIM }),
13
+ });
14
+ if (!res.ok) {
15
+ const body = await res.text().catch(() => '');
16
+ throw new Error(`openai embeddings ${res.status}: ${body.slice(0, 200)}`);
17
+ }
18
+ const json = (await res.json());
19
+ const vectors = [...json.data].sort((a, b) => a.index - b.index).map((d) => d.embedding);
20
+ return { vectors, tokens: json.usage?.total_tokens ?? 0 };
21
+ }
22
+ export async function embedOne(text, apiKey, fetchImpl = fetch) {
23
+ const { vectors, tokens } = await embedBatch([text], apiKey, fetchImpl);
24
+ const vector = vectors[0];
25
+ if (!vector)
26
+ throw new Error('embeddings: empty response');
27
+ return { vector, tokens };
28
+ }
@@ -1,14 +1,14 @@
1
1
  import { EntitySchema } from '@mikro-orm/core';
2
- import type { ModuleDef } from '@coffer-org/sdk/module';
2
+ import type { ShelfDef } from '@coffer-org/sdk/shelf';
3
3
  import type { ExtendDef } from '@coffer-org/sdk/extend';
4
4
  import type { SettingsDef } from '@coffer-org/sdk/settings';
5
5
  import type { PluginManifest } from '@coffer-org/sdk/plugin';
6
- export declare function moduleTableName(vault: string, module: string): string;
7
- export declare function buildEntitySchema(m: ModuleDef): EntitySchema;
6
+ export declare function shelfTableName(library: string, shelf: string): string;
7
+ export declare function buildEntitySchema(m: ShelfDef): EntitySchema;
8
8
  export declare function buildExtendEntitySchema(e: ExtendDef): EntitySchema;
9
9
  export declare function buildSettingsEntitySchema(pluginId: string, settings: SettingsDef): EntitySchema;
10
- export declare function childTableName(vault: string, module: string, key: string): string;
11
- export declare function buildCollectionEntities(m: ModuleDef): EntitySchema[];
10
+ export declare function childTableName(library: string, shelf: string, key: string): string;
11
+ export declare function buildCollectionEntities(m: ShelfDef): EntitySchema[];
12
12
  export declare function buildExtendCollectionEntities(e: ExtendDef): EntitySchema[];
13
13
  export declare const EventSchema: EntitySchema<any, never, import("@mikro-orm/core").EntityCtor<any>>;
14
14
  export declare const PluginRowSchema: EntitySchema<any, never, import("@mikro-orm/core").EntityCtor<any>>;
@@ -16,9 +16,13 @@ export declare const MigrationRowSchema: EntitySchema<any, never, import("@mikro
16
16
  export declare const SeedRowSchema: EntitySchema<any, never, import("@mikro-orm/core").EntityCtor<any>>;
17
17
  export declare const EmbeddingSchema: EntitySchema<any, never, import("@mikro-orm/core").EntityCtor<any>>;
18
18
  export declare const PluginStateSchema: EntitySchema<any, never, import("@mikro-orm/core").EntityCtor<any>>;
19
+ export declare const ThreadMessageSchema: EntitySchema<any, never, import("@mikro-orm/core").EntityCtor<any>>;
19
20
  export declare function buildPluginEntities(plugins: PluginManifest[]): EntitySchema[];
20
21
  export declare const MsgLogSchema: EntitySchema<any, never, import("@mikro-orm/core").EntityCtor<any>>;
21
22
  export declare const UserSchema: EntitySchema<any, never, import("@mikro-orm/core").EntityCtor<any>>;
22
23
  export declare const SessionSchema: EntitySchema<any, never, import("@mikro-orm/core").EntityCtor<any>>;
23
24
  export declare const ApiTokenSchema: EntitySchema<any, never, import("@mikro-orm/core").EntityCtor<any>>;
25
+ export declare const OAuthClientSchema: EntitySchema<any, never, import("@mikro-orm/core").EntityCtor<any>>;
26
+ export declare const OAuthCodeSchema: EntitySchema<any, never, import("@mikro-orm/core").EntityCtor<any>>;
27
+ export declare const OAuthTokenSchema: EntitySchema<any, never, import("@mikro-orm/core").EntityCtor<any>>;
24
28
  export declare const systemEntities: EntitySchema[];
@@ -1,5 +1,5 @@
1
1
  import { EntitySchema } from '@mikro-orm/core';
2
- import { fieldEntries, collectionGroups, storageColumns } from '@coffer-org/sdk/module';
2
+ import { fieldEntries, collectionGroups, storageColumns } from '@coffer-org/sdk/shelf';
3
3
  function mikroType(col) {
4
4
  if (col === 'integer')
5
5
  return 'integer';
@@ -34,8 +34,8 @@ function addFieldProps(properties, fields, plainNullable) {
34
34
  }
35
35
  }
36
36
  }
37
- export function moduleTableName(vault, module) {
38
- return `${vault}__${module}`;
37
+ export function shelfTableName(library, shelf) {
38
+ return `${library}__${shelf}`;
39
39
  }
40
40
  export function buildEntitySchema(m) {
41
41
  const properties = {
@@ -44,7 +44,7 @@ export function buildEntitySchema(m) {
44
44
  updated_at: { type: 'text' },
45
45
  };
46
46
  addFieldProps(properties, m.fields, (f) => !f.required);
47
- const name = moduleTableName(m.vault, m.module);
47
+ const name = shelfTableName(m.library, m.shelf);
48
48
  return new EntitySchema({ name, tableName: name, properties });
49
49
  }
50
50
  export function buildExtendEntitySchema(e) {
@@ -63,8 +63,8 @@ export function buildSettingsEntitySchema(pluginId, settings) {
63
63
  const name = `_settings__${pluginId}`;
64
64
  return new EntitySchema({ name, tableName: name, properties });
65
65
  }
66
- export function childTableName(vault, module, key) {
67
- return `${vault}__${module}__${key}`;
66
+ export function childTableName(library, shelf, key) {
67
+ return `${library}__${shelf}__${key}`;
68
68
  }
69
69
  function buildChildSchemaAt(prefix, c) {
70
70
  const properties = {
@@ -85,7 +85,7 @@ function buildCollectionEntitiesAt(prefix, fields) {
85
85
  return out;
86
86
  }
87
87
  export function buildCollectionEntities(m) {
88
- return buildCollectionEntitiesAt(`${m.vault}__${m.module}`, m.fields);
88
+ return buildCollectionEntitiesAt(`${m.library}__${m.shelf}`, m.fields);
89
89
  }
90
90
  export function buildExtendCollectionEntities(e) {
91
91
  return buildCollectionEntitiesAt(`extend__${e.id}`, e.fields);
@@ -151,10 +151,24 @@ export const PluginStateSchema = new EntitySchema({
151
151
  value: { type: 'text' },
152
152
  },
153
153
  });
154
+ export const ThreadMessageSchema = new EntitySchema({
155
+ name: '_ThreadMessage',
156
+ tableName: '_thread_message',
157
+ properties: {
158
+ connector: { type: 'text', primary: true },
159
+ chat_id: { type: 'text', primary: true },
160
+ msg_id: { type: 'text', primary: true },
161
+ role: { type: 'text' },
162
+ sender: { type: 'text', nullable: true },
163
+ text: { type: 'text' },
164
+ ts: { type: 'integer' },
165
+ reply_to_id: { type: 'text', nullable: true },
166
+ },
167
+ });
154
168
  export function buildPluginEntities(plugins) {
155
169
  return plugins.flatMap((p) => [
156
- ...(p.vaults ?? []).flatMap((v) => v.modules.flatMap((m) => [buildEntitySchema(m), ...buildCollectionEntities(m)])),
157
- ...(p.vaultModules ?? []).flatMap((m) => [buildEntitySchema(m), ...buildCollectionEntities(m)]),
170
+ ...(p.libraries ?? []).flatMap((v) => v.shelves.flatMap((m) => [buildEntitySchema(m), ...buildCollectionEntities(m)])),
171
+ ...(p.libraryShelves ?? []).flatMap((m) => [buildEntitySchema(m), ...buildCollectionEntities(m)]),
158
172
  ...(p.extends_ ?? []).flatMap((e) => [buildExtendEntitySchema(e), ...buildExtendCollectionEntities(e)]),
159
173
  ...(p.settings && p.settings.fields.length > 0 ? [buildSettingsEntitySchema(p.id, p.settings)] : []),
160
174
  ]);
@@ -170,7 +184,6 @@ export const MsgLogSchema = new EntitySchema({
170
184
  user_id: { type: 'text' },
171
185
  role: { type: 'text' },
172
186
  text: { type: 'text' },
173
- session_id: { type: 'text', nullable: true },
174
187
  tokens_in: { type: 'integer', nullable: true },
175
188
  tokens_out: { type: 'integer', nullable: true },
176
189
  ms: { type: 'integer', nullable: true },
@@ -212,8 +225,49 @@ export const ApiTokenSchema = new EntitySchema({
212
225
  revoked: { type: 'integer' },
213
226
  },
214
227
  });
228
+ export const OAuthClientSchema = new EntitySchema({
229
+ name: '_OAuthClient',
230
+ tableName: '_oauth_clients',
231
+ properties: {
232
+ client_id: { type: 'text', primary: true },
233
+ client_name: { type: 'text' },
234
+ redirect_uris: { type: 'text' },
235
+ created_at: { type: 'text' },
236
+ },
237
+ });
238
+ export const OAuthCodeSchema = new EntitySchema({
239
+ name: '_OAuthCode',
240
+ tableName: '_oauth_codes',
241
+ properties: {
242
+ code_hash: { type: 'text', primary: true },
243
+ client_id: { type: 'text' },
244
+ user_id: { type: 'integer' },
245
+ redirect_uri: { type: 'text' },
246
+ code_challenge: { type: 'text' },
247
+ resource: { type: 'text' },
248
+ expires_at: { type: 'text' },
249
+ created_at: { type: 'text' },
250
+ },
251
+ });
252
+ export const OAuthTokenSchema = new EntitySchema({
253
+ name: '_OAuthToken',
254
+ tableName: '_oauth_tokens',
255
+ properties: {
256
+ token_hash: { type: 'text', primary: true },
257
+ kind: { type: 'text' },
258
+ client_id: { type: 'text' },
259
+ user_id: { type: 'integer' },
260
+ resource: { type: 'text' },
261
+ expires_at: { type: 'text' },
262
+ created_at: { type: 'text' },
263
+ last_used_at: { type: 'text', nullable: true },
264
+ revoked: { type: 'integer' },
265
+ },
266
+ });
215
267
  export const systemEntities = [
216
268
  EventSchema, PluginRowSchema, MigrationRowSchema, SeedRowSchema,
217
269
  EmbeddingSchema, PluginStateSchema, MsgLogSchema,
218
270
  UserSchema, SessionSchema, ApiTokenSchema,
271
+ OAuthClientSchema, OAuthCodeSchema, OAuthTokenSchema,
272
+ ThreadMessageSchema,
219
273
  ];
@@ -1,7 +1,9 @@
1
+ import type { EntityManager } from '@mikro-orm/core';
1
2
  export declare function splitBody(body: unknown): {
2
3
  base: Record<string, unknown>;
3
4
  extData: Record<string, Record<string, unknown>>;
4
5
  };
5
- export declare function deleteExtends(vault: string, module: string, id: number): Promise<void>;
6
- export declare function validateExtends(vault: string, module: string, extData: Record<string, Record<string, unknown>>): void;
7
- export declare function saveExtends(vault: string, module: string, baseId: number, extData: Record<string, Record<string, unknown>>): Promise<void>;
6
+ export declare function deleteExtends(em: EntityManager, library: string, shelf: string, id: number): Promise<void>;
7
+ export declare function readExtends(em: EntityManager, library: string, shelf: string, id: number): Promise<Record<string, unknown>>;
8
+ export declare function validateExtends(library: string, shelf: string, extData: Record<string, Record<string, unknown>>): void;
9
+ export declare function saveExtends(em: EntityManager, library: string, shelf: string, baseId: number, extData: Record<string, Record<string, unknown>>): Promise<void>;
package/dist/extend-io.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { getExtendsFor } from "./registry-context.js";
2
2
  import { buildExtendZodPartial } from '@coffer-org/sdk/extend';
3
- import { upsertExtendRecord, deleteExtendRecord } from "./extend-table.js";
3
+ import { upsertExtendRecord, deleteExtendRecord, getExtendRecord } from "./extend-table.js";
4
4
  import { toIssue, ValidationError } from "./mutate.js";
5
5
  export function splitBody(body) {
6
6
  const raw = (body ?? {});
@@ -14,14 +14,21 @@ export function splitBody(body) {
14
14
  }
15
15
  return { base, extData };
16
16
  }
17
- export async function deleteExtends(vault, module, id) {
18
- for (const e of getExtendsFor(vault, module)) {
19
- await deleteExtendRecord(e, id);
17
+ export async function deleteExtends(em, library, shelf, id) {
18
+ for (const e of getExtendsFor(library, shelf)) {
19
+ await deleteExtendRecord(em, e, id);
20
20
  }
21
21
  }
22
- export function validateExtends(vault, module, extData) {
22
+ export async function readExtends(em, library, shelf, id) {
23
+ const out = {};
24
+ for (const e of getExtendsFor(library, shelf)) {
25
+ out[e.id] = (await getExtendRecord(e, id, em)) ?? null;
26
+ }
27
+ return out;
28
+ }
29
+ export function validateExtends(library, shelf, extData) {
23
30
  const issues = [];
24
- for (const e of getExtendsFor(vault, module)) {
31
+ for (const e of getExtendsFor(library, shelf)) {
25
32
  const data = extData[e.id];
26
33
  if (data === undefined)
27
34
  continue;
@@ -33,15 +40,15 @@ export function validateExtends(vault, module, extData) {
33
40
  if (issues.length)
34
41
  throw new ValidationError(issues);
35
42
  }
36
- export async function saveExtends(vault, module, baseId, extData) {
37
- validateExtends(vault, module, extData);
38
- for (const e of getExtendsFor(vault, module)) {
43
+ export async function saveExtends(em, library, shelf, baseId, extData) {
44
+ validateExtends(library, shelf, extData);
45
+ for (const e of getExtendsFor(library, shelf)) {
39
46
  const data = extData[e.id];
40
47
  if (data === undefined)
41
48
  continue;
42
49
  const parsed = buildExtendZodPartial(e).safeParse(data);
43
50
  if (parsed.success && Object.keys(parsed.data).length > 0) {
44
- await upsertExtendRecord(e, baseId, parsed.data);
51
+ await upsertExtendRecord(em, e, baseId, parsed.data);
45
52
  }
46
53
  }
47
54
  }
@@ -1,7 +1,8 @@
1
+ import type { EntityManager } from '@mikro-orm/core';
1
2
  import type { ExtendDef } from '@coffer-org/sdk/extend';
2
3
  type Row = Record<string, unknown>;
3
4
  export declare function extendEntityName(e: ExtendDef): string;
4
- export declare function getExtendRecord(e: ExtendDef, baseId: number): Promise<Row | undefined>;
5
- export declare function upsertExtendRecord(e: ExtendDef, baseId: number, data: Row): Promise<void>;
6
- export declare function deleteExtendRecord(e: ExtendDef, baseId: number): Promise<void>;
5
+ export declare function getExtendRecord(e: ExtendDef, baseId: number, em?: EntityManager): Promise<Row | undefined>;
6
+ export declare function upsertExtendRecord(em: EntityManager, e: ExtendDef, baseId: number, data: Row): Promise<void>;
7
+ export declare function deleteExtendRecord(em: EntityManager, e: ExtendDef, baseId: number): Promise<void>;
7
8
  export {};
@@ -4,8 +4,8 @@ import { splitAt, writeAt, readAt, deleteAt } from "./collection-io.js";
4
4
  export function extendEntityName(e) {
5
5
  return `extend__${e.id}`;
6
6
  }
7
- export async function getExtendRecord(e, baseId) {
8
- const fork = getEm().fork();
7
+ export async function getExtendRecord(e, baseId, em) {
8
+ const fork = em ?? getEm().fork();
9
9
  const name = extendEntityName(e);
10
10
  const row = await fork.findOne(name, { base_id: baseId });
11
11
  if (!row)
@@ -14,24 +14,16 @@ export async function getExtendRecord(e, baseId) {
14
14
  const collections = await readAt(fork, name, e.fields, baseId);
15
15
  return { ...flat, ...collections };
16
16
  }
17
- export async function upsertExtendRecord(e, baseId, data) {
17
+ export async function upsertExtendRecord(em, e, baseId, data) {
18
18
  const name = extendEntityName(e);
19
19
  const { base, collections } = splitAt(e.fields, data);
20
- await getEm()
21
- .fork()
22
- .transactional(async (tx) => {
23
- await tx.upsert(name, { base_id: baseId, ...base });
24
- await writeAt(tx, name, e.fields, baseId, collections);
25
- });
20
+ await em.upsert(name, { base_id: baseId, ...base });
21
+ await writeAt(em, name, e.fields, baseId, collections);
26
22
  }
27
- export async function deleteExtendRecord(e, baseId) {
23
+ export async function deleteExtendRecord(em, e, baseId) {
28
24
  const name = extendEntityName(e);
29
- await getEm()
30
- .fork()
31
- .transactional(async (tx) => {
32
- await deleteAt(tx, name, e.fields, baseId);
33
- const ref = tx.getReference(name, baseId);
34
- tx.remove(ref);
35
- await tx.flush();
36
- });
25
+ await deleteAt(em, name, e.fields, baseId);
26
+ const ref = em.getReference(name, baseId);
27
+ em.remove(ref);
28
+ await em.flush();
37
29
  }
@@ -0,0 +1,7 @@
1
+ import type { LayoutEl } from '@coffer-org/sdk/fields';
2
+ export declare const MASK = "********";
3
+ export declare function passwordKeys(fields: LayoutEl[]): string[];
4
+ export declare function maskSecrets(fields: LayoutEl[], row: Record<string, unknown>): Record<string, unknown>;
5
+ export declare function preserveSecrets(fields: LayoutEl[], incoming: Record<string, unknown>, existing: Record<string, unknown>): Record<string, unknown>;
6
+ export declare function maskTree(fields: LayoutEl[], obj: Record<string, unknown>): Record<string, unknown>;
7
+ export declare function preserveTree(fields: LayoutEl[], incoming: Record<string, unknown>, existing: Record<string, unknown>): Record<string, unknown>;