@coffer-org/server 1.1.0 → 1.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.
Files changed (85) hide show
  1. package/dist/collection-io.d.ts +17 -13
  2. package/dist/collection-io.js +99 -43
  3. package/dist/db.d.ts +1 -1
  4. package/dist/db.js +22 -25
  5. package/dist/dialect.d.ts +4 -0
  6. package/dist/dialect.js +14 -0
  7. package/dist/embed.d.ts +14 -0
  8. package/dist/embed.js +46 -0
  9. package/dist/embeddings.d.ts +38 -0
  10. package/dist/embeddings.js +109 -0
  11. package/dist/entity-schema.d.ts +6 -17
  12. package/dist/entity-schema.js +103 -68
  13. package/dist/extend-io.d.ts +3 -4
  14. package/dist/extend-io.js +24 -11
  15. package/dist/extend-table.d.ts +5 -4
  16. package/dist/extend-table.js +26 -17
  17. package/dist/fs-list.d.ts +11 -0
  18. package/dist/fs-list.js +18 -0
  19. package/dist/index.d.ts +0 -1
  20. package/dist/index.js +267 -189
  21. package/dist/local-api.d.ts +4 -5
  22. package/dist/local-api.js +14 -42
  23. package/dist/migrations.d.ts +24 -0
  24. package/dist/migrations.js +198 -0
  25. package/dist/msg-log.d.ts +12 -0
  26. package/dist/msg-log.js +5 -0
  27. package/dist/mutate.d.ts +6 -4
  28. package/dist/mutate.js +47 -37
  29. package/dist/plugin-discovery.d.ts +1 -12
  30. package/dist/plugin-discovery.js +15 -28
  31. package/dist/plugin-hooks.d.ts +36 -26
  32. package/dist/plugin-hooks.js +0 -2
  33. package/dist/plugin-runtime.d.ts +0 -23
  34. package/dist/plugin-runtime.js +33 -67
  35. package/dist/plugin-state.d.ts +2 -0
  36. package/dist/plugin-state.js +11 -0
  37. package/dist/plugins-api.d.ts +0 -1
  38. package/dist/plugins-api.js +2 -10
  39. package/dist/records-api.d.ts +19 -0
  40. package/dist/records-api.js +116 -0
  41. package/dist/registry-context.d.ts +1 -7
  42. package/dist/registry-context.js +4 -3
  43. package/dist/schema-api.d.ts +1 -0
  44. package/dist/schema-api.js +11 -0
  45. package/dist/schema-sync.d.ts +0 -1
  46. package/dist/schema-sync.js +1 -10
  47. package/dist/secrets.d.ts +7 -0
  48. package/dist/secrets.js +60 -0
  49. package/dist/seeds.d.ts +14 -0
  50. package/dist/seeds.js +23 -0
  51. package/dist/temporal.d.ts +0 -16
  52. package/dist/temporal.js +0 -6
  53. package/dist/uploads.d.ts +4 -0
  54. package/dist/uploads.js +12 -0
  55. package/package.json +12 -4
  56. package/dist/collection-io.d.ts.map +0 -1
  57. package/dist/collection-io.js.map +0 -1
  58. package/dist/db.d.ts.map +0 -1
  59. package/dist/db.js.map +0 -1
  60. package/dist/entity-schema.d.ts.map +0 -1
  61. package/dist/entity-schema.js.map +0 -1
  62. package/dist/extend-io.d.ts.map +0 -1
  63. package/dist/extend-io.js.map +0 -1
  64. package/dist/extend-table.d.ts.map +0 -1
  65. package/dist/extend-table.js.map +0 -1
  66. package/dist/index.d.ts.map +0 -1
  67. package/dist/index.js.map +0 -1
  68. package/dist/local-api.d.ts.map +0 -1
  69. package/dist/local-api.js.map +0 -1
  70. package/dist/mutate.d.ts.map +0 -1
  71. package/dist/mutate.js.map +0 -1
  72. package/dist/plugin-discovery.d.ts.map +0 -1
  73. package/dist/plugin-discovery.js.map +0 -1
  74. package/dist/plugin-hooks.d.ts.map +0 -1
  75. package/dist/plugin-hooks.js.map +0 -1
  76. package/dist/plugin-runtime.d.ts.map +0 -1
  77. package/dist/plugin-runtime.js.map +0 -1
  78. package/dist/plugins-api.d.ts.map +0 -1
  79. package/dist/plugins-api.js.map +0 -1
  80. package/dist/registry-context.d.ts.map +0 -1
  81. package/dist/registry-context.js.map +0 -1
  82. package/dist/schema-sync.d.ts.map +0 -1
  83. package/dist/schema-sync.js.map +0 -1
  84. package/dist/temporal.d.ts.map +0 -1
  85. package/dist/temporal.js.map +0 -1
