@tscircuit/fake-snippets 0.0.7 → 0.0.9

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 (37) hide show
  1. package/bun-tests/fake-snippets-api/fixtures/get-test-server.ts +2 -5
  2. package/bun-tests/fake-snippets-api/routes/packages/{list.test.ts → list-1.test.ts} +0 -55
  3. package/bun-tests/fake-snippets-api/routes/packages/list-2.test.ts +59 -0
  4. package/bun-tests/fake-snippets-api/routes/snippets/add_star.test.ts +32 -27
  5. package/bun-tests/fake-snippets-api/routes/snippets/create.test.ts +34 -1
  6. package/bun-tests/fake-snippets-api/routes/snippets/get.test.ts +114 -0
  7. package/bun-tests/fake-snippets-api/routes/snippets/get_image.test.ts +10 -6
  8. package/bun-tests/fake-snippets-api/routes/snippets/images.test.ts +8 -6
  9. package/bun-tests/fake-snippets-api/routes/snippets/list_newest.test.ts +2 -2
  10. package/bun-tests/fake-snippets-api/routes/snippets/list_trending.test.ts +10 -10
  11. package/bun-tests/fake-snippets-api/routes/snippets/remove_star.test.ts +8 -6
  12. package/bun-tests/fake-snippets-api/routes/snippets/search.test.ts +1 -1
  13. package/bun-tests/fake-snippets-api/routes/snippets/star-count.test.ts +19 -12
  14. package/bun-tests/fake-snippets-api/routes/snippets/update.test.ts +57 -16
  15. package/bun.lock +148 -569
  16. package/dist/bundle.js +844 -194
  17. package/fake-snippets-api/lib/db/db-client.ts +761 -147
  18. package/fake-snippets-api/lib/db/schema.ts +27 -6
  19. package/fake-snippets-api/lib/public-mapping/public-map-package.ts +8 -0
  20. package/fake-snippets-api/routes/api/packages/list.ts +4 -1
  21. package/fake-snippets-api/routes/api/snippets/add_star.ts +30 -8
  22. package/fake-snippets-api/routes/api/snippets/create.ts +123 -29
  23. package/fake-snippets-api/routes/api/snippets/delete.ts +5 -5
  24. package/fake-snippets-api/routes/api/snippets/download.ts +24 -10
  25. package/fake-snippets-api/routes/api/snippets/get.ts +46 -13
  26. package/fake-snippets-api/routes/api/snippets/list.ts +37 -2
  27. package/fake-snippets-api/routes/api/snippets/update.ts +36 -14
  28. package/package.json +4 -5
  29. package/src/components/CodeAndPreview.tsx +13 -48
  30. package/src/components/CodeEditor.tsx +10 -7
  31. package/src/components/EditorNav.tsx +0 -21
  32. package/src/components/PreviewContent.tsx +2 -2
  33. package/src/components/ViewSnippetHeader.tsx +4 -0
  34. package/src/hooks/use-global-store.ts +0 -5
  35. package/src/hooks/use-package-as-snippet.ts +78 -0
  36. package/src/hooks/use-run-tsx/index.tsx +4 -0
  37. package/src/lib/jlc-parts-engine.ts +4 -2
@@ -1,26 +1,23 @@
1
- import { createStore, type StoreApi } from "zustand/vanilla"
2
- import { immer } from "zustand/middleware/immer"
3
- import { hoist, type HoistedStoreApi } from "zustand-hoist"
4
- import { z } from "zod"
1
+ import type { z } from "zod"
2
+ import { hoist } from "zustand-hoist"
3
+ import { createStore } from "zustand/vanilla"
5
4
 
5
+ import { combine } from "zustand/middleware"
6
6
  import {
7
+ type Account,
8
+ type AccountPackage,
9
+ type LoginPage,
10
+ type Order,
11
+ type OrderFile,
12
+ type Package,
13
+ type PackageFile,
14
+ type PackageRelease,
15
+ type Session,
16
+ type Snippet,
7
17
  databaseSchema,
8
- Snippet,
9
- Session,
10
- LoginPage,
11
- Account,
12
- type DatabaseSchema,
13
- snippetSchema,
14
- Order,
15
- OrderFile,
16
- AccountSnippet,
17
- packageReleaseSchema,
18
- packageSchema,
19
- Package,
20
- PackageRelease,
21
- PackageFile,
18
+ type packageSchema,
19
+ type snippetSchema,
22
20
  } from "./schema.ts"
23
- import { combine } from "zustand/middleware"
24
21
  import { seed as seedFn } from "./seed"
25
22
 
