@docstack/client 0.0.4 → 0.0.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 (54) hide show
  1. package/lib/core/attribute.js +406 -0
  2. package/lib/core/attribute.js.map +1 -0
  3. package/lib/core/class.d.ts +15 -16
  4. package/lib/core/class.js +761 -0
  5. package/lib/core/class.js.map +1 -0
  6. package/lib/core/crypto-engine/index.js +229 -0
  7. package/lib/core/crypto-engine/index.js.map +1 -0
  8. package/lib/core/crypto-engine/utils.js +88 -0
  9. package/lib/core/crypto-engine/utils.js.map +1 -0
  10. package/lib/core/datamodel/index.js +1308 -0
  11. package/lib/core/datamodel/index.js.map +1 -0
  12. package/lib/core/domain.d.ts +7 -8
  13. package/lib/core/domain.js +423 -0
  14. package/lib/core/domain.js.map +1 -0
  15. package/lib/core/index.js +520 -0
  16. package/lib/core/index.js.map +1 -0
  17. package/lib/core/job-engine/index.js +220 -0
  18. package/lib/core/job-engine/index.js.map +1 -0
  19. package/lib/core/policy-engine/index.js +232 -0
  20. package/lib/core/policy-engine/index.js.map +1 -0
  21. package/lib/core/query-engine/accumulators.js +258 -0
  22. package/lib/core/query-engine/accumulators.js.map +1 -0
  23. package/lib/core/query-engine/evaluator.js +179 -0
  24. package/lib/core/query-engine/evaluator.js.map +1 -0
  25. package/lib/core/query-engine/executor.js +405 -0
  26. package/lib/core/query-engine/executor.js.map +1 -0
  27. package/lib/core/query-engine/index.js +4 -0
  28. package/lib/core/query-engine/index.js.map +1 -0
  29. package/lib/core/query-engine/parser.js +515 -0
  30. package/lib/core/query-engine/parser.js.map +1 -0
  31. package/lib/core/query-engine/planner.js +330 -0
  32. package/lib/core/query-engine/planner.js.map +1 -0
  33. package/lib/core/stack.js +1817 -0
  34. package/lib/core/stack.js.map +1 -0
  35. package/lib/core/test-utils/docstack.js +222 -0
  36. package/lib/core/test-utils/docstack.js.map +1 -0
  37. package/lib/core/trigger/index.js +81 -0
  38. package/lib/core/trigger/index.js.map +1 -0
  39. package/lib/index.js +4 -8231
  40. package/lib/index.js.map +1 -1
  41. package/lib/index.umd.js +10 -4
  42. package/lib/plugins/pouchdb.js +368 -0
  43. package/lib/plugins/pouchdb.js.map +1 -0
  44. package/lib/utils/crypto/index.js +34 -0
  45. package/lib/utils/crypto/index.js.map +1 -0
  46. package/lib/utils/index.js +58 -0
  47. package/lib/utils/index.js.map +1 -0
  48. package/lib/utils/logger/index.js +20 -0
  49. package/lib/utils/logger/index.js.map +1 -0
  50. package/lib/utils/logger/transport.js +28 -0
  51. package/lib/utils/logger/transport.js.map +1 -0
  52. package/lib/workers/dataModel.js +48 -0
  53. package/lib/workers/dataModel.js.map +1 -0
  54. package/package.json +2 -2
