') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); chore(*): Improve @clerk/backend DX [Part 2 - Errors subpath exports] by dimkl · Pull Request #2362 · clerk/javascript · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changeset/fifty-snakes-accept.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
---
'@clerk/clerk-sdk-node': major
---

Drop all pre-instantiated Backend API resources (`allowlistIdentifiers`, `clients`, `emailAddresses`, `emails`, `invitations`, `organizations`, `phoneNumbers`, `redirectUrls`, `sessions`, `signInTokens`, `users`, `domains`). Use the `clerkClient` import instead.
```typescript
// Before
import { users } from "@clerk/clerk-sdk-node"
// After
import { clerkClient } from "@clerk/clerk-sdk-node"
clerkClient.users
```
23 changes: 23 additions & 0 deletions .changeset/tasty-terms-fly.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
---
'@clerk/clerk-sdk-node': major
'@clerk/backend': major
'@clerk/nextjs': major
---

Changes in `@clerk/backend` exports:
- Drop Internal `deserialize` helper
Comment thread
dimkl marked this conversation as resolved.
- Introduce `/errors` subpath export, eg:
```typescript
import {
TokenVerificationError,
TokenVerificationErrorAction,
TokenVerificationErrorCode,
TokenVerificationErrorReason } from '@clerk/backend/errors';
```
- Drop errors from top-level export
```typescript
// Before
import { TokenVerificationError, TokenVerificationErrorReason } from '@clerk/backend';
// After
import { TokenVerificationError, TokenVerificationErrorReason } from '@clerk/backend/errors';
```
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -58,11 +58,11 @@ jobs:

- name: Lint types using attw
timeout-minutes: ${{ fromJSON(vars.TIMEOUT_MINUTES_SHORT) }}
run: npx turbo lint:attw $TURBO_ARGS --filter=!nextjs --only
run: npx turbo lint:attw $TURBO_ARGS --filter=!nextjs --filter=!backend --only

- name: Lint types using attw [Errors Allowed]
timeout-minutes: ${{ fromJSON(vars.TIMEOUT_MINUTES_SHORT) }}
run: npx turbo lint:attw $TURBO_ARGS --filter=nextjs --continue --only
run: npx turbo lint:attw $TURBO_ARGS --filter=nextjs --filter=backend --continue --only
continue-on-error: true # TODO: Remove this when all related errors are fixed

- name: Run lint
Expand Down
25 changes: 23 additions & 2 deletions packages/backend/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,9 +24,30 @@
"default": "./dist/runtime/browser/crypto.mjs"
}
},
"exports": {
".": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.mjs"
},
"require": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"./errors": {
"import": {
"types": "./dist/errors.d.ts",
"default": "./dist/errors.mjs"
},
"require": {
"types": "./dist/errors.d.ts",
"default": "./dist/errors.js"
}
},
"./package.json": "./package.json"
},
"main": "./dist/index.js",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Let's drop main but keep types since TS configs with moduleResolution set to node will not be able to resolve the types otherwise.

TS will use the exports field only if moduleResolution is set to node16, nodenext or bundler. Many projects still use node though, so let's not be disruptive.

Extra context: https://www.typescriptlang.org/tsconfig#moduleResolution

@dimkldimklDec 15, 2023

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

i will apply it as part of: #2365

"module": "./dist/esm/index.js",
"types": "./dist/types/index.d.ts",
"files": [
"dist"
],
Expand Down
16 changes: 13 additions & 3 deletions packages/backend/src/__tests__/exports.test.ts
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
import type QUnit from 'qunit';

import * as errorExports from '../errors';
import * as publicExports from '../index';

export default (QUnit: QUnit) => {
Expand DownExpand Up@@ -29,8 +30,6 @@ export default (QUnit: QUnit) => {
'Session',
'SignInToken',
'Token',
'TokenVerificationError',
'TokenVerificationErrorReason',
'User',
'Verification',
'buildRequestUrl',
Expand All@@ -40,7 +39,6 @@ export default (QUnit: QUnit) => {
'createIsomorphicRequest',
'debugRequestState',
'decodeJwt',
'deserialize',
'hasValidSignature',
'makeAuthObjectSerializable',
'prunePrivateMetadata',
Expand All@@ -55,4 +53,16 @@ export default (QUnit: QUnit) => {
assert.deepEqual(Object.keys(publicExports).sort(), exportedApiKeys);
});
});