26
23
  export const createDatabase = ({ seed }: { seed?: boolean } = {}) => {
@@ -35,7 +32,7 @@ export type DbClient = ReturnType<typeof createDatabase>
35
32
 
36
33
  const initializer = combine(databaseSchema.parse({}), (set, get) => ({
37
34
  addOrder: (order: Omit<Order, "order_id">): Order => {
38
- let newOrder = { order_id: `order_${get().idCounter + 1}`, ...order }
35
+ const newOrder = { order_id: `order_${get().idCounter + 1}`, ...order }
39
36
  set((state) => {
40
37
  return {
41
38
  orders: [...state.orders, newOrder],
@@ -89,50 +86,252 @@ const initializer = combine(databaseSchema.parse({}), (set, get) => ({
89
86
 
90
87
  return newAccount
91
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
+ },
92
136
  addSnippet: (
93
137
  snippet: Omit<
94
138
  z.input<typeof snippetSchema>,
95
139
  "snippet_id" | "package_release_id"
96
140
  >,
97
141
  ): Snippet => {
98
- const newSnippetId = `snippet_${get().idCounter + 1}`
99
- const newPackageRelease = packageReleaseSchema.parse({
100
- package_release_id: `package_release_${get().idCounter + 1}`,
101
- 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,
102
180
  version: "0.0.1",
103
- is_locked: false,
104
181
  is_latest: true,
105
- created_at: new Date().toISOString(),
106
- updated_at: new Date().toISOString(),
107
- })
108
- const newSnippet = snippetSchema.parse({
109
- ...snippet,
110
- 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++}`,
111
194
  package_release_id: newPackageRelease.package_release_id,
195
+ file_path: "index.tsx",
196
+ content_text: snippet.code || "",
197
+ created_at: currentTime,
112
198
  })
113
- set((state) => {
114
- return {
115
- snippets: [...state.snippets, newSnippet],
116
- packageReleases: [...state.packageReleases, newPackageRelease],
117
- idCounter: state.idCounter + 2,
118
- }
119
- })
120
- 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
+ }
121
261
  },
122
262
  getNewestSnippets: (limit: number): Snippet[] => {
123
263
  const state = get()
124
- 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
+ )
125
324
  .map((snippet) => ({
126
325
  ...snippet,
127
- star_count: state.accountSnippets.filter(
128
- (as) => as.snippet_id === snippet.snippet_id && as.has_starred,
129
- ).length,
326
+ description: snippet.description || "",
130
327
  }))
131
328
  .sort(
132
329
  (a, b) =>
133
330
  new Date(b.created_at).getTime() - new Date(a.created_at).getTime(),
134
331
  )
135
332
  .slice(0, limit)
333
+
334
+ return snippetPackages
136
335
  },
137
336
  getTrendingSnippets: (limit: number, since: string): Snippet[] => {
138
337
  const state = get()
@@ -140,32 +339,68 @@ const initializer = combine(databaseSchema.parse({}), (set, get) => ({
140
339
 
141
340
  // Get star counts within time period
142
341
  const recentStars = new Map<string, number>()
143
- state.accountSnippets.forEach((as) => {
144
- 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) {
145
344
  recentStars.set(
146
- as.snippet_id,
147
- (recentStars.get(as.snippet_id) || 0) + 1,
345
+ ap.package_id,
346
+ (recentStars.get(ap.package_id) || 0) + 1,
148
347
  )
149
348
  }
150
349
  })
151
350
 
152
- return [...state.snippets]
153
- .map((snippet) => ({
154
- ...snippet,
155
- 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,
156
355
  }))
157
356
  .sort((a, b) => b.star_count - a.star_count)
158
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[]
159
394
  },
160
- getSnippetsByAuthor: (authorName?: string): Snippet[] => {
395
+ getPackagesByAuthor: (authorName?: string): Package[] => {
161
396
  const state = get()
162
- const snippets = authorName
163
- ? state.snippets.filter((snippet) => snippet.owner_name === authorName)
164
- : state.snippets
165
- return snippets.map((snippet) => ({
166
- ...snippet,
167
- star_count: state.accountSnippets.filter(
168
- (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,
169
404
  ).length,
170
405
  }))
171
406
  },
@@ -174,68 +409,398 @@ const initializer = combine(databaseSchema.parse({}), (set, get) => ({
174
409
  snippetName: string,
175
410
  ): Snippet | undefined => {
176
411
  const state = get()
177
- return state.snippets.find(
178
- (snippet) =>
179
- 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,
180
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",
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
+ }
181
469
  },
182
470
  updateSnippet: (
183
- snippet_id: string,
471
+ snippetId: string,
184
472
  updates: Partial<Snippet>,
185
473
  ): Snippet | undefined => {
186
- let updatedSnippet: Snippet | undefined
187
474
  set((state) => {
188
- const snippetIndex = state.snippets.findIndex(
189
- (snippet) => snippet.snippet_id === snippet_id,
475
+ const packageIndex = state.packages.findIndex(
476
+ (pkg) => pkg.package_id === snippetId && pkg.is_snippet === true,
190
477
  )
191
- if (snippetIndex === -1) {
478
+ if (packageIndex === -1) {
192
479
  return state
193
480
  }
194
- const updatedSnippets = [...state.snippets]
195
- updatedSnippet = {
196
- ...updatedSnippets[snippetIndex],
197
- ...updates,
198
- 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,
199
590
  }
200
- updatedSnippets[snippetIndex] = updatedSnippet
201
- return { ...state, snippets: updatedSnippets }
202
591
  })
203
- 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
+ }
204
643
  },
205
- getSnippetById: (snippet_id: string): Snippet | undefined => {
644
+ getSnippetById: (snippetId: string): Snippet | undefined => {
206
645
  const state = get()
207
- const snippet = state.snippets.find(
208
- (snippet) => snippet.snippet_id === snippet_id,
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,
649
+ )
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",
209
668
  )
210
- if (!snippet) return undefined
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
211
676
  return {
212
- ...snippet,
213
- star_count: state.accountSnippets.filter(
214
- (as) => as.snippet_id === snippet_id && as.has_starred,
215
- ).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
+ : [],
216
704
  }
217
705
  },
218
706
  searchSnippets: (query: string): Snippet[] => {
219
707
  const state = get()
220
708
  const lowercaseQuery = query.toLowerCase()
221
- return state.snippets
222
- .filter(
223
- (snippet) =>
224
- snippet.name.toLowerCase().includes(lowercaseQuery) ||
225
- snippet.description?.toLowerCase().includes(lowercaseQuery) ||
226
- snippet.code.toLowerCase().includes(lowercaseQuery),
227
- )
228
- .map((snippet) => ({
229
- ...snippet,
230
- star_count: state.accountSnippets.filter(
231
- (as) => as.snippet_id === snippet.snippet_id && as.has_starred,
232
- ).length,
233
- }))
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)
234
799
  },
235
- deleteSnippet: (snippet_id: string): boolean => {
800
+ deleteSnippet: (snippetId: string): boolean => {
236
801
  let deleted = false
237
802
  set((state) => {
238
- const index = state.snippets.findIndex((s) => s.snippet_id === snippet_id)
803
+ const index = state.snippets.findIndex((s) => s.snippet_id === snippetId)
239
804
  if (index !== -1) {
240
805
  state.snippets.splice(index, 1)
241
806
  deleted = true
@@ -277,32 +842,29 @@ const initializer = combine(databaseSchema.parse({}), (set, get) => ({
277
842
  }))
278
843
  return newLoginPage
279
844
  },
280
- getLoginPage: (login_page_id: string): LoginPage | undefined => {
845
+ getLoginPage: (loginPageId: string): LoginPage | undefined => {
281
846
  const state = get()
282
- return state.loginPages.find((lp) => lp.login_page_id === login_page_id)
847
+ return state.loginPages.find((lp) => lp.login_page_id === loginPageId)
283
848
  },
284
- updateLoginPage: (
285
- login_page_id: string,
286
- updates: Partial<LoginPage>,
287
- ): void => {
849
+ updateLoginPage: (loginPageId: string, updates: Partial<LoginPage>): void => {
288
850
  set((state) => ({
289
851
  loginPages: state.loginPages.map((lp) =>
290
- lp.login_page_id === login_page_id ? { ...lp, ...updates } : lp,
852
+ lp.login_page_id === loginPageId ? { ...lp, ...updates } : lp,
291
853
  ),
292
854
  }))
293
855
  },
294
- getAccount: (account_id: string): Account | undefined => {
856
+ getAccount: (accountId: string): Account | undefined => {
295
857
  const state = get()
296
- return state.accounts.find((account) => account.account_id === account_id)
858
+ return state.accounts.find((account) => account.account_id === accountId)
297
859
  },
298
860
  updateAccount: (
299
- account_id: string,
861
+ accountId: string,
300
862
  updates: Partial<Account>,
301
863
  ): Account | undefined => {
302
864
  let updatedAccount: Account | undefined
303
865
  set((state) => {
304
866
  const accountIndex = state.accounts.findIndex(
305
- (account) => account.account_id === account_id,
867
+ (account) => account.account_id === accountId,
306
868
  )
307
869
  if (accountIndex !== -1) {
308
870
  updatedAccount = { ...state.accounts[accountIndex] }
@@ -329,72 +891,112 @@ const initializer = combine(databaseSchema.parse({}), (set, get) => ({
329
891
  }))
330
892
  return newSession
331
893
  },
332
- addStar: (account_id: string, snippet_id: string): AccountSnippet => {
894
+ addStar: (accountId: string, packageId: string): AccountPackage => {
333
895
  const now = new Date().toISOString()
334
- const accountSnippet = {
335
- account_id,
336
- snippet_id,
337
- has_starred: true,
896
+ const accountPackage = {
897
+ account_package_id: `ap_${Date.now()}`,
898
+ account_id: accountId,
899
+ package_id: packageId,
900
+ is_starred: true,
338
901
  created_at: now,
339
902
  updated_at: now,
340
903
  }
341
- set((state) => ({
342
- accountSnippets: [...state.accountSnippets, accountSnippet],
343
- }))
344
- 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
345
930
  },
346
- removeStar: (account_id: string, snippet_id: string): void => {
347
- set((state) => ({
348
- accountSnippets: state.accountSnippets.filter(
349
- (as) => !(as.account_id === account_id && as.snippet_id === snippet_id),
350
- ),
351
- }))
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
+ })
352
963
  },
353
- hasStarred: (account_id: string, snippet_id: string): boolean => {
964
+ hasStarred: (accountId: string, packageId: string): boolean => {
354
965
  const state = get()
355
- return state.accountSnippets.some(
356
- (as) =>
357
- as.account_id === account_id &&
358
- as.snippet_id === snippet_id &&
359
- as.has_starred,
966
+ return state.accountPackages.some(
967
+ (ap) =>
968
+ ap.account_id === accountId &&
969
+ ap.package_id === packageId &&
970
+ ap.is_starred,
360
971
  )
361
972
  },
362
973
  addPackage: (
363
974
  _package: Omit<z.input<typeof packageSchema>, "package_id">,
364
975
  ): Package => {
976
+ const timestamp = Date.now()
365
977
  const newPackage = {
366
- package_id: `package_${Date.now()}`,
978
+ package_id: `package_${timestamp}`,
367
979
  ..._package,
368
980
  }
369
- const packageRelease = packageReleaseSchema.parse({
370
- package_release_id: `package_release_${Date.now()}`,
371
- package_id: newPackage.package_id,
372
- version: "0.0.1",
373
- is_locked: false,
374
- is_latest: true,
375
- created_at: new Date().toISOString(),
376
- updated_at: new Date().toISOString(),
377
- })
378
- newPackage.latest_package_release_id = packageRelease.package_release_id
379
981
  set((state) => ({
380
- packages: [...state.packages, newPackage],
982
+ packages: [...state.packages, newPackage as Package],
381
983
  }))
382
- return newPackage
984
+ return newPackage as Package
383
985
  },
384
- getPackageById: (package_id: string): Package | undefined => {
986
+ getPackageById: (packageId: string): Package | undefined => {
385
987
  const state = get()
386
- const pkg = state.packages.find((pkg) => pkg.package_id === package_id)
988
+ const pkg = state.packages.find((pkg) => pkg.package_id === packageId)
387
989
  if (!pkg) return undefined
388
990
  return {
389
991
  ...pkg,
390
992
  }
391
993
  },
392
994
  getPackageReleaseById: (
393
- package_release_id: string,
995
+ packageReleaseId: string,
394
996
  ): PackageRelease | undefined => {
395
997
  const state = get()
396
998
  return state.packageReleases.find(
397
- (pr) => pr.package_release_id === package_release_id,
999
+ (pr) => pr.package_release_id === packageReleaseId,
398
1000
  )
399
1001
  },
400
1002
  addPackageRelease: (
@@ -430,4 +1032,16 @@ const initializer = combine(databaseSchema.parse({}), (set, get) => ({
430
1032
  }))
431
1033
  return newPackageFile
432
1034
  },
1035
+ getStarCount: (packageId: string): number => {
1036
+ const state = get()
1037
+ return state.accountPackages.filter(
1038
+ (ap) => ap.package_id === packageId && ap.is_starred,
1039
+ ).length
1040
+ },
1041
+ getPackageFilesByReleaseId: (packageReleaseId: string): PackageFile[] => {
1042
+ const state = get()
1043
+ return state.packageFiles.filter(
1044
+ (pf) => pf.package_release_id === packageReleaseId,
1045
+ )
1046
+ },
433
1047
  }))