Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: deprecate orderings string values, resolves #269 #279

Merged
merged 7 commits into from
Apr 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
});