@executor-js/emulate 0.6.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.
Files changed (48) hide show
  1. package/README.md +1044 -0
  2. package/dist/api.d.ts +24 -0
  3. package/dist/api.js +2665 -0
  4. package/dist/api.js.map +1 -0
  5. package/dist/chunk-D6EKRYGP.js +1615 -0
  6. package/dist/chunk-D6EKRYGP.js.map +1 -0
  7. package/dist/chunk-WVQMFHQM.js +83 -0
  8. package/dist/chunk-WVQMFHQM.js.map +1 -0
  9. package/dist/dist-7FDUSG5I.js +24368 -0
  10. package/dist/dist-7FDUSG5I.js.map +1 -0
  11. package/dist/dist-7N4COJHK.js +1814 -0
  12. package/dist/dist-7N4COJHK.js.map +1 -0
  13. package/dist/dist-BTEY33DJ.js +2334 -0
  14. package/dist/dist-BTEY33DJ.js.map +1 -0
  15. package/dist/dist-DK26ESP2.js +595 -0
  16. package/dist/dist-DK26ESP2.js.map +1 -0
  17. package/dist/dist-IYZPDKJW.js +1284 -0
  18. package/dist/dist-IYZPDKJW.js.map +1 -0
  19. package/dist/dist-JJ2ZRCAX.js +189 -0
  20. package/dist/dist-JJ2ZRCAX.js.map +1 -0
  21. package/dist/dist-K4CVTD6K.js +1570 -0
  22. package/dist/dist-K4CVTD6K.js.map +1 -0
  23. package/dist/dist-M3GVASMR.js +1254 -0
  24. package/dist/dist-M3GVASMR.js.map +1 -0
  25. package/dist/dist-OYYGWKZQ.js +1533 -0
  26. package/dist/dist-OYYGWKZQ.js.map +1 -0
  27. package/dist/dist-P3SBBRFR.js +3169 -0
  28. package/dist/dist-P3SBBRFR.js.map +1 -0
  29. package/dist/dist-RMPDKZUA.js +1183 -0
  30. package/dist/dist-RMPDKZUA.js.map +1 -0
  31. package/dist/dist-WBKONLOE.js +2154 -0
  32. package/dist/dist-WBKONLOE.js.map +1 -0
  33. package/dist/dist-XM5HSBDC.js +1090 -0
  34. package/dist/dist-XM5HSBDC.js.map +1 -0
  35. package/dist/dist-XVVIYXQG.js +4241 -0
  36. package/dist/dist-XVVIYXQG.js.map +1 -0
  37. package/dist/dist-YPRJYQHW.js +5109 -0
  38. package/dist/dist-YPRJYQHW.js.map +1 -0
  39. package/dist/dist-ZEC77OKZ.js +913 -0
  40. package/dist/dist-ZEC77OKZ.js.map +1 -0
  41. package/dist/fonts/GeistPixel-Square.woff2 +0 -0
  42. package/dist/fonts/favicon.ico +0 -0
  43. package/dist/fonts/geist-sans.woff2 +0 -0
  44. package/dist/helpers-LXLP3DFE-LBOTATT5.js +17 -0
  45. package/dist/helpers-LXLP3DFE-LBOTATT5.js.map +1 -0
  46. package/dist/index.js +3005 -0
  47. package/dist/index.js.map +1 -0
  48. package/package.json +83 -0
