@hotmeshio/hotmesh 0.5.3 → 0.5.4

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 (38) hide show
  1. package/README.md +67 -134
  2. package/build/index.d.ts +1 -3
  3. package/build/index.js +1 -5
  4. package/build/modules/enums.d.ts +4 -0
  5. package/build/modules/enums.js +5 -1
  6. package/build/modules/utils.d.ts +1 -9
  7. package/build/modules/utils.js +0 -6
  8. package/build/package.json +3 -4
  9. package/build/services/connector/factory.d.ts +2 -2
  10. package/build/services/connector/factory.js +11 -8
  11. package/build/services/connector/providers/postgres.d.ts +47 -0
  12. package/build/services/connector/providers/postgres.js +107 -0
  13. package/build/services/hotmesh/index.d.ts +8 -0
  14. package/build/services/hotmesh/index.js +27 -0
  15. package/build/services/memflow/client.d.ts +1 -1
  16. package/build/services/memflow/client.js +8 -6
  17. package/build/services/memflow/worker.js +3 -0
  18. package/build/services/pipe/functions/cron.js +1 -1
  19. package/build/services/store/providers/postgres/kvtables.js +19 -6
  20. package/build/services/store/providers/postgres/postgres.js +13 -2
  21. package/build/services/stream/providers/postgres/postgres.d.ts +6 -3
  22. package/build/services/stream/providers/postgres/postgres.js +169 -59
  23. package/build/services/sub/providers/postgres/postgres.d.ts +9 -0
  24. package/build/services/sub/providers/postgres/postgres.js +109 -18
  25. package/build/services/worker/index.js +4 -0
  26. package/build/types/hotmesh.d.ts +19 -5
  27. package/build/types/index.d.ts +0 -2
  28. package/env.example +11 -0
  29. package/index.ts +0 -4
  30. package/package.json +3 -4
  31. package/build/services/meshdata/index.d.ts +0 -795
  32. package/build/services/meshdata/index.js +0 -1235
  33. package/build/services/meshos/index.d.ts +0 -293
  34. package/build/services/meshos/index.js +0 -547
  35. package/build/types/manifest.d.ts +0 -52
  36. package/build/types/manifest.js +0 -2
  37. package/build/types/meshdata.d.ts +0 -252
  38. package/build/types/meshdata.js +0 -2
