Skip to content

Commit

Permalink
chore: deprecate orderings string values, resolves #269 (#279)
Browse files Browse the repository at this point in the history
* chore: deprecate `orderings` string values, resolves #269

* fix: add warning for `string` and `string[]` `orderings` values

* test: remove unused `@ts-expect-error`

* fix: update message name

---------

Co-authored-by: lihbr <[email protected]>
Co-authored-by: Angelo Ashmore <[email protected]>
  • Loading branch information
3 people authored Apr 11, 2023
1 parent a520aa4 commit 25b9019
Show file tree
Hide file tree
Showing 5 changed files with 272 additions and 14 deletions.
50 changes: 50 additions & 0 deletions messages/filters-must-be-an-array.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# `filters` must be an array

Prismic queries can be filtered using the `filters` query parameter.

The `filters` parameter accepts an array of filters like the following:

```typescript
import * as prismic from "@prismicio/client";

const client = prismic.createClient("my-repo-name");

const blogPosts = await client.getAllByType("blog_post", {
filters: [
prismic.filter.not("my.document.uid", "hidden"),
prismic.filter.dateBefore(
"document.first_publication_date",
new Date("1991-03-07"),
),
],
});
```

This query will fetch all `blog_post` documents _except_ those that:

1. Have a UID of `"hidden"`.
2. Were first published before March 7, 1991.

## Non-array values are deprecated

In versions of `@prismicio/client` < v7.0, the `filters` parameter (previously called the `predicates` parameter) could be provided as a single string.

**Strings as `filters` values are deprecated**. Use an array instead.

```typescript
// ✅ Correct
await client.getAllByType("blog_post", {
filters: [
prismic.filter.not("my.document.uid", "hidden"),
prismic.filter.dateBefore(
"document.first_publication_date",
new Date("1991-03-07"),
),
],
});

// ❌ Incorrect
await client.getAllByType("blog_post", {
filters: prismic.filter.not("my.document.uid", "hidden"),
});
```
57 changes: 57 additions & 0 deletions messages/orderings-must-be-an-array-of-objects.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# `orderings` must be an array of objects

Prismic's Rest API accepts an `orderings` parameter to determine the sort order of query results.

The `orderings` parameter can be provided to `@prismicio/client` queries like the following:

```typescript
import * as prismic from "@prismicio/client";

const client = prismic.createClient("my-repo-name");

const blogPosts = await client.getAllByType("blog_post", {
orderings: [
{ field: "my.blog_post.published_at", direction: "desc" },
{ field: "blog_post.first_publication_date", direction: "desc" },
{ field: "my.blog_post.title" },
],
});
```

This query will fetch all `blog_post` documents sorted in the following order:

1. By the `published_at` field in descending order.
2. By the `first_publication_date` metadata in descending order.
3. By the `title` field in ascending order.

## Strings and arrays of strings are deprecated

In versions of `@prismicio/client` < v7.0, the `orderings` parameter could be provided as a string or an array of strings.

**Strings and arrays of strings as `orderings` values are deprecated**. Use the object syntax instead.

```typescript
// ✅ Correct
await client.getAllByType("blog_post", {
orderings: [
{ field: "my.blog_post.published_at", direction: "desc" },
{ field: "blog_post.first_publication_date", direction: "desc" },
{ field: "my.blog_post.title" },
],
});

// ❌ Incorrect
await client.getAllByType("blog_post", {
orderings: [
"my.blog_post.published_at desc",
"blog_post.first_publication_date desc",
"my.blog_post.title",
],
});

// ❌ Incorrect
await client.getAllByType("blog_post", {
orderings:
"my.blog_post.published_at desc,blog_post.first_publication_date desc,my.blog_post.title",
});
```
82 changes: 70 additions & 12 deletions src/buildQueryURL.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { castArray } from "./lib/castArray";
import { devMsg } from "./lib/devMsg";

