s3db.js 10.0.18 → 11.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.
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Setup logic for EventualConsistencyPlugin
3
- * @module eventual-consistency/setup
2
+ * Install logic for EventualConsistencyPlugin
3
+ * @module eventual-consistency/install
4
4
  */
5
5
 
6
6
  import tryFn from "../../concerns/try-fn.js";
@@ -11,14 +11,14 @@ import { startConsolidationTimer } from "./consolidation.js";
11
11
  import { startGarbageCollectionTimer } from "./garbage-collection.js";
12
12
 
13
13
  /**
14
- * Setup plugin for all configured resources
14
+ * Install plugin for all configured resources
15
15
  *
16
16
  * @param {Object} database - Database instance
17
17
  * @param {Map} fieldHandlers - Field handlers map
18
- * @param {Function} completeFieldSetupFn - Function to complete setup for a field
18
+ * @param {Function} completeFieldSetupFn - Function to complete field setup for a field
19
19
  * @param {Function} watchForResourceFn - Function to watch for resource creation
20
20
  */
21
- export async function onSetup(database, fieldHandlers, completeFieldSetupFn, watchForResourceFn) {
21
+ export async function onInstall(database, fieldHandlers, completeFieldSetupFn, watchForResourceFn) {
22
22
  // Iterate over all resource/field combinations
23
23
  for (const [resourceName, resourceHandlers] of fieldHandlers) {
24
24
  const targetResource = database.resources[resourceName];
@@ -70,7 +70,7 @@ export function watchForResource(resourceName, database, fieldHandlers, complete
70
70
  }
71
71
 
72
72
  /**
73
- * Complete setup for a single field handler
73
+ * Complete field setup for a single field handler
74
74
  *
75
75
  * @param {Object} handler - Field handler
76
76
  * @param {Object} database - Database instance
@@ -174,6 +174,7 @@ async function createAnalyticsResource(handler, database, resourceName, fieldNam
174
174
  name: analyticsResourceName,
175
175
  attributes: {
176
176
  id: 'string|required',
177
+ field: 'string|required',
177
178
  period: 'string|required',
178
179
  cohort: 'string|required',
179
180
  transactionCount: 'number|required',
@@ -13,11 +13,10 @@ export class FullTextPlugin extends Plugin {
13
13
  this.indexes = new Map(); // In-memory index for simplicity
14
14
  }
15
15
 
16
- async setup(database) {
17
- this.database = database;
16
+ async onInstall() {
18
17
 
19
18
  // Create index resource if it doesn't exist
20
- const [ok, err, indexResource] = await tryFn(() => database.createResource({
19
+ const [ok, err, indexResource] = await tryFn(() => this.database.createResource({
21
20
  name: 'plg_fulltext_indexes',
22
21
  attributes: {
23
22
  id: 'string|required',
@@ -29,7 +28,7 @@ export class FullTextPlugin extends Plugin {
29
28
  lastUpdated: 'string|required'
30
29
  }
31
30
  }));
32
- this.indexResource = ok ? indexResource : database.resources.fulltext_indexes;
31
+ this.indexResource = ok ? indexResource : this.database.resources.fulltext_indexes;
33
32
 
34
33
  // Load existing indexes
35
34
  await this.loadIndexes();
@@ -31,12 +31,11 @@ export class MetricsPlugin extends Plugin {
31
31
  this.flushTimer = null;
32
32
  }
33
33
 
34
- async setup(database) {
35
- this.database = database;
34
+ async onInstall() {
36
35
  if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') return;
37
36
 
38
37
  const [ok, err] = await tryFn(async () => {
39
- const [ok1, err1, metricsResource] = await tryFn(() => database.createResource({
38
+ const [ok1, err1, metricsResource] = await tryFn(() => this.database.createResource({
40
39
  name: 'plg_metrics',
41
40
  attributes: {
42
41
  id: 'string|required',
@@ -51,9 +50,9 @@ export class MetricsPlugin extends Plugin {
51
50
  metadata: 'json'
52
51
  }
53
52
  }));
54
- this.metricsResource = ok1 ? metricsResource : database.resources.plg_metrics;
53
+ this.metricsResource = ok1 ? metricsResource : this.database.resources.plg_metrics;
55
54
 
56
- const [ok2, err2, errorsResource] = await tryFn(() => database.createResource({
55
+ const [ok2, err2, errorsResource] = await tryFn(() => this.database.createResource({
57
56
  name: 'plg_error_logs',
58
57
  attributes: {
59
58
  id: 'string|required',
@@ -64,9 +63,9 @@ export class MetricsPlugin extends Plugin {
64
63
  metadata: 'json'
65
64
  }
66
65
  }));
67
- this.errorsResource = ok2 ? errorsResource : database.resources.plg_error_logs;
66
+ this.errorsResource = ok2 ? errorsResource : this.database.resources.plg_error_logs;
68
67
 
69
- const [ok3, err3, performanceResource] = await tryFn(() => database.createResource({
68
+ const [ok3, err3, performanceResource] = await tryFn(() => this.database.createResource({
70
69
  name: 'plg_performance_logs',
71
70
  attributes: {
72
71
  id: 'string|required',
@@ -77,13 +76,13 @@ export class MetricsPlugin extends Plugin {
77
76
  metadata: 'json'
78
77
  }
79
78
  }));
80
- this.performanceResource = ok3 ? performanceResource : database.resources.plg_performance_logs;
79
+ this.performanceResource = ok3 ? performanceResource : this.database.resources.plg_performance_logs;
81
80
  });
82
81
  if (!ok) {
83
82
  // Resources might already exist
84
- this.metricsResource = database.resources.plg_metrics;
85
- this.errorsResource = database.resources.plg_error_logs;
86
- this.performanceResource = database.resources.plg_performance_logs;
83
+ this.metricsResource = this.database.resources.plg_metrics;
84
+ this.errorsResource = this.database.resources.plg_error_logs;
85
+ this.performanceResource = this.database.resources.plg_performance_logs;
87
86
  }
88
87
 
89
88
  // Use database hooks for automatic resource discovery
@@ -1,4 +1,5 @@
1
1
  import EventEmitter from "events";
2
+ import { PluginStorage } from "../concerns/plugin-storage.js";
2
3
 
3
4
  export class Plugin extends EventEmitter {
4
5
  constructor(options = {}) {
@@ -6,13 +7,50 @@ export class Plugin extends EventEmitter {
6
7
  this.name = this.constructor.name;
7
8
  this.options = options;
8
9
  this.hooks = new Map();
10
+
11
+ // Auto-generate slug from class name (CamelCase -> kebab-case)
12
+ // e.g., EventualConsistencyPlugin -> eventual-consistency-plugin
13
+ this.slug = options.slug || this._generateSlug();
14
+
15
+ // Storage instance (lazy-loaded)
16
+ this._storage = null;
17
+ }
18
+
19
+ /**
20
+ * Generate kebab-case slug from class name
21
+ * @private
22
+ * @returns {string}
23
+ */
24
+ _generateSlug() {
25
+ return this.name
26
+ .replace(/Plugin$/, '') // Remove "Plugin" suffix
27
+ .replace(/([a-z])([A-Z])/g, '$1-$2') // CamelCase -> kebab-case
28
+ .toLowerCase();
9
29
  }
10
30
 
11
- async setup(database) {
31
+ /**
32
+ * Get PluginStorage instance (lazy-loaded)
33
+ * @returns {PluginStorage}
34
+ */
35
+ getStorage() {
36
+ if (!this._storage) {
37
+ if (!this.database || !this.database.client) {
38
+ throw new Error('Plugin must be installed before accessing storage');
39
+ }
40
+ this._storage = new PluginStorage(this.database.client, this.slug);
41
+ }
42
+ return this._storage;
43
+ }
44
+
45
+ /**
46
+ * Install plugin
47
+ * @param {Database} database - Database instance
48
+ */
49
+ async install(database) {
12
50
  this.database = database;
13
- this.beforeSetup();
14
- await this.onSetup();
15
- this.afterSetup();
51
+ this.beforeInstall();
52
+ await this.onInstall();
53
+ this.afterInstall();
16
54
  }
17
55
 
18
56
  async start() {
@@ -27,8 +65,28 @@ export class Plugin extends EventEmitter {
27
65
  this.afterStop();
28
66
  }
29
67
 
68
+ /**
69
+ * Uninstall plugin and cleanup all data
70
+ * @param {Object} options - Uninstall options
71
+ * @param {boolean} options.purgeData - Delete all plugin data from S3 (default: false)
72
+ */
73
+ async uninstall(options = {}) {
74
+ const { purgeData = false } = options;
75
+
76
+ this.beforeUninstall();
77
+ await this.onUninstall(options);
78
+
79
+ // Purge all plugin data if requested
80
+ if (purgeData && this._storage) {
81
+ const deleted = await this._storage.deleteAll();
82
+ this.emit('plugin.dataPurged', { deleted });
83
+ }
84
+
85
+ this.afterUninstall();
86
+ }
87
+
30
88
  // Override these methods in subclasses
31
- async onSetup() {
89
+ async onInstall() {
32
90
  // Override in subclasses
33
91
  }
34
92
 
@@ -40,6 +98,10 @@ export class Plugin extends EventEmitter {
40
98
  // Override in subclasses
41
99
  }
42
100
 
101
+ async onUninstall(options) {
102
+ // Override in subclasses
103
+ }
104
+
43
105
  // Hook management methods
44
106
  addHook(resource, event, handler) {
45
107
  if (!this.hooks.has(resource)) {
@@ -182,12 +244,12 @@ export class Plugin extends EventEmitter {
182
244
  }
183
245
 
184
246
  // Event emission methods
185
- beforeSetup() {
186
- this.emit("plugin.beforeSetup", new Date());
247
+ beforeInstall() {
248
+ this.emit("plugin.beforeInstall", new Date());
187
249
  }
188
250
 
189
- afterSetup() {
190
- this.emit("plugin.afterSetup", new Date());
251
+ afterInstall() {
252
+ this.emit("plugin.afterInstall", new Date());
191
253
  }
192
254
 
193
255
  beforeStart() {
@@ -205,6 +267,14 @@ export class Plugin extends EventEmitter {
205
267
  afterStop() {
206
268
  this.emit("plugin.afterStop", new Date());
207
269
  }
270
+
271
+ beforeUninstall() {
272
+ this.emit("plugin.beforeUninstall", new Date());
273
+ }
274
+
275
+ afterUninstall() {
276
+ this.emit("plugin.afterUninstall", new Date());
277
+ }
208
278
  }
209
279
 
210
280
  export default Plugin;
@@ -1,3 +1,4 @@
1
+ import { Plugin } from './plugin.class.js';
1
2
  import { createConsumer } from './consumers/index.js';
2
3
  import tryFn from "../concerns/try-fn.js";
3
4
 
@@ -20,16 +21,16 @@ import tryFn from "../concerns/try-fn.js";
20
21
  // reconnectInterval: 2000,
21
22
  // });
22
23
 
23
- export class QueueConsumerPlugin {
24
+ export class QueueConsumerPlugin extends Plugin {
24
25
  constructor(options = {}) {
26
+ super(options);
25
27
  this.options = options;
26
28
  // New pattern: consumers = [{ driver, config, consumers: [{ queueUrl, resources, ... }] }]
27
29
  this.driversConfig = Array.isArray(options.consumers) ? options.consumers : [];
28
30
  this.consumers = [];
29
31
  }
30
32
 
31
- async setup(database) {
32
- this.database = database;
33
+ async onInstall() {
33
34
 
34
35
  for (const driverDef of this.driversConfig) {
35
36
  const { driver, config: driverConfig = {}, consumers: consumerDefs = [] } = driverDef;
@@ -236,12 +236,10 @@ export class ReplicatorPlugin extends Plugin {
236
236
  this.eventListenersInstalled.add(resource.name);
237
237
  }
238
238
 
239
- async setup(database) {
240
- this.database = database;
241
-
239
+ async onInstall() {
242
240
  // Create replicator log resource if enabled
243
241
  if (this.config.persistReplicatorLog) {
244
- const [ok, err, logResource] = await tryFn(() => database.createResource({
242
+ const [ok, err, logResource] = await tryFn(() => this.database.createResource({
245
243
  name: this.config.replicatorLogResource || 'plg_replicator_logs',
246
244
  attributes: {
247
245
  id: 'string|required',
@@ -253,24 +251,24 @@ export class ReplicatorPlugin extends Plugin {
253
251
  },
254
252
  behavior: 'truncate-data'
255
253
  }));
256
-
254
+
257
255
  if (ok) {
258
256
  this.replicatorLogResource = logResource;
259
257
  } else {
260
- this.replicatorLogResource = database.resources[this.config.replicatorLogResource || 'plg_replicator_logs'];
258
+ this.replicatorLogResource = this.database.resources[this.config.replicatorLogResource || 'plg_replicator_logs'];
261
259
  }
262
260
  }
263
261
 
264
262
  // Initialize replicators
265
- await this.initializeReplicators(database);
266
-
263
+ await this.initializeReplicators(this.database);
264
+
267
265
  // Use database hooks for automatic resource discovery
268
266
  this.installDatabaseHooks();
269
-
267
+
270
268
  // Install event listeners for existing resources
271
- for (const resource of Object.values(database.resources)) {
269
+ for (const resource of Object.values(this.database.resources)) {
272
270
  if (resource.name !== (this.config.replicatorLogResource || 'plg_replicator_logs')) {
273
- this.installEventListeners(resource, database, this);
271
+ this.installEventListeners(resource, this.database, this);
274
272
  }
275
273
  }
276
274
  }
@@ -334,8 +332,8 @@ export class ReplicatorPlugin extends Plugin {
334
332
  }
335
333
 
336
334
  async uploadMetadataFile(database) {
337
- if (typeof database.uploadMetadataFile === 'function') {
338
- await database.uploadMetadataFile();
335
+ if (typeof this.database.uploadMetadataFile === 'function') {
336
+ await this.database.uploadMetadataFile();
339
337
  }
340
338
  }
341
339
 
@@ -97,7 +97,7 @@ export class S3QueuePlugin extends Plugin {
97
97
  this.lockCleanupInterval = null;
98
98
  }
99
99
 
100
- async onSetup() {
100
+ async onInstall() {
101
101
  // Get target resource
102
102
  this.targetResource = this.database.resources[this.config.resource];
103
103
  if (!this.targetResource) {
@@ -29,11 +29,11 @@ import { idGenerator } from "../concerns/id.js";
29
29
  * schedule: '0 3 * * *',
30
30
  * description: 'Clean up expired records',
31
31
  * action: async (database, context) => {
32
- * const expired = await database.resource('sessions')
32
+ * const expired = await this.database.resource('sessions')
33
33
  * .list({ where: { expiresAt: { $lt: new Date() } } });
34
34
  *
35
35
  * for (const record of expired) {
36
- * await database.resource('sessions').delete(record.id);
36
+ * await this.database.resource('sessions').delete(record.id);
37
37
  * }
38
38
  *
39
39
  * return { deleted: expired.length };
@@ -48,8 +48,8 @@ import { idGenerator } from "../concerns/id.js";
48
48
  * schedule: '0 9 * * MON',
49
49
  * description: 'Generate weekly analytics report',
50
50
  * action: async (database, context) => {
51
- * const users = await database.resource('users').count();
52
- * const orders = await database.resource('orders').count({
51
+ * const users = await this.database.resource('users').count();
52
+ * const orders = await this.database.resource('orders').count({
53
53
  * where: {
54
54
  * createdAt: {
55
55
  * $gte: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)
@@ -64,7 +64,7 @@ import { idGenerator } from "../concerns/id.js";
64
64
  * createdAt: new Date().toISOString()
65
65
  * };
66
66
  *
67
- * await database.resource('reports').insert(report);
67
+ * await this.database.resource('reports').insert(report);
68
68
  * return report;
69
69
  * }
70
70
  * },
@@ -106,7 +106,7 @@ import { idGenerator } from "../concerns/id.js";
106
106
  * const hourAgo = new Date(now.getTime() - 60 * 60 * 1000);
107
107
  *
108
108
  * // Aggregate metrics from the last hour
109
- * const events = await database.resource('events').list({
109
+ * const events = await this.database.resource('events').list({
110
110
  * where: {
111
111
  * timestamp: {
112
112
  * $gte: hourAgo.getTime(),
@@ -120,7 +120,7 @@ import { idGenerator } from "../concerns/id.js";
120
120
  * return acc;
121
121
  * }, {});
122
122
  *
123
- * await database.resource('hourly_metrics').insert({
123
+ * await this.database.resource('hourly_metrics').insert({
124
124
  * hour: hourAgo.toISOString().slice(0, 13),
125
125
  * metrics: aggregated,
126
126
  * total: events.length,
@@ -217,8 +217,7 @@ export class SchedulerPlugin extends Plugin {
217
217
  return true; // Simplified validation
218
218
  }
219
219
 
220
- async setup(database) {
221
- this.database = database;
220
+ async onInstall() {
222
221
 
223
222
  // Create lock resource for distributed locking
224
223
  await this._createLockResource();
@@ -61,7 +61,7 @@ import tryFn from "../concerns/try-fn.js";
61
61
  *
62
62
  * actions: {
63
63
  * onConfirmed: async (context, event, machine) => {
64
- * await machine.database.resource('inventory').update(context.productId, {
64
+ * await machine.this.database.resource('inventory').update(context.productId, {
65
65
  * quantity: { $decrement: context.quantity }
66
66
  * });
67
67
  * await machine.sendNotification(context.customerEmail, 'order_confirmed');
@@ -73,7 +73,7 @@ import tryFn from "../concerns/try-fn.js";
73
73
  *
74
74
  * guards: {
75
75
  * canShip: async (context, event, machine) => {
76
- * const inventory = await machine.database.resource('inventory').get(context.productId);
76
+ * const inventory = await machine.this.database.resource('inventory').get(context.productId);
77
77
  * return inventory.quantity >= context.quantity;
78
78
  * }
79
79
  * },
@@ -138,8 +138,7 @@ export class StateMachinePlugin extends Plugin {
138
138
  }
139
139
  }
140
140
 
141
- async setup(database) {
142
- this.database = database;
141
+ async onInstall() {
143
142
 
144
143
  // Create state storage resource if persistence is enabled
145
144
  if (this.config.persistTransitions) {
package/src/s3db.d.ts CHANGED
@@ -1069,11 +1069,207 @@ declare module 's3db.js' {
1069
1069
  deleteBackup(backupId: string): Promise<void>;
1070
1070
  }
1071
1071
 
1072
+ /** Eventual Consistency Plugin Config */
1073
+ export interface EventualConsistencyPluginConfig extends PluginConfig {
1074
+ /** Resource name to field names mapping (required) */
1075
+ resources: Record<string, string[]>;
1076
+
1077
+ /** Consolidation settings */
1078
+ consolidation?: {
1079
+ /** Consolidation mode: 'sync' or 'async' (default: 'async') */
1080
+ mode?: 'sync' | 'async';
1081
+ /** Consolidation interval in seconds (default: 300) */
1082
+ interval?: number;
1083
+ /** Consolidation concurrency (default: 5) */
1084
+ concurrency?: number;
1085
+ /** Consolidation window in hours (default: 24) */
1086
+ window?: number;
1087
+ /** Enable auto-consolidation (default: true) */
1088
+ auto?: boolean;
1089
+ };
1090
+
1091
+ /** Lock settings */
1092
+ locks?: {
1093
+ /** Lock timeout in seconds (default: 300) */
1094
+ timeout?: number;
1095
+ };
1096
+
1097
+ /** Garbage collection settings */
1098
+ garbageCollection?: {
1099
+ /** Transaction retention in days (default: 30) */
1100
+ retention?: number;
1101
+ /** GC interval in seconds (default: 86400) */
1102
+ interval?: number;
1103
+ };
1104
+
1105
+ /** Analytics settings */
1106
+ analytics?: {
1107
+ /** Enable analytics (default: false) */
1108
+ enabled?: boolean;
1109
+ /** Time periods to track (default: ['hour', 'day', 'month']) */
1110
+ periods?: Array<'hour' | 'day' | 'month'>;
1111
+ /** Metrics to track (default: ['count', 'sum', 'avg', 'min', 'max']) */
1112
+ metrics?: Array<'count' | 'sum' | 'avg' | 'min' | 'max'>;
1113
+ /** Rollup strategy (default: 'incremental') */
1114
+ rollupStrategy?: 'incremental' | 'full';
1115
+ /** Analytics retention in days (default: 365) */
1116
+ retentionDays?: number;
1117
+ };
1118
+
1119
+ /** Batch transaction settings */
1120
+ batch?: {
1121
+ /** Enable batch transactions (default: false) */
1122
+ enabled?: boolean;
1123
+ /** Batch size (default: 100) */
1124
+ size?: number;
1125
+ };
1126
+
1127
+ /** Late arrivals handling */
1128
+ lateArrivals?: {
1129
+ /** Strategy for late arrivals (default: 'warn') */
1130
+ strategy?: 'warn' | 'ignore' | 'error';
1131
+ };
1132
+
1133
+ /** Checkpoint settings */
1134
+ checkpoints?: {
1135
+ /** Enable checkpoints (default: true) */
1136
+ enabled?: boolean;
1137
+ /** Checkpoint strategy (default: 'hourly') */
1138
+ strategy?: 'hourly' | 'daily' | 'threshold';
1139
+ /** Checkpoint retention in days (default: 90) */
1140
+ retention?: number;
1141
+ /** Checkpoint threshold (default: 1000) */
1142
+ threshold?: number;
1143
+ /** Delete consolidated transactions (default: true) */
1144
+ deleteConsolidated?: boolean;
1145
+ /** Enable auto-checkpoint (default: true) */
1146
+ auto?: boolean;
1147
+ };
1148
+
1149
+ /** Cohort settings */
1150
+ cohort?: {
1151
+ /** Timezone for cohorts (default: UTC or TZ env var) */
1152
+ timezone?: string;
1153
+ };
1154
+
1155
+ /** Custom reducer function */
1156
+ reducer?: (transactions: any[]) => number;
1157
+
1158
+ /** Enable verbose logging (default: false) */
1159
+ verbose?: boolean;
1160
+ }
1161
+
1162
+ /** Analytics query options */
1163
+ export interface EventualConsistencyAnalyticsOptions {
1164
+ /** Period to query */
1165
+ period?: 'hour' | 'day' | 'month';
1166
+ /** Start date (YYYY-MM-DD or YYYY-MM-DD HH:00) */
1167
+ startDate?: string;
1168
+ /** End date (YYYY-MM-DD or YYYY-MM-DD HH:00) */
1169
+ endDate?: string;
1170
+ /** Single date (YYYY-MM-DD) */
1171
+ date?: string;
1172
+ /** Operation breakdown */
1173
+ breakdown?: 'operations';
1174
+ /** Fill gaps with zeros for continuous data (default: false) */
1175
+ fillGaps?: boolean;
1176
+ }
1177
+
1178
+ /** Top records query options */
1179
+ export interface EventualConsistencyTopRecordsOptions {
1180
+ /** Period to query */
1181
+ period?: 'hour' | 'day' | 'month';
1182
+ /** Date for the query */
1183
+ date?: string;
1184
+ /** Metric to sort by: 'transactionCount' or 'totalValue' */
1185
+ metric?: 'transactionCount' | 'totalValue';
1186
+ /** Limit results (default: 10) */
1187
+ limit?: number;
1188
+ }
1189
+
1190
+ /** Analytics result */
1191
+ export interface EventualConsistencyAnalyticsResult {
1192
+ /** Cohort identifier (date/hour/month string) */
1193
+ cohort: string;
1194
+ /** Number of transactions */
1195
+ count: number;
1196
+ /** Sum of values */
1197
+ sum: number;
1198
+ /** Average value */
1199
+ avg: number;
1200
+ /** Minimum value */
1201
+ min: number;
1202
+ /** Maximum value */
1203
+ max: number;
1204
+ /** Number of distinct records */
1205
+ recordCount: number;
1206
+ /** Breakdown by operation (if requested) */
1207
+ add?: { count: number; sum: number };
1208
+ sub?: { count: number; sum: number };
1209
+ set?: { count: number; sum: number };
1210
+ }
1211
+
1212
+ /** Top record result */
1213
+ export interface EventualConsistencyTopRecordResult {
1214
+ /** Record ID */
1215
+ recordId: string;
1216
+ /** Number of transactions or total value */
1217
+ count: number;
1218
+ /** Total value */
1219
+ sum: number;
1220
+ }
1221
+
1222
+ /** Cohort information */
1223
+ export interface EventualConsistencyCohortInfo {
1224
+ /** Date in YYYY-MM-DD format */
1225
+ date: string;
1226
+ /** Hour in YYYY-MM-DD HH:00 format */
1227
+ hour: string;
1228
+ /** Month in YYYY-MM format */
1229
+ month: string;
1230
+ }
1231
+
1072
1232
  /** Eventual Consistency Plugin */
1073
1233
  export class EventualConsistencyPlugin extends Plugin {
1074
- constructor(config?: any);
1234
+ constructor(config: EventualConsistencyPluginConfig);
1235
+
1236
+ // Lifecycle methods
1075
1237
  setup(database: Database): Promise<void>;
1076
- createTransaction(resourceName: string): any;
1238
+
1239
+ // Analytics methods
1240
+ getAnalytics(resourceName: string, field: string, options?: EventualConsistencyAnalyticsOptions): Promise<EventualConsistencyAnalyticsResult[]>;
1241
+ getMonthByDay(resourceName: string, field: string, month: string, options?: EventualConsistencyAnalyticsOptions): Promise<EventualConsistencyAnalyticsResult[]>;
1242
+ getDayByHour(resourceName: string, field: string, date: string, options?: EventualConsistencyAnalyticsOptions): Promise<EventualConsistencyAnalyticsResult[]>;
1243
+ getLastNDays(resourceName: string, field: string, days?: number, options?: EventualConsistencyAnalyticsOptions): Promise<EventualConsistencyAnalyticsResult[]>;
1244
+ getYearByMonth(resourceName: string, field: string, year: number, options?: EventualConsistencyAnalyticsOptions): Promise<EventualConsistencyAnalyticsResult[]>;
1245
+ getMonthByHour(resourceName: string, field: string, month: string, options?: EventualConsistencyAnalyticsOptions): Promise<EventualConsistencyAnalyticsResult[]>;
1246
+ getTopRecords(resourceName: string, field: string, options?: EventualConsistencyTopRecordsOptions): Promise<EventualConsistencyTopRecordResult[]>;
1247
+
1248
+ // Utility methods
1249
+ getCohortInfo(date: Date): EventualConsistencyCohortInfo;
1250
+ createPartitionConfig(): any;
1251
+ createTransaction(handler: any, data: any): Promise<any>;
1252
+ }
1253
+
1254
+ /** Resource extensions added by EventualConsistencyPlugin */
1255
+ export interface EventualConsistencyResourceExtensions {
1256
+ /** Set field value (replaces current value) */
1257
+ set(id: string, field: string, value: number): Promise<number>;
1258
+
1259
+ /** Increment field value */
1260
+ add(id: string, field: string, amount: number): Promise<number>;
1261
+
1262
+ /** Decrement field value */
1263
+ sub(id: string, field: string, amount: number): Promise<number>;
1264
+
1265
+ /** Manually trigger consolidation */
1266
+ consolidate(id: string, field: string): Promise<number>;
1267
+
1268
+ /** Get consolidated value without applying */
1269
+ getConsolidatedValue(id: string, field: string, options?: any): Promise<number>;
1270
+
1271
+ /** Recalculate from scratch (rebuilds from transaction log) */
1272
+ recalculate(id: string, field: string): Promise<number>;
1077
1273
  }
1078
1274
 
1079
1275
  /** Scheduler Plugin */