effect 3.18.4 → 3.19.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.
- package/HashRing/package.json +6 -0
- package/dist/cjs/Array.js.map +1 -1
- package/dist/cjs/Effect.js.map +1 -1
- package/dist/cjs/Graph.js +290 -177
- package/dist/cjs/Graph.js.map +1 -1
- package/dist/cjs/HashRing.js +257 -0
- package/dist/cjs/HashRing.js.map +1 -0
- package/dist/cjs/JSONSchema.js +39 -8
- package/dist/cjs/JSONSchema.js.map +1 -1
- package/dist/cjs/TestClock.js +8 -8
- package/dist/cjs/TestClock.js.map +1 -1
- package/dist/cjs/index.js +4 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/internal/version.js +1 -1
- package/dist/dts/Array.d.ts +3 -3
- package/dist/dts/Array.d.ts.map +1 -1
- package/dist/dts/Effect.d.ts +6 -1
- package/dist/dts/Effect.d.ts.map +1 -1
- package/dist/dts/Graph.d.ts +147 -49
- package/dist/dts/Graph.d.ts.map +1 -1
- package/dist/dts/HashRing.d.ts +158 -0
- package/dist/dts/HashRing.d.ts.map +1 -0
- package/dist/dts/JSONSchema.d.ts +3 -2
- package/dist/dts/JSONSchema.d.ts.map +1 -1
- package/dist/dts/Types.d.ts +1 -1
- package/dist/dts/Types.d.ts.map +1 -1
- package/dist/dts/index.d.ts +5 -0
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/esm/Array.js.map +1 -1
- package/dist/esm/Effect.js.map +1 -1
- package/dist/esm/Graph.js +286 -175
- package/dist/esm/Graph.js.map +1 -1
- package/dist/esm/HashRing.js +245 -0
- package/dist/esm/HashRing.js.map +1 -0
- package/dist/esm/JSONSchema.js +35 -6
- package/dist/esm/JSONSchema.js.map +1 -1
- package/dist/esm/TestClock.js +8 -8
- package/dist/esm/TestClock.js.map +1 -1
- package/dist/esm/index.js +5 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/internal/version.js +1 -1
- package/package.json +9 -1
- package/src/Array.ts +4 -4
- package/src/Effect.ts +6 -1
- package/src/Graph.ts +415 -218
- package/src/HashRing.ts +387 -0
- package/src/JSONSchema.ts +39 -9
- package/src/TestClock.ts +9 -9
- package/src/Types.ts +3 -1
- package/src/index.ts +6 -0
- package/src/internal/version.ts +1 -1
package/src/HashRing.ts
ADDED
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since 3.19.0
|
|
3
|
+
* @experimental
|
|
4
|
+
*/
|
|
5
|
+
import { dual } from "./Function.js"
|
|
6
|
+
import * as Hash from "./Hash.js"
|
|
7
|
+
import * as Inspectable from "./Inspectable.js"
|
|
8
|
+
import * as Iterable from "./Iterable.js"
|
|
9
|
+
import { type Pipeable, pipeArguments } from "./Pipeable.js"
|
|
10
|
+
import { hasProperty } from "./Predicate.js"
|
|
11
|
+
import * as PrimaryKey from "./PrimaryKey.js"
|
|
12
|
+
|
|
13
|
+
const TypeId = "~effect/cluster/HashRing" as const
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @since 3.19.0
|
|
17
|
+
* @category Models
|
|
18
|
+
* @experimental
|
|
19
|
+
*/
|
|
20
|
+
export interface HashRing<A extends PrimaryKey.PrimaryKey> extends Pipeable, Iterable<A> {
|
|
21
|
+
readonly [TypeId]: typeof TypeId
|
|
22
|
+
readonly baseWeight: number
|
|
23
|
+
totalWeightCache: number
|
|
24
|
+
readonly nodes: Map<string, [node: A, weight: number]>
|
|
25
|
+
ring: Array<[hash: number, node: string]>
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @since 3.19.0
|
|
30
|
+
* @category Guards
|
|
31
|
+
* @experimental
|
|
32
|
+
*/
|
|
33
|
+
export const isHashRing = (u: unknown): u is HashRing<any> => hasProperty(u, TypeId)
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @since 3.19.0
|
|
37
|
+
* @category Constructors
|
|
38
|
+
* @experimental
|
|
39
|
+
*/
|
|
40
|
+
export const make = <A extends PrimaryKey.PrimaryKey>(options?: {
|
|
41
|
+
readonly baseWeight?: number | undefined
|
|
42
|
+
}): HashRing<A> => {
|
|
43
|
+
const self = Object.create(Proto)
|
|
44
|
+
self.baseWeight = Math.max(options?.baseWeight ?? 128, 1)
|
|
45
|
+
self.totalWeightCache = 0
|
|
46
|
+
self.nodes = new Map()
|
|
47
|
+
self.ring = []
|
|
48
|
+
return self
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const Proto = {
|
|
52
|
+
[TypeId]: TypeId,
|
|
53
|
+
[Symbol.iterator]<A extends PrimaryKey.PrimaryKey>(this: HashRing<A>): Iterator<A> {
|
|
54
|
+
return Iterable.map(this.nodes.values(), ([n]) => n)[Symbol.iterator]()
|
|
55
|
+
},
|
|
56
|
+
pipe() {
|
|
57
|
+
return pipeArguments(this, arguments)
|
|
58
|
+
},
|
|
59
|
+
...Inspectable.BaseProto,
|
|
60
|
+
toJSON(this: HashRing<any>) {
|
|
61
|
+
return {
|
|
62
|
+
_id: "HashRing",
|
|
63
|
+
baseWeight: this.baseWeight,
|
|
64
|
+
nodes: this.ring.map(([, n]) => this.nodes.get(n)![0])
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Add new nodes to the ring. If a node already exists in the ring, it
|
|
71
|
+
* will be updated. For example, you can use this to update the node's weight.
|
|
72
|
+
*
|
|
73
|
+
* @since 3.19.0
|
|
74
|
+
* @category Combinators
|
|
75
|
+
* @experimental
|
|
76
|
+
*/
|
|
77
|
+
export const addMany: {
|
|
78
|
+
/**
|
|
79
|
+
* Add new nodes to the ring. If a node already exists in the ring, it
|
|
80
|
+
* will be updated. For example, you can use this to update the node's weight.
|
|
81
|
+
*
|
|
82
|
+
* @since 3.19.0
|
|
83
|
+
* @category Combinators
|
|
84
|
+
* @experimental
|
|
85
|
+
*/
|
|
86
|
+
<A extends PrimaryKey.PrimaryKey>(
|
|
87
|
+
nodes: Iterable<A>,
|
|
88
|
+
options?: {
|
|
89
|
+
readonly weight?: number | undefined
|
|
90
|
+
}
|
|
91
|
+
): (self: HashRing<A>) => HashRing<A>
|
|
92
|
+
/**
|
|
93
|
+
* Add new nodes to the ring. If a node already exists in the ring, it
|
|
94
|
+
* will be updated. For example, you can use this to update the node's weight.
|
|
95
|
+
*
|
|
96
|
+
* @since 3.19.0
|
|
97
|
+
* @category Combinators
|
|
98
|
+
* @experimental
|
|
99
|
+
*/
|
|
100
|
+
<A extends PrimaryKey.PrimaryKey>(
|
|
101
|
+
self: HashRing<A>,
|
|
102
|
+
nodes: Iterable<A>,
|
|
103
|
+
options?: {
|
|
104
|
+
readonly weight?: number | undefined
|
|
105
|
+
}
|
|
106
|
+
): HashRing<A>
|
|
107
|
+
} = dual(
|
|
108
|
+
(args) => isHashRing(args[0]),
|
|
109
|
+
<A extends PrimaryKey.PrimaryKey>(self: HashRing<A>, nodes: Iterable<A>, options?: {
|
|
110
|
+
readonly weight?: number | undefined
|
|
111
|
+
}): HashRing<A> => {
|
|
112
|
+
const weight = Math.max(options?.weight ?? 1, 0.1)
|
|
113
|
+
const keys: Array<string> = []
|
|
114
|
+
let toRemove: Set<string> | undefined
|
|
115
|
+
for (const node of nodes) {
|
|
116
|
+
const key = PrimaryKey.value(node)
|
|
117
|
+
const entry = self.nodes.get(key)
|
|
118
|
+
if (entry) {
|
|
119
|
+
if (entry[1] === weight) continue
|
|
120
|
+
toRemove ??= new Set()
|
|
121
|
+
toRemove.add(key)
|
|
122
|
+
self.totalWeightCache -= entry[1]
|
|
123
|
+
self.totalWeightCache += weight
|
|
124
|
+
entry[1] = weight
|
|
125
|
+
} else {
|
|
126
|
+
self.nodes.set(key, [node, weight])
|
|
127
|
+
self.totalWeightCache += weight
|
|
128
|
+
}
|
|
129
|
+
keys.push(key)
|
|
130
|
+
}
|
|
131
|
+
if (toRemove) {
|
|
132
|
+
self.ring = self.ring.filter(([, n]) => !toRemove.has(n))
|
|
133
|
+
}
|
|
134
|
+
addNodesToRing(self, keys, Math.round(weight * self.baseWeight))
|
|
135
|
+
return self
|
|
136
|
+
}
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
function addNodesToRing<A extends PrimaryKey.PrimaryKey>(self: HashRing<A>, keys: Array<string>, weight: number) {
|
|
140
|
+
for (let i = weight; i > 0; i--) {
|
|
141
|
+
for (let j = 0; j < keys.length; j++) {
|
|
142
|
+
const key = keys[j]
|
|
143
|
+
self.ring.push([
|
|
144
|
+
Hash.string(`${key}:${i}`),
|
|
145
|
+
key
|
|
146
|
+
])
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
self.ring.sort((a, b) => a[0] - b[0])
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Add a new node to the ring. If the node already exists in the ring, it
|
|
154
|
+
* will be updated. For example, you can use this to update the node's weight.
|
|
155
|
+
*
|
|
156
|
+
* @since 3.19.0
|
|
157
|
+
* @category Combinators
|
|
158
|
+
* @experimental
|
|
159
|
+
*/
|
|
160
|
+
export const add: {
|
|
161
|
+
/**
|
|
162
|
+
* Add a new node to the ring. If the node already exists in the ring, it
|
|
163
|
+
* will be updated. For example, you can use this to update the node's weight.
|
|
164
|
+
*
|
|
165
|
+
* @since 3.19.0
|
|
166
|
+
* @category Combinators
|
|
167
|
+
* @experimental
|
|
168
|
+
*/
|
|
169
|
+
<A extends PrimaryKey.PrimaryKey>(
|
|
170
|
+
node: A,
|
|
171
|
+
options?: {
|
|
172
|
+
readonly weight?: number | undefined
|
|
173
|
+
}
|
|
174
|
+
): (self: HashRing<A>) => HashRing<A>
|
|
175
|
+
/**
|
|
176
|
+
* Add a new node to the ring. If the node already exists in the ring, it
|
|
177
|
+
* will be updated. For example, you can use this to update the node's weight.
|
|
178
|
+
*
|
|
179
|
+
* @since 3.19.0
|
|
180
|
+
* @category Combinators
|
|
181
|
+
* @experimental
|
|
182
|
+
*/
|
|
183
|
+
<A extends PrimaryKey.PrimaryKey>(
|
|
184
|
+
self: HashRing<A>,
|
|
185
|
+
node: A,
|
|
186
|
+
options?: {
|
|
187
|
+
readonly weight?: number | undefined
|
|
188
|
+
}
|
|
189
|
+
): HashRing<A>
|
|
190
|
+
} = dual((args) => isHashRing(args[0]), <A extends PrimaryKey.PrimaryKey>(self: HashRing<A>, node: A, options?: {
|
|
191
|
+
readonly weight?: number | undefined
|
|
192
|
+
}): HashRing<A> => addMany(self, [node], options))
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Removes the node from the ring. No-op's if the node does not exist.
|
|
196
|
+
*
|
|
197
|
+
* @since 3.19.0
|
|
198
|
+
* @category Combinators
|
|
199
|
+
* @experimental
|
|
200
|
+
*/
|
|
201
|
+
export const remove: {
|
|
202
|
+
/**
|
|
203
|
+
* Removes the node from the ring. No-op's if the node does not exist.
|
|
204
|
+
*
|
|
205
|
+
* @since 3.19.0
|
|
206
|
+
* @category Combinators
|
|
207
|
+
* @experimental
|
|
208
|
+
*/
|
|
209
|
+
<A extends PrimaryKey.PrimaryKey>(node: A): (self: HashRing<A>) => HashRing<A>
|
|
210
|
+
/**
|
|
211
|
+
* Removes the node from the ring. No-op's if the node does not exist.
|
|
212
|
+
*
|
|
213
|
+
* @since 3.19.0
|
|
214
|
+
* @category Combinators
|
|
215
|
+
* @experimental
|
|
216
|
+
*/
|
|
217
|
+
<A extends PrimaryKey.PrimaryKey>(self: HashRing<A>, node: A): HashRing<A>
|
|
218
|
+
} = dual(2, <A extends PrimaryKey.PrimaryKey>(self: HashRing<A>, node: A): HashRing<A> => {
|
|
219
|
+
const key = PrimaryKey.value(node)
|
|
220
|
+
const entry = self.nodes.get(key)
|
|
221
|
+
if (entry) {
|
|
222
|
+
self.nodes.delete(key)
|
|
223
|
+
self.ring = self.ring.filter(([, n]) => n !== key)
|
|
224
|
+
self.totalWeightCache -= entry[1]
|
|
225
|
+
}
|
|
226
|
+
return self
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* @since 3.19.0
|
|
231
|
+
* @category Combinators
|
|
232
|
+
* @experimental
|
|
233
|
+
*/
|
|
234
|
+
export const has: {
|
|
235
|
+
/**
|
|
236
|
+
* @since 3.19.0
|
|
237
|
+
* @category Combinators
|
|
238
|
+
* @experimental
|
|
239
|
+
*/
|
|
240
|
+
<A extends PrimaryKey.PrimaryKey>(node: A): (self: HashRing<A>) => boolean
|
|
241
|
+
/**
|
|
242
|
+
* @since 3.19.0
|
|
243
|
+
* @category Combinators
|
|
244
|
+
* @experimental
|
|
245
|
+
*/
|
|
246
|
+
<A extends PrimaryKey.PrimaryKey>(self: HashRing<A>, node: A): boolean
|
|
247
|
+
} = dual(
|
|
248
|
+
2,
|
|
249
|
+
<A extends PrimaryKey.PrimaryKey>(self: HashRing<A>, node: A): boolean => self.nodes.has(PrimaryKey.value(node))
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Gets the node which should handle the given input. Returns undefined if
|
|
254
|
+
* the hashring has no elements with weight.
|
|
255
|
+
*
|
|
256
|
+
* @since 3.19.0
|
|
257
|
+
* @category Combinators
|
|
258
|
+
* @experimental
|
|
259
|
+
*/
|
|
260
|
+
export const get = <A extends PrimaryKey.PrimaryKey>(self: HashRing<A>, input: string): A | undefined => {
|
|
261
|
+
if (self.ring.length === 0) {
|
|
262
|
+
return undefined
|
|
263
|
+
}
|
|
264
|
+
const index = getIndexForInput(self, Hash.string(input))[0]
|
|
265
|
+
const node = self.ring[index][1]!
|
|
266
|
+
return self.nodes.get(node)![0]
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Distributes `count` shards across the nodes in the ring, attempting to
|
|
271
|
+
* balance the number of shards allocated to each node. Returns undefined if
|
|
272
|
+
* the hashring has no elements with weight.
|
|
273
|
+
*
|
|
274
|
+
* @since 3.19.0
|
|
275
|
+
* @category Combinators
|
|
276
|
+
* @experimental
|
|
277
|
+
*/
|
|
278
|
+
export const getShards = <A extends PrimaryKey.PrimaryKey>(self: HashRing<A>, count: number): Array<A> | undefined => {
|
|
279
|
+
if (self.ring.length === 0) {
|
|
280
|
+
return undefined
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const shards = new Array<A>(count)
|
|
284
|
+
|
|
285
|
+
// for tracking how many shards have been allocated to each node
|
|
286
|
+
const allocations = new Map<string, number>()
|
|
287
|
+
// for tracking which shards still need to be allocated
|
|
288
|
+
const remaining = new Set<number>()
|
|
289
|
+
// for tracking which nodes have reached the max allocation
|
|
290
|
+
const exclude = new Set<string>()
|
|
291
|
+
|
|
292
|
+
// First pass - allocate the closest nodes, skipping nodes that have reached
|
|
293
|
+
// max
|
|
294
|
+
const distances = new Array<[shard: number, node: string, distance: number]>(count)
|
|
295
|
+
for (let shard = 0; shard < count; shard++) {
|
|
296
|
+
const hash = (shardHashes[shard] ??= Hash.string(`shard-${shard}`))
|
|
297
|
+
const [index, distance] = getIndexForInput(self, hash)
|
|
298
|
+
const node = self.ring[index][1]!
|
|
299
|
+
distances[shard] = [shard, node, distance]
|
|
300
|
+
remaining.add(shard)
|
|
301
|
+
}
|
|
302
|
+
distances.sort((a, b) => a[2] - b[2])
|
|
303
|
+
for (let i = 0; i < count; i++) {
|
|
304
|
+
const [shard, node] = distances[i]
|
|
305
|
+
if (exclude.has(node)) continue
|
|
306
|
+
const [value, weight] = self.nodes.get(node)!
|
|
307
|
+
shards[shard] = value
|
|
308
|
+
remaining.delete(shard)
|
|
309
|
+
const nodeCount = (allocations.get(node) ?? 0) + 1
|
|
310
|
+
allocations.set(node, nodeCount)
|
|
311
|
+
const maxPerNode = Math.max(1, Math.floor(count * (weight / self.totalWeightCache)))
|
|
312
|
+
if (nodeCount >= maxPerNode) {
|
|
313
|
+
exclude.add(node)
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Second pass - allocate any remaining shards, skipping nodes that have
|
|
318
|
+
// reached max
|
|
319
|
+
let allAtMax = exclude.size === self.nodes.size
|
|
320
|
+
remaining.forEach((shard) => {
|
|
321
|
+
const index = getIndexForInput(self, shardHashes[shard], allAtMax ? undefined : exclude)[0]
|
|
322
|
+
const node = self.ring[index][1]
|
|
323
|
+
const [value, weight] = self.nodes.get(node)!
|
|
324
|
+
shards[shard] = value
|
|
325
|
+
|
|
326
|
+
if (allAtMax) return
|
|
327
|
+
const nodeCount = (allocations.get(node) ?? 0) + 1
|
|
328
|
+
allocations.set(node, nodeCount)
|
|
329
|
+
const maxPerNode = Math.max(1, Math.floor(count * (weight / self.totalWeightCache)))
|
|
330
|
+
if (nodeCount >= maxPerNode) {
|
|
331
|
+
exclude.add(node)
|
|
332
|
+
if (exclude.size === self.nodes.size) {
|
|
333
|
+
allAtMax = true
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
return shards
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const shardHashes: Array<number> = []
|
|
342
|
+
|
|
343
|
+
function getIndexForInput<A extends PrimaryKey.PrimaryKey>(
|
|
344
|
+
self: HashRing<A>,
|
|
345
|
+
hash: number,
|
|
346
|
+
exclude?: ReadonlySet<string> | undefined
|
|
347
|
+
): readonly [index: number, distance: number] {
|
|
348
|
+
const ring = self.ring
|
|
349
|
+
const len = ring.length
|
|
350
|
+
|
|
351
|
+
let mid: number
|
|
352
|
+
let lo = 0
|
|
353
|
+
let hi = len - 1
|
|
354
|
+
|
|
355
|
+
while (lo <= hi) {
|
|
356
|
+
mid = ((lo + hi) / 2) >>> 0
|
|
357
|
+
if (ring[mid][0] >= hash) {
|
|
358
|
+
hi = mid - 1
|
|
359
|
+
} else {
|
|
360
|
+
lo = mid + 1
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
const a = lo === len ? lo - 1 : lo
|
|
364
|
+
const distA = Math.abs(ring[a][0] - hash)
|
|
365
|
+
if (exclude === undefined) {
|
|
366
|
+
const b = lo - 1
|
|
367
|
+
if (b < 0) {
|
|
368
|
+
return [a, distA]
|
|
369
|
+
}
|
|
370
|
+
const distB = Math.abs(ring[b][0] - hash)
|
|
371
|
+
return distA <= distB ? [a, distA] : [b, distB]
|
|
372
|
+
} else if (!exclude.has(ring[a][1])) {
|
|
373
|
+
return [a, distA]
|
|
374
|
+
}
|
|
375
|
+
const range = Math.max(lo, len - lo)
|
|
376
|
+
for (let i = 1; i < range; i++) {
|
|
377
|
+
let index = lo - i
|
|
378
|
+
if (index >= 0 && index < len && !exclude.has(ring[index][1])) {
|
|
379
|
+
return [index, Math.abs(ring[index][0] - hash)]
|
|
380
|
+
}
|
|
381
|
+
index = lo + i
|
|
382
|
+
if (index >= 0 && index < len && !exclude.has(ring[index][1])) {
|
|
383
|
+
return [index, Math.abs(ring[index][0] - hash)]
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return [a, distA]
|
|
387
|
+
}
|
package/src/JSONSchema.ts
CHANGED
|
@@ -163,7 +163,8 @@ export interface JsonSchema7Boolean extends JsonSchemaAnnotations {
|
|
|
163
163
|
*/
|
|
164
164
|
export interface JsonSchema7Array extends JsonSchemaAnnotations {
|
|
165
165
|
type: "array"
|
|
166
|
-
items?: JsonSchema7 | Array<JsonSchema7>
|
|
166
|
+
items?: JsonSchema7 | Array<JsonSchema7> | false
|
|
167
|
+
prefixItems?: Array<JsonSchema7>
|
|
167
168
|
minItems?: number
|
|
168
169
|
maxItems?: number
|
|
169
170
|
additionalItems?: JsonSchema7 | boolean
|
|
@@ -258,7 +259,7 @@ export const make = <A, I, R>(schema: Schema.Schema<A, I, R>): JsonSchema7Root =
|
|
|
258
259
|
definitions
|
|
259
260
|
})
|
|
260
261
|
const out: JsonSchema7Root = {
|
|
261
|
-
$schema,
|
|
262
|
+
$schema: getMetaSchemaUri("jsonSchema7"),
|
|
262
263
|
$defs: {},
|
|
263
264
|
...jsonSchema
|
|
264
265
|
}
|
|
@@ -270,12 +271,25 @@ export const make = <A, I, R>(schema: Schema.Schema<A, I, R>): JsonSchema7Root =
|
|
|
270
271
|
return out
|
|
271
272
|
}
|
|
272
273
|
|
|
273
|
-
type Target = "jsonSchema7" | "jsonSchema2019-09" | "openApi3.1"
|
|
274
|
+
type Target = "jsonSchema7" | "jsonSchema2019-09" | "openApi3.1" | "jsonSchema2020-12"
|
|
274
275
|
|
|
275
276
|
type TopLevelReferenceStrategy = "skip" | "keep"
|
|
276
277
|
|
|
277
278
|
type AdditionalPropertiesStrategy = "allow" | "strict"
|
|
278
279
|
|
|
280
|
+
/** @internal */
|
|
281
|
+
export function getMetaSchemaUri(target: Target) {
|
|
282
|
+
switch (target) {
|
|
283
|
+
case "jsonSchema7":
|
|
284
|
+
return "http://json-schema.org/draft-07/schema#"
|
|
285
|
+
case "jsonSchema2019-09":
|
|
286
|
+
return "https://json-schema.org/draft/2019-09/schema"
|
|
287
|
+
case "jsonSchema2020-12":
|
|
288
|
+
case "openApi3.1":
|
|
289
|
+
return "https://json-schema.org/draft/2020-12/schema"
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
279
293
|
/**
|
|
280
294
|
* Returns a JSON Schema with additional options and definitions.
|
|
281
295
|
*
|
|
@@ -365,8 +379,6 @@ const constEmptyStruct: JsonSchema7empty = {
|
|
|
365
379
|
]
|
|
366
380
|
}
|
|
367
381
|
|
|
368
|
-
const $schema = "http://json-schema.org/draft-07/schema#"
|
|
369
|
-
|
|
370
382
|
function getRawDescription(annotated: AST.Annotated | undefined): string | undefined {
|
|
371
383
|
if (annotated !== undefined) return Option.getOrUndefined(AST.getDescriptionAnnotation(annotated))
|
|
372
384
|
}
|
|
@@ -529,6 +541,7 @@ function isContentSchemaSupported(options: GoOptions): boolean {
|
|
|
529
541
|
case "jsonSchema7":
|
|
530
542
|
return false
|
|
531
543
|
case "jsonSchema2019-09":
|
|
544
|
+
case "jsonSchema2020-12":
|
|
532
545
|
case "openApi3.1":
|
|
533
546
|
return true
|
|
534
547
|
}
|
|
@@ -716,7 +729,11 @@ function go(
|
|
|
716
729
|
const len = ast.elements.length
|
|
717
730
|
if (len > 0) {
|
|
718
731
|
output.minItems = len - ast.elements.filter((element) => element.isOptional).length
|
|
719
|
-
|
|
732
|
+
if (options.target === "jsonSchema7") {
|
|
733
|
+
output.items = elements
|
|
734
|
+
} else {
|
|
735
|
+
output.prefixItems = elements
|
|
736
|
+
}
|
|
720
737
|
}
|
|
721
738
|
// ---------------------------------------------
|
|
722
739
|
// handle rest element
|
|
@@ -726,9 +743,18 @@ function go(
|
|
|
726
743
|
const head = rest[0]
|
|
727
744
|
const isHomogeneous = restLength === 1 && ast.elements.every((e) => e.type === ast.rest[0].type)
|
|
728
745
|
if (isHomogeneous) {
|
|
729
|
-
|
|
746
|
+
if (options.target === "jsonSchema7") {
|
|
747
|
+
output.items = head
|
|
748
|
+
} else {
|
|
749
|
+
output.items = head
|
|
750
|
+
delete output.prefixItems
|
|
751
|
+
}
|
|
730
752
|
} else {
|
|
731
|
-
|
|
753
|
+
if (options.target === "jsonSchema7") {
|
|
754
|
+
output.additionalItems = head
|
|
755
|
+
} else {
|
|
756
|
+
output.items = head
|
|
757
|
+
}
|
|
732
758
|
}
|
|
733
759
|
|
|
734
760
|
// ---------------------------------------------
|
|
@@ -740,7 +766,11 @@ function go(
|
|
|
740
766
|
}
|
|
741
767
|
} else {
|
|
742
768
|
if (len > 0) {
|
|
743
|
-
|
|
769
|
+
if (options.target === "jsonSchema7") {
|
|
770
|
+
output.additionalItems = false
|
|
771
|
+
} else {
|
|
772
|
+
output.items = false
|
|
773
|
+
}
|
|
744
774
|
} else {
|
|
745
775
|
output.maxItems = 0
|
|
746
776
|
}
|
package/src/TestClock.ts
CHANGED
|
@@ -438,17 +438,17 @@ export class TestClockImpl implements TestClock {
|
|
|
438
438
|
export const live = (data: Data): Layer.Layer<TestClock, never, Annotations.TestAnnotations | Live.TestLive> =>
|
|
439
439
|
layer.scoped(
|
|
440
440
|
TestClock,
|
|
441
|
-
core.gen(function*(
|
|
442
|
-
const live = yield*
|
|
443
|
-
const annotations = yield*
|
|
444
|
-
const clockState = yield*
|
|
445
|
-
const warningState = yield*
|
|
446
|
-
const suspendedWarningState = yield*
|
|
441
|
+
core.gen(function*() {
|
|
442
|
+
const live = yield* Live.TestLive
|
|
443
|
+
const annotations = yield* Annotations.TestAnnotations
|
|
444
|
+
const clockState = yield* core.sync(() => ref.unsafeMake(data))
|
|
445
|
+
const warningState = yield* circular.makeSynchronized(WarningData.start)
|
|
446
|
+
const suspendedWarningState = yield* circular.makeSynchronized(SuspendedWarningData.start)
|
|
447
447
|
const testClock = new TestClockImpl(clockState, live, annotations, warningState, suspendedWarningState)
|
|
448
|
-
yield*
|
|
449
|
-
yield*
|
|
448
|
+
yield* fiberRuntime.withClockScoped(testClock)
|
|
449
|
+
yield* fiberRuntime.addFinalizer(
|
|
450
450
|
() => core.zipRight(testClock.warningDone(), testClock.suspendedWarningDone())
|
|
451
|
-
)
|
|
451
|
+
)
|
|
452
452
|
return testClock
|
|
453
453
|
})
|
|
454
454
|
)
|
package/src/Types.ts
CHANGED
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
* @since 2.0.0
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
type _TupleOf<T, N extends number, R extends Array<unknown>> =
|
|
7
|
+
type _TupleOf<T, N extends number, R extends Array<unknown>> = `${N}` extends `-${number}` ? never
|
|
8
|
+
: R["length"] extends N ? R
|
|
9
|
+
: _TupleOf<T, N, [T, ...R]>
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
12
|
* Represents a tuple with a fixed number of elements of type `T`.
|
package/src/index.ts
CHANGED
package/src/internal/version.ts
CHANGED