@objectstack/driver-memory 0.6.1 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @objectstack/driver-memory
2
2
 
3
+ ## 0.7.2
4
+
5
+ ### Patch Changes
6
+
7
+ - fb41cc0: Patch release: Updated documentation and JSON schemas
8
+ - Updated dependencies [fb41cc0]
9
+ - @objectstack/spec@0.7.2
10
+ - @objectstack/core@0.7.2
11
+
12
+ ## 0.7.1
13
+
14
+ ### Patch Changes
15
+
16
+ - Patch release for maintenance and stability improvements
17
+ - Updated dependencies
18
+ - @objectstack/spec@0.7.1
19
+ - @objectstack/core@0.7.1
20
+
3
21
  ## 0.6.1
4
22
 
5
23
  ### Patch Changes
package/README.md ADDED
@@ -0,0 +1,227 @@
1
+ # @objectstack/driver-memory
2
+
3
+ In-Memory Driver for ObjectStack. A reference implementation of the DriverInterface that stores data in memory using JavaScript arrays.
4
+
5
+ ## Plugin Capabilities
6
+
7
+ This driver implements the ObjectStack plugin capability protocol:
8
+ - **Type**: `driver`
9
+ - **Protocol**: `com.objectstack.protocol.storage.v1` (partial conformance)
10
+ - **Provides**: `DriverInterface` for data storage operations
11
+ - **Features**:
12
+ - ✅ Basic CRUD operations
13
+ - ✅ Pagination (limit/offset)
14
+ - ❌ Advanced query filters
15
+ - ❌ Aggregations
16
+ - ❌ Sorting
17
+ - ❌ Transactions
18
+ - ❌ Joins
19
+
20
+ See [objectstack.config.ts](./objectstack.config.ts) for the complete capability manifest.
21
+
22
+ ## Features
23
+
24
+ - 🚀 **Zero Dependencies**: Pure JavaScript implementation
25
+ - 🧪 **Perfect for Testing**: Volatile storage ideal for unit tests
26
+ - 📝 **TypeScript First**: Fully typed with TypeScript
27
+ - 🔍 **Reference Implementation**: Clean example of DriverInterface
28
+ - ⚡ **Fast**: In-memory operations are lightning fast
29
+
30
+ ## Installation
31
+
32
+ ```bash
33
+ pnpm add @objectstack/driver-memory
34
+ ```
35
+
36
+ ## Usage
37
+
38
+ ### With ObjectStack Runtime
39
+
40
+ ```typescript
41
+ import { InMemoryDriver } from '@objectstack/driver-memory';
42
+ import { DriverPlugin } from '@objectstack/runtime';
43
+ import { ObjectKernel } from '@objectstack/runtime';
44
+
45
+ const kernel = new ObjectKernel();
46
+
47
+ // Create and register the driver
48
+ const memoryDriver = new InMemoryDriver();
49
+ kernel.use(new DriverPlugin(memoryDriver, 'memory'));
50
+
51
+ await kernel.bootstrap();
52
+ ```
53
+
54
+ ### Standalone Usage
55
+
56
+ ```typescript
57
+ import { InMemoryDriver } from '@objectstack/driver-memory';
58
+
59
+ const driver = new InMemoryDriver({
60
+ seedData: true // Pre-populate with example data
61
+ });
62
+
63
+ // Initialize
64
+ await driver.connect();
65
+
66
+ // Create a record
67
+ const user = await driver.create('user', {
68
+ name: 'John Doe',
69
+ email: 'john@example.com'
70
+ });
71
+
72
+ // Find records
73
+ const users = await driver.find('user', {
74
+ limit: 10,
75
+ offset: 0
76
+ });
77
+
78
+ // Get by ID
79
+ const foundUser = await driver.findOne('user', user.id);
80
+
81
+ // Update
82
+ await driver.update('user', user.id, {
83
+ name: 'Jane Doe'
84
+ });
85
+
86
+ // Delete
87
+ await driver.delete('user', user.id);
88
+
89
+ // Count
90
+ const count = await driver.count('user');
91
+
92
+ // Cleanup
93
+ await driver.disconnect();
94
+ ```
95
+
96
+ ## API Reference
97
+
98
+ ### InMemoryDriver
99
+
100
+ The main driver class that implements `DriverInterface`.
101
+
102
+ #### Constructor Options
103
+
104
+ ```typescript
105
+ interface DriverOptions {
106
+ /**
107
+ * Pre-populate the database with example data on startup
108
+ * @default false
109
+ */
110
+ seedData?: boolean;
111
+
112
+ /**
113
+ * Logger instance
114
+ */
115
+ logger?: Logger;
116
+ }
117
+ ```
118
+
119
+ #### Methods
120
+
121
+ - `connect()` - Initialize the driver (no-op for in-memory)
122
+ - `disconnect()` - Cleanup resources (clears all data)
123
+ - `create(object, data)` - Create a new record
124
+ - `find(object, query?)` - Query records with optional pagination
125
+ - `findOne(object, id)` - Get a single record by ID
126
+ - `update(object, id, data)` - Update a record
127
+ - `delete(object, id)` - Delete a record
128
+ - `count(object, query?)` - Count total records
129
+ - `getSchema(object)` - Get object schema definition
130
+ - `query(query)` - Execute a raw query (limited support)
131
+
132
+ #### Capabilities
133
+
134
+ The driver declares its capabilities via the `supports` property:
135
+
136
+ ```typescript
137
+ {
138
+ transactions: false,
139
+ queryFilters: false,
140
+ queryAggregations: false,
141
+ querySorting: false,
142
+ queryPagination: true, // ✅ Supported via limit/offset
143
+ queryWindowFunctions: false,
144
+ querySubqueries: false,
145
+ joins: false,
146
+ fullTextSearch: false,
147
+ vectorSearch: false,
148
+ geoSpatial: false
149
+ }
150
+ ```
151
+
152
+ ## Data Storage
153
+
154
+ The in-memory driver stores data in a simple Map structure:
155
+
156
+ ```typescript
157
+ private tables: Map<string, Array<Record<string, any>>> = new Map();
158
+ ```
159
+
160
+ **Important**: All data is lost when the process exits or `disconnect()` is called. This driver is **not suitable for production use**.
161
+
162
+ ## Use Cases
163
+
164
+ ✅ **Good for:**
165
+ - Unit testing
166
+ - Integration testing
167
+ - Development/prototyping
168
+ - CI/CD pipelines
169
+ - Examples and tutorials
170
+ - Learning ObjectStack
171
+
172
+ ❌ **Not suitable for:**
173
+ - Production environments
174
+ - Data persistence requirements
175
+ - Large datasets (memory constraints)
176
+ - Multi-process scenarios
177
+ - Concurrent write operations
178
+
179
+ ## Testing Example
180
+
181
+ ```typescript
182
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
183
+ import { InMemoryDriver } from '@objectstack/driver-memory';
184
+
185
+ describe('User CRUD', () => {
186
+ let driver: InMemoryDriver;
187
+
188
+ beforeEach(async () => {
189
+ driver = new InMemoryDriver();
190
+ await driver.connect();
191
+ });
192
+
193
+ afterEach(async () => {
194
+ await driver.disconnect();
195
+ });
196
+
197
+ it('should create and retrieve a user', async () => {
198
+ const user = await driver.create('user', {
199
+ name: 'Test User',
200
+ email: 'test@example.com'
201
+ });
202
+
203
+ expect(user.id).toBeDefined();
204
+
205
+ const found = await driver.findOne('user', user.id);
206
+ expect(found.name).toBe('Test User');
207
+ });
208
+ });
209
+ ```
210
+
211
+ ## Relationship to Other Drivers
212
+
213
+ This driver serves as a reference implementation. For production use, consider:
214
+
215
+ - **@objectstack/driver-postgres** - PostgreSQL driver with full SQL capabilities
216
+ - **@objectstack/driver-mongodb** - MongoDB driver for document storage
217
+ - **@objectstack/driver-redis** - Redis driver for caching and key-value storage
218
+
219
+ ## License
220
+
221
+ Apache-2.0
222
+
223
+ ## Related Packages
224
+
225
+ - [@objectstack/runtime](../../runtime) - ObjectStack Runtime
226
+ - [@objectstack/spec](../../spec) - ObjectStack Specifications
227
+ - [@objectstack/core](../../core) - Core Interfaces and Types
@@ -1,5 +1,5 @@
1
1
  import { QueryInput } from '@objectstack/spec/data';
