@coffer-org/core 1.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/dist/compose.d.ts +32 -0
- package/dist/compose.d.ts.map +1 -0
- package/dist/compose.js +88 -0
- package/dist/compose.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/search.d.ts +32 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +64 -0
- package/dist/search.js.map +1 -0
- package/package.json +26 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Композиція реєстру з набору плагінів — ЧИСТА функція (ядро).
|
|
3
|
+
* НЕ імпортує жодних плагінів: набір передається ззовні (композиційний корінь).
|
|
4
|
+
* Так @coffer-org/core не залежить від плагінів (немає циклу core→plugins→core).
|
|
5
|
+
*/
|
|
6
|
+
import type { ModuleDef } from '@coffer-org/sdk/module';
|
|
7
|
+
import type { OptionItem } from '@coffer-org/sdk/fields';
|
|
8
|
+
import type { VaultBundle } from '@coffer-org/sdk/vault';
|
|
9
|
+
import type { ExtendDef } from '@coffer-org/sdk/extend';
|
|
10
|
+
import { type PluginManifest } from '@coffer-org/sdk/plugin';
|
|
11
|
+
export interface Registry {
|
|
12
|
+
/** Плагіни в топологічному порядку (для міграцій/seed/init). */
|
|
13
|
+
order: PluginManifest[];
|
|
14
|
+
vaults: VaultBundle[];
|
|
15
|
+
modules: ModuleDef[];
|
|
16
|
+
extends_: ExtendDef[];
|
|
17
|
+
getModule(vault: string, module: string): ModuleDef | undefined;
|
|
18
|
+
getVault(id: string): VaultBundle | undefined;
|
|
19
|
+
getExtendsFor(vault: string, module: string): ExtendDef[];
|
|
20
|
+
/** Об'єднані опції поля: base + source + fieldContrib з усіх плагінів. */
|
|
21
|
+
getFieldOptions(vault: string, module: string, field: string): OptionItem[];
|
|
22
|
+
/** Глобальний іменований список опцій (countries, colors, …). */
|
|
23
|
+
getOptionList(name: string): OptionItem[];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Збирає реєстр із плагінів. opts.disabled — м'яке вимкнення (плагін не реєструється,
|
|
27
|
+
* але дані лишаються). Кидає, якщо увімкнений плагін залежить від вимкненого.
|
|
28
|
+
*/
|
|
29
|
+
export declare function composeRegistry(plugins: PluginManifest[], opts?: {
|
|
30
|
+
disabled?: Set<string>;
|
|
31
|
+
}): Registry;
|
|
32
|
+
//# sourceMappingURL=compose.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAGxD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAExD,OAAO,EAAmB,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAE9E,MAAM,WAAW,QAAQ;IACvB,gEAAgE;IAChE,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB,QAAQ,EAAE,SAAS,EAAE,CAAC;IACtB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAAC;IAChE,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAAC;IAC9C,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,CAAC;IAC1D,0EAA0E;IAC1E,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5E,iEAAiE;IACjE,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,EAAE,CAAC;CAC3C;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,cAAc,EAAE,EAAE,IAAI,GAAE;IAAE,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CAAO,GAAG,QAAQ,CAsF1G"}
|
package/dist/compose.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { fieldEntries } from '@coffer-org/sdk/module';
|
|
2
|
+
import { bindSelectSourceZod } from '@coffer-org/sdk/fields';
|
|
3
|
+
import { extendMatches } from '@coffer-org/sdk/extend';
|
|
4
|
+
import { assemblePlugins } from '@coffer-org/sdk/plugin';
|
|
5
|
+
/**
|
|
6
|
+
* Збирає реєстр із плагінів. opts.disabled — м'яке вимкнення (плагін не реєструється,
|
|
7
|
+
* але дані лишаються). Кидає, якщо увімкнений плагін залежить від вимкненого.
|
|
8
|
+
*/
|
|
9
|
+
export function composeRegistry(plugins, opts = {}) {
|
|
10
|
+
const disabled = opts.disabled ?? new Set();
|
|
11
|
+
const enabled = plugins.filter((p) => !disabled.has(p.id));
|
|
12
|
+
for (const p of enabled) {
|
|
13
|
+
for (const d of p.dependsOn) {
|
|
14
|
+
if (disabled.has(d)) {
|
|
15
|
+
throw new Error(`[plugin] '${p.id}' увімкнено, але залежність '${d}' вимкнено`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const assembled = assemblePlugins(enabled);
|
|
20
|
+
const { order, vaults, modules, extends_ } = assembled;
|
|
21
|
+
// Зібрати всі fieldContribs у Map<"vault/module/field", {options, suggestions}>
|
|
22
|
+
const contribMap = new Map();
|
|
23
|
+
for (const p of enabled) {
|
|
24
|
+
for (const contrib of p.fieldContribs ?? []) {
|
|
25
|
+
const key = `${contrib.vault}/${contrib.module}/${contrib.field}`;
|
|
26
|
+
const acc = contribMap.get(key) ?? { options: [], suggestions: [] };
|
|
27
|
+
if (contrib.options)
|
|
28
|
+
acc.options.push(...contrib.options);
|
|
29
|
+
if (contrib.suggestions)
|
|
30
|
+
acc.suggestions.push(...contrib.suggestions);
|
|
31
|
+
contribMap.set(key, acc);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// Зібрати глобальні іменовані списки опцій
|
|
35
|
+
const optionListMap = new Map();
|
|
36
|
+
for (const p of enabled) {
|
|
37
|
+
for (const list of p.optionLists ?? []) {
|
|
38
|
+
optionListMap.set(list.name, list.options);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const getOptionList = (name) => optionListMap.get(name) ?? [];
|
|
42
|
+
// Влити fieldContribs ПРЯМО в поля цільових модулів (єдине джерело правди:
|
|
43
|
+
// form/display/table/zod читають field.options та hints.suggestions).
|
|
44
|
+
for (const m of modules) {
|
|
45
|
+
for (const [key, f] of fieldEntries(m.fields)) {
|
|
46
|
+
const c = contribMap.get(`${m.vault}/${m.module}/${key}`);
|
|
47
|
+
if (!c)
|
|
48
|
+
continue;
|
|
49
|
+
if (c.options.length) {
|
|
50
|
+
const seen = new Set((f.options ?? []).map((o) => o.value));
|
|
51
|
+
f.options = [...(f.options ?? []), ...c.options.filter((o) => !seen.has(o.value))];
|
|
52
|
+
}
|
|
53
|
+
if (c.suggestions.length) {
|
|
54
|
+
const cur = f.hints['suggestions'] ?? [];
|
|
55
|
+
const seen = new Set(cur);
|
|
56
|
+
f.hints['suggestions'] = [...cur, ...c.suggestions.filter((s) => !seen.has(s))];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Select-поля: enum-валідація з ПОВНОГО набору опцій (inline + влиті contribs + source).
|
|
61
|
+
// Factory будує enum лише з inline-опцій, відомих на момент оголошення; fieldContribs
|
|
62
|
+
// вливаються пізніше (вище), тож без цієї пересборки сервер відхиляв би значення,
|
|
63
|
+
// додані плагінами (напр. type 'device'/'drone' у things/item).
|
|
64
|
+
// Заразом прекомп'ютимо resolved-опції кожного поля: після compose вони статичні,
|
|
65
|
+
// тож getFieldOptions — це один Map.get без алокацій.
|
|
66
|
+
const fieldOptionsMap = new Map();
|
|
67
|
+
for (const m of modules) {
|
|
68
|
+
for (const [key, f] of fieldEntries(m.fields)) {
|
|
69
|
+
const src = f.hints['source'];
|
|
70
|
+
const base = f.options ?? []; // вже містить влиті contribs
|
|
71
|
+
const sourceOpts = src ? (optionListMap.get(src) ?? []) : [];
|
|
72
|
+
const seen = new Set(base.map((o) => o.value));
|
|
73
|
+
const resolved = [...base, ...sourceOpts.filter((o) => !seen.has(o.value))];
|
|
74
|
+
if (f.prim === 'select' && resolved.length) {
|
|
75
|
+
bindSelectSourceZod(f, resolved.map((o) => o.value));
|
|
76
|
+
}
|
|
77
|
+
if (resolved.length) {
|
|
78
|
+
fieldOptionsMap.set(`${m.vault}/${m.module}/${key}`, resolved);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const getModule = (vault, module) => modules.find((m) => m.vault === vault && m.module === module);
|
|
83
|
+
const getVault = (id) => vaults.find((v) => v.meta.id === id);
|
|
84
|
+
const getExtendsFor = (vault, module) => extends_.filter((e) => extendMatches(e, vault, module));
|
|
85
|
+
const getFieldOptions = (vault, module, field) => fieldOptionsMap.get(`${vault}/${module}/${field}`) ?? [];
|
|
86
|
+
return { order, vaults, modules, extends_, getModule, getVault, getExtendsFor, getFieldOptions, getOptionList };
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=compose.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compose.js","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC"}
|
package/dist/search.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Чиста логіка текстового матчингу для пошуку (глобального та спискового).
|
|
3
|
+
* Незалежна від БД та i18n.
|
|
4
|
+
*
|
|
5
|
+
* SQLite-оператор LIKE сворачивает регістр лише для ASCII (A–Z), тому
|
|
6
|
+
* кириличний пошук не може покладатися на нього — матчимо в JS.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Нормалізація рядка: нижній регістр (Unicode) + зріз діакритики.
|
|
10
|
+
* NFD-декомпозиція дає «й→и», «ё→е», «ї→і» та ігнор будь-якої діакритики
|
|
11
|
+
* безкоштовно (precomposed-кирилиця має канонічну декомпозицію).
|
|
12
|
+
*/
|
|
13
|
+
export declare function foldText(s: string): string;
|
|
14
|
+
/** Запит → масив непорожніх folded-токенів. */
|
|
15
|
+
export declare function tokenize(query: string): string[];
|
|
16
|
+
/**
|
|
17
|
+
* Матч + score. `tokens` мають бути отримані з `tokenize` (вже folded).
|
|
18
|
+
* `title` / `otherFields` — сирі рядки (folding робиться всередині).
|
|
19
|
+
*
|
|
20
|
+
* Кожен токен (AND) має бути підрядком хоча б одного поля (OR між полями).
|
|
21
|
+
* Якщо хоч один токен не знайдено ніде → null (нема матчу). Інакше — score
|
|
22
|
+
* (більший = краще): збіг у `title` важить більше за `otherFields`,
|
|
23
|
+
* префіксний збіг — більше за збіг усередині.
|
|
24
|
+
*/
|
|
25
|
+
export declare function matchScore(title: string, otherFields: string[], tokens: string[]): number | null;
|
|
26
|
+
/**
|
|
27
|
+
* Як `matchScore`, але приймає вже folded `title`/`otherFields` (через `foldText`).
|
|
28
|
+
* Дозволяє згорнути поля один раз і перевикористати їх (напр. для сниппета),
|
|
29
|
+
* не складаючи їх повторно.
|
|
30
|
+
*/
|
|
31
|
+
export declare function matchScoreFolded(foldedTitle: string, foldedOthers: string[], tokens: string[]): number | null;
|
|
32
|
+
//# sourceMappingURL=search.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../src/search.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE1C;AAED,+CAA+C;AAC/C,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAIhD;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,IAAI,CAEhG;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,IAAI,CAmB7G"}
|
package/dist/search.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Чиста логіка текстового матчингу для пошуку (глобального та спискового).
|
|
3
|
+
* Незалежна від БД та i18n.
|
|
4
|
+
*
|
|
5
|
+
* SQLite-оператор LIKE сворачивает регістр лише для ASCII (A–Z), тому
|
|
6
|
+
* кириличний пошук не може покладатися на нього — матчимо в JS.
|
|
7
|
+
*/
|
|
8
|
+
const COMBINING_MARKS = /[̀-ͯ]/g;
|
|
9
|
+
/**
|
|
10
|
+
* Нормалізація рядка: нижній регістр (Unicode) + зріз діакритики.
|
|
11
|
+
* NFD-декомпозиція дає «й→и», «ё→е», «ї→і» та ігнор будь-якої діакритики
|
|
12
|
+
* безкоштовно (precomposed-кирилиця має канонічну декомпозицію).
|
|
13
|
+
*/
|
|
14
|
+
export function foldText(s) {
|
|
15
|
+
return s.toLowerCase().normalize('NFD').replace(COMBINING_MARKS, '');
|
|
16
|
+
}
|
|
17
|
+
/** Запит → масив непорожніх folded-токенів. */
|
|
18
|
+
export function tokenize(query) {
|
|
19
|
+
return foldText(query)
|
|
20
|
+
.split(/\s+/)
|
|
21
|
+
.filter((t) => t.length > 0);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Матч + score. `tokens` мають бути отримані з `tokenize` (вже folded).
|
|
25
|
+
* `title` / `otherFields` — сирі рядки (folding робиться всередині).
|
|
26
|
+
*
|
|
27
|
+
* Кожен токен (AND) має бути підрядком хоча б одного поля (OR між полями).
|
|
28
|
+
* Якщо хоч один токен не знайдено ніде → null (нема матчу). Інакше — score
|
|
29
|
+
* (більший = краще): збіг у `title` важить більше за `otherFields`,
|
|
30
|
+
* префіксний збіг — більше за збіг усередині.
|
|
31
|
+
*/
|
|
32
|
+
export function matchScore(title, otherFields, tokens) {
|
|
33
|
+
return matchScoreFolded(foldText(title), otherFields.map(foldText), tokens);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Як `matchScore`, але приймає вже folded `title`/`otherFields` (через `foldText`).
|
|
37
|
+
* Дозволяє згорнути поля один раз і перевикористати їх (напр. для сниппета),
|
|
38
|
+
* не складаючи їх повторно.
|
|
39
|
+
*/
|
|
40
|
+
export function matchScoreFolded(foldedTitle, foldedOthers, tokens) {
|
|
41
|
+
if (tokens.length === 0)
|
|
42
|
+
return null;
|
|
43
|
+
let score = 0;
|
|
44
|
+
for (const token of tokens) {
|
|
45
|
+
let best = 0;
|
|
46
|
+
const ti = foldedTitle.indexOf(token);
|
|
47
|
+
if (ti === 0)
|
|
48
|
+
best = 4; // префікс у заголовку
|
|
49
|
+
else if (ti > 0)
|
|
50
|
+
best = 3; // всередині заголовку
|
|
51
|
+
for (const fld of foldedOthers) {
|
|
52
|
+
const fi = fld.indexOf(token);
|
|
53
|
+
if (fi === 0)
|
|
54
|
+
best = Math.max(best, 2); // префікс в іншому полі
|
|
55
|
+
else if (fi > 0)
|
|
56
|
+
best = Math.max(best, 1); // всередині іншого поля
|
|
57
|
+
}
|
|
58
|
+
if (best === 0)
|
|
59
|
+
return null; // токен не знайдено ніде
|
|
60
|
+
score += best;
|
|
61
|
+
}
|
|
62
|
+
return score;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.js","sourceRoot":"","sources":["../src/search.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,eAAe,GAAG,QAAQ,CAAC;AAEjC;;;;GAIG;AACH,MAAM,UAAU,QAAQ,CAAC,CAAS;IAChC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC,CAAC;AACvE,CAAC;AAED,+CAA+C;AAC/C,MAAM,UAAU,QAAQ,CAAC,KAAa;IACpC,OAAO,QAAQ,CAAC,KAAK,CAAC;SACnB,KAAK,CAAC,KAAK,CAAC;SACZ,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AACjC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,UAAU,CAAC,KAAa,EAAE,WAAqB,EAAE,MAAgB;IAC/E,OAAO,gBAAgB,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,CAAC;AAC9E,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,WAAmB,EAAE,YAAsB,EAAE,MAAgB;IAC5F,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACrC,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,IAAI,GAAG,CAAC,CAAC;QACb,MAAM,EAAE,GAAG,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACtC,IAAI,EAAE,KAAK,CAAC;YACV,IAAI,GAAG,CAAC,CAAC,CAAC,sBAAsB;aAC7B,IAAI,EAAE,GAAG,CAAC;YAAE,IAAI,GAAG,CAAC,CAAC,CAAC,sBAAsB;QACjD,KAAK,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;YAC/B,MAAM,EAAE,GAAG,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YAC9B,IAAI,EAAE,KAAK,CAAC;gBACV,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,wBAAwB;iBAC/C,IAAI,EAAE,GAAG,CAAC;gBAAE,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,wBAAwB;QACrE,CAAC;QACD,IAAI,IAAI,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC,CAAC,yBAAyB;QACtD,KAAK,IAAI,IAAI,CAAC;IAChB,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@coffer-org/core",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"files": [
|
|
6
|
+
"dist"
|
|
7
|
+
],
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./*": {
|
|
14
|
+
"types": "./dist/*.d.ts",
|
|
15
|
+
"default": "./dist/*.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc -b tsconfig.build.json",
|
|
20
|
+
"prepack": "npm run build && node ../../scripts/swap-exports.mjs dist",
|
|
21
|
+
"postpack": "node ../../scripts/swap-exports.mjs src"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@coffer-org/sdk": "^1.1.0"
|
|
25
|
+
}
|
|
26
|
+
}
|