@strav/social 1.0.0-alpha.22 → 1.0.0-alpha.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@strav/social",
3
- "version": "1.0.0-alpha.22",
3
+ "version": "1.0.0-alpha.25",
4
4
  "description": "Strav social-login module — provider-agnostic OAuth/OIDC client. Normalized profile + token DTOs, state + PKCE helpers, capability gating, multi-provider routing. Line / Google / Facebook adapters ship as subpath imports (`@strav/social/line`, `@strav/social/google`, `@strav/social/facebook`).",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
7
7
  "types": "./src/index.ts",
8
8
  "exports": {
9
9
  ".": "./src/index.ts",
10
- "./line": "./src/line/index.ts",
11
- "./google": "./src/google/index.ts",
12
- "./facebook": "./src/facebook/index.ts",
10
+ "./line": "./src/drivers/line/index.ts",
11
+ "./google": "./src/drivers/google/index.ts",
12
+ "./facebook": "./src/drivers/facebook/index.ts",
13
13
  "./tenanted": "./src/tenanted/index.ts"
14
14
  },
15
15
  "files": [
@@ -23,9 +23,9 @@
23
23
  "access": "public"
24
24
  },
25
25
  "dependencies": {
26
- "@strav/database": "1.0.0-alpha.22",
27
- "@strav/http": "1.0.0-alpha.22",
28
- "@strav/kernel": "1.0.0-alpha.22"
26
+ "@strav/database": "1.0.0-alpha.25",
27
+ "@strav/http": "1.0.0-alpha.25",
28
+ "@strav/kernel": "1.0.0-alpha.25"
29
29
  },
30
30
  "peerDependencies": {
31
31
  "@types/bun": ">=1.3.14"
@@ -9,7 +9,7 @@
9
9
  * going to production.
10
10
  */
11
11
 
12
- import type { ProviderConfig } from '../types.ts'
12
+ import type { ProviderConfig } from '../../types.ts'
13
13
 
14
14
  export interface FacebookProviderConfig extends ProviderConfig {
15
15
  driver: 'facebook'
@@ -33,23 +33,23 @@
33
33
  * Graph API directly.
34
34
  */
35
35
 
36
- import type { OAuthTokens, SocialProfile } from '../dto/index.ts'
37
- import type { SocialCapability } from '../social_capabilities.ts'
36
+ import type { OAuthTokens, SocialProfile } from '../../dto/index.ts'
37
+ import type { SocialCapability } from '../../social_capabilities.ts'
38
38
  import type {
39
39
  AuthorizeInput,
40
40
  AuthorizeResult,
41
41
  ExchangeInput,
42
42
  RefreshInput,
43
43
  SocialDriver,
44
- } from '../social_driver.ts'
44
+ } from '../../social_driver.ts'
45
45
  import {
46
46
  InvalidTokenError,
47
47
  OAuthExchangeError,
48
48
  ProviderUnsupportedError,
49
49
  SocialProviderError,
50
50
  StateMismatchError,
51
- } from '../social_error.ts'
52
- import { codeChallengeFor, randomCodeVerifier, randomState } from '../pkce.ts'
51
+ } from '../../social_error.ts'
52
+ import { codeChallengeFor, randomCodeVerifier, randomState } from '../../pkce.ts'
53
53
  import {
54
54
  DEFAULT_FACEBOOK_PROFILE_FIELDS,
55
55
  facebookEndpoints,
@@ -4,8 +4,8 @@
4
4
  */
5
5
 
6
6
  import { type Application, ServiceProvider } from '@strav/kernel'
7
- import { SocialConfigError } from '../social_error.ts'
8
- import { SocialManager } from '../social_manager.ts'
7
+ import { SocialConfigError } from '../../social_error.ts'
8
+ import { SocialManager } from '../../social_manager.ts'
9
9
  import type { FacebookProviderConfig } from './facebook_config.ts'
10
10
  import { FacebookSocialDriver } from './facebook_driver.ts'
11
11
 
@@ -11,7 +11,7 @@
11
11
  * enforced per-authorize via `authorize({ extra: { hd: 'example.com' } })`.
12
12
  */
13
13
 
14
- import type { ProviderConfig } from '../types.ts'
14
+ import type { ProviderConfig } from '../../types.ts'
15
15
 
16
16
  export interface GoogleProviderConfig extends ProviderConfig {
17
17
  driver: 'google'
@@ -32,22 +32,22 @@
32
32
  * consent to one Workspace domain.
33
33
  */
34
34
 
35
- import type { OAuthTokens, SocialProfile } from '../dto/index.ts'
36
- import type { SocialCapability } from '../social_capabilities.ts'
35
+ import type { OAuthTokens, SocialProfile } from '../../dto/index.ts'
36
+ import type { SocialCapability } from '../../social_capabilities.ts'
37
37
  import type {
38
38
  AuthorizeInput,
39
39
  AuthorizeResult,
40
40
  ExchangeInput,
41
41
  RefreshInput,
42
42
  SocialDriver,
43
- } from '../social_driver.ts'
43
+ } from '../../social_driver.ts'
44
44
  import {
45
45
  InvalidTokenError,
46
46
  OAuthExchangeError,
47
47
  SocialProviderError,
48
48
  StateMismatchError,
49
- } from '../social_error.ts'
50
- import { codeChallengeFor, randomCodeVerifier, randomState } from '../pkce.ts'
49
+ } from '../../social_error.ts'
50
+ import { codeChallengeFor, randomCodeVerifier, randomState } from '../../pkce.ts'
51
51
  import { GOOGLE_ENDPOINTS, type GoogleProviderConfig } from './google_config.ts'