2
- import { DriverOptions } from '@objectstack/spec/system';
2
+ import { DriverOptions } from '@objectstack/spec/data';
3
3
  import { DriverInterface } from '@objectstack/core';
4
4
  /**
5
5
  * Example: In-Memory Driver
@@ -11,6 +11,7 @@ export declare class InMemoryDriver implements DriverInterface {
11
11
  name: string;
12
12
  version: string;
13
13
  private config;
14
+ private logger;
14
15
  constructor(config?: any);
15
16
  install(ctx: any): void;
16
17
  supports: {
@@ -1 +1 @@
1
- {"version":3,"file":"memory-driver.d.ts","sourceRoot":"","sources":["../src/memory-driver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD;;;;;GAKG;AACH,qBAAa,cAAe,YAAW,eAAe;IACpD,IAAI,SAAsB;IAC1B,OAAO,SAAW;IAClB,OAAO,CAAC,MAAM,CAAM;gBAER,MAAM,CAAC,EAAE,GAAG;IAKxB,OAAO,CAAC,GAAG,EAAE,GAAG;IAMhB,QAAQ;;;;;;;;;;;;;;MAmBN;IAEF;;OAEG;IACH,OAAO,CAAC,EAAE,CAA6B;IAMjC,OAAO;IAIP,UAAU;IAKV,WAAW;IAQX,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,GAAG,EAAE;IASpC,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,aAAa;IAc9D,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,aAAa;IAOtE,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,aAAa;IAKlE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,CAAC,EAAE,aAAa;;;;;IAezE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,CAAC,EAAE,aAAa;IAkB9F,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,YAAY,CAAC,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,aAAa;IAiBlG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa;IAUnE,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,aAAa;IAQjE,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,EAAE,OAAO,CAAC,EAAE,aAAa;;;;;IAIpF,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;KAAE,EAAE,EAAE,OAAO,CAAC,EAAE,aAAa;IAIjH,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,EAAE,aAAa;IAQ5E,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,aAAa;IAO/D,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa;IAMjD,gBAAgB;IAIhB,MAAM;IACN,QAAQ;IAMd,OAAO,CAAC,QAAQ;IAOhB,OAAO,CAAC,UAAU;CAGnB"}
1
+ {"version":3,"file":"memory-driver.d.ts","sourceRoot":"","sources":["../src/memory-driver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,eAAe,EAAwB,MAAM,mBAAmB,CAAC;AAE1E;;;;;GAKG;AACH,qBAAa,cAAe,YAAW,eAAe;IACpD,IAAI,SAAsB;IAC1B,OAAO,SAAW;IAClB,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,MAAM,CAAS;gBAEX,MAAM,CAAC,EAAE,GAAG;IAOxB,OAAO,CAAC,GAAG,EAAE,GAAG;IAUhB,QAAQ;;;;;;;;;;;;;;MAmBN;IAEF;;OAEG;IACH,OAAO,CAAC,EAAE,CAA6B;IAMjC,OAAO;IAIP,UAAU;IAWV,WAAW;IAYX,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,GAAG,EAAE;IASpC,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,aAAa;IAiB9D,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,aAAa;IAStE,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,aAAa;IAUlE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,CAAC,EAAE,aAAa;;;;;IAkBzE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,CAAC,EAAE,aAAa;IAsB9F,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,YAAY,CAAC,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,aAAa;IAqBlG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa;IAgBnE,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,aAAa;IAUjE,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,EAAE,OAAO,CAAC,EAAE,aAAa;;;;;IAOpF,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;KAAE,EAAE,EAAE,OAAO,CAAC,EAAE,aAAa;IAOjH,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,EAAE,aAAa;IAU5E,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,aAAa;IAO/D,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa;IAQjD,gBAAgB;IAIhB,MAAM;IACN,QAAQ;IAMd,OAAO,CAAC,QAAQ;IAOhB,OAAO,CAAC,UAAU;CAGnB"}
@@ -1,3 +1,4 @@
1
+ import { createLogger } from '@objectstack/core';
1
2
  /**
2
3
  * Example: In-Memory Driver
3
4
  *
@@ -31,37 +32,54 @@ export class InMemoryDriver {
31
32
  */
