@nextsparkjs/core 0.1.0-beta.172 → 0.1.0-beta.174

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"team.service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/team.service.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAOpD,MAAM,WAAW,mBAAoB,SAAQ,IAAI;IAC/C,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,eAAgB,SAAQ,IAAI;IAC3C,QAAQ,EAAE,QAAQ,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACnC;AAMD,qBAAa,WAAW;IAKtB;;;;;;;;;OASG;WACU,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IAY3E;;;;;;;;OAQG;WACU,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IAW1D;;;;;;;;OAQG;WACU,SAAS,IAAI,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IAO9C;;;;;;;;OAQG;WACU,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;IAK1C;;;;;;;;OAQG;WACU,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IA2CrE;;;;;;;;;OASG;WACU,kBAAkB,CAC7B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC;IA2BtC;;;;;;;;OAQG;WACU,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;QAC7C,EAAE,EAAE,MAAM,CAAA;QACV,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;QACnB,KAAK,EAAE,MAAM,CAAA;QACb,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;KACrB,GAAG,IAAI,CAAC;IAmBT;;;;;;;;OAQG;WACU,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IAgB1D;;;;;;;;;;OAUG;WACU,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAsEjE;;;;;;;;;;OAUG;WACU,MAAM,CACjB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,iBAAiB,EACvB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC;IA+DhB;;;;;;;;;OASG;WACU,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoClE;;;;;;;;;OASG;WACU,eAAe,CAC1B,IAAI,EAAE,MAAM,EACZ,aAAa,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC,OAAO,CAAC;IAoBnB;;;;;;;;;OASG;WACU,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAmC5D;;;;;;;;;;OAUG;WACU,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAmB3E;;;;;;;;OAQG;WACU,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAYtD"}
1
+ {"version":3,"file":"team.service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/team.service.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAOpD,MAAM,WAAW,mBAAoB,SAAQ,IAAI;IAC/C,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,eAAgB,SAAQ,IAAI;IAC3C,QAAQ,EAAE,QAAQ,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACnC;AAMD,qBAAa,WAAW;IAKtB;;;;;;;;;OASG;WACU,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IAY3E;;;;;;;;OAQG;WACU,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IAW1D;;;;;;;;OAQG;WACU,SAAS,IAAI,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IAO9C;;;;;;;;OAQG;WACU,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;IAK1C;;;;;;;;OAQG;WACU,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IA2CrE;;;;;;;;;OASG;WACU,kBAAkB,CAC7B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC;IA2BtC;;;;;;;;OAQG;WACU,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;QAC7C,EAAE,EAAE,MAAM,CAAA;QACV,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;QACnB,KAAK,EAAE,MAAM,CAAA;QACb,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;KACrB,GAAG,IAAI,CAAC;IAmBT;;;;;;;;OAQG;WACU,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IAgB1D;;;;;;;;;;OAUG;WACU,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAsEjE;;;;;;;;;;OAUG;WACU,MAAM,CACjB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,iBAAiB,EACvB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC;IA+DhB;;;;;;;;;OASG;WACU,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoClE;;;;;;;;;OASG;WACU,eAAe,CAC1B,IAAI,EAAE,MAAM,EACZ,aAAa,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC,OAAO,CAAC;IAoBnB;;;;;;;;;OASG;WACU,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAmC5D;;;;;;;;;;OAUG;WACU,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IA0B3E;;;;;;;;OAQG;WACU,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAYtD"}
@@ -410,7 +410,10 @@ class TeamService {
410
410
  throw new Error("User ID and Team ID are required");
411
411
  }
412
412
  const member = await queryOneWithRLS(
413
- 'SELECT id FROM "team_members" WHERE "teamId" = $1 AND "userId" = $2',
413
+ `SELECT tm.id
414
+ FROM "team_members" tm
415
+ INNER JOIN "teams" t ON t.id = tm."teamId"
416
+ WHERE tm."teamId" = $1 AND tm."userId" = $2 AND t."deletedAt" IS NULL`,
414
417
  [teamId, userId],
415
418
  userId
416
419
  );
