@wabot-dev/framework 0.9.2 → 0.9.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/src/addon/async/pg/PgCronJobRepository.js +7 -5
  2. package/dist/src/addon/async/pg/PgJobRepository.js +7 -5
  3. package/dist/src/addon/async/pg/PgTransactionAdapter.js +4 -4
  4. package/dist/src/addon/auth/api-key/ApiKey.js +4 -4
  5. package/dist/src/addon/auth/api-key/PgApiKeyRepository.js +9 -8
  6. package/dist/src/addon/auth/jwt/JwtRefreshToken.js +4 -4
  7. package/dist/src/addon/auth/jwt/PgJwtRefreshTokenRepository.js +6 -5
  8. package/dist/src/addon/chat-bot/pg/PgChatMemory.js +8 -7
  9. package/dist/src/addon/chat-bot/pg/PgChatRepository.js +7 -6
  10. package/dist/src/addon/chat-controller/cmd/@cmd.js +7 -2
  11. package/dist/src/addon/chat-controller/cmd/CmdChannel.js +85 -61
  12. package/dist/src/addon/chat-controller/cmd/CmdChannelConfig.js +8 -0
  13. package/dist/src/addon/chat-controller/cmd/CmdChannelServer.js +169 -0
  14. package/dist/src/addon/chat-controller/cmd/cmdChannelSocketPath.js +16 -0
  15. package/dist/src/addon/chat-controller/cmd/runCmdClient.js +226 -0
  16. package/dist/src/core/repository/CrudRepository.js +25 -0
  17. package/dist/src/feature/pg/@pgExtension.js +33 -0
  18. package/dist/src/feature/pg/PgJsonRepositoryAdapter.js +50 -0
  19. package/dist/src/feature/pg/index.js +4 -7
  20. package/dist/src/feature/project-runner/ProjectRunner.js +67 -17
  21. package/dist/src/feature/repository/@memoryExtension.js +29 -0
  22. package/dist/src/feature/repository/@query.js +22 -0
  23. package/dist/src/feature/repository/@queryExtension.js +21 -0
  24. package/dist/src/feature/repository/@repository.js +170 -0
  25. package/dist/src/feature/repository/MemoryRepositoryAdapter.js +110 -0
  26. package/dist/src/feature/repository/RepositoryAdapterRegistry.js +27 -0
  27. package/dist/src/feature/repository/RepositoryMetadataStore.js +102 -0
  28. package/dist/src/feature/repository/evaluateQueryAst.js +134 -0
  29. package/dist/src/index.d.ts +197 -47
  30. package/dist/src/index.js +18 -7
  31. package/package.json +4 -2
  32. package/dist/src/feature/pg/query/@pgJsonRepository.js +0 -73
  33. package/dist/src/feature/pg/query/@query.js +0 -14
  34. package/dist/src/feature/pg/query/PgJsonRepository.js +0 -23
  35. package/dist/src/feature/pg/query/PgRepositoryMetadataStore.js +0 -44
  36. /package/dist/src/feature/pg/{query/buildQuerySql.js → buildQuerySql.js} +0 -0
  37. /package/dist/src/feature/{pg/query → repository}/parseQueryMethodName.js +0 -0
