From 988494a5fe5026db0c76f94835441692b7540013 Mon Sep 17 00:00:00 2001 From: Paul Visscher Date: Tue, 29 Apr 2025 15:37:29 +0200 Subject: [PATCH] feat: add partial zod support --- docs/.gitignore | 1 - docs/docs/intro.md | 9 -- docs/docs/jsonexpr.md | 192 ---------------------------------- docs/docusaurus.config.ts | 97 ----------------- docs/package.json | 36 ------- docs/sidebars.ts | 7 -- docs/src/css/custom.css | 22 ---- docs/static/img/favicon.ico | Bin 7200 -> 0 bytes docs/static/img/logo.png | Bin 26080 -> 0 bytes docs/static/img/logow.png | Bin 24517 -> 0 bytes package-lock.json | 19 +++- package.json | 9 +- src/engine/policy.ts | 17 ++- src/expressions/input.spec.ts | 84 +++++++++------ src/expressions/input.ts | 63 +++++++++-- test/policies.spec.ts | 2 +- 16 files changed, 141 insertions(+), 417 deletions(-) delete mode 100644 docs/.gitignore delete mode 100644 docs/docs/intro.md delete mode 100644 docs/docs/jsonexpr.md delete mode 100644 docs/docusaurus.config.ts delete mode 100644 docs/package.json delete mode 100644 docs/sidebars.ts delete mode 100644 docs/src/css/custom.css delete mode 100644 docs/static/img/favicon.ico delete mode 100644 docs/static/img/logo.png delete mode 100644 docs/static/img/logow.png diff --git a/docs/.gitignore b/docs/.gitignore deleted file mode 100644 index 5069cfc..0000000 --- a/docs/.gitignore +++ /dev/null @@ -1 +0,0 @@ -docs/api \ No newline at end of file diff --git a/docs/docs/intro.md b/docs/docs/intro.md deleted file mode 100644 index 387036b..0000000 --- a/docs/docs/intro.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -sidebar_position: 0 -title: Overview -slug: / ---- - -# Introduction - -This is the package documentation diff --git a/docs/docs/jsonexpr.md b/docs/docs/jsonexpr.md deleted file mode 100644 index 7ba75ff..0000000 --- a/docs/docs/jsonexpr.md +++ /dev/null @@ -1,192 +0,0 @@ -# JSONExpressions (jsonexpr) -JSONExpressions (jsonexpr) is a simple logical language that can be used to specify rules for data manipulation and evaluation. It is a standardized way of expressing logical statements in a JSON format, and is intended to be used as a building block for creating more complex systems that need to perform logical operations on data. - -JSONExpressions borrows heavily from [JSONLogic](https://jsonlogic.com), which is another data interchange format for expressing logical operations in JSON. - -Some of the key concepts that JSONExpressions borrows from JSONLogic include: - -* The use of JSON as the base data interchange format -* The ability to specify logical operations using a standardized syntax -* The support for variables and functions in logical expressions -* The use of input data to determine the values of variables in the logical expressions - -There are also some notable differences between JSONExpressions and JSONLogic. For example, JSONExpressions introduces additional operators and functions, and has a slightly different syntax for specifying logical expressions. - -Overall, JSONExpressions builds upon the foundation established by JSONLogic, providing a more powerful and flexible way to specify logical expressions in JSON. - -## Scope - -This specification defines JSONExpressions, a data interchange format for expressing logical operations. - -## Definitions - -* JSON: The JavaScript Object Notation (JSON) data interchange format, as defined in ECMA-404. -* `object`: A JSON object, as defined in ECMA-404. -* `array`: A JSON array, as defined in ECMA-404. -* `boolean`: A JSON boolean, as defined in ECMA-404. -* `null`: A JSON null, as defined in ECMA-404. -* `number`: A JSON number, as defined in ECMA-404. -* `string`: A JSON string, as defined in ECMA-404. - -## Operators - -JSONRule supports the following operators: - -### `var` -An operator to access the values of specific elements in arrays or objects. A data accessor is a string that specifies the path to the element in the data. - -There are two types of data accessors: - -1. Object accessors: An object accessor is a string that specifies the name of a property in an object. The name is specified as a string. -2. Array accessors: An array accessor is a string that specifies the index of an element in an array. The index is specified as a positive integer. For example, "0" is the first element of the array, "1" is the second element, and so on. - -Here is an example of a JSONExpressions document that uses data accessors: -```json -{ - "==": [ - { "var": "x.0" }, - { "var": "y.foo" } - ] -} -``` - -In this example, the `==` operator compares the value of the first element of the array `x` to the value of the property foo in the object `y`. The values of `x` and $y would be specified in the input data passed to the JSONExpressions document. - -Data accessors can be nested to access elements within arrays or objects. For example, the following data accessor would access the `bar` property of the second element of the `baz` array: - -``` -"x.baz.1.bar" -``` - -Note that data accessors are not the same as variables, which are used to reference the values of variables in the input data. Data accessors are used to access specific elements in the input data itself. - -### `if` -An operator that takes three arguments: a boolean expression, a value to return if the boolean expression is true, and a value to return if the boolean expression is false. - -For example: - -```json -{ - "if": [ - true, - "foo", - "bar" - ] -} -``` -In this example, the if operator takes three arguments: - -* A boolean expression: `true` -* A value to return if the boolean expression is `true`: `"foo"` -* A value to return if the boolean expression is `false`: `"bar"` - -Since the boolean expression is true in this example, the result of the if operator would be "foo". - -### `and` -An array of boolean expressions, which returns `true` if all of the expressions are `true`, and `false` otherwise. - -For example: - -```json -{ "and": [true, false, true] } -``` - -This expression would return `false`, because not all of the boolean expressions in the array are `true`. - -### `or` -An array of boolean expressions, which returns true if at least one of the expressions is `true`, and `false` otherwise. -For example: - -```json -{ "or": [false, false, true] } -``` - -This expression would return `true`, because at least one of the boolean expressions in the array is `true`. - -### `not` -A boolean expression, which returns the negation of the expression. - -For example: - -```json -{ "not": true } -``` - -This expression would return `false`, because the negation of `true` is `false`. - -### `==` -An array of two expressions, which returns `true` if the expressions are equal, and `false` otherwise. - -For example: - -```json -{ "==": [1, 1] } -``` - -This expression would return `true`, because the two expressions (1 and 1) are equal. - -### `!=` -An array of two expressions, which returns `true` if the expressions are not equal, and `false` otherwise. - -For example: - -```json -{ "!=": [1, 2] } -``` - -This expression would return `true`, because the two expressions (1 and 2) are not equal. - -### `>` -An array of two expressions, which returns `true` if the first expression is greater than the second expression, and `false` otherwise. - -For example: - -```json -{ ">": [2, 1] } -``` - -This expression would return `true`, because the first expression (2) is greater than the second expression (1). - -### `>=` -An array of two expressions, which returns `true` if the first expression is greater than or equal to the second expression, and `false` otherwise. - -For example: - -```json -{ ">=": [1, 1] } -``` - -This expression would return `true`, because the first expression (1) is equal to the second expression (1). - -### `<` -An array of two expressions, which returns `true` if the first expression is less than the second expression, and `false` otherwise. - -For example: - -```json -{ "<": [1, 2] } -``` - -This expression would return `true`, because the first expression (1) is less than the second expression (2). - -### `<=` -An array of two expressions, which returns `true` if the first expression is less than or equal to the second expression, and `false` otherwise. - -For example: - -```json -{ "<=": [1, 1] } -``` - -This expression would return `true`, because the first expression (1) is equal to the second expression (1). - -### Input data - -JSONExpressions supports the use of input data in expressions. The input data is a JSON object that contains the values of the variables used in the JSONExpressions document. - -### Evaluation -To evaluate a JSONExpressions document, the following steps should be taken: - -1. Replace all occurrences of variables in the JSONLogic document with the corresponding values from the input data. -2. Evaluate all functions and operators in the JSONLogic document, following the rules defined in the specification. -3. Return the resulting value. \ No newline at end of file diff --git a/docs/docusaurus.config.ts b/docs/docusaurus.config.ts deleted file mode 100644 index 8239787..0000000 --- a/docs/docusaurus.config.ts +++ /dev/null @@ -1,97 +0,0 @@ -import path from 'node:path' -import type * as Preset from '@docusaurus/preset-classic' -import type { Config } from '@docusaurus/types' -import { themes } from 'prism-react-renderer' -import packageJSON from '../package.json' - -const { description, homepage, name, repository } = packageJSON - -const [organizationName, projectName] = name.replace('@', '').split('/') -const url = new URL(homepage) - -const config: Config = { - title: name, - tagline: description, - url: url.origin, - baseUrl: url.pathname, - trailingSlash: false, - onBrokenLinks: 'warn', - onBrokenMarkdownLinks: 'warn', - favicon: 'img/favicon.ico', - - organizationName, - projectName, - - i18n: { - defaultLocale: 'en', - locales: ['en'], - }, - plugins: [ - [ - 'docusaurus-plugin-typedoc-api', - { - projectRoot: path.join(__dirname, '..'), - packages: ['.'], - }, - ], - ], - - presets: [ - [ - '@docusaurus/preset-classic', - { - docs: { - routeBasePath: '/', - sidebarPath: './sidebars.ts', - }, - blog: false, - theme: { - customCss: './src/css/custom.css', - }, - } satisfies Preset.Options, - ], - ], - - themeConfig: { - navbar: { - title: name, - logo: { - alt: 'Logo', - src: 'img/logo.png', - srcDark: 'img/logow.png', - }, - items: [ - { - type: 'doc', - docId: 'intro', - position: 'left', - label: 'Docs', - }, - { - to: 'api', - label: 'API', - position: 'left', - }, - { - href: repository.url, - label: 'GitHub', - position: 'right', - }, - ], - } satisfies Preset.ThemeConfig, - footer: { - style: 'dark', - links: [], - copyright: `Copyright © ${new Date().getFullYear()} SkyLeague Technologies B.V.`, - }, - prism: { - theme: themes.github, - darkTheme: themes.dracula, - }, - }, - future: { - experimental_faster: true, - }, -} - -export default config diff --git a/docs/package.json b/docs/package.json deleted file mode 100644 index b30bf84..0000000 --- a/docs/package.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "@skyleague/documentation", - "private": true, - "scripts": { - "docusaurus": "docusaurus", - "start": "docusaurus start", - "prebuild": "npm install", - "build": "docusaurus build --out-dir=../.docs", - "swizzle": "docusaurus swizzle", - "deploy": "docusaurus deploy", - "clear": "docusaurus clear", - "serve": "docusaurus serve --dir=../.docs", - "write-translations": "docusaurus write-translations", - "write-heading-ids": "docusaurus write-heading-ids" - }, - "dependencies": { - "@docusaurus/core": "^3.6.3", - "@docusaurus/preset-classic": "^3.6.3", - "@docusaurus/faster": "^3.6.3", - "@mdx-js/react": "^3.1.0", - "prism-react-renderer": "^2.4.1", - "react": "^18.3.1", - "react-dom": "^18.3.1" - }, - "devDependencies": { - "@docusaurus/module-type-aliases": "^3.6.3", - "docusaurus-plugin-typedoc-api": "^4.4.0" - }, - "browserslist": { - "production": [">0.5%", "not dead", "not op_mini all"], - "development": ["last 1 chrome version", "last 1 firefox version", "last 1 safari version"] - }, - "engines": { - "node": ">=22" - } -} diff --git a/docs/sidebars.ts b/docs/sidebars.ts deleted file mode 100644 index 20abb29..0000000 --- a/docs/sidebars.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { SidebarsConfig } from '@docusaurus/plugin-content-docs' - -const sidebars: SidebarsConfig = { - autogenerated: [{ type: 'autogenerated', dirName: '.' }], -} - -export default sidebars diff --git a/docs/src/css/custom.css b/docs/src/css/custom.css deleted file mode 100644 index 9a4d9ac..0000000 --- a/docs/src/css/custom.css +++ /dev/null @@ -1,22 +0,0 @@ -:root { - --ifm-color-primary: hsl(217, 63%, 46%); - --ifm-color-primary-dark: hsl(217, 63%, 44%); - --ifm-color-primary-darker: hsl(217, 63%, 42%); - --ifm-color-primary-darkest: hsl(217, 63%, 40%) b; - --ifm-color-primary-light: hsl(217, 63%, 48%); - --ifm-color-primary-lighter: hsl(217, 63%, 50%); - --ifm-color-primary-lightest: hsl(217, 63%, 52%); - --ifm-code-font-size: 95%; - --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); -} - -[data-theme="dark"] { - --ifm-color-primary: #eaa22f; - --ifm-color-primary-dark: hsl(217, 63%, 46%); - --ifm-color-primary-darker: hsl(217, 63%, 44%); - --ifm-color-primary-darkest: hsl(217, 63%, 42%); - --ifm-color-primary-light: hsl(217, 54%, 73%); - --ifm-color-primary-lighter: hsl(217, 54%, 75%); - --ifm-color-primary-lightest: hsl(217, 54%, 77%); - --docusaurus-highlighted-code-line-bg: #232933; -} diff --git a/docs/static/img/favicon.ico b/docs/static/img/favicon.ico deleted file mode 100644 index ae636a0b382c900bbeab27825b656a4dd1ff2002..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7200 zcmdT|&u>jZ6uxgG5~)Pf5UE6kl!zcv4O;I#?L#R_{jB!YuLzBVM5?53iVV< z-6*sfVBQd0SgHsK(3aEvQf!+olZMddlPF&WBsI!|Z#V4TtwOsEu=itDR@N*h2gTSL zwJJmQp?(}-cw7RIPK!W(%4xptoU8MMj7Z%)7EraCc+eQ3yaG%EHR9Ro*FS~%E)H<1o3b3A%<)I&MtOl{5R)0xkoR(LO&mjv{Q-jg+l$=@43cWY0 z6_RtsGVjDZ9}WYO4%O9sJHO0)9Axl-9&4C*lqUl`5jpRS({X@yQeLx?=gDP2lJ}<> z&2kYEUn$qn@GuV=7yxvaXM)|O{LMd2YsT-ickLJqx~?5&g9tBAvOvc3U_v(tWrSNJ zgTNDG!@$z?@8b7|*X+=o0)64q7d_W)klu3Md#&V6rwZ`y6HDHiSRcx`z4k-)IE(C` zb6Qqe>#x+@%4=@vEv=gXxIs+;*MS*;7E9t&Nspa2E!x~mSJRA=Ma0BX6QR@n9M*Gc zRz=EUYstT=s-TBKy~5MaSI$85Q=JK-khIJXFUEWch{H}-Z(N^M4v6~o!Wpx|m5`xO ze-eACyT%Y4IsmA*$`X&Sg8+|oUV!5Fv&whSmRC?olTKT9C1fMi@5P?*V#HHn+rUQT z{1t545KDA7i+4q>0TGrXnc6!1noBXopn7YUF%b%AH?&Una zp8@Q124EkOCPP0%eMHBJ z-9YMiQ>NDX$xSjScc#7(^rRN3T&VqGJ=ghDsr_;u8GUu*Y>#Q3?y=N9(Ee7n?2G-3 tW>)g~`U{ufj|=tckl diff --git a/docs/static/img/logo.png b/docs/static/img/logo.png deleted file mode 100644 index 70b3ca966e4851987212eaa44c62fb8add25bbc1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26080 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 diff --git a/docs/static/img/logow.png b/docs/static/img/logow.png deleted file mode 100644 index 07253e25b882eb2aea6c123b041b74d3664dfce8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24517 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