@@ -1,5 +1,5 @@
1
1
  {
2
- "generated": "2026-06-22T23:25:01.851Z",
2
+ "generated": "2026-06-25T14:35:28.210Z",
3
3
  "totalClasses": 1081,
4
4
  "classes": [
5
5
  "!text-2xl",
@@ -24,8 +24,10 @@ import { withRateLimitTier } from '@nextsparkjs/core/lib/api/rate-limit'
24
24
  const VALID_PATH_PATTERNS = [
25
25
  // Entity folders
26
26
  /^(contents\/)?themes\/[\w-]+\/entities\/[\w-]+\/api\/docs\.md$/,
27
- // Theme routes
27
+ // Theme routes (legacy: docs mirrored under app/api)
28
28
  /^(contents\/)?themes\/[\w-]+\/app\/api\/.*\/docs\.md$/,
29
+ // Theme routes (colocated next to the handler under api/)
30
+ /^(contents\/)?themes\/[\w-]+\/api\/.*\/docs\.md$/,
29
31
  // Core routes (monorepo mode)
30
32
  /^packages\/core\/templates\/app\/api\/.*\/docs\.md$/,
31
33
  // Core routes (NPM mode)
@@ -66,8 +66,13 @@ export const GET = withRateLimitTier(withApiLogging(async (req: NextRequest): Pr
66
66
  }
67
67
 
68
68
  // Build WHERE clause based on filters
69
- // If scope=all (superadmin), don't filter by user
70
- let whereClause = requestAllTeams ? 'WHERE 1=1' : 'WHERE tm."userId" = $1'
69
+ // If scope=all (superadmin), don't filter by user.
70
+ // For a user's own list, exclude soft-deleted teams: a member (e.g. the former
71
+ // owner of a discarded personal team) keeps their team_members row, so without
72
+ // this filter a stale active-team pointer to a deleted team resurfaces in the
73
+ // list and the client team resolver (which matches on membership, not deletedAt)
74
+ // re-selects it. scope=all stays unfiltered so admins can still see deleted teams.
75
+ let whereClause = requestAllTeams ? 'WHERE 1=1' : 'WHERE tm."userId" = $1 AND t."deletedAt" IS NULL'
71
76
  const queryValues: unknown[] = requestAllTeams ? [] : [authResult.user!.id]
72
77
  let paramCount = requestAllTeams ? 1 : 2
73
78
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextsparkjs/core",
3
- "version": "0.1.0-beta.172",
3
+ "version": "0.1.0-beta.174",
4
4
  "description": "NextSpark - The complete SaaS framework for Next.js",
5
5
  "license": "MIT",
6
6
  "author": "NextSpark <hello@nextspark.dev>",
@@ -469,7 +469,7 @@
469
469
  "tailwind-merge": "^3.3.1",
470
470
  "uuid": "^13.0.0",
471
471
  "zod": "^4.1.5",
472
- "@nextsparkjs/testing": "0.1.0-beta.172"
472
+ "@nextsparkjs/testing": "0.1.0-beta.174"
473
473
  },
474
474
  "scripts": {
475
475
  "postinstall": "node scripts/postinstall.mjs || true",
@@ -439,75 +439,84 @@ async function discoverEntityFolders(config, results, processedEndpoints) {
439
439
  */
440
440
  async function discoverThemeRoutes(config, results, processedEndpoints) {
441
441
  const themeName = config.activeTheme
442
- const themeApiDir = join(config.themesDir, themeName, 'app', 'api')
443
442
 
444
- if (!existsSync(themeApiDir)) {
445
- verbose(`No theme API routes found for "${themeName}"`)
446
- return
447
- }
443
+ // Process every docs.md / presets.ts under `baseDir`, mapping each route
444
+ // directory to an endpoint via `endpointForDir(routeDir)`.
445
+ const processTree = async (baseDir, endpointForDir) => {
446
+ if (!existsSync(baseDir)) return
447
+
448
+ const docFiles = await findFilesRecursive(baseDir, 'docs.md')
449
+ const presetFiles = await findFilesRecursive(baseDir, 'presets.ts')
450
+
451
+ for (const docsPath of docFiles) {
452
+ try {
453
+ const routeDir = dirname(docsPath)
454
+ const endpoint = endpointForDir(routeDir)
455
+ if (processedEndpoints.has(endpoint)) continue
456
+
457
+ const content = await readFile(docsPath, 'utf-8')
458
+ const title = extractMarkdownTitle(content)
459
+ const slug = basename(routeDir)
460
+
461
+ results.docs.push({
462
+ slug,
463
+ title,
464
+ endpoint,
465
+ filePath: getStoragePath(docsPath, config),
466
+ source: 'route',
467
+ themeName
468
+ })
469
+ verbose(` Theme route doc discovered: ${endpoint}`)
448
470
 
449
- // Find all docs.md files in theme routes
450
- const docFiles = await findFilesRecursive(themeApiDir, 'docs.md')
451
- const presetFiles = await findFilesRecursive(themeApiDir, 'presets.ts')
452
-
453
- // Process docs
454
- for (const docsPath of docFiles) {
455
- try {
456
- const routeDir = dirname(docsPath)
457
- const relativeDir = relative(join(config.themesDir, themeName, 'app'), routeDir)
458
- const endpoint = '/' + normalizePath(relativeDir)
459
-
460
- const content = await readFile(docsPath, 'utf-8')
461
- const title = extractMarkdownTitle(content)
462
-
463
- // Extract slug from endpoint
464
- const slug = basename(routeDir)
465
-
466
- results.docs.push({
467
- slug,
468
- title,
469
- endpoint,
470
- filePath: getStoragePath(docsPath, config),
471
- source: 'route',
472
- themeName
473
- })
474
- verbose(` Theme route doc discovered: ${endpoint}`)
475
-
476
- processedEndpoints.add(endpoint)
477
- } catch (error) {
478
- log(` ERROR: Failed to read theme route doc: ${error.message}`, 'error')
471
+ processedEndpoints.add(endpoint)
472
+ } catch (error) {
473
+ log(` ERROR: Failed to read theme route doc: ${error.message}`, 'error')
474
+ }
479
475
  }
480
- }
481
476
 
482
- // Process presets
483
- for (const presetsPath of presetFiles) {
484
- try {
485
- const routeDir = dirname(presetsPath)
486
- const relativeDir = relative(join(config.themesDir, themeName, 'app'), routeDir)
487
- const endpoint = '/' + normalizePath(relativeDir)
488
-
489
- const content = await readFile(presetsPath, 'utf-8')
490
- const presetConfig = parsePresetFile(content)
491
-
492
- const slug = basename(routeDir)
493
- const finalEndpoint = presetConfig?.endpoint || endpoint
494
-
495
- results.presets.push({
496
- slug,
497
- endpoint: finalEndpoint,
498
- summary: presetConfig?.summary || '',
499
- presets: presetConfig?.presets || [],
500
- sourcePath: getStoragePath(presetsPath, config),
501
- source: 'route',
502
- themeName
503
- })
504
- verbose(` Theme route presets discovered: ${finalEndpoint} (${presetConfig?.presets?.length || 0} presets)`)
505
-
506
- processedEndpoints.add(finalEndpoint)
507
- } catch (error) {
508
- log(` ERROR: Failed to parse theme route presets: ${error.message}`, 'error')
477
+ for (const presetsPath of presetFiles) {
478
+ try {
479
+ const routeDir = dirname(presetsPath)
480
+ const endpoint = endpointForDir(routeDir)
481
+
482
+ const content = await readFile(presetsPath, 'utf-8')
483
+ const presetConfig = parsePresetFile(content)
484
+
485
+ const slug = basename(routeDir)
486
+ const finalEndpoint = presetConfig?.endpoint || endpoint
487
+ if (processedEndpoints.has(finalEndpoint)) continue
488
+
489
+ results.presets.push({
490
+ slug,
491
+ endpoint: finalEndpoint,
492
+ summary: presetConfig?.summary || '',
493
+ presets: presetConfig?.presets || [],
494
+ sourcePath: getStoragePath(presetsPath, config),
495
+ source: 'route',
496
+ themeName
497
+ })
498
+ verbose(` Theme route presets discovered: ${finalEndpoint} (${presetConfig?.presets?.length || 0} presets)`)
499
+
500
+ processedEndpoints.add(finalEndpoint)
501
+ } catch (error) {
502
+ log(` ERROR: Failed to parse theme route presets: ${error.message}`, 'error')
503
+ }
509
504
  }
510
505
  }
506
+
507
+ // Legacy tree: {theme}/app/api/** — the docs path mirrors the full request URL
508
+ // under `app/` (e.g. app/api/v1/theme/{theme}/foo -> /api/v1/theme/{theme}/foo).
509
+ const appDir = join(config.themesDir, themeName, 'app')
510
+ await processTree(join(appDir, 'api'), (routeDir) =>
511
+ '/' + normalizePath(relative(appDir, routeDir))
512
+ )
513
+
514
+ // Colocated tree: {theme}/api/** — docs/presets live next to the route handler,
515
+ // mapped the same way the handlers are (api/foo -> /api/v1/theme/{theme}/foo).
516
+ const apiDir = join(config.themesDir, themeName, 'api')
517
+ await processTree(apiDir, (routeDir) =>
518
+ '/api/v1/theme/' + themeName + '/' + normalizePath(relative(apiDir, routeDir))
519
+ )
511
520
  }
512
521
 
513
522
  /**
@@ -24,8 +24,10 @@ import { withRateLimitTier } from '@nextsparkjs/core/lib/api/rate-limit'
24
24
  const VALID_PATH_PATTERNS = [
25
25
  // Entity folders
26
26
  /^(contents\/)?themes\/[\w-]+\/entities\/[\w-]+\/api\/docs\.md$/,
27
- // Theme routes
27
+ // Theme routes (legacy: docs mirrored under app/api)
28
28
  /^(contents\/)?themes\/[\w-]+\/app\/api\/.*\/docs\.md$/,
29
+ // Theme routes (colocated next to the handler under api/)
30
+ /^(contents\/)?themes\/[\w-]+\/api\/.*\/docs\.md$/,
29
31
  // Core routes (monorepo mode)
30
32
  /^packages\/core\/templates\/app\/api\/.*\/docs\.md$/,
31
33
  // Core routes (NPM mode)
@@ -66,8 +66,13 @@ export const GET = withRateLimitTier(withApiLogging(async (req: NextRequest): Pr
66
66
  }
67
67
 
68
68
  // Build WHERE clause based on filters
69
- // If scope=all (superadmin), don't filter by user
70
- let whereClause = requestAllTeams ? 'WHERE 1=1' : 'WHERE tm."userId" = $1'
69
+ // If scope=all (superadmin), don't filter by user.
70
+ // For a user's own list, exclude soft-deleted teams: a member (e.g. the former
71
+ // owner of a discarded personal team) keeps their team_members row, so without
72
+ // this filter a stale active-team pointer to a deleted team resurfaces in the
73
+ // list and the client team resolver (which matches on membership, not deletedAt)
74
+ // re-selects it. scope=all stays unfiltered so admins can still see deleted teams.
75
+ let whereClause = requestAllTeams ? 'WHERE 1=1' : 'WHERE tm."userId" = $1 AND t."deletedAt" IS NULL'
71
76
  const queryValues: unknown[] = requestAllTeams ? [] : [authResult.user!.id]
72
77
  let paramCount = requestAllTeams ? 1 : 2
73
78