@@ -0,0 +1,170 @@
1
+ import { container, singleton } from '../../core/injection/index.js';
2
+ import { parseQueryMethodName } from './parseQueryMethodName.js';
3
+ import { RepositoryAdapterRegistry } from './RepositoryAdapterRegistry.js';
4
+ import { RepositoryMetadataStore } from './RepositoryMetadataStore.js';
5
+
6
+ const RUNTIME_KEY = Symbol('wabot:repositoryRuntime');
7
+ const EXTENSION_KEY = Symbol('wabot:repositoryExtension');
8
+ const AST_CACHE_KEY = Symbol('wabot:repositoryAstCache');
9
+ function getConfig(self) {
10
+ const ctor = self.constructor;
11
+ const config = container.resolve(RepositoryMetadataStore).getRepositoryConfig(ctor);
12
+ if (!config) {
13
+ throw new Error(`${ctor.name} must be decorated with @repository`);
14
+ }
15
+ return config;
16
+ }
17
+ function getRuntime(self) {
18
+ let runtime = self[RUNTIME_KEY];
19
+ if (runtime)
20
+ return runtime;
21
+ const config = getConfig(self);
22
+ const adapter = container.resolve(RepositoryAdapterRegistry).getDefault();
23
+ runtime = adapter.build(config);
24
+ Object.defineProperty(self, RUNTIME_KEY, { value: runtime, enumerable: false });
25
+ return runtime;
26
+ }
27
+ function getExtension(self) {
28
+ const cached = self[EXTENSION_KEY];
29
+ if (cached !== undefined)
30
+ return cached;
31
+ const ctor = self.constructor;
32
+ const adapter = container.resolve(RepositoryAdapterRegistry).getDefault();
33
+ const store = container.resolve(RepositoryMetadataStore);
34
+ const ExtensionCtor = store.getExtension(ctor, adapter.id);
35
+ if (!ExtensionCtor) {
36
+ throw new Error(`${ctor.name}.extension is not available: no extension registered ` +
37
+ `for adapter "${adapter.id.description ?? 'unknown'}". ` +
38
+ `Import the extension class so its @<adapter>Extension decorator runs, ` +
39
+ `or check the active adapter.`);
40
+ }
41
+ if (typeof adapter.buildExtension !== 'function') {
42
+ throw new Error(`${ctor.name}.extension cannot be built: adapter ` +
43
+ `"${adapter.id.description ?? 'unknown'}" does not implement buildExtension().`);
44
+ }
45
+ const config = getConfig(self);
46
+ const ext = adapter.buildExtension(config, ExtensionCtor);
47
+ Object.defineProperty(self, EXTENSION_KEY, { value: ext, enumerable: false });
48
+ return ext;
49
+ }
50
+ function installExtensionAccessor(target) {
51
+ const proto = target.prototype;
52
+ if (Object.getOwnPropertyDescriptor(proto, 'extension'))
53
+ return;
54
+ Object.defineProperty(proto, 'extension', {
55
+ get() {
56
+ return getExtension(this);
57
+ },
58
+ configurable: true,
59
+ enumerable: false,
60
+ });
61
+ }
62
+ function getAst(self, methodName) {
63
+ let cache = self[AST_CACHE_KEY];
64
+ if (!cache) {
65
+ cache = new Map();
66
+ Object.defineProperty(self, AST_CACHE_KEY, { value: cache, enumerable: false });
67
+ }
68
+ let ast = cache.get(methodName);
69
+ if (!ast) {
70
+ ast = parseQueryMethodName(methodName);
71
+ cache.set(methodName, ast);
72
+ }
73
+ return ast;
74
+ }
75
+ function makeExtensionImpl(methodName) {
76
+ return function (...args) {
77
+ const ext = getExtension(this);
78
+ const fn = ext[methodName];
79
+ if (typeof fn !== 'function') {
80
+ throw new Error(`${this.constructor.name}.${methodName}: ` +
81
+ `the active adapter's extension does not implement this method.`);
82
+ }
83
+ return fn.apply(ext, args);
84
+ };
85
+ }
86
+ function makeQueryImpl(methodName) {
87
+ return async function (...args) {
88
+ const ast = getAst(this, methodName);
89
+ const runtime = getRuntime(this);
90
+ switch (ast.prefix) {
91
+ case 'find':
92
+ return runtime.runQuery(ast, args);
93
+ case 'findOne': {
94
+ const rows = await runtime.runQuery(ast, args);
95
+ return rows[0] ?? null;
96
+ }
97
+ case 'count':
98
+ return runtime.runCount(ast, args);
99
+ case 'exists':
100
+ return runtime.runExists(ast, args);
101
+ case 'delete':
102
+ return runtime.runDelete(ast, args);
103
+ }
104
+ };
105
+ }
106
+ const CRUD_METHODS = {
107
+ async find(id) {
108
+ return getRuntime(this).find(id);
109
+ },
110
+ async findOrThrow(id) {
111
+ return getRuntime(this).findOrThrow(id);
112
+ },
113
+ async findByIds(ids) {
114
+ return getRuntime(this).findByIds(ids);
115
+ },
116
+ async findAll() {
117
+ return getRuntime(this).findAll();
118
+ },
119
+ async create(item) {
120
+ return getRuntime(this).create(item);
121
+ },
122
+ async update(item) {
123
+ return getRuntime(this).update(item);
124
+ },
125
+ async delete(item) {
126
+ return getRuntime(this).delete(item);
127
+ },
128
+ };
129
+ function installCrudMethods(target) {
130
+ const proto = target.prototype;
131
+ for (const [name, fn] of Object.entries(CRUD_METHODS)) {
132
+ if (Object.prototype.hasOwnProperty.call(proto, name)) {
133
+ const existing = proto[name];
134
+ if (typeof existing === 'function')
135
+ continue;
136
+ }
137
+ Object.defineProperty(proto, name, { value: fn, writable: true, configurable: true });
138
+ }
139
+ }
140
+ function repository(config) {
141
+ return function (target) {
142
+ const store = container.resolve(RepositoryMetadataStore);
143
+ store.saveRepositoryConfig(target, config);
144
+ installCrudMethods(target);
145
+ installExtensionAccessor(target);
146
+ const queryMethods = store.getQueryMethods(target);
147
+ for (const meta of queryMethods) {
148
+ if (Object.prototype.hasOwnProperty.call(target.prototype, meta.functionName)) {
149
+ const existing = target.prototype[meta.functionName];
150
+ if (typeof existing === 'function' && existing.length > 0) {
151
+ continue;
152
+ }
153
+ }
154
+ target.prototype[meta.functionName] = makeQueryImpl(meta.functionName);
155
+ }
156
+ const extensionMethods = store.getExtensionMethods(target);
157
+ for (const meta of extensionMethods) {
158
+ if (Object.prototype.hasOwnProperty.call(target.prototype, meta.functionName)) {
159
+ const existing = target.prototype[meta.functionName];
160
+ if (typeof existing === 'function' && existing.length > 0) {
161
+ continue;
162
+ }
163
+ }
164
+ target.prototype[meta.functionName] = makeExtensionImpl(meta.functionName);
165
+ }
166
+ singleton()(target);
167
+ };
168
+ }
169
+
170
+ export { repository };
@@ -0,0 +1,110 @@
1
+ import { generate } from 'short-uuid';
2
+ import { CustomError } from '../../core/error/CustomError.js';
3
+ import '../../core/error/setupErrorHandlers.js';
4
+ import { evaluateQueryAst } from './evaluateQueryAst.js';
5
+
6
+ function cloneEntity(config, item) {
7
+ const data = JSON.parse(JSON.stringify(item['data']));
8
+ return new config.constructor(data);
9
+ }
10
+ class MemoryRepositoryExtension {
11
+ items;
12
+ config;
13
+ constructor(items, config) {
14
+ this.items = items;
15
+ this.config = config;
16
+ }
17
+ clone(item) {
18
+ return cloneEntity(this.config, item);
19
+ }
20
+ }
21
+ class MemoryRepositoryRuntime {
22
+ items;
23
+ config;
24
+ constructor(items, config) {
25
+ this.items = items;
26
+ this.config = config;
27
+ }
28
+ async find(id) {
29
+ const item = this.items.get(id);
30
+ return item ? cloneEntity(this.config, item) : null;
31
+ }
32
+ async findOrThrow(id) {
33
+ const item = await this.find(id);
34
+ if (!item) {
35
+ throw new CustomError({
36
+ message: `Not found ${this.config.constructor.name} with id = '${id}'`,
37
+ httpCode: 404,
38
+ });
39
+ }
40
+ return item;
41
+ }
42
+ async findByIds(ids) {
43
+ const out = [];
44
+ for (const id of ids) {
45
+ const item = this.items.get(id);
46
+ if (item)
47
+ out.push(cloneEntity(this.config, item));
48
+ }
49
+ return out;
50
+ }
51
+ async findAll() {
52
+ return [...this.items.values()].map((i) => cloneEntity(this.config, i));
53
+ }
54
+ async create(item) {
55
+ if (item.wasCreated()) {
56
+ throw new Error('Item already created');
57
+ }
58
+ item['data'].id = generate();
59
+ item['data'].createdAt = new Date().getTime();
60
+ item.validate();
61
+ this.items.set(item.id, cloneEntity(this.config, item));
62
+ }
63
+ async update(item) {
64
+ item.validate();
65
+ if (!this.items.has(item.id)) {
66
+ throw new Error(`Update failed: no affected rows`);
67
+ }
68
+ this.items.set(item.id, cloneEntity(this.config, item));
69
+ }
70
+ async delete(item) {
71
+ this.items.delete(item.id);
72
+ }
73
+ async runQuery(ast, args) {
74
+ const result = evaluateQueryAst(this.items.values(), ast, args);
75
+ return result.map((i) => cloneEntity(this.config, i));
76
+ }
77
+ async runCount(ast, args) {
78
+ return evaluateQueryAst(this.items.values(), ast, args).length;
79
+ }
80
+ async runExists(ast, args) {
81
+ return evaluateQueryAst(this.items.values(), ast, args).length > 0;
82
+ }
83
+ async runDelete(ast, args) {
84
+ const matched = evaluateQueryAst(this.items.values(), ast, args);
85
+ for (const item of matched) {
86
+ this.items.delete(item.id);
87
+ }
88
+ }
89
+ }
90
+ const MEMORY_ADAPTER_ID = Symbol('wabot:memory-adapter');
91
+ class MemoryRepositoryAdapter {
92
+ id = MEMORY_ADAPTER_ID;
93
+ stores = new Map();
94
+ getStore(config) {
95
+ let store = this.stores.get(config);
96
+ if (!store) {
97
+ store = new Map();
98
+ this.stores.set(config, store);
99
+ }
100
+ return store;
101
+ }
102
+ build(config) {
103
+ return new MemoryRepositoryRuntime(this.getStore(config), config);
104
+ }
105
+ buildExtension(config, ExtensionCtor) {
106
+ return new ExtensionCtor(this.getStore(config), config);
107
+ }
108
+ }
109
+
110
+ export { MEMORY_ADAPTER_ID, MemoryRepositoryAdapter, MemoryRepositoryExtension };
@@ -0,0 +1,27 @@
1
+ import { __decorate } from 'tslib';
2
+ import { singleton } from '../../core/injection/index.js';
3
+
4
+ let RepositoryAdapterRegistry = class RepositoryAdapterRegistry {
5
+ adapter = null;
6
+ setDefault(adapter) {
7
+ this.adapter = adapter;
8
+ }
9
+ getDefault() {
10
+ if (!this.adapter) {
11
+ throw new Error('No repository adapter registered. ' +
12
+ 'Register one with container.resolve(RepositoryAdapterRegistry).setDefault(adapter).');
13
+ }
14
+ return this.adapter;
15
+ }
16
+ hasDefault() {
17
+ return this.adapter !== null;
18
+ }
19
+ clear() {
20
+ this.adapter = null;
21
+ }
22
+ };
23
+ RepositoryAdapterRegistry = __decorate([
24
+ singleton()
25
+ ], RepositoryAdapterRegistry);
26
+
27
+ export { RepositoryAdapterRegistry };
@@ -0,0 +1,102 @@
1
+ import { __decorate } from 'tslib';
2
+ import { singleton } from '../../core/injection/index.js';
3
+
4
+ let RepositoryMetadataStore = class RepositoryMetadataStore {
5
+ queryMethods = new Map();
6
+ extensionMethods = new Map();
7
+ repositoryConfigs = new Map();
8
+ extensions = new Map();
9
+ saveQueryMethodMetadata(metadata) {
10
+ let perClass = this.queryMethods.get(metadata.repositoryConstructor);
11
+ if (!perClass) {
12
+ perClass = new Map();
13
+ this.queryMethods.set(metadata.repositoryConstructor, perClass);
14
+ }
15
+ perClass.set(metadata.functionName, metadata);
16
+ }
17
+ saveExtensionMethodMetadata(metadata) {
18
+ let perClass = this.extensionMethods.get(metadata.repositoryConstructor);
19
+ if (!perClass) {
20
+ perClass = new Map();
21
+ this.extensionMethods.set(metadata.repositoryConstructor, perClass);
22
+ }
23
+ perClass.set(metadata.functionName, metadata);
24
+ }
25
+ saveRepositoryConfig(ctor, config) {
26
+ this.repositoryConfigs.set(ctor, config);
27
+ }
28
+ getRepositoryConfig(ctor) {
29
+ return this.repositoryConfigs.get(ctor);
30
+ }
31
+ getQueryMethods(ctor) {
32
+ return this.collectMethodsFromHierarchy(ctor, this.queryMethods);
33
+ }
34
+ getExtensionMethods(ctor) {
35
+ return this.collectMethodsFromHierarchy(ctor, this.extensionMethods);
36
+ }
37
+ collectMethodsFromHierarchy(ctor, source) {
38
+ const collected = new Map();
39
+ const hierarchy = [];
40
+ let proto = ctor.prototype;
41
+ while (proto && proto.constructor !== Object) {
42
+ hierarchy.unshift(proto.constructor);
43
+ proto = Object.getPrototypeOf(proto);
44
+ }
45
+ for (const cls of hierarchy) {
46
+ const perClass = source.get(cls);
47
+ if (perClass) {
48
+ for (const [name, meta] of perClass) {
49
+ collected.set(name, meta);
50
+ }
51
+ }
52
+ }
53
+ return [...collected.values()];
54
+ }
55
+ saveExtension(repositoryConstructor, adapterId, extensionConstructor) {
56
+ let perRepo = this.extensions.get(repositoryConstructor);
57
+ if (!perRepo) {
58
+ perRepo = new Map();
59
+ this.extensions.set(repositoryConstructor, perRepo);
60
+ }
61
+ const existing = perRepo.get(adapterId);
62
+ if (existing && existing !== extensionConstructor) {
63
+ throw new Error(`Extension conflict on ${repositoryConstructor.name}: ` +
64
+ `adapter "${adapterId.description ?? 'unknown'}" already has ` +
65
+ `extension ${existing.name}; cannot register ${extensionConstructor.name}.`);
66
+ }
67
+ perRepo.set(adapterId, extensionConstructor);
68
+ }
69
+ getExtension(ctor, adapterId) {
70
+ let proto = ctor.prototype;
71
+ while (proto && proto.constructor !== Object) {
72
+ const perRepo = this.extensions.get(proto.constructor);
73
+ if (perRepo) {
74
+ const ext = perRepo.get(adapterId);
75
+ if (ext)
76
+ return ext;
77
+ }
78
+ proto = Object.getPrototypeOf(proto);
79
+ }
80
+ return undefined;
81
+ }
82
+ validateExtensionsRegistered(adapterId) {
83
+ const offenders = [];
84
+ for (const ctor of this.extensionMethods.keys()) {
85
+ if (!this.getExtension(ctor, adapterId)) {
86
+ offenders.push(ctor.name);
87
+ }
88
+ }
89
+ if (offenders.length === 0)
90
+ return;
91
+ throw new Error(`Repository extension wiring error: the following repositories declare ` +
92
+ `@queryExtension methods but no extension is registered for adapter ` +
93
+ `"${adapterId.description ?? 'unknown'}":\n - ${offenders.join('\n - ')}\n` +
94
+ `Did you forget to import the extension classes (so their decorators run), ` +
95
+ `or are you running with the wrong adapter?`);
96
+ }
97
+ };
98
+ RepositoryMetadataStore = __decorate([
99
+ singleton()
100
+ ], RepositoryMetadataStore);
101
+
102
+ export { RepositoryMetadataStore };
@@ -0,0 +1,134 @@
1
+ function getField(item, field) {
2
+ return item?.data?.[field];
3
+ }
4
+ function compare(a, b) {
5
+ if (a == null && b == null)
6
+ return 0;
7
+ if (a == null)
8
+ return -1;
9
+ if (b == null)
10
+ return 1;
11
+ if (typeof a === 'number' && typeof b === 'number')
12
+ return a - b;
13
+ return String(a).localeCompare(String(b));
14
+ }
15
+ function likeToRegex(pattern) {
16
+ let regex = '';
17
+ for (const ch of pattern) {
18
+ if (ch === '%')
19
+ regex += '.*';
20
+ else if (ch === '_')
21
+ regex += '.';
22
+ else
23
+ regex += ch.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
24
+ }
25
+ return new RegExp('^' + regex + '$');
26
+ }
27
+ function operatorArity(op) {
28
+ if (op === 'IsNull' || op === 'IsNotNull')
29
+ return 0;
30
+ return 1;
31
+ }
32
+ function applyOperator(value, op, arg) {
33
+ switch (op) {
34
+ case 'Equals':
35
+ return value == arg;
36
+ case 'Not':
37
+ return value != arg;
38
+ case 'Like':
39
+ if (value == null)
40
+ return false;
41
+ return likeToRegex(String(arg)).test(String(value));
42
+ case 'NotLike':
43
+ if (value == null)
44
+ return false;
45
+ return !likeToRegex(String(arg)).test(String(value));
46
+ case 'In':
47
+ return Array.isArray(arg) && arg.some((x) => x == value);
48
+ case 'NotIn':
49
+ return Array.isArray(arg) && !arg.some((x) => x == value);
50
+ case 'Gt':
51
+ return value != null && arg != null && compare(value, arg) > 0;
52
+ case 'Gte':
53
+ return value != null && arg != null && compare(value, arg) >= 0;
54
+ case 'Lt':
55
+ return value != null && arg != null && compare(value, arg) < 0;
56
+ case 'Lte':
57
+ return value != null && arg != null && compare(value, arg) <= 0;
58
+ case 'IsNull':
59
+ return value == null;
60
+ case 'IsNotNull':
61
+ return value != null;
62
+ }
63
+ }
64
+ function bindArgs(conditions, args) {
65
+ let expectedArity = 0;
66
+ for (const c of conditions)
67
+ expectedArity += operatorArity(c.operator);
68
+ if (args.length !== expectedArity) {
69
+ throw new Error(`Query expected ${expectedArity} argument(s), received ${args.length}`);
70
+ }
71
+ const bound = [];
72
+ let idx = 0;
73
+ for (const cond of conditions) {
74
+ if (operatorArity(cond.operator) === 0) {
75
+ bound.push({ cond, arg: undefined });
76
+ }
77
+ else {
78
+ bound.push({ cond, arg: args[idx] });
79
+ idx += 1;
80
+ }
81
+ }
82
+ return bound;
83
+ }
84
+ // Matches SQL precedence: AND binds tighter than OR.
85
+ // Group consecutive conditions joined by And; start a new group on Or; OR across groups.
86
+ function matches(item, bound) {
87
+ if (bound.length === 0)
88
+ return true;
89
+ const groups = [[]];
90
+ for (const entry of bound) {
91
+ if (entry.cond.connector === 'Or' && groups[groups.length - 1].length > 0) {
92
+ groups.push([entry]);
93
+ }
94
+ else {
95
+ groups[groups.length - 1].push(entry);
96
+ }
97
+ }
98
+ return groups.some((group) => group.every(({ cond, arg }) => applyOperator(getField(item, cond.field), cond.operator, arg)));
99
+ }
100
+ function sortByOrderBy(items, ast) {
101
+ if (ast.orderBy.length === 0)
102
+ return items;
103
+ const copy = [...items];
104
+ copy.sort((a, b) => {
105
+ for (const o of ast.orderBy) {
106
+ const av = getField(a, o.field);
107
+ const bv = getField(b, o.field);
108
+ const c = compare(av, bv);
109
+ if (c !== 0)
110
+ return o.direction === 'ASC' ? c : -c;
111
+ }
112
+ return 0;
113
+ });
114
+ return copy;
115
+ }
116
+ function applyLimit(items, ast) {
117
+ if (ast.prefix === 'findOne')
118
+ return items.slice(0, 1);
119
+ if (ast.limit !== undefined)
120
+ return items.slice(0, ast.limit);
121
+ return items;
122
+ }
123
+ function evaluateQueryAst(items, ast, args) {
124
+ const bound = bindArgs(ast.conditions, args);
125
+ const filtered = [];
126
+ for (const item of items) {
127
+ if (matches(item, bound))
128
+ filtered.push(item);
129
+ }
130
+ const sorted = sortByOrderBy(filtered, ast);
131
+ return applyLimit(sorted, ast);
132
+ }
133
+
134
+ export { evaluateQueryAst };