@@ -1,20 +1,24 @@
1
1
  import type { EntityManager } from '@mikro-orm/core';
2
2
  import type { ModuleDef } from '@coffer-org/sdk/module';
3
+ import type { LayoutEl } from '@coffer-org/sdk/fields';
3
4
  type Row = Record<string, unknown>;
4
- /** Розділити вхід на base-колонки і collection-масиви (по ключах collection-груп). */
5
- export declare function splitCollections(m: ModuleDef, input: Row): {
5
+ export declare function scalarKeys(fields: LayoutEl[]): string[];
6
+ export declare function splitAt(fields: LayoutEl[], input: Row): {
6
7
  base: Row;
7
8
  collections: Record<string, Row[]>;
8
9
  };
9
- /** Записати collection-рядки: повна заміна (delete-all + insert) у межах транзакції tx. */
10
- export declare function writeCollections(tx: EntityManager, m: ModuleDef, parentId: string, collections: Record<string, Row[]>): Promise<void>;
11
- /** Прочитати всі collection-рядки запису, nest під ключ групи. ORDER BY position. */
12
- export declare function readCollections(em: EntityManager, m: ModuleDef, parentId: string): Promise<Record<string, Row[]>>;
13
- /** nested {addr:{city}} → плоско {addr__city}. */
14
- export declare function flattenEmbedded(m: ModuleDef, input: Row): Row;
15
- /** плоско {addr__city} → nested {addr:{city}}. */
16
- export declare function nestEmbedded(m: ModuleDef, row: Row): Row;
17
- /** Видалити всі collection-рядки запису (каскад при delete батька). */
18
- export declare function deleteCollections(tx: EntityManager, m: ModuleDef, parentId: string): Promise<void>;
10
+ export declare function writeAt(tx: EntityManager, prefix: string, fields: LayoutEl[], parentId: number, collections: Record<string, Row[]>): Promise<void>;
11
+ export declare function readAt(em: EntityManager, prefix: string, fields: LayoutEl[], parentId: number): Promise<Record<string, Row[]>>;
12
+ export declare function deleteAt(tx: EntityManager, prefix: string, fields: LayoutEl[], parentId: number): Promise<void>;
13
+ export declare const splitCollections: (m: ModuleDef, input: Row) => {
14
+ base: Row;
15
+ collections: Record<string, Row[]>;
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>;
20
+ export declare function flattenEmbeddedAt(fields: LayoutEl[], input: Row): Row;
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;
19
24
  export {};
20
- //# sourceMappingURL=collection-io.d.ts.map
@@ -1,15 +1,26 @@
1
- /**
2
- * Read/Write 1↔M collection-груп. Кожна collection-група → child-таблиця
3
- * vault__module__key з parent_id+position. Дзеркало extend-table.ts, але N рядків.
4
- */
5
- import { randomUUID } from 'node:crypto';
6
1
  import { serialize } from '@mikro-orm/core';
7
- import { collectionGroups } from '@coffer-org/sdk/module';
8
- import { isGroup, isEmbeddedGroup } from '@coffer-org/sdk/fields';
9
- import { childTableName } from './entity-schema.js';
10
- /** Розділити вхід на base-колонки і collection-масиви (по ключах collection-груп). */
11
- export function splitCollections(m, input) {
12
- const keys = new Set(collectionGroups(m.fields).map((c) => c.key));
2
+ import { collectionGroups, fieldEntries, storageColumns } from '@coffer-org/sdk/module';
3
+ import { isGroup, isEmbeddedGroup, isJsonStored } from '@coffer-org/sdk/fields';
4
+ function flattenLayout(fields) {
5
+ const out = [];
6
+ for (const f of fields) {
7
+ if (isGroup(f) && !f.key)
8
+ out.push(...flattenLayout(f.fields));
9
+ else
10
+ out.push(f);
11
+ }
12
+ return out;
13
+ }
14
+ function collKeySet(fields) {
15
+ return new Set(collectionGroups(flattenLayout(fields)).map((c) => c.key));
16
+ }
17
+ export function scalarKeys(fields) {
18
+ return fieldEntries(fields)
19
+ .filter(([, fm]) => !fm.virtual)
20
+ .flatMap(([k, fm]) => storageColumns(k, fm).map(([c]) => c));
21
+ }
22
+ export function splitAt(fields, input) {
23
+ const keys = collKeySet(fields);
13
24
  const base = {};
14
25
  const collections = {};
15
26
  for (const [k, v] of Object.entries(input)) {
@@ -22,39 +33,71 @@ export function splitCollections(m, input) {
22
33
  }
23
34
  return { base, collections };
24
35
  }
25
- /** Записати collection-рядки: повна заміна (delete-all + insert) у межах транзакції tx. */
26
- export async function writeCollections(tx, m, parentId, collections) {
27
- for (const c of collectionGroups(m.fields)) {
36
+ export async function writeAt(tx, prefix, fields, parentId, collections) {
37
+ for (const c of collectionGroups(fields)) {
28
38
  const rows = collections[c.key];
29
39
  if (rows === undefined)
30
40
  continue;
31
- const table = childTableName(m.vault, m.module, c.key);
41
+ const table = `${prefix}__${c.key}`;
42
+ const childFields = c.group.fields;
43
+ const existing = serialize(await tx.find(table, { parent_id: parentId }));
44
+ for (const ex of existing)
45
+ await deleteAt(tx, table, childFields, ex.id);
32
46
  await tx.nativeDelete(table, { parent_id: parentId });
47
+ const sKeys = scalarKeys(childFields);
48
+ const jsonKeys = new Set(fieldEntries(childFields).filter(([, fm]) => isJsonStored(fm)).map(([k]) => k));
33
49
  let position = 0;
34
50
  for (const r of rows) {
35
- const childKeys = c.group.fields.filter((f) => 'key' in f).map((f) => f.key);
51
+ const flatR = flattenEmbeddedAt(childFields, r);
36
52
  const picked = {};
37
- for (const ck of childKeys)
38
- if (r[ck] !== undefined)
39
- picked[ck] = r[ck];
40
- await tx.upsert(table, { id: randomUUID(), parent_id: parentId, position, ...picked });
53
+ for (const k of sKeys)
54
+ if (flatR[k] !== undefined)
55
+ picked[k] = jsonKeys.has(k) && flatR[k] !== null && typeof flatR[k] === 'object' ? JSON.stringify(flatR[k]) : flatR[k];
56
+ const child = tx.create(table, { parent_id: parentId, position, ...picked });
57
+ await tx.flush();
58
+ const childId = child.id;
59
+ const { collections: nested } = splitAt(childFields, r);
60
+ await writeAt(tx, table, childFields, childId, nested);
41
61
  position += 1;
42
62
  }
43
63
  }
44
64
  }
45
- /** Прочитати всі collection-рядки запису, nest під ключ групи. ORDER BY position. */
46
- export async function readCollections(em, m, parentId) {
65
+ export async function readAt(em, prefix, fields, parentId) {
47
66
  const out = {};
48
- for (const c of collectionGroups(m.fields)) {
49
- const table = childTableName(m.vault, m.module, c.key);
50
- const rows = await em.find(table, { parent_id: parentId }, {
51
- orderBy: { position: 'asc' },
52
- });
53
- out[c.key] = serialize(rows).map(({ id: _id, parent_id: _pid, position: _pos, ...rest }) => rest);
67
+ for (const c of collectionGroups(fields)) {
68
+ const table = `${prefix}__${c.key}`;
69
+ const childFields = c.group.fields;
70
+ const rows = serialize(await em.find(table, { parent_id: parentId }, { orderBy: { position: 'asc' } }));
71
+ const acc = [];
72
+ for (const row of rows) {
73
+ const { id, parent_id: _pid, position: _pos, ...rest } = row;
74
+ const nested = await readAt(em, table, childFields, id);
75
+ acc.push({ ...nestEmbeddedAt(childFields, rest), ...nested });
76
+ }
77
+ out[c.key] = acc;
54
78
  }
55
79
  return out;
56
80
  }
57
- /** Зібрати embedded-групи [key, GroupEl] (рекурсивно крізь layout). */
81
+ export async function deleteAt(tx, prefix, fields, parentId) {
82
+ for (const c of collectionGroups(fields)) {
83
+ const table = `${prefix}__${c.key}`;
84
+ const childFields = c.group.fields;
85
+ const children = serialize(await tx.find(table, { parent_id: parentId }));
86
+ for (const ch of children)
87
+ await deleteAt(tx, table, childFields, ch.id);
88
+ await tx.nativeDelete(table, { parent_id: parentId });
89
+ }
90
+ }
91
+ const modPrefix = (m) => `${m.vault}__${m.module}`;
92
+ export const splitCollections = (m, input) => splitAt(m.fields, input);
93
+ export const writeCollections = (tx, m, parentId, collections) => writeAt(tx, modPrefix(m), m.fields, parentId, collections);
94
+ export const readCollections = (em, m, parentId) => readAt(em, modPrefix(m), m.fields, parentId);
95
+ export const deleteCollections = (tx, m, parentId) => deleteAt(tx, modPrefix(m), m.fields, parentId);
96
+ function columnsTargets(fields) {
97
+ return fieldEntries(fields)
98
+ .filter(([, fm]) => fm.columns)
99
+ .map(([k, fm]) => [k, Object.keys(fm.columns)]);
100
+ }
58
101
  function embeddedGroups(fields) {
59
102
  const out = [];
60
103
  for (const it of fields) {
@@ -65,10 +108,9 @@ function embeddedGroups(fields) {
65
108
  }
66
109
  return out;
67
110
  }
68
- /** nested {addr:{city}} плоско {addr__city}. */
69
- export function flattenEmbedded(m, input) {
111
+ export function flattenEmbeddedAt(fields, input) {
70
112
  const out = { ...input };
71
- for (const [key, g] of embeddedGroups(m.fields)) {
113
+ for (const [key, g] of embeddedGroups(fields)) {
72
114
  const nested = out[key];
73
115
  delete out[key];
74
116
  if (nested && typeof nested === 'object') {
@@ -78,12 +120,32 @@ export function flattenEmbedded(m, input) {
78
120
  }
79
121
  }
80
122
  }
123
+ for (const [key, subs] of columnsTargets(fields)) {
124
+ const obj = out[key];
125
+ delete out[key];
126
+ if (obj && typeof obj === 'object')
127
+ for (const s of subs)
128
+ out[`${key}__${s}`] = obj[s];
129
+ }
81
130
  return out;
82
131
  }
83
- /** плоско {addr__city} nested {addr:{city}}. */
84
- export function nestEmbedded(m, row) {
132
+ export function nestEmbeddedAt(fields, row) {
85
133
  const out = { ...row };
86
- for (const [key, g] of embeddedGroups(m.fields)) {
134
+ for (const [key, subs] of columnsTargets(fields)) {
135
+ const nested = {};
136
+ let any = false;
137
+ for (const s of subs) {
138
+ const col = `${key}__${s}`;
139
+ if (col in out) {
140
+ nested[s] = out[col];
141
+ delete out[col];
142
+ any = true;
143
+ }
144
+ }
145
+ if (any)
146
+ out[key] = nested;
147
+ }
148
+ for (const [key, g] of embeddedGroups(fields)) {
87
149
  const nested = {};
88
150
  let any = false;
89
151
  for (const f of g.fields) {
@@ -101,11 +163,5 @@ export function nestEmbedded(m, row) {
101
163
  }
102
164
  return out;
103
165
  }
104
- /** Видалити всі collection-рядки запису (каскад при delete батька). */
105
- export async function deleteCollections(tx, m, parentId) {
106
- for (const c of collectionGroups(m.fields)) {
107
- const table = childTableName(m.vault, m.module, c.key);
108
- await tx.nativeDelete(table, { parent_id: parentId });
109
- }
110
- }
111
- //# sourceMappingURL=collection-io.js.map
166
+ export const flattenEmbedded = (m, input) => flattenEmbeddedAt(m.fields, input);
167
+ export const nestEmbedded = (m, row) => nestEmbeddedAt(m.fields, row);
package/dist/db.d.ts CHANGED
@@ -2,5 +2,5 @@ import { MikroORM } from '@mikro-orm/core';
2
2
  import type { EntitySchema, EntityManager } from '@mikro-orm/core';
3
3
  export declare function getOrm(): MikroORM;
4
4
  export declare function getEm(): EntityManager;
5
+ export declare function closeDb(): Promise<void>;
5
6
  export declare function initDb(entities: EntitySchema[]): Promise<MikroORM>;
6
- //# sourceMappingURL=db.d.ts.map
package/dist/db.js CHANGED
@@ -1,45 +1,42 @@
1
- /**
2
- * Підключення до БД через MikroORM (SQLite).
3
- * ORM-шар навмисно тонкий: щоб перейти на Postgres —
4
- * 1. `npm install @mikro-orm/postgresql`,
5
- * 2. змінити SqliteDriver на PostgreSqlDriver,
6
- * 3. dbName на рядок підключення.
7
- *
8
- * WAL-режим: у MikroORM v7 @mikro-orm/sqlite працює на better-sqlite3
9
- * (синхронний драйвер, без knex-пулу), тому WAL ставимо через PRAGMA одразу
10
- * після MikroORM.init(). WAL персистентний (зберігається в заголовку файлу БД),
11
- * тож одного виклику достатньо. foreign_keys=ON MikroORM вмикає сам.
12
- */
13
1
  import { mkdirSync } from 'node:fs';
14
2
  import { dirname } from 'node:path';
15
3
  import { MikroORM, EntityCaseNamingStrategy } from '@mikro-orm/core';
16
4
  import { SqliteDriver } from '@mikro-orm/sqlite';
17
- const DB_PATH = process.env['DB_PATH'] ?? 'data/app.db';
18
- mkdirSync(dirname(DB_PATH), { recursive: true });
19
- // Синглтони — заповнюються в initDb().
20
5
  let _orm;
21
6
  export function getOrm() {
22
7
  if (!_orm)
23
- throw new Error('initDb() ще не викликано');
8
+ throw new Error('initDb() has not been called yet');
24
9
  return _orm;
25
10
  }
26
11
  export function getEm() {
27
12
  if (!_orm)
28
- throw new Error('initDb() ще не викликано');
13
+ throw new Error('initDb() has not been called yet');
29
14
  return _orm.em;
30
15
  }
16
+ export async function closeDb() {
17
+ if (!_orm)
18
+ return;
19
+ await _orm.close();
20
+ _orm = undefined;
21
+ }
31
22
  export async function initDb(entities) {
32
- _orm = await MikroORM.init({
33
- driver: SqliteDriver,
34
- dbName: DB_PATH,
23
+ const driverName = process.env['DB_DRIVER'] ?? 'sqlite';
24
+ const common = {
35
25
  entities,
36
- // Властивості → колонки дослівно (camelCase поля плагінів зберігаються як є):
37
- // camelCase-поля плагінів лишаються camelCase у БД, snake_case системні — як є.
38
26
  namingStrategy: EntityCaseNamingStrategy,
39
27
  discovery: { warnWhenNoEntities: false },
40
- });
41
- // WAL через PRAGMA (better-sqlite3, без knex-пулу — див. коментар угорі файлу).
28
+ };
29
+ if (driverName === 'postgresql') {
30
+ const { PostgreSqlDriver } = await import('@mikro-orm/postgresql');
31
+ const clientUrl = process.env['DATABASE_URL'];
32
+ if (!clientUrl)
33
+ throw new Error('DB_DRIVER=postgresql requires DATABASE_URL');
34
+ _orm = await MikroORM.init({ driver: PostgreSqlDriver, clientUrl, ...common });
35
+ return _orm;
36
+ }
37
+ const dbPath = process.env['DB_PATH'] ?? 'data/app.db';
38
+ mkdirSync(dirname(dbPath), { recursive: true });
39
+ _orm = await MikroORM.init({ driver: SqliteDriver, dbName: dbPath, ...common });
42
40
  await _orm.em.getConnection().execute('PRAGMA journal_mode = WAL');
43
41
  return _orm;
44
42
  }
45
- //# sourceMappingURL=db.js.map
@@ -0,0 +1,4 @@
1
+ import type { EntityManager } from '@mikro-orm/core';
2
+ export type Dialect = 'sqlite' | 'postgresql' | 'mysql';
3
+ export declare function dialectOf(em: EntityManager): Dialect;
4
+ export declare function getDialect(): Dialect;
@@ -0,0 +1,14 @@
1
+ import { getEm } from "./db.js";
2
+ export function dialectOf(em) {
3
+ const name = em.getPlatform().constructor.name;
4
+ if (name.startsWith('Sqlite'))
5
+ return 'sqlite';
6
+ if (name.startsWith('PostgreSql'))
7
+ return 'postgresql';
8
+ if (name.startsWith('MySql') || name.startsWith('MariaDb'))
9
+ return 'mysql';
10
+ throw new Error(`[dialect] unknown platform: ${name}`);
11
+ }
12
+ export function getDialect() {
13
+ return dialectOf(getEm());
14
+ }
@@ -0,0 +1,14 @@
1
+ export interface EmbedRec {
2
+ url: string;
3
+ title?: string;
4
+ description?: string;
5
+ thumbnail?: string;
6
+ provider?: string;
7
+ type?: 'video' | 'rich' | 'photo' | 'link';
8
+ html?: string;
9
+ width?: number;
10
+ height?: number;
11
+ }
12
+ export declare function mapOembed(url: string, o: Record<string, unknown>): EmbedRec;
13
+ export declare function mapOg(url: string, r: Record<string, unknown>): EmbedRec;
14
+ export declare function resolveEmbed(url: string): Promise<EmbedRec | null>;
package/dist/embed.js ADDED
@@ -0,0 +1,46 @@
1
+ import { extract } from '@extractus/oembed-extractor';
2
+ import ogs from 'open-graph-scraper';
3
+ const num = (v) => (typeof v === 'number' ? v : undefined);
4
+ const str = (v) => (typeof v === 'string' && v ? v : undefined);
5
+ export function mapOembed(url, o) {
6
+ const t = str(o.type);
7
+ const type = t === 'video' || t === 'rich' || t === 'photo' ? t : 'rich';
8
+ return {
9
+ url,
10
+ type,
11
+ title: str(o.title),
12
+ thumbnail: str(o.thumbnail_url),
13
+ provider: str(o.provider_name),
14
+ html: str(o.html),
15
+ width: num(o.width),
16
+ height: num(o.height),
17
+ };
18
+ }
19
+ export function mapOg(url, r) {
20
+ const imgs = r.ogImage;
21
+ return {
22
+ url,
23
+ type: 'link',
24
+ title: str(r.ogTitle),
25
+ description: str(r.ogDescription),
26
+ thumbnail: Array.isArray(imgs) ? str(imgs[0]?.url) : undefined,
27
+ provider: str(r.ogSiteName),
28
+ };
29
+ }
30
+ export async function resolveEmbed(url) {
31
+ try {
32
+ const o = (await extract(url));
33
+ if (o)
34
+ return mapOembed(url, o);
35
+ }
36
+ catch {
37
+ }
38
+ try {
39
+ const { result, error } = await ogs({ url });
40
+ if (!error && result)
41
+ return mapOg(url, result);
42
+ }
43
+ catch {
44
+ }
45
+ return null;
46
+ }
@@ -0,0 +1,38 @@
1
+ export type EmbeddingHit = {
2
+ type: string;
3
+ recordId: number;
4
+ snippet: string;
5
+ distance: number;
6
+ };
7
+ export type EmbeddingRow = {
8
+ type: string;
9
+ recordId: number;
10
+ snippet: string;
11
+ full: Float32Array;
12
+ };
13
+ export type EventRow = {
14
+ id: number;
15
+ op: string;
16
+ type: string;
17
+ record_id: number | null;
18
+ after: string | null;
19
+ };
20
+ export declare function invalidateEmbeddingCache(): void;
21
+ export declare function encodeVector(v: number[]): Buffer;
22
+ export declare function decodeVector(b: Buffer): Float32Array;
23
+ export declare function migrateEmbeddingVectorsToBlob(): Promise<number>;
24
+ export declare function cosine(a: ArrayLike<number>, b: ArrayLike<number>): number;
25
+ export declare function twoPassSearch(rows: EmbeddingRow[], queryVec: ArrayLike<number>, k: number, opts?: {
26
+ coarseDims?: number;
27
+ poolMinFactor?: number;
28
+ }): EmbeddingHit[];
29
+ export declare function upsertEmbedding(args: {
30
+ type: string;
31
+ recordId: number;
32
+ snippet: string;
33
+ vector: number[];
34
+ model: string;
35
+ }): Promise<void>;
36
+ export declare function deleteEmbedding(type: string, recordId: number): Promise<void>;
37
+ export declare function searchEmbeddings(queryVec: number[], k: number): Promise<EmbeddingHit[]>;
38
+ export declare function listEventsSince(lastId: number, limit: number): Promise<EventRow[]>;
@@ -0,0 +1,109 @@
1
+ import { getEm } from "./db.js";
2
+ import { dialectOf } from "./dialect.js";
3
+ const COARSE_DIMS = 64;
4
+ const POOL_MIN_FACTOR = 4;
5
+ let cache = null;
6
+ export function invalidateEmbeddingCache() {
7
+ cache = null;
8
+ }
9
+ async function loadCache() {
10
+ if (cache)
11
+ return cache;
12
+ const em = getEm().fork();
13
+ const rows = (await em.find('_Embedding', {}));
14
+ cache = rows.map((r) => ({
15
+ type: r.type, recordId: r.record_id, snippet: r.snippet, full: decodeVector(r.vector),
16
+ }));
17
+ return cache;
18
+ }
19
+ export function encodeVector(v) {
20
+ return Buffer.from(new Float32Array(v).buffer);
21
+ }
22
+ export function decodeVector(b) {
23
+ return new Float32Array(b.buffer, b.byteOffset, Math.floor(b.byteLength / 4));
24
+ }
25
+ function decodeBase64Vector(s) {
26
+ const buf = Buffer.from(s, 'base64');
27
+ return new Float32Array(buf.buffer, buf.byteOffset, Math.floor(buf.byteLength / 4));
28
+ }
29
+ export async function migrateEmbeddingVectorsToBlob() {
30
+ const em = getEm().fork();
31
+ if (dialectOf(em) !== 'sqlite')
32
+ return 0;
33
+ const [{ n }] = (await em.getConnection().execute("SELECT count(*) AS n FROM _embeddings WHERE typeof(vector) = 'text'"));
34
+ if (n === 0)
35
+ return 0;
36
+ const rows = (await em.find('_Embedding', {}));
37
+ let migrated = 0;
38
+ for (const r of rows) {
39
+ if (typeof r.vector !== 'string')
40
+ continue;
41
+ const f = decodeBase64Vector(r.vector);
42
+ await em.nativeUpdate('_Embedding', { type: r.type, record_id: r.record_id }, { vector: Buffer.from(f.buffer, f.byteOffset, f.byteLength) });
43
+ migrated++;
44
+ }
45
+ if (migrated > 0) {
46
+ invalidateEmbeddingCache();
47
+ }
48
+ return migrated;
49
+ }
50
+ export function cosine(a, b) {
51
+ let dot = 0, na = 0, nb = 0;
52
+ const n = Math.min(a.length, b.length);
53
+ for (let i = 0; i < n; i++) {
54
+ dot += a[i] * b[i];
55
+ na += a[i] * a[i];
56
+ nb += b[i] * b[i];
57
+ }
58
+ const denom = Math.sqrt(na) * Math.sqrt(nb);
59
+ return denom ? dot / denom : 0;
60
+ }
61
+ export function twoPassSearch(rows, queryVec, k, opts = {}) {
62
+ const N = rows.length;
63
+ if (N === 0)
64
+ return [];
65
+ const coarseDims = opts.coarseDims ?? COARSE_DIMS;
66
+ const poolMinFactor = opts.poolMinFactor ?? POOL_MIN_FACTOR;
67
+ const qFull = queryVec instanceof Float32Array ? queryVec : Float32Array.from(queryVec);
68
+ const m = Math.min(coarseDims, qFull.length);
69
+ const qSmall = qFull.subarray(0, m);
70
+ const poolSize = Math.min(N, Math.max(k * poolMinFactor, Math.ceil(N * 0.1)));
71
+ const pool = rows
72
+ .map((r, i) => ({ i, d: 1 - cosine(r.full.subarray(0, m), qSmall) }))
73
+ .sort((a, b) => a.d - b.d)
74
+ .slice(0, poolSize);
75
+ return pool
76
+ .map(({ i }) => {
77
+ const r = rows[i];
78
+ return { type: r.type, recordId: r.recordId, snippet: r.snippet, distance: 1 - cosine(r.full, qFull) };
79
+ })
80
+ .sort((a, b) => a.distance - b.distance)
81
+ .slice(0, k);
82
+ }
83
+ export async function upsertEmbedding(args) {
84
+ const em = getEm().fork();
85
+ await em.upsert('_Embedding', {
86
+ type: args.type,
87
+ record_id: args.recordId,
88
+ snippet: args.snippet,
89
+ vector: encodeVector(args.vector),
90
+ model: args.model,
91
+ dim: args.vector.length,
92
+ updated_at: new Date().toISOString(),
93
+ });
94
+ await em.flush();
95
+ invalidateEmbeddingCache();
96
+ }
97
+ export async function deleteEmbedding(type, recordId) {
98
+ const em = getEm().fork();
99
+ await em.nativeDelete('_Embedding', { type, record_id: recordId });
100
+ invalidateEmbeddingCache();
101
+ }
102
+ export async function searchEmbeddings(queryVec, k) {
103
+ return twoPassSearch(await loadCache(), queryVec, k);
104
+ }
105
+ export async function listEventsSince(lastId, limit) {
106
+ const em = getEm().fork();
107
+ const rows = (await em.find('_Event', { id: { $gt: lastId } }, { orderBy: { id: 'ASC' }, limit }));
108
+ return rows;
109
+ }
@@ -1,32 +1,21 @@
1
- /**
2
- * MikroORM EntitySchema — похідна від ModuleDef/ExtendDef (джерело істини: f.*).
3
- * DB-agnostic: імпортує лише @mikro-orm/core + типи ядра.
4
- * Колонки: text→text, integer→integer, real→float, boolean→boolean.
5
- *
6
- * Фізичну схему створює runtime-синк (schema-sync.ts) з цих описів.
7
- * Тут — лише in-memory опис для типобезпечних запитів.
8
- */
9
1
  import { EntitySchema } from '@mikro-orm/core';
10
2
  import type { ModuleDef } from '@coffer-org/sdk/module';
11
3
  import type { ExtendDef } from '@coffer-org/sdk/extend';
12
4
  import type { SettingsDef } from '@coffer-org/sdk/settings';
13
5
  import type { PluginManifest } from '@coffer-org/sdk/plugin';
14
- /** Ім'я сутності (= ім'я таблиці) для модуля: vault__module. */
15
6
  export declare function moduleTableName(vault: string, module: string): string;
16
- /** EntitySchema для одного модуля плагіна. */
17
7
  export declare function buildEntitySchema(m: ModuleDef): EntitySchema;
18
- /** EntitySchema для extend-таблиці (PK = base_id). */
19
8
  export declare function buildExtendEntitySchema(e: ExtendDef): EntitySchema;
20
- /** EntitySchema для singleton-таблиці налаштувань плагіна. PK = plugin_id. */
21
9
  export declare function buildSettingsEntitySchema(pluginId: string, settings: SettingsDef): EntitySchema;
22
- /** Ім'я child-таблиці collection-групи. */
23
10
  export declare function childTableName(vault: string, module: string, key: string): string;
24
- /** Усі child-схеми модуля (рекурсивно: collection усередині collection). */
25
11
  export declare function buildCollectionEntities(m: ModuleDef): EntitySchema[];
12
+ export declare function buildExtendCollectionEntities(e: ExtendDef): EntitySchema[];
26
13
  export declare const EventSchema: EntitySchema<any, never, import("@mikro-orm/core").EntityCtor<any>>;
27
14
  export declare const PluginRowSchema: EntitySchema<any, never, import("@mikro-orm/core").EntityCtor<any>>;
28
- /** Схеми сутностей плагінів (модулі власних vault'ів + vaultModules + extends + settings). */
15
+ export declare const MigrationRowSchema: EntitySchema<any, never, import("@mikro-orm/core").EntityCtor<any>>;
16
+ export declare const SeedRowSchema: EntitySchema<any, never, import("@mikro-orm/core").EntityCtor<any>>;
17
+ export declare const EmbeddingSchema: EntitySchema<any, never, import("@mikro-orm/core").EntityCtor<any>>;
18
+ export declare const PluginStateSchema: EntitySchema<any, never, import("@mikro-orm/core").EntityCtor<any>>;
29
19
  export declare function buildPluginEntities(plugins: PluginManifest[]): EntitySchema[];
30
- /** Системні схеми реєструються в initDb() ДО читання _plugins. */
20
+ export declare const MsgLogSchema: EntitySchema<any, never, import("@mikro-orm/core").EntityCtor<any>>;
31
21
  export declare const systemEntities: EntitySchema[];
32
- //# sourceMappingURL=entity-schema.d.ts.map