Skip to content

chore(audit): clean exports/types, fix mutations, rename Spotify search, Vercel method, and remove unused#12

Merged
remcostoeten merged 3 commits into
masterfrom
chore/audit-cleanup
Aug 24, 2025
Merged

chore(audit): clean exports/types, fix mutations, rename Spotify search, Vercel method, and remove unused#12
remcostoeten merged 3 commits into
masterfrom
chore/audit-cleanup

Conversation

@remcostoeten

@remcostoeten remcostoeten commented Aug 24, 2025

Copy link
Copy Markdown
Owner

This PR addresses the audit items:

  1. Resolve core export/type conflicts
  • Remove wildcard re-export of api-factory-enhanced from core/index.ts
  • Make TApiClient and TApiConfig explicit exports from api-factory
  • Remove conflicting TResult re-export (keep canonical core/result)
  1. Fix mutation signatures in google-calendar and google-drive
  • Use (data, options) with path placeholders in options
  1. Rename Spotify’s search convenience method
  • Rename to searchAll to avoid collision with search resource
  1. Vercel: align resources and header typing
  • Add deployments.createDeployment (POST /v13/deployments)
  • Fix header typing for team selector
  1. Enhanced presets typing
  • Safe fallback across default/authenticated/unauthenticated keys
  1. Remove unused/redundant files and imports
  • Remove core/http, unified-config, normalize, and method-factory references
  • Clean imports in resource-factory and api-factory
  1. Standardize module casting and switch to simple client
  • Module factory now uses base client (createFyncApi) to simplify types

Typecheck passes with npx tsc --noEmit.

Follow-up: We can re-introduce the enhanced client behind a separate named export with non-conflicting types if desired.

Summary by CodeRabbit

  • New Features
    • Added GitLab API client with high-level helpers.
    • Introduced enhanced API factory usage pattern; expanded type re-exports (GitLab, Spotify).
  • Breaking Changes
    • Released v4.0.0 with streamlined Core exports; removed legacy error/HTTP types and related re-exports.
    • Shift toward result-based responses; some prior error-handling APIs are no longer available.
    • Package adds ./gitlab subpath export; some type names/exports changed or removed.
  • Documentation
    • Added GitLab usage, SECURITY policy, and new architecture guides.
    • Updated README and examples to the unified, result-first pattern and added GitLab examples.

- Update package.json with new dependencies/configuration
- Enhance error exports structure
- Refactor resource factory implementation
- Extend core types definitions
- Update TypeScript configuration
- Remove redundant examples/API_CORE_EXAMPLES.md with outdated architecture
- Rewrite README.md to be cleaner and more focused
  - Remove emojis, keep npm badges
  - Focus on 6 supported APIs with enhanced features
  - Add comprehensive examples for all APIs
  - Document advanced features (caching, rate limiting, error handling)
- Add SECURITY.md with comprehensive security guidelines
  - API token security best practices
  - Environment variable setup
  - Production deployment security
  - Common security pitfalls
- Clean up package.json
  - Remove unused 'glob' dependency
  - Optimize build scripts
  - Update keywords to reflect actual features
  - Add SECURITY.md to published files
- All changes maintain version 4.0.0 consistency
…export, fix mutation call signatures, rename Spotify search method, add Vercel createDeployment, clean unused files, and switch module factory to simple client

- Remove wildcard re-export of api-factory-enhanced to avoid type collisions
- Make TApiClient and TApiConfig explicit exports; stop re-exporting conflicting TResult
- Fix POST/PUT/PATCH call signature to (data, options) and move path params to options
- Rename Spotify search convenience to searchAll to avoid clash with resource
- Add deployments.createDeployment and correct Vercel headers typing
- Remove unused core/http, unified-config, normalize, and method-factory references
- Use base api client in module-factory; export necessary types
- Make resource-factory safer re: method narrowing
- Typecheck passes (tsc --noEmit)
@coderabbitai

coderabbitai Bot commented Aug 24, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Adds a full GitLab API module with extensive typed resources and helpers, updates exports and examples, and bumps package to 4.0.0. Core public types and error/http type surfaces are removed or narrowed, with minor core factory tweaks. Significant documentation overhaul adds new architecture/style guides and security policies; some legacy docs/examples are deleted.

Changes