@@ -1,547 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MeshOS = void 0;
4
- const index_1 = require("../meshdata/index");
5
- const utils_1 = require("../../modules/utils");
6
- const logger_1 = require("../logger");
7
- /**
8
- * MeshOS is an abstract base class for schema-driven entity management within the Mesh network.
9
- * It provides a foundation for defining custom entities.
10
- * By subclassing MeshOS, you can create entities with specific schemas and behaviors, enabling
11
- * structured data storage, retrieval, and transactional workflows.
12
- *
13
- * ### Subclassing MeshOS
14
- *
15
- * Standard CRUD methods are included and use your provided schema to
16
- * fields to return in the response: create, retrieve, update, delete.
17
- *
18
- * Search methods are included and use your provided schema to
19
- * fields to return in the response: count, find, aggregate.
20
- *
21
- * Implement other methods as needed for the entity's
22
- * functionality; For example, subclass/override methods like `create`
23
- * to also spawn a transactional workflow
24
- *
25
- * @example
26
- * ```typescript
27
- * import { MeshOS } from '@hotmeshio/hotmesh';
28
- * import { Types } from '@hotmeshio/hotmesh';
29
- * import { schema } from './schema'; // Import your schema
30
- * import * as workflows from './workflows';
31
- *
32
- * class Widget extends MeshOS {
33
- *
34
- * //Subclass the `connect` method to connect workers and
35
- * // hooks (optional) when the container starts
36
- * async connect() {
37
- * await this.meshData.connect({
38
- * entity: this.getEntity(),
39
- * //the `target widget workflow` runs as a transaction
40
- * target: function() {
41
- * return { hello: 'world' };
42
- * },
43
- * options: {
44
- * namespace: this.getNamespace(),
45
- * taskQueue: this.getTaskQueue(),
46
- * },
47
- * });
48
- * }
49
- *
50
- * // subclass the `create` method to start a transactional
51
- * // workflow; use the options/search field to set default
52
- * // record data `{ ...input}` and invoke the `target widget workflow`
53
- * async create(input: Types.StringAnyType): Promise<Types.StringStringType> {
54
- * return await this.meshData.exec<Types.StringStringType>({
55
- * entity: this.getEntity(),
56
- * args: [{ ...input }],
57
- * options: {
58
- * id: input.id,
59
- * ttl: '6 months',
60
- * taskQueue: this.getTaskQueue(),
61
- * namespace: this.getNamespace(),
62
- * search: { data: { ...input }},
63
- * },
64
- * });
65
- * }
66
- * }
67
- * ```
68
- *
69
- * ### Defining the Schema
70
- *
71
- * The schema defines the data model for your entity and is used for indexing and searching within the mesh network.
72
- * Each field in the schema specifies the data type, whether it's required, and other indexing options.
73
- *
74
- * Here's an example of a schema (`schema.ts`):
75
- *
76
- * ```typescript
77
- * import { Types } from '@hotmeshio/hotmesh';
78
- *
79
- * export const schema: Types.WorkflowSearchSchema = {
80
- * /**
81
- * * Unique identifier for the widget, including the entity prefix.
82
- * *\/
83
- * id: {
84
- * type: 'TAG',
85
- * primitive: 'string',
86
- * required: true,
87
- * examples: ['H56789'],
88
- * },
89
- * /**
90
- * * entity type
91
- * *\/
92
- * $entity: {
93
- * type: 'TAG',
94
- * primitive: 'string',
95
- * required: true,
96
- * examples: ['widget'],
97
- * },
98
- * /**
99
- * * Field indicating whether the widget is active ('y') or pruned ('n').
100
- * *\/
101
- * active: {
102
- * type: 'TAG',
103
- * primitive: 'string',
104
- * required: true,
105
- * examples: ['y', 'n'],
106
- * },
107
- * // ... other fields as needed
108
- * };
109
- * ```
110
- *
111
- * In your entity class (`Widget`), you use this schema in the
112
- * `getSearchOptions` method to define how your entity's data
113
- * is indexed and searched.
114
- */
115
- class MeshOS {
116
- /**
117
- * Instances of MeshOS are typically initialized as a set, using a manifest.json
118
- * file that describes statically the fully set names, passwords, entities, etc.
119
- * The static init method is invoked to start this process (typically at server
120
- * startup).
121
- */
122
- constructor(providerConfig, namespace, entity, taskQueue, schema) {
123
- this.connected = false;
124
- this.workflow = {};
125
- this.namespace = namespace; // e.g., 'sandbox', 'default', 'production'
126
- this.entity = entity;
127
- this.taskQueue = taskQueue;
128
- this.schema = schema;
129
- this.meshData = this.initializeMeshData(providerConfig);
130
- }
131
- /**
132
- * Return entity (e.g, book, library, user)
133
- */
134
- getEntity() {
135
- return this.entity;
136
- }
137
- /**
138
- * Get Search options (initializes the search index, specific to the backend provider)
139
- */
140
- getSearchOptions() {
141
- return {
142
- index: `${this.getNamespace()}-${this.getEntity()}`,
143
- prefix: [this.getEntity()],
144
- schema: this.getSchema(),
145
- };
146
- }
147
- /**
148
- * Speficy a more-specific task queue than the default queue (v1, v1priority, v2, acmecorp, etc)
149
- */
150
- getTaskQueue() {
151
- return this.taskQueue;
152
- }
153
- /**
154
- * Initialize MeshData instance (this backs/supports the class
155
- * --the true provider of functionality)
156
- */
157
- initializeMeshData(providerConfig) {
158
- return new index_1.MeshData(providerConfig, this.getSearchOptions());
159
- }
160
- /**
161
- * Default target function
162
- */
163
- async defaultTargetFn() {
164
- return 'OK';
165
- }
166
- /**
167
- * Get namespace
168
- */
169
- getNamespace() {
170
- return this.namespace;
171
- }
172
- /**
173
- * Get schema
174
- */
175
- getSchema() {
176
- return this.schema;
177
- }
178
- /**
179
- * Connect to the database
180
- */
181
- async connect() {
182
- this.connected = await this.meshData.connect({
183
- entity: this.getEntity(),
184
- target: this.defaultTargetFn,
185
- options: {
186
- namespace: this.getNamespace(),
187
- taskQueue: this.getTaskQueue(),
188
- },
189
- });
190
- }
191
- /**
192
- * Create the search index
193
- */
194
- async index() {
195
- await this.meshData.createSearchIndex(this.getEntity(), { namespace: this.getNamespace() }, this.getSearchOptions());
196
- }
197
- // On-container shutdown commands
198
- /**
199
- * Shutdown all connections
200
- */
201
- static async shutdown() {
202
- await index_1.MeshData.shutdown();
203
- }
204
- /**
205
- * Get index name
206
- */
207
- getIndexName() {
208
- return this.getSearchOptions().index;
209
- }
210
- /**
211
- * Create the data record
212
- * NOTE: subclasses should override this method (or create
213
- * an alternate method) for invoking a workflow when
214
- * creating the record.
215
- */
216
- async create(body) {
217
- const id = body.id || (0, utils_1.guid)();
218
- await this.meshData.set(this.getEntity(), id, {
219
- search: { data: body },
220
- namespace: this.getNamespace(),
221
- });
222
- return this.retrieve(id);
223
- }
224
- /**
225
- * Retrieve the record data
226
- */
227
- async retrieve(id, sparse = false) {
228
- const opts = this.getSearchOptions();
229
- const fields = sparse ? ['id'] : Object.keys(opts?.schema || {});
230
- const result = await this.meshData.get(this.getEntity(), id, {
231
- fields,
232
- namespace: this.getNamespace(),
233
- });
234
- if (!result?.id)
235
- throw new Error(`${this.getEntity()} not found`);
236
- return result;
237
- }
238
- /**
239
- * Update the record data
240
- */
241
- async update(id, body) {
242
- await this.retrieve(id);
243
- await this.meshData.set(this.getEntity(), id, {
244
- search: { data: body },
245
- namespace: this.getNamespace(),
246
- });
247
- return this.retrieve(id);
248
- }
249
- /**
250
- * Delete the record/workflow
251
- */
252
- async delete(id) {
253
- await this.retrieve(id);
254
- await this.meshData.flush(this.getEntity(), id, this.getNamespace());
255
- return true;
256
- }
257
- /**
258
- * Find matching records
259
- */
260
- async find(query = [], start = 0, size = 100) {
261
- const opts = this.getSearchOptions();
262
- return this.meshData.findWhere(this.getEntity(), {
263
- query,
264
- return: Object.keys(opts?.schema || {}),
265
- limit: { start, size },
266
- options: { namespace: this.getNamespace() },
267
- });
268
- }
269
- /**
270
- * Count matching entities
271
- */
272
- async count(query) {
273
- return this.meshData.findWhere(this.getEntity(), {
274
- query,
275
- count: true,
276
- options: { namespace: this.getNamespace() },
277
- });
278
- }
279
- /**
280
- * Aggregate matching entities
281
- */
282
- async aggregate(filter = [], apply = [], rows = [], columns = [], reduce = [], sort = [], start = 0, size = 100) {
283
- const command = this.buildAggregateCommand(filter, apply, rows, columns, reduce, sort);
284
- try {
285
- const results = await this.meshData.find(this.getEntity(), {
286
- index: this.getIndexName(),
287
- namespace: this.getNamespace(),
288
- taskQueue: this.getTaskQueue(),
289
- search: this.getSearchOptions(),
290
- }, ...command);
291
- return {
292
- count: results[0],
293
- query: command.join(' '),
294
- data: (0, utils_1.arrayToHash)(results),
295
- };
296
- }
297
- catch (e) {
298
- throw e;
299
- }
300
- }
301
- /**
302
- * Build aggregate command
303
- */
304
- buildAggregateCommand(filter, apply, rows, columns, reduce, sort) {
305
- const command = ['FT.AGGREGATE', this.getIndexName() || 'default'];
306
- const opts = this.getSearchOptions();
307
- // Add filter
308
- command.push(this.buildFilterCommand(filter));
309
- // Add apply
310
- apply.forEach((a) => command.push('APPLY', a.expression, 'AS', a.as));
311
- // Add groupBy
312
- const groupBy = rows.concat(columns);
313
- if (groupBy.length > 0) {
314
- command.push('GROUPBY', `${groupBy.length}`, ...groupBy.map((g) => opts?.schema?.[g] ? `@_${g}` : `@${g}`));
315
- }
316
- // Add reduce
317
- reduce.forEach((r) => {
318
- const op = r.operation.toUpperCase();
319
- if (op === 'COUNT') {
320
- command.push('REDUCE', op, '0', 'AS', r.as ?? 'count');
321
- }
322
- else if ([
323
- 'COUNT_DISTINCT',
324
- 'COUNT_DISTINCTISH',
325
- 'SUM',
326
- 'AVG',
327
- 'MIN',
328
- 'MAX',
329
- 'STDDEV',
330
- 'TOLIST',
331
- ].includes(op)) {
332
- const property = r.property
333
- ? opts?.schema?.[r.property]
334
- ? `@_${r.property}`
335
- : `@${r.property}`
336
- : '';
337
- command.push('REDUCE', op, '1', property, 'AS', r.as ?? `${r.operation}_${r.property}`);
338
- }
339
- });
340
- // Add sort
341
- if (sort.length > 0) {
342
- command.push('SORTBY', `${2 * sort.length}`, ...sort.flatMap((s) => [
343
- opts?.schema?.[s.field] ? `@_${s.field}` : `@${s.field}`,
344
- s.order.toUpperCase() || 'DESC',
345
- ]));
346
- }
347
- return command;
348
- }
349
- /**
350
- * Build filter command
351
- */
352
- buildFilterCommand(filter) {
353
- if (filter.length === 0)
354
- return '*';
355
- const opts = this.getSearchOptions();
356
- return filter
357
- .map((q) => {
358
- const type = opts?.schema?.[q.field]?.type ?? 'TEXT';
359
- switch (type) {
360
- case 'TAG':
361
- return `@_${q.field}:{${q.value}}`;
362
- case 'TEXT':
363
- return `@_${q.field}:${q.value}`;
364
- case 'NUMERIC':
365
- return `@_${q.field}:[${q.value}]`;
366
- }
367
- })
368
- .join(' ');
369
- }
370
- /**
371
- * Instance initializer
372
- */
373
- async init(search = true) {
374
- await this.connect();
375
- if (search) {
376
- await this.index();
377
- }
378
- }
379
- // Static registration methods
380
- /**
381
- * Register a database
382
- */
383
- static registerDatabase(id, config) {
384
- MeshOS.databases[id] = config;
385
- }
386
- /**
387
- * Register a namespace
388
- */
389
- static registerNamespace(id, config) {
390
- MeshOS.namespaces[id] = config;
391
- }
392
- /**
393
- * Register an entity
394
- */
395
- static registerEntity(id, config) {
396
- MeshOS.entities[id] = config;
397
- }
398
- /**
399
- * Register a schema
400
- */
401
- static registerSchema(id, schema) {
402
- MeshOS.schemas[id] = schema;
403
- }
404
- /**
405
- * Register a profile
406
- */
407
- static registerProfile(id, config) {
408
- MeshOS.profiles[id] = config;
409
- }
410
- /**
411
- * Register a class
412
- */
413
- static registerClass(id, entityClass) {
414
- MeshOS.classes[id] = entityClass;
415
- }
416
- /**
417
- * Initialize profiles
418
- */
419
- static async init(p = MeshOS.profiles) {
420
- for (const key in p) {
421
- const profile = p[key];
422
- if (profile.db?.connection) {
423
- MeshOS.logger.info(`meshos-initializing`, {
424
- db: profile.db.name,
425
- key,
426
- });
427
- profile.instances = {};
428
- for (const ns in profile.namespaces) {
429
- const namespace = profile.namespaces[ns];
430
- MeshOS.logger.info(`meshos-initializing-namespace`, {
431
- namespace: ns,
432
- label: namespace.label,
433
- });
434
- let pinstances = profile.instances[ns];
435
- if (!pinstances) {
436
- pinstances = {};
437
- profile.instances[ns] = pinstances;
438
- }
439
- for (const entity of namespace.entities) {
440
- MeshOS.logger.info(`meshos-initializing-entity`, {
441
- entity: entity.name,
442
- label: entity.label,
443
- });
444
- const instance = pinstances[entity.name] = new entity.class(profile.db.connection, ns, entity.name, entity.taskQueue, entity.schema);
445
- await instance.init(profile.db.search);
446
- }
447
- }
448
- }
449
- }
450
- }
451
- /**
452
- * Find entity instance
453
- */
454
- static findEntity(database, namespace, entity) {
455
- if (!database || !MeshOS.profiles[database]) {
456
- const activeProfiles = Object.keys(MeshOS.profiles).filter((key) => MeshOS.profiles[key]?.db);
457
- throw new Error(`The database query parameter [${database}] was not found. Use one of: ${activeProfiles.join(', ')}`);
458
- }
459
- if (!namespace || !MeshOS.profiles[database]?.instances?.[namespace]) {
460
- const activeNamespaces = Object.keys(MeshOS.profiles[database]?.instances ?? {});
461
- throw new Error(`The namespace query parameter [${namespace}] was not found. Use one of: ${activeNamespaces.join(', ')}`);
462
- }
463
- const entities = MeshOS.profiles[database]?.instances?.[namespace] ?? {};
464
- if (!entity || entity?.startsWith('-') || entity === '*') {
465
- entity = Object.keys(entities)[0];
466
- }
467
- else if (entity?.endsWith('*')) {
468
- entity = entity.slice(0, -1);
469
- }
470
- const target = MeshOS.profiles[database]?.instances?.[namespace]?.[entity];
471
- if (!target) {
472
- const fallback = Object.keys(entities)[0];
473
- MeshOS.logger.error(`meshos-entity-not-found`, {
474
- database,
475
- namespace,
476
- entity,
477
- fallback,
478
- });
479
- return MeshOS.profiles[database]?.instances?.[namespace]?.[fallback];
480
- }
481
- return target;
482
- }
483
- /**
484
- * Find schemas
485
- */
486
- static findSchemas(database, ns) {
487
- if (!database || !MeshOS.profiles[database]) {
488
- const activeProfiles = Object.keys(MeshOS.profiles).filter((key) => MeshOS.profiles[key]?.db);
489
- throw new Error(`The database query parameter [${database}] was not found. Use one of: ${activeProfiles.join(', ')}`);
490
- }
491
- const profile = MeshOS.profiles[database];
492
- const namespacedInstance = profile.instances[ns];
493
- const schemas = {};
494
- for (const entityName in namespacedInstance) {
495
- const entityInstance = namespacedInstance[entityName];
496
- const opts = entityInstance.getSearchOptions();
497
- schemas[opts.index ?? entityName] = opts.schema;
498
- }
499
- return schemas;
500
- }
501
- /**
502
- * Serialize profiles to JSON
503
- */
504
- static toJSON(p = MeshOS.profiles) {
505
- const result = {};
506
- for (const key in p) {
507
- const profile = p[key];
508
- if (!profile.db) {
509
- continue;
510
- }
511
- else {
512
- // Remove the sensitive `connection` field if present
513
- const { connection, ...dbWithoutConnection } = profile.db;
514
- result[key] = {
515
- db: { ...dbWithoutConnection },
516
- namespaces: {},
517
- };
518
- }
519
- for (const ns in profile.namespaces) {
520
- const namespace = profile.namespaces[ns];
521
- result[key].namespaces[ns] = {
522
- name: namespace.name,
523
- label: namespace.label,
524
- module: namespace.module,
525
- entities: [],
526
- };
527
- for (const entity of namespace.entities) {
528
- result[key].namespaces[ns].entities.push({
529
- name: entity.name,
530
- label: entity.label,
531
- schema: entity.schema,
532
- });
533
- }
534
- }
535
- }
536
- return result;
537
- }
538
- }
539
- exports.MeshOS = MeshOS;
540
- // Static properties
541
- MeshOS.databases = {};
542
- MeshOS.namespaces = {};
543
- MeshOS.entities = {};
544
- MeshOS.schemas = {};
545
- MeshOS.profiles = {};
546
- MeshOS.classes = {};
547
- MeshOS.logger = new logger_1.LoggerService('hotmesh', 'meshos');
@@ -1,52 +0,0 @@
1
- import { MeshOS } from '../services/meshos';
2
- import { WorkflowSearchSchema } from './memflow';
3
- import { ProviderConfig, ProvidersConfig } from './provider';
4
- export type DB = {
5
- name: string;
6
- label: string;
7
- search: boolean;
8
- connection: ProviderConfig | ProvidersConfig;
9
- };
10
- export type SubClassInstance<T extends typeof MeshOS> = T extends abstract new (...args: any) => infer R ? R : never;
11
- export type AllSubclassInstances = SubClassInstance<(typeof MeshOS)['classes'][keyof (typeof MeshOS)['classes']]>;
12
- export type EntityInstanceTypes = MeshOS;
13
- export type SubclassType<T extends MeshOS = MeshOS> = new (...args: any[]) => T;
14
- export type Entity = {
15
- name: string;
16
- label: string;
17
- /**
18
- * A more-specific value for workers when targeting version-specifc
19
- * or priority-specific task queues.
20
- * @default default
21
- */
22
- taskQueue?: string;
23
- schema: WorkflowSearchSchema;
24
- class: SubclassType;
25
- };
26
- export type Namespace = {
27
- name: string;
28
- /**
29
- * @deprecated; unused; name is the type; label is human-readable
30
- */
31
- type: string;
32
- label: string;
33
- module: 'hotmesh' | 'meshcall' | 'memflow' | 'meshdata' | 'meshos';
34
- entities: Entity[];
35
- };
36
- export type Namespaces = {
37
- [key: string]: Namespace;
38
- };
39
- export type Instance = {
40
- [key: string]: EntityInstanceTypes;
41
- };
42
- export type Instances = {
43
- [key: string]: Instance;
44
- };
45
- export type Profile = {
46
- db: DB;
47
- namespaces: Namespaces;
48
- instances?: Instances;
49
- };
50
- export type Profiles = {
51
- [key: string]: Profile;
52
- };
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });