@tscircuit/fake-snippets 0.0.8 → 0.0.10

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 (36) hide show
  1. package/bun-tests/fake-snippets-api/routes/packages/list-1.test.ts +0 -3
  2. package/bun-tests/fake-snippets-api/routes/packages/update.test.ts +161 -0
  3. package/bun-tests/fake-snippets-api/routes/snippets/add_star.test.ts +32 -27
  4. package/bun-tests/fake-snippets-api/routes/snippets/get.test.ts +114 -0
  5. package/bun-tests/fake-snippets-api/routes/snippets/get_image.test.ts +10 -6
  6. package/bun-tests/fake-snippets-api/routes/snippets/images.test.ts +8 -6
  7. package/bun-tests/fake-snippets-api/routes/snippets/list_newest.test.ts +2 -2
  8. package/bun-tests/fake-snippets-api/routes/snippets/list_trending.test.ts +10 -10
  9. package/bun-tests/fake-snippets-api/routes/snippets/remove_star.test.ts +8 -6
  10. package/bun-tests/fake-snippets-api/routes/snippets/search.test.ts +1 -1
  11. package/bun-tests/fake-snippets-api/routes/snippets/star-count.test.ts +19 -12
  12. package/bun-tests/fake-snippets-api/routes/snippets/update.test.ts +57 -16
  13. package/bun.lock +145 -569
  14. package/dist/bundle.js +905 -248
  15. package/fake-snippets-api/lib/db/db-client.ts +748 -108
  16. package/fake-snippets-api/lib/db/schema.ts +12 -0
  17. package/fake-snippets-api/lib/public-mapping/public-map-package.ts +2 -1
  18. package/fake-snippets-api/routes/api/packages/list.ts +4 -1
  19. package/fake-snippets-api/routes/api/packages/update.ts +91 -0
  20. package/fake-snippets-api/routes/api/snippets/add_star.ts +30 -8
  21. package/fake-snippets-api/routes/api/snippets/create.ts +16 -16
  22. package/fake-snippets-api/routes/api/snippets/delete.ts +5 -5
  23. package/fake-snippets-api/routes/api/snippets/download.ts +24 -10
  24. package/fake-snippets-api/routes/api/snippets/get.ts +46 -13
  25. package/fake-snippets-api/routes/api/snippets/list.ts +37 -2
  26. package/fake-snippets-api/routes/api/snippets/update.ts +36 -14
  27. package/package.json +3 -5
  28. package/src/components/CodeAndPreview.tsx +13 -48
  29. package/src/components/CodeEditor.tsx +10 -7
  30. package/src/components/EditorNav.tsx +0 -21
  31. package/src/components/PreviewContent.tsx +2 -2
  32. package/src/components/ViewSnippetHeader.tsx +4 -0
  33. package/src/hooks/use-global-store.ts +0 -5
  34. package/src/hooks/use-package-as-snippet.ts +78 -0
  35. package/src/hooks/use-run-tsx/index.tsx +4 -0
  36. package/src/lib/jlc-parts-engine.ts +4 -2
@@ -5,7 +5,7 @@ import { createStore } from "zustand/vanilla"
5
5
  import { combine } from "zustand/middleware"
6
6
  import {
7
7
  type Account,
8
- type AccountSnippet,
8
+ type AccountPackage,
9
9
  type LoginPage,
10
10
  type Order,
11
11
  type OrderFile,
@@ -15,9 +15,8 @@ import {
15
15
  type Session,
16
16
  type Snippet,
17
17
  databaseSchema,
18
- packageReleaseSchema,
19
18
  type packageSchema,
20
- snippetSchema,
19
+ type snippetSchema,
21
20
  } from "./schema.ts"
22
21
  import { seed as seedFn } from "./seed"
23
22
 
@@ -87,50 +86,252 @@ const initializer = combine(databaseSchema.parse({}), (set, get) => ({
87
86
 
88
87
  return newAccount
89
88
  },
89
+ addAccountPackage: (
90
+ accountPackage: Omit<AccountPackage, "account_package_id">,
91
+ ): AccountPackage => {
92
+ const newAccountPackage = {
93
+ account_package_id: `ap_${get().idCounter + 1}`,
94
+ ...accountPackage,
95
+ }
96
+ set((state) => {
97
+ return {
98
+ accountPackages: [...state.accountPackages, newAccountPackage],
99
+ idCounter: state.idCounter + 1,
100
+ }
101
+ })
102
+ return newAccountPackage
103
+ },
104
+ getAccountPackageById: (
105
+ accountPackageId: string,
106
+ ): AccountPackage | undefined => {
107
+ const state = get()
108
+ return state.accountPackages.find(
109
+ (ap) => ap.account_package_id === accountPackageId,
110
+ )
111
+ },
112
+ updateAccountPackage: (
113
+ accountPackageId: string,
114
+ updates: Partial<AccountPackage>,
115
+ ): void => {
116
+ set((state) => ({
117
+ accountPackages: state.accountPackages.map((ap) =>
118
+ ap.account_package_id === accountPackageId ? { ...ap, ...updates } : ap,
119
+ ),
120
+ }))
121
+ },
122
+ deleteAccountPackage: (accountPackageId: string): boolean => {
123
+ let deleted = false
124
+ set((state) => {
125
+ const index = state.accountPackages.findIndex(
126
+ (ap) => ap.account_package_id === accountPackageId,
127
+ )
128
+ if (index !== -1) {
129
+ state.accountPackages.splice(index, 1)
130
+ deleted = true
131
+ }
132
+ return state
133
+ })
134
+ return deleted
135
+ },
90
136
  addSnippet: (
91
137
  snippet: Omit<
92
138
  z.input<typeof snippetSchema>,
93
139
  "snippet_id" | "package_release_id"
94
140
  >,
95
141
  ): Snippet => {
96
- const newSnippetId = `snippet_${get().idCounter + 1}`
97
- const newPackageRelease = packageReleaseSchema.parse({
98
- package_release_id: `package_release_${get().idCounter + 1}`,
99
- package_id: newSnippetId,
142
+ const timestamp = Date.now()
143
+ const currentTime = new Date(timestamp).toISOString()
144
+
145
+ const newState = get()
146
+ const nextId = newState.idCounter + 1
147
+
148
+ // Create the package that will serve as our snippet
149
+ const newPackage = {
150
+ package_id: `pkg_${nextId}`,
151
+ creator_account_id: snippet.owner_name, // Using owner_name as account_id since we don't have context
152
+ owner_org_id: "", // Empty string instead of null to match type
153
+ owner_github_username: snippet.owner_name,
154
+ is_source_from_github: false,
155
+ description: snippet.description || "",
156
+ name: `${snippet.owner_name}/${snippet.unscoped_name}`,
157
+ unscoped_name: snippet.unscoped_name,
158
+ latest_version: "0.0.1",
159
+ license: null,
160
+ star_count: 0,
161
+ created_at: currentTime,
162
+ updated_at: currentTime,
163
+ ai_description: null,
164
+ is_snippet: true,
165
+ is_board: snippet.snippet_type === "board",
166
+ is_package: snippet.snippet_type === "package",
167
+ is_model: snippet.snippet_type === "model",
168
+ is_footprint: snippet.snippet_type === "footprint",
169
+ snippet_type: snippet.snippet_type,
170
+ is_private: false,
171
+ is_public: true,
172
+ is_unlisted: false,
173
+ latest_package_release_id: `package_release_${nextId}`,
174
+ }
175
+
176
+ // Create package release
177
+ const newPackageRelease = {
178
+ package_release_id: `package_release_${nextId}`,
179
+ package_id: newPackage.package_id,
100
180
  version: "0.0.1",
101
- is_locked: false,
102
181
  is_latest: true,
103
- created_at: new Date().toISOString(),
104
- updated_at: new Date().toISOString(),
105
- })
106
- const newSnippet = snippetSchema.parse({
107
- ...snippet,
108
- snippet_id: newSnippetId,
182
+ is_locked: false,
183
+ created_at: currentTime,
184
+ updated_at: currentTime,
185
+ }
186
+
187
+ // Add all the files
188
+ const packageFiles: PackageFile[] = []
189
+ let fileIdCounter = nextId
190
+
191
+ // Add main code file
192
+ packageFiles.push({
193
+ package_file_id: `package_file_${fileIdCounter++}`,
109
194
  package_release_id: newPackageRelease.package_release_id,
195
+ file_path: "index.tsx",
196
+ content_text: snippet.code || "",
197
+ created_at: currentTime,
110
198
  })
111
- set((state) => {
112
- return {
113
- snippets: [...state.snippets, newSnippet],
114
- packageReleases: [...state.packageReleases, newPackageRelease],
115
- idCounter: state.idCounter + 2,
116
- }
117
- })
118
- return { ...newSnippet, snippet_id: newSnippetId }
199
+
200
+ // Add DTS file if provided
201
+ if (snippet.dts) {
202
+ packageFiles.push({
203
+ package_file_id: `package_file_${fileIdCounter++}`,
204
+ package_release_id: newPackageRelease.package_release_id,
205
+ file_path: "/dist/index.d.ts",
206
+ content_text: snippet.dts,
207
+ created_at: currentTime,
208
+ })
209
+ }
210
+
211
+ // Add compiled JS if provided
212
+ if (snippet.compiled_js) {
213
+ packageFiles.push({
214
+ package_file_id: `package_file_${fileIdCounter++}`,
215
+ package_release_id: newPackageRelease.package_release_id,
216
+ file_path: "/dist/index.js",
217
+ content_text: snippet.compiled_js,
218
+ created_at: currentTime,
219
+ })
220
+ }
221
+
222
+ // Add circuit JSON if provided
223
+ if (snippet.circuit_json && snippet.circuit_json.length > 0) {
224
+ packageFiles.push({
225
+ package_file_id: `package_file_${fileIdCounter++}`,
226
+ package_release_id: newPackageRelease.package_release_id,
227
+ file_path: "/dist/circuit.json",
228
+ content_text: JSON.stringify(snippet.circuit_json),
229
+ created_at: currentTime,
230
+ })
231
+ }
232
+
233
+ // Update the database state atomically
234
+ set((state) => ({
235
+ ...state,
236
+ packages: [...state.packages, newPackage],
237
+ packageReleases: [...state.packageReleases, newPackageRelease],
238
+ packageFiles: [...state.packageFiles, ...packageFiles],
239
+ idCounter: fileIdCounter,
240
+ }))
241
+
242
+ // Return in the same format as create endpoint
243
+ return {
244
+ snippet_id: newPackage.package_id,
245
+ package_release_id: newPackageRelease.package_release_id,
246
+ name: newPackage.name,
247
+ unscoped_name: newPackage.unscoped_name,
248
+ owner_name: snippet.owner_name,
249
+ code: snippet.code || "",
250
+ dts: snippet.dts,
251
+ compiled_js: snippet.compiled_js,
252
+ star_count: 0,
253
+ created_at: currentTime,
254
+ updated_at: currentTime,
255
+ snippet_type: snippet.snippet_type,
256
+ circuit_json: snippet.circuit_json || [],
257
+ description: snippet.description || "",
258
+ is_starred: false,
259
+ version: "0.0.1",
260
+ }
119
261
  },
120
262
  getNewestSnippets: (limit: number): Snippet[] => {
121
263
  const state = get()
122
- return [...state.snippets]
264
+
265
+ // Get all packages that are snippets
266
+ const snippetPackages = state.packages
267
+ .filter((pkg) => pkg.is_snippet === true)
268
+ .map((pkg) => {
269
+ // Get the package release
270
+ const packageRelease = state.packageReleases.find(
271
+ (pr) =>
272
+ pr.package_release_id === pkg.latest_package_release_id &&
273
+ pr.is_latest === true,
274
+ )
275
+ if (!packageRelease) return null
276
+
277
+ // Get the package files
278
+ const packageFiles = state.packageFiles.filter(
279
+ (file) =>
280
+ file.package_release_id === packageRelease.package_release_id,
281
+ )
282
+
283
+ // Get the code file
284
+ const codeFile = packageFiles.find(
285
+ (file) =>
286
+ file.file_path === "index.ts" || file.file_path === "index.tsx",
287
+ )
288
+
289
+ // Check if starred
290
+ const isStarred = state.accountPackages.some(
291
+ (ap) => ap.package_id === pkg.package_id && ap.is_starred,
292
+ )
293
+
294
+ // Convert to snippet format
295
+ return {
296
+ snippet_id: pkg.package_id,
297
+ package_release_id: pkg.latest_package_release_id || "",
298
+ unscoped_name: pkg.unscoped_name,
299
+ name: pkg.name,
300
+ owner_name: pkg.owner_github_username || "",
301
+ description: pkg.description || "",
302
+ snippet_type: pkg.snippet_type || "board",
303
+ code: codeFile?.content_text || "",
304
+ dts:
305
+ packageFiles.find((file) => file.file_path === "/dist/index.d.ts")
306
+ ?.content_text || "",
307
+ compiled_js:
308
+ packageFiles.find((file) => file.file_path === "/dist/index.js")
309
+ ?.content_text || "",
310
+ created_at: pkg.created_at,
311
+ updated_at: pkg.updated_at,
312
+ star_count: pkg.star_count || 0,
313
+ is_starred: isStarred,
314
+ version: pkg.latest_version || "0.0.1",
315
+ circuit_json:
316
+ packageFiles
317
+ .filter((file) => file.file_path === "/dist/circuit.json")
318
+ .flatMap((file) => JSON.parse(file.content_text || "[]")) || [],
319
+ }
320
+ })
321
+ .filter(
322
+ (snippet): snippet is NonNullable<typeof snippet> => snippet !== null,
323
+ )
123
324
  .map((snippet) => ({
124
325
  ...snippet,
125
- star_count: state.accountSnippets.filter(
126
- (as) => as.snippet_id === snippet.snippet_id && as.has_starred,
127
- ).length,
326
+ description: snippet.description || "",
128
327
  }))
129
328
  .sort(
130
329
  (a, b) =>
131
330
  new Date(b.created_at).getTime() - new Date(a.created_at).getTime(),
132
331
  )
133
332
  .slice(0, limit)
333
+
334
+ return snippetPackages
134
335
  },
135
336
  getTrendingSnippets: (limit: number, since: string): Snippet[] => {
136
337
  const state = get()
@@ -138,32 +339,68 @@ const initializer = combine(databaseSchema.parse({}), (set, get) => ({
138
339
 
139
340
  // Get star counts within time period
140
341
  const recentStars = new Map<string, number>()
141
- state.accountSnippets.forEach((as) => {
142
- if (as.has_starred && new Date(as.created_at).getTime() >= sinceDate) {
342
+ state.accountPackages.forEach((ap) => {
343
+ if (ap.is_starred && new Date(ap.created_at).getTime() >= sinceDate) {
143
344
  recentStars.set(
144
- as.snippet_id,
145
- (recentStars.get(as.snippet_id) || 0) + 1,
345
+ ap.package_id,
346
+ (recentStars.get(ap.package_id) || 0) + 1,
146
347
  )
147
348
  }
148
349
  })
149
350
 
150
- return [...state.snippets]
151
- .map((snippet) => ({
152
- ...snippet,
153
- star_count: recentStars.get(snippet.snippet_id) || 0,
351
+ const packagesWithStarCount = [...state.packages]
352
+ .map((pkg) => ({
353
+ ...pkg,
354
+ star_count: recentStars.get(pkg.package_id) || 0,
154
355
  }))
155
356
  .sort((a, b) => b.star_count - a.star_count)
156
357
  .slice(0, limit)
358
+
359
+ const trendingSnippets = packagesWithStarCount
360
+ .map((pkg) => {
361
+ const packageRelease = state.packageReleases.find(
362
+ (pr) =>
363
+ pr.package_release_id === pkg.latest_package_release_id &&
364
+ pr.is_latest === true,
365
+ )
366
+ if (!packageRelease) return null
367
+
368
+ const packageFiles = state.packageFiles.filter(
369
+ (file) =>
370
+ file.package_release_id === packageRelease.package_release_id,
371
+ )
372
+
373
+ const codeFile = packageFiles.find(
374
+ (file) =>
375
+ file.file_path === "index.ts" || file.file_path === "index.tsx",
376
+ )
377
+
378
+ return {
379
+ snippet_id: pkg.package_id,
380
+ package_release_id: pkg.latest_package_release_id || "",
381
+ unscoped_name: pkg.unscoped_name,
382
+ name: pkg.name,
383
+ owner_name: pkg.owner_github_username || "",
384
+ code: codeFile ? codeFile.content_text || "" : "",
385
+ created_at: pkg.created_at,
386
+ updated_at: pkg.updated_at,
387
+ snippet_type: pkg.is_snippet ? "board" : "package",
388
+ star_count: pkg.star_count,
389
+ }
390
+ })
391
+ .filter((snippet) => snippet !== null)
392
+
393
+ return trendingSnippets as Snippet[]
157
394
  },
158
- getSnippetsByAuthor: (authorName?: string): Snippet[] => {
395
+ getPackagesByAuthor: (authorName?: string): Package[] => {
159
396
  const state = get()
160
- const snippets = authorName
161
- ? state.snippets.filter((snippet) => snippet.owner_name === authorName)
162
- : state.snippets
163
- return snippets.map((snippet) => ({
164
- ...snippet,
165
- star_count: state.accountSnippets.filter(
166
- (as) => as.snippet_id === snippet.snippet_id && as.has_starred,
397
+ const packages = authorName
398
+ ? state.packages.filter((pkg) => pkg.owner_github_username === authorName)
399
+ : state.packages
400
+ return packages.map((pkg) => ({
401
+ ...pkg,
402
+ star_count: state.accountPackages.filter(
403
+ (ap) => ap.package_id === pkg.package_id && ap.is_starred,
167
404
  ).length,
168
405
  }))
169
406
  },
@@ -172,63 +409,393 @@ const initializer = combine(databaseSchema.parse({}), (set, get) => ({
172
409
  snippetName: string,
173
410
  ): Snippet | undefined => {
174
411
  const state = get()
175
- return state.snippets.find(
176
- (snippet) =>
177
- snippet.owner_name === authorName && snippet.name === snippetName,
412
+ // Look for the package that represents this snippet with case-insensitive matching
413
+ const _package = state.packages.find(
414
+ (pkg) =>
415
+ pkg.owner_github_username?.toLowerCase() === authorName.toLowerCase() &&
416
+ pkg.name.toLowerCase() === snippetName.toLowerCase() &&
417
+ pkg.is_snippet === true,
418
+ )
419
+ if (!_package) return undefined
420
+
421
+ // Get the package release
422
+ const packageRelease = state.packageReleases.find(
423
+ (pr) =>
424
+ pr.package_release_id === _package.latest_package_release_id &&
425
+ pr.is_latest === true,
426
+ )
427
+ if (!packageRelease) return undefined
428
+
429
+ // Get the package files
430
+ const packageFiles = state.packageFiles.filter(
431
+ (file) => file.package_release_id === packageRelease.package_release_id,
432
+ )
433
+
434
+ // Get the code file (index.ts or index.tsx)
435
+ const codeFile = packageFiles.find(
436
+ (file) => file.file_path === "index.ts" || file.file_path === "index.tsx",
178
437
  )
438
+
439
+ // Map the package data to match the Snippet structure
440
+ return {
441
+ snippet_id: _package.package_id,
442
+ package_release_id: _package.latest_package_release_id || "",
443
+ unscoped_name: _package.unscoped_name,
444
+ name: _package.name,
445
+ owner_name: _package.owner_github_username || "",
446
+ description: _package.description || "",
447
+ snippet_type: _package.snippet_type || "board",
448
+ code: codeFile?.content_text || "",
449
+ dts:
450
+ packageFiles.find((file) => file.file_path === "/dist/index.d.ts")
451
+ ?.content_text || "",
452
+ compiled_js:
453
+ packageFiles.find((file) => file.file_path === "/dist/index.js")
454
+ ?.content_text || "",
455
+ created_at: _package.created_at,
456
+ updated_at: _package.updated_at,
457
+ star_count: _package.star_count || 0,
458
+ is_starred: false,
459
+ version: _package.latest_version || "0.0.1",
460
+ circuit_json: packageFiles.find(
461
+ (file) => file.file_path === "/dist/circuit.json",
462
+ )?.content_text
463
+ ? JSON.parse(
464
+ packageFiles.find((file) => file.file_path === "/dist/circuit.json")
465
+ ?.content_text || "[]",
466
+ )
467
+ : [],
468
+ }
179
469
  },
180
470
  updateSnippet: (
181
471
  snippetId: string,
182
472
  updates: Partial<Snippet>,
183
473
  ): Snippet | undefined => {
184
- let updatedSnippet: Snippet | undefined
185
474
  set((state) => {
186
- const snippetIndex = state.snippets.findIndex(
187
- (snippet) => snippet.snippet_id === snippetId,
475
+ const packageIndex = state.packages.findIndex(
476
+ (pkg) => pkg.package_id === snippetId && pkg.is_snippet === true,
188
477
  )
189
- if (snippetIndex === -1) {
478
+ if (packageIndex === -1) {
190
479
  return state
191
480
  }
192
- const updatedSnippets = [...state.snippets]
193
- updatedSnippet = {
194
- ...updatedSnippets[snippetIndex],
195
- ...updates,
196
- updated_at: updates.updated_at || new Date().toISOString(),
481
+
482
+ const timestamp = Date.now()
483
+ const currentTime = new Date(timestamp).toISOString()
484
+
485
+ // Update the package
486
+ const updatedPackages = [...state.packages]
487
+ const currentPackage = updatedPackages[packageIndex]
488
+ updatedPackages[packageIndex] = {
489
+ ...currentPackage,
490
+ description: updates.description || currentPackage.description,
491
+ updated_at: currentTime,
492
+ }
493
+
494
+ // Get the current package release
495
+ const packageRelease = state.packageReleases.find(
496
+ (pr) =>
497
+ pr.package_release_id === currentPackage.latest_package_release_id,
498
+ )
499
+ if (!packageRelease) return state
500
+
501
+ // Update package files if code/dts/js changed
502
+ const updatedFiles = [...state.packageFiles]
503
+ const packageFiles = updatedFiles.filter(
504
+ (file) => file.package_release_id === packageRelease.package_release_id,
505
+ )
506
+
507
+ if (updates.code !== undefined) {
508
+ const codeFileIndex = packageFiles.findIndex(
509
+ (file) =>
510
+ file.file_path === "index.tsx" || file.file_path === "index.ts",
511
+ )
512
+ if (codeFileIndex >= 0) {
513
+ updatedFiles[codeFileIndex] = {
514
+ ...packageFiles[codeFileIndex],
515
+ content_text: updates.code,
516
+ created_at: currentTime,
517
+ }
518
+ } else {
519
+ updatedFiles.push({
520
+ package_file_id: `package_file_${timestamp}`,
521
+ package_release_id: packageRelease.package_release_id,
522
+ file_path: "index.tsx",
523
+ content_text: updates.code,
524
+ created_at: currentTime,
525
+ })
526
+ }
527
+ }
528
+
529
+ if (updates.dts !== undefined) {
530
+ const dtsFileIndex = packageFiles.findIndex(
531
+ (file) => file.file_path === "/dist/index.d.ts",
532
+ )
533
+ if (dtsFileIndex >= 0) {
534
+ updatedFiles[dtsFileIndex] = {
535
+ ...packageFiles[dtsFileIndex],
536
+ content_text: updates.dts,
537
+ created_at: currentTime,
538
+ }
539
+ } else {
540
+ updatedFiles.push({
541
+ package_file_id: `package_file_${timestamp}`,
542
+ package_release_id: packageRelease.package_release_id,
543
+ file_path: "/dist/index.d.ts",
544
+ content_text: updates.dts,
545
+ created_at: currentTime,
546
+ })
547
+ }
548
+ }
549
+
550
+ if (updates.compiled_js !== undefined) {
551
+ const jsFileIndex = packageFiles.findIndex(
552
+ (file) => file.file_path === "/dist/index.js",
553
+ )
554
+ if (jsFileIndex >= 0) {
555
+ updatedFiles[jsFileIndex] = {
556
+ ...packageFiles[jsFileIndex],
557
+ content_text: updates.compiled_js,
558
+ created_at: currentTime,
559
+ }
560
+ } else {
561
+ updatedFiles.push({
562
+ package_file_id: `package_file_${timestamp}`,
563
+ package_release_id: packageRelease.package_release_id,
564
+ file_path: "/dist/index.js",
565
+ content_text: updates.compiled_js,
566
+ created_at: currentTime,
567
+ })
568
+ }
569
+ }
570
+
571
+ // Update circuit JSON if provided
572
+ if (updates.circuit_json !== undefined) {
573
+ const circuitFileIndex = packageFiles.findIndex(
574
+ (file) => file.file_path === "/dist/circuit.json",
575
+ )
576
+ if (circuitFileIndex >= 0) {
577
+ updatedFiles[circuitFileIndex] = {
578
+ ...packageFiles[circuitFileIndex],
579
+ content_text: JSON.stringify(updates.circuit_json),
580
+ created_at: new Date().toISOString(),
581
+ }
582
+ }
583
+ }
584
+
585
+ // Return updated state
586
+ return {
587
+ ...state,
588
+ packages: updatedPackages,
589
+ packageFiles: updatedFiles,
197
590
  }
198
- updatedSnippets[snippetIndex] = updatedSnippet
199
- return { ...state, snippets: updatedSnippets }
200
591
  })
201
- return updatedSnippet
592
+
593
+ // Get the updated snippet to return
594
+ const updatedPackage = get().packages.find(
595
+ (pkg) => pkg.package_id === snippetId,
596
+ )
597
+ if (!updatedPackage) return undefined
598
+
599
+ const packageRelease = get().packageReleases.find(
600
+ (pr) =>
601
+ pr.package_release_id === updatedPackage.latest_package_release_id,
602
+ )
603
+ if (!packageRelease) return undefined
604
+
605
+ const packageFiles = get().packageFiles.filter(
606
+ (file) => file.package_release_id === packageRelease.package_release_id,
607
+ )
608
+
609
+ const codeFile = packageFiles.find(
610
+ (file) => file.file_path === "index.ts" || file.file_path === "index.tsx",
611
+ )
612
+ const dtsFile = packageFiles.find(
613
+ (file) => file.file_path === "/dist/index.d.ts",
614
+ )
615
+ const jsFile = packageFiles.find(
616
+ (file) => file.file_path === "/dist/index.js",
617
+ )
618
+ const circuitFile = packageFiles.find(
619
+ (file) => file.file_path === "/dist/circuit.json",
620
+ )
621
+
622
+ // Return in snippet format
623
+ return {
624
+ snippet_id: updatedPackage.package_id,
625
+ package_release_id: updatedPackage.latest_package_release_id || "",
626
+ unscoped_name: updatedPackage.unscoped_name,
627
+ name: updatedPackage.name,
628
+ owner_name: updatedPackage.owner_github_username || "",
629
+ description: updatedPackage.description || "",
630
+ snippet_type: updatedPackage.snippet_type || "board",
631
+ code: codeFile?.content_text || "",
632
+ dts: dtsFile?.content_text || "",
633
+ compiled_js: jsFile?.content_text || "",
634
+ created_at: updatedPackage.created_at,
635
+ updated_at: updatedPackage.updated_at,
636
+ star_count: updatedPackage.star_count || 0,
637
+ is_starred: false,
638
+ version: updatedPackage.latest_version || "0.0.1",
639
+ circuit_json: circuitFile
640
+ ? JSON.parse(circuitFile.content_text || "[]")
641
+ : [],
642
+ }
202
643
  },
203
644
  getSnippetById: (snippetId: string): Snippet | undefined => {
204
645
  const state = get()
205
- const snippet = state.snippets.find(
206
- (snippet) => snippet.snippet_id === snippetId,
646
+ // Look for the package that represents this snippet
647
+ const _package = state.packages.find(
648
+ (pkg) => pkg.package_id === snippetId && pkg.is_snippet === true,
207
649
  )
208
- if (!snippet) return undefined
650
+ if (!_package) return undefined
651
+
652
+ // Get the package release
653
+ const packageRelease = state.packageReleases.find(
654
+ (pr) =>
655
+ pr.package_release_id === _package.latest_package_release_id &&
656
+ pr.is_latest === true,
657
+ )
658
+ if (!packageRelease) return undefined
659
+
660
+ // Get the package files
661
+ const packageFiles = state.packageFiles.filter(
662
+ (file) => file.package_release_id === packageRelease.package_release_id,
663
+ )
664
+
665
+ // Get the code file (index.ts or index.tsx)
666
+ const codeFile = packageFiles.find(
667
+ (file) => file.file_path === "index.ts" || file.file_path === "index.tsx",
668
+ )
669
+
670
+ // Check if the current user has starred this snippet
671
+ const isStarred = state.accountPackages.some(
672
+ (ap) => ap.package_id === snippetId && ap.is_starred,
673
+ )
674
+
675
+ // Map the package data to match the Snippet structure
209
676
  return {
210
- ...snippet,
211
- star_count: state.accountSnippets.filter(
212
- (as) => as.snippet_id === snippetId && as.has_starred,
213
- ).length,
677
+ snippet_id: _package.package_id,
678
+ package_release_id: _package.latest_package_release_id || "",
679
+ unscoped_name: _package.unscoped_name,
680
+ name: _package.name,
681
+ owner_name: _package.owner_github_username || "",
682
+ description: _package.description || "",
683
+ snippet_type: _package.snippet_type || "board",
684
+ code: codeFile?.content_text || "",
685
+ dts:
686
+ packageFiles.find((file) => file.file_path === "/dist/index.d.ts")
687
+ ?.content_text || "",
688
+ compiled_js:
689
+ packageFiles.find((file) => file.file_path === "/dist/index.js")
690
+ ?.content_text || "",
691
+ created_at: _package.created_at,
692
+ updated_at: _package.updated_at,
693
+ star_count: _package.star_count || 0,
694
+ is_starred: isStarred,
695
+ version: _package.latest_version || "0.0.1",
696
+ circuit_json: packageFiles.find(
697
+ (file) => file.file_path === "/dist/circuit.json",
698
+ )?.content_text
699
+ ? JSON.parse(
700
+ packageFiles.find((file) => file.file_path === "/dist/circuit.json")
701
+ ?.content_text || "[]",
702
+ )
703
+ : [],
214
704
  }
215
705
  },
216
706
  searchSnippets: (query: string): Snippet[] => {
217
707
  const state = get()
218
708
  const lowercaseQuery = query.toLowerCase()
219
- return state.snippets
220
- .filter(
221
- (snippet) =>
222
- snippet.name.toLowerCase().includes(lowercaseQuery) ||
223
- snippet.description?.toLowerCase().includes(lowercaseQuery) ||
224
- snippet.code.toLowerCase().includes(lowercaseQuery),
225
- )
226
- .map((snippet) => ({
227
- ...snippet,
228
- star_count: state.accountSnippets.filter(
229
- (as) => as.snippet_id === snippet.snippet_id && as.has_starred,
230
- ).length,
231
- }))
709
+
710
+ // Get all packages that are snippets
711
+ const packages = state.packages.filter((pkg) => pkg.is_snippet === true)
712
+
713
+ // Find packages that match by name or description
714
+ const matchingPackagesByMetadata = packages.filter(
715
+ (pkg) =>
716
+ pkg.name.toLowerCase().includes(lowercaseQuery) ||
717
+ pkg.description?.toLowerCase().includes(lowercaseQuery),
718
+ )
719
+
720
+ // Find packages that match by code content in any file
721
+ const matchingFilesByContent = state.packageFiles.filter(
722
+ (file) =>
723
+ file.content_text?.toLowerCase().includes(lowercaseQuery) ?? false,
724
+ )
725
+
726
+ // Get the packages for matching files
727
+ const matchingPackagesByContent = matchingFilesByContent
728
+ .map((file) => {
729
+ // Find the package release for this file
730
+ const packageRelease = state.packageReleases.find(
731
+ (pr) => pr.package_release_id === file.package_release_id,
732
+ )
733
+ if (!packageRelease) return null
734
+
735
+ // Find the package for this release
736
+ return packages.find(
737
+ (pkg) =>
738
+ pkg.latest_package_release_id === packageRelease.package_release_id,
739
+ )
740
+ })
741
+ .filter((pkg): pkg is NonNullable<typeof pkg> => pkg !== null)
742
+
743
+ // Combine both sets of matching packages and remove duplicates
744
+ const matchingPackages = [
745
+ ...new Set([...matchingPackagesByMetadata, ...matchingPackagesByContent]),
746
+ ]
747
+
748
+ // Convert matching packages to snippet format
749
+ return matchingPackages
750
+ .map((pkg) => {
751
+ const packageRelease = state.packageReleases.find(
752
+ (pr) =>
753
+ pr.package_release_id === pkg.latest_package_release_id &&
754
+ pr.is_latest === true,
755
+ )
756
+ if (!packageRelease) return null
757
+
758
+ const packageFiles = state.packageFiles.filter(
759
+ (file) =>
760
+ file.package_release_id === packageRelease.package_release_id,
761
+ )
762
+
763
+ const codeFile = packageFiles.find(
764
+ (file) =>
765
+ file.file_path === "index.ts" || file.file_path === "index.tsx",
766
+ )
767
+
768
+ const isStarred = state.accountPackages.some(
769
+ (ap) => ap.package_id === pkg.package_id && ap.is_starred,
770
+ )
771
+
772
+ return {
773
+ snippet_id: pkg.package_id,
774
+ package_release_id: pkg.latest_package_release_id || "",
775
+ unscoped_name: pkg.unscoped_name,
776
+ name: pkg.name,
777
+ owner_name: pkg.owner_github_username || "",
778
+ description: pkg.description || "",
779
+ snippet_type: pkg.snippet_type || "board",
780
+ code: codeFile?.content_text || "",
781
+ dts:
782
+ packageFiles.find((file) => file.file_path === "/dist/index.d.ts")
783
+ ?.content_text || "",
784
+ compiled_js:
785
+ packageFiles.find((file) => file.file_path === "/dist/index.js")
786
+ ?.content_text || "",
787
+ created_at: pkg.created_at,
788
+ updated_at: pkg.updated_at,
789
+ star_count: pkg.star_count || 0,
790
+ is_starred: isStarred,
791
+ version: pkg.latest_version || "0.0.1",
792
+ circuit_json:
793
+ packageFiles
794
+ .filter((file) => file.file_path === "/dist/circuit.json")
795
+ .flatMap((file) => JSON.parse(file.content_text || "[]")) || [],
796
+ } as Snippet
797
+ })
798
+ .filter((snippet): snippet is Snippet => snippet !== null)
232
799
  },
233
800
  deleteSnippet: (snippetId: string): boolean => {
234
801
  let deleted = false
@@ -324,58 +891,119 @@ const initializer = combine(databaseSchema.parse({}), (set, get) => ({
324
891
  }))
325
892
  return newSession
326
893
  },
327
- addStar: (accountId: string, snippetId: string): AccountSnippet => {
894
+ addStar: (accountId: string, packageId: string): AccountPackage => {
328
895
  const now = new Date().toISOString()
329
- const accountSnippet = {
896
+ const accountPackage = {
897
+ account_package_id: `ap_${Date.now()}`,
330
898
  account_id: accountId,
331
- snippet_id: snippetId,
332
- has_starred: true,
899
+ package_id: packageId,
900
+ is_starred: true,
333
901
  created_at: now,
334
902
  updated_at: now,
335
903
  }
336
- set((state) => ({
337
- accountSnippets: [...state.accountSnippets, accountSnippet],
338
- }))
339
- return accountSnippet
904
+
905
+ // Update the package's star count
906
+ set((state) => {
907
+ // Find the package and increment its star count
908
+ const packageIndex = state.packages.findIndex(
909
+ (pkg) => pkg.package_id === packageId,
910
+ )
911
+ if (packageIndex >= 0) {
912
+ const updatedPackages = [...state.packages]
913
+ updatedPackages[packageIndex] = {
914
+ ...updatedPackages[packageIndex],
915
+ star_count: (updatedPackages[packageIndex].star_count || 0) + 1,
916
+ }
917
+
918
+ return {
919
+ packages: updatedPackages,
920
+ accountPackages: [...state.accountPackages, accountPackage],
921
+ }
922
+ }
923
+
924
+ return {
925
+ accountPackages: [...state.accountPackages, accountPackage],
926
+ }
927
+ })
928
+
929
+ return accountPackage
340
930
  },
341
- removeStar: (accountId: string, snippetId: string): void => {
342
- set((state) => ({
343
- accountSnippets: state.accountSnippets.filter(
344
- (as) => !(as.account_id === accountId && as.snippet_id === snippetId),
345
- ),
346
- }))
931
+ removeStar: (accountId: string, packageId: string): void => {
932
+ set((state) => {
933
+ // Find the package and decrement its star count
934
+ const packageIndex = state.packages.findIndex(
935
+ (pkg) => pkg.package_id === packageId,
936
+ )
937
+
938
+ if (packageIndex >= 0) {
939
+ const updatedPackages = [...state.packages]
940
+ updatedPackages[packageIndex] = {
941
+ ...updatedPackages[packageIndex],
942
+ star_count: Math.max(
943
+ 0,
944
+ (updatedPackages[packageIndex].star_count || 0) - 1,
945
+ ),
946
+ }
947
+
948
+ return {
949
+ packages: updatedPackages,
950
+ accountPackages: state.accountPackages.filter(
951
+ (ap) =>
952
+ !(ap.account_id === accountId && ap.package_id === packageId),
953
+ ),
954
+ }
955
+ }
956
+
957
+ return {
958
+ accountPackages: state.accountPackages.filter(
959
+ (ap) => !(ap.account_id === accountId && ap.package_id === packageId),
960
+ ),
961
+ }
962
+ })
347
963
  },
348
- hasStarred: (accountId: string, snippetId: string): boolean => {
964
+ hasStarred: (accountId: string, packageId: string): boolean => {
349
965
  const state = get()
350
- return state.accountSnippets.some(
351
- (as) =>
352
- as.account_id === accountId &&
353
- as.snippet_id === snippetId &&
354
- as.has_starred,
966
+ return state.accountPackages.some(
967
+ (ap) =>
968
+ ap.account_id === accountId &&
969
+ ap.package_id === packageId &&
970
+ ap.is_starred,
355
971
  )
356
972
  },
357
973
  addPackage: (
358
974
  _package: Omit<z.input<typeof packageSchema>, "package_id">,
359
975
  ): Package => {
976
+ const timestamp = Date.now()
360
977
  const newPackage = {
361
- package_id: `package_${Date.now()}`,
978
+ package_id: `package_${timestamp}`,
362
979
  ..._package,
363
980
  }
364
- const packageRelease = packageReleaseSchema.parse({
365
- package_release_id: `package_release_${Date.now()}`,
366
- package_id: newPackage.package_id,
367
- version: "0.0.1",
368
- is_locked: false,
369
- is_latest: true,
370
- created_at: new Date().toISOString(),
371
- updated_at: new Date().toISOString(),
372
- })
373
- newPackage.latest_package_release_id = packageRelease.package_release_id
374
981
  set((state) => ({
375
982
  packages: [...state.packages, newPackage as Package],
376
983
  }))
377
984
  return newPackage as Package
378
985
  },
986
+ updatePackage: (
987
+ packageId: string,
988
+ updates: Partial<Package>,
989
+ ): Package | undefined => {
990
+ let updatedPackage: Package | undefined
991
+ set((state) => {
992
+ const packageIndex = state.packages.findIndex(
993
+ (pkg) => pkg.package_id === packageId,
994
+ )
995
+ if (packageIndex === -1) return state
996
+
997
+ const updatedPackages = [...state.packages]
998
+ updatedPackages[packageIndex] = {
999
+ ...updatedPackages[packageIndex],
1000
+ ...updates,
1001
+ }
1002
+ updatedPackage = updatedPackages[packageIndex]
1003
+ return { ...state, packages: updatedPackages }
1004
+ })
1005
+ return updatedPackage
1006
+ },
379
1007
  getPackageById: (packageId: string): Package | undefined => {
380
1008
  const state = get()
381
1009
  const pkg = state.packages.find((pkg) => pkg.package_id === packageId)
@@ -425,4 +1053,16 @@ const initializer = combine(databaseSchema.parse({}), (set, get) => ({
425
1053
  }))
426
1054
  return newPackageFile
427
1055
  },
1056
+ getStarCount: (packageId: string): number => {
1057
+ const state = get()
1058
+ return state.accountPackages.filter(
1059
+ (ap) => ap.package_id === packageId && ap.is_starred,
1060
+ ).length
1061
+ },
1062
+ getPackageFilesByReleaseId: (packageReleaseId: string): PackageFile[] => {
1063
+ const state = get()
1064
+ return state.packageFiles.filter(
1065
+ (pf) => pf.package_release_id === packageReleaseId,
1066
+ )
1067
+ },
428
1068
  }))