Cohort / File(s) Summary
GitLab module (new)
fync/src/gitlab/index.ts, fync/src/gitlab/types/*, fync/src/gitlab/types/index.ts, fync/src/index.ts
Introduces a complete GitLab client with resources and convenience helpers; adds comprehensive GitLab type exports; exposes GitLab from top-level index.
Core API surface and internals
fync/src/core/index.ts, fync/src/core/api-factory.ts, fync/src/core/api-factory-enhanced.ts, fync/src/core/method-factory.ts, fync/src/core/module-factory.ts, fync/src/core/resource-factory.ts, fync/src/core/errors/*, fync/src/core/http/*, fync/src/core/types/base.ts, fync/src/core/types/index.ts, fync/src/core/types/pagination.ts, fync/src/core/types/response.ts
Reduces/restructures public core surface: removes error/http/type exports, adjusts base types, exports TApiConfig/TApiClient, tweaks rate-limit preset resolution, changes resource path interpolation return, and minor formatting/type-source updates.
Documentation overhaul
.cursor/rules/fync-architecture.mdc, GEMINI.md, WARP.md, README.md, fync/README.md, IMPLEMENTED_METHODS.md, fync/SECURITY.md, architecture.md
Adds new architecture/style guides and security policy; updates README(s) and implemented methods to include GitLab; removes old architecture doc.
Examples update/removal
fync/examples/unified-usage.ts, fync/examples/API_CORE_EXAMPLES.md
Adds GitLab usage examples; removes legacy core API examples doc.
Existing modules: type assertions and minor tweak
fync/src/github/index.ts, fync/src/google-calendar/index.ts, fync/src/google-drive/index.ts, fync/src/npm/index.ts, fync/src/spotify/index.ts, fync/src/vercel/index.ts
Relaxes type assertions via intermediate unknown; Spotify assigns method via any; Vercel omits empty headers when no teamId.
NPM types restructuring
fync/src/npm/types/npm-package.ts
Refactors TNpmPackageVersion and related types: inlines shapes, adds fields (files, dist, config, flags), replaces several shared type indirections with Records, and adjusts metadata.
Package and build config
fync/package.json, fync/tsconfig.json
Bumps version to 4.0.0; adds ./gitlab export; updates scripts/keywords/files; removes a dependency; adds skipLibCheck.
Utilities and unified-config removal
fync/src/utils/normalize.ts, fync/src/types/unified-config.ts
Removes normalization helpers and unified config/types.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Dev as Developer
  participant GL as GitLab(config)
  participant AB as createApiBuilder
  participant RES as Resources
  participant API as GitLab REST API

  Dev->>GL: GitLab({ token })
  activate GL
  GL->>AB: createApiBuilder({ baseUrl, headers: Bearer token })
  AB-->>GL: api client
  GL->>RES: define resources (users, projects, groups, ...)
  RES-->>GL: resource methods
  GL-->>Dev: module with helpers

  note over Dev,GL: Example helper: getProjectFromUrl(url)
  Dev->>GL: getProjectFromUrl("https://gitlab.com/group/proj")
  activate GL
  GL->>GL: parseGitLabUrl(url) -> "group/proj"
  GL->>API: GET /projects/{encoded path}
  API-->>GL: 200 Project JSON
  GL-->>Dev: Project
  deactivate GL
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested labels

enhancement, documentation

Poem

I hop through commits with whiskers alight,
New GitLab trails under moonlit byte.
Types bloom like clover, neat and tight,
Old weeds pulled, the garden right.
Cache and limits, softly stay—
Ship 4.0.0, and bound away! 🐇✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/audit-cleanup

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@remcostoeten
remcostoeten merged commit 44a3f47 into master Aug 24, 2025
1 check was pending
@remcostoeten
remcostoeten deleted the chore/audit-cleanup branch August 24, 2025 02:46
coderabbitai Bot added a commit that referenced this pull request Aug 24, 2025
Docstrings generation was requested by @remcostoeten.

* #12 (comment)

The following files were modified:

* `fync/examples/unified-usage.ts`
* `fync/src/core/api-factory-enhanced.ts`
* `fync/src/core/api-factory.ts`
* `fync/src/core/module-factory.ts`
* `fync/src/core/resource-factory.ts`
* `fync/src/github/index.ts`
* `fync/src/gitlab/index.ts`
* `fync/src/google-calendar/index.ts`
* `fync/src/google-drive/index.ts`
* `fync/src/npm/index.ts`
* `fync/src/spotify/index.ts`
* `fync/src/vercel/index.ts`
@coderabbitai

coderabbitai Bot commented Aug 24, 2025

Copy link
Copy Markdown
Contributor

Note

Generated docstrings for this pull request at #13

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 20

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (12)
IMPLEMENTED_METHODS.md (2)

127-150: Rename Spotify convenience method to searchAll to avoid collision with the search resource

PR objective states the convenience method was renamed from search to searchAll. The doc still shows search(query, ...), which collides in naming with the search resource.

Apply this diff:

- - `search(query, types, options?)` - Multi-type search
+ - `searchAll(query, types, options?)` - Multi-type search across multiple types

I can sweep examples/README snippets in the repo to align any references if you want.


153-159: Add Vercel deployments.createDeployment and update method counts

The PR objective adds deployments.createDeployment (POST /v13/deployments). It’s missing from the deployments list. Also, adding it bumps the Vercel total by +1 and the global total by +1.

Apply these diffs:

-**Vercel** - 36 total methods
+**Vercel** - 37 total methods
- - **deployments**: `listDeployments`, `getDeployment`, `deleteDeployment`, `getDeploymentEvents`, `getDeploymentFiles`, `cancelDeployment`
+ - **deployments**: `listDeployments`, `getDeployment`, `createDeployment`, `deleteDeployment`, `getDeploymentEvents`, `getDeploymentFiles`, `cancelDeployment`
-### Total: 428 methods implemented across all 7 modules
+### Total: 429 methods implemented across all 7 modules
fync/src/core/module-factory.ts (1)

60-62: Token propagation is a no-op when defaultConfig.auth is missing

As written, if userConfig.token is provided but finalConfig.auth isn’t preset to bearer, the token is silently ignored. This is brittle across modules.

Apply this diff to make token handling robust:

-		if (userConfig.token && finalConfig.auth?.type === "bearer") {
-			finalConfig.auth.token = userConfig.token;
-		}
+		if (userConfig.token) {
+			if (!finalConfig.auth) {
+				finalConfig.auth = { type: "bearer", token: userConfig.token } as any;
+			} else if (finalConfig.auth.type === "bearer") {
+				finalConfig.auth.token = userConfig.token;
+			}
+		}

If you intentionally rely on per-module defaults to set auth.type, at least add a comment to document the contract. Otherwise, this change removes a subtle footgun.

fync/src/google-drive/index.ts (2)

232-238: Escape user input in Drive query strings to avoid malformed queries.

Interpolating raw values into the q parameter can break the Drive query when the string contains quotes or operators.

Add a small helper and use it:

@@
-	drive.searchFiles = async function (query: string) {
+	function escapeDriveQueryValue(v: string) {
+		return String(v).replaceAll("'", "\\'");
+	}
+	drive.searchFiles = async function (query: string) {
 		const response = await base.files.listFiles({
-			q: `name contains '${query}'`,
+			q: `name contains '${escapeDriveQueryValue(query)}'`,
 			fields: "files(id, name, mimeType, size, createdTime, modifiedTime)",
 		});
@@
-	drive.getFilesByName = async function (name: string) {
+	drive.getFilesByName = async function (name: string) {
 		const response = await base.files.listFiles({
-			q: `name = '${name}'`,
+			q: `name = '${escapeDriveQueryValue(name)}'`,
 			fields: "files(id, name, mimeType, size, createdTime, modifiedTime)",
 		});

If the core builder supports query parameter encoding instead, prefer passing raw values via a structured options object to avoid manual string assembly.

Also applies to: 240-246


177-180: Fix Drive mutation wrappers to use (data, options) signature

The Drive wrappers for updateFile, copyFile, moveFile, and restoreFile still embed the fileId (and other path params) into the body object. This breaks path‐parameter substitution and may leak those fields into the request body. Please update each wrapper to pass the request body as the first argument and path placeholders in a second options object.

Affected locations in fync/src/google-drive/index.ts:

  • Lines 178–180 (drive.updateFile)
  • Lines 186–189 (drive.copyFile)
  • Lines 191–197 (drive.moveFile)
  • Lines 312–314 (drive.restoreFile)

Apply these diffs:

drive.updateFile = function (fileId: string, metadata?: any, content?: any) {
-   const body = content ? { ...metadata, media: content } : metadata;
-   return base.files.updateFile({ fileId, ...body });
+   const body = content ? { ...metadata, media: content } : metadata;
+   return base.files.updateFile(body, { fileId });
};

drive.copyFile = function (fileId: string, name?: string) {
-   const body = name ? { name } : {};
-   return base.files.copyFile({ fileId, ...body });
+   const body = name ? { name } : {};
+   return base.files.copyFile(body, { fileId });
};

drive.moveFile = function (fileId: string, parentId: string) {
-   return base.files.updateFile({
-     fileId,
-     addParents: parentId,
-     removeParents: "root",
-   });
+   return base.files.updateFile(
+     { addParents: parentId, removeParents: "root" },
+     { fileId },
+   );
};

drive.restoreFile = function (fileId: string) {
-   return base.files.updateFile({ fileId, trashed: false });
+   return base.files.updateFile({ trashed: false }, { fileId });
};
fync/src/google-calendar/index.ts (1)

270-288: Mutations still use legacy single-arg signature; switch to (data, options) to avoid path/body collisions.

Per PR objective (“Fix mutation signatures”), POST/PUT/DELETE should pass body as first arg and path placeholders via options second arg. Current implementations merge placeholders into the body, risking runtime bugs and conflicting keys.

Apply this diff:

-  calendar.createEvent = function (calendarId: string, event: any) {
-    return base.events.insertEvent({ calendarId, ...event });
-  };
+  calendar.createEvent = function (calendarId: string, event: any) {
+    return base.events.insertEvent(event, { calendarId });
+  };

-  calendar.updateEvent = function (
-    calendarId: string,
-    eventId: string,
-    event: any,
-  ) {
-    return base.events.updateEvent({ calendarId, eventId, ...event });
-  };
+  calendar.updateEvent = function (
+    calendarId: string,
+    eventId: string,
+    event: any,
+  ) {
+    return base.events.updateEvent(event, { calendarId, eventId });
+  };

-  calendar.deleteEvent = function (calendarId: string, eventId: string) {
-    return base.events.deleteEvent({ calendarId, eventId });
-  };
+  calendar.deleteEvent = function (calendarId: string, eventId: string) {
+    // No body for DELETE; pass placeholders via options
+    return base.events.deleteEvent(undefined, { calendarId, eventId });
+  };

-  calendar.quickAddEvent = function (calendarId: string, text: string) {
-    return base.events.quickAddEvent({ calendarId, text });
-  };
+  calendar.quickAddEvent = function (calendarId: string, text: string) {
+    // quickAdd expects text as a query param; empty body + params in options
+    return base.events.quickAddEvent({}, { calendarId, text });
+  };
fync/src/vercel/index.ts (1)

20-31: Missing deployments.createDeployment endpoint definition causes runtime error.

vercel.redeployProject calls base.deployments.createDeployment(...), but the resource lacks this method. This will throw at runtime.

Add the method:

 const deploymentResource = defineResource({
   name: "deployments",
   basePath: "/v13/deployments",
   methods: {
     listDeployments: { path: "" },
     getDeployment: { path: "/{deploymentId}" },
+    createDeployment: { path: "", method: "POST" },
     deleteDeployment: { path: "/{deploymentId}", method: "DELETE" },
     getDeploymentEvents: { path: "/{deploymentId}/events" },
     getDeploymentFiles: { path: "/{deploymentId}/files" },
     cancelDeployment: { path: "/{deploymentId}/cancel", method: "PATCH" },
   },
 });
fync/src/spotify/index.ts (2)

159-165: Rename convenience search to searchAll to avoid resource collision (per PR objective).

The PR objective says “Rename Spotify convenience method: search → searchAll.” Current code reintroduces search by casting to any, shadowing the search resource and defeating the goal.

Apply this cohesive change:

- type TSpotifyModule = TModule<typeof resources> & {
+ type TSpotifyModule = TModule<typeof resources> & {
   getTrack: (trackId: string) => Promise<any>;
   getArtist: (artistId: string) => Promise<any>;
   getAlbum: (albumId: string) => Promise<any>;
   getPlaylist: (playlistId: string) => Promise<any>;
-  search: (query: string, types: string[], options?: any) => Promise<any>;
+  searchAll: (query: string, types: Array<"track" | "artist" | "album" | "playlist">, options?: any) => Promise<any>;
   getMyTopTracks: (options?: any) => Promise<any>;
   getMyTopArtists: (options?: any) => Promise<any>;
   getRecentlyPlayed: (options?: any) => Promise<any>;
   getCurrentlyPlaying: () => Promise<any>;
   getRecommendations: (options: any) => Promise<any>;
   createPlaylist: (userId: string, name: string, options?: any) => Promise<any>;
   addTracksToPlaylist: (
     playlistId: string,
     trackUris: string[],
   ) => Promise<any>;
   playTrack: (trackUri: string, deviceId?: string) => Promise<any>;
   pausePlayback: () => Promise<any>;
   skipToNext: () => Promise<any>;
   skipToPrevious: () => Promise<any>;
   getUserPlaylists: (userId?: string) => Promise<any>;
   getAudioFeatures: (trackId: string) => Promise<any>;
   searchTracks: (query: string, options?: any) => Promise<any>;
   searchArtists: (query: string, options?: any) => Promise<any>;
   searchAlbums: (query: string, options?: any) => Promise<any>;
   searchPlaylists: (query: string, options?: any) => Promise<any>;
 };
-  (spotify as any).search = function (query: string, types: string[], options?: any) {
+  spotify.searchAll = function (query: string, types: Array<"track" | "artist" | "album" | "playlist">, options?: any) {
     return base.search.search({
       q: query,
       type: types.join(","),
       limit: options?.limit || 20,
       offset: options?.offset || 0,
       ...options,
     });
   };
-  spotify.searchTracks = function (query: string, options?: any) {
-    return spotify.search(query, ["track"], options);
-  };
+  spotify.searchTracks = function (query: string, options?: any) {
+    return spotify.searchAll(query, ["track"], options);
+  };

-  spotify.searchArtists = function (query: string, options?: any) {
-    return spotify.search(query, ["artist"], options);
-  };
+  spotify.searchArtists = function (query: string, options?: any) {
+    return spotify.searchAll(query, ["artist"], options);
+  };

-  spotify.searchAlbums = function (query: string, options?: any) {
-    return spotify.search(query, ["album"], options);
-  };
+  spotify.searchAlbums = function (query: string, options?: any) {
+    return spotify.searchAll(query, ["album"], options);
+  };

-  spotify.searchPlaylists = function (query: string, options?: any) {
-    return spotify.search(query, ["playlist"], options);
-  };
+  spotify.searchPlaylists = function (query: string, options?: any) {
+    return spotify.searchAll(query, ["playlist"], options);
+  };

This removes the need for the (spotify as any) escape hatch and prevents overriding the search resource.

Also applies to: 207-215, 304-318


11-12: Update Spotify audio features/analysis endpoint paths

The Spotify Web API defines these endpoints at the root level, not under /tracks/{id}. Using the current paths will result in 404s. Please update as follows:

  • fync/src/spotify/index.ts (around lines 11–12):
  • getTrackAudioFeatures: { path: "/{id}/audio-features" },
  • getTrackAudioAnalysis: { path: "/{id}/audio-analysis" },
  • getTrackAudioFeatures: { path: "/audio-features/{id}" }, // GET /v1/audio-features/{id} (developer.spotify.com)
  • getTrackAudioAnalysis: { path: "/audio-analysis/{id}" }, // GET /v1/audio-analysis/{id} (developer.spotify.com)
- Also apply the same changes around lines 300–302 where these endpoints are referenced.

</blockquote></details>
<details>
<summary>fync/examples/unified-usage.ts (1)</summary><blockquote>

`193-197`: **Rename all remaining `spotify.search` calls to `spotify.searchAll`**

The grep output shows that several convenience calls weren’t updated as part of the PR:

- **fync/examples/unified-usage.ts** (line 193)  
- **fync/src/spotify/index.ts** (lines 305, 309, 313, 317)  

Please update each invocation of the old method to use the new name. For example:

```diff
- const multiSearch = await spotify.search("queen", ["artist", "track", "album"], {
+ const multiSearch = await spotify.searchAll("queen", ["artist", "track", "album"], {
   limit: 10,
   offset: 0,
   market: "US",
});

And in fync/src/spotify/index.ts:

-    return spotify.search(query, ["track"], options);
+    return spotify.searchAll(query, ["track"], options);

(Apply similarly at lines 309, 313, and 317.)

fync/src/core/api-factory.ts (2)

35-53: Avoid emitting auth headers with missing credentials; optionally fail fast.

Today, bearer/oauth2 will send Authorization: Bearer undefined if token is absent; same for basic/apikey with missing credentials. That’s brittle and can leak intent.

Safer default:

 case "bearer":
-  return { Authorization: `Bearer ${auth.token}` };
+  return auth.token ? { Authorization: `Bearer ${auth.token}` } : {};
 case "basic": {
   const { username = "", password = "" } = auth.credentials || {};
-  const encoded = Buffer.from(`${username}:${password}`).toString("base64");
-  return { Authorization: `Basic ${encoded}` };
+  if (!username && !password) return {};
+  const encoded = Buffer.from(`${username}:${password}`).toString("base64");
+  return { Authorization: `Basic ${encoded}` };
 }
 case "apikey":
-  return { "X-API-Key": auth.key || "" };
+  return auth.key ? { "X-API-Key": auth.key } : {};
 case "oauth2":
-  return { Authorization: `Bearer ${auth.token}` };
+  return auth.token ? { Authorization: `Bearer ${auth.token}` } : {};

Optionally, throw on missing required credentials for stricter behavior.


79-114: Handle non‑JSON/empty responses and avoid JSON.stringify for non‑JSON bodies.

Two issues:

  • 204/205/304 (or empty body) will throw on response.json().
  • File uploads or form submissions break when we unconditionally JSON.stringify.

Robust request body/response handling:

- if (options?.body && method !== "GET") {
-   fetchOptions.body = JSON.stringify(options.body);
- }
+ // Body handling: only JSON-stringify plain objects; let fetch set headers for FormData/Blob/ArrayBuffer
+ if (method !== "GET" && options?.body !== undefined) {
+   const b = options.body as unknown;
+   const isFormLike =
+     typeof FormData !== "undefined" && b instanceof FormData ||
+     (typeof Blob !== "undefined" && b instanceof Blob) ||
+     (typeof ArrayBuffer !== "undefined" && b instanceof ArrayBuffer) ||
+     (typeof URLSearchParams !== "undefined" && b instanceof URLSearchParams);
+   if (isFormLike) {
+     // Let fetch set Content-Type including boundaries
+     // Caller can still override via options.headers
+     fetchOptions.body = b as any;
+     delete (headers as any)["Content-Type"];
+   } else if (typeof b === "string" || b instanceof Uint8Array) {
+     fetchOptions.body = b as any;
+   } else {
+     fetchOptions.body = JSON.stringify(b);
+   }
+ }
...
- const data = await response.json();
- return data as T;
+ // Response handling: parse JSON only when appropriate
+ const ct = response.headers.get("content-type") || "";
+ if (response.status === 204 || response.status === 205 || response.status === 304) {
+   return undefined as T;
+ }
+ if (!ct || !ct.includes("application/json")) {
+   const text = await response.text();
+   // Best effort: return text when non-JSON
+   return text as unknown as T;
+ }
+ const data = await response.json();
+ return data as T;
🧹 Nitpick comments (59)
fync/tsconfig.json (1)

10-12: Deduplicate skipLibCheck and normalize indentation in compilerOptions

You’ve got two skipLibCheck entries (Lines 8 and 12). TS reads the last occurrence, but duplication is confusing. Also, the changed lines are tab-indented whereas the rest of the file uses spaces.

Apply this diff:

-		"declaration": true,
-		"declarationMap": true,
-		"skipLibCheck": true,
+    "declaration": true,
+    "declarationMap": true,

Optional: if you’re standardizing paths for new modules in this PR (GitLab, Google Drive/Calendar, Vercel), consider adding/removing path aliases consistently in this block in a follow-up.

IMPLEMENTED_METHODS.md (1)

3-3: Refresh “Updated” date for accuracy (optional)

The header says “Updated Jan 27 2025” while this PR is dated Aug 24, 2025. Consider updating for clarity or removing the date if it’s maintained elsewhere.

-## Summary (Updated Jan 27 2025)
+## Summary (Updated Aug 24, 2025)
fync/src/gitlab/types/gitlab-common.ts (2)

13-19: Prefer an exported const for member access levels to enable reuse at runtime

Defining TGitLabMemberAccess as a type describing an object is less practical than exporting a const value and deriving the type. This allows reuse for validation/mapping at runtime and clean type inference.

-export type TGitLabMemberAccess = {
-	guest: 10;
-	reporter: 20;
-	developer: 30;
-	maintainer: 40;
-	owner: 50;
-};
+export const GITLAB_MEMBER_ACCESS = {
+  guest: 10,
+  reporter: 20,
+  developer: 30,
+  maintainer: 40,
+  owner: 50,
+} as const;
+export type TGitLabMemberAccess = typeof GITLAB_MEMBER_ACCESS;
+export type TGitLabAccessLevel = typeof GITLAB_MEMBER_ACCESS[keyof typeof GITLAB_MEMBER_ACCESS];

1-7: Verify GitLab config alignment with core TApiConfig

TGitLabConfig exposes token, baseUrl, cache, cacheTTL, userAgent. Ensure these map cleanly to core TApiConfig used by createFyncApi/createApiBuilder (headers/auth). In particular:

  • userAgent should be injected into headers as "User-Agent".
  • cache/cacheTTL should be either honored by the core client or removed to avoid a misleading surface.

Happy to wire a small adapter in the GitLab module that maps TGitLabConfig → TApiConfig to guarantee consistency.

fync/src/core/module-factory.ts (1)

26-37: Guard reserved key collisions and tighten helper typing

Resources can overwrite the reserved api property; and helpers are typed as Function. Minor hardening improves safety.

-const apiClient = createFyncApi(config.apiConfig);
-const module = { api: apiClient } as TModule<TResources>;
+const apiClient = createFyncApi(config.apiConfig);
+const module = { api: apiClient } as TModule<TResources>;
+// Prevent collisions with reserved keys
+if (Object.prototype.hasOwnProperty.call(config.resources, "api")) {
+  throw new Error("Resource name 'api' is reserved");
+}

Optionally change the helpers type in TModuleConfig to Record<string, (...args: any[]) => any> to avoid the very-broad Function type.

fync/src/npm/index.ts (1)

73-73: Casting through unknown is acceptable but avoidable.

The pattern base as unknown as TNpmModule is a pragmatic escape hatch. Consider tightening the return type of createApiBuilder via generics so the augmentation can be safely expressed with a single assertion or none.

-const npm = base as unknown as TNpmModule;
+// If createApiBuilder<TResources, TAugment>() can parameterize augmentation,
+// this extra 'unknown' hop becomes unnecessary.
+const npm = base as TNpmModule;

If changing core typings is out of scope, keep the current line; it’s consistent with the module pattern elsewhere.

fync/src/github/index.ts (2)

259-259: Cast-through-unknown aligns with other modules.

Fine to keep for consistency. If you later tighten createApiBuilder’s return type, you can drop the unknown hop.


305-308: Starred count likely undercounts due to pagination.

getUserStarredCount uses a single page; GitHub paginates starred repos. If accuracy matters, follow Link headers or iterate pages with per_page=100 until exhausted.

-	github.getUserStarredCount = async function (username: string) {
-		const starred = await base.users.getUserStarred({ username });
-		return Array.isArray(starred) ? starred.length : 0;
-	};
+	github.getUserStarredCount = async function (username: string) {
+		let page = 1, total = 0, batch: any[] = [];
+		do {
+			batch = await base.users.getUserStarred({ username, per_page: 100, page });
+			total += Array.isArray(batch) ? batch.length : 0;
+			page += 1;
+		} while (Array.isArray(batch) && batch.length === 100);
+		return total;
+	};
fync/src/google-calendar/index.ts (1)

255-264: Avoid swallowing errors when aggregating events across calendars.

Silent catch {} hides useful failures (e.g., permission issues or rate limits), making debugging hard and masking partial results.

For example, collect errors and return them alongside results or at least log with context:

-        try {
+        try {
           const eventsResponse = await base.events.listEvents({
             calendarId: cal.id,
             maxResults,
             singleEvents: true,
             orderBy: "startTime",
           });
           results.push({ calendar: cal, events: eventsResponse.items || [] });
-        } catch {}
+        } catch (err) {
+          // Option A: push an error record
+          results.push({ calendar: cal, events: [], error: err instanceof Error ? err.message : String(err) });
+          // Option B: or minimally log
+          // console.warn("Failed to list events for calendar", cal.id, err);
+        }
fync/src/core/api-factory-enhanced.ts (1)

105-113: Preset selection works; prefer authenticated preset when a token is present.

Current order picks default before authenticated. For providers like GitHub (no default), you already fall through; for others, it’s fine. As a refinement, prefer authenticated when a bearer token exists, then default, then unauthenticated.

-const ratePreset = typeof config.rateLimit === "string"
-  ? (() => {
-    const preset = RATE_LIMIT_PRESETS[config.rateLimit as keyof typeof RATE_LIMIT_PRESETS] as any;
-    return preset?.default ?? preset?.authenticated ?? preset?.unauthenticated ?? undefined;
-  })()
-  : config.rateLimit;
+const ratePreset = typeof config.rateLimit === "string"
+  ? (() => {
+    const preset = RATE_LIMIT_PRESETS[config.rateLimit as keyof typeof RATE_LIMIT_PRESETS] as any;
+    if (config.auth?.type === "bearer" && preset?.authenticated) return preset.authenticated;
+    return preset?.default ?? preset?.unauthenticated ?? undefined;
+  })()
+  : config.rateLimit;
README.md (2)

96-109: Double‑check GitLab getUser argument: GitLab’s GET /users/{id} expects a numeric ID.

Your example passes a username string, which likely 404s against the canonical endpoint. If your convenience wrapper truly supports username, please call that out. Otherwise, update the snippet to use a numeric ID or provide a username-based helper.

Suggested doc tweak (numeric ID example):

-// Get user info
-const user = await gitlab.getUser('remcostoeten')
+// Get user info (GitLab expects numeric user ID)
+const user = await gitlab.getUser(278964)

Would you like me to add a lightweight getUserByUsername(username: string) convenience that resolves the ID and then calls getUser(id)?


259-272: Minor phrasing polish (optional).

“User and group information — Get profiles, followers, following...” reads a bit GitHub‑ish. If GitLab objects don’t expose “followers/following” consistently, consider softening this bullet to “profile info and relationships.”

fync/src/core/resource-factory.ts (3)

25-42: interpolatePath only replaces the first occurrence per key; support repeated placeholders and safer replacement.

Current String.replace will replace only once. Rare, but easy to make robust and still readable.

Apply:

-  if (path.includes(placeholder)) {
-    path = path.replace(placeholder, encodeURIComponent(String(value)));
-  } else {
+  if (path.includes(placeholder)) {
+    path = path.replaceAll(placeholder, encodeURIComponent(String(value)));
+  } else {

Optionally, consider handling array query values by appending multiple entries instead of String(value) on arrays.


66-69: Unreachable "GET" fallback in POST/PUT/PATCH branch; simplify and strengthen typing.

Inside this branch, definition.method is already one of POST/PUT/PATCH. The "GET" fallback is dead code and obscures intent.

- const response = await apiClient[
-   (definition.method || "GET").toLowerCase() as "post" | "put" | "patch"
- ](path, data, { params: queryParams });
+ const verb = definition.method!.toLowerCase() as "post" | "put" | "patch";
+ const response = await apiClient[verb](path, data, { params: queryParams });

18-22: Consider carrying method-specific data/response typing through TResource (future improvement).

Returning Promise<any> everywhere limits type safety. If/when you’re ready, extend TMethodDefinition with generics like <TReq = unknown, TRes = unknown> and thread them through TResource. Not blocking for this PR.

fync/examples/unified-usage.ts (1)

13-15: GitLab getUser likely needs numeric ID (same as README note).

If your wrapper supports username, please document it. Otherwise switch to a numeric ID to keep examples copy‑pasteable.

-const user = await gitlab.getUser("remcostoeten");
+const user = await gitlab.getUser(278964);
fync/src/core/api-factory.ts (2)

73-77: Header precedence: let per-config headers override auth if desired (optional).

Current order places authHeaders after config.headers, making it hard to override Authorization per-config. Consider swapping the merge order or documenting the precedence.

-const defaultHeaders = {
-  "Content-Type": "application/json",
-  ...config.headers,
-  ...authHeaders,
-};
+const defaultHeaders = {
+  "Content-Type": "application/json",
+  ...authHeaders,
+  ...config.headers, // allow user-provided headers to win
+};

41-45: Browser compatibility note (optional): Buffer usage ties this to Node.

Buffer is Node‑only. If browser support is intended later, provide a safe branch using btoa or a small base64 helper when Buffer is undefined.

fync/package.json (4)

3-3: Major bump to 4.0.0—ensure breaking changes are documented.

At minimum, call out the Spotify convenience rename (searchsearchAll) and the tightened core type exports in a CHANGELOG or release notes.

I can draft a succinct CHANGELOG entry for 4.0.0 if you want.


63-65: Build pipeline assumption: tsc must output to dist/ for Babel step.

Make sure tsconfig.json sets outDir: "dist"; otherwise babel dist ... will fail. If not guaranteed, consider rimraf dist-cjs && mkdir -p dist-cjs pre-step and a guard in CI.


74-76: Release scripts are convenient but can create two commits per release.

npm version creates its own commit/tag; combined with the pre‑commit here, you’ll often get an extra empty commit. That’s fine, but if you want a single canonical version bump commit, drop the manual git add/commit.


117-118: Undici as a runtime dependency might be unnecessary on Node 18+.

You aren’t importing it, and Node 18+ ships fetch. Consider removing to reduce install size unless you plan to use Undici APIs directly.

fync/src/gitlab/types/gitlab-user.ts (2)

89-89: Specialize base entity IDs to number

Tighten id to number at the base by using the generic: TBaseEntity<number>. This avoids accidental widening to string | number.

-} & TBaseEntity;
+} & TBaseEntity<number>;

Repeat the same change at Lines 98, 115, and 141 for all GitLab user-related types.

Also applies to: 98-98, 115-115, 141-141


63-74: Factor status into a reusable type

status is a coherent sub-structure used across GitLab entities. Extracting it into TGitLabUserStatus improves reuse and cohesion.

Proposed addition (in the same file or gitlab-common.ts):

export type TGitLabUserStatus = {
  emoji: string;
  message: string;
  message_html: string;
  availability: "not_set" | "busy";
  clear_status_at: string | null;
};

export type TGitLabUser = {
  // ...
  status?: TGitLabUserStatus | null;
} & TBaseEntity<number>;
WARP.md (2)

87-87: Fix code fence language specifier

ts path=null start=null breaks some Markdown tooling. Use a plain language identifier.

-```ts path=null start=null
+```ts

11-31: Unify package manager commands to avoid confusion

You recommend pnpm install but use npm run for scripts. Either show both variants or standardize on one to reduce friction for newcomers.

Example reword:

  • Install deps: pnpm install (or npm install)
  • Build ESM/CJS: pnpm build && pnpm build:cjs (or npm run build && npm run build:cjs)
fync/src/gitlab/types/gitlab-project.ts (4)

159-166: Doc comment mismatch: autoclose vs auto devops

The comment says “Auto devops enabled” but the field is autoclose_referenced_issues. Adjust the comment to reflect the actual field.

- /** Auto devops enabled */
- autoclose_referenced_issues: boolean;
+ /** Autoclose referenced issues */
+ autoclose_referenced_issues: boolean;

