@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.
- package/dist/collection-io.d.ts +17 -13
- package/dist/collection-io.js +99 -43
- package/dist/db.d.ts +1 -1
- package/dist/db.js +22 -25
- package/dist/dialect.d.ts +4 -0
- package/dist/dialect.js +14 -0
- package/dist/embed.d.ts +14 -0
- package/dist/embed.js +46 -0
- package/dist/embeddings.d.ts +38 -0
- package/dist/embeddings.js +109 -0
- package/dist/entity-schema.d.ts +6 -17
- package/dist/entity-schema.js +103 -68
- package/dist/extend-io.d.ts +3 -4
- package/dist/extend-io.js +24 -11
- package/dist/extend-table.d.ts +5 -4
- package/dist/extend-table.js +26 -17
- package/dist/fs-list.d.ts +11 -0
- package/dist/fs-list.js +18 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +267 -189
- package/dist/local-api.d.ts +4 -5
- package/dist/local-api.js +14 -42
- package/dist/migrations.d.ts +24 -0
- package/dist/migrations.js +198 -0
- package/dist/msg-log.d.ts +12 -0
- package/dist/msg-log.js +5 -0
- package/dist/mutate.d.ts +6 -4
- package/dist/mutate.js +47 -37
- package/dist/plugin-discovery.d.ts +1 -12
- package/dist/plugin-discovery.js +15 -28
- package/dist/plugin-hooks.d.ts +36 -26
- package/dist/plugin-hooks.js +0 -2
- package/dist/plugin-runtime.d.ts +0 -23
- package/dist/plugin-runtime.js +33 -67
- package/dist/plugin-state.d.ts +2 -0
- package/dist/plugin-state.js +11 -0
- package/dist/plugins-api.d.ts +0 -1
- package/dist/plugins-api.js +2 -10
- package/dist/records-api.d.ts +19 -0
- package/dist/records-api.js +116 -0
- package/dist/registry-context.d.ts +1 -7
- package/dist/registry-context.js +4 -3
- package/dist/schema-api.d.ts +1 -0
- package/dist/schema-api.js +11 -0
- package/dist/schema-sync.d.ts +0 -1
- package/dist/schema-sync.js +1 -10
- package/dist/secrets.d.ts +7 -0
- package/dist/secrets.js +60 -0
- package/dist/seeds.d.ts +14 -0
- package/dist/seeds.js +23 -0
- package/dist/temporal.d.ts +0 -16
- package/dist/temporal.js +0 -6
- package/dist/uploads.d.ts +4 -0
- package/dist/uploads.js +12 -0
- package/package.json +12 -4
- package/dist/collection-io.d.ts.map +0 -1
- package/dist/collection-io.js.map +0 -1
- package/dist/db.d.ts.map +0 -1
- package/dist/db.js.map +0 -1
- package/dist/entity-schema.d.ts.map +0 -1
- package/dist/entity-schema.js.map +0 -1
- package/dist/extend-io.d.ts.map +0 -1
- package/dist/extend-io.js.map +0 -1
- package/dist/extend-table.d.ts.map +0 -1
- package/dist/extend-table.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/local-api.d.ts.map +0 -1
- package/dist/local-api.js.map +0 -1
- package/dist/mutate.d.ts.map +0 -1
- package/dist/mutate.js.map +0 -1
- package/dist/plugin-discovery.d.ts.map +0 -1
- package/dist/plugin-discovery.js.map +0 -1
- package/dist/plugin-hooks.d.ts.map +0 -1
- package/dist/plugin-hooks.js.map +0 -1
- package/dist/plugin-runtime.d.ts.map +0 -1
- package/dist/plugin-runtime.js.map +0 -1
- package/dist/plugins-api.d.ts.map +0 -1
- package/dist/plugins-api.js.map +0 -1
- package/dist/registry-context.d.ts.map +0 -1
- package/dist/registry-context.js.map +0 -1
- package/dist/schema-sync.d.ts.map +0 -1
- package/dist/schema-sync.js.map +0 -1
- package/dist/temporal.d.ts.map +0 -1
- package/dist/temporal.js.map +0 -1
package/dist/collection-io.d.ts
CHANGED
|
@@ -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
|
-
|
|
5
|
-
export declare function
|
|
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
|
-
|
|
10
|
-
export declare function
|
|
11
|
-
|
|
12
|
-
export declare
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
export declare
|
|
17
|
-
|
|
18
|
-
export declare
|
|
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
|
package/dist/collection-io.js
CHANGED
|
@@ -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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
26
|
-
|
|
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 =
|
|
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
|
|
51
|
+
const flatR = flattenEmbeddedAt(childFields, r);
|
|
36
52
|
const picked = {};
|
|
37
|
-
for (const
|
|
38
|
-
if (
|
|
39
|
-
picked[
|
|
40
|
-
|
|
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
|
-
|
|
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(
|
|
49
|
-
const table =
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
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
|
-
|
|
69
|
-
export function flattenEmbedded(m, input) {
|
|
111
|
+
export function flattenEmbeddedAt(fields, input) {
|
|
70
112
|
const out = { ...input };
|
|
71
|
-
for (const [key, g] of embeddedGroups(
|
|
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
|
-
|
|
84
|
-
export function nestEmbedded(m, row) {
|
|
132
|
+
export function nestEmbeddedAt(fields, row) {
|
|
85
133
|
const out = { ...row };
|
|
86
|
-
for (const [key,
|
|
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
|
-
|
|
105
|
-
export
|
|
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
|
-
|
|
33
|
-
|
|
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
|
-
|
|
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
|
package/dist/dialect.js
ADDED
|
@@ -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
|
+
}
|
package/dist/embed.d.ts
ADDED
|
@@ -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
|
+
}
|
package/dist/entity-schema.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|