@sanity/sdk 2.5.0 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/index.d.ts +429 -27
  2. package/dist/index.js +657 -266
  3. package/dist/index.js.map +1 -1
  4. package/package.json +4 -3
  5. package/src/_exports/index.ts +18 -3
  6. package/src/auth/authMode.test.ts +56 -0
  7. package/src/auth/authMode.ts +71 -0
  8. package/src/auth/authStore.test.ts +85 -4
  9. package/src/auth/authStore.ts +63 -125
  10. package/src/auth/authStrategy.ts +39 -0
  11. package/src/auth/dashboardAuth.ts +132 -0
  12. package/src/auth/standaloneAuth.ts +109 -0
  13. package/src/auth/studioAuth.ts +217 -0
  14. package/src/auth/studioModeAuth.test.ts +43 -1
  15. package/src/auth/studioModeAuth.ts +10 -1
  16. package/src/auth/subscribeToStateAndFetchCurrentUser.ts +21 -6
  17. package/src/client/clientStore.test.ts +45 -43
  18. package/src/client/clientStore.ts +23 -9
  19. package/src/config/loggingConfig.ts +149 -0
  20. package/src/config/sanityConfig.ts +82 -22
  21. package/src/projection/getProjectionState.ts +6 -5
  22. package/src/projection/projectionQuery.test.ts +38 -55
  23. package/src/projection/projectionQuery.ts +27 -31
  24. package/src/projection/projectionStore.test.ts +4 -4
  25. package/src/projection/projectionStore.ts +3 -2
  26. package/src/projection/resolveProjection.ts +2 -2
  27. package/src/projection/statusQuery.test.ts +35 -0
  28. package/src/projection/statusQuery.ts +71 -0
  29. package/src/projection/subscribeToStateAndFetchBatches.test.ts +63 -50
  30. package/src/projection/subscribeToStateAndFetchBatches.ts +106 -27
  31. package/src/projection/types.ts +12 -0
  32. package/src/projection/util.ts +0 -1
  33. package/src/query/queryStore.test.ts +64 -0
  34. package/src/query/queryStore.ts +33 -11
  35. package/src/releases/getPerspectiveState.test.ts +17 -14
  36. package/src/releases/getPerspectiveState.ts +58 -38
  37. package/src/releases/releasesStore.test.ts +59 -61
  38. package/src/releases/releasesStore.ts +21 -35
  39. package/src/releases/utils/isReleasePerspective.ts +7 -0
  40. package/src/store/createActionBinder.test.ts +211 -1
  41. package/src/store/createActionBinder.ts +102 -13
  42. package/src/store/createSanityInstance.test.ts +85 -1
  43. package/src/store/createSanityInstance.ts +55 -4
  44. package/src/utils/logger-usage-example.md +141 -0
  45. package/src/utils/logger.test.ts +757 -0
  46. package/src/utils/logger.ts +537 -0
@@ -1,5 +1,6 @@
1
- import {describe, expect, it, vi} from 'vitest'
1
+ import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest'
2
2
 
3
+ import {configureLogging, type LogHandler, resetLogging} from '../utils/logger'
3
4
  import {createSanityInstance} from './createSanityInstance'
4
5
 
5
6
  describe('createSanityInstance', () => {
@@ -81,4 +82,87 @@ describe('createSanityInstance', () => {
81
82
  const child = parent.createChild({auth: {token: 'my-token'}})
82
83
  expect(child.config.auth).toEqual({apiHost: 'api.sanity.work', token: 'my-token'})
83
84
  })
85
+
86
+ describe('logging', () => {
87
+ const mockHandler: LogHandler = {
88
+ error: vi.fn(),
89
+ warn: vi.fn(),
90
+ info: vi.fn(),
91
+ debug: vi.fn(),
92
+ trace: vi.fn(),
93
+ }
94
+
95
+ beforeEach(() => {
96
+ vi.clearAllMocks()
97
+ configureLogging({
98
+ level: 'debug',
99
+ namespaces: ['sdk'],
100
+ handler: mockHandler,
101
+ })
102
+ })
103
+
104
+ afterEach(() => {
105
+ resetLogging()
106
+ })
107
+
108
+ it('should log instance creation at info level', () => {
109
+ createSanityInstance({projectId: 'test-proj', dataset: 'test-ds'})
110
+
111
+ expect(mockHandler.info).toHaveBeenCalledWith(
112
+ expect.stringContaining('[INFO] [sdk]'),
113
+ expect.objectContaining({
114
+ hasProjectId: true,
115
+ hasDataset: true,
116
+ }),
117
+ )
118
+ })
119
+
120
+ it('should log configuration details at debug level', () => {
121
+ createSanityInstance({projectId: 'test-proj', dataset: 'test-ds'})
122
+
123
+ expect(mockHandler.debug).toHaveBeenCalledWith(
124
+ expect.stringContaining('[DEBUG] [sdk]'),
125
+ expect.objectContaining({
126
+ projectId: 'test-proj',
127
+ dataset: 'test-ds',
128
+ }),
129
+ )
130
+ })
131
+
132
+ it('should log instance disposal', () => {
133
+ const instance = createSanityInstance({projectId: 'test-proj'})
134
+ vi.clearAllMocks() // Clear creation logs
135
+
136
+ instance.dispose()
137
+
138
+ expect(mockHandler.info).toHaveBeenCalledWith(
139
+ expect.stringContaining('Instance disposed'),
140
+ expect.anything(),
141
+ )
142
+ })
143
+
144
+ it('should log child instance creation at debug level', () => {
145
+ const parent = createSanityInstance({projectId: 'parent-proj'})
146
+ vi.clearAllMocks() // Clear parent creation logs
147
+
148
+ parent.createChild({dataset: 'child-ds'})
149
+
150
+ expect(mockHandler.debug).toHaveBeenCalledWith(
151
+ expect.stringContaining('Creating child instance'),
152
+ expect.objectContaining({
153
+ overridingDataset: true,
154
+ }),
155
+ )
156
+ })
157
+
158
+ it('should include instance context in logs', () => {
159
+ createSanityInstance({projectId: 'my-project', dataset: 'my-dataset'})
160
+
161
+ // Check that logs include the instance context (project and dataset)
162
+ expect(mockHandler.info).toHaveBeenCalledWith(
163
+ expect.stringMatching(/\[project:my-project\].*\[dataset:my-dataset\]/),
164
+ expect.anything(),
165
+ )
166
+ })
167
+ })
84
168
  })