/**
* Create a union of the given object's values, and optionally specify which
Expand Down Expand Up @@ -154,8 +155,24 @@ export interface QueryParams {
* can specify as many fields as you want.
*
* {@link https://prismic.io/docs/rest-api-technical-reference#orderings}
*
* @remarks Strings and arrays of strings are deprecated as of
* `@prismicio/[email protected]`. Please migrate to the more explicit
* array of objects.
*
* @example
*
* ```typescript
* buildQueryURL(endpoint,{
* orderings: [
* { field: "my.product.price", direction: "desc" },
* { field: "my.product.title" },
* ],
* });
* ```
*/
orderings?: Ordering | string | (Ordering | string)[];
// TODO: Update TSDoc with deprecated API removal in v8
orderings?: string | Ordering | (string | Ordering)[];

/**
* The `routes` option allows you to define how a document's `url` field is
Expand Down Expand Up @@ -202,7 +219,7 @@ type BuildQueryURLParams = {
filters?: string | string[];

/**
* @deprecated Renamed to `filters`
* @deprecated Renamed to `filters`. Ensure the value is an array of filters, not a single, non-array filter.
*/
predicates?: string | string[];
};
Expand Down Expand Up @@ -235,15 +252,31 @@ type ValidParamName =
*
* @returns String representation of the Ordering.
*/
const castOrderingToString = (ordering: Ordering | string): string =>
typeof ordering === "string"
? ordering
: [
ordering.field,
ordering.direction === "desc" ? ordering.direction : undefined,
]
.filter(Boolean)
.join(" ");
const castOrderingToString = (ordering: Ordering | string): string => {
// TODO: Remove the following when `orderings` strings are no longer supported.
if (typeof ordering === "string") {
if (process.env.NODE_ENV === "development") {
const [field, direction] = ordering.split(" ");

const objectForm =
direction === "desc"
? `{ field: "${field}", direction: "desc" }`
: `{ field: "${field}" }`;

console.warn(
`[@prismicio/client] A string value was provided to the \`orderings\` query parameter. Strings are deprecated. Please convert it to the object form: ${objectForm}. For more details, see ${devMsg(
"orderings-must-be-an-array-of-objects",
)}`,
);
}

return ordering;
}

return ordering.direction === "desc"
? `${ordering.field} desc`
: ordering.field;
};

export type BuildQueryURLArgs = QueryParams & BuildQueryURLParams;

