EdgeAIG's picture
download
raw
12.8 kB
/**
* 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.