@@ -2,6 +2,7 @@ import {pick} from 'lodash-es'
2
2
 
3
3
  import {type SanityConfig} from '../config/sanityConfig'
4
4
  import {insecureRandomId} from '../utils/ids'
5
+ import {createLogger, type InstanceContext} from '../utils/logger'
5
6
 
6
7
  /**
7
8
  * Represents a Sanity.io resource instance with its own configuration and lifecycle
@@ -76,15 +77,53 @@ export function createSanityInstance(config: SanityConfig = {}): SanityInstance
76
77
  const disposeListeners = new Map<string, () => void>()
77
78
  const disposed = {current: false}
78
79
 
80
+ // Create instance context for logging
81
+ const instanceContext: InstanceContext = {
82
+ instanceId,
83
+ projectId: config.projectId,
84
+ dataset: config.dataset,
85
+ }
86
+
87
+ // Create logger with instance context
88
+ const logger = createLogger('sdk', {instanceContext})
89
+
90
+ // Log instance creation
91
+ logger.info('Sanity instance created', {
92
+ hasProjectId: !!config.projectId,
93
+ hasDataset: !!config.dataset,
94
+ hasAuth: !!config.auth,
95
+ hasPerspective: !!config.perspective,
96
+ })
97
+
98
+ // Log configuration details at debug level
99
+ logger.debug('Instance configuration', {
100
+ projectId: config.projectId,
101
+ dataset: config.dataset,
102
+ perspective: config.perspective,
103
+ hasStudioConfig: !!config.studio,
104
+ hasStudioTokenSource: !!config.studio?.auth?.token,
105
+ legacyStudioMode: config.studioMode?.enabled,
106
+ hasAuthProviders: !!config.auth?.providers,
107
+ hasAuthToken: !!config.auth?.token,
108
+ })
109
+
79
110
  const instance: SanityInstance = {
80
111
  instanceId,
81
112
  config,
82
113
  isDisposed: () => disposed.current,
83
114
  dispose: () => {
84
- if (disposed.current) return
115
+ if (disposed.current) {
116
+ logger.trace('Dispose called on already disposed instance', {internal: true})
117
+ return
118
+ }
119
+ logger.trace('Disposing instance', {
120
+ internal: true,
121
+ listenerCount: disposeListeners.size,
122
+ })
85
123
  disposed.current = true
86
124
  disposeListeners.forEach((listener) => listener())
87
125
  disposeListeners.clear()
126
+ logger.info('Instance disposed')
88
127
  },
89
128
  onDispose: (cb) => {
90
129
  const listenerId = insecureRandomId()
@@ -94,8 +133,14 @@ export function createSanityInstance(config: SanityConfig = {}): SanityInstance
94
133
  }
95
134
  },
96
135
  getParent: () => undefined,
97
- createChild: (next) =>
98
- Object.assign(
136
+ createChild: (next) => {
137
+ logger.debug('Creating child instance', {
138
+ parentInstanceId: instanceId.slice(0, 8),
139
+ overridingProjectId: !!next.projectId,
140
+ overridingDataset: !!next.dataset,
141
+ overridingAuth: !!next.auth,
142
+ })
143
+ const child = Object.assign(
99
144
  createSanityInstance({
100
145
  ...config,
101
146
  ...next,
@@ -104,7 +149,13 @@ export function createSanityInstance(config: SanityConfig = {}): SanityInstance
104
149
  : config.auth && next.auth && {auth: {...config.auth, ...next.auth}}),
105
150
  }),
106
151
  {getParent: () => instance},
107
- ),
152
+ )
153
+ logger.trace('Child instance created', {
154
+ internal: true,
155
+ childInstanceId: child.instanceId.slice(0, 8),
156
+ })
157
+ return child
158
+ },
108
159
  match: (targetConfig) => {
109
160
  if (
110
161
  Object.entries(pick(targetConfig, 'auth', 'projectId', 'dataset')).every(
@@ -0,0 +1,141 @@
1
+ # Logger Usage Examples
2
+
3
+ This document shows how to use the logging infrastructure in different scenarios.
4
+
5
+ ## For SDK Users
6
+
7
+ ### Basic Setup
8
+
9
+ ```typescript
10
+ import {configureLogging, createSanityInstance} from '@sanity/sdk'
11
+
12
+ // Enable logging in development
13
+ if (process.env.NODE_ENV === 'development') {
14
+ configureLogging({
15
+ level: 'info',
16
+ namespaces: ['auth', 'document', 'query'],
17
+ })
18
+ }
19
+
20
+ const instance = createSanityInstance({
21
+ projectId: 'my-project',
22
+ dataset: 'production',
23
+ })
24
+ ```
25
+
26
+ ### Expected Output
27
+
28
+ ```
29
+ [2024-12-01T22:15:29.000Z] [INFO] [sdk] Logging configured
30
+ level: "info"
31
+ namespaces: ["auth", "document", "query"]
32
+ [2024-12-01T22:15:30.123Z] [INFO] [auth] Checking URL for auth code
33
+ [2024-12-01T22:15:30.456Z] [INFO] [auth] [project:my-project] [dataset:production] User logged in
34
+ userId: "user-123"
35
+ [2024-12-01T22:15:31.789Z] [INFO] [document] [project:my-project] [dataset:production] Document synced
36
+ documentId: "draft.post-1"
37
+ ```
38
+
39
+ ## For SDK Maintainers
40
+
41
+ ### Using Logger in Stores
42
+
43
+ ```typescript
44
+ // In authStore.ts
45
+ import {createLogger, getInstanceContext} from '../utils/logger'
46
+ import {defineStore} from '../store/defineStore'
47
+
48
+ export const authStore = defineStore<AuthStoreState>({
49
+ name: 'Auth',
50
+
51
+ initialize(context) {
52
+ const {instance} = context
53
+
54
+ // Create logger with instance context
55
+ const logger = createLogger('auth', {
56
+ instanceContext: getInstanceContext(instance),
57
+ })
58
+
59
+ // Logs will automatically include [project:x] [dataset:y]
60
+ logger.info('Auth store initialized')
61
+
62
+ // With additional context
63
+ logger.debug('Fetching current user', {
64
+ method: 'cookie',
65
+ projectId: instance.config.projectId,
66
+ })
67
+ },
68
+ })
69
+ ```
70
+
71
+ ### Using Logger in Static Utils
72
+
73
+ ```typescript
74
+ // In utils/ids.ts (no instance context)
75
+ import {createLogger} from './logger'
76
+
77
+ const logger = createLogger('sdk')
78
+
79
+ export function getDraftId(id: string): string {
80
+ logger.trace('Converting to draft ID', {inputId: id})
81
+ return `drafts.${id}`
82
+ }
83
+ ```
84
+
85
+ ### Performance Timing
86
+
87
+ ```typescript
88
+ import {createTimer} from '../utils/logger'
89
+
90
+ async function fetchDocument(id: string) {
91
+ const timer = createTimer('document', 'fetchDocument')
92
+
93
+ try {
94
+ const doc = await client.fetch(query, {id})
95
+ timer.end('Document fetched successfully', {documentId: id})
96
+ return doc
97
+ } catch (error) {
98
+ timer.end('Document fetch failed', {documentId: id, error})
99
+ throw error
100
+ }
101
+ }
102
+ ```
103
+
104
+ ## Multi-Instance Debugging
105
+
106
+ When working with multiple instances, logs automatically show which instance they came from:
107
+
108
+ ```typescript
109
+ const prodInstance = createSanityInstance({
110
+ projectId: 'my-project',
111
+ dataset: 'production',
112
+ })
113
+
114
+ const stagingInstance = createSanityInstance({
115
+ projectId: 'my-project',
116
+ dataset: 'staging',
117
+ })
118
+
119
+ // Logs will clearly show which instance:
120
+ // [INFO] [query] [project:my-project] [dataset:production] Query executed
121
+ // [INFO] [query] [project:my-project] [dataset:staging] Query executed
122
+ ```
123
+
124
+ ## Environment-Based Configuration
125
+
126
+ ```typescript
127
+ // Configure based on environment
128
+ const logLevel = process.env.SANITY_LOG_LEVEL || 'warn'
129
+ const logNamespaces = process.env.SANITY_LOG_NAMESPACES?.split(',') || []
130
+
131
+ configureLogging({
132
+ level: logLevel as LogLevel,
133
+ namespaces: logNamespaces,
134
+ })
135
+ ```
136
+
137
+ Then run your app with:
138
+
139
+ ```bash
140
+ SANITY_LOG_LEVEL=debug SANITY_LOG_NAMESPACES=auth,document npm start
141
+ ```