@openmdm/drizzle-adapter 0.2.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024-present OpenMDM Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,11 @@
1
+ // src/schema.ts
2
+ import { mdmSchema, getTableNames, getColumnNames } from "@openmdm/core/schema";
3
+ var DEFAULT_TABLE_PREFIX = "mdm_";
4
+
5
+ export {
6
+ DEFAULT_TABLE_PREFIX,
7
+ mdmSchema,
8
+ getTableNames,
9
+ getColumnNames
10
+ };
11
+ //# sourceMappingURL=chunk-ULFOOXEW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/schema.ts"],"sourcesContent":["/**\n * OpenMDM Drizzle Schema\n *\n * This file exports the schema definition for use with schema generators.\n * For ready-to-use Drizzle tables, import from:\n * - @openmdm/drizzle-adapter/postgres\n * - @openmdm/drizzle-adapter/mysql\n * - @openmdm/drizzle-adapter/sqlite\n */\n\nexport { mdmSchema, getTableNames, getColumnNames } from '@openmdm/core/schema';\n\n/**\n * Table prefix for MDM tables.\n * Can be customized via the adapter options.\n */\nexport const DEFAULT_TABLE_PREFIX = 'mdm_';\n\n/**\n * Schema options for customizing table generation\n */\nexport interface SchemaOptions {\n /** Prefix for all MDM tables (default: 'mdm_') */\n tablePrefix?: string;\n /** Custom schema name (PostgreSQL only) */\n schema?: string;\n}\n"],"mappings":";AAUA,SAAS,WAAW,eAAe,sBAAsB;AAMlD,IAAM,uBAAuB;","names":[]}
@@ -0,0 +1,58 @@
1
+ import { DatabaseAdapter } from '@openmdm/core';
2
+ import { mdmDevices, mdmPolicies, mdmApplications, mdmCommands, mdmEvents, mdmGroups, mdmDeviceGroups, mdmPushTokens } from './postgres.js';
3
+ export { DEFAULT_TABLE_PREFIX, SchemaOptions } from './schema.js';
4
+ import 'drizzle-orm';
5
+ import 'drizzle-orm/pg-core';
6
+ import '@openmdm/core/schema';
7
+
8
+ /**
9
+ * OpenMDM Drizzle Adapter
10
+ *
11
+ * Database adapter for Drizzle ORM, supporting PostgreSQL, MySQL, and SQLite.
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * import { drizzle } from 'drizzle-orm/node-postgres';
16
+ * import { drizzleAdapter } from '@openmdm/drizzle-adapter';
17
+ * import { mdmSchema } from '@openmdm/drizzle-adapter/postgres';
18
+ * import { createMDM } from '@openmdm/core';
19
+ *
20
+ * const pool = new Pool({ connectionString: DATABASE_URL });
21
+ * const db = drizzle(pool, { schema: mdmSchema });
22
+ *
23
+ * const mdm = createMDM({
24
+ * database: drizzleAdapter(db),
25
+ * // ...
26
+ * });
27
+ * ```
28
+ */
29
+
30
+ type DrizzleDB = {
31
+ select: (columns?: unknown) => unknown;
32
+ insert: (table: unknown) => unknown;
33
+ update: (table: unknown) => unknown;
34
+ delete: (table: unknown) => unknown;
35
+ query: Record<string, unknown>;
36
+ transaction: <T>(fn: (tx: DrizzleDB) => Promise<T>) => Promise<T>;
37
+ };
38
+ interface DrizzleAdapterOptions {
39
+ /**
40
+ * Table references - pass your imported Drizzle tables
41
+ */
42
+ tables: {
43
+ devices: typeof mdmDevices;
44
+ policies: typeof mdmPolicies;
45
+ applications: typeof mdmApplications;
46
+ commands: typeof mdmCommands;
47
+ events: typeof mdmEvents;
48
+ groups: typeof mdmGroups;
49
+ deviceGroups: typeof mdmDeviceGroups;
50
+ pushTokens: typeof mdmPushTokens;
51
+ };
52
+ }
53
+ /**
54
+ * Create a Drizzle database adapter for OpenMDM
55
+ */
56
+ declare function drizzleAdapter(db: DrizzleDB, options: DrizzleAdapterOptions): DatabaseAdapter;
57
+
58
+ export { type DrizzleAdapterOptions, drizzleAdapter };
package/dist/index.js ADDED
@@ -0,0 +1,588 @@
1
+ import {
2
+ DEFAULT_TABLE_PREFIX
3
+ } from "./chunk-ULFOOXEW.js";
4
+
5
+ // src/index.ts
6
+ import { eq, and, or, like, inArray, desc, sql } from "drizzle-orm";
7
+ import { nanoid } from "nanoid";
8
+ function drizzleAdapter(db, options) {
9
+ const { tables } = options;
10
+ const {
11
+ devices,
12
+ policies,
13
+ applications,
14
+ commands,
15
+ events,
16
+ groups,
17
+ deviceGroups,
18
+ pushTokens
19
+ } = tables;
20
+ const generateId = () => nanoid(21);
21
+ const toDevice = (row) => ({
22
+ id: row.id,
23
+ externalId: row.externalId,
24
+ enrollmentId: row.enrollmentId,
25
+ status: row.status,
26
+ model: row.model,
27
+ manufacturer: row.manufacturer,
28
+ osVersion: row.osVersion,
29
+ serialNumber: row.serialNumber,
30
+ imei: row.imei,
31
+ macAddress: row.macAddress,
32
+ androidId: row.androidId,
33
+ policyId: row.policyId,
34
+ lastHeartbeat: row.lastHeartbeat,
35
+ lastSync: row.lastSync,
36
+ batteryLevel: row.batteryLevel,
37
+ storageUsed: row.storageUsed,
38
+ storageTotal: row.storageTotal,
39
+ location: row.latitude && row.longitude ? {
40
+ latitude: parseFloat(row.latitude),
41
+ longitude: parseFloat(row.longitude),
42
+ timestamp: row.locationTimestamp
43
+ } : null,
44
+ installedApps: row.installedApps,
45
+ tags: row.tags,
46
+ metadata: row.metadata,
47
+ createdAt: row.createdAt,
48
+ updatedAt: row.updatedAt
49
+ });
50
+ const toPolicy = (row) => ({
51
+ id: row.id,
52
+ name: row.name,
53
+ description: row.description,
54
+ isDefault: row.isDefault,
55
+ settings: row.settings,
56
+ createdAt: row.createdAt,
57
+ updatedAt: row.updatedAt
58
+ });
59
+ const toApplication = (row) => ({
60
+ id: row.id,
61
+ name: row.name,
62
+ packageName: row.packageName,
63
+ version: row.version,
64
+ versionCode: row.versionCode,
65
+ url: row.url,
66
+ hash: row.hash,
67
+ size: row.size,
68
+ minSdkVersion: row.minSdkVersion,
69
+ showIcon: row.showIcon,
70
+ runAfterInstall: row.runAfterInstall,
71
+ runAtBoot: row.runAtBoot,
72
+ isSystem: row.isSystem,
73
+ isActive: row.isActive,
74
+ metadata: row.metadata,
75
+ createdAt: row.createdAt,
76
+ updatedAt: row.updatedAt
77
+ });
78
+ const toCommand = (row) => ({
79
+ id: row.id,
80
+ deviceId: row.deviceId,
81
+ type: row.type,
82
+ payload: row.payload,
83
+ status: row.status,
84
+ result: row.result,
85
+ error: row.error,
86
+ createdAt: row.createdAt,
87
+ sentAt: row.sentAt,
88
+ acknowledgedAt: row.acknowledgedAt,
89
+ completedAt: row.completedAt
90
+ });
91
+ const toEvent = (row) => ({
92
+ id: row.id,
93
+ deviceId: row.deviceId,
94
+ type: row.type,
95
+ payload: row.payload,
96
+ createdAt: row.createdAt
97
+ });
98
+ const toGroup = (row) => ({
99
+ id: row.id,
100
+ name: row.name,
101
+ description: row.description,
102
+ policyId: row.policyId,
103
+ parentId: row.parentId,
104
+ metadata: row.metadata,
105
+ createdAt: row.createdAt,
106
+ updatedAt: row.updatedAt
107
+ });
108
+ const toPushToken = (row) => ({
109
+ id: row.id,
110
+ deviceId: row.deviceId,
111
+ provider: row.provider,
112
+ token: row.token,
113
+ isActive: row.isActive,
114
+ createdAt: row.createdAt,
115
+ updatedAt: row.updatedAt
116
+ });
117
+ return {
118
+ // ============================================
119
+ // Device Methods
120
+ // ============================================
121
+ async findDevice(id) {
122
+ const result = await db.select().from(devices).where(eq(devices.id, id)).limit(1);
123
+ return result[0] ? toDevice(result[0]) : null;
124
+ },
125
+ async findDeviceByEnrollmentId(enrollmentId) {
126
+ const result = await db.select().from(devices).where(eq(devices.enrollmentId, enrollmentId)).limit(1);
127
+ return result[0] ? toDevice(result[0]) : null;
128
+ },
129
+ async listDevices(filter) {
130
+ const limit = filter?.limit ?? 100;
131
+ const offset = filter?.offset ?? 0;
132
+ let query = db.select().from(devices);
133
+ const conditions = [];
134
+ if (filter?.status) {
135
+ if (Array.isArray(filter.status)) {
136
+ conditions.push(inArray(devices.status, filter.status));
137
+ } else {
138
+ conditions.push(eq(devices.status, filter.status));
139
+ }
140
+ }
141
+ if (filter?.policyId) {
142
+ conditions.push(eq(devices.policyId, filter.policyId));
143
+ }
144
+ if (filter?.search) {
145
+ const searchPattern = `%${filter.search}%`;
146
+ conditions.push(
147
+ or(
148
+ like(devices.model, searchPattern),
149
+ like(devices.manufacturer, searchPattern),
150
+ like(devices.enrollmentId, searchPattern),
151
+ like(devices.serialNumber, searchPattern)
152
+ )
153
+ );
154
+ }
155
+ if (conditions.length > 0) {
156
+ query = query.where(and(...conditions));
157
+ }
158
+ const countResult = await db.select({ count: sql`count(*)` }).from(devices).where(conditions.length > 0 ? and(...conditions) : void 0);
159
+ const total = Number(countResult[0]?.count ?? 0);
160
+ const result = await query.orderBy(desc(devices.createdAt)).limit(limit).offset(offset);
161
+ return {
162
+ devices: result.map(toDevice),
163
+ total,
164
+ limit,
165
+ offset
166
+ };
167
+ },
168
+ async createDevice(data) {
169
+ const id = generateId();
170
+ const now = /* @__PURE__ */ new Date();
171
+ const deviceData = {
172
+ id,
173
+ enrollmentId: data.enrollmentId,
174
+ externalId: data.externalId ?? null,
175
+ status: "pending",
176
+ model: data.model ?? null,
177
+ manufacturer: data.manufacturer ?? null,
178
+ osVersion: data.osVersion ?? null,
179
+ serialNumber: data.serialNumber ?? null,
180
+ imei: data.imei ?? null,
181
+ macAddress: data.macAddress ?? null,
182
+ androidId: data.androidId ?? null,
183
+ policyId: data.policyId ?? null,
184
+ tags: data.tags ?? null,
185
+ metadata: data.metadata ?? null,
186
+ createdAt: now,
187
+ updatedAt: now
188
+ };
189
+ await db.insert(devices).values(deviceData);
190
+ return this.findDevice(id);
191
+ },
192
+ async updateDevice(id, data) {
193
+ const updateData = {
194
+ updatedAt: /* @__PURE__ */ new Date()
195
+ };
196
+ if (data.externalId !== void 0) updateData.externalId = data.externalId;
197
+ if (data.status !== void 0) updateData.status = data.status;
198
+ if (data.policyId !== void 0) updateData.policyId = data.policyId;
199
+ if (data.model !== void 0) updateData.model = data.model;
200
+ if (data.manufacturer !== void 0)
201
+ updateData.manufacturer = data.manufacturer;
202
+ if (data.osVersion !== void 0) updateData.osVersion = data.osVersion;
203
+ if (data.batteryLevel !== void 0)
204
+ updateData.batteryLevel = data.batteryLevel;
205
+ if (data.storageUsed !== void 0)
206
+ updateData.storageUsed = data.storageUsed;
207
+ if (data.storageTotal !== void 0)
208
+ updateData.storageTotal = data.storageTotal;
209
+ if (data.lastHeartbeat !== void 0)
210
+ updateData.lastHeartbeat = data.lastHeartbeat;
211
+ if (data.lastSync !== void 0) updateData.lastSync = data.lastSync;
212
+ if (data.installedApps !== void 0)
213
+ updateData.installedApps = data.installedApps;
214
+ if (data.tags !== void 0) updateData.tags = data.tags;
215
+ if (data.metadata !== void 0) updateData.metadata = data.metadata;
216
+ if (data.location) {
217
+ updateData.latitude = data.location.latitude.toString();
218
+ updateData.longitude = data.location.longitude.toString();
219
+ updateData.locationTimestamp = data.location.timestamp;
220
+ }
221
+ await db.update(devices).set(updateData).where(eq(devices.id, id));
222
+ return this.findDevice(id);
223
+ },
224
+ async deleteDevice(id) {
225
+ await db.delete(devices).where(eq(devices.id, id));
226
+ },
227
+ async countDevices(filter) {
228
+ const result = await this.listDevices({ ...filter, limit: 0 });
229
+ return result.total;
230
+ },
231
+ // ============================================
232
+ // Policy Methods
233
+ // ============================================
234
+ async findPolicy(id) {
235
+ const result = await db.select().from(policies).where(eq(policies.id, id)).limit(1);
236
+ return result[0] ? toPolicy(result[0]) : null;
237
+ },
238
+ async findDefaultPolicy() {
239
+ const result = await db.select().from(policies).where(eq(policies.isDefault, true)).limit(1);
240
+ return result[0] ? toPolicy(result[0]) : null;
241
+ },
242
+ async listPolicies() {
243
+ const result = await db.select().from(policies).orderBy(desc(policies.createdAt));
244
+ return result.map(toPolicy);
245
+ },
246
+ async createPolicy(data) {
247
+ const id = generateId();
248
+ const now = /* @__PURE__ */ new Date();
249
+ const policyData = {
250
+ id,
251
+ name: data.name,
252
+ description: data.description ?? null,
253
+ isDefault: data.isDefault ?? false,
254
+ settings: data.settings,
255
+ createdAt: now,
256
+ updatedAt: now
257
+ };
258
+ await db.insert(policies).values(policyData);
259
+ return this.findPolicy(id);
260
+ },
261
+ async updatePolicy(id, data) {
262
+ const updateData = {
263
+ updatedAt: /* @__PURE__ */ new Date()
264
+ };
265
+ if (data.name !== void 0) updateData.name = data.name;
266
+ if (data.description !== void 0)
267
+ updateData.description = data.description;
268
+ if (data.isDefault !== void 0) updateData.isDefault = data.isDefault;
269
+ if (data.settings !== void 0) updateData.settings = data.settings;
270
+ await db.update(policies).set(updateData).where(eq(policies.id, id));
271
+ return this.findPolicy(id);
272
+ },
273
+ async deletePolicy(id) {
274
+ await db.delete(policies).where(eq(policies.id, id));
275
+ },
276
+ // ============================================
277
+ // Application Methods
278
+ // ============================================
279
+ async findApplication(id) {
280
+ const result = await db.select().from(applications).where(eq(applications.id, id)).limit(1);
281
+ return result[0] ? toApplication(result[0]) : null;
282
+ },
283
+ async findApplicationByPackage(packageName, version) {
284
+ let query = db.select().from(applications).where(eq(applications.packageName, packageName));
285
+ if (version) {
286
+ query = query.where(eq(applications.version, version));
287
+ }
288
+ const result = await query.orderBy(desc(applications.versionCode)).limit(1);
289
+ return result[0] ? toApplication(result[0]) : null;
290
+ },
291
+ async listApplications(activeOnly) {
292
+ let query = db.select().from(applications);
293
+ if (activeOnly) {
294
+ query = query.where(eq(applications.isActive, true));
295
+ }
296
+ const result = await query.orderBy(desc(applications.createdAt));
297
+ return result.map(toApplication);
298
+ },
299
+ async createApplication(data) {
300
+ const id = generateId();
301
+ const now = /* @__PURE__ */ new Date();
302
+ const appData = {
303
+ id,
304
+ name: data.name,
305
+ packageName: data.packageName,
306
+ version: data.version,
307
+ versionCode: data.versionCode,
308
+ url: data.url,
309
+ hash: data.hash ?? null,
310
+ size: data.size ?? null,
311
+ minSdkVersion: data.minSdkVersion ?? null,
312
+ showIcon: data.showIcon ?? true,
313
+ runAfterInstall: data.runAfterInstall ?? false,
314
+ runAtBoot: data.runAtBoot ?? false,
315
+ isSystem: data.isSystem ?? false,
316
+ isActive: true,
317
+ metadata: data.metadata ?? null,
318
+ createdAt: now,
319
+ updatedAt: now
320
+ };
321
+ await db.insert(applications).values(appData);
322
+ return this.findApplication(id);
323
+ },
324
+ async updateApplication(id, data) {
325
+ const updateData = {
326
+ updatedAt: /* @__PURE__ */ new Date()
327
+ };
328
+ if (data.name !== void 0) updateData.name = data.name;
329
+ if (data.version !== void 0) updateData.version = data.version;
330
+ if (data.versionCode !== void 0)
331
+ updateData.versionCode = data.versionCode;
332
+ if (data.url !== void 0) updateData.url = data.url;
333
+ if (data.hash !== void 0) updateData.hash = data.hash;
334
+ if (data.size !== void 0) updateData.size = data.size;
335
+ if (data.minSdkVersion !== void 0)
336
+ updateData.minSdkVersion = data.minSdkVersion;
337
+ if (data.showIcon !== void 0) updateData.showIcon = data.showIcon;
338
+ if (data.runAfterInstall !== void 0)
339
+ updateData.runAfterInstall = data.runAfterInstall;
340
+ if (data.runAtBoot !== void 0) updateData.runAtBoot = data.runAtBoot;
341
+ if (data.isActive !== void 0) updateData.isActive = data.isActive;
342
+ if (data.metadata !== void 0) updateData.metadata = data.metadata;
343
+ await db.update(applications).set(updateData).where(eq(applications.id, id));
344
+ return this.findApplication(id);
345
+ },
346
+ async deleteApplication(id) {
347
+ await db.delete(applications).where(eq(applications.id, id));
348
+ },
349
+ // ============================================
350
+ // Command Methods
351
+ // ============================================
352
+ async findCommand(id) {
353
+ const result = await db.select().from(commands).where(eq(commands.id, id)).limit(1);
354
+ return result[0] ? toCommand(result[0]) : null;
355
+ },
356
+ async listCommands(filter) {
357
+ let query = db.select().from(commands);
358
+ const conditions = [];
359
+ if (filter?.deviceId) {
360
+ conditions.push(eq(commands.deviceId, filter.deviceId));
361
+ }
362
+ if (filter?.status) {
363
+ if (Array.isArray(filter.status)) {
364
+ conditions.push(inArray(commands.status, filter.status));
365
+ } else {
366
+ conditions.push(eq(commands.status, filter.status));
367
+ }
368
+ }
369
+ if (filter?.type) {
370
+ if (Array.isArray(filter.type)) {
371
+ conditions.push(inArray(commands.type, filter.type));
372
+ } else {
373
+ conditions.push(eq(commands.type, filter.type));
374
+ }
375
+ }
376
+ if (conditions.length > 0) {
377
+ query = query.where(and(...conditions));
378
+ }
379
+ const limit = filter?.limit ?? 100;
380
+ const offset = filter?.offset ?? 0;
381
+ const result = await query.orderBy(desc(commands.createdAt)).limit(limit).offset(offset);
382
+ return result.map(toCommand);
383
+ },
384
+ async createCommand(data) {
385
+ const id = generateId();
386
+ const now = /* @__PURE__ */ new Date();
387
+ const commandData = {
388
+ id,
389
+ deviceId: data.deviceId,
390
+ type: data.type,
391
+ payload: data.payload ?? null,
392
+ status: "pending",
393
+ createdAt: now
394
+ };
395
+ await db.insert(commands).values(commandData);
396
+ return this.findCommand(id);
397
+ },
398
+ async updateCommand(id, data) {
399
+ const updateData = {};
400
+ if (data.status !== void 0) updateData.status = data.status;
401
+ if (data.result !== void 0) updateData.result = data.result;
402
+ if (data.error !== void 0) updateData.error = data.error;
403
+ if (data.sentAt !== void 0) updateData.sentAt = data.sentAt;
404
+ if (data.acknowledgedAt !== void 0)
405
+ updateData.acknowledgedAt = data.acknowledgedAt;
406
+ if (data.completedAt !== void 0)
407
+ updateData.completedAt = data.completedAt;
408
+ await db.update(commands).set(updateData).where(eq(commands.id, id));
409
+ return this.findCommand(id);
410
+ },
411
+ async getPendingCommands(deviceId) {
412
+ return this.listCommands({
413
+ deviceId,
414
+ status: ["pending", "sent"]
415
+ });
416
+ },
417
+ // ============================================
418
+ // Event Methods
419
+ // ============================================
420
+ async createEvent(data) {
421
+ const id = generateId();
422
+ const now = /* @__PURE__ */ new Date();
423
+ const eventData = {
424
+ id,
425
+ deviceId: data.deviceId,
426
+ type: data.type,
427
+ payload: data.payload,
428
+ createdAt: now
429
+ };
430
+ await db.insert(events).values(eventData);
431
+ return {
432
+ ...eventData,
433
+ createdAt: now
434
+ };
435
+ },
436
+ async listEvents(filter) {
437
+ let query = db.select().from(events);
438
+ const conditions = [];
439
+ if (filter?.deviceId) {
440
+ conditions.push(eq(events.deviceId, filter.deviceId));
441
+ }
442
+ if (filter?.type) {
443
+ if (Array.isArray(filter.type)) {
444
+ conditions.push(inArray(events.type, filter.type));
445
+ } else {
446
+ conditions.push(eq(events.type, filter.type));
447
+ }
448
+ }
449
+ if (conditions.length > 0) {
450
+ query = query.where(and(...conditions));
451
+ }
452
+ const limit = filter?.limit ?? 100;
453
+ const offset = filter?.offset ?? 0;
454
+ const result = await query.orderBy(desc(events.createdAt)).limit(limit).offset(offset);
455
+ return result.map(toEvent);
456
+ },
457
+ // ============================================
458
+ // Group Methods
459
+ // ============================================
460
+ async findGroup(id) {
461
+ const result = await db.select().from(groups).where(eq(groups.id, id)).limit(1);
462
+ return result[0] ? toGroup(result[0]) : null;
463
+ },
464
+ async listGroups() {
465
+ const result = await db.select().from(groups).orderBy(groups.name);
466
+ return result.map(toGroup);
467
+ },
468
+ async createGroup(data) {
469
+ const id = generateId();
470
+ const now = /* @__PURE__ */ new Date();
471
+ const groupData = {
472
+ id,
473
+ name: data.name,
474
+ description: data.description ?? null,
475
+ policyId: data.policyId ?? null,
476
+ parentId: data.parentId ?? null,
477
+ metadata: data.metadata ?? null,
478
+ createdAt: now,
479
+ updatedAt: now
480
+ };
481
+ await db.insert(groups).values(groupData);
482
+ return this.findGroup(id);
483
+ },
484
+ async updateGroup(id, data) {
485
+ const updateData = {
486
+ updatedAt: /* @__PURE__ */ new Date()
487
+ };
488
+ if (data.name !== void 0) updateData.name = data.name;
489
+ if (data.description !== void 0)
490
+ updateData.description = data.description;
491
+ if (data.policyId !== void 0) updateData.policyId = data.policyId;
492
+ if (data.parentId !== void 0) updateData.parentId = data.parentId;
493
+ if (data.metadata !== void 0) updateData.metadata = data.metadata;
494
+ await db.update(groups).set(updateData).where(eq(groups.id, id));
495
+ return this.findGroup(id);
496
+ },
497
+ async deleteGroup(id) {
498
+ await db.delete(groups).where(eq(groups.id, id));
499
+ },
500
+ async listDevicesInGroup(groupId) {
501
+ const result = await db.select({ device: devices }).from(deviceGroups).innerJoin(devices, eq(deviceGroups.deviceId, devices.id)).where(eq(deviceGroups.groupId, groupId));
502
+ return result.map(
503
+ (r) => toDevice(r.device)
504
+ );
505
+ },
506
+ async addDeviceToGroup(deviceId, groupId) {
507
+ await db.insert(deviceGroups).values({
508
+ deviceId,
509
+ groupId,
510
+ createdAt: /* @__PURE__ */ new Date()
511
+ });
512
+ },
513
+ async removeDeviceFromGroup(deviceId, groupId) {
514
+ await db.delete(deviceGroups).where(
515
+ and(
516
+ eq(deviceGroups.deviceId, deviceId),
517
+ eq(deviceGroups.groupId, groupId)
518
+ )
519
+ );
520
+ },
521
+ async getDeviceGroups(deviceId) {
522
+ const result = await db.select({ group: groups }).from(deviceGroups).innerJoin(groups, eq(deviceGroups.groupId, groups.id)).where(eq(deviceGroups.deviceId, deviceId));
523
+ return result.map(
524
+ (r) => toGroup(r.group)
525
+ );
526
+ },
527
+ // ============================================
528
+ // Push Token Methods
529
+ // ============================================
530
+ async findPushToken(deviceId, provider) {
531
+ const result = await db.select().from(pushTokens).where(
532
+ and(
533
+ eq(pushTokens.deviceId, deviceId),
534
+ eq(pushTokens.provider, provider)
535
+ )
536
+ ).limit(1);
537
+ return result[0] ? toPushToken(result[0]) : null;
538
+ },
539
+ async upsertPushToken(data) {
540
+ const existing = await this.findPushToken(data.deviceId, data.provider);
541
+ const now = /* @__PURE__ */ new Date();
542
+ if (existing) {
543
+ await db.update(pushTokens).set({
544
+ token: data.token,
545
+ isActive: true,
546
+ updatedAt: now
547
+ }).where(eq(pushTokens.id, existing.id));
548
+ return this.findPushToken(data.deviceId, data.provider);
549
+ }
550
+ const id = generateId();
551
+ await db.insert(pushTokens).values({
552
+ id,
553
+ deviceId: data.deviceId,
554
+ provider: data.provider,
555
+ token: data.token,
556
+ isActive: true,
557
+ createdAt: now,
558
+ updatedAt: now
559
+ });
560
+ return this.findPushToken(data.deviceId, data.provider);
561
+ },
562
+ async deletePushToken(deviceId, provider) {
563
+ if (provider) {
564
+ await db.delete(pushTokens).where(
565
+ and(
566
+ eq(pushTokens.deviceId, deviceId),
567
+ eq(pushTokens.provider, provider)
568
+ )
569
+ );
570
+ } else {
571
+ await db.delete(pushTokens).where(eq(pushTokens.deviceId, deviceId));
572
+ }
573
+ },
574
+ // ============================================
575
+ // Transaction Support
576
+ // ============================================
577
+ async transaction(fn) {
578
+ return db.transaction(async () => {
579
+ return fn();
580
+ });
581
+ }
582
+ };
583
+ }
584
+ export {
585
+ DEFAULT_TABLE_PREFIX,
586
+ drizzleAdapter
587
+ };
588
+ //# sourceMappingURL=index.js.map