| /** | |
| * Models URL query parameters as ordered string pairs. | |
| * | |
| * `UrlParams` is used for HTTP client query strings, URL-encoded form bodies, | |
| * and server-side decoding. Values can be built from records, iterables, or | |
| * native `URLSearchParams`, then updated, serialized, converted to a `URL`, or | |
| * decoded with schemas. | |
| * | |
| * @since 4.0.0 | |
| */ | |
| import * as Arr from "../../Array.js"; | |
| import * as Data from "../../Data.js"; | |
| import * as Effect from "../../Effect.js"; | |
| import * as Equal from "../../Equal.js"; | |
| import * as Equ from "../../Equivalence.js"; | |
| import { dual } from "../../Function.js"; | |
| import * as Hash from "../../Hash.js"; | |
| import { PipeInspectableProto } from "../../internal/core.js"; | |
| import * as Option from "../../Option.js"; | |
| import { hasProperty } from "../../Predicate.js"; | |
| import * as Result from "../../Result.js"; | |
| import * as Schema from "../../Schema.js"; | |
| import * as SchemaIssue from "../../SchemaIssue.js"; | |
| import * as SchemaTransformation from "../../SchemaTransformation.js"; | |
| import * as Tuple from "../../Tuple.js"; | |
| const TypeId = "~effect/http/UrlParams"; | |
| /** | |
| * Returns `true` when a value is a `UrlParams` instance. | |
| * | |
| * @category guards | |
| * @since 4.0.0 | |
| */ | |
| export const isUrlParams = u => hasProperty(u, TypeId); | |
| const Proto = { | |
| ...PipeInspectableProto, | |
| [TypeId]: TypeId, | |
| [Symbol.iterator]() { | |
| return this.params[Symbol.iterator](); | |
| }, | |
| toJSON() { | |
| return { | |
| _id: "UrlParams", | |
| params: Object.fromEntries(this.params) | |
| }; | |
| }, | |
| [Equal.symbol](that) { | |
| return Equivalence(this, that); | |
| }, | |
| [Hash.symbol]() { | |
| return Hash.array(this.params.flat()); | |
| } | |
| }; | |
| /** | |
| * Creates `UrlParams` from ordered string key-value pairs. | |
| * | |
| * **Details** | |
| * | |
| * The input pairs are used as-is and are not coerced or normalized. | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const make = params => { | |
| const self = Object.create(Proto); | |
| self.params = params; | |
| return self; | |
| }; | |
| /** | |
| * Creates `UrlParams` from a supported input shape. | |
| * | |
| * **Details** | |
| * | |
| * Primitive values are converted to strings, arrays produce repeated parameters, | |
| * nested records use bracket notation, and `undefined` values are omitted. | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const fromInput = input => { | |
| const parsed = fromInputNested(input); | |
| const out = []; | |
| for (let i = 0; i < parsed.length; i++) { | |
| if (Array.isArray(parsed[i][0])) { | |
| const [keys, value] = parsed[i]; | |
| out.push([`${keys[0]}[${keys.slice(1).join("][")}]`, value]); | |
| } else { | |
| out.push(parsed[i]); | |
| } | |
| } | |
| return make(out); | |
| }; | |
| const fromInputNested = input => { | |
| const entries = typeof input[Symbol.iterator] === "function" ? Arr.fromIterable(input) : Object.entries(input); | |
| const out = []; | |
| for (const [key, value] of entries) { | |
| if (Array.isArray(value)) { | |
| for (let i = 0; i < value.length; i++) { | |
| if (value[i] !== undefined) { | |
| out.push([key, String(value[i])]); | |
| } | |
| } | |
| } else if (typeof value === "object") { | |
| const nested = fromInputNested(value); | |
| for (const [k, v] of nested) { | |
| out.push([[key, ...(typeof k === "string" ? [k] : k)], v]); | |
| } | |
| } else if (value !== undefined) { | |
| out.push([key, String(value)]); | |
| } | |
| } | |
| return out; | |
| }; | |
| /** | |
| * Provides an order-sensitive `Equivalence` instance for `UrlParams`. | |
| * | |
| * **Details** | |
| * | |
| * Two values are equivalent when they contain the same key-value pairs in the same | |
| * order. | |
| * | |
| * @category instances | |
| * @since 4.0.0 | |
| */ | |
| export const Equivalence = /*#__PURE__*/Equ.make((a, b) => arrayEquivalence(a.params, b.params)); | |
| const arrayEquivalence = /*#__PURE__*/Arr.makeEquivalence(/*#__PURE__*/Tuple.makeEquivalence([/*#__PURE__*/Equ.strictEqual(), /*#__PURE__*/Equ.strictEqual()])); | |
| /** | |
| * Schema for `UrlParams`. | |
| * | |
| * **Details** | |
| * | |
| * The encoded representation is an array of string key-value tuples. | |
| * | |
| * @category schemas | |
| * @since 4.0.0 | |
| */ | |
| export const UrlParamsSchema = /*#__PURE__*/Schema.declare(isUrlParams, { | |
| typeConstructor: { | |
| _tag: "effect/http/UrlParams" | |
| }, | |
| generation: { | |
| runtime: `UrlParams.UrlParamsSchema`, | |
| Type: `UrlParams.UrlParams`, | |
| Encoded: `typeof UrlParams.UrlParamsSchema["Encoded"]`, | |
| importDeclaration: `import * as UrlParams from "effect/unstable/http/UrlParams"` | |
| }, | |
| expected: "UrlParams", | |
| toEquivalence: () => Equivalence, | |
| toCodec: () => Schema.link()(Schema.Array(Schema.Tuple([Schema.String, Schema.String])), SchemaTransformation.transform({ | |
| decode: make, | |
| encode: self => self.params | |
| })) | |
| }); | |
| /** | |
| * An empty `UrlParams` value. | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const empty = /*#__PURE__*/make([]); | |
| /** | |
| * Returns all values for a query parameter key in insertion order. | |
| * | |
| * **Details** | |
| * | |
| * Returns an empty array when the key is absent. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const getAll = /*#__PURE__*/dual(2, (self, key) => Arr.reduce(self.params, [], (acc, [k, value]) => { | |
| if (k === key) { | |
| acc.push(value); | |
| } | |
| return acc; | |
| })); | |
| /** | |
| * Returns the first value for a query parameter key safely. | |
| * | |
| * **When to use** | |
| * | |
| * Use when duplicate query parameters are ordered and the first occurrence has | |
| * precedence. | |
| * | |
| * **Details** | |
| * | |
| * Returns `Option.none` when the key is absent. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const getFirst = /*#__PURE__*/dual(2, (self, key) => Arr.findFirst(self.params, ([k]) => k === key).pipe(Option.map(([, value]) => value))); | |
| /** | |
| * Returns the last value for a query parameter key safely. | |
| * | |
| * **When to use** | |
| * | |
| * Use when duplicate query parameters are ordered and the last occurrence has | |
| * precedence. | |
| * | |
| * **Details** | |
| * | |
| * Returns `Option.none` when the key is absent. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const getLast = /*#__PURE__*/dual(2, (self, key) => Arr.findLast(self.params, ([k]) => k === key).pipe(Option.map(([, value]) => value))); | |
| /** | |
| * Sets a query parameter to a single value. | |
| * | |
| * **Details** | |
| * | |
| * Existing values for the same key are removed, and the new value is appended to | |
| * the end. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const set = /*#__PURE__*/dual(3, (self, key, value) => make(Arr.append(Arr.filter(self.params, ([k]) => k !== key), [key, String(value)]))); | |
| /** | |
| * Transforms the underlying ordered key-value pairs of `UrlParams`. | |
| * | |
| * **Details** | |
| * | |
| * The result is wrapped in a new `UrlParams` value. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const transform = /*#__PURE__*/dual(2, (self, f) => make(f(self.params))); | |
| /** | |
| * Sets multiple query parameters from input. | |
| * | |
| * **Details** | |
| * | |
| * Keys present in the input replace existing values for those keys, while | |
| * unmentioned existing parameters are preserved. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const setAll = /*#__PURE__*/dual(2, (self, input) => { | |
| const out = fromInput(input); | |
| const params = out.params; | |
| const keys = new Set(); | |
| for (let i = 0; i < params.length; i++) { | |
| keys.add(params[i][0]); | |
| } | |
| for (let i = 0; i < self.params.length; i++) { | |
| if (keys.has(self.params[i][0])) continue; | |
| params.push(self.params[i]); | |
| } | |
| return out; | |
| }); | |
| /** | |
| * Appends a query parameter value without removing existing values for the key. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const append = /*#__PURE__*/dual(3, (self, key, value) => make(Arr.append(self.params, [key, String(value)]))); | |
| /** | |
| * Appends all query parameters produced from the supplied input. | |
| * | |
| * **Details** | |
| * | |
| * Existing parameters are preserved. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const appendAll = /*#__PURE__*/dual(2, (self, input) => transform(self, Arr.appendAll(fromInput(input).params))); | |
| /** | |
| * Removes all query parameter values for the specified key. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const remove = /*#__PURE__*/dual(2, (self, key) => transform(self, Arr.filter(([k]) => k !== key))); | |
| /** | |
| * Error returned when constructing a `URL` from `UrlParams` fails. | |
| * | |
| * @category errors | |
| * @since 4.0.0 | |
| */ | |
| export class UrlParamsError extends /*#__PURE__*/Data.TaggedError("UrlParamsError") {} | |
| /** | |
| * Creates a `URL` safely by appending `UrlParams` and an optional hash to a URL string. | |
| * | |
| * **Details** | |
| * | |
| * Returns a `Result` that fails with `UrlParamsError` if the URL cannot be | |
| * constructed. | |
| * | |
| * @category converting | |
| * @since 4.0.0 | |
| */ | |
| export const makeUrl = (url, params, hash) => { | |
| try { | |
| const urlInstance = new URL(url, baseUrl()); | |
| for (let i = 0; i < params.params.length; i++) { | |
| const [key, value] = params.params[i]; | |
| if (value !== undefined) { | |
| urlInstance.searchParams.append(key, value); | |
| } | |
| } | |
| if (hash !== undefined) { | |
| urlInstance.hash = hash; | |
| } | |
| return Result.succeed(urlInstance); | |
| } catch (e) { | |
| return Result.fail(new UrlParamsError({ | |
| cause: e | |
| })); | |
| } | |
| }; | |
| /** | |
| * Serializes `UrlParams` to a URL query string without a leading question mark. | |
| * | |
| * @category converting | |
| * @since 4.0.0 | |
| */ | |
| export const toString = self => new URLSearchParams(self.params).toString(); | |
| const baseUrl = () => { | |
| if ("location" in globalThis && globalThis.location !== undefined && globalThis.location.origin !== undefined && globalThis.location.pathname !== undefined) { | |
| return location.origin + location.pathname; | |
| } | |
| return undefined; | |
| }; | |
| /** | |
| * Builds a `Record` containing all the key-value pairs in the given `UrlParams` | |
| * as `string` (if only one value for a key) or a `NonEmptyArray<string>` | |
| * (when more than one value for a key) | |
| * | |
| * **Example** (Converting parameters to a record) | |
| * | |
| * ```ts | |
| * import { UrlParams } from "effect/unstable/http" | |
| * import * as assert from "node:assert" | |
| * | |
| * const urlParams = UrlParams.fromInput({ | |
| * a: 1, | |
| * b: true, | |
| * c: "string", | |
| * e: [1, 2, 3] | |
| * }) | |
| * const result = UrlParams.toRecord(urlParams) | |
| * | |
| * assert.deepStrictEqual( | |
| * result, | |
| * { "a": "1", "b": "true", "c": "string", "e": ["1", "2", "3"] } | |
| * ) | |
| * ``` | |
| * | |
| * @category converting | |
| * @since 4.0.0 | |
| */ | |
| export const toRecord = self => { | |
| const out = {}; | |
| for (const [k, value] of self.params) { | |
| const curr = out[k]; | |
| if (curr === undefined) { | |
| out[k] = value; | |
| } else if (typeof curr === "string") { | |
| out[k] = [curr, value]; | |
| } else { | |
| curr.push(value); | |
| } | |
| } | |
| return out; | |
| }; | |
| /** | |
| * Builds a readonly record from `UrlParams`. | |
| * | |
| * **Details** | |
| * | |
| * Keys with one value map to a string, and keys with multiple values map to a | |
| * non-empty readonly array of strings. | |
| * | |
| * @category converting | |
| * @since 4.0.0 | |
| */ | |
| export const toReadonlyRecord = toRecord; | |
| /** | |
| * Extracts a JSON value from the first occurrence of the given `field` in the | |
| * `UrlParams`. | |
| * | |
| * **Example** (Decoding JSON parameter fields) | |
| * | |
| * ```ts | |
| * import { Schema } from "effect" | |
| * import { UrlParams } from "effect/unstable/http" | |
| * | |
| * const extractFoo = UrlParams.schemaJsonField("foo").pipe( | |
| * Schema.decodeTo(Schema.Struct({ | |
| * some: Schema.String, | |
| * number: Schema.Number | |
| * })) | |
| * ) | |
| * | |
| * console.log( | |
| * Schema.decodeSync(extractFoo)(UrlParams.fromInput({ | |
| * foo: JSON.stringify({ some: "bar", number: 42 }), | |
| * baz: "qux" | |
| * })) | |
| * ) | |
| * ``` | |
| * | |
| * @category schemas | |
| * @since 4.0.0 | |
| */ | |
| export const schemaJsonField = field => UrlParamsSchema.pipe(Schema.decodeTo(Schema.UnknownFromJsonString, SchemaTransformation.transformOrFail({ | |
| decode: params => Option.match(getFirst(params, field), { | |
| onNone: () => Effect.fail(new SchemaIssue.Pointer([field], new SchemaIssue.MissingKey(undefined))), | |
| onSome: Effect.succeed | |
| }), | |
| encode: value => Effect.succeed(make([[field, value]])) | |
| }))); | |
| /** | |
| * Schema that decodes `UrlParams` into a record of key-value pairs. | |
| * | |
| * **Details** | |
| * | |
| * Keys with one value decode to a string, and keys with multiple values decode to | |
| * a non-empty readonly array of strings. | |
| * | |
| * **Example** (Decoding URL parameters to a record) | |
| * | |
| * ```ts | |
| * import { Schema } from "effect" | |
| * import { UrlParams } from "effect/unstable/http" | |
| * | |
| * const toStruct = UrlParams.schemaRecord.pipe( | |
| * Schema.decodeTo(Schema.Struct({ | |
| * some: Schema.String, | |
| * number: Schema.FiniteFromString | |
| * })) | |
| * ) | |
| * | |
| * console.log( | |
| * Schema.decodeSync(toStruct)(UrlParams.fromInput({ | |
| * some: "value", | |
| * number: 42 | |
| * })) | |
| * ) | |
| * ``` | |
| * | |
| * @category schemas | |
| * @since 4.0.0 | |
| */ | |
| export const schemaRecord = /*#__PURE__*/UrlParamsSchema.pipe(/*#__PURE__*/Schema.decodeTo(/*#__PURE__*/Schema.Record(Schema.String, /*#__PURE__*/Schema.Union([Schema.String, /*#__PURE__*/Schema.NonEmptyArray(Schema.String)])), /*#__PURE__*/SchemaTransformation.transform({ | |
| decode: toReadonlyRecord, | |
| encode: fromInput | |
| }))); | |
| //# sourceMappingURL=UrlParams.js.map |
Xet Storage Details
- Size:
- 12.8 kB
- Xet hash:
- 46fafd3c53d3c490c03e578558367d5a3baf3047b666ae2ff2479605cf899555
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.