@@ -0,0 +1,761 @@
1
+ var _a;
2
+ import { Class as Class_, isAttributeModel } from "@docstack/shared";
3
+ import createLogger from "../utils/logger/index";
4
+ import Attribute from "./attribute";
5
+ import { Trigger } from "./trigger/index";
6
+ import { z } from "zod";
7
+ import clientLogger from "../utils/logger/index";
8
+ /**
9
+ * Represents a data class (schema definition) in the DocStack database.
10
+ *
11
+ * A Class defines the structure of documents, including their attributes,
12
+ * validation rules (via Zod), and triggers that execute during document operations.
13
+ *
14
+ * Use the static factory methods ({@link Class.create}, {@link Class.fetch}) to
15
+ * instantiate classes - the constructor is private.
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * // Create a new class with schema
20
+ * const taskClass = await Class.create(stack, 'Task', 'class', 'User Tasks');
21
+ *
22
+ * // Add attributes to define the schema
23
+ * await Attribute.create(taskClass, 'title', 'string', 'Task Title', { mandatory: true });
24
+ * await Attribute.create(taskClass, 'isComplete', 'boolean', 'Done?', { defaultValue: false });
25
+ *
26
+ * // Create documents (cards) of this class
27
+ * const task = await taskClass.add({ title: 'My Task', isComplete: false });
28
+ * ```
29
+ *
30
+ * @extends Class_
31
+ */
32
+ class Class extends Class_ {
33
+ constructor() {
34
+ super();
35
+ /** Map of attribute names to Attribute instances defining the schema. */
36
+ this.attributes = {};
37
+ /** The raw schema definition from the ClassModel. */
38
+ this.schema = {};
39
+ /** Zod schema for runtime validation of document data. */
40
+ this.schemaZOD = z.object({});
41
+ /** Current state indicating if the class is processing an operation. */
42
+ this.state = "idle";
43
+ /** Array of triggers that execute before/after document operations. */
44
+ this.triggers = [];
45
+ // TODO: Test
46
+ /*
47
+ inheritAttributes( parentClass: Class ) {
48
+ let parentAttributes = parentClass.getAttributes();
49
+ for ( let attribute of parentAttributes ) {
50
+ this.addAttribute(attribute);
51
+ }
52
+ } */
53
+ this.build = () => {
54
+ return new Promise(async (resolve, reject) => {
55
+ let stack = this.getStack();
56
+ if (stack) {
57
+ // if (parentClassName) this.setParentClass(parentClassName);
58
+ let classModel = await stack.addClass(this);
59
+ // Hydrate model
60
+ if (classModel) {
61
+ this.setModel(classModel);
62
+ _a.logger.info("build - classModel", { classModel: classModel });
63
+ this.setId(classModel._id);
64
+ resolve(this);
65
+ }
66
+ else {
67
+ reject("unable to get classModel. Check logs");
68
+ }
69
+ }
70
+ else {
71
+ reject("Missing stack assignment");
72
+ }
73
+ });
74
+ };
75
+ this.init = (stack, id, name, type, description, schema = {}
76
+ // parentClass: Class | null
77
+ ) => {
78
+ // this.parentClass = parentClass;
79
+ if (stack) {
80
+ this.stack = stack;
81
+ }
82
+ this.name = name;
83
+ this.id = id;
84
+ this.description = description;
85
+ this.type = type;
86
+ // this.attributes = [];
87
+ // this.stack = null;
88
+ // this.id = null;
89
+ // if (schema) {
90
+ // this.schema = schema;
91
+ // }
92
+ this.setModel({
93
+ "~class": type, _id: id, active: true,
94
+ name, description,
95
+ schema, triggers: [],
96
+ });
97
+ this.logger = clientLogger(stack).child({ module: "class", className: this.name });
98
+ // TODO: Waiting for test of method
99
+ // if (parentClass) this.inheritAttributes(parentClass);
100
+ };
101
+ this.uniqueCheck = async (doc) => {
102
+ const fnLogger = this.logger.child({ method: "uniqueCheck", args: { doc } });
103
+ const duplicate = await this.getByPrimaryKeys(doc);
104
+ if (duplicate == null || duplicate._id == doc._id) {
105
+ fnLogger.info("No duplicate found for doc");
106
+ return true;
107
+ }
108
+ else {
109
+ fnLogger.info("Duplicate found for doc", { duplicate });
110
+ return false;
111
+ }
112
+ };
113
+ this.bulkUniqueCheck = async (pKs) => {
114
+ var _b;
115
+ const fnLogger = this.logger.child({ method: "bulkUniqueCheck", args: { pKs } });
116
+ const ddocId = await ((_b = this.stack) === null || _b === void 0 ? void 0 : _b.addDesignDocumentPKs(this.name, pKs, true));
117
+ fnLogger.info(`Created temporary design document '${ddocId}'`);
118
+ if (this.stack && ddocId) {
119
+ try {
120
+ const result = await this.stack.db.query(`${ddocId}/by_pKeys`, {
121
+ group: true,
122
+ reduce: '_count'
123
+ });
124
+ const hasDuplicates = result.rows.some(row => row.value > 1);
125
+ if (hasDuplicates) {
126
+ // 3a. Rollback: new schema is invalid
127
+ fnLogger.error('Schema change invalid: new duplicates found.');
128
+ const finalTempDoc = await this.stack.db.get(ddocId);
129
+ await this.stack.db.remove(finalTempDoc);
130
+ return false; // Indicate failure
131
+ }
132
+ else {
133
+ // 3b. Execute: new schema is valid. Replace the live document.
134
+ fnLogger.info('Bulk unique check completed: no new duplicates found');
135
+ const finalTempDoc = await this.stack.db.get(ddocId);
136
+ // Clean up the temporary document
137
+ await this.stack.db.remove(finalTempDoc);
138
+ return true; // Indicate success
139
+ }
140
+ }
141
+ catch (err) {
142
+ console.error('Error during schema validation:', err);
143
+ // Ensure the temporary document is removed on error
144
+ try {
145
+ const finalTempDoc = await this.stack.db.get(ddocId);
146
+ await this.stack.db.remove(finalTempDoc);
147
+ }
148
+ catch (e) { /* ignore */ }
149
+ return false;
150
+ }
151
+ }
152
+ else {
153
+ fnLogger.error(`Was unable to create temporary design document to group by`);
154
+ return false;
155
+ }
156
+ };
157
+ /**
158
+ * Validates document data against the class schema using Zod.
159
+ *
160
+ * @param data - The document data to validate
161
+ * @returns `true` if validation passes, `false` otherwise
162
+ */
163
+ this.validate = async (data) => {
164
+ const fnLogger = this.logger.child({ method: "validate" });
165
+ const result = await this.schemaZOD.safeParseAsync(data);
166
+ fnLogger.debug("Got result", { result });
167
+ if (result.success) {
168
+ return true;
169
+ }
170
+ else {
171
+ return false;
172
+ }
173
+ };
174
+ // TODO Turn into method (after factory method instantiation refactory is done)
175
+ this.setId = (id) => {
176
+ this.id = id;
177
+ };
178
+ this.getName = () => {
179
+ return this.name;
180
+ };
181
+ this.getStack = () => {
182
+ return this.stack;
183
+ };
184
+ this.getDescription = () => {
185
+ return this.description;
186
+ };
187
+ this.getType = () => {
188
+ return this.type;
189
+ };
190
+ this.getId = () => {
191
+ return this.id;
192
+ };
193
+ /**
194
+ * Builds the schema object from the current attributes.
195
+ * @returns The schema definition object
196
+ */
197
+ this.buildSchema = () => {
198
+ let schema = {};
199
+ Object.entries(this.attributes).forEach(t => {
200
+ schema[t[0]] = t[1].model;
201
+ });
202
+ return schema;
203
+ };
204
+ /**
205
+ * Returns the current ClassModel representation of this class.
206
+ * @returns The ClassModel document
207
+ */
208
+ this.getModel = () => {
209
+ let triggers = [];
210
+ for (const trigger of this.triggers) {
211
+ triggers.push(trigger.model);
212
+ }
213
+ let model = {
214
+ _id: this.id,
215
+ name: this.getName(),
216
+ description: this.getDescription(),
217
+ "~class": this.getType(),
218
+ schema: this.buildSchema(),
219
+ triggers: triggers,
220
+ active: true,
221
+ _rev: this.model ? this.model._rev : "",
222
+ "~createTimestamp": this.model ? this.model["~createTimestamp"] : undefined,
223
+ };
224
+ return model;
225
+ };
226
+ // [TODO] Change into buildFromModel
227
+ /**
228
+ * It hydrates attributes and triggers from given model
229
+ * @param model
230
+ */
231
+ this.setModel = (model) => {
232
+ _a.logger.info("setModel - got incoming model", { model: model });
233
+ // Retreive current class model
234
+ let currentModel = this.getModel();
235
+ // Set model arg to the overwrite of the current model with the given one
236
+ model = Object.assign(currentModel, model);
237
+ if (model.schema) {
238
+ // model.schema = {...this.model.schema, ...model.schema};
239
+ this.attributes = {};
240
+ this.schemaZOD = z.object({});
241
+ for (const [key, attrModel] of Object.entries(model.schema)) {
242
+ let attribute = new Attribute(this, attrModel.name, attrModel.type, attrModel.description, attrModel.config);
243
+ this.attributes[attrModel.name] = attribute;
244
+ this.schemaZOD = this.schemaZOD.extend({
245
+ [attrModel.name]: attribute.field
246
+ });
247
+ }
248
+ }
249
+ if (model.triggers) {
250
+ this.triggers = [];
251
+ for (const trigger of model.triggers) {
252
+ let trigger_ = new Trigger(trigger, this);
253
+ this.triggers.push(trigger_);
254
+ }
255
+ }
256
+ this.name = model.name;
257
+ this.description = model.description;
258
+ this.type = model["~class"];
259
+ this.model = model;
260
+ _a.logger.info("setModel - model after processing", { model: model });
261
+ };
262
+ /**
263
+ * Returns the primary key attribute names for this class.
264
+ * @returns Array of attribute names marked as primary keys
265
+ */
266
+ this.getPrimaryKeys = () => {
267
+ return Object.values(this.attributes).filter(attr => attr.isPrimaryKey())
268
+ .map(attr => attr.getName());
269
+ };
270
+ this.getAttributes = (...names) => {
271
+ let attributes = {};
272
+ for (const attribute of Object.values(this.attributes)) {
273
+ if (names.length > 0) {
274
+ // filter with given names
275
+ for (let name of names) {
276
+ // match?
277
+ if (name != null && attribute.getName() == name) {
278
+ attributes[attribute.name] = attribute;
279
+ }
280
+ }
281
+ }
282
+ else {
283
+ // no filter provided add all
284
+ attributes[attribute.name] = attribute;
285
+ }
286
+ }
287
+ return attributes;
288
+ };
289
+ this.hasAllAttributes = (...names) => {
290
+ let result = false;
291
+ let attributes = this.getAttributes(...names);
292
+ for (let attribute of Object.values(attributes)) {
293
+ result = names.includes(attribute.getName());
294
+ if (!result)
295
+ break;
296
+ }
297
+ return result;
298
+ };
299
+ this.hasAnyAttributes = (...names) => {
300
+ let result = false;
301
+ let attributes = this.getAttributes(...names);
302
+ for (let attribute of Object.values(attributes)) {
303
+ result = names.includes(attribute.getName());
304
+ if (result)
305
+ break;
306
+ }
307
+ return result;
308
+ };
309
+ this.getEncryptedAttributes = () => {
310
+ return Object.values(this.attributes).filter((attribute) => {
311
+ const config = attribute.model.config;
312
+ return (config === null || config === void 0 ? void 0 : config.encrypted) === true && (config === null || config === void 0 ? void 0 : config.primaryKey) !== true;
313
+ });
314
+ };
315
+ // interface of hasAnyAttributes
316
+ this.hasAttribute = (name) => {
317
+ return this.hasAnyAttributes(name);
318
+ };
319
+ /**
320
+ * Adds a new attribute to the class schema.
321
+ * Persists the change to the database.
322
+ *
323
+ * @param attribute - The Attribute instance or AttributeModel to add
324
+ * @returns This Class instance for chaining
325
+ *
326
+ * @example
327
+ * ```typescript
328
+ * await taskClass.addAttribute(new Attribute(taskClass, 'dueDate', 'date', 'Due Date'));
329
+ * // Or use Attribute.create() for a simpler API
330
+ * ```
331
+ */
332
+ this.addAttribute = async (attribute) => {
333
+ var _b;
334
+ const fnLogger = this.logger.child({ method: "addAttribute", args: { attribute: attribute.name } });
335
+ const attribute_ = isAttributeModel(attribute)
336
+ ? new Attribute(this, attribute.name, attribute.type, attribute.description, attribute.config) : attribute;
337
+ try {
338
+ let name = attribute_.getName();
339
+ // console.log("Adding attribute", {className: this.name, attribute: name})
340
+ if (!this.hasAttribute(name)) {
341
+ fnLogger.info("Adding attribute", { name: name, type: attribute_.getModel() });
342
+ this.attributes[name] = attribute_;
343
+ let attributeModel = attribute_.getModel();
344
+ fnLogger.info("Adding attribute to schema", { attributeModel: attributeModel });
345
+ const currentSchema = (_b = this.model.schema) !== null && _b !== void 0 ? _b : {};
346
+ this.model.schema = Object.assign(Object.assign({}, currentSchema), { [name]: attributeModel });
347
+ this.schemaZOD = this.schemaZOD.extend({
348
+ [name]: attribute_.field
349
+ });
350
+ // TODO:
351
+ // this.schema[name] = attributeModel; // sometimes getting schema undefined
352
+ // update class on db
353
+ fnLogger.info("Checking for requirements before updating class on db", { stack: (this.stack != null), id: this.id });
354
+ if (this.stack && this.id) {
355
+ // debugger;
356
+ fnLogger.info("Updating class on db");
357
+ let res = await this.stack.updateClass(this);
358
+ return this;
359
+ // TODO: Check if this class has subclasses
360
+ }
361
+ else {
362
+ fnLogger.error("Class not updated on db because of missing stack or id");
363
+ return this;
364
+ }
365
+ }
366
+ else {
367
+ fnLogger.error("Attribute with name " + name + " already exists within this Class");
368
+ return this;
369
+ }
370
+ }
371
+ catch (e) {
372
+ fnLogger.error("Falied adding attribute because: ", e);
373
+ return this;
374
+ }
375
+ };
376
+ /**
377
+ * Modifies an existing attribute in the class schema.
378
+ *
379
+ * @param name - The name of the attribute to modify
380
+ * @param attribute - The new Attribute or AttributeModel definition
381
+ * @returns This Class instance for chaining
382
+ */
383
+ this.modifyAttribute = async (name, attribute) => {
384
+ const fnLogger = this.logger.child({ method: "modifyAttribute", args: { name } });
385
+ const originSchema = Object.assign({}, this.model.schema[name]), originAttr = this.attributes[name];
386
+ const attribute_ = isAttributeModel(attribute)
387
+ ? new Attribute(this, attribute.name, attribute.type, attribute.description, attribute.config) : attribute;
388
+ try {
389
+ fnLogger.info(`Attempting to change attribute definition.`);
390
+ delete this.model.schema[name];
391
+ delete this.attributes[name];
392
+ this.schemaZOD = this.schemaZOD.omit({ [name]: true });
393
+ return this.addAttribute(attribute_);
394
+ }
395
+ catch (e) {
396
+ // Revert
397
+ this.model.schema[name] = originSchema;
398
+ this.attributes[name] = originAttr;
399
+ fnLogger.error(`Failed at removing attribute from class.'`);
400
+ }
401
+ return this;
402
+ };
403
+ /**
404
+ * Removes an attribute from the class schema.
405
+ *
406
+ * @param name - The name of the attribute to remove
407
+ * @returns This Class instance for chaining
408
+ */
409
+ this.removeAttribute = async (name) => {
410
+ const fnLogger = this.logger.child({ method: "removeAttribute", args: { name } });
411
+ const originSchema = Object.assign({}, this.model.schema[name]), originAttr = this.attributes[name];
412
+ try {
413
+ fnLogger.info(`Attempting to remove attribute from class.`);
414
+ delete this.model.schema[name];
415
+ delete this.attributes[name];
416
+ this.schemaZOD = this.schemaZOD.omit({ [name]: true });
417
+ if (this.stack) {
418
+ this.stack.updateClass(this);
419
+ }
420
+ else
421
+ throw new Error("Missing stack, cannot perform updates.");
422
+ }
423
+ catch (e) {
424
+ // Revert
425
+ this.model.schema[name] = originSchema;
426
+ this.attributes[name] = originAttr;
427
+ fnLogger.error(`Failed at removing attribute from class.'`);
428
+ }
429
+ return this;
430
+ };
431
+ /**
432
+ * Creates a new document (card) of this class type.
433
+ *
434
+ * @param params - The document data
435
+ * @returns The created document, or `null` if stack is not defined
436
+ *
437
+ * @example
438
+ * ```typescript
439
+ * const task = await taskClass.addCard({
440
+ * title: 'My Task',
441
+ * isComplete: false
442
+ * });
443
+ * ```
444
+ */
445
+ this.addCard = async (params) => {
446
+ const fnLogger = this.logger.child({ method: "addCard", args: { params } });
447
+ if (!this.stack) {
448
+ fnLogger.error("Stack is not defined");
449
+ return null;
450
+ }
451
+ return await this.stack.createDoc(null, this.getName(), this, params);
452
+ };
453
+ /**
454
+ * Creates multiple documents (cards) of this class type in a batch.
455
+ *
456
+ * @param paramsArray - Array of document data objects
457
+ * @returns Array of created documents
458
+ */
459
+ this.addCards = async (paramsArray) => {
460
+ const fnLogger = this.logger.child({ method: "addCards", args: { paramsArray } });
461
+ if (!this.stack) {
462
+ fnLogger.error("Stack is not defined");
463
+ return [];
464
+ }
465
+ let addedCards = [];
466
+ addedCards = await this.stack.createDocs(paramsArray.map(params => ({ docId: null, params })), this.getName(), this);
467
+ return addedCards;
468
+ };
469
+ this.getByPrimaryKeys = async (params) => {
470
+ const fnLogger = this.logger.child({ method: "getByPrimaryKeys" });
471
+ // attempt to retrieve card by primary key
472
+ let filter = {};
473
+ let primaryKeys = this.getPrimaryKeys();
474
+ fnLogger.info("Got primary keys", { primaryKeys });
475
+ if (primaryKeys.length) {
476
+ // executes a reducer function on each element of the primaryKeys array
477
+ // that sets each primary key prop to the corresponding param value
478
+ primaryKeys.reduce((accumulator, currentValue) => accumulator[currentValue] = params[currentValue], filter);
479
+ fnLogger.info("Defined filter", { filter });
480
+ let cards = await this.getCards(filter, undefined, 0, 1);
481
+ if (cards.length > 0) {
482
+ return cards[0];
483
+ }
484
+ else {
485
+ fnLogger.info("Did not find any documents with given primary key", { filter });
486
+ return null;
487
+ }
488
+ }
489
+ else {
490
+ fnLogger.info("Class has no field specified as primary key");
491
+ return null;
492
+ }
493
+ };
494
+ this.addOrUpdateCard = async (params, cardId) => {
495
+ const fnLogger = this.logger.child({ method: "addOrUpdateCard", args: { params, cardId } });
496
+ return new Promise(async (resolve, reject) => {
497
+ if (cardId) {
498
+ fnLogger.info("Provided document's id, performing an update");
499
+ const res = await this.updateCard(cardId, params);
500
+ resolve(res);
501
+ }
502
+ else {
503
+ fnLogger.info("No document id provided, checking for PKs");
504
+ const card = await this.getByPrimaryKeys(params);
505
+ if (card == null) {
506
+ const res = await this.addCard(params);
507
+ resolve(res);
508
+ }
509
+ else {
510
+ fnLogger.error("Duplicate card by keys");
511
+ reject("Duplicate card by keys");
512
+ }
513
+ }
514
+ });
515
+ };
516
+ /**
517
+ * Updates an existing document (card) of this class.
518
+ *
519
+ * @param cardId - The document ID to update
520
+ * @param params - The updated document data
521
+ * @returns The updated document, or `null` if stack is not defined
522
+ */
523
+ this.updateCard = async (cardId, params) => {
524
+ return new Promise(async (resolve, reject) => {
525
+ if (this.stack) {
526
+ const res = await this.stack.createDoc(cardId, this.getName(), this, params);
527
+ resolve(res);
528
+ }
529
+ else {
530
+ _a.logger.info("no stack defined");
531
+ resolve(null);
532
+ }
533
+ });
534
+ };
535
+ /**
536
+ * Soft-deletes a document by setting its `active` flag to `false`.
537
+ *
538
+ * @param cardId - The document ID to delete
539
+ * @returns `true` if successful, `false` otherwise
540
+ */
541
+ this.deleteCard = async (cardId) => {
542
+ const fnLogger = this.logger.child({ method: "deleteCard", args: { cardId } });
543
+ if (this.stack) {
544
+ const res = await this.stack.deleteDocument(cardId);
545
+ return res;
546
+ }
547
+ else {
548
+ fnLogger.error("Stack is not defined");
549
+ return false;
550
+ }
551
+ };
552
+ /**
553
+ * Retrieves documents (cards) of this class type.
554
+ *
555
+ * @param selector - Optional PouchDB/Mango selector for filtering
556
+ * @param fields - Optional list of fields to return
557
+ * @param skip - Number of documents to skip
558
+ * @param limit - Maximum number of documents to return
559
+ * @returns Array of matching documents
560
+ *
561
+ * @example
562
+ * ```typescript
563
+ * // Get all tasks
564
+ * const allTasks = await taskClass.getCards();
565
+ *
566
+ * // Get incomplete tasks
567
+ * const incomplete = await taskClass.getCards({ isComplete: { $eq: false } });
568
+ * ```
569
+ */
570
+ this.getCards = async (selector, fields, skip, limit) => {
571
+ const _selector = Object.assign(Object.assign({}, (selector || {})), { "~class": { $eq: this.name } });
572
+ this.logger.info("getCards - selector", { selector: _selector, fields, skip, limit });
573
+ let docs = (await this.stack.findDocuments(_selector, fields, skip, limit)).docs;
574
+ return docs;
575
+ };
576
+ /**
577
+ * Adds a trigger to this class.
578
+ * Triggers execute before or after document operations.
579
+ *
580
+ * @param name - The trigger name
581
+ * @param model - The trigger model containing the execution logic
582
+ * @returns This Class instance for chaining
583
+ *
584
+ * @example
585
+ * ```typescript
586
+ * await taskClass.addTrigger('generate-slug', {
587
+ * name: 'generate-slug',
588
+ * order: 'before',
589
+ * run: `document.slug = document.title.toLowerCase().replace(/\\s+/g, '-'); return document;`
590
+ * });
591
+ * ```
592
+ */
593
+ this.addTrigger = async (name, model) => {
594
+ const fnLogger = this.logger.child({ method: "addTrigger" });
595
+ try {
596
+ const trigger = new Trigger(model, this);
597
+ this.triggers.push(trigger);
598
+ if (this.stack) {
599
+ this.setModel();
600
+ let res = await this.stack.updateClass(this);
601
+ }
602
+ else {
603
+ throw new Error(`Stack is not defined. Can't update class`);
604
+ }
605
+ }
606
+ catch (e) {
607
+ fnLogger.error(e);
608
+ }
609
+ return this;
610
+ };
611
+ /**
612
+ * Removes a trigger from this class by name.
613
+ *
614
+ * @param name - The name of the trigger to remove
615
+ * @returns This Class instance for chaining
616
+ */
617
+ this.removeTrigger = async (name) => {
618
+ this.triggers = this.triggers.filter(t => t.name != name);
619
+ return this;
620
+ };
621
+ // Private constructor to prevent direct instantiation
622
+ /* Populated on async build */
623
+ // this.id = null;
624
+ }
625
+ async add(...paramsArray) {
626
+ const fnLogger = this.logger.child({ method: "add", args: { paramsArray } });
627
+ const addedCards = await this.addCards(paramsArray);
628
+ fnLogger.info("Added cards", { addedCards });
629
+ if (paramsArray.length === 1)
630
+ return addedCards[0] || null;
631
+ return addedCards;
632
+ }
633
+ async get(...cardId) {
634
+ const fnLogger = this.logger.child({ method: "get", args: { cardId } });
635
+ if (typeof cardId === "string") {
636
+ let docs = await this.getCards({ _id: { $eq: cardId } });
637
+ return docs[0] || null;
638
+ }
639
+ else {
640
+ let docs = await this.getCards({ _id: { $in: cardId } });
641
+ fnLogger.info("Fetched documents", { docs });
642
+ return docs;
643
+ }
644
+ }
645
+ }
646
+ _a = Class;
647
+ Class.logger = createLogger().child({ module: "class" });
648
+ /**
649
+ * Gets a Class instance without persisting it to the database.
650
+ * Use this for working with existing class models or for testing.
651
+ * Sets up a document change listener for real-time updates.
652
+ *
653
+ * @param stack - The parent stack instance
654
+ * @param id - The class ID
655
+ * @param name - The class name
656
+ * @param type - The class type
657
+ * @param description - Optional description
658
+ * @param schema - Initial schema definition
659
+ * @returns A new Class instance (not persisted)
660
+ */
661
+ Class.get = (stack, id, name, type, description, schema = {}) => {
662
+ const class_ = new _a();
663
+ _a.logger.info("Received schema", { schema });
664
+ class_.init(stack, id, name, type, description, schema);
665
+ // Add listener for new documents of this class type
666
+ class_.stack.onClassDoc(name)
667
+ .on("change", (change) => {
668
+ const evt = new CustomEvent("doc", {
669
+ detail: change
670
+ });
671
+ class_.dispatchEvent(evt);
672
+ });
673
+ return class_;
674
+ };
675
+ /**
676
+ * Creates a new class and persists it to the database.
677
+ * This is the primary factory method for creating new classes.
678
+ *
679
+ * @param stack - The parent stack instance
680
+ * @param name - The name for the new class
681
+ * @param type - The class type (typically 'class')
682
+ * @param description - Optional description of the class
683
+ * @param schema - Initial schema definition
684
+ * @returns The persisted Class instance
685
+ *
686
+ * @example
687
+ * ```typescript
688
+ * const userClass = await Class.create(stack, 'User', 'class', 'Application users');
689
+ * ```
690
+ */
691
+ Class.create = async (stack, name, type, description, schema = {}) => {
692
+ const class_ = _a.get(stack, name, name, type, description, schema);
693
+ await class_.build();
694
+ return class_;
695
+ };
696
+ /**
697
+ * Builds a Class instance from an existing ClassModel document.
698
+ * Hydrates attributes and triggers from the model.
699
+ *
700
+ * @param stack - The parent stack instance
701
+ * @param classModel - The ClassModel document from the database
702
+ * @returns The hydrated Class instance
703
+ */
704
+ Class.buildFromModel = async (stack, classModel) => {
705
+ _a.logger.info("buildFromModel - Instantiate from model", { classModel });
706
+ if (classModel._rev) {
707
+ let classObj = _a.get(stack, classModel._id, classModel.name, classModel["~class"], classModel.description, classModel.schema);
708
+ classObj.setModel(classModel);
709
+ return classObj;
710
+ }
711
+ else {
712
+ let classObj = await _a.create(stack, classModel.name, classModel["~class"], classModel["~class"], classModel.schema);
713
+ classObj.setModel(classModel);
714
+ return classObj;
715
+ }
716
+ };
717
+ /**
718
+ * Fetches a class by its document ID.
719
+ *
720
+ * @param stack - The parent stack instance
721
+ * @param classId - The class document ID
722
+ * @returns The Class instance
723
+ * @throws Error if the class is not found
724
+ */
725
+ Class.fetchById = async (stack, classId) => {
726
+ try {
727
+ let classModel = await stack.db.get(classId);
728
+ const classObj = await _a.buildFromModel(stack, classModel);
729
+ return classObj;
730
+ }
731
+ catch (error) {
732
+ throw new Error(`Class not found: ${classId}`);
733
+ }
734
+ };
735
+ /**
736
+ * Fetches a class by its name.
737
+ * This is the most common way to retrieve an existing class.
738
+ *
739
+ * @param stack - The parent stack instance
740
+ * @param className - The class name to fetch
741
+ * @returns The Class instance, or `null` if not found
742
+ *
743
+ * @example
744
+ * ```typescript
745
+ * const taskClass = await Class.fetch(stack, 'Task');
746
+ * if (taskClass) {
747
+ * const tasks = await taskClass.getCards();
748
+ * }
749
+ * ```
750
+ */
751
+ Class.fetch = async (stack, className) => {
752
+ let classModel = await stack.getClassModel(className);
753
+ if (classModel) {
754
+ return _a.buildFromModel(stack, classModel);
755
+ }
756
+ else {
757
+ return null;
758
+ }
759
+ };
760
+ export default Class;
761
+ //# sourceMappingURL=class.js.map