@kaeawc/auto-mobile 0.0.49 → 0.0.51

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,4 +1,4 @@
1
- import type { Kysely } from "kysely";
1
+ import { sql, type Kysely } from "kysely";
2
2
 
3
3
  /**
4
4
  * Session-scope the video recording archive (issue #4752).
@@ -12,10 +12,17 @@ import type { Kysely } from "kysely";
12
12
  * existing archive.
13
13
  */
14
14
  export async function up(db: Kysely<unknown>): Promise<void> {
15
- await db.schema
16
- .alterTable("video_recordings")
17
- .addColumn("owner_session_uuid", "text")
18
- .execute();
15
+ const existingColumn = await sql<{ name: string }>`
16
+ SELECT name FROM pragma_table_info('video_recordings')
17
+ WHERE name = 'owner_session_uuid'
18
+ `.execute(db);
19
+
20
+ if (existingColumn.rows.length === 0) {
21
+ await db.schema
22
+ .alterTable("video_recordings")
23
+ .addColumn("owner_session_uuid", "text")
24
+ .execute();
25
+ }
19
26
 
20
27
  await db.schema
21
28
  .createIndex("idx_video_recordings_owner_session")
@@ -0,0 +1,159 @@
1
+ import { type Kysely, sql } from "kysely";
2
+
3
+ /**
4
+ * Phase 1 of the (app, build) navigation model (#4837, issue #4984).
5
+ *
6
+ * Adds a build-key provenance dimension to the app-level union navigation graph.
7
+ * The union tables (navigation_apps / navigation_nodes / navigation_edges) are
8
+ * left untouched — the build key is provenance ON each node/edge, recorded as
9
+ * separate observation rows, NOT a separate-graph partition. A node/edge can have
10
+ * many observation records (reached by many builds/devices/sessions).
11
+ *
12
+ * Backward-compat (AC4): existing single-build rows are backfilled to a DEFAULT
13
+ * build key per app (version_code=0, content_hash='') with non-null legacy
14
+ * sentinels for device_id/session_uuid, so today's behavior falls out as the
15
+ * degenerate one-build case with no data loss.
16
+ */
17
+ export async function up(db: Kysely<unknown>): Promise<void> {
18
+ // Build-key dimension: (packageId=app_id, versionCode, contentHash). Normalized
19
+ // so observation rows carry a cheap int reference instead of repeating strings.
20
+ await db.schema
21
+ .createTable("navigation_build_keys")
22
+ .ifNotExists()
23
+ .addColumn("id", "integer", col => col.primaryKey().autoIncrement())
24
+ .addColumn("app_id", "text", col =>
25
+ col.notNull().references("navigation_apps.app_id").onDelete("cascade")
26
+ )
27
+ .addColumn("version_code", "integer", col => col.notNull())
28
+ .addColumn("content_hash", "text", col => col.notNull())
29
+ .addColumn("created_at", "text", col => col.notNull().defaultTo(sql`(datetime('now'))`))
30
+ .execute();
31
+
32
+ await db.schema
33
+ .createIndex("idx_navigation_build_keys_unique")
34
+ .ifNotExists()
35
+ .on("navigation_build_keys")
36
+ .columns(["app_id", "version_code", "content_hash"])
37
+ .unique()
38
+ .execute();
39
+
40
+ // Per-node observation records: {buildKey, deviceId, sessionUuid, firstSeen, lastSeen}.
41
+ await db.schema
42
+ .createTable("navigation_node_observations")
43
+ .ifNotExists()
44
+ .addColumn("id", "integer", col => col.primaryKey().autoIncrement())
45
+ .addColumn("node_id", "integer", col =>
46
+ col.notNull().references("navigation_nodes.id").onDelete("cascade")
47
+ )
48
+ .addColumn("build_key_id", "integer", col =>
49
+ col.notNull().references("navigation_build_keys.id").onDelete("cascade")
50
+ )
51
+ .addColumn("device_id", "text", col => col.notNull())
52
+ .addColumn("session_uuid", "text", col => col.notNull())
53
+ .addColumn("first_seen_at", "integer", col => col.notNull())
54
+ .addColumn("last_seen_at", "integer", col => col.notNull())
55
+ .addColumn("created_at", "text", col => col.notNull().defaultTo(sql`(datetime('now'))`))
56
+ .execute();
57
+
58
+ await db.schema
59
+ .createIndex("idx_navigation_node_observations_unique")
60
+ .ifNotExists()
61
+ .on("navigation_node_observations")
62
+ .columns(["node_id", "build_key_id", "device_id", "session_uuid"])
63
+ .unique()
64
+ .execute();
65
+
66
+ await db.schema
67
+ .createIndex("idx_navigation_node_observations_build")
68
+ .ifNotExists()
69
+ .on("navigation_node_observations")
70
+ .column("build_key_id")
71
+ .execute();
72
+
73
+ await db.schema
74
+ .createIndex("idx_navigation_node_observations_device")
75
+ .ifNotExists()
76
+ .on("navigation_node_observations")
77
+ .column("device_id")
78
+ .execute();
79
+
80
+ // Per-edge observation records (symmetric to nodes).
81
+ await db.schema
82
+ .createTable("navigation_edge_observations")
83
+ .ifNotExists()
84
+ .addColumn("id", "integer", col => col.primaryKey().autoIncrement())
85
+ .addColumn("edge_id", "integer", col =>
86
+ col.notNull().references("navigation_edges.id").onDelete("cascade")
87
+ )
88
+ .addColumn("build_key_id", "integer", col =>
89
+ col.notNull().references("navigation_build_keys.id").onDelete("cascade")
90
+ )
91
+ .addColumn("device_id", "text", col => col.notNull())
92
+ .addColumn("session_uuid", "text", col => col.notNull())
93
+ .addColumn("first_seen_at", "integer", col => col.notNull())
94
+ .addColumn("last_seen_at", "integer", col => col.notNull())
95
+ .addColumn("created_at", "text", col => col.notNull().defaultTo(sql`(datetime('now'))`))
96
+ .execute();
97
+
98
+ await db.schema
99
+ .createIndex("idx_navigation_edge_observations_unique")
100
+ .ifNotExists()
101
+ .on("navigation_edge_observations")
102
+ .columns(["edge_id", "build_key_id", "device_id", "session_uuid"])
103
+ .unique()
104
+ .execute();
105
+
106
+ await db.schema
107
+ .createIndex("idx_navigation_edge_observations_build")
108
+ .ifNotExists()
109
+ .on("navigation_edge_observations")
110
+ .column("build_key_id")
111
+ .execute();
112
+
113
+ await db.schema
114
+ .createIndex("idx_navigation_edge_observations_device")
115
+ .ifNotExists()
116
+ .on("navigation_edge_observations")
117
+ .column("device_id")
118
+ .execute();
119
+
120
+ // --- Backfill (AC4): one DEFAULT build key per app, one observation per row. ---
121
+ // Kysely's SqliteAdapter reports supportsTransactionalDdl = false, so this up()
122
+ // is NOT wrapped in a transaction. If a later statement fails after an earlier
123
+ // insert, the migration is not recorded and reruns from the top — so every
124
+ // backfill uses INSERT OR IGNORE (safe against its UNIQUE index) to stay
125
+ // idempotent and retry-safe rather than wedging on a duplicate-key violation.
126
+ await sql`
127
+ INSERT OR IGNORE INTO navigation_build_keys (app_id, version_code, content_hash)
128
+ SELECT app_id, 0, '' FROM navigation_apps
129
+ `.execute(db);
130
+
131
+ // Normalize with min/max: legacy getOrCreateNode replaced last_seen_at
132
+ // unconditionally, so out-of-order commits could leave first_seen_at > last_seen_at
133
+ // on existing rows. Copying those verbatim would carry the inversion into the
134
+ // observation window (the runtime upserts only keep FUTURE writes monotonic).
135
+ await sql`
136
+ INSERT OR IGNORE INTO navigation_node_observations
137
+ (node_id, build_key_id, device_id, session_uuid, first_seen_at, last_seen_at)
138
+ SELECT n.id, bk.id, 'legacy', 'legacy',
139
+ min(n.first_seen_at, n.last_seen_at), max(n.first_seen_at, n.last_seen_at)
140
+ FROM navigation_nodes n
141
+ JOIN navigation_build_keys bk
142
+ ON bk.app_id = n.app_id AND bk.version_code = 0 AND bk.content_hash = ''
143
+ `.execute(db);
144
+
145
+ await sql`
146
+ INSERT OR IGNORE INTO navigation_edge_observations
147
+ (edge_id, build_key_id, device_id, session_uuid, first_seen_at, last_seen_at)
148
+ SELECT e.id, bk.id, 'legacy', 'legacy', e.timestamp, e.timestamp
149
+ FROM navigation_edges e
150
+ JOIN navigation_build_keys bk
151
+ ON bk.app_id = e.app_id AND bk.version_code = 0 AND bk.content_hash = ''
152
+ `.execute(db);
153
+ }
154
+
155
+ export async function down(db: Kysely<unknown>): Promise<void> {
156
+ await db.schema.dropTable("navigation_edge_observations").execute();
157
+ await db.schema.dropTable("navigation_node_observations").execute();
158
+ await db.schema.dropTable("navigation_build_keys").execute();
159
+ }