Expand Down Expand Up @@ -273,6 +306,16 @@ export const buildQueryURL = (
const url = new URL(`documents/search`, `${endpoint}/`);

if (filters) {
// TODO: Remove warning when we remove support for string `filters` values.
if (process.env.NODE_ENV === "development" && !Array.isArray(filters)) {
console.warn(
`[@prismicio/client] A non-array value was provided to the \`filters\` query parameter (\`${filters}\`). Non-array values are deprecated. Please convert it to an array. For more details, see ${devMsg(
"filters-must-be-an-array",
)}`,
);
}

// TODO: Remove `castArray` when we remove support for string `filters` values.
for (const filter of castArray(filters)) {
url.searchParams.append("q", `[${filter}]`);
}
Expand All @@ -297,6 +340,18 @@ export const buildQueryURL = (
const scopedValue = params[name];

if (scopedValue != null) {
// TODO: Remove the following warning when `orderings` strings are no longer supported.
if (
process.env.NODE_ENV === "development" &&
typeof scopedValue === "string"
) {
console.warn(
`[@prismicio/client] A string value was provided to the \`orderings\` query parameter. Strings are deprecated. Please convert it to an array of objects. For more details, see ${devMsg(
"orderings-must-be-an-array-of-objects",
)}`,
);
}

const v = castArray(scopedValue)
.map((ordering) => castOrderingToString(ordering))
.join(",");
Expand All @@ -310,7 +365,10 @@ export const buildQueryURL = (
}

if (value != null) {
url.searchParams.set(name, castArray(value).join(","));
url.searchParams.set(
name,
castArray<string | number | Route | Ordering>(value).join(","),
);
}
}

Expand Down
20 changes: 20 additions & 0 deletions src/lib/devMsg.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { version } from "../../package.json";

/**
* Returns a `prismic.dev/msg` URL for a given message slug.
*
* @example
*
* ```ts
* devMsg("missing-param");
* // => "https://prismic.dev/msg/client/v1.2.3/missing-param.md"
* ```
*
* @param slug - Slug for the message. This corresponds to a Markdown file in
* the Git repository's `/messages` directory.
*
* @returns The `prismic.dev/msg` URL for the given slug.
*/
export const devMsg = (slug: string): string => {
return `https://prismic.dev/msg/client/v${version}/${slug}`;
};
77 changes: 75 additions & 2 deletions test/buildQueryURL.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { expect, it } from "vitest";
import { expect, it, vi } from "vitest";

import * as prismic from "../src";

Expand Down Expand Up @@ -79,7 +79,7 @@ it("supports params", () => {
fetchLinks: "fetchLinks",
graphQuery: "graphQuery",
lang: "lang",
orderings: "orderings",
orderings: [{ field: "orderings" }],
routes: "routes",
brokenRoute: "brokenRoute",
}),
Expand Down Expand Up @@ -139,6 +139,7 @@ it("supports empty orderings param", () => {
decodeURIComponent(
prismic.buildQueryURL(endpoint, {
ref: "ref",
// TODO: Remove test with deprecated API in v8
orderings: "",
}),
),
Expand All @@ -163,6 +164,7 @@ it("supports array orderings param", () => {
decodeURIComponent(
prismic.buildQueryURL(endpoint, {
ref: "ref",
// TODO: Remove test with deprecated API in v8
orderings: ["page.title", { field: "page.subtitle" }],
}),
),
Expand All @@ -176,6 +178,7 @@ it("supports setting direction of ordering param", () => {
decodeURIComponent(
prismic.buildQueryURL(endpoint, {
ref: "ref",
// TODO: Remove test with deprecated API in v8
orderings: ["page.title", { field: "page.subtitle", direction: "asc" }],
}),
),
Expand All @@ -188,6 +191,7 @@ it("supports setting direction of ordering param", () => {
prismic.buildQueryURL(endpoint, {
ref: "ref",
orderings: [
// TODO: Remove test with deprecated API in v8
"page.title",
{ field: "page.subtitle", direction: "desc" },
],
Expand Down Expand Up @@ -246,3 +250,72 @@ it("supports array routes param", () => {
)}`,
);
});

it("warns if NODE_ENV is development and a string is provided to `orderings`", () => {
const originalEnv = { ...process.env };

process.env.NODE_ENV = "development";

const consoleWarnSpy = vi
.spyOn(console, "warn")
.mockImplementation(() => void 0);

prismic.buildQueryURL(endpoint, {
ref: "ref",
orderings: "orderings",
});

expect(consoleWarnSpy).toHaveBeenCalledWith(
expect.stringMatching(/orderings-must-be-an-array-of-objects/i),
);

consoleWarnSpy.mockRestore();

process.env = originalEnv;
});

it("warns if NODE_ENV is development and an array of strings is provided to `orderings`", () => {
const originalEnv = { ...process.env };

process.env.NODE_ENV = "development";

const consoleWarnSpy = vi
.spyOn(console, "warn")
.mockImplementation(() => void 0);

prismic.buildQueryURL(endpoint, {
ref: "ref",
orderings: ["orderings"],
});

expect(consoleWarnSpy).toHaveBeenCalledWith(
expect.stringMatching(/orderings-must-be-an-array-of-objects/i),
);

consoleWarnSpy.mockRestore();

process.env = originalEnv;
});

it("warns if NODE_ENV is development and a string is provided to `filters`", () => {
const originalEnv = { ...process.env };

process.env.NODE_ENV = "development";

const consoleWarnSpy = vi
.spyOn(console, "warn")
.mockImplementation(() => void 0);

prismic.buildQueryURL(endpoint, {
ref: "ref",
filters: "filters",
});

expect(consoleWarnSpy).toHaveBeenCalledWith(
expect.stringMatching(/filters-must-be-an-array/i),
);

consoleWarnSpy.mockRestore();

process.env = originalEnv;
});

0 comments on commit 25b9019

Please sign in to comment.