@sanity/sdk 2.18.0 → 3.0.0-rc.1

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 (53) hide show
  1. package/dist/_chunks-dts/createGroqSearchFilter.d.ts +233 -12
  2. package/dist/_chunks-dts/createGroqSearchFilter.d.ts.map +1 -1
  3. package/dist/_chunks-es/_internal.js +1 -0
  4. package/dist/_chunks-es/_internal.js.map +1 -1
  5. package/dist/_chunks-es/createGroqSearchFilter.js +284 -6
  6. package/dist/_chunks-es/createGroqSearchFilter.js.map +1 -1
  7. package/dist/_chunks-es/telemetryManager.js +2 -2
  8. package/dist/_exports/_internal.d.ts +2 -2
  9. package/dist/_exports/_internal.d.ts.map +1 -1
  10. package/dist/_exports/_internal.js +3 -1
  11. package/dist/index.d.ts +599 -185
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +492 -337
  14. package/dist/index.js.map +1 -1
  15. package/package.json +7 -7
  16. package/src/_exports/_internal.ts +11 -0
  17. package/src/_exports/index.ts +57 -12
  18. package/src/access/checkPermissions.test.ts +55 -0
  19. package/src/access/checkPermissions.ts +54 -0
  20. package/src/applications/applications.test-d.ts +83 -0
  21. package/src/applications/applications.test.ts +130 -0
  22. package/src/applications/applications.ts +364 -0
  23. package/src/applications/userApplications.test.ts +126 -0
  24. package/src/applications/userApplications.ts +261 -0
  25. package/src/auth/getOrganizationVerificationState.test.ts +7 -7
  26. package/src/auth/getOrganizationVerificationState.ts +4 -4
  27. package/src/document/documentStore.concurrency.test.ts +313 -22
  28. package/src/installations/installations.test-d.ts +68 -0
  29. package/src/installations/installations.test.ts +62 -0
  30. package/src/installations/installations.ts +202 -0
  31. package/src/organization/organization.test-d.ts +18 -84
  32. package/src/organization/organization.test.ts +11 -11
  33. package/src/organization/organization.ts +26 -54
  34. package/src/organizations/organizations.test-d.ts +11 -32
  35. package/src/organizations/organizations.test.ts +8 -8
  36. package/src/organizations/organizations.ts +29 -54
  37. package/src/project/project.test-d.ts +19 -75
  38. package/src/project/project.test.ts +57 -10
  39. package/src/project/project.ts +33 -52
  40. package/src/projects/projects.test-d.ts +15 -21
  41. package/src/projects/projects.test.ts +8 -8
  42. package/src/projects/projects.ts +25 -49
  43. package/src/store/fetcherStore.test-d.ts +121 -0
  44. package/src/store/fetcherStore.test.ts +299 -0
  45. package/src/store/fetcherStore.ts +801 -0
  46. package/src/utils/buildQuery.test.ts +14 -0
  47. package/src/utils/buildQuery.ts +17 -0
  48. package/src/utils/includeShape.ts +36 -0
  49. package/src/utils/setCleanupTimeout.ts +21 -8
  50. package/dist/_chunks-es/version.js +0 -7
  51. package/dist/_chunks-es/version.js.map +0 -1
  52. package/src/utils/defineIntent.test.ts +0 -477
  53. package/src/utils/defineIntent.ts +0 -244