52
52
 
53
53
  const PROVIDER = 'google'
@@ -8,8 +8,8 @@
8
8
  */
9
9
 
10
10
  import { type Application, ServiceProvider } from '@strav/kernel'
11
- import { SocialConfigError } from '../social_error.ts'
12
- import { SocialManager } from '../social_manager.ts'
11
+ import { SocialConfigError } from '../../social_error.ts'
12
+ import { SocialManager } from '../../social_manager.ts'
13
13
  import type { GoogleProviderConfig } from './google_config.ts'
14
14
  import { GoogleSocialDriver } from './google_driver.ts'
15
15
 
@@ -8,7 +8,7 @@
8
8
  * inside the channel (Line approval required for production).
9
9
  */
10
10
 
11
- import type { ProviderConfig } from '../types.ts'
11
+ import type { ProviderConfig } from '../../types.ts'
12
12
 
13
13
  export interface LineProviderConfig extends ProviderConfig {
14
14
  driver: 'line'
@@ -29,22 +29,22 @@
29
29
  * Token / refresh / revoke all use the standard OAuth2 endpoints.
30
30
  */
31
31
 
32
- import type { OAuthTokens, SocialProfile } from '../dto/index.ts'
33
- import type { SocialCapability } from '../social_capabilities.ts'
32
+ import type { OAuthTokens, SocialProfile } from '../../dto/index.ts'
33
+ import type { SocialCapability } from '../../social_capabilities.ts'
34
34
  import type {
35
35
  AuthorizeInput,
36
36
  AuthorizeResult,
37
37
  ExchangeInput,
38
38
  RefreshInput,
39
39
  SocialDriver,
40
- } from '../social_driver.ts'
40
+ } from '../../social_driver.ts'
41
41
  import {
42
42
  InvalidTokenError,
43
43
  OAuthExchangeError,
44
44
  SocialProviderError,
45
45
  StateMismatchError,
46
- } from '../social_error.ts'
47
- import { codeChallengeFor, randomCodeVerifier, randomState } from '../pkce.ts'
46
+ } from '../../social_error.ts'
47
+ import { codeChallengeFor, randomCodeVerifier, randomState } from '../../pkce.ts'
48
48
  import { LINE_ENDPOINTS, type LineProviderConfig } from './line_config.ts'
49
49
 
50
50
  const PROVIDER = 'line'
@@ -9,8 +9,8 @@
9
9
  */
10
10
 
11
11
  import { type Application, ServiceProvider } from '@strav/kernel'
12
- import { SocialConfigError } from '../social_error.ts'
13
- import { SocialManager } from '../social_manager.ts'
12
+ import { SocialConfigError } from '../../social_error.ts'
13
+ import { SocialManager } from '../../social_manager.ts'
14
14
  import type { LineProviderConfig } from './line_config.ts'
15
15
  import { LineSocialDriver } from './line_driver.ts'
16
16
 
@@ -26,10 +26,8 @@
26
26
  * encrypt/decrypt throws `ConfigError`.
27
27
  */
28
28
 
29
- // biome-ignore lint/style/useImportType: PostgresDatabase + SchemaRegistry value imports for @inject() metadata.
30
- import { PostgresDatabase, quoteIdent, Repository, SchemaRegistry } from '@strav/database'
31
- // biome-ignore lint/style/useImportType: Cipher + EventBus value imports for @inject() metadata.
32
- import { Cipher, EventBus, inject, ulid } from '@strav/kernel'
29
+ import { quoteIdent, Repository } from '@strav/database'
30
+ import { ulid } from '@strav/kernel'
33
31
  import type { OAuthTokens, SocialProfile } from '../dto/index.ts'
34
32
  import { SocialAccount } from './social_account.ts'
35
33
  import { socialAccountSchema } from './social_account_schema.ts'
@@ -48,21 +46,10 @@ export interface DisconnectInput {
48
46
  provider: string
49
47
  }
50
48
 
