From dea836c1625c30dc8f9842dd7cc615e085b36d6a Mon Sep 17 00:00:00 2001 From: JuanGalilea Date: Wed, 2 Sep 2026 11:47:38 +0200 Subject: [PATCH 1/7] switch settings to the right formatter --- .vscode/settings.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 408daa5ef..ee706c419 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,21 +1,21 @@ { "[javascript]": { - "editor.defaultFormatter": "biomejs.biome" + "editor.defaultFormatter": "oxc.oxc-vscode" }, "[javascriptreact]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" + "editor.defaultFormatter": "oxc.oxc-vscode" }, "[json]": { - "editor.defaultFormatter": "biomejs.biome" + "editor.defaultFormatter": "oxc.oxc-vscode" }, "[typescript]": { - "editor.defaultFormatter": "biomejs.biome" + "editor.defaultFormatter": "oxc.oxc-vscode" }, "[typescriptreact]": { - "editor.defaultFormatter": "biomejs.biome" + "editor.defaultFormatter": "oxc.oxc-vscode" }, "[jsonc]": { - "editor.defaultFormatter": "biomejs.biome" + "editor.defaultFormatter": "oxc.oxc-vscode" }, "[yaml]": { "editor.defaultFormatter": "esbenp.prettier-vscode" From a15ca529921555b5e0d091b944213cbba26d549f Mon Sep 17 00:00:00 2001 From: JuanGalilea Date: Wed, 2 Sep 2026 11:48:03 +0200 Subject: [PATCH 2/7] copy all code --- src/lib/schema-to-ts/canonical.ts | 83 +++++++ src/lib/schema-to-ts/check.ts | 93 ++++++++ src/lib/schema-to-ts/compile.ts | 50 +++++ src/lib/schema-to-ts/diagnostics.ts | 67 ++++++ src/lib/schema-to-ts/emit.ts | 108 +++++++++ src/lib/schema-to-ts/hash.ts | 16 ++ src/lib/schema-to-ts/index.ts | 21 ++ src/lib/schema-to-ts/ir.ts | 81 +++++++ src/lib/schema-to-ts/json-schema-to-ir.ts | 233 ++++++++++++++++++++ src/lib/schema-to-ts/preprocess/dataset.ts | 15 ++ src/lib/schema-to-ts/preprocess/input.ts | 13 ++ src/lib/schema-to-ts/preprocess/nullable.ts | 61 +++++ 12 files changed, 841 insertions(+) create mode 100644 src/lib/schema-to-ts/canonical.ts create mode 100644 src/lib/schema-to-ts/check.ts create mode 100644 src/lib/schema-to-ts/compile.ts create mode 100644 src/lib/schema-to-ts/diagnostics.ts create mode 100644 src/lib/schema-to-ts/emit.ts create mode 100644 src/lib/schema-to-ts/hash.ts create mode 100644 src/lib/schema-to-ts/index.ts create mode 100644 src/lib/schema-to-ts/ir.ts create mode 100644 src/lib/schema-to-ts/json-schema-to-ir.ts create mode 100644 src/lib/schema-to-ts/preprocess/dataset.ts create mode 100644 src/lib/schema-to-ts/preprocess/input.ts create mode 100644 src/lib/schema-to-ts/preprocess/nullable.ts diff --git a/src/lib/schema-to-ts/canonical.ts b/src/lib/schema-to-ts/canonical.ts new file mode 100644 index 000000000..cb33ef77a --- /dev/null +++ b/src/lib/schema-to-ts/canonical.ts @@ -0,0 +1,83 @@ +import { type EmitOptions } from './emit.js'; +import { type IRNode, type IRRoot } from './ir.js'; + +/** + * Canonical serialization that feeds the hash. Purpose-written rather than JSON.stringify, + * which would depend on key insertion order and on `undefined` vs absent — one refactor and + * every hash in the wild would move. + * + * All collections are sorted here, while the emitter reproduces authored order. Reordering + * properties or enum members therefore never reports drift: it cannot change the type. + * + * `irVersion` is deliberately absent — the header carries it as a prefix, outside the digest, + * so a version mismatch is reportable instead of an opaque hash difference. + */ + +/** `null` and `unknown` last so nothing about the ranking looks like emission order. */ +const KIND_RANK: Record = { + literal: 0, + string: 1, + number: 2, + boolean: 3, + array: 4, + object: 5, + null: 6, + unknown: 7, + // Unreachable: union() flattens, so a union is never a member of a union. + union: 8, +}; + +/** UTF-16 code unit order. Never localeCompare — it is locale-dependent. */ +function byCodeUnit(a: string, b: string): number { + return a < b ? -1 : a > b ? 1 : 0; +} + +export function canonicalNode(node: IRNode): string { + switch (node.kind) { + case 'string': + return 's'; + case 'number': + return 'n'; + case 'boolean': + return 'b'; + case 'null': + return 'z'; + case 'unknown': + return '?'; + case 'literal': + return `l:${typeof node.value}:${JSON.stringify(node.value)}`; + case 'array': + return `a[${canonicalNode(node.items)}]`; + case 'union': { + const members = node.members + .map((m) => ({ rank: KIND_RANK[m.kind], text: canonicalNode(m) })) + .sort((x, y) => x.rank - y.rank || byCodeUnit(x.text, y.text)) + .map((m) => m.text); + return `u[${members.join(',')}]`; + } + case 'object': { + const props = [...node.props] + .sort((x, y) => byCodeUnit(x.name, y.name)) + .map( + (p) => + `${JSON.stringify(p.name)}:${p.required ? 'R' : '-'}${p.hasDefault ? 'D' : '-'}:${canonicalNode(p.node)}`, + ); + return `o[${props.join(',')}|${node.valueType ? canonicalNode(node.valueType) : ''}|${node.open ? '+' : '-'}]`; + } + } +} + +/** Everything the emitter reads besides the IR. Type names and variants change emitted bytes. */ +export function canonicalOptions(opts: EmitOptions): string { + const types = [...opts.types] + .sort((x, y) => byCodeUnit(x.name, y.name)) + .map((t) => `${JSON.stringify(t.name)}:${t.variant}`); + return `t[${types.join(',')}]`; +} + +export function canonical(ir: IRRoot, opts: EmitOptions): string { + // `unknownRoot` only reaches the output when the root really is unknown. Including it + // otherwise would report drift for a regeneration that is a no-op. + const rootOption = ir.root.kind === 'unknown' ? `r:${opts.unknownRoot ?? 'unknown'}` : ''; + return `${canonicalNode(ir.root)}|${canonicalOptions(opts)}${rootOption}`; +} diff --git a/src/lib/schema-to-ts/check.ts b/src/lib/schema-to-ts/check.ts new file mode 100644 index 000000000..b1dc70d2a --- /dev/null +++ b/src/lib/schema-to-ts/check.ts @@ -0,0 +1,93 @@ +import { HEADER_PREFIX, HEADER_SUFFIX, type EmitOptions } from './emit.js'; +import { irHash } from './hash.js'; +import { IR_VERSION, type IRRoot } from './ir.js'; + +/** + * Drift detection without parsing TypeScript. The generated file carries a planted + * fingerprint, so the check is immune to whatever a formatter did to the body. + * + * The hash pattern is deliberately loose (`[0-9a-f]+`, not a fixed length) and so is the + * version: a file written by a future version must still parse far enough for us to *report* + * the version mismatch rather than fail to recognise the header at all. + * + * Scanning the whole file rather than the leading comment block means a CLI-prepended banner + * (an eslint-disable, a license header) costs nothing. + */ +const HEADER_RE = new RegExp( + `^${escapeRegExp(HEADER_PREFIX)} v(\\d+)-([0-9a-f]+) — ${escapeRegExp(HEADER_SUFFIX)}\\r?$`, + 'gm', +); + +function escapeRegExp(text: string): string { + return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +} + +export interface Header { + version: number; + hash: string; + /** Byte offset of the header line, so a CLI can point at it. */ + index: number; +} + +export type CheckReason = + | 'match' + | 'hash-mismatch' // regenerate + | 'missing-header' // not ours — refuse to overwrite without --force + | 'duplicate-header' // two headers, most likely a botched merge + | 'version-mismatch'; // regenerate, and expect a real diff + +export interface Comparison { + stale: boolean; + reason: CheckReason; + /** Hash computed from the schema now. */ + expected: string; + /** Hash read out of the file, or null when there was no single header to read. */ + found: string | null; + expectedVersion: number; + foundVersion: number | null; +} + +/** Every header in the file. More than one means the file is corrupt. */ +export function readHeaders(source: string): Header[] { + // Fresh lastIndex per call: the regex is module-level and `g` makes matchAll stateful. + HEADER_RE.lastIndex = 0; + return [...source.matchAll(HEADER_RE)].map((match) => ({ + version: Number(match[1]), + hash: match[2]!, + index: match.index, + })); +} + +/** The sole header, or null when there is none — or more than one. */ +export function readHeader(source: string): Header | null { + const headers = readHeaders(source); + return headers.length === 1 ? headers[0]! : null; +} + +/** IR-level comparison. The public `check` in compile.ts lifts a schema and calls this. */ +export function compareToHeader(source: string, ir: IRRoot, opts: EmitOptions): Comparison { + const expected = irHash(ir, opts); + const headers = readHeaders(source); + + const base = { expected, expectedVersion: IR_VERSION }; + if (headers.length === 0) { + return { ...base, stale: true, reason: 'missing-header', found: null, foundVersion: null }; + } + if (headers.length > 1) { + // Which one is authoritative is unknowable, so report neither. + return { ...base, stale: true, reason: 'duplicate-header', found: null, foundVersion: null }; + } + + const header = headers[0]!; + const found = { found: header.hash, foundVersion: header.version }; + + // Version first: hashes from different versions are not comparable, because the canonical + // form itself may have changed meaning. + if (header.version !== IR_VERSION) { + return { ...base, ...found, stale: true, reason: 'version-mismatch' }; + } + if (header.hash !== expected) { + return { ...base, ...found, stale: true, reason: 'hash-mismatch' }; + } + return { ...base, ...found, stale: false, reason: 'match' }; +} diff --git a/src/lib/schema-to-ts/compile.ts b/src/lib/schema-to-ts/compile.ts new file mode 100644 index 000000000..9edf5e129 --- /dev/null +++ b/src/lib/schema-to-ts/compile.ts @@ -0,0 +1,50 @@ +import { compareToHeader, type CheckReason } from './check.js'; +import type { Diagnostic, Notice } from './diagnostics.js'; +import { emit, type EmitOptions } from './emit.js'; +import { jsonSchemaToIR } from './json-schema-to-ir.js'; + +/** + * The public facade. Schema in, TypeScript out — the IR never crosses the boundary, so it + * stays free to change behind an `IR_VERSION` bump. + * + * The schema kind is deliberately *not* a parameter: preprocessing is a separate call, so + * these take whatever `normalizeInputSchema` / `normalizeDatasetSchema` produced. + */ +export type CompileOptions = EmitOptions; + +export interface CompileResult { + /** Complete file text, header included. Never written anywhere — this library does no IO. */ + source: string; + /** Fidelity was lost: something is typed `unknown` that could have been precise. */ + diagnostics: Diagnostic[]; + /** Schema lint with no type impact. */ + notices: Notice[]; +} + +export function compile(schema: unknown, opts: CompileOptions): CompileResult { + const { ir, diagnostics, notices } = jsonSchemaToIR(schema); + return { source: emit(ir, opts), diagnostics, notices }; +} + +/** + * Staleness and schema health are orthogonal, and both are available here for free — the + * schema has to be lifted either way. A CLI typically fails on + * `stale || diagnostics.some(d => d.severity === 'error')`. + */ +export interface CheckResult { + stale: boolean; + reason: CheckReason; + /** Hash computed from the schema now. */ + expected: string; + /** Hash read from the file, or null when there was no single header to read. */ + found: string | null; + expectedVersion: number; + foundVersion: number | null; + diagnostics: Diagnostic[]; + notices: Notice[]; +} + +export function check(source: string, schema: unknown, opts: CompileOptions): CheckResult { + const { ir, diagnostics, notices } = jsonSchemaToIR(schema); + return { ...compareToHeader(source, ir, opts), diagnostics, notices }; +} diff --git a/src/lib/schema-to-ts/diagnostics.ts b/src/lib/schema-to-ts/diagnostics.ts new file mode 100644 index 000000000..d30ead886 --- /dev/null +++ b/src/lib/schema-to-ts/diagnostics.ts @@ -0,0 +1,67 @@ +/** + * A diagnostic means *fidelity was lost* — a node that could have been typed precisely was + * degraded. Nothing else is a diagnostic. Schema lint with no type impact is a notice. + * + * Neither feeds the hash: every diagnostic already implies an IR difference (a node became + * `unknown`), so hashing them would only discriminate cases where the emitted file is + * byte-identical. + */ + +/** Malformed input we cannot read. The CLI refuses to write when any of these are present. */ +export type ErrorCode = + | 'malformed-schema' // not a JSON object + | 'malformed-type' // `type` is neither a string nor an array of strings + | 'unknown-type-name' // `type` names something outside the 7 JSON Schema types + | 'empty-type-array' // `type: []` + | 'malformed-properties' // `properties` is not an object + | 'malformed-required' // `required` is not an array of strings + | 'malformed-items' // `items` is neither an object nor an array + | 'malformed-enum' // `enum` is not a non-empty array + | 'malformed-additional-properties'; // `additionalProperties` is neither boolean nor object + +/** Well-formed JSON Schema we do not support yet. */ +export type WarningCode = + | 'unsupported-keyword' // oneOf / anyOf / allOf / not / if-then-else / $ref / patternProperties + | 'unsupported-tuple-items' // positional `items: [...]` + | 'unsupported-enum-values'; // `enum` holding objects or arrays + +/** No type impact whatsoever. */ +export type NoticeCode = + | 'required-unknown-property' // `required` names a property that does not exist + | 'empty-schema'; // `{}` — faithfully `unknown`, not a degradation + +export interface Diagnostic { + /** JSON Pointer into the schema */ + path: string; + severity: 'error' | 'warning'; + code: ErrorCode | WarningCode; + message: string; +} + +export interface Notice { + path: string; + code: NoticeCode; + message: string; +} + +/** JSON Pointer escaping (RFC 6901). */ +export function pointer(base: string, ...segments: string[]): string { + return base + segments.map((s) => `/${s.replace(/~/g, '~0').replace(/\//g, '~1')}`).join(''); +} + +export class Report { + readonly diagnostics: Diagnostic[] = []; + readonly notices: Notice[] = []; + + error(path: string, code: ErrorCode, message: string): void { + this.diagnostics.push({ path, severity: 'error', code, message }); + } + + warn(path: string, code: WarningCode, message: string): void { + this.diagnostics.push({ path, severity: 'warning', code, message }); + } + + notice(path: string, code: NoticeCode, message: string): void { + this.notices.push({ path, code, message }); + } +} diff --git a/src/lib/schema-to-ts/emit.ts b/src/lib/schema-to-ts/emit.ts new file mode 100644 index 000000000..fbbde78e0 --- /dev/null +++ b/src/lib/schema-to-ts/emit.ts @@ -0,0 +1,108 @@ +import { irHash } from './hash.js'; +import { IR_VERSION, type IRNode, type IRRoot } from './ir.js'; + +/** + * IR → TypeScript. Apify-blind, no IO: takes an IR, returns the complete file text. + * + * `type` always, never `interface` — one code path for object, union, Record and unknown + * roots, and no declaration merging that would let a user silently augment generated types. + */ + +/** + * Stated relative to the data, not to "the code": + * - `supplied` is the writer's obligation, so be permissive (only `required` is mandatory, + * open objects accept extras). + * - `received` is the reader's view, so be precise (defaults are materialized by the + * platform, objects are closed so property typos are caught). + */ +export type Variant = 'supplied' | 'received'; + +export interface EmitOptions { + types: { name: string; variant: Variant }[]; + /** For a root we could not read, `unknown` is the truth; `record` is a softer guess. */ + unknownRoot?: 'unknown' | 'record'; +} + +const INDENT = ' '; + +/** Exactly TypeScript's ASCII identifier rule. Reserved words are legal property names. */ +const BARE_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/; + +export const HEADER_PREFIX = '// @generated schema-ts'; +export const HEADER_SUFFIX = 'do not edit'; + +/** Version covers both IR semantics and emitted output — bump it when either changes. */ +export function header(hash: string): string { + return `${HEADER_PREFIX} v${IR_VERSION}-${hash} — ${HEADER_SUFFIX}`; +} + +export function emit(ir: IRRoot, opts: EmitOptions): string { + // Authored order for emission; the canonical form sorts for the hash. + const declarations = opts.types.map(({ name, variant }) => { + const body = ir.root.kind === 'unknown' ? unknownRoot(opts) : renderNode(ir.root, variant, ''); + return `export type ${name} = ${body};`; + }); + + return [header(irHash(ir, opts)), '', ...declarations.flatMap((d) => [d, ''])].join('\n'); +} + +function unknownRoot(opts: EmitOptions): string { + return opts.unknownRoot === 'record' ? 'Record' : 'unknown'; +} + +function renderNode(node: IRNode, variant: Variant, indent: string): string { + switch (node.kind) { + case 'string': + return 'string'; + case 'number': + return 'number'; + case 'boolean': + return 'boolean'; + case 'null': + return 'null'; + case 'unknown': + return 'unknown'; + case 'literal': + return JSON.stringify(node.value); + case 'union': + return node.members.map((m) => renderNode(m, variant, indent)).join(' | '); + // `Array` always, so no element ever needs parenthesizing. + case 'array': + return `Array<${renderNode(node.items, variant, indent)}>`; + case 'object': + return renderObject(node, variant, indent); + } +} + +function renderObject(node: Extract, variant: Variant, indent: string): string { + const values = node.valueType ? renderNode(node.valueType, variant, indent) : null; + + if (node.props.length === 0) { + // Never a bare `{}`: in TS that means "anything non-nullish", so `x = 5` would pass. + if (values !== null) return `Record`; + return node.open ? 'Record' : 'Record'; + } + + const inner = indent + INDENT; + const members = node.props.map((prop) => { + const optional = variant === 'supplied' ? !prop.required : !prop.required && !prop.hasDefault; + const type = renderNode(prop.node, variant, inner); + // `?` and `| undefined` together, so the type is right under exactOptionalPropertyTypes. + // `unknown` already admits undefined, so widening it would be noise. + const suffix = optional && type !== 'unknown' ? ' | undefined' : ''; + return `${inner}${key(prop.name)}${optional ? '?' : ''}: ${type}${suffix};`; + }); + const literal = `{\n${members.join('\n')}\n${indent}}`; + + // Parenthesized unconditionally: `&` binds tighter than `|` so it is already correct inside + // a union, but `{...} & Record<...> | null` is a horrible thing to read. Keeping it + // unconditional means the object never has to know what context it sits in. + if (values !== null) return `(${literal} & Record)`; + // Extras are the writer's privilege; the reader gets a closed type so typos are caught. + if (variant === 'supplied' && node.open) return `(${literal} & Record)`; + return literal; +} + +function key(name: string): string { + return BARE_IDENTIFIER.test(name) ? name : JSON.stringify(name); +} diff --git a/src/lib/schema-to-ts/hash.ts b/src/lib/schema-to-ts/hash.ts new file mode 100644 index 000000000..4c21891ac --- /dev/null +++ b/src/lib/schema-to-ts/hash.ts @@ -0,0 +1,16 @@ +import { createHash } from 'node:crypto'; + +import { canonical } from './canonical.js'; +import { type EmitOptions } from './emit.js'; +import { type IRRoot } from './ir.js'; + +/** + * `node:crypto` is a builtin, not a dependency, so the zero-runtime-dependency rule holds. + * Truncated to 16 hex chars: the threat model is accidental collision on a per-file identity + * check, and the failure mode of a collision is a missed drift warning, not corruption. + */ +export const HASH_LENGTH = 16; + +export function irHash(ir: IRRoot, opts: EmitOptions): string { + return createHash('sha256').update(canonical(ir, opts)).digest('hex').slice(0, HASH_LENGTH); +} diff --git a/src/lib/schema-to-ts/index.ts b/src/lib/schema-to-ts/index.ts new file mode 100644 index 000000000..15e7e3628 --- /dev/null +++ b/src/lib/schema-to-ts/index.ts @@ -0,0 +1,21 @@ +/** + * Public surface. Deliberately narrow: everything exported here becomes a compatibility + * surface, and the parts left out — the IR, the emitter, the canonical serializer, the hash — + * are precisely the parts we want free to change behind an `IR_VERSION` bump. + * + * Adding an export later is non-breaking; removing one is not. + */ + +// Apify-aware rewrites. Separate calls, because the schema kind is not the core's business. +export { normalizeInputSchema } from './preprocess/input.js'; +export { normalizeDatasetSchema } from './preprocess/dataset.js'; + +// Schema in, TypeScript out. The IR never crosses this boundary. +export { compile, check } from './compile.js'; +export type { CheckResult, CompileOptions, CompileResult } from './compile.js'; +export type { Variant } from './emit.js'; +export type { CheckReason, Header } from './check.js'; +export type { Diagnostic, Notice } from './diagnostics.js'; + +// Answers "is this file ours?" without needing a schema. +export { readHeader } from './check.js'; diff --git a/src/lib/schema-to-ts/ir.ts b/src/lib/schema-to-ts/ir.ts new file mode 100644 index 000000000..c3ab2c36d --- /dev/null +++ b/src/lib/schema-to-ts/ir.ts @@ -0,0 +1,81 @@ +/** + * The IR is exactly the emitter's input: if something changes the emitted text it belongs + * here, and if it cannot, it does not. That is what makes hash(IR) a sound drift signal. + * + * Deliberately Apify-blind and structural — `editor`, `prefill`, `title`, `pattern` and + * friends never reach this file. + */ + +export type IRNode = + | { kind: 'string' } + | { kind: 'number' } // `integer` collapses to `number` + | { kind: 'boolean' } + | { kind: 'null' } + | { kind: 'unknown' } + | { kind: 'literal'; value: string | number | boolean } + | { kind: 'union'; members: IRNode[] } + | { kind: 'array'; items: IRNode } + | { kind: 'object'; props: IRProp[]; valueType?: IRNode; open: boolean }; + +/** `props` is an array so authored order is structural, not a function of JS key order. */ +export interface IRProp { + name: string; + node: IRNode; + /** listed in `required` */ + required: boolean; + /** has a `default`, which the platform materializes into the received record */ + hasDefault: boolean; +} + +export const IR_VERSION = 1; + +export interface IRRoot { + irVersion: typeof IR_VERSION; + root: IRNode; +} + +export const UNKNOWN: IRNode = { kind: 'unknown' }; + +/** + * Structural key used only for de-duplicating union members. The canonical serializer that + * feeds the hash is a separate, sorted representation — do not conflate them. + */ +export function nodeKey(node: IRNode): string { + switch (node.kind) { + case 'literal': + return `l:${typeof node.value}:${String(node.value)}`; + case 'union': + return `u[${node.members.map(nodeKey).join(',')}]`; + case 'array': + return `a[${nodeKey(node.items)}]`; + case 'object': + return `o[${node.props + .map((p) => `${p.name}${p.required ? 'R' : '-'}${p.hasDefault ? 'D' : '-'}:${nodeKey(p.node)}`) + .join(',')}|${node.valueType ? nodeKey(node.valueType) : ''}|${node.open ? '+' : '-'}]`; + default: + return node.kind; + } +} + +/** Flattens, de-dupes, and collapses so there is exactly one IR per type. */ +export function union(members: IRNode[]): IRNode { + const flat: IRNode[] = []; + const seen = new Set(); + const push = (node: IRNode): void => { + if (node.kind === 'union') { + node.members.forEach(push); + return; + } + const key = nodeKey(node); + if (seen.has(key)) return; + seen.add(key); + flat.push(node); + }; + members.forEach(push); + + if (flat.length === 0) return UNKNOWN; + if (flat.length === 1) return flat[0]!; + // `unknown` absorbs everything it is unioned with. + if (flat.some((n) => n.kind === 'unknown')) return UNKNOWN; + return { kind: 'union', members: flat }; +} diff --git a/src/lib/schema-to-ts/json-schema-to-ir.ts b/src/lib/schema-to-ts/json-schema-to-ir.ts new file mode 100644 index 000000000..3963fa1f5 --- /dev/null +++ b/src/lib/schema-to-ts/json-schema-to-ir.ts @@ -0,0 +1,233 @@ +import { Report, pointer, type Diagnostic, type Notice } from './diagnostics.js'; +import { IR_VERSION, UNKNOWN, union, type IRNode, type IRProp, type IRRoot } from './ir.js'; + +export interface Lifted { + ir: IRRoot; + diagnostics: Diagnostic[]; + notices: Notice[]; +} + +const JSON_SCHEMA_TYPES = ['string', 'number', 'integer', 'boolean', 'object', 'array', 'null'] as const; +type JsonSchemaType = (typeof JSON_SCHEMA_TYPES)[number]; + +/** + * Keywords that carry type meaning we cannot represent. Anything *not* listed here and not + * read below is ignored in silence — the core tolerates extraneous fields, so `editor`, + * `prefill`, `title`, `pattern`, `minimum` and the rest produce nothing at all. + * + * `$defs` is deliberately absent: without a `$ref` pointing at it, it is dead weight. + */ +const UNSUPPORTED_KEYWORDS = [ + 'oneOf', + 'anyOf', + 'allOf', + 'not', + 'if', + 'then', + 'else', + '$ref', + 'patternProperties', +] as const; + +type Obj = Record; + +function isObj(value: unknown): value is Obj { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} + +export function jsonSchemaToIR(schema: unknown): Lifted { + const report = new Report(); + const root = toNode(schema, '', report); + return { ir: { irVersion: IR_VERSION, root }, diagnostics: report.diagnostics, notices: report.notices }; +} + +function toNode(schema: unknown, path: string, report: Report): IRNode { + if (!isObj(schema)) { + report.error(path, 'malformed-schema', `expected a JSON object, got ${describe(schema)}`); + return UNKNOWN; + } + + const unsupported = UNSUPPORTED_KEYWORDS.filter((k) => k in schema); + if (unsupported.length > 0) { + report.warn(path, 'unsupported-keyword', `${unsupported.join(', ')} is not supported yet`); + return UNKNOWN; + } + + // `enum` fully determines the type, so it wins over `type` when both are present. + if ('enum' in schema) return fromEnum(schema.enum, path, report); + + const types = readTypes(schema, path, report); + if (types === null) return UNKNOWN; + + return union(types.map((t) => fromType(t, schema, path, report))); +} + +/** Returns null when `type` is unusable; infers from siblings when `type` is absent. */ +function readTypes(schema: Obj, path: string, report: Report): JsonSchemaType[] | null { + if (!('type' in schema)) { + if ('properties' in schema || 'additionalProperties' in schema) return ['object']; + if ('items' in schema) return ['array']; + report.notice(path, 'empty-schema', 'no type information, treated as unknown'); + return null; + } + + const raw = schema.type; + const names = typeof raw === 'string' ? [raw] : raw; + + if (!Array.isArray(names) || !names.every((n) => typeof n === 'string')) { + report.error(path, 'malformed-type', `\`type\` must be a string or an array of strings, got ${describe(raw)}`); + return null; + } + if (names.length === 0) { + report.error(path, 'empty-type-array', '`type: []` matches nothing'); + return null; + } + + const unknownName = names.find((n) => !(JSON_SCHEMA_TYPES as readonly string[]).includes(n)); + if (unknownName !== undefined) { + report.error(path, 'unknown-type-name', `\`${unknownName}\` is not a JSON Schema type`); + return null; + } + + return [...new Set(names as JsonSchemaType[])]; +} + +function fromType(type: JsonSchemaType, schema: Obj, path: string, report: Report): IRNode { + switch (type) { + case 'string': + return { kind: 'string' }; + case 'number': + case 'integer': + return { kind: 'number' }; + case 'boolean': + return { kind: 'boolean' }; + case 'null': + return { kind: 'null' }; + case 'array': + return fromArray(schema, path, report); + case 'object': + return fromObject(schema, path, report); + } +} + +function fromArray(schema: Obj, path: string, report: Report): IRNode { + if (!('items' in schema)) return { kind: 'array', items: UNKNOWN }; + + const { items } = schema; + if (Array.isArray(items)) { + // A tuple is still an array, so `Array` is a sound degradation. + report.warn(pointer(path, 'items'), 'unsupported-tuple-items', 'positional `items` is not supported yet'); + return { kind: 'array', items: UNKNOWN }; + } + if (!isObj(items)) { + report.error(pointer(path, 'items'), 'malformed-items', `expected an object or array, got ${describe(items)}`); + return UNKNOWN; + } + + return { kind: 'array', items: toNode(items, pointer(path, 'items'), report) }; +} + +function fromObject(schema: Obj, path: string, report: Report): IRNode { + const required = readRequired(schema, path, report); + if (required === null) return UNKNOWN; + + const props = readProps(schema, path, required, report); + if (props === null) return UNKNOWN; + + const additional = readAdditional(schema, path, report); + if (additional === null) return UNKNOWN; + + for (const name of required) { + if (!props.some((p) => p.name === name)) { + report.notice( + pointer(path, 'required'), + 'required-unknown-property', + `\`${name}\` is required but not declared in \`properties\``, + ); + } + } + + return additional.valueType + ? { kind: 'object', props, valueType: additional.valueType, open: additional.open } + : { kind: 'object', props, open: additional.open }; +} + +function readRequired(schema: Obj, path: string, report: Report): string[] | null { + if (!('required' in schema)) return []; + const raw = schema.required; + if (!Array.isArray(raw) || !raw.every((n) => typeof n === 'string')) { + report.error(pointer(path, 'required'), 'malformed-required', `expected an array of strings, got ${describe(raw)}`); + return null; + } + return raw as string[]; +} + +function readProps(schema: Obj, path: string, required: string[], report: Report): IRProp[] | null { + if (!('properties' in schema)) return []; + const raw = schema.properties; + if (!isObj(raw)) { + report.error(pointer(path, 'properties'), 'malformed-properties', `expected an object, got ${describe(raw)}`); + return null; + } + + // Object.keys preserves authored order, which the emitter reproduces. + return Object.keys(raw).map((name) => ({ + name, + node: toNode(raw[name], pointer(path, 'properties', name), report), + required: required.includes(name), + hasDefault: isObj(raw[name]) && 'default' in (raw[name] as Obj), + })); +} + +/** `{}` / `true` / absent are all "open"; only a non-empty subschema types the extra keys. */ +function readAdditional(schema: Obj, path: string, report: Report): { open: boolean; valueType?: IRNode } | null { + if (!('additionalProperties' in schema)) return { open: true }; + + const raw = schema.additionalProperties; + if (typeof raw === 'boolean') return { open: raw }; + if (!isObj(raw)) { + report.error( + pointer(path, 'additionalProperties'), + 'malformed-additional-properties', + `expected a boolean or an object, got ${describe(raw)}`, + ); + return null; + } + if (Object.keys(raw).length === 0) return { open: true }; + + return { open: true, valueType: toNode(raw, pointer(path, 'additionalProperties'), report) }; +} + +function fromEnum(raw: unknown, path: string, report: Report): IRNode { + if (!Array.isArray(raw) || raw.length === 0) { + report.error(pointer(path, 'enum'), 'malformed-enum', `expected a non-empty array, got ${describe(raw)}`); + return UNKNOWN; + } + + const members: IRNode[] = []; + for (const value of raw) { + if (value === null) { + members.push({ kind: 'null' }); + continue; + } + if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { + members.push({ kind: 'literal', value }); + continue; + } + report.warn( + pointer(path, 'enum'), + 'unsupported-enum-values', + `\`enum\` holding ${describe(value)} cannot be expressed as a literal`, + ); + return UNKNOWN; + } + + return union(members); +} + +function describe(value: unknown): string { + if (value === null) return 'null'; + if (value === undefined) return 'undefined'; + if (Array.isArray(value)) return 'an array'; + return typeof value === 'object' ? 'an object' : `${typeof value} (${JSON.stringify(value)})`; +} diff --git a/src/lib/schema-to-ts/preprocess/dataset.ts b/src/lib/schema-to-ts/preprocess/dataset.ts new file mode 100644 index 000000000..7e531a87c --- /dev/null +++ b/src/lib/schema-to-ts/preprocess/dataset.ts @@ -0,0 +1,15 @@ +import { normalizeNullable } from './nullable.js'; + +/** + * Extracts `fields` and applies the `nullable` rewrite. Dataset schemas are nominally plain + * JSON Schema, but real ones use `nullable` as freely as input schemas do. + * + * `actorSpecification`, `views` and `$schema` are dropped: display order, column labels and + * table components carry nothing type-relevant. + */ +export function normalizeDatasetSchema(raw: unknown): unknown { + // Anything that is not an object goes through untouched, so the core's diagnostic names + // what was actually there instead of the `undefined` we would otherwise manufacture. + if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) return raw; + return normalizeNullable((raw as Record).fields); +} diff --git a/src/lib/schema-to-ts/preprocess/input.ts b/src/lib/schema-to-ts/preprocess/input.ts new file mode 100644 index 000000000..af01387db --- /dev/null +++ b/src/lib/schema-to-ts/preprocess/input.ts @@ -0,0 +1,13 @@ +import { normalizeNullable } from './nullable.js'; + +/** + * An input schema *is* the JSON Schema — there is no wrapper to unwrap — so the only rewrite + * is the `nullable` shorthand. + * + * Everything else the format adds (`editor`, `prefill`, `example`, `enumTitles`, `unit`, + * `isSecret`, `sectionCaption`, `schemaVersion`, …) is left in place: the core ignores keys it + * does not recognise, so there is nothing to gain by stripping them. + */ +export function normalizeInputSchema(raw: unknown): unknown { + return normalizeNullable(raw); +} diff --git a/src/lib/schema-to-ts/preprocess/nullable.ts b/src/lib/schema-to-ts/preprocess/nullable.ts new file mode 100644 index 000000000..3d7278cc9 --- /dev/null +++ b/src/lib/schema-to-ts/preprocess/nullable.ts @@ -0,0 +1,61 @@ +/** + * Rewrites the `nullable` shorthand into the JSON Schema it stands for: + * `{ type: 'string', nullable: true }` becomes `{ type: ['string', 'null'] }`. + * + * Shared rather than input-specific: real dataset schemas use `nullable` too, even though + * they are nominally plain JSON Schema. + * + * Pure — the caller's object is never mutated. Idempotent, since `nullable` is consumed and + * `'null'` is never appended twice. + */ + +type Obj = Record; + +function isObj(value: unknown): value is Obj { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} + +/** Only these keys hold subschemas. `default`, `example`, `prefill` and `enum` hold *data*, + * which must be copied verbatim — a `nullable` key inside an example is not a schema. */ +const SUBSCHEMA_KEYS = new Set(['properties', 'items', 'additionalProperties']); + +export function normalizeNullable(schema: unknown): unknown { + // Positional `items: [...]`: every member is a subschema. + if (Array.isArray(schema)) return schema.map(normalizeNullable); + if (!isObj(schema)) return schema; + + const result: Obj = {}; + for (const [key, value] of Object.entries(schema)) { + if (key === 'nullable') continue; // consumed below + if (!SUBSCHEMA_KEYS.has(key)) { + result[key] = value; + continue; + } + result[key] = + key === 'properties' && isObj(value) + ? Object.fromEntries(Object.entries(value).map(([name, sub]) => [name, normalizeNullable(sub)])) + : normalizeNullable(value); + } + + if (schema.nullable === true) { + const widened = withNull(schema); + // Assigning `undefined` would make `'type' in schema` true and trip malformed-type. + if (widened !== undefined) result.type = widened; + } + return result; +} + +/** + * Every branch is lossless. When `type` is absent the sibling keywords say what it would have + * been, and a schema with no keywords at all (`{}`) already admits null, so dropping the flag + * there changes nothing. + */ +function withNull(schema: Obj): unknown { + const { type } = schema; + if (typeof type === 'string') return type === 'null' ? type : [type, 'null']; + if (Array.isArray(type)) return type.includes('null') ? type : [...type, 'null']; + if (type !== undefined) return type; // malformed — leave it for the core to report + if ('properties' in schema || 'additionalProperties' in schema) return ['object', 'null']; + if ('items' in schema) return ['array', 'null']; + return undefined; +} From 21fced0d2a710f85db4898fe805281db04d43714 Mon Sep 17 00:00:00 2001 From: JuanGalilea Date: Wed, 2 Sep 2026 11:49:20 +0200 Subject: [PATCH 3/7] unit tests --- package.json | 1 + test/local/lib/schema-to-ts/canonical.test.ts | 119 ++++++ test/local/lib/schema-to-ts/check.test.ts | 175 ++++++++ .../lib/schema-to-ts/diagnostics.test.ts | 42 ++ test/local/lib/schema-to-ts/emit.test.ts | 267 ++++++++++++ test/local/lib/schema-to-ts/hash.test.ts | 387 ++++++++++++++++++ test/local/lib/schema-to-ts/ir.test.ts | 96 +++++ .../schema-to-ts/json-schema-to-ir.test.ts | 353 ++++++++++++++++ .../schema-to-ts/preprocess/dataset.test.ts | 69 ++++ .../lib/schema-to-ts/preprocess/input.test.ts | 30 ++ .../schema-to-ts/preprocess/nullable.test.ts | 114 ++++++ test/tsconfig.json | 4 +- 12 files changed, 1655 insertions(+), 2 deletions(-) create mode 100644 test/local/lib/schema-to-ts/canonical.test.ts create mode 100644 test/local/lib/schema-to-ts/check.test.ts create mode 100644 test/local/lib/schema-to-ts/diagnostics.test.ts create mode 100644 test/local/lib/schema-to-ts/emit.test.ts create mode 100644 test/local/lib/schema-to-ts/hash.test.ts create mode 100644 test/local/lib/schema-to-ts/ir.test.ts create mode 100644 test/local/lib/schema-to-ts/json-schema-to-ir.test.ts create mode 100644 test/local/lib/schema-to-ts/preprocess/dataset.test.ts create mode 100644 test/local/lib/schema-to-ts/preprocess/input.test.ts create mode 100644 test/local/lib/schema-to-ts/preprocess/nullable.test.ts diff --git a/package.json b/package.json index e3cfe29f2..b5b48810f 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "dev:actor": "tsx ./src/entrypoints/actor.ts", "test:all": "pnpm run test:local && pnpm run test:api", "test:local": "vitest run --testNamePattern \"^((?!\\[api]).)*$\" --exclude ./test/api --exclude ./test/e2e", + "test:lib:local": "vitest run --dir ./test/local/lib", "test:e2e": "vitest run --testNamePattern \"\\[e2e\\]\" --exclude ./test/api", "test:e2e:local": "vitest run --testNamePattern \"^(?=.*\\[e2e\\])(?!.*\\[api\\]).*$\" --exclude ./test/api", "test:api": "vitest run --testNamePattern \"^(?=.*\\[api\\])(?!.*\\[e2e\\]).*$\" --exclude ./test/e2e", diff --git a/test/local/lib/schema-to-ts/canonical.test.ts b/test/local/lib/schema-to-ts/canonical.test.ts new file mode 100644 index 000000000..f9969c193 --- /dev/null +++ b/test/local/lib/schema-to-ts/canonical.test.ts @@ -0,0 +1,119 @@ +import { describe, expect, test } from 'vitest'; + +import { canonical, canonicalNode, canonicalOptions } from '../../../../src/lib/schema-to-ts/canonical.js'; +import type { EmitOptions } from '../../../../src/lib/schema-to-ts/emit.js'; +import { IR_VERSION, type IRNode, type IRProp, type IRRoot } from '../../../../src/lib/schema-to-ts/ir.js'; + +const str: IRNode = { kind: 'string' }; +const num: IRNode = { kind: 'number' }; +const nul: IRNode = { kind: 'null' }; + +const prop = (name: string, node: IRNode = str, extra: Partial = {}): IRProp => ({ + name, + node, + required: false, + hasDefault: false, + ...extra, +}); + +const root = (node: IRNode): IRRoot => ({ irVersion: IR_VERSION, root: node }); +const opts = (types: EmitOptions['types']): EmitOptions => ({ types }); + +describe('canonicalNode', () => { + test('sorts object properties, so reordering cannot move the hash', () => { + const a: IRNode = { kind: 'object', props: [prop('a'), prop('b')], open: true }; + const b: IRNode = { kind: 'object', props: [prop('b'), prop('a')], open: true }; + expect(canonicalNode(a)).toBe(canonicalNode(b)); + }); + + test('sorts union members by kind rank, so reordering cannot move the hash', () => { + expect(canonicalNode({ kind: 'union', members: [str, nul] })).toBe( + canonicalNode({ kind: 'union', members: [nul, str] }), + ); + }); + + test('still separates unions of different members', () => { + expect(canonicalNode({ kind: 'union', members: [str, nul] })).not.toBe( + canonicalNode({ kind: 'union', members: [num, nul] }), + ); + }); + + test('separates literals that differ only by type', () => { + expect(canonicalNode({ kind: 'literal', value: 1 })).not.toBe(canonicalNode({ kind: 'literal', value: '1' })); + }); + + test('sorts same-rank literals deterministically', () => { + const lit = (value: string): IRNode => ({ kind: 'literal', value }); + expect(canonicalNode({ kind: 'union', members: [lit('a'), lit('b')] })).toBe( + canonicalNode({ kind: 'union', members: [lit('b'), lit('a')] }), + ); + }); + + test('escapes property names so separators in a name cannot forge structure', () => { + const tricky: IRNode = { kind: 'object', props: [prop('a,"b":-,'), prop('c')], open: true }; + const forged: IRNode = { kind: 'object', props: [prop('a'), prop('b'), prop('c')], open: true }; + expect(canonicalNode(tricky)).not.toBe(canonicalNode(forged)); + }); + + test('distinguishes required, hasDefault, open and valueType', () => { + const base: IRNode = { kind: 'object', props: [prop('a')], open: true }; + const keys = new Set([ + canonicalNode(base), + canonicalNode({ kind: 'object', props: [prop('a', str, { required: true })], open: true }), + canonicalNode({ kind: 'object', props: [prop('a', str, { hasDefault: true })], open: true }), + canonicalNode({ kind: 'object', props: [prop('a')], open: false }), + canonicalNode({ kind: 'object', props: [prop('a')], valueType: num, open: true }), + ]); + expect(keys.size).toBe(5); + }); + + test('descends into arrays', () => { + expect(canonicalNode({ kind: 'array', items: str })).not.toBe(canonicalNode({ kind: 'array', items: num })); + }); +}); + +describe('canonicalOptions', () => { + test('sorts declarations by name — the CLI config order is not load-bearing', () => { + expect( + canonicalOptions( + opts([ + { name: 'A', variant: 'received' }, + { name: 'B', variant: 'supplied' }, + ]), + ), + ).toBe( + canonicalOptions( + opts([ + { name: 'B', variant: 'supplied' }, + { name: 'A', variant: 'received' }, + ]), + ), + ); + }); + + test('names and variants are part of the identity', () => { + const base = canonicalOptions(opts([{ name: 'A', variant: 'received' }])); + expect(base).not.toBe(canonicalOptions(opts([{ name: 'B', variant: 'received' }]))); + expect(base).not.toBe(canonicalOptions(opts([{ name: 'A', variant: 'supplied' }]))); + }); +}); + +describe('canonical', () => { + test('omits the IR version — the header carries it as a prefix', () => { + const ir = root(str); + expect(canonical(ir, opts([{ name: 'A', variant: 'received' }]))).not.toContain(String(IR_VERSION)); + }); + + test('unknownRoot counts only when the root is actually unknown', () => { + const declarations = opts([{ name: 'A', variant: 'received' }]); + const objectRoot = root({ kind: 'object', props: [prop('a')], open: true }); + expect(canonical(objectRoot, { ...declarations, unknownRoot: 'record' })).toBe( + canonical(objectRoot, { ...declarations, unknownRoot: 'unknown' }), + ); + + const unknownRoot = root({ kind: 'unknown' }); + expect(canonical(unknownRoot, { ...declarations, unknownRoot: 'record' })).not.toBe( + canonical(unknownRoot, { ...declarations, unknownRoot: 'unknown' }), + ); + }); +}); diff --git a/test/local/lib/schema-to-ts/check.test.ts b/test/local/lib/schema-to-ts/check.test.ts new file mode 100644 index 000000000..5e093fa79 --- /dev/null +++ b/test/local/lib/schema-to-ts/check.test.ts @@ -0,0 +1,175 @@ +import { describe, expect, test } from 'vitest'; + +import { compareToHeader, readHeader, readHeaders } from '../../../../src/lib/schema-to-ts/check.js'; +import { emit, header, type EmitOptions } from '../../../../src/lib/schema-to-ts/emit.js'; +import { IR_VERSION, type IRNode, type IRRoot } from '../../../../src/lib/schema-to-ts/ir.js'; +import { jsonSchemaToIR } from '../../../../src/lib/schema-to-ts/json-schema-to-ir.js'; + +const OPTS: EmitOptions = { types: [{ name: 'Input', variant: 'received' }] }; + +const SCHEMA = { + type: 'object', + properties: { a: { type: 'string' }, b: { type: 'integer', default: 1 } }, + required: ['a'], +}; + +function irOf(schema: unknown): IRRoot { + return jsonSchemaToIR(schema).ir; +} + +const wrap = (node: IRNode): IRRoot => ({ irVersion: IR_VERSION, root: node }); + +describe('readHeaders', () => { + test('round-trips whatever the emitter writes', () => { + const parsed = readHeader(header('abcdef0123456789')); + expect(parsed).toEqual({ version: IR_VERSION, hash: 'abcdef0123456789', index: 0 }); + }); + + test('finds the header below a CLI-prepended banner', () => { + const source = [ + '/* eslint-disable */', + '// generated by our CLI', + header('aaaa'), + '', + 'export type T = string;', + ].join('\n'); + expect(readHeader(source)?.hash).toBe('aaaa'); + }); + + test('tolerates CRLF line endings', () => { + const source = [header('bbbb'), '', 'export type T = string;'].join('\r\n'); + expect(readHeader(source)?.hash).toBe('bbbb'); + }); + + test('is null when there is no header', () => { + expect(readHeader('export type T = string;\n')).toBeNull(); + expect(readHeaders('export type T = string;\n')).toEqual([]); + }); + + test('does not match a near-miss', () => { + expect(readHeader('// @generated schema-ts v1-NOTHEX — do not edit\n')).toBeNull(); + expect(readHeader('// @generated schema-ts v1-abcd - do not edit\n')).toBeNull(); + expect(readHeader(' // @generated schema-ts v1-abcd — do not edit\n')).toBeNull(); + }); + + test('reads a future version rather than failing to recognise the line', () => { + expect(readHeader('// @generated schema-ts v99-abcd — do not edit\n')).toEqual({ + version: 99, + hash: 'abcd', + index: 0, + }); + }); + + test('reports every header, and readHeader refuses to guess between them', () => { + const source = [header('aaaa'), 'export type T = string;', header('bbbb')].join('\n'); + expect(readHeaders(source).map((h) => h.hash)).toEqual(['aaaa', 'bbbb']); + expect(readHeader(source)).toBeNull(); + }); + + test('is not stateful across calls', () => { + const source = header('cccc'); + expect(readHeader(source)?.hash).toBe('cccc'); + expect(readHeader(source)?.hash).toBe('cccc'); + }); +}); + +describe('compareToHeader', () => { + test('a freshly emitted file matches', () => { + const ir = irOf(SCHEMA); + const result = compareToHeader(emit(ir, OPTS), ir, OPTS); + expect(result.stale).toBe(false); + expect(result.reason).toBe('match'); + expect(result.found).toBe(result.expected); + }); + + test('a type-relevant schema edit is stale', () => { + const source = emit(irOf(SCHEMA), OPTS); + const edited = irOf({ ...SCHEMA, required: ['a', 'b'] }); + const result = compareToHeader(source, edited, OPTS); + expect(result).toMatchObject({ stale: true, reason: 'hash-mismatch' }); + expect(result.found).not.toBe(result.expected); + }); + + test('a cosmetic schema edit is not', () => { + const source = emit(irOf(SCHEMA), OPTS); + const cosmetic = irOf({ + ...SCHEMA, + title: 'A title', + properties: { a: { type: 'string', editor: 'textarea', prefill: 'x' }, b: { type: 'number', default: 1 } }, + }); + expect(compareToHeader(source, cosmetic, OPTS).reason).toBe('match'); + }); + + test('renaming the declaration or switching the variant is stale', () => { + const ir = irOf(SCHEMA); + const source = emit(ir, OPTS); + expect(compareToHeader(source, ir, { types: [{ name: 'Renamed', variant: 'received' }] }).reason).toBe( + 'hash-mismatch', + ); + expect(compareToHeader(source, ir, { types: [{ name: 'Input', variant: 'supplied' }] }).reason).toBe( + 'hash-mismatch', + ); + }); + + test('a hand-written file is not ours to overwrite', () => { + const result = compareToHeader('export type Input = { a: string };\n', irOf(SCHEMA), OPTS); + expect(result).toMatchObject({ stale: true, reason: 'missing-header', found: null, foundVersion: null }); + }); + + test('two headers — a botched merge — is its own failure', () => { + const ir = irOf(SCHEMA); + const source = emit(ir, OPTS) + emit(ir, OPTS); + expect(compareToHeader(source, ir, OPTS)).toMatchObject({ + stale: true, + reason: 'duplicate-header', + found: null, + }); + }); + + test('version mismatch outranks the hash, since hashes are not comparable across versions', () => { + const ir = irOf(SCHEMA); + const fromTheFuture = emit(ir, OPTS).replace(`v${IR_VERSION}-`, 'v99-'); + const result = compareToHeader(fromTheFuture, ir, OPTS); + // Same hash, different version — still reported as a version mismatch, not a match. + expect(result).toMatchObject({ stale: true, reason: 'version-mismatch', foundVersion: 99 }); + expect(result.found).toBe(result.expected); + expect(result.expectedVersion).toBe(IR_VERSION); + }); + + test('an unknown root is checkable like anything else', () => { + const ir = wrap({ kind: 'unknown' }); + expect(compareToHeader(emit(ir, OPTS), ir, OPTS).reason).toBe('match'); + // The softer root is a different file, so it must not pass as the same one. + expect(compareToHeader(emit(ir, OPTS), ir, { ...OPTS, unknownRoot: 'record' }).reason).toBe('hash-mismatch'); + }); +}); + +describe('formatting immunity', () => { + /** The whole reason the fingerprint exists: Prettier must not be able to fake drift. */ + const reformat = (source: string): string => + source + .replace(/^ {4}/gm, ' ') // re-indent + .replace(/;$/gm, ',') // trailing commas instead of semicolons + .replace(/\n\n/g, '\n\n\n') // extra blank lines + .replace(/^export type/m, '\nexport type'); // stray whitespace + + test('survives a reformatted body', () => { + const ir = irOf(SCHEMA); + const result = compareToHeader(reformat(emit(ir, OPTS)), ir, OPTS); + expect(result.reason).toBe('match'); + expect(result.stale).toBe(false); + }); + + test('survives CRLF normalisation and a trailing-newline change', () => { + const ir = irOf(SCHEMA); + const crlf = emit(ir, OPTS).replace(/\n/g, '\r\n').trimEnd(); + expect(compareToHeader(crlf, ir, OPTS).reason).toBe('match'); + }); + + test('but a real schema change still lands', () => { + const source = reformat(emit(irOf(SCHEMA), OPTS)); + expect(compareToHeader(source, irOf({ ...SCHEMA, properties: { a: { type: 'number' } } }), OPTS).reason).toBe( + 'hash-mismatch', + ); + }); +}); diff --git a/test/local/lib/schema-to-ts/diagnostics.test.ts b/test/local/lib/schema-to-ts/diagnostics.test.ts new file mode 100644 index 000000000..671916ec0 --- /dev/null +++ b/test/local/lib/schema-to-ts/diagnostics.test.ts @@ -0,0 +1,42 @@ +import { describe, expect, test } from 'vitest'; + +import { Report, pointer } from '../../../../src/lib/schema-to-ts/diagnostics.js'; + +describe('pointer', () => { + test('appends segments to a base', () => { + expect(pointer('', 'properties', 'a')).toBe('/properties/a'); + expect(pointer('/properties/a', 'items')).toBe('/properties/a/items'); + }); + + test('root is the empty string', () => { + expect(pointer('')).toBe(''); + }); + + test('escapes ~ and / per RFC 6901', () => { + expect(pointer('', 'properties', 'a/b')).toBe('/properties/a~1b'); + expect(pointer('', 'properties', 'a~b')).toBe('/properties/a~0b'); + // `~1` in a raw name must not be mistaken for an escaped slash on the way back. + expect(pointer('', 'properties', '~1')).toBe('/properties/~01'); + }); +}); + +describe('Report', () => { + test('routes by severity and keeps insertion order', () => { + const report = new Report(); + report.warn('/a', 'unsupported-keyword', 'w'); + report.error('/b', 'malformed-type', 'e'); + report.notice('/c', 'empty-schema', 'n'); + + expect(report.diagnostics).toEqual([ + { path: '/a', severity: 'warning', code: 'unsupported-keyword', message: 'w' }, + { path: '/b', severity: 'error', code: 'malformed-type', message: 'e' }, + ]); + expect(report.notices).toEqual([{ path: '/c', code: 'empty-schema', message: 'n' }]); + }); + + test('starts empty', () => { + const report = new Report(); + expect(report.diagnostics).toEqual([]); + expect(report.notices).toEqual([]); + }); +}); diff --git a/test/local/lib/schema-to-ts/emit.test.ts b/test/local/lib/schema-to-ts/emit.test.ts new file mode 100644 index 000000000..c9a2fdcb6 --- /dev/null +++ b/test/local/lib/schema-to-ts/emit.test.ts @@ -0,0 +1,267 @@ +import { describe, expect, test } from 'vitest'; + +import { emit, header, type EmitOptions, type Variant } from '../../../../src/lib/schema-to-ts/emit.js'; +import { HASH_LENGTH } from '../../../../src/lib/schema-to-ts/hash.js'; +import { IR_VERSION, type IRNode, type IRProp, type IRRoot } from '../../../../src/lib/schema-to-ts/ir.js'; + +const str: IRNode = { kind: 'string' }; +const num: IRNode = { kind: 'number' }; +const nul: IRNode = { kind: 'null' }; + +const prop = (name: string, node: IRNode = str, extra: Partial = {}): IRProp => ({ + name, + node, + required: false, + hasDefault: false, + ...extra, +}); + +const wrap = (node: IRNode): IRRoot => ({ irVersion: IR_VERSION, root: node }); +const obj = (props: IRProp[], extra: Partial> = {}): IRNode => ({ + kind: 'object', + props, + open: true, + ...extra, +}); + +/** Emits one declaration and returns just its body, which is what most assertions care about. */ +function body(node: IRNode, variant: Variant = 'received', opts: Partial = {}): string { + const text = emit(wrap(node), { types: [{ name: 'T', variant }], ...opts }); + return text + .split('\n') + .slice(2) + .join('\n') + .replace(/^export type T = /, '') + .replace(/;\n*$/, ''); +} + +describe('header', () => { + test('carries the version as a prefix and a 16-hex hash', () => { + const text = emit(wrap(str), { types: [{ name: 'T', variant: 'received' }] }); + expect(text.split('\n')[0]).toMatch( + new RegExp(`^// @generated schema-ts v${IR_VERSION}-[0-9a-f]{${HASH_LENGTH}} — do not edit$`), + ); + }); + + test('is built from the same helper the checker will parse', () => { + expect(header('abc')).toBe(`// @generated schema-ts v${IR_VERSION}-abc — do not edit`); + }); + + test('appears exactly once no matter how many declarations there are', () => { + const text = emit(wrap(obj([prop('a')])), { + types: [ + { name: 'A', variant: 'received' }, + { name: 'B', variant: 'supplied' }, + ], + }); + expect(text.split('\n').filter((l) => l.startsWith('// @generated'))).toHaveLength(1); + }); +}); + +describe('declarations', () => { + test('emit in EmitOptions order, not sorted order', () => { + const text = emit(wrap(str), { + types: [ + { name: 'Zeta', variant: 'received' }, + { name: 'Alpha', variant: 'supplied' }, + ], + }); + expect(text).toContain('export type Zeta = string;\n\nexport type Alpha = string;'); + }); + + test('always `type`, never `interface`', () => { + const text = emit(wrap(obj([prop('a')])), { types: [{ name: 'T', variant: 'received' }] }); + expect(text).toContain('export type T = {'); + expect(text).not.toContain('interface'); + }); + + test('file ends with a newline', () => { + const result = emit(wrap(str), { types: [{ name: 'T', variant: 'received' }] }); + expect(result).toMatch(/;\n$/); + }); +}); + +describe('variance', () => { + const node = obj([ + prop('req', str, { required: true }), + prop('defaulted', str, { hasDefault: true }), + prop('plain', str), + prop('both', str, { required: true, hasDefault: true }), + ]); + + test('received treats platform-materialized defaults as present', () => { + expect(body(node, 'received')).toBe( + [ + '{', + ' req: string;', + ' defaulted: string;', + ' plain?: string | undefined;', + ' both: string;', + '}', + ].join('\n'), + ); + }); + + test('supplied only demands what is required', () => { + expect(body(node, 'supplied')).toBe( + [ + '({', + ' req: string;', + ' defaulted?: string | undefined;', + ' plain?: string | undefined;', + ' both: string;', + '} & Record)', + ].join('\n'), + ); + }); + + test('supplied opens objects for extras; received closes them so typos are caught', () => { + expect(body(obj([prop('a')], { open: true }), 'supplied')).toContain('& Record'); + expect(body(obj([prop('a')], { open: true }), 'received')).not.toContain('Record'); + }); + + test('a closed object is closed to the writer too', () => { + expect(body(obj([prop('a')], { open: false }), 'supplied')).not.toContain('Record'); + }); + + test('variance recurses into nested objects', () => { + const nested = obj([prop('outer', obj([prop('inner', str, { hasDefault: true })]), { required: true })]); + expect(body(nested, 'received')).toContain('inner: string;'); + expect(body(nested, 'supplied')).toContain('inner?: string | undefined;'); + }); +}); + +describe('objects', () => { + test('a propertyless open object is a Record, never a bare {}', () => { + expect(body(obj([]))).toBe('Record'); + }); + + test('a propertyless closed object admits no keys at all', () => { + expect(body(obj([], { open: false }))).toBe('Record'); + }); + + test('a dictionary types its values', () => { + expect(body(obj([], { valueType: num }))).toBe('Record'); + }); + + test('a dictionary with declared properties is an intersection', () => { + expect(body(obj([prop('x')], { valueType: num }))).toBe( + ['({', ' x?: string | undefined;', '} & Record)'].join('\n'), + ); + }); + + test('a typed value overrides the open-object index signature', () => { + expect(body(obj([prop('x')], { valueType: num }), 'supplied')).not.toContain('Record'); + }); + + test('never emits a bare {}', () => { + for (const variant of ['received', 'supplied'] as const) { + expect(body(obj([]), variant)).not.toBe('{}'); + expect(body(obj([], { open: false }), variant)).not.toBe('{}'); + } + }); + + test('indents nested objects by four spaces per level', () => { + expect(body(obj([prop('a', obj([prop('b', obj([prop('c')]))]))]))).toBe( + [ + '{', + ' a?: {', + ' b?: {', + ' c?: string | undefined;', + ' } | undefined;', + ' } | undefined;', + '}', + ].join('\n'), + ); + }); +}); + +describe('property names', () => { + test('are bare when they are TypeScript identifiers', () => { + for (const name of ['plain', '_leading', '$dollar', 'fbid_v2', 'A1', 'class', 'default', 'new']) { + expect(body(obj([prop(name, str, { required: true })]))).toContain(` ${name}: string;`); + } + }); + + test('are quoted and escaped when they are not', () => { + expect(body(obj([prop('not-an-ident', str, { required: true })]))).toContain('"not-an-ident": string;'); + expect(body(obj([prop('2fa', str, { required: true })]))).toContain('"2fa": string;'); + expect(body(obj([prop('has space', str, { required: true })]))).toContain('"has space": string;'); + expect(body(obj([prop('has "quotes"', str, { required: true })]))).toContain('"has \\"quotes\\"": string;'); + expect(body(obj([prop('back\\slash', str, { required: true })]))).toContain('"back\\\\slash": string;'); + }); +}); + +describe('nodes', () => { + test('scalars', () => { + expect(body(str)).toBe('string'); + expect(body(num)).toBe('number'); + expect(body({ kind: 'boolean' })).toBe('boolean'); + expect(body(nul)).toBe('null'); + expect(body({ kind: 'unknown' })).toBe('unknown'); + }); + + test('literals keep their JSON type', () => { + expect(body({ kind: 'literal', value: 'posts' })).toBe('"posts"'); + expect(body({ kind: 'literal', value: 1 })).toBe('1'); + expect(body({ kind: 'literal', value: true })).toBe('true'); + }); + + test('unions emit in authored order — only the hash sorts', () => { + expect(body({ kind: 'union', members: [str, nul] })).toBe('string | null'); + expect(body({ kind: 'union', members: [nul, str] })).toBe('null | string'); + }); + + test('arrays are always Array, so no element ever needs parenthesizing', () => { + expect(body({ kind: 'array', items: str })).toBe('Array'); + expect(body({ kind: 'array', items: { kind: 'union', members: [str, nul] } })).toBe('Array'); + expect(body({ kind: 'array', items: { kind: 'array', items: num } })).toBe('Array>'); + }); + + test('an unknown property is not widened with | undefined', () => { + expect(body(obj([prop('a', { kind: 'unknown' })]))).toContain('a?: unknown;'); + }); +}); + +describe('unknown root', () => { + test('is the truth by default', () => { + expect(body({ kind: 'unknown' })).toBe('unknown'); + }); + + test('can be softened to a Record on request', () => { + expect(body({ kind: 'unknown' }, 'received', { unknownRoot: 'record' })).toBe('Record'); + }); + + test('the softer guess changes the hash, so the two are not interchangeable', () => { + const ir = wrap({ kind: 'unknown' }); + const types: EmitOptions['types'] = [{ name: 'T', variant: 'received' }]; + expect(emit(ir, { types }).split('\n')[0]).not.toBe(emit(ir, { types, unknownRoot: 'record' }).split('\n')[0]); + }); +}); + +/** + * The contract the emitter is written against, as a consumer would compile our output — not this + * repo's tsconfig.json, which is about compiling *our* source and answers to a different audience. + * Pinned on purpose: `exactOptionalPropertyTypes` is why we emit `name?: T | undefined` at all, so + * if someone relaxed it at the repo root to unblock a src file, this gate must keep asserting it + * rather than silently stop testing the thing it exists for. `types: []` proves generated output + * stands alone with no ambient @types on the machine. + */ +const GATE_TSCONFIG = JSON.stringify( + { + compilerOptions: { + noEmit: true, + strict: true, + exactOptionalPropertyTypes: true, + target: 'esnext', + module: 'nodenext', + moduleResolution: 'nodenext', + allowImportingTsExtensions: true, + skipLibCheck: true, + types: [], + }, + include: ['*.ts'], + }, + null, + 4, +); diff --git a/test/local/lib/schema-to-ts/hash.test.ts b/test/local/lib/schema-to-ts/hash.test.ts new file mode 100644 index 000000000..57114aed0 --- /dev/null +++ b/test/local/lib/schema-to-ts/hash.test.ts @@ -0,0 +1,387 @@ +import { describe, expect, test } from 'vitest'; + +import type { EmitOptions } from '../../../../src/lib/schema-to-ts/emit.js'; +import { HASH_LENGTH, irHash } from '../../../../src/lib/schema-to-ts/hash.js'; +import { jsonSchemaToIR } from '../../../../src/lib/schema-to-ts/json-schema-to-ir.js'; + +/** + * The tables below are the load-bearing test of the whole design: the hash must move for + * every type-relevant edit and for nothing else. If one of these flips, either the frontend + * started reading something it should ignore, or it stopped reading something it must. + */ + +type Schema = Record; + +const BASE: Schema = { + type: 'object', + properties: { + a: { type: 'string' }, + b: { type: 'integer', default: 1 }, + c: { type: 'array', items: { type: 'string' } }, + d: { type: 'string', enum: ['x', 'y'] }, + e: { type: 'object', properties: { z: { type: 'boolean' } } }, + }, + required: ['a'], +}; + +const OPTS: EmitOptions = { types: [{ name: 'Input', variant: 'received' }] }; + +function hashOf(schema: unknown, opts: EmitOptions = OPTS): string { + return irHash(jsonSchemaToIR(schema).ir, opts); +} + +/** Deep-clones BASE and hands it to a mutator, so cases cannot leak into each other. */ +function mutate(fn: (schema: Schema) => void): Schema { + const clone = structuredClone(BASE) as Schema; + fn(clone); + return clone; +} + +function props(schema: Schema): Record { + return schema.properties as Record; +} + +describe('shape', () => { + test('is 16 lowercase hex characters', () => { + expect(hashOf(BASE)).toMatch(new RegExp(`^[0-9a-f]{${HASH_LENGTH}}$`)); + }); + + test('is deterministic across calls', () => { + expect(hashOf(BASE)).toBe(hashOf(BASE)); + }); +}); + +describe('must NOT move the hash', () => { + const cases: [string, () => Schema][] = [ + [ + 'title and description', + () => + mutate((s) => { + s.title = 'A title'; + s.description = 'Prose'; + props(s).a!.title = 'Field'; + props(s).a!.description = 'More prose with HTML'; + }), + ], + [ + 'editor', + () => + mutate((s) => { + props(s).a!.editor = 'textarea'; + }), + ], + [ + 'prefill', + () => + mutate((s) => { + props(s).a!.prefill = 'hello'; + }), + ], + [ + 'example', + () => + mutate((s) => { + props(s).a!.example = 'hello'; + }), + ], + [ + 'unit', + () => + mutate((s) => { + props(s).b!.unit = 'ms'; + }), + ], + [ + 'isSecret', + () => + mutate((s) => { + props(s).a!.isSecret = true; + }), + ], + [ + 'sectionCaption and sectionDescription', + () => + mutate((s) => { + props(s).a!.sectionCaption = 'Section'; + props(s).a!.sectionDescription = 'Why'; + }), + ], + [ + 'pattern, minLength, maxLength', + () => + mutate((s) => { + props(s).a!.pattern = '^a$'; + props(s).a!.minLength = 1; + props(s).a!.maxLength = 9; + }), + ], + [ + 'minimum and maximum', + () => + mutate((s) => { + props(s).b!.minimum = 1; + props(s).b!.maximum = 9; + }), + ], + [ + 'uniqueItems, minItems, maxItems', + () => + mutate((s) => { + props(s).c!.uniqueItems = true; + props(s).c!.minItems = 1; + props(s).c!.maxItems = 9; + }), + ], + [ + 'enumTitles and enumSuggestedValues', + () => + mutate((s) => { + props(s).d!.enumTitles = ['Ex', 'Why']; + props(s).d!.enumSuggestedValues = ['x', 'y', 'z']; + }), + ], + [ + 'integer -> number', + () => + mutate((s) => { + props(s).b!.type = 'number'; + }), + ], + [ + 'reordered properties', + () => + mutate((s) => { + const p = props(s); + s.properties = { e: p.e!, d: p.d!, c: p.c!, b: p.b!, a: p.a! }; + }), + ], + [ + 'reordered enum', + () => + mutate((s) => { + props(s).d!.enum = ['y', 'x']; + }), + ], + [ + 'duplicated enum member', + () => + mutate((s) => { + props(s).d!.enum = ['x', 'y', 'x']; + }), + ], + [ + 'type as a single-element array', + () => + mutate((s) => { + props(s).a!.type = ['string']; + }), + ], + [ + 'duplicated type member', + () => + mutate((s) => { + props(s).a!.type = ['string', 'string']; + }), + ], + [ + 'additionalProperties: {}', + () => + mutate((s) => { + s.additionalProperties = {}; + }), + ], + [ + 'additionalProperties: true', + () => + mutate((s) => { + s.additionalProperties = true; + }), + ], + [ + '$defs with no $ref', + () => + mutate((s) => { + s.$defs = { x: { type: 'number' } }; + }), + ], + [ + 'required naming an undeclared property', + () => + mutate((s) => { + s.required = ['a', 'ghost']; + }), + ], + [ + 'an unrecognized keyword', + () => + mutate((s) => { + props(s).a!.someFutureKeyword = 42; + }), + ], + ]; + + test.each(cases)('%s', (_label, build) => { + expect(hashOf(build())).toBe(hashOf(BASE)); + }); + + test('declaration order in EmitOptions', () => { + const forward: EmitOptions = { + types: [ + { name: 'A', variant: 'received' }, + { name: 'B', variant: 'supplied' }, + ], + }; + const reversed: EmitOptions = { + types: [ + { name: 'B', variant: 'supplied' }, + { name: 'A', variant: 'received' }, + ], + }; + expect(hashOf(BASE, forward)).toBe(hashOf(BASE, reversed)); + }); + + test('unknownRoot, when the root is readable', () => { + expect(hashOf(BASE, { ...OPTS, unknownRoot: 'record' })).toBe(hashOf(BASE, { ...OPTS, unknownRoot: 'unknown' })); + }); +}); + +describe('MUST move the hash', () => { + const cases: [string, () => Schema][] = [ + [ + 'added property', + () => + mutate((s) => { + props(s).f = { type: 'string' }; + }), + ], + [ + 'removed property', + () => + mutate((s) => { + delete props(s).a; + }), + ], + [ + 'renamed property', + () => + mutate((s) => { + const p = props(s); + s.properties = { renamed: p.a!, b: p.b!, c: p.c!, d: p.d!, e: p.e! }; + }), + ], + [ + 'newly required property', + () => + mutate((s) => { + s.required = ['a', 'b']; + }), + ], + [ + 'no longer required', + () => + mutate((s) => { + s.required = []; + }), + ], + [ + 'added default', + () => + mutate((s) => { + props(s).a!.default = 'x'; + }), + ], + [ + 'removed default', + () => + mutate((s) => { + delete props(s).b!.default; + }), + ], + [ + 'changed type', + () => + mutate((s) => { + props(s).a!.type = 'number'; + }), + ], + [ + 'widened to nullable', + () => + mutate((s) => { + props(s).a!.type = ['string', 'null']; + }), + ], + [ + 'changed array element type', + () => + mutate((s) => { + props(s).c!.items = { type: 'number' }; + }), + ], + [ + 'removed array items', + () => + mutate((s) => { + delete props(s).c!.items; + }), + ], + [ + 'added enum member', + () => + mutate((s) => { + props(s).d!.enum = ['x', 'y', 'z']; + }), + ], + [ + 'dropped enum for a bare string', + () => + mutate((s) => { + delete props(s).d!.enum; + }), + ], + [ + 'additionalProperties: false', + () => + mutate((s) => { + s.additionalProperties = false; + }), + ], + [ + 'additionalProperties as a subschema', + () => + mutate((s) => { + s.additionalProperties = { type: 'string' }; + }), + ], + [ + 'changed nested property', + () => + mutate((s) => { + props(props(s).e!).z!.type = 'number'; + }), + ], + [ + 'degraded by an unsupported keyword', + () => + mutate((s) => { + props(s).a!.oneOf = []; + }), + ], + ]; + + test.each(cases)('%s', (_label, build) => { + expect(hashOf(build())).not.toBe(hashOf(BASE)); + }); + + test('renamed declaration', () => { + expect(hashOf(BASE, { types: [{ name: 'Renamed', variant: 'received' }] })).not.toBe(hashOf(BASE)); + }); + + test('switched variant', () => { + expect(hashOf(BASE, { types: [{ name: 'Input', variant: 'supplied' }] })).not.toBe(hashOf(BASE)); + }); + + test('unknownRoot, when the root is unknown', () => { + expect(hashOf(42, { ...OPTS, unknownRoot: 'record' })).not.toBe(hashOf(42, { ...OPTS, unknownRoot: 'unknown' })); + }); +}); diff --git a/test/local/lib/schema-to-ts/ir.test.ts b/test/local/lib/schema-to-ts/ir.test.ts new file mode 100644 index 000000000..286b08d0a --- /dev/null +++ b/test/local/lib/schema-to-ts/ir.test.ts @@ -0,0 +1,96 @@ +import { describe, expect, test } from 'vitest'; + +import { UNKNOWN, nodeKey, union, type IRNode } from '../../../../src/lib/schema-to-ts/ir.js'; + +const str: IRNode = { kind: 'string' }; +const num: IRNode = { kind: 'number' }; +const nul: IRNode = { kind: 'null' }; + +describe('nodeKey', () => { + test('separates literals of different types that stringify the same', () => { + expect(nodeKey({ kind: 'literal', value: 1 })).not.toBe(nodeKey({ kind: 'literal', value: '1' })); + expect(nodeKey({ kind: 'literal', value: true })).not.toBe(nodeKey({ kind: 'literal', value: 'true' })); + }); + + test('is stable for structurally identical nodes', () => { + const a: IRNode = { + kind: 'object', + props: [{ name: 'a', node: str, required: true, hasDefault: false }], + open: true, + }; + const b: IRNode = { + kind: 'object', + props: [{ name: 'a', node: str, required: true, hasDefault: false }], + open: true, + }; + expect(nodeKey(a)).toBe(nodeKey(b)); + }); + + test('distinguishes required, hasDefault, open, and valueType', () => { + const base = { name: 'a', node: str, required: false, hasDefault: false }; + const obj = (props: (typeof base)[], extra: Partial> = {}): IRNode => ({ + kind: 'object', + props, + open: true, + ...extra, + }); + const keys = new Set([ + nodeKey(obj([base])), + nodeKey(obj([{ ...base, required: true }])), + nodeKey(obj([{ ...base, hasDefault: true }])), + nodeKey(obj([base], { open: false })), + nodeKey(obj([base], { valueType: num })), + ]); + expect(keys.size).toBe(5); + }); + + test('distinguishes property order', () => { + const p = (name: string) => ({ name, node: str, required: false, hasDefault: false }); + expect(nodeKey({ kind: 'object', props: [p('a'), p('b')], open: true })).not.toBe( + nodeKey({ kind: 'object', props: [p('b'), p('a')], open: true }), + ); + }); + + test('descends into arrays and unions', () => { + expect(nodeKey({ kind: 'array', items: str })).not.toBe(nodeKey({ kind: 'array', items: num })); + expect(nodeKey({ kind: 'union', members: [str, nul] })).not.toBe(nodeKey({ kind: 'union', members: [num, nul] })); + }); +}); + +describe('union', () => { + test('empty collapses to unknown', () => { + expect(union([])).toEqual(UNKNOWN); + }); + + test('single member collapses to that member', () => { + expect(union([str])).toEqual(str); + }); + + test('preserves authored order — the emitter reproduces it', () => { + expect(union([nul, str])).toEqual({ kind: 'union', members: [nul, str] }); + expect(union([str, nul])).toEqual({ kind: 'union', members: [str, nul] }); + }); + + test('de-dupes structurally identical members', () => { + expect(union([str, str])).toEqual(str); + expect(union([str, nul, str])).toEqual({ kind: 'union', members: [str, nul] }); + }); + + test('keeps literals that differ only by type', () => { + const one: IRNode = { kind: 'literal', value: 1 }; + const oneStr: IRNode = { kind: 'literal', value: '1' }; + expect(union([one, oneStr])).toEqual({ kind: 'union', members: [one, oneStr] }); + }); + + test('flattens nested unions', () => { + expect(union([{ kind: 'union', members: [str, nul] }, num])).toEqual({ + kind: 'union', + members: [str, nul, num], + }); + }); + + test('unknown absorbs the rest, matching TS semantics for `string | unknown`', () => { + expect(union([str, UNKNOWN])).toEqual(UNKNOWN); + expect(union([UNKNOWN, str, nul])).toEqual(UNKNOWN); + }); +}); diff --git a/test/local/lib/schema-to-ts/json-schema-to-ir.test.ts b/test/local/lib/schema-to-ts/json-schema-to-ir.test.ts new file mode 100644 index 000000000..6aa4a6997 --- /dev/null +++ b/test/local/lib/schema-to-ts/json-schema-to-ir.test.ts @@ -0,0 +1,353 @@ +import { describe, expect, test } from 'vitest'; + +import { IR_VERSION, UNKNOWN, type IRNode, type IRProp } from '../../../../src/lib/schema-to-ts/ir.js'; +import { jsonSchemaToIR, type Lifted } from '../../../../src/lib/schema-to-ts/json-schema-to-ir.js'; + +const str: IRNode = { kind: 'string' }; +const num: IRNode = { kind: 'number' }; +const bool: IRNode = { kind: 'boolean' }; +const nul: IRNode = { kind: 'null' }; + +/** Lifts a single field so paths are predictably `/properties/f`. */ +function field(schema: unknown): Lifted & { node: IRNode } { + const lifted = jsonSchemaToIR({ type: 'object', properties: { f: schema } }); + return { ...lifted, node: propNode(lifted.ir.root, 'f') }; +} + +function props(node: IRNode): IRProp[] { + if (node.kind !== 'object') throw new Error(`expected an object node, got ${node.kind}`); + return node.props; +} + +function propNode(node: IRNode, name: string): IRNode { + const found = props(node).find((p) => p.name === name); + if (!found) throw new Error(`no property ${name}`); + return found.node; +} + +/** `severity path code` triples — assert on these, not on message wording. */ +function codes(lifted: Lifted): string[] { + return lifted.diagnostics.map((d) => `${d.severity} ${d.path} ${d.code}`); +} + +function noticeCodes(lifted: Lifted): string[] { + return lifted.notices.map((n) => `${n.path} ${n.code}`); +} + +function expectClean(lifted: Lifted): void { + expect(codes(lifted)).toEqual([]); + expect(noticeCodes(lifted)).toEqual([]); +} + +describe('root', () => { + test('stamps the IR version', () => { + expect(jsonSchemaToIR({ type: 'string' }).ir.irVersion).toBe(IR_VERSION); + }); + + test('a non-object root degrades the whole tree', () => { + for (const bad of [null, 'nope', 42, [], true]) { + const lifted = jsonSchemaToIR(bad); + expect(lifted.ir.root).toEqual(UNKNOWN); + expect(codes(lifted)).toEqual(['error malformed-schema']); + } + }); + + test('a propertyless object is legal JSON Schema, not an error', () => { + const lifted = jsonSchemaToIR({ type: 'object' }); + expect(lifted.ir.root).toEqual({ kind: 'object', props: [], open: true }); + expectClean(lifted); + }); +}); + +describe('primitives', () => { + test('maps the scalar types', () => { + expect(field({ type: 'string' }).node).toEqual(str); + expect(field({ type: 'number' }).node).toEqual(num); + expect(field({ type: 'boolean' }).node).toEqual(bool); + expect(field({ type: 'null' }).node).toEqual(nul); + }); + + test('integer collapses to number, so integer<->number is not a type change', () => { + expect(field({ type: 'integer' }).node).toEqual(num); + expect(field({ type: ['integer', 'number'] }).node).toEqual(num); + }); +}); + +describe('type arrays', () => { + test('become unions in authored order', () => { + expect(field({ type: ['string', 'null'] }).node).toEqual({ kind: 'union', members: [str, nul] }); + expect(field({ type: ['null', 'string'] }).node).toEqual({ kind: 'union', members: [nul, str] }); + }); + + test('de-dupe and collapse to a single node', () => { + expect(field({ type: ['string', 'string'] }).node).toEqual(str); + expect(field({ type: ['string'] }).node).toEqual(str); + }); + + test('a malformed member degrades the whole node', () => { + const lifted = field({ type: ['string', 'object'], properties: 7 }); + expect(lifted.node).toEqual(UNKNOWN); + expect(codes(lifted)).toEqual(['error /properties/f/properties malformed-properties']); + }); +}); + +describe('malformed type', () => { + test('rejects a type that is not a string or array of strings', () => { + const lifted = field({ type: 42 }); + expect(lifted.node).toEqual(UNKNOWN); + expect(codes(lifted)).toEqual(['error /properties/f malformed-type']); + }); + + test('rejects a type name outside the seven JSON Schema types', () => { + expect(codes(field({ type: 'strng' }))).toEqual(['error /properties/f unknown-type-name']); + expect(codes(field({ type: ['string', 'strng'] }))).toEqual(['error /properties/f unknown-type-name']); + }); + + test('rejects an empty type array', () => { + expect(codes(field({ type: [] }))).toEqual(['error /properties/f empty-type-array']); + }); +}); + +describe('absent type', () => { + test('is inferred from properties, additionalProperties, or items', () => { + expect(field({ properties: { x: { type: 'string' } } }).node.kind).toBe('object'); + expect(field({ additionalProperties: { type: 'string' } }).node.kind).toBe('object'); + expect(field({ items: { type: 'boolean' } }).node).toEqual({ kind: 'array', items: bool }); + }); + + test('a bare {} is faithfully unknown — a notice, not a diagnostic', () => { + const lifted = field({}); + expect(lifted.node).toEqual(UNKNOWN); + expect(codes(lifted)).toEqual([]); + expect(noticeCodes(lifted)).toEqual(['/properties/f empty-schema']); + }); +}); + +describe('enum', () => { + test('wins over type and de-dupes', () => { + const lifted = field({ type: ['string', 'null'], enum: ['x', 'y', 'x'] }); + expect(lifted.node).toEqual({ + kind: 'union', + members: [ + { kind: 'literal', value: 'x' }, + { kind: 'literal', value: 'y' }, + ], + }); + expectClean(lifted); + }); + + test('mixes literal types and null', () => { + expect(field({ enum: ['a', 1, true, null] }).node).toEqual({ + kind: 'union', + members: [{ kind: 'literal', value: 'a' }, { kind: 'literal', value: 1 }, { kind: 'literal', value: true }, nul], + }); + }); + + test('a single member collapses to a bare literal', () => { + expect(field({ enum: ['only'] }).node).toEqual({ kind: 'literal', value: 'only' }); + }); + + test('objects and arrays cannot be literals', () => { + const lifted = field({ enum: [{ x: 1 }] }); + expect(lifted.node).toEqual(UNKNOWN); + expect(codes(lifted)).toEqual(['warning /properties/f/enum unsupported-enum-values']); + }); + + test('must be a non-empty array', () => { + expect(codes(field({ enum: [] }))).toEqual(['error /properties/f/enum malformed-enum']); + expect(codes(field({ enum: 'nope' }))).toEqual(['error /properties/f/enum malformed-enum']); + }); +}); + +describe('objects', () => { + test('preserve authored property order and record required/hasDefault', () => { + const lifted = jsonSchemaToIR({ + type: 'object', + properties: { + b: { type: 'string' }, + a: { type: 'string', default: 'x' }, + c: { type: 'string' }, + }, + required: ['a', 'c'], + }); + expect(props(lifted.ir.root)).toEqual([ + { name: 'b', node: str, required: false, hasDefault: false }, + { name: 'a', node: str, required: true, hasDefault: true }, + { name: 'c', node: str, required: true, hasDefault: false }, + ]); + expectClean(lifted); + }); + + test('nest, and paths point at the offending subschema', () => { + const lifted = jsonSchemaToIR({ + type: 'object', + properties: { outer: { type: 'object', properties: { inner: { type: 'strng' } } } }, + }); + expect(codes(lifted)).toEqual(['error /properties/outer/properties/inner unknown-type-name']); + expect(propNode(propNode(lifted.ir.root, 'outer'), 'inner')).toEqual(UNKNOWN); + }); + + test('one malformed property does not take out its siblings', () => { + const lifted = jsonSchemaToIR({ + type: 'object', + properties: { good: { type: 'string' }, bad: 'not a schema', alsoGood: { type: 'number' } }, + }); + expect(props(lifted.ir.root)).toEqual([ + { name: 'good', node: str, required: false, hasDefault: false }, + { name: 'bad', node: UNKNOWN, required: false, hasDefault: false }, + { name: 'alsoGood', node: num, required: false, hasDefault: false }, + ]); + expect(codes(lifted)).toEqual(['error /properties/bad malformed-schema']); + }); + + test('malformed properties or required degrade the object', () => { + expect(codes(field({ type: 'object', properties: [] }))).toEqual([ + 'error /properties/f/properties malformed-properties', + ]); + expect(field({ type: 'object', properties: [] }).node).toEqual(UNKNOWN); + + expect(codes(field({ type: 'object', required: 'nope' }))).toEqual([ + 'error /properties/f/required malformed-required', + ]); + expect(codes(field({ type: 'object', required: [1] }))).toEqual([ + 'error /properties/f/required malformed-required', + ]); + }); + + test('required naming an undeclared property is a notice with no type impact', () => { + const lifted = jsonSchemaToIR({ + type: 'object', + properties: { a: { type: 'string' } }, + required: ['a', 'ghost'], + }); + expect(codes(lifted)).toEqual([]); + expect(noticeCodes(lifted)).toEqual(['/required required-unknown-property']); + expect(props(lifted.ir.root)).toHaveLength(1); + }); +}); + +describe('additionalProperties', () => { + test('absent, {} and true are all open; false is closed', () => { + expect(field({ type: 'object' }).node).toEqual({ kind: 'object', props: [], open: true }); + expect(field({ type: 'object', additionalProperties: {} }).node).toEqual({ + kind: 'object', + props: [], + open: true, + }); + expect(field({ type: 'object', additionalProperties: true }).node).toEqual({ + kind: 'object', + props: [], + open: true, + }); + expect(field({ type: 'object', additionalProperties: false }).node).toEqual({ + kind: 'object', + props: [], + open: false, + }); + }); + + test('a non-empty subschema types the extra keys', () => { + expect(field({ type: 'object', additionalProperties: { type: 'string' } }).node).toEqual({ + kind: 'object', + props: [], + valueType: str, + open: true, + }); + }); + + test('combines with declared properties', () => { + expect( + field({ + type: 'object', + properties: { x: { type: 'string' } }, + additionalProperties: { type: 'number' }, + }).node, + ).toEqual({ + kind: 'object', + props: [{ name: 'x', node: str, required: false, hasDefault: false }], + valueType: num, + open: true, + }); + }); + + test('must be a boolean or an object', () => { + const lifted = field({ type: 'object', additionalProperties: 42 }); + expect(lifted.node).toEqual(UNKNOWN); + expect(codes(lifted)).toEqual(['error /properties/f/additionalProperties malformed-additional-properties']); + }); + + test('reports inside the extra-key subschema at its own path', () => { + expect(codes(field({ type: 'object', additionalProperties: { type: 'strng' } }))).toEqual([ + 'error /properties/f/additionalProperties unknown-type-name', + ]); + }); +}); + +describe('arrays', () => { + test('without items hold unknown', () => { + const lifted = field({ type: 'array' }); + expect(lifted.node).toEqual({ kind: 'array', items: UNKNOWN }); + expectClean(lifted); + }); + + test('carry their element type', () => { + expect(field({ type: 'array', items: { type: 'string' } }).node).toEqual({ kind: 'array', items: str }); + }); + + test('a tuple degrades the element, not the array — a tuple is still an array', () => { + const lifted = field({ type: 'array', items: [{ type: 'string' }] }); + expect(lifted.node).toEqual({ kind: 'array', items: UNKNOWN }); + expect(codes(lifted)).toEqual(['warning /properties/f/items unsupported-tuple-items']); + }); + + test('items that are neither object nor array degrade the array', () => { + const lifted = field({ type: 'array', items: 7 }); + expect(lifted.node).toEqual(UNKNOWN); + expect(codes(lifted)).toEqual(['error /properties/f/items malformed-items']); + }); +}); + +describe('unsupported keywords', () => { + test.each(['oneOf', 'anyOf', 'allOf', 'not', 'if', 'then', 'else', '$ref', 'patternProperties'])( + '%s degrades the node', + (keyword) => { + const lifted = field({ type: 'string', [keyword]: {} }); + expect(lifted.node).toEqual(UNKNOWN); + expect(codes(lifted)).toEqual(['warning /properties/f unsupported-keyword']); + }, + ); + + test('reports every offending keyword in one diagnostic', () => { + const lifted = field({ oneOf: [], allOf: [] }); + expect(codes(lifted)).toEqual(['warning /properties/f unsupported-keyword']); + expect(lifted.diagnostics[0]!.message).toContain('oneOf, allOf'); + }); + + test('$defs alone is dead weight, not a warning', () => { + expectClean(field({ type: 'string', $defs: { x: { type: 'number' } } })); + }); +}); + +describe('extraneous keywords', () => { + test('are ignored in total silence — the core never sees Apify sugar', () => { + const lifted = field({ + type: 'string', + title: 'A title', + description: 'Prose with HTML', + editor: 'textfield', + prefill: 'x', + example: 'y', + pattern: '^a$', + minLength: 1, + maxLength: 9, + isSecret: true, + unit: 'ms', + sectionCaption: 'Section', + sectionDescription: 'More prose', + enumTitles: ['One'], + enumSuggestedValues: ['a', 'b'], + }); + expect(lifted.node).toEqual(str); + expectClean(lifted); + }); +}); diff --git a/test/local/lib/schema-to-ts/preprocess/dataset.test.ts b/test/local/lib/schema-to-ts/preprocess/dataset.test.ts new file mode 100644 index 000000000..66a9e966d --- /dev/null +++ b/test/local/lib/schema-to-ts/preprocess/dataset.test.ts @@ -0,0 +1,69 @@ +import { describe, expect, test } from 'vitest'; + +import { emit } from '../../../../../src/lib/schema-to-ts/emit.js'; +import { jsonSchemaToIR } from '../../../../../src/lib/schema-to-ts/json-schema-to-ir.js'; +import { normalizeDatasetSchema } from '../../../../../src/lib/schema-to-ts/preprocess/dataset.js'; + +describe('normalizeDatasetSchema', () => { + test('extracts fields and drops everything around it', () => { + const fields = { type: 'object', properties: { a: { type: 'string' } } }; + expect( + normalizeDatasetSchema({ + actorSpecification: 1, + fields, + views: { overview: { title: 'Overview', display: { component: 'table' } } }, + $schema: 'https://apify.com/schemas/v1/dataset.json', + }), + ).toEqual(fields); + }); + + test('applies the nullable rewrite — real dataset schemas use it too', () => { + expect( + normalizeDatasetSchema({ + fields: { + type: 'object', + properties: { a: { type: 'string', nullable: true } }, + additionalProperties: true, + nullable: true, + }, + }), + ).toEqual({ + type: ['object', 'null'], + properties: { a: { type: ['string', 'null'] } }, + additionalProperties: true, + }); + }); + + test('yields undefined when there is no fields key, which the core reports at the root', () => { + expect(normalizeDatasetSchema({ actorSpecification: 1 })).toBeUndefined(); + + const lifted = jsonSchemaToIR(normalizeDatasetSchema({ actorSpecification: 1 })); + expect(lifted.ir.root).toEqual({ kind: 'unknown' }); + expect(lifted.diagnostics).toEqual([ + { + path: '', + severity: 'error', + code: 'malformed-schema', + message: 'expected a JSON object, got undefined', + }, + ]); + }); + + test('passes non-objects through, so the diagnostic names what was actually there', () => { + for (const [raw, expected] of [ + [null, 'null'], + ['nope', 'string ("nope")'], + [42, 'number (42)'], + [[], 'an array'], + ] as const) { + expect(normalizeDatasetSchema(raw)).toEqual(raw); + const lifted = jsonSchemaToIR(normalizeDatasetSchema(raw)); + expect(lifted.diagnostics[0]?.message).toBe(`expected a JSON object, got ${expected}`); + } + }); + + test('does not care what fields contains — the core judges that', () => { + expect(normalizeDatasetSchema({ fields: 'garbage' })).toBe('garbage'); + expect(jsonSchemaToIR(normalizeDatasetSchema({ fields: 'garbage' })).diagnostics[0]?.code).toBe('malformed-schema'); + }); +}); diff --git a/test/local/lib/schema-to-ts/preprocess/input.test.ts b/test/local/lib/schema-to-ts/preprocess/input.test.ts new file mode 100644 index 000000000..5463fd63f --- /dev/null +++ b/test/local/lib/schema-to-ts/preprocess/input.test.ts @@ -0,0 +1,30 @@ +import { describe, expect, test } from 'vitest'; + +import { emit } from '../../../../../src/lib/schema-to-ts/emit.js'; +import { jsonSchemaToIR } from '../../../../../src/lib/schema-to-ts/json-schema-to-ir.js'; +import { normalizeInputSchema } from '../../../../../src/lib/schema-to-ts/preprocess/input.js'; + +describe('normalizeInputSchema', () => { + test('leaves the format sugar in place — the core ignores what it does not recognise', () => { + const schema = { + type: 'object', + schemaVersion: 1, + title: 'A title', + properties: { + a: { + type: 'string', + editor: 'textfield', + prefill: 'x', + example: 'y', + pattern: '^a$', + unit: 'ms', + isSecret: true, + sectionCaption: 'Section', + enumTitles: ['One'], + }, + }, + required: ['a'], + }; + expect(normalizeInputSchema(schema)).toEqual(schema); + }); +}); diff --git a/test/local/lib/schema-to-ts/preprocess/nullable.test.ts b/test/local/lib/schema-to-ts/preprocess/nullable.test.ts new file mode 100644 index 000000000..457794e02 --- /dev/null +++ b/test/local/lib/schema-to-ts/preprocess/nullable.test.ts @@ -0,0 +1,114 @@ +import { describe, expect, test } from 'vitest'; + +import { normalizeNullable } from '../../../../../src/lib/schema-to-ts/preprocess/nullable.js'; + +describe('normalizeNullable', () => { + test('widens the declared type and consumes the flag', () => { + expect(normalizeNullable({ type: 'string', nullable: true })).toEqual({ type: ['string', 'null'] }); + }); + + test('a false flag is dropped without widening anything', () => { + expect(normalizeNullable({ type: 'integer', nullable: false })).toEqual({ type: 'integer' }); + }); + + test('appends to an existing type array, and never twice', () => { + expect(normalizeNullable({ type: ['string', 'number'], nullable: true })).toEqual({ + type: ['string', 'number', 'null'], + }); + expect(normalizeNullable({ type: ['string', 'null'], nullable: true })).toEqual({ + type: ['string', 'null'], + }); + expect(normalizeNullable({ type: 'null', nullable: true })).toEqual({ type: 'null' }); + }); + + test('is idempotent', () => { + const once = normalizeNullable({ type: 'string', nullable: true }); + expect(normalizeNullable(once)).toEqual(once); + }); + + test('infers the type from siblings when it is absent, so nothing is silently lost', () => { + expect(normalizeNullable({ properties: { a: { type: 'string' } }, nullable: true })).toEqual({ + properties: { a: { type: 'string' } }, + type: ['object', 'null'], + }); + expect(normalizeNullable({ additionalProperties: { type: 'string' }, nullable: true })).toEqual({ + additionalProperties: { type: 'string' }, + type: ['object', 'null'], + }); + expect(normalizeNullable({ items: { type: 'string' }, nullable: true })).toEqual({ + items: { type: 'string' }, + type: ['array', 'null'], + }); + }); + + test('adds no type key to a bare schema, which already admits null', () => { + expect(normalizeNullable({ nullable: true })).toEqual({}); + expect(normalizeNullable({ nullable: true })).not.toHaveProperty('type'); + }); + + test('leaves a malformed type alone for the core to report', () => { + expect(normalizeNullable({ type: 42, nullable: true })).toEqual({ type: 42 }); + }); + + test('recurses through every subschema position', () => { + expect( + normalizeNullable({ + type: 'object', + properties: { + a: { type: 'string', nullable: true }, + b: { type: 'array', items: { type: 'number', nullable: true } }, + c: { type: 'object', additionalProperties: { type: 'boolean', nullable: true } }, + d: { type: 'object', properties: { deep: { type: 'string', nullable: true } } }, + }, + }), + ).toEqual({ + type: 'object', + properties: { + a: { type: ['string', 'null'] }, + b: { type: 'array', items: { type: ['number', 'null'] } }, + c: { type: 'object', additionalProperties: { type: ['boolean', 'null'] } }, + d: { type: 'object', properties: { deep: { type: ['string', 'null'] } } }, + }, + }); + }); + + test('recurses into positional items members', () => { + expect(normalizeNullable({ type: 'array', items: [{ type: 'string', nullable: true }] })).toEqual({ + type: 'array', + items: [{ type: ['string', 'null'] }], + }); + }); + + test('never rewrites data — a `nullable` key inside an example is not a schema', () => { + const schema = { + type: 'object', + properties: { a: { type: 'string' } }, + default: { nullable: true, type: 'string' }, + example: { nullable: true }, + prefill: [{ nullable: true }], + enum: [{ nullable: true }], + }; + expect(normalizeNullable(schema)).toEqual(schema); + }); + + test('preserves property order', () => { + const result = normalizeNullable({ + type: 'object', + properties: { z: { type: 'string' }, a: { type: 'string', nullable: true }, m: { type: 'string' } }, + }) as { properties: Record }; + expect(Object.keys(result.properties)).toEqual(['z', 'a', 'm']); + }); + + test("does not mutate the caller's object", () => { + const schema = { type: 'string', nullable: true, properties: { a: { type: 'string', nullable: true } } }; + const before = structuredClone(schema); + normalizeNullable(schema); + expect(schema).toEqual(before); + }); + + test('passes non-objects through', () => { + for (const value of [null, undefined, 42, 'nope', true]) { + expect(normalizeNullable(value)).toBe(value); + } + }); +}); diff --git a/test/tsconfig.json b/test/tsconfig.json index 6fbb8d11b..209047cb9 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -5,7 +5,7 @@ "allowJs": true, "checkJs": false, "noEmit": true, - "rootDir": "." + "rootDir": ".." }, - "include": ["./**/*.ts", "./**/*.js", "../src"] + "include": ["./**/*.ts", "./**/*.js", "../src/**/*.ts"] } From 33f0fee25cecfb193f7a28ef68150a384ddcf930 Mon Sep 17 00:00:00 2001 From: JuanGalilea Date: Wed, 2 Sep 2026 11:49:31 +0200 Subject: [PATCH 4/7] copy fixtures --- .../schema-to-ts/dataset/comment-scraper.json | 165 +++++++++++++++ .../dataset/tiktok-followers-scraper.json | 188 ++++++++++++++++++ .../expected/dataset/comment-scraper.ts | 68 +++++++ .../dataset/tiktok-followers-scraper.ts | 148 ++++++++++++++ .../expected/input/api-scraper.ts | 24 +++ .../expected/input/comment-scraper.ts | 14 ++ .../input/free-amazon-product-scraper.ts | 22 ++ .../lib/schema-to-ts/input/api-scraper.json | 89 +++++++++ .../schema-to-ts/input/comment-scraper.json | 36 ++++ .../input/free-amazon-product-scraper.json | 73 +++++++ .../schema-to-ts/kvstore/maps-to-polygon.json | 60 ++++++ 11 files changed, 887 insertions(+) create mode 100644 test/local/__fixtures__/lib/schema-to-ts/dataset/comment-scraper.json create mode 100644 test/local/__fixtures__/lib/schema-to-ts/dataset/tiktok-followers-scraper.json create mode 100644 test/local/__fixtures__/lib/schema-to-ts/expected/dataset/comment-scraper.ts create mode 100644 test/local/__fixtures__/lib/schema-to-ts/expected/dataset/tiktok-followers-scraper.ts create mode 100644 test/local/__fixtures__/lib/schema-to-ts/expected/input/api-scraper.ts create mode 100644 test/local/__fixtures__/lib/schema-to-ts/expected/input/comment-scraper.ts create mode 100644 test/local/__fixtures__/lib/schema-to-ts/expected/input/free-amazon-product-scraper.ts create mode 100644 test/local/__fixtures__/lib/schema-to-ts/input/api-scraper.json create mode 100644 test/local/__fixtures__/lib/schema-to-ts/input/comment-scraper.json create mode 100644 test/local/__fixtures__/lib/schema-to-ts/input/free-amazon-product-scraper.json create mode 100644 test/local/__fixtures__/lib/schema-to-ts/kvstore/maps-to-polygon.json diff --git a/test/local/__fixtures__/lib/schema-to-ts/dataset/comment-scraper.json b/test/local/__fixtures__/lib/schema-to-ts/dataset/comment-scraper.json new file mode 100644 index 000000000..a648d85c3 --- /dev/null +++ b/test/local/__fixtures__/lib/schema-to-ts/dataset/comment-scraper.json @@ -0,0 +1,165 @@ +{ + "actorSpecification": 1, + "fields": { + "type": "object", + "properties": { + "postUrl": { + "type": ["string", "null"], + "examples": ["https://www.instagram.com/p/ABCdef1234g/"], + "description": "URL of the Instagram post containing the comment" + }, + "commentUrl": { + "type": ["string", "null"], + "examples": ["https://www.instagram.com/p/ABCdef1234g/c/17987654321/"], + "description": "Direct URL to the specific comment" + }, + "id": { + "type": ["string", "null"], + "examples": ["17987654321"], + "description": "Unique identifier of the comment" + }, + "text": { + "type": ["string", "null"], + "examples": ["Great post! Love this content."], + "description": "Text content of the comment" + }, + "ownerUsername": { + "type": ["string", "null"], + "examples": ["exampleuser"], + "description": "Username of the comment author" + }, + "ownerProfilePicUrl": { + "type": ["string", "null"], + "examples": ["https://example.com/profile/pic/123456.jpg"], + "description": "Profile picture URL of the comment author" + }, + "timestamp": { + "type": ["string", "null"], + "examples": ["2025-01-15T10:30:00.000Z"], + "description": "ISO 8601 timestamp when the comment was posted" + }, + "likesCount": { + "type": ["number", "null"], + "examples": [42], + "description": "Number of likes on the comment" + }, + "owner": { + "examples": [ + { + "id": "123456789", + "username": "exampleuser", + "is_verified": false + } + ], + "description": "Detailed information about the comment author", + "properties": { + "fbid_v2": { + "type": ["string", "null"] + }, + "id": { + "type": ["string", "null"] + }, + "is_verified": { + "type": ["boolean", "null"] + }, + "profile_pic_url": { + "type": ["string", "null"] + }, + "username": { + "type": ["string", "null"] + }, + "full_name": { + "type": ["string", "null"] + }, + "is_mentionable": { + "type": ["boolean", "null"] + }, + "is_private": { + "type": ["boolean", "null"] + }, + "latest_reel_media": { + "type": ["number", "null"] + }, + "profile_pic_id": { + "type": ["string", "null"] + } + }, + "type": ["object", "null"] + }, + "url": { + "type": ["string", "null"], + "examples": ["https://www.instagram.com/p/ABCdef1234g/"], + "description": "URL associated with the comment or post" + }, + "parentCommentUrl": { + "type": ["string", "null"], + "examples": ["https://www.instagram.com/p/ABCdef1234g/c/17987654321/"], + "description": "URL of the parent comment" + }, + "requestErrorMessages": { + "examples": [["HTTP 404 NOT FOUND"]], + "description": "Array of error messages encountered during scraping", + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "error": { + "type": ["string", "null"], + "examples": ["Failed to load comment data"], + "description": "Error message if comment scraping failed" + }, + "errorDescription": { + "type": ["string", "null"], + "examples": ["The comment may have been deleted or the post is private"], + "description": "Detailed description of the error that occurred" + }, + "repliesCount": { + "type": ["number", "null"], + "examples": [5], + "description": "Number of replies to this comment" + }, + "replies": { + "examples": [[]], + "description": "Replies to this comment", + "items": { + "type": "object", + "additionalProperties": {} + }, + "type": ["array", "null"] + } + }, + "additionalProperties": {} + }, + "views": { + "overview": { + "title": "Overview", + "description": "", + "transformation": { + "fields": ["id", "text", "timestamp", "ownerUsername", "ownerProfilePicUrl", "postUrl"] + }, + "display": { + "component": "table", + "properties": { + "id": { + "label": "Comment ID", + "format": "text" + }, + "ownerUsername": { + "label": "Commenter", + "format": "text" + }, + "ownerProfilePicUrl": { + "label": "Commenter profile picture", + "format": "image" + }, + "timestamp": { + "label": "Commented on", + "format": "text" + } + } + } + } + }, + "$schema": "https://apify.com/schemas/v1/dataset.json" +} diff --git a/test/local/__fixtures__/lib/schema-to-ts/dataset/tiktok-followers-scraper.json b/test/local/__fixtures__/lib/schema-to-ts/dataset/tiktok-followers-scraper.json new file mode 100644 index 000000000..2ae5b0306 --- /dev/null +++ b/test/local/__fixtures__/lib/schema-to-ts/dataset/tiktok-followers-scraper.json @@ -0,0 +1,188 @@ +{ + "actorSpecification": 1, + "fields": { + "type": "object", + "properties": { + "authorMeta": { + "type": "object", + "description": "Metadata about the user whose connections are being scraped", + "properties": { + "id": { "type": "string", "nullable": true }, + "name": { "type": "string", "nullable": true }, + "profileUrl": { "type": "string", "nullable": true }, + "verified": { "type": "boolean", "nullable": true }, + "privateAccount": { "type": "boolean", "nullable": true }, + "nickName": { "type": "string", "nullable": true }, + "avatar": { "type": "string", "nullable": true }, + "signature": { "type": "string", "nullable": true }, + "bioLink": { "type": "string", "nullable": true }, + "region": { "type": "string", "nullable": true }, + "following": { "type": "number", "nullable": true }, + "fans": { "type": "number", "nullable": true }, + "video": { "type": "number", "nullable": true }, + "heart": { "type": "number", "nullable": true }, + "digg": { "type": "number", "nullable": true }, + "friends": { "type": "number", "nullable": true }, + "commerceUserInfo": { + "type": "object", + "properties": { + "commerceUser": { "type": "boolean", "nullable": true }, + "category": { "type": "string", "nullable": true } + }, + "required": [], + "additionalProperties": true, + "nullable": true + }, + "isUnderAge18": { "type": "boolean", "nullable": true }, + "roomId": { "type": "string", "nullable": true }, + "ttSeller": { "type": "boolean", "nullable": true }, + "createTime": { "type": "number", "nullable": true }, + "followDatasetUrl": { "type": "string", "nullable": true }, + "originalAvatarUrl": { "type": "string", "nullable": true } + }, + "required": [], + "additionalProperties": true, + "nullable": true, + "example": { + "id": "6784642169778881542", + "name": "exampleuser", + "profileUrl": "https://www.tiktok.com/@exampleuser", + "nickName": "Example User", + "verified": false, + "fans": 12500, + "video": 48, + "avatar": "https://p16-sign.tiktokcdn-us.com/example-avatar.jpg" + } + }, + "connectedTo": { + "type": "object", + "description": "Metadata about the connected user (follower or following)", + "properties": { + "id": { "type": "string", "nullable": true }, + "name": { "type": "string", "nullable": true }, + "profileUrl": { "type": "string", "nullable": true }, + "verified": { "type": "boolean", "nullable": true }, + "privateAccount": { "type": "boolean", "nullable": true }, + "nickName": { "type": "string", "nullable": true }, + "avatar": { "type": "string", "nullable": true }, + "signature": { "type": "string", "nullable": true }, + "bioLink": { "type": "string", "nullable": true }, + "region": { "type": "string", "nullable": true }, + "following": { "type": "number", "nullable": true }, + "fans": { "type": "number", "nullable": true }, + "video": { "type": "number", "nullable": true }, + "heart": { "type": "number", "nullable": true }, + "digg": { "type": "number", "nullable": true }, + "friends": { "type": "number", "nullable": true }, + "commerceUserInfo": { + "type": "object", + "properties": { + "commerceUser": { "type": "boolean", "nullable": true }, + "category": { "type": "string", "nullable": true } + }, + "required": [], + "additionalProperties": true, + "nullable": true + }, + "isUnderAge18": { "type": "boolean", "nullable": true }, + "roomId": { "type": "string", "nullable": true }, + "ttSeller": { "type": "boolean", "nullable": true }, + "createTime": { "type": "number", "nullable": true }, + "followDatasetUrl": { "type": "string", "nullable": true }, + "originalAvatarUrl": { "type": "string", "nullable": true } + }, + "required": [], + "additionalProperties": true, + "nullable": true, + "example": { + "id": "9876543210987654321", + "name": "sampleuser", + "profileUrl": "https://www.tiktok.com/@sampleuser", + "nickName": "Sample User", + "verified": false, + "fans": 5000, + "video": 20, + "avatar": "https://p16-sign.tiktokcdn-us.com/sample-avatar.jpg" + } + }, + "connectionType": { + "type": "string", + "description": "Type of connection: whether the connected user is a follower or someone being followed", + "nullable": true, + "example": "FOLLOWER" + }, + "connectionDescription": { + "type": "string", + "description": "Human-readable description of the connection relationship", + "nullable": true, + "example": "sampleuser follows exampleuser" + } + }, + "required": [], + "additionalProperties": true, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "views": { + "overview": { + "title": "Overview 🔎", + "description": "", + "transformation": { + "fields": [ + "authorMeta.name", + "connectedTo.avatar", + "connectedTo.name", + "connectedTo.nickName", + "connectedTo.verified", + "connectedTo.fans", + "connectedTo.video", + "connectedTo.signature", + "connectedTo.bioLink", + "connectionType", + "connectionDescription" + ], + "flatten": ["authorMeta", "connectedTo"] + }, + "display": { + "component": "table", + "properties": { + "authorMeta.name": { + "label": "Source User" + }, + "connectedTo.avatar": { + "label": "Avatar", + "format": "image" + }, + "connectedTo.name": { + "label": "Username" + }, + "connectedTo.nickName": { + "label": "Nickname" + }, + "connectedTo.verified": { + "label": "Verified?" + }, + "connectedTo.fans": { + "label": "Followers", + "format": "number" + }, + "connectedTo.video": { + "label": "Videos", + "format": "number" + }, + "connectedTo.signature": { + "label": "Bio" + }, + "connectedTo.bioLink": { + "label": "Bio Link" + }, + "connectionType": { + "label": "Connection Type" + }, + "connectionDescription": { + "label": "Description" + } + } + } + } + } +} diff --git a/test/local/__fixtures__/lib/schema-to-ts/expected/dataset/comment-scraper.ts b/test/local/__fixtures__/lib/schema-to-ts/expected/dataset/comment-scraper.ts new file mode 100644 index 000000000..8490938a7 --- /dev/null +++ b/test/local/__fixtures__/lib/schema-to-ts/expected/dataset/comment-scraper.ts @@ -0,0 +1,68 @@ +// oxlint-disable +// @generated schema-ts v1-55348b59cbce2e42 — do not edit + +export type Comment = { + postUrl?: string | null | undefined; + commentUrl?: string | null | undefined; + id?: string | null | undefined; + text?: string | null | undefined; + ownerUsername?: string | null | undefined; + ownerProfilePicUrl?: string | null | undefined; + timestamp?: string | null | undefined; + likesCount?: number | null | undefined; + owner?: + | { + fbid_v2?: string | null | undefined; + id?: string | null | undefined; + is_verified?: boolean | null | undefined; + profile_pic_url?: string | null | undefined; + username?: string | null | undefined; + full_name?: string | null | undefined; + is_mentionable?: boolean | null | undefined; + is_private?: boolean | null | undefined; + latest_reel_media?: number | null | undefined; + profile_pic_id?: string | null | undefined; + } + | null + | undefined; + url?: string | null | undefined; + parentCommentUrl?: string | null | undefined; + requestErrorMessages?: Array | null | undefined; + error?: string | null | undefined; + errorDescription?: string | null | undefined; + repliesCount?: number | null | undefined; + replies?: Array> | null | undefined; +}; + +export type CommentDraft = { + postUrl?: string | null | undefined; + commentUrl?: string | null | undefined; + id?: string | null | undefined; + text?: string | null | undefined; + ownerUsername?: string | null | undefined; + ownerProfilePicUrl?: string | null | undefined; + timestamp?: string | null | undefined; + likesCount?: number | null | undefined; + owner?: + | ({ + fbid_v2?: string | null | undefined; + id?: string | null | undefined; + is_verified?: boolean | null | undefined; + profile_pic_url?: string | null | undefined; + username?: string | null | undefined; + full_name?: string | null | undefined; + is_mentionable?: boolean | null | undefined; + is_private?: boolean | null | undefined; + latest_reel_media?: number | null | undefined; + profile_pic_id?: string | null | undefined; + } & Record) + | null + | undefined; + url?: string | null | undefined; + parentCommentUrl?: string | null | undefined; + requestErrorMessages?: Array | null | undefined; + error?: string | null | undefined; + errorDescription?: string | null | undefined; + repliesCount?: number | null | undefined; + replies?: Array> | null | undefined; +} & Record; diff --git a/test/local/__fixtures__/lib/schema-to-ts/expected/dataset/tiktok-followers-scraper.ts b/test/local/__fixtures__/lib/schema-to-ts/expected/dataset/tiktok-followers-scraper.ts new file mode 100644 index 000000000..87715ba09 --- /dev/null +++ b/test/local/__fixtures__/lib/schema-to-ts/expected/dataset/tiktok-followers-scraper.ts @@ -0,0 +1,148 @@ +// oxlint-disable +// @generated schema-ts v1-7da5060298a63eb7 — do not edit + +export type Connection = { + authorMeta?: + | { + id?: string | null | undefined; + name?: string | null | undefined; + profileUrl?: string | null | undefined; + verified?: boolean | null | undefined; + privateAccount?: boolean | null | undefined; + nickName?: string | null | undefined; + avatar?: string | null | undefined; + signature?: string | null | undefined; + bioLink?: string | null | undefined; + region?: string | null | undefined; + following?: number | null | undefined; + fans?: number | null | undefined; + video?: number | null | undefined; + heart?: number | null | undefined; + digg?: number | null | undefined; + friends?: number | null | undefined; + commerceUserInfo?: + | { + commerceUser?: boolean | null | undefined; + category?: string | null | undefined; + } + | null + | undefined; + isUnderAge18?: boolean | null | undefined; + roomId?: string | null | undefined; + ttSeller?: boolean | null | undefined; + createTime?: number | null | undefined; + followDatasetUrl?: string | null | undefined; + originalAvatarUrl?: string | null | undefined; + } + | null + | undefined; + connectedTo?: + | { + id?: string | null | undefined; + name?: string | null | undefined; + profileUrl?: string | null | undefined; + verified?: boolean | null | undefined; + privateAccount?: boolean | null | undefined; + nickName?: string | null | undefined; + avatar?: string | null | undefined; + signature?: string | null | undefined; + bioLink?: string | null | undefined; + region?: string | null | undefined; + following?: number | null | undefined; + fans?: number | null | undefined; + video?: number | null | undefined; + heart?: number | null | undefined; + digg?: number | null | undefined; + friends?: number | null | undefined; + commerceUserInfo?: + | { + commerceUser?: boolean | null | undefined; + category?: string | null | undefined; + } + | null + | undefined; + isUnderAge18?: boolean | null | undefined; + roomId?: string | null | undefined; + ttSeller?: boolean | null | undefined; + createTime?: number | null | undefined; + followDatasetUrl?: string | null | undefined; + originalAvatarUrl?: string | null | undefined; + } + | null + | undefined; + connectionType?: string | null | undefined; + connectionDescription?: string | null | undefined; +}; + +export type ConnectionDraft = { + authorMeta?: + | ({ + id?: string | null | undefined; + name?: string | null | undefined; + profileUrl?: string | null | undefined; + verified?: boolean | null | undefined; + privateAccount?: boolean | null | undefined; + nickName?: string | null | undefined; + avatar?: string | null | undefined; + signature?: string | null | undefined; + bioLink?: string | null | undefined; + region?: string | null | undefined; + following?: number | null | undefined; + fans?: number | null | undefined; + video?: number | null | undefined; + heart?: number | null | undefined; + digg?: number | null | undefined; + friends?: number | null | undefined; + commerceUserInfo?: + | ({ + commerceUser?: boolean | null | undefined; + category?: string | null | undefined; + } & Record) + | null + | undefined; + isUnderAge18?: boolean | null | undefined; + roomId?: string | null | undefined; + ttSeller?: boolean | null | undefined; + createTime?: number | null | undefined; + followDatasetUrl?: string | null | undefined; + originalAvatarUrl?: string | null | undefined; + } & Record) + | null + | undefined; + connectedTo?: + | ({ + id?: string | null | undefined; + name?: string | null | undefined; + profileUrl?: string | null | undefined; + verified?: boolean | null | undefined; + privateAccount?: boolean | null | undefined; + nickName?: string | null | undefined; + avatar?: string | null | undefined; + signature?: string | null | undefined; + bioLink?: string | null | undefined; + region?: string | null | undefined; + following?: number | null | undefined; + fans?: number | null | undefined; + video?: number | null | undefined; + heart?: number | null | undefined; + digg?: number | null | undefined; + friends?: number | null | undefined; + commerceUserInfo?: + | ({ + commerceUser?: boolean | null | undefined; + category?: string | null | undefined; + } & Record) + | null + | undefined; + isUnderAge18?: boolean | null | undefined; + roomId?: string | null | undefined; + ttSeller?: boolean | null | undefined; + createTime?: number | null | undefined; + followDatasetUrl?: string | null | undefined; + originalAvatarUrl?: string | null | undefined; + } & Record) + | null + | undefined; + connectionType?: string | null | undefined; + connectionDescription?: string | null | undefined; +} & Record; diff --git a/test/local/__fixtures__/lib/schema-to-ts/expected/input/api-scraper.ts b/test/local/__fixtures__/lib/schema-to-ts/expected/input/api-scraper.ts new file mode 100644 index 000000000..cc23f733e --- /dev/null +++ b/test/local/__fixtures__/lib/schema-to-ts/expected/input/api-scraper.ts @@ -0,0 +1,24 @@ +// oxlint-disable +// @generated schema-ts v1-5df1437f19b33bed — do not edit + +export type Input = { + directUrls?: Array | undefined; + resultsType: 'posts' | 'comments' | 'details' | 'mentions' | 'reels' | 'stories'; + resultsLimit?: number | undefined; + onlyPostsNewerThan?: string | undefined; + search?: string | undefined; + searchType: 'user' | 'hashtag' | 'place'; + searchLimit?: number | undefined; + addParentData: boolean; +}; + +export type InputArgs = { + directUrls?: Array | undefined; + resultsType?: 'posts' | 'comments' | 'details' | 'mentions' | 'reels' | 'stories' | undefined; + resultsLimit?: number | undefined; + onlyPostsNewerThan?: string | undefined; + search?: string | undefined; + searchType?: 'user' | 'hashtag' | 'place' | undefined; + searchLimit?: number | undefined; + addParentData?: boolean | undefined; +} & Record; diff --git a/test/local/__fixtures__/lib/schema-to-ts/expected/input/comment-scraper.ts b/test/local/__fixtures__/lib/schema-to-ts/expected/input/comment-scraper.ts new file mode 100644 index 000000000..a1136b5f1 --- /dev/null +++ b/test/local/__fixtures__/lib/schema-to-ts/expected/input/comment-scraper.ts @@ -0,0 +1,14 @@ +// oxlint-disable +// @generated schema-ts v1-d1bb2b62dfb769b2 — do not edit + +export type Input = { + directUrls: Array; + resultsLimit?: number | undefined; + includeNestedComments?: boolean | undefined; +}; + +export type InputArgs = { + directUrls: Array; + resultsLimit?: number | undefined; + includeNestedComments?: boolean | undefined; +} & Record; diff --git a/test/local/__fixtures__/lib/schema-to-ts/expected/input/free-amazon-product-scraper.ts b/test/local/__fixtures__/lib/schema-to-ts/expected/input/free-amazon-product-scraper.ts new file mode 100644 index 000000000..24f355c2c --- /dev/null +++ b/test/local/__fixtures__/lib/schema-to-ts/expected/input/free-amazon-product-scraper.ts @@ -0,0 +1,22 @@ +// oxlint-disable +// @generated schema-ts v1-25c4855449863324 — do not edit + +export type Input = { + categoryUrls: Array; + maxItemsPerStartUrl?: number | undefined; + maxSearchPagesPerStartUrl?: number | undefined; + maxProductVariantsAsSeparateResults?: number | undefined; + useCaptchaSolver: boolean; + scrapeProductVariantPrices: boolean; + scrapeProductDetails: boolean | null; +}; + +export type InputArgs = { + categoryUrls: Array; + maxItemsPerStartUrl?: number | undefined; + maxSearchPagesPerStartUrl?: number | undefined; + maxProductVariantsAsSeparateResults?: number | undefined; + useCaptchaSolver?: boolean | undefined; + scrapeProductVariantPrices?: boolean | undefined; + scrapeProductDetails?: boolean | null | undefined; +} & Record; diff --git a/test/local/__fixtures__/lib/schema-to-ts/input/api-scraper.json b/test/local/__fixtures__/lib/schema-to-ts/input/api-scraper.json new file mode 100644 index 000000000..b7a52173a --- /dev/null +++ b/test/local/__fixtures__/lib/schema-to-ts/input/api-scraper.json @@ -0,0 +1,89 @@ +{ + "title": "Input schema for Instagram scraper", + "type": "object", + "description": "Enter Instagram URLs and get posts, comments or page details from them.
Alternatively set up the actor to search Instagram for profiles, hashtags or places. If you need any help, follow [this tutorial](https://blog.apify.com/scrape-instagram-posts-comments-and-more-21d05506aeb3/).", + "schemaVersion": 1, + "properties": { + "directUrls": { + "title": "Instagram URLs you want to scrape", + "type": "array", + "description": "Add one or more Instagram URLs to scrape. The field is optional, but you need to either use this field or search query below.", + "editor": "stringList", + "placeholderValue": "URL", + "prefill": ["https://www.instagram.com/humansofny/"], + "items": { + "type": "string", + "pattern": "https:\\/\\/(www\\.)?instagram\\.com\\/.+" + }, + "uniqueItems": true + }, + "resultsType": { + "title": "What do you want to scrape from each page?", + "type": "string", + "description": "You can choose to get posts, comments or details from Instagram URLs. Comments can only be scraped from post URLs.
❗Please note that the stories type has been deprecated. It used to return reels data, which wasn’t aligned with its purpose. Please use reels instead.", + "editor": "select", + "enum": ["posts", "comments", "details", "mentions", "reels", "stories"], + "enumTitles": [ + "Scrape posts", + "Scrape comments", + "Scrape details of a profile, post, hashtag or place", + "Scrape profile mentions", + "Scrape profile reels", + " " + ], + "default": "posts", + "prefill": "posts" + }, + "resultsLimit": { + "title": "Max results per URL", + "type": "integer", + "description": "How many posts or comments (max 50 comments per post) you want to scrape from each Instagram URL. If you set this to 1, you will get a single post from each page.", + "editor": "number", + "prefill": 200, + "minimum": 1 + }, + "onlyPostsNewerThan": { + "title": "Newer than", + "type": "string", + "description": "Limit how far back to the history the scraper should go. The date should be in YYYY-MM-DD or full ISO absolute format or in relative format e.g. 1 days, 2 months, 3 years. All time values are taken in UTC timezone", + "editor": "datepicker", + "dateType": "absoluteOrRelative", + "pattern": "^(\\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])(T[0-2]\\d:[0-5]\\d(:[0-5]\\d)?(\\.\\d+)?Z?)?$|^(\\d+)\\s*(minute|hour|day|week|month|year)s?$" + }, + "search": { + "title": "Search query", + "type": "string", + "description": "Provide a search query which will be used to search Instagram for profiles, hashtags or places.", + "editor": "textfield", + "sectionCaption": "Scrape based on search query instead of URL", + "sectionDescription": "Instagram search is available indirectly. The scraper gets the data combining results from Instagram (or Facebook Ads) and Google search. For that reason results might be slightly different from what you see on Instagram as a logged in user." + }, + "searchType": { + "title": "Search type", + "type": "string", + "description": "What type of pages to search for (you can look for hashtags, profiles or places).", + "editor": "select", + "enum": ["user", "hashtag", "place"], + "enumTitles": ["Search users", "Search hashtags", "Search places"], + "default": "hashtag", + "prefill": "hashtag" + }, + "searchLimit": { + "title": "Search results limit", + "type": "integer", + "description": "How many search results (hashtags, users or places) should be returned.", + "editor": "number", + "prefill": 1, + "minimum": 1, + "maximum": 250 + }, + "addParentData": { + "title": "Add metadata", + "type": "boolean", + "description": "Only for feed items - add data source to results, i.e. for profile posts metadata is profile, for tag posts metadata is hashtag", + "editor": "hidden", + "default": false + } + }, + "required": [] +} diff --git a/test/local/__fixtures__/lib/schema-to-ts/input/comment-scraper.json b/test/local/__fixtures__/lib/schema-to-ts/input/comment-scraper.json new file mode 100644 index 000000000..a7f303bb9 --- /dev/null +++ b/test/local/__fixtures__/lib/schema-to-ts/input/comment-scraper.json @@ -0,0 +1,36 @@ +{ + "title": "Input schema for Instagram Comments scraper", + "type": "object", + "description": "To scrape comments from Instagram posts or reels, just paste one or multiple Instagram URLs, choose the number of comments, click on ▷ Start and your data is on its way! If you need any guidance, just follow this video tutorial.

⚠️ Free usage gets only the top 15 comments, sorted by newest. Upgrade to Starter plan for full access.
📅 If you need to scrape comments filtered by date, use our Instagram Scraper instead.", + "schemaVersion": 1, + "properties": { + "directUrls": { + "title": "🔗 Instagram posts or reels URLs", + "type": "array", + "description": "Add one or multiple URLs to scrape comments from - posts or reels. You can add URLs one by one or upload a list using the Bulk edit option.", + "editor": "stringList", + "placeholderValue": "URL", + "prefill": ["https://www.instagram.com/p/DN8-GjPkgjS", "https://www.instagram.com/reel/DDIJAfeyemG"], + "items": { + "type": "string", + "pattern": "https?:\\/\\/(?:www\\.)?instagram\\.com\\/(?!reels\\/audio\\/)(?:(?:[^!@#$%^&*(){},'\"\\/\\s`\\\\=-]+\\/)?(?:p|reel)|reels)\\/[^\\/]+" + }, + "uniqueItems": true + }, + "resultsLimit": { + "title": "💯 Up to [number] of comments", + "type": "integer", + "description": "Set the number of comments you expect to scrape from each post or reel.

If set to 5, you will get 5 comments per URL. If you add 2 URLs, you will extract 10 results altogether.
❗️ If you choose to scrape replies as well, the total number will be greater than you set for comments.", + "editor": "number", + "prefill": 15, + "minimum": 1 + }, + "includeNestedComments": { + "title": "$ Include replies", + "type": "boolean", + "description": "This feature is for paying users only. If checked, the scraper will extract replies for each comment.
⚠️ Note that each reply/comment will be displayed as a separate result, so you'll get more results in total than you set in `resultsLimit` section above.", + "editor": "checkbox" + } + }, + "required": ["directUrls"] +} diff --git a/test/local/__fixtures__/lib/schema-to-ts/input/free-amazon-product-scraper.json b/test/local/__fixtures__/lib/schema-to-ts/input/free-amazon-product-scraper.json new file mode 100644 index 000000000..ffa292183 --- /dev/null +++ b/test/local/__fixtures__/lib/schema-to-ts/input/free-amazon-product-scraper.json @@ -0,0 +1,73 @@ +{ + "title": "Input schema for the store-amazon-categories actor.", + "type": "object", + "schemaVersion": 1, + "description": "Amazon sometimes restricts search terms to maximum of 7 pages, please validate the number of pages in the URL. To overcome this limit, try to use one of the regular categories and combine it with the search terms. E.g. instead of just searching for `Diamond rings`, find `Jewelry` or `Women's rings` category and combine them with the search term.", + "properties": { + "categoryUrls": { + "title": "Urls of Amazon categories", + "type": "array", + "description": "Enter urls of Amazon categories. You can also use specific product URLs directly.", + "editor": "requestListSources", + "prefill": [{ "url": "https://www.amazon.com/s?k=keyboard" }], + "example": [{ "url": "https://www.amazon.com/s?k=keyboard" }], + "uniqueItems": true + }, + "maxItemsPerStartUrl": { + "title": "Max results (per start URL)", + "type": "integer", + "description": "Enter the maximum number of results you want to scrape per each star URL. This will let the scraper know when to stop.", + "minimum": 0, + "prefill": 100, + "example": 100, + "unit": "results", + "nullable": false + }, + "maxSearchPagesPerStartUrl": { + "sectionCaption": "Other Settings", + "sectionDescription": "These settings are not required for the scraper to run, but they can help you get better results.", + "title": "Maximum search pages (per start URL)", + "type": "integer", + "description": "Enter the maximum number of search pages to scrape for each start URL. E.g. only scrape the first 5 pages.\n\nThis field can also be used with the `maxItemsPerStartUrl` option, the scraper will stop after either one of these limits is reached.", + "minimum": 1, + "prefill": 9999, + "example": 9999, + "unit": "pages", + "nullable": false + }, + "maxProductVariantsAsSeparateResults": { + "title": "Maximum product variants per product (as separate results)", + "type": "integer", + "description": "Enter the maximum number of product variants you want to scrape per each product, outputted as separate results.\n\n**Please beware** that **you can already get** most of the **variant details** changes, in the **`variantDetails` field**, which is outputted by default.\n\nNote that this **will increase the number of requests** and **extend the scraping time**.", + "minimum": 0, + "prefill": 0, + "example": 0, + "unit": "variants", + "nullable": false + }, + "useCaptchaSolver": { + "title": "Use Captcha solver (warning: see description)", + "type": "boolean", + "description": "If enabled the scraper will automatically solve captchas thrown by Amazon. This will decrease the amount of request retries and increase the speed of the scraper.

***IMPORTANT*** - This option works well only for the `'.com'` Amazon domain, but even for that one Amazon doesn't show a few product fields after solving a captcha (specifically: 'attributes', 'manufacturer attributes', and 'bestseller ranks')", + "default": false, + "example": false + }, + "scrapeProductVariantPrices": { + "title": "Scrape product variant prices", + "type": "boolean", + "description": "Enable this option to extract prices of different variations of a product. Useful when you need prices for each variant.\n\nNote that this **will increase the number of requests** and **extend the scraping time**.", + "default": false, + "example": false + }, + "scrapeProductDetails": { + "title": "Scrape product details", + "type": "boolean", + "description": "If enabled, the scraper will extract each found product from the category page in detail (this is the default behavior).\n\nIf disabled, the scraper will only extract the quick product information from the category page. Useful for faster and more lightweight searches for products.", + "editor": "hidden", + "default": true, + "example": true, + "nullable": true + } + }, + "required": ["categoryUrls"] +} diff --git a/test/local/__fixtures__/lib/schema-to-ts/kvstore/maps-to-polygon.json b/test/local/__fixtures__/lib/schema-to-ts/kvstore/maps-to-polygon.json new file mode 100644 index 000000000..174766118 --- /dev/null +++ b/test/local/__fixtures__/lib/schema-to-ts/kvstore/maps-to-polygon.json @@ -0,0 +1,60 @@ +{ + "$schema": "https://apify.com/schemas/v1/key-value-store.json", + "actorKeyValueStoreSchemaVersion": 1, + "title": "Maps to Polygon", + "description": "Maps to Polygon key-value store", + "collections": { + "meme": { + "key": "meme", + "title": "Meme", + "description": "Meme search result", + "jsonSchema": { + "type": "object", + "properties": { + "url": { "type": "string" }, + "topLeft": { + "type": "object", + "properties": { "x": { "type": "number" }, "y": { "type": "number" } } + }, + "bottomRight": { + "type": "object", + "properties": { "x": { "type": "number" }, "y": { "type": "number" } } + } + }, + "required": ["url", "topLeft", "bottomRight"], + "additionalProperties": false + }, + "contentTypes": ["application/json"] + }, + "initial": { + "key": "initial.png", + "title": "Initial Screenshot", + "description": "The initial screenshot of the search result", + "contentTypes": ["image/png"] + }, + "cropped": { + "key": "cropped.png", + "title": "Map Area", + "description": "The cropped screenshot of the search result", + "contentTypes": ["image/png"] + }, + "binary": { + "key": "binary.png", + "title": "Binary Image", + "description": "Binary image that should only show the outline", + "contentTypes": ["image/png"] + }, + "gift_wrapping": { + "key": "gift_wrapping.png", + "title": "Gift Wrapping Image", + "description": "Convex Hull wrapping search result", + "contentTypes": ["image/png"] + }, + "final": { + "key": "final.png", + "title": "Final Image", + "description": "Final image with the coordinates and detected area", + "contentTypes": ["image/png"] + } + } +} From ced2f5411c2e577a1f433d36a657e51188b15d57 Mon Sep 17 00:00:00 2001 From: JuanGalilea Date: Wed, 2 Sep 2026 15:23:51 +0200 Subject: [PATCH 5/7] full generation tests --- test/__setup__/vite-raw-import.d.ts | 8 + .../schema-to-ts/dataset/google-places.json | 2032 ++++ .../expected/dataset/google-places.ts | 256 + .../expected/input/google-places.ts | 8845 +++++++++++++++++ .../lib/schema-to-ts/input/google-places.json | 5180 ++++++++++ test/local/lib/schema-to-ts/compile.test.ts | 223 + test/local/lib/schema-to-ts/emit.test.ts | 27 - .../schema-to-ts/preprocess/dataset.test.ts | 1 - .../lib/schema-to-ts/preprocess/input.test.ts | 2 - 9 files changed, 16544 insertions(+), 30 deletions(-) create mode 100644 test/__setup__/vite-raw-import.d.ts create mode 100644 test/local/__fixtures__/lib/schema-to-ts/dataset/google-places.json create mode 100644 test/local/__fixtures__/lib/schema-to-ts/expected/dataset/google-places.ts create mode 100644 test/local/__fixtures__/lib/schema-to-ts/expected/input/google-places.ts create mode 100644 test/local/__fixtures__/lib/schema-to-ts/input/google-places.json create mode 100644 test/local/lib/schema-to-ts/compile.test.ts diff --git a/test/__setup__/vite-raw-import.d.ts b/test/__setup__/vite-raw-import.d.ts new file mode 100644 index 000000000..5d4531270 --- /dev/null +++ b/test/__setup__/vite-raw-import.d.ts @@ -0,0 +1,8 @@ +/** + * Vite (and so Vitest) serves a `?raw` import as the file's text. Declared here rather than by + * pulling in `vite/client`, which would drop every asset and worker wildcard into the test project. + */ +declare module '*?raw' { + const source: string; + export default source; +} diff --git a/test/local/__fixtures__/lib/schema-to-ts/dataset/google-places.json b/test/local/__fixtures__/lib/schema-to-ts/dataset/google-places.json new file mode 100644 index 000000000..a69e61d2e --- /dev/null +++ b/test/local/__fixtures__/lib/schema-to-ts/dataset/google-places.json @@ -0,0 +1,2032 @@ +{ + "$schema": "https://apify.com/schemas/v1/dataset.json", + "actorSpecification": 1, + "fields": { + "type": "object", + "properties": { + "webResults": { + "type": "array", + "items": { + "$ref": "#/definitions/WebResult" + }, + "description": "Web search results related to the place", + "examples": [ + [ + { + "title": "Crystal Clean Laundromat", + "url": "https://example.com", + "description": "Laundromat services" + } + ] + ] + }, + "userPlaceNote": { + "type": "string", + "description": "User's personal note about the place", + "examples": ["My favorite laundromat"] + }, + "tableReservationLinks": { + "type": "array", + "items": { + "$ref": "#/definitions/Link" + }, + "description": "Links for table reservations", + "examples": [ + [ + { + "name": "OpenTable", + "url": "https://www.opentable.com/r/restaurant" + } + ] + ] + }, + "tableReservationProviders": { + "type": "array", + "items": { + "$ref": "#/definitions/TableReservationProvider" + }, + "description": "Table reservation providers available for the place. Populated only when `scrapeTableReservationProvider` is enabled." + }, + "bookingLinks": { + "type": "array", + "items": { + "$ref": "#/definitions/Link" + }, + "description": "Links for booking (hotels, tours, etc.)", + "examples": [ + [ + { + "name": "fastfreshlaundry.com", + "url": "https://www.fastfreshlaundry.com/pickup-and-delivery/" + } + ] + ] + }, + "orderOnline": { + "type": "object", + "properties": { + "pickUps": { + "type": "array", + "items": { + "$ref": "#/definitions/PickUpItem" + }, + "description": "Pickup options" + }, + "deliveries": { + "type": "array", + "items": { + "$ref": "#/definitions/DeliveryItem" + }, + "description": "Delivery options" + } + }, + "required": ["pickUps", "deliveries"], + "description": "Online ordering options (pickup and delivery). Omitted unless `scrapeOrderOnline` is enabled. Note: Google may also omit this data in ~5% of cases.", + "examples": [ + { + "pickUps": [ + { + "name": "Pickup", + "orderUrl": "https://example.com/pickup" + } + ], + "deliveries": [ + { + "name": "Delivery", + "url": "https://example.com/delivery", + "deliveryFees": "$5", + "deliveryTime": "30-45 min" + } + ] + } + ] + }, + "questionsAndAnswers": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/QuestionAndAnswers" + } + }, + { + "$ref": "#/definitions/QuestionAndAnswers" + } + ], + "description": "Questions and answers from the Q&A section", + "examples": [ + [ + { + "question": "Do they accept credit cards?", + "answers": [ + { + "answer": "Yes", + "answerDate": "2 months ago" + } + ] + } + ] + ] + }, + "ownerUpdates": { + "type": "array", + "items": { + "$ref": "#/definitions/OwnerUpdate" + }, + "description": "Updates posted by the business owner", + "examples": [ + [ + { + "text": "Now open on Sundays!", + "date": "2025-06-15T10:00:00.000Z" + } + ] + ] + }, + "restaurantData": { + "type": "object", + "properties": { + "tableReservationProvider": { + "anyOf": [ + { + "$ref": "#/definitions/TableReservationProvider" + }, + { + "type": "null" + } + ], + "description": "Table reservation provider information" + } + }, + "description": "Restaurant-specific data" + }, + "reviewsCount": { + "type": ["number", "null"], + "description": "Total number of reviews" + }, + "title": { + "type": "string", + "description": "Name/title of the place", + "examples": ["Crystal Clean Laundromat"] + }, + "placeId": { + "type": "string", + "description": "Unique Google Place ID", + "examples": ["ChIJ9ckBii1754gRk8w_xAu7tTA"] + }, + "address": { + "type": ["string", "null"], + "description": "Full address of the place", + "examples": ["1408 S Crystal Lake Dr, Orlando, FL 32806"] + }, + "location": { + "anyOf": [ + { + "$ref": "#/definitions/Coordinates" + }, + { + "type": "null" + } + ], + "description": "Geographic coordinates (latitude and longitude)", + "examples": [ + { + "lat": 28.525325, + "lng": -81.3437558 + } + ] + }, + "categories": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Array of category names for the place", + "examples": [["Laundromat", "Clothing alteration service", "Dry cleaner"]] + }, + "isAdvertisement": { + "type": "boolean", + "description": "Whether this place is a paid advertisement" + }, + "categoryName": { + "type": ["string", "null"], + "description": "Primary category name", + "examples": ["Laundromat"] + }, + "totalScore": { + "type": ["number", "null"], + "description": "Average rating score (0-5)" + }, + "permanentlyClosed": { + "type": "boolean", + "description": "Whether the place is permanently closed" + }, + "temporarilyClosed": { + "type": "boolean", + "description": "Whether the place is temporarily closed" + }, + "url": { + "type": "string", + "description": "Google Maps URL for the place", + "examples": [ + "https://www.google.com/maps/search/?api=1&query=Crystal%20Clean%20Laundromat&query_place_id=ChIJ9ckBii1754gRk8w_xAu7tTA" + ] + }, + "price": { + "type": ["string", "null"], + "description": "Price level indicator (e.g., \"$\", \"$$\", \"$$$\")", + "examples": ["$$"] + }, + "cid": { + "type": ["string", "null"], + "description": "Google CID (Customer ID) - numeric identifier", + "examples": ["3509917143816719507"] + }, + "fid": { + "type": ["string", "null"], + "description": "Feature ID - More info at https://dataforseo.com/help-center/what-is-cid-place-id-feature-id", + "examples": ["0x88e77b2d8a01c9f5:0x30b5bb0bc43fcc93"] + }, + "imageUrl": { + "type": ["string", "null"], + "description": "URL of the main place image", + "examples": [ + "https://lh3.googleusercontent.com/p/AF1QipO46METc51OY74zH4zSLZp2aBWDfyzi-ISukdK1=w426-h240-k-no" + ] + }, + "hotelStars": { + "type": ["string", "null"], + "description": "Hotel star rating (for hotels)", + "examples": ["3-star hotel"] + }, + "scrapedAt": { + "type": "string", + "description": "Timestamp when the data was scraped (ISO 8601)", + "examples": ["2025-12-11T20:48:53.297Z"] + }, + "searchPageUrl": { + "type": "string", + "description": "URL of the search page where this place was found", + "examples": ["https://www.google.com/maps/search/laundromat+near+orlando"] + }, + "searchString": { + "type": "string", + "description": "Search query used to find this place", + "examples": ["laundromat near orlando"] + }, + "inputPlaceId": { + "type": "string", + "description": "Place ID from the input if specified", + "examples": ["ChIJ9ckBii1754gRk8w_xAu7tTA"] + }, + "inputStartUrl": { + "type": "string", + "description": "Start URL from the input if specified", + "examples": ["https://www.google.com/maps/place/..."] + }, + "language": { + "type": "string", + "description": "Language code used for scraping", + "examples": ["en"] + }, + "rank": { + "type": "number", + "description": "Position in search results (1-based)" + }, + "kgmid": { + "type": ["string", "null"], + "description": "Google Knowledge Graph ID", + "examples": ["/g/11b6q62wr1"] + }, + "businessProfileId": { + "type": ["string", "null"], + "description": "Business Profile ID", + "examples": ["112318156752526390"] + }, + "neighborhood": { + "type": ["string", "null"], + "description": "Neighborhood or district name", + "examples": ["Lake Como"] + }, + "street": { + "type": ["string", "null"], + "description": "Street address including building number", + "examples": ["1408 S Crystal Lake Dr"] + }, + "city": { + "type": ["string", "null"], + "description": "City name", + "examples": ["Orlando"] + }, + "countryCode": { + "type": ["string", "null"], + "description": "Two-letter country code (ISO 3166-1 alpha-2)", + "examples": ["US"] + }, + "postalCode": { + "type": ["string", "null"], + "description": "Postal or ZIP code", + "examples": ["32806"] + }, + "state": { + "type": ["string", "null"], + "description": "State or province name", + "examples": ["Florida"] + }, + "emails": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Array of email addresses found", + "examples": [["info@example.com", "support@example.com"]] + }, + "phones": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Array of phone numbers found", + "examples": [["+1-555-123-4567", "+1-555-987-6543"]] + }, + "phonesUncertain": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Array of uncertain/possible phone numbers", + "examples": [["+1-555-000-0000"]] + }, + "linkedIns": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Array of LinkedIn profile URLs", + "examples": [["https://www.linkedin.com/company/example"]] + }, + "twitters": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Array of Twitter profile URLs", + "examples": [["https://twitter.com/example"]] + }, + "instagrams": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Array of Instagram profile URLs", + "examples": [["https://www.instagram.com/example/"]] + }, + "facebooks": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Array of Facebook profile URLs", + "examples": [["https://www.facebook.com/example"]] + }, + "youtubes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Array of YouTube channel URLs", + "examples": [["https://www.youtube.com/channel/example"]] + }, + "tiktoks": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Array of TikTok profile URLs", + "examples": [["https://www.tiktok.com/@example"]] + }, + "pinterests": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Array of Pinterest profile URLs", + "examples": [["https://www.pinterest.com/example/"]] + }, + "discords": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Array of Discord server URLs", + "examples": [["https://discord.gg/example"]] + }, + "facebookProfiles": { + "type": "array", + "items": { + "description": "This represents scraped social media profile. We don't care much about its fields in this actor so we define it as unknown" + }, + "description": "Array of Facebook profile data", + "examples": [ + [ + { + "url": "https://www.facebook.com/example", + "likes": 1000 + } + ] + ] + }, + "instagramProfiles": { + "type": "array", + "items": { + "description": "This represents scraped social media profile. We don't care much about its fields in this actor so we define it as unknown" + }, + "description": "Array of Instagram profile data", + "examples": [ + [ + { + "url": "https://www.instagram.com/example/", + "followers": 5000 + } + ] + ] + }, + "youtubeProfiles": { + "type": "array", + "items": { + "description": "This represents scraped social media profile. We don't care much about its fields in this actor so we define it as unknown" + }, + "description": "Array of YouTube profile data", + "examples": [ + [ + { + "url": "https://www.youtube.com/channel/example", + "subscribers": 10000 + } + ] + ] + }, + "tiktokProfiles": { + "type": "array", + "items": { + "description": "This represents scraped social media profile. We don't care much about its fields in this actor so we define it as unknown" + }, + "description": "Array of TikTok profile data", + "examples": [ + [ + { + "url": "https://www.tiktok.com/@example", + "followers": 20000 + } + ] + ] + }, + "twitterProfiles": { + "type": "array", + "items": { + "description": "This represents scraped social media profile. We don't care much about its fields in this actor so we define it as unknown" + }, + "description": "Array of Twitter profile data", + "examples": [ + [ + { + "url": "https://twitter.com/example", + "followers": 3000 + } + ] + ] + }, + "description": { + "type": ["string", "null"], + "description": "Brief description of the place", + "examples": ["Full-service laundromat with modern equipment and free WiFi"] + }, + "phone": { + "type": ["string", "null"], + "description": "Formatted phone number", + "examples": ["(407) 896-9355"] + }, + "phoneUnformatted": { + "type": ["string", "null"], + "description": "Unformatted phone number with country code", + "examples": ["+14078969355"] + }, + "imagesCount": { + "type": "number", + "description": "Total number of images available" + }, + "openingHours": { + "$ref": "#/definitions/OpeningHours", + "description": "Opening hours for each day of the week", + "examples": [ + [ + { + "day": "Monday", + "hours": "7 AM to 9 PM" + }, + { + "day": "Tuesday", + "hours": "7 AM to 9 PM" + } + ] + ] + }, + "additionalOpeningHours": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/OpeningHours" + }, + "description": "Additional opening hours for specific services (e.g., delivery, pickup)", + "examples": [ + { + "Delivery": [ + { + "day": "Monday", + "hours": "9 AM–6 PM" + } + ] + } + ] + }, + "claimThisBusiness": { + "type": "boolean", + "description": "Whether the business can be claimed by its owner" + }, + "peopleAlsoSearch": { + "type": "array", + "items": { + "$ref": "#/definitions/PeopleAlsoSearchSingle" + }, + "description": "Related places suggested by Google", + "examples": [ + [ + { + "category": "People also search for", + "title": "Thornton Park Laundry", + "reviewsCount": 92, + "totalScore": 3.7 + } + ] + ] + }, + "additionalInfo": { + "$ref": "#/definitions/AdditionalInfo", + "description": "Additional information grouped by categories (accessibility, amenities, etc.)", + "examples": [ + { + "Service options": [ + { + "Online estimates": true + } + ] + } + ] + }, + "reviewsTags": { + "type": "array", + "items": { + "$ref": "#/definitions/Tag" + }, + "description": "Tags extracted from reviews", + "examples": [ + [ + { + "title": "coffee", + "count": 67 + }, + { + "title": "cookies", + "count": 47 + } + ] + ] + }, + "placesTags": { + "type": "array", + "items": { + "$ref": "#/definitions/Tag" + }, + "description": "Tags related to the place itself", + "examples": [ + [ + { + "title": "family-friendly", + "count": 10 + } + ] + ] + }, + "imageCategories": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Categories available in the image gallery", + "examples": [["All", "Latest", "Videos", "Exterior", "Inside"]] + }, + "gasPrices": { + "type": "array", + "items": { + "$ref": "#/definitions/GasPrice" + }, + "description": "Gas prices (for gas stations)", + "examples": [ + [ + { + "priceTag": "$3.45", + "gasType": "Regular", + "price": 3.45 + } + ] + ] + }, + "reserveTableUrl": { + "type": ["string", "null"], + "description": "URL for making table reservations", + "examples": ["https://www.opentable.com/restaurant/profile/12345"] + }, + "googleFoodUrl": { + "type": ["string", "null"], + "description": "Google Food ordering URL", + "examples": ["https://food.google.com/chooseprovider?restaurantId=..."] + }, + "website": { + "type": "string", + "description": "Official website of the place", + "examples": ["https://www.fastfreshlaundry.com/locations/crystal-clean-laundromat/"] + }, + "leadsEnrichment": { + "type": "array", + "items": { + "$ref": "#/definitions/LeadsEnrichmentResult" + }, + "description": "Enriched leads data from external sources", + "examples": [ + [ + { + "firstName": "John", + "lastName": "Doe", + "jobTitle": "Manager", + "email": "john@example.com" + } + ] + ] + }, + "parentPlaceUrl": { + "type": "string", + "description": "URL of the parent place (for places within other places)", + "examples": ["https://www.google.com/maps/place/Shopping+Mall/..."] + }, + "hotelDescription": { + "type": ["string", "null"], + "description": "Description of the hotel", + "examples": ["Modern hotel with pool and free breakfast"] + }, + "checkInDate": { + "type": ["string", "null"], + "description": "Check-in date for the search query", + "examples": ["2025-08-15"] + }, + "checkOutDate": { + "type": ["string", "null"], + "description": "Check-out date for the search query", + "examples": ["2025-08-20"] + }, + "similarHotelsNearby": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": ["string", "null"], + "description": "Name of the similar hotel", + "examples": ["Hotel Nearby"] + }, + "rating": { + "type": ["number", "null"], + "description": "Average rating" + }, + "reviews": { + "type": ["number", "null"], + "description": "Number of reviews" + }, + "description": { + "type": ["string", "null"], + "description": "Brief description", + "examples": ["Budget hotel near downtown"] + }, + "price": { + "type": ["string", "null"], + "description": "Price per night", + "examples": ["$89"] + } + }, + "required": ["name", "rating", "reviews", "description", "price"] + }, + "description": "Array of similar hotels in the area", + "examples": [ + [ + { + "name": "Hotel Nearby", + "rating": 4.2, + "reviews": 150, + "description": "Budget hotel", + "price": "$89" + } + ] + ] + }, + "hotelReviewSummary": { + "$ref": "#/definitions/HotelReviewSummary", + "description": "Summary of hotel reviews by traveler type" + }, + "hotelAds": { + "type": "array", + "items": { + "$ref": "#/definitions/HotelAd" + }, + "description": "Array of booking advertisements for the hotel", + "examples": [ + [ + { + "title": "Booking.com", + "price": "$150", + "isOfficialSite": false + } + ] + ] + }, + "subTitle": { + "type": ["string", "null"], + "description": "Subtitle or secondary name", + "examples": ["Downtown location"] + }, + "ownerDescription": { + "type": ["string", "null"], + "description": "Description written by the owner in Google Business Profile, shown in the \"From the business\" section. Unlike `description`, which is Google's own editorial summary. Kept in the language the owner wrote it in.", + "examples": ["Our cozy living room where we share a love for hand brews and little brunches."] + }, + "menu": { + "type": ["string", "null"], + "description": "URL to the menu (for restaurants)", + "examples": ["https://example.com/menu.pdf"] + }, + "servicesLink": { + "type": ["string", "null"], + "description": "URL to book services or appointments (for service businesses like barbershops, spas)", + "examples": ["https://example.com/book"] + }, + "locatedIn": { + "type": ["string", "null"], + "description": "Name of the location where this place is situated", + "examples": ["Terminal 3, Orlando International Airport"] + }, + "floor": { + "type": ["string", "null"], + "description": "Floor number or level", + "examples": ["2nd floor"] + }, + "plusCode": { + "type": ["string", "null"], + "description": "Plus Code for the location", + "examples": ["GMG4+4F Orlando, Florida"] + }, + "reviewsDistribution": { + "$ref": "#/definitions/ReviewsDistribution", + "description": "Distribution of reviews across star ratings", + "examples": [ + { + "oneStar": 24, + "twoStar": 10, + "threeStar": 17, + "fourStar": 50, + "fiveStar": 720 + } + ] + }, + "reviewsRemovedNotice": { + "$ref": "#/definitions/ReviewsRemovedNotice", + "description": "Notice about reviews removed due to legal defamation complaints", + "examples": [ + { + "text": "One review removed due to a defamation complaint." + } + ] + }, + "updatesFromCustomers": { + "$ref": "#/definitions/UpdateFromCustomer", + "description": "Latest update posted by a customer", + "examples": [ + { + "text": "Great service!", + "language": "en", + "postDate": "1 week ago" + } + ] + }, + "openingHoursBusinessConfirmationText": { + "type": "string", + "description": "Confirmation text about opening hours from the business", + "examples": ["Hours updated 2 weeks ago"] + }, + "wasOpenAtScrapeTime": { + "type": ["boolean", "null"], + "description": "Whether the place was open at the moment it was scraped, as reported by Google in the place's own time zone. Always `false` for permanently or temporarily closed places, and `null` when the status is missing or unrecognized, e.g. the place publishes no opening hours." + }, + "popularTimesLiveText": { + "type": ["string", "null"], + "description": "Human-readable text describing current occupancy level", + "examples": ["Less busy than usual"] + }, + "popularTimesLivePercent": { + "type": ["number", "null"], + "description": "Current occupancy as a percentage (0-100)" + }, + "popularTimesHistogram": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "object", + "properties": { + "hour": { + "type": "number" + }, + "occupancyPercent": { + "type": "number" + } + }, + "required": ["hour", "occupancyPercent"] + } + }, + "description": "Hourly occupancy data for each day of the week", + "examples": [ + { + "Mo": [ + { + "hour": 9, + "occupancyPercent": 56 + }, + { + "hour": 10, + "occupancyPercent": 62 + } + ] + } + ] + }, + "isExternalServicePlace": { + "type": "boolean", + "description": "Whether this place data comes from an external service" + }, + "externalServiceProvider": { + "type": ["string", "null"], + "description": "Name of the external service provider", + "examples": ["Tripadvisor"] + }, + "externalId": { + "type": "string", + "description": "Identifier in the external service", + "examples": ["d123456"] + } + }, + "required": [ + "address", + "businessProfileId", + "categories", + "categoryName", + "checkInDate", + "checkOutDate", + "cid", + "city", + "countryCode", + "description", + "fid", + "floor", + "gasPrices", + "hotelDescription", + "hotelStars", + "imageCategories", + "imageUrl", + "imagesCount", + "isAdvertisement", + "kgmid", + "language", + "locatedIn", + "location", + "menu", + "neighborhood", + "ownerDescription", + "permanentlyClosed", + "phone", + "phoneUnformatted", + "placeId", + "plusCode", + "popularTimesHistogram", + "popularTimesLivePercent", + "popularTimesLiveText", + "postalCode", + "price", + "reserveTableUrl", + "reviewsCount", + "scrapedAt", + "servicesLink", + "state", + "street", + "subTitle", + "temporarilyClosed", + "title", + "totalScore", + "url", + "wasOpenAtScrapeTime" + ], + "definitions": { + "WebResult": { + "type": "object", + "properties": { + "title": { + "type": ["string", "null"], + "description": "Title of the web result", + "examples": ["Crystal Clean Laundromat - Orlando, FL"] + }, + "displayedUrl": { + "type": ["string", "null"], + "description": "Displayed URL (may be shortened or formatted)", + "examples": ["fastfreshlaundry.com"] + }, + "url": { + "type": ["string", "null"], + "description": "Full URL to the web page", + "examples": ["https://www.fastfreshlaundry.com/locations/crystal-clean-laundromat/"] + }, + "description": { + "type": ["string", "null"], + "description": "Description or snippet from the web page", + "examples": ["Full-service laundromat with modern equipment and free WiFi"] + } + }, + "required": ["title", "displayedUrl", "url", "description"], + "description": "A web search result related to the place" + }, + "Link": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Display name for the link", + "examples": ["fastfreshlaundry.com"] + }, + "url": { + "type": "string", + "description": "[URL of the link]", + "examples": ["https://www.fastfreshlaundry.com/pickup-and-delivery/"] + } + }, + "required": ["url"], + "description": "A simple link with name and URL" + }, + "TableReservationProvider": { + "type": "object", + "properties": { + "name": { + "type": ["string", "null"], + "description": "Name of the reservation provider", + "examples": ["OpenTable"] + }, + "email": { + "type": ["string", "null"], + "description": "Email address of the provider", + "examples": ["reservations@example.com"] + }, + "phone": { + "type": ["string", "null"], + "description": "Phone number of the provider", + "examples": ["+1-555-123-4567"] + }, + "address": { + "type": ["string", "null"], + "description": "Address of the provider", + "examples": ["123 Main St, New York, NY"] + }, + "reserveTableUrl": { + "type": "string", + "description": "URL to make a table reservation", + "examples": ["https://www.opentable.com/restaurant/profile/12345"] + } + }, + "required": ["name", "email", "phone", "address", "reserveTableUrl"], + "description": "Information about a table reservation service provider" + }, + "PickUpItem": { + "type": "object", + "properties": { + "name": { + "type": ["string", "null"], + "description": "Name of the pickup service", + "examples": ["Uber Eats"] + }, + "url": { + "type": ["string", "null"], + "description": "URL to the service website", + "examples": ["https://www.ubereats.com"] + }, + "orderUrl": { + "type": "string", + "description": "Direct URL to place an order", + "examples": ["https://www.ubereats.com/store/example-restaurant"] + }, + "pickUpTime": { + "type": ["string", "null"], + "description": "Estimated pickup time", + "examples": ["15-25 min"] + }, + "pickUpFees": { + "type": ["string", "null"], + "description": "Pickup fees", + "examples": ["$2.99"] + } + }, + "required": ["name", "url", "orderUrl", "pickUpTime", "pickUpFees"], + "description": "Information about a pickup ordering option" + }, + "DeliveryItem": { + "type": "object", + "properties": { + "name": { + "type": ["string", "null"], + "description": "Name of the delivery service", + "examples": ["DoorDash"] + }, + "url": { + "type": "string", + "description": "URL to the service website", + "examples": ["https://www.doordash.com"] + }, + "deliveryFees": { + "type": ["string", "null"], + "description": "Delivery fees", + "examples": ["$5.99"] + }, + "deliveryTime": { + "type": ["string", "null"], + "description": "Estimated delivery time", + "examples": ["30-45 min"] + } + }, + "required": ["name", "url", "deliveryFees", "deliveryTime"], + "description": "Information about a delivery ordering option" + }, + "QuestionAndAnswers": { + "type": "object", + "properties": { + "question": { + "type": ["string", "null"], + "description": "The question text", + "examples": ["Do they accept credit cards?"] + }, + "answers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "answer": { + "type": ["string", "null"], + "description": "The answer text", + "examples": ["Yes, they accept credit cards and mobile payments"] + }, + "answerDate": { + "type": ["string", "null"], + "description": "When the answer was posted", + "examples": ["3 months ago"] + }, + "answeredBy": { + "anyOf": [ + { + "type": "object", + "properties": { + "name": { + "type": ["string", "null"], + "description": "Name of the person who answered", + "examples": ["John Doe"] + }, + "url": { + "type": ["string", "null"], + "description": "URL to the answerer's profile", + "examples": ["https://www.google.com/maps/contrib/123456"] + } + }, + "required": ["name", "url"] + }, + { + "type": "null" + } + ], + "description": "Information about who provided the answer", + "examples": [ + { + "name": "John Doe", + "url": "https://www.google.com/maps/contrib/123" + } + ] + } + }, + "required": ["answer", "answerDate", "answeredBy"], + "description": "An answer to a question posted on Google Maps" + }, + "description": "Array of answers to the question", + "examples": [ + [ + { + "answer": "Yes, they accept all major credit cards", + "answerDate": "2 months ago", + "answeredBy": { + "name": "Jane Smith" + } + } + ] + ] + }, + "askDate": { + "type": ["string", "null"], + "description": "When the question was asked", + "examples": ["6 months ago"] + }, + "askedBy": { + "anyOf": [ + { + "type": "object", + "properties": { + "name": { + "type": ["string", "null"], + "description": "Name of the person who asked", + "examples": ["Mary Johnson"] + }, + "url": { + "type": ["string", "null"], + "description": "URL to the asker's profile", + "examples": ["https://www.google.com/maps/contrib/789456"] + } + }, + "required": ["name", "url"] + }, + { + "type": "null" + } + ], + "description": "Information about who asked the question", + "examples": [ + { + "name": "Mary Johnson", + "url": "https://www.google.com/maps/contrib/789" + } + ] + } + }, + "required": ["question", "answers", "askDate", "askedBy"], + "description": "A question and its answers from the Q&A section on Google Maps" + }, + "OwnerUpdate": { + "type": "object", + "properties": { + "text": { + "type": ["string", "null"], + "description": "Text content of the owner's update", + "examples": ["We are open this fourth of July - because clean never takes a day off!"] + }, + "buttonText": { + "type": ["string", "null"], + "description": "Text displayed on the action button", + "examples": ["Order online"] + }, + "buttonLink": { + "type": ["string", "null"], + "description": "URL linked to the action button", + "examples": ["http://www.fastfreshlaundry.com/"] + }, + "date": { + "type": ["string", "null"], + "description": "Date when the update was posted (ISO 8601 format)", + "examples": ["2025-07-04T21:03:52.000Z"] + }, + "imageUrl": { + "type": ["string", "null"], + "description": "URL of the image attached to the update", + "examples": [ + "https://lh3.googleusercontent.com/geougc/AF1QipNtZqWouyANvs4cg7qWebqvjZjk1j9hBMDy-Kci=h400-no" + ] + } + }, + "required": ["text", "buttonText", "buttonLink", "date", "imageUrl"], + "description": "An update posted by the business owner" + }, + "Coordinates": { + "type": "object", + "properties": { + "lat": { + "type": "number" + }, + "lng": { + "type": "number" + } + }, + "required": ["lat", "lng"] + }, + "OpeningHours": { + "type": "array", + "items": { + "type": "object", + "properties": { + "day": { + "type": "string" + }, + "hours": { + "type": "string" + } + }, + "required": ["day", "hours"] + }, + "description": "Opening hours for all seven days of the week in sorted order", + "examples": [ + [ + { + "day": "Monday", + "hours": "7 AM to 9 PM" + }, + { + "day": "Tuesday", + "hours": "7 AM to 9 PM" + }, + { + "day": "Wednesday", + "hours": "7 AM to 9 PM" + }, + { + "day": "Thursday", + "hours": "7 AM to 9 PM" + }, + { + "day": "Friday", + "hours": "7 AM to 9 PM" + }, + { + "day": "Saturday", + "hours": "6 AM to 9 PM" + }, + { + "day": "Sunday", + "hours": "6 AM to 9 PM" + } + ] + ] + }, + "PeopleAlsoSearchSingle": { + "type": "object", + "properties": { + "category": { + "type": ["string", "null"], + "description": "Category of the suggestion (e.g., \"People also search for\")", + "examples": ["People also search for"] + }, + "title": { + "type": ["string", "null"], + "description": "Name of the suggested place", + "examples": ["Thornton Park Laundry"] + }, + "reviewsCount": { + "type": ["number", "null"], + "description": "Number of reviews for the suggested place" + }, + "totalScore": { + "type": ["number", "null"], + "description": "Average rating of the suggested place" + } + }, + "required": ["category", "title", "reviewsCount", "totalScore"], + "description": "A related place suggestion from \"People also search for\" section" + }, + "AdditionalInfo": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": { + "type": "boolean" + } + } + }, + "description": "Additional information about the place grouped by categories (accessibility, amenities, etc.)", + "examples": [ + { + "Service options": [ + { + "Online estimates": true + }, + { + "Onsite services": true + } + ] + } + ] + }, + "Tag": { + "type": "object", + "properties": { + "title": { + "type": ["string", "null"], + "description": "The tag text", + "examples": ["coffee"] + }, + "count": { + "type": ["number", "null"], + "description": "Number of times this tag appears" + } + }, + "required": ["title", "count"], + "description": "A tag with frequency count extracted from reviews or place descriptions" + }, + "GasPrice": { + "type": "object", + "properties": { + "priceTag": { + "type": ["string", "null"], + "description": "Formatted price string with currency", + "examples": ["$3.45"] + }, + "updatedAt": { + "type": "string", + "description": "When the price was last updated", + "examples": ["2 hours ago"] + }, + "unit": { + "type": ["string", "null"], + "description": "Unit of measurement (e.g., gallon, liter)", + "examples": ["gallon"] + }, + "currency": { + "type": ["string", "null"], + "description": "Currency code", + "examples": ["USD"] + }, + "price": { + "type": ["number", "null"], + "description": "Numeric price value" + }, + "gasType": { + "type": ["string", "null"], + "description": "Type of gasoline (e.g., Regular, Premium, Diesel)", + "examples": ["Regular"] + } + }, + "required": ["priceTag", "updatedAt", "unit", "currency", "price", "gasType"], + "description": "Gas price information for gas stations" + }, + "LeadsEnrichmentResult": { + "type": "object", + "properties": { + "personId": { + "type": "string", + "description": "Unique identifier for the person", + "examples": ["5f9b3f3e4f3b9a001f3e4b1a"] + }, + "firstName": { + "type": ["string", "null"], + "description": "First name", + "examples": ["John"] + }, + "lastName": { + "type": ["string", "null"], + "description": "Last name", + "examples": ["Doe"] + }, + "fullName": { + "type": ["string", "null"], + "description": "Full name", + "examples": ["John Doe"] + }, + "linkedinProfile": { + "type": ["string", "null"], + "description": "LinkedIn profile URL", + "examples": ["https://www.linkedin.com/in/johndoe"] + }, + "email": { + "type": ["string", "null"], + "description": "Email address", + "examples": ["john.doe@example.com"] + }, + "mobileNumber": { + "type": ["string", "null"], + "description": "Mobile phone number", + "examples": ["+1-555-123-4567"] + }, + "jobTitle": { + "type": ["string", "null"], + "description": "Job title", + "examples": ["Marketing Manager"] + }, + "industry": { + "type": ["string", "null"], + "description": "Industry sector", + "examples": ["Technology"] + }, + "city": { + "type": ["string", "null"], + "description": "City of residence", + "examples": ["San Francisco"] + }, + "state": { + "type": ["string", "null"], + "description": "State or province", + "examples": ["California"] + }, + "country": { + "type": ["string", "null"], + "description": "Country", + "examples": ["United States"] + }, + "companyId": { + "type": ["string", "null"], + "description": "Unique identifier for the company", + "examples": ["5f9b3f3e4f3b9a001f3e4b1b"] + }, + "companyName": { + "type": ["string", "null"], + "description": "Company name", + "examples": ["Acme Corporation"] + }, + "companyWebsite": { + "type": ["string", "null"], + "description": "Company website URL", + "examples": ["https://www.acmecorp.com"] + }, + "companySize": { + "type": ["string", "null"], + "description": "Size range of the company", + "examples": ["100-500 employees"] + }, + "companyLinkedin": { + "type": ["string", "null"], + "description": "Company LinkedIn profile URL", + "examples": ["https://www.linkedin.com/company/acmecorp"] + }, + "companyCity": { + "type": ["string", "null"], + "description": "City where company is located", + "examples": ["New York"] + }, + "companyState": { + "type": ["string", "null"], + "description": "State where company is located", + "examples": ["New York"] + }, + "companyCountry": { + "type": ["string", "null"], + "description": "Country where company is located", + "examples": ["United States"] + }, + "companyPhoneNumber": { + "type": ["string", "null"], + "description": "Company phone number", + "examples": ["+1-555-987-6543"] + }, + "headline": { + "type": ["string", "null"], + "description": "Professional headline", + "examples": ["Experienced Marketing Professional"] + }, + "departments": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Departments the person is associated with", + "examples": [["Marketing", "Sales"]] + }, + "seniority": { + "type": ["string", "null"], + "description": "Seniority level", + "examples": ["Manager"] + }, + "photoUrl": { + "type": ["string", "null"], + "description": "URL to profile photo", + "examples": ["https://media.licdn.com/dms/image/..."] + }, + "twitter": { + "type": ["string", "null"], + "description": "Twitter profile URL", + "examples": ["https://twitter.com/johndoe"] + }, + "emailVerification": { + "type": "object", + "properties": { + "email": { + "type": "string", + "description": "Email this verification is for", + "examples": ["john.doe@example.com"] + }, + "quality": { + "type": "string", + "enum": ["", "good", "bad", "risky", "unknown"], + "description": "Quality classification of the email address", + "examples": ["good"] + }, + "result": { + "type": "string", + "enum": ["ok", "catch_all", "unknown", "error", "disposable", "invalid"], + "description": "Verification result status", + "examples": ["ok"] + }, + "subResult": { + "type": "string", + "description": "Detailed sub-result providing additional verification context", + "examples": ["mailbox_verified"] + }, + "free": { + "type": "boolean", + "description": "Whether the email is from a free email provider" + }, + "role": { + "type": "boolean", + "description": "Whether the email is a role-based address (e.g., info@, support@)" + }, + "error": { + "type": "string", + "description": "Error message if verification failed, empty string otherwise", + "examples": [""] + } + }, + "required": ["email"], + "description": "Email verification result for a given email if it was enabled", + "examples": [ + { + "email": "john@example.com", + "quality": "good", + "result": "ok", + "subResult": "mailbox_verified", + "free": false, + "role": false, + "error": "" + } + ] + } + }, + "required": [ + "personId", + "firstName", + "lastName", + "fullName", + "linkedinProfile", + "email", + "mobileNumber", + "jobTitle", + "industry", + "city", + "state", + "country", + "companyId", + "companyName", + "companyWebsite", + "companySize", + "companyLinkedin", + "companyCity", + "companyState", + "companyCountry", + "companyPhoneNumber", + "headline", + "departments", + "seniority", + "photoUrl", + "twitter" + ], + "description": "Enriched lead information from external data sources" + }, + "HotelReviewSummary": { + "type": "object", + "properties": { + "overall": { + "$ref": "#/definitions/HotelReviewerGroupSummary", + "description": "Overall review summary across all traveler types" + }, + "bussiness": { + "$ref": "#/definitions/HotelReviewerGroupSummary", + "description": "Review summary from business travelers" + }, + "couples": { + "$ref": "#/definitions/HotelReviewerGroupSummary", + "description": "Review summary from couples" + }, + "solo": { + "$ref": "#/definitions/HotelReviewerGroupSummary", + "description": "Review summary from solo travelers" + }, + "families": { + "$ref": "#/definitions/HotelReviewerGroupSummary", + "description": "Review summary from families" + }, + "friends": { + "$ref": "#/definitions/HotelReviewerGroupSummary", + "description": "Review summary from groups of friends" + } + }, + "description": "Summary of hotel reviews broken down by traveler type" + }, + "HotelReviewerGroupSummary": { + "type": "object", + "properties": { + "rating": { + "type": ["number", "null"], + "description": "Overall rating for this traveler group" + }, + "rooms": { + "type": "object", + "properties": { + "rating": { + "type": ["number", "null"], + "description": "Average rating for this category" + }, + "reviews": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Array of review excerpts mentioning this category", + "examples": [["Clean and spacious", "Very comfortable beds"]] + } + }, + "required": ["rating", "reviews"], + "description": "Review summary for rooms" + }, + "servicesAndFacilities": { + "type": "object", + "properties": { + "rating": { + "type": ["number", "null"], + "description": "Average rating for this category" + }, + "reviews": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Array of review excerpts mentioning this category", + "examples": [["Clean and spacious", "Very comfortable beds"]] + } + }, + "required": ["rating", "reviews"], + "description": "Review summary for services and facilities" + }, + "location": { + "type": "object", + "properties": { + "rating": { + "type": ["number", "null"], + "description": "Average rating for this category" + }, + "reviews": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Array of review excerpts mentioning this category", + "examples": [["Clean and spacious", "Very comfortable beds"]] + } + }, + "required": ["rating", "reviews"], + "description": "Review summary for location" + } + }, + "required": ["rating"], + "description": "Review summary for a specific traveler group with category breakdowns" + }, + "HotelAd": { + "type": "object", + "properties": { + "title": { + "type": ["string", "null"], + "description": "Title or name of the booking provider", + "examples": ["Booking.com"] + }, + "googleUrl": { + "type": ["string", "null"], + "description": "Google redirect URL for the ad", + "examples": ["https://www.google.com/travel/hotels/..."] + }, + "url": { + "type": ["string", "null"], + "description": "Direct URL to the booking site", + "examples": ["https://www.booking.com/hotel/..."] + }, + "price": { + "type": ["string", "null"], + "description": "Displayed price", + "examples": ["$150 per night"] + }, + "isOfficialSite": { + "type": "boolean", + "description": "Whether this is the hotel's official website" + } + }, + "required": ["title", "googleUrl", "url", "price", "isOfficialSite"], + "description": "A hotel booking advertisement" + }, + "ReviewsDistribution": { + "type": "object", + "properties": { + "oneStar": { + "type": "number", + "description": "Number of 1-star reviews" + }, + "twoStar": { + "type": "number", + "description": "Number of 2-star reviews" + }, + "threeStar": { + "type": "number", + "description": "Number of 3-star reviews" + }, + "fourStar": { + "type": "number", + "description": "Number of 4-star reviews" + }, + "fiveStar": { + "type": "number", + "description": "Number of 5-star reviews" + } + }, + "required": ["oneStar", "twoStar", "threeStar", "fourStar", "fiveStar"], + "description": "Distribution of reviews across star ratings" + }, + "ReviewsRemovedNotice": { + "type": "object", + "properties": { + "text": { + "type": "string", + "description": "Notice text as shown in the banner", + "examples": ["One review removed due to a defamation complaint."] + } + }, + "required": ["text"], + "description": "Notice shown when reviews were removed from the place due to legal defamation complaints\n(e.g. under German law). Text is localized to the run's language and reports the removed reviews\ncount as Google phrases it (e.g. \"One\", \"11 to 20\", \"Six to ten\")." + }, + "UpdateFromCustomer": { + "type": "object", + "properties": { + "text": { + "type": ["string", "null"], + "description": "Text content of the update", + "examples": ["Excellent laundry, clean and fresh environment, new machines and all working"] + }, + "language": { + "type": ["string", "null"], + "description": "Language code of the update", + "examples": ["en"] + }, + "postDate": { + "type": ["string", "null"], + "description": "When the update was posted", + "examples": ["11 months ago"] + }, + "postedBy": { + "type": "object", + "properties": { + "name": { + "type": ["string", "null"], + "description": "Name of the person who posted", + "examples": ["Aura Toro"] + }, + "url": { + "type": ["string", "null"], + "description": "URL to the poster's profile", + "examples": ["https://www.google.com/maps/contrib/106833772378732355960?hl=en-US"] + }, + "title": { + "type": ["string", "null"], + "description": "Title or badge (e.g., \"Local Guide\")", + "examples": ["Local Guide"] + }, + "totalReviews": { + "type": ["number", "null"], + "description": "Total number of reviews by this person" + } + }, + "required": ["name", "url", "title", "totalReviews"], + "description": "Information about who posted the update" + }, + "media": { + "type": "array", + "items": { + "type": "object", + "properties": { + "link": { + "type": "string" + }, + "postDate": { + "type": ["string", "null"] + } + }, + "required": ["link", "postDate"] + }, + "description": "Array of media items (photos/videos) attached to the update", + "examples": [ + [ + { + "link": "https://lh3.googleusercontent.com/...", + "postDate": "11 months ago" + } + ] + ] + } + }, + "required": ["text", "language", "postDate", "postedBy", "media"], + "description": "A customer update or post about the place" + } + } + }, + "views": { + "overview": { + "title": "Overview", + "description": "It can take about one minute until the first results are available.", + "transformation": { + "fields": [ + "title", + "totalScore", + "reviewsCount", + "street", + "city", + "state", + "countryCode", + "website", + "phone", + "categories", + "url", + "categoryName" + ] + }, + "display": { + "component": "table", + "properties": { + "title": { + "label": "Place name" + } + } + } + }, + "contactInfo": { + "title": "Contact info", + "description": "It can take about one minute until the first results are available.", + "transformation": { + "fields": [ + "title", + "categories", + "address", + "neighborhood", + "street", + "city", + "postalCode", + "state", + "countryCode", + "website", + "emails", + "phone", + "phoneUnformatted", + "location", + "plusCode", + "categoryName" + ] + }, + "display": { + "component": "table", + "properties": { + "title": { + "label": "Place name" + } + } + } + }, + "socialmedia": { + "title": "Social media", + "description": "It can take about one minute until the first results are available.", + "transformation": { + "fields": [ + "title", + "instagrams", + "facebooks", + "linkedIns", + "youtubes", + "tiktoks", + "twitters", + "pinterests" + ] + }, + "display": { + "component": "table", + "properties": { + "title": { + "label": "Place name" + } + } + } + }, + "rating": { + "title": "Rating", + "description": "It can take about one minute until the first results are available.", + "transformation": { + "fields": ["title", "totalScore", "reviewsCount", "reviewsDistribution", "reviewsTags"] + }, + "display": { + "component": "table", + "properties": { + "title": { + "label": "Place name" + } + } + } + }, + "reviews": { + "title": "Reviews (if any)", + "description": "It can take about one minute until the first results are available.", + "transformation": { + "fields": [ + "title", + "stars", + "text", + "publishedAtDate", + "likesCount", + "name", + "reviewerNumberOfReviews", + "isLocalGuide", + "responseFromOwnerDate", + "responseFromOwnerText", + "reviews" + ], + "unwind": ["reviews"] + }, + "display": { + "component": "table", + "properties": { + "title": { + "label": "Place name" + } + } + } + }, + "leadsEnrichment": { + "title": "Lead Enrichment", + "description": "It can take about one minute until the first results are available.", + "transformation": { + "fields": [ + "title", + "firstName", + "lastName", + "fullName", + "linkedinProfile", + "email", + "emailVerification", + "mobileNumber", + "headline", + "jobTitle", + "departments", + "seniority", + "industry", + "photoUrl", + "city", + "state", + "country", + "companyName", + "companyWebsite", + "companySize", + "companyLinkedin", + "companyCity", + "companyState", + "companyCountry", + "companyPhoneNumber", + "twitter", + "leadsEnrichment" + ], + "unwind": ["leadsEnrichment"] + }, + "display": { + "component": "table" + } + }, + "socialProfiles": { + "title": "Social profiles", + "description": "Detailed social media profile information for discovered social profiles' URLs", + "transformation": { + "fields": [ + "title", + "website", + "facebookProfiles", + "instagramProfiles", + "youtubeProfiles", + "tiktokProfiles", + "twitterProfiles" + ] + }, + "display": { + "component": "table" + } + } + } +} \ No newline at end of file diff --git a/test/local/__fixtures__/lib/schema-to-ts/expected/dataset/google-places.ts b/test/local/__fixtures__/lib/schema-to-ts/expected/dataset/google-places.ts new file mode 100644 index 000000000..798e9bd0b --- /dev/null +++ b/test/local/__fixtures__/lib/schema-to-ts/expected/dataset/google-places.ts @@ -0,0 +1,256 @@ +// oxlint-disable +// @generated schema-ts v1-641d818735632fe7 — do not edit + +export type Place = { + webResults?: Array | undefined; + userPlaceNote?: string | undefined; + tableReservationLinks?: Array | undefined; + tableReservationProviders?: Array | undefined; + bookingLinks?: Array | undefined; + orderOnline?: + | { + pickUps: Array; + deliveries: Array; + } + | undefined; + questionsAndAnswers?: unknown; + ownerUpdates?: Array | undefined; + restaurantData?: + | { + tableReservationProvider?: unknown; + } + | undefined; + reviewsCount: number | null; + title: string; + placeId: string; + address: string | null; + location: unknown; + categories: Array; + isAdvertisement: boolean; + categoryName: string | null; + totalScore: number | null; + permanentlyClosed: boolean; + temporarilyClosed: boolean; + url: string; + price: string | null; + cid: string | null; + fid: string | null; + imageUrl: string | null; + hotelStars: string | null; + scrapedAt: string; + searchPageUrl?: string | undefined; + searchString?: string | undefined; + inputPlaceId?: string | undefined; + inputStartUrl?: string | undefined; + language: string; + rank?: number | undefined; + kgmid: string | null; + businessProfileId: string | null; + neighborhood: string | null; + street: string | null; + city: string | null; + countryCode: string | null; + postalCode: string | null; + state: string | null; + emails?: Array | undefined; + phones?: Array | undefined; + phonesUncertain?: Array | undefined; + linkedIns?: Array | undefined; + twitters?: Array | undefined; + instagrams?: Array | undefined; + facebooks?: Array | undefined; + youtubes?: Array | undefined; + tiktoks?: Array | undefined; + pinterests?: Array | undefined; + discords?: Array | undefined; + facebookProfiles?: Array | undefined; + instagramProfiles?: Array | undefined; + youtubeProfiles?: Array | undefined; + tiktokProfiles?: Array | undefined; + twitterProfiles?: Array | undefined; + description: string | null; + phone: string | null; + phoneUnformatted: string | null; + imagesCount: number; + openingHours?: unknown; + additionalOpeningHours?: Record | undefined; + claimThisBusiness?: boolean | undefined; + peopleAlsoSearch?: Array | undefined; + additionalInfo?: unknown; + reviewsTags?: Array | undefined; + placesTags?: Array | undefined; + imageCategories: Array; + gasPrices: Array; + reserveTableUrl: string | null; + googleFoodUrl?: string | null | undefined; + website?: string | undefined; + leadsEnrichment?: Array | undefined; + parentPlaceUrl?: string | undefined; + hotelDescription: string | null; + checkInDate: string | null; + checkOutDate: string | null; + similarHotelsNearby?: + | Array<{ + name: string | null; + rating: number | null; + reviews: number | null; + description: string | null; + price: string | null; + }> + | undefined; + hotelReviewSummary?: unknown; + hotelAds?: Array | undefined; + subTitle: string | null; + ownerDescription: string | null; + menu: string | null; + servicesLink: string | null; + locatedIn: string | null; + floor: string | null; + plusCode: string | null; + reviewsDistribution?: unknown; + reviewsRemovedNotice?: unknown; + updatesFromCustomers?: unknown; + openingHoursBusinessConfirmationText?: string | undefined; + wasOpenAtScrapeTime: boolean | null; + popularTimesLiveText: string | null; + popularTimesLivePercent: number | null; + popularTimesHistogram: Record< + string, + Array<{ + hour: number; + occupancyPercent: number; + }> + >; + isExternalServicePlace?: boolean | undefined; + externalServiceProvider?: string | null | undefined; + externalId?: string | undefined; +}; + +export type PlaceDraft = { + webResults?: Array | undefined; + userPlaceNote?: string | undefined; + tableReservationLinks?: Array | undefined; + tableReservationProviders?: Array | undefined; + bookingLinks?: Array | undefined; + orderOnline?: + | ({ + pickUps: Array; + deliveries: Array; + } & Record) + | undefined; + questionsAndAnswers?: unknown; + ownerUpdates?: Array | undefined; + restaurantData?: + | ({ + tableReservationProvider?: unknown; + } & Record) + | undefined; + reviewsCount: number | null; + title: string; + placeId: string; + address: string | null; + location: unknown; + categories: Array; + isAdvertisement: boolean; + categoryName: string | null; + totalScore: number | null; + permanentlyClosed: boolean; + temporarilyClosed: boolean; + url: string; + price: string | null; + cid: string | null; + fid: string | null; + imageUrl: string | null; + hotelStars: string | null; + scrapedAt: string; + searchPageUrl?: string | undefined; + searchString?: string | undefined; + inputPlaceId?: string | undefined; + inputStartUrl?: string | undefined; + language: string; + rank?: number | undefined; + kgmid: string | null; + businessProfileId: string | null; + neighborhood: string | null; + street: string | null; + city: string | null; + countryCode: string | null; + postalCode: string | null; + state: string | null; + emails?: Array | undefined; + phones?: Array | undefined; + phonesUncertain?: Array | undefined; + linkedIns?: Array | undefined; + twitters?: Array | undefined; + instagrams?: Array | undefined; + facebooks?: Array | undefined; + youtubes?: Array | undefined; + tiktoks?: Array | undefined; + pinterests?: Array | undefined; + discords?: Array | undefined; + facebookProfiles?: Array | undefined; + instagramProfiles?: Array | undefined; + youtubeProfiles?: Array | undefined; + tiktokProfiles?: Array | undefined; + twitterProfiles?: Array | undefined; + description: string | null; + phone: string | null; + phoneUnformatted: string | null; + imagesCount: number; + openingHours?: unknown; + additionalOpeningHours?: Record | undefined; + claimThisBusiness?: boolean | undefined; + peopleAlsoSearch?: Array | undefined; + additionalInfo?: unknown; + reviewsTags?: Array | undefined; + placesTags?: Array | undefined; + imageCategories: Array; + gasPrices: Array; + reserveTableUrl: string | null; + googleFoodUrl?: string | null | undefined; + website?: string | undefined; + leadsEnrichment?: Array | undefined; + parentPlaceUrl?: string | undefined; + hotelDescription: string | null; + checkInDate: string | null; + checkOutDate: string | null; + similarHotelsNearby?: + | Array< + { + name: string | null; + rating: number | null; + reviews: number | null; + description: string | null; + price: string | null; + } & Record + > + | undefined; + hotelReviewSummary?: unknown; + hotelAds?: Array | undefined; + subTitle: string | null; + ownerDescription: string | null; + menu: string | null; + servicesLink: string | null; + locatedIn: string | null; + floor: string | null; + plusCode: string | null; + reviewsDistribution?: unknown; + reviewsRemovedNotice?: unknown; + updatesFromCustomers?: unknown; + openingHoursBusinessConfirmationText?: string | undefined; + wasOpenAtScrapeTime: boolean | null; + popularTimesLiveText: string | null; + popularTimesLivePercent: number | null; + popularTimesHistogram: Record< + string, + Array< + { + hour: number; + occupancyPercent: number; + } & Record + > + >; + isExternalServicePlace?: boolean | undefined; + externalServiceProvider?: string | null | undefined; + externalId?: string | undefined; +} & Record; diff --git a/test/local/__fixtures__/lib/schema-to-ts/expected/input/google-places.ts b/test/local/__fixtures__/lib/schema-to-ts/expected/input/google-places.ts new file mode 100644 index 000000000..c06d43ae7 --- /dev/null +++ b/test/local/__fixtures__/lib/schema-to-ts/expected/input/google-places.ts @@ -0,0 +1,8845 @@ +// oxlint-disable +// @generated schema-ts v1-3d755201c8cdec14 — do not edit + +export type Input = { + searchStringsArray?: Array | undefined; + locationQuery?: string | undefined; + maxCrawledPlacesPerSearch?: number | undefined; + language: + | 'en' + | 'af' + | 'az' + | 'id' + | 'ms' + | 'bs' + | 'ca' + | 'cs' + | 'da' + | 'de' + | 'et' + | 'es' + | 'es-419' + | 'eu' + | 'fil' + | 'fr' + | 'gl' + | 'hr' + | 'zu' + | 'is' + | 'it' + | 'sw' + | 'lv' + | 'lt' + | 'hu' + | 'nl' + | 'no' + | 'uz' + | 'pl' + | 'pt-BR' + | 'pt-PT' + | 'ro' + | 'sq' + | 'sk' + | 'sl' + | 'fi' + | 'sv' + | 'vi' + | 'tr' + | 'el' + | 'bg' + | 'ky' + | 'kk' + | 'mk' + | 'mn' + | 'ru' + | 'sr' + | 'uk' + | 'ka' + | 'hy' + | 'iw' + | 'ur' + | 'ar' + | 'fa' + | 'am' + | 'ne' + | 'hi' + | 'mr' + | 'bn' + | 'pa' + | 'gu' + | 'ta' + | 'te' + | 'kn' + | 'ml' + | 'si' + | 'th' + | 'lo' + | 'my' + | 'km' + | 'ko' + | 'ja' + | 'zh-CN' + | 'zh-TW'; + categoryFilterWords?: + | Array< + | 'abbey' + | 'accountant' + | 'accounting' + | 'acupuncturist' + | 'aeroclub' + | 'agriculture' + | 'airline' + | 'airport' + | 'airstrip' + | 'allergist' + | 'amphitheater' + | 'amphitheatre' + | 'anesthesiologist' + | 'appraiser' + | 'aquarium' + | 'arboretum' + | 'architect' + | 'archive' + | 'arena' + | 'artist' + | 'ashram' + | 'astrologer' + | 'atm' + | 'attorney' + | 'audiologist' + | 'auditor' + | 'auditorium' + | 'bakery' + | 'band' + | 'bank' + | 'bar' + | 'barrister' + | 'basilica' + | 'bazar' + | 'beach' + | 'beautician' + | 'bistro' + | 'blacksmith' + | 'bodega' + | 'bookbinder' + | 'botanica' + | 'boutique' + | 'brasserie' + | 'brewery' + | 'brewpub' + | 'bricklayer' + | 'bridge' + | 'builder' + | 'building' + | 'bullring' + | 'butchers' + | 'cafe' + | 'cafeteria' + | 'campground' + | 'cannery' + | 'cardiologist' + | 'carpenter' + | 'cars' + | 'carvery' + | 'cashpoint' + | 'casino' + | 'castle' + | 'caterer' + | 'catering' + | 'cathedral' + | 'cattery' + | 'cemetery' + | 'chalet' + | 'chapel' + | 'charcuterie' + | 'charity' + | 'chemist' + | 'childminder' + | 'chiropractor' + | 'choir' + | 'church' + | 'churreria' + | 'circus' + | 'cleaners' + | 'clergyman' + | 'clinic' + | 'club' + | 'coalfield' + | 'college' + | 'company' + | 'computers' + | 'congregation' + | 'construction' + | 'consultant' + | 'contractor' + | 'conveyancer' + | 'coppersmith' + | 'cottage' + | 'council' + | 'counselor' + | 'courthouse' + | 'creperie' + | 'dairy' + | 'deli' + | 'delicatessen' + | 'dentist' + | 'dermatologist' + | 'design' + | 'dhaba' + | 'diabetologist' + | 'dietitian' + | 'diner' + | 'distillery' + | 'dj' + | 'doctor' + | 'doula' + | 'dressmaker' + | 'dyeworks' + | 'eatery' + | 'education' + | 'electrician' + | 'electronics' + | 'embassy' + | 'endocrinologist' + | 'endodontist' + | 'endoscopist' + | 'engineer' + | 'engraver' + | 'entertainer' + | 'entertainment' + | 'establishment' + | 'executor' + | 'exhibit' + | 'exporter' + | 'fairground' + | 'farm' + | 'farmstay' + | 'favela' + | 'festival' + | 'florist' + | 'fortress' + | 'foundation' + | 'foundry' + | 'frituur' + | 'garden' + | 'gardener' + | 'gasfitter' + | 'gastroenterologist' + | 'gastropub' + | 'gemologist' + | 'genealogist' + | 'geriatrician' + | 'glazier' + | 'goldsmith' + | 'government' + | 'greengrocer' + | 'greenhouse' + | 'grill' + | 'gurudwara' + | 'gym' + | 'gynecologist' + | 'haberdashery' + | 'hairdresser' + | 'hammam' + | 'handicraft' + | 'handyman/handywoman/handyperson' + | 'health' + | 'heliport' + | 'hematologist' + | 'hepatologist' + | 'herbalist' + | 'homeopath' + | 'homestay' + | 'hospice' + | 'hospital' + | 'hostel' + | 'hotel' + | 'hypermarket' + | 'immunologist' + | 'importer' + | 'inn' + | 'instruction' + | 'intensivist' + | 'internist' + | 'island' + | 'jeweler' + | 'joiner' + | 'junkyard' + | 'karaoke' + | 'kennel' + | 'kindergarten' + | 'kinesiologist' + | 'kinesiotherapist' + | 'kiosk' + | 'laboratory' + | 'lake' + | 'landscaper' + | 'lapidary' + | 'laundromat' + | 'laundry' + | 'lawyer' + | 'library' + | 'lido' + | 'liquidator' + | 'locksmith' + | 'lodge' + | 'lodging' + | 'lounge' + | 'lyceum' + | 'magician' + | 'makerspace' + | 'manufacturer' + | 'marae' + | 'marina' + | 'market' + | 'mechanic' + | 'memorial' + | 'metalwork' + | 'meyhane' + | 'midwife' + | 'mill' + | 'mine' + | 'mission' + | 'mohel' + | 'monastery' + | 'monument' + | 'mortuary' + | 'mosque' + | 'motel' + | 'mover' + | 'musalla' + | 'museum' + | 'musician' + | 'nephrologist' + | 'neurologist' + | 'neurophysiologist' + | 'neuropsychologist' + | 'neurosurgeon' + | 'newsstand' + | 'numerologist' + | 'nutritionist' + | 'observatory' + | 'obstetrician-gynecologist' + | 'office' + | 'oilfield' + | 'oncologist' + | 'onsen' + | 'ophthalmologist' + | 'optician' + | 'optometrist' + | 'orchard' + | 'orchestra' + | 'orphanage' + | 'orthodontist' + | 'orthoptist' + | 'osteopath' + | 'otolaryngologist' + | 'pagoda' + | 'painter' + | 'painting' + | 'parapharmacy' + | 'parish' + | 'park' + | 'parking' + | 'pathologist' + | 'patisserie' + | 'pediatrician' + | 'pedorthist' + | 'periodontist' + | 'pharmacy' + | 'photographer' + | 'physiatrist' + | 'physiotherapist' + | 'planetarium' + | 'plasterer' + | 'playground' + | 'playgroup' + | 'plumber' + | 'podiatrist' + | 'pre-school' + | 'preschool' + | 'priest' + | 'prison' + | 'proctologist' + | 'promenade' + | 'prosthodontist' + | 'psychiatrist' + | 'psychic' + | 'psychoanalyst' + | 'psychologist' + | 'psychotherapist' + | 'pub' + | 'publisher' + | 'pulmonologist' + | 'pyrotechnician' + | 'quarry' + | 'radiologist' + | 'radiotherapist' + | 'rafting' + | 'ranch' + | 'recreation' + | 'recruiter' + | 'rectory' + | 'recycling' + | 'reflexologist' + | 'remodeler' + | 'restaurant' + | 'rheumatologist' + | 'river' + | 'rodeo' + | 'rugby' + | 'sacem' + | 'saddlery' + | 'sailmaker' + | 'sambodrome' + | 'sauna' + | 'school' + | 'scouting' + | 'sculptor' + | 'sculpture' + | 'seitai' + | 'seminary' + | 'services' + | 'sexologist' + | 'shelter' + | 'shipyard' + | 'shop' + | 'shopfitter' + | 'showroom' + | 'shrine' + | 'silversmith' + | 'skatepark' + | 'slaughterhouse' + | 'soapland' + | 'spa' + | 'sports' + | 'stable' + | 'stadium' + | 'stage' + | 'statuary' + | 'store' + | 'stylist' + | 'supermarket' + | 'surgeon' + | 'surveyor' + | 'synagogue' + | 'tailor' + | 'takeaway' + | 'tannery' + | 'taxidermist' + | 'telecommunications' + | 'toolroom' + | 'travel' + | 'turnery' + | 'university' + | 'urologist' + | 'velodrome' + | 'venereologist' + | 'veterinarian' + | 'villa' + | 'vineyard' + | 'warehouse' + | 'weir' + | 'welder' + | 'wholesaler' + | 'winery' + | 'woods' + | 'woodworker' + | 'yakatabune' + | 'yeshiva' + | 'zoo' + | 'abarth dealer' + | 'abortion clinic' + | 'abrasives supplier' + | 'academic department' + | 'açaí shop' + | 'acaraje restaurant' + | 'accounting firm' + | 'accounting school' + | 'acoustical consultant' + | 'acrylic store' + | 'acupuncture clinic' + | 'acupuncture school' + | 'acura dealer' + | 'administrative attorney' + | 'adoption agency' + | 'advertising agency' + | 'advertising photographer' + | 'advertising service' + | 'aerial photographer' + | 'aerobics instructor' + | 'aeromodel shop' + | 'aeronautical engineer' + | 'aerospace company' + | 'afghan restaurant' + | 'african restaurant' + | 'agenzia entrate' + | 'aggregate supplier' + | 'agistment service' + | 'agricultural association' + | 'agricultural cooperative' + | 'agricultural engineer' + | 'agricultural organization' + | 'agricultural production' + | 'agricultural service' + | 'agrochemicals supplier' + | 'aikido club' + | 'aikido school' + | 'air taxi' + | 'airbrushing service' + | 'aircraft dealer' + | 'aircraft manufacturer' + | 'alcohol manufacturer' + | 'alliance church' + | 'alsace restaurant' + | 'alternator supplier' + | 'aluminium supplier' + | 'aluminum supplier' + | 'aluminum welder' + | 'aluminum window' + | 'ambulance service' + | 'american restaurant' + | 'ammunition supplier' + | 'amusement center' + | 'amusement park' + | 'anago restaurant' + | 'andalusian restaurant' + | 'andhra restaurant' + | 'anganwadi center' + | 'anglican church' + | 'animal hospital' + | 'animal shelter' + | 'animation studio' + | 'anime club' + | 'antenna service' + | 'antique store' + | 'apartment building' + | 'apartment complex' + | 'apostolic church' + | 'apparel company' + | 'appliance store' + | 'apprenticeship center' + | 'aquaculture farm' + | 'aquarium shop' + | 'aquatic centre' + | 'arab restaurant' + | 'arborist service' + | 'archaeological museum' + | 'archery club' + | 'archery range' + | 'archery store' + | 'architects association' + | 'architectural designer' + | 'architecture firm' + | 'architecture school' + | 'argentinian restaurant' + | 'armenian church' + | 'armenian restaurant' + | 'army facility' + | 'army museum' + | 'aromatherapy class' + | 'aromatherapy service' + | 'art cafe' + | 'art center' + | 'art dealer' + | 'art gallery' + | 'art museum' + | 'art school' + | 'art studio' + | 'artistic handicrafts' + | 'arts organization' + | 'asian restaurant' + | 'asphalt contractor' + | 'assamese restaurant' + | 'assistante maternelle' + | 'asturian restaurant' + | 'athletic club' + | 'athletic field' + | 'athletic park' + | 'athletic track' + | 'atv dealer' + | 'auction house' + | 'audi dealer' + | 'australian restaurant' + | 'austrian restaurant' + | 'auto auction' + | 'auto broker' + | 'auto market' + | 'auto painting' + | 'auto upholsterer' + | 'auto wrecker' + | 'automation company' + | 'aviation consultant' + | 'awadhi restaurant' + | 'awning supplier' + | 'ayurvedic clinic' + | 'azerbaijani restaurant' + | 'baby store' + | 'baden restaurant' + | 'badminton club' + | 'badminton complex' + | 'badminton court' + | 'bag shop' + | 'bagel shop' + | 'bait shop' + | 'bakery equipment' + | 'bakso restaurant' + | 'balinese restaurant' + | 'ballet school' + | 'ballet theater' + | 'balloon artist' + | 'balloon store' + | 'bangladeshi restaurant' + | 'bangle shop' + | 'bankruptcy attorney' + | 'bankruptcy service' + | 'banner store' + | 'banquet hall' + | 'baptist church' + | 'bar pmu' + | 'bar tabac' + | 'barbecue area' + | 'barbecue restaurant' + | 'barber school' + | 'barber shop' + | 'bariatric surgeon' + | 'bark supplier' + | 'barrel supplier' + | 'bartending school' + | 'baseball club' + | 'baseball field' + | 'basket supplier' + | 'basketball club' + | 'basketball court' + | 'basque restaurant' + | 'batak restaurant' + | 'bathroom remodeler' + | 'bathroom renovator' + | 'battery manufacturer' + | 'battery store' + | 'battery wholesaler' + | 'bavarian restaurant' + | 'beach club' + | 'beach pavillion' + | 'bead store' + | 'bead wholesaler' + | 'bearing supplier' + | 'beauty parlour' + | 'beauty salon' + | 'beauty school' + | 'bed shop' + | 'bedding store' + | 'beer distributor' + | 'beer garden' + | 'beer hall' + | 'beer store' + | 'belgian restaurant' + | 'belt shop' + | 'bengali restaurant' + | 'bentley dealer' + | 'berry restaurant' + | 'betawi restaurant' + | 'betting agency' + | 'beverage distributor' + | 'beverage supplier' + | 'bicycle club' + | 'bicycle rack' + | 'bicycle shop' + | 'bicycle store' + | 'bicycle wholesaler' + | 'bike wash' + | 'bilingual school' + | 'bingo hall' + | 'biochemistry lab' + | 'biofeedback therapist' + | 'biotechnology company' + | 'bird shop' + | 'birth center' + | 'biryani restaurant' + | 'blinds shop' + | 'blood bank' + | 'blueprint service' + | 'blues club' + | 'bmw dealer' + | 'bmx club' + | 'bmx park' + | 'boarding house' + | 'boarding school' + | 'boat builders' + | 'boat club' + | 'boat dealer' + | 'boat ramp' + | 'boating instructor' + | 'boiler manufacturer' + | 'boiler supplier' + | 'bonesetting house' + | 'book publisher' + | 'book store' + | 'bookkeeping service' + | 'books wholesaler' + | 'boot camp' + | 'boot store' + | 'border guard' + | 'botanical garden' + | 'bowling alley' + | 'bowling club' + | 'boxing club' + | 'boxing gym' + | 'boxing ring' + | "boys' hostel" + | 'bpo company' + | 'brake shop' + | 'branding agency' + | 'brazilian pastelaria' + | 'brazilian restaurant' + | 'breakfast restaurant' + | 'brick manufacturer' + | 'bridal shop' + | 'bridge club' + | 'british restaurant' + | 'brunch restaurant' + | 'buddhist temple' + | 'buffet restaurant' + | 'bugatti dealer' + | 'buick dealer' + | 'building consultant' + | 'building designer' + | 'building firm' + | 'building inspector' + | 'building society' + | 'bulgarian restaurant' + | 'burmese restaurant' + | 'burrito restaurant' + | 'bus charter' + | 'bus company' + | 'bus depot' + | 'bus station' + | 'bus stop' + | 'business attorney' + | 'business broker' + | 'business center' + | 'business park' + | 'business school' + | 'butcher shop' + | 'butsudan store' + | 'cabaret club' + | 'cabinet maker' + | 'cabinet store' + | 'cable company' + | 'cadillac dealer' + | 'cajun restaurant' + | 'cake shop' + | 'californian restaurant' + | 'call center' + | 'call shop' + | 'calligraphy lesson' + | 'cambodian restaurant' + | 'camera store' + | 'camping cabin' + | 'camping farm' + | 'camping store' + | 'canadian restaurant' + | 'candle store' + | 'candy store' + | 'cannabis club' + | 'cannabis store' + | 'canoeing area' + | 'cantabrian restaurant' + | 'cantonese restaurant' + | 'capoeira school' + | 'capsule hotel' + | 'car dealer' + | 'car factory' + | 'car manufacturer' + | 'car wash' + | 'carabinieri police' + | 'care services' + | 'caribbean restaurant' + | 'carnival club' + | 'carpet installer' + | 'carpet manufacturer' + | 'carpet store' + | 'carpet wholesaler' + | 'casket service' + | 'castilian restaurant' + | 'cat breeder' + | 'cat cafe' + | 'cat trainer' + | 'catalonian restaurant' + | 'catholic cathedral' + | 'catholic church' + | 'catholic school' + | 'cattle farm' + | 'cattle market' + | 'caucasian restaurant' + | 'cbse school' + | 'cd store' + | 'ceiling supplier' + | 'cement manufacturer' + | 'cement supplier' + | 'cendol restaurant' + | 'central bank' + | 'ceramic manufacturer' + | 'ceramics wholesaler' + | 'certification agency' + | 'charter school' + | 'chartered accountant' + | 'chauffeur service' + | 'cheese manufacturer' + | 'cheese shop' + | 'cheesesteak restaurant' + | 'chemical exporter' + | 'chemical industry' + | 'chemical manufacturer' + | 'chemical plant' + | 'chemical wholesaler' + | 'chemistry lab' + | 'chesapeake restaurant' + | 'chess club' + | 'chess instructor' + | 'chevrolet dealer' + | 'chicken restaurant' + | 'chicken shop' + | 'child psychiatrist' + | 'child psychologist' + | 'childbirth class' + | 'children hall' + | 'children policlinic' + | "children's cafe" + | "children's camp" + | "children's club" + | "children's hospital" + | "children's store" + | 'childrens store' + | 'chilean restaurant' + | 'chimney services' + | 'chimney sweep' + | 'chinaware store' + | 'chinese bakery' + | 'chinese restaurant' + | 'chinese supermarket' + | 'chinese takeaway' + | 'chocolate artisan' + | 'chocolate cafe' + | 'chocolate factory' + | 'chocolate shop' + | 'chop bar' + | 'chophouse restaurant' + | 'christian church' + | 'christian college' + | 'christmas market' + | 'christmas store' + | 'chrysler dealer' + | 'cider bar' + | 'cider mill' + | 'cigar shop' + | 'citroen dealer' + | 'city courthouse' + | 'city hall' + | 'city park' + | 'civic center' + | 'civil engineer' + | 'civil police' + | 'cleaning service' + | 'clothes market' + | 'clothing manufacturer' + | 'clothing shop' + | 'clothing store' + | 'clothing supplier' + | 'clothing wholesaler' + | 'co-ed school' + | 'coaching center' + | 'coaching service' + | 'coal exporter' + | 'coal supplier' + | 'cocktail bar' + | 'coffee roasters' + | 'coffee shop' + | 'coffee stand' + | 'coffee store' + | 'coffee wholesaler' + | 'coffin supplier' + | 'coin dealer' + | 'collectibles store' + | 'colombian restaurant' + | 'comedy club' + | 'comic cafe' + | 'commercial agent' + | 'commercial photographer' + | 'commercial printer' + | 'community center' + | 'community college' + | 'community garden' + | 'community school' + | 'company registry' + | 'computer club' + | 'computer consultant' + | 'computer service' + | 'computer shop' + | 'computer store' + | 'computer wholesaler' + | 'concert hall' + | 'concrete contractor' + | 'concrete factory' + | 'condiments supplier' + | 'condominium complex' + | 'confectionery store' + | 'confectionery wholesaler' + | 'conference center' + | 'conservative club' + | 'conservative synagogue' + | 'consignment shop' + | 'construction company' + | 'container service' + | 'container supplier' + | 'container terminal' + | 'containers supplier' + | 'continental restaurant' + | 'convenience store' + | 'convention center' + | 'cookie shop' + | 'cooking class' + | 'cooking school' + | 'cooling plant' + | 'cooperative bank' + | 'copper supplier' + | 'copy shop' + | 'copywriting service' + | 'corporate campus' + | 'corporate office' + | 'cosmetic dentist' + | 'cosmetic surgeon' + | 'cosmetics industry' + | 'cosmetics shop' + | 'cosmetics store' + | 'cosmetics wholesaler' + | 'cosplay cafe' + | 'costume store' + | 'cottage rental' + | 'cottage village' + | 'cotton exporter' + | 'cotton mill' + | 'cotton supplier' + | 'countertop contractor' + | 'countertop store' + | 'country club' + | 'country house' + | 'country park' + | 'courier service' + | 'court reporter' + | 'couscous restaurant' + | 'coworking space' + | 'crab house' + | 'craft store' + | 'cramming school' + | 'crane dealer' + | 'crane service' + | 'craniosacral therapy' + | 'credit union' + | 'cremation service' + | 'creole restaurant' + | 'cricket club' + | 'cricket ground' + | 'cricket shop' + | 'croatian restaurant' + | 'crop grower' + | 'croquet club' + | 'cruise agency' + | 'cruise terminal' + | 'crypto atm' + | 'cuban restaurant' + | 'culinary school' + | 'cultural association' + | 'cultural center' + | 'cultural landmark' + | 'cupcake shop' + | 'cupra dealer' + | 'curling club' + | 'curling hall' + | 'curtain store' + | 'custom tailor' + | 'customs broker' + | 'customs consultant' + | 'customs office' + | 'customs warehouse' + | 'cutlery store' + | 'cycling park' + | 'czech restaurant' + | 'dacia dealer' + | 'daihatsu dealer' + | 'dairy farm' + | 'dairy store' + | 'dairy supplier' + | 'dance club' + | 'dance company' + | 'dance hall' + | 'dance pavillion' + | 'dance restaurant' + | 'dance school' + | 'dance store' + | 'danish restaurant' + | 'dart bar' + | 'dating service' + | 'day spa' + | 'day-use onsen' + | 'deaf church' + | 'deaf service' + | 'debt collecting' + | 'decal supplier' + | 'deck builder' + | 'delivery restaurant' + | 'delivery service' + | 'demolition contractor' + | 'dental clinic' + | 'dental hygienist' + | 'dental laboratory' + | 'dental radiology' + | 'dental school' + | 'department store' + | 'desalination plant' + | 'design agency' + | 'design engineer' + | 'design institute' + | 'dessert restaurant' + | 'dessert shop' + | 'detention center' + | 'diabetes center' + | 'diagnostic center' + | 'dialysis center' + | 'diamond buyer' + | 'diamond dealer' + | 'diaper service' + | 'digital printer' + | 'dinner theater' + | 'dirt supplier' + | 'disco club' + | 'discount store' + | 'discount supermarket' + | 'distribution service' + | 'district attorney' + | 'district justice' + | 'district office' + | 'dive club' + | 'dive shop' + | 'diving center' + | 'divorce lawyer' + | 'divorce service' + | 'dj service' + | 'do-it-yourself shop' + | 'dock builder' + | 'dodge dealer' + | 'dog breeder' + | 'dog cafe' + | 'dog park' + | 'dog trainer' + | 'dog walker' + | 'dojo restaurant' + | 'doll store' + | 'dollar store' + | 'domestic airport' + | 'dominican restaurant' + | 'donations center' + | 'donut shop' + | 'door manufacturer' + | 'door shop' + | 'door supplier' + | 'door warehouse' + | 'drafting service' + | 'drainage service' + | 'drama school' + | 'drawing lessons' + | 'dress shop' + | 'dress store' + | 'drilling contractor' + | 'driveshaft shop' + | 'driving school' + | 'drone service' + | 'drone shop' + | 'drug store' + | 'drum school' + | 'drum store' + | 'dry cleaner' + | 'ducati dealer' + | 'dude ranch' + | 'dumpling restaurant' + | 'durum restaurant' + | 'dutch restaurant' + | 'dvd store' + | 'dye store' + | 'dynamometer supplier' + | 'e-commerce service' + | 'eclectic restaurant' + | 'ecological park' + | 'ecologists association' + | 'economic consultant' + | 'ecuadorian restaurant' + | 'education center' + | 'education centre' + | 'educational consultant' + | 'educational institution' + | 'egg supplier' + | 'egyptian restaurant' + | 'electrical engineer' + | 'electrical substation' + | 'electronics company' + | 'electronics engineer' + | 'electronics manufacturer' + | 'electronics store' + | 'electronics wholesaler' + | 'elementary school' + | 'elevator manufacturer' + | 'elevator service' + | 'embossing service' + | 'embroidery service' + | 'embroidery shop' + | 'emdr psychotherapist' + | 'emergency room' + | 'emergency training' + | 'employment agency' + | 'employment attorney' + | 'employment center' + | 'employment consultant' + | 'energy supplier' + | 'engineering consultant' + | 'engineering school' + | 'english restaurant' + | 'entertainment agency' + | 'envelope supplier' + | 'environment office' + | 'environmental consultant' + | 'environmental engineer' + | 'environmental organization' + | 'episcopal church' + | 'equestrian club' + | 'equestrian facility' + | 'equestrian store' + | 'equipment exporter' + | 'equipment importer' + | 'equipment supplier' + | 'eritrean restaurant' + | 'erotic massage' + | 'escrow service' + | 'espresso bar' + | 'estate agent' + | 'estate appraiser' + | 'estate liquidator' + | 'ethiopian restaurant' + | 'ethnographic museum' + | 'european restaurant' + | 'evangelical church' + | 'evening school' + | 'event planner' + | 'event venue' + | 'excavating contractor' + | 'exhibition planner' + | 'eyebrow bar' + | 'eyelash salon' + | 'fabric store' + | 'fabric wholesaler' + | 'fabrication engineer' + | 'facial spa' + | 'falafel restaurant' + | 'family counselor' + | 'family restaurant' + | 'farm bureau' + | 'farm school' + | 'farm shop' + | "farmers' market" + | 'farrier service' + | 'fashion designer' + | 'fast food' + | 'fastener supplier' + | 'fax service' + | 'federal police' + | 'feed manufacturer' + | 'fence contractor' + | 'fencing salon' + | 'fencing school' + | 'ferrari dealer' + | 'ferris wheel' + | 'ferry service' + | 'fertility clinic' + | 'fertility physician' + | 'fertilizer supplier' + | 'festival hall' + | 'fiat dealer' + | 'fiberglass supplier' + | 'figurine shop' + | 'filipino restaurant' + | 'filtration plant' + | 'finance broker' + | 'financial advisor' + | 'financial audit' + | 'financial consultant' + | 'financial institution' + | 'financial planner' + | 'fingerprinting service' + | 'finnish restaurant' + | 'fire station' + | 'firearms academy' + | 'fireplace manufacturer' + | 'fireplace store' + | 'firewood supplier' + | 'fireworks store' + | 'fireworks supplier' + | 'fish farm' + | 'fish processing' + | 'fish restaurant' + | 'fish spa' + | 'fish store' + | 'fishing camp' + | 'fishing charter' + | 'fishing club' + | 'fishing pier' + | 'fishing pond' + | 'fishing store' + | 'fitness center' + | 'fitness centre' + | 'flag store' + | 'flamenco school' + | 'flamenco theater' + | 'flea market' + | 'flight school' + | 'floating market' + | 'flooring contractor' + | 'flooring store' + | 'floridian restaurant' + | 'flour mill' + | 'flower delivery' + | 'flower designer' + | 'flower market' + | 'fmcg manufacturer' + | 'fondue restaurant' + | 'food bank' + | 'food broker' + | 'food court' + | 'food manufacturer' + | 'food producer' + | 'food store' + | 'foot bath' + | 'foot care' + | 'football club' + | 'football field' + | 'footwear wholesaler' + | 'ford dealer' + | 'foreclosure service' + | 'foreign consulate' + | 'forensic consultant' + | 'forestry service' + | 'forklift dealer' + | 'fountain contractor' + | 'foursquare church' + | 'franconian restaurant' + | 'fraternal organization' + | 'free clinic' + | 'freestyle wrestling' + | 'french restaurant' + | 'friends church' + | 'fruit parlor' + | 'fruit wholesaler' + | 'fruits wholesaler' + | 'fuel pump' + | 'fuel supplier' + | 'fugu restaurant' + | 'funeral director' + | 'funeral home' + | 'fur manufacturer' + | 'fur service' + | 'furnace store' + | 'furniture accessories' + | 'furniture maker' + | 'furniture manufacturer' + | 'furniture store' + | 'furniture wholesaler' + | 'fusion restaurant' + | 'futon store' + | 'futsal court' + | 'galician restaurant' + | 'gambling house' + | 'gambling instructor' + | 'game store' + | 'garage builder' + | 'garbage dump' + | 'garden center' + | 'garment exporter' + | 'gas company' + | 'gas engineer' + | 'gas shop' + | 'gas station' + | 'gasket manufacturer' + | 'gastrointestinal surgeon' + | 'gated community' + | 'gay bar' + | 'gay sauna' + | 'gazebo builder' + | 'general contractor' + | 'general hospital' + | 'general practitioner' + | 'general store' + | 'genesis dealer' + | 'geological service' + | 'georgian restaurant' + | 'geotechnical engineer' + | 'german restaurant' + | 'ghost town' + | 'gift shop' + | 'gimbap restaurant' + | 'girl bar' + | "girls' hostel" + | 'glass blower' + | 'glass industry' + | 'glass manufacturer' + | 'glass merchant' + | 'glass shop' + | 'glassware manufacturer' + | 'glassware store' + | 'glassware wholesaler' + | 'gluten-free restaurant' + | 'gmc dealer' + | 'goan restaurant' + | 'gold dealer' + | 'goldfish store' + | 'golf club' + | 'golf course' + | 'golf instructor' + | 'golf shop' + | 'gospel church' + | 'government college' + | 'government hospital' + | 'government office' + | 'government school' + | 'gps supplier' + | 'graduate school' + | 'grain elevator' + | 'grammar school' + | 'granite supplier' + | 'graphic designer' + | 'gravel pit' + | 'gravel plant' + | 'greek restaurant' + | 'greyhound stadium' + | 'grill store' + | 'grocery store' + | 'group accommodation' + | 'group home' + | 'grow shop' + | 'guardia civil' + | 'guatemalan restaurant' + | 'guest house' + | 'guitar instructor' + | 'guitar store' + | 'gujarati restaurant' + | 'gun club' + | 'gun shop' + | 'gutter service' + | 'gymnasium school' + | 'gymnastics center' + | 'gymnastics club' + | 'gyro restaurant' + | 'gyudon restaurant' + | 'hair salon' + | 'haitian restaurant' + | 'hakka restaurant' + | 'halal restaurant' + | 'haleem restaurant' + | 'halfway house' + | 'ham shop' + | 'hamburger restaurant' + | 'hand surgeon' + | 'handbags shop' + | 'handball club' + | 'handball court' + | 'handicraft exporter' + | 'handicraft fair' + | 'handicraft museum' + | 'handicraft school' + | 'handicrafts wholesaler' + | 'hardware shop' + | 'hardware store' + | 'harley-davidson dealer' + | 'hat shop' + | 'haunted house' + | 'hawaiian restaurant' + | 'hawker stall' + | 'hay supplier' + | 'health consultant' + | 'health counselor' + | 'health resort' + | 'health spa' + | 'heart hospital' + | 'heating contractor' + | 'height works' + | 'helicopter charter' + | 'herb shop' + | 'heritage building' + | 'heritage museum' + | 'heritage preservation' + | 'heritage railroad' + | 'high school' + | 'highway patrol' + | 'hiking area' + | 'hiking guide' + | 'hindu priest' + | 'hindu temple' + | 'hispanic church' + | 'historical landmark' + | 'historical place' + | 'historical society' + | 'history museum' + | 'hoagie restaurant' + | 'hobby store' + | 'hockey club' + | 'hockey field' + | 'hockey rink' + | 'holding company' + | 'holiday apartment' + | 'holiday flat' + | 'holiday home' + | 'holiday park' + | 'home builder' + | 'home help' + | 'home inspector' + | 'homekill service' + | 'homeless service' + | 'homeless shelter' + | 'homeopathic pharmacy' + | "homeowners' association" + | 'homewares shop' + | 'honda dealer' + | 'honduran restaurant' + | 'honey farm' + | 'hookah bar' + | 'hookah store' + | 'horse breeder' + | 'horse trainer' + | 'horseshoe smith' + | 'horsestable studfarm' + | 'hose supplier' + | 'hospital department' + | 'host club' + | 'house sitter' + | 'housing association' + | 'housing authority' + | 'housing complex' + | 'housing cooperative' + | 'housing development' + | 'housing society' + | 'hungarian restaurant' + | 'hunting area' + | 'hunting club' + | 'hunting preserve' + | 'hunting store' + | 'hvac contractor' + | 'hyderabadi restaurant' + | 'hydraulic engineer' + | 'hypnotherapy service' + | 'hyundai dealer' + | 'ice supplier' + | 'icelandic restaurant' + | 'icse school' + | 'image consultant' + | 'imax theater' + | 'immigration attorney' + | 'impermeabilization service' + | 'incense supplier' + | 'incineration plant' + | 'indian restaurant' + | 'indian takeaway' + | 'indonesian restaurant' + | 'indoor cycling' + | 'indoor lodging' + | 'indoor playground' + | 'indoor snowcenter' + | 'industrial consultant' + | 'industrial engineer' + | 'industrial supermarket' + | 'infiniti dealer' + | 'information services' + | 'insolvency service' + | 'installation service' + | 'instrumentation engineer' + | 'insulation contractor' + | 'insulator supplier' + | 'insurance agency' + | 'insurance attorney' + | 'insurance broker' + | 'insurance company' + | 'interior decoration' + | 'interior decorator' + | 'interior designer' + | 'international airport' + | 'international school' + | 'internet cafe' + | 'internet shop' + | 'investment bank' + | 'investment company' + | 'investment service' + | 'irish pub' + | 'irish restaurant' + | 'iron works' + | 'israeli restaurant' + | 'isuzu dealer' + | 'italian restaurant' + | 'izakaya restaurant' + | 'jaguar dealer' + | 'jain temple' + | 'jamaican restaurant' + | 'janitorial service' + | 'japanese delicatessen' + | 'japanese inn' + | 'japanese restaurant' + | 'japanese steakhouse' + | 'javanese restaurant' + | 'jazz club' + | 'jeans shop' + | 'jeep dealer' + | 'jewellery store' + | 'jewelry appraiser' + | 'jewelry buyer' + | 'jewelry designer' + | 'jewelry engraver' + | 'jewelry exporter' + | 'jewelry manufacturer' + | 'jewelry store' + | 'jewish restaurant' + | 'judaica store' + | 'judicial auction' + | 'judicial scrivener' + | 'judo club' + | 'judo school' + | 'juice shop' + | 'jujitsu school' + | 'junior college' + | 'junk dealer' + | 'justice department' + | 'jute exporter' + | 'jute mill' + | 'kabaddi club' + | 'kaiseki restaurant' + | 'karaoke bar' + | 'karate club' + | 'karate school' + | 'karma dealer' + | 'karnataka restaurant' + | 'kashmiri restaurant' + | 'kazakhstani restaurant' + | 'kebab shop' + | 'kerala restaurant' + | 'kerosene supplier' + | 'kia dealer' + | 'kickboxing school' + | 'kimono store' + | 'kitchen remodeler' + | 'kitchen renovator' + | 'kite shop' + | 'knife store' + | 'knit shop' + | 'knitting instructor' + | 'knitwear manufacturer' + | 'kofta restaurant' + | 'konkani restaurant' + | 'korean church' + | 'korean restaurant' + | 'koshari restaurant' + | 'kosher restaurant' + | 'kushiyaki restaurant' + | 'labor union' + | 'ladder supplier' + | 'lamborghini dealer' + | 'lamination service' + | 'lancia dealer' + | 'land allotment' + | 'land surveyor' + | 'landscape architect' + | 'landscape designer' + | 'landscape gardener' + | 'language school' + | 'laotian restaurant' + | 'lasik surgeon' + | 'laundry service' + | 'law firm' + | 'law library' + | 'law school' + | 'lawyers association' + | 'leagues club' + | 'learning center' + | 'leasing service' + | 'leather exporter' + | 'leather wholesaler' + | 'lebanese restaurant' + | 'lechon restaurant' + | 'legal services' + | 'leisure centre' + | 'lesbian bar' + | 'lexus dealer' + | 'license bureau' + | 'life coach' + | 'lighting consultant' + | 'lighting contractor' + | 'lighting manufacturer' + | 'lighting store' + | 'ligurian restaurant' + | 'limousine service' + | 'linens store' + | 'lingerie manufacturer' + | 'lingerie store' + | 'lingerie wholesaler' + | 'linoleum store' + | 'liquor store' + | 'literacy program' + | 'lithuanian restaurant' + | 'livery company' + | 'livestock breeder' + | 'livestock dealer' + | 'livestock producer' + | 'loan agency' + | 'lock store' + | 'locks supplier' + | 'log cabins' + | 'logging contractor' + | 'logistics service' + | 'lombardian restaurant' + | 'loss adjuster' + | 'lottery retailer' + | 'lottery shop' + | 'love hotel' + | 'lpg conversion' + | 'luggage store' + | 'luggage wholesaler' + | 'lumber store' + | 'lunch restaurant' + | 'lutheran church' + | 'machine construction' + | 'machine shop' + | 'machine workshop' + | 'machining manufacturer' + | 'macrobiotic restaurant' + | 'madrilian restaurant' + | 'magazine store' + | 'magic store' + | 'mailbox supplier' + | 'mailing service' + | 'majorcan restaurant' + | 'make-up artist' + | 'malaysian restaurant' + | 'maltese restaurant' + | 'mammography service' + | 'manado restaurant' + | 'management school' + | 'mandarin restaurant' + | 'manor house' + | 'maori organization' + | 'map store' + | 'mapping service' + | 'marathi restaurant' + | 'marble contractor' + | 'marble supplier' + | 'marche restaurant' + | 'marine engineer' + | 'marine surveyor' + | 'maritime museum' + | 'market researcher' + | 'marketing agency' + | 'marketing consultant' + | 'marriage celebrant' + | 'maserati dealer' + | 'masonry contractor' + | 'massage parlor' + | 'massage school' + | 'massage service' + | 'massage spa' + | 'massage therapist' + | 'maternity hospital' + | 'maternity store' + | 'mathematics school' + | 'mattress store' + | 'mausoleum builder' + | 'maybach dealer' + | 'mazda dealer' + | 'mclaren dealer' + | 'meal delivery' + | 'meat packer' + | 'meat processor' + | 'meat wholesaler' + | 'mechanical contractor' + | 'mechanical engineer' + | 'mechanical plant' + | 'media company' + | 'media consultant' + | 'media house' + | 'mediation service' + | 'medical center' + | 'medical centre' + | 'medical clinic' + | 'medical examiner' + | 'medical group' + | 'medical laboratory' + | 'medical lawyer' + | 'medical office' + | 'medical school' + | 'medical spa' + | 'medicine exporter' + | 'meditation center' + | 'meditation instructor' + | 'mediterranean restaurant' + | 'mehandi class' + | 'mehndi designer' + | 'memorial estate' + | 'memorial park' + | "men's tailor" + | 'mennonite church' + | 'mens tailor' + | 'mercantile development' + | 'mercedes-benz dealer' + | 'messianic synagogue' + | 'metal fabricator' + | 'metal finisher' + | 'metal supplier' + | 'metal workshop' + | 'metallurgy company' + | 'metalware dealer' + | 'metalware producer' + | 'methodist church' + | 'mexican restaurant' + | 'mg dealer' + | 'middle school' + | 'military base' + | 'military board' + | 'military cemetery' + | 'military hospital' + | 'military school' + | 'military town' + | 'millwork shop' + | 'mini dealer' + | 'miniatures store' + | 'mining company' + | 'mining consultant' + | 'mining engineer' + | 'mining equipment' + | 'mirror shop' + | 'mitsubishi dealer' + | 'mobile caterer' + | 'model shop' + | 'modeling agency' + | 'modeling school' + | 'mold maker' + | 'molding supplier' + | 'momo restaurant' + | 'monogramming service' + | 'montessori school' + | 'monument maker' + | 'moped dealer' + | 'moravian church' + | 'moroccan restaurant' + | 'mortgage broker' + | 'mortgage lender' + | 'motorcycle dealer' + | 'motorcycle shop' + | 'motoring club' + | 'motorsports store' + | 'mountain cabin' + | 'mountain peak' + | 'mountaineering class' + | 'movie studio' + | 'movie theater' + | 'moving company' + | 'mri center' + | 'muffler shop' + | 'mughlai restaurant' + | 'mulch supplier' + | 'municipal guard' + | 'murtabak restaurant' + | 'music college' + | 'music conservatory' + | 'music instructor' + | 'music producer' + | 'music publisher' + | 'music school' + | 'music store' + | 'musical club' + | 'nail salon' + | 'nasi restaurant' + | 'national forest' + | 'national library' + | 'national museum' + | 'national park' + | 'national reserve' + | 'nature preserve' + | 'naturopathic practitioner' + | 'naval base' + | 'navarraise restaurant' + | 'neapolitan restaurant' + | 'needlework shop' + | 'neonatal physician' + | 'nepalese restaurant' + | 'netball club' + | 'news service' + | 'newspaper publisher' + | 'nicaraguan restaurant' + | 'night club' + | 'night market' + | 'nissan dealer' + | 'non-denominational church' + | 'non-governmental organization' + | 'non-profit organization' + | 'noodle shop' + | 'norwegian restaurant' + | 'notaries association' + | 'notary public' + | 'notions store' + | 'novelties wholesaler' + | 'novelty store' + | 'nudist club' + | 'nudist park' + | 'nurse practitioner' + | 'nursery school' + | 'nursing agency' + | 'nursing association' + | 'nursing home' + | 'nursing school' + | 'nut store' + | 'nyonya restaurant' + | 'oaxacan restaurant' + | 'observation deck' + | 'occupational therapist' + | 'oden restaurant' + | 'odia restaurant' + | 'oil refinery' + | 'okonomiyaki restaurant' + | 'oldsmobile dealer' + | 'opel dealer' + | 'open university' + | 'opera company' + | 'opera house' + | 'ophthalmology clinic' + | 'optical wholesaler' + | 'oral surgeon' + | 'orchid farm' + | 'orchid grower' + | 'organic farm' + | 'organic restaurant' + | 'organic shop' + | 'orthodox church' + | 'orthodox synagogue' + | 'orthopedic clinic' + | 'orthopedic surgeon' + | 'otolaryngology clinic' + | 'outdoor bath' + | 'outerwear store' + | 'outlet mall' + | 'outlet store' + | 'oyster supplier' + | 'paan shop' + | 'package locker' + | 'packaging company' + | 'padang restaurant' + | 'padel club' + | 'padel court' + | 'paint manufacturer' + | 'paint store' + | 'paintball center' + | 'paintball store' + | 'painting lessons' + | 'painting studio' + | 'paintings store' + | 'paisa restaurant' + | 'pakistani restaurant' + | 'palatine restaurant' + | 'pallet supplier' + | 'pan-asian restaurant' + | 'pancake restaurant' + | 'panipuri shop' + | 'paper distributor' + | 'paper exporter' + | 'paper mill' + | 'paper store' + | 'paraguayan restaurant' + | 'parking garage' + | 'parking grounds' + | 'parking lot' + | 'parkour spot' + | 'parochial school' + | 'parsi restaurant' + | 'parsi temple' + | 'party planner' + | 'party store' + | 'passport agent' + | 'passport office' + | 'pasta shop' + | 'pastry shop' + | 'patent attorney' + | 'patent office' + | 'paving contractor' + | 'pawn shop' + | 'payroll service' + | 'pedestrian zone' + | 'pediatric cardiologist' + | 'pediatric clinic' + | 'pediatric dentist' + | 'pediatric dermatologist' + | 'pediatric endocrinologist' + | 'pediatric gastroenterologist' + | 'pediatric hematologist' + | 'pediatric nephrologist' + | 'pediatric neurologist' + | 'pediatric oncologist' + | 'pediatric ophthalmologist' + | 'pediatric pulmonologist' + | 'pediatric rheumatologist' + | 'pediatric surgeon' + | 'pediatric urologist' + | 'pempek restaurant' + | 'pen store' + | 'pension office' + | 'pentecostal church' + | 'perfume store' + | 'perinatal center' + | 'persian restaurant' + | 'personal trainer' + | 'peruvian restaurant' + | 'pet cemetery' + | 'pet groomer' + | 'pet shop' + | 'pet sitter' + | 'pet store' + | 'pet trainer' + | 'petrol station' + | 'peugeot dealer' + | 'pharmaceutical company' + | 'pharmaceutical lab' + | 'philharmonic hall' + | 'pho restaurant' + | 'photo agency' + | 'photo booth' + | 'photo lab' + | 'photo shop' + | 'photography class' + | 'photography school' + | 'photography service' + | 'photography studio' + | 'physical therapist' + | 'physician assistant' + | 'physiotherapy center' + | 'piadina restaurant' + | 'piano bar' + | 'piano instructor' + | 'piano maker' + | 'piano store' + | 'pickleball court' + | 'picnic ground' + | 'pie shop' + | 'piedmontese restaurant' + | 'pig farm' + | 'pilaf restaurant' + | 'pilates studio' + | 'pilgrim hostel' + | 'pipe supplier' + | 'pizza delivery' + | 'pizza restaurant' + | 'pizza takeaway' + | 'pizza takeout' + | 'plant nursery' + | 'plastic surgeon' + | 'plastic wholesaler' + | 'plating service' + | 'plywood supplier' + | 'poke bar' + | 'police academy' + | 'police department' + | 'polish restaurant' + | 'polo club' + | 'polygraph service' + | 'polymer supplier' + | 'polynesian restaurant' + | 'polytechnic institute' + | 'pond contractor' + | 'pontiac dealer' + | 'pony club' + | 'pool hall' + | 'popcorn store' + | 'porridge restaurant' + | 'porsche dealer' + | 'port authority' + | 'portrait studio' + | 'portuguese restaurant' + | 'post office' + | 'postal code' + | 'poster store' + | 'pottery classes' + | 'pottery manufacturer' + | 'pottery store' + | 'poultry farm' + | 'poultry store' + | 'power station' + | 'pozole restaurant' + | 'prawn fishing' + | 'precision engineer' + | 'preparatory school' + | 'presbyterian church' + | 'press advisory' + | 'pretzel store' + | 'primary school' + | 'print shop' + | 'private college' + | 'private hospital' + | 'private investigator' + | 'private tutor' + | 'private university' + | 'probation office' + | 'process server' + | 'produce market' + | 'produce wholesaler' + | 'professional association' + | 'professional organizer' + | 'propane supplier' + | 'propeller shop' + | 'property consultant' + | 'property developer' + | 'property investment' + | 'property maintenance' + | 'protected area' + | 'protestant church' + | 'provence restaurant' + | 'psychiatric hospital' + | 'psychomotor therapist' + | 'psychopedagogy clinic' + | 'public bath' + | 'public bathroom' + | 'public beach' + | 'public housing' + | 'public library' + | 'public sauna' + | 'public university' + | 'pueblan restaurant' + | 'pump supplier' + | 'pumpkin patch' + | 'punjabi restaurant' + | 'puppet theater' + | 'quaker church' + | 'quantity surveyor' + | 'quilt shop' + | 'raclette restaurant' + | 'racquetball club' + | 'radiator shop' + | 'radio broadcaster' + | 'rail museum' + | 'railing contractor' + | 'railroad company' + | 'railroad contractor' + | 'railway services' + | 'rajasthani restaurant' + | 'ram dealer' + | 'ramen restaurant' + | 'real estate' + | 'record company' + | 'record store' + | 'recording studio' + | 'recreation center' + | 'recycling center' + | 'reenactment site' + | 'reform synagogue' + | 'reformed church' + | 'refrigerator store' + | 'refugee camp' + | 'regional airport' + | 'regional council' + | 'registration office' + | 'registry office' + | 'rehabilitation center' + | 'rehearsal studio' + | 'reiki therapist' + | 'religious destination' + | 'religious institution' + | 'religious lodging' + | 'religious organization' + | 'religious school' + | 'renault dealer' + | 'renovation contractor' + | 'repair service' + | 'reptile store' + | 'research engineer' + | 'research foundation' + | 'research institute' + | 'residential building' + | 'residential college' + | 'residents association' + | 'resort hotel' + | 'rest stop' + | 'resume service' + | 'retirement community' + | 'retirement home' + | 'retreat center' + | 'rice mill' + | 'rice restaurant' + | 'rice shop' + | 'rice wholesaler' + | 'river port' + | 'road cycling' + | 'rock climbing' + | 'rock shop' + | 'roller coaster' + | 'roman restaurant' + | 'romanian restaurant' + | 'roofing contractor' + | 'roofing service' + | 'rowing area' + | 'rowing club' + | 'rsl club' + | 'rug store' + | 'rugby club' + | 'rugby field' + | 'rugby store' + | 'running store' + | 'russian restaurant' + | 'rv dealer' + | 'rv park' + | 'saab dealer' + | 'sailing club' + | 'sailing school' + | 'sake brewery' + | 'salad shop' + | 'salsa bar' + | 'salsa classes' + | 'salvadoran restaurant' + | 'salvage dealer' + | 'salvage yard' + | 'samba school' + | 'sambo school' + | 'sand plant' + | 'sandblasting service' + | 'sandwich shop' + | 'sanitary inspection' + | 'sanitation service' + | 'sardinian restaurant' + | 'saree shop' + | 'sashimi restaurant' + | 'satay restaurant' + | 'saturn dealer' + | 'sauna club' + | 'sauna store' + | 'savings bank' + | 'saw mill' + | 'scale supplier' + | 'scandinavian restaurant' + | 'scenic spot' + | 'scenography company' + | 'school cafeteria' + | 'school center' + | 'school house' + | 'science museum' + | 'scottish restaurant' + | 'scout hall' + | 'scout home' + | 'scrapbooking store' + | 'screen printer' + | 'screen store' + | 'screw supplier' + | 'scuba instructor' + | 'sculpture museum' + | 'seafood farm' + | 'seafood market' + | 'seafood restaurant' + | 'seafood wholesaler' + | 'seal shop' + | 'seaplane base' + | 'seat dealer' + | 'seblak restaurant' + | 'secondary school' + | 'security service' + | 'seed supplier' + | 'self-catering accommodation' + | 'self-storage facility' + | 'serbian restaurant' + | 'service establishment' + | 'serviced accommodation' + | 'serviced apartment' + | 'sewing company' + | 'sewing shop' + | 'seychelles restaurant' + | 'sfiha restaurant' + | 'shanghainese restaurant' + | 'sharpening service' + | 'shawarma restaurant' + | 'shed builder' + | 'sheep shearer' + | 'sheltered housing' + | 'shelving store' + | 'shinto shrine' + | 'shipping company' + | 'shipping service' + | 'shochu brewery' + | 'shoe factory' + | 'shoe shop' + | 'shoe store' + | 'shogi lesson' + | 'shooting range' + | 'shopping centre' + | 'shopping mall' + | 'shredding service' + | 'shrimp farm' + | 'sichuan restaurant' + | 'sicilian restaurant' + | 'siding contractor' + | 'sign shop' + | 'signwriting service' + | 'silk store' + | 'singaporean restaurant' + | 'singles organization' + | 'skate shop' + | 'skateboard shop' + | 'skating instructor' + | 'ski club' + | 'ski resort' + | 'ski school' + | 'ski shop' + | 'skittle club' + | 'skoda dealer' + | 'skydiving center' + | 'skylight contractor' + | 'sleep clinic' + | 'smart dealer' + | 'smart shop' + | 'smoke shop' + | 'snack bar' + | 'snowboard shop' + | 'snowmobile dealer' + | 'soccer club' + | 'soccer field' + | 'soccer practice' + | 'soccer store' + | 'social club' + | 'social worker' + | 'sod supplier' + | 'sofa store' + | 'softball club' + | 'softball field' + | 'software company' + | 'soondae restaurant' + | 'soto restaurant' + | 'soup kitchen' + | 'soup restaurant' + | 'soup shop' + | 'souvenir manufacturer' + | 'souvenir store' + | 'spa garden' + | 'spanish restaurant' + | 'special educator' + | 'specialized clinic' + | 'specialized hospital' + | 'speech pathologist' + | 'sperm bank' + | 'spice exporter' + | 'spice store' + | 'spice wholesaler' + | 'spices exporter' + | 'spiritist center' + | 'sports bar' + | 'sports club' + | 'sports complex' + | 'sports school' + | 'sportswear store' + | 'sportwear manufacturer' + | 'spring supplier' + | 'squash club' + | 'squash court' + | 'stair contractor' + | 'stamp shop' + | 'stand bar' + | 'state archive' + | 'state park' + | 'state parliament' + | 'state police' + | 'stationery manufacturer' + | 'stationery store' + | 'stationery wholesaler' + | 'std clinic' + | 'steak house' + | 'steamboat restaurant' + | 'steel distributor' + | 'steel erector' + | 'steel fabricator' + | 'sticker manufacturer' + | 'stitching class' + | 'stock broker' + | 'stone carving' + | 'stone cutter' + | 'stone supplier' + | 'storage facility' + | 'structural engineer' + | 'stucco contractor' + | 'student dormitory' + | 'student union' + | 'studying center' + | 'subaru dealer' + | 'subway station' + | 'sugar factory' + | 'sugar shack' + | 'sukiyaki restaurant' + | 'sunblind supplier' + | 'sundae restaurant' + | 'sundanese restaurant' + | 'sunglasses store' + | 'sunroom contractor' + | 'superannuation consultant' + | 'superfund site' + | 'support group' + | 'surf school' + | 'surf shop' + | 'surgical center' + | 'surgical oncologist' + | 'surinamese restaurant' + | 'surplus store' + | 'sushi restaurant' + | 'sushi takeaway' + | 'suzuki dealer' + | 'swabian restaurant' + | 'swedish restaurant' + | 'swim club' + | 'swimming basin' + | 'swimming competition' + | 'swimming facility' + | 'swimming instructor' + | 'swimming lake' + | 'swimming pool' + | 'swimming school' + | 'swimwear store' + | 'swiss restaurant' + | 'syrian restaurant' + | 't-shirt store' + | 'tabascan restaurant' + | 'tacaca restaurant' + | 'tack shop' + | 'taco restaurant' + | 'taekwondo school' + | 'taiwanese restaurant' + | 'takeout restaurant' + | 'takoyaki restaurant' + | 'talent agency' + | 'tamale shop' + | 'tanning salon' + | 'taoist temple' + | 'tapas bar' + | 'tapas restaurant' + | 'tatami store' + | 'tattoo artist' + | 'tattoo shop' + | 'tax assessor' + | 'tax attorney' + | 'tax consultant' + | 'tax department' + | 'tax preparation' + | 'taxi service' + | 'taxi stand' + | 'taxicab stand' + | 'tb clinic' + | 'tea exporter' + | 'tea house' + | 'tea manufacturer' + | 'tea store' + | 'tea wholesaler' + | 'teachers college' + | 'technical school' + | 'technical university' + | 'technology museum' + | 'technology park' + | 'tegal restaurant' + | 'telecommunication school' + | 'telecommunications contractor' + | 'telecommunications engineer' + | 'telemarketing service' + | 'telephone company' + | 'telephone exchange' + | 'telescope store' + | 'television station' + | 'temaki restaurant' + | 'temp agency' + | 'tempura restaurant' + | 'tenant ownership' + | 'tennis club' + | 'tennis court' + | 'tennis instructor' + | 'tennis store' + | 'teppanyaki restaurant' + | 'tesla showroom' + | 'tex-mex restaurant' + | 'textile engineer' + | 'textile exporter' + | 'textile merchant' + | 'textile mill' + | 'thai restaurant' + | 'theater company' + | 'theater production' + | 'theme park' + | 'thermal baths' + | 'thread supplier' + | 'thrift store' + | 'thuringian restaurant' + | 'tibetan restaurant' + | 'tiffin center' + | 'tiki bar' + | 'tile contractor' + | 'tile manufacturer' + | 'tile store' + | 'timeshare agency' + | 'tire service' + | 'tire shop' + | 'title company' + | 'toast restaurant' + | 'tobacco shop' + | 'tobacco supplier' + | 'tofu restaurant' + | 'tofu shop' + | 'toiletries store' + | 'toll station' + | 'tongue restaurant' + | 'tonkatsu restaurant' + | 'tool manufacturer' + | 'tool store' + | 'tool wholesaler' + | 'topography company' + | 'topsoil supplier' + | 'tortilla shop' + | 'tour agency' + | 'tour operator' + | 'tourist attraction' + | 'towing service' + | 'townhouse complex' + | 'toy library' + | 'toy manufacturer' + | 'toy museum' + | 'toy store' + | 'toyota dealer' + | 'tractor dealer' + | 'trade school' + | 'trading company' + | 'traditional market' + | 'traditional teahouse' + | 'traffic officer' + | 'trailer dealer' + | 'trailer manufacturer' + | 'train depot' + | 'train station' + | 'train yard' + | 'training center' + | 'training centre' + | 'training consultant' + | 'training provider' + | 'tram stop' + | 'transcription service' + | 'transit depot' + | 'transit station' + | 'transit stop' + | 'translation service' + | 'transmission shop' + | 'transplant surgeon' + | 'transport hub' + | 'transportation service' + | 'travel agency' + | 'travel agent' + | 'travel clinic' + | 'travel lounge' + | 'tree farm' + | 'tree service' + | 'trial attorney' + | 'tribal headquarters' + | 'trolleybus stop' + | 'trophy shop' + | 'truck dealer' + | 'truck farmer' + | 'truck stop' + | 'trucking company' + | 'truss manufacturer' + | 'trust bank' + | 'tunisian restaurant' + | 'turf supplier' + | 'turkish restaurant' + | 'turkmen restaurant' + | 'tuscan restaurant' + | 'tutoring service' + | 'tuxedo shop' + | 'typewriter supplier' + | 'typing service' + | 'tyre manufacturer' + | 'ukrainian restaurant' + | 'unagi restaurant' + | 'underwear store' + | 'unemployment office' + | 'uniform store' + | 'unity church' + | 'university department' + | 'university hospital' + | 'university library' + | 'upholstery shop' + | 'urology clinic' + | 'uruguayan restaurant' + | 'utility contractor' + | 'valencian restaurant' + | 'vaporizer store' + | 'variety store' + | 'vascular surgeon' + | 'vastu consultant' + | 'vegan restaurant' + | 'vegetable wholesaler' + | 'vegetarian restaurant' + | 'vehicle exporter' + | 'vehicle repair' + | 'venetian restaurant' + | 'venezuelan restaurant' + | 'veterans center' + | 'veterans hospital' + | 'veterans organization' + | 'veterinary care' + | 'veterinary pharmacy' + | 'video arcade' + | 'video karaoke' + | 'video store' + | 'vietnamese restaurant' + | 'village hall' + | 'vineyard church' + | 'violin shop' + | 'visitor center' + | 'vocal instructor' + | 'vocational school' + | 'volkswagen dealer' + | 'volleyball club' + | 'volleyball court' + | 'volleyball instructor' + | 'volunteer organization' + | 'volvo dealer' + | 'waldorf kindergarten' + | 'waldorf school' + | 'walk-in clinic' + | 'wallpaper installer' + | 'wallpaper store' + | 'war museum' + | 'warehouse club' + | 'warehouse store' + | 'watch manufacturer' + | 'watch store' + | 'water mill' + | 'water park' + | 'water works' + | 'waterbed store' + | 'waterproofing service' + | 'wax museum' + | 'wax supplier' + | 'weaving mill' + | 'web designer' + | 'website designer' + | 'wedding bakery' + | 'wedding buffet' + | 'wedding chapel' + | 'wedding photographer' + | 'wedding planner' + | 'wedding service' + | 'wedding store' + | 'wedding venue' + | 'weigh station' + | 'weightlifting area' + | 'wellness center' + | 'wellness hotel' + | 'wellness program' + | 'welsh restaurant' + | 'wesleyan church' + | 'western restaurant' + | 'wheel store' + | 'wheelchair store' + | 'wholesale bakery' + | 'wholesale drugstore' + | 'wholesale florist' + | 'wholesale grocer' + | 'wholesale jeweler' + | 'wholesale market' + | 'wi-fi spot' + | 'wicker store' + | 'wig shop' + | 'wildlife park' + | 'wildlife refuge' + | 'wind farm' + | 'window supplier' + | 'windsurfing store' + | 'wine bar' + | 'wine cellar' + | 'wine club' + | 'wine store' + | 'wok restaurant' + | 'wood supplier' + | 'wool store' + | 'wrestling school' + | 'x-ray lab' + | 'yacht broker' + | 'yacht club' + | 'yakiniku restaurant' + | 'yakisoba restaurant' + | 'yakitori restaurant' + | 'yarn store' + | 'yemeni restaurant' + | 'yoga instructor' + | 'yoga studio' + | 'youth center' + | 'youth club' + | 'youth hostel' + | 'youth organization' + | 'yucatan restaurant' + | '3d printing service' + | 'aboriginal art gallery' + | 'abundant life church' + | 'acrobatic diving pool' + | 'addiction treatment center' + | 'adult dvd store' + | 'adult education school' + | 'adult entertainment club' + | 'adult entertainment store' + | 'adventure sports center' + | 'aerated drinks supplier' + | 'aerial sports center' + | 'aero dance class' + | 'african goods store' + | 'after school program' + | 'agricultural high school' + | 'agricultural machinery manufacturer' + | 'agricultural product wholesaler' + | 'air compressor supplier' + | 'air conditioning contractor' + | 'air conditioning store' + | 'air filter supplier' + | 'air force base' + | 'airbrushing supply store' + | 'aircraft maintenance company' + | 'aircraft rental service' + | 'aircraft supply store' + | 'airline ticket agency' + | 'airport shuttle service' + | 'alcohol retail monopoly' + | 'alcoholic beverage wholesaler' + | 'alcoholism treatment program' + | 'alfa romeo dealer' + | 'alternative fuel station' + | 'alternative medicine clinic' + | 'alternative medicine practitioner' + | 'aluminum frames supplier' + | 'american grocery store' + | 'amish furniture store' + | 'amusement machine supplier' + | 'amusement park ride' + | 'amusement ride supplier' + | 'angler fish restaurant' + | 'animal control service' + | 'animal feed store' + | 'animal protection organization' + | 'animal rescue service' + | 'animal watering hole' + | 'antique furniture store' + | 'apartment rental agency' + | 'appliance parts supplier' + | 'appliance rental service' + | 'appliance repair service' + | 'appliances customer service' + | 'architectural salvage store' + | 'armed forces association' + | 'aromatherapy supply store' + | 'art restoration service' + | 'art supply store' + | 'artificial plant supplier' + | 'asbestos testing service' + | 'asian fusion restaurant' + | 'asian grocery store' + | 'asphalt mixing plant' + | 'assisted living facility' + | 'association / organization' + | 'aston martin dealer' + | 'attorney referral service' + | 'atv rental service' + | 'atv repair shop' + | 'audio visual consultant' + | 'australian goods store' + | 'auto accessories wholesaler' + | 'auto body shop' + | 'auto bodywork mechanic' + | 'auto chemistry shop' + | 'auto electrical service' + | 'auto glass shop' + | 'auto insurance agency' + | 'auto machine shop' + | 'auto parts manufacturer' + | 'auto parts market' + | 'auto parts store' + | 'auto repair shop' + | 'auto restoration service' + | 'auto rickshaw stand' + | 'auto spring shop' + | 'auto sunroof shop' + | 'auto tag agency' + | 'automobile storage facility' + | 'aviation training institute' + | 'ayam penyet restaurant' + | 'baby clothing store' + | 'baby swimming school' + | 'bail bonds service' + | 'baking supply store' + | 'ballroom dance instructor' + | 'banking and finance' + | 'bar stool supplier' + | 'barber supply store' + | 'baseball goods store' + | 'basketball court contractor' + | 'bathroom supply store' + | 'batik clothing store' + | 'batting cage center' + | 'beach cleaning service' + | 'beach clothing store' + | 'beach entertainment shop' + | 'beach volleyball club' + | 'beach volleyball court' + | 'beauty product supplier' + | 'beauty products wholesaler' + | 'beauty supply store' + | 'bed & breakfast' + | 'bedroom furniture store' + | 'bee relocation service' + | 'bicycle rental service' + | 'bicycle repair shop' + | 'bike sharing station' + | 'bikram yoga studio' + | 'billiards supply store' + | 'bird control service' + | 'bird watching area' + | 'birth certificate service' + | 'birth control center' + | 'blast cleaning service' + | 'blood donation center' + | 'blood testing service' + | 'bmw motorcycle dealer' + | 'board game club' + | 'board of education' + | 'boat accessories supplier' + | 'boat cleaning service' + | 'boat cover supplier' + | 'boat detailing service' + | 'boat rental service' + | 'boat repair shop' + | 'boat storage facility' + | 'boat tour agency' + | 'boat trailer dealer' + | 'bocce ball court' + | 'body piercing shop' + | 'body shaping class' + | 'bonsai plant supplier' + | 'boot repair shop' + | 'border crossing station' + | 'bottled water supplier' + | 'bouncy castle hire' + | 'bowling supply shop' + | 'box lunch supplier' + | "boys' high school" + | 'bpo placement agency' + | 'brewing supply store' + | 'bubble tea store' + | 'buddhist supplies store' + | 'building materials market' + | 'building materials store' + | 'building materials supplier' + | 'building restoration service' + | 'bungee jumping center' + | 'burglar alarm store' + | 'bus ticket agency' + | 'bus tour agency' + | 'business administration service' + | 'business banking service' + | 'business development service' + | 'business management consultant' + | 'business networking company' + | 'butane gas supplier' + | 'butcher shop deli' + | 'cabin rental agency' + | 'calvary chapel church' + | 'camera repair shop' + | 'camper shell supplier' + | 'cancer treatment center' + | 'cane furniture store' + | 'cape verdean restaurant' + | 'car accessories store' + | 'car alarm supplier' + | 'car battery store' + | 'car detailing service' + | 'car inspection station' + | 'car leasing service' + | 'car rental agency' + | 'car sharing location' + | 'car stereo store' + | 'career guidance service' + | 'carpet cleaning service' + | 'carriage ride service' + | 'cat boarding service' + | 'cell phone store' + | 'central american restaurant' + | 'central european restaurant' + | 'central heating service' + | 'central javanese restaurant' + | 'certified public accountant' + | 'chamber of agriculture' + | 'chamber of commerce' + | 'chamber of handicrafts' + | 'champon noodle restaurant' + | 'check cashing service' + | 'chicken wings restaurant' + | 'child care agency' + | "children's amusement center" + | "children's clothing store" + | "children's furniture store" + | "children's health service" + | "children's party buffet" + | "children's party service" + | 'chinese language instructor' + | 'chinese language school' + | 'chinese medicine clinic' + | 'chinese medicine store' + | 'chinese noodle restaurant' + | 'chinese tea house' + | 'christian book store' + | 'christmas tree farm' + | 'church of christ' + | 'church supply store' + | 'cig kofte restaurant' + | 'cinema equipment supplier' + | 'citizen information bureau' + | 'city district office' + | 'city employment department' + | 'city government office' + | 'city tax office' + | 'civil engineering company' + | 'civil examinations academy' + | 'civil law attorney' + | 'cleaning products supplier' + | 'clock repair service' + | 'closed circuit television' + | 'clothing alteration service' + | 'coast guard station' + | 'coffee machine supplier' + | 'coffee vending machine' + | 'coin operated locker' + | 'cold cut store' + | 'cold noodle restaurant' + | 'cold storage facility' + | 'college of agriculture' + | 'comic book store' + | 'commercial refrigerator supplier' + | 'commissioner for oaths' + | 'community health center' + | 'community health centre' + | 'comprehensive secondary school' + | 'computer accessories store' + | 'computer desk store' + | 'computer hardware manufacturer' + | 'computer networking service' + | 'computer repair service' + | 'computer security service' + | 'computer software store' + | 'computer training school' + | 'concrete product supplier' + | 'condominium rental agency' + | 'conservatory of music' + | 'construction equipment supplier' + | 'construction machine dealer' + | 'construction material wholesaler' + | 'consumer advice center' + | 'contact lenses supplier' + | 'contemporary louisiana restaurant' + | 'convention information bureau' + | 'copier repair service' + | 'copying supply store' + | 'corporate gift supplier' + | 'cosmetic products manufacturer' + | 'cost accounting service' + | 'costa rican restaurant' + | 'costume jewelry shop' + | 'costume rental service' + | 'country food restaurant' + | 'county government office' + | 'court executive officer' + | 'crane rental agency' + | 'creative cuisine restaurant' + | 'credit counseling service' + | 'credit reporting agency' + | 'crime victim service' + | 'criminal justice attorney' + | 'crushed stone supplier' + | 'cured ham bar' + | 'cured ham store' + | 'cured ham warehouse' + | 'currency exchange service' + | 'custom home builder' + | 'custom label printer' + | 'custom t-shirt store' + | 'cycle rickshaw stand' + | 'dart supply store' + | 'data entry service' + | 'data recovery service' + | 'database management company' + | 'day care center' + | 'debris removal service' + | 'debt collection agency' + | 'delivery chinese restaurant' + | 'dental implants periodontist' + | 'dental implants provider' + | 'dental insurance agency' + | 'dental supply store' + | 'denture care center' + | 'department of housing' + | 'department of transportation' + | 'designer clothing store' + | 'desktop publishing service' + | 'diabetes equipment supplier' + | 'diesel engine dealer' + | 'diesel fuel supplier' + | 'digital printing service' + | 'dim sum restaurant' + | 'direct mail advertising' + | 'disability equipment supplier' + | 'disc golf course' + | 'display stand manufacturer' + | 'disposable tableware supplier' + | 'distance learning center' + | 'district government office' + | 'dj supply store' + | 'dogsled ride service' + | 'doll restoration service' + | 'doner kebab restaurant' + | 'double glazing installer' + | 'drafting equipment supplier' + | 'dried flower shop' + | 'dried seafood store' + | 'drilling equipment supplier' + | 'drinking water fountain' + | "driver's license office" + | 'driving test center' + | 'drug testing service' + | 'dry fruit store' + | 'dry ice supplier' + | 'dry wall contractor' + | 'ds automobiles dealer' + | 'dump truck dealer' + | 'dumpster rental service' + | 'duty free store' + | 'e commerce agency' + | 'ear piercing service' + | 'earth works company' + | 'east african restaurant' + | 'east javanese restaurant' + | 'eastern european restaurant' + | 'eastern orthodox church' + | 'economic development agency' + | 'educational supply store' + | 'educational testing service' + | 'eftpos equipment supplier' + | 'elder law attorney' + | 'electric bicycle store' + | 'electric generator shop' + | 'electric motor store' + | 'electric motorcycle dealer' + | 'electric utility company' + | 'electrical appliance wholesaler' + | 'electrical equipment supplier' + | 'electrical installation service' + | 'electrical products wholesaler' + | 'electrical repair shop' + | 'electrical supply store' + | 'electronic engineering service' + | 'electronic parts supplier' + | 'electronics accessories wholesaler' + | 'electronics hire shop' + | 'electronics repair shop' + | 'electronics vending machine' + | 'emergency care physician' + | 'emergency care service' + | 'emergency dental service' + | 'emergency locksmith service' + | 'emergency training school' + | 'emergency veterinarian service' + | 'engine rebuilding service' + | 'english language camp' + | 'english language school' + | 'environmental health service' + | 'environmental protection organization' + | 'equipment rental agency' + | 'escape room center' + | 'estate planning attorney' + | 'event management company' + | 'event planning service' + | 'event technology service' + | 'event ticket seller' + | 'executive search firm' + | 'exercise equipment store' + | 'extended stay hotel' + | 'eye care center' + | 'fabric product manufacturer' + | 'factory equipment supplier' + | 'faculty of law' + | 'faculty of pharmacy' + | 'faculty of science' + | 'family law attorney' + | 'family planning center' + | 'family planning counselor' + | 'family practice physician' + | 'family service center' + | 'farm equipment supplier' + | 'farm household tour' + | 'fashion accessories shop' + | 'fashion accessories store' + | 'fashion design school' + | 'fast food restaurant' + | 'federal credit union' + | 'federal government office' + | 'felt boots store' + | 'fence supply store' + | 'feng shui consultant' + | 'feng shui shop' + | 'fiberglass repair service' + | 'filipino grocery store' + | 'film production company' + | 'fine dining restaurant' + | 'finishing materials supplier' + | 'fire alarm supplier' + | 'fire fighters academy' + | 'fire protection consultant' + | 'fire protection service' + | 'first aid station' + | 'fitness equipment wholesaler' + | 'fitted furniture supplier' + | 'flamenco dance store' + | 'floor refinishing service' + | 'fmcg goods wholesaler' + | 'foam rubber producer' + | 'foam rubber supplier' + | 'folk high school' + | 'food and drink' + | 'food machinery supplier' + | 'food manufacturing supply' + | 'food processing company' + | 'food processing equipment' + | 'food products supplier' + | 'food seasoning manufacturer' + | 'foot massage parlor' + | 'foreign trade consultant' + | 'foreman builders association' + | 'forklift rental service' + | 'formal wear store' + | 'fortune telling services' + | 'foster care service' + | 'free parking lot' + | 'freight forwarding service' + | 'french language school' + | 'french steakhouse restaurant' + | 'fresh food market' + | 'fried chicken takeaway' + | 'frozen dessert supplier' + | 'frozen food manufacturer' + | 'frozen food store' + | 'frozen yogurt shop' + | 'full gospel church' + | 'function room facility' + | 'funeral celebrant service' + | 'fur coat shop' + | 'furnace parts supplier' + | 'furnace repair service' + | 'furnished apartment building' + | 'furniture accessories supplier' + | 'furniture rental service' + | 'furniture repair shop' + | 'garage door supplier' + | 'garbage collection service' + | 'garden building supplier' + | 'garden machinery supplier' + | 'gas cylinders supplier' + | 'gas installation service' + | 'gas logs supplier' + | 'gay night club' + | 'general education school' + | 'general practice attorney' + | 'geological research company' + | 'german language school' + | 'gift basket store' + | 'gift wrap store' + | "girls' high school" + | 'glass block supplier' + | 'glass cutting service' + | 'glass etching service' + | 'glass repair service' + | 'glasses repair service' + | 'gold mining company' + | 'golf cart dealer' + | 'golf course builder' + | 'golf driving range' + | 'gourmet grocery store' + | 'government economic program' + | 'government ration shop' + | 'graffiti removal service' + | 'greek orthodox church' + | 'green energy supplier' + | 'greeting card shop' + | 'grocery delivery service' + | 'gutter cleaning service' + | 'gypsum product supplier' + | 'hair extension technician' + | 'hair extensions supplier' + | 'hair removal service' + | 'hair replacement service' + | 'hair transplantation clinic' + | 'handicapped transportation service' + | 'hang gliding center' + | 'haute french restaurant' + | 'hawaiian goods store' + | 'head start center' + | 'health food restaurant' + | 'health food store' + | 'health insurance agency' + | 'hearing aid store' + | 'heating equipment supplier' + | 'heating oil supplier' + | 'helicopter tour agency' + | 'helium gas supplier' + | 'herbal medicine store' + | 'high ropes course' + | 'higher secondary school' + | 'historical place museum' + | 'hiv testing center' + | 'hockey supply store' + | 'holiday apartment rental' + | 'holistic medicine practitioner' + | 'home audio store' + | 'home automation company' + | 'home cinema installation' + | 'home furniture shop' + | 'home goods store' + | 'home improvement store' + | 'home insurance agency' + | 'home staging service' + | 'home theater store' + | 'horse boarding stable' + | 'horse rental service' + | 'horse riding field' + | 'horse riding school' + | 'horse trailer dealer' + | 'horseback riding service' + | 'hospitality high school' + | 'hot bedstone spa' + | 'hot dog restaurant' + | 'hot dog stand' + | 'hot pot restaurant' + | 'hot tub store' + | 'hotel management school' + | 'hotel supply store' + | 'house cleaning service' + | 'house clearance service' + | 'house sitter agency' + | 'houseboat rental service' + | 'household chemicals supplier' + | 'household goods wholesaler' + | 'housing utility company' + | 'hua gong shop' + | 'hub cap supplier' + | 'human resource consulting' + | 'hydraulic equipment supplier' + | 'hydraulic repair service' + | 'hydroelectric power plant' + | 'hydroponics equipment supplier' + | 'hygiene articles wholesaler' + | 'hyperbaric medicine physician' + | 'ice cream shop' + | 'ice hockey club' + | 'ice skating club' + | 'ice skating instructor' + | 'ice skating rink' + | 'ikan bakar restaurant' + | 'import export company' + | 'indian grocery store' + | 'indian motorcycle dealer' + | 'indian muslim restaurant' + | 'indian sizzler restaurant' + | 'indian sweets shop' + | 'indoor golf course' + | 'indoor swimming pool' + | 'industrial chemicals wholesaler' + | 'industrial design company' + | 'industrial engineers association' + | 'industrial equipment supplier' + | 'industrial gas supplier' + | 'infectious disease physician' + | 'institute of technology' + | 'insulation materials store' + | 'intellectual property registry' + | 'interior architect office' + | 'interior construction contractor' + | 'interior fitting contractor' + | 'interior plant service' + | 'internal medicine ward' + | 'international trade consultant' + | 'internet marketing service' + | 'internet service provider' + | 'invitation printing service' + | 'irish goods store' + | 'iron ware dealer' + | 'irrigation equipment supplier' + | 'italian grocery store' + | 'janitorial equipment supplier' + | 'japanese confectionery shop' + | 'japanese curry restaurant' + | 'japanese grocery store' + | 'japanese language instructor' + | 'japanese regional restaurant' + | 'japanese sweets restaurant' + | 'japanese-style business hotel' + | 'japanized western restaurant' + | 'jewelry equipment supplier' + | 'jewelry repair service' + | 'junk removal service' + | 'juvenile detention center' + | 'kalle pache restaurant' + | 'kawasaki motorcycle dealer' + | 'key duplication service' + | 'kitchen furniture store' + | 'kitchen supply store' + | 'korean barbecue restaurant' + | 'korean beef restaurant' + | 'korean grocery store' + | 'korean rib restaurant' + | 'kosher grocery store' + | 'kung fu school' + | 'labor relations attorney' + | 'laboratory equipment supplier' + | "ladies' clothes shop" + | 'laminating equipment supplier' + | 'lamp repair service' + | 'lamp shade supplier' + | 'land planning authority' + | 'land reform institute' + | 'land rover dealer' + | 'land surveying office' + | 'landscape lighting designer' + | 'landscaping supply store' + | 'laser cutting service' + | 'laser equipment supplier' + | 'laser tag center' + | 'latin american restaurant' + | 'law book store' + | 'lawn bowls club' + | 'lawn care service' + | 'lawn mower store' + | 'leather cleaning service' + | 'leather coats store' + | 'leather goods manufacturer' + | 'leather goods store' + | 'leather goods supplier' + | 'leather goods wholesaler' + | 'leather repair service' + | 'legal affairs bureau' + | 'life insurance agency' + | 'light bulb supplier' + | 'lighting products wholesaler' + | 'line marking service' + | 'little league club' + | 'little league field' + | 'live music bar' + | 'live music venue' + | 'livestock auction house' + | 'local government office' + | 'local history museum' + | 'local medical services' + | 'log home builder' + | 'lost property office' + | 'luggage repair service' + | 'luggage storage facility' + | 'lymph drainage therapist' + | 'machine knife supplier' + | 'machine maintenance service' + | 'machine repair service' + | 'machinery parts manufacturer' + | 'mailbox rental service' + | 'mailing machine supplier' + | 'main customs office' + | 'manufactured home transporter' + | 'marine supply store' + | 'marquee hire service' + | 'marriage license bureau' + | 'martial arts club' + | 'martial arts school' + | 'masonry supply store' + | 'massage supply store' + | 'match box manufacturer' + | 'measuring instruments supplier' + | 'meat dish restaurant' + | 'meat products store' + | 'medical billing service' + | 'medical book store' + | 'medical certificate service' + | 'medical equipment manufacturer' + | 'medical equipment supplier' + | 'medical supply store' + | 'medical technology manufacturer' + | 'medical transcription service' + | 'meeting planning service' + | "men's clothes shop" + | "men's clothing store" + | "men's health physician" + | 'mental health clinic' + | 'mental health service' + | 'metal construction company' + | 'metal industry suppliers' + | 'metal machinery supplier' + | 'metal polishing service' + | 'metal processing company' + | 'metal stamping service' + | 'metal working shop' + | 'metaphysical supply store' + | 'metropolitan train company' + | 'mexican goods store' + | 'mexican grocery store' + | 'mexican torta restaurant' + | 'middle eastern restaurant' + | 'military recruiting office' + | 'milk delivery service' + | 'mineral water company' + | 'miniature golf course' + | 'minibus taxi service' + | 'ministry of education' + | 'miso cutlet restaurant' + | 'missing persons organization' + | 'mobile disco service' + | 'mobile home dealer' + | 'mobile home park' + | 'mobile money agent' + | 'mobile network operator' + | 'mobile phone shop' + | 'mobility equipment supplier' + | 'model design company' + | 'model portfolio studio' + | 'model train store' + | 'modern art museum' + | 'modern british restaurant' + | 'modern european restaurant' + | 'modern french restaurant' + | 'modern indian restaurant' + | 'modern izakaya restaurant' + | 'modular home builder' + | 'modular home dealer' + | 'money order service' + | 'money transfer service' + | 'mongolian barbecue restaurant' + | 'motor scooter dealer' + | 'motor vehicle dealer' + | 'motorcycle driving school' + | 'motorcycle insurance agency' + | 'motorcycle parts store' + | 'motorcycle rental agency' + | 'motorcycle repair shop' + | 'mountain cable car' + | 'movie rental kiosk' + | 'movie rental store' + | 'moving supply store' + | 'municipal administration office' + | 'museum of zoology' + | 'music box store' + | 'musical instrument manufacturer' + | 'musical instrument store' + | 'musician and composer' + | 'mutton barbecue restaurant' + | 'nasi goreng restaurant' + | 'nasi uduk restaurant' + | 'native american restaurant' + | 'natural goods store' + | 'natural history museum' + | 'natural stone exporter' + | 'natural stone supplier' + | 'natural stone wholesaler' + | 'neon sign shop' + | 'new age church' + | 'new american restaurant' + | 'new england restaurant' + | 'new zealand restaurant' + | 'newspaper distribution service' + | 'non vegetarian restaurant' + | 'north african restaurant' + | 'north indian restaurant' + | 'northern italian restaurant' + | 'nuclear power company' + | 'nuclear power plant' + | 'nuevo latino restaurant' + | 'occupational health service' + | 'occupational medical physician' + | 'off roading area' + | 'offal barbecue restaurant' + | 'office accessories wholesaler' + | 'office equipment supplier' + | 'office furniture store' + | 'office refurbishment service' + | 'office supply store' + | 'office supply wholesaler' + | 'offset printing service' + | 'oil change service' + | 'olive oil cooperative' + | 'olive oil manufacturer' + | 'open air museum' + | 'optical products manufacturer' + | 'organic drug store' + | 'organic food store' + | 'oriental goods store' + | 'oriental medicine clinic' + | 'oriental medicine store' + | 'oriental rug store' + | 'orthopedic shoe store' + | 'orthopedic supplies store' + | 'outboard motor store' + | 'outdoor activity organiser' + | 'outdoor equestrian facility' + | 'outdoor furniture store' + | 'outdoor sports store' + | 'outdoor swimming pool' + | 'oxygen cocktail spot' + | 'oxygen equipment supplier' + | 'oyster bar restaurant' + | 'pacific rim restaurant' + | 'packaging supply store' + | 'pain control clinic' + | 'pain management physician' + | 'paint stripping service' + | 'painter and decorator' + | 'paper bag supplier' + | 'paralegal services provider' + | 'park and garden' + | 'park and ride' + | 'passport photo processor' + | 'paternity testing service' + | 'patients support association' + | 'patio enclosure supplier' + | 'paving materials supplier' + | 'pecel lele restaurant' + | 'pennsylvania dutch restaurant' + | 'performing arts group' + | 'performing arts theater' + | 'permanent make-up clinic' + | 'personal chef service' + | 'personal injury attorney' + | 'personal injury lawyer' + | 'personal watercraft dealer' + | 'pest control service' + | 'pet adoption service' + | 'pet boarding service' + | 'pet care service' + | 'pet moving service' + | 'pet supply store' + | 'petroleum products company' + | 'pharmaceutical products wholesaler' + | 'phone repair service' + | 'photo restoration service' + | 'physical examination center' + | 'physical fitness program' + | 'physical rehabilitation center' + | 'physical therapy clinic' + | 'physician referral service' + | 'physiotherapy equipment supplier' + | 'piano moving service' + | 'piano repair service' + | 'piano tuning service' + | 'picture frame shop' + | 'pinball machine supplier' + | 'pine furniture shop' + | 'place of worship' + | 'plast window store' + | 'plastic bag supplier' + | 'plastic bags wholesaler' + | 'plastic fabrication company' + | 'plastic products supplier' + | 'plastic products wholesaler' + | 'plastic resin manufacturer' + | 'plastic surgery clinic' + | 'playground equipment supplier' + | 'plumbing supply store' + | 'pneumatic tools supplier' + | 'police supply store' + | 'political party office' + | 'pond fish supplier' + | 'pond supply store' + | 'pony ride service' + | 'pool billard club' + | 'pool cleaning service' + | 'port operating company' + | 'portable building manufacturer' + | 'portable toilet supplier' + | 'powder coating service' + | 'power plant consultant' + | 'powersports vehicle dealer' + | 'practitioner service location' + | 'pregnancy care center' + | 'pressure washing service' + | 'printed music publisher' + | 'printer repair service' + | 'printing equipment supplier' + | 'private educational institution' + | 'private equity firm' + | 'private golf course' + | 'private sector bank' + | 'promotional products supplier' + | 'property administration service' + | 'property investment company' + | 'property management company' + | 'protective clothing supplier' + | 'psychoneurological specialized clinic' + | 'psychosomatic medical practitioner' + | "public defender's office" + | 'public educational institution' + | 'public golf course' + | 'public health department' + | 'public medical center' + | 'public parking space' + | 'public prosecutors office' + | 'public relations firm' + | 'public safety office' + | 'public sector bank' + | 'public swimming pool' + | 'public works department' + | 'puerto rican restaurant' + | 'pvc windows supplier' + | 'race car dealer' + | 'radiator repair service' + | 'raft trip outfitter' + | 'railroad equipment supplier' + | 'railroad ties supplier' + | 'rainwater tank supplier' + | 'rare book store' + | 'raw food restaurant' + | 'real estate agency' + | 'real estate agent' + | 'real estate appraiser' + | 'real estate attorney' + | 'real estate auctioneer' + | 'real estate consultant' + | 'real estate developer' + | 'real estate school' + | 'real estate surveyor' + | 'records storage facility' + | 'recycling drop-off location' + | 'refrigerated transport service' + | 'refrigerator repair service' + | 'regional government office' + | 'registered general nurse' + | 'religious book store' + | 'religious goods store' + | "renter's insurance agency" + | 'reproductive health clinic' + | 'restaurant or cafe' + | 'restaurant supply store' + | 'retaining wall supplier' + | 'rice cake shop' + | 'rice cracker shop' + | 'road construction company' + | 'road safety town' + | 'rock climbing gym' + | 'rock climbing instructor' + | 'rock music club' + | 'roller skating club' + | 'roller skating rink' + | 'roofing supply store' + | 'roommate referral service' + | 'rubber products supplier' + | 'rubber stamp store' + | 'rugby league club' + | 'russian grocery store' + | 'russian orthodox church' + | 'rustic furniture store' + | 'rv detailing service' + | 'rv repair shop' + | 'rv storage facility' + | 'rv supply store' + | 'safety equipment supplier' + | 'sailing event area' + | 'satellite communication service' + | 'saw sharpening service' + | 'scaffolding rental service' + | 'scale model club' + | 'scale repair service' + | 'school administration office' + | 'school bus service' + | 'school district office' + | 'school supply store' + | 'scientific equipment supplier' + | 'scooter rental service' + | 'scooter repair shop' + | 'scrap metal dealer' + | 'screen printing shop' + | 'screen repair service' + | 'scuba tour agency' + | 'seasonal goods store' + | 'second hand store' + | 'security guard service' + | 'security system supplier' + | 'self defense school' + | 'self service restaurant' + | 'self storage facility' + | 'semi conductor supplier' + | 'senior citizen center' + | 'senior high school' + | 'septic system service' + | 'seventh-day adventist church' + | 'sewage disposal service' + | 'sewage treatment plant' + | 'sewing machine store' + | 'sheet metal contractor' + | 'sheet music store' + | 'shipping equipment industry' + | 'shoe repair shop' + | 'shoe shining service' + | 'shooting event area' + | 'shower door shop' + | 'sightseeing tour agency' + | 'silk plant shop' + | 'singing telegram service' + | 'sixth form college' + | 'skate sharpening service' + | 'skeet shooting range' + | 'ski rental service' + | 'ski repair service' + | 'skin care clinic' + | 'small plates restaurant' + | 'smart car dealer' + | 'smog inspection station' + | 'snow removal service' + | 'snowboard rental service' + | 'snowmobile rental service' + | 'soba noodle shop' + | 'social security attorney' + | 'social security office' + | 'social services organization' + | 'social welfare center' + | 'societe de flocage' + | 'soft drinks shop' + | 'software training institute' + | 'soil testing service' + | 'solar energy company' + | 'solid fuel company' + | 'solid waste engineer' + | 'soto ayam restaurant' + | 'soul food restaurant' + | 'south african restaurant' + | 'south american restaurant' + | 'south asian restaurant' + | 'south indian restaurant' + | 'south sulawesi restaurant' + | 'southeast asian restaurant' + | 'southern italian restaurant' + | 'southern restaurant (us)' + | 'soy sauce maker' + | 'space of remembrance' + | 'special education school' + | 'sport tour agency' + | 'sporting goods store' + | 'sports accessories wholesaler' + | 'sports activity location' + | 'sports card store' + | 'sports injury clinic' + | 'sports massage therapist' + | 'sports medicine clinic' + | 'sports medicine physician' + | 'sports memorabilia store' + | 'sports nutrition store' + | 'sri lankan restaurant' + | 'stained glass studio' + | 'stainless steel plant' + | 'stall installation service' + | 'stamp collectors club' + | 'staple food package' + | 'state employment department' + | 'state government office' + | 'state liquor store' + | 'state owned farm' + | 'std testing service' + | 'steamed bun shop' + | 'steel construction company' + | 'steel framework contractor' + | 'steelwork design service' + | 'stereo rental store' + | 'stereo repair service' + | 'stock exchange building' + | 'store equipment supplier' + | 'stores and shopping' + | 'student housing center' + | 'students parents association' + | 'students support association' + | 'suburban train line' + | 'summer camp organizer' + | 'summer toboggan run' + | 'super public bath' + | 'supplementary educational institute' + | 'surf lifesaving club' + | 'surgical products wholesaler' + | 'surgical supply store' + | 'suzuki motorcycle dealer' + | 'swimming pool contractor' + | 'table tennis club' + | 'table tennis facility' + | 'tai chi school' + | 'tata motors dealer' + | 'tattoo removal service' + | "tax collector's office" + | 'tax preparation service' + | 'tea market place' + | 'teeth whitening service' + | 'telecommunications equipment supplier' + | 'telecommunications service provider' + | 'telephone answering service' + | 'television repair service' + | 'tent rental service' + | 'thai massage therapist' + | 'theater supply store' + | 'theatrical costume supplier' + | 'tile cleaning service' + | 'tire repair shop' + | 'toner cartridge supplier' + | 'tool rental service' + | 'tool repair shop' + | 'tourist information center' + | 'towing equipment provider' + | 'tractor repair shop' + | 'trading card store' + | 'traditional american restaurant' + | 'traditional costume club' + | 'traditional kostume store' + | 'trailer rental service' + | 'trailer repair shop' + | 'trailer supply store' + | 'train repairing center' + | 'train ticket agency' + | 'transportation escort service' + | 'triumph motorcycle dealer' + | 'tropical fish store' + | 'truck accessories store' + | 'truck driving school' + | 'truck parts supplier' + | 'truck rental agency' + | 'truck repair shop' + | 'truck topper supplier' + | 'tsukigime parking lot' + | 'tune up supplier' + | 'typewriter repair service' + | 'udon noodle restaurant' + | 'unfinished furniture store' + | 'unitarian universalist church' + | 'united methodist church' + | 'upholstery cleaning service' + | 'urban planning department' + | 'urgent care center' + | 'used appliance store' + | 'used bicycle shop' + | 'used book store' + | 'used car dealer' + | 'used cd store' + | 'used clothing store' + | 'used computer store' + | 'used furniture store' + | 'used game store' + | 'used motorcycle dealer' + | 'used tire shop' + | 'used truck dealer' + | 'utility trailer dealer' + | 'uyghur cuisine restaurant' + | 'vacuum cleaner store' + | 'valet parking service' + | 'van rental agency' + | 'vcr repair service' + | 'vegetable wholesale market' + | 'vehicle inspection service' + | 'vehicle repair shop' + | 'vehicle shipping agent' + | 'vehicle wrapping service' + | 'vending machine supplier' + | 'ventilating equipment manufacturer' + | 'venture capital company' + | 'veterans affairs department' + | 'video conferencing service' + | 'video duplication service' + | 'video editing service' + | 'video game store' + | 'video production service' + | 'vintage clothing store' + | 'vinyl sign shop' + | 'virtual office rental' + | 'visa consulting service' + | 'vocational gymnasium school' + | 'vocational secondary school' + | 'voter registration office' + | 'waste management service' + | 'waste transfer station' + | 'watch repair service' + | 'water cooler supplier' + | 'water filter supplier' + | 'water polo pool' + | 'water pump supplier' + | 'water purification company' + | 'water ski shop' + | 'water skiing club' + | 'water skiing instructor' + | 'water skiing service' + | 'water testing service' + | 'water treatment plant' + | 'water treatment supplier' + | 'water utility company' + | 'waterbed repair service' + | 'weather forecast service' + | 'web hosting company' + | 'wedding souvenir shop' + | 'weight loss service' + | 'welding gas supplier' + | 'welding supply store' + | 'well drilling contractor' + | 'west african restaurant' + | 'western apparel store' + | 'wheel alignment service' + | 'wheelchair rental service' + | 'wheelchair repair service' + | 'wholesale food store' + | 'wholesale plant nursery' + | 'wholesaler household appliances' + | 'wildlife rescue service' + | 'willow basket manufacturer' + | 'wind turbine builder' + | 'window cleaning service' + | 'window installation service' + | 'window tinting service' + | 'window treatment store' + | 'wine storage facility' + | 'winemaking supply store' + | 'wing chun school' + | "women's clothing store" + | "women's health clinic" + | "women's personal trainer" + | 'wood frame supplier' + | 'wood stove shop' + | 'wood working class' + | 'woodworking supply store' + | 'work clothes store' + | 'yamaha motorcycle dealer' + | 'yoga retreat center' + | 'youth care service' + | 'youth clothing store' + | 'adult day care center' + | 'adult foster care service' + | 'air compressor repair service' + | 'air conditioning repair service' + | 'air conditioning system supplier' + | 'air duct cleaning service' + | 'antique furniture restoration service' + | 'asian household goods store' + | 'assemblies of god church' + | 'audio visual equipment supplier' + | 'audiovisual equipment rental service' + | 'auto air conditioning service' + | 'auto body parts supplier' + | 'auto care products store' + | 'auto dent removal service' + | 'auto glass repair service' + | 'auto radiator repair service' + | 'auto tune up service' + | 'auto window tinting service' + | 'balloon ride tour agency' + | 'bar restaurant furniture store' + | 'beauty products vending machine' + | 'building equipment hire service' + | 'business to business service' + | 'cake decorating equipment shop' + | 'canoe & kayak store' + | 'canoe and kayak club' + | 'car security system installer' + | 'cardiovascular and thoracic surgeon' + | 'carport and pergola builder' + | 'cash and carry wholesaler' + | 'cell phone accessory store' + | 'cell phone charging station' + | 'chess and card club' + | 'child health care centre' + | 'church of the nazarene' + | 'city department of transportation' + | 'classified ads newspaper publisher' + | 'clock and watch maker' + | 'clothes and fabric manufacturer' + | 'clothes and fabric wholesaler' + | 'clothing wholesale market place' + | 'coach and minibus hire' + | 'commercial real estate agency' + | 'commercial real estate inspector' + | 'compressed natural gas station' + | 'computer support and services' + | 'concrete metal framework supplier' + | 'construction machine rental service' + | 'conveyor belt sushi restaurant' + | 'curtain supplier and maker' + | 'custom confiscated goods store' + | 'dairy farm equipment supplier' + | 'dan dan noodle restaurant' + | 'dealer of fiat professional' + | 'department of motor vehicles' + | 'department of public safety' + | 'department of social services' + | 'diesel engine repair service' + | 'disciples of christ church' + | 'dog day care center' + | 'domestic abuse treatment center' + | 'drivers license training school' + | 'dry wall supply store' + | 'dryer vent cleaning service' + | 'eating disorder treatment center' + | 'electric motor repair shop' + | 'electric motor scooter dealer' + | 'electric motor vehicle dealer' + | 'electric vehicle charging station' + | 'electrolysis hair removal service' + | 'energy equipment and solutions' + | 'environment renewable natural resources' + | 'executive suite rental agency' + | 'exhibition and trade centre' + | 'family day care service' + | 'farm equipment repair service' + | 'farming and cattle raising' + | 'fiber optic products supplier' + | 'film and photograph library' + | 'fire damage restoration service' + | 'fire department equipment supplier' + | 'fire protection equipment supplier' + | 'fire protection system supplier' + | 'fish & chips restaurant' + | 'fish and chips takeaway' + | 'food and beverage consultant' + | 'food and beverage exporter' + | 'foreign exchange students organization' + | 'foreign languages program school' + | 'fruit and vegetable processing' + | 'fruit and vegetable store' + | 'fruit and vegetable wholesaler' + | 'full dress rental service' + | 'glass & mirror shop' + | 'ground self defense force' + | 'guardia di finanza police' + | 'haute couture fashion house' + | 'health and beauty shop' + | 'hearing aid repair service' + | 'hearing assistance earphone store' + | 'hip hop dance class' + | 'home health care service' + | 'home help service agency' + | 'hospital equipment and supplies' + | 'hospitality and tourism school' + | 'hot tub repair service' + | 'hot water system supplier' + | 'hua niao market place' + | 'hunting and fishing store' + | 'ice cream equipment supplier' + | 'immigration & naturalization service' + | 'income protection insurance agency' + | 'income tax help association' + | 'industrial real estate agency' + | 'industrial technical engineers association' + | 'industrial vacuum equipment supplier' + | 'iron and steel store' + | 'it support and services' + | 'japanese cheap sweets shop' + | "jehovah's witness kingdom hall" + | 'karaoke equipment rental service' + | 'kilt shop and hire' + | 'kushiage and kushikatsu restaurant' + | 'laser hair removal service' + | 'lawn equipment rental service' + | 'lawn irrigation equipment supplier' + | 'lawn mower repair service' + | 'lawn sprinkler system contractor' + | 'learner driver training area' + | 'license plate frames supplier' + | 'low income housing program' + | 'marine self defense force' + | 'marriage or relationship counselor' + | 'martial arts supply store' + | 'material handling equipment supplier' + | 'medical diagnostic imaging center' + | 'metal detecting equipment supplier' + | 'metal heat treating service' + | 'microwave oven repair service' + | 'mobile home rental agency' + | 'mobile home supply store' + | 'mobile phone repair shop' + | 'model car play area' + | 'motor scooter repair shop' + | 'moving and storage service' + | 'muay thai boxing gym' + | 'municipal department of tourism' + | 'museum of space history' + | 'music management and promotion' + | 'musical instrument rental service' + | 'musical instrument repair shop' + | 'native american goods store' + | 'natural rock climbing area' + | 'non smoking holiday home' + | 'north eastern indian restaurant' + | 'occupational safety and health' + | 'off track betting shop' + | 'offal pot cooking restaurant' + | 'office equipment rental service' + | 'office equipment repair service' + | 'office space rental agency' + | 'oil field equipment supplier' + | 'olive oil bottling company' + | 'optical instrument repair service' + | 'oral and maxillofacial surgeon' + | 'orthotics & prosthetics service' + | 'paper shredding machine supplier' + | 'parking lot for bicycles' + | 'parking lot for motorcycles' + | 'party equipment rental service' + | 'pay by weight restaurant' + | 'plant and machinery hire' + | 'plastic injection molding service' + | 'plus size clothing store' + | 'power plant equipment supplier' + | 'printer ink refill store' + | 'professional and hobby associations' + | 'qing fang market place' + | 'racing car parts store' + | 'ready mix concrete supplier' + | 'real estate rental agency' + | 'recreational vehicle rental agency' + | 'research and product development' + | 'retail space rental agency' + | 'rolled metal products supplier' + | 'safe & vault shop' + | 'sand & gravel supplier' + | 'sand and gravel supplier' + | 'school for the deaf' + | 'screen printing supply store' + | 'security system installation service' + | 'self service car wash' + | 'self service health station' + | 'sewing machine repair service' + | 'shipbuilding and repair company' + | 'shipping and mailing service' + | 'shop supermarket furniture store' + | 'single sex secondary school' + | 'small appliance repair service' + | 'small claims assistance service' + | 'small engine repair service' + | 'social security financial department' + | 'solar energy equipment supplier' + | 'solar energy system service' + | 'solar panel maintenance service' + | 'solar photovoltaic power plant' + | 'south east asian restaurant' + | 'spa and health club' + | 'sports equipment rental service' + | 'stage lighting equipment supplier' + | 'state office of education' + | 'student career counseling office' + | 'study at home school' + | 'sweets and dessert buffet' + | 'swimming pool repair service' + | 'swimming pool supply store' + | 'syokudo and teishoku restaurant' + | 'table tennis supply store' + | 'tattoo and piercing shop' + | 'tea and coffee shop' + | 'tennis court construction company' + | 'threads and yarns wholesaler' + | 'tool & die shop' + | 'trade fair construction company' + | 'united church of canada' + | 'united church of christ' + | 'used auto parts store' + | 'used musical instrument store' + | 'used office furniture store' + | 'used store fixture supplier' + | 'vacation home rental agency' + | 'vacuum cleaner repair shop' + | 'vacuum cleaning system supplier' + | 'vegetarian cafe and deli' + | 'video camera repair service' + | 'video conferencing equipment supplier' + | 'video equipment repair service' + | 'video game rental kiosk' + | 'video game rental store' + | 'visa and passport office' + | 'vitamin & supplements store' + | 'washer & dryer store' + | 'water damage restoration service' + | 'water jet cutting service' + | 'water softening equipment supplier' + | 'water tank cleaning service' + | 'water works equipment supplier' + | 'waxing hair removal service' + | 'wedding dress rental service' + | 'whale watching tour agency' + | 'wildlife and safari park' + | 'wine wholesaler and importer' + | 'wood floor installation service' + | 'wood floor refinishing service' + | 'youth social services organization' + | 'architectural and engineering model maker' + | 'army & navy surplus shop' + | 'audio visual equipment repair service' + | 'bottle & can redemption center' + | 'canoe & kayak tour agency' + | 'car finance and loan company' + | 'car repair and maintenance service' + | 'catering food and drink supplier' + | 'coin operated laundry equipment supplier' + | 'combined primary and secondary school' + | 'disability services and support organization' + | 'dress and tuxedo rental service' + | 'electric vehicle charging station contractor' + | 'electronics retail and repair shop' + | 'federal agency for technical relief' + | 'flavours fragrances and aroma supplier' + | 'floor sanding and polishing service' + | 'ice cream and drink shop' + | 'industrial spares and products wholesaler' + | 'institute of geography and statistics' + | 'multimedia and electronic book publisher' + | 'oil & natural gas company' + | 'oil and gas exploration service' + | 'organ donation and tissue bank' + | 'outdoor clothing and equipment shop' + | 'pet food and animal feeds' + | 'pick your own farm produce' + | 'polythene and plastic sheeting supplier' + | 'road construction machine repair service' + | 'sheepskin and wool products supplier' + | 'short term apartment rental agency' + | 'skin care products vending machine' + | 'solar hot water system supplier' + | 'sukiyaki and shabu shabu restaurant' + | 'united states armed forces base' + | 'washer & dryer repair service' + | 'water sports equipment rental service' + | 'wood and laminate flooring supplier' + | 'aboriginal and torres strait islander organisation' + | 'hong kong style fast food restaurant' + | 'roads ports and canals engineers association' + | 'church of jesus christ of latter-day saints' + > + | undefined; + searchMatching: 'all' | 'only_includes' | 'only_exact'; + placeMinimumStars: '' | 'two' | 'twoAndHalf' | 'three' | 'threeAndHalf' | 'four' | 'fourAndHalf'; + website: 'allPlaces' | 'withWebsite' | 'withoutWebsite'; + skipClosedPlaces: boolean; + scrapePlaceDetailPage: boolean; + scrapeTableReservationProvider: boolean; + scrapeOrderOnline: boolean; + includeWebResults: boolean; + scrapeDirectories: boolean; + maxQuestions: number; + scrapeContacts: boolean; + scrapeSocialMediaProfiles: { + facebooks?: boolean | undefined; + instagrams?: boolean | undefined; + youtubes?: boolean | undefined; + tiktoks?: boolean | undefined; + twitters?: boolean | undefined; + }; + maximumLeadsEnrichmentRecords: number; + leadsEnrichmentDepartments?: + | Array< + | 'c_suite' + | 'product' + | 'engineering_technical' + | 'design' + | 'education' + | 'finance' + | 'human_resources' + | 'information_technology' + | 'legal' + | 'marketing' + | 'medical_health' + | 'operations' + | 'sales' + | 'consulting' + > + | undefined; + verifyLeadsEnrichmentEmails: boolean; + maxReviews: number; + reviewsStartDate?: string | undefined; + reviewsSort: 'newest' | 'mostRelevant' | 'highestRanking' | 'lowestRanking'; + reviewsFilterString: string; + reviewsOrigin: 'all' | 'google'; + scrapeReviewsPersonalData: boolean; + maxImages: number; + scrapeImageAuthors: boolean; + enableCompetitorAnalysis: boolean; + maxCompetitorsToAnalyze: number; + countryCode?: + | '' + | 'us' + | 'af' + | 'al' + | 'dz' + | 'as' + | 'ad' + | 'ao' + | 'ai' + | 'aq' + | 'ag' + | 'ar' + | 'am' + | 'aw' + | 'au' + | 'at' + | 'az' + | 'bs' + | 'bh' + | 'bd' + | 'bb' + | 'by' + | 'be' + | 'bz' + | 'bj' + | 'bm' + | 'bt' + | 'bo' + | 'ba' + | 'bw' + | 'bv' + | 'br' + | 'io' + | 'bn' + | 'bg' + | 'bf' + | 'bi' + | 'kh' + | 'cm' + | 'ca' + | 'cv' + | 'ky' + | 'cf' + | 'td' + | 'cl' + | 'cn' + | 'cx' + | 'cc' + | 'co' + | 'km' + | 'cg' + | 'cd' + | 'ck' + | 'cr' + | 'ci' + | 'hr' + | 'cu' + | 'cy' + | 'cz' + | 'dk' + | 'dj' + | 'dm' + | 'do' + | 'ec' + | 'eg' + | 'sv' + | 'gq' + | 'er' + | 'ee' + | 'et' + | 'fk' + | 'fo' + | 'fj' + | 'fi' + | 'fr' + | 'gf' + | 'pf' + | 'tf' + | 'ga' + | 'gm' + | 'ge' + | 'de' + | 'gh' + | 'gi' + | 'gr' + | 'gl' + | 'gd' + | 'gp' + | 'gu' + | 'gt' + | 'gn' + | 'gw' + | 'gy' + | 'ht' + | 'hm' + | 'va' + | 'hn' + | 'hu' + | 'is' + | 'in' + | 'id' + | 'ir' + | 'iq' + | 'ie' + | 'il' + | 'it' + | 'jm' + | 'jp' + | 'jo' + | 'kz' + | 'ke' + | 'ki' + | 'kp' + | 'kr' + | 'kw' + | 'kg' + | 'la' + | 'lv' + | 'lb' + | 'ls' + | 'lr' + | 'ly' + | 'li' + | 'lt' + | 'lu' + | 'mo' + | 'mk' + | 'mg' + | 'mw' + | 'my' + | 'mv' + | 'ml' + | 'mt' + | 'mh' + | 'mq' + | 'mr' + | 'mu' + | 'yt' + | 'mx' + | 'fm' + | 'md' + | 'mc' + | 'mn' + | 'me' + | 'ms' + | 'ma' + | 'mz' + | 'mm' + | 'na' + | 'nr' + | 'np' + | 'nl' + | 'an' + | 'nc' + | 'nz' + | 'ni' + | 'ne' + | 'ng' + | 'nu' + | 'nf' + | 'mp' + | 'no' + | 'om' + | 'pk' + | 'pw' + | 'ps' + | 'pa' + | 'pg' + | 'py' + | 'pe' + | 'ph' + | 'pn' + | 'pl' + | 'pt' + | 'pr' + | 'qa' + | 're' + | 'ro' + | 'ru' + | 'rw' + | 'sh' + | 'kn' + | 'lc' + | 'pm' + | 'vc' + | 'ws' + | 'sm' + | 'st' + | 'sa' + | 'sn' + | 'rs' + | 'sc' + | 'sl' + | 'sg' + | 'sk' + | 'si' + | 'sb' + | 'so' + | 'za' + | 'gs' + | 'ss' + | 'es' + | 'lk' + | 'sd' + | 'sr' + | 'sj' + | 'sz' + | 'se' + | 'ch' + | 'sy' + | 'tw' + | 'tj' + | 'tz' + | 'th' + | 'tl' + | 'tg' + | 'tk' + | 'to' + | 'tt' + | 'tn' + | 'tr' + | 'tm' + | 'tc' + | 'tv' + | 'ug' + | 'ua' + | 'ae' + | 'gb' + | 'um' + | 'uy' + | 'uz' + | 'vu' + | 've' + | 'vn' + | 'vg' + | 'vi' + | 'wf' + | 'eh' + | 'ye' + | 'zm' + | 'zw' + | undefined; + city?: string | undefined; + state?: string | undefined; + county?: string | undefined; + postalCode?: string | undefined; + customGeolocation?: Record | undefined; + startUrls?: Array | undefined; + placeIds?: Array | undefined; + allPlacesNoSearchAction: '' | 'all_places_no_search_ocr' | 'all_places_no_search_mouse'; +}; + +export type InputArgs = { + searchStringsArray?: Array | undefined; + locationQuery?: string | undefined; + maxCrawledPlacesPerSearch?: number | undefined; + language?: + | 'en' + | 'af' + | 'az' + | 'id' + | 'ms' + | 'bs' + | 'ca' + | 'cs' + | 'da' + | 'de' + | 'et' + | 'es' + | 'es-419' + | 'eu' + | 'fil' + | 'fr' + | 'gl' + | 'hr' + | 'zu' + | 'is' + | 'it' + | 'sw' + | 'lv' + | 'lt' + | 'hu' + | 'nl' + | 'no' + | 'uz' + | 'pl' + | 'pt-BR' + | 'pt-PT' + | 'ro' + | 'sq' + | 'sk' + | 'sl' + | 'fi' + | 'sv' + | 'vi' + | 'tr' + | 'el' + | 'bg' + | 'ky' + | 'kk' + | 'mk' + | 'mn' + | 'ru' + | 'sr' + | 'uk' + | 'ka' + | 'hy' + | 'iw' + | 'ur' + | 'ar' + | 'fa' + | 'am' + | 'ne' + | 'hi' + | 'mr' + | 'bn' + | 'pa' + | 'gu' + | 'ta' + | 'te' + | 'kn' + | 'ml' + | 'si' + | 'th' + | 'lo' + | 'my' + | 'km' + | 'ko' + | 'ja' + | 'zh-CN' + | 'zh-TW' + | undefined; + categoryFilterWords?: + | Array< + | 'abbey' + | 'accountant' + | 'accounting' + | 'acupuncturist' + | 'aeroclub' + | 'agriculture' + | 'airline' + | 'airport' + | 'airstrip' + | 'allergist' + | 'amphitheater' + | 'amphitheatre' + | 'anesthesiologist' + | 'appraiser' + | 'aquarium' + | 'arboretum' + | 'architect' + | 'archive' + | 'arena' + | 'artist' + | 'ashram' + | 'astrologer' + | 'atm' + | 'attorney' + | 'audiologist' + | 'auditor' + | 'auditorium' + | 'bakery' + | 'band' + | 'bank' + | 'bar' + | 'barrister' + | 'basilica' + | 'bazar' + | 'beach' + | 'beautician' + | 'bistro' + | 'blacksmith' + | 'bodega' + | 'bookbinder' + | 'botanica' + | 'boutique' + | 'brasserie' + | 'brewery' + | 'brewpub' + | 'bricklayer' + | 'bridge' + | 'builder' + | 'building' + | 'bullring' + | 'butchers' + | 'cafe' + | 'cafeteria' + | 'campground' + | 'cannery' + | 'cardiologist' + | 'carpenter' + | 'cars' + | 'carvery' + | 'cashpoint' + | 'casino' + | 'castle' + | 'caterer' + | 'catering' + | 'cathedral' + | 'cattery' + | 'cemetery' + | 'chalet' + | 'chapel' + | 'charcuterie' + | 'charity' + | 'chemist' + | 'childminder' + | 'chiropractor' + | 'choir' + | 'church' + | 'churreria' + | 'circus' + | 'cleaners' + | 'clergyman' + | 'clinic' + | 'club' + | 'coalfield' + | 'college' + | 'company' + | 'computers' + | 'congregation' + | 'construction' + | 'consultant' + | 'contractor' + | 'conveyancer' + | 'coppersmith' + | 'cottage' + | 'council' + | 'counselor' + | 'courthouse' + | 'creperie' + | 'dairy' + | 'deli' + | 'delicatessen' + | 'dentist' + | 'dermatologist' + | 'design' + | 'dhaba' + | 'diabetologist' + | 'dietitian' + | 'diner' + | 'distillery' + | 'dj' + | 'doctor' + | 'doula' + | 'dressmaker' + | 'dyeworks' + | 'eatery' + | 'education' + | 'electrician' + | 'electronics' + | 'embassy' + | 'endocrinologist' + | 'endodontist' + | 'endoscopist' + | 'engineer' + | 'engraver' + | 'entertainer' + | 'entertainment' + | 'establishment' + | 'executor' + | 'exhibit' + | 'exporter' + | 'fairground' + | 'farm' + | 'farmstay' + | 'favela' + | 'festival' + | 'florist' + | 'fortress' + | 'foundation' + | 'foundry' + | 'frituur' + | 'garden' + | 'gardener' + | 'gasfitter' + | 'gastroenterologist' + | 'gastropub' + | 'gemologist' + | 'genealogist' + | 'geriatrician' + | 'glazier' + | 'goldsmith' + | 'government' + | 'greengrocer' + | 'greenhouse' + | 'grill' + | 'gurudwara' + | 'gym' + | 'gynecologist' + | 'haberdashery' + | 'hairdresser' + | 'hammam' + | 'handicraft' + | 'handyman/handywoman/handyperson' + | 'health' + | 'heliport' + | 'hematologist' + | 'hepatologist' + | 'herbalist' + | 'homeopath' + | 'homestay' + | 'hospice' + | 'hospital' + | 'hostel' + | 'hotel' + | 'hypermarket' + | 'immunologist' + | 'importer' + | 'inn' + | 'instruction' + | 'intensivist' + | 'internist' + | 'island' + | 'jeweler' + | 'joiner' + | 'junkyard' + | 'karaoke' + | 'kennel' + | 'kindergarten' + | 'kinesiologist' + | 'kinesiotherapist' + | 'kiosk' + | 'laboratory' + | 'lake' + | 'landscaper' + | 'lapidary' + | 'laundromat' + | 'laundry' + | 'lawyer' + | 'library' + | 'lido' + | 'liquidator' + | 'locksmith' + | 'lodge' + | 'lodging' + | 'lounge' + | 'lyceum' + | 'magician' + | 'makerspace' + | 'manufacturer' + | 'marae' + | 'marina' + | 'market' + | 'mechanic' + | 'memorial' + | 'metalwork' + | 'meyhane' + | 'midwife' + | 'mill' + | 'mine' + | 'mission' + | 'mohel' + | 'monastery' + | 'monument' + | 'mortuary' + | 'mosque' + | 'motel' + | 'mover' + | 'musalla' + | 'museum' + | 'musician' + | 'nephrologist' + | 'neurologist' + | 'neurophysiologist' + | 'neuropsychologist' + | 'neurosurgeon' + | 'newsstand' + | 'numerologist' + | 'nutritionist' + | 'observatory' + | 'obstetrician-gynecologist' + | 'office' + | 'oilfield' + | 'oncologist' + | 'onsen' + | 'ophthalmologist' + | 'optician' + | 'optometrist' + | 'orchard' + | 'orchestra' + | 'orphanage' + | 'orthodontist' + | 'orthoptist' + | 'osteopath' + | 'otolaryngologist' + | 'pagoda' + | 'painter' + | 'painting' + | 'parapharmacy' + | 'parish' + | 'park' + | 'parking' + | 'pathologist' + | 'patisserie' + | 'pediatrician' + | 'pedorthist' + | 'periodontist' + | 'pharmacy' + | 'photographer' + | 'physiatrist' + | 'physiotherapist' + | 'planetarium' + | 'plasterer' + | 'playground' + | 'playgroup' + | 'plumber' + | 'podiatrist' + | 'pre-school' + | 'preschool' + | 'priest' + | 'prison' + | 'proctologist' + | 'promenade' + | 'prosthodontist' + | 'psychiatrist' + | 'psychic' + | 'psychoanalyst' + | 'psychologist' + | 'psychotherapist' + | 'pub' + | 'publisher' + | 'pulmonologist' + | 'pyrotechnician' + | 'quarry' + | 'radiologist' + | 'radiotherapist' + | 'rafting' + | 'ranch' + | 'recreation' + | 'recruiter' + | 'rectory' + | 'recycling' + | 'reflexologist' + | 'remodeler' + | 'restaurant' + | 'rheumatologist' + | 'river' + | 'rodeo' + | 'rugby' + | 'sacem' + | 'saddlery' + | 'sailmaker' + | 'sambodrome' + | 'sauna' + | 'school' + | 'scouting' + | 'sculptor' + | 'sculpture' + | 'seitai' + | 'seminary' + | 'services' + | 'sexologist' + | 'shelter' + | 'shipyard' + | 'shop' + | 'shopfitter' + | 'showroom' + | 'shrine' + | 'silversmith' + | 'skatepark' + | 'slaughterhouse' + | 'soapland' + | 'spa' + | 'sports' + | 'stable' + | 'stadium' + | 'stage' + | 'statuary' + | 'store' + | 'stylist' + | 'supermarket' + | 'surgeon' + | 'surveyor' + | 'synagogue' + | 'tailor' + | 'takeaway' + | 'tannery' + | 'taxidermist' + | 'telecommunications' + | 'toolroom' + | 'travel' + | 'turnery' + | 'university' + | 'urologist' + | 'velodrome' + | 'venereologist' + | 'veterinarian' + | 'villa' + | 'vineyard' + | 'warehouse' + | 'weir' + | 'welder' + | 'wholesaler' + | 'winery' + | 'woods' + | 'woodworker' + | 'yakatabune' + | 'yeshiva' + | 'zoo' + | 'abarth dealer' + | 'abortion clinic' + | 'abrasives supplier' + | 'academic department' + | 'açaí shop' + | 'acaraje restaurant' + | 'accounting firm' + | 'accounting school' + | 'acoustical consultant' + | 'acrylic store' + | 'acupuncture clinic' + | 'acupuncture school' + | 'acura dealer' + | 'administrative attorney' + | 'adoption agency' + | 'advertising agency' + | 'advertising photographer' + | 'advertising service' + | 'aerial photographer' + | 'aerobics instructor' + | 'aeromodel shop' + | 'aeronautical engineer' + | 'aerospace company' + | 'afghan restaurant' + | 'african restaurant' + | 'agenzia entrate' + | 'aggregate supplier' + | 'agistment service' + | 'agricultural association' + | 'agricultural cooperative' + | 'agricultural engineer' + | 'agricultural organization' + | 'agricultural production' + | 'agricultural service' + | 'agrochemicals supplier' + | 'aikido club' + | 'aikido school' + | 'air taxi' + | 'airbrushing service' + | 'aircraft dealer' + | 'aircraft manufacturer' + | 'alcohol manufacturer' + | 'alliance church' + | 'alsace restaurant' + | 'alternator supplier' + | 'aluminium supplier' + | 'aluminum supplier' + | 'aluminum welder' + | 'aluminum window' + | 'ambulance service' + | 'american restaurant' + | 'ammunition supplier' + | 'amusement center' + | 'amusement park' + | 'anago restaurant' + | 'andalusian restaurant' + | 'andhra restaurant' + | 'anganwadi center' + | 'anglican church' + | 'animal hospital' + | 'animal shelter' + | 'animation studio' + | 'anime club' + | 'antenna service' + | 'antique store' + | 'apartment building' + | 'apartment complex' + | 'apostolic church' + | 'apparel company' + | 'appliance store' + | 'apprenticeship center' + | 'aquaculture farm' + | 'aquarium shop' + | 'aquatic centre' + | 'arab restaurant' + | 'arborist service' + | 'archaeological museum' + | 'archery club' + | 'archery range' + | 'archery store' + | 'architects association' + | 'architectural designer' + | 'architecture firm' + | 'architecture school' + | 'argentinian restaurant' + | 'armenian church' + | 'armenian restaurant' + | 'army facility' + | 'army museum' + | 'aromatherapy class' + | 'aromatherapy service' + | 'art cafe' + | 'art center' + | 'art dealer' + | 'art gallery' + | 'art museum' + | 'art school' + | 'art studio' + | 'artistic handicrafts' + | 'arts organization' + | 'asian restaurant' + | 'asphalt contractor' + | 'assamese restaurant' + | 'assistante maternelle' + | 'asturian restaurant' + | 'athletic club' + | 'athletic field' + | 'athletic park' + | 'athletic track' + | 'atv dealer' + | 'auction house' + | 'audi dealer' + | 'australian restaurant' + | 'austrian restaurant' + | 'auto auction' + | 'auto broker' + | 'auto market' + | 'auto painting' + | 'auto upholsterer' + | 'auto wrecker' + | 'automation company' + | 'aviation consultant' + | 'awadhi restaurant' + | 'awning supplier' + | 'ayurvedic clinic' + | 'azerbaijani restaurant' + | 'baby store' + | 'baden restaurant' + | 'badminton club' + | 'badminton complex' + | 'badminton court' + | 'bag shop' + | 'bagel shop' + | 'bait shop' + | 'bakery equipment' + | 'bakso restaurant' + | 'balinese restaurant' + | 'ballet school' + | 'ballet theater' + | 'balloon artist' + | 'balloon store' + | 'bangladeshi restaurant' + | 'bangle shop' + | 'bankruptcy attorney' + | 'bankruptcy service' + | 'banner store' + | 'banquet hall' + | 'baptist church' + | 'bar pmu' + | 'bar tabac' + | 'barbecue area' + | 'barbecue restaurant' + | 'barber school' + | 'barber shop' + | 'bariatric surgeon' + | 'bark supplier' + | 'barrel supplier' + | 'bartending school' + | 'baseball club' + | 'baseball field' + | 'basket supplier' + | 'basketball club' + | 'basketball court' + | 'basque restaurant' + | 'batak restaurant' + | 'bathroom remodeler' + | 'bathroom renovator' + | 'battery manufacturer' + | 'battery store' + | 'battery wholesaler' + | 'bavarian restaurant' + | 'beach club' + | 'beach pavillion' + | 'bead store' + | 'bead wholesaler' + | 'bearing supplier' + | 'beauty parlour' + | 'beauty salon' + | 'beauty school' + | 'bed shop' + | 'bedding store' + | 'beer distributor' + | 'beer garden' + | 'beer hall' + | 'beer store' + | 'belgian restaurant' + | 'belt shop' + | 'bengali restaurant' + | 'bentley dealer' + | 'berry restaurant' + | 'betawi restaurant' + | 'betting agency' + | 'beverage distributor' + | 'beverage supplier' + | 'bicycle club' + | 'bicycle rack' + | 'bicycle shop' + | 'bicycle store' + | 'bicycle wholesaler' + | 'bike wash' + | 'bilingual school' + | 'bingo hall' + | 'biochemistry lab' + | 'biofeedback therapist' + | 'biotechnology company' + | 'bird shop' + | 'birth center' + | 'biryani restaurant' + | 'blinds shop' + | 'blood bank' + | 'blueprint service' + | 'blues club' + | 'bmw dealer' + | 'bmx club' + | 'bmx park' + | 'boarding house' + | 'boarding school' + | 'boat builders' + | 'boat club' + | 'boat dealer' + | 'boat ramp' + | 'boating instructor' + | 'boiler manufacturer' + | 'boiler supplier' + | 'bonesetting house' + | 'book publisher' + | 'book store' + | 'bookkeeping service' + | 'books wholesaler' + | 'boot camp' + | 'boot store' + | 'border guard' + | 'botanical garden' + | 'bowling alley' + | 'bowling club' + | 'boxing club' + | 'boxing gym' + | 'boxing ring' + | "boys' hostel" + | 'bpo company' + | 'brake shop' + | 'branding agency' + | 'brazilian pastelaria' + | 'brazilian restaurant' + | 'breakfast restaurant' + | 'brick manufacturer' + | 'bridal shop' + | 'bridge club' + | 'british restaurant' + | 'brunch restaurant' + | 'buddhist temple' + | 'buffet restaurant' + | 'bugatti dealer' + | 'buick dealer' + | 'building consultant' + | 'building designer' + | 'building firm' + | 'building inspector' + | 'building society' + | 'bulgarian restaurant' + | 'burmese restaurant' + | 'burrito restaurant' + | 'bus charter' + | 'bus company' + | 'bus depot' + | 'bus station' + | 'bus stop' + | 'business attorney' + | 'business broker' + | 'business center' + | 'business park' + | 'business school' + | 'butcher shop' + | 'butsudan store' + | 'cabaret club' + | 'cabinet maker' + | 'cabinet store' + | 'cable company' + | 'cadillac dealer' + | 'cajun restaurant' + | 'cake shop' + | 'californian restaurant' + | 'call center' + | 'call shop' + | 'calligraphy lesson' + | 'cambodian restaurant' + | 'camera store' + | 'camping cabin' + | 'camping farm' + | 'camping store' + | 'canadian restaurant' + | 'candle store' + | 'candy store' + | 'cannabis club' + | 'cannabis store' + | 'canoeing area' + | 'cantabrian restaurant' + | 'cantonese restaurant' + | 'capoeira school' + | 'capsule hotel' + | 'car dealer' + | 'car factory' + | 'car manufacturer' + | 'car wash' + | 'carabinieri police' + | 'care services' + | 'caribbean restaurant' + | 'carnival club' + | 'carpet installer' + | 'carpet manufacturer' + | 'carpet store' + | 'carpet wholesaler' + | 'casket service' + | 'castilian restaurant' + | 'cat breeder' + | 'cat cafe' + | 'cat trainer' + | 'catalonian restaurant' + | 'catholic cathedral' + | 'catholic church' + | 'catholic school' + | 'cattle farm' + | 'cattle market' + | 'caucasian restaurant' + | 'cbse school' + | 'cd store' + | 'ceiling supplier' + | 'cement manufacturer' + | 'cement supplier' + | 'cendol restaurant' + | 'central bank' + | 'ceramic manufacturer' + | 'ceramics wholesaler' + | 'certification agency' + | 'charter school' + | 'chartered accountant' + | 'chauffeur service' + | 'cheese manufacturer' + | 'cheese shop' + | 'cheesesteak restaurant' + | 'chemical exporter' + | 'chemical industry' + | 'chemical manufacturer' + | 'chemical plant' + | 'chemical wholesaler' + | 'chemistry lab' + | 'chesapeake restaurant' + | 'chess club' + | 'chess instructor' + | 'chevrolet dealer' + | 'chicken restaurant' + | 'chicken shop' + | 'child psychiatrist' + | 'child psychologist' + | 'childbirth class' + | 'children hall' + | 'children policlinic' + | "children's cafe" + | "children's camp" + | "children's club" + | "children's hospital" + | "children's store" + | 'childrens store' + | 'chilean restaurant' + | 'chimney services' + | 'chimney sweep' + | 'chinaware store' + | 'chinese bakery' + | 'chinese restaurant' + | 'chinese supermarket' + | 'chinese takeaway' + | 'chocolate artisan' + | 'chocolate cafe' + | 'chocolate factory' + | 'chocolate shop' + | 'chop bar' + | 'chophouse restaurant' + | 'christian church' + | 'christian college' + | 'christmas market' + | 'christmas store' + | 'chrysler dealer' + | 'cider bar' + | 'cider mill' + | 'cigar shop' + | 'citroen dealer' + | 'city courthouse' + | 'city hall' + | 'city park' + | 'civic center' + | 'civil engineer' + | 'civil police' + | 'cleaning service' + | 'clothes market' + | 'clothing manufacturer' + | 'clothing shop' + | 'clothing store' + | 'clothing supplier' + | 'clothing wholesaler' + | 'co-ed school' + | 'coaching center' + | 'coaching service' + | 'coal exporter' + | 'coal supplier' + | 'cocktail bar' + | 'coffee roasters' + | 'coffee shop' + | 'coffee stand' + | 'coffee store' + | 'coffee wholesaler' + | 'coffin supplier' + | 'coin dealer' + | 'collectibles store' + | 'colombian restaurant' + | 'comedy club' + | 'comic cafe' + | 'commercial agent' + | 'commercial photographer' + | 'commercial printer' + | 'community center' + | 'community college' + | 'community garden' + | 'community school' + | 'company registry' + | 'computer club' + | 'computer consultant' + | 'computer service' + | 'computer shop' + | 'computer store' + | 'computer wholesaler' + | 'concert hall' + | 'concrete contractor' + | 'concrete factory' + | 'condiments supplier' + | 'condominium complex' + | 'confectionery store' + | 'confectionery wholesaler' + | 'conference center' + | 'conservative club' + | 'conservative synagogue' + | 'consignment shop' + | 'construction company' + | 'container service' + | 'container supplier' + | 'container terminal' + | 'containers supplier' + | 'continental restaurant' + | 'convenience store' + | 'convention center' + | 'cookie shop' + | 'cooking class' + | 'cooking school' + | 'cooling plant' + | 'cooperative bank' + | 'copper supplier' + | 'copy shop' + | 'copywriting service' + | 'corporate campus' + | 'corporate office' + | 'cosmetic dentist' + | 'cosmetic surgeon' + | 'cosmetics industry' + | 'cosmetics shop' + | 'cosmetics store' + | 'cosmetics wholesaler' + | 'cosplay cafe' + | 'costume store' + | 'cottage rental' + | 'cottage village' + | 'cotton exporter' + | 'cotton mill' + | 'cotton supplier' + | 'countertop contractor' + | 'countertop store' + | 'country club' + | 'country house' + | 'country park' + | 'courier service' + | 'court reporter' + | 'couscous restaurant' + | 'coworking space' + | 'crab house' + | 'craft store' + | 'cramming school' + | 'crane dealer' + | 'crane service' + | 'craniosacral therapy' + | 'credit union' + | 'cremation service' + | 'creole restaurant' + | 'cricket club' + | 'cricket ground' + | 'cricket shop' + | 'croatian restaurant' + | 'crop grower' + | 'croquet club' + | 'cruise agency' + | 'cruise terminal' + | 'crypto atm' + | 'cuban restaurant' + | 'culinary school' + | 'cultural association' + | 'cultural center' + | 'cultural landmark' + | 'cupcake shop' + | 'cupra dealer' + | 'curling club' + | 'curling hall' + | 'curtain store' + | 'custom tailor' + | 'customs broker' + | 'customs consultant' + | 'customs office' + | 'customs warehouse' + | 'cutlery store' + | 'cycling park' + | 'czech restaurant' + | 'dacia dealer' + | 'daihatsu dealer' + | 'dairy farm' + | 'dairy store' + | 'dairy supplier' + | 'dance club' + | 'dance company' + | 'dance hall' + | 'dance pavillion' + | 'dance restaurant' + | 'dance school' + | 'dance store' + | 'danish restaurant' + | 'dart bar' + | 'dating service' + | 'day spa' + | 'day-use onsen' + | 'deaf church' + | 'deaf service' + | 'debt collecting' + | 'decal supplier' + | 'deck builder' + | 'delivery restaurant' + | 'delivery service' + | 'demolition contractor' + | 'dental clinic' + | 'dental hygienist' + | 'dental laboratory' + | 'dental radiology' + | 'dental school' + | 'department store' + | 'desalination plant' + | 'design agency' + | 'design engineer' + | 'design institute' + | 'dessert restaurant' + | 'dessert shop' + | 'detention center' + | 'diabetes center' + | 'diagnostic center' + | 'dialysis center' + | 'diamond buyer' + | 'diamond dealer' + | 'diaper service' + | 'digital printer' + | 'dinner theater' + | 'dirt supplier' + | 'disco club' + | 'discount store' + | 'discount supermarket' + | 'distribution service' + | 'district attorney' + | 'district justice' + | 'district office' + | 'dive club' + | 'dive shop' + | 'diving center' + | 'divorce lawyer' + | 'divorce service' + | 'dj service' + | 'do-it-yourself shop' + | 'dock builder' + | 'dodge dealer' + | 'dog breeder' + | 'dog cafe' + | 'dog park' + | 'dog trainer' + | 'dog walker' + | 'dojo restaurant' + | 'doll store' + | 'dollar store' + | 'domestic airport' + | 'dominican restaurant' + | 'donations center' + | 'donut shop' + | 'door manufacturer' + | 'door shop' + | 'door supplier' + | 'door warehouse' + | 'drafting service' + | 'drainage service' + | 'drama school' + | 'drawing lessons' + | 'dress shop' + | 'dress store' + | 'drilling contractor' + | 'driveshaft shop' + | 'driving school' + | 'drone service' + | 'drone shop' + | 'drug store' + | 'drum school' + | 'drum store' + | 'dry cleaner' + | 'ducati dealer' + | 'dude ranch' + | 'dumpling restaurant' + | 'durum restaurant' + | 'dutch restaurant' + | 'dvd store' + | 'dye store' + | 'dynamometer supplier' + | 'e-commerce service' + | 'eclectic restaurant' + | 'ecological park' + | 'ecologists association' + | 'economic consultant' + | 'ecuadorian restaurant' + | 'education center' + | 'education centre' + | 'educational consultant' + | 'educational institution' + | 'egg supplier' + | 'egyptian restaurant' + | 'electrical engineer' + | 'electrical substation' + | 'electronics company' + | 'electronics engineer' + | 'electronics manufacturer' + | 'electronics store' + | 'electronics wholesaler' + | 'elementary school' + | 'elevator manufacturer' + | 'elevator service' + | 'embossing service' + | 'embroidery service' + | 'embroidery shop' + | 'emdr psychotherapist' + | 'emergency room' + | 'emergency training' + | 'employment agency' + | 'employment attorney' + | 'employment center' + | 'employment consultant' + | 'energy supplier' + | 'engineering consultant' + | 'engineering school' + | 'english restaurant' + | 'entertainment agency' + | 'envelope supplier' + | 'environment office' + | 'environmental consultant' + | 'environmental engineer' + | 'environmental organization' + | 'episcopal church' + | 'equestrian club' + | 'equestrian facility' + | 'equestrian store' + | 'equipment exporter' + | 'equipment importer' + | 'equipment supplier' + | 'eritrean restaurant' + | 'erotic massage' + | 'escrow service' + | 'espresso bar' + | 'estate agent' + | 'estate appraiser' + | 'estate liquidator' + | 'ethiopian restaurant' + | 'ethnographic museum' + | 'european restaurant' + | 'evangelical church' + | 'evening school' + | 'event planner' + | 'event venue' + | 'excavating contractor' + | 'exhibition planner' + | 'eyebrow bar' + | 'eyelash salon' + | 'fabric store' + | 'fabric wholesaler' + | 'fabrication engineer' + | 'facial spa' + | 'falafel restaurant' + | 'family counselor' + | 'family restaurant' + | 'farm bureau' + | 'farm school' + | 'farm shop' + | "farmers' market" + | 'farrier service' + | 'fashion designer' + | 'fast food' + | 'fastener supplier' + | 'fax service' + | 'federal police' + | 'feed manufacturer' + | 'fence contractor' + | 'fencing salon' + | 'fencing school' + | 'ferrari dealer' + | 'ferris wheel' + | 'ferry service' + | 'fertility clinic' + | 'fertility physician' + | 'fertilizer supplier' + | 'festival hall' + | 'fiat dealer' + | 'fiberglass supplier' + | 'figurine shop' + | 'filipino restaurant' + | 'filtration plant' + | 'finance broker' + | 'financial advisor' + | 'financial audit' + | 'financial consultant' + | 'financial institution' + | 'financial planner' + | 'fingerprinting service' + | 'finnish restaurant' + | 'fire station' + | 'firearms academy' + | 'fireplace manufacturer' + | 'fireplace store' + | 'firewood supplier' + | 'fireworks store' + | 'fireworks supplier' + | 'fish farm' + | 'fish processing' + | 'fish restaurant' + | 'fish spa' + | 'fish store' + | 'fishing camp' + | 'fishing charter' + | 'fishing club' + | 'fishing pier' + | 'fishing pond' + | 'fishing store' + | 'fitness center' + | 'fitness centre' + | 'flag store' + | 'flamenco school' + | 'flamenco theater' + | 'flea market' + | 'flight school' + | 'floating market' + | 'flooring contractor' + | 'flooring store' + | 'floridian restaurant' + | 'flour mill' + | 'flower delivery' + | 'flower designer' + | 'flower market' + | 'fmcg manufacturer' + | 'fondue restaurant' + | 'food bank' + | 'food broker' + | 'food court' + | 'food manufacturer' + | 'food producer' + | 'food store' + | 'foot bath' + | 'foot care' + | 'football club' + | 'football field' + | 'footwear wholesaler' + | 'ford dealer' + | 'foreclosure service' + | 'foreign consulate' + | 'forensic consultant' + | 'forestry service' + | 'forklift dealer' + | 'fountain contractor' + | 'foursquare church' + | 'franconian restaurant' + | 'fraternal organization' + | 'free clinic' + | 'freestyle wrestling' + | 'french restaurant' + | 'friends church' + | 'fruit parlor' + | 'fruit wholesaler' + | 'fruits wholesaler' + | 'fuel pump' + | 'fuel supplier' + | 'fugu restaurant' + | 'funeral director' + | 'funeral home' + | 'fur manufacturer' + | 'fur service' + | 'furnace store' + | 'furniture accessories' + | 'furniture maker' + | 'furniture manufacturer' + | 'furniture store' + | 'furniture wholesaler' + | 'fusion restaurant' + | 'futon store' + | 'futsal court' + | 'galician restaurant' + | 'gambling house' + | 'gambling instructor' + | 'game store' + | 'garage builder' + | 'garbage dump' + | 'garden center' + | 'garment exporter' + | 'gas company' + | 'gas engineer' + | 'gas shop' + | 'gas station' + | 'gasket manufacturer' + | 'gastrointestinal surgeon' + | 'gated community' + | 'gay bar' + | 'gay sauna' + | 'gazebo builder' + | 'general contractor' + | 'general hospital' + | 'general practitioner' + | 'general store' + | 'genesis dealer' + | 'geological service' + | 'georgian restaurant' + | 'geotechnical engineer' + | 'german restaurant' + | 'ghost town' + | 'gift shop' + | 'gimbap restaurant' + | 'girl bar' + | "girls' hostel" + | 'glass blower' + | 'glass industry' + | 'glass manufacturer' + | 'glass merchant' + | 'glass shop' + | 'glassware manufacturer' + | 'glassware store' + | 'glassware wholesaler' + | 'gluten-free restaurant' + | 'gmc dealer' + | 'goan restaurant' + | 'gold dealer' + | 'goldfish store' + | 'golf club' + | 'golf course' + | 'golf instructor' + | 'golf shop' + | 'gospel church' + | 'government college' + | 'government hospital' + | 'government office' + | 'government school' + | 'gps supplier' + | 'graduate school' + | 'grain elevator' + | 'grammar school' + | 'granite supplier' + | 'graphic designer' + | 'gravel pit' + | 'gravel plant' + | 'greek restaurant' + | 'greyhound stadium' + | 'grill store' + | 'grocery store' + | 'group accommodation' + | 'group home' + | 'grow shop' + | 'guardia civil' + | 'guatemalan restaurant' + | 'guest house' + | 'guitar instructor' + | 'guitar store' + | 'gujarati restaurant' + | 'gun club' + | 'gun shop' + | 'gutter service' + | 'gymnasium school' + | 'gymnastics center' + | 'gymnastics club' + | 'gyro restaurant' + | 'gyudon restaurant' + | 'hair salon' + | 'haitian restaurant' + | 'hakka restaurant' + | 'halal restaurant' + | 'haleem restaurant' + | 'halfway house' + | 'ham shop' + | 'hamburger restaurant' + | 'hand surgeon' + | 'handbags shop' + | 'handball club' + | 'handball court' + | 'handicraft exporter' + | 'handicraft fair' + | 'handicraft museum' + | 'handicraft school' + | 'handicrafts wholesaler' + | 'hardware shop' + | 'hardware store' + | 'harley-davidson dealer' + | 'hat shop' + | 'haunted house' + | 'hawaiian restaurant' + | 'hawker stall' + | 'hay supplier' + | 'health consultant' + | 'health counselor' + | 'health resort' + | 'health spa' + | 'heart hospital' + | 'heating contractor' + | 'height works' + | 'helicopter charter' + | 'herb shop' + | 'heritage building' + | 'heritage museum' + | 'heritage preservation' + | 'heritage railroad' + | 'high school' + | 'highway patrol' + | 'hiking area' + | 'hiking guide' + | 'hindu priest' + | 'hindu temple' + | 'hispanic church' + | 'historical landmark' + | 'historical place' + | 'historical society' + | 'history museum' + | 'hoagie restaurant' + | 'hobby store' + | 'hockey club' + | 'hockey field' + | 'hockey rink' + | 'holding company' + | 'holiday apartment' + | 'holiday flat' + | 'holiday home' + | 'holiday park' + | 'home builder' + | 'home help' + | 'home inspector' + | 'homekill service' + | 'homeless service' + | 'homeless shelter' + | 'homeopathic pharmacy' + | "homeowners' association" + | 'homewares shop' + | 'honda dealer' + | 'honduran restaurant' + | 'honey farm' + | 'hookah bar' + | 'hookah store' + | 'horse breeder' + | 'horse trainer' + | 'horseshoe smith' + | 'horsestable studfarm' + | 'hose supplier' + | 'hospital department' + | 'host club' + | 'house sitter' + | 'housing association' + | 'housing authority' + | 'housing complex' + | 'housing cooperative' + | 'housing development' + | 'housing society' + | 'hungarian restaurant' + | 'hunting area' + | 'hunting club' + | 'hunting preserve' + | 'hunting store' + | 'hvac contractor' + | 'hyderabadi restaurant' + | 'hydraulic engineer' + | 'hypnotherapy service' + | 'hyundai dealer' + | 'ice supplier' + | 'icelandic restaurant' + | 'icse school' + | 'image consultant' + | 'imax theater' + | 'immigration attorney' + | 'impermeabilization service' + | 'incense supplier' + | 'incineration plant' + | 'indian restaurant' + | 'indian takeaway' + | 'indonesian restaurant' + | 'indoor cycling' + | 'indoor lodging' + | 'indoor playground' + | 'indoor snowcenter' + | 'industrial consultant' + | 'industrial engineer' + | 'industrial supermarket' + | 'infiniti dealer' + | 'information services' + | 'insolvency service' + | 'installation service' + | 'instrumentation engineer' + | 'insulation contractor' + | 'insulator supplier' + | 'insurance agency' + | 'insurance attorney' + | 'insurance broker' + | 'insurance company' + | 'interior decoration' + | 'interior decorator' + | 'interior designer' + | 'international airport' + | 'international school' + | 'internet cafe' + | 'internet shop' + | 'investment bank' + | 'investment company' + | 'investment service' + | 'irish pub' + | 'irish restaurant' + | 'iron works' + | 'israeli restaurant' + | 'isuzu dealer' + | 'italian restaurant' + | 'izakaya restaurant' + | 'jaguar dealer' + | 'jain temple' + | 'jamaican restaurant' + | 'janitorial service' + | 'japanese delicatessen' + | 'japanese inn' + | 'japanese restaurant' + | 'japanese steakhouse' + | 'javanese restaurant' + | 'jazz club' + | 'jeans shop' + | 'jeep dealer' + | 'jewellery store' + | 'jewelry appraiser' + | 'jewelry buyer' + | 'jewelry designer' + | 'jewelry engraver' + | 'jewelry exporter' + | 'jewelry manufacturer' + | 'jewelry store' + | 'jewish restaurant' + | 'judaica store' + | 'judicial auction' + | 'judicial scrivener' + | 'judo club' + | 'judo school' + | 'juice shop' + | 'jujitsu school' + | 'junior college' + | 'junk dealer' + | 'justice department' + | 'jute exporter' + | 'jute mill' + | 'kabaddi club' + | 'kaiseki restaurant' + | 'karaoke bar' + | 'karate club' + | 'karate school' + | 'karma dealer' + | 'karnataka restaurant' + | 'kashmiri restaurant' + | 'kazakhstani restaurant' + | 'kebab shop' + | 'kerala restaurant' + | 'kerosene supplier' + | 'kia dealer' + | 'kickboxing school' + | 'kimono store' + | 'kitchen remodeler' + | 'kitchen renovator' + | 'kite shop' + | 'knife store' + | 'knit shop' + | 'knitting instructor' + | 'knitwear manufacturer' + | 'kofta restaurant' + | 'konkani restaurant' + | 'korean church' + | 'korean restaurant' + | 'koshari restaurant' + | 'kosher restaurant' + | 'kushiyaki restaurant' + | 'labor union' + | 'ladder supplier' + | 'lamborghini dealer' + | 'lamination service' + | 'lancia dealer' + | 'land allotment' + | 'land surveyor' + | 'landscape architect' + | 'landscape designer' + | 'landscape gardener' + | 'language school' + | 'laotian restaurant' + | 'lasik surgeon' + | 'laundry service' + | 'law firm' + | 'law library' + | 'law school' + | 'lawyers association' + | 'leagues club' + | 'learning center' + | 'leasing service' + | 'leather exporter' + | 'leather wholesaler' + | 'lebanese restaurant' + | 'lechon restaurant' + | 'legal services' + | 'leisure centre' + | 'lesbian bar' + | 'lexus dealer' + | 'license bureau' + | 'life coach' + | 'lighting consultant' + | 'lighting contractor' + | 'lighting manufacturer' + | 'lighting store' + | 'ligurian restaurant' + | 'limousine service' + | 'linens store' + | 'lingerie manufacturer' + | 'lingerie store' + | 'lingerie wholesaler' + | 'linoleum store' + | 'liquor store' + | 'literacy program' + | 'lithuanian restaurant' + | 'livery company' + | 'livestock breeder' + | 'livestock dealer' + | 'livestock producer' + | 'loan agency' + | 'lock store' + | 'locks supplier' + | 'log cabins' + | 'logging contractor' + | 'logistics service' + | 'lombardian restaurant' + | 'loss adjuster' + | 'lottery retailer' + | 'lottery shop' + | 'love hotel' + | 'lpg conversion' + | 'luggage store' + | 'luggage wholesaler' + | 'lumber store' + | 'lunch restaurant' + | 'lutheran church' + | 'machine construction' + | 'machine shop' + | 'machine workshop' + | 'machining manufacturer' + | 'macrobiotic restaurant' + | 'madrilian restaurant' + | 'magazine store' + | 'magic store' + | 'mailbox supplier' + | 'mailing service' + | 'majorcan restaurant' + | 'make-up artist' + | 'malaysian restaurant' + | 'maltese restaurant' + | 'mammography service' + | 'manado restaurant' + | 'management school' + | 'mandarin restaurant' + | 'manor house' + | 'maori organization' + | 'map store' + | 'mapping service' + | 'marathi restaurant' + | 'marble contractor' + | 'marble supplier' + | 'marche restaurant' + | 'marine engineer' + | 'marine surveyor' + | 'maritime museum' + | 'market researcher' + | 'marketing agency' + | 'marketing consultant' + | 'marriage celebrant' + | 'maserati dealer' + | 'masonry contractor' + | 'massage parlor' + | 'massage school' + | 'massage service' + | 'massage spa' + | 'massage therapist' + | 'maternity hospital' + | 'maternity store' + | 'mathematics school' + | 'mattress store' + | 'mausoleum builder' + | 'maybach dealer' + | 'mazda dealer' + | 'mclaren dealer' + | 'meal delivery' + | 'meat packer' + | 'meat processor' + | 'meat wholesaler' + | 'mechanical contractor' + | 'mechanical engineer' + | 'mechanical plant' + | 'media company' + | 'media consultant' + | 'media house' + | 'mediation service' + | 'medical center' + | 'medical centre' + | 'medical clinic' + | 'medical examiner' + | 'medical group' + | 'medical laboratory' + | 'medical lawyer' + | 'medical office' + | 'medical school' + | 'medical spa' + | 'medicine exporter' + | 'meditation center' + | 'meditation instructor' + | 'mediterranean restaurant' + | 'mehandi class' + | 'mehndi designer' + | 'memorial estate' + | 'memorial park' + | "men's tailor" + | 'mennonite church' + | 'mens tailor' + | 'mercantile development' + | 'mercedes-benz dealer' + | 'messianic synagogue' + | 'metal fabricator' + | 'metal finisher' + | 'metal supplier' + | 'metal workshop' + | 'metallurgy company' + | 'metalware dealer' + | 'metalware producer' + | 'methodist church' + | 'mexican restaurant' + | 'mg dealer' + | 'middle school' + | 'military base' + | 'military board' + | 'military cemetery' + | 'military hospital' + | 'military school' + | 'military town' + | 'millwork shop' + | 'mini dealer' + | 'miniatures store' + | 'mining company' + | 'mining consultant' + | 'mining engineer' + | 'mining equipment' + | 'mirror shop' + | 'mitsubishi dealer' + | 'mobile caterer' + | 'model shop' + | 'modeling agency' + | 'modeling school' + | 'mold maker' + | 'molding supplier' + | 'momo restaurant' + | 'monogramming service' + | 'montessori school' + | 'monument maker' + | 'moped dealer' + | 'moravian church' + | 'moroccan restaurant' + | 'mortgage broker' + | 'mortgage lender' + | 'motorcycle dealer' + | 'motorcycle shop' + | 'motoring club' + | 'motorsports store' + | 'mountain cabin' + | 'mountain peak' + | 'mountaineering class' + | 'movie studio' + | 'movie theater' + | 'moving company' + | 'mri center' + | 'muffler shop' + | 'mughlai restaurant' + | 'mulch supplier' + | 'municipal guard' + | 'murtabak restaurant' + | 'music college' + | 'music conservatory' + | 'music instructor' + | 'music producer' + | 'music publisher' + | 'music school' + | 'music store' + | 'musical club' + | 'nail salon' + | 'nasi restaurant' + | 'national forest' + | 'national library' + | 'national museum' + | 'national park' + | 'national reserve' + | 'nature preserve' + | 'naturopathic practitioner' + | 'naval base' + | 'navarraise restaurant' + | 'neapolitan restaurant' + | 'needlework shop' + | 'neonatal physician' + | 'nepalese restaurant' + | 'netball club' + | 'news service' + | 'newspaper publisher' + | 'nicaraguan restaurant' + | 'night club' + | 'night market' + | 'nissan dealer' + | 'non-denominational church' + | 'non-governmental organization' + | 'non-profit organization' + | 'noodle shop' + | 'norwegian restaurant' + | 'notaries association' + | 'notary public' + | 'notions store' + | 'novelties wholesaler' + | 'novelty store' + | 'nudist club' + | 'nudist park' + | 'nurse practitioner' + | 'nursery school' + | 'nursing agency' + | 'nursing association' + | 'nursing home' + | 'nursing school' + | 'nut store' + | 'nyonya restaurant' + | 'oaxacan restaurant' + | 'observation deck' + | 'occupational therapist' + | 'oden restaurant' + | 'odia restaurant' + | 'oil refinery' + | 'okonomiyaki restaurant' + | 'oldsmobile dealer' + | 'opel dealer' + | 'open university' + | 'opera company' + | 'opera house' + | 'ophthalmology clinic' + | 'optical wholesaler' + | 'oral surgeon' + | 'orchid farm' + | 'orchid grower' + | 'organic farm' + | 'organic restaurant' + | 'organic shop' + | 'orthodox church' + | 'orthodox synagogue' + | 'orthopedic clinic' + | 'orthopedic surgeon' + | 'otolaryngology clinic' + | 'outdoor bath' + | 'outerwear store' + | 'outlet mall' + | 'outlet store' + | 'oyster supplier' + | 'paan shop' + | 'package locker' + | 'packaging company' + | 'padang restaurant' + | 'padel club' + | 'padel court' + | 'paint manufacturer' + | 'paint store' + | 'paintball center' + | 'paintball store' + | 'painting lessons' + | 'painting studio' + | 'paintings store' + | 'paisa restaurant' + | 'pakistani restaurant' + | 'palatine restaurant' + | 'pallet supplier' + | 'pan-asian restaurant' + | 'pancake restaurant' + | 'panipuri shop' + | 'paper distributor' + | 'paper exporter' + | 'paper mill' + | 'paper store' + | 'paraguayan restaurant' + | 'parking garage' + | 'parking grounds' + | 'parking lot' + | 'parkour spot' + | 'parochial school' + | 'parsi restaurant' + | 'parsi temple' + | 'party planner' + | 'party store' + | 'passport agent' + | 'passport office' + | 'pasta shop' + | 'pastry shop' + | 'patent attorney' + | 'patent office' + | 'paving contractor' + | 'pawn shop' + | 'payroll service' + | 'pedestrian zone' + | 'pediatric cardiologist' + | 'pediatric clinic' + | 'pediatric dentist' + | 'pediatric dermatologist' + | 'pediatric endocrinologist' + | 'pediatric gastroenterologist' + | 'pediatric hematologist' + | 'pediatric nephrologist' + | 'pediatric neurologist' + | 'pediatric oncologist' + | 'pediatric ophthalmologist' + | 'pediatric pulmonologist' + | 'pediatric rheumatologist' + | 'pediatric surgeon' + | 'pediatric urologist' + | 'pempek restaurant' + | 'pen store' + | 'pension office' + | 'pentecostal church' + | 'perfume store' + | 'perinatal center' + | 'persian restaurant' + | 'personal trainer' + | 'peruvian restaurant' + | 'pet cemetery' + | 'pet groomer' + | 'pet shop' + | 'pet sitter' + | 'pet store' + | 'pet trainer' + | 'petrol station' + | 'peugeot dealer' + | 'pharmaceutical company' + | 'pharmaceutical lab' + | 'philharmonic hall' + | 'pho restaurant' + | 'photo agency' + | 'photo booth' + | 'photo lab' + | 'photo shop' + | 'photography class' + | 'photography school' + | 'photography service' + | 'photography studio' + | 'physical therapist' + | 'physician assistant' + | 'physiotherapy center' + | 'piadina restaurant' + | 'piano bar' + | 'piano instructor' + | 'piano maker' + | 'piano store' + | 'pickleball court' + | 'picnic ground' + | 'pie shop' + | 'piedmontese restaurant' + | 'pig farm' + | 'pilaf restaurant' + | 'pilates studio' + | 'pilgrim hostel' + | 'pipe supplier' + | 'pizza delivery' + | 'pizza restaurant' + | 'pizza takeaway' + | 'pizza takeout' + | 'plant nursery' + | 'plastic surgeon' + | 'plastic wholesaler' + | 'plating service' + | 'plywood supplier' + | 'poke bar' + | 'police academy' + | 'police department' + | 'polish restaurant' + | 'polo club' + | 'polygraph service' + | 'polymer supplier' + | 'polynesian restaurant' + | 'polytechnic institute' + | 'pond contractor' + | 'pontiac dealer' + | 'pony club' + | 'pool hall' + | 'popcorn store' + | 'porridge restaurant' + | 'porsche dealer' + | 'port authority' + | 'portrait studio' + | 'portuguese restaurant' + | 'post office' + | 'postal code' + | 'poster store' + | 'pottery classes' + | 'pottery manufacturer' + | 'pottery store' + | 'poultry farm' + | 'poultry store' + | 'power station' + | 'pozole restaurant' + | 'prawn fishing' + | 'precision engineer' + | 'preparatory school' + | 'presbyterian church' + | 'press advisory' + | 'pretzel store' + | 'primary school' + | 'print shop' + | 'private college' + | 'private hospital' + | 'private investigator' + | 'private tutor' + | 'private university' + | 'probation office' + | 'process server' + | 'produce market' + | 'produce wholesaler' + | 'professional association' + | 'professional organizer' + | 'propane supplier' + | 'propeller shop' + | 'property consultant' + | 'property developer' + | 'property investment' + | 'property maintenance' + | 'protected area' + | 'protestant church' + | 'provence restaurant' + | 'psychiatric hospital' + | 'psychomotor therapist' + | 'psychopedagogy clinic' + | 'public bath' + | 'public bathroom' + | 'public beach' + | 'public housing' + | 'public library' + | 'public sauna' + | 'public university' + | 'pueblan restaurant' + | 'pump supplier' + | 'pumpkin patch' + | 'punjabi restaurant' + | 'puppet theater' + | 'quaker church' + | 'quantity surveyor' + | 'quilt shop' + | 'raclette restaurant' + | 'racquetball club' + | 'radiator shop' + | 'radio broadcaster' + | 'rail museum' + | 'railing contractor' + | 'railroad company' + | 'railroad contractor' + | 'railway services' + | 'rajasthani restaurant' + | 'ram dealer' + | 'ramen restaurant' + | 'real estate' + | 'record company' + | 'record store' + | 'recording studio' + | 'recreation center' + | 'recycling center' + | 'reenactment site' + | 'reform synagogue' + | 'reformed church' + | 'refrigerator store' + | 'refugee camp' + | 'regional airport' + | 'regional council' + | 'registration office' + | 'registry office' + | 'rehabilitation center' + | 'rehearsal studio' + | 'reiki therapist' + | 'religious destination' + | 'religious institution' + | 'religious lodging' + | 'religious organization' + | 'religious school' + | 'renault dealer' + | 'renovation contractor' + | 'repair service' + | 'reptile store' + | 'research engineer' + | 'research foundation' + | 'research institute' + | 'residential building' + | 'residential college' + | 'residents association' + | 'resort hotel' + | 'rest stop' + | 'resume service' + | 'retirement community' + | 'retirement home' + | 'retreat center' + | 'rice mill' + | 'rice restaurant' + | 'rice shop' + | 'rice wholesaler' + | 'river port' + | 'road cycling' + | 'rock climbing' + | 'rock shop' + | 'roller coaster' + | 'roman restaurant' + | 'romanian restaurant' + | 'roofing contractor' + | 'roofing service' + | 'rowing area' + | 'rowing club' + | 'rsl club' + | 'rug store' + | 'rugby club' + | 'rugby field' + | 'rugby store' + | 'running store' + | 'russian restaurant' + | 'rv dealer' + | 'rv park' + | 'saab dealer' + | 'sailing club' + | 'sailing school' + | 'sake brewery' + | 'salad shop' + | 'salsa bar' + | 'salsa classes' + | 'salvadoran restaurant' + | 'salvage dealer' + | 'salvage yard' + | 'samba school' + | 'sambo school' + | 'sand plant' + | 'sandblasting service' + | 'sandwich shop' + | 'sanitary inspection' + | 'sanitation service' + | 'sardinian restaurant' + | 'saree shop' + | 'sashimi restaurant' + | 'satay restaurant' + | 'saturn dealer' + | 'sauna club' + | 'sauna store' + | 'savings bank' + | 'saw mill' + | 'scale supplier' + | 'scandinavian restaurant' + | 'scenic spot' + | 'scenography company' + | 'school cafeteria' + | 'school center' + | 'school house' + | 'science museum' + | 'scottish restaurant' + | 'scout hall' + | 'scout home' + | 'scrapbooking store' + | 'screen printer' + | 'screen store' + | 'screw supplier' + | 'scuba instructor' + | 'sculpture museum' + | 'seafood farm' + | 'seafood market' + | 'seafood restaurant' + | 'seafood wholesaler' + | 'seal shop' + | 'seaplane base' + | 'seat dealer' + | 'seblak restaurant' + | 'secondary school' + | 'security service' + | 'seed supplier' + | 'self-catering accommodation' + | 'self-storage facility' + | 'serbian restaurant' + | 'service establishment' + | 'serviced accommodation' + | 'serviced apartment' + | 'sewing company' + | 'sewing shop' + | 'seychelles restaurant' + | 'sfiha restaurant' + | 'shanghainese restaurant' + | 'sharpening service' + | 'shawarma restaurant' + | 'shed builder' + | 'sheep shearer' + | 'sheltered housing' + | 'shelving store' + | 'shinto shrine' + | 'shipping company' + | 'shipping service' + | 'shochu brewery' + | 'shoe factory' + | 'shoe shop' + | 'shoe store' + | 'shogi lesson' + | 'shooting range' + | 'shopping centre' + | 'shopping mall' + | 'shredding service' + | 'shrimp farm' + | 'sichuan restaurant' + | 'sicilian restaurant' + | 'siding contractor' + | 'sign shop' + | 'signwriting service' + | 'silk store' + | 'singaporean restaurant' + | 'singles organization' + | 'skate shop' + | 'skateboard shop' + | 'skating instructor' + | 'ski club' + | 'ski resort' + | 'ski school' + | 'ski shop' + | 'skittle club' + | 'skoda dealer' + | 'skydiving center' + | 'skylight contractor' + | 'sleep clinic' + | 'smart dealer' + | 'smart shop' + | 'smoke shop' + | 'snack bar' + | 'snowboard shop' + | 'snowmobile dealer' + | 'soccer club' + | 'soccer field' + | 'soccer practice' + | 'soccer store' + | 'social club' + | 'social worker' + | 'sod supplier' + | 'sofa store' + | 'softball club' + | 'softball field' + | 'software company' + | 'soondae restaurant' + | 'soto restaurant' + | 'soup kitchen' + | 'soup restaurant' + | 'soup shop' + | 'souvenir manufacturer' + | 'souvenir store' + | 'spa garden' + | 'spanish restaurant' + | 'special educator' + | 'specialized clinic' + | 'specialized hospital' + | 'speech pathologist' + | 'sperm bank' + | 'spice exporter' + | 'spice store' + | 'spice wholesaler' + | 'spices exporter' + | 'spiritist center' + | 'sports bar' + | 'sports club' + | 'sports complex' + | 'sports school' + | 'sportswear store' + | 'sportwear manufacturer' + | 'spring supplier' + | 'squash club' + | 'squash court' + | 'stair contractor' + | 'stamp shop' + | 'stand bar' + | 'state archive' + | 'state park' + | 'state parliament' + | 'state police' + | 'stationery manufacturer' + | 'stationery store' + | 'stationery wholesaler' + | 'std clinic' + | 'steak house' + | 'steamboat restaurant' + | 'steel distributor' + | 'steel erector' + | 'steel fabricator' + | 'sticker manufacturer' + | 'stitching class' + | 'stock broker' + | 'stone carving' + | 'stone cutter' + | 'stone supplier' + | 'storage facility' + | 'structural engineer' + | 'stucco contractor' + | 'student dormitory' + | 'student union' + | 'studying center' + | 'subaru dealer' + | 'subway station' + | 'sugar factory' + | 'sugar shack' + | 'sukiyaki restaurant' + | 'sunblind supplier' + | 'sundae restaurant' + | 'sundanese restaurant' + | 'sunglasses store' + | 'sunroom contractor' + | 'superannuation consultant' + | 'superfund site' + | 'support group' + | 'surf school' + | 'surf shop' + | 'surgical center' + | 'surgical oncologist' + | 'surinamese restaurant' + | 'surplus store' + | 'sushi restaurant' + | 'sushi takeaway' + | 'suzuki dealer' + | 'swabian restaurant' + | 'swedish restaurant' + | 'swim club' + | 'swimming basin' + | 'swimming competition' + | 'swimming facility' + | 'swimming instructor' + | 'swimming lake' + | 'swimming pool' + | 'swimming school' + | 'swimwear store' + | 'swiss restaurant' + | 'syrian restaurant' + | 't-shirt store' + | 'tabascan restaurant' + | 'tacaca restaurant' + | 'tack shop' + | 'taco restaurant' + | 'taekwondo school' + | 'taiwanese restaurant' + | 'takeout restaurant' + | 'takoyaki restaurant' + | 'talent agency' + | 'tamale shop' + | 'tanning salon' + | 'taoist temple' + | 'tapas bar' + | 'tapas restaurant' + | 'tatami store' + | 'tattoo artist' + | 'tattoo shop' + | 'tax assessor' + | 'tax attorney' + | 'tax consultant' + | 'tax department' + | 'tax preparation' + | 'taxi service' + | 'taxi stand' + | 'taxicab stand' + | 'tb clinic' + | 'tea exporter' + | 'tea house' + | 'tea manufacturer' + | 'tea store' + | 'tea wholesaler' + | 'teachers college' + | 'technical school' + | 'technical university' + | 'technology museum' + | 'technology park' + | 'tegal restaurant' + | 'telecommunication school' + | 'telecommunications contractor' + | 'telecommunications engineer' + | 'telemarketing service' + | 'telephone company' + | 'telephone exchange' + | 'telescope store' + | 'television station' + | 'temaki restaurant' + | 'temp agency' + | 'tempura restaurant' + | 'tenant ownership' + | 'tennis club' + | 'tennis court' + | 'tennis instructor' + | 'tennis store' + | 'teppanyaki restaurant' + | 'tesla showroom' + | 'tex-mex restaurant' + | 'textile engineer' + | 'textile exporter' + | 'textile merchant' + | 'textile mill' + | 'thai restaurant' + | 'theater company' + | 'theater production' + | 'theme park' + | 'thermal baths' + | 'thread supplier' + | 'thrift store' + | 'thuringian restaurant' + | 'tibetan restaurant' + | 'tiffin center' + | 'tiki bar' + | 'tile contractor' + | 'tile manufacturer' + | 'tile store' + | 'timeshare agency' + | 'tire service' + | 'tire shop' + | 'title company' + | 'toast restaurant' + | 'tobacco shop' + | 'tobacco supplier' + | 'tofu restaurant' + | 'tofu shop' + | 'toiletries store' + | 'toll station' + | 'tongue restaurant' + | 'tonkatsu restaurant' + | 'tool manufacturer' + | 'tool store' + | 'tool wholesaler' + | 'topography company' + | 'topsoil supplier' + | 'tortilla shop' + | 'tour agency' + | 'tour operator' + | 'tourist attraction' + | 'towing service' + | 'townhouse complex' + | 'toy library' + | 'toy manufacturer' + | 'toy museum' + | 'toy store' + | 'toyota dealer' + | 'tractor dealer' + | 'trade school' + | 'trading company' + | 'traditional market' + | 'traditional teahouse' + | 'traffic officer' + | 'trailer dealer' + | 'trailer manufacturer' + | 'train depot' + | 'train station' + | 'train yard' + | 'training center' + | 'training centre' + | 'training consultant' + | 'training provider' + | 'tram stop' + | 'transcription service' + | 'transit depot' + | 'transit station' + | 'transit stop' + | 'translation service' + | 'transmission shop' + | 'transplant surgeon' + | 'transport hub' + | 'transportation service' + | 'travel agency' + | 'travel agent' + | 'travel clinic' + | 'travel lounge' + | 'tree farm' + | 'tree service' + | 'trial attorney' + | 'tribal headquarters' + | 'trolleybus stop' + | 'trophy shop' + | 'truck dealer' + | 'truck farmer' + | 'truck stop' + | 'trucking company' + | 'truss manufacturer' + | 'trust bank' + | 'tunisian restaurant' + | 'turf supplier' + | 'turkish restaurant' + | 'turkmen restaurant' + | 'tuscan restaurant' + | 'tutoring service' + | 'tuxedo shop' + | 'typewriter supplier' + | 'typing service' + | 'tyre manufacturer' + | 'ukrainian restaurant' + | 'unagi restaurant' + | 'underwear store' + | 'unemployment office' + | 'uniform store' + | 'unity church' + | 'university department' + | 'university hospital' + | 'university library' + | 'upholstery shop' + | 'urology clinic' + | 'uruguayan restaurant' + | 'utility contractor' + | 'valencian restaurant' + | 'vaporizer store' + | 'variety store' + | 'vascular surgeon' + | 'vastu consultant' + | 'vegan restaurant' + | 'vegetable wholesaler' + | 'vegetarian restaurant' + | 'vehicle exporter' + | 'vehicle repair' + | 'venetian restaurant' + | 'venezuelan restaurant' + | 'veterans center' + | 'veterans hospital' + | 'veterans organization' + | 'veterinary care' + | 'veterinary pharmacy' + | 'video arcade' + | 'video karaoke' + | 'video store' + | 'vietnamese restaurant' + | 'village hall' + | 'vineyard church' + | 'violin shop' + | 'visitor center' + | 'vocal instructor' + | 'vocational school' + | 'volkswagen dealer' + | 'volleyball club' + | 'volleyball court' + | 'volleyball instructor' + | 'volunteer organization' + | 'volvo dealer' + | 'waldorf kindergarten' + | 'waldorf school' + | 'walk-in clinic' + | 'wallpaper installer' + | 'wallpaper store' + | 'war museum' + | 'warehouse club' + | 'warehouse store' + | 'watch manufacturer' + | 'watch store' + | 'water mill' + | 'water park' + | 'water works' + | 'waterbed store' + | 'waterproofing service' + | 'wax museum' + | 'wax supplier' + | 'weaving mill' + | 'web designer' + | 'website designer' + | 'wedding bakery' + | 'wedding buffet' + | 'wedding chapel' + | 'wedding photographer' + | 'wedding planner' + | 'wedding service' + | 'wedding store' + | 'wedding venue' + | 'weigh station' + | 'weightlifting area' + | 'wellness center' + | 'wellness hotel' + | 'wellness program' + | 'welsh restaurant' + | 'wesleyan church' + | 'western restaurant' + | 'wheel store' + | 'wheelchair store' + | 'wholesale bakery' + | 'wholesale drugstore' + | 'wholesale florist' + | 'wholesale grocer' + | 'wholesale jeweler' + | 'wholesale market' + | 'wi-fi spot' + | 'wicker store' + | 'wig shop' + | 'wildlife park' + | 'wildlife refuge' + | 'wind farm' + | 'window supplier' + | 'windsurfing store' + | 'wine bar' + | 'wine cellar' + | 'wine club' + | 'wine store' + | 'wok restaurant' + | 'wood supplier' + | 'wool store' + | 'wrestling school' + | 'x-ray lab' + | 'yacht broker' + | 'yacht club' + | 'yakiniku restaurant' + | 'yakisoba restaurant' + | 'yakitori restaurant' + | 'yarn store' + | 'yemeni restaurant' + | 'yoga instructor' + | 'yoga studio' + | 'youth center' + | 'youth club' + | 'youth hostel' + | 'youth organization' + | 'yucatan restaurant' + | '3d printing service' + | 'aboriginal art gallery' + | 'abundant life church' + | 'acrobatic diving pool' + | 'addiction treatment center' + | 'adult dvd store' + | 'adult education school' + | 'adult entertainment club' + | 'adult entertainment store' + | 'adventure sports center' + | 'aerated drinks supplier' + | 'aerial sports center' + | 'aero dance class' + | 'african goods store' + | 'after school program' + | 'agricultural high school' + | 'agricultural machinery manufacturer' + | 'agricultural product wholesaler' + | 'air compressor supplier' + | 'air conditioning contractor' + | 'air conditioning store' + | 'air filter supplier' + | 'air force base' + | 'airbrushing supply store' + | 'aircraft maintenance company' + | 'aircraft rental service' + | 'aircraft supply store' + | 'airline ticket agency' + | 'airport shuttle service' + | 'alcohol retail monopoly' + | 'alcoholic beverage wholesaler' + | 'alcoholism treatment program' + | 'alfa romeo dealer' + | 'alternative fuel station' + | 'alternative medicine clinic' + | 'alternative medicine practitioner' + | 'aluminum frames supplier' + | 'american grocery store' + | 'amish furniture store' + | 'amusement machine supplier' + | 'amusement park ride' + | 'amusement ride supplier' + | 'angler fish restaurant' + | 'animal control service' + | 'animal feed store' + | 'animal protection organization' + | 'animal rescue service' + | 'animal watering hole' + | 'antique furniture store' + | 'apartment rental agency' + | 'appliance parts supplier' + | 'appliance rental service' + | 'appliance repair service' + | 'appliances customer service' + | 'architectural salvage store' + | 'armed forces association' + | 'aromatherapy supply store' + | 'art restoration service' + | 'art supply store' + | 'artificial plant supplier' + | 'asbestos testing service' + | 'asian fusion restaurant' + | 'asian grocery store' + | 'asphalt mixing plant' + | 'assisted living facility' + | 'association / organization' + | 'aston martin dealer' + | 'attorney referral service' + | 'atv rental service' + | 'atv repair shop' + | 'audio visual consultant' + | 'australian goods store' + | 'auto accessories wholesaler' + | 'auto body shop' + | 'auto bodywork mechanic' + | 'auto chemistry shop' + | 'auto electrical service' + | 'auto glass shop' + | 'auto insurance agency' + | 'auto machine shop' + | 'auto parts manufacturer' + | 'auto parts market' + | 'auto parts store' + | 'auto repair shop' + | 'auto restoration service' + | 'auto rickshaw stand' + | 'auto spring shop' + | 'auto sunroof shop' + | 'auto tag agency' + | 'automobile storage facility' + | 'aviation training institute' + | 'ayam penyet restaurant' + | 'baby clothing store' + | 'baby swimming school' + | 'bail bonds service' + | 'baking supply store' + | 'ballroom dance instructor' + | 'banking and finance' + | 'bar stool supplier' + | 'barber supply store' + | 'baseball goods store' + | 'basketball court contractor' + | 'bathroom supply store' + | 'batik clothing store' + | 'batting cage center' + | 'beach cleaning service' + | 'beach clothing store' + | 'beach entertainment shop' + | 'beach volleyball club' + | 'beach volleyball court' + | 'beauty product supplier' + | 'beauty products wholesaler' + | 'beauty supply store' + | 'bed & breakfast' + | 'bedroom furniture store' + | 'bee relocation service' + | 'bicycle rental service' + | 'bicycle repair shop' + | 'bike sharing station' + | 'bikram yoga studio' + | 'billiards supply store' + | 'bird control service' + | 'bird watching area' + | 'birth certificate service' + | 'birth control center' + | 'blast cleaning service' + | 'blood donation center' + | 'blood testing service' + | 'bmw motorcycle dealer' + | 'board game club' + | 'board of education' + | 'boat accessories supplier' + | 'boat cleaning service' + | 'boat cover supplier' + | 'boat detailing service' + | 'boat rental service' + | 'boat repair shop' + | 'boat storage facility' + | 'boat tour agency' + | 'boat trailer dealer' + | 'bocce ball court' + | 'body piercing shop' + | 'body shaping class' + | 'bonsai plant supplier' + | 'boot repair shop' + | 'border crossing station' + | 'bottled water supplier' + | 'bouncy castle hire' + | 'bowling supply shop' + | 'box lunch supplier' + | "boys' high school" + | 'bpo placement agency' + | 'brewing supply store' + | 'bubble tea store' + | 'buddhist supplies store' + | 'building materials market' + | 'building materials store' + | 'building materials supplier' + | 'building restoration service' + | 'bungee jumping center' + | 'burglar alarm store' + | 'bus ticket agency' + | 'bus tour agency' + | 'business administration service' + | 'business banking service' + | 'business development service' + | 'business management consultant' + | 'business networking company' + | 'butane gas supplier' + | 'butcher shop deli' + | 'cabin rental agency' + | 'calvary chapel church' + | 'camera repair shop' + | 'camper shell supplier' + | 'cancer treatment center' + | 'cane furniture store' + | 'cape verdean restaurant' + | 'car accessories store' + | 'car alarm supplier' + | 'car battery store' + | 'car detailing service' + | 'car inspection station' + | 'car leasing service' + | 'car rental agency' + | 'car sharing location' + | 'car stereo store' + | 'career guidance service' + | 'carpet cleaning service' + | 'carriage ride service' + | 'cat boarding service' + | 'cell phone store' + | 'central american restaurant' + | 'central european restaurant' + | 'central heating service' + | 'central javanese restaurant' + | 'certified public accountant' + | 'chamber of agriculture' + | 'chamber of commerce' + | 'chamber of handicrafts' + | 'champon noodle restaurant' + | 'check cashing service' + | 'chicken wings restaurant' + | 'child care agency' + | "children's amusement center" + | "children's clothing store" + | "children's furniture store" + | "children's health service" + | "children's party buffet" + | "children's party service" + | 'chinese language instructor' + | 'chinese language school' + | 'chinese medicine clinic' + | 'chinese medicine store' + | 'chinese noodle restaurant' + | 'chinese tea house' + | 'christian book store' + | 'christmas tree farm' + | 'church of christ' + | 'church supply store' + | 'cig kofte restaurant' + | 'cinema equipment supplier' + | 'citizen information bureau' + | 'city district office' + | 'city employment department' + | 'city government office' + | 'city tax office' + | 'civil engineering company' + | 'civil examinations academy' + | 'civil law attorney' + | 'cleaning products supplier' + | 'clock repair service' + | 'closed circuit television' + | 'clothing alteration service' + | 'coast guard station' + | 'coffee machine supplier' + | 'coffee vending machine' + | 'coin operated locker' + | 'cold cut store' + | 'cold noodle restaurant' + | 'cold storage facility' + | 'college of agriculture' + | 'comic book store' + | 'commercial refrigerator supplier' + | 'commissioner for oaths' + | 'community health center' + | 'community health centre' + | 'comprehensive secondary school' + | 'computer accessories store' + | 'computer desk store' + | 'computer hardware manufacturer' + | 'computer networking service' + | 'computer repair service' + | 'computer security service' + | 'computer software store' + | 'computer training school' + | 'concrete product supplier' + | 'condominium rental agency' + | 'conservatory of music' + | 'construction equipment supplier' + | 'construction machine dealer' + | 'construction material wholesaler' + | 'consumer advice center' + | 'contact lenses supplier' + | 'contemporary louisiana restaurant' + | 'convention information bureau' + | 'copier repair service' + | 'copying supply store' + | 'corporate gift supplier' + | 'cosmetic products manufacturer' + | 'cost accounting service' + | 'costa rican restaurant' + | 'costume jewelry shop' + | 'costume rental service' + | 'country food restaurant' + | 'county government office' + | 'court executive officer' + | 'crane rental agency' + | 'creative cuisine restaurant' + | 'credit counseling service' + | 'credit reporting agency' + | 'crime victim service' + | 'criminal justice attorney' + | 'crushed stone supplier' + | 'cured ham bar' + | 'cured ham store' + | 'cured ham warehouse' + | 'currency exchange service' + | 'custom home builder' + | 'custom label printer' + | 'custom t-shirt store' + | 'cycle rickshaw stand' + | 'dart supply store' + | 'data entry service' + | 'data recovery service' + | 'database management company' + | 'day care center' + | 'debris removal service' + | 'debt collection agency' + | 'delivery chinese restaurant' + | 'dental implants periodontist' + | 'dental implants provider' + | 'dental insurance agency' + | 'dental supply store' + | 'denture care center' + | 'department of housing' + | 'department of transportation' + | 'designer clothing store' + | 'desktop publishing service' + | 'diabetes equipment supplier' + | 'diesel engine dealer' + | 'diesel fuel supplier' + | 'digital printing service' + | 'dim sum restaurant' + | 'direct mail advertising' + | 'disability equipment supplier' + | 'disc golf course' + | 'display stand manufacturer' + | 'disposable tableware supplier' + | 'distance learning center' + | 'district government office' + | 'dj supply store' + | 'dogsled ride service' + | 'doll restoration service' + | 'doner kebab restaurant' + | 'double glazing installer' + | 'drafting equipment supplier' + | 'dried flower shop' + | 'dried seafood store' + | 'drilling equipment supplier' + | 'drinking water fountain' + | "driver's license office" + | 'driving test center' + | 'drug testing service' + | 'dry fruit store' + | 'dry ice supplier' + | 'dry wall contractor' + | 'ds automobiles dealer' + | 'dump truck dealer' + | 'dumpster rental service' + | 'duty free store' + | 'e commerce agency' + | 'ear piercing service' + | 'earth works company' + | 'east african restaurant' + | 'east javanese restaurant' + | 'eastern european restaurant' + | 'eastern orthodox church' + | 'economic development agency' + | 'educational supply store' + | 'educational testing service' + | 'eftpos equipment supplier' + | 'elder law attorney' + | 'electric bicycle store' + | 'electric generator shop' + | 'electric motor store' + | 'electric motorcycle dealer' + | 'electric utility company' + | 'electrical appliance wholesaler' + | 'electrical equipment supplier' + | 'electrical installation service' + | 'electrical products wholesaler' + | 'electrical repair shop' + | 'electrical supply store' + | 'electronic engineering service' + | 'electronic parts supplier' + | 'electronics accessories wholesaler' + | 'electronics hire shop' + | 'electronics repair shop' + | 'electronics vending machine' + | 'emergency care physician' + | 'emergency care service' + | 'emergency dental service' + | 'emergency locksmith service' + | 'emergency training school' + | 'emergency veterinarian service' + | 'engine rebuilding service' + | 'english language camp' + | 'english language school' + | 'environmental health service' + | 'environmental protection organization' + | 'equipment rental agency' + | 'escape room center' + | 'estate planning attorney' + | 'event management company' + | 'event planning service' + | 'event technology service' + | 'event ticket seller' + | 'executive search firm' + | 'exercise equipment store' + | 'extended stay hotel' + | 'eye care center' + | 'fabric product manufacturer' + | 'factory equipment supplier' + | 'faculty of law' + | 'faculty of pharmacy' + | 'faculty of science' + | 'family law attorney' + | 'family planning center' + | 'family planning counselor' + | 'family practice physician' + | 'family service center' + | 'farm equipment supplier' + | 'farm household tour' + | 'fashion accessories shop' + | 'fashion accessories store' + | 'fashion design school' + | 'fast food restaurant' + | 'federal credit union' + | 'federal government office' + | 'felt boots store' + | 'fence supply store' + | 'feng shui consultant' + | 'feng shui shop' + | 'fiberglass repair service' + | 'filipino grocery store' + | 'film production company' + | 'fine dining restaurant' + | 'finishing materials supplier' + | 'fire alarm supplier' + | 'fire fighters academy' + | 'fire protection consultant' + | 'fire protection service' + | 'first aid station' + | 'fitness equipment wholesaler' + | 'fitted furniture supplier' + | 'flamenco dance store' + | 'floor refinishing service' + | 'fmcg goods wholesaler' + | 'foam rubber producer' + | 'foam rubber supplier' + | 'folk high school' + | 'food and drink' + | 'food machinery supplier' + | 'food manufacturing supply' + | 'food processing company' + | 'food processing equipment' + | 'food products supplier' + | 'food seasoning manufacturer' + | 'foot massage parlor' + | 'foreign trade consultant' + | 'foreman builders association' + | 'forklift rental service' + | 'formal wear store' + | 'fortune telling services' + | 'foster care service' + | 'free parking lot' + | 'freight forwarding service' + | 'french language school' + | 'french steakhouse restaurant' + | 'fresh food market' + | 'fried chicken takeaway' + | 'frozen dessert supplier' + | 'frozen food manufacturer' + | 'frozen food store' + | 'frozen yogurt shop' + | 'full gospel church' + | 'function room facility' + | 'funeral celebrant service' + | 'fur coat shop' + | 'furnace parts supplier' + | 'furnace repair service' + | 'furnished apartment building' + | 'furniture accessories supplier' + | 'furniture rental service' + | 'furniture repair shop' + | 'garage door supplier' + | 'garbage collection service' + | 'garden building supplier' + | 'garden machinery supplier' + | 'gas cylinders supplier' + | 'gas installation service' + | 'gas logs supplier' + | 'gay night club' + | 'general education school' + | 'general practice attorney' + | 'geological research company' + | 'german language school' + | 'gift basket store' + | 'gift wrap store' + | "girls' high school" + | 'glass block supplier' + | 'glass cutting service' + | 'glass etching service' + | 'glass repair service' + | 'glasses repair service' + | 'gold mining company' + | 'golf cart dealer' + | 'golf course builder' + | 'golf driving range' + | 'gourmet grocery store' + | 'government economic program' + | 'government ration shop' + | 'graffiti removal service' + | 'greek orthodox church' + | 'green energy supplier' + | 'greeting card shop' + | 'grocery delivery service' + | 'gutter cleaning service' + | 'gypsum product supplier' + | 'hair extension technician' + | 'hair extensions supplier' + | 'hair removal service' + | 'hair replacement service' + | 'hair transplantation clinic' + | 'handicapped transportation service' + | 'hang gliding center' + | 'haute french restaurant' + | 'hawaiian goods store' + | 'head start center' + | 'health food restaurant' + | 'health food store' + | 'health insurance agency' + | 'hearing aid store' + | 'heating equipment supplier' + | 'heating oil supplier' + | 'helicopter tour agency' + | 'helium gas supplier' + | 'herbal medicine store' + | 'high ropes course' + | 'higher secondary school' + | 'historical place museum' + | 'hiv testing center' + | 'hockey supply store' + | 'holiday apartment rental' + | 'holistic medicine practitioner' + | 'home audio store' + | 'home automation company' + | 'home cinema installation' + | 'home furniture shop' + | 'home goods store' + | 'home improvement store' + | 'home insurance agency' + | 'home staging service' + | 'home theater store' + | 'horse boarding stable' + | 'horse rental service' + | 'horse riding field' + | 'horse riding school' + | 'horse trailer dealer' + | 'horseback riding service' + | 'hospitality high school' + | 'hot bedstone spa' + | 'hot dog restaurant' + | 'hot dog stand' + | 'hot pot restaurant' + | 'hot tub store' + | 'hotel management school' + | 'hotel supply store' + | 'house cleaning service' + | 'house clearance service' + | 'house sitter agency' + | 'houseboat rental service' + | 'household chemicals supplier' + | 'household goods wholesaler' + | 'housing utility company' + | 'hua gong shop' + | 'hub cap supplier' + | 'human resource consulting' + | 'hydraulic equipment supplier' + | 'hydraulic repair service' + | 'hydroelectric power plant' + | 'hydroponics equipment supplier' + | 'hygiene articles wholesaler' + | 'hyperbaric medicine physician' + | 'ice cream shop' + | 'ice hockey club' + | 'ice skating club' + | 'ice skating instructor' + | 'ice skating rink' + | 'ikan bakar restaurant' + | 'import export company' + | 'indian grocery store' + | 'indian motorcycle dealer' + | 'indian muslim restaurant' + | 'indian sizzler restaurant' + | 'indian sweets shop' + | 'indoor golf course' + | 'indoor swimming pool' + | 'industrial chemicals wholesaler' + | 'industrial design company' + | 'industrial engineers association' + | 'industrial equipment supplier' + | 'industrial gas supplier' + | 'infectious disease physician' + | 'institute of technology' + | 'insulation materials store' + | 'intellectual property registry' + | 'interior architect office' + | 'interior construction contractor' + | 'interior fitting contractor' + | 'interior plant service' + | 'internal medicine ward' + | 'international trade consultant' + | 'internet marketing service' + | 'internet service provider' + | 'invitation printing service' + | 'irish goods store' + | 'iron ware dealer' + | 'irrigation equipment supplier' + | 'italian grocery store' + | 'janitorial equipment supplier' + | 'japanese confectionery shop' + | 'japanese curry restaurant' + | 'japanese grocery store' + | 'japanese language instructor' + | 'japanese regional restaurant' + | 'japanese sweets restaurant' + | 'japanese-style business hotel' + | 'japanized western restaurant' + | 'jewelry equipment supplier' + | 'jewelry repair service' + | 'junk removal service' + | 'juvenile detention center' + | 'kalle pache restaurant' + | 'kawasaki motorcycle dealer' + | 'key duplication service' + | 'kitchen furniture store' + | 'kitchen supply store' + | 'korean barbecue restaurant' + | 'korean beef restaurant' + | 'korean grocery store' + | 'korean rib restaurant' + | 'kosher grocery store' + | 'kung fu school' + | 'labor relations attorney' + | 'laboratory equipment supplier' + | "ladies' clothes shop" + | 'laminating equipment supplier' + | 'lamp repair service' + | 'lamp shade supplier' + | 'land planning authority' + | 'land reform institute' + | 'land rover dealer' + | 'land surveying office' + | 'landscape lighting designer' + | 'landscaping supply store' + | 'laser cutting service' + | 'laser equipment supplier' + | 'laser tag center' + | 'latin american restaurant' + | 'law book store' + | 'lawn bowls club' + | 'lawn care service' + | 'lawn mower store' + | 'leather cleaning service' + | 'leather coats store' + | 'leather goods manufacturer' + | 'leather goods store' + | 'leather goods supplier' + | 'leather goods wholesaler' + | 'leather repair service' + | 'legal affairs bureau' + | 'life insurance agency' + | 'light bulb supplier' + | 'lighting products wholesaler' + | 'line marking service' + | 'little league club' + | 'little league field' + | 'live music bar' + | 'live music venue' + | 'livestock auction house' + | 'local government office' + | 'local history museum' + | 'local medical services' + | 'log home builder' + | 'lost property office' + | 'luggage repair service' + | 'luggage storage facility' + | 'lymph drainage therapist' + | 'machine knife supplier' + | 'machine maintenance service' + | 'machine repair service' + | 'machinery parts manufacturer' + | 'mailbox rental service' + | 'mailing machine supplier' + | 'main customs office' + | 'manufactured home transporter' + | 'marine supply store' + | 'marquee hire service' + | 'marriage license bureau' + | 'martial arts club' + | 'martial arts school' + | 'masonry supply store' + | 'massage supply store' + | 'match box manufacturer' + | 'measuring instruments supplier' + | 'meat dish restaurant' + | 'meat products store' + | 'medical billing service' + | 'medical book store' + | 'medical certificate service' + | 'medical equipment manufacturer' + | 'medical equipment supplier' + | 'medical supply store' + | 'medical technology manufacturer' + | 'medical transcription service' + | 'meeting planning service' + | "men's clothes shop" + | "men's clothing store" + | "men's health physician" + | 'mental health clinic' + | 'mental health service' + | 'metal construction company' + | 'metal industry suppliers' + | 'metal machinery supplier' + | 'metal polishing service' + | 'metal processing company' + | 'metal stamping service' + | 'metal working shop' + | 'metaphysical supply store' + | 'metropolitan train company' + | 'mexican goods store' + | 'mexican grocery store' + | 'mexican torta restaurant' + | 'middle eastern restaurant' + | 'military recruiting office' + | 'milk delivery service' + | 'mineral water company' + | 'miniature golf course' + | 'minibus taxi service' + | 'ministry of education' + | 'miso cutlet restaurant' + | 'missing persons organization' + | 'mobile disco service' + | 'mobile home dealer' + | 'mobile home park' + | 'mobile money agent' + | 'mobile network operator' + | 'mobile phone shop' + | 'mobility equipment supplier' + | 'model design company' + | 'model portfolio studio' + | 'model train store' + | 'modern art museum' + | 'modern british restaurant' + | 'modern european restaurant' + | 'modern french restaurant' + | 'modern indian restaurant' + | 'modern izakaya restaurant' + | 'modular home builder' + | 'modular home dealer' + | 'money order service' + | 'money transfer service' + | 'mongolian barbecue restaurant' + | 'motor scooter dealer' + | 'motor vehicle dealer' + | 'motorcycle driving school' + | 'motorcycle insurance agency' + | 'motorcycle parts store' + | 'motorcycle rental agency' + | 'motorcycle repair shop' + | 'mountain cable car' + | 'movie rental kiosk' + | 'movie rental store' + | 'moving supply store' + | 'municipal administration office' + | 'museum of zoology' + | 'music box store' + | 'musical instrument manufacturer' + | 'musical instrument store' + | 'musician and composer' + | 'mutton barbecue restaurant' + | 'nasi goreng restaurant' + | 'nasi uduk restaurant' + | 'native american restaurant' + | 'natural goods store' + | 'natural history museum' + | 'natural stone exporter' + | 'natural stone supplier' + | 'natural stone wholesaler' + | 'neon sign shop' + | 'new age church' + | 'new american restaurant' + | 'new england restaurant' + | 'new zealand restaurant' + | 'newspaper distribution service' + | 'non vegetarian restaurant' + | 'north african restaurant' + | 'north indian restaurant' + | 'northern italian restaurant' + | 'nuclear power company' + | 'nuclear power plant' + | 'nuevo latino restaurant' + | 'occupational health service' + | 'occupational medical physician' + | 'off roading area' + | 'offal barbecue restaurant' + | 'office accessories wholesaler' + | 'office equipment supplier' + | 'office furniture store' + | 'office refurbishment service' + | 'office supply store' + | 'office supply wholesaler' + | 'offset printing service' + | 'oil change service' + | 'olive oil cooperative' + | 'olive oil manufacturer' + | 'open air museum' + | 'optical products manufacturer' + | 'organic drug store' + | 'organic food store' + | 'oriental goods store' + | 'oriental medicine clinic' + | 'oriental medicine store' + | 'oriental rug store' + | 'orthopedic shoe store' + | 'orthopedic supplies store' + | 'outboard motor store' + | 'outdoor activity organiser' + | 'outdoor equestrian facility' + | 'outdoor furniture store' + | 'outdoor sports store' + | 'outdoor swimming pool' + | 'oxygen cocktail spot' + | 'oxygen equipment supplier' + | 'oyster bar restaurant' + | 'pacific rim restaurant' + | 'packaging supply store' + | 'pain control clinic' + | 'pain management physician' + | 'paint stripping service' + | 'painter and decorator' + | 'paper bag supplier' + | 'paralegal services provider' + | 'park and garden' + | 'park and ride' + | 'passport photo processor' + | 'paternity testing service' + | 'patients support association' + | 'patio enclosure supplier' + | 'paving materials supplier' + | 'pecel lele restaurant' + | 'pennsylvania dutch restaurant' + | 'performing arts group' + | 'performing arts theater' + | 'permanent make-up clinic' + | 'personal chef service' + | 'personal injury attorney' + | 'personal injury lawyer' + | 'personal watercraft dealer' + | 'pest control service' + | 'pet adoption service' + | 'pet boarding service' + | 'pet care service' + | 'pet moving service' + | 'pet supply store' + | 'petroleum products company' + | 'pharmaceutical products wholesaler' + | 'phone repair service' + | 'photo restoration service' + | 'physical examination center' + | 'physical fitness program' + | 'physical rehabilitation center' + | 'physical therapy clinic' + | 'physician referral service' + | 'physiotherapy equipment supplier' + | 'piano moving service' + | 'piano repair service' + | 'piano tuning service' + | 'picture frame shop' + | 'pinball machine supplier' + | 'pine furniture shop' + | 'place of worship' + | 'plast window store' + | 'plastic bag supplier' + | 'plastic bags wholesaler' + | 'plastic fabrication company' + | 'plastic products supplier' + | 'plastic products wholesaler' + | 'plastic resin manufacturer' + | 'plastic surgery clinic' + | 'playground equipment supplier' + | 'plumbing supply store' + | 'pneumatic tools supplier' + | 'police supply store' + | 'political party office' + | 'pond fish supplier' + | 'pond supply store' + | 'pony ride service' + | 'pool billard club' + | 'pool cleaning service' + | 'port operating company' + | 'portable building manufacturer' + | 'portable toilet supplier' + | 'powder coating service' + | 'power plant consultant' + | 'powersports vehicle dealer' + | 'practitioner service location' + | 'pregnancy care center' + | 'pressure washing service' + | 'printed music publisher' + | 'printer repair service' + | 'printing equipment supplier' + | 'private educational institution' + | 'private equity firm' + | 'private golf course' + | 'private sector bank' + | 'promotional products supplier' + | 'property administration service' + | 'property investment company' + | 'property management company' + | 'protective clothing supplier' + | 'psychoneurological specialized clinic' + | 'psychosomatic medical practitioner' + | "public defender's office" + | 'public educational institution' + | 'public golf course' + | 'public health department' + | 'public medical center' + | 'public parking space' + | 'public prosecutors office' + | 'public relations firm' + | 'public safety office' + | 'public sector bank' + | 'public swimming pool' + | 'public works department' + | 'puerto rican restaurant' + | 'pvc windows supplier' + | 'race car dealer' + | 'radiator repair service' + | 'raft trip outfitter' + | 'railroad equipment supplier' + | 'railroad ties supplier' + | 'rainwater tank supplier' + | 'rare book store' + | 'raw food restaurant' + | 'real estate agency' + | 'real estate agent' + | 'real estate appraiser' + | 'real estate attorney' + | 'real estate auctioneer' + | 'real estate consultant' + | 'real estate developer' + | 'real estate school' + | 'real estate surveyor' + | 'records storage facility' + | 'recycling drop-off location' + | 'refrigerated transport service' + | 'refrigerator repair service' + | 'regional government office' + | 'registered general nurse' + | 'religious book store' + | 'religious goods store' + | "renter's insurance agency" + | 'reproductive health clinic' + | 'restaurant or cafe' + | 'restaurant supply store' + | 'retaining wall supplier' + | 'rice cake shop' + | 'rice cracker shop' + | 'road construction company' + | 'road safety town' + | 'rock climbing gym' + | 'rock climbing instructor' + | 'rock music club' + | 'roller skating club' + | 'roller skating rink' + | 'roofing supply store' + | 'roommate referral service' + | 'rubber products supplier' + | 'rubber stamp store' + | 'rugby league club' + | 'russian grocery store' + | 'russian orthodox church' + | 'rustic furniture store' + | 'rv detailing service' + | 'rv repair shop' + | 'rv storage facility' + | 'rv supply store' + | 'safety equipment supplier' + | 'sailing event area' + | 'satellite communication service' + | 'saw sharpening service' + | 'scaffolding rental service' + | 'scale model club' + | 'scale repair service' + | 'school administration office' + | 'school bus service' + | 'school district office' + | 'school supply store' + | 'scientific equipment supplier' + | 'scooter rental service' + | 'scooter repair shop' + | 'scrap metal dealer' + | 'screen printing shop' + | 'screen repair service' + | 'scuba tour agency' + | 'seasonal goods store' + | 'second hand store' + | 'security guard service' + | 'security system supplier' + | 'self defense school' + | 'self service restaurant' + | 'self storage facility' + | 'semi conductor supplier' + | 'senior citizen center' + | 'senior high school' + | 'septic system service' + | 'seventh-day adventist church' + | 'sewage disposal service' + | 'sewage treatment plant' + | 'sewing machine store' + | 'sheet metal contractor' + | 'sheet music store' + | 'shipping equipment industry' + | 'shoe repair shop' + | 'shoe shining service' + | 'shooting event area' + | 'shower door shop' + | 'sightseeing tour agency' + | 'silk plant shop' + | 'singing telegram service' + | 'sixth form college' + | 'skate sharpening service' + | 'skeet shooting range' + | 'ski rental service' + | 'ski repair service' + | 'skin care clinic' + | 'small plates restaurant' + | 'smart car dealer' + | 'smog inspection station' + | 'snow removal service' + | 'snowboard rental service' + | 'snowmobile rental service' + | 'soba noodle shop' + | 'social security attorney' + | 'social security office' + | 'social services organization' + | 'social welfare center' + | 'societe de flocage' + | 'soft drinks shop' + | 'software training institute' + | 'soil testing service' + | 'solar energy company' + | 'solid fuel company' + | 'solid waste engineer' + | 'soto ayam restaurant' + | 'soul food restaurant' + | 'south african restaurant' + | 'south american restaurant' + | 'south asian restaurant' + | 'south indian restaurant' + | 'south sulawesi restaurant' + | 'southeast asian restaurant' + | 'southern italian restaurant' + | 'southern restaurant (us)' + | 'soy sauce maker' + | 'space of remembrance' + | 'special education school' + | 'sport tour agency' + | 'sporting goods store' + | 'sports accessories wholesaler' + | 'sports activity location' + | 'sports card store' + | 'sports injury clinic' + | 'sports massage therapist' + | 'sports medicine clinic' + | 'sports medicine physician' + | 'sports memorabilia store' + | 'sports nutrition store' + | 'sri lankan restaurant' + | 'stained glass studio' + | 'stainless steel plant' + | 'stall installation service' + | 'stamp collectors club' + | 'staple food package' + | 'state employment department' + | 'state government office' + | 'state liquor store' + | 'state owned farm' + | 'std testing service' + | 'steamed bun shop' + | 'steel construction company' + | 'steel framework contractor' + | 'steelwork design service' + | 'stereo rental store' + | 'stereo repair service' + | 'stock exchange building' + | 'store equipment supplier' + | 'stores and shopping' + | 'student housing center' + | 'students parents association' + | 'students support association' + | 'suburban train line' + | 'summer camp organizer' + | 'summer toboggan run' + | 'super public bath' + | 'supplementary educational institute' + | 'surf lifesaving club' + | 'surgical products wholesaler' + | 'surgical supply store' + | 'suzuki motorcycle dealer' + | 'swimming pool contractor' + | 'table tennis club' + | 'table tennis facility' + | 'tai chi school' + | 'tata motors dealer' + | 'tattoo removal service' + | "tax collector's office" + | 'tax preparation service' + | 'tea market place' + | 'teeth whitening service' + | 'telecommunications equipment supplier' + | 'telecommunications service provider' + | 'telephone answering service' + | 'television repair service' + | 'tent rental service' + | 'thai massage therapist' + | 'theater supply store' + | 'theatrical costume supplier' + | 'tile cleaning service' + | 'tire repair shop' + | 'toner cartridge supplier' + | 'tool rental service' + | 'tool repair shop' + | 'tourist information center' + | 'towing equipment provider' + | 'tractor repair shop' + | 'trading card store' + | 'traditional american restaurant' + | 'traditional costume club' + | 'traditional kostume store' + | 'trailer rental service' + | 'trailer repair shop' + | 'trailer supply store' + | 'train repairing center' + | 'train ticket agency' + | 'transportation escort service' + | 'triumph motorcycle dealer' + | 'tropical fish store' + | 'truck accessories store' + | 'truck driving school' + | 'truck parts supplier' + | 'truck rental agency' + | 'truck repair shop' + | 'truck topper supplier' + | 'tsukigime parking lot' + | 'tune up supplier' + | 'typewriter repair service' + | 'udon noodle restaurant' + | 'unfinished furniture store' + | 'unitarian universalist church' + | 'united methodist church' + | 'upholstery cleaning service' + | 'urban planning department' + | 'urgent care center' + | 'used appliance store' + | 'used bicycle shop' + | 'used book store' + | 'used car dealer' + | 'used cd store' + | 'used clothing store' + | 'used computer store' + | 'used furniture store' + | 'used game store' + | 'used motorcycle dealer' + | 'used tire shop' + | 'used truck dealer' + | 'utility trailer dealer' + | 'uyghur cuisine restaurant' + | 'vacuum cleaner store' + | 'valet parking service' + | 'van rental agency' + | 'vcr repair service' + | 'vegetable wholesale market' + | 'vehicle inspection service' + | 'vehicle repair shop' + | 'vehicle shipping agent' + | 'vehicle wrapping service' + | 'vending machine supplier' + | 'ventilating equipment manufacturer' + | 'venture capital company' + | 'veterans affairs department' + | 'video conferencing service' + | 'video duplication service' + | 'video editing service' + | 'video game store' + | 'video production service' + | 'vintage clothing store' + | 'vinyl sign shop' + | 'virtual office rental' + | 'visa consulting service' + | 'vocational gymnasium school' + | 'vocational secondary school' + | 'voter registration office' + | 'waste management service' + | 'waste transfer station' + | 'watch repair service' + | 'water cooler supplier' + | 'water filter supplier' + | 'water polo pool' + | 'water pump supplier' + | 'water purification company' + | 'water ski shop' + | 'water skiing club' + | 'water skiing instructor' + | 'water skiing service' + | 'water testing service' + | 'water treatment plant' + | 'water treatment supplier' + | 'water utility company' + | 'waterbed repair service' + | 'weather forecast service' + | 'web hosting company' + | 'wedding souvenir shop' + | 'weight loss service' + | 'welding gas supplier' + | 'welding supply store' + | 'well drilling contractor' + | 'west african restaurant' + | 'western apparel store' + | 'wheel alignment service' + | 'wheelchair rental service' + | 'wheelchair repair service' + | 'wholesale food store' + | 'wholesale plant nursery' + | 'wholesaler household appliances' + | 'wildlife rescue service' + | 'willow basket manufacturer' + | 'wind turbine builder' + | 'window cleaning service' + | 'window installation service' + | 'window tinting service' + | 'window treatment store' + | 'wine storage facility' + | 'winemaking supply store' + | 'wing chun school' + | "women's clothing store" + | "women's health clinic" + | "women's personal trainer" + | 'wood frame supplier' + | 'wood stove shop' + | 'wood working class' + | 'woodworking supply store' + | 'work clothes store' + | 'yamaha motorcycle dealer' + | 'yoga retreat center' + | 'youth care service' + | 'youth clothing store' + | 'adult day care center' + | 'adult foster care service' + | 'air compressor repair service' + | 'air conditioning repair service' + | 'air conditioning system supplier' + | 'air duct cleaning service' + | 'antique furniture restoration service' + | 'asian household goods store' + | 'assemblies of god church' + | 'audio visual equipment supplier' + | 'audiovisual equipment rental service' + | 'auto air conditioning service' + | 'auto body parts supplier' + | 'auto care products store' + | 'auto dent removal service' + | 'auto glass repair service' + | 'auto radiator repair service' + | 'auto tune up service' + | 'auto window tinting service' + | 'balloon ride tour agency' + | 'bar restaurant furniture store' + | 'beauty products vending machine' + | 'building equipment hire service' + | 'business to business service' + | 'cake decorating equipment shop' + | 'canoe & kayak store' + | 'canoe and kayak club' + | 'car security system installer' + | 'cardiovascular and thoracic surgeon' + | 'carport and pergola builder' + | 'cash and carry wholesaler' + | 'cell phone accessory store' + | 'cell phone charging station' + | 'chess and card club' + | 'child health care centre' + | 'church of the nazarene' + | 'city department of transportation' + | 'classified ads newspaper publisher' + | 'clock and watch maker' + | 'clothes and fabric manufacturer' + | 'clothes and fabric wholesaler' + | 'clothing wholesale market place' + | 'coach and minibus hire' + | 'commercial real estate agency' + | 'commercial real estate inspector' + | 'compressed natural gas station' + | 'computer support and services' + | 'concrete metal framework supplier' + | 'construction machine rental service' + | 'conveyor belt sushi restaurant' + | 'curtain supplier and maker' + | 'custom confiscated goods store' + | 'dairy farm equipment supplier' + | 'dan dan noodle restaurant' + | 'dealer of fiat professional' + | 'department of motor vehicles' + | 'department of public safety' + | 'department of social services' + | 'diesel engine repair service' + | 'disciples of christ church' + | 'dog day care center' + | 'domestic abuse treatment center' + | 'drivers license training school' + | 'dry wall supply store' + | 'dryer vent cleaning service' + | 'eating disorder treatment center' + | 'electric motor repair shop' + | 'electric motor scooter dealer' + | 'electric motor vehicle dealer' + | 'electric vehicle charging station' + | 'electrolysis hair removal service' + | 'energy equipment and solutions' + | 'environment renewable natural resources' + | 'executive suite rental agency' + | 'exhibition and trade centre' + | 'family day care service' + | 'farm equipment repair service' + | 'farming and cattle raising' + | 'fiber optic products supplier' + | 'film and photograph library' + | 'fire damage restoration service' + | 'fire department equipment supplier' + | 'fire protection equipment supplier' + | 'fire protection system supplier' + | 'fish & chips restaurant' + | 'fish and chips takeaway' + | 'food and beverage consultant' + | 'food and beverage exporter' + | 'foreign exchange students organization' + | 'foreign languages program school' + | 'fruit and vegetable processing' + | 'fruit and vegetable store' + | 'fruit and vegetable wholesaler' + | 'full dress rental service' + | 'glass & mirror shop' + | 'ground self defense force' + | 'guardia di finanza police' + | 'haute couture fashion house' + | 'health and beauty shop' + | 'hearing aid repair service' + | 'hearing assistance earphone store' + | 'hip hop dance class' + | 'home health care service' + | 'home help service agency' + | 'hospital equipment and supplies' + | 'hospitality and tourism school' + | 'hot tub repair service' + | 'hot water system supplier' + | 'hua niao market place' + | 'hunting and fishing store' + | 'ice cream equipment supplier' + | 'immigration & naturalization service' + | 'income protection insurance agency' + | 'income tax help association' + | 'industrial real estate agency' + | 'industrial technical engineers association' + | 'industrial vacuum equipment supplier' + | 'iron and steel store' + | 'it support and services' + | 'japanese cheap sweets shop' + | "jehovah's witness kingdom hall" + | 'karaoke equipment rental service' + | 'kilt shop and hire' + | 'kushiage and kushikatsu restaurant' + | 'laser hair removal service' + | 'lawn equipment rental service' + | 'lawn irrigation equipment supplier' + | 'lawn mower repair service' + | 'lawn sprinkler system contractor' + | 'learner driver training area' + | 'license plate frames supplier' + | 'low income housing program' + | 'marine self defense force' + | 'marriage or relationship counselor' + | 'martial arts supply store' + | 'material handling equipment supplier' + | 'medical diagnostic imaging center' + | 'metal detecting equipment supplier' + | 'metal heat treating service' + | 'microwave oven repair service' + | 'mobile home rental agency' + | 'mobile home supply store' + | 'mobile phone repair shop' + | 'model car play area' + | 'motor scooter repair shop' + | 'moving and storage service' + | 'muay thai boxing gym' + | 'municipal department of tourism' + | 'museum of space history' + | 'music management and promotion' + | 'musical instrument rental service' + | 'musical instrument repair shop' + | 'native american goods store' + | 'natural rock climbing area' + | 'non smoking holiday home' + | 'north eastern indian restaurant' + | 'occupational safety and health' + | 'off track betting shop' + | 'offal pot cooking restaurant' + | 'office equipment rental service' + | 'office equipment repair service' + | 'office space rental agency' + | 'oil field equipment supplier' + | 'olive oil bottling company' + | 'optical instrument repair service' + | 'oral and maxillofacial surgeon' + | 'orthotics & prosthetics service' + | 'paper shredding machine supplier' + | 'parking lot for bicycles' + | 'parking lot for motorcycles' + | 'party equipment rental service' + | 'pay by weight restaurant' + | 'plant and machinery hire' + | 'plastic injection molding service' + | 'plus size clothing store' + | 'power plant equipment supplier' + | 'printer ink refill store' + | 'professional and hobby associations' + | 'qing fang market place' + | 'racing car parts store' + | 'ready mix concrete supplier' + | 'real estate rental agency' + | 'recreational vehicle rental agency' + | 'research and product development' + | 'retail space rental agency' + | 'rolled metal products supplier' + | 'safe & vault shop' + | 'sand & gravel supplier' + | 'sand and gravel supplier' + | 'school for the deaf' + | 'screen printing supply store' + | 'security system installation service' + | 'self service car wash' + | 'self service health station' + | 'sewing machine repair service' + | 'shipbuilding and repair company' + | 'shipping and mailing service' + | 'shop supermarket furniture store' + | 'single sex secondary school' + | 'small appliance repair service' + | 'small claims assistance service' + | 'small engine repair service' + | 'social security financial department' + | 'solar energy equipment supplier' + | 'solar energy system service' + | 'solar panel maintenance service' + | 'solar photovoltaic power plant' + | 'south east asian restaurant' + | 'spa and health club' + | 'sports equipment rental service' + | 'stage lighting equipment supplier' + | 'state office of education' + | 'student career counseling office' + | 'study at home school' + | 'sweets and dessert buffet' + | 'swimming pool repair service' + | 'swimming pool supply store' + | 'syokudo and teishoku restaurant' + | 'table tennis supply store' + | 'tattoo and piercing shop' + | 'tea and coffee shop' + | 'tennis court construction company' + | 'threads and yarns wholesaler' + | 'tool & die shop' + | 'trade fair construction company' + | 'united church of canada' + | 'united church of christ' + | 'used auto parts store' + | 'used musical instrument store' + | 'used office furniture store' + | 'used store fixture supplier' + | 'vacation home rental agency' + | 'vacuum cleaner repair shop' + | 'vacuum cleaning system supplier' + | 'vegetarian cafe and deli' + | 'video camera repair service' + | 'video conferencing equipment supplier' + | 'video equipment repair service' + | 'video game rental kiosk' + | 'video game rental store' + | 'visa and passport office' + | 'vitamin & supplements store' + | 'washer & dryer store' + | 'water damage restoration service' + | 'water jet cutting service' + | 'water softening equipment supplier' + | 'water tank cleaning service' + | 'water works equipment supplier' + | 'waxing hair removal service' + | 'wedding dress rental service' + | 'whale watching tour agency' + | 'wildlife and safari park' + | 'wine wholesaler and importer' + | 'wood floor installation service' + | 'wood floor refinishing service' + | 'youth social services organization' + | 'architectural and engineering model maker' + | 'army & navy surplus shop' + | 'audio visual equipment repair service' + | 'bottle & can redemption center' + | 'canoe & kayak tour agency' + | 'car finance and loan company' + | 'car repair and maintenance service' + | 'catering food and drink supplier' + | 'coin operated laundry equipment supplier' + | 'combined primary and secondary school' + | 'disability services and support organization' + | 'dress and tuxedo rental service' + | 'electric vehicle charging station contractor' + | 'electronics retail and repair shop' + | 'federal agency for technical relief' + | 'flavours fragrances and aroma supplier' + | 'floor sanding and polishing service' + | 'ice cream and drink shop' + | 'industrial spares and products wholesaler' + | 'institute of geography and statistics' + | 'multimedia and electronic book publisher' + | 'oil & natural gas company' + | 'oil and gas exploration service' + | 'organ donation and tissue bank' + | 'outdoor clothing and equipment shop' + | 'pet food and animal feeds' + | 'pick your own farm produce' + | 'polythene and plastic sheeting supplier' + | 'road construction machine repair service' + | 'sheepskin and wool products supplier' + | 'short term apartment rental agency' + | 'skin care products vending machine' + | 'solar hot water system supplier' + | 'sukiyaki and shabu shabu restaurant' + | 'united states armed forces base' + | 'washer & dryer repair service' + | 'water sports equipment rental service' + | 'wood and laminate flooring supplier' + | 'aboriginal and torres strait islander organisation' + | 'hong kong style fast food restaurant' + | 'roads ports and canals engineers association' + | 'church of jesus christ of latter-day saints' + > + | undefined; + searchMatching?: 'all' | 'only_includes' | 'only_exact' | undefined; + placeMinimumStars?: '' | 'two' | 'twoAndHalf' | 'three' | 'threeAndHalf' | 'four' | 'fourAndHalf' | undefined; + website?: 'allPlaces' | 'withWebsite' | 'withoutWebsite' | undefined; + skipClosedPlaces?: boolean | undefined; + scrapePlaceDetailPage?: boolean | undefined; + scrapeTableReservationProvider?: boolean | undefined; + scrapeOrderOnline?: boolean | undefined; + includeWebResults?: boolean | undefined; + scrapeDirectories?: boolean | undefined; + maxQuestions?: number | undefined; + scrapeContacts?: boolean | undefined; + scrapeSocialMediaProfiles?: + | ({ + facebooks?: boolean | undefined; + instagrams?: boolean | undefined; + youtubes?: boolean | undefined; + tiktoks?: boolean | undefined; + twitters?: boolean | undefined; + } & Record) + | undefined; + maximumLeadsEnrichmentRecords?: number | undefined; + leadsEnrichmentDepartments?: + | Array< + | 'c_suite' + | 'product' + | 'engineering_technical' + | 'design' + | 'education' + | 'finance' + | 'human_resources' + | 'information_technology' + | 'legal' + | 'marketing' + | 'medical_health' + | 'operations' + | 'sales' + | 'consulting' + > + | undefined; + verifyLeadsEnrichmentEmails?: boolean | undefined; + maxReviews?: number | undefined; + reviewsStartDate?: string | undefined; + reviewsSort?: 'newest' | 'mostRelevant' | 'highestRanking' | 'lowestRanking' | undefined; + reviewsFilterString?: string | undefined; + reviewsOrigin?: 'all' | 'google' | undefined; + scrapeReviewsPersonalData?: boolean | undefined; + maxImages?: number | undefined; + scrapeImageAuthors?: boolean | undefined; + enableCompetitorAnalysis?: boolean | undefined; + maxCompetitorsToAnalyze?: number | undefined; + countryCode?: + | '' + | 'us' + | 'af' + | 'al' + | 'dz' + | 'as' + | 'ad' + | 'ao' + | 'ai' + | 'aq' + | 'ag' + | 'ar' + | 'am' + | 'aw' + | 'au' + | 'at' + | 'az' + | 'bs' + | 'bh' + | 'bd' + | 'bb' + | 'by' + | 'be' + | 'bz' + | 'bj' + | 'bm' + | 'bt' + | 'bo' + | 'ba' + | 'bw' + | 'bv' + | 'br' + | 'io' + | 'bn' + | 'bg' + | 'bf' + | 'bi' + | 'kh' + | 'cm' + | 'ca' + | 'cv' + | 'ky' + | 'cf' + | 'td' + | 'cl' + | 'cn' + | 'cx' + | 'cc' + | 'co' + | 'km' + | 'cg' + | 'cd' + | 'ck' + | 'cr' + | 'ci' + | 'hr' + | 'cu' + | 'cy' + | 'cz' + | 'dk' + | 'dj' + | 'dm' + | 'do' + | 'ec' + | 'eg' + | 'sv' + | 'gq' + | 'er' + | 'ee' + | 'et' + | 'fk' + | 'fo' + | 'fj' + | 'fi' + | 'fr' + | 'gf' + | 'pf' + | 'tf' + | 'ga' + | 'gm' + | 'ge' + | 'de' + | 'gh' + | 'gi' + | 'gr' + | 'gl' + | 'gd' + | 'gp' + | 'gu' + | 'gt' + | 'gn' + | 'gw' + | 'gy' + | 'ht' + | 'hm' + | 'va' + | 'hn' + | 'hu' + | 'is' + | 'in' + | 'id' + | 'ir' + | 'iq' + | 'ie' + | 'il' + | 'it' + | 'jm' + | 'jp' + | 'jo' + | 'kz' + | 'ke' + | 'ki' + | 'kp' + | 'kr' + | 'kw' + | 'kg' + | 'la' + | 'lv' + | 'lb' + | 'ls' + | 'lr' + | 'ly' + | 'li' + | 'lt' + | 'lu' + | 'mo' + | 'mk' + | 'mg' + | 'mw' + | 'my' + | 'mv' + | 'ml' + | 'mt' + | 'mh' + | 'mq' + | 'mr' + | 'mu' + | 'yt' + | 'mx' + | 'fm' + | 'md' + | 'mc' + | 'mn' + | 'me' + | 'ms' + | 'ma' + | 'mz' + | 'mm' + | 'na' + | 'nr' + | 'np' + | 'nl' + | 'an' + | 'nc' + | 'nz' + | 'ni' + | 'ne' + | 'ng' + | 'nu' + | 'nf' + | 'mp' + | 'no' + | 'om' + | 'pk' + | 'pw' + | 'ps' + | 'pa' + | 'pg' + | 'py' + | 'pe' + | 'ph' + | 'pn' + | 'pl' + | 'pt' + | 'pr' + | 'qa' + | 're' + | 'ro' + | 'ru' + | 'rw' + | 'sh' + | 'kn' + | 'lc' + | 'pm' + | 'vc' + | 'ws' + | 'sm' + | 'st' + | 'sa' + | 'sn' + | 'rs' + | 'sc' + | 'sl' + | 'sg' + | 'sk' + | 'si' + | 'sb' + | 'so' + | 'za' + | 'gs' + | 'ss' + | 'es' + | 'lk' + | 'sd' + | 'sr' + | 'sj' + | 'sz' + | 'se' + | 'ch' + | 'sy' + | 'tw' + | 'tj' + | 'tz' + | 'th' + | 'tl' + | 'tg' + | 'tk' + | 'to' + | 'tt' + | 'tn' + | 'tr' + | 'tm' + | 'tc' + | 'tv' + | 'ug' + | 'ua' + | 'ae' + | 'gb' + | 'um' + | 'uy' + | 'uz' + | 'vu' + | 've' + | 'vn' + | 'vg' + | 'vi' + | 'wf' + | 'eh' + | 'ye' + | 'zm' + | 'zw' + | undefined; + city?: string | undefined; + state?: string | undefined; + county?: string | undefined; + postalCode?: string | undefined; + customGeolocation?: Record | undefined; + startUrls?: Array | undefined; + placeIds?: Array | undefined; + allPlacesNoSearchAction?: '' | 'all_places_no_search_ocr' | 'all_places_no_search_mouse' | undefined; +} & Record; diff --git a/test/local/__fixtures__/lib/schema-to-ts/input/google-places.json b/test/local/__fixtures__/lib/schema-to-ts/input/google-places.json new file mode 100644 index 000000000..127513980 --- /dev/null +++ b/test/local/__fixtures__/lib/schema-to-ts/input/google-places.json @@ -0,0 +1,5180 @@ +{ + "title": "Google Maps Scraper", + "description": "To extract place data or contact details, simply enter a 🔍 Search term, add a 📍 Location, and set the 💯 Number of places to extract.

The 🔍 Search filters & categories section and the add-on sections below offer extra filters, sorting, and data like reviews or images. Options marked with the ($) sign cost extra - see the pricing details.

Sections marked with an asterisk* are just alternative ways to start the input (📡 Geolocation parameters, 🔗 URLs, 🧭 all places). They can be combined with any of the features and sorting options.", + "type": "object", + "schemaVersion": 1, + "properties": { + "searchStringsArray": { + "title": "🔍 Search term(s)", + "type": "array", + "description": "Type what you'd normally search for in the Google Maps search bar, like English breakfast or pet shelter. Aim for unique terms for faster processing. Using similar terms (e.g., bar vs. restaurant vs. cafe) may slightly increase your capture rate but is less efficient.

⚠️ Searching for a specific place? If you're looking for a particular business or location (e.g., M&M Indian Thai Halal Restaurant), make sure to also specify the city or country in the 📍 Location field below to get more accurate and reliable results. Without location context, Google Maps may return results from unexpected areas.

⚠️ Heads up: Adding a location directly to the search, e.g., restaurant Pittsburgh, can limit you to a maximum of 120 results per search term due to Google Maps' scrolling limit.

You can also use direct place IDs here in the format place_id:ChIJ8_JBApXMDUcRDzXcYUPTGUY. See the [detailed description](https://apify.com/compass/crawler-google-places#search-terms).", + "editor": "stringList", + "prefill": [ + "restaurant" + ], + "example": [ + "restaurant" + ] + }, + "locationQuery": { + "title": "📍 Location (use only one location per run)", + "type": "string", + "description": "Define location using free text. Simpler formats work best; e.g., use City + Country rather than City + Country + State.

🌍 You can just set the whole country or state as the location: Google Maps Scraper intelligently splits it into subregions internally, so there's no need to search city by city or neighborhood by neighborhood yourself.

Verify with the OpenStreetMap webapp for visual validation of the exact area you want to cover.

💡 Pro tip: Always specify a location when searching for specific place names in the 🔍 Search terms field above. This helps narrow down results to the geographic area you're interested in and prevents getting results from unrelated locations.

⚠️ Automatically defined City polygons may be smaller than expected (e.g., they don't include agglomeration areas). If you need to define the whole city area, head over to the 📡 Geolocation parameters* section instead to select Country, State, County, City, or Postal code.
For an even more precise location definition (especially when using City name as a starting point), head over to 🛰 Custom search area section to create polygon shapes of the areas you want to scrape. Note that 📍 Location settings always take priority over 📡 Geolocation* (so use either section but not both at the same time).

For guidance and tricks on location definition, check the Apify tutorial.", + "editor": "textfield", + "prefill": "New York, USA", + "example": "New York, USA" + }, + "maxCrawledPlacesPerSearch": { + "title": "💯 Number of places to extract (per each search term or URL)", + "type": "integer", + "description": "Number of results you expect to get per each Search term, Category or URL. The higher the number, the longer it will take.

If you want to scrape all the places available, leave this field empty.", + "prefill": 50, + "example": 50, + "minimum": 1 + }, + "language": { + "title": "🌍 Language", + "description": "Results will be scraped in this language.", + "enum": [ + "en", + "af", + "az", + "id", + "ms", + "bs", + "ca", + "cs", + "da", + "de", + "et", + "es", + "es-419", + "eu", + "fil", + "fr", + "gl", + "hr", + "zu", + "is", + "it", + "sw", + "lv", + "lt", + "hu", + "nl", + "no", + "uz", + "pl", + "pt-BR", + "pt-PT", + "ro", + "sq", + "sk", + "sl", + "fi", + "sv", + "vi", + "tr", + "el", + "bg", + "ky", + "kk", + "mk", + "mn", + "ru", + "sr", + "uk", + "ka", + "hy", + "iw", + "ur", + "ar", + "fa", + "am", + "ne", + "hi", + "mr", + "bn", + "pa", + "gu", + "ta", + "te", + "kn", + "ml", + "si", + "th", + "lo", + "my", + "km", + "ko", + "ja", + "zh-CN", + "zh-TW" + ], + "enumTitles": [ + "English", + "Afrikaans", + "azərbaycan", + "BahasaIndonesia", + "BahasaMelayu", + "bosanski", + "català", + "Čeština", + "Dansk", + "Deutsch (Deutschland)", + "eesti", + "Español (España)", + "Español (Latinoamérica)", + "euskara", + "Filipino", + "Français (France)", + "galego", + "Hrvatski", + "isiZulu", + "íslenska", + "Italiano", + "Kiswahili", + "latviešu", + "lietuvių", + "magyar", + "Nederlands", + "norsk", + "oʻzbekcha", + "polski", + "Português (Brasil)", + "Português (Portugal)", + "română", + "shqip", + "Slovenčina", + "slovenščina", + "Suomi", + "Svenska", + "TiếngViệt", + "Türkçe", + "Ελληνικά", + "български", + "кыргызча", + "қазақтілі", + "македонски", + "монгол", + "Русский", + "српски (ћирилица)", + "Українська", + "ქართული", + "հայերեն", + "עברית", + "اردو", + "العربية", + "فارسی", + "አማርኛ", + "नेपाली", + "हिन्दी", + "मराठी", + "বাংলা", + "ਪੰਜਾਬੀ", + "ગુજરાતી", + "தமிழ்", + "తెలుగు", + "ಕನ್ನಡ", + "മലയാളം", + "සිංහල", + "ไทย", + "ລາວ", + "ဗမာ", + "ខ្មែរ", + "한국어", + "日本語", + "简体中文", + "繁體中文" + ], + "type": "string", + "editor": "select", + "default": "en", + "example": "en", + "prefill": "en" + }, + "categoryFilterWords": { + "title": "🎢 Place categories ($)", + "type": "array", + "description": "You can limit the places that are scraped based on the Category filter; you can choose as many categories for one flat fee for the whole field. ⚠️ Using categories can sometimes lead to false negatives, as many places do not properly categorize themselves, and there are over 4,000 available categories which Google Maps has. Using categories might filter out places that you’d like to scrape. To avoid this problem, you must list all categories that you want to scrape, including synonyms, e.g., divorce lawyer, divorce attorney, divorce service, etc. See the [detailed description](https://apify.com/compass/crawler-google-places#categories).", + "editor": "select", + "items": { + "type": "string", + "enum": [ + "abbey", + "accountant", + "accounting", + "acupuncturist", + "aeroclub", + "agriculture", + "airline", + "airport", + "airstrip", + "allergist", + "amphitheater", + "amphitheatre", + "anesthesiologist", + "appraiser", + "aquarium", + "arboretum", + "architect", + "archive", + "arena", + "artist", + "ashram", + "astrologer", + "atm", + "attorney", + "audiologist", + "auditor", + "auditorium", + "bakery", + "band", + "bank", + "bar", + "barrister", + "basilica", + "bazar", + "beach", + "beautician", + "bistro", + "blacksmith", + "bodega", + "bookbinder", + "botanica", + "boutique", + "brasserie", + "brewery", + "brewpub", + "bricklayer", + "bridge", + "builder", + "building", + "bullring", + "butchers", + "cafe", + "cafeteria", + "campground", + "cannery", + "cardiologist", + "carpenter", + "cars", + "carvery", + "cashpoint", + "casino", + "castle", + "caterer", + "catering", + "cathedral", + "cattery", + "cemetery", + "chalet", + "chapel", + "charcuterie", + "charity", + "chemist", + "childminder", + "chiropractor", + "choir", + "church", + "churreria", + "circus", + "cleaners", + "clergyman", + "clinic", + "club", + "coalfield", + "college", + "company", + "computers", + "congregation", + "construction", + "consultant", + "contractor", + "conveyancer", + "coppersmith", + "cottage", + "council", + "counselor", + "courthouse", + "creperie", + "dairy", + "deli", + "delicatessen", + "dentist", + "dermatologist", + "design", + "dhaba", + "diabetologist", + "dietitian", + "diner", + "distillery", + "dj", + "doctor", + "doula", + "dressmaker", + "dyeworks", + "eatery", + "education", + "electrician", + "electronics", + "embassy", + "endocrinologist", + "endodontist", + "endoscopist", + "engineer", + "engraver", + "entertainer", + "entertainment", + "establishment", + "executor", + "exhibit", + "exporter", + "fairground", + "farm", + "farmstay", + "favela", + "festival", + "florist", + "fortress", + "foundation", + "foundry", + "frituur", + "garden", + "gardener", + "gasfitter", + "gastroenterologist", + "gastropub", + "gemologist", + "genealogist", + "geriatrician", + "glazier", + "goldsmith", + "government", + "greengrocer", + "greenhouse", + "grill", + "gurudwara", + "gym", + "gynecologist", + "haberdashery", + "hairdresser", + "hammam", + "handicraft", + "handyman/handywoman/handyperson", + "health", + "heliport", + "hematologist", + "hepatologist", + "herbalist", + "homeopath", + "homestay", + "hospice", + "hospital", + "hostel", + "hotel", + "hypermarket", + "immunologist", + "importer", + "inn", + "instruction", + "intensivist", + "internist", + "island", + "jeweler", + "joiner", + "junkyard", + "karaoke", + "kennel", + "kindergarten", + "kinesiologist", + "kinesiotherapist", + "kiosk", + "laboratory", + "lake", + "landscaper", + "lapidary", + "laundromat", + "laundry", + "lawyer", + "library", + "lido", + "liquidator", + "locksmith", + "lodge", + "lodging", + "lounge", + "lyceum", + "magician", + "makerspace", + "manufacturer", + "marae", + "marina", + "market", + "mechanic", + "memorial", + "metalwork", + "meyhane", + "midwife", + "mill", + "mine", + "mission", + "mohel", + "monastery", + "monument", + "mortuary", + "mosque", + "motel", + "mover", + "musalla", + "museum", + "musician", + "nephrologist", + "neurologist", + "neurophysiologist", + "neuropsychologist", + "neurosurgeon", + "newsstand", + "numerologist", + "nutritionist", + "observatory", + "obstetrician-gynecologist", + "office", + "oilfield", + "oncologist", + "onsen", + "ophthalmologist", + "optician", + "optometrist", + "orchard", + "orchestra", + "orphanage", + "orthodontist", + "orthoptist", + "osteopath", + "otolaryngologist", + "pagoda", + "painter", + "painting", + "parapharmacy", + "parish", + "park", + "parking", + "pathologist", + "patisserie", + "pediatrician", + "pedorthist", + "periodontist", + "pharmacy", + "photographer", + "physiatrist", + "physiotherapist", + "planetarium", + "plasterer", + "playground", + "playgroup", + "plumber", + "podiatrist", + "pre-school", + "preschool", + "priest", + "prison", + "proctologist", + "promenade", + "prosthodontist", + "psychiatrist", + "psychic", + "psychoanalyst", + "psychologist", + "psychotherapist", + "pub", + "publisher", + "pulmonologist", + "pyrotechnician", + "quarry", + "radiologist", + "radiotherapist", + "rafting", + "ranch", + "recreation", + "recruiter", + "rectory", + "recycling", + "reflexologist", + "remodeler", + "restaurant", + "rheumatologist", + "river", + "rodeo", + "rugby", + "sacem", + "saddlery", + "sailmaker", + "sambodrome", + "sauna", + "school", + "scouting", + "sculptor", + "sculpture", + "seitai", + "seminary", + "services", + "sexologist", + "shelter", + "shipyard", + "shop", + "shopfitter", + "showroom", + "shrine", + "silversmith", + "skatepark", + "slaughterhouse", + "soapland", + "spa", + "sports", + "stable", + "stadium", + "stage", + "statuary", + "store", + "stylist", + "supermarket", + "surgeon", + "surveyor", + "synagogue", + "tailor", + "takeaway", + "tannery", + "taxidermist", + "telecommunications", + "toolroom", + "travel", + "turnery", + "university", + "urologist", + "velodrome", + "venereologist", + "veterinarian", + "villa", + "vineyard", + "warehouse", + "weir", + "welder", + "wholesaler", + "winery", + "woods", + "woodworker", + "yakatabune", + "yeshiva", + "zoo", + "abarth dealer", + "abortion clinic", + "abrasives supplier", + "academic department", + "açaí shop", + "acaraje restaurant", + "accounting firm", + "accounting school", + "acoustical consultant", + "acrylic store", + "acupuncture clinic", + "acupuncture school", + "acura dealer", + "administrative attorney", + "adoption agency", + "advertising agency", + "advertising photographer", + "advertising service", + "aerial photographer", + "aerobics instructor", + "aeromodel shop", + "aeronautical engineer", + "aerospace company", + "afghan restaurant", + "african restaurant", + "agenzia entrate", + "aggregate supplier", + "agistment service", + "agricultural association", + "agricultural cooperative", + "agricultural engineer", + "agricultural organization", + "agricultural production", + "agricultural service", + "agrochemicals supplier", + "aikido club", + "aikido school", + "air taxi", + "airbrushing service", + "aircraft dealer", + "aircraft manufacturer", + "alcohol manufacturer", + "alliance church", + "alsace restaurant", + "alternator supplier", + "aluminium supplier", + "aluminum supplier", + "aluminum welder", + "aluminum window", + "ambulance service", + "american restaurant", + "ammunition supplier", + "amusement center", + "amusement park", + "anago restaurant", + "andalusian restaurant", + "andhra restaurant", + "anganwadi center", + "anglican church", + "animal hospital", + "animal shelter", + "animation studio", + "anime club", + "antenna service", + "antique store", + "apartment building", + "apartment complex", + "apostolic church", + "apparel company", + "appliance store", + "apprenticeship center", + "aquaculture farm", + "aquarium shop", + "aquatic centre", + "arab restaurant", + "arborist service", + "archaeological museum", + "archery club", + "archery range", + "archery store", + "architects association", + "architectural designer", + "architecture firm", + "architecture school", + "argentinian restaurant", + "armenian church", + "armenian restaurant", + "army facility", + "army museum", + "aromatherapy class", + "aromatherapy service", + "art cafe", + "art center", + "art dealer", + "art gallery", + "art museum", + "art school", + "art studio", + "artistic handicrafts", + "arts organization", + "asian restaurant", + "asphalt contractor", + "assamese restaurant", + "assistante maternelle", + "asturian restaurant", + "athletic club", + "athletic field", + "athletic park", + "athletic track", + "atv dealer", + "auction house", + "audi dealer", + "australian restaurant", + "austrian restaurant", + "auto auction", + "auto broker", + "auto market", + "auto painting", + "auto upholsterer", + "auto wrecker", + "automation company", + "aviation consultant", + "awadhi restaurant", + "awning supplier", + "ayurvedic clinic", + "azerbaijani restaurant", + "baby store", + "baden restaurant", + "badminton club", + "badminton complex", + "badminton court", + "bag shop", + "bagel shop", + "bait shop", + "bakery equipment", + "bakso restaurant", + "balinese restaurant", + "ballet school", + "ballet theater", + "balloon artist", + "balloon store", + "bangladeshi restaurant", + "bangle shop", + "bankruptcy attorney", + "bankruptcy service", + "banner store", + "banquet hall", + "baptist church", + "bar pmu", + "bar tabac", + "barbecue area", + "barbecue restaurant", + "barber school", + "barber shop", + "bariatric surgeon", + "bark supplier", + "barrel supplier", + "bartending school", + "baseball club", + "baseball field", + "basket supplier", + "basketball club", + "basketball court", + "basque restaurant", + "batak restaurant", + "bathroom remodeler", + "bathroom renovator", + "battery manufacturer", + "battery store", + "battery wholesaler", + "bavarian restaurant", + "beach club", + "beach pavillion", + "bead store", + "bead wholesaler", + "bearing supplier", + "beauty parlour", + "beauty salon", + "beauty school", + "bed shop", + "bedding store", + "beer distributor", + "beer garden", + "beer hall", + "beer store", + "belgian restaurant", + "belt shop", + "bengali restaurant", + "bentley dealer", + "berry restaurant", + "betawi restaurant", + "betting agency", + "beverage distributor", + "beverage supplier", + "bicycle club", + "bicycle rack", + "bicycle shop", + "bicycle store", + "bicycle wholesaler", + "bike wash", + "bilingual school", + "bingo hall", + "biochemistry lab", + "biofeedback therapist", + "biotechnology company", + "bird shop", + "birth center", + "biryani restaurant", + "blinds shop", + "blood bank", + "blueprint service", + "blues club", + "bmw dealer", + "bmx club", + "bmx park", + "boarding house", + "boarding school", + "boat builders", + "boat club", + "boat dealer", + "boat ramp", + "boating instructor", + "boiler manufacturer", + "boiler supplier", + "bonesetting house", + "book publisher", + "book store", + "bookkeeping service", + "books wholesaler", + "boot camp", + "boot store", + "border guard", + "botanical garden", + "bowling alley", + "bowling club", + "boxing club", + "boxing gym", + "boxing ring", + "boys' hostel", + "bpo company", + "brake shop", + "branding agency", + "brazilian pastelaria", + "brazilian restaurant", + "breakfast restaurant", + "brick manufacturer", + "bridal shop", + "bridge club", + "british restaurant", + "brunch restaurant", + "buddhist temple", + "buffet restaurant", + "bugatti dealer", + "buick dealer", + "building consultant", + "building designer", + "building firm", + "building inspector", + "building society", + "bulgarian restaurant", + "burmese restaurant", + "burrito restaurant", + "bus charter", + "bus company", + "bus depot", + "bus station", + "bus stop", + "business attorney", + "business broker", + "business center", + "business park", + "business school", + "butcher shop", + "butsudan store", + "cabaret club", + "cabinet maker", + "cabinet store", + "cable company", + "cadillac dealer", + "cajun restaurant", + "cake shop", + "californian restaurant", + "call center", + "call shop", + "calligraphy lesson", + "cambodian restaurant", + "camera store", + "camping cabin", + "camping farm", + "camping store", + "canadian restaurant", + "candle store", + "candy store", + "cannabis club", + "cannabis store", + "canoeing area", + "cantabrian restaurant", + "cantonese restaurant", + "capoeira school", + "capsule hotel", + "car dealer", + "car factory", + "car manufacturer", + "car wash", + "carabinieri police", + "care services", + "caribbean restaurant", + "carnival club", + "carpet installer", + "carpet manufacturer", + "carpet store", + "carpet wholesaler", + "casket service", + "castilian restaurant", + "cat breeder", + "cat cafe", + "cat trainer", + "catalonian restaurant", + "catholic cathedral", + "catholic church", + "catholic school", + "cattle farm", + "cattle market", + "caucasian restaurant", + "cbse school", + "cd store", + "ceiling supplier", + "cement manufacturer", + "cement supplier", + "cendol restaurant", + "central bank", + "ceramic manufacturer", + "ceramics wholesaler", + "certification agency", + "charter school", + "chartered accountant", + "chauffeur service", + "cheese manufacturer", + "cheese shop", + "cheesesteak restaurant", + "chemical exporter", + "chemical industry", + "chemical manufacturer", + "chemical plant", + "chemical wholesaler", + "chemistry lab", + "chesapeake restaurant", + "chess club", + "chess instructor", + "chevrolet dealer", + "chicken restaurant", + "chicken shop", + "child psychiatrist", + "child psychologist", + "childbirth class", + "children hall", + "children policlinic", + "children's cafe", + "children's camp", + "children's club", + "children's hospital", + "children's store", + "childrens store", + "chilean restaurant", + "chimney services", + "chimney sweep", + "chinaware store", + "chinese bakery", + "chinese restaurant", + "chinese supermarket", + "chinese takeaway", + "chocolate artisan", + "chocolate cafe", + "chocolate factory", + "chocolate shop", + "chop bar", + "chophouse restaurant", + "christian church", + "christian college", + "christmas market", + "christmas store", + "chrysler dealer", + "cider bar", + "cider mill", + "cigar shop", + "citroen dealer", + "city courthouse", + "city hall", + "city park", + "civic center", + "civil engineer", + "civil police", + "cleaning service", + "clothes market", + "clothing manufacturer", + "clothing shop", + "clothing store", + "clothing supplier", + "clothing wholesaler", + "co-ed school", + "coaching center", + "coaching service", + "coal exporter", + "coal supplier", + "cocktail bar", + "coffee roasters", + "coffee shop", + "coffee stand", + "coffee store", + "coffee wholesaler", + "coffin supplier", + "coin dealer", + "collectibles store", + "colombian restaurant", + "comedy club", + "comic cafe", + "commercial agent", + "commercial photographer", + "commercial printer", + "community center", + "community college", + "community garden", + "community school", + "company registry", + "computer club", + "computer consultant", + "computer service", + "computer shop", + "computer store", + "computer wholesaler", + "concert hall", + "concrete contractor", + "concrete factory", + "condiments supplier", + "condominium complex", + "confectionery store", + "confectionery wholesaler", + "conference center", + "conservative club", + "conservative synagogue", + "consignment shop", + "construction company", + "container service", + "container supplier", + "container terminal", + "containers supplier", + "continental restaurant", + "convenience store", + "convention center", + "cookie shop", + "cooking class", + "cooking school", + "cooling plant", + "cooperative bank", + "copper supplier", + "copy shop", + "copywriting service", + "corporate campus", + "corporate office", + "cosmetic dentist", + "cosmetic surgeon", + "cosmetics industry", + "cosmetics shop", + "cosmetics store", + "cosmetics wholesaler", + "cosplay cafe", + "costume store", + "cottage rental", + "cottage village", + "cotton exporter", + "cotton mill", + "cotton supplier", + "countertop contractor", + "countertop store", + "country club", + "country house", + "country park", + "courier service", + "court reporter", + "couscous restaurant", + "coworking space", + "crab house", + "craft store", + "cramming school", + "crane dealer", + "crane service", + "craniosacral therapy", + "credit union", + "cremation service", + "creole restaurant", + "cricket club", + "cricket ground", + "cricket shop", + "croatian restaurant", + "crop grower", + "croquet club", + "cruise agency", + "cruise terminal", + "crypto atm", + "cuban restaurant", + "culinary school", + "cultural association", + "cultural center", + "cultural landmark", + "cupcake shop", + "cupra dealer", + "curling club", + "curling hall", + "curtain store", + "custom tailor", + "customs broker", + "customs consultant", + "customs office", + "customs warehouse", + "cutlery store", + "cycling park", + "czech restaurant", + "dacia dealer", + "daihatsu dealer", + "dairy farm", + "dairy store", + "dairy supplier", + "dance club", + "dance company", + "dance hall", + "dance pavillion", + "dance restaurant", + "dance school", + "dance store", + "danish restaurant", + "dart bar", + "dating service", + "day spa", + "day-use onsen", + "deaf church", + "deaf service", + "debt collecting", + "decal supplier", + "deck builder", + "delivery restaurant", + "delivery service", + "demolition contractor", + "dental clinic", + "dental hygienist", + "dental laboratory", + "dental radiology", + "dental school", + "department store", + "desalination plant", + "design agency", + "design engineer", + "design institute", + "dessert restaurant", + "dessert shop", + "detention center", + "diabetes center", + "diagnostic center", + "dialysis center", + "diamond buyer", + "diamond dealer", + "diaper service", + "digital printer", + "dinner theater", + "dirt supplier", + "disco club", + "discount store", + "discount supermarket", + "distribution service", + "district attorney", + "district justice", + "district office", + "dive club", + "dive shop", + "diving center", + "divorce lawyer", + "divorce service", + "dj service", + "do-it-yourself shop", + "dock builder", + "dodge dealer", + "dog breeder", + "dog cafe", + "dog park", + "dog trainer", + "dog walker", + "dojo restaurant", + "doll store", + "dollar store", + "domestic airport", + "dominican restaurant", + "donations center", + "donut shop", + "door manufacturer", + "door shop", + "door supplier", + "door warehouse", + "drafting service", + "drainage service", + "drama school", + "drawing lessons", + "dress shop", + "dress store", + "drilling contractor", + "driveshaft shop", + "driving school", + "drone service", + "drone shop", + "drug store", + "drum school", + "drum store", + "dry cleaner", + "ducati dealer", + "dude ranch", + "dumpling restaurant", + "durum restaurant", + "dutch restaurant", + "dvd store", + "dye store", + "dynamometer supplier", + "e-commerce service", + "eclectic restaurant", + "ecological park", + "ecologists association", + "economic consultant", + "ecuadorian restaurant", + "education center", + "education centre", + "educational consultant", + "educational institution", + "egg supplier", + "egyptian restaurant", + "electrical engineer", + "electrical substation", + "electronics company", + "electronics engineer", + "electronics manufacturer", + "electronics store", + "electronics wholesaler", + "elementary school", + "elevator manufacturer", + "elevator service", + "embossing service", + "embroidery service", + "embroidery shop", + "emdr psychotherapist", + "emergency room", + "emergency training", + "employment agency", + "employment attorney", + "employment center", + "employment consultant", + "energy supplier", + "engineering consultant", + "engineering school", + "english restaurant", + "entertainment agency", + "envelope supplier", + "environment office", + "environmental consultant", + "environmental engineer", + "environmental organization", + "episcopal church", + "equestrian club", + "equestrian facility", + "equestrian store", + "equipment exporter", + "equipment importer", + "equipment supplier", + "eritrean restaurant", + "erotic massage", + "escrow service", + "espresso bar", + "estate agent", + "estate appraiser", + "estate liquidator", + "ethiopian restaurant", + "ethnographic museum", + "european restaurant", + "evangelical church", + "evening school", + "event planner", + "event venue", + "excavating contractor", + "exhibition planner", + "eyebrow bar", + "eyelash salon", + "fabric store", + "fabric wholesaler", + "fabrication engineer", + "facial spa", + "falafel restaurant", + "family counselor", + "family restaurant", + "farm bureau", + "farm school", + "farm shop", + "farmers' market", + "farrier service", + "fashion designer", + "fast food", + "fastener supplier", + "fax service", + "federal police", + "feed manufacturer", + "fence contractor", + "fencing salon", + "fencing school", + "ferrari dealer", + "ferris wheel", + "ferry service", + "fertility clinic", + "fertility physician", + "fertilizer supplier", + "festival hall", + "fiat dealer", + "fiberglass supplier", + "figurine shop", + "filipino restaurant", + "filtration plant", + "finance broker", + "financial advisor", + "financial audit", + "financial consultant", + "financial institution", + "financial planner", + "fingerprinting service", + "finnish restaurant", + "fire station", + "firearms academy", + "fireplace manufacturer", + "fireplace store", + "firewood supplier", + "fireworks store", + "fireworks supplier", + "fish farm", + "fish processing", + "fish restaurant", + "fish spa", + "fish store", + "fishing camp", + "fishing charter", + "fishing club", + "fishing pier", + "fishing pond", + "fishing store", + "fitness center", + "fitness centre", + "flag store", + "flamenco school", + "flamenco theater", + "flea market", + "flight school", + "floating market", + "flooring contractor", + "flooring store", + "floridian restaurant", + "flour mill", + "flower delivery", + "flower designer", + "flower market", + "fmcg manufacturer", + "fondue restaurant", + "food bank", + "food broker", + "food court", + "food manufacturer", + "food producer", + "food store", + "foot bath", + "foot care", + "football club", + "football field", + "footwear wholesaler", + "ford dealer", + "foreclosure service", + "foreign consulate", + "forensic consultant", + "forestry service", + "forklift dealer", + "fountain contractor", + "foursquare church", + "franconian restaurant", + "fraternal organization", + "free clinic", + "freestyle wrestling", + "french restaurant", + "friends church", + "fruit parlor", + "fruit wholesaler", + "fruits wholesaler", + "fuel pump", + "fuel supplier", + "fugu restaurant", + "funeral director", + "funeral home", + "fur manufacturer", + "fur service", + "furnace store", + "furniture accessories", + "furniture maker", + "furniture manufacturer", + "furniture store", + "furniture wholesaler", + "fusion restaurant", + "futon store", + "futsal court", + "galician restaurant", + "gambling house", + "gambling instructor", + "game store", + "garage builder", + "garbage dump", + "garden center", + "garment exporter", + "gas company", + "gas engineer", + "gas shop", + "gas station", + "gasket manufacturer", + "gastrointestinal surgeon", + "gated community", + "gay bar", + "gay sauna", + "gazebo builder", + "general contractor", + "general hospital", + "general practitioner", + "general store", + "genesis dealer", + "geological service", + "georgian restaurant", + "geotechnical engineer", + "german restaurant", + "ghost town", + "gift shop", + "gimbap restaurant", + "girl bar", + "girls' hostel", + "glass blower", + "glass industry", + "glass manufacturer", + "glass merchant", + "glass shop", + "glassware manufacturer", + "glassware store", + "glassware wholesaler", + "gluten-free restaurant", + "gmc dealer", + "goan restaurant", + "gold dealer", + "goldfish store", + "golf club", + "golf course", + "golf instructor", + "golf shop", + "gospel church", + "government college", + "government hospital", + "government office", + "government school", + "gps supplier", + "graduate school", + "grain elevator", + "grammar school", + "granite supplier", + "graphic designer", + "gravel pit", + "gravel plant", + "greek restaurant", + "greyhound stadium", + "grill store", + "grocery store", + "group accommodation", + "group home", + "grow shop", + "guardia civil", + "guatemalan restaurant", + "guest house", + "guitar instructor", + "guitar store", + "gujarati restaurant", + "gun club", + "gun shop", + "gutter service", + "gymnasium school", + "gymnastics center", + "gymnastics club", + "gyro restaurant", + "gyudon restaurant", + "hair salon", + "haitian restaurant", + "hakka restaurant", + "halal restaurant", + "haleem restaurant", + "halfway house", + "ham shop", + "hamburger restaurant", + "hand surgeon", + "handbags shop", + "handball club", + "handball court", + "handicraft exporter", + "handicraft fair", + "handicraft museum", + "handicraft school", + "handicrafts wholesaler", + "hardware shop", + "hardware store", + "harley-davidson dealer", + "hat shop", + "haunted house", + "hawaiian restaurant", + "hawker stall", + "hay supplier", + "health consultant", + "health counselor", + "health resort", + "health spa", + "heart hospital", + "heating contractor", + "height works", + "helicopter charter", + "herb shop", + "heritage building", + "heritage museum", + "heritage preservation", + "heritage railroad", + "high school", + "highway patrol", + "hiking area", + "hiking guide", + "hindu priest", + "hindu temple", + "hispanic church", + "historical landmark", + "historical place", + "historical society", + "history museum", + "hoagie restaurant", + "hobby store", + "hockey club", + "hockey field", + "hockey rink", + "holding company", + "holiday apartment", + "holiday flat", + "holiday home", + "holiday park", + "home builder", + "home help", + "home inspector", + "homekill service", + "homeless service", + "homeless shelter", + "homeopathic pharmacy", + "homeowners' association", + "homewares shop", + "honda dealer", + "honduran restaurant", + "honey farm", + "hookah bar", + "hookah store", + "horse breeder", + "horse trainer", + "horseshoe smith", + "horsestable studfarm", + "hose supplier", + "hospital department", + "host club", + "house sitter", + "housing association", + "housing authority", + "housing complex", + "housing cooperative", + "housing development", + "housing society", + "hungarian restaurant", + "hunting area", + "hunting club", + "hunting preserve", + "hunting store", + "hvac contractor", + "hyderabadi restaurant", + "hydraulic engineer", + "hypnotherapy service", + "hyundai dealer", + "ice supplier", + "icelandic restaurant", + "icse school", + "image consultant", + "imax theater", + "immigration attorney", + "impermeabilization service", + "incense supplier", + "incineration plant", + "indian restaurant", + "indian takeaway", + "indonesian restaurant", + "indoor cycling", + "indoor lodging", + "indoor playground", + "indoor snowcenter", + "industrial consultant", + "industrial engineer", + "industrial supermarket", + "infiniti dealer", + "information services", + "insolvency service", + "installation service", + "instrumentation engineer", + "insulation contractor", + "insulator supplier", + "insurance agency", + "insurance attorney", + "insurance broker", + "insurance company", + "interior decoration", + "interior decorator", + "interior designer", + "international airport", + "international school", + "internet cafe", + "internet shop", + "investment bank", + "investment company", + "investment service", + "irish pub", + "irish restaurant", + "iron works", + "israeli restaurant", + "isuzu dealer", + "italian restaurant", + "izakaya restaurant", + "jaguar dealer", + "jain temple", + "jamaican restaurant", + "janitorial service", + "japanese delicatessen", + "japanese inn", + "japanese restaurant", + "japanese steakhouse", + "javanese restaurant", + "jazz club", + "jeans shop", + "jeep dealer", + "jewellery store", + "jewelry appraiser", + "jewelry buyer", + "jewelry designer", + "jewelry engraver", + "jewelry exporter", + "jewelry manufacturer", + "jewelry store", + "jewish restaurant", + "judaica store", + "judicial auction", + "judicial scrivener", + "judo club", + "judo school", + "juice shop", + "jujitsu school", + "junior college", + "junk dealer", + "justice department", + "jute exporter", + "jute mill", + "kabaddi club", + "kaiseki restaurant", + "karaoke bar", + "karate club", + "karate school", + "karma dealer", + "karnataka restaurant", + "kashmiri restaurant", + "kazakhstani restaurant", + "kebab shop", + "kerala restaurant", + "kerosene supplier", + "kia dealer", + "kickboxing school", + "kimono store", + "kitchen remodeler", + "kitchen renovator", + "kite shop", + "knife store", + "knit shop", + "knitting instructor", + "knitwear manufacturer", + "kofta restaurant", + "konkani restaurant", + "korean church", + "korean restaurant", + "koshari restaurant", + "kosher restaurant", + "kushiyaki restaurant", + "labor union", + "ladder supplier", + "lamborghini dealer", + "lamination service", + "lancia dealer", + "land allotment", + "land surveyor", + "landscape architect", + "landscape designer", + "landscape gardener", + "language school", + "laotian restaurant", + "lasik surgeon", + "laundry service", + "law firm", + "law library", + "law school", + "lawyers association", + "leagues club", + "learning center", + "leasing service", + "leather exporter", + "leather wholesaler", + "lebanese restaurant", + "lechon restaurant", + "legal services", + "leisure centre", + "lesbian bar", + "lexus dealer", + "license bureau", + "life coach", + "lighting consultant", + "lighting contractor", + "lighting manufacturer", + "lighting store", + "ligurian restaurant", + "limousine service", + "linens store", + "lingerie manufacturer", + "lingerie store", + "lingerie wholesaler", + "linoleum store", + "liquor store", + "literacy program", + "lithuanian restaurant", + "livery company", + "livestock breeder", + "livestock dealer", + "livestock producer", + "loan agency", + "lock store", + "locks supplier", + "log cabins", + "logging contractor", + "logistics service", + "lombardian restaurant", + "loss adjuster", + "lottery retailer", + "lottery shop", + "love hotel", + "lpg conversion", + "luggage store", + "luggage wholesaler", + "lumber store", + "lunch restaurant", + "lutheran church", + "machine construction", + "machine shop", + "machine workshop", + "machining manufacturer", + "macrobiotic restaurant", + "madrilian restaurant", + "magazine store", + "magic store", + "mailbox supplier", + "mailing service", + "majorcan restaurant", + "make-up artist", + "malaysian restaurant", + "maltese restaurant", + "mammography service", + "manado restaurant", + "management school", + "mandarin restaurant", + "manor house", + "maori organization", + "map store", + "mapping service", + "marathi restaurant", + "marble contractor", + "marble supplier", + "marche restaurant", + "marine engineer", + "marine surveyor", + "maritime museum", + "market researcher", + "marketing agency", + "marketing consultant", + "marriage celebrant", + "maserati dealer", + "masonry contractor", + "massage parlor", + "massage school", + "massage service", + "massage spa", + "massage therapist", + "maternity hospital", + "maternity store", + "mathematics school", + "mattress store", + "mausoleum builder", + "maybach dealer", + "mazda dealer", + "mclaren dealer", + "meal delivery", + "meat packer", + "meat processor", + "meat wholesaler", + "mechanical contractor", + "mechanical engineer", + "mechanical plant", + "media company", + "media consultant", + "media house", + "mediation service", + "medical center", + "medical centre", + "medical clinic", + "medical examiner", + "medical group", + "medical laboratory", + "medical lawyer", + "medical office", + "medical school", + "medical spa", + "medicine exporter", + "meditation center", + "meditation instructor", + "mediterranean restaurant", + "mehandi class", + "mehndi designer", + "memorial estate", + "memorial park", + "men's tailor", + "mennonite church", + "mens tailor", + "mercantile development", + "mercedes-benz dealer", + "messianic synagogue", + "metal fabricator", + "metal finisher", + "metal supplier", + "metal workshop", + "metallurgy company", + "metalware dealer", + "metalware producer", + "methodist church", + "mexican restaurant", + "mg dealer", + "middle school", + "military base", + "military board", + "military cemetery", + "military hospital", + "military school", + "military town", + "millwork shop", + "mini dealer", + "miniatures store", + "mining company", + "mining consultant", + "mining engineer", + "mining equipment", + "mirror shop", + "mitsubishi dealer", + "mobile caterer", + "model shop", + "modeling agency", + "modeling school", + "mold maker", + "molding supplier", + "momo restaurant", + "monogramming service", + "montessori school", + "monument maker", + "moped dealer", + "moravian church", + "moroccan restaurant", + "mortgage broker", + "mortgage lender", + "motorcycle dealer", + "motorcycle shop", + "motoring club", + "motorsports store", + "mountain cabin", + "mountain peak", + "mountaineering class", + "movie studio", + "movie theater", + "moving company", + "mri center", + "muffler shop", + "mughlai restaurant", + "mulch supplier", + "municipal guard", + "murtabak restaurant", + "music college", + "music conservatory", + "music instructor", + "music producer", + "music publisher", + "music school", + "music store", + "musical club", + "nail salon", + "nasi restaurant", + "national forest", + "national library", + "national museum", + "national park", + "national reserve", + "nature preserve", + "naturopathic practitioner", + "naval base", + "navarraise restaurant", + "neapolitan restaurant", + "needlework shop", + "neonatal physician", + "nepalese restaurant", + "netball club", + "news service", + "newspaper publisher", + "nicaraguan restaurant", + "night club", + "night market", + "nissan dealer", + "non-denominational church", + "non-governmental organization", + "non-profit organization", + "noodle shop", + "norwegian restaurant", + "notaries association", + "notary public", + "notions store", + "novelties wholesaler", + "novelty store", + "nudist club", + "nudist park", + "nurse practitioner", + "nursery school", + "nursing agency", + "nursing association", + "nursing home", + "nursing school", + "nut store", + "nyonya restaurant", + "oaxacan restaurant", + "observation deck", + "occupational therapist", + "oden restaurant", + "odia restaurant", + "oil refinery", + "okonomiyaki restaurant", + "oldsmobile dealer", + "opel dealer", + "open university", + "opera company", + "opera house", + "ophthalmology clinic", + "optical wholesaler", + "oral surgeon", + "orchid farm", + "orchid grower", + "organic farm", + "organic restaurant", + "organic shop", + "orthodox church", + "orthodox synagogue", + "orthopedic clinic", + "orthopedic surgeon", + "otolaryngology clinic", + "outdoor bath", + "outerwear store", + "outlet mall", + "outlet store", + "oyster supplier", + "paan shop", + "package locker", + "packaging company", + "padang restaurant", + "padel club", + "padel court", + "paint manufacturer", + "paint store", + "paintball center", + "paintball store", + "painting lessons", + "painting studio", + "paintings store", + "paisa restaurant", + "pakistani restaurant", + "palatine restaurant", + "pallet supplier", + "pan-asian restaurant", + "pancake restaurant", + "panipuri shop", + "paper distributor", + "paper exporter", + "paper mill", + "paper store", + "paraguayan restaurant", + "parking garage", + "parking grounds", + "parking lot", + "parkour spot", + "parochial school", + "parsi restaurant", + "parsi temple", + "party planner", + "party store", + "passport agent", + "passport office", + "pasta shop", + "pastry shop", + "patent attorney", + "patent office", + "paving contractor", + "pawn shop", + "payroll service", + "pedestrian zone", + "pediatric cardiologist", + "pediatric clinic", + "pediatric dentist", + "pediatric dermatologist", + "pediatric endocrinologist", + "pediatric gastroenterologist", + "pediatric hematologist", + "pediatric nephrologist", + "pediatric neurologist", + "pediatric oncologist", + "pediatric ophthalmologist", + "pediatric pulmonologist", + "pediatric rheumatologist", + "pediatric surgeon", + "pediatric urologist", + "pempek restaurant", + "pen store", + "pension office", + "pentecostal church", + "perfume store", + "perinatal center", + "persian restaurant", + "personal trainer", + "peruvian restaurant", + "pet cemetery", + "pet groomer", + "pet shop", + "pet sitter", + "pet store", + "pet trainer", + "petrol station", + "peugeot dealer", + "pharmaceutical company", + "pharmaceutical lab", + "philharmonic hall", + "pho restaurant", + "photo agency", + "photo booth", + "photo lab", + "photo shop", + "photography class", + "photography school", + "photography service", + "photography studio", + "physical therapist", + "physician assistant", + "physiotherapy center", + "piadina restaurant", + "piano bar", + "piano instructor", + "piano maker", + "piano store", + "pickleball court", + "picnic ground", + "pie shop", + "piedmontese restaurant", + "pig farm", + "pilaf restaurant", + "pilates studio", + "pilgrim hostel", + "pipe supplier", + "pizza delivery", + "pizza restaurant", + "pizza takeaway", + "pizza takeout", + "plant nursery", + "plastic surgeon", + "plastic wholesaler", + "plating service", + "plywood supplier", + "poke bar", + "police academy", + "police department", + "polish restaurant", + "polo club", + "polygraph service", + "polymer supplier", + "polynesian restaurant", + "polytechnic institute", + "pond contractor", + "pontiac dealer", + "pony club", + "pool hall", + "popcorn store", + "porridge restaurant", + "porsche dealer", + "port authority", + "portrait studio", + "portuguese restaurant", + "post office", + "postal code", + "poster store", + "pottery classes", + "pottery manufacturer", + "pottery store", + "poultry farm", + "poultry store", + "power station", + "pozole restaurant", + "prawn fishing", + "precision engineer", + "preparatory school", + "presbyterian church", + "press advisory", + "pretzel store", + "primary school", + "print shop", + "private college", + "private hospital", + "private investigator", + "private tutor", + "private university", + "probation office", + "process server", + "produce market", + "produce wholesaler", + "professional association", + "professional organizer", + "propane supplier", + "propeller shop", + "property consultant", + "property developer", + "property investment", + "property maintenance", + "protected area", + "protestant church", + "provence restaurant", + "psychiatric hospital", + "psychomotor therapist", + "psychopedagogy clinic", + "public bath", + "public bathroom", + "public beach", + "public housing", + "public library", + "public sauna", + "public university", + "pueblan restaurant", + "pump supplier", + "pumpkin patch", + "punjabi restaurant", + "puppet theater", + "quaker church", + "quantity surveyor", + "quilt shop", + "raclette restaurant", + "racquetball club", + "radiator shop", + "radio broadcaster", + "rail museum", + "railing contractor", + "railroad company", + "railroad contractor", + "railway services", + "rajasthani restaurant", + "ram dealer", + "ramen restaurant", + "real estate", + "record company", + "record store", + "recording studio", + "recreation center", + "recycling center", + "reenactment site", + "reform synagogue", + "reformed church", + "refrigerator store", + "refugee camp", + "regional airport", + "regional council", + "registration office", + "registry office", + "rehabilitation center", + "rehearsal studio", + "reiki therapist", + "religious destination", + "religious institution", + "religious lodging", + "religious organization", + "religious school", + "renault dealer", + "renovation contractor", + "repair service", + "reptile store", + "research engineer", + "research foundation", + "research institute", + "residential building", + "residential college", + "residents association", + "resort hotel", + "rest stop", + "resume service", + "retirement community", + "retirement home", + "retreat center", + "rice mill", + "rice restaurant", + "rice shop", + "rice wholesaler", + "river port", + "road cycling", + "rock climbing", + "rock shop", + "roller coaster", + "roman restaurant", + "romanian restaurant", + "roofing contractor", + "roofing service", + "rowing area", + "rowing club", + "rsl club", + "rug store", + "rugby club", + "rugby field", + "rugby store", + "running store", + "russian restaurant", + "rv dealer", + "rv park", + "saab dealer", + "sailing club", + "sailing school", + "sake brewery", + "salad shop", + "salsa bar", + "salsa classes", + "salvadoran restaurant", + "salvage dealer", + "salvage yard", + "samba school", + "sambo school", + "sand plant", + "sandblasting service", + "sandwich shop", + "sanitary inspection", + "sanitation service", + "sardinian restaurant", + "saree shop", + "sashimi restaurant", + "satay restaurant", + "saturn dealer", + "sauna club", + "sauna store", + "savings bank", + "saw mill", + "scale supplier", + "scandinavian restaurant", + "scenic spot", + "scenography company", + "school cafeteria", + "school center", + "school house", + "science museum", + "scottish restaurant", + "scout hall", + "scout home", + "scrapbooking store", + "screen printer", + "screen store", + "screw supplier", + "scuba instructor", + "sculpture museum", + "seafood farm", + "seafood market", + "seafood restaurant", + "seafood wholesaler", + "seal shop", + "seaplane base", + "seat dealer", + "seblak restaurant", + "secondary school", + "security service", + "seed supplier", + "self-catering accommodation", + "self-storage facility", + "serbian restaurant", + "service establishment", + "serviced accommodation", + "serviced apartment", + "sewing company", + "sewing shop", + "seychelles restaurant", + "sfiha restaurant", + "shanghainese restaurant", + "sharpening service", + "shawarma restaurant", + "shed builder", + "sheep shearer", + "sheltered housing", + "shelving store", + "shinto shrine", + "shipping company", + "shipping service", + "shochu brewery", + "shoe factory", + "shoe shop", + "shoe store", + "shogi lesson", + "shooting range", + "shopping centre", + "shopping mall", + "shredding service", + "shrimp farm", + "sichuan restaurant", + "sicilian restaurant", + "siding contractor", + "sign shop", + "signwriting service", + "silk store", + "singaporean restaurant", + "singles organization", + "skate shop", + "skateboard shop", + "skating instructor", + "ski club", + "ski resort", + "ski school", + "ski shop", + "skittle club", + "skoda dealer", + "skydiving center", + "skylight contractor", + "sleep clinic", + "smart dealer", + "smart shop", + "smoke shop", + "snack bar", + "snowboard shop", + "snowmobile dealer", + "soccer club", + "soccer field", + "soccer practice", + "soccer store", + "social club", + "social worker", + "sod supplier", + "sofa store", + "softball club", + "softball field", + "software company", + "soondae restaurant", + "soto restaurant", + "soup kitchen", + "soup restaurant", + "soup shop", + "souvenir manufacturer", + "souvenir store", + "spa garden", + "spanish restaurant", + "special educator", + "specialized clinic", + "specialized hospital", + "speech pathologist", + "sperm bank", + "spice exporter", + "spice store", + "spice wholesaler", + "spices exporter", + "spiritist center", + "sports bar", + "sports club", + "sports complex", + "sports school", + "sportswear store", + "sportwear manufacturer", + "spring supplier", + "squash club", + "squash court", + "stair contractor", + "stamp shop", + "stand bar", + "state archive", + "state park", + "state parliament", + "state police", + "stationery manufacturer", + "stationery store", + "stationery wholesaler", + "std clinic", + "steak house", + "steamboat restaurant", + "steel distributor", + "steel erector", + "steel fabricator", + "sticker manufacturer", + "stitching class", + "stock broker", + "stone carving", + "stone cutter", + "stone supplier", + "storage facility", + "structural engineer", + "stucco contractor", + "student dormitory", + "student union", + "studying center", + "subaru dealer", + "subway station", + "sugar factory", + "sugar shack", + "sukiyaki restaurant", + "sunblind supplier", + "sundae restaurant", + "sundanese restaurant", + "sunglasses store", + "sunroom contractor", + "superannuation consultant", + "superfund site", + "support group", + "surf school", + "surf shop", + "surgical center", + "surgical oncologist", + "surinamese restaurant", + "surplus store", + "sushi restaurant", + "sushi takeaway", + "suzuki dealer", + "swabian restaurant", + "swedish restaurant", + "swim club", + "swimming basin", + "swimming competition", + "swimming facility", + "swimming instructor", + "swimming lake", + "swimming pool", + "swimming school", + "swimwear store", + "swiss restaurant", + "syrian restaurant", + "t-shirt store", + "tabascan restaurant", + "tacaca restaurant", + "tack shop", + "taco restaurant", + "taekwondo school", + "taiwanese restaurant", + "takeout restaurant", + "takoyaki restaurant", + "talent agency", + "tamale shop", + "tanning salon", + "taoist temple", + "tapas bar", + "tapas restaurant", + "tatami store", + "tattoo artist", + "tattoo shop", + "tax assessor", + "tax attorney", + "tax consultant", + "tax department", + "tax preparation", + "taxi service", + "taxi stand", + "taxicab stand", + "tb clinic", + "tea exporter", + "tea house", + "tea manufacturer", + "tea store", + "tea wholesaler", + "teachers college", + "technical school", + "technical university", + "technology museum", + "technology park", + "tegal restaurant", + "telecommunication school", + "telecommunications contractor", + "telecommunications engineer", + "telemarketing service", + "telephone company", + "telephone exchange", + "telescope store", + "television station", + "temaki restaurant", + "temp agency", + "tempura restaurant", + "tenant ownership", + "tennis club", + "tennis court", + "tennis instructor", + "tennis store", + "teppanyaki restaurant", + "tesla showroom", + "tex-mex restaurant", + "textile engineer", + "textile exporter", + "textile merchant", + "textile mill", + "thai restaurant", + "theater company", + "theater production", + "theme park", + "thermal baths", + "thread supplier", + "thrift store", + "thuringian restaurant", + "tibetan restaurant", + "tiffin center", + "tiki bar", + "tile contractor", + "tile manufacturer", + "tile store", + "timeshare agency", + "tire service", + "tire shop", + "title company", + "toast restaurant", + "tobacco shop", + "tobacco supplier", + "tofu restaurant", + "tofu shop", + "toiletries store", + "toll station", + "tongue restaurant", + "tonkatsu restaurant", + "tool manufacturer", + "tool store", + "tool wholesaler", + "topography company", + "topsoil supplier", + "tortilla shop", + "tour agency", + "tour operator", + "tourist attraction", + "towing service", + "townhouse complex", + "toy library", + "toy manufacturer", + "toy museum", + "toy store", + "toyota dealer", + "tractor dealer", + "trade school", + "trading company", + "traditional market", + "traditional teahouse", + "traffic officer", + "trailer dealer", + "trailer manufacturer", + "train depot", + "train station", + "train yard", + "training center", + "training centre", + "training consultant", + "training provider", + "tram stop", + "transcription service", + "transit depot", + "transit station", + "transit stop", + "translation service", + "transmission shop", + "transplant surgeon", + "transport hub", + "transportation service", + "travel agency", + "travel agent", + "travel clinic", + "travel lounge", + "tree farm", + "tree service", + "trial attorney", + "tribal headquarters", + "trolleybus stop", + "trophy shop", + "truck dealer", + "truck farmer", + "truck stop", + "trucking company", + "truss manufacturer", + "trust bank", + "tunisian restaurant", + "turf supplier", + "turkish restaurant", + "turkmen restaurant", + "tuscan restaurant", + "tutoring service", + "tuxedo shop", + "typewriter supplier", + "typing service", + "tyre manufacturer", + "ukrainian restaurant", + "unagi restaurant", + "underwear store", + "unemployment office", + "uniform store", + "unity church", + "university department", + "university hospital", + "university library", + "upholstery shop", + "urology clinic", + "uruguayan restaurant", + "utility contractor", + "valencian restaurant", + "vaporizer store", + "variety store", + "vascular surgeon", + "vastu consultant", + "vegan restaurant", + "vegetable wholesaler", + "vegetarian restaurant", + "vehicle exporter", + "vehicle repair", + "venetian restaurant", + "venezuelan restaurant", + "veterans center", + "veterans hospital", + "veterans organization", + "veterinary care", + "veterinary pharmacy", + "video arcade", + "video karaoke", + "video store", + "vietnamese restaurant", + "village hall", + "vineyard church", + "violin shop", + "visitor center", + "vocal instructor", + "vocational school", + "volkswagen dealer", + "volleyball club", + "volleyball court", + "volleyball instructor", + "volunteer organization", + "volvo dealer", + "waldorf kindergarten", + "waldorf school", + "walk-in clinic", + "wallpaper installer", + "wallpaper store", + "war museum", + "warehouse club", + "warehouse store", + "watch manufacturer", + "watch store", + "water mill", + "water park", + "water works", + "waterbed store", + "waterproofing service", + "wax museum", + "wax supplier", + "weaving mill", + "web designer", + "website designer", + "wedding bakery", + "wedding buffet", + "wedding chapel", + "wedding photographer", + "wedding planner", + "wedding service", + "wedding store", + "wedding venue", + "weigh station", + "weightlifting area", + "wellness center", + "wellness hotel", + "wellness program", + "welsh restaurant", + "wesleyan church", + "western restaurant", + "wheel store", + "wheelchair store", + "wholesale bakery", + "wholesale drugstore", + "wholesale florist", + "wholesale grocer", + "wholesale jeweler", + "wholesale market", + "wi-fi spot", + "wicker store", + "wig shop", + "wildlife park", + "wildlife refuge", + "wind farm", + "window supplier", + "windsurfing store", + "wine bar", + "wine cellar", + "wine club", + "wine store", + "wok restaurant", + "wood supplier", + "wool store", + "wrestling school", + "x-ray lab", + "yacht broker", + "yacht club", + "yakiniku restaurant", + "yakisoba restaurant", + "yakitori restaurant", + "yarn store", + "yemeni restaurant", + "yoga instructor", + "yoga studio", + "youth center", + "youth club", + "youth hostel", + "youth organization", + "yucatan restaurant", + "3d printing service", + "aboriginal art gallery", + "abundant life church", + "acrobatic diving pool", + "addiction treatment center", + "adult dvd store", + "adult education school", + "adult entertainment club", + "adult entertainment store", + "adventure sports center", + "aerated drinks supplier", + "aerial sports center", + "aero dance class", + "african goods store", + "after school program", + "agricultural high school", + "agricultural machinery manufacturer", + "agricultural product wholesaler", + "air compressor supplier", + "air conditioning contractor", + "air conditioning store", + "air filter supplier", + "air force base", + "airbrushing supply store", + "aircraft maintenance company", + "aircraft rental service", + "aircraft supply store", + "airline ticket agency", + "airport shuttle service", + "alcohol retail monopoly", + "alcoholic beverage wholesaler", + "alcoholism treatment program", + "alfa romeo dealer", + "alternative fuel station", + "alternative medicine clinic", + "alternative medicine practitioner", + "aluminum frames supplier", + "american grocery store", + "amish furniture store", + "amusement machine supplier", + "amusement park ride", + "amusement ride supplier", + "angler fish restaurant", + "animal control service", + "animal feed store", + "animal protection organization", + "animal rescue service", + "animal watering hole", + "antique furniture store", + "apartment rental agency", + "appliance parts supplier", + "appliance rental service", + "appliance repair service", + "appliances customer service", + "architectural salvage store", + "armed forces association", + "aromatherapy supply store", + "art restoration service", + "art supply store", + "artificial plant supplier", + "asbestos testing service", + "asian fusion restaurant", + "asian grocery store", + "asphalt mixing plant", + "assisted living facility", + "association / organization", + "aston martin dealer", + "attorney referral service", + "atv rental service", + "atv repair shop", + "audio visual consultant", + "australian goods store", + "auto accessories wholesaler", + "auto body shop", + "auto bodywork mechanic", + "auto chemistry shop", + "auto electrical service", + "auto glass shop", + "auto insurance agency", + "auto machine shop", + "auto parts manufacturer", + "auto parts market", + "auto parts store", + "auto repair shop", + "auto restoration service", + "auto rickshaw stand", + "auto spring shop", + "auto sunroof shop", + "auto tag agency", + "automobile storage facility", + "aviation training institute", + "ayam penyet restaurant", + "baby clothing store", + "baby swimming school", + "bail bonds service", + "baking supply store", + "ballroom dance instructor", + "banking and finance", + "bar stool supplier", + "barber supply store", + "baseball goods store", + "basketball court contractor", + "bathroom supply store", + "batik clothing store", + "batting cage center", + "beach cleaning service", + "beach clothing store", + "beach entertainment shop", + "beach volleyball club", + "beach volleyball court", + "beauty product supplier", + "beauty products wholesaler", + "beauty supply store", + "bed & breakfast", + "bedroom furniture store", + "bee relocation service", + "bicycle rental service", + "bicycle repair shop", + "bike sharing station", + "bikram yoga studio", + "billiards supply store", + "bird control service", + "bird watching area", + "birth certificate service", + "birth control center", + "blast cleaning service", + "blood donation center", + "blood testing service", + "bmw motorcycle dealer", + "board game club", + "board of education", + "boat accessories supplier", + "boat cleaning service", + "boat cover supplier", + "boat detailing service", + "boat rental service", + "boat repair shop", + "boat storage facility", + "boat tour agency", + "boat trailer dealer", + "bocce ball court", + "body piercing shop", + "body shaping class", + "bonsai plant supplier", + "boot repair shop", + "border crossing station", + "bottled water supplier", + "bouncy castle hire", + "bowling supply shop", + "box lunch supplier", + "boys' high school", + "bpo placement agency", + "brewing supply store", + "bubble tea store", + "buddhist supplies store", + "building materials market", + "building materials store", + "building materials supplier", + "building restoration service", + "bungee jumping center", + "burglar alarm store", + "bus ticket agency", + "bus tour agency", + "business administration service", + "business banking service", + "business development service", + "business management consultant", + "business networking company", + "butane gas supplier", + "butcher shop deli", + "cabin rental agency", + "calvary chapel church", + "camera repair shop", + "camper shell supplier", + "cancer treatment center", + "cane furniture store", + "cape verdean restaurant", + "car accessories store", + "car alarm supplier", + "car battery store", + "car detailing service", + "car inspection station", + "car leasing service", + "car rental agency", + "car sharing location", + "car stereo store", + "career guidance service", + "carpet cleaning service", + "carriage ride service", + "cat boarding service", + "cell phone store", + "central american restaurant", + "central european restaurant", + "central heating service", + "central javanese restaurant", + "certified public accountant", + "chamber of agriculture", + "chamber of commerce", + "chamber of handicrafts", + "champon noodle restaurant", + "check cashing service", + "chicken wings restaurant", + "child care agency", + "children's amusement center", + "children's clothing store", + "children's furniture store", + "children's health service", + "children's party buffet", + "children's party service", + "chinese language instructor", + "chinese language school", + "chinese medicine clinic", + "chinese medicine store", + "chinese noodle restaurant", + "chinese tea house", + "christian book store", + "christmas tree farm", + "church of christ", + "church supply store", + "cig kofte restaurant", + "cinema equipment supplier", + "citizen information bureau", + "city district office", + "city employment department", + "city government office", + "city tax office", + "civil engineering company", + "civil examinations academy", + "civil law attorney", + "cleaning products supplier", + "clock repair service", + "closed circuit television", + "clothing alteration service", + "coast guard station", + "coffee machine supplier", + "coffee vending machine", + "coin operated locker", + "cold cut store", + "cold noodle restaurant", + "cold storage facility", + "college of agriculture", + "comic book store", + "commercial refrigerator supplier", + "commissioner for oaths", + "community health center", + "community health centre", + "comprehensive secondary school", + "computer accessories store", + "computer desk store", + "computer hardware manufacturer", + "computer networking service", + "computer repair service", + "computer security service", + "computer software store", + "computer training school", + "concrete product supplier", + "condominium rental agency", + "conservatory of music", + "construction equipment supplier", + "construction machine dealer", + "construction material wholesaler", + "consumer advice center", + "contact lenses supplier", + "contemporary louisiana restaurant", + "convention information bureau", + "copier repair service", + "copying supply store", + "corporate gift supplier", + "cosmetic products manufacturer", + "cost accounting service", + "costa rican restaurant", + "costume jewelry shop", + "costume rental service", + "country food restaurant", + "county government office", + "court executive officer", + "crane rental agency", + "creative cuisine restaurant", + "credit counseling service", + "credit reporting agency", + "crime victim service", + "criminal justice attorney", + "crushed stone supplier", + "cured ham bar", + "cured ham store", + "cured ham warehouse", + "currency exchange service", + "custom home builder", + "custom label printer", + "custom t-shirt store", + "cycle rickshaw stand", + "dart supply store", + "data entry service", + "data recovery service", + "database management company", + "day care center", + "debris removal service", + "debt collection agency", + "delivery chinese restaurant", + "dental implants periodontist", + "dental implants provider", + "dental insurance agency", + "dental supply store", + "denture care center", + "department of housing", + "department of transportation", + "designer clothing store", + "desktop publishing service", + "diabetes equipment supplier", + "diesel engine dealer", + "diesel fuel supplier", + "digital printing service", + "dim sum restaurant", + "direct mail advertising", + "disability equipment supplier", + "disc golf course", + "display stand manufacturer", + "disposable tableware supplier", + "distance learning center", + "district government office", + "dj supply store", + "dogsled ride service", + "doll restoration service", + "doner kebab restaurant", + "double glazing installer", + "drafting equipment supplier", + "dried flower shop", + "dried seafood store", + "drilling equipment supplier", + "drinking water fountain", + "driver's license office", + "driving test center", + "drug testing service", + "dry fruit store", + "dry ice supplier", + "dry wall contractor", + "ds automobiles dealer", + "dump truck dealer", + "dumpster rental service", + "duty free store", + "e commerce agency", + "ear piercing service", + "earth works company", + "east african restaurant", + "east javanese restaurant", + "eastern european restaurant", + "eastern orthodox church", + "economic development agency", + "educational supply store", + "educational testing service", + "eftpos equipment supplier", + "elder law attorney", + "electric bicycle store", + "electric generator shop", + "electric motor store", + "electric motorcycle dealer", + "electric utility company", + "electrical appliance wholesaler", + "electrical equipment supplier", + "electrical installation service", + "electrical products wholesaler", + "electrical repair shop", + "electrical supply store", + "electronic engineering service", + "electronic parts supplier", + "electronics accessories wholesaler", + "electronics hire shop", + "electronics repair shop", + "electronics vending machine", + "emergency care physician", + "emergency care service", + "emergency dental service", + "emergency locksmith service", + "emergency training school", + "emergency veterinarian service", + "engine rebuilding service", + "english language camp", + "english language school", + "environmental health service", + "environmental protection organization", + "equipment rental agency", + "escape room center", + "estate planning attorney", + "event management company", + "event planning service", + "event technology service", + "event ticket seller", + "executive search firm", + "exercise equipment store", + "extended stay hotel", + "eye care center", + "fabric product manufacturer", + "factory equipment supplier", + "faculty of law", + "faculty of pharmacy", + "faculty of science", + "family law attorney", + "family planning center", + "family planning counselor", + "family practice physician", + "family service center", + "farm equipment supplier", + "farm household tour", + "fashion accessories shop", + "fashion accessories store", + "fashion design school", + "fast food restaurant", + "federal credit union", + "federal government office", + "felt boots store", + "fence supply store", + "feng shui consultant", + "feng shui shop", + "fiberglass repair service", + "filipino grocery store", + "film production company", + "fine dining restaurant", + "finishing materials supplier", + "fire alarm supplier", + "fire fighters academy", + "fire protection consultant", + "fire protection service", + "first aid station", + "fitness equipment wholesaler", + "fitted furniture supplier", + "flamenco dance store", + "floor refinishing service", + "fmcg goods wholesaler", + "foam rubber producer", + "foam rubber supplier", + "folk high school", + "food and drink", + "food machinery supplier", + "food manufacturing supply", + "food processing company", + "food processing equipment", + "food products supplier", + "food seasoning manufacturer", + "foot massage parlor", + "foreign trade consultant", + "foreman builders association", + "forklift rental service", + "formal wear store", + "fortune telling services", + "foster care service", + "free parking lot", + "freight forwarding service", + "french language school", + "french steakhouse restaurant", + "fresh food market", + "fried chicken takeaway", + "frozen dessert supplier", + "frozen food manufacturer", + "frozen food store", + "frozen yogurt shop", + "full gospel church", + "function room facility", + "funeral celebrant service", + "fur coat shop", + "furnace parts supplier", + "furnace repair service", + "furnished apartment building", + "furniture accessories supplier", + "furniture rental service", + "furniture repair shop", + "garage door supplier", + "garbage collection service", + "garden building supplier", + "garden machinery supplier", + "gas cylinders supplier", + "gas installation service", + "gas logs supplier", + "gay night club", + "general education school", + "general practice attorney", + "geological research company", + "german language school", + "gift basket store", + "gift wrap store", + "girls' high school", + "glass block supplier", + "glass cutting service", + "glass etching service", + "glass repair service", + "glasses repair service", + "gold mining company", + "golf cart dealer", + "golf course builder", + "golf driving range", + "gourmet grocery store", + "government economic program", + "government ration shop", + "graffiti removal service", + "greek orthodox church", + "green energy supplier", + "greeting card shop", + "grocery delivery service", + "gutter cleaning service", + "gypsum product supplier", + "hair extension technician", + "hair extensions supplier", + "hair removal service", + "hair replacement service", + "hair transplantation clinic", + "handicapped transportation service", + "hang gliding center", + "haute french restaurant", + "hawaiian goods store", + "head start center", + "health food restaurant", + "health food store", + "health insurance agency", + "hearing aid store", + "heating equipment supplier", + "heating oil supplier", + "helicopter tour agency", + "helium gas supplier", + "herbal medicine store", + "high ropes course", + "higher secondary school", + "historical place museum", + "hiv testing center", + "hockey supply store", + "holiday apartment rental", + "holistic medicine practitioner", + "home audio store", + "home automation company", + "home cinema installation", + "home furniture shop", + "home goods store", + "home improvement store", + "home insurance agency", + "home staging service", + "home theater store", + "horse boarding stable", + "horse rental service", + "horse riding field", + "horse riding school", + "horse trailer dealer", + "horseback riding service", + "hospitality high school", + "hot bedstone spa", + "hot dog restaurant", + "hot dog stand", + "hot pot restaurant", + "hot tub store", + "hotel management school", + "hotel supply store", + "house cleaning service", + "house clearance service", + "house sitter agency", + "houseboat rental service", + "household chemicals supplier", + "household goods wholesaler", + "housing utility company", + "hua gong shop", + "hub cap supplier", + "human resource consulting", + "hydraulic equipment supplier", + "hydraulic repair service", + "hydroelectric power plant", + "hydroponics equipment supplier", + "hygiene articles wholesaler", + "hyperbaric medicine physician", + "ice cream shop", + "ice hockey club", + "ice skating club", + "ice skating instructor", + "ice skating rink", + "ikan bakar restaurant", + "import export company", + "indian grocery store", + "indian motorcycle dealer", + "indian muslim restaurant", + "indian sizzler restaurant", + "indian sweets shop", + "indoor golf course", + "indoor swimming pool", + "industrial chemicals wholesaler", + "industrial design company", + "industrial engineers association", + "industrial equipment supplier", + "industrial gas supplier", + "infectious disease physician", + "institute of technology", + "insulation materials store", + "intellectual property registry", + "interior architect office", + "interior construction contractor", + "interior fitting contractor", + "interior plant service", + "internal medicine ward", + "international trade consultant", + "internet marketing service", + "internet service provider", + "invitation printing service", + "irish goods store", + "iron ware dealer", + "irrigation equipment supplier", + "italian grocery store", + "janitorial equipment supplier", + "japanese confectionery shop", + "japanese curry restaurant", + "japanese grocery store", + "japanese language instructor", + "japanese regional restaurant", + "japanese sweets restaurant", + "japanese-style business hotel", + "japanized western restaurant", + "jewelry equipment supplier", + "jewelry repair service", + "junk removal service", + "juvenile detention center", + "kalle pache restaurant", + "kawasaki motorcycle dealer", + "key duplication service", + "kitchen furniture store", + "kitchen supply store", + "korean barbecue restaurant", + "korean beef restaurant", + "korean grocery store", + "korean rib restaurant", + "kosher grocery store", + "kung fu school", + "labor relations attorney", + "laboratory equipment supplier", + "ladies' clothes shop", + "laminating equipment supplier", + "lamp repair service", + "lamp shade supplier", + "land planning authority", + "land reform institute", + "land rover dealer", + "land surveying office", + "landscape lighting designer", + "landscaping supply store", + "laser cutting service", + "laser equipment supplier", + "laser tag center", + "latin american restaurant", + "law book store", + "lawn bowls club", + "lawn care service", + "lawn mower store", + "leather cleaning service", + "leather coats store", + "leather goods manufacturer", + "leather goods store", + "leather goods supplier", + "leather goods wholesaler", + "leather repair service", + "legal affairs bureau", + "life insurance agency", + "light bulb supplier", + "lighting products wholesaler", + "line marking service", + "little league club", + "little league field", + "live music bar", + "live music venue", + "livestock auction house", + "local government office", + "local history museum", + "local medical services", + "log home builder", + "lost property office", + "luggage repair service", + "luggage storage facility", + "lymph drainage therapist", + "machine knife supplier", + "machine maintenance service", + "machine repair service", + "machinery parts manufacturer", + "mailbox rental service", + "mailing machine supplier", + "main customs office", + "manufactured home transporter", + "marine supply store", + "marquee hire service", + "marriage license bureau", + "martial arts club", + "martial arts school", + "masonry supply store", + "massage supply store", + "match box manufacturer", + "measuring instruments supplier", + "meat dish restaurant", + "meat products store", + "medical billing service", + "medical book store", + "medical certificate service", + "medical equipment manufacturer", + "medical equipment supplier", + "medical supply store", + "medical technology manufacturer", + "medical transcription service", + "meeting planning service", + "men's clothes shop", + "men's clothing store", + "men's health physician", + "mental health clinic", + "mental health service", + "metal construction company", + "metal industry suppliers", + "metal machinery supplier", + "metal polishing service", + "metal processing company", + "metal stamping service", + "metal working shop", + "metaphysical supply store", + "metropolitan train company", + "mexican goods store", + "mexican grocery store", + "mexican torta restaurant", + "middle eastern restaurant", + "military recruiting office", + "milk delivery service", + "mineral water company", + "miniature golf course", + "minibus taxi service", + "ministry of education", + "miso cutlet restaurant", + "missing persons organization", + "mobile disco service", + "mobile home dealer", + "mobile home park", + "mobile money agent", + "mobile network operator", + "mobile phone shop", + "mobility equipment supplier", + "model design company", + "model portfolio studio", + "model train store", + "modern art museum", + "modern british restaurant", + "modern european restaurant", + "modern french restaurant", + "modern indian restaurant", + "modern izakaya restaurant", + "modular home builder", + "modular home dealer", + "money order service", + "money transfer service", + "mongolian barbecue restaurant", + "motor scooter dealer", + "motor vehicle dealer", + "motorcycle driving school", + "motorcycle insurance agency", + "motorcycle parts store", + "motorcycle rental agency", + "motorcycle repair shop", + "mountain cable car", + "movie rental kiosk", + "movie rental store", + "moving supply store", + "municipal administration office", + "museum of zoology", + "music box store", + "musical instrument manufacturer", + "musical instrument store", + "musician and composer", + "mutton barbecue restaurant", + "nasi goreng restaurant", + "nasi uduk restaurant", + "native american restaurant", + "natural goods store", + "natural history museum", + "natural stone exporter", + "natural stone supplier", + "natural stone wholesaler", + "neon sign shop", + "new age church", + "new american restaurant", + "new england restaurant", + "new zealand restaurant", + "newspaper distribution service", + "non vegetarian restaurant", + "north african restaurant", + "north indian restaurant", + "northern italian restaurant", + "nuclear power company", + "nuclear power plant", + "nuevo latino restaurant", + "occupational health service", + "occupational medical physician", + "off roading area", + "offal barbecue restaurant", + "office accessories wholesaler", + "office equipment supplier", + "office furniture store", + "office refurbishment service", + "office supply store", + "office supply wholesaler", + "offset printing service", + "oil change service", + "olive oil cooperative", + "olive oil manufacturer", + "open air museum", + "optical products manufacturer", + "organic drug store", + "organic food store", + "oriental goods store", + "oriental medicine clinic", + "oriental medicine store", + "oriental rug store", + "orthopedic shoe store", + "orthopedic supplies store", + "outboard motor store", + "outdoor activity organiser", + "outdoor equestrian facility", + "outdoor furniture store", + "outdoor sports store", + "outdoor swimming pool", + "oxygen cocktail spot", + "oxygen equipment supplier", + "oyster bar restaurant", + "pacific rim restaurant", + "packaging supply store", + "pain control clinic", + "pain management physician", + "paint stripping service", + "painter and decorator", + "paper bag supplier", + "paralegal services provider", + "park and garden", + "park and ride", + "passport photo processor", + "paternity testing service", + "patients support association", + "patio enclosure supplier", + "paving materials supplier", + "pecel lele restaurant", + "pennsylvania dutch restaurant", + "performing arts group", + "performing arts theater", + "permanent make-up clinic", + "personal chef service", + "personal injury attorney", + "personal injury lawyer", + "personal watercraft dealer", + "pest control service", + "pet adoption service", + "pet boarding service", + "pet care service", + "pet moving service", + "pet supply store", + "petroleum products company", + "pharmaceutical products wholesaler", + "phone repair service", + "photo restoration service", + "physical examination center", + "physical fitness program", + "physical rehabilitation center", + "physical therapy clinic", + "physician referral service", + "physiotherapy equipment supplier", + "piano moving service", + "piano repair service", + "piano tuning service", + "picture frame shop", + "pinball machine supplier", + "pine furniture shop", + "place of worship", + "plast window store", + "plastic bag supplier", + "plastic bags wholesaler", + "plastic fabrication company", + "plastic products supplier", + "plastic products wholesaler", + "plastic resin manufacturer", + "plastic surgery clinic", + "playground equipment supplier", + "plumbing supply store", + "pneumatic tools supplier", + "police supply store", + "political party office", + "pond fish supplier", + "pond supply store", + "pony ride service", + "pool billard club", + "pool cleaning service", + "port operating company", + "portable building manufacturer", + "portable toilet supplier", + "powder coating service", + "power plant consultant", + "powersports vehicle dealer", + "practitioner service location", + "pregnancy care center", + "pressure washing service", + "printed music publisher", + "printer repair service", + "printing equipment supplier", + "private educational institution", + "private equity firm", + "private golf course", + "private sector bank", + "promotional products supplier", + "property administration service", + "property investment company", + "property management company", + "protective clothing supplier", + "psychoneurological specialized clinic", + "psychosomatic medical practitioner", + "public defender's office", + "public educational institution", + "public golf course", + "public health department", + "public medical center", + "public parking space", + "public prosecutors office", + "public relations firm", + "public safety office", + "public sector bank", + "public swimming pool", + "public works department", + "puerto rican restaurant", + "pvc windows supplier", + "race car dealer", + "radiator repair service", + "raft trip outfitter", + "railroad equipment supplier", + "railroad ties supplier", + "rainwater tank supplier", + "rare book store", + "raw food restaurant", + "real estate agency", + "real estate agent", + "real estate appraiser", + "real estate attorney", + "real estate auctioneer", + "real estate consultant", + "real estate developer", + "real estate school", + "real estate surveyor", + "records storage facility", + "recycling drop-off location", + "refrigerated transport service", + "refrigerator repair service", + "regional government office", + "registered general nurse", + "religious book store", + "religious goods store", + "renter's insurance agency", + "reproductive health clinic", + "restaurant or cafe", + "restaurant supply store", + "retaining wall supplier", + "rice cake shop", + "rice cracker shop", + "road construction company", + "road safety town", + "rock climbing gym", + "rock climbing instructor", + "rock music club", + "roller skating club", + "roller skating rink", + "roofing supply store", + "roommate referral service", + "rubber products supplier", + "rubber stamp store", + "rugby league club", + "russian grocery store", + "russian orthodox church", + "rustic furniture store", + "rv detailing service", + "rv repair shop", + "rv storage facility", + "rv supply store", + "safety equipment supplier", + "sailing event area", + "satellite communication service", + "saw sharpening service", + "scaffolding rental service", + "scale model club", + "scale repair service", + "school administration office", + "school bus service", + "school district office", + "school supply store", + "scientific equipment supplier", + "scooter rental service", + "scooter repair shop", + "scrap metal dealer", + "screen printing shop", + "screen repair service", + "scuba tour agency", + "seasonal goods store", + "second hand store", + "security guard service", + "security system supplier", + "self defense school", + "self service restaurant", + "self storage facility", + "semi conductor supplier", + "senior citizen center", + "senior high school", + "septic system service", + "seventh-day adventist church", + "sewage disposal service", + "sewage treatment plant", + "sewing machine store", + "sheet metal contractor", + "sheet music store", + "shipping equipment industry", + "shoe repair shop", + "shoe shining service", + "shooting event area", + "shower door shop", + "sightseeing tour agency", + "silk plant shop", + "singing telegram service", + "sixth form college", + "skate sharpening service", + "skeet shooting range", + "ski rental service", + "ski repair service", + "skin care clinic", + "small plates restaurant", + "smart car dealer", + "smog inspection station", + "snow removal service", + "snowboard rental service", + "snowmobile rental service", + "soba noodle shop", + "social security attorney", + "social security office", + "social services organization", + "social welfare center", + "societe de flocage", + "soft drinks shop", + "software training institute", + "soil testing service", + "solar energy company", + "solid fuel company", + "solid waste engineer", + "soto ayam restaurant", + "soul food restaurant", + "south african restaurant", + "south american restaurant", + "south asian restaurant", + "south indian restaurant", + "south sulawesi restaurant", + "southeast asian restaurant", + "southern italian restaurant", + "southern restaurant (us)", + "soy sauce maker", + "space of remembrance", + "special education school", + "sport tour agency", + "sporting goods store", + "sports accessories wholesaler", + "sports activity location", + "sports card store", + "sports injury clinic", + "sports massage therapist", + "sports medicine clinic", + "sports medicine physician", + "sports memorabilia store", + "sports nutrition store", + "sri lankan restaurant", + "stained glass studio", + "stainless steel plant", + "stall installation service", + "stamp collectors club", + "staple food package", + "state employment department", + "state government office", + "state liquor store", + "state owned farm", + "std testing service", + "steamed bun shop", + "steel construction company", + "steel framework contractor", + "steelwork design service", + "stereo rental store", + "stereo repair service", + "stock exchange building", + "store equipment supplier", + "stores and shopping", + "student housing center", + "students parents association", + "students support association", + "suburban train line", + "summer camp organizer", + "summer toboggan run", + "super public bath", + "supplementary educational institute", + "surf lifesaving club", + "surgical products wholesaler", + "surgical supply store", + "suzuki motorcycle dealer", + "swimming pool contractor", + "table tennis club", + "table tennis facility", + "tai chi school", + "tata motors dealer", + "tattoo removal service", + "tax collector's office", + "tax preparation service", + "tea market place", + "teeth whitening service", + "telecommunications equipment supplier", + "telecommunications service provider", + "telephone answering service", + "television repair service", + "tent rental service", + "thai massage therapist", + "theater supply store", + "theatrical costume supplier", + "tile cleaning service", + "tire repair shop", + "toner cartridge supplier", + "tool rental service", + "tool repair shop", + "tourist information center", + "towing equipment provider", + "tractor repair shop", + "trading card store", + "traditional american restaurant", + "traditional costume club", + "traditional kostume store", + "trailer rental service", + "trailer repair shop", + "trailer supply store", + "train repairing center", + "train ticket agency", + "transportation escort service", + "triumph motorcycle dealer", + "tropical fish store", + "truck accessories store", + "truck driving school", + "truck parts supplier", + "truck rental agency", + "truck repair shop", + "truck topper supplier", + "tsukigime parking lot", + "tune up supplier", + "typewriter repair service", + "udon noodle restaurant", + "unfinished furniture store", + "unitarian universalist church", + "united methodist church", + "upholstery cleaning service", + "urban planning department", + "urgent care center", + "used appliance store", + "used bicycle shop", + "used book store", + "used car dealer", + "used cd store", + "used clothing store", + "used computer store", + "used furniture store", + "used game store", + "used motorcycle dealer", + "used tire shop", + "used truck dealer", + "utility trailer dealer", + "uyghur cuisine restaurant", + "vacuum cleaner store", + "valet parking service", + "van rental agency", + "vcr repair service", + "vegetable wholesale market", + "vehicle inspection service", + "vehicle repair shop", + "vehicle shipping agent", + "vehicle wrapping service", + "vending machine supplier", + "ventilating equipment manufacturer", + "venture capital company", + "veterans affairs department", + "video conferencing service", + "video duplication service", + "video editing service", + "video game store", + "video production service", + "vintage clothing store", + "vinyl sign shop", + "virtual office rental", + "visa consulting service", + "vocational gymnasium school", + "vocational secondary school", + "voter registration office", + "waste management service", + "waste transfer station", + "watch repair service", + "water cooler supplier", + "water filter supplier", + "water polo pool", + "water pump supplier", + "water purification company", + "water ski shop", + "water skiing club", + "water skiing instructor", + "water skiing service", + "water testing service", + "water treatment plant", + "water treatment supplier", + "water utility company", + "waterbed repair service", + "weather forecast service", + "web hosting company", + "wedding souvenir shop", + "weight loss service", + "welding gas supplier", + "welding supply store", + "well drilling contractor", + "west african restaurant", + "western apparel store", + "wheel alignment service", + "wheelchair rental service", + "wheelchair repair service", + "wholesale food store", + "wholesale plant nursery", + "wholesaler household appliances", + "wildlife rescue service", + "willow basket manufacturer", + "wind turbine builder", + "window cleaning service", + "window installation service", + "window tinting service", + "window treatment store", + "wine storage facility", + "winemaking supply store", + "wing chun school", + "women's clothing store", + "women's health clinic", + "women's personal trainer", + "wood frame supplier", + "wood stove shop", + "wood working class", + "woodworking supply store", + "work clothes store", + "yamaha motorcycle dealer", + "yoga retreat center", + "youth care service", + "youth clothing store", + "adult day care center", + "adult foster care service", + "air compressor repair service", + "air conditioning repair service", + "air conditioning system supplier", + "air duct cleaning service", + "antique furniture restoration service", + "asian household goods store", + "assemblies of god church", + "audio visual equipment supplier", + "audiovisual equipment rental service", + "auto air conditioning service", + "auto body parts supplier", + "auto care products store", + "auto dent removal service", + "auto glass repair service", + "auto radiator repair service", + "auto tune up service", + "auto window tinting service", + "balloon ride tour agency", + "bar restaurant furniture store", + "beauty products vending machine", + "building equipment hire service", + "business to business service", + "cake decorating equipment shop", + "canoe & kayak store", + "canoe and kayak club", + "car security system installer", + "cardiovascular and thoracic surgeon", + "carport and pergola builder", + "cash and carry wholesaler", + "cell phone accessory store", + "cell phone charging station", + "chess and card club", + "child health care centre", + "church of the nazarene", + "city department of transportation", + "classified ads newspaper publisher", + "clock and watch maker", + "clothes and fabric manufacturer", + "clothes and fabric wholesaler", + "clothing wholesale market place", + "coach and minibus hire", + "commercial real estate agency", + "commercial real estate inspector", + "compressed natural gas station", + "computer support and services", + "concrete metal framework supplier", + "construction machine rental service", + "conveyor belt sushi restaurant", + "curtain supplier and maker", + "custom confiscated goods store", + "dairy farm equipment supplier", + "dan dan noodle restaurant", + "dealer of fiat professional", + "department of motor vehicles", + "department of public safety", + "department of social services", + "diesel engine repair service", + "disciples of christ church", + "dog day care center", + "domestic abuse treatment center", + "drivers license training school", + "dry wall supply store", + "dryer vent cleaning service", + "eating disorder treatment center", + "electric motor repair shop", + "electric motor scooter dealer", + "electric motor vehicle dealer", + "electric vehicle charging station", + "electrolysis hair removal service", + "energy equipment and solutions", + "environment renewable natural resources", + "executive suite rental agency", + "exhibition and trade centre", + "family day care service", + "farm equipment repair service", + "farming and cattle raising", + "fiber optic products supplier", + "film and photograph library", + "fire damage restoration service", + "fire department equipment supplier", + "fire protection equipment supplier", + "fire protection system supplier", + "fish & chips restaurant", + "fish and chips takeaway", + "food and beverage consultant", + "food and beverage exporter", + "foreign exchange students organization", + "foreign languages program school", + "fruit and vegetable processing", + "fruit and vegetable store", + "fruit and vegetable wholesaler", + "full dress rental service", + "glass & mirror shop", + "ground self defense force", + "guardia di finanza police", + "haute couture fashion house", + "health and beauty shop", + "hearing aid repair service", + "hearing assistance earphone store", + "hip hop dance class", + "home health care service", + "home help service agency", + "hospital equipment and supplies", + "hospitality and tourism school", + "hot tub repair service", + "hot water system supplier", + "hua niao market place", + "hunting and fishing store", + "ice cream equipment supplier", + "immigration & naturalization service", + "income protection insurance agency", + "income tax help association", + "industrial real estate agency", + "industrial technical engineers association", + "industrial vacuum equipment supplier", + "iron and steel store", + "it support and services", + "japanese cheap sweets shop", + "jehovah's witness kingdom hall", + "karaoke equipment rental service", + "kilt shop and hire", + "kushiage and kushikatsu restaurant", + "laser hair removal service", + "lawn equipment rental service", + "lawn irrigation equipment supplier", + "lawn mower repair service", + "lawn sprinkler system contractor", + "learner driver training area", + "license plate frames supplier", + "low income housing program", + "marine self defense force", + "marriage or relationship counselor", + "martial arts supply store", + "material handling equipment supplier", + "medical diagnostic imaging center", + "metal detecting equipment supplier", + "metal heat treating service", + "microwave oven repair service", + "mobile home rental agency", + "mobile home supply store", + "mobile phone repair shop", + "model car play area", + "motor scooter repair shop", + "moving and storage service", + "muay thai boxing gym", + "municipal department of tourism", + "museum of space history", + "music management and promotion", + "musical instrument rental service", + "musical instrument repair shop", + "native american goods store", + "natural rock climbing area", + "non smoking holiday home", + "north eastern indian restaurant", + "occupational safety and health", + "off track betting shop", + "offal pot cooking restaurant", + "office equipment rental service", + "office equipment repair service", + "office space rental agency", + "oil field equipment supplier", + "olive oil bottling company", + "optical instrument repair service", + "oral and maxillofacial surgeon", + "orthotics & prosthetics service", + "paper shredding machine supplier", + "parking lot for bicycles", + "parking lot for motorcycles", + "party equipment rental service", + "pay by weight restaurant", + "plant and machinery hire", + "plastic injection molding service", + "plus size clothing store", + "power plant equipment supplier", + "printer ink refill store", + "professional and hobby associations", + "qing fang market place", + "racing car parts store", + "ready mix concrete supplier", + "real estate rental agency", + "recreational vehicle rental agency", + "research and product development", + "retail space rental agency", + "rolled metal products supplier", + "safe & vault shop", + "sand & gravel supplier", + "sand and gravel supplier", + "school for the deaf", + "screen printing supply store", + "security system installation service", + "self service car wash", + "self service health station", + "sewing machine repair service", + "shipbuilding and repair company", + "shipping and mailing service", + "shop supermarket furniture store", + "single sex secondary school", + "small appliance repair service", + "small claims assistance service", + "small engine repair service", + "social security financial department", + "solar energy equipment supplier", + "solar energy system service", + "solar panel maintenance service", + "solar photovoltaic power plant", + "south east asian restaurant", + "spa and health club", + "sports equipment rental service", + "stage lighting equipment supplier", + "state office of education", + "student career counseling office", + "study at home school", + "sweets and dessert buffet", + "swimming pool repair service", + "swimming pool supply store", + "syokudo and teishoku restaurant", + "table tennis supply store", + "tattoo and piercing shop", + "tea and coffee shop", + "tennis court construction company", + "threads and yarns wholesaler", + "tool & die shop", + "trade fair construction company", + "united church of canada", + "united church of christ", + "used auto parts store", + "used musical instrument store", + "used office furniture store", + "used store fixture supplier", + "vacation home rental agency", + "vacuum cleaner repair shop", + "vacuum cleaning system supplier", + "vegetarian cafe and deli", + "video camera repair service", + "video conferencing equipment supplier", + "video equipment repair service", + "video game rental kiosk", + "video game rental store", + "visa and passport office", + "vitamin & supplements store", + "washer & dryer store", + "water damage restoration service", + "water jet cutting service", + "water softening equipment supplier", + "water tank cleaning service", + "water works equipment supplier", + "waxing hair removal service", + "wedding dress rental service", + "whale watching tour agency", + "wildlife and safari park", + "wine wholesaler and importer", + "wood floor installation service", + "wood floor refinishing service", + "youth social services organization", + "architectural and engineering model maker", + "army & navy surplus shop", + "audio visual equipment repair service", + "bottle & can redemption center", + "canoe & kayak tour agency", + "car finance and loan company", + "car repair and maintenance service", + "catering food and drink supplier", + "coin operated laundry equipment supplier", + "combined primary and secondary school", + "disability services and support organization", + "dress and tuxedo rental service", + "electric vehicle charging station contractor", + "electronics retail and repair shop", + "federal agency for technical relief", + "flavours fragrances and aroma supplier", + "floor sanding and polishing service", + "ice cream and drink shop", + "industrial spares and products wholesaler", + "institute of geography and statistics", + "multimedia and electronic book publisher", + "oil & natural gas company", + "oil and gas exploration service", + "organ donation and tissue bank", + "outdoor clothing and equipment shop", + "pet food and animal feeds", + "pick your own farm produce", + "polythene and plastic sheeting supplier", + "road construction machine repair service", + "sheepskin and wool products supplier", + "short term apartment rental agency", + "skin care products vending machine", + "solar hot water system supplier", + "sukiyaki and shabu shabu restaurant", + "united states armed forces base", + "washer & dryer repair service", + "water sports equipment rental service", + "wood and laminate flooring supplier", + "aboriginal and torres strait islander organisation", + "hong kong style fast food restaurant", + "roads ports and canals engineers association", + "church of jesus christ of latter-day saints" + ] + }, + "example": [ + "pizza", + "italian" + ], + "sectionCaption": "🔍 Add-on: Search filters & categories", + "sectionDescription": "Narrow down which places get scraped.

Filters (exact name match, minimum rating, website, skip closed) are charged for each place returned, per filter you turn on - so the more filters you enable, the higher the cost.

The Categories field is charged only once, no matter how many categories you pick - so add as many relevant ones as you like (for example, Hotels and Seaside hotels) to catch more results.

⚠️ Categories can cause false negatives, as many places don't categorize themselves correctly.

Exact rates depend on your subscription plan - see the pricing details, or the category guide for more." + }, + "searchMatching": { + "title": "Get exact name matches (no similar results) ($)", + "description": "Restrict what places are scraped based on matching their name with provided 🔍 Search term. E.g., all places that have chicken in their name vs. places called Kentucky Fried Chicken.", + "enum": [ + "all", + "only_includes", + "only_exact" + ], + "enumTitles": [ + "Scrape all places as provided by Google", + "Scrape only places that include the search term in their title", + "Scrape only places that match the search term exactly with their title" + ], + "type": "string", + "editor": "select", + "default": "all", + "example": "all" + }, + "placeMinimumStars": { + "title": "Set a minimum star rating ($)", + "description": "Scrape only places with a rating equal to or above the selected stars. Places without reviews will also be skipped. Keep in mind, filtering by reviews reduces the number of places found per credit spent, as many will be excluded.", + "enum": [ + "", + "two", + "twoAndHalf", + "three", + "threeAndHalf", + "four", + "fourAndHalf" + ], + "enumTitles": [ + "All", + "⭐️ 2+ star", + "⭐️ 2.5+ stars", + "⭐️ 3+ stars", + "⭐️ 3.5+ stars", + "⭐️ 4+ stars", + "⭐️ 4.5+ stars" + ], + "type": "string", + "editor": "select", + "default": "", + "example": "" + }, + "website": { + "title": "Scrape places with/without a website ($)", + "description": "Use this to exclude places without a website, or vice versa. This option is turned off by default.", + "enum": [ + "allPlaces", + "withWebsite", + "withoutWebsite" + ], + "enumTitles": [ + "Scrape all places", + "Scrape only places with a website", + "Scrape only places without a website" + ], + "type": "string", + "editor": "select", + "default": "allPlaces", + "example": "allPlaces" + }, + "skipClosedPlaces": { + "title": "⏩ Skip closed places ($)", + "type": "boolean", + "description": "Skip places that are marked as temporary or permanently closed. Ideal for focusing on currently open places.", + "default": false, + "example": false + }, + "scrapePlaceDetailPage": { + "title": "Scrape place detail page ($)", + "type": "boolean", + "description": "Scrape detail pages of each place the Actor finds. This will slow down the Actor since it needs to open another page for each place individually.

The fields available only when scrapePlaceDetailPage is enabled include: `reviewsDistribution`, `reviewsRemovedNotice`, `imageCategories`, popularTimes fields, `openingHours`, `BusinessConfirmationText`, `peopleAlsoSearch`, `reviewsTags`, `updatesFromCustomers`, `questionsAndAnswers`, `tableReservationLinks`, `ownerUpdates` and hotel fields.

Enabling this also ensures that `reviewsCount` will be scraped.

This option needs to be enabled if you wish to use any of the options below.", + "default": false, + "example": false, + "sectionCaption": "📌 Add-on: Additional place details scraping", + "sectionDescription": "Extract additional details about each place beyond the basics.

Significant discounts are available on higher-tier plans. Please see the Pricing tab for your exact rate based on your subscription.

There’s one flat fee for adding the additional details, regardless of how many toggles are used or the number of questions extracted. This event is automatically applied when scraping reviews or images.

Enabling this also ensures that `reviewsCount` will be scraped." + }, + "scrapeTableReservationProvider": { + "title": "Scrape table reservation provider data ($)", + "type": "boolean", + "description": "Scrape table reservation provider data like name, address, email or phone. This data is present only in restaurants that have a blue \"RESERVE A TABLE\" button.", + "default": false, + "example": false + }, + "scrapeOrderOnline": { + "title": "🛵 Scrape order online widget data ($)", + "type": "boolean", + "description": "Scrape the 'Order online' section of restaurants to get pickup and delivery providers (Uber Eats, DoorDash, etc.), along with fees and estimated times.", + "default": false, + "example": false + }, + "includeWebResults": { + "title": "🌐 Include \"Web results\" ($)", + "type": "boolean", + "description": "Extract the \"Web results\" section located at the bottom of every place listing.", + "default": false, + "example": false + }, + "scrapeDirectories": { + "title": "🛍 Scrape inside places (e.g. malls or shopping center) ($)", + "type": "boolean", + "description": "Some places (e.g. malls) can have multiple businesses located inside them. This option will scrape inside the \"Directory\" or \"At this place\" as per different categories (example here). Turn this toggle on to include those places in your results.

⚠️ Note that full place details need to be scraped in order to scrape directories.", + "default": false, + "example": false + }, + "maxQuestions": { + "title": "Number of questions to extract ($)", + "type": "integer", + "description": "Set the number of questions per place you expect to scrape. If you fill in 0 or leave the field empty, only the first question and answer will be scraped. To extract all questions, type 999 into the field.

⚠️ Note that some of the fields contain personal data.", + "default": 0, + "minimum": 0, + "unit": "questions per place", + "example": 0 + }, + "scrapeContacts": { + "title": "⏩ Add-on: Company contacts enrichment (from website) ($)", + "type": "boolean", + "description": "Enrich Google Maps places with contact details extracted from the business website, including business emails and social media profiles (Meta, LinkedIn, X, etc.).

We exclude contacts of big chains: mcdonalds, starbucks, dominos, pizzahut, burgerking, kfc, subway, wendys, dunkindonuts, tacobell.", + "default": false, + "example": false, + "sectionCaption": "🏢 Add-on: Company contacts enrichment", + "sectionDescription": "In this section, you can enrich your output with comprehensive contact info and social media profile metrics.

Available features:
  • Company contacts enrichment: Scrapes the business website to find emails, phone numbers, and social media links.

  • Social media profile enrichment: Scrapes the discovered profiles to get detailed data like follower counts, descriptions, and verification status (supports Facebook, Instagram, YouTube, TikTok, X (Twitter)).

Pricing notice:
Significant discounts are available on higher-tier plans. Please see the Pricing tab for your exact rate based on your subscription.

Tip: Make sure to toggle \"scrape only places with a website\" in the \"Add-on: Search filters & categories\" section to optimize your cost." + }, + "scrapeSocialMediaProfiles": { + "title": "🔍 Add-on: Social media profile enrichment ($)", + "type": "object", + "description": "Enable enrichment for any social media profiles found. This add-on retrieves detailed public data for each profile, including profile names, follower/following counts, descriptions, post/video counts, and verification status.

You are charged a flat rate for the total number of profiles enriched, regardless of how many platforms (Facebook, YouTube, etc.) you select.
Feature dependency:
To use this feature, the Company contacts enrichment (from website) add-on is enabled automatically. This ensures the enriched social media data is combined with the main contact record for each domain.

Output: Enriched profiles are available in the Social profiles output view tab.", + "editor": "schemaBased", + "default": { + "facebooks": false, + "instagrams": false, + "youtubes": false, + "tiktoks": false, + "twitters": false + }, + "prefill": { + "facebooks": false, + "instagrams": false, + "youtubes": false, + "tiktoks": false, + "twitters": false + }, + "properties": { + "facebooks": { + "title": "Enable Facebook profile scraping", + "type": "boolean", + "description": "Enable scraping detailed Facebook profile information for discovered Facebook URLs. This will provide additional data like follower counts, profile pictures, and more." + }, + "instagrams": { + "title": "Enable Instagram profile scraping", + "type": "boolean", + "description": "Enable scraping detailed Instagram profile information for discovered Instagram URLs. This will provide additional data like follower counts, profile pictures, and more." + }, + "youtubes": { + "title": "Enable YouTube channel scraping", + "type": "boolean", + "description": "Enable scraping detailed YouTube channel information for discovered YouTube URLs. This will provide additional data like subscriber counts, channel descriptions, and more." + }, + "tiktoks": { + "title": "Enable TikTok profile scraping", + "type": "boolean", + "description": "Enable scraping detailed TikTok profile information for discovered TikTok URLs. This will provide additional data like follower counts, profile pictures, and more." + }, + "twitters": { + "title": "Enable X (Twitter) profile scraping", + "type": "boolean", + "description": "Enable scraping detailed Twitter profile information for discovered X (Twitter) URLs. This will provide additional data like follower counts, profile pictures, and more." + } + }, + "example": { + "facebooks": false, + "instagrams": false, + "youtubes": false, + "tiktoks": false, + "twitters": false + } + }, + "maximumLeadsEnrichmentRecords": { + "title": "⏩ Add-on: Extract business leads information - Maximum leads per place ($)", + "description": "Enrich your results with detailed contact and company information, including employee names, job titles, emails, phone numbers, LinkedIn profiles, and key company data like industry and number of employees.

This setting allows you to set the maximum number of leads records you want to scrape per each place found on the map (that has a website). By default, it's set to 0 which means that no leads information will be scraped.

⚠️ Note that some of the fields contain personal data. GDPR protects personal data in the European Union and by other regulations around the world. You should not scrape personal data unless you have a legitimate reason to do so. If you're unsure whether your use case is legitimate, please consult an attorney.

We exclude leads of big chains as these are not related to the local places: mcdonalds, starbucks, dominos, pizzahut, burgerking, kfc, subway, wendys, dunkindonuts, tacobell.", + "type": "integer", + "default": 0, + "minimum": 0, + "example": 0, + "prefill": 0, + "sectionCaption": "👥 Add-on: Business leads enrichment", + "sectionDescription": "In this section, you can enrich your output with detailed leads information for employees working at the business (place found), including their full name, work email address, phone number, job title, and LinkedIn profile. You will also get company data such as industry, number of employees, and more. The leads enrichment works only when the place has an associated website.

Significant discounts are available on higher-tier plans. Please see the Pricing tab for your exact rate based on your subscription.

⚠️ Important: How costs are calculated
The number of leads you request is per place found. Setting this to a high number can significantly increase your costs.

Example: Requesting 10 leads for a search that finds 1,000 places will result in an attempt to find 10,000 leads. You will only be charged for leads that are successfully found." + }, + "leadsEnrichmentDepartments": { + "title": "Leads departments selection", + "description": "You can use this filter to include only specific departments (like Sales, Marketing, or C-Suite). Note: This will only work if the ⏩ Add-on: Extract business leads information - Maximum leads per place (maximumLeadsEnrichmentRecords) option is enabled. Please note that some job titles are sometimes miscategorized in the wrong departments.", + "type": "array", + "editor": "select", + "items": { + "type": "string", + "enum": [ + "c_suite", + "product", + "engineering_technical", + "design", + "education", + "finance", + "human_resources", + "information_technology", + "legal", + "marketing", + "medical_health", + "operations", + "sales", + "consulting" + ], + "enumTitles": [ + "C-Suite", + "Product", + "Engineering & Technical", + "Design", + "Education", + "Finance", + "Human Resources", + "Information Technology", + "Legal", + "Marketing", + "Medical & Health", + "Operations", + "Sales", + "Consulting" + ] + }, + "example": [ + "sales", + "marketing" + ] + }, + "verifyLeadsEnrichmentEmails": { + "title": "⏩ Add-on: Email verification ($)", + "type": "boolean", + "description": "When enabled, verifies the email address of each lead extracted during business leads enrichment. Each lead receives an emailVerification object with the verification result and quality assessment.

Charged (decisive results): valid (ok), invalid, and disposable email addresses.
Not charged: catch-all, unknown, and error results.

⚠️ This add-on requires business leads enrichment to be enabled.", + "default": false + }, + "maxReviews": { + "title": "Number of reviews to extract ($)", + "type": "integer", + "description": "Set the number of reviews you expect to get per place.

Please be aware that the `Add-on: additional place details scraped` charge applies to each place you scrape for reviews, as the Actor must access the detail page first. All prices are determined by your subscription plan.

To extract all reviews, set this to 99999. If left empty, no reviews will be scraped.

Each output place item can contain maximum 5,000 reviews so in case more reviews are extracted, a duplicate place is stored with the next 5,000 reviews and so on.
⚠️ Enabling this feature might slow the search down.", + "default": 0, + "example": 0, + "minimum": 0, + "unit": "reviews per place", + "sectionCaption": "⭐️ Add-on: Reviews", + "sectionDescription": "If you want to extract reviews in your results, fill in the input fields below.

Significant discounts are available on higher-tier plans. Please see the Pricing tab for your exact rate based on your subscription.

Note that some of the fields contain **personal data**. GDPR protects personal data in the European Union and by other regulations around the world. You should not scrape personal data unless you have a legitimate reason to do so. If you're unsure whether your use case is legitimate, please consult an attorney." + }, + "reviewsStartDate": { + "title": "Only scrape reviews newer than [date]", + "type": "string", + "description": "Either absolute date (e.g. `2024-05-03`) or relative date from now into the past (e.g. `8 days`, `3 months`). JSON input also supports adding time in both absolute (ISO standard, e.g. `2024-05-03T20:00:00`) and relative (e.g. `3 hours`) formats. Absolute time is always interpreted in the UTC timezone, not your local timezone - please convert accordingly. Supported relative date & time units: `minutes`, `hours`, `days`, `weeks`, `months`, `years`.

⚠️ Heads up: If this parameter is specified, you must choose the 'Newest' sort by value. The reason for this is that with this parameter entered, the Actor stops scraping reviews as soon as it finds the first review that's older than the specified date. If the sorting is not set to 'Newest', it might encounter a review older than the specified date before it reaches the desired review count and not scrape the desired amount of reviews.", + "editor": "datepicker", + "example": "2024-01-01", + "dateType": "absoluteOrRelative", + "pattern": "^(\\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])(T[0-2]\\d:[0-5]\\d(:[0-5]\\d)?(\\.\\d+)?Z?)?$|^(\\d+)\\s*(minute|hour|day|week|month|year)s?$" + }, + "reviewsSort": { + "title": "Sort reviews by", + "description": "Define how reviews are sorted.", + "type": "string", + "editor": "select", + "default": "newest", + "enum": [ + "newest", + "mostRelevant", + "highestRanking", + "lowestRanking" + ], + "enumTitles": [ + "Newest", + "Most relevant", + "Highest ranking", + "Lowest ranking" + ], + "example": "newest" + }, + "reviewsFilterString": { + "title": "Filter reviews by keywords", + "type": "string", + "description": "If you enter keywords, only reviews containing those keywords will be scraped. Leave it blank to scrape all reviews.", + "default": "", + "editor": "textarea", + "example": "" + }, + "reviewsOrigin": { + "title": "Reviews origin", + "description": "Select whether you want all reviews (from Google, Tripadvisor, etc.) or only reviews from Google.", + "type": "string", + "editor": "select", + "default": "all", + "example": "all", + "enum": [ + "all", + "google" + ], + "enumTitles": [ + "All reviews", + "Google" + ] + }, + "scrapeReviewsPersonalData": { + "title": "🧛‍♂️ Include reviewers' data", + "type": "boolean", + "description": "This setting allows you to get personal data about the reviewer (their ID, name, URL, and photo URL) and about the review (URL). Note: review ID (reviewId) is always included regardless of this setting.

⚠️ Personal data is protected by the GDPR in the European Union and by other regulations around the world. You should not scrape personal data unless you have a legitimate reason to do so. If you're unsure whether your reason is legitimate, consult your lawyers.", + "default": true, + "example": true + }, + "maxImages": { + "title": "Number of additional images to extract ($)", + "type": "integer", + "description": "Set the number of images per place you expect to scrape.

Please be aware that the `Add-on: additional place details scraped` charge applies to each place you scrape for images, as the Actor must access the detail page first. All prices are determined by your subscription plan.

To extract all images, set this to 99999. If left empty, no images will be scraped. The higher the number, the slower the search.", + "default": 0, + "minimum": 0, + "unit": "images per place", + "example": 0, + "sectionCaption": "🖼️ Add-on: Images", + "sectionDescription": "Choose if you want to extract additional images.

The main image is already included by default and free of charge.

Significant discounts are available on higher-tier plans. Please see the Pricing tab for your exact rate based on your subscription." + }, + "scrapeImageAuthors": { + "title": "🧑‍🎨 Include the image authors", + "type": "boolean", + "description": "Include the author name for each image.

⚠️ Enabling this toggle may slow down processing as it requires fetching information for each image individually.", + "default": false, + "example": false + }, + "enableCompetitorAnalysis": { + "title": "Enable competitor analysis ($)", + "type": "boolean", + "description": "When enabled, runs an AI-powered competitor analysis on the top scraped places and stores a report in the competitorAnalysis named dataset. The report includes a ranked comparison, per-place strengths/weaknesses analysis with review-backed claims, social media insights, and a strategic market overview.

⚠️ Best results with a single, focused search term. The analysis ranks and compares all collected places against each other, so they should all be in the same business category and market segment (e.g. italian restaurants Vienna). Using multiple search terms that span different categories (e.g. restaurants + coffee shops + hair salons) will produce a meaningless comparison. Multiple search terms are fine as long as they target the same segment (e.g. sushi restaurants NYC + japanese restaurants NYC).

⚙️ What gets charged when you enable this (for all scraped places, capped at 100 max):
  • ($) Additional place details - for all places in the run.
  • ($) Company contacts enrichment - for all places in the run (used to discover social media profiles).
  • ($) At least 100 reviews - per place. Reviews provide sources and citations in the report. The AI uses the first 100 reviews for analysis; any extras are included in the output but not used for analysis.
  • ($) Social media profile enrichment - for all places in the run.
  • ($) Competitor analysis event - one per place.
", + "default": false, + "sectionCaption": "🤖 Add-on: Competitor analysis ($)", + "sectionDescription": "Produces a ranked comparison of the top scraped places:
  • Per-place strengths/weaknesses with review-backed claims
  • Social media insights
  • Aspect-by-aspect comparisons
  • Strategic market overview
Per place, this automatically charges:
  • Additional place details
  • Company contacts enrichment
  • At least 100 reviews
  • Social media profile enrichment
  • One Competitor analysis event
" + }, + "maxCompetitorsToAnalyze": { + "title": "Max competitors to analyze", + "type": "integer", + "description": "Total number of places to scrape and analyze, capped across all search terms combined. Capped at 100 - this directly bounds your total analysis cost, since every scraped place generates charges. Set to 0 to skip the analysis even if enableCompetitorAnalysis is on.", + "default": 30, + "minimum": 0, + "maximum": 100, + "example": 30, + "prefill": 30 + }, + "countryCode": { + "title": "🗺 Country", + "type": "string", + "description": "Set the country, e.g., United States.", + "editor": "select", + "enum": [ + "", + "us", + "af", + "al", + "dz", + "as", + "ad", + "ao", + "ai", + "aq", + "ag", + "ar", + "am", + "aw", + "au", + "at", + "az", + "bs", + "bh", + "bd", + "bb", + "by", + "be", + "bz", + "bj", + "bm", + "bt", + "bo", + "ba", + "bw", + "bv", + "br", + "io", + "bn", + "bg", + "bf", + "bi", + "kh", + "cm", + "ca", + "cv", + "ky", + "cf", + "td", + "cl", + "cn", + "cx", + "cc", + "co", + "km", + "cg", + "cd", + "ck", + "cr", + "ci", + "hr", + "cu", + "cy", + "cz", + "dk", + "dj", + "dm", + "do", + "ec", + "eg", + "sv", + "gq", + "er", + "ee", + "et", + "fk", + "fo", + "fj", + "fi", + "fr", + "gf", + "pf", + "tf", + "ga", + "gm", + "ge", + "de", + "gh", + "gi", + "gr", + "gl", + "gd", + "gp", + "gu", + "gt", + "gn", + "gw", + "gy", + "ht", + "hm", + "va", + "hn", + "hu", + "is", + "in", + "id", + "ir", + "iq", + "ie", + "il", + "it", + "jm", + "jp", + "jo", + "kz", + "ke", + "ki", + "kp", + "kr", + "kw", + "kg", + "la", + "lv", + "lb", + "ls", + "lr", + "ly", + "li", + "lt", + "lu", + "mo", + "mk", + "mg", + "mw", + "my", + "mv", + "ml", + "mt", + "mh", + "mq", + "mr", + "mu", + "yt", + "mx", + "fm", + "md", + "mc", + "mn", + "me", + "ms", + "ma", + "mz", + "mm", + "na", + "nr", + "np", + "nl", + "an", + "nc", + "nz", + "ni", + "ne", + "ng", + "nu", + "nf", + "mp", + "no", + "om", + "pk", + "pw", + "ps", + "pa", + "pg", + "py", + "pe", + "ph", + "pn", + "pl", + "pt", + "pr", + "qa", + "re", + "ro", + "ru", + "rw", + "sh", + "kn", + "lc", + "pm", + "vc", + "ws", + "sm", + "st", + "sa", + "sn", + "rs", + "sc", + "sl", + "sg", + "sk", + "si", + "sb", + "so", + "za", + "gs", + "ss", + "es", + "lk", + "sd", + "sr", + "sj", + "sz", + "se", + "ch", + "sy", + "tw", + "tj", + "tz", + "th", + "tl", + "tg", + "tk", + "to", + "tt", + "tn", + "tr", + "tm", + "tc", + "tv", + "ug", + "ua", + "ae", + "gb", + "um", + "uy", + "uz", + "vu", + "ve", + "vn", + "vg", + "vi", + "wf", + "eh", + "ye", + "zm", + "zw" + ], + "enumTitles": [ + "", + "United States", + "Afghanistan", + "Albania", + "Algeria", + "American Samoa", + "Andorra", + "Angola", + "Anguilla", + "Antarctica", + "Antigua and Barbuda", + "Argentina", + "Armenia", + "Aruba", + "Australia", + "Austria", + "Azerbaijan", + "Bahamas", + "Bahrain", + "Bangladesh", + "Barbados", + "Belarus", + "Belgium", + "Belize", + "Benin", + "Bermuda", + "Bhutan", + "Bolivia", + "Bosnia and Herzegovina", + "Botswana", + "Bouvet Island", + "Brazil", + "British Indian Ocean Territory", + "Brunei Darussalam", + "Bulgaria", + "Burkina Faso", + "Burundi", + "Cambodia", + "Cameroon", + "Canada", + "Cape Verde", + "Cayman Islands", + "Central African Republic", + "Chad", + "Chile", + "China", + "Christmas Island", + "Cocos (Keeling) Islands", + "Colombia", + "Comoros", + "Congo-Brazzaville", + "Congo, Democratic Republic of the", + "Cook Islands", + "Costa Rica", + "Cote D'ivoire", + "Croatia", + "Cuba", + "Cyprus", + "Czech Republic", + "Denmark", + "Djibouti", + "Dominica", + "Dominican Republic", + "Ecuador", + "Egypt", + "El Salvador", + "Equatorial Guinea", + "Eritrea", + "Estonia", + "Ethiopia", + "Falkland Islands", + "Faroe Islands", + "Fiji", + "Finland", + "France", + "French Guiana", + "French Polynesia", + "French Southern Territories", + "Gabon", + "Gambia", + "Georgia", + "Germany", + "Ghana", + "Gibraltar", + "Greece", + "Greenland", + "Grenada", + "Guadeloupe", + "Guam", + "Guatemala", + "Guinea", + "Guinea-Bissau", + "Guyana", + "Haiti", + "Heard Island and Mcdonald Islands", + "Vatican City State", + "Honduras", + "Hungary", + "Iceland", + "India", + "Indonesia", + "Iran", + "Iraq", + "Ireland", + "Israel", + "Italy", + "Jamaica", + "Japan", + "Jordan", + "Kazakhstan", + "Kenya", + "Kiribati", + "Korea, Democratic People's Republic of", + "Korea", + "Kuwait", + "Kyrgyzstan", + "Laos", + "Latvia", + "Lebanon", + "Lesotho", + "Liberia", + "Libyan Arab Jamahiriya", + "Liechtenstein", + "Lithuania", + "Luxembourg", + "Macao", + "Macedonia", + "Madagascar", + "Malawi", + "Malaysia", + "Maldives", + "Mali", + "Malta", + "Marshall Islands", + "Martinique", + "Mauritania", + "Mauritius", + "Mayotte", + "Mexico", + "Micronesia", + "Moldova", + "Monaco", + "Mongolia", + "Montenegro", + "Montserrat", + "Morocco", + "Mozambique", + "Myanmar", + "Namibia", + "Nauru", + "Nepal", + "Netherlands", + "Netherlands Antilles", + "New Caledonia", + "New Zealand", + "Nicaragua", + "Niger", + "Nigeria", + "Niue", + "Norfolk Island", + "Northern Mariana Islands", + "Norway", + "Oman", + "Pakistan", + "Palau", + "Palestine", + "Panama", + "Papua New Guinea", + "Paraguay", + "Peru", + "Philippines", + "Pitcairn", + "Poland", + "Portugal", + "Puerto Rico", + "Qatar", + "Reunion", + "Romania", + "Russian Federation", + "Rwanda", + "Saint Helena", + "Saint Kitts and Nevis", + "Saint Lucia", + "Saint Pierre and Miquelon", + "Saint Vincent and the Grenadines", + "Samoa", + "San Marino", + "Sao Tome and Principe", + "Saudi Arabia", + "Senegal", + "Serbia", + "Seychelles", + "Sierra Leone", + "Singapore", + "Slovakia", + "Slovenia", + "Solomon Islands", + "Somalia", + "South Africa", + "South Georgia and the South Sandwich Islands", + "South Sudan", + "Spain", + "Sri Lanka", + "Sudan", + "Suriname", + "Svalbard and Jan Mayen", + "Swaziland", + "Sweden", + "Switzerland", + "Syrian Arab Republic", + "Taiwan", + "Tajikistan", + "Tanzania", + "Thailand", + "Timor-Leste", + "Togo", + "Tokelau", + "Tonga", + "Trinidad and Tobago", + "Tunisia", + "Turkey", + "Turkmenistan", + "Turks and Caicos Islands", + "Tuvalu", + "Uganda", + "Ukraine", + "United Arab Emirates", + "United Kingdom", + "United States Minor Outlying Islands", + "Uruguay", + "Uzbekistan", + "Vanuatu", + "Venezuela", + "Viet Nam", + "Virgin Islands, British", + "Virgin Islands, U.S.", + "Wallis and Futuna", + "Western Sahara", + "Yemen", + "Zambia", + "Zimbabwe" + ], + "example": "US", + "sectionCaption": "📡 Geolocation parameters*", + "sectionDescription": "If free text 📍Location doesn't yield desired area, you can try a combination of specific location types or custom geolocation (polygon or circle). Just make sure to clear the 📍Location field first, as it always has priority.

You can customize your area by:
1. using combinations of specific 🗺 location types (Country, City, State, County, and Postal code). You can always check with the Structured tab of OpenStreetMap webapp for validation of location types you want to cover.
2. defining a 🛰 Custom search area with pairs of coordinates (shaped like polygon or circle). Tutorial here." + }, + "city": { + "title": "🌇 City", + "type": "string", + "description": "Enter the city, e.g., Pittsburgh.

⚠️ Do not include State or Country names here.

⚠️ Automatic City polygons may be smaller than expected (e.g., they don't include agglomeration areas).", + "editor": "textfield", + "example": "New York" + }, + "state": { + "title": "State", + "type": "string", + "description": "Set a state, e.g., Massachusetts (mainly for the US addresses).", + "editor": "textfield", + "example": "New York" + }, + "county": { + "title": "County", + "type": "string", + "description": "Set the county, e.g., New York County.

⚠️ Note that county may represent different administrative areas in different countries: a county (e.g., US), regional district (e.g., Canada) or département (e.g., France).", + "editor": "textfield", + "example": "New York County" + }, + "postalCode": { + "title": "Postal code", + "type": "string", + "description": "Set the postal code, e.g., 10001.

⚠️ Combine Postal code only with 🗺 Country, never with 🌇 City. You can only input one postal code at a time.", + "editor": "textfield", + "example": "10001" + }, + "customGeolocation": { + "title": "🛰 Custom search area (coordinate order must be: [↕ longitude, ↔ latitude])", + "type": "object", + "editor": "json", + "description": "Use this field to define the exact search area if other search area parameters don't work for you. See readme or the Apify guide for details.", + "example": { + "type": "Point", + "coordinates": [ + -73.9857, + 40.7484 + ] + } + }, + "startUrls": { + "title": "Google Maps URLs", + "type": "array", + "description": "Max 300 results per search URL. Valid format for URLs contains https://google.com/maps/. This feature also supports uncommon URL formats such as: https://google.com/maps?cid=***, https://goo.gl/maps/***, and custom place list URL.", + "editor": "requestListSources", + "example": [ + { + "url": "https://www.google.com/maps/place/Yellowstone+National+Park/@44.5857951,-110.5140571,9z/data=!3m1!4b1!4m5!3m4!1s0x5351e55555555555:0xaca8f930348fe1bb!8m2!3d44.427963!4d-110.588455?hl=en-GB" + } + ], + "sectionCaption": "🔗 Scrape with Google Maps URLs or place IDs*", + "sectionDescription": "Use this section to copy URLs directly from Google Maps and paste them here. Valid format for URLs contains https://google.com/maps/. Each URL can get you a maximum of 300 results. To overcome this limit, use the 📡 Geolocation parameters* section above.

In addition to URLs, you can also provide place IDs. A place ID has the format ChIJreV9aqYWdkgROM_boL6YbwA - only 27-character IDs starting with ChIJ or GhIJ are supported.

Note that the Actor charges the Add-on: additional place details scraped event for each place ID or URL that points to a specific place on Google Maps. The event price depends on your subscription plan.

⚠️ Don't combine this section with the 🔍 Search term as the 🔗 URLs always have priority." + }, + "placeIds": { + "title": "🗃 Place IDs", + "type": "array", + "description": "List of place IDs. Add them one by one or upload a list using the Bulk edit option. A place ID has the format ChIJreV9aqYWdkgROM_boL6YbwA - only 27-character IDs starting with ChIJ or GhIJ are supported.", + "editor": "stringList", + "items": { + "type": "string", + "pattern": "\\S+" + }, + "example": [ + "ChIJabcdEFGHijklMNOPqrstUVWX" + ] + }, + "allPlacesNoSearchAction": { + "title": "Scrape all places", + "description": "Extract all places visible on the map. A good zoom is chosen automatically based on the area size. If you want to override it, set `zoom` (a number from 1 to 21) in JSON input. Higher zoom will scrape more places but will take longer to finish. You can test what place pins are visible with a specific zoom by changing the 16z part of the Google Maps URL.", + "enum": [ + "", + "all_places_no_search_ocr", + "all_places_no_search_mouse" + ], + "enumTitles": [ + "Not applied (normal search or direct places)", + "Scrape all places visible on the map (ignore search terms)", + "DEPRECATED: Scrape by Moving mouse (very slow)" + ], + "type": "string", + "editor": "select", + "default": "", + "example": "", + "sectionCaption": "🧭 Scraping places without search terms or URLs*", + "sectionDescription": "Will scrape all places visible on the map. If you want to override the default zoom (based on area size), you need to use `zoom` (a number from 1 to 21) in JSON input. Higher zoom will scrape more places but will take longer to finish. You can test what place pins are visible with a specific zoom by changing the 16z part of the Google Maps URL." + } + }, + "required": [] +} \ No newline at end of file diff --git a/test/local/lib/schema-to-ts/compile.test.ts b/test/local/lib/schema-to-ts/compile.test.ts new file mode 100644 index 000000000..cbf36d696 --- /dev/null +++ b/test/local/lib/schema-to-ts/compile.test.ts @@ -0,0 +1,223 @@ +// oxlint-disable import/default -- `?raw` is a Vite import; the rule resolves it to the .ts file and finds no default. +import { describe, expect, test } from 'vitest'; + +import { + check, + compile, + normalizeDatasetSchema, + normalizeInputSchema, + type CompileOptions, +} from '../../../../src/lib/schema-to-ts/index.js'; +import datasetCommentScraper from '../../__fixtures__/lib/schema-to-ts/dataset/comment-scraper.json' with { type: 'json' }; +import datasetGooglePlaces from '../../__fixtures__/lib/schema-to-ts/dataset/google-places.json' with { type: 'json' }; +import datasetTiktokFollowers from '../../__fixtures__/lib/schema-to-ts/dataset/tiktok-followers-scraper.json' with { type: 'json' }; +import datasetCommentScraperExpected from '../../__fixtures__/lib/schema-to-ts/expected/dataset/comment-scraper.ts?raw'; +import datasetGooglePlacesExpected from '../../__fixtures__/lib/schema-to-ts/expected/dataset/google-places.ts?raw'; +import datasetTiktokFollowersExpected from '../../__fixtures__/lib/schema-to-ts/expected/dataset/tiktok-followers-scraper.ts?raw'; +import inputApiScraperExpected from '../../__fixtures__/lib/schema-to-ts/expected/input/api-scraper.ts?raw'; +import inputCommentScraperExpected from '../../__fixtures__/lib/schema-to-ts/expected/input/comment-scraper.ts?raw'; +import inputFreeAmazonExpected from '../../__fixtures__/lib/schema-to-ts/expected/input/free-amazon-product-scraper.ts?raw'; +import inputGooglePlacesExpected from '../../__fixtures__/lib/schema-to-ts/expected/input/google-places.ts?raw'; +import inputApiScraper from '../../__fixtures__/lib/schema-to-ts/input/api-scraper.json' with { type: 'json' }; +import inputCommentScraper from '../../__fixtures__/lib/schema-to-ts/input/comment-scraper.json' with { type: 'json' }; +import inputFreeAmazon from '../../__fixtures__/lib/schema-to-ts/input/free-amazon-product-scraper.json' with { type: 'json' }; +import inputGooglePlaces from '../../__fixtures__/lib/schema-to-ts/input/google-places.json' with { type: 'json' }; +import kvStoreMapsToPolygon from '../../__fixtures__/lib/schema-to-ts/kvstore/maps-to-polygon.json' with { type: 'json' }; + +/** + * Published schemas, compiled through the public facade. The unit tests own the edge cases; + * these own the claim that the whole pipeline survives schemas nobody wrote for us — every + * fixture here is a real Actor's schema, keywords, HTML descriptions and all. + */ + +/** What a CLI would ask for: the Actor's view of its input, and the caller's. */ +const INPUT: CompileOptions = { + types: [ + { name: 'Input', variant: 'received' }, + { name: 'InputArgs', variant: 'supplied' }, + ], +}; + +const datasetTypes = (name: string): CompileOptions => ({ + types: [ + { name, variant: 'received' }, + { name: `${name}Draft`, variant: 'supplied' }, + ], +}); + +/** + * The expected files are checked in as real `.ts`, so the repo's formatter owns their layout: + * tabs, unions broken across lines with a leading pipe, single-quoted literals, and none of the + * parentheses the emitter puts around every intersection. That is the point — a generated file + * lives in a repo with a formatter — so compare only what a formatter cannot touch. The exact + * bytes the emitter writes are emit.test.ts's business. + */ +function significant(source: string): string { + return source + .replace(/^\/\/ oxlint-disable\n/, '') + .replace(/'([^']*)'/g, '"$1"') + .replace(/[()]/g, '') + .replace(/\s+/g, ' ') + .replace(/ ?([{}<>|;:,&?=]) ?/g, '$1') + .replace(/([:=<])\|/g, '$1') + .trim(); +} + +/** A property no fixture declares, so every case has a type-relevant edit available. */ +function withExtraProperty(schema: unknown): unknown { + const root = schema as { properties: Record }; + return { ...root, properties: { ...root.properties, addedLater: { type: 'string' } } }; +} + +/** Diagnostics and notices counted by code, so a case can state the fidelity it knows it loses. */ +function tally(items: { code: string }[]): Record { + const counts: Record = {}; + for (const { code } of items) counts[code] = (counts[code] ?? 0) + 1; + return counts; +} + +const CASES = [ + { + label: 'input/api-scraper', + schema: normalizeInputSchema(inputApiScraper), + expected: inputApiScraperExpected, + opts: INPUT, + }, + { + label: 'input/comment-scraper', + schema: normalizeInputSchema(inputCommentScraper), + expected: inputCommentScraperExpected, + opts: INPUT, + }, + { + label: 'input/free-amazon-product-scraper', + schema: normalizeInputSchema(inputFreeAmazon), + expected: inputFreeAmazonExpected, + opts: INPUT, + }, + { + label: 'input/google-places', + schema: normalizeInputSchema(inputGooglePlaces), + expected: inputGooglePlacesExpected, + opts: INPUT, + }, + { + label: 'dataset/comment-scraper', + schema: normalizeDatasetSchema(datasetCommentScraper), + expected: datasetCommentScraperExpected, + opts: datasetTypes('Comment'), + }, + { + label: 'dataset/google-places', + schema: normalizeDatasetSchema(datasetGooglePlaces), + expected: datasetGooglePlacesExpected, + opts: datasetTypes('Place'), + // The only fixture here that uses $ref. Every one of those 23 places is a named + // definition the schema spells out and we hand back as `unknown` — this is the gap, + // pinned so it cannot widen quietly and so closing it shows up as a diff. + diagnostics: { 'unsupported-keyword': 23 }, + notices: { 'empty-schema': 5 }, + }, + { + label: 'dataset/tiktok-followers-scraper', + schema: normalizeDatasetSchema(datasetTiktokFollowers), + expected: datasetTiktokFollowersExpected, + opts: datasetTypes('Connection'), + }, +]; + +describe.each(CASES)('$label', ({ schema, expected, opts, diagnostics: lost, notices: lint }) => { + test('compiles to the checked-in TypeScript', () => { + expect(significant(compile(schema, opts).source)).toBe(significant(expected)); + }); + + test('and that comparison has teeth — a schema that moved does not pass it', () => { + expect(significant(compile(withExtraProperty(schema), opts).source)).not.toBe(significant(expected)); + }); + + test('loses exactly the fidelity it admits to, and no more', () => { + const { diagnostics, notices } = compile(schema, opts); + expect(tally(diagnostics)).toEqual(lost ?? {}); + expect(tally(notices)).toEqual(lint ?? {}); + }); + + test('check reads the formatted file as current — the fingerprint outlives the formatter', () => { + expect(check(expected, schema, opts)).toMatchObject({ stale: false, reason: 'match' }); + }); + + test('check still sees a schema that gained a property', () => { + expect(check(expected, withExtraProperty(schema), opts)).toMatchObject({ + stale: true, + reason: 'hash-mismatch', + }); + }); +}); + +describe('what the published schemas exercise', () => { + const source = (schema: unknown, opts = INPUT) => compile(schema, opts).source; + + test('an enum becomes a literal union', () => { + expect(source(normalizeInputSchema(inputApiScraper))).toContain( + `resultsType: "posts" | "comments" | "details" | "mentions" | "reels" | "stories";`, + ); + }); + + test('a default is present for the Actor and optional for the caller', () => { + const text = source(normalizeInputSchema(inputApiScraper)); + expect(text).toContain('addParentData: boolean;'); + expect(text).toContain('addParentData?: boolean | undefined;'); + }); + + test('an array with no items declaration is Array, not Array', () => { + expect(source(normalizeInputSchema(inputFreeAmazon))).toContain('categoryUrls: Array;'); + }); + + test('nullable widens the type; nullable: false is a no-op', () => { + const text = source(normalizeInputSchema(inputFreeAmazon)); + expect(text).toContain('scrapeProductDetails: boolean | null;'); + expect(text).toContain('maxItemsPerStartUrl?: number | undefined;'); + }); + + test('dataset fields are optional and nullable, since a run may not fill them', () => { + expect(source(normalizeDatasetSchema(datasetCommentScraper), datasetTypes('Comment'))).toContain( + 'postUrl?: string | null | undefined;', + ); + }); + + test('an array of untyped objects is Array>', () => { + expect(source(normalizeDatasetSchema(datasetCommentScraper), datasetTypes('Comment'))).toContain( + 'replies?: Array> | null | undefined;', + ); + }); + + test('a nullable nested object keeps its shape inside the union', () => { + const text = source(normalizeDatasetSchema(datasetTiktokFollowers), datasetTypes('Connection')); + expect(text).toContain('commerceUser?: boolean | null | undefined;'); + expect(text).toContain('} | null | undefined;'); + }); +}); + +describe('kvstore/maps-to-polygon', () => { + const meme = kvStoreMapsToPolygon.collections.meme.jsonSchema; + + test('a collection schema needs no preprocessing — it is plain JSON Schema already', () => { + const { source, diagnostics, notices } = compile(meme, { types: [{ name: 'Meme', variant: 'received' }] }); + expect(diagnostics).toEqual([]); + expect(notices).toEqual([]); + expect(source).toContain(['export type Meme = {', ' url: string;', ' topLeft: {'].join('\n')); + }); + + test('additionalProperties: false closes the object for the writer too', () => { + const { source } = compile(meme, { types: [{ name: 'Meme', variant: 'supplied' }] }); + // The root refused extras, so it stays closed even in the permissive variant. The + // nested objects never said so, and they do open up. + expect(source).toContain('export type Meme = {'); + expect(source).toContain('topLeft: ({'); + expect(source).toContain(' } & Record);'); + }); + + test('the same collection round-trips through check', () => { + const opts: CompileOptions = { types: [{ name: 'Meme', variant: 'received' }] }; + expect(check(compile(meme, opts).source, meme, opts)).toMatchObject({ stale: false, reason: 'match' }); + }); +}); diff --git a/test/local/lib/schema-to-ts/emit.test.ts b/test/local/lib/schema-to-ts/emit.test.ts index c9a2fdcb6..f0aa13c42 100644 --- a/test/local/lib/schema-to-ts/emit.test.ts +++ b/test/local/lib/schema-to-ts/emit.test.ts @@ -238,30 +238,3 @@ describe('unknown root', () => { expect(emit(ir, { types }).split('\n')[0]).not.toBe(emit(ir, { types, unknownRoot: 'record' }).split('\n')[0]); }); }); - -/** - * The contract the emitter is written against, as a consumer would compile our output — not this - * repo's tsconfig.json, which is about compiling *our* source and answers to a different audience. - * Pinned on purpose: `exactOptionalPropertyTypes` is why we emit `name?: T | undefined` at all, so - * if someone relaxed it at the repo root to unblock a src file, this gate must keep asserting it - * rather than silently stop testing the thing it exists for. `types: []` proves generated output - * stands alone with no ambient @types on the machine. - */ -const GATE_TSCONFIG = JSON.stringify( - { - compilerOptions: { - noEmit: true, - strict: true, - exactOptionalPropertyTypes: true, - target: 'esnext', - module: 'nodenext', - moduleResolution: 'nodenext', - allowImportingTsExtensions: true, - skipLibCheck: true, - types: [], - }, - include: ['*.ts'], - }, - null, - 4, -); diff --git a/test/local/lib/schema-to-ts/preprocess/dataset.test.ts b/test/local/lib/schema-to-ts/preprocess/dataset.test.ts index 66a9e966d..0656b9450 100644 --- a/test/local/lib/schema-to-ts/preprocess/dataset.test.ts +++ b/test/local/lib/schema-to-ts/preprocess/dataset.test.ts @@ -1,6 +1,5 @@ import { describe, expect, test } from 'vitest'; -import { emit } from '../../../../../src/lib/schema-to-ts/emit.js'; import { jsonSchemaToIR } from '../../../../../src/lib/schema-to-ts/json-schema-to-ir.js'; import { normalizeDatasetSchema } from '../../../../../src/lib/schema-to-ts/preprocess/dataset.js'; diff --git a/test/local/lib/schema-to-ts/preprocess/input.test.ts b/test/local/lib/schema-to-ts/preprocess/input.test.ts index 5463fd63f..aafb7c4ad 100644 --- a/test/local/lib/schema-to-ts/preprocess/input.test.ts +++ b/test/local/lib/schema-to-ts/preprocess/input.test.ts @@ -1,7 +1,5 @@ import { describe, expect, test } from 'vitest'; -import { emit } from '../../../../../src/lib/schema-to-ts/emit.js'; -import { jsonSchemaToIR } from '../../../../../src/lib/schema-to-ts/json-schema-to-ir.js'; import { normalizeInputSchema } from '../../../../../src/lib/schema-to-ts/preprocess/input.js'; describe('normalizeInputSchema', () => { From a1ff023344a3c1a3348f2ab9cc15bd258cb0263d Mon Sep 17 00:00:00 2001 From: JuanGalilea Date: Wed, 2 Sep 2026 16:08:43 +0200 Subject: [PATCH 6/7] windows compat? --- test/local/lib/schema-to-ts/compile.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/local/lib/schema-to-ts/compile.test.ts b/test/local/lib/schema-to-ts/compile.test.ts index cbf36d696..19c6d6a1d 100644 --- a/test/local/lib/schema-to-ts/compile.test.ts +++ b/test/local/lib/schema-to-ts/compile.test.ts @@ -54,7 +54,7 @@ const datasetTypes = (name: string): CompileOptions => ({ */ function significant(source: string): string { return source - .replace(/^\/\/ oxlint-disable\n/, '') + .replace(/^\/\/ oxlint-disable\r?\n/, '') .replace(/'([^']*)'/g, '"$1"') .replace(/[()]/g, '') .replace(/\s+/g, ' ') From 42891f8978d66887a22d151185249f0d52fa8005 Mon Sep 17 00:00:00 2001 From: JuanGalilea Date: Thu, 3 Sep 2026 16:44:47 +0200 Subject: [PATCH 7/7] change compilation. perspective missing and tests need improvement --- src/commands/actor/generate-schema-types.ts | 118 +++++++++--------- .../actor/generate-schema-types.test.ts | 74 ++++------- 2 files changed, 87 insertions(+), 105 deletions(-) diff --git a/src/commands/actor/generate-schema-types.ts b/src/commands/actor/generate-schema-types.ts index c4f127a7b..c1be62129 100644 --- a/src/commands/actor/generate-schema-types.ts +++ b/src/commands/actor/generate-schema-types.ts @@ -2,9 +2,6 @@ import { mkdir, stat, writeFile } from 'node:fs/promises'; import path from 'node:path'; import process from 'node:process'; -import type { JSONSchema4 } from 'json-schema'; -import { compile, type Options } from 'json-schema-to-typescript'; - import { ApifyCommand } from '../../lib/command-framework/apify-command.js'; import { Args } from '../../lib/command-framework/args.js'; import { Flags } from '../../lib/command-framework/flags.js'; @@ -16,6 +13,8 @@ import { readStorageSchema, } from '../../lib/input_schema.js'; import { error, info, success, warning } from '../../lib/outputs.js'; +import { compile as basedCompile, type CompileResult } from '../../lib/schema-to-ts/compile.js'; +import type { Diagnostic, Notice } from '../../lib/schema-to-ts/diagnostics.js'; import { clearAllRequired, makePropertiesRequired, @@ -140,22 +139,17 @@ just as if the command were run from that directory with no argument.`; ? clearAllRequired(inputSchema) : makePropertiesRequired(inputSchema); - const compileOptions: Partial = { - bannerComment: BANNER_COMMENT, - maxItems: -1, - unknownAny: true, - format: true, - additionalProperties: !this.flags.strict, - $refOptions: { resolve: { external: false, file: false, http: false } }, - }; - - const result = await compile(stripTitles(schemaToCompile) as JSONSchema4, name, compileOptions); + const result = basedCompile(stripTitles(schemaToCompile), { + types: [{ name, variant: 'received' }], + }); + notifyDiagnostics('input', result); + // const result2 = await compile(stripTitles(schemaToCompile) as JSONSchema4, name, compileOptions); const outputDir = path.resolve(effectiveCwd, this.flags.output); await mkdir(outputDir, { recursive: true }); const outputFile = path.join(outputDir, `${name}.ts`); - await writeFile(outputFile, result, 'utf-8'); + await writeFile(outputFile, result.source, 'utf-8'); success({ message: `Generated types written to ${outputFile}` }); @@ -163,9 +157,9 @@ just as if the command were run from that directory with no argument.`; // (this includes both "no argument" and "directory argument" modes) if (!forcePath) { const schemaResults = await Promise.allSettled([ - this.generateDatasetTypes({ cwd: effectiveCwd, outputDir, compileOptions }), - this.generateOutputTypes({ cwd: effectiveCwd, outputDir, compileOptions }), - this.generateKvsTypes({ cwd: effectiveCwd, outputDir, compileOptions }), + this.generateDatasetTypes({ cwd: effectiveCwd, outputDir }), + this.generateOutputTypes({ cwd: effectiveCwd, outputDir }), + this.generateKvsTypes({ cwd: effectiveCwd, outputDir }), ]); const schemaLabels = ['Dataset', 'Output', 'Key-Value Store']; @@ -186,15 +180,7 @@ just as if the command were run from that directory with no argument.`; } } - private async generateDatasetTypes({ - cwd, - outputDir, - compileOptions, - }: { - cwd: string; - outputDir: string; - compileOptions: Partial; - }) { + private async generateDatasetTypes({ cwd, outputDir }: { cwd: string; outputDir: string }) { const datasetResult = readDatasetSchema({ cwd }); if (!datasetResult) { @@ -219,24 +205,19 @@ just as if the command were run from that directory with no argument.`; const datasetName = 'dataset'; const schemaToCompile = this.flags.allOptional ? clearAllRequired(prepared) : prepared; - - const result = await compile(stripTitles(schemaToCompile) as JSONSchema4, datasetName, compileOptions); + const result = basedCompile(stripTitles(schemaToCompile), { + types: [{ name: datasetName, variant: 'supplied' }], + unknownRoot: 'record', + }); + notifyDiagnostics('Dataset', result); const outputFile = path.join(outputDir, `${datasetName}.ts`); - await writeFile(outputFile, result, 'utf-8'); + await writeFile(outputFile, result.source, 'utf-8'); success({ message: `Generated types written to ${outputFile}` }); } - private async generateOutputTypes({ - cwd, - outputDir, - compileOptions, - }: { - cwd: string; - outputDir: string; - compileOptions: Partial; - }) { + private async generateOutputTypes({ cwd, outputDir }: { cwd: string; outputDir: string }) { const outputResult = readOutputSchema({ cwd }); if (!outputResult) { @@ -261,24 +242,19 @@ just as if the command were run from that directory with no argument.`; const outputName = 'output'; const schemaToCompile = this.flags.allOptional ? clearAllRequired(prepared) : prepared; + const result = basedCompile(stripTitles(schemaToCompile), { + types: [{ name: outputName, variant: 'supplied' }], + }); + // const result = await compile(stripTitles(schemaToCompile) as JSONSchema4, outputName, compileOptions); - const result = await compile(stripTitles(schemaToCompile) as JSONSchema4, outputName, compileOptions); - + notifyDiagnostics('output', result); const outputFile = path.join(outputDir, `${outputName}.ts`); - await writeFile(outputFile, result, 'utf-8'); + await writeFile(outputFile, result.source, 'utf-8'); success({ message: `Generated types written to ${outputFile}` }); } - private async generateKvsTypes({ - cwd, - outputDir, - compileOptions, - }: { - cwd: string; - outputDir: string; - compileOptions: Partial; - }) { + private async generateKvsTypes({ cwd, outputDir }: { cwd: string; outputDir: string }) { const kvsResult = readStorageSchema({ cwd, key: 'keyValueStore', label: 'Key-Value Store' }); if (!kvsResult) { @@ -305,22 +281,48 @@ just as if the command were run from that directory with no argument.`; } const parts: string[] = []; + const diagnostics: Diagnostic[] = []; + const notices: Notice[] = []; for (const { name, schema } of collections) { const schemaToCompile = this.flags.allOptional ? clearAllRequired(schema) : schema; - - const compiled = await compile(stripTitles(schemaToCompile) as JSONSchema4, name, { - ...compileOptions, - // Only the first collection gets the banner comment - bannerComment: parts.length === 0 ? (compileOptions.bannerComment as string) : '', + const result = basedCompile(stripTitles(schemaToCompile), { + types: [{ name, variant: 'supplied' }], }); - parts.push(compiled); + parts.push(result.source); + notices.push(...result.notices); + diagnostics.push(...result.diagnostics); } - + const finalSource = parts.join('\n'); + notifyDiagnostics('key-value-store', { source: finalSource, diagnostics, notices }); const outputFile = path.join(outputDir, 'key-value-store.ts'); - await writeFile(outputFile, parts.join('\n'), 'utf-8'); + await writeFile(outputFile, finalSource, 'utf-8'); success({ message: `Generated types written to ${outputFile}` }); } } + +function notifyDiagnostics(label: string, result: CompileResult) { + const errors: Diagnostic[] = []; + const warnings: Diagnostic[] = []; + + for (const diagnostic of result.diagnostics) { + (diagnostic.severity === 'error' ? errors : warnings).push(diagnostic); + } + + const format = (diagnostics: Diagnostic[]) => + diagnostics.map(({ path: at, code, message }) => ` ${at || ''} [${code}] ${message}`).join('\n'); + + if (errors.length > 0) { + error({ + message: `Found ${errors.length} error(s) in the ${label} schema:\n${format(errors)}`, + }); + } + + if (warnings.length > 0) { + warning({ + message: `Found ${warnings.length} unsupported construct(s) in the ${label} schema, the affected values are typed as 'unknown':\n${format(warnings)}`, + }); + } +} diff --git a/test/local/commands/actor/generate-schema-types.test.ts b/test/local/commands/actor/generate-schema-types.test.ts index a065cb7a8..900abda58 100644 --- a/test/local/commands/actor/generate-schema-types.test.ts +++ b/test/local/commands/actor/generate-schema-types.test.ts @@ -129,7 +129,7 @@ describe('apify actor generate-schema-types', () => { expect(lastErrorMessage()).include('Generated types written to'); const generatedFile = await readFile(joinPath('output', 'input.ts'), 'utf-8'); - expect(generatedFile).toContain('export interface'); + expect(generatedFile).toContain('export type'); expect(generatedFile).toContain('searchQuery'); }); @@ -142,7 +142,7 @@ describe('apify actor generate-schema-types', () => { expect(lastErrorMessage()).include(join('__generated__', 'actor', 'input.ts')); const generatedFile = await readFile(joinPath('src', '__generated__', 'actor', 'input.ts'), 'utf-8'); - expect(generatedFile).toContain('export interface'); + expect(generatedFile).toContain('export type'); }); it('should generate strict types by default (no index signature)', async () => { @@ -168,7 +168,7 @@ describe('apify actor generate-schema-types', () => { const generatedFile = await readFile(joinPath('output-non-strict', 'input.ts'), 'utf-8'); // Verify the file is generated with the interface - expect(generatedFile).toContain('export interface'); + expect(generatedFile).toContain('export type'); }); it('should fail when schema file does not exist', async () => { @@ -234,29 +234,6 @@ describe('apify actor generate-schema-types', () => { expect(generatedFile).toMatch(/crawlerType:/); }); - it('should make all properties optional with --all-optional flag', async () => { - const outputDir = joinPath('output-all-optional'); - - await testRunCommand(ActorGenerateSchemaTypesCommand, { - args_path: complexInputSchemaPath, - flags_output: outputDir, - 'flags_all-optional': true, - }); - - const generatedFile = await readFile(joinPath('output-all-optional', 'input.ts'), 'utf-8'); - - // With --all-optional, ALL properties should be optional - including originally required ones - expect(generatedFile).toMatch(/startUrls\?:/); - expect(generatedFile).toMatch(/searchQuery\?:/); - expect(generatedFile).toMatch(/maxItems\?:/); - expect(generatedFile).toMatch(/includeImages\?:/); - expect(generatedFile).toMatch(/proxyConfig\?:/); - - // Nested required properties should also become optional - expect(generatedFile).toMatch(/useApifyProxy\?:/); - expect(generatedFile).not.toMatch(/useApifyProxy:/); // ensure it's not non-optional - }); - describe('dataset schema', () => { it('should generate types from dataset schema referenced in actor.json', async () => { const outputDir = joinPath('ds-output'); @@ -267,10 +244,10 @@ describe('apify actor generate-schema-types', () => { }); const generatedFile = await readFile(joinPath('ds-output', 'dataset.ts'), 'utf-8'); - expect(generatedFile).toContain('export interface'); - expect(generatedFile).toContain('title'); - expect(generatedFile).toContain('url'); - expect(generatedFile).toContain('price'); + expect(generatedFile).toContain('export type'); + expect(generatedFile).toContain('title: string;'); + expect(generatedFile).toContain('url: string;'); + expect(generatedFile).toContain('price?: number | undefined;'); }); it('should generate types from dataset schema embedded in actor.json', async () => { @@ -295,7 +272,7 @@ describe('apify actor generate-schema-types', () => { }); const generatedFile = await readFile(joinPath('ds-output-embedded', 'dataset.ts'), 'utf-8'); - expect(generatedFile).toContain('export interface'); + expect(generatedFile).toContain('export type'); expect(generatedFile).toContain('name'); expect(generatedFile).toContain('value'); }); @@ -342,7 +319,7 @@ describe('apify actor generate-schema-types', () => { }); const generatedFile = await readFile(joinPath('out-output', 'output.ts'), 'utf-8'); - expect(generatedFile).toContain('export interface'); + expect(generatedFile).toContain('export type'); expect(generatedFile).toContain('productPage'); expect(generatedFile).toContain('screenshot'); expect(generatedFile).toContain('report'); @@ -369,7 +346,7 @@ describe('apify actor generate-schema-types', () => { }); const generatedFile = await readFile(joinPath('out-output-embedded', 'output.ts'), 'utf-8'); - expect(generatedFile).toContain('export interface'); + expect(generatedFile).toContain('export type'); expect(generatedFile).toContain('resultPage'); expect(generatedFile).toContain('dataExport'); }); @@ -410,7 +387,7 @@ describe('apify actor generate-schema-types', () => { }); const generatedFile = await readFile(joinPath('kvs-output', 'key-value-store.ts'), 'utf-8'); - expect(generatedFile).toContain('export interface'); + expect(generatedFile).toContain('export type'); // Only "results" collection has jsonSchema; "screenshots" does not expect(generatedFile).toContain('totalItems'); expect(generatedFile).toContain('summary'); @@ -445,7 +422,7 @@ describe('apify actor generate-schema-types', () => { }); const generatedFile = await readFile(joinPath('kvs-output-embedded', 'key-value-store.ts'), 'utf-8'); - expect(generatedFile).toContain('export interface'); + expect(generatedFile).toContain('export type'); expect(generatedFile).toContain('runCount'); expect(generatedFile).toContain('avgDuration'); }); @@ -507,16 +484,16 @@ describe('apify actor generate-schema-types', () => { const outputDir = join(projectDir, 'src', '__generated__', 'actor'); const inputFile = await readFile(join(outputDir, 'input.ts'), 'utf-8'); - expect(inputFile).toContain('export interface'); + expect(inputFile).toContain('export type'); const datasetFile = await readFile(join(outputDir, 'dataset.ts'), 'utf-8'); - expect(datasetFile).toContain('export interface'); + expect(datasetFile).toContain('export type'); const outputFile = await readFile(join(outputDir, 'output.ts'), 'utf-8'); - expect(outputFile).toContain('export interface'); + expect(outputFile).toContain('export type'); const kvsFile = await readFile(join(outputDir, 'key-value-store.ts'), 'utf-8'); - expect(kvsFile).toContain('export interface'); + expect(kvsFile).toContain('export type'); }); it('should discover input schema from default locations in the directory', async () => { @@ -542,7 +519,7 @@ describe('apify actor generate-schema-types', () => { const outputDir = join(projectDir, 'src', '__generated__', 'actor'); const generatedFile = await readFile(join(outputDir, 'input.ts'), 'utf-8'); - expect(generatedFile).toContain('export interface'); + expect(generatedFile).toContain('export type'); expect(generatedFile).toContain('query'); }); @@ -557,7 +534,7 @@ describe('apify actor generate-schema-types', () => { // Output should be inside the project directory, not in cwd const outputFile = join(projectDir, 'src', '__generated__', 'actor', 'input.ts'); const generatedFile = await readFile(outputFile, 'utf-8'); - expect(generatedFile).toContain('export interface'); + expect(generatedFile).toContain('export type'); }); it('should fail with clear error when directory has no schemas', async () => { @@ -572,18 +549,17 @@ describe('apify actor generate-schema-types', () => { }); }); - it('should write successful schemas and report error for the failing one', async () => { + it('warns about unsupported JsonSchema feature usage', async () => { const outputDir = joinPath('partial-fail-output'); - // Dataset schema has a $ref that cannot be resolved (file resolution is disabled), - // so dataset compilation will throw while input compilation succeeds. + // `$ref` is not a supported feature (yet) await setupActorConfig(joinPath(), { datasetSchemaRef: { actorSpecification: 1, fields: { type: 'object', properties: { - x: { $ref: './nonexistent.json' }, + myRef: { $ref: 'someRef' }, }, }, views: {}, @@ -596,11 +572,15 @@ describe('apify actor generate-schema-types', () => { // input.ts must have been written despite the dataset failure const inputFile = await readFile(joinPath('partial-fail-output', 'input.ts'), 'utf-8'); - expect(inputFile).toContain('export interface'); + expect(inputFile).toContain('export type'); // An error naming the failing schema must be logged const allErrors = logMessages.error.join('\n'); - expect(allErrors).toContain('Failed to generate types for Dataset schema'); + expect(allErrors).toContain('[unsupported-keyword] $ref is not supported'); + + // dataset.ts must have been written despite the dataset failure + const datasetFile = await readFile(joinPath('partial-fail-output', 'dataset.ts'), 'utf-8'); + expect(datasetFile).toContain('myRef?: unknown;'); }); });