@@ -0,0 +1,14 @@
1
+ import {describe, expect, it} from 'vitest'
2
+
3
+ import {buildQuery} from './buildQuery'
4
+
5
+ describe('buildQuery', () => {
6
+ it('stringifies scalars, drops undefined, and passes string arrays through', () => {
7
+ expect(buildQuery({a: true, b: 0, c: 'x', d: undefined, e: ['p', 'q']})).toEqual({
8
+ a: 'true',
9
+ b: '0',
10
+ c: 'x',
11
+ e: ['p', 'q'],
12
+ })
13
+ })
14
+ })
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Serialises store options into a `@sanity/client` request query object.
3
+ * Scalars are stringified and `undefined` values dropped; string arrays pass
4
+ * through untouched so the client serialises them as repeated keys
5
+ * (`permissions=a&permissions=b`).
6
+ *
7
+ * @internal
8
+ */
9
+ export function buildQuery(
10
+ params: Record<string, string | number | boolean | string[] | undefined>,
11
+ ): Record<string, string | string[]> {
12
+ return Object.fromEntries(
13
+ Object.entries(params)
14
+ .filter(([, value]) => value !== undefined)
15
+ .map(([key, value]) => [key, Array.isArray(value) ? value : String(value)]),
16
+ )
17
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * A response field that the API embeds only when its `include` token was
3
+ * requested. Given the full set of tokens `All`, the token `Key` this field
4
+ * maps to, and the tokens actually `Requested`, it resolves to:
5
+ *
6
+ * - `unknown` (field absent) when nothing — or nothing matching `Key` — was requested
7
+ * - `Field` (present, as declared) when a literal request list contains `Key`
8
+ * - `Partial<Field>` (optional) when `Requested` is the wide `All` union, i.e.
9
+ * an `include` array whose members aren't known at the type level
10
+ *
11
+ * Absent arms return `unknown` so they vanish under intersection. Mirrors the
12
+ * `boolean extends Flag` trick the organization/project types use, but for an
13
+ * include-token union rather than a boolean flag.
14
+ *
15
+ * @internal
16
+ */
17
+ export type Included<All extends string, Key extends All, Requested extends All, Field> = [
18
+ All,
19
+ ] extends [Requested]
20
+ ? Partial<Field>
21
+ : [Key] extends [Requested]
22
+ ? Field
23
+ : unknown
24
+
25
+ /**
26
+ * Whether the requested include tokens intersect a given set — used to decide
27
+ * whether a parent field (e.g. an application's `activeDeployment`) is present,
28
+ * when requesting any of several tokens forces it in.
29
+ *
30
+ * @internal
31
+ */
32
+ export type IncludesAny<Requested extends string, Set extends string> = [Requested & Set] extends [
33
+ never,
34
+ ]
35
+ ? false
36
+ : true
@@ -1,3 +1,13 @@
1
+ // Calls `.unref()` on a timer when running in Node.js. In Node.js, timers are
2
+ // objects with an `unref()` method; in browsers they are numbers. Narrow at
3
+ // runtime so this works in both environments without type assertions.
4
+ function unref(timer: ReturnType<typeof setTimeout> | ReturnType<typeof setInterval>): void {
5
+ const t: unknown = timer
6
+ if (typeof t === 'object' && t !== null && 'unref' in t && typeof t.unref === 'function') {
7
+ t.unref()
8
+ }
9
+ }
10
+
1
11
  /**
2
12
  * Like `setTimeout`, but calls `.unref()` on the timer when running in Node.js.
3
13
  *
@@ -11,14 +21,17 @@
11
21
  */
12
22
  export function setCleanupTimeout(fn: () => void, delay: number): ReturnType<typeof setTimeout> {
13
23
  const timer = setTimeout(fn, delay)
24
+ unref(timer)
25
+ return timer
26
+ }
14
27
 
15
- // In Node.js, setTimeout returns an object with an `unref()` method.
16
- // In browsers, it returns a number. We assign to `unknown` and narrow
17
- // at runtime so this works in both environments without type assertions.
18
- const t: unknown = timer
19
- if (typeof t === 'object' && t !== null && 'unref' in t && typeof t.unref === 'function') {
20
- t.unref()
21
- }
22
-
28
+ /**
29
+ * Like `setInterval`, but calls `.unref()` on the timer when running in Node.js
30
+ * so a recurring poll never keeps the process alive. See {@link setCleanupTimeout}
31
+ * for why cleanup and background timers must not block process exit.
32
+ */
33
+ export function setCleanupInterval(fn: () => void, delay: number): ReturnType<typeof setInterval> {
34
+ const timer = setInterval(fn, delay)
35
+ unref(timer)
23
36
  return timer
24
37
  }
@@ -1,7 +0,0 @@
1
- import { getEnv } from "./createGroqSearchFilter.js";
2
- var version = "2.18.0";
3
- const CORE_SDK_VERSION = getEnv("PKG_VERSION") || `${version}-development`;
4
- export {
5
- CORE_SDK_VERSION
6
- };
7
- //# sourceMappingURL=version.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"version.js","sources":["../../src/version.ts"],"sourcesContent":["import {version} from '../package.json'\nimport {getEnv} from './utils/getEnv'\n\n/**\n * This version is provided by pkg-utils at build time\n * @internal\n */\nexport const CORE_SDK_VERSION = getEnv('PKG_VERSION') || `${version}-development`\n"],"names":[],"mappings":";;AAOO,MAAM,mBAAmB,OAAO,aAAa,KAAK,GAAG,OAAO;"}
@@ -1,477 +0,0 @@
1
- import {describe, expect, test} from 'vitest'
2
-
3
- import {defineIntent, type Intent, type IntentFilter} from './defineIntent'
4
-
5
- describe('defineIntent', () => {
6
- test('should return a valid intent object when all required fields are provided', () => {
7
- const intent: Intent = {
8
- id: 'viewHat',
9
- action: 'view',
10
- title: 'View a hat',
11
- description: 'This lets you view a hat',
12
- filters: [
13
- {
14
- projectId: 'some-project',
15
- dataset: 'a-dataset',
16
- types: ['hat'],
17
- },
18
- ],
19
- }
20
-
21
- const result = defineIntent(intent)
22
-
23
- expect(result).toEqual(intent)
24
- expect(result).toBe(intent)
25
- })
26
-
27
- test('should throw error when filters array is empty', () => {
28
- const intent: Intent = {
29
- id: 'globalIntent',
30
- action: 'create',
31
- title: 'Global Intent',
32
- description: 'An intent with no filters',
33
- filters: [],
34
- }
35
-
36
- expect(() => defineIntent(intent)).toThrow(
37
- "Intent must have at least one filter. If you want to match everything, use {types: ['*']}",
38
- )
39
- })
40
-
41
- test('should work with wildcard filter to match everything', () => {
42
- const intent: Intent = {
43
- id: 'globalIntent',
44
- action: 'create',
45
- title: 'Global Intent',
46
- description: 'An intent that matches everything',
47
- filters: [
48
- {
49
- types: ['*'],
50
- },
51
- ],
52
- }
53
-
54
- const result = defineIntent(intent)
55
-
56
- expect(result).toEqual(intent)
57
- expect(result.filters[0].types).toEqual(['*'])
58
- })
59
-
60
- test('should work with partial filters', () => {
61
- const intent: Intent = {
62
- id: 'partialFilter',
63
- action: 'edit',
64
- title: 'Partial Filter Intent',
65
- description: 'An intent with partial filter criteria',
66
- filters: [
67
- {
68
- projectId: 'some-project',
69
- types: ['*'], // Add required types
70
- },
71
- {
72
- types: ['document'],
73
- // No projectId or dataset specified
74
- },
75
- ],
76
- }
77
-
78
- const result = defineIntent(intent)
79
-
80
- expect(result).toEqual(intent)
81
- })
82
-
83
- test('should throw error when id is missing', () => {
84
- const intent = {
85
- action: 'view',
86
- title: 'Test Intent',
87
- description: 'Test description',
88
- filters: [],
89
- } as unknown as Intent
90
-
91
- expect(() => defineIntent(intent)).toThrow('Intent must have an id')
92
- })
93
-
94
- test('should throw error when id is empty string', () => {
95
- const intent: Intent = {
96
- id: '',
97
- action: 'view',
98
- title: 'Test Intent',
99
- description: 'Test description',
100
- filters: [],
101
- }
102
-
103
- expect(() => defineIntent(intent)).toThrow('Intent must have an id')
104
- })
105
-
106
- test('should throw error when action is missing', () => {
107
- const intent = {
108
- id: 'test',
109
- title: 'Test Intent',
110
- description: 'Test description',
111
- filters: [],
112
- } as unknown as Intent
113
-
114
- expect(() => defineIntent(intent)).toThrow('Intent must have an action')
115
- })
116
-
117
- test('should throw error when action is empty string', () => {
118
- const intent = {
119
- id: 'test',
120
- action: '',
121
- title: 'Test Intent',
122
- description: 'Test description',
123
- filters: [],
124
- } as unknown as Intent
125
-
126
- expect(() => defineIntent(intent)).toThrow('Intent must have an action')
127
- })
128
-
129
- test('should throw error when title is missing', () => {
130
- const intent = {
131
- id: 'test',
132
- action: 'view',
133
- description: 'Test description',
134
- filters: [],
135
- } as unknown as Intent
136
-
137
- expect(() => defineIntent(intent)).toThrow('Intent must have a title')
138
- })
139
-
140
- test('should throw error when title is empty string', () => {
141
- const intent: Intent = {
142
- id: 'test',
143
- action: 'view',
144
- title: '',
145
- description: 'Test description',
146
- filters: [],
147
- }
148
-
149
- expect(() => defineIntent(intent)).toThrow('Intent must have a title')
150
- })
151
-
152
- test('should throw error when filters are missing', () => {
153
- const intent = {
154
- id: 'test',
155
- action: 'view',
156
- title: 'Test Intent',
157
- description: 'Test description',
158
- } as unknown as Intent
159
-
160
- expect(() => defineIntent(intent)).toThrow('Intent must have a filters array')
161
- })
162
-
163
- test('should throw error when filters are not an array', () => {
164
- const intent = {
165
- id: 'test',
166
- action: 'view',
167
- title: 'Test Intent',
168
- description: 'Test description',
169
- filters: 'not an array',
170
- } as unknown as Intent
171
-
172
- expect(() => defineIntent(intent)).toThrow('Intent must have a filters array')
173
- })
174
-
175
- test('should work with complex filter combinations', () => {
176
- const intent: Intent = {
177
- id: 'complexIntent',
178
- action: 'edit',
179
- title: 'Complex Intent',
180
- description: 'An intent with multiple complex filters',
181
- filters: [
182
- {
183
- projectId: 'project-1',
184
- dataset: 'production',
185
- types: ['article', 'blogPost'],
186
- },
187
- {
188
- projectId: 'project-2',
189
- dataset: 'staging',
190
- types: ['product'],
191
- },
192
- {
193
- // Filter with only types
194
- types: ['global-document'],
195
- },
196
- ],
197
- }
198
-
199
- const result = defineIntent(intent)
200
-
201
- expect(result).toEqual(intent)
202
- expect(result.filters).toHaveLength(3)
203
- expect(result.filters[0].types).toEqual(['article', 'blogPost'])
204
- expect(result.filters[2].projectId).toBeUndefined()
205
- expect(result.filters[2].dataset).toBeUndefined()
206
- })
207
-
208
- // Filter validation tests
209
- test('should throw error for empty filter object', () => {
210
- const intent = {
211
- id: 'test',
212
- action: 'view',
213
- title: 'Test Intent',
214
- description: 'Test description',
215
- filters: [{}],
216
- } as unknown as Intent
217
-
218
- expect(() => defineIntent(intent)).toThrow(
219
- "Filter at index 0 must have a types property. Use ['*'] to match all document types.",
220
- )
221
- })
222
-
223
- test('should throw error for non-object filter', () => {
224
- const intent = {
225
- id: 'test',
226
- action: 'view',
227
- title: 'Test Intent',
228
- description: 'Test description',
229
- filters: ['not an object'],
230
- } as unknown as Intent
231
-
232
- expect(() => defineIntent(intent)).toThrow('Filter at index 0 must be an object')
233
- })
234
-
235
- test('should throw error for non-string projectId', () => {
236
- const intent = {
237
- id: 'test',
238
- action: 'view',
239
- title: 'Test Intent',
240
- description: 'Test description',
241
- filters: [{projectId: 123, types: ['*']}],
242
- } as unknown as Intent
243
-
244
- expect(() => defineIntent(intent)).toThrow('Filter at index 0: projectId must be a string')
245
- })
246
-
247
- test('should throw error for empty projectId', () => {
248
- const intent: Intent = {
249
- id: 'test',
250
- action: 'view',
251
- title: 'Test Intent',
252
- description: 'Test description',
253
- filters: [{projectId: '', types: ['*']}],
254
- }
255
-
256
- expect(() => defineIntent(intent)).toThrow('Filter at index 0: projectId cannot be empty')
257
- })
258
-
259
- test('should throw error for whitespace-only projectId', () => {
260
- const intent: Intent = {
261
- id: 'test',
262
- action: 'view',
263
- title: 'Test Intent',
264
- description: 'Test description',
265
- filters: [{projectId: ' ', types: ['*']}],
266
- }
267
-
268
- expect(() => defineIntent(intent)).toThrow('Filter at index 0: projectId cannot be empty')
269
- })
270
-
271
- test('should throw error for non-string dataset', () => {
272
- const intent = {
273
- id: 'test',
274
- action: 'view',
275
- title: 'Test Intent',
276
- description: 'Test description',
277
- filters: [{projectId: 'test', dataset: 123, types: ['*']}],
278
- } as unknown as Intent
279
-
280
- expect(() => defineIntent(intent)).toThrow('Filter at index 0: dataset must be a string')
281
- })
282
-
283
- test('should throw error for empty dataset', () => {
284
- const intent: Intent = {
285
- id: 'test',
286
- action: 'view',
287
- title: 'Test Intent',
288
- description: 'Test description',
289
- filters: [{dataset: '', types: ['*']}],
290
- }
291
-
292
- expect(() => defineIntent(intent)).toThrow('Filter at index 0: dataset cannot be empty')
293
- })
294
-
295
- test('should throw error when dataset is specified without projectId', () => {
296
- const intent: Intent = {
297
- id: 'test',
298
- action: 'view',
299
- title: 'Test Intent',
300
- description: 'Test description',
301
- filters: [{dataset: 'production', types: ['*']}],
302
- }
303
-
304
- expect(() => defineIntent(intent)).toThrow(
305
- 'Filter at index 0: dataset cannot be specified without projectId',
306
- )
307
- })
308
-
309
- test('should throw error when dataset is specified with empty projectId', () => {
310
- const intent: Intent = {
311
- id: 'test',
312
- action: 'view',
313
- title: 'Test Intent',
314
- description: 'Test description',
315
- filters: [{projectId: '', dataset: 'production', types: ['*']}],
316
- }
317
-
318
- expect(() => defineIntent(intent)).toThrow('Filter at index 0: projectId cannot be empty')
319
- })
320
-
321
- test('should work when dataset is specified with valid projectId', () => {
322
- const intent: Intent = {
323
- id: 'test',
324
- action: 'view',
325
- title: 'Test Intent',
326
- description: 'Test description',
327
- filters: [{projectId: 'my-project', dataset: 'production', types: ['*']}],
328
- }
329
-
330
- const result = defineIntent(intent)
331
- expect(result).toEqual(intent)
332
- })
333
-
334
- test('should throw error for non-array types', () => {
335
- const intent = {
336
- id: 'test',
337
- action: 'view',
338
- title: 'Test Intent',
339
- description: 'Test description',
340
- filters: [{types: 'not-an-array'}],
341
- } as unknown as Intent
342
-
343
- expect(() => defineIntent(intent)).toThrow('Filter at index 0: types must be an array')
344
- })
345
-
346
- test('should throw error for empty types array', () => {
347
- const intent: Intent = {
348
- id: 'test',
349
- action: 'view',
350
- title: 'Test Intent',
351
- description: 'Test description',
352
- filters: [{types: []}],
353
- }
354
-
355
- expect(() => defineIntent(intent)).toThrow('Filter at index 0: types array cannot be empty')
356
- })
357
-
358
- test('should throw error for non-string type in types array', () => {
359
- const intent = {
360
- id: 'test',
361
- action: 'view',
362
- title: 'Test Intent',
363
- description: 'Test description',
364
- filters: [{types: ['valid', 123, 'also-valid']}],
365
- } as unknown as Intent
366
-
367
- expect(() => defineIntent(intent)).toThrow('Filter at index 0: types[1] must be a string')
368
- })
369
-
370
- test('should throw error for empty string in types array', () => {
371
- const intent: Intent = {
372
- id: 'test',
373
- action: 'view',
374
- title: 'Test Intent',
375
- description: 'Test description',
376
- filters: [{types: ['valid', '', 'also-valid']}],
377
- }
378
-
379
- expect(() => defineIntent(intent)).toThrow('Filter at index 0: types[1] cannot be empty')
380
- })
381
-
382
- test('should throw error for whitespace-only string in types array', () => {
383
- const intent: Intent = {
384
- id: 'test',
385
- action: 'view',
386
- title: 'Test Intent',
387
- description: 'Test description',
388
- filters: [{types: ['valid', ' ', 'also-valid']}],
389
- }
390
-
391
- expect(() => defineIntent(intent)).toThrow('Filter at index 0: types[1] cannot be empty')
392
- })
393
-
394
- test('should throw error when wildcard is mixed with other types', () => {
395
- const intent: Intent = {
396
- id: 'test',
397
- action: 'view',
398
- title: 'Test Intent',
399
- description: 'Test description',
400
- filters: [{types: ['*', 'document']}],
401
- }
402
-
403
- expect(() => defineIntent(intent)).toThrow(
404
- "Filter at index 0: when using wildcard '*', it must be the only type in the array",
405
- )
406
- })
407
-
408
- test('should throw error when wildcard appears with other types in different order', () => {
409
- const intent: Intent = {
410
- id: 'test',
411
- action: 'view',
412
- title: 'Test Intent',
413
- description: 'Test description',
414
- filters: [{types: ['document', 'article', '*']}],
415
- }
416
-
417
- expect(() => defineIntent(intent)).toThrow(
418
- "Filter at index 0: when using wildcard '*', it must be the only type in the array",
419
- )
420
- })
421
-
422
- test('should work with valid individual filter properties', () => {
423
- const intent: Intent = {
424
- id: 'test',
425
- action: 'view',
426
- title: 'Test Intent',
427
- description: 'Test description',
428
- filters: [
429
- {projectId: 'my-project', types: ['*']},
430
- {projectId: 'my-project', dataset: 'production', types: ['*']},
431
- {types: ['document']},
432
- {types: ['*']}, // Valid wildcard usage
433
- ],
434
- }
435
-
436
- const result = defineIntent(intent)
437
- expect(result).toEqual(intent)
438
- })
439
-
440
- test('should provide correct filter index in error messages for multiple filters', () => {
441
- const intent: Intent = {
442
- id: 'test',
443
- action: 'view',
444
- title: 'Test Intent',
445
- description: 'Test description',
446
- filters: [
447
- {projectId: 'valid-project', types: ['*']},
448
- {projectId: '', types: ['*']},
449
- ],
450
- }
451
-
452
- expect(() => defineIntent(intent)).toThrow('Filter at index 1: projectId cannot be empty')
453
- })
454
- })
455
-
456
- describe('IntentFilter interface', () => {
457
- test('should require types property', () => {
458
- // This is more of a TypeScript compile-time test
459
- // but we can verify the structure is as expected
460
- const filter1: IntentFilter = {types: ['*']} // types is now required
461
- const filter2: IntentFilter = {projectId: 'test', types: ['*']} // types is now required
462
- const filter3: IntentFilter = {dataset: 'test', types: ['*']} // types is now required
463
- const filter4: IntentFilter = {types: ['test']}
464
- const filter5: IntentFilter = {
465
- projectId: 'test',
466
- dataset: 'test',
467
- types: ['test'],
468
- }
469
-
470
- // These should all be valid filter objects
471
- expect(filter1).toBeDefined()
472
- expect(filter2).toBeDefined()
473
- expect(filter3).toBeDefined()
474
- expect(filter4).toBeDefined()
475
- expect(filter5).toBeDefined()
476
- })
477
- })