287-287: Constrain access level to a known enum, not a bare number

GitLab access levels are a fixed set (e.g., 0, 5, 10, 20, 30, 40, 50). Encoding them as number loses safety and IDE help.

Add in gitlab-common.ts:

export type TGitLabAccessLevel = 0 | 5 | 10 | 20 | 30 | 40 | 50;

Then update here:

- /** Access level */
- access_level: number;
+ /** Access level */
+ access_level: TGitLabAccessLevel;

Also applies to: 292-292


271-271: Narrow base entity id to number

Same suggestion as for user types: specialize TBaseEntity<number> to keep id precise.

-} & TBaseEntity;
+} & TBaseEntity<number>;

235-239: Self-referential type can bloat declarations

forked_from_project?: TGitLabProject; introduces deep recursion in .d.ts. Consider a lightweight reference type to avoid large emit graphs.

export type TGitLabProjectRef = Pick<TGitLabProject, "id" | "name" | "path_with_namespace" | "web_url">;
- /** Forked from project */
- forked_from_project?: TGitLabProject;
+ /** Forked from project (lightweight ref) */
+ forked_from_project?: TGitLabProjectRef;
GEMINI.md (2)

31-38: Add language to fenced code block (fixes MD040)

Specify a language for the file tree to satisfy markdownlint and improve rendering.