51
- @inject()
52
49
  export class SocialAccountRepository extends Repository<SocialAccount> {
53
50
  static override readonly schema = socialAccountSchema
54
51
  static override readonly model = SocialAccount
55
52
 
56
- // biome-ignore lint/complexity/noUselessConstructor: explicit constructor forces TS to emit `design:paramtypes` for @inject(). The fourth param is the Cipher for @encrypt token columns — apps must register EncryptionProvider before this repository resolves.
57
- constructor(
58
- db: PostgresDatabase,
59
- events: EventBus,
60
- registry?: SchemaRegistry,
61
- cipher?: Cipher,
62
- ) {
63
- super(db, events, registry, cipher)
64
- }
65
-
66
53
  /**
67
54
  * Upsert a social account by `(provider, provider_user_id)`.
68
55
  * Insert on first link; update tokens + cached profile fields
@@ -5,10 +5,48 @@
5
5
  * (`config.social.providers[name]`). The manager holds one
6
6
  * driver per configured name and routes calls into it.
7
7
  *
8
- * Methods drivers don't support throw `ProviderUnsupportedError`
9
- * synchronously. The driver's `capabilities` set declares the
10
- * supported feature set apps that branch on capability avoid
11
- * the throw by checking first.
8
+ * ## Capability gating the "honest LSP violation" pattern
9
+ *
10
+ * Strictly speaking, the interface below violates Liskov substitution
11
+ * because `FacebookSocialDriver.refresh()` throws synchronously instead
12
+ * of returning `Promise<OAuthTokens>` (Facebook doesn't issue refresh
13
+ * tokens). Same shape for any future driver that lacks an operation
14
+ * its interface declares.
15
+ *
16
+ * The framework prefers this trade-off over the alternative (every
17
+ * provider gets its own narrowed sub-interface, every app does a
18
+ * `if (isRefreshable(driver))` narrowing dance) for three reasons:
19
+ *
20
+ * 1. **Discoverability**: apps see ONE `SocialDriver` interface
21
+ * with all the operations a sign-in flow needs. They learn the
22
+ * surface once, not per-adapter.
23
+ *
24
+ * 2. **Capability flags are the typed truth**: each driver
25
+ * declares `capabilities: ReadonlySet<SocialCapability>`. Apps
26
+ * that care about portability check the flag and branch — the
27
+ * `unsupported` throw becomes the safety net for callers who
28
+ * forgot, not the primary API.
29
+ *
30
+ * 3. **Failures are loud and synchronous**. Drivers use the
31
+ * `unsupported(provider, op, reason?)` helper so the throw
32
+ * happens on the function call, NOT after a network round-trip
33
+ * had a chance to bill the user / consume rate limit. Apps fail
34
+ * fast.
35
+ *
36
+ * Apps that want compile-time safety against unsupported ops wrap the
37
+ * driver themselves:
38
+ *
39
+ * ```ts
40
+ * function refreshableDriver(d: SocialDriver) {
41
+ * if (!d.capabilities.has('tokens.refresh')) return null
42
+ * return d // narrowed by convention; refresh() is now safe to call.
43
+ * }
44
+ * ```
45
+ *
46
+ * A split into `SocialDriver` + `RefreshableSocialDriver` + ... may
47
+ * land later (`docs/code-quality.md` action item #4) — but doing so
48
+ * carries the manager's `use(name): SocialDriver` return type through
49
+ * a wider refactor. Until then, the capability set IS the contract.
12
50
  */
13
51
 
14
52
  import type { OAuthTokens, SocialProfile } from './dto/index.ts'
@@ -11,10 +11,8 @@
11
11
  * branching on a tenancy flag.
12
12
  */
13
13
 
14
- // biome-ignore lint/style/useImportType: PostgresDatabase value import for @inject() metadata.
15
- import { PostgresDatabase, quoteIdent, Repository, SchemaRegistry } from '@strav/database'
16
- // biome-ignore lint/style/useImportType: Cipher + EventBus value imports for @inject() metadata.
17
- import { Cipher, EventBus, inject, ulid } from '@strav/kernel'
14
+ import { quoteIdent, Repository } from '@strav/database'
15
+ import { ulid } from '@strav/kernel'
18
16
  import type { OAuthTokens, SocialProfile } from '../dto/index.ts'
19
17
  import { SocialAccountAlreadyLinkedError } from '../ledger/social_account_repository.ts'
20
18
  import { TenantedSocialAccount } from './tenanted_social_account.ts'
@@ -32,21 +30,10 @@ export interface DisconnectInput {
32
30
  provider: string
33
31
  }
34
32
 
35
- @inject()
36
33
  export class TenantedSocialAccountRepository extends Repository<TenantedSocialAccount> {
37
34
  static override readonly schema = tenantedSocialAccountSchema
38
35
  static override readonly model = TenantedSocialAccount
39
36
 
40
- // biome-ignore lint/complexity/noUselessConstructor: explicit constructor forces TS to emit `design:paramtypes` for @inject(). The fourth param is the Cipher for @encrypt token columns.
41
- constructor(
42
- db: PostgresDatabase,
43
- events: EventBus,
44
- registry?: SchemaRegistry,
45
- cipher?: Cipher,
46
- ) {
47
- super(db, events, registry, cipher)
48
- }
49
-
50
37
  async connect(input: ConnectInput): Promise<TenantedSocialAccount> {
51
38
  const existing = await this.findByProviderIdentity(
52
39
  input.provider,
File without changes
File without changes
File without changes