@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/src/mysql.ts ADDED
@@ -0,0 +1,24 @@
1
+ /**
2
+ * OpenMDM Drizzle Schema for MySQL
3
+ *
4
+ * Ready-to-use Drizzle table definitions for MySQL databases.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * import { mdmDevices, mdmPolicies } from '@openmdm/drizzle-adapter/mysql';
9
+ * import { drizzle } from 'drizzle-orm/mysql2';
10
+ *
11
+ * const db = drizzle(connection, { schema: { mdmDevices, mdmPolicies, ... } });
12
+ * ```
13
+ */
14
+
15
+ // MySQL schema implementation
16
+ // TODO: Implement MySQL-specific schema
17
+ // For now, users should use the PostgreSQL schema as reference
18
+
19
+ export const placeholder = 'MySQL schema coming soon';
20
+
21
+ throw new Error(
22
+ '@openmdm/drizzle-adapter/mysql is not yet implemented. ' +
23
+ 'Please use @openmdm/drizzle-adapter/postgres for now, or contribute the MySQL schema!'
24
+ );
@@ -0,0 +1,487 @@
1
+ /**
2
+ * OpenMDM Drizzle Schema for PostgreSQL
3
+ *
4
+ * Ready-to-use Drizzle table definitions for PostgreSQL databases.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * import { mdmDevices, mdmPolicies } from '@openmdm/drizzle-adapter/postgres';
9
+ * import { drizzle } from 'drizzle-orm/node-postgres';
10
+ *
11
+ * const db = drizzle(pool, { schema: { mdmDevices, mdmPolicies, ... } });
12
+ * ```
13
+ */
14
+
15
+ import {
16
+ pgTable,
17
+ pgEnum,
18
+ text,
19
+ varchar,
20
+ boolean,
21
+ integer,
22
+ bigint,
23
+ timestamp,
24
+ json,
25
+ index,
26
+ uniqueIndex,
27
+ primaryKey,
28
+ } from 'drizzle-orm/pg-core';
29
+ import { relations } from 'drizzle-orm';
30
+
31
+ // ============================================
32
+ // Enums
33
+ // ============================================
34
+
35
+ export const deviceStatusEnum = pgEnum('mdm_device_status', [
36
+ 'pending',
37
+ 'enrolled',
38
+ 'unenrolled',
39
+ 'blocked',
40
+ ]);
41
+
42
+ export const commandStatusEnum = pgEnum('mdm_command_status', [
43
+ 'pending',
44
+ 'sent',
45
+ 'acknowledged',
46
+ 'completed',
47
+ 'failed',
48
+ 'cancelled',
49
+ ]);
50
+
51
+ export const pushProviderEnum = pgEnum('mdm_push_provider', [
52
+ 'fcm',
53
+ 'mqtt',
54
+ 'websocket',
55
+ ]);
56
+
57
+ export const deployTargetTypeEnum = pgEnum('mdm_deploy_target_type', [
58
+ 'policy',
59
+ 'group',
60
+ ]);
61
+
62
+ export const deployActionEnum = pgEnum('mdm_deploy_action', [
63
+ 'install',
64
+ 'update',
65
+ 'uninstall',
66
+ ]);
67
+
68
+ // ============================================
69
+ // Devices Table
70
+ // ============================================
71
+
72
+ export const mdmDevices = pgTable(
73
+ 'mdm_devices',
74
+ {
75
+ id: varchar('id', { length: 36 }).primaryKey(),
76
+ externalId: varchar('external_id', { length: 255 }),
77
+ enrollmentId: varchar('enrollment_id', { length: 255 }).notNull().unique(),
78
+ status: deviceStatusEnum('status').notNull().default('pending'),
79
+
80
+ // Device Info
81
+ model: varchar('model', { length: 255 }),
82
+ manufacturer: varchar('manufacturer', { length: 255 }),
83
+ osVersion: varchar('os_version', { length: 50 }),
84
+ serialNumber: varchar('serial_number', { length: 255 }),
85
+ imei: varchar('imei', { length: 50 }),
86
+ macAddress: varchar('mac_address', { length: 50 }),
87
+ androidId: varchar('android_id', { length: 100 }),
88
+
89
+ // MDM State
90
+ policyId: varchar('policy_id', { length: 36 }).references(
91
+ () => mdmPolicies.id,
92
+ { onDelete: 'set null' }
93
+ ),
94
+ lastHeartbeat: timestamp('last_heartbeat', { withTimezone: true }),
95
+ lastSync: timestamp('last_sync', { withTimezone: true }),
96
+
97
+ // Telemetry
98
+ batteryLevel: integer('battery_level'),
99
+ storageUsed: bigint('storage_used', { mode: 'number' }),
100
+ storageTotal: bigint('storage_total', { mode: 'number' }),
101
+ latitude: varchar('latitude', { length: 50 }),
102
+ longitude: varchar('longitude', { length: 50 }),
103
+ locationTimestamp: timestamp('location_timestamp', { withTimezone: true }),
104
+
105
+ // JSON fields
106
+ installedApps: json('installed_apps').$type<
107
+ Array<{ packageName: string; version: string; versionCode?: number }>
108
+ >(),
109
+ tags: json('tags').$type<Record<string, string>>(),
110
+ metadata: json('metadata').$type<Record<string, unknown>>(),
111
+
112
+ // Timestamps
113
+ createdAt: timestamp('created_at', { withTimezone: true })
114
+ .notNull()
115
+ .defaultNow(),
116
+ updatedAt: timestamp('updated_at', { withTimezone: true })
117
+ .notNull()
118
+ .defaultNow(),
119
+ },
120
+ (table) => [
121
+ index('mdm_devices_status_idx').on(table.status),
122
+ index('mdm_devices_policy_id_idx').on(table.policyId),
123
+ index('mdm_devices_last_heartbeat_idx').on(table.lastHeartbeat),
124
+ index('mdm_devices_mac_address_idx').on(table.macAddress),
125
+ index('mdm_devices_serial_number_idx').on(table.serialNumber),
126
+ ]
127
+ );
128
+
129
+ // ============================================
130
+ // Policies Table
131
+ // ============================================
132
+
133
+ export const mdmPolicies = pgTable(
134
+ 'mdm_policies',
135
+ {
136
+ id: varchar('id', { length: 36 }).primaryKey(),
137
+ name: varchar('name', { length: 255 }).notNull(),
138
+ description: text('description'),
139
+ isDefault: boolean('is_default').notNull().default(false),
140
+ settings: json('settings').notNull().$type<Record<string, unknown>>(),
141
+ createdAt: timestamp('created_at', { withTimezone: true })
142
+ .notNull()
143
+ .defaultNow(),
144
+ updatedAt: timestamp('updated_at', { withTimezone: true })
145
+ .notNull()
146
+ .defaultNow(),
147
+ },
148
+ (table) => [
149
+ index('mdm_policies_name_idx').on(table.name),
150
+ index('mdm_policies_is_default_idx').on(table.isDefault),
151
+ ]
152
+ );
153
+
154
+ // ============================================
155
+ // Applications Table
156
+ // ============================================
157
+
158
+ export const mdmApplications = pgTable(
159
+ 'mdm_applications',
160
+ {
161
+ id: varchar('id', { length: 36 }).primaryKey(),
162
+ name: varchar('name', { length: 255 }).notNull(),
163
+ packageName: varchar('package_name', { length: 255 }).notNull(),
164
+ version: varchar('version', { length: 50 }).notNull(),
165
+ versionCode: integer('version_code').notNull(),
166
+ url: text('url').notNull(),
167
+ hash: varchar('hash', { length: 64 }), // SHA-256
168
+ size: bigint('size', { mode: 'number' }),
169
+ minSdkVersion: integer('min_sdk_version'),
170
+
171
+ // Deployment settings
172
+ showIcon: boolean('show_icon').notNull().default(true),
173
+ runAfterInstall: boolean('run_after_install').notNull().default(false),
174
+ runAtBoot: boolean('run_at_boot').notNull().default(false),
175
+ isSystem: boolean('is_system').notNull().default(false),
176
+
177
+ // State
178
+ isActive: boolean('is_active').notNull().default(true),
179
+
180
+ // Metadata
181
+ metadata: json('metadata').$type<Record<string, unknown>>(),
182
+ createdAt: timestamp('created_at', { withTimezone: true })
183
+ .notNull()
184
+ .defaultNow(),
185
+ updatedAt: timestamp('updated_at', { withTimezone: true })
186
+ .notNull()
187
+ .defaultNow(),
188
+ },
189
+ (table) => [
190
+ index('mdm_applications_package_name_idx').on(table.packageName),
191
+ uniqueIndex('mdm_applications_package_version_idx').on(
192
+ table.packageName,
193
+ table.version
194
+ ),
195
+ index('mdm_applications_is_active_idx').on(table.isActive),
196
+ ]
197
+ );
198
+
199
+ // ============================================
200
+ // Commands Table
201
+ // ============================================
202
+
203
+ export const mdmCommands = pgTable(
204
+ 'mdm_commands',
205
+ {
206
+ id: varchar('id', { length: 36 }).primaryKey(),
207
+ deviceId: varchar('device_id', { length: 36 })
208
+ .notNull()
209
+ .references(() => mdmDevices.id, { onDelete: 'cascade' }),
210
+ type: varchar('type', { length: 50 }).notNull(),
211
+ payload: json('payload').$type<Record<string, unknown>>(),
212
+ status: commandStatusEnum('status').notNull().default('pending'),
213
+ result: json('result').$type<{
214
+ success: boolean;
215
+ message?: string;
216
+ data?: unknown;
217
+ }>(),
218
+ error: text('error'),
219
+ createdAt: timestamp('created_at', { withTimezone: true })
220
+ .notNull()
221
+ .defaultNow(),
222
+ sentAt: timestamp('sent_at', { withTimezone: true }),
223
+ acknowledgedAt: timestamp('acknowledged_at', { withTimezone: true }),
224
+ completedAt: timestamp('completed_at', { withTimezone: true }),
225
+ },
226
+ (table) => [
227
+ index('mdm_commands_device_id_idx').on(table.deviceId),
228
+ index('mdm_commands_status_idx').on(table.status),
229
+ index('mdm_commands_device_status_idx').on(table.deviceId, table.status),
230
+ index('mdm_commands_created_at_idx').on(table.createdAt),
231
+ ]
232
+ );
233
+
234
+ // ============================================
235
+ // Events Table
236
+ // ============================================
237
+
238
+ export const mdmEvents = pgTable(
239
+ 'mdm_events',
240
+ {
241
+ id: varchar('id', { length: 36 }).primaryKey(),
242
+ deviceId: varchar('device_id', { length: 36 })
243
+ .notNull()
244
+ .references(() => mdmDevices.id, { onDelete: 'cascade' }),
245
+ type: varchar('type', { length: 100 }).notNull(),
246
+ payload: json('payload').notNull().$type<Record<string, unknown>>(),
247
+ createdAt: timestamp('created_at', { withTimezone: true })
248
+ .notNull()
249
+ .defaultNow(),
250
+ },
251
+ (table) => [
252
+ index('mdm_events_device_id_idx').on(table.deviceId),
253
+ index('mdm_events_type_idx').on(table.type),
254
+ index('mdm_events_device_type_idx').on(table.deviceId, table.type),
255
+ index('mdm_events_created_at_idx').on(table.createdAt),
256
+ ]
257
+ );
258
+
259
+ // ============================================
260
+ // Groups Table
261
+ // ============================================
262
+
263
+ export const mdmGroups = pgTable(
264
+ 'mdm_groups',
265
+ {
266
+ id: varchar('id', { length: 36 }).primaryKey(),
267
+ name: varchar('name', { length: 255 }).notNull(),
268
+ description: text('description'),
269
+ policyId: varchar('policy_id', { length: 36 }).references(
270
+ () => mdmPolicies.id,
271
+ { onDelete: 'set null' }
272
+ ),
273
+ parentId: varchar('parent_id', { length: 36 }),
274
+ metadata: json('metadata').$type<Record<string, unknown>>(),
275
+ createdAt: timestamp('created_at', { withTimezone: true })
276
+ .notNull()
277
+ .defaultNow(),
278
+ updatedAt: timestamp('updated_at', { withTimezone: true })
279
+ .notNull()
280
+ .defaultNow(),
281
+ },
282
+ (table) => [
283
+ index('mdm_groups_name_idx').on(table.name),
284
+ index('mdm_groups_policy_id_idx').on(table.policyId),
285
+ index('mdm_groups_parent_id_idx').on(table.parentId),
286
+ ]
287
+ );
288
+
289
+ // ============================================
290
+ // Device Groups (Many-to-Many)
291
+ // ============================================
292
+
293
+ export const mdmDeviceGroups = pgTable(
294
+ 'mdm_device_groups',
295
+ {
296
+ deviceId: varchar('device_id', { length: 36 })
297
+ .notNull()
298
+ .references(() => mdmDevices.id, { onDelete: 'cascade' }),
299
+ groupId: varchar('group_id', { length: 36 })
300
+ .notNull()
301
+ .references(() => mdmGroups.id, { onDelete: 'cascade' }),
302
+ createdAt: timestamp('created_at', { withTimezone: true })
303
+ .notNull()
304
+ .defaultNow(),
305
+ },
306
+ (table) => [
307
+ primaryKey({ columns: [table.deviceId, table.groupId] }),
308
+ index('mdm_device_groups_group_id_idx').on(table.groupId),
309
+ ]
310
+ );
311
+
312
+ // ============================================
313
+ // Push Tokens Table
314
+ // ============================================
315
+
316
+ export const mdmPushTokens = pgTable(
317
+ 'mdm_push_tokens',
318
+ {
319
+ id: varchar('id', { length: 36 }).primaryKey(),
320
+ deviceId: varchar('device_id', { length: 36 })
321
+ .notNull()
322
+ .references(() => mdmDevices.id, { onDelete: 'cascade' }),
323
+ provider: pushProviderEnum('provider').notNull(),
324
+ token: text('token').notNull(),
325
+ isActive: boolean('is_active').notNull().default(true),
326
+ createdAt: timestamp('created_at', { withTimezone: true })
327
+ .notNull()
328
+ .defaultNow(),
329
+ updatedAt: timestamp('updated_at', { withTimezone: true })
330
+ .notNull()
331
+ .defaultNow(),
332
+ },
333
+ (table) => [
334
+ index('mdm_push_tokens_device_id_idx').on(table.deviceId),
335
+ uniqueIndex('mdm_push_tokens_provider_token_idx').on(
336
+ table.provider,
337
+ table.token
338
+ ),
339
+ index('mdm_push_tokens_is_active_idx').on(table.isActive),
340
+ ]
341
+ );
342
+
343
+ // ============================================
344
+ // App Deployments Table
345
+ // ============================================
346
+
347
+ export const mdmAppDeployments = pgTable(
348
+ 'mdm_app_deployments',
349
+ {
350
+ id: varchar('id', { length: 36 }).primaryKey(),
351
+ applicationId: varchar('application_id', { length: 36 })
352
+ .notNull()
353
+ .references(() => mdmApplications.id, { onDelete: 'cascade' }),
354
+ targetType: deployTargetTypeEnum('target_type').notNull(),
355
+ targetId: varchar('target_id', { length: 36 }).notNull(),
356
+ action: deployActionEnum('action').notNull().default('install'),
357
+ isRequired: boolean('is_required').notNull().default(false),
358
+ createdAt: timestamp('created_at', { withTimezone: true })
359
+ .notNull()
360
+ .defaultNow(),
361
+ },
362
+ (table) => [
363
+ index('mdm_app_deployments_application_id_idx').on(table.applicationId),
364
+ index('mdm_app_deployments_target_idx').on(table.targetType, table.targetId),
365
+ ]
366
+ );
367
+
368
+ // ============================================
369
+ // Relations
370
+ // ============================================
371
+
372
+ export const mdmDevicesRelations = relations(mdmDevices, ({ one, many }) => ({
373
+ policy: one(mdmPolicies, {
374
+ fields: [mdmDevices.policyId],
375
+ references: [mdmPolicies.id],
376
+ }),
377
+ commands: many(mdmCommands),
378
+ events: many(mdmEvents),
379
+ pushTokens: many(mdmPushTokens),
380
+ deviceGroups: many(mdmDeviceGroups),
381
+ }));
382
+
383
+ export const mdmPoliciesRelations = relations(mdmPolicies, ({ many }) => ({
384
+ devices: many(mdmDevices),
385
+ groups: many(mdmGroups),
386
+ }));
387
+
388
+ export const mdmCommandsRelations = relations(mdmCommands, ({ one }) => ({
389
+ device: one(mdmDevices, {
390
+ fields: [mdmCommands.deviceId],
391
+ references: [mdmDevices.id],
392
+ }),
393
+ }));
394
+
395
+ export const mdmEventsRelations = relations(mdmEvents, ({ one }) => ({
396
+ device: one(mdmDevices, {
397
+ fields: [mdmEvents.deviceId],
398
+ references: [mdmDevices.id],
399
+ }),
400
+ }));
401
+
402
+ export const mdmGroupsRelations = relations(mdmGroups, ({ one, many }) => ({
403
+ policy: one(mdmPolicies, {
404
+ fields: [mdmGroups.policyId],
405
+ references: [mdmPolicies.id],
406
+ }),
407
+ parent: one(mdmGroups, {
408
+ fields: [mdmGroups.parentId],
409
+ references: [mdmGroups.id],
410
+ relationName: 'parentChild',
411
+ }),
412
+ children: many(mdmGroups, { relationName: 'parentChild' }),
413
+ deviceGroups: many(mdmDeviceGroups),
414
+ }));
415
+
416
+ export const mdmDeviceGroupsRelations = relations(
417
+ mdmDeviceGroups,
418
+ ({ one }) => ({
419
+ device: one(mdmDevices, {
420
+ fields: [mdmDeviceGroups.deviceId],
421
+ references: [mdmDevices.id],
422
+ }),
423
+ group: one(mdmGroups, {
424
+ fields: [mdmDeviceGroups.groupId],
425
+ references: [mdmGroups.id],
426
+ }),
427
+ })
428
+ );
429
+
430
+ export const mdmPushTokensRelations = relations(mdmPushTokens, ({ one }) => ({
431
+ device: one(mdmDevices, {
432
+ fields: [mdmPushTokens.deviceId],
433
+ references: [mdmDevices.id],
434
+ }),
435
+ }));
436
+
437
+ export const mdmApplicationsRelations = relations(
438
+ mdmApplications,
439
+ ({ many }) => ({
440
+ deployments: many(mdmAppDeployments),
441
+ })
442
+ );
443
+
444
+ export const mdmAppDeploymentsRelations = relations(
445
+ mdmAppDeployments,
446
+ ({ one }) => ({
447
+ application: one(mdmApplications, {
448
+ fields: [mdmAppDeployments.applicationId],
449
+ references: [mdmApplications.id],
450
+ }),
451
+ })
452
+ );
453
+
454
+ // ============================================
455
+ // Export all tables for easy schema setup
456
+ // ============================================
457
+
458
+ export const mdmSchema = {
459
+ // Tables
460
+ mdmDevices,
461
+ mdmPolicies,
462
+ mdmApplications,
463
+ mdmCommands,
464
+ mdmEvents,
465
+ mdmGroups,
466
+ mdmDeviceGroups,
467
+ mdmPushTokens,
468
+ mdmAppDeployments,
469
+ // Enums
470
+ deviceStatusEnum,
471
+ commandStatusEnum,
472
+ pushProviderEnum,
473
+ deployTargetTypeEnum,
474
+ deployActionEnum,
475
+ // Relations
476
+ mdmDevicesRelations,
477
+ mdmPoliciesRelations,
478
+ mdmCommandsRelations,
479
+ mdmEventsRelations,
480
+ mdmGroupsRelations,
481
+ mdmDeviceGroupsRelations,
482
+ mdmPushTokensRelations,
483
+ mdmApplicationsRelations,
484
+ mdmAppDeploymentsRelations,
485
+ };
486
+
487
+ export type MDMSchema = typeof mdmSchema;
package/src/schema.ts ADDED
@@ -0,0 +1,27 @@
1
+ /**
2
+ * OpenMDM Drizzle Schema
3
+ *
4
+ * This file exports the schema definition for use with schema generators.
5
+ * For ready-to-use Drizzle tables, import from:
6
+ * - @openmdm/drizzle-adapter/postgres
7
+ * - @openmdm/drizzle-adapter/mysql
8
+ * - @openmdm/drizzle-adapter/sqlite
9
+ */
10
+
11
+ export { mdmSchema, getTableNames, getColumnNames } from '@openmdm/core/schema';
12
+
13
+ /**
14
+ * Table prefix for MDM tables.
15
+ * Can be customized via the adapter options.
16
+ */
17
+ export const DEFAULT_TABLE_PREFIX = 'mdm_';
18
+
19
+ /**
20
+ * Schema options for customizing table generation
21
+ */
22
+ export interface SchemaOptions {
23
+ /** Prefix for all MDM tables (default: 'mdm_') */
24
+ tablePrefix?: string;
25
+ /** Custom schema name (PostgreSQL only) */
26
+ schema?: string;
27
+ }
package/src/sqlite.ts ADDED
@@ -0,0 +1,24 @@
1
+ /**
2
+ * OpenMDM Drizzle Schema for SQLite
3
+ *
4
+ * Ready-to-use Drizzle table definitions for SQLite databases.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * import { mdmDevices, mdmPolicies } from '@openmdm/drizzle-adapter/sqlite';
9
+ * import { drizzle } from 'drizzle-orm/better-sqlite3';
10
+ *
11
+ * const db = drizzle(sqlite, { schema: { mdmDevices, mdmPolicies, ... } });
12
+ * ```
13
+ */
14
+
15
+ // SQLite schema implementation
16
+ // TODO: Implement SQLite-specific schema
17
+ // For now, users should use the PostgreSQL schema as reference
18
+
19
+ export const placeholder = 'SQLite schema coming soon';
20
+
21
+ throw new Error(
22
+ '@openmdm/drizzle-adapter/sqlite is not yet implemented. ' +
23
+ 'Please use @openmdm/drizzle-adapter/postgres for now, or contribute the SQLite schema!'
24
+ );