@tomorrowos/sdk 0.4.7 → 0.5.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/README.md +30 -0
- package/dist/cli.js +122 -2
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/store/factory.d.ts +20 -0
- package/dist/store/factory.d.ts.map +1 -0
- package/dist/store/factory.js +44 -0
- package/dist/store/migration.d.ts +12 -0
- package/dist/store/migration.d.ts.map +1 -0
- package/dist/store/migration.js +47 -0
- package/dist/store/postgres-store.d.ts +43 -0
- package/dist/store/postgres-store.d.ts.map +1 -0
- package/dist/store/postgres-store.js +435 -0
- package/dist/store/sqlite-store.d.ts +41 -0
- package/dist/store/sqlite-store.d.ts.map +1 -0
- package/dist/store/sqlite-store.js +367 -0
- package/dist/store/types.d.ts +22 -0
- package/dist/store/types.d.ts.map +1 -1
- package/package.json +5 -2
- package/templates/cms-starter/.env.example +10 -0
- package/templates/cms-starter/package.json +3 -2
- package/templates/cms-starter/server.ts +8 -4
- package/templates/cms-starter/template/databaseInterface/README.md +14 -0
- package/templates/cms-starter/template/databaseInterface/sqlite-interface.ts +10 -0
- package/templates/cms-starter/template/databaseInterface/supabase-interface.ts +17 -0
package/README.md
CHANGED
|
@@ -38,6 +38,36 @@ npm install
|
|
|
38
38
|
npm run dev
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
+
`tomorrowos init` creates `data/tomorrowos.db` with the TomorrowOS SQLite
|
|
42
|
+
schema. The starter server uses that SQLite database by default, so pairings,
|
|
43
|
+
playlists, and device assignments survive normal server restarts.
|
|
44
|
+
|
|
45
|
+
To switch to Supabase/Postgres, edit `.env`:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
TOMORROWOS_STORE=supabase
|
|
49
|
+
DATABASE_URL=postgresql://...
|
|
50
|
+
DATABASE_SSL=true
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
For throwaway demos/tests, set `TOMORROWOS_STORE=memory`. The generated starter
|
|
54
|
+
also includes `template/databaseInterface/` with SQLite and Supabase examples
|
|
55
|
+
for customers who want to build their own `TomorrowOSStore` implementation.
|
|
56
|
+
|
|
57
|
+
Migrate database records between supported stores:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npx tomorrowos migrate \
|
|
61
|
+
--from sqlite \
|
|
62
|
+
--from-sqlite ./data/tomorrowos.db \
|
|
63
|
+
--to supabase \
|
|
64
|
+
--to-database-url "$DATABASE_URL"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The migrate command supports `sqlite`, `postgres`, and `supabase` in either
|
|
68
|
+
direction. It migrates TomorrowOS database records only; copy `public/uploads`
|
|
69
|
+
or object-storage media separately.
|
|
70
|
+
|
|
41
71
|
Build a player (placeholder in current SDK — see `PLAYER_INSTALL.md` for platform tooling):
|
|
42
72
|
|
|
43
73
|
```bash
|
package/dist/cli.js
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
import fs from "fs";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import { fileURLToPath } from "url";
|
|
5
|
+
import { migrateTomorrowOSData } from "./store/migration.js";
|
|
6
|
+
import { PostgresStore } from "./store/postgres-store.js";
|
|
7
|
+
import { SQLiteStore } from "./store/sqlite-store.js";
|
|
5
8
|
function packageRoot() {
|
|
6
9
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
7
10
|
return path.resolve(here, "..");
|
|
@@ -32,6 +35,12 @@ function removeStarterArtifacts(destDir) {
|
|
|
32
35
|
if (fs.existsSync(modulesPath))
|
|
33
36
|
fs.rmSync(modulesPath, { recursive: true, force: true });
|
|
34
37
|
}
|
|
38
|
+
function initializeStarterSQLite(destDir) {
|
|
39
|
+
const dbPath = path.join(path.resolve(destDir), "data", "tomorrowos.db");
|
|
40
|
+
const store = new SQLiteStore(dbPath);
|
|
41
|
+
store.close();
|
|
42
|
+
return dbPath;
|
|
43
|
+
}
|
|
35
44
|
/** Align generated cms-starter package.json with the installed SDK version. */
|
|
36
45
|
function patchStarterPackageJson(destDir) {
|
|
37
46
|
const pkgPath = path.join(path.resolve(destDir), "package.json");
|
|
@@ -68,9 +77,11 @@ function copyStarter(destDir, force) {
|
|
|
68
77
|
});
|
|
69
78
|
patchStarterPackageJson(resolved);
|
|
70
79
|
removeStarterArtifacts(resolved);
|
|
80
|
+
const dbPath = initializeStarterSQLite(resolved);
|
|
71
81
|
const sdkVer = getSdkVersion();
|
|
72
82
|
console.log(`[tomorrowos] Created CMS project at ${resolved}`);
|
|
73
83
|
console.log(`[tomorrowos] @tomorrowos/sdk dependency: ^${sdkVer}`);
|
|
84
|
+
console.log(`[tomorrowos] Initialized SQLite database: ${dbPath}`);
|
|
74
85
|
console.log("Next: cd there, run npm install, then npm start");
|
|
75
86
|
}
|
|
76
87
|
function cmdBuild(argv) {
|
|
@@ -80,11 +91,93 @@ function cmdBuild(argv) {
|
|
|
80
91
|
console.log("Use your TomorrowOS player repository (assemble Web app + config), then run the platform SDK (e.g. Tizen CLI tizen package) from the build output directory.");
|
|
81
92
|
process.exitCode = 0;
|
|
82
93
|
}
|
|
94
|
+
function optionValue(argv, name) {
|
|
95
|
+
const withEquals = `${name}=`;
|
|
96
|
+
const found = argv.find((arg) => arg.startsWith(withEquals));
|
|
97
|
+
if (found)
|
|
98
|
+
return found.slice(withEquals.length);
|
|
99
|
+
const idx = argv.indexOf(name);
|
|
100
|
+
if (idx >= 0)
|
|
101
|
+
return argv[idx + 1];
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
function normalizeDriver(raw, side) {
|
|
105
|
+
const driver = String(raw || "").trim().toLowerCase();
|
|
106
|
+
if (driver === "sqlite" || driver === "postgres" || driver === "supabase") {
|
|
107
|
+
return driver;
|
|
108
|
+
}
|
|
109
|
+
throw new Error(`--${side} must be sqlite, postgres, or supabase.`);
|
|
110
|
+
}
|
|
111
|
+
function resolvePostgresSsl(raw, databaseUrl) {
|
|
112
|
+
const value = raw?.trim().toLowerCase();
|
|
113
|
+
if (value === "false" || value === "0" || value === "off")
|
|
114
|
+
return false;
|
|
115
|
+
if (value === "true" || value === "1" || value === "on") {
|
|
116
|
+
return { rejectUnauthorized: false };
|
|
117
|
+
}
|
|
118
|
+
if (/supabase\.(co|com)|sslmode=require/i.test(databaseUrl)) {
|
|
119
|
+
return { rejectUnauthorized: false };
|
|
120
|
+
}
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
function createMigrationStore(argv, side) {
|
|
124
|
+
const driver = normalizeDriver(optionValue(argv, `--${side}`), side);
|
|
125
|
+
if (driver === "sqlite") {
|
|
126
|
+
const dbPath = optionValue(argv, `--${side}-sqlite`) ??
|
|
127
|
+
optionValue(argv, `--${side}-sqlite-path`);
|
|
128
|
+
if (!dbPath) {
|
|
129
|
+
throw new Error(`--${side}=sqlite requires --${side}-sqlite ./data/tomorrowos.db`);
|
|
130
|
+
}
|
|
131
|
+
const store = new SQLiteStore(dbPath);
|
|
132
|
+
return {
|
|
133
|
+
label: `sqlite:${path.resolve(dbPath)}`,
|
|
134
|
+
store,
|
|
135
|
+
close: async () => store.close()
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
const databaseUrl = optionValue(argv, `--${side}-database-url`) ??
|
|
139
|
+
optionValue(argv, `--${side}-url`);
|
|
140
|
+
if (!databaseUrl) {
|
|
141
|
+
throw new Error(`--${side}=${driver} requires --${side}-database-url postgresql://...`);
|
|
142
|
+
}
|
|
143
|
+
const store = new PostgresStore({
|
|
144
|
+
connectionString: databaseUrl,
|
|
145
|
+
ssl: resolvePostgresSsl(optionValue(argv, `--${side}-database-ssl`), databaseUrl)
|
|
146
|
+
});
|
|
147
|
+
return {
|
|
148
|
+
label: `${driver}:DATABASE_URL`,
|
|
149
|
+
store,
|
|
150
|
+
close: async () => store.close()
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
async function cmdMigrate(argv) {
|
|
154
|
+
if (argv.includes("-h") || argv.includes("--help")) {
|
|
155
|
+
printMigrateHelp();
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const from = createMigrationStore(argv, "from");
|
|
159
|
+
const to = createMigrationStore(argv, "to");
|
|
160
|
+
try {
|
|
161
|
+
console.log(`[tomorrowos] Migrating data from ${from.label} to ${to.label}`);
|
|
162
|
+
const result = await migrateTomorrowOSData(from.store, to.store);
|
|
163
|
+
console.log("[tomorrowos] Migration complete:");
|
|
164
|
+
console.log(` pending codes: ${result.pendingCodes}`);
|
|
165
|
+
console.log(` device registry records: ${result.deviceRegistry}`);
|
|
166
|
+
console.log(` paired devices: ${result.pairedDevices}`);
|
|
167
|
+
console.log(` playlists: ${result.playlists}`);
|
|
168
|
+
console.log(` device assignments: ${result.deviceAssignments}`);
|
|
169
|
+
console.log("[tomorrowos] Note: media files in public/uploads or object storage are not copied by database migration.");
|
|
170
|
+
}
|
|
171
|
+
finally {
|
|
172
|
+
await Promise.allSettled([from.close(), to.close()]);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
83
175
|
function printHelp() {
|
|
84
176
|
console.log(`tomorrowos — TomorrowOS SDK CLI
|
|
85
177
|
|
|
86
178
|
Usage:
|
|
87
179
|
tomorrowos init [directory] Copy cms-starter template (default: my-tomorrowos-cms)
|
|
180
|
+
tomorrowos migrate [options] Migrate TomorrowOS data between supported databases
|
|
88
181
|
tomorrowos build --platform … Placeholder; player packaging lives in the player repo
|
|
89
182
|
|
|
90
183
|
Options:
|
|
@@ -93,9 +186,29 @@ Options:
|
|
|
93
186
|
Examples:
|
|
94
187
|
npx @tomorrowos/sdk init
|
|
95
188
|
npx @tomorrowos/sdk init ./my-cms
|
|
189
|
+
npx @tomorrowos/sdk migrate --from sqlite --from-sqlite ./data/tomorrowos.db --to supabase --to-database-url "$DATABASE_URL"
|
|
96
190
|
`);
|
|
97
191
|
}
|
|
98
|
-
function
|
|
192
|
+
function printMigrateHelp() {
|
|
193
|
+
console.log(`tomorrowos migrate — migrate TomorrowOS SDK data
|
|
194
|
+
|
|
195
|
+
Supported stores:
|
|
196
|
+
sqlite
|
|
197
|
+
postgres
|
|
198
|
+
supabase
|
|
199
|
+
|
|
200
|
+
Examples:
|
|
201
|
+
tomorrowos migrate --from sqlite --from-sqlite ./data/tomorrowos.db --to supabase --to-database-url "$DATABASE_URL"
|
|
202
|
+
tomorrowos migrate --from supabase --from-database-url "$DATABASE_URL" --to sqlite --to-sqlite ./data/tomorrowos.db
|
|
203
|
+
tomorrowos migrate --from postgres --from-database-url "$OLD_DATABASE_URL" --to postgres --to-database-url "$NEW_DATABASE_URL"
|
|
204
|
+
|
|
205
|
+
Notes:
|
|
206
|
+
- Migrates pairing/device/playlist/assignment database records.
|
|
207
|
+
- Does not copy media files from public/uploads or object storage.
|
|
208
|
+
- Target records with the same primary keys are overwritten/updated.
|
|
209
|
+
`);
|
|
210
|
+
}
|
|
211
|
+
async function main() {
|
|
99
212
|
const argv = process.argv.slice(2);
|
|
100
213
|
if (argv.length === 0 || argv[0] === "-h" || argv[0] === "--help") {
|
|
101
214
|
printHelp();
|
|
@@ -114,8 +227,15 @@ function main() {
|
|
|
114
227
|
cmdBuild(rest);
|
|
115
228
|
return;
|
|
116
229
|
}
|
|
230
|
+
if (cmd === "migrate") {
|
|
231
|
+
await cmdMigrate(rest);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
117
234
|
console.error(`[tomorrowos] Unknown command: ${cmd}`);
|
|
118
235
|
printHelp();
|
|
119
236
|
process.exit(1);
|
|
120
237
|
}
|
|
121
|
-
main()
|
|
238
|
+
main().catch((err) => {
|
|
239
|
+
console.error("[tomorrowos]", err instanceof Error ? err.message : err);
|
|
240
|
+
process.exit(1);
|
|
241
|
+
});
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
export { TomorrowOS } from "./tomorrowos.js";
|
|
2
2
|
export type { DeviceListItem, ListenOptions, TomorrowOSBrand, TomorrowOSOptions } from "./tomorrowos.js";
|
|
3
|
-
export type { DevicePlaylistAssignment, DeviceRegistryRecord, PairedDeviceRecord, PendingCodeRecord, PlaylistItemRecord, PlaylistSchedule, PublishedPlaylistSnapshot, StoredPlaylist, TomorrowOSStore } from "./store/types.js";
|
|
3
|
+
export type { DevicePlaylistAssignment, DeviceRegistryRecord, PairedDeviceRecord, PendingCodeEntry, PendingCodeRecord, DeviceRegistryEntry, PlaylistItemRecord, PlaylistSchedule, PublishedPlaylistSnapshot, StoredPlaylist, TomorrowOSDataSnapshot, TomorrowOSMigratableStore, TomorrowOSStore } from "./store/types.js";
|
|
4
4
|
export { PlaylistCatalog } from "./playlist-catalog.js";
|
|
5
5
|
export type { BuiltDevicePolicy, SavePlaylistInput } from "./playlist-catalog.js";
|
|
6
6
|
export { generateRandomPairingCode, isValidPairingCodeFormat, normalizePairingCode, PAIRING_CODE_ALPHABET, PAIRING_CODE_LENGTH } from "./pairing-code.js";
|
|
7
7
|
export { MemoryStore } from "./store/memory-store.js";
|
|
8
|
+
export { PostgresStore } from "./store/postgres-store.js";
|
|
9
|
+
export type { PostgresStoreOptions } from "./store/postgres-store.js";
|
|
10
|
+
export { SQLiteStore } from "./store/sqlite-store.js";
|
|
11
|
+
export { createTomorrowOSStore, defaultSQLitePath } from "./store/factory.js";
|
|
12
|
+
export type { CreateTomorrowOSStoreOptions, TomorrowOSStoreDriver } from "./store/factory.js";
|
|
13
|
+
export { exportTomorrowOSData, importTomorrowOSData, migrateTomorrowOSData } from "./store/migration.js";
|
|
14
|
+
export type { MigrationResult } from "./store/migration.js";
|
|
8
15
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,YAAY,EACV,cAAc,EACd,aAAa,EACb,eAAe,EACf,iBAAiB,EAClB,MAAM,iBAAiB,CAAC;AAEzB,YAAY,EACV,wBAAwB,EACxB,oBAAoB,EACpB,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,yBAAyB,EACzB,cAAc,EACd,eAAe,EAChB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAClF,OAAO,EACL,yBAAyB,EACzB,wBAAwB,EACxB,oBAAoB,EACpB,qBAAqB,EACrB,mBAAmB,EACpB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,YAAY,EACV,cAAc,EACd,aAAa,EACb,eAAe,EACf,iBAAiB,EAClB,MAAM,iBAAiB,CAAC;AAEzB,YAAY,EACV,wBAAwB,EACxB,oBAAoB,EACpB,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,EACnB,kBAAkB,EAClB,gBAAgB,EAChB,yBAAyB,EACzB,cAAc,EACd,sBAAsB,EACtB,yBAAyB,EACzB,eAAe,EAChB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAClF,OAAO,EACL,yBAAyB,EACzB,wBAAwB,EACxB,oBAAoB,EACpB,qBAAqB,EACrB,mBAAmB,EACpB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,YAAY,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EAClB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,4BAA4B,EAC5B,qBAAqB,EACtB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,EACtB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -2,3 +2,7 @@ export { TomorrowOS } from "./tomorrowos.js";
|
|
|
2
2
|
export { PlaylistCatalog } from "./playlist-catalog.js";
|
|
3
3
|
export { generateRandomPairingCode, isValidPairingCodeFormat, normalizePairingCode, PAIRING_CODE_ALPHABET, PAIRING_CODE_LENGTH } from "./pairing-code.js";
|
|
4
4
|
export { MemoryStore } from "./store/memory-store.js";
|
|
5
|
+
export { PostgresStore } from "./store/postgres-store.js";
|
|
6
|
+
export { SQLiteStore } from "./store/sqlite-store.js";
|
|
7
|
+
export { createTomorrowOSStore, defaultSQLitePath } from "./store/factory.js";
|
|
8
|
+
export { exportTomorrowOSData, importTomorrowOSData, migrateTomorrowOSData } from "./store/migration.js";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { PoolConfig } from "pg";
|
|
2
|
+
import type { TomorrowOSStore } from "./types.js";
|
|
3
|
+
export type TomorrowOSStoreDriver = "sqlite" | "postgres" | "supabase" | "memory";
|
|
4
|
+
export interface CreateTomorrowOSStoreOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Defaults to TOMORROWOS_STORE, then postgres when DATABASE_URL is set,
|
|
7
|
+
* otherwise sqlite. Use memory only for tests or throwaway demos.
|
|
8
|
+
*/
|
|
9
|
+
driver?: TomorrowOSStoreDriver;
|
|
10
|
+
/** Defaults to TOMORROWOS_DB_PATH, then ./data/tomorrowos.db. */
|
|
11
|
+
sqlitePath?: string;
|
|
12
|
+
/** Supabase/Postgres connection string. Defaults to DATABASE_URL. */
|
|
13
|
+
databaseUrl?: string;
|
|
14
|
+
/** Defaults from DATABASE_SSL / DATABASE_URL. */
|
|
15
|
+
postgresSsl?: PoolConfig["ssl"];
|
|
16
|
+
env?: NodeJS.ProcessEnv;
|
|
17
|
+
}
|
|
18
|
+
export declare function defaultSQLitePath(cwd?: string): string;
|
|
19
|
+
export declare function createTomorrowOSStore(options?: CreateTomorrowOSStoreOptions): TomorrowOSStore;
|
|
20
|
+
//# sourceMappingURL=factory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/store/factory.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAKrC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,MAAM,MAAM,qBAAqB,GAAG,QAAQ,GAAG,UAAU,GAAG,UAAU,GAAG,QAAQ,CAAC;AAElF,MAAM,WAAW,4BAA4B;IAC3C;;;OAGG;IACH,MAAM,CAAC,EAAE,qBAAqB,CAAC;IAC/B,iEAAiE;IACjE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qEAAqE;IACrE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iDAAiD;IACjD,WAAW,CAAC,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC;IAChC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACzB;AAED,wBAAgB,iBAAiB,CAAC,GAAG,SAAgB,GAAG,MAAM,CAE7D;AAED,wBAAgB,qBAAqB,CACnC,OAAO,GAAE,4BAAiC,GACzC,eAAe,CAmCjB"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { MemoryStore } from "./memory-store.js";
|
|
3
|
+
import { PostgresStore } from "./postgres-store.js";
|
|
4
|
+
import { SQLiteStore } from "./sqlite-store.js";
|
|
5
|
+
export function defaultSQLitePath(cwd = process.cwd()) {
|
|
6
|
+
return path.join(cwd, "data", "tomorrowos.db");
|
|
7
|
+
}
|
|
8
|
+
export function createTomorrowOSStore(options = {}) {
|
|
9
|
+
const env = options.env ?? process.env;
|
|
10
|
+
const databaseUrl = options.databaseUrl ?? env.DATABASE_URL;
|
|
11
|
+
const driver = (options.driver ??
|
|
12
|
+
env.TOMORROWOS_STORE ??
|
|
13
|
+
(databaseUrl ? "postgres" : "sqlite")).toLowerCase();
|
|
14
|
+
if (driver === "memory") {
|
|
15
|
+
return new MemoryStore();
|
|
16
|
+
}
|
|
17
|
+
if (driver === "postgres" || driver === "supabase") {
|
|
18
|
+
if (!databaseUrl) {
|
|
19
|
+
throw new Error(`TOMORROWOS_STORE=${driver} requires DATABASE_URL. Set DATABASE_URL in .env or unset TOMORROWOS_STORE to use SQLite.`);
|
|
20
|
+
}
|
|
21
|
+
return new PostgresStore({
|
|
22
|
+
connectionString: databaseUrl,
|
|
23
|
+
ssl: options.postgresSsl ?? resolvePostgresSsl(env, databaseUrl)
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
if (driver === "sqlite") {
|
|
27
|
+
return new SQLiteStore({
|
|
28
|
+
databasePath: options.sqlitePath ?? env.TOMORROWOS_DB_PATH ?? defaultSQLitePath()
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
throw new Error(`Unsupported TOMORROWOS_STORE "${driver}". Use "sqlite", "supabase", "postgres", "memory", or pass a custom TomorrowOSStore to new TomorrowOS({ store }).`);
|
|
32
|
+
}
|
|
33
|
+
function resolvePostgresSsl(env, databaseUrl) {
|
|
34
|
+
const raw = env.DATABASE_SSL?.trim().toLowerCase();
|
|
35
|
+
if (raw === "false" || raw === "0" || raw === "off")
|
|
36
|
+
return false;
|
|
37
|
+
if (raw === "true" || raw === "1" || raw === "on") {
|
|
38
|
+
return { rejectUnauthorized: false };
|
|
39
|
+
}
|
|
40
|
+
if (/supabase\.(co|com)|sslmode=require/i.test(databaseUrl)) {
|
|
41
|
+
return { rejectUnauthorized: false };
|
|
42
|
+
}
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { TomorrowOSDataSnapshot, TomorrowOSMigratableStore } from "./types.js";
|
|
2
|
+
export interface MigrationResult {
|
|
3
|
+
pendingCodes: number;
|
|
4
|
+
deviceRegistry: number;
|
|
5
|
+
pairedDevices: number;
|
|
6
|
+
playlists: number;
|
|
7
|
+
deviceAssignments: number;
|
|
8
|
+
}
|
|
9
|
+
export declare function exportTomorrowOSData(store: TomorrowOSMigratableStore): Promise<TomorrowOSDataSnapshot>;
|
|
10
|
+
export declare function importTomorrowOSData(store: TomorrowOSMigratableStore, snapshot: TomorrowOSDataSnapshot): Promise<MigrationResult>;
|
|
11
|
+
export declare function migrateTomorrowOSData(from: TomorrowOSMigratableStore, to: TomorrowOSMigratableStore): Promise<MigrationResult>;
|
|
12
|
+
//# sourceMappingURL=migration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migration.d.ts","sourceRoot":"","sources":["../../src/store/migration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,sBAAsB,EACtB,yBAAyB,EAC1B,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,eAAe;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,yBAAyB,GAC/B,OAAO,CAAC,sBAAsB,CAAC,CA2BjC;AAED,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,yBAAyB,EAChC,QAAQ,EAAE,sBAAsB,GAC/B,OAAO,CAAC,eAAe,CAAC,CA+B1B;AAED,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,yBAAyB,EAC/B,EAAE,EAAE,yBAAyB,GAC5B,OAAO,CAAC,eAAe,CAAC,CAG1B"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export async function exportTomorrowOSData(store) {
|
|
2
|
+
const [pendingCodes, deviceRegistry, pairedDevices, playlists] = await Promise.all([
|
|
3
|
+
store.listPendingCodes(),
|
|
4
|
+
store.listDeviceRegistry(),
|
|
5
|
+
store.listPairedDevices(),
|
|
6
|
+
store.listPlaylists()
|
|
7
|
+
]);
|
|
8
|
+
const deviceAssignments = await Promise.all(pairedDevices.map(async ({ deviceId }) => ({
|
|
9
|
+
deviceId,
|
|
10
|
+
assignments: await store.getDeviceAssignments(deviceId)
|
|
11
|
+
})));
|
|
12
|
+
return {
|
|
13
|
+
pendingCodes,
|
|
14
|
+
deviceRegistry,
|
|
15
|
+
pairedDevices,
|
|
16
|
+
playlists,
|
|
17
|
+
deviceAssignments
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export async function importTomorrowOSData(store, snapshot) {
|
|
21
|
+
for (const { deviceId, record } of snapshot.deviceRegistry) {
|
|
22
|
+
await store.setDeviceRegistry(deviceId, record);
|
|
23
|
+
}
|
|
24
|
+
for (const { deviceId, record } of snapshot.pairedDevices) {
|
|
25
|
+
await store.setPairedDevice(deviceId, record);
|
|
26
|
+
}
|
|
27
|
+
for (const playlist of snapshot.playlists) {
|
|
28
|
+
await store.setPlaylist(playlist);
|
|
29
|
+
}
|
|
30
|
+
for (const { deviceId, assignments } of snapshot.deviceAssignments) {
|
|
31
|
+
await store.setDeviceAssignments(deviceId, assignments);
|
|
32
|
+
}
|
|
33
|
+
for (const { code, record } of snapshot.pendingCodes) {
|
|
34
|
+
await store.setPendingCode(code, record);
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
pendingCodes: snapshot.pendingCodes.length,
|
|
38
|
+
deviceRegistry: snapshot.deviceRegistry.length,
|
|
39
|
+
pairedDevices: snapshot.pairedDevices.length,
|
|
40
|
+
playlists: snapshot.playlists.length,
|
|
41
|
+
deviceAssignments: snapshot.deviceAssignments.reduce((total, entry) => total + entry.assignments.length, 0)
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export async function migrateTomorrowOSData(from, to) {
|
|
45
|
+
const snapshot = await exportTomorrowOSData(from);
|
|
46
|
+
return importTomorrowOSData(to, snapshot);
|
|
47
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type PoolConfig } from "pg";
|
|
2
|
+
import type { DevicePlaylistAssignment, DeviceRegistryEntry, DeviceRegistryRecord, PairedDeviceEntry, PairedDeviceRecord, PendingCodeEntry, PendingCodeRecord, StoredPlaylist, TomorrowOSMigratableStore } from "./types.js";
|
|
3
|
+
export interface PostgresStoreOptions {
|
|
4
|
+
connectionString: string;
|
|
5
|
+
ssl?: PoolConfig["ssl"];
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Durable store backed by Postgres. Supabase works through its Postgres
|
|
9
|
+
* connection string, usually provided as DATABASE_URL.
|
|
10
|
+
*/
|
|
11
|
+
export declare class PostgresStore implements TomorrowOSMigratableStore {
|
|
12
|
+
private readonly pool;
|
|
13
|
+
private ready?;
|
|
14
|
+
constructor(options: PostgresStoreOptions | string);
|
|
15
|
+
init(): Promise<void>;
|
|
16
|
+
close(): Promise<void>;
|
|
17
|
+
private ensureReady;
|
|
18
|
+
setPendingCode(code: string, record: PendingCodeRecord): Promise<void>;
|
|
19
|
+
getPendingCode(code: string): Promise<PendingCodeRecord | undefined>;
|
|
20
|
+
deletePendingCode(code: string): Promise<void>;
|
|
21
|
+
listPendingCodes(): Promise<PendingCodeEntry[]>;
|
|
22
|
+
getDeviceRegistry(deviceId: string): Promise<DeviceRegistryRecord | undefined>;
|
|
23
|
+
setDeviceRegistry(deviceId: string, record: DeviceRegistryRecord): Promise<void>;
|
|
24
|
+
getDeviceRegistryByCode(code: string): Promise<{
|
|
25
|
+
deviceId: string;
|
|
26
|
+
record: DeviceRegistryRecord;
|
|
27
|
+
} | undefined>;
|
|
28
|
+
listDeviceRegistry(): Promise<DeviceRegistryEntry[]>;
|
|
29
|
+
setPairedDevice(deviceId: string, record: PairedDeviceRecord): Promise<void>;
|
|
30
|
+
getPairedDevice(deviceId: string): Promise<PairedDeviceRecord | undefined>;
|
|
31
|
+
deletePairedDevice(deviceId: string): Promise<void>;
|
|
32
|
+
listPairedDevices(): Promise<PairedDeviceEntry[]>;
|
|
33
|
+
listPlaylists(): Promise<StoredPlaylist[]>;
|
|
34
|
+
getPlaylist(id: string): Promise<StoredPlaylist | undefined>;
|
|
35
|
+
setPlaylist(record: StoredPlaylist): Promise<void>;
|
|
36
|
+
isPlaylistNameTaken(name: string, excludeId?: string): Promise<boolean>;
|
|
37
|
+
getDeviceAssignments(deviceId: string): Promise<DevicePlaylistAssignment[]>;
|
|
38
|
+
setDeviceAssignments(deviceId: string, assignments: DevicePlaylistAssignment[]): Promise<void>;
|
|
39
|
+
private mapDeviceRegistryRow;
|
|
40
|
+
private mapPairedDeviceRow;
|
|
41
|
+
private mapPlaylistRow;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=postgres-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres-store.d.ts","sourceRoot":"","sources":["../../src/store/postgres-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,UAAU,EAAE,MAAM,IAAI,CAAC;AAE3C,OAAO,KAAK,EACV,wBAAwB,EACxB,mBAAmB,EACnB,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EAGjB,cAAc,EACd,yBAAyB,EAC1B,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,oBAAoB;IACnC,gBAAgB,EAAE,MAAM,CAAC;IACzB,GAAG,CAAC,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC;CACzB;AAyDD;;;GAGG;AACH,qBAAa,aAAc,YAAW,yBAAyB;IAC7D,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAO;IAC5B,OAAO,CAAC,KAAK,CAAC,CAAgB;gBAElB,OAAO,EAAE,oBAAoB,GAAG,MAAM;IAY5C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAsErB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAId,WAAW;IAKnB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAWtE,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC;IAepE,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK9C,gBAAgB,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAiB/C,iBAAiB,CACrB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAAC;IAWtC,iBAAiB,CACrB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,IAAI,CAAC;IA4BV,uBAAuB,CAC3B,IAAI,EAAE,MAAM,GACX,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,oBAAoB,CAAA;KAAE,GAAG,SAAS,CAAC;IAepE,kBAAkB,IAAI,OAAO,CAAC,mBAAmB,EAAE,CAAC;IAapD,eAAe,CACnB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,kBAAkB,GACzB,OAAO,CAAC,IAAI,CAAC;IAwCV,eAAe,CACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC;IAWpC,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOnD,iBAAiB,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC;IAajD,aAAa,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;IAU1C,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC;IAW5D,WAAW,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAkClD,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAevE,oBAAoB,CACxB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,wBAAwB,EAAE,CAAC;IAqBhC,oBAAoB,CACxB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,wBAAwB,EAAE,GACtC,OAAO,CAAC,IAAI,CAAC;IAuChB,OAAO,CAAC,oBAAoB;IAU5B,OAAO,CAAC,kBAAkB;IAc1B,OAAO,CAAC,cAAc;CAYvB"}
|