9f4mYC@r7scjU9vjZ#+41VW4X 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 Odqr7QnQAH1kpBS$C=22" @@ -31,8 +32,8 @@ "@docusaurus/core": "^3.6.3", "@docusaurus/faster": "^3.6.3", "@docusaurus/preset-classic": "^3.6.3", - "@mdx-js/react": "^3.0.1", - "prism-react-renderer": "^2.3.1", + "@mdx-js/react": "^3.1.0", + "prism-react-renderer": "^2.4.1", "react": "^18.3.1", "react-dom": "^18.3.1" }, @@ -41,7 +42,7 @@ "docusaurus-plugin-typedoc-api": "^4.4.0" }, "engines": { - "node": ">=20" + "node": ">=22" } }, "node_modules/@algolia/autocomplete-core": { @@ -24726,6 +24727,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zod": { + "version": "3.24.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.3.tgz", + "integrity": "sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/package.json b/package.json index 7a0b134..78f3f2c 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "types": "./.dist/index.d.ts", "sideEffects": false, "node-standards": { - "extends": ["library", "docusaurus"] + "extends": ["library"] }, "engines": { "node": ">=22" @@ -19,7 +19,6 @@ "files": [".dist", "package.json"], "scripts": { "build": "tsc -p tsconfig.dist.json", - "build:docs": "npm run --prefix=docs build", "check:coverage": "vitest run --coverage=true", "check:project": "node-standards lint", "check:types": "tsc -p tsconfig.json", @@ -37,7 +36,8 @@ "@skyleague/node-standards": "^11.0.1", "@skyleague/therefore": "^7.15.1", "typescript": "^5.8.3", - "uuid": "^11.1.0" + "uuid": "^11.1.0", + "zod": "^3.24.3" }, "publishConfig": { "access": "public", @@ -49,6 +49,5 @@ ".": "./.dist/index.js", "./package.json": "./package.json", "./*.js": "./.dist/*.js" - }, - "workspaces": ["docs"] + } } diff --git a/src/engine/policy.ts b/src/engine/policy.ts index e052552..5c0c90f 100644 --- a/src/engine/policy.ts +++ b/src/engine/policy.ts @@ -121,8 +121,9 @@ export function $policy>( ) const outputNodes = allNodes.filter((f) => f._type !== 'fact' && f.name !== undefined) - const properties = Object.fromEntries(inputNodes.map((e) => [e.name, e.expr('definition')])) - const outputExpression = Object.fromEntries(outputNodes.map((f) => [f.name, f.expr('expression')])) + let cachedProperties: Record | undefined + let cachedOutputExpression: Record | undefined + return { evaluate: ((input: Record) => { const ctx = new EvaluationContext(input) @@ -137,13 +138,19 @@ export function $policy>( return { input: input, output: ctx.state } }) as Policy, OutputFromFacts>['evaluate'], expr: () => { - const input = properties + if (cachedProperties === undefined) { + cachedProperties = Object.fromEntries(inputNodes.map((e) => [e.name, e.expr('definition')])) + } + if (cachedOutputExpression === undefined) { + cachedOutputExpression = Object.fromEntries(outputNodes.map((f) => [f.name, f.expr('expression')])) + } + return { meta: { version, }, - input, - output: outputExpression, + input: cachedProperties, + output: cachedOutputExpression, } }, } diff --git a/src/expressions/input.spec.ts b/src/expressions/input.spec.ts index f81969f..d4d18ea 100644 --- a/src/expressions/input.spec.ts +++ b/src/expressions/input.spec.ts @@ -8,6 +8,7 @@ import { $policy } from '../engine/policy.js' import { forAll, tuple } from '@skyleague/axioms' import { arbitrary } from '@skyleague/therefore' import { describe, expect, it } from 'vitest' +import { z } from 'zod' describe('fact', () => { it('simple single one is used as input', () => { @@ -18,41 +19,60 @@ describe('fact', () => { }) expect(policy.expr()).toMatchInlineSnapshot(` - { - "input": { - "person": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": true, - "properties": { - "age": { - "type": "number", - }, - "birthDate": { - "type": "string", - }, - "firstName": { - "type": "string", - }, - "lastName": { - "type": "string", - }, + { + "input": { + "person": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": true, + "properties": { + "age": { + "type": "number", + }, + "birthDate": { + "type": "string", + }, + "firstName": { + "type": "string", + }, + "lastName": { + "type": "string", }, - "required": [ - "firstName", - "lastName", - "birthDate", - "age", - ], - "title": "Person", - "type": "object", }, + "required": [ + "firstName", + "lastName", + "birthDate", + "age", + ], + "title": "Person", + "type": "object", }, - "meta": { - "version": "1.0.0", - }, - "output": {}, - } - `) + }, + "meta": { + "version": "1.0.0", + }, + "output": {}, + } + `) + }) + + it('simple single one is used as input - zod', () => { + const zodPerson = z.object({ + firstName: z.string(), + lastName: z.string(), + birthDate: z.string(), + age: z.number(), + }) + zodPerson.safeParse + const person = $fact(zodPerson, 'person') + const policy = $policy({ person }) + forAll(arbitrary(Person), (p) => { + expect(policy.evaluate({ person: p }).input.person).toEqual(p) + }) + + expect(() => policy.expr()).toThrowErrorMatchingInlineSnapshot( + '[Error: Cannot infer definition for fact with zod schema]', + ) }) it('simple multiple is used as input', () => { diff --git a/src/expressions/input.ts b/src/expressions/input.ts index dd8e50f..6b7dfd3 100644 --- a/src/expressions/input.ts +++ b/src/expressions/input.ts @@ -1,10 +1,8 @@ -import type { DefinitionType, FactExpression, InferExpressionType, InputExpression, LiteralExpression } from '../engine/types.js' -import type { FromExpr } from '../json/jsonexpr.type.js' - +import { inspect } from 'node:util' import { JSONPath, type JSONPathValue } from '@skyleague/jsonpath' import type { Schema } from '@skyleague/therefore' - -import { inspect } from 'node:util' +import type { DefinitionType, FactExpression, InferExpressionType, InputExpression, LiteralExpression } from '../engine/types.js' +import type { FromExpr } from '../json/jsonexpr.type.js' // biome-ignore lint/suspicious/noExplicitAny: this is needed for greedy matching export interface Fact extends FactExpression { @@ -13,7 +11,60 @@ export interface Fact extends FactExpress _type: 'fact' } -export function $fact(schema: Pick, 'schema' | 'is'>, name: Name): Fact { +export function $fact( + schema: { + safeParse: (x: unknown) => + | { + success: true + data: T + error?: never + } + | { + success: false + error: unknown + data?: never + } + }, + name: Name, +): Fact +export function $fact(schema: Pick, 'schema' | 'is'>, name: Name): Fact +export function $fact( + schema: + | Pick, 'schema' | 'is'> + | { + safeParse: (x: unknown) => + | { + success: true + data: T + error?: never + } + | { + success: false + error: unknown + data?: never + } + }, + name: Name, +): Fact { + if ('safeParse' in schema) { + return { + _type: 'fact', + dependsOn: [], + name: name, + fn: ((_: unknown, ctx: { input: Record }) => { + return ctx.input[name] + }) as Fact['fn'], + expr: (definition: DefinitionType): InferExpressionType => { + if (definition === 'definition') { + throw new Error('Cannot infer definition for fact with zod schema') + } + return { fact: name.toString() } as unknown as InferExpressionType + }, + [inspect.custom]() { + return `$fact({schema: }, "${name}")` + }, + } + } return { _type: 'fact', dependsOn: [], diff --git a/test/policies.spec.ts b/test/policies.spec.ts index 633ff08..cd0577c 100644 --- a/test/policies.spec.ts +++ b/test/policies.spec.ts @@ -27,7 +27,7 @@ describe('arbitrary', () => { tests: 100000, }, ) - }) + }, 100000) }) describe('any string starts with', () => {