-```
+```text
 src/
 ├── [api-name]/
 │   ├── index.ts          # Main API implementation
 │   ├── types.ts          # API-specific types (optional)
 │   └── README.md         # Usage documentation (optional)
 └── core/                 # Shared architecture (DO NOT MODIFY)

67-67: Remove trailing punctuation in headings (fixes MD026)

Headings should not end with a colon.

-#### Resource Rules:
+#### Resource Rules
-#### Method Definition Rules:
+#### Method Definition Rules
-#### Auth Types:
+#### Auth Types

Also applies to: 72-72, 100-100

.cursor/rules/fync-architecture.mdc (1)

221-230: Clarify body/options calling convention with a non-GET example and highlight path placeholders.

Good guidance, but add a short note that “options” must include all {param} placeholders for the path. This prevents common misuse when POST endpoints still have path params.

I can add a one-liner below the example if you want.

Also applies to: 246-254

fync/src/gitlab/types/gitlab-snippet.ts (1)

5-13: Prefer TBaseEntity (or remove duplicate id) to avoid redundant declarations.

You declare id: number and also intersect with TBaseEntity, which also carries id. Make the intent explicit and avoid drift.

Apply:

-export type TGitLabSnippet = {
-	/** Snippet ID */
-	id: number;
+export type TGitLabSnippet = {
+	/** Snippet ID */
+	id: number;
 	/** Snippet title */
 	title: string;
 	/** Snippet file name */
 	file_name: string;
@@
-} & TBaseEntity;
+} & TBaseEntity<number>;

Optionally drop the explicit id: number; field and rely solely on TBaseEntity<number> for consistency across types. I can batch-apply that across GitLab types.

Also applies to: 37-37

fync/src/gitlab/types/gitlab-group.ts (2)

31-33: Potential redundancy with public vs visibility.

A public: boolean flag can drift from visibility === "public". Prefer a single source of truth.

Either remove public or mark it deprecated in a comment if it exists in legacy payloads.

Also applies to: 13-15


4-7: Consider TBaseEntity<number> to make numeric IDs explicit.

Same pattern as other types: intersecting with TBaseEntity<number> makes the intent explicit and avoids id type drift.

Apply:

-} & TBaseEntity;
+} & TBaseEntity<number>;

Also applies to: 171-171

fync/src/gitlab/index.ts (3)

41-45: Reminder: repository file endpoints require a ref query parameter.

/projects/{id}/repository/files/{file_path} and the README variant will 400 without a ref. Ensure callers pass { ref: "main" } (or similar). Optionally provide a convenience method that discovers the default branch and supplies it.

I can add a getProjectReadmeAtDefaultBranch(projectId) helper that looks up the default branch then fetches the README.


16-17: Check existence of followers/following endpoints.

/users/{id}/followers and /users/{id}/following don’t appear in many GitLab deployments. If not supported, remove to avoid 404s.

I can scrub the module after you confirm.


208-226: Type the convenience methods with your public GitLab types.

Everything returns Promise<any>. Given you added rich types under fync/src/gitlab/types, wire them in for better DX.

Example:

-type TGitLabModule = TModule<typeof resources> & {
-	getUser: (id: string | number) => Promise<any>;
-	getProject: (id: string | number) => Promise<any>;
+type TGitLabModule = TModule<typeof resources> & {
+	getUser: (id: string | number) => Promise<TGitLabUser>;
+	getProject: (id: string | number) => Promise<TGitLabProject>;
 	// ...
 };

I can generate a full typed surface if you confirm which type maps exist (Project, Group, Issue, etc.).

fync/src/gitlab/types/gitlab-merge-request.ts (2)

4-6: Remove duplicate id and specialize TBaseEntity for numeric IDs

You're declaring id: number and also intersecting with TBaseEntity which itself declares an id. Prefer specializing the base and letting it carry the id to avoid duplication and future drift.

Apply:

-export type TGitLabMergeRequest = {
-	/** Merge request ID */
-	id: number;
+export type TGitLabMergeRequest = TBaseEntity<number> & {
+	/** Merge request internal ID */

16-20: Forward-compat for status unions

GitLab periodically adds new status values. Consider widening union types to accept unknown strings to prevent breaking consumers on minor API updates, while still guiding with known literals.

Example pattern:

- status: "created" | "waiting_for_resource" | "preparing" | "pending" | "running" | "success" | "failed" | "canceled" | "skipped" | "manual" | "scheduled";
+ status: "created" | "waiting_for_resource" | "preparing" | "pending" | "running" | "success" | "failed" | "canceled" | "skipped" | "manual" | "scheduled" | (string & {});

Apply similarly to merge_status, detailed_merge_status, and pipeline/head_pipeline statuses.

Also applies to: 112-125, 155-171

fync/src/gitlab/types/gitlab-pipeline.ts (3)

4-6: Remove duplicate id and specialize TBaseEntity

Same pattern as MRs: avoid repeating the id field and specialize the base for numeric IDs.

-export type TGitLabPipeline = {
-	/** Pipeline ID */
-	id: number;
+export type TGitLabPipeline = TBaseEntity<number> & {
+	/** Pipeline IID */

130-155: Reuse existing commit type to reduce duplication

TGitLabJob.commit duplicates TGitLabMergeRequestCommit with only trailers added. Reuse the existing type and extend it locally.

+import type { TGitLabMergeRequestCommit } from "./gitlab-merge-request";
@@
-	/** Commit */
-	commit: {
-		/** Commit ID */
-		id: string;
-		/** Short ID */
-		short_id: string;
-		/** Title */
-		title: string;
-		/** Author name */
-		author_name: string;
-		/** Author email */
-		author_email: string;
-		/** Message */
-		message: string;
-		/** Authored date */
-		authored_date: string;
-		/** Committer name */
-		committer_name: string;
-		/** Committer email */
-		committer_email: string;
-		/** Committed date */
-		committed_date: string;
-		/** Trailers */
-		trailers: Record<string, string>;
-		/** Web URL */
-		web_url: string;
-	};
+	/** Commit */
+	commit: TGitLabMergeRequestCommit & {
+		/** Trailers */
+		trailers: Record<string, string>;
+	};

This keeps one source of truth for commit fields and simplifies future updates.

Also applies to: 1-2


15-18: Optional: widen pipeline status/source unions for resilience

As with MR statuses, consider allowing unknown strings to avoid breakage on new GitLab enum values.

- status: "created" | "waiting_for_resource" | "preparing" | "pending" | "running" | "success" | "failed" | "canceled" | "skipped" | "manual" | "scheduled";
+ status: "created" | "waiting_for_resource" | "preparing" | "pending" | "running" | "success" | "failed" | "canceled" | "skipped" | "manual" | "scheduled" | (string & {});
@@
- source: "push" | "web" | "schedule" | "api" | "external" | "pipeline" | "chat" | "webide" | "merge_request_event" | "external_pull_request_event" | "parent_pipeline" | "ondemand_dast_scan" | "ondemand_dast_validation";
+ source: "push" | "web" | "schedule" | "api" | "external" | "pipeline" | "chat" | "webide" | "merge_request_event" | "external_pull_request_event" | "parent_pipeline" | "ondemand_dast_scan" | "ondemand_dast_validation" | (string & {});
fync/src/gitlab/types/gitlab-search.ts (1)

127-129: Scope completeness (optional)

Depending on coverage targets, consider including additional scopes like "epics" or "notes" if you plan to expose them soon.

-	scope?: "projects" | "issues" | "merge_requests" | "milestones" | "wiki_blobs" | "commits" | "blobs" | "users";
+	scope?: "projects" | "issues" | "merge_requests" | "milestones" | "wiki_blobs" | "commits" | "blobs" | "users" | "epics" | "notes";
fync/src/npm/types/npm-package.ts (1)

33-35: Prefer unknown over any and keep optionality accurate

Minor polish: use Record<string, unknown> instead of any, and ensure optionality matches registry payloads.

-	config?: Record<string, any>;
+	config?: Record<string, unknown>;
@@
-	publishConfig?: Record<string, any>;
+	publishConfig?: Record<string, unknown>;

Also applies to: 41-45

fync/src/gitlab/types/gitlab-issue.ts (13)

1-2: Double-check import path stability given PR’s export cleanups

PR touches core exports. Please verify that ../../core/types still re-exports TBaseEntity. If not, import from the concrete file to avoid brittle barrels.

Proposed change if the barrel no longer re-exports:

-import type { TBaseEntity } from "../../core/types";
+import type { TBaseEntity } from "../../core/types/base";

5-6: Avoid duplicate id typing; parametrize TBaseEntity with number

id: number is declared explicitly and also comes from & TBaseEntity (defaulting to string | number). Intersecting works but is redundant and obscures intent. Prefer TBaseEntity<number> and drop the explicit id property.

 export type TGitLabIssue = {
-	/** Issue ID */
-	id: number;
 	/** Issue internal ID */
 	iid: number;
@@
-} & TBaseEntity;
+} & TBaseEntity<number>;

Also applies to: 172-172


175-176: Same duplication in TGitLabIssueNote; make id come from TBaseEntity<number>

Mirror the approach above for notes as well.

 export type TGitLabIssueNote = {
-	/** Note ID */
-	id: number;
@@
-} & TBaseEntity;
+} & TBaseEntity<number>;

Also applies to: 236-236


3-3: Factor shared helper types at the top

You repeat the “references” shape and hardcode issue types inline. Introduce small helpers to dedupe and centralize these unions.

+// Shared helpers
+type TGitLabReferences = {
+	short: string;
+	relative: string;
+	full: string;
+};
+
+// Align with GitLab v4 issue types (please confirm set below)
+export type TGitLabIssueType =
+	| "issue"
+	| "incident"
+	| "test_case"
+	| "task";
+// NOTE: If "requirement" is truly returned in your payloads, extend the union here instead of inline.

24-24: Centralize issue_type union and confirm values

Move to TGitLabIssueType and please confirm whether "requirement" is actually returned by your GitLab instance/API version. My understanding is that “requirement” is a separate entity rather than an issue_type.

-	issue_type: "issue" | "incident" | "test_case" | "requirement" | "task";
+	issue_type: TGitLabIssueType;

104-111: DRY up duplicate references structures

Use the TGitLabReferences alias in both places.

 	/** References */
-	references: {
-		/** Short reference */
-		short: string;
-		/** Relative reference */
-		relative: string;
-		/** Full reference */
-		full: string;
-	};
+	references: TGitLabReferences;
@@
-		/** Epic references */
-		references: {
-			/** Short reference */
-			short: string;
-			/** Relative reference */
-			relative: string;
-			/** Full reference */
-			full: string;
-		};
+		/** Epic references */
+		references: TGitLabReferences;

Also applies to: 139-146


51-51: Verify merge_requests_count field; make optional if not always present

I don’t consistently see merge_requests_count on issue payloads. If this isn’t guaranteed, mark as optional to avoid over-typing.

-	merge_requests_count: number;
+	merge_requests_count?: number;

67-76: Clarify time unit semantics in time_stats

GitLab returns seconds for these numeric fields. Minor doc tweak helps consumers avoid misinterpretation.

-		/** Time estimate */
-		time_estimate: number;
-		/** Total time spent */
-		total_time_spent: number;
+		/** Time estimate (seconds) */
+		time_estimate: number;
+		/** Total time spent (seconds) */
+		total_time_spent: number;

87-89: Consider narrowing task_status to the known values (if stable in your payloads)

If your API responses are consistent, replace string with a union of known statuses. Otherwise, leave as string.

-	/** Task status */
-	task_status: string;
+	/** Task status */
+	task_status:
+		| "none"
+		| "todo"
+		| "incomplete"
+		| "done"
+		| (string & {}); // fallback for unknowns

188-188: noteable_type likely needs "Commit" (and possibly "Epic") or the type name should narrow to Issue-only

You named the type TGitLabIssueNote but allow MR/Snippet noteables; either:

  • Rename to TGitLabNote for general notes, or
  • Narrow to "Issue" only.

If keeping it general, add "Commit" (common) and consider "Epic" depending on your scope.

-	noteable_type: "Issue" | "MergeRequest" | "Snippet";
+	noteable_type: "Issue" | "MergeRequest" | "Snippet" | "Commit";
+	// Add "Epic" here if you intend to cover group epics as noteables:
+	// | "Epic";

202-202: Broaden type for notes to include "Note" (plain) and "LegacyDiffNote"

Plain system/user notes often come as "Note". Legacy diff notes can appear in older/compat modes.

-	type: "DiscussionNote" | "DiffNote" | null;
+	type: "Note" | "DiscussionNote" | "DiffNote" | "LegacyDiffNote" | null;

204-204: commands_changes should be optional

It’s only present when a quick action mutates state. Make it optional to reflect reality.

-	/** Commands changes */
-	commands_changes: {
+	/** Commands changes (present only when quick actions apply) */
+	commands_changes?: {

233-233: Document timestamp format

Minor doc: specify ISO-8601 to help callers parse correctly.

-			/** Spent at */
-			spent_at: string;
+			/** Spent at (ISO-8601) */
+			spent_at: string;

Comment on lines +122 to +141
export function [ApiName](config: { token: string }): T[ApiName]Module {
const base = build[ApiName](config, resources);
const [apiName] = base as T[ApiName]Module;

// Implement convenience methods
[apiName].getUser = function (id: string) {
return base.users.getUser({ id });
};

[apiName].searchItems = function (query: string, options?: any) {
return base.search.search({
q: query,
limit: options?.limit || 20,
...options,
});
};

return [apiName];
}
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Template bug: invalid destructuring and identifier usage in Step 6.

const [apiName] = base as T[ApiName]Module; is not valid JavaScript/TypeScript. This will mislead contributors copying the template.

Apply:

-export function [ApiName](config: { token: string }): T[ApiName]Module {
-    const base = build[ApiName](config, resources);
-    const [apiName] = base as T[ApiName]Module;
+export function [ApiName](config: { token: string }): T[ApiName]Module {
+    const base = build[ApiName](config, resources);
+    const api = base as T[ApiName]Module;
 
     // Implement convenience methods
-    [apiName].getUser = function (id: string) {
+    api.getUser = function (id: string) {
         return base.users.getUser({ id });
     };
 
-    [apiName].searchItems = function (query: string, options?: any) {
+    api.searchItems = function (query: string, options?: any) {
         return base.search.search({
             q: query,
             limit: options?.limit || 20,
             ...options,
         });
     };
 
-    return [apiName];
+    return api;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function [ApiName](config: { token: string }): T[ApiName]Module {
const base = build[ApiName](config, resources);
const [apiName] = base as T[ApiName]Module;
// Implement convenience methods
[apiName].getUser = function (id: string) {
return base.users.getUser({ id });
};
[apiName].searchItems = function (query: string, options?: any) {
return base.search.search({
q: query,
limit: options?.limit || 20,
...options,
});
};
return [apiName];
}
```
export function [ApiName](config: { token: string }): T[ApiName]Module {
const base = build[ApiName](config, resources);
const api = base as T[ApiName]Module;
// Implement convenience methods
api.getUser = function (id: string) {
return base.users.getUser({ id });
};
api.searchItems = function (query: string, options?: any) {
return base.search.search({
q: query,
limit: options?.limit || 20,
...options,
});
};
return api;
}
🤖 Prompt for AI Agents
.cursor/rules/fync-architecture.mdc around lines 122-141: the template uses
invalid destructuring and placeholder identifier syntax (`const [apiName] = base
as T[ApiName]Module;` and later `[apiName].getUser`) which is not valid JS/TS;
replace this with a real variable (e.g., const api = base as T[ApiName]Module;)
then attach the convenience methods to that variable (api.getUser = ...,
api.searchItems = ...) and return api; ensure all placeholder bracketed
identifiers are removed and replaced with the chosen variable name.

Comment on lines +309 to +318
```typescript
{
path: "/users/{id}",
transform: (response: any) => ({
id: response.user_id,
name: response.full_name,
email: response.email_address,
})
}
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Rule conflict: arrow function used where arrows are prohibited.

The guide forbids arrow functions but the transform example uses one.

Apply:

 {
     path: "/users/{id}",
-    transform: (response: any) => ({
-        id: response.user_id,
-        name: response.full_name,
-        email: response.email_address,
-    })
+    transform: function (response: any) {
+        return {
+            id: response.user_id,
+            name: response.full_name,
+            email: response.email_address,
+        };
+    }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```typescript
{
path: "/users/{id}",
transform: (response: any) => ({
id: response.user_id,
name: response.full_name,
email: response.email_address,
})
}
```
{
path: "/users/{id}",
transform: function (response: any) {
return {
id: response.user_id,
name: response.full_name,
email: response.email_address,
};
}
}
🤖 Prompt for AI Agents
In .cursor/rules/fync-architecture.mdc around lines 309 to 318 the example uses
an arrow function for the transform property which conflicts with the guide;
replace the arrow with a regular function expression or declaration (e.g.,
transform: function(response: any) { return { id: response.user_id, name:
response.full_name, email: response.email_address }; }) making sure to preserve
types and returned object shape.

Comment thread fync/README.md
Comment on lines +19 to 27
## Supported APIs

// Get user's playlists
const playlists = await spotify.me.playlists.get()

// Get user's saved tracks
const savedTracks = await spotify.me.tracks.get()
- **GitHub** - Repositories, users, issues, pull requests, actions, and more
- **Spotify** - Tracks, playlists, user profiles, playback control
- **NPM Registry** - Package information, versions, downloads, search
- **Google Calendar** - Events, calendars, scheduling
- **Google Drive** - Files, folders, sharing, metadata
- **Vercel** - Projects, deployments, analytics

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Docs drift vs. PR objectives: fix factory import, add GitLab, update mutation signature, mention Spotify searchAll

  • The PR removes the wildcard re-export of the enhanced factory and standardizes on the base client. The README still imports createApiFactoryEnhanced from @remcostoeten/fync/core.
  • GitLab is now a supported API but is missing from the “Supported APIs” list.
  • PR notes Calendar/Drive mutations now use (data, options) with path placeholders; the Calendar example still uses a single-arg signature.
  • PR renames the Spotify convenience method searchsearchAll. Consider showcasing it alongside resource-scoped searches.

Proposed diffs:

Add GitLab to Supported APIs:

-## Supported APIs
+## Supported APIs
@@
 - **Vercel** - Projects, deployments, analytics
+- **GitLab** - Projects, groups, issues, merge requests, pipelines, snippets, search

Use base factory instead of enhanced one:

-import { createApiFactoryEnhanced } from '@remcostoeten/fync/core'
+import { createFyncApi } from '@remcostoeten/fync/core'
@@
-const api = createApiFactoryEnhanced({
+const api = createFyncApi({
   baseUrl: 'https://api.github.com',
   useResult: true // Enable TResult pattern
 })

Update Calendar create mutation to pass path placeholders via options:

-const newEvent = await calendar.calendar('primary').events.create({
-  summary: 'Meeting',
-  start: { dateTime: '2024-01-15T10:00:00Z' },
-  end: { dateTime: '2024-01-15T11:00:00Z' }
-})
+const newEvent = await calendar.calendar('primary').events.create(
+  {
+    summary: 'Meeting',
+    start: { dateTime: '2024-01-15T10:00:00Z' },
+    end: { dateTime: '2024-01-15T11:00:00Z' }
+  },
+  { path: { calendarId: 'primary' } } // (data, options) with path placeholders
+)

Mention the renamed Spotify convenience method:

 // Search and discovery
 const tracks = await spotify.search.tracks('bohemian rhapsody')
 const artists = await spotify.search.artists('queen')
+// Multi-scope convenience search across tracks, artists, albums, playlists
+const all = await spotify.searchAll('queen')

If useResult is no longer controlled by createFyncApi, we should also revise that section—see next comment.

Also applies to: 167-185, 119-125, 78-81

🧰 Tools
🪛 LanguageTool

[grammar] ~21-~21: There might be a mistake here.
Context: ...issues, pull requests, actions, and more - Spotify - Tracks, playlists, user prof...

(QB_NEW_EN)


[grammar] ~22-~22: There might be a mistake here.
Context: ...aylists, user profiles, playback control - NPM Registry - Package information, ve...

(QB_NEW_EN)


[grammar] ~23-~23: There might be a mistake here.
Context: ...information, versions, downloads, search - Google Calendar - Events, calendars, s...

(QB_NEW_EN)


[grammar] ~24-~24: There might be a mistake here.
Context: ...lendar** - Events, calendars, scheduling - Google Drive - Files, folders, sharing...

(QB_NEW_EN)


[grammar] ~25-~25: There might be a mistake here.
Context: ...ve** - Files, folders, sharing, metadata - Vercel - Projects, deployments, analyt...

(QB_NEW_EN)

Comment thread fync/README.md
Comment on lines +167 to 185
## Advanced Features

Fync provides a comprehensive error handling system with structured error information:
### Error Handling with TResult Pattern

```typescript
import { initializeErrorHandling, BaseError, HttpErrorHandler } from '@remcostoeten/fync/core'
import { createApiFactoryEnhanced } from '@remcostoeten/fync/core'

// Initialize error handling system
initializeErrorHandling()

// Use HTTP error handler with retry logic
const errorHandler = new HttpErrorHandler({
maxAttempts: 3,
baseDelay: 1000,
backoffMultiplier: 2
const api = createApiFactoryEnhanced({
baseUrl: 'https://api.github.com',
useResult: true // Enable TResult pattern
})

try {
const result = await errorHandler.execute(
async () => {
// Your API call here
return await api.getData()
},
{
service: 'github',
endpoint: '/user',
method: 'GET'
}
)
} catch (error) {
if (error instanceof BaseError) {
console.log('Error code:', error.info.code)
console.log('Service:', error.info.service)
console.log('Category:', error.info.category)
console.log('User message:', error.info.userMessage)
console.log('Suggested action:', error.info.suggestedAction)
console.log('Is retryable:', error.info.isRetryable)
}
const result = await api.get('/user')
if (result.success) {
console.log(result.data)
} else {
console.error(result.error)
}
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Confirm whether useResult is still supported and where TResult is exported

The PR states “Remove conflicting TResult re-export and keep canonical core/result.” If useResult is still a valid option on the base factory, great. If not, the example should be updated to show the current error-handling mechanism, and imports should come from @remcostoeten/fync/core/result.

Run:

Once confirmed, adjust the README imports and examples accordingly. I can send a follow-up patch.


🏁 Script executed:

#!/bin/bash
# Verify where TResult is exported from and whether useResult is supported
rg -nP 'export\s+(type|interface)\s+TResult' -C2 fync/src
rg -nP '\buseResult\b' -C2 fync/src
# Check any re-exports at core/index.ts
rg -nP 'export\s+\*.*api-factory-enhanced' fync/src/core/index.ts -C2

Length of output: 399


🏁 Script executed:

#!/bin/bash
# Locate the definition of createApiFactoryEnhanced to inspect its parameters
rg -nP -n 'export\s+(async\s+)?function\s+createApiFactoryEnhanced' -C5 fync/src
rg -nP -n 'createApiFactoryEnhanced\<\' -C5 fync/src

Length of output: 202


🏁 Script executed:

#!/bin/bash
# Locate all occurrences of createApiFactoryEnhanced to find its definition and usage
rg -n 'createApiFactoryEnhanced' -C5 fync/src

# Inspect core/index.ts to see which symbols are re-exported
sed -n '1,200p' fync/src/core/index.ts

Length of output: 414


Update README code snippet to reflect current API and TResult export

The useResult option and the createApiFactoryEnhanced export have been removed in favor of the core factory returning TResult by default, and the canonical TResult/TApiError types now live in core/result.ts. Please update fync/README.md as follows:

  • Replace the import of createApiFactoryEnhanced with the base createApiFactory.
  • Remove the useResult: true option (the factory returns a TResult–wrapped response by default).
  • Import TResult and TApiError from @remcostoeten/fync/core/result instead of relying on a conflicting re-export.
  • Adjust the example to include generics on the request and illustrate the new error-handling pattern.

Example revised snippet:

- import { createApiFactoryEnhanced } from '@remcostoeten/fync/core'
+ import { createApiFactory } from '@remcostoeten/fync/core'
+ import { TResult, TApiError } from '@remcostoeten/fync/core/result'

- const api = createApiFactoryEnhanced({
-   baseUrl: 'https://api.github.com',
-   useResult: true // Enable TResult pattern
- })
+ const api = createApiFactory({
+   baseUrl: 'https://api.github.com'
+ })

- const result = await api.get('/user')
+ // Specify the expected data type; TResult<User, TApiError> is returned automatically
+ type User = { login: string; /* … */ }
+ const result: TResult<User, TApiError> = await api.get<User>('/user')

if (result.success) {
  console.log(result.data)
} else {
  console.error(result.error)
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
## Advanced Features
Fync provides a comprehensive error handling system with structured error information:
### Error Handling with TResult Pattern
```typescript
import { initializeErrorHandling, BaseError, HttpErrorHandler } from '@remcostoeten/fync/core'
import { createApiFactoryEnhanced } from '@remcostoeten/fync/core'
// Initialize error handling system
initializeErrorHandling()
// Use HTTP error handler with retry logic
const errorHandler = new HttpErrorHandler({
maxAttempts: 3,
baseDelay: 1000,
backoffMultiplier: 2
const api = createApiFactoryEnhanced({
baseUrl: 'https://api.github.com',
useResult: true // Enable TResult pattern
})
try {
const result = await errorHandler.execute(
async () => {
// Your API call here
return await api.getData()
},
{
service: 'github',
endpoint: '/user',
method: 'GET'
}
)
} catch (error) {
if (error instanceof BaseError) {
console.log('Error code:', error.info.code)
console.log('Service:', error.info.service)
console.log('Category:', error.info.category)
console.log('User message:', error.info.userMessage)
console.log('Suggested action:', error.info.suggestedAction)
console.log('Is retryable:', error.info.isRetryable)
}
const result = await api.get('/user')
if (result.success) {
console.log(result.data)
} else {
console.error(result.error)
}
```
import { createApiFactory } from '@remcostoeten/fync/core'
import { TResult, TApiError } from '@remcostoeten/fync/core/result'
const api = createApiFactory({
baseUrl: 'https://api.github.com'
})
// Specify the expected data type; TResult<User, TApiError> is returned automatically
type User = { login: string; /**/ }
const result: TResult<User, TApiError> = await api.get<User>('/user')
if (result.success) {
console.log(result.data)
} else {
console.error(result.error)
}
🤖 Prompt for AI Agents
In fync/README.md around lines 167–185, the example uses the removed
createApiFactoryEnhanced export and the obsolete useResult option and doesn't
import TResult/TApiError from the new location; update the snippet to import
createApiFactory from '@remcostoeten/fync/core', remove the useResult option
(the factory returns TResult by default), import TResult and TApiError from
'@remcostoeten/fync/core/result', and adjust the example to use generics on the
request (e.g., TResult<MyData, TApiError>) and show checking result.success then
using result.data or logging result.error to illustrate the new TResult-based
error-handling pattern.

Comment thread fync/SECURITY.md
Comment on lines +7 to +11
1. **Do not** open a public GitHub issue
2. Email security concerns to: [your-email@domain.com]
3. Include detailed information about the vulnerability
4. Allow reasonable time for us to address the issue before public disclosure

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Replace placeholder security contacts with real channels and define an SLA.

Responsible disclosure needs a working contact and an acknowledgement/response timeline. The placeholders make the policy non-actionable.

Apply this diff as a starting point (adjust to your actual domain/contact):

-1. **Do not** open a public GitHub issue
-2. Email security concerns to: [your-email@domain.com]
+1. **Do not** open a public GitHub issue
+2. Email security concerns to: security@fync.dev
 3. Include detailed information about the vulnerability
 4. Allow reasonable time for us to address the issue before public disclosure
-For security-related questions or concerns:
-- Email: [your-security-email@domain.com]
+For security-related questions or concerns:
+- Email: security@fync.dev
 - GitHub: [@remcostoeten](https://github.com/remcostoeten)

Optionally add a security.txt at /.well-known/security.txt and link to it here. Do you want me to draft that file?

Also applies to: 175-179

🤖 Prompt for AI Agents
In fync/SECURITY.md around lines 7-11 (and similarly update lines 175-179),
replace the placeholder email with your real security contact(s) (e.g.,
security@yourdomain.com and an encrypted PGP key or vulnerability disclosure
team alias), add a clear SLA: an acknowledgement timeframe (e.g., within 48
hours) and a remediation/response timeline (e.g., initial triage within 5
business days and periodic updates), list alternative contact channels (PGP
fingerprint, web form, or a ticket URL), and optionally note that a security.txt
is served at /.well-known/security.txt with a link; ensure the wording is
actionable and non-placeholder.

@@ -0,0 +1,273 @@
import type { TBaseEntity } from "../../core/types";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Confirm TBaseEntity export path

Same note as in MR types: verify ../../core/types actually exports TBaseEntity.

Run:


🏁 Script executed:

#!/bin/bash
fd -a 'core/types/index.ts'
rg -n 'export .* TBaseEntity' fync/src/core/types -C2

Length of output: 340


Incorrect import path for TBaseEntity
The module fync/src/core/types does not export TBaseEntity directly (there’s no index.ts barrel file). The only declaration is in core/types/base.ts, so importing from "../../core/types" will fail at compile time.

• Location needing update:

  • File fync/src/gitlab/types/gitlab-pipeline.ts, line 1
  • Current:
    import type { TBaseEntity } from "../../core/types";
  • Should be either:
    import type { TBaseEntity } from "../../core/types/base";

– Or add a new fync/src/core/types/index.ts that re-exports TBaseEntity from base.ts if you prefer a barrel file.

🤖 Prompt for AI Agents
In fync/src/gitlab/types/gitlab-pipeline.ts at line 1, the import path "import
type { TBaseEntity } from \"../../core/types\";" is incorrect because
TBaseEntity is declared in core/types/base.ts; update the import to reference
the concrete file path "../../core/types/base" or alternatively add a new barrel
file fync/src/core/types/index.ts that re-exports TBaseEntity from base.ts and
then keep the original import. Ensure the chosen fix is applied consistently
across imports that reference "../../core/types".

Comment on lines +38 to +41
/** Number of followers */
followers?: number;
/** Number of users following */
following?: number;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Remove GitHub-specific fields from TGitLabUser

followers, following, and public_repos don't exist on GitLab v4 user payloads. Keeping them will mislead consumers and break type-level guarantees.

Apply this diff:

   /** Number of followers */
-  followers?: number;
   /** Number of users following */
-  following?: number;
@@
-  /** Number of public projects */
-  public_repos?: number;
+  /** Public email */
+  public_email?: string | null;

Also applies to: 55-57


🌐 Web query:

GitLab API v4 "Users" response fields list (verify whether `followers`, `following`, and `public_repos` exist; confirm `public_email`).

💡 Result:

Short answer: GitLab API v4 user objects do NOT include followers, following, or public_repos. They do include public_email (named public_email in the user object). [1][2]

Supporting cites:

  • Official GitLab API Users docs (response fields such as id, username, name, state, avatar_url, web_url, created_at, bio, location, public_email, etc.). No followers/following/public_repos fields are listed. [1]
  • Example GET /users/:id response in the docs shows public_email and other profile fields; no followers/following/public_repos present. [2]

References:

  1. https://docs.gitlab.com/ee/api/users.html
  2. https://docs.gitlab.com/ee/api/users.html#single-user (example response)

Remove GitHub-specific fields from TGitLabUser
GitLab API v4 user objects do not include followers, following, or public_repos, but they do include public_email. Keeping the GitHub-specific fields will mislead consumers and break type-level guarantees.

Apply this diff:

   /** Number of followers */
-  followers?: number;
   /** Number of users following */
-  following?: number;
@@
-  /** Number of public projects */
-  public_repos?: number;
+  /** Public email */
+  public_email?: string | null;

Also applies to lines 55–57.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/** Number of followers */
followers?: number;
/** Number of users following */
following?: number;
// --- around lines 38–41 in fync/src/gitlab/types/gitlab-user.ts ---
/** Number of followers */
/** Number of users following */
// --- around lines 55–57 in fync/src/gitlab/types/gitlab-user.ts ---
/** Public email */
public_email?: string | null;
🤖 Prompt for AI Agents
In fync/src/gitlab/types/gitlab-user.ts around lines 38–41 and 55–57, the type
includes GitHub-specific fields (followers, following, public_repos) that do not
exist on GitLab API v4 user objects; remove those fields and replace them with
GitLab-appropriate properties (add public_email?: string) so the TGitLabUser
type matches GitLab v4 schema and avoids misleading consumers and type breaks.

@@ -1,55 +1,68 @@
import type { TBaseEntity } from "../../core/types";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix impossible/incorrect intersections with TBaseEntity across NPM types

Several NPM types intersect with TBaseEntity<...> which injects a required id field and timestamp shape that the NPM registry responses do not have. In one case (TNpmVulnerability) this creates an unsatisfiable type: id: string (from base) ∩ id: number (local) ⇒ never.

This will confuse consumers and can silently break type safety. Remove TBaseEntity from NPM shapes and keep the actual fields (_id, _rev, etc.). Also, make files optional; it’s not guaranteed in registry metadata.

Apply:

-import type { TBaseEntity } from "../../core/types";
@@
-type TNpmPackageInfo = TBaseEntity<string> & {
+type TNpmPackageInfo = {
 	_id: string;
 	_rev: string;
@@
-};
+};
@@
-type TNpmDownloadsPoint = TBaseEntity<string> & {
+type TNpmDownloadsPoint = {
 	downloads: number;
 	day: string;
 };
@@
-type TNpmDownloadsResponse = TBaseEntity<string> & {
+type TNpmDownloadsResponse = {
 	downloads: TNpmDownloadsPoint[];
 	start: string;
 	end: string;
 	package: string;
 };
@@
-type TNpmDownloadCount = TBaseEntity<string> & {
+type TNpmDownloadCount = {
 	downloads: number;
 	start: string;
 	end: string;
 	package: string;
 };
@@
-type TNpmUser = TBaseEntity<string> & {
+type TNpmUser = {
 	_id: string;
 	_rev: string;
@@
-};
+};
@@
-type TNpmOrganization = TBaseEntity<string> & {
+type TNpmOrganization = {
 	_id: string;
 	_rev: string;
@@
-};
+};
@@
-type TNpmVulnerability = TBaseEntity<string> & {
+type TNpmVulnerability = {
 	id: number;
@@
-};
+};
@@
-	files: string[];
+	files?: string[];

If you intended to share timestamp fields, introduce a separate TTimestamps-compatible shape with NPM’s field names (e.g., created, updated) rather than reusing TBaseEntity.

Also applies to: 66-85, 125-136, 137-143, 144-157, 158-167, 168-185

Comment thread GEMINI.md
Comment on lines +120 to +139
export function [ApiName](config: { token: string }): T[ApiName]Module {
const base = build[ApiName](config, resources);
const [apiName] = base as T[ApiName]Module;

// Implement convenience methods
[apiName].getUser = function (id: string) {
return base.users.getUser({ id });
};

[apiName].searchItems = function (query: string, options?: any) {
return base.search.search({
q: query,
limit: options?.limit || 20,
...options,
});
};

return [apiName];
}
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix invalid template code (destructuring and return value are wrong)

const [apiName] = base as T[ApiName]Module; is invalid, and returning [apiName] is incorrect. This snippet will confuse adopters.

Apply this diff:

-export function [ApiName](config: { token: string }): T[ApiName]Module {
-    const base = build[ApiName](config, resources);
-    const [apiName] = base as T[ApiName]Module;
-
-    // Implement convenience methods
-    [apiName].getUser = function (id: string) {
-        return base.users.getUser({ id });
-    };
-
-    [apiName].searchItems = function (query: string, options?: any) {
-        return base.search.search({
-            q: query,
-            limit: options?.limit || 20,
-            ...options,
-        });
-    };
-
-    return [apiName];
-}
+export function [ApiName](config: { token: string }): T[ApiName]Module {
+    const base = build[ApiName](config, resources) as T[ApiName]Module;
+
+    // Implement convenience methods
+    base.getUser = function (id: string) {
+        return base.users.getUser({ id });
+    };
+
+    base.searchItems = function (query: string, options?: any) {
+        return base.search.search({
+            q: query,
+            limit: options?.limit ?? 20,
+            ...options,
+        });
+    };
+
+    return base;
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function [ApiName](config: { token: string }): T[ApiName]Module {
const base = build[ApiName](config, resources);
const [apiName] = base as T[ApiName]Module;
// Implement convenience methods
[apiName].getUser = function (id: string) {
return base.users.getUser({ id });
};
[apiName].searchItems = function (query: string, options?: any) {
return base.search.search({
q: query,
limit: options?.limit || 20,
...options,
});
};
return [apiName];
}
```
export function [ApiName](config: { token: string }): T[ApiName]Module {
const base = build[ApiName](config, resources) as T[ApiName]Module;
// Implement convenience methods
base.getUser = function (id: string) {
return base.users.getUser({ id });
};
base.searchItems = function (query: string, options?: any) {
return base.search.search({
q: query,
limit: options?.limit ?? 20,
...options,
});
};
return base;
}
🤖 Prompt for AI Agents
In GEMINI.md around lines 120 to 139, the snippet incorrectly destructures and
returns the module: replace "const [apiName] = base as T[ApiName]Module;" with a
single identifier binding like "const api = base as T[ApiName]Module;" then
attach convenience methods to that identifier (e.g., api.getUser = ...
api.searchItems = ...) and finally return that identifier (return api); ensure
you do not use array/destructuring syntax and that the returned value is the
module object.

Comment thread GEMINI.md
Comment on lines +182 to +192
export function [ApiName](config: { token: string }): T[ApiName]Module {
const base = build[ApiName](config, resources);
const [apiName] = base as T[ApiName]Module;

// Implement convenience methods
[apiName].getExample = function (id: string) {
return base.examples.getExample({ id });
};

return [apiName];
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Same bug in the full template block

Mirror the above fix in the “Complete Implementation Template” section to keep guidance consistent and correct.

-export function [ApiName](config: { token: string }): T[ApiName]Module {
-    const base = build[ApiName](config, resources);
-    const [apiName] = base as T[ApiName]Module;
-
-    // Implement convenience methods
-    [apiName].getExample = function (id: string) {
-        return base.examples.getExample({ id });
-    };
-
-    return [apiName];
-}
+export function [ApiName](config: { token: string }): T[ApiName]Module {
+    const base = build[ApiName](config, resources) as T[ApiName]Module;
+
+    // Implement convenience methods
+    base.getExample = function (id: string) {
+        return base.examples.getExample({ id });
+    };
+
+    return base;
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function [ApiName](config: { token: string }): T[ApiName]Module {
const base = build[ApiName](config, resources);
const [apiName] = base as T[ApiName]Module;
// Implement convenience methods
[apiName].getExample = function (id: string) {
return base.examples.getExample({ id });
};
return [apiName];
}
export function [ApiName](config: { token: string }): T[ApiName]Module {
const base = build[ApiName](config, resources) as T[ApiName]Module;
// Implement convenience methods
base.getExample = function (id: string) {
return base.examples.getExample({ id });
};
return base;
}
🤖 Prompt for AI Agents
In GEMINI.md around lines 182–192, the “Complete Implementation Template”
incorrectly destructures and returns [apiName] instead of working with the built
module; update the snippet to cast base to the module type (e.g., const
apiModule = base as T[ApiName]Module), attach convenience methods to apiModule
(apiModule.getExample = function(id: string) { return
apiModule.examples.getExample({ id }); }), and return apiModule so the template
mirrors the earlier fixed example.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant