@monbolc/lowcode-editor-core 2.0.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/es/di.js +67 -0
- package/es/di.js.map +1 -0
- package/es/editor.js +75 -0
- package/es/editor.js.map +1 -0
- package/es/i18n.js +50 -0
- package/es/i18n.js.map +1 -0
- package/es/index.js +15 -0
- package/es/index.js.map +1 -0
- package/es/plugin.js +107 -0
- package/es/plugin.js.map +1 -0
- package/es/types.js +8 -0
- package/es/types.js.map +1 -0
- package/lib/di.d.ts +38 -0
- package/lib/di.js +71 -0
- package/lib/di.js.map +1 -0
- package/lib/editor.d.ts +34 -0
- package/lib/editor.js +79 -0
- package/lib/editor.js.map +1 -0
- package/lib/i18n.d.ts +23 -0
- package/lib/i18n.js +54 -0
- package/lib/i18n.js.map +1 -0
- package/lib/index.d.ts +13 -0
- package/lib/index.js +22 -0
- package/lib/index.js.map +1 -0
- package/lib/plugin.d.ts +34 -0
- package/lib/plugin.js +111 -0
- package/lib/plugin.js.map +1 -0
- package/lib/types.d.ts +114 -0
- package/lib/types.js +9 -0
- package/lib/types.js.map +1 -0
- package/package.json +41 -0
package/es/di.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @monbolc/lowcode-editor-core — DIContainer
|
|
3
|
+
*
|
|
4
|
+
* A tiny, type-friendly dependency injection container. Each entry is
|
|
5
|
+
* keyed by a constructor / factory function. Resolve is async because
|
|
6
|
+
* a factory may itself call `get(...)` to satisfy its own deps.
|
|
7
|
+
*
|
|
8
|
+
* Why a custom container instead of InversifyJS / tsyringe?
|
|
9
|
+
* - The editor's DI surface is small (a few dozen services at most).
|
|
10
|
+
* - We want zero runtime deps and a TS-friendly API.
|
|
11
|
+
* - We do not need scoping / child containers / aspect interception.
|
|
12
|
+
*/
|
|
13
|
+
import { uid } from '@monbolc/lowcode-utils';
|
|
14
|
+
export class DIContainer {
|
|
15
|
+
constructor() {
|
|
16
|
+
this.services = new Map();
|
|
17
|
+
this.id = uid('di');
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Register a service. The `ctor` is the lookup key — call `get(ctor)`
|
|
21
|
+
* to retrieve the instance. The `factory` builds the instance.
|
|
22
|
+
*/
|
|
23
|
+
register(ctor, factory, scope = 'singleton') {
|
|
24
|
+
if (this.services.has(ctor)) {
|
|
25
|
+
throw new Error(`[DIContainer:${this.id}] service already registered for this ctor`);
|
|
26
|
+
}
|
|
27
|
+
this.services.set(ctor, { factory, scope });
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Resolve a service. If a singleton was already built, returns the
|
|
31
|
+
* cached instance; otherwise calls the factory and caches.
|
|
32
|
+
*/
|
|
33
|
+
async get(ctor) {
|
|
34
|
+
const reg = this.services.get(ctor);
|
|
35
|
+
if (!reg) {
|
|
36
|
+
throw new Error(`[DIContainer:${this.id}] no service registered for this ctor`);
|
|
37
|
+
}
|
|
38
|
+
if (reg.instance !== undefined && reg.scope === 'singleton') {
|
|
39
|
+
return reg.instance;
|
|
40
|
+
}
|
|
41
|
+
const instance = await reg.factory(this);
|
|
42
|
+
if (reg.scope === 'singleton')
|
|
43
|
+
reg.instance = instance;
|
|
44
|
+
return instance;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Synchronous lookup. Returns undefined if the service is not yet built
|
|
48
|
+
* (or not registered at all). Useful for non-async consumers.
|
|
49
|
+
*/
|
|
50
|
+
peek(ctor) {
|
|
51
|
+
const reg = this.services.get(ctor);
|
|
52
|
+
return reg?.instance;
|
|
53
|
+
}
|
|
54
|
+
/** True if a factory is registered for this key. */
|
|
55
|
+
has(ctor) {
|
|
56
|
+
return this.services.has(ctor);
|
|
57
|
+
}
|
|
58
|
+
/** Drop all registrations and cached instances. */
|
|
59
|
+
clear() {
|
|
60
|
+
this.services.clear();
|
|
61
|
+
}
|
|
62
|
+
/** Number of registered services. */
|
|
63
|
+
size() {
|
|
64
|
+
return this.services.size;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=di.js.map
|
package/es/di.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"di.js","sourceRoot":"","sources":["../src/di.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,GAAG,EAAE,MAAM,wBAAwB,CAAC;AAa7C,MAAM,OAAO,WAAW;IAAxB;QACmB,aAAQ,GAAG,IAAI,GAAG,EAA2C,CAAC;QAC9D,OAAE,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC;IAqDlC,CAAC;IAnDC;;;OAGG;IACH,QAAQ,CAAI,IAAgB,EAAE,OAAmB,EAAE,QAAmC,WAAW;QAC/F,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAwB,CAAC,EAAE,CAAC;YAChD,MAAM,IAAI,KAAK,CAAC,gBAAgB,IAAI,CAAC,EAAE,4CAA4C,CAAC,CAAC;QACvF,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAwB,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;IAClE,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,GAAG,CAAI,IAAgB;QAC3B,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAwB,CAAC,CAAC;QACxD,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,MAAM,IAAI,KAAK,CAAC,gBAAgB,IAAI,CAAC,EAAE,uCAAuC,CAAC,CAAC;QAClF,CAAC;QACD,IAAI,GAAG,CAAC,QAAQ,KAAK,SAAS,IAAI,GAAG,CAAC,KAAK,KAAK,WAAW,EAAE,CAAC;YAC5D,OAAO,GAAG,CAAC,QAAa,CAAC;QAC3B,CAAC;QACD,MAAM,QAAQ,GAAG,MAAO,GAAG,CAAC,OAAsB,CAAC,IAAI,CAAC,CAAC;QACzD,IAAI,GAAG,CAAC,KAAK,KAAK,WAAW;YAAE,GAAG,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACvD,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED;;;OAGG;IACH,IAAI,CAAI,IAAgB;QACtB,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAwB,CAAC,CAAC;QACxD,OAAO,GAAG,EAAE,QAAyB,CAAC;IACxC,CAAC;IAED,oDAAoD;IACpD,GAAG,CAAI,IAAgB;QACrB,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAwB,CAAC,CAAC;IACrD,CAAC;IAED,mDAAmD;IACnD,KAAK;QACH,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;IAED,qCAAqC;IACrC,IAAI;QACF,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;IAC5B,CAAC;CACF"}
|
package/es/editor.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @monbolc/lowcode-editor-core — Editor (composition root)
|
|
3
|
+
*
|
|
4
|
+
* The `Editor` class wires DI, i18n, the plugin manager, and the command
|
|
5
|
+
* manager into a single object. Subclasses (e.g. a future
|
|
6
|
+
* `BrowserEditor`) can override `init` to also mount UI.
|
|
7
|
+
*/
|
|
8
|
+
import { Emitter } from '@monbolc/lowcode-utils';
|
|
9
|
+
import { CommandManager } from '@monbolc/lowcode-plugin-command';
|
|
10
|
+
import { DIContainer } from './di.js';
|
|
11
|
+
import { I18nImpl } from './i18n.js';
|
|
12
|
+
import { PluginManager } from './plugin.js';
|
|
13
|
+
export class Editor {
|
|
14
|
+
constructor(options = {}) {
|
|
15
|
+
this.events = new Emitter();
|
|
16
|
+
this.i18n = new I18nImpl();
|
|
17
|
+
this.di = new DIContainer();
|
|
18
|
+
this.commands = new CommandManager();
|
|
19
|
+
this._ready = false;
|
|
20
|
+
this.id = `editor_${Math.random().toString(36).slice(2, 10)}`;
|
|
21
|
+
this.plugins = new PluginManager(this.events);
|
|
22
|
+
this.initialPlugins = options.plugins ?? [];
|
|
23
|
+
if (options.locale)
|
|
24
|
+
this.i18n.setLocale(options.locale);
|
|
25
|
+
}
|
|
26
|
+
get ready() {
|
|
27
|
+
return this._ready;
|
|
28
|
+
}
|
|
29
|
+
async init(extraPlugins = []) {
|
|
30
|
+
if (this._ready) {
|
|
31
|
+
throw new Error(`[Editor:${this.id}] init() called twice`);
|
|
32
|
+
}
|
|
33
|
+
this.events.emit('phase', { name: 'init' });
|
|
34
|
+
for (const p of this.initialPlugins)
|
|
35
|
+
this.plugins.register(p);
|
|
36
|
+
for (const p of extraPlugins)
|
|
37
|
+
this.plugins.register(p);
|
|
38
|
+
this.events.emit('phase', { name: 'register' });
|
|
39
|
+
const ctx = {
|
|
40
|
+
editor: this,
|
|
41
|
+
i18n: this.i18n,
|
|
42
|
+
di: this.di,
|
|
43
|
+
events: this.events,
|
|
44
|
+
plugins: this.plugins,
|
|
45
|
+
commands: this.commands,
|
|
46
|
+
};
|
|
47
|
+
await this.plugins.initAll(ctx);
|
|
48
|
+
this._ready = true;
|
|
49
|
+
this.events.emit('phase', { name: 'ready' });
|
|
50
|
+
}
|
|
51
|
+
async destroy() {
|
|
52
|
+
if (!this._ready)
|
|
53
|
+
return;
|
|
54
|
+
this.events.emit('phase', { name: 'destroy' });
|
|
55
|
+
const ctx = {
|
|
56
|
+
editor: this,
|
|
57
|
+
i18n: this.i18n,
|
|
58
|
+
di: this.di,
|
|
59
|
+
events: this.events,
|
|
60
|
+
plugins: this.plugins,
|
|
61
|
+
commands: this.commands,
|
|
62
|
+
};
|
|
63
|
+
await this.plugins.destroyAll(ctx);
|
|
64
|
+
this.di.clear();
|
|
65
|
+
this.commands.clearHistory();
|
|
66
|
+
this._ready = false;
|
|
67
|
+
}
|
|
68
|
+
async get(ctor) {
|
|
69
|
+
return this.di.get(ctor);
|
|
70
|
+
}
|
|
71
|
+
peek(ctor) {
|
|
72
|
+
return this.di.peek(ctor);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=editor.js.map
|
package/es/editor.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"editor.js","sourceRoot":"","sources":["../src/editor.ts"],"names":[],"mappings":"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"}
|
package/es/i18n.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @monbolc/lowcode-editor-core — I18n
|
|
3
|
+
*
|
|
4
|
+
* Tiny internationalization helper. Plugins call `i18n.register({...})`
|
|
5
|
+
* to add strings; consumers call `i18n.t('pluginName.someKey')` to get
|
|
6
|
+
* a string in the active locale (or the default if missing).
|
|
7
|
+
*
|
|
8
|
+
* Shape: { default: string, byLocale?: Record<locale, string> }
|
|
9
|
+
* Example:
|
|
10
|
+
* i18n.register({ 'designer.duplicate': { default: 'Duplicate', byLocale: { zh_CN: '复制' } } });
|
|
11
|
+
* i18n.t('designer.duplicate'); // 'Duplicate' (or '复制' if locale='zh_CN')
|
|
12
|
+
*/
|
|
13
|
+
export class I18nImpl {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.messages = new Map();
|
|
16
|
+
this._locale = '';
|
|
17
|
+
}
|
|
18
|
+
get locale() {
|
|
19
|
+
return this._locale;
|
|
20
|
+
}
|
|
21
|
+
setLocale(locale) {
|
|
22
|
+
this._locale = locale ?? '';
|
|
23
|
+
}
|
|
24
|
+
register(messages) {
|
|
25
|
+
for (const [id, value] of Object.entries(messages)) {
|
|
26
|
+
// Allow shorthand: `'foo': 'Hello'` instead of `{ default: 'Hello' }`.
|
|
27
|
+
const normalized = typeof value === 'string' ? { default: value } : value;
|
|
28
|
+
this.messages.set(id, normalized);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
t(id, locale, fallback) {
|
|
32
|
+
const msg = this.messages.get(id);
|
|
33
|
+
if (!msg) {
|
|
34
|
+
if (fallback !== undefined)
|
|
35
|
+
return fallback;
|
|
36
|
+
// Last resort: return the id itself so missing translations are visible.
|
|
37
|
+
return id;
|
|
38
|
+
}
|
|
39
|
+
const activeLocale = locale ?? this._locale;
|
|
40
|
+
if (activeLocale && msg.byLocale?.[activeLocale] !== undefined) {
|
|
41
|
+
return msg.byLocale[activeLocale];
|
|
42
|
+
}
|
|
43
|
+
return msg.default;
|
|
44
|
+
}
|
|
45
|
+
/** Number of registered message ids. */
|
|
46
|
+
size() {
|
|
47
|
+
return this.messages.size;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=i18n.js.map
|
package/es/i18n.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n.js","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,MAAM,OAAO,QAAQ;IAArB;QACmB,aAAQ,GAAG,IAAI,GAAG,EAAuB,CAAC;QACnD,YAAO,GAAW,EAAE,CAAC;IAqC/B,CAAC;IAnCC,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED,SAAS,CAAC,MAAc;QACtB,IAAI,CAAC,OAAO,GAAG,MAAM,IAAI,EAAE,CAAC;IAC9B,CAAC;IAED,QAAQ,CAAC,QAA8C;QACrD,KAAK,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;YACnD,uEAAuE;YACvE,MAAM,UAAU,GACd,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;YACzD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,EAAE,UAAU,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;IAED,CAAC,CAAC,EAAU,EAAE,MAAe,EAAE,QAAiB;QAC9C,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAClC,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,IAAI,QAAQ,KAAK,SAAS;gBAAE,OAAO,QAAQ,CAAC;YAC5C,yEAAyE;YACzE,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,MAAM,YAAY,GAAG,MAAM,IAAI,IAAI,CAAC,OAAO,CAAC;QAC5C,IAAI,YAAY,IAAI,GAAG,CAAC,QAAQ,EAAE,CAAC,YAAY,CAAC,KAAK,SAAS,EAAE,CAAC;YAC/D,OAAO,GAAG,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;QACpC,CAAC;QACD,OAAO,GAAG,CAAC,OAAO,CAAC;IACrB,CAAC;IAED,wCAAwC;IACxC,IAAI;QACF,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;IAC5B,CAAC;CACF"}
|
package/es/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @monbolc/lowcode-editor-core — barrel export
|
|
3
|
+
*
|
|
4
|
+
* SapuLowcodeEngine core API surface (L2). DI + i18n + plugin registry +
|
|
5
|
+
* command manager + event bus. No React, no mobx — that's L3+.
|
|
6
|
+
*/
|
|
7
|
+
// --- DI ---
|
|
8
|
+
export { DIContainer } from './di.js';
|
|
9
|
+
// --- i18n ---
|
|
10
|
+
export { I18nImpl } from './i18n.js';
|
|
11
|
+
// --- plugin manager ---
|
|
12
|
+
export { PluginManager } from './plugin.js';
|
|
13
|
+
// --- editor (composition root) ---
|
|
14
|
+
export { Editor } from './editor.js';
|
|
15
|
+
//# sourceMappingURL=index.js.map
|
package/es/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAeH,aAAa;AACb,OAAO,EAAE,WAAW,EAAE,MAAM,MAAM,CAAC;AAGnC,eAAe;AACf,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAElC,yBAAyB;AACzB,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,oCAAoC;AACpC,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC"}
|
package/es/plugin.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @monbolc/lowcode-editor-core — PluginManager
|
|
3
|
+
*
|
|
4
|
+
* Validates plugin names, topologically sorts by `dependencies`,
|
|
5
|
+
* and exposes registration APIs.
|
|
6
|
+
*/
|
|
7
|
+
export class PluginManager {
|
|
8
|
+
constructor(eventBus) {
|
|
9
|
+
this.plugins = new Map();
|
|
10
|
+
this.sortedCache = null;
|
|
11
|
+
this.eventBus = eventBus;
|
|
12
|
+
}
|
|
13
|
+
register(plugin) {
|
|
14
|
+
if (this.plugins.has(plugin.name)) {
|
|
15
|
+
throw new Error(`[PluginManager] plugin "${plugin.name}" is already registered`);
|
|
16
|
+
}
|
|
17
|
+
if (!/^[A-Za-z0-9_@./\-]+$/.test(plugin.name)) {
|
|
18
|
+
throw new Error(`[PluginManager] plugin name "${plugin.name}" contains invalid characters`);
|
|
19
|
+
}
|
|
20
|
+
this.plugins.set(plugin.name, plugin);
|
|
21
|
+
this.sortedCache = null; // invalidate sort cache
|
|
22
|
+
this.eventBus.emit('pluginRegistered', { name: plugin.name });
|
|
23
|
+
}
|
|
24
|
+
unregister(name) {
|
|
25
|
+
const existed = this.plugins.delete(name);
|
|
26
|
+
if (existed)
|
|
27
|
+
this.sortedCache = null;
|
|
28
|
+
return existed;
|
|
29
|
+
}
|
|
30
|
+
has(name) {
|
|
31
|
+
return this.plugins.has(name);
|
|
32
|
+
}
|
|
33
|
+
get(name) {
|
|
34
|
+
return this.plugins.get(name);
|
|
35
|
+
}
|
|
36
|
+
list() {
|
|
37
|
+
return Array.from(this.plugins.values());
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Return plugins in dependency order: deps first, then dependents.
|
|
41
|
+
* Throws on circular or missing deps.
|
|
42
|
+
*/
|
|
43
|
+
sortedByDeps() {
|
|
44
|
+
if (this.sortedCache)
|
|
45
|
+
return this.sortedCache;
|
|
46
|
+
const result = [];
|
|
47
|
+
const visited = new Set();
|
|
48
|
+
const visiting = new Set();
|
|
49
|
+
const visit = (plugin) => {
|
|
50
|
+
if (visited.has(plugin.name))
|
|
51
|
+
return;
|
|
52
|
+
if (visiting.has(plugin.name)) {
|
|
53
|
+
throw new Error(`[PluginManager] circular dependency at "${plugin.name}"`);
|
|
54
|
+
}
|
|
55
|
+
visiting.add(plugin.name);
|
|
56
|
+
for (const dep of plugin.dependencies ?? []) {
|
|
57
|
+
const sub = this.plugins.get(dep);
|
|
58
|
+
if (!sub) {
|
|
59
|
+
throw new Error(`[PluginManager] plugin "${plugin.name}" depends on missing plugin "${dep}"`);
|
|
60
|
+
}
|
|
61
|
+
visit(sub);
|
|
62
|
+
}
|
|
63
|
+
visiting.delete(plugin.name);
|
|
64
|
+
visited.add(plugin.name);
|
|
65
|
+
result.push(plugin);
|
|
66
|
+
};
|
|
67
|
+
for (const plugin of this.plugins.values())
|
|
68
|
+
visit(plugin);
|
|
69
|
+
this.sortedCache = result;
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Run init() for each plugin in dep order. If any throws, the error
|
|
74
|
+
* is rethrown but earlier plugins remain initialized.
|
|
75
|
+
*/
|
|
76
|
+
async initAll(ctx) {
|
|
77
|
+
for (const plugin of this.sortedByDeps()) {
|
|
78
|
+
if (!plugin.init)
|
|
79
|
+
continue;
|
|
80
|
+
try {
|
|
81
|
+
await plugin.init(ctx);
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
this.eventBus.emit('error', { plugin: plugin.name, error: err });
|
|
85
|
+
throw err;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Run destroy() in reverse init order. Errors are swallowed and emitted
|
|
91
|
+
* so one bad plugin doesn't prevent others from cleaning up.
|
|
92
|
+
*/
|
|
93
|
+
async destroyAll(ctx) {
|
|
94
|
+
const plugins = [...this.sortedByDeps()].reverse();
|
|
95
|
+
for (const plugin of plugins) {
|
|
96
|
+
if (!plugin.destroy)
|
|
97
|
+
continue;
|
|
98
|
+
try {
|
|
99
|
+
await plugin.destroy(ctx);
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
this.eventBus.emit('error', { plugin: plugin.name, error: err });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=plugin.js.map
|
package/es/plugin.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
|
package/es/types.js
ADDED
package/es/types.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
|
package/lib/di.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @monbolc/lowcode-editor-core — DIContainer
|
|
3
|
+
*
|
|
4
|
+
* A tiny, type-friendly dependency injection container. Each entry is
|
|
5
|
+
* keyed by a constructor / factory function. Resolve is async because
|
|
6
|
+
* a factory may itself call `get(...)` to satisfy its own deps.
|
|
7
|
+
*
|
|
8
|
+
* Why a custom container instead of InversifyJS / tsyringe?
|
|
9
|
+
* - The editor's DI surface is small (a few dozen services at most).
|
|
10
|
+
* - We want zero runtime deps and a TS-friendly API.
|
|
11
|
+
* - We do not need scoping / child containers / aspect interception.
|
|
12
|
+
*/
|
|
13
|
+
export type Factory<T> = (container: DIContainer) => T | Promise<T>;
|
|
14
|
+
export declare class DIContainer {
|
|
15
|
+
private readonly services;
|
|
16
|
+
private readonly id;
|
|
17
|
+
/**
|
|
18
|
+
* Register a service. The `ctor` is the lookup key — call `get(ctor)`
|
|
19
|
+
* to retrieve the instance. The `factory` builds the instance.
|
|
20
|
+
*/
|
|
21
|
+
register<T>(ctor: Factory<T>, factory: Factory<T>, scope?: 'singleton' | 'transient'): void;
|
|
22
|
+
/**
|
|
23
|
+
* Resolve a service. If a singleton was already built, returns the
|
|
24
|
+
* cached instance; otherwise calls the factory and caches.
|
|
25
|
+
*/
|
|
26
|
+
get<T>(ctor: Factory<T>): Promise<T>;
|
|
27
|
+
/**
|
|
28
|
+
* Synchronous lookup. Returns undefined if the service is not yet built
|
|
29
|
+
* (or not registered at all). Useful for non-async consumers.
|
|
30
|
+
*/
|
|
31
|
+
peek<T>(ctor: Factory<T>): T | undefined;
|
|
32
|
+
/** True if a factory is registered for this key. */
|
|
33
|
+
has<T>(ctor: Factory<T>): boolean;
|
|
34
|
+
/** Drop all registrations and cached instances. */
|
|
35
|
+
clear(): void;
|
|
36
|
+
/** Number of registered services. */
|
|
37
|
+
size(): number;
|
|
38
|
+
}
|
package/lib/di.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @monbolc/lowcode-editor-core — DIContainer
|
|
4
|
+
*
|
|
5
|
+
* A tiny, type-friendly dependency injection container. Each entry is
|
|
6
|
+
* keyed by a constructor / factory function. Resolve is async because
|
|
7
|
+
* a factory may itself call `get(...)` to satisfy its own deps.
|
|
8
|
+
*
|
|
9
|
+
* Why a custom container instead of InversifyJS / tsyringe?
|
|
10
|
+
* - The editor's DI surface is small (a few dozen services at most).
|
|
11
|
+
* - We want zero runtime deps and a TS-friendly API.
|
|
12
|
+
* - We do not need scoping / child containers / aspect interception.
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.DIContainer = void 0;
|
|
16
|
+
const lowcode_utils_1 = require("@monbolc/lowcode-utils");
|
|
17
|
+
class DIContainer {
|
|
18
|
+
constructor() {
|
|
19
|
+
this.services = new Map();
|
|
20
|
+
this.id = (0, lowcode_utils_1.uid)('di');
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Register a service. The `ctor` is the lookup key — call `get(ctor)`
|
|
24
|
+
* to retrieve the instance. The `factory` builds the instance.
|
|
25
|
+
*/
|
|
26
|
+
register(ctor, factory, scope = 'singleton') {
|
|
27
|
+
if (this.services.has(ctor)) {
|
|
28
|
+
throw new Error(`[DIContainer:${this.id}] service already registered for this ctor`);
|
|
29
|
+
}
|
|
30
|
+
this.services.set(ctor, { factory, scope });
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Resolve a service. If a singleton was already built, returns the
|
|
34
|
+
* cached instance; otherwise calls the factory and caches.
|
|
35
|
+
*/
|
|
36
|
+
async get(ctor) {
|
|
37
|
+
const reg = this.services.get(ctor);
|
|
38
|
+
if (!reg) {
|
|
39
|
+
throw new Error(`[DIContainer:${this.id}] no service registered for this ctor`);
|
|
40
|
+
}
|
|
41
|
+
if (reg.instance !== undefined && reg.scope === 'singleton') {
|
|
42
|
+
return reg.instance;
|
|
43
|
+
}
|
|
44
|
+
const instance = await reg.factory(this);
|
|
45
|
+
if (reg.scope === 'singleton')
|
|
46
|
+
reg.instance = instance;
|
|
47
|
+
return instance;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Synchronous lookup. Returns undefined if the service is not yet built
|
|
51
|
+
* (or not registered at all). Useful for non-async consumers.
|
|
52
|
+
*/
|
|
53
|
+
peek(ctor) {
|
|
54
|
+
const reg = this.services.get(ctor);
|
|
55
|
+
return reg?.instance;
|
|
56
|
+
}
|
|
57
|
+
/** True if a factory is registered for this key. */
|
|
58
|
+
has(ctor) {
|
|
59
|
+
return this.services.has(ctor);
|
|
60
|
+
}
|
|
61
|
+
/** Drop all registrations and cached instances. */
|
|
62
|
+
clear() {
|
|
63
|
+
this.services.clear();
|
|
64
|
+
}
|
|
65
|
+
/** Number of registered services. */
|
|
66
|
+
size() {
|
|
67
|
+
return this.services.size;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
exports.DIContainer = DIContainer;
|
|
71
|
+
//# sourceMappingURL=di.js.map
|
package/lib/di.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"di.js","sourceRoot":"","sources":["../src/di.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;GAWG;;;AAEH,0DAA6C;AAa7C,MAAa,WAAW;IAAxB;QACmB,aAAQ,GAAG,IAAI,GAAG,EAA2C,CAAC;QAC9D,OAAE,GAAG,IAAA,mBAAG,EAAC,IAAI,CAAC,CAAC;IAqDlC,CAAC;IAnDC;;;OAGG;IACH,QAAQ,CAAI,IAAgB,EAAE,OAAmB,EAAE,QAAmC,WAAW;QAC/F,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAwB,CAAC,EAAE,CAAC;YAChD,MAAM,IAAI,KAAK,CAAC,gBAAgB,IAAI,CAAC,EAAE,4CAA4C,CAAC,CAAC;QACvF,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAwB,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;IAClE,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,GAAG,CAAI,IAAgB;QAC3B,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAwB,CAAC,CAAC;QACxD,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,MAAM,IAAI,KAAK,CAAC,gBAAgB,IAAI,CAAC,EAAE,uCAAuC,CAAC,CAAC;QAClF,CAAC;QACD,IAAI,GAAG,CAAC,QAAQ,KAAK,SAAS,IAAI,GAAG,CAAC,KAAK,KAAK,WAAW,EAAE,CAAC;YAC5D,OAAO,GAAG,CAAC,QAAa,CAAC;QAC3B,CAAC;QACD,MAAM,QAAQ,GAAG,MAAO,GAAG,CAAC,OAAsB,CAAC,IAAI,CAAC,CAAC;QACzD,IAAI,GAAG,CAAC,KAAK,KAAK,WAAW;YAAE,GAAG,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACvD,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED;;;OAGG;IACH,IAAI,CAAI,IAAgB;QACtB,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAwB,CAAC,CAAC;QACxD,OAAO,GAAG,EAAE,QAAyB,CAAC;IACxC,CAAC;IAED,oDAAoD;IACpD,GAAG,CAAI,IAAgB;QACrB,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAwB,CAAC,CAAC;IACrD,CAAC;IAED,mDAAmD;IACnD,KAAK;QACH,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;IAED,qCAAqC;IACrC,IAAI;QACF,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;IAC5B,CAAC;CACF;AAvDD,kCAuDC"}
|
package/lib/editor.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @monbolc/lowcode-editor-core — Editor (composition root)
|
|
3
|
+
*
|
|
4
|
+
* The `Editor` class wires DI, i18n, the plugin manager, and the command
|
|
5
|
+
* manager into a single object. Subclasses (e.g. a future
|
|
6
|
+
* `BrowserEditor`) can override `init` to also mount UI.
|
|
7
|
+
*/
|
|
8
|
+
import { Emitter } from '@monbolc/lowcode-utils';
|
|
9
|
+
import { CommandManager } from '@monbolc/lowcode-plugin-command';
|
|
10
|
+
import { DIContainer } from './di';
|
|
11
|
+
import type { Factory } from './di';
|
|
12
|
+
import type { EditorEvents, IEditor, IPlugin, IPluginManager, I18n } from './types';
|
|
13
|
+
export interface EditorOptions {
|
|
14
|
+
/** Locale to use by default (e.g. "en_US", "zh_CN"). Empty = `default`. */
|
|
15
|
+
locale?: string;
|
|
16
|
+
/** Pre-registered plugins. May be added later via `init` too. */
|
|
17
|
+
plugins?: IPlugin[];
|
|
18
|
+
}
|
|
19
|
+
export declare class Editor implements IEditor {
|
|
20
|
+
readonly events: Emitter<EditorEvents>;
|
|
21
|
+
readonly i18n: I18n;
|
|
22
|
+
readonly di: DIContainer;
|
|
23
|
+
readonly plugins: IPluginManager;
|
|
24
|
+
readonly commands: CommandManager;
|
|
25
|
+
readonly id: string;
|
|
26
|
+
private _ready;
|
|
27
|
+
private readonly initialPlugins;
|
|
28
|
+
constructor(options?: EditorOptions);
|
|
29
|
+
get ready(): boolean;
|
|
30
|
+
init(extraPlugins?: IPlugin[]): Promise<void>;
|
|
31
|
+
destroy(): Promise<void>;
|
|
32
|
+
get<T>(ctor: Factory<T>): Promise<T>;
|
|
33
|
+
peek<T>(ctor: Factory<T>): T | undefined;
|
|
34
|
+
}
|
package/lib/editor.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @monbolc/lowcode-editor-core — Editor (composition root)
|
|
4
|
+
*
|
|
5
|
+
* The `Editor` class wires DI, i18n, the plugin manager, and the command
|
|
6
|
+
* manager into a single object. Subclasses (e.g. a future
|
|
7
|
+
* `BrowserEditor`) can override `init` to also mount UI.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.Editor = void 0;
|
|
11
|
+
const lowcode_utils_1 = require("@monbolc/lowcode-utils");
|
|
12
|
+
const lowcode_plugin_command_1 = require("@monbolc/lowcode-plugin-command");
|
|
13
|
+
const di_1 = require("./di");
|
|
14
|
+
const i18n_1 = require("./i18n");
|
|
15
|
+
const plugin_1 = require("./plugin");
|
|
16
|
+
class Editor {
|
|
17
|
+
constructor(options = {}) {
|
|
18
|
+
this.events = new lowcode_utils_1.Emitter();
|
|
19
|
+
this.i18n = new i18n_1.I18nImpl();
|
|
20
|
+
this.di = new di_1.DIContainer();
|
|
21
|
+
this.commands = new lowcode_plugin_command_1.CommandManager();
|
|
22
|
+
this._ready = false;
|
|
23
|
+
this.id = `editor_${Math.random().toString(36).slice(2, 10)}`;
|
|
24
|
+
this.plugins = new plugin_1.PluginManager(this.events);
|
|
25
|
+
this.initialPlugins = options.plugins ?? [];
|
|
26
|
+
if (options.locale)
|
|
27
|
+
this.i18n.setLocale(options.locale);
|
|
28
|
+
}
|
|
29
|
+
get ready() {
|
|
30
|
+
return this._ready;
|
|
31
|
+
}
|
|
32
|
+
async init(extraPlugins = []) {
|
|
33
|
+
if (this._ready) {
|
|
34
|
+
throw new Error(`[Editor:${this.id}] init() called twice`);
|
|
35
|
+
}
|
|
36
|
+
this.events.emit('phase', { name: 'init' });
|
|
37
|
+
for (const p of this.initialPlugins)
|
|
38
|
+
this.plugins.register(p);
|
|
39
|
+
for (const p of extraPlugins)
|
|
40
|
+
this.plugins.register(p);
|
|
41
|
+
this.events.emit('phase', { name: 'register' });
|
|
42
|
+
const ctx = {
|
|
43
|
+
editor: this,
|
|
44
|
+
i18n: this.i18n,
|
|
45
|
+
di: this.di,
|
|
46
|
+
events: this.events,
|
|
47
|
+
plugins: this.plugins,
|
|
48
|
+
commands: this.commands,
|
|
49
|
+
};
|
|
50
|
+
await this.plugins.initAll(ctx);
|
|
51
|
+
this._ready = true;
|
|
52
|
+
this.events.emit('phase', { name: 'ready' });
|
|
53
|
+
}
|
|
54
|
+
async destroy() {
|
|
55
|
+
if (!this._ready)
|
|
56
|
+
return;
|
|
57
|
+
this.events.emit('phase', { name: 'destroy' });
|
|
58
|
+
const ctx = {
|
|
59
|
+
editor: this,
|
|
60
|
+
i18n: this.i18n,
|
|
61
|
+
di: this.di,
|
|
62
|
+
events: this.events,
|
|
63
|
+
plugins: this.plugins,
|
|
64
|
+
commands: this.commands,
|
|
65
|
+
};
|
|
66
|
+
await this.plugins.destroyAll(ctx);
|
|
67
|
+
this.di.clear();
|
|
68
|
+
this.commands.clearHistory();
|
|
69
|
+
this._ready = false;
|
|
70
|
+
}
|
|
71
|
+
async get(ctor) {
|
|
72
|
+
return this.di.get(ctor);
|
|
73
|
+
}
|
|
74
|
+
peek(ctor) {
|
|
75
|
+
return this.di.peek(ctor);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
exports.Editor = Editor;
|
|
79
|
+
//# sourceMappingURL=editor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"editor.js","sourceRoot":"","sources":["../src/editor.ts"],"names":[],"mappings":"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"}
|
package/lib/i18n.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @monbolc/lowcode-editor-core — I18n
|
|
3
|
+
*
|
|
4
|
+
* Tiny internationalization helper. Plugins call `i18n.register({...})`
|
|
5
|
+
* to add strings; consumers call `i18n.t('pluginName.someKey')` to get
|
|
6
|
+
* a string in the active locale (or the default if missing).
|
|
7
|
+
*
|
|
8
|
+
* Shape: { default: string, byLocale?: Record<locale, string> }
|
|
9
|
+
* Example:
|
|
10
|
+
* i18n.register({ 'designer.duplicate': { default: 'Duplicate', byLocale: { zh_CN: '复制' } } });
|
|
11
|
+
* i18n.t('designer.duplicate'); // 'Duplicate' (or '复制' if locale='zh_CN')
|
|
12
|
+
*/
|
|
13
|
+
import type { I18n, I18nMessage } from './types';
|
|
14
|
+
export declare class I18nImpl implements I18n {
|
|
15
|
+
private readonly messages;
|
|
16
|
+
private _locale;
|
|
17
|
+
get locale(): string;
|
|
18
|
+
setLocale(locale: string): void;
|
|
19
|
+
register(messages: Record<string, I18nMessage | string>): void;
|
|
20
|
+
t(id: string, locale?: string, fallback?: string): string;
|
|
21
|
+
/** Number of registered message ids. */
|
|
22
|
+
size(): number;
|
|
23
|
+
}
|
package/lib/i18n.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @monbolc/lowcode-editor-core — I18n
|
|
4
|
+
*
|
|
5
|
+
* Tiny internationalization helper. Plugins call `i18n.register({...})`
|
|
6
|
+
* to add strings; consumers call `i18n.t('pluginName.someKey')` to get
|
|
7
|
+
* a string in the active locale (or the default if missing).
|
|
8
|
+
*
|
|
9
|
+
* Shape: { default: string, byLocale?: Record<locale, string> }
|
|
10
|
+
* Example:
|
|
11
|
+
* i18n.register({ 'designer.duplicate': { default: 'Duplicate', byLocale: { zh_CN: '复制' } } });
|
|
12
|
+
* i18n.t('designer.duplicate'); // 'Duplicate' (or '复制' if locale='zh_CN')
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.I18nImpl = void 0;
|
|
16
|
+
class I18nImpl {
|
|
17
|
+
constructor() {
|
|
18
|
+
this.messages = new Map();
|
|
19
|
+
this._locale = '';
|
|
20
|
+
}
|
|
21
|
+
get locale() {
|
|
22
|
+
return this._locale;
|
|
23
|
+
}
|
|
24
|
+
setLocale(locale) {
|
|
25
|
+
this._locale = locale ?? '';
|
|
26
|
+
}
|
|
27
|
+
register(messages) {
|
|
28
|
+
for (const [id, value] of Object.entries(messages)) {
|
|
29
|
+
// Allow shorthand: `'foo': 'Hello'` instead of `{ default: 'Hello' }`.
|
|
30
|
+
const normalized = typeof value === 'string' ? { default: value } : value;
|
|
31
|
+
this.messages.set(id, normalized);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
t(id, locale, fallback) {
|
|
35
|
+
const msg = this.messages.get(id);
|
|
36
|
+
if (!msg) {
|
|
37
|
+
if (fallback !== undefined)
|
|
38
|
+
return fallback;
|
|
39
|
+
// Last resort: return the id itself so missing translations are visible.
|
|
40
|
+
return id;
|
|
41
|
+
}
|
|
42
|
+
const activeLocale = locale ?? this._locale;
|
|
43
|
+
if (activeLocale && msg.byLocale?.[activeLocale] !== undefined) {
|
|
44
|
+
return msg.byLocale[activeLocale];
|
|
45
|
+
}
|
|
46
|
+
return msg.default;
|
|
47
|
+
}
|
|
48
|
+
/** Number of registered message ids. */
|
|
49
|
+
size() {
|
|
50
|
+
return this.messages.size;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
exports.I18nImpl = I18nImpl;
|
|
54
|
+
//# sourceMappingURL=i18n.js.map
|
package/lib/i18n.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n.js","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;GAWG;;;AAIH,MAAa,QAAQ;IAArB;QACmB,aAAQ,GAAG,IAAI,GAAG,EAAuB,CAAC;QACnD,YAAO,GAAW,EAAE,CAAC;IAqC/B,CAAC;IAnCC,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED,SAAS,CAAC,MAAc;QACtB,IAAI,CAAC,OAAO,GAAG,MAAM,IAAI,EAAE,CAAC;IAC9B,CAAC;IAED,QAAQ,CAAC,QAA8C;QACrD,KAAK,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;YACnD,uEAAuE;YACvE,MAAM,UAAU,GACd,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;YACzD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,EAAE,UAAU,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;IAED,CAAC,CAAC,EAAU,EAAE,MAAe,EAAE,QAAiB;QAC9C,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAClC,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,IAAI,QAAQ,KAAK,SAAS;gBAAE,OAAO,QAAQ,CAAC;YAC5C,yEAAyE;YACzE,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,MAAM,YAAY,GAAG,MAAM,IAAI,IAAI,CAAC,OAAO,CAAC;QAC5C,IAAI,YAAY,IAAI,GAAG,CAAC,QAAQ,EAAE,CAAC,YAAY,CAAC,KAAK,SAAS,EAAE,CAAC;YAC/D,OAAO,GAAG,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;QACpC,CAAC;QACD,OAAO,GAAG,CAAC,OAAO,CAAC;IACrB,CAAC;IAED,wCAAwC;IACxC,IAAI;QACF,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;IAC5B,CAAC;CACF;AAvCD,4BAuCC"}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @monbolc/lowcode-editor-core — barrel export
|
|
3
|
+
*
|
|
4
|
+
* SapuLowcodeEngine core API surface (L2). DI + i18n + plugin registry +
|
|
5
|
+
* command manager + event bus. No React, no mobx — that's L3+.
|
|
6
|
+
*/
|
|
7
|
+
export type { EditorEvents, EditorPhase, IEditor, I18n, I18nMessage, IPlugin, IPluginContext, IPluginManager, ServiceFactory, } from './types';
|
|
8
|
+
export { DIContainer } from './di';
|
|
9
|
+
export type { Factory } from './di';
|
|
10
|
+
export { I18nImpl } from './i18n';
|
|
11
|
+
export { PluginManager } from './plugin';
|
|
12
|
+
export { Editor } from './editor';
|
|
13
|
+
export type { EditorOptions } from './editor';
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @monbolc/lowcode-editor-core — barrel export
|
|
4
|
+
*
|
|
5
|
+
* SapuLowcodeEngine core API surface (L2). DI + i18n + plugin registry +
|
|
6
|
+
* command manager + event bus. No React, no mobx — that's L3+.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.Editor = exports.PluginManager = exports.I18nImpl = exports.DIContainer = void 0;
|
|
10
|
+
// --- DI ---
|
|
11
|
+
var di_1 = require("./di");
|
|
12
|
+
Object.defineProperty(exports, "DIContainer", { enumerable: true, get: function () { return di_1.DIContainer; } });
|
|
13
|
+
// --- i18n ---
|
|
14
|
+
var i18n_1 = require("./i18n");
|
|
15
|
+
Object.defineProperty(exports, "I18nImpl", { enumerable: true, get: function () { return i18n_1.I18nImpl; } });
|
|
16
|
+
// --- plugin manager ---
|
|
17
|
+
var plugin_1 = require("./plugin");
|
|
18
|
+
Object.defineProperty(exports, "PluginManager", { enumerable: true, get: function () { return plugin_1.PluginManager; } });
|
|
19
|
+
// --- editor (composition root) ---
|
|
20
|
+
var editor_1 = require("./editor");
|
|
21
|
+
Object.defineProperty(exports, "Editor", { enumerable: true, get: function () { return editor_1.Editor; } });
|
|
22
|
+
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAeH,aAAa;AACb,2BAAmC;AAA1B,iGAAA,WAAW,OAAA;AAGpB,eAAe;AACf,+BAAkC;AAAzB,gGAAA,QAAQ,OAAA;AAEjB,yBAAyB;AACzB,mCAAyC;AAAhC,uGAAA,aAAa,OAAA;AAEtB,oCAAoC;AACpC,mCAAkC;AAAzB,gGAAA,MAAM,OAAA"}
|
package/lib/plugin.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @monbolc/lowcode-editor-core — PluginManager
|
|
3
|
+
*
|
|
4
|
+
* Validates plugin names, topologically sorts by `dependencies`,
|
|
5
|
+
* and exposes registration APIs.
|
|
6
|
+
*/
|
|
7
|
+
import { Emitter } from '@monbolc/lowcode-utils';
|
|
8
|
+
import type { EditorEvents, IPlugin, IPluginContext, IPluginManager } from './types';
|
|
9
|
+
export declare class PluginManager implements IPluginManager {
|
|
10
|
+
private readonly plugins;
|
|
11
|
+
private sortedCache;
|
|
12
|
+
private readonly eventBus;
|
|
13
|
+
constructor(eventBus: Emitter<EditorEvents>);
|
|
14
|
+
register(plugin: IPlugin): void;
|
|
15
|
+
unregister(name: string): boolean;
|
|
16
|
+
has(name: string): boolean;
|
|
17
|
+
get(name: string): IPlugin | undefined;
|
|
18
|
+
list(): IPlugin[];
|
|
19
|
+
/**
|
|
20
|
+
* Return plugins in dependency order: deps first, then dependents.
|
|
21
|
+
* Throws on circular or missing deps.
|
|
22
|
+
*/
|
|
23
|
+
sortedByDeps(): IPlugin[];
|
|
24
|
+
/**
|
|
25
|
+
* Run init() for each plugin in dep order. If any throws, the error
|
|
26
|
+
* is rethrown but earlier plugins remain initialized.
|
|
27
|
+
*/
|
|
28
|
+
initAll(ctx: IPluginContext): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Run destroy() in reverse init order. Errors are swallowed and emitted
|
|
31
|
+
* so one bad plugin doesn't prevent others from cleaning up.
|
|
32
|
+
*/
|
|
33
|
+
destroyAll(ctx: IPluginContext): Promise<void>;
|
|
34
|
+
}
|
package/lib/plugin.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @monbolc/lowcode-editor-core — PluginManager
|
|
4
|
+
*
|
|
5
|
+
* Validates plugin names, topologically sorts by `dependencies`,
|
|
6
|
+
* and exposes registration APIs.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.PluginManager = void 0;
|
|
10
|
+
class PluginManager {
|
|
11
|
+
constructor(eventBus) {
|
|
12
|
+
this.plugins = new Map();
|
|
13
|
+
this.sortedCache = null;
|
|
14
|
+
this.eventBus = eventBus;
|
|
15
|
+
}
|
|
16
|
+
register(plugin) {
|
|
17
|
+
if (this.plugins.has(plugin.name)) {
|
|
18
|
+
throw new Error(`[PluginManager] plugin "${plugin.name}" is already registered`);
|
|
19
|
+
}
|
|
20
|
+
if (!/^[A-Za-z0-9_@./\-]+$/.test(plugin.name)) {
|
|
21
|
+
throw new Error(`[PluginManager] plugin name "${plugin.name}" contains invalid characters`);
|
|
22
|
+
}
|
|
23
|
+
this.plugins.set(plugin.name, plugin);
|
|
24
|
+
this.sortedCache = null; // invalidate sort cache
|
|
25
|
+
this.eventBus.emit('pluginRegistered', { name: plugin.name });
|
|
26
|
+
}
|
|
27
|
+
unregister(name) {
|
|
28
|
+
const existed = this.plugins.delete(name);
|
|
29
|
+
if (existed)
|
|
30
|
+
this.sortedCache = null;
|
|
31
|
+
return existed;
|
|
32
|
+
}
|
|
33
|
+
has(name) {
|
|
34
|
+
return this.plugins.has(name);
|
|
35
|
+
}
|
|
36
|
+
get(name) {
|
|
37
|
+
return this.plugins.get(name);
|
|
38
|
+
}
|
|
39
|
+
list() {
|
|
40
|
+
return Array.from(this.plugins.values());
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Return plugins in dependency order: deps first, then dependents.
|
|
44
|
+
* Throws on circular or missing deps.
|
|
45
|
+
*/
|
|
46
|
+
sortedByDeps() {
|
|
47
|
+
if (this.sortedCache)
|
|
48
|
+
return this.sortedCache;
|
|
49
|
+
const result = [];
|
|
50
|
+
const visited = new Set();
|
|
51
|
+
const visiting = new Set();
|
|
52
|
+
const visit = (plugin) => {
|
|
53
|
+
if (visited.has(plugin.name))
|
|
54
|
+
return;
|
|
55
|
+
if (visiting.has(plugin.name)) {
|
|
56
|
+
throw new Error(`[PluginManager] circular dependency at "${plugin.name}"`);
|
|
57
|
+
}
|
|
58
|
+
visiting.add(plugin.name);
|
|
59
|
+
for (const dep of plugin.dependencies ?? []) {
|
|
60
|
+
const sub = this.plugins.get(dep);
|
|
61
|
+
if (!sub) {
|
|
62
|
+
throw new Error(`[PluginManager] plugin "${plugin.name}" depends on missing plugin "${dep}"`);
|
|
63
|
+
}
|
|
64
|
+
visit(sub);
|
|
65
|
+
}
|
|
66
|
+
visiting.delete(plugin.name);
|
|
67
|
+
visited.add(plugin.name);
|
|
68
|
+
result.push(plugin);
|
|
69
|
+
};
|
|
70
|
+
for (const plugin of this.plugins.values())
|
|
71
|
+
visit(plugin);
|
|
72
|
+
this.sortedCache = result;
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Run init() for each plugin in dep order. If any throws, the error
|
|
77
|
+
* is rethrown but earlier plugins remain initialized.
|
|
78
|
+
*/
|
|
79
|
+
async initAll(ctx) {
|
|
80
|
+
for (const plugin of this.sortedByDeps()) {
|
|
81
|
+
if (!plugin.init)
|
|
82
|
+
continue;
|
|
83
|
+
try {
|
|
84
|
+
await plugin.init(ctx);
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
this.eventBus.emit('error', { plugin: plugin.name, error: err });
|
|
88
|
+
throw err;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Run destroy() in reverse init order. Errors are swallowed and emitted
|
|
94
|
+
* so one bad plugin doesn't prevent others from cleaning up.
|
|
95
|
+
*/
|
|
96
|
+
async destroyAll(ctx) {
|
|
97
|
+
const plugins = [...this.sortedByDeps()].reverse();
|
|
98
|
+
for (const plugin of plugins) {
|
|
99
|
+
if (!plugin.destroy)
|
|
100
|
+
continue;
|
|
101
|
+
try {
|
|
102
|
+
await plugin.destroy(ctx);
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
this.eventBus.emit('error', { plugin: plugin.name, error: err });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
exports.PluginManager = PluginManager;
|
|
111
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
|
package/lib/types.d.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @monbolc/lowcode-editor-core — public types
|
|
3
|
+
*
|
|
4
|
+
* Editor public API: plugin context, editor composition root, and the
|
|
5
|
+
* service-registration shapes that plugins use to wire themselves in.
|
|
6
|
+
*/
|
|
7
|
+
import type { Emitter } from '@monbolc/lowcode-utils';
|
|
8
|
+
import type { ICommandManager } from '@monbolc/lowcode-plugin-command';
|
|
9
|
+
import type { DIContainer, Factory } from './di';
|
|
10
|
+
/**
|
|
11
|
+
* Alias for `Factory<T>` — used as the registration key in DI.
|
|
12
|
+
* Exposed as a separate name so plugin authors can think of it as
|
|
13
|
+
* "the key I registered this service under".
|
|
14
|
+
*/
|
|
15
|
+
export type ServiceFactory<T> = Factory<T>;
|
|
16
|
+
/** Lifecycle phases fired in order during Editor.init(). */
|
|
17
|
+
export type EditorPhase = 'init' | 'register' | 'ready' | 'destroy';
|
|
18
|
+
export interface EditorEvents extends Record<string, unknown> {
|
|
19
|
+
/** A lifecycle phase is about to enter. */
|
|
20
|
+
phase: {
|
|
21
|
+
name: EditorPhase;
|
|
22
|
+
};
|
|
23
|
+
/** An error happened in a plugin. */
|
|
24
|
+
error: {
|
|
25
|
+
plugin: string;
|
|
26
|
+
error: unknown;
|
|
27
|
+
};
|
|
28
|
+
/** A plugin was registered. */
|
|
29
|
+
pluginRegistered: {
|
|
30
|
+
name: string;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The public context object passed to each plugin's `init` callback.
|
|
35
|
+
* Plugins call `ctx.something.register(...)` to wire themselves in.
|
|
36
|
+
*
|
|
37
|
+
* The full set of fields is filled out by the concrete `Editor` impl;
|
|
38
|
+
* this interface is intentionally narrow to make plugin authoring
|
|
39
|
+
* discoverable.
|
|
40
|
+
*/
|
|
41
|
+
export interface IPluginContext {
|
|
42
|
+
/** Editor identifier (for diagnostics). */
|
|
43
|
+
readonly editor: IEditor;
|
|
44
|
+
/** Internationalization helper. */
|
|
45
|
+
readonly i18n: I18n;
|
|
46
|
+
/** DI container — used by plugins to register / consume services. */
|
|
47
|
+
readonly di: DIContainer;
|
|
48
|
+
/** Event bus. */
|
|
49
|
+
readonly events: Emitter<EditorEvents>;
|
|
50
|
+
/** Plugin registry. */
|
|
51
|
+
readonly plugins: IPluginManager;
|
|
52
|
+
/** Command manager. */
|
|
53
|
+
readonly commands: ICommandManager;
|
|
54
|
+
}
|
|
55
|
+
export interface IEditor {
|
|
56
|
+
/** Emitter for editor-wide events. */
|
|
57
|
+
readonly events: Emitter<EditorEvents>;
|
|
58
|
+
/** Boot the editor. Returns when all plugins' init() has resolved. */
|
|
59
|
+
init(plugins?: IPlugin[]): Promise<void>;
|
|
60
|
+
/** Tear down all plugins and release resources. */
|
|
61
|
+
destroy(): Promise<void>;
|
|
62
|
+
/** Whether init has finished successfully. */
|
|
63
|
+
readonly ready: boolean;
|
|
64
|
+
/** Get a service from the DI container. */
|
|
65
|
+
get<T>(ctor: ServiceFactory<T>): Promise<T>;
|
|
66
|
+
/** Try to get a service synchronously. Returns undefined if not yet registered. */
|
|
67
|
+
peek<T>(ctor: ServiceFactory<T>): T | undefined;
|
|
68
|
+
}
|
|
69
|
+
export interface IPlugin {
|
|
70
|
+
/** Unique plugin name (e.g. `"@monbolc/plugin-designer"`). */
|
|
71
|
+
name: string;
|
|
72
|
+
/**
|
|
73
|
+
* Optional list of plugin names this one depends on.
|
|
74
|
+
* The PluginManager will ensure they init first.
|
|
75
|
+
*/
|
|
76
|
+
dependencies?: string[];
|
|
77
|
+
/**
|
|
78
|
+
* Initialize the plugin. Called once after all deps are ready.
|
|
79
|
+
* `ctx` exposes the public API the plugin can use to register services.
|
|
80
|
+
*/
|
|
81
|
+
init?(ctx: IPluginContext): void | Promise<void>;
|
|
82
|
+
/**
|
|
83
|
+
* Tear-down hook. Called in reverse init order.
|
|
84
|
+
*/
|
|
85
|
+
destroy?(ctx: IPluginContext): void | Promise<void>;
|
|
86
|
+
}
|
|
87
|
+
export interface IPluginManager {
|
|
88
|
+
/** Register a plugin. Validates name uniqueness. */
|
|
89
|
+
register(plugin: IPlugin): void;
|
|
90
|
+
/** Unregister a plugin by name. */
|
|
91
|
+
unregister(name: string): boolean;
|
|
92
|
+
/** True if a plugin with this name is registered. */
|
|
93
|
+
has(name: string): boolean;
|
|
94
|
+
/** Get the registered plugin, or undefined. */
|
|
95
|
+
get(name: string): IPlugin | undefined;
|
|
96
|
+
/** All registered plugins, sorted by dependency order. */
|
|
97
|
+
list(): IPlugin[];
|
|
98
|
+
}
|
|
99
|
+
export interface I18nMessage {
|
|
100
|
+
/** Default locale string. */
|
|
101
|
+
default: string;
|
|
102
|
+
/** Optional per-locale overrides. Locale code → string. */
|
|
103
|
+
byLocale?: Record<string, string>;
|
|
104
|
+
}
|
|
105
|
+
export interface I18n {
|
|
106
|
+
/** Register (or merge) a bag of messages. */
|
|
107
|
+
register(messages: Record<string, I18nMessage | string>): void;
|
|
108
|
+
/** Resolve a message id, optionally with the active locale. */
|
|
109
|
+
t(id: string, locale?: string, fallback?: string): string;
|
|
110
|
+
/** Set / change the active locale. Empty string = use `default`. */
|
|
111
|
+
setLocale(locale: string): void;
|
|
112
|
+
/** Currently active locale. */
|
|
113
|
+
readonly locale: string;
|
|
114
|
+
}
|
package/lib/types.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @monbolc/lowcode-editor-core — public types
|
|
4
|
+
*
|
|
5
|
+
* Editor public API: plugin context, editor composition root, and the
|
|
6
|
+
* service-registration shapes that plugins use to wire themselves in.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
//# sourceMappingURL=types.js.map
|
package/lib/types.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";AAAA;;;;;GAKG"}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@monbolc/lowcode-editor-core",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Core API: DI container + i18n + plugin registry for SapuLowcodeEngine (L2)",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"module": "es/index.js",
|
|
8
|
+
"types": "lib/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./lib/index.d.ts",
|
|
12
|
+
"import": "./es/index.js",
|
|
13
|
+
"require": "./lib/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": ["lib", "es"],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc -p tsconfig.json",
|
|
19
|
+
"build:es": "tsc -p tsconfig.esm.json && node ../../scripts/add-js-extensions.mjs es",
|
|
20
|
+
"clean": "rimraf lib es"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@monbolc/lowcode-types": "^2.0.0",
|
|
24
|
+
"@monbolc/lowcode-utils": "^2.0.0",
|
|
25
|
+
"@monbolc/lowcode-plugin-command": "^2.0.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"rimraf": "^5.0.5",
|
|
29
|
+
"typescript": "^5.4.5"
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public",
|
|
33
|
+
"registry": "https://registry.npmjs.org/"
|
|
34
|
+
},
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git@github.com:monbolc/sapu-lowcode-engine.git",
|
|
38
|
+
"directory": "packages/editor-core"
|
|
39
|
+
},
|
|
40
|
+
"keywords": ["lowcode", "engine", "editor", "di", "i18n", "plugin"]
|
|
41
|
+
}
|