module('subpath /errors exports', () => {
test('should not include a breaking change', assert => {
const exportedApiKeys = [
'TokenVerificationError',
'TokenVerificationErrorAction',
'TokenVerificationErrorCode',
'TokenVerificationErrorReason',
];
assert.deepEqual(Object.keys(errorExports).sort(), exportedApiKeys);
});
});
};
1 change: 0 additions & 1 deletion packages/backend/src/api/resources/index.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
export * from './AllowlistIdentifier';
export * from './Client';
export * from './DeletedObject';
export * from './Deserializer';
export * from './Email';
export * from './EmailAddress';

Expand Down
File renamed without changes.
11 changes: 9 additions & 2 deletions packages/backend/src/index.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,7 +2,7 @@ import type { TelemetryCollectorOptions } from '@clerk/shared/telemetry';
import { TelemetryCollector } from '@clerk/shared/telemetry';
import type { SDKMetadata } from '@clerk/types';

import type { CreateBackendApiOptions } from './api';
import type { ApiClient, CreateBackendApiOptions } from './api';
import { createBackendApiClient } from './api';
import type { CreateAuthenticateRequestOptions } from './tokens';
import { createAuthenticateRequest } from './tokens';
Expand All@@ -25,7 +25,14 @@ export type ClerkOptions = CreateBackendApiOptions &
>
> & { sdkMetadata?: SDKMetadata; telemetry?: Pick<TelemetryCollectorOptions, 'disabled' | 'debug'> };