32
33
  this.db = {};
33
34
  this.config = config || {};
35
+ this.logger = config?.logger || createLogger({ level: 'info', format: 'pretty' });
36
+ this.logger.debug('InMemory driver instance created', { config: this.config });
34
37
  }
35
38
  // Duck-typed RuntimePlugin hook
36
39
  install(ctx) {
40
+ this.logger.debug('Installing InMemory driver via plugin hook');
37
41
  if (ctx.engine && ctx.engine.ql && typeof ctx.engine.ql.registerDriver === 'function') {
38
42
  ctx.engine.ql.registerDriver(this);
43
+ this.logger.info('InMemory driver registered with ObjectQL engine');
44
+ }
45
+ else {
46
+ this.logger.warn('Could not register driver - ObjectQL engine not found in context');
39
47
  }
40
48
  }
41
49
  // ===================================
42
50
  // Lifecycle
43
51
  // ===================================
44
52
  async connect() {
45
- console.log('🔌 [InMemory] Database Connected (Virtual)');
53
+ this.logger.info('InMemory Database Connected (Virtual)');
46
54
  }
47
55
  async disconnect() {
56
+ const tableCount = Object.keys(this.db).length;
57
+ const recordCount = Object.values(this.db).reduce((sum, table) => sum + table.length, 0);
48
58
  this.db = {};
49
- console.log('🔌 [InMemory] Database Disconnected & Cleared');
59
+ this.logger.info('InMemory Database Disconnected & Cleared', {
60
+ tableCount,
61
+ recordCount
62
+ });
50
63
  }
51
64
  async checkHealth() {
65
+ this.logger.debug('Health check performed', {
66
+ tableCount: Object.keys(this.db).length,
67
+ status: 'healthy'
68
+ });
52
69
  return true;
53
70
  }
54
71
  // ===================================
55
72
  // Execution
56
73
  // ===================================
57
74
  async execute(command, params) {
58
- console.log('⚠️ [InMemory] Raw execution not supported, received:', command);
75
+ this.logger.warn('Raw execution not supported in InMemory driver', { command });
59
76
  return null;
60
77
  }
61
78
  // ===================================
62
79
  // CRUD
63
80
  // ===================================
64
81
  async find(object, query, options) {
82
+ this.logger.debug('Find operation', { object, query });
65
83
  const table = this.getTable(object);
66
84
  // 💡 Naive Implementation
67
85
  let results = [...table];
@@ -69,19 +87,25 @@ export class InMemoryDriver {
69
87
  if (query.limit) {
70
88
  results = results.slice(0, query.limit);
71
89
  }
90
+ this.logger.debug('Find completed', { object, resultCount: results.length });
72
91
  return results;
73
92
  }
74
93
  async *findStream(object, query, options) {
94
+ this.logger.debug('FindStream operation', { object });
75
95
  const results = await this.find(object, query, options);
76
96
  for (const record of results) {
77
97
  yield record;
78
98
  }
79
99
  }
80
100
  async findOne(object, query, options) {
101
+ this.logger.debug('FindOne operation', { object, query });
81
102
  const results = await this.find(object, { ...query, limit: 1 }, options);
82
- return results[0] || null;
103
+ const result = results[0] || null;
104
+ this.logger.debug('FindOne completed', { object, found: !!result });
105
+ return result;
83
106
  }
84
107
  async create(object, data, options) {
108
+ this.logger.debug('Create operation', { object, hasData: !!data });
85
109
  const table = this.getTable(object);
86
110
  // COMPATIBILITY: Driver must return 'id' as string
87
111
  const newRecord = {
@@ -91,12 +115,15 @@ export class InMemoryDriver {
91
115
  updated_at: data.updated_at || new Date(),
92
116
  };
93
117
  table.push(newRecord);
118
+ this.logger.debug('Record created', { object, id: newRecord.id, tableSize: table.length });
94
119
  return newRecord;
95
120
  }
96
121
  async update(object, id, data, options) {
122
+ this.logger.debug('Update operation', { object, id });
97
123
  const table = this.getTable(object);
98
124
  const index = table.findIndex(r => r.id == id);
99
125
  if (index === -1) {
126
+ this.logger.warn('Record not found for update', { object, id });
100
127
  throw new Error(`Record with ID ${id} not found in ${object}`);
101
128
  }
102
129
  const updatedRecord = {
@@ -105,9 +132,11 @@ export class InMemoryDriver {
105
132
  updated_at: new Date()
106
133
  };
107
134
  table[index] = updatedRecord;
135
+ this.logger.debug('Record updated', { object, id });
108
136
  return updatedRecord;
109
137
  }
110
138
  async upsert(object, data, conflictKeys, options) {
139
+ this.logger.debug('Upsert operation', { object, conflictKeys });
111
140
  const table = this.getTable(object);
112
141
  let existingRecord = null;
113
142
  if (data.id) {
@@ -117,34 +146,50 @@ export class InMemoryDriver {
117
146
  existingRecord = table.find(r => conflictKeys.every(key => r[key] === data[key]));
118
147
  }
119
148
  if (existingRecord) {
149
+ this.logger.debug('Record exists, updating', { object, id: existingRecord.id });
120
150
  return this.update(object, existingRecord.id, data, options);
121
151
  }
122
152
  else {
153
+ this.logger.debug('Record does not exist, creating', { object });
123
154
  return this.create(object, data, options);
124
155
  }
125
156
  }
126
157
  async delete(object, id, options) {
158
+ this.logger.debug('Delete operation', { object, id });
127
159
  const table = this.getTable(object);
128
160
  const index = table.findIndex(r => r.id == id);
129
- if (index === -1)
161
+ if (index === -1) {
162
+ this.logger.warn('Record not found for deletion', { object, id });
130
163
  return false;
164
+ }
131
165
  table.splice(index, 1);
166
+ this.logger.debug('Record deleted', { object, id, tableSize: table.length });
132
167
  return true;
133
168
  }
134
169
  async count(object, query, options) {
135
- return this.getTable(object).length;
170
+ const count = this.getTable(object).length;
171
+ this.logger.debug('Count operation', { object, count });
172
+ return count;
136
173
  }
137
174
  // ===================================
138
175
  // Bulk Operations
139
176
  // ===================================
140
177
  async bulkCreate(object, dataArray, options) {
141
- return Promise.all(dataArray.map(data => this.create(object, data, options)));
178
+ this.logger.debug('BulkCreate operation', { object, count: dataArray.length });
179
+ const results = await Promise.all(dataArray.map(data => this.create(object, data, options)));
180
+ this.logger.debug('BulkCreate completed', { object, count: results.length });
181
+ return results;
142
182
  }
143
183
  async bulkUpdate(object, updates, options) {
144
- return Promise.all(updates.map(u => this.update(object, u.id, u.data, options)));
184
+ this.logger.debug('BulkUpdate operation', { object, count: updates.length });
185
+ const results = await Promise.all(updates.map(u => this.update(object, u.id, u.data, options)));
186
+ this.logger.debug('BulkUpdate completed', { object, count: results.length });
187
+ return results;
145
188
  }
146
189
  async bulkDelete(object, ids, options) {
190
+ this.logger.debug('BulkDelete operation', { object, count: ids.length });
147
191
  await Promise.all(ids.map(id => this.delete(object, id, options)));
192
+ this.logger.debug('BulkDelete completed', { object, count: ids.length });
148
193
  }
149
194
  // ===================================
150
195
  // Schema & Transactions
@@ -152,12 +197,14 @@ export class InMemoryDriver {
152
197
  async syncSchema(object, schema, options) {
153
198
  if (!this.db[object]) {
154
199
  this.db[object] = [];
155
- console.log(`✨ [InMemory] Created table: ${object}`);
200
+ this.logger.info('Created in-memory table', { object });
156
201
  }
157
202
  }
158
203
  async dropTable(object, options) {
159
204
  if (this.db[object]) {
205
+ const recordCount = this.db[object].length;
160
206
  delete this.db[object];
207
+ this.logger.info('Dropped in-memory table', { object, recordCount });
161
208
  }
162
209
  }
163
210
  async beginTransaction() {
@@ -1,11 +1,17 @@
1
1
  import { ObjectStackManifest } from '@objectstack/spec/system';
2
2
 
3
+ /**
4
+ * In-Memory Driver Plugin Manifest
5
+ *
6
+ * Reference implementation of a storage driver that stores data in memory.
7
+ * Demonstrates the driver protocol implementation pattern.
8
+ */
3
9
  const MemoryDriverPlugin: ObjectStackManifest = {
4
10
  id: 'com.objectstack.driver.memory',
5
11
  name: 'In-Memory Driver',
6
12
  version: '1.0.0',
7
13
  type: 'driver',
8
- description: 'A reference specificiation implementation of the DriverInterface using in-memory arrays.',
14
+ description: 'A reference specification implementation of the DriverInterface using in-memory arrays. Suitable for testing and development.',
9
15
 
10
16
  configuration: {
11
17
  title: 'Memory Driver Settings',
@@ -18,9 +24,234 @@ const MemoryDriverPlugin: ObjectStackManifest = {
18
24
  }
19
25
  },
20
26
 
27
+ // Plugin Capability Declaration
28
+ capabilities: {
29
+ // Protocols This Driver Implements
30
+ implements: [
31
+ {
32
+ protocol: {
33
+ id: 'com.objectstack.protocol.storage.v1',
34
+ label: 'Storage Protocol v1',
35
+ version: { major: 1, minor: 0, patch: 0 },
36
+ description: 'Standard data storage and retrieval operations',
37
+ },
38
+ conformance: 'partial',
39
+ implementedFeatures: [
40
+ 'basic_crud',
41
+ 'pagination',
42
+ ],
43
+ features: [
44
+ {
45
+ name: 'basic_crud',
46
+ enabled: true,
47
+ description: 'Create, read, update, delete operations',
48
+ },
49
+ {
50
+ name: 'pagination',
51
+ enabled: true,
52
+ description: 'Basic pagination via limit/offset',
53
+ },
54
+ {
55
+ name: 'query_filters',
56
+ enabled: false,
57
+ description: 'Advanced query filtering',
58
+ },
59
+ {
60
+ name: 'aggregations',
61
+ enabled: false,
62
+ description: 'Count, sum, avg operations',
63
+ },
64
+ {
65
+ name: 'sorting',
66
+ enabled: false,
67
+ description: 'Result set sorting',
68
+ },
69
+ {
70
+ name: 'transactions',
71
+ enabled: false,
72
+ description: 'ACID transaction support',
73
+ },
74
+ {
75
+ name: 'joins',
76
+ enabled: false,
77
+ description: 'Cross-object joins',
78
+ },
79
+ ],
80
+ certified: false,
81
+ },
82
+ ],
83
+
84
+ // Interfaces This Driver Provides
85
+ provides: [
86
+ {
87
+ id: 'com.objectstack.driver.memory.interface.driver',
88
+ name: 'DriverInterface',
89
+ description: 'Standard ObjectStack driver interface for data operations',
90
+ version: { major: 1, minor: 0, patch: 0 },
91
+ stability: 'stable',
92
+ methods: [
93
+ {
94
+ name: 'connect',
95
+ description: 'Initialize driver connection',
96
+ parameters: [],
97
+ returnType: 'Promise<void>',
98
+ async: true,
99
+ },
100
+ {
101
+ name: 'disconnect',
102
+ description: 'Close driver connection',
103
+ parameters: [],
104
+ returnType: 'Promise<void>',
105
+ async: true,
106
+ },
107
+ {
108
+ name: 'create',
109
+ description: 'Create a new record',
110
+ parameters: [
111
+ {
112
+ name: 'object',
113
+ type: 'string',
114
+ required: true,
115
+ description: 'Object name',
116
+ },
117
+ {
118
+ name: 'data',
119
+ type: 'Record<string, any>',
120
+ required: true,
121
+ description: 'Record data',
122
+ },
123
+ ],
124
+ returnType: 'Promise<any>',
125
+ async: true,
126
+ },
127
+ {
128
+ name: 'find',
129
+ description: 'Query records',
130
+ parameters: [
131
+ {
132
+ name: 'object',
133
+ type: 'string',
134
+ required: true,
135
+ description: 'Object name',
136
+ },
137
+ {
138
+ name: 'query',
139
+ type: 'QueryInput',
140
+ required: false,
141
+ description: 'Query parameters',
142
+ },
143
+ ],
144
+ returnType: 'Promise<any[]>',
145
+ async: true,
146
+ },
147
+ {
148
+ name: 'findOne',
149
+ description: 'Find a single record by ID',
150
+ parameters: [
151
+ {
152
+ name: 'object',
153
+ type: 'string',
154
+ required: true,
155
+ description: 'Object name',
156
+ },
157
+ {
158
+ name: 'id',
159
+ type: 'string',
160
+ required: true,
161
+ description: 'Record ID',
162
+ },
163
+ ],
164
+ returnType: 'Promise<any>',
165
+ async: true,
166
+ },
167
+ {
168
+ name: 'update',
169
+ description: 'Update a record',
170
+ parameters: [
171
+ {
172
+ name: 'object',
173
+ type: 'string',
174
+ required: true,
175
+ description: 'Object name',
176
+ },
177
+ {
178
+ name: 'id',
179
+ type: 'string',
180
+ required: true,
181
+ description: 'Record ID',
182
+ },
183
+ {
184
+ name: 'data',
185
+ type: 'Record<string, any>',
186
+ required: true,
187
+ description: 'Updated record data',
188
+ },
189
+ ],
190
+ returnType: 'Promise<any>',
191
+ async: true,
192
+ },
193
+ {
194
+ name: 'delete',
195
+ description: 'Delete a record',
196
+ parameters: [
197
+ {
198
+ name: 'object',
199
+ type: 'string',
200
+ required: true,
201
+ description: 'Object name',
202
+ },
203
+ {
204
+ name: 'id',
205
+ type: 'string',
206
+ required: true,
207
+ description: 'Record ID',
208
+ },
209
+ ],
210
+ returnType: 'Promise<boolean>',
211
+ async: true,
212
+ },
213
+ {
214
+ name: 'count',
215
+ description: 'Count records',
216
+ parameters: [
217
+ {
218
+ name: 'object',
219
+ type: 'string',
220
+ required: true,
221
+ description: 'Object name',
222
+ },
223
+ {
224
+ name: 'query',
225
+ type: 'QueryInput',
226
+ required: false,
227
+ description: 'Query parameters',
228
+ },
229
+ ],
230
+ returnType: 'Promise<number>',
231
+ async: true,
232
+ },
233
+ ],
234
+ },
235
+ ],
236
+
237
+ // No external plugin dependencies (this is a core driver)
238
+ requires: [],
239
+
240
+ // No extension points defined
241
+ extensionPoints: [],
242
+
243
+ // No extensions contributed
244
+ extensions: [],
245
+ },
246
+
21
247
  contributes: {
22
- // If there was a 'drivers' definition here, we would use it.
23
- // For now, we register via code (src/index.ts)
248
+ drivers: [
249
+ {
250
+ id: 'memory',
251
+ label: 'In-Memory Storage',
252
+ description: 'Stores data in memory (volatile, for testing/development)',
253
+ },
254
+ ],
24
255
  }
25
256
  };
26
257
 
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@objectstack/driver-memory",
3
- "version": "0.6.1",
3
+ "version": "0.7.2",
4
4
  "description": "In-Memory Driver for ObjectStack (Reference Implementation)",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "dependencies": {
8
- "@objectstack/core": "0.6.1",
9
- "@objectstack/spec": "0.6.1"
8
+ "@objectstack/core": "0.7.2",
9
+ "@objectstack/spec": "0.7.2"
10
10
  },
11
11
  "devDependencies": {
12
12
  "typescript": "^5.0.0"
@@ -1,6 +1,6 @@
1
1
  import { QueryAST, QueryInput } from '@objectstack/spec/data';
2
- import { DriverOptions } from '@objectstack/spec/system';
3
- import { DriverInterface } from '@objectstack/core';
2
+ import { DriverOptions } from '@objectstack/spec/data';
3
+ import { DriverInterface, Logger, createLogger } from '@objectstack/core';
4
4
 
5
5
  /**
6
6
  * Example: In-Memory Driver
@@ -12,15 +12,22 @@ export class InMemoryDriver implements DriverInterface {
12
12
  name = 'in-memory-driver';
13
13
  version = '0.0.1';
14
14
  private config: any;
15
+ private logger: Logger;
15
16
 
16
17
  constructor(config?: any) {
17
18
  this.config = config || {};
19
+ this.logger = config?.logger || createLogger({ level: 'info', format: 'pretty' });
20
+ this.logger.debug('InMemory driver instance created', { config: this.config });
18
21
  }
19
22
 
20
23
  // Duck-typed RuntimePlugin hook
21
24
  install(ctx: any) {
25
+ this.logger.debug('Installing InMemory driver via plugin hook');
22
26
  if (ctx.engine && ctx.engine.ql && typeof ctx.engine.ql.registerDriver === 'function') {
23
27
  ctx.engine.ql.registerDriver(this);
28
+ this.logger.info('InMemory driver registered with ObjectQL engine');
29
+ } else {
30
+ this.logger.warn('Could not register driver - ObjectQL engine not found in context');
24
31
  }
25
32
  }
26
33
 
@@ -55,15 +62,25 @@ export class InMemoryDriver implements DriverInterface {
55
62
  // ===================================
56
63
 
57
64
  async connect() {
58
- console.log('🔌 [InMemory] Database Connected (Virtual)');
65
+ this.logger.info('InMemory Database Connected (Virtual)');
59
66
  }
60
67
 
61
68
  async disconnect() {
69
+ const tableCount = Object.keys(this.db).length;
70
+ const recordCount = Object.values(this.db).reduce((sum, table) => sum + table.length, 0);
71
+
62
72
  this.db = {};
63
- console.log('🔌 [InMemory] Database Disconnected & Cleared');
73
+ this.logger.info('InMemory Database Disconnected & Cleared', {
74
+ tableCount,
75
+ recordCount
76
+ });
64
77
  }
65
78
 
66
79
  async checkHealth() {
80
+ this.logger.debug('Health check performed', {
81
+ tableCount: Object.keys(this.db).length,
82
+ status: 'healthy'
83
+ });
67
84
  return true;
68
85
  }
69
86
 
@@ -72,7 +89,7 @@ export class InMemoryDriver implements DriverInterface {
72
89
  // ===================================
73
90
 
74
91
  async execute(command: any, params?: any[]) {
75
- console.log('⚠️ [InMemory] Raw execution not supported, received:', command);
92
+ this.logger.warn('Raw execution not supported in InMemory driver', { command });
76
93
  return null;
77
94
  }
78
95
 
@@ -81,6 +98,8 @@ export class InMemoryDriver implements DriverInterface {
81
98
  // ===================================
82
99
 
83
100
  async find(object: string, query: QueryInput, options?: DriverOptions) {
101
+ this.logger.debug('Find operation', { object, query });
102
+
84
103
  const table = this.getTable(object);
85
104
 
86
105
  // 💡 Naive Implementation
@@ -91,10 +110,13 @@ export class InMemoryDriver implements DriverInterface {
91
110
  results = results.slice(0, query.limit);
92
111
  }
93
112
 
113
+ this.logger.debug('Find completed', { object, resultCount: results.length });
94
114
  return results;
95
115
  }
96
116
 
97
117
  async *findStream(object: string, query: QueryInput, options?: DriverOptions) {
118
+ this.logger.debug('FindStream operation', { object });
119
+
98
120
  const results = await this.find(object, query, options);
99
121
  for (const record of results) {
100
122
  yield record;
@@ -102,11 +124,18 @@ export class InMemoryDriver implements DriverInterface {
102
124
  }
103
125
 
104
126
  async findOne(object: string, query: QueryInput, options?: DriverOptions) {
127
+ this.logger.debug('FindOne operation', { object, query });
128
+
105
129
  const results = await this.find(object, { ...query, limit: 1 }, options);
106
- return results[0] || null;
130
+ const result = results[0] || null;
131
+
132
+ this.logger.debug('FindOne completed', { object, found: !!result });
133
+ return result;
107
134
  }
108
135
 
109
136
  async create(object: string, data: Record<string, any>, options?: DriverOptions) {
137
+ this.logger.debug('Create operation', { object, hasData: !!data });
138
+
110
139
  const table = this.getTable(object);
111
140
 
112
141
  // COMPATIBILITY: Driver must return 'id' as string
@@ -118,14 +147,18 @@ export class InMemoryDriver implements DriverInterface {
118
147
  };
119
148
 
120
149
  table.push(newRecord);
150
+ this.logger.debug('Record created', { object, id: newRecord.id, tableSize: table.length });
121
151
  return newRecord;
122
152
  }
123
153
 
124
154
  async update(object: string, id: string | number, data: Record<string, any>, options?: DriverOptions) {
155
+ this.logger.debug('Update operation', { object, id });
156
+
125
157
  const table = this.getTable(object);
126
158
  const index = table.findIndex(r => r.id == id);
127
159
 
128
160
  if (index === -1) {
161
+ this.logger.warn('Record not found for update', { object, id });
129
162
  throw new Error(`Record with ID ${id} not found in ${object}`);
130
163
  }
131
164
 
@@ -136,10 +169,13 @@ export class InMemoryDriver implements DriverInterface {
136
169
  };
137
170
 
138
171
  table[index] = updatedRecord;
172
+ this.logger.debug('Record updated', { object, id });
139
173
  return updatedRecord;
140
174
  }
141
175
 
142
176
  async upsert(object: string, data: Record<string, any>, conflictKeys?: string[], options?: DriverOptions) {
177
+ this.logger.debug('Upsert operation', { object, conflictKeys });
178
+
143
179
  const table = this.getTable(object);
144
180
  let existingRecord: any = null;
145
181
 
@@ -150,24 +186,34 @@ export class InMemoryDriver implements DriverInterface {
150
186
  }
151
187
 
152
188
  if (existingRecord) {
189
+ this.logger.debug('Record exists, updating', { object, id: existingRecord.id });
153
190
  return this.update(object, existingRecord.id, data, options);
154
191
  } else {
192
+ this.logger.debug('Record does not exist, creating', { object });
155
193
  return this.create(object, data, options);
156
194
  }
157
195
  }
158
196
 
159
197
  async delete(object: string, id: string | number, options?: DriverOptions) {
198
+ this.logger.debug('Delete operation', { object, id });
199
+
160
200
  const table = this.getTable(object);
161
201
  const index = table.findIndex(r => r.id == id);
162
202
 
163
- if (index === -1) return false;
203
+ if (index === -1) {
204
+ this.logger.warn('Record not found for deletion', { object, id });
205
+ return false;
206
+ }
164
207
 
165
208
  table.splice(index, 1);
209
+ this.logger.debug('Record deleted', { object, id, tableSize: table.length });
166
210
  return true;
167
211
  }
168
212
 
169
213
  async count(object: string, query?: QueryInput, options?: DriverOptions) {
170
- return this.getTable(object).length;
214
+ const count = this.getTable(object).length;
215
+ this.logger.debug('Count operation', { object, count });
216
+ return count;
171
217
  }
172
218
 
173
219
  // ===================================
@@ -175,15 +221,23 @@ export class InMemoryDriver implements DriverInterface {
175
221
  // ===================================
176
222
 
177
223
  async bulkCreate(object: string, dataArray: Record<string, any>[], options?: DriverOptions) {
178
- return Promise.all(dataArray.map(data => this.create(object, data, options)));
224
+ this.logger.debug('BulkCreate operation', { object, count: dataArray.length });
225
+ const results = await Promise.all(dataArray.map(data => this.create(object, data, options)));
226
+ this.logger.debug('BulkCreate completed', { object, count: results.length });
227
+ return results;
179
228
  }
180
229
 
181
230
  async bulkUpdate(object: string, updates: { id: string | number, data: Record<string, any> }[], options?: DriverOptions) {
182
- return Promise.all(updates.map(u => this.update(object, u.id, u.data, options)));
231
+ this.logger.debug('BulkUpdate operation', { object, count: updates.length });
232
+ const results = await Promise.all(updates.map(u => this.update(object, u.id, u.data, options)));
233
+ this.logger.debug('BulkUpdate completed', { object, count: results.length });
234
+ return results;
183
235
  }
184
236
 
185
237
  async bulkDelete(object: string, ids: (string | number)[], options?: DriverOptions) {
238
+ this.logger.debug('BulkDelete operation', { object, count: ids.length });
186
239
  await Promise.all(ids.map(id => this.delete(object, id, options)));
240
+ this.logger.debug('BulkDelete completed', { object, count: ids.length });
187
241
  }
188
242
 
189
243
  // ===================================
@@ -193,13 +247,15 @@ export class InMemoryDriver implements DriverInterface {
193
247
  async syncSchema(object: string, schema: any, options?: DriverOptions) {
194
248
  if (!this.db[object]) {
195
249
  this.db[object] = [];
196
- console.log(`✨ [InMemory] Created table: ${object}`);
250
+ this.logger.info('Created in-memory table', { object });
197
251
  }
198
252
  }
199
253
 
200
254
  async dropTable(object: string, options?: DriverOptions) {
201
255
  if (this.db[object]) {
256
+ const recordCount = this.db[object].length;
202
257
  delete this.db[object];
258
+ this.logger.info('Dropped in-memory table', { object, recordCount });
203
259
  }
204
260
  }
205
261