@@ -0,0 +1,595 @@
1
+ // ../@emulators/spotify/dist/index.js
2
+ import { timingSafeEqual } from "crypto";
3
+ function getSpotifyStore(store) {
4
+ return {
5
+ clients: store.collection("spotify.clients", ["client_id"]),
6
+ artists: store.collection("spotify.artists", ["spotify_id", "name"]),
7
+ albums: store.collection("spotify.albums", ["spotify_id", "artist_id"]),
8
+ tracks: store.collection("spotify.tracks", ["spotify_id", "album_id", "artist_id"])
9
+ };
10
+ }
11
+ var B62 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
12
+ function spotifyId() {
13
+ const b = new Uint8Array(22);
14
+ crypto.getRandomValues(b);
15
+ return Array.from(b, (x) => B62[x % 62]).join("");
16
+ }
17
+ var KEY = "spotify.tokens";
18
+ function issueToken(store, token, rec) {
19
+ const m = store.getData(KEY) ?? {};
20
+ m[token] = rec;
21
+ store.setData(KEY, m);
22
+ }
23
+ function lookupToken(store, token) {
24
+ return (store.getData(KEY) ?? {})[token];
25
+ }
26
+ function createErrorHandler(documentationUrl) {
27
+ return async (c, next) => {
28
+ if (documentationUrl) {
29
+ c.set("docsUrl", documentationUrl);
30
+ }
31
+ await next();
32
+ };
33
+ }
34
+ var errorHandler = createErrorHandler();
35
+ var isDebug = typeof process !== "undefined" && (process.env.DEBUG === "1" || process.env.DEBUG === "true" || process.env.EMULATE_DEBUG === "1");
36
+ function constantTimeSecretEqual(a, b) {
37
+ const bufA = Buffer.from(a, "utf-8");
38
+ const bufB = Buffer.from(b, "utf-8");
39
+ if (bufA.length !== bufB.length) return false;
40
+ return timingSafeEqual(bufA, bufB);
41
+ }
42
+ function bodyStr(v) {
43
+ if (typeof v === "string") return v;
44
+ if (Array.isArray(v) && typeof v[0] === "string") return v[0];
45
+ return "";
46
+ }
47
+ function appsRoutes({ app, store, baseUrl }) {
48
+ const ss = getSpotifyStore(store);
49
+ app.get(
50
+ "/_emulator/apps",
51
+ (c) => c.json({
52
+ apps: ss.clients.all().map((cl) => ({ client_id: cl.client_id, client_secret: cl.client_secret, name: cl.name })),
53
+ token_url: `${baseUrl}/api/token`
54
+ })
55
+ );
56
+ app.post("/_emulator/apps", async (c) => {
57
+ const body = await c.req.parseBody().catch(() => ({}));
58
+ const client_id = bodyStr(body.client_id) || `app_${spotifyId().slice(0, 18)}`;
59
+ const client_secret = bodyStr(body.client_secret) || spotifyId();
60
+ if (!ss.clients.findOneBy("client_id", client_id)) {
61
+ ss.clients.insert({ client_id, client_secret, name: bodyStr(body.name) || "App" });
62
+ }
63
+ return c.json({ client_id, client_secret, token_url: `${baseUrl}/api/token`, grant_type: "client_credentials" });
64
+ });
65
+ }
66
+ function tokenRoutes({ app, store }) {
67
+ const ss = getSpotifyStore(store);
68
+ app.post("/api/token", async (c) => {
69
+ const body = await c.req.parseBody();
70
+ let clientId = bodyStr(body.client_id);
71
+ let clientSecret = bodyStr(body.client_secret);
72
+ const basic = /^Basic\s+(.+)$/i.exec(c.req.header("Authorization") ?? "");
73
+ if (basic) {
74
+ try {
75
+ const decoded = atob(basic[1].trim());
76
+ const sep = decoded.indexOf(":");
77
+ clientId = decoded.slice(0, sep);
78
+ clientSecret = decoded.slice(sep + 1);
79
+ } catch {
80
+ }
81
+ }
82
+ if (bodyStr(body.grant_type) !== "client_credentials") {
83
+ return c.json(
84
+ { error: "unsupported_grant_type", error_description: "grant_type must be client_credentials" },
85
+ 400
86
+ );
87
+ }
88
+ const client = ss.clients.findOneBy("client_id", clientId);
89
+ if (!client || !constantTimeSecretEqual(clientSecret, client.client_secret)) {
90
+ return c.json({ error: "invalid_client", error_description: "Invalid client" }, 401);
91
+ }
92
+ const token = `BQ${spotifyId()}${spotifyId()}`;
93
+ issueToken(store, token, { clientId, scopes: bodyStr(body.scope).split(" ").filter(Boolean) });
94
+ return c.json({ access_token: token, token_type: "Bearer", expires_in: 3600 });
95
+ });
96
+ }
97
+ function catalogRoutes({ app, store, baseUrl }) {
98
+ const ss = getSpotifyStore(store);
99
+ const authed = (c) => {
100
+ const m = /^Bearer\s+(.+)$/i.exec(c.req.header("Authorization") ?? "");
101
+ return !!m && !!lookupToken(store, m[1].trim());
102
+ };
103
+ const unauthorized = (c) => c.json({ error: { status: 401, message: "Invalid access token" } }, 401);
104
+ const notFound = (c) => c.json({ error: { status: 404, message: "Non existing id" } }, 404);
105
+ const artistObj = (a) => ({
106
+ id: a.spotify_id,
107
+ name: a.name,
108
+ type: "artist",
109
+ genres: a.genres,
110
+ popularity: a.popularity,
111
+ followers: { href: null, total: a.followers },
112
+ uri: `spotify:artist:${a.spotify_id}`,
113
+ href: `${baseUrl}/v1/artists/${a.spotify_id}`,
114
+ external_urls: { spotify: `https://open.spotify.com/artist/${a.spotify_id}` }
115
+ });
116
+ const albumObj = (al) => {
117
+ const ar = ss.artists.findOneBy("spotify_id", al.artist_id);
118
+ return {
119
+ id: al.spotify_id,
120
+ name: al.name,
121
+ type: "album",
122
+ album_type: al.album_type,
123
+ release_date: al.release_date,
124
+ total_tracks: al.total_tracks,
125
+ artists: ar ? [{ id: ar.spotify_id, name: ar.name, type: "artist", uri: `spotify:artist:${ar.spotify_id}` }] : [],
126
+ uri: `spotify:album:${al.spotify_id}`,
127
+ href: `${baseUrl}/v1/albums/${al.spotify_id}`,
128
+ external_urls: { spotify: `https://open.spotify.com/album/${al.spotify_id}` }
129
+ };
130
+ };
131
+ const trackObj = (t) => {
132
+ const al = ss.albums.findOneBy("spotify_id", t.album_id);
133
+ const ar = ss.artists.findOneBy("spotify_id", t.artist_id);
134
+ return {
135
+ id: t.spotify_id,
136
+ name: t.name,
137
+ type: "track",
138
+ duration_ms: t.duration_ms,
139
+ popularity: t.popularity,
140
+ track_number: t.track_number,
141
+ explicit: false,
142
+ album: al ? { id: al.spotify_id, name: al.name, uri: `spotify:album:${al.spotify_id}` } : null,
143
+ artists: ar ? [{ id: ar.spotify_id, name: ar.name, type: "artist" }] : [],
144
+ uri: `spotify:track:${t.spotify_id}`,
145
+ external_urls: { spotify: `https://open.spotify.com/track/${t.spotify_id}` }
146
+ };
147
+ };
148
+ app.get("/v1/search", (c) => {
149
+ if (!authed(c)) return unauthorized(c);
150
+ const q = (c.req.query("q") ?? "").toLowerCase();
151
+ const types = (c.req.query("type") ?? "artist,album,track").split(",").map((s) => s.trim());
152
+ const m = (name) => q ? name.toLowerCase().includes(q) : true;
153
+ const out = {};
154
+ if (types.includes("artist"))
155
+ out.artists = {
156
+ items: ss.artists.all().filter((a) => m(a.name)).map(artistObj)
157
+ };
158
+ if (types.includes("album"))
159
+ out.albums = {
160
+ items: ss.albums.all().filter((a) => m(a.name)).map(albumObj)
161
+ };
162
+ if (types.includes("track"))
163
+ out.tracks = {
164
+ items: ss.tracks.all().filter((t) => m(t.name)).map(trackObj)
165
+ };
166
+ return c.json(out);
167
+ });
168
+ app.get("/v1/artists/:id", (c) => {
169
+ if (!authed(c)) return unauthorized(c);
170
+ const a = ss.artists.findOneBy("spotify_id", c.req.param("id"));
171
+ return a ? c.json(artistObj(a)) : notFound(c);
172
+ });
173
+ app.get("/v1/artists/:id/albums", (c) => {
174
+ if (!authed(c)) return unauthorized(c);
175
+ const items = ss.albums.findBy("artist_id", c.req.param("id")).map(albumObj);
176
+ return c.json({ href: `${baseUrl}/v1/artists/${c.req.param("id")}/albums`, items, total: items.length });
177
+ });
178
+ app.get("/v1/albums/:id", (c) => {
179
+ if (!authed(c)) return unauthorized(c);
180
+ const al = ss.albums.findOneBy("spotify_id", c.req.param("id"));
181
+ if (!al) return notFound(c);
182
+ const items = ss.tracks.findBy("album_id", al.spotify_id).map(trackObj);
183
+ return c.json({ ...albumObj(al), tracks: { items, total: items.length } });
184
+ });
185
+ app.get("/v1/tracks/:id", (c) => {
186
+ if (!authed(c)) return unauthorized(c);
187
+ const t = ss.tracks.findOneBy("spotify_id", c.req.param("id"));
188
+ return t ? c.json(trackObj(t)) : notFound(c);
189
+ });
190
+ app.get("/v1/me", (c) => {
191
+ if (!authed(c)) return unauthorized(c);
192
+ return c.json(
193
+ {
194
+ error: {
195
+ status: 403,
196
+ message: "This endpoint requires a user-authorized token; a client-credentials token has no user."
197
+ }
198
+ },
199
+ 403
200
+ );
201
+ });
202
+ }
203
+ function openapiRoutes({ app, baseUrl }) {
204
+ app.get("/openapi.json", (c) => c.json(buildSpec(baseUrl)));
205
+ }
206
+ var ok = (description) => ({
207
+ description,
208
+ content: { "application/json": { schema: { type: "object" } } }
209
+ });
210
+ function buildSpec(baseUrl) {
211
+ return {
212
+ openapi: "3.1.0",
213
+ info: {
214
+ title: "Spotify Web API (Emulated)",
215
+ version: "1.0.0",
216
+ description: "Emulated subset of the Spotify Web API. OAuth 2.0 Client Credentials (app token, no user). Mint credentials via POST /_emulator/apps, then exchange them at the token endpoint."
217
+ },
218
+ servers: [{ url: baseUrl }],
219
+ components: {
220
+ securitySchemes: {
221
+ spotifyOAuth: {
222
+ type: "oauth2",
223
+ description: "Client Credentials \u2014 app-only token, no user context.",
224
+ flows: {
225
+ clientCredentials: {
226
+ tokenUrl: `${baseUrl}/api/token`,
227
+ scopes: {}
228
+ }
229
+ }
230
+ }
231
+ }
232
+ },
233
+ security: [{ spotifyOAuth: [] }],
234
+ paths: {
235
+ "/v1/search": {
236
+ get: {
237
+ operationId: "search",
238
+ summary: "Search the catalog",
239
+ parameters: [
240
+ { name: "q", in: "query", required: true, schema: { type: "string" }, description: "Search query." },
241
+ {
242
+ name: "type",
243
+ in: "query",
244
+ required: true,
245
+ schema: { type: "string" },
246
+ description: "Comma-separated item types: artist, album, track."
247
+ },
248
+ { name: "limit", in: "query", required: false, schema: { type: "integer" } }
249
+ ],
250
+ responses: { "200": ok("Search results grouped by type.") }
251
+ }
252
+ },
253
+ "/v1/artists/{id}": {
254
+ get: {
255
+ operationId: "getArtist",
256
+ summary: "Get an artist",
257
+ parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
258
+ responses: { "200": ok("Artist object.") }
259
+ }
260
+ },
261
+ "/v1/artists/{id}/albums": {
262
+ get: {
263
+ operationId: "getArtistAlbums",
264
+ summary: "Get an artist's albums",
265
+ parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
266
+ responses: { "200": ok("Paged album list.") }
267
+ }
268
+ },
269
+ "/v1/albums/{id}": {
270
+ get: {
271
+ operationId: "getAlbum",
272
+ summary: "Get an album (with tracks)",
273
+ parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
274
+ responses: { "200": ok("Album object.") }
275
+ }
276
+ },
277
+ "/v1/tracks/{id}": {
278
+ get: {
279
+ operationId: "getTrack",
280
+ summary: "Get a track",
281
+ parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
282
+ responses: { "200": ok("Track object.") }
283
+ }
284
+ },
285
+ "/v1/me": {
286
+ get: {
287
+ operationId: "getCurrentUser",
288
+ summary: "Get the current user (403 for app tokens \u2014 there is no user)",
289
+ responses: { "403": ok("Client-credentials tokens have no user, faithfully to Spotify.") }
290
+ }
291
+ }
292
+ }
293
+ };
294
+ }
295
+ var manifest = {
296
+ id: "spotify",
297
+ name: "Spotify",
298
+ description: "Stateful Spotify Web API emulator focused on client credentials OAuth and catalog APIs.",
299
+ docsUrl: "https://docs.emulators.dev/spotify",
300
+ surfaces: [
301
+ {
302
+ id: "token",
303
+ kind: "oauth",
304
+ title: "Client credentials token endpoint",
305
+ status: "supported",
306
+ basePath: "/api/token"
307
+ },
308
+ { id: "rest", kind: "rest", title: "REST API", status: "partial", basePath: "/" }
309
+ ],
310
+ auth: [
311
+ {
312
+ id: "client-credentials",
313
+ title: "OAuth client credentials",
314
+ type: "oauth-client-credentials",
315
+ status: "supported"
316
+ }
317
+ ],
318
+ specs: [
319
+ {
320
+ kind: "openapi",
321
+ title: "Spotify Web API subset",
322
+ coverage: "hand-authored",
323
+ url: "/openapi.json",
324
+ operations: [
325
+ {
326
+ operationId: "token",
327
+ method: "POST",
328
+ path: "/api/token",
329
+ status: "hand-authored",
330
+ summary: "Client credentials grant \u2014 mint an app-only access token."
331
+ },
332
+ {
333
+ operationId: "search",
334
+ method: "GET",
335
+ path: "/v1/search",
336
+ status: "hand-authored",
337
+ summary: "Search the catalog for artists, albums, and tracks."
338
+ },
339
+ {
340
+ operationId: "getArtist",
341
+ method: "GET",
342
+ path: "/v1/artists/:id",
343
+ status: "hand-authored"
344
+ },
345
+ {
346
+ operationId: "getArtistAlbums",
347
+ method: "GET",
348
+ path: "/v1/artists/:id/albums",
349
+ status: "hand-authored"
350
+ },
351
+ {
352
+ operationId: "getAlbum",
353
+ method: "GET",
354
+ path: "/v1/albums/:id",
355
+ status: "hand-authored",
356
+ summary: "Get an album with its tracks."
357
+ },
358
+ {
359
+ operationId: "getTrack",
360
+ method: "GET",
361
+ path: "/v1/tracks/:id",
362
+ status: "hand-authored"
363
+ },
364
+ {
365
+ operationId: "getCurrentUserProfile",
366
+ method: "GET",
367
+ path: "/v1/me",
368
+ status: "partial",
369
+ summary: "Modelled to return 403 \u2014 a client-credentials token has no user, faithful to Spotify."
370
+ },
371
+ {
372
+ operationId: "getSeveralArtists",
373
+ method: "GET",
374
+ path: "/v1/artists",
375
+ status: "unsupported"
376
+ },
377
+ {
378
+ operationId: "getArtistTopTracks",
379
+ method: "GET",
380
+ path: "/v1/artists/:id/top-tracks",
381
+ status: "unsupported"
382
+ },
383
+ {
384
+ operationId: "getAlbumTracks",
385
+ method: "GET",
386
+ path: "/v1/albums/:id/tracks",
387
+ status: "unsupported"
388
+ },
389
+ {
390
+ operationId: "getRecommendations",
391
+ method: "GET",
392
+ path: "/v1/recommendations",
393
+ status: "unsupported"
394
+ },
395
+ {
396
+ operationId: "getPlaylist",
397
+ method: "GET",
398
+ path: "/v1/playlists/:id",
399
+ status: "unsupported",
400
+ summary: "Playlists are not seeded or served by the emulator."
401
+ }
402
+ ]
403
+ }
404
+ ],
405
+ seedSchema: {
406
+ description: "Seed OAuth apps (clients) and a catalog of artists, albums, and tracks.",
407
+ fields: [
408
+ {
409
+ key: "clients",
410
+ title: "OAuth apps",
411
+ description: "Client credentials addressable at the token endpoint.",
412
+ example: [{ client_id: "demo-client-id", client_secret: "demo-client-secret", name: "Demo App" }]
413
+ },
414
+ {
415
+ key: "artists",
416
+ title: "Artists",
417
+ description: "Catalog artists, each with optional nested albums and tracks.",
418
+ example: [
419
+ {
420
+ name: "Daft Punk",
421
+ genres: ["electronic", "french house"],
422
+ popularity: 88,
423
+ followers: 9e6,
424
+ albums: [
425
+ {
426
+ name: "Discovery",
427
+ release_date: "2001-03-12",
428
+ tracks: [{ name: "One More Time", duration_ms: 320357 }, { name: "Digital Love" }]
429
+ }
430
+ ]
431
+ }
432
+ ]
433
+ }
434
+ ],
435
+ example: {
436
+ clients: [{ client_id: "demo-client-id", client_secret: "demo-client-secret", name: "Demo App" }],
437
+ artists: [
438
+ {
439
+ name: "Tame Impala",
440
+ genres: ["psychedelic rock"],
441
+ popularity: 85,
442
+ followers: 6e6,
443
+ albums: [
444
+ {
445
+ name: "Currents",
446
+ release_date: "2015-07-17",
447
+ tracks: [{ name: "Let It Happen" }, { name: "The Less I Know the Better" }]
448
+ }
449
+ ]
450
+ }
451
+ ]
452
+ }
453
+ },
454
+ stateModel: {
455
+ description: "Entities seeded and read by Spotify provider calls.",
456
+ collections: [
457
+ { name: "spotify.clients", title: "OAuth apps" },
458
+ { name: "spotify.artists", title: "Artists" },
459
+ { name: "spotify.albums", title: "Albums" },
460
+ { name: "spotify.tracks", title: "Tracks" }
461
+ ]
462
+ },
463
+ connections: [
464
+ {
465
+ id: "spotify-web-api-node",
466
+ title: "spotify-web-api-node (TypeScript)",
467
+ kind: "sdk",
468
+ language: "typescript",
469
+ description: "Point the Spotify SDK's account and API base hosts at the emulator instance.",
470
+ template: 'import SpotifyWebApi from "spotify-web-api-node";\n\nconst spotify = new SpotifyWebApi({\n clientId: "{{clientId}}",\n clientSecret: "{{clientSecret}}",\n});\n\n// Route both the accounts (token) and API hosts at the emulator.\nconst base = "{{baseUrl}}";\n\n// Client credentials grant against the emulator token endpoint.\nconst auth = Buffer.from("{{clientId}}:{{clientSecret}}").toString("base64");\nconst res = await fetch(`${base}/api/token`, {\n method: "POST",\n headers: {\n authorization: `Basic ${auth}`,\n "content-type": "application/x-www-form-urlencoded",\n },\n body: new URLSearchParams({ grant_type: "client_credentials" }),\n});\nconst { access_token } = await res.json();\nspotify.setAccessToken(access_token);\n\nconst results = await fetch(`${base}/v1/search?q=daft&type=artist`, {\n headers: { authorization: `Bearer ${access_token}` },\n}).then((r) => r.json());'
471
+ },
472
+ {
473
+ id: "spotify-env",
474
+ title: "Spotify base URL (env)",
475
+ kind: "env",
476
+ language: "bash",
477
+ description: "Point your app at the emulator instead of the real Spotify accounts and API hosts.",
478
+ template: "SPOTIFY_BASE_URL={{baseUrl}}\nSPOTIFY_TOKEN_URL={{baseUrl}}/api/token\nSPOTIFY_CLIENT_ID={{clientId}}\nSPOTIFY_CLIENT_SECRET={{clientSecret}}"
479
+ },
480
+ {
481
+ id: "curl-token",
482
+ title: "curl (client credentials)",
483
+ kind: "curl",
484
+ language: "bash",
485
+ description: "Exchange client credentials for an app token, then call the catalog.",
486
+ template: 'curl -s -X POST {{baseUrl}}/api/token \\\n -u "{{clientId}}:{{clientSecret}}" \\\n -d grant_type=client_credentials'
487
+ },
488
+ {
489
+ id: "curl-search",
490
+ title: "curl (search)",
491
+ kind: "curl",
492
+ language: "bash",
493
+ description: "Call the catalog directly with an app token.",
494
+ template: 'curl -s "{{baseUrl}}/v1/search?q=daft&type=artist" -H "authorization: Bearer {{token}}"'
495
+ }
496
+ ]
497
+ };
498
+ function seedFromConfig(store, _baseUrl, config) {
499
+ const ss = getSpotifyStore(store);
500
+ for (const c of config.clients ?? []) {
501
+ if (!ss.clients.findOneBy("client_id", c.client_id)) {
502
+ ss.clients.insert({ client_id: c.client_id, client_secret: c.client_secret, name: c.name ?? c.client_id });
503
+ }
504
+ }
505
+ for (const a of config.artists ?? []) {
506
+ let artist = ss.artists.findOneBy("name", a.name);
507
+ if (!artist) {
508
+ artist = ss.artists.insert({
509
+ spotify_id: spotifyId(),
510
+ name: a.name,
511
+ genres: a.genres ?? [],
512
+ popularity: a.popularity ?? 50,
513
+ followers: a.followers ?? 1e3
514
+ });
515
+ }
516
+ for (const al of a.albums ?? []) {
517
+ const album = ss.albums.insert({
518
+ spotify_id: spotifyId(),
519
+ name: al.name,
520
+ artist_id: artist.spotify_id,
521
+ album_type: "album",
522
+ release_date: al.release_date ?? "2020-01-01",
523
+ total_tracks: (al.tracks ?? []).length
524
+ });
525
+ let n = 1;
526
+ for (const t of al.tracks ?? []) {
527
+ ss.tracks.insert({
528
+ spotify_id: spotifyId(),
529
+ name: t.name,
530
+ album_id: album.spotify_id,
531
+ artist_id: artist.spotify_id,
532
+ duration_ms: t.duration_ms ?? 2e5,
533
+ popularity: 50,
534
+ track_number: n++
535
+ });
536
+ }
537
+ }
538
+ }
539
+ }
540
+ var spotifyPlugin = {
541
+ name: "spotify",
542
+ register(app, store, webhooks, baseUrl, tokenMap) {
543
+ const ctx = { app, store, webhooks, baseUrl, tokenMap };
544
+ appsRoutes(ctx);
545
+ tokenRoutes(ctx);
546
+ catalogRoutes(ctx);
547
+ openapiRoutes(ctx);
548
+ },
549
+ seed(store, baseUrl) {
550
+ seedFromConfig(store, baseUrl, {
551
+ clients: [{ client_id: "demo-client-id", client_secret: "demo-client-secret", name: "Demo App" }],
552
+ artists: [
553
+ {
554
+ name: "Daft Punk",
555
+ genres: ["electronic", "french house"],
556
+ popularity: 88,
557
+ followers: 9e6,
558
+ albums: [
559
+ {
560
+ name: "Discovery",
561
+ release_date: "2001-03-12",
562
+ tracks: [
563
+ { name: "One More Time" },
564
+ { name: "Harder, Better, Faster, Stronger" },
565
+ { name: "Digital Love" }
566
+ ]
567
+ }
568
+ ]
569
+ },
570
+ {
571
+ name: "Tame Impala",
572
+ genres: ["psychedelic rock"],
573
+ popularity: 85,
574
+ followers: 6e6,
575
+ albums: [
576
+ {
577
+ name: "Currents",
578
+ release_date: "2015-07-17",
579
+ tracks: [{ name: "Let It Happen" }, { name: "The Less I Know the Better" }]
580
+ }
581
+ ]
582
+ }
583
+ ]
584
+ });
585
+ }
586
+ };
587
+ var index_default = spotifyPlugin;
588
+ export {
589
+ index_default as default,
590
+ getSpotifyStore,
591
+ manifest,
592
+ seedFromConfig,
593
+ spotifyPlugin
594
+ };
595
+ //# sourceMappingURL=dist-DK26ESP2.js.map