export function createClerkClient(options: ClerkOptions) {
// The current exported type resolves the following issue in packages importing createClerkClient
// TS4023: Exported variable 'clerkClient' has or is using name 'AuthErrorReason' from external module "/packages/backend/dist/index" but cannot be named.
export type ClerkClient = {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Discussed offline, problematic area with types and type inference, will come back to this after v5 or next week.

telemetry: TelemetryCollector;
} & ApiClient &
ReturnType<typeof createAuthenticateRequest>;

export function createClerkClient(options: ClerkOptions): ClerkClient {
const opts = { ...options };
const apiClient = createBackendApiClient(opts);
const requestState = createAuthenticateRequest({ options: opts, apiClient });
Expand Down
2 changes: 1 addition & 1 deletion packages/backend/src/runtime.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,7 +13,7 @@
*/

// @ts-ignore - These are package subpaths
import crypto from '#crypto';
import { webcrypto as crypto } from '#crypto';

type Runtime = {
crypto: Crypto;
Expand Down
2 changes: 1 addition & 1 deletion packages/backend/src/runtime/browser/crypto.mjs
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
export default crypto;
export const webcrypto = crypto;
12 changes: 1 addition & 11 deletions packages/backend/src/runtime/node/crypto.js
Original file line numberDiff line numberDiff line change
@@ -1,11 +1 @@
let webcrypto;
try {
webcrypto = require('node:crypto').webcrypto;
if (!webcrypto) {
webcrypto = global.crypto;
}
} catch (e) {
webcrypto = global.crypto;
}

module.exports = webcrypto;
module.exports.webcrypto = require('node:crypto').webcrypto;
2 changes: 1 addition & 1 deletion packages/backend/src/tokens/__tests__/keys.test.ts
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
import type QUnit from 'qunit';
import sinon from 'sinon';

import { TokenVerificationError, TokenVerificationErrorAction, TokenVerificationErrorReason } from '../../errors';
import runtime from '../../runtime';
import { jsonError, jsonOk } from '../../util/testUtils';
import { TokenVerificationError, TokenVerificationErrorAction, TokenVerificationErrorReason } from '../errors';
import {
mockJwks,
mockJwtPayload,
Expand Down
2 changes: 1 addition & 1 deletion packages/backend/src/tokens/__tests__/request.test.ts
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
import type QUnit from 'qunit';
import sinon from 'sinon';

import { TokenVerificationErrorReason } from '../../errors';
import runtime from '../../runtime';
import { jsonOk } from '../../util/testUtils';
import { AuthErrorReason, type AuthReason, AuthStatus, type RequestState } from '../authStatus';
import { TokenVerificationErrorReason } from '../errors';
import { mockInvalidSignatureJwt, mockJwks, mockJwt, mockJwtPayload, mockMalformedJwt } from '../fixtures';
import type { AuthenticateRequestOptions } from '../request';
import { authenticateRequest, loadOptionsFromHeaders } from '../request';
Expand Down
2 changes: 1 addition & 1 deletion packages/backend/src/tokens/authStatus.ts
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
import type { JwtPayload } from '@clerk/types';

import { createBackendApiClient } from '../api';
import type { TokenVerificationErrorReason } from '../errors';
import type { SignedInAuthObject, SignedInAuthObjectOptions, SignedOutAuthObject } from './authObjects';
import { signedInAuthObject, signedOutAuthObject } from './authObjects';
import type { TokenVerificationErrorReason } from './errors';

export enum AuthStatus {
SignedIn = 'signed-in',
Expand Down
2 changes: 1 addition & 1 deletion packages/backend/src/tokens/handshake.ts
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
import { TokenVerificationError, TokenVerificationErrorAction, TokenVerificationErrorReason } from './errors';
import { TokenVerificationError, TokenVerificationErrorAction, TokenVerificationErrorReason } from '../errors';
import { decodeJwt, hasValidSignature, type VerifyJwtOptions } from './jwt';
import { assertHeaderAlgorithm, assertHeaderType } from './jwt/assertions';
import { loadClerkJWKFromLocal, loadClerkJWKFromRemote } from './keys';
Expand Down
1 change: 0 additions & 1 deletion packages/backend/src/tokens/index.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
export * from './authObjects';
export { AuthStatus } from './authStatus';
export type { RequestState } from './authStatus';
export { TokenVerificationError, TokenVerificationErrorReason } from './errors';
export * from './factory';
export { debugRequestState } from './request';
export type { AuthenticateRequestOptions, OptionalVerifyTokenOptions } from './request';
2 changes: 1 addition & 1 deletion packages/backend/src/tokens/jwt/assertions.ts
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
import { TokenVerificationError, TokenVerificationErrorAction, TokenVerificationErrorReason } from '../errors';
import { TokenVerificationError, TokenVerificationErrorAction, TokenVerificationErrorReason } from '../../errors';
import { algs } from './algorithms';

export type IssuerResolver = string | ((iss: string) => boolean);
Expand Down
2 changes: 1 addition & 1 deletion packages/backend/src/tokens/jwt/verifyJwt.ts
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
import type { Jwt, JwtPayload } from '@clerk/types';

import { TokenVerificationError, TokenVerificationErrorAction, TokenVerificationErrorReason } from '../../errors';
// DO NOT CHANGE: Runtime needs to be imported as a default export so that we can stub its dependencies with Sinon.js
// For more information refer to https://sinonjs.org/how-to/stub-dependency/
import runtime from '../../runtime';
import { base64url } from '../../util/rfc4648';
import { TokenVerificationError, TokenVerificationErrorAction, TokenVerificationErrorReason } from '../errors';
import { getCryptoAlgorithm } from './algorithms';
import {
assertActivationClaim,
Expand Down
12 changes: 6 additions & 6 deletions packages/backend/src/tokens/keys.ts
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
import { API_URL, API_VERSION, JWKS_CACHE_TTL_MS, MAX_CACHE_LAST_UPDATED_AT_SECONDS } from '../constants';
import {
TokenVerificationError,
TokenVerificationErrorAction,
TokenVerificationErrorCode,
TokenVerificationErrorReason,
} from '../errors';
// DO NOT CHANGE: Runtime needs to be imported as a default export so that we can stub its dependencies with Sinon.js
// For more information refer to https://sinonjs.org/how-to/stub-dependency/
import runtime from '../runtime';
import { joinPaths } from '../util/path';
import { getErrorObjectByCode } from '../util/request';
import { callWithRetry } from '../util/shared';
import {
TokenVerificationError,
TokenVerificationErrorAction,
TokenVerificationErrorCode,
TokenVerificationErrorReason,
} from './errors';

type JsonWebKeyWithKid = JsonWebKey & { kid: string };

Expand Down
4 changes: 2 additions & 2 deletions packages/backend/src/tokens/request.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,13 +2,13 @@ import { parsePublishableKey } from '@clerk/shared/keys';
import type { JwtPayload } from '@clerk/types';

import { constants } from '../constants';
import type { TokenCarrier } from '../errors';
import { TokenVerificationError, TokenVerificationErrorReason } from '../errors';
import { assertValidSecretKey } from '../util/assertValidSecretKey';
import { buildRequest, stripAuthorizationHeader } from '../util/IsomorphicRequest';
import { isDevelopmentFromSecretKey } from '../util/shared';
import type { AuthStatusOptionsType, RequestState } from './authStatus';
import { AuthErrorReason, handshake, signedIn, signedOut } from './authStatus';
import type { TokenCarrier } from './errors';
import { TokenVerificationError, TokenVerificationErrorReason } from './errors';
import { verifyHandshakeToken } from './handshake';
import { decodeJwt } from './jwt';
import { verifyToken, type VerifyTokenOptions } from './verify';
Expand Down
2 changes: 1 addition & 1 deletion packages/backend/src/tokens/verify.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
import type { JwtPayload } from '@clerk/types';

import { TokenVerificationError, TokenVerificationErrorAction, TokenVerificationErrorReason } from './errors';
import { TokenVerificationError, TokenVerificationErrorAction, TokenVerificationErrorReason } from '../errors';
import type { VerifyJwtOptions } from './jwt';
import { decodeJwt, verifyJwt } from './jwt';
import type { LoadClerkJWKFromRemoteOptions } from './keys';
Expand Down
2 changes: 1 addition & 1 deletion packages/backend/tsconfig.declarations.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,6 @@
"emitDeclarationOnly": true,
"declarationMap": true,
"sourceMap": false,
"declarationDir": "./dist/types"
"declarationDir": "./dist"
}
}
3 changes: 1 addition & 2 deletions packages/backend/tsup.config.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,7 +10,7 @@ export default defineConfig(overrideOptions => {
const shouldPublish = !!overrideOptions.env?.publish;

const common: Options = {
entry: ['src/index.ts'],
entry: ['src/index.ts', 'src/errors.ts'],
onSuccess: `cpy 'src/runtime/**/*.{mjs,js,cjs}' dist/runtime`,
sourcemap: true,
define: {
Expand All@@ -19,7 +19,6 @@ export default defineConfig(overrideOptions => {
__DEV__: `${isWatch}`,
},
external: ['#crypto'],
legacyOutput: true,
bundle: true,
clean: true,
minify: false,
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,14 +25,10 @@ exports[`module exports should not change unless explicitly set 1`] = `
"Session",
"SignInToken",
"Token",
"TokenVerificationError",
"TokenVerificationErrorReason",
"User",
"Verification",
"allowlistIdentifiers",
"buildRequestUrl",
"clerkClient",
"clients",
"constants",
"createAuthenticateRequest",
"createClerkClient",
Expand All@@ -41,24 +37,15 @@ exports[`module exports should not change unless explicitly set 1`] = `
"createIsomorphicRequest",
"debugRequestState",
"decodeJwt",
"deserialize",
"domains",
"emailAddresses",
"emails",
"hasValidSignature",
"invitations",
"makeAuthObjectSerializable",
"organizations",
"phoneNumbers",
"prunePrivateMetadata",
"redirect",
"requireAuth",
"sanitizeAuthObject",
"sessions",
"signJwt",
"signedInAuthObject",
"signedOutAuthObject",
"users",
"verifyJwt",
"verifyToken",
"withAuth",
Expand Down
Loading