@workerstack/database 0.21.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/README.md +197 -33
  2. package/docs/meta-contract.md +55 -1
  3. package/package.json +1 -6
  4. package/src/codegen/analyzer-types.ts +6 -1
  5. package/src/codegen/annotations.ts +66 -9
  6. package/src/codegen/format-constraints.ts +114 -0
  7. package/src/codegen/generators/dtos/entity-file.ts +2 -1
  8. package/src/codegen/generators/dtos/example-meta.ts +22 -2
  9. package/src/codegen/generators/dtos/field-schema.ts +31 -8
  10. package/src/codegen/generators/dtos/ip-patterns.ts +25 -11
  11. package/src/codegen/generators/dtos/number-range.ts +22 -3
  12. package/src/codegen/generators/dtos/schema-aliases.ts +5 -3
  13. package/src/codegen/generators/dtos/text-format.ts +63 -0
  14. package/src/codegen/generators/dtos/text-schema.ts +49 -2
  15. package/src/codegen/generators/manifest.ts +9 -1
  16. package/src/codegen/runtime/read-comb.ts +71 -3
  17. package/src/codegen/runtime/read-tables.ts +31 -19
  18. package/src/codegen/runtime/snapshot-types.ts +3 -1
  19. package/src/codegen/runtime/to-meta.ts +6 -3
  20. package/src/formats/catalog.ts +255 -0
  21. package/src/formats/iana-time-zone.ts +117 -0
  22. package/src/formats/iso3166-1.ts +78 -0
  23. package/src/formats/iso4217.ts +42 -0
  24. package/src/formats/iso639-1.ts +41 -0
  25. package/src/formats/uts35.ts +143 -0
  26. package/src/index.ts +5 -0
  27. package/src/meta/column.ts +22 -9
  28. package/src/pg/columns.ts +6 -4
  29. package/src/sqlite/columns.ts +6 -4
  30. package/src/sqlite/constraints.ts +120 -6
  31. package/types/codegen/analyzer-types.d.ts +6 -1
  32. package/types/codegen/analyzer-types.d.ts.map +1 -1
  33. package/types/codegen/annotations.d.ts +12 -7
  34. package/types/codegen/annotations.d.ts.map +1 -1
  35. package/types/codegen/format-constraints.d.ts +22 -0
  36. package/types/codegen/format-constraints.d.ts.map +1 -0
  37. package/types/codegen/generators/dtos/entity-file.d.ts.map +1 -1
  38. package/types/codegen/generators/dtos/example-meta.d.ts.map +1 -1
  39. package/types/codegen/generators/dtos/field-schema.d.ts.map +1 -1
  40. package/types/codegen/generators/dtos/ip-patterns.d.ts +7 -0
  41. package/types/codegen/generators/dtos/ip-patterns.d.ts.map +1 -1
  42. package/types/codegen/generators/dtos/number-range.d.ts +8 -0
  43. package/types/codegen/generators/dtos/number-range.d.ts.map +1 -1
  44. package/types/codegen/generators/dtos/schema-aliases.d.ts +1 -1
  45. package/types/codegen/generators/dtos/schema-aliases.d.ts.map +1 -1
  46. package/types/codegen/generators/dtos/text-format.d.ts +31 -0
  47. package/types/codegen/generators/dtos/text-format.d.ts.map +1 -0
  48. package/types/codegen/generators/dtos/text-schema.d.ts +8 -1
  49. package/types/codegen/generators/dtos/text-schema.d.ts.map +1 -1
  50. package/types/codegen/generators/manifest.d.ts +5 -0
  51. package/types/codegen/generators/manifest.d.ts.map +1 -1
  52. package/types/codegen/runtime/read-comb.d.ts +17 -3
  53. package/types/codegen/runtime/read-comb.d.ts.map +1 -1
  54. package/types/codegen/runtime/read-tables.d.ts.map +1 -1
  55. package/types/codegen/runtime/snapshot-types.d.ts +3 -1
  56. package/types/codegen/runtime/snapshot-types.d.ts.map +1 -1
  57. package/types/codegen/runtime/to-meta.d.ts.map +1 -1
  58. package/types/formats/catalog.d.ts +51 -0
  59. package/types/formats/catalog.d.ts.map +1 -0
  60. package/types/formats/iana-time-zone.d.ts +7 -0
  61. package/types/formats/iana-time-zone.d.ts.map +1 -0
  62. package/types/formats/iso3166-1.d.ts +12 -0
  63. package/types/formats/iso3166-1.d.ts.map +1 -0
  64. package/types/formats/iso4217.d.ts +7 -0
  65. package/types/formats/iso4217.d.ts.map +1 -0
  66. package/types/formats/iso639-1.d.ts +6 -0
  67. package/types/formats/iso639-1.d.ts.map +1 -0
  68. package/types/formats/uts35.d.ts +13 -0
  69. package/types/formats/uts35.d.ts.map +1 -0
  70. package/types/index.d.ts +5 -0
  71. package/types/index.d.ts.map +1 -1
  72. package/types/meta/column.d.ts +7 -1
  73. package/types/meta/column.d.ts.map +1 -1
  74. package/types/pg/columns.d.ts +3 -3
  75. package/types/pg/columns.d.ts.map +1 -1
  76. package/types/sqlite/columns.d.ts +3 -3
  77. package/types/sqlite/columns.d.ts.map +1 -1
  78. package/types/sqlite/constraints.d.ts +117 -5
  79. package/types/sqlite/constraints.d.ts.map +1 -1
  80. package/src/sqlite/patterns.ts +0 -19
  81. package/types/sqlite/patterns.d.ts +0 -19
  82. package/types/sqlite/patterns.d.ts.map +0 -1
@@ -1,5 +1,7 @@
1
- /** What the runtime reader reads of a c helper's own record: its constraints and an enum's state machine, as JSON */
2
- import type { SnapshotComb } from "./snapshot-types.ts"
1
+ /** What the runtime reader reads of a c helper's own record: its constraints, a column schema and an enum's state machine, as JSON */
2
+ import * as z from "zod"
3
+ import type { CombColumnMeta } from "../../meta/column.ts"
4
+ import type { ModuleExport, SnapshotComb } from "./snapshot-types.ts"
3
5
 
4
6
  const STRING_LIST = (value: unknown): value is string[] =>
5
7
  Array.isArray(value) && value.every((item) => typeof item === "string")
@@ -21,6 +23,72 @@ function constraintsOf(raw: unknown, at: string): Record<string, boolean | numbe
21
23
  return constraints
22
24
  }
23
25
 
26
+ /** The packages a schema found nowhere might have been in, as its error names them; empty when every module loaded */
27
+ const skipped = (unloaded: readonly string[]): string =>
28
+ unloaded.length === 0 ? "" : ` (${unloaded.join(", ")} would not load on their own)`
29
+
30
+ /* the helpers whose `schema` key names a zod schema the generated DTOs pipe the value into; on any other the key stays a constraint, which the constraint check refuses by name */
31
+ // lookup-table: the c helpers that take a column schema
32
+ const SCHEMA_KINDS: ReadonlySet<string> = /* @__PURE__ */ new Set(["integer", "real", "text"])
33
+
34
+ const DEFS = "#/$defs/"
35
+
36
+ /**
37
+ * `node` with each `$ref` replaced by the entry of `defs` it points at, its sibling keywords kept:
38
+ * zod files a schema carrying an `id` under the root's `$defs`, a pointer that would dangle once the
39
+ * literal sits in another document's `allOf`. Every `$ref` zod writes points into `$defs`, as
40
+ * `cycles: "throw"` leaves no reference back to the root
41
+ */
42
+ function inlined(node: unknown, defs: Readonly<Record<string, unknown>>): unknown {
43
+ if (Array.isArray(node)) return node.map((each) => inlined(each, defs))
44
+ if (typeof node !== "object" || node === null) return node
45
+ const { $ref, ...rest } = node as Record<string, unknown>
46
+ const own = Object.fromEntries(Object.entries(rest).map(([key, value]) => [key, inlined(value, defs)]))
47
+ if ($ref === undefined) return own
48
+ const name = ($ref as string).slice(DEFS.length).replaceAll("~1", "/").replaceAll("~0", "~")
49
+ return { ...(inlined(defs[name], defs) as object), ...own }
50
+ }
51
+
52
+ /** The input JSON Schema of a zod schema as a self-contained literal, `$schema` dropped; an error naming the column when zod has none */
53
+ function inputJsonOf(schema: z.ZodType, at: string): Readonly<Record<string, unknown>> {
54
+ let json: Record<string, unknown>
55
+ try {
56
+ json = z.toJSONSchema(schema, { cycles: "throw", io: "input" }) as Record<string, unknown>
57
+ } catch (error) {
58
+ throw new Error(
59
+ `${at}: the schema its schema key is given has no JSON Schema form (zod: ${(error as Error).message}); comb publishes every DTO rule`,
60
+ { cause: error },
61
+ )
62
+ }
63
+ const { $defs, $schema: _dropped, ...root } = json
64
+ return inlined(root, ($defs ?? {}) as Record<string, unknown>) as Record<string, unknown>
65
+ }
66
+
67
+ /**
68
+ * A helper's constraints with its `schema` key read: on a text or number column, taken out for the
69
+ * export that holds it and its input JSON Schema; on any other helper, kept as `true` for the
70
+ * constraint check to refuse. An error naming the column for a value that is no zod schema, and
71
+ * for one no module exports, naming the modules that would not load (`unloaded`)
72
+ */
73
+ function columnSchemaOf(
74
+ meta: CombColumnMeta,
75
+ at: string,
76
+ { locate, unloaded }: { locate: (value: unknown) => ModuleExport | null; unloaded: readonly string[] },
77
+ ): Pick<SnapshotComb, "schema" | "schemaJson"> & { constraints: Record<string, unknown> } {
78
+ const { schema: declared, ...constraints } = (meta.constraints ?? {}) as Record<string, unknown>
79
+ if (declared === undefined) return { constraints, schema: null, schemaJson: null }
80
+ if (!SCHEMA_KINDS.has(meta.kind))
81
+ return { constraints: { ...constraints, schema: true }, schema: null, schemaJson: null }
82
+ if (typeof declared !== "object" || declared === null || !("_zod" in declared))
83
+ throw new Error(`${at}: the constraint schema is no zod schema; comb's DTOs are zod 4, so the schema must be one`)
84
+ const schema = locate(declared)
85
+ if (schema === null)
86
+ throw new Error(
87
+ `${at}: no module of the project exports the schema its schema key is given${skipped(unloaded)}; export it, above the table that names it, so the generated DTOs can import it`,
88
+ )
89
+ return { constraints, schema, schemaJson: inputJsonOf(declared as z.ZodType, at) }
90
+ }
91
+
24
92
  /* the keys a c.enum's state machine has; values, which docs/state-machines.md §4 holds in the same const, read from c.enum's second argument */
25
93
  const STATE_KEYS = ["initial", "terminal", "transitions", "values"]
26
94
 
@@ -141,4 +209,4 @@ function refuseJsonDocument(
141
209
  )
142
210
  }
143
211
 
144
- export { constraintsOf, refuseJsonDocument, statesOf, STRING_LIST }
212
+ export { columnSchemaOf, constraintsOf, refuseJsonDocument, skipped, statesOf, STRING_LIST }
@@ -1,16 +1,16 @@
1
1
  /**
2
2
  * The runtime reader: load the tables module, find every drizzle table it exports, and read each
3
3
  * as drizzle built it (getTableConfig) and as comb's `c` helpers recorded it (combColumnMeta).
4
- * Objects a generated file must import, a c.json schema or a c.enum values array, are located by
5
- * identity among the exports of the project modules the tables module loads.
4
+ * Objects a generated file must import, a c.json schema or a text or number column's schema, are
5
+ * located by identity among the exports of the project modules the tables module loads.
6
6
  */
7
7
  import { Column, getTableColumns, is, type SQL, type Table } from "drizzle-orm"
8
8
  import { ExtraConfigColumn, getTableConfig as pgConfig, IndexedColumn, PgDialect, PgTable } from "drizzle-orm/pg-core"
9
9
  import { getTableConfig as sqliteConfig, SQLiteDialect, SQLiteTable } from "drizzle-orm/sqlite-core"
10
10
  import { byCodeUnit } from "../utils/order.ts"
11
- import { combColumnMeta } from "../../meta/column.ts"
11
+ import { combColumnMeta, type CombColumnMeta } from "../../meta/column.ts"
12
12
  import { dimensionsOf, filledByDefault, type ReadColumn, unnamedUnique } from "./read-column.ts"
13
- import { constraintsOf, refuseJsonDocument, statesOf, STRING_LIST } from "./read-comb.ts"
13
+ import { columnSchemaOf, constraintsOf, refuseJsonDocument, skipped, statesOf, STRING_LIST } from "./read-comb.ts"
14
14
  import type { ElementNulls } from "./element-types.ts"
15
15
  import type {
16
16
  ModuleExport,
@@ -52,29 +52,41 @@ type Context = {
52
52
  unloaded: readonly string[]
53
53
  }
54
54
 
55
+ /** Where `value` is exported, the first of `exportsHolding`; null when nowhere, or when nothing was given */
56
+ const locate = (value: unknown, context: Context): ModuleExport | null =>
57
+ value === undefined ? null : (exportsHolding(value, context.loaded, context.tablesPath)[0] ?? null)
58
+
59
+ /** Where a c.json column's schema is exported; an error naming the column when nowhere, or for a schema that is no stored document */
60
+ function jsonSchemaOf(
61
+ meta: CombColumnMeta,
62
+ column: ReadColumn,
63
+ { at, context }: { at: string; context: Context },
64
+ ): ModuleExport | null {
65
+ if (meta.kind !== "json") return null
66
+ const schema = locate(meta.schema, context)
67
+ if (schema === null)
68
+ throw new Error(
69
+ `${at}: no module of the project exports the schema its c.json is given${skipped(context.unloaded)}; export it, so the generated DTOs can import it`,
70
+ )
71
+ refuseJsonDocument(meta.schema, at, {
72
+ filled: filledByDefault(column),
73
+ notNull: column.notNull && !column.dimensions,
74
+ })
75
+ return schema
76
+ }
77
+
55
78
  function combOf(column: ReadColumn, at: string, context: Context): SnapshotComb | null {
56
79
  const meta = combColumnMeta(column)
57
80
  if (meta === undefined) return null
58
- const located = (value: unknown) =>
59
- value === undefined ? null : (exportsHolding(value, context.loaded, context.tablesPath)[0] ?? null)
60
81
  if (meta.values !== undefined && !STRING_LIST(meta.values)) throw new Error(`${at}: c.enum takes an array of strings`)
61
- const schema = located(meta.schema)
62
- if (meta.kind === "json" && schema === null) {
63
- const skipped = context.unloaded.length === 0 ? "" : ` (${context.unloaded.join(", ")} would not load on their own)`
64
- throw new Error(
65
- `${at}: no module of the project exports the schema its c.json is given${skipped}; export it, so the generated DTOs can import it`,
66
- )
67
- }
68
- if (meta.kind === "json")
69
- refuseJsonDocument(meta.schema, at, {
70
- filled: filledByDefault(column),
71
- notNull: column.notNull && !column.dimensions,
72
- })
82
+ const own = columnSchemaOf(meta, at, { locate: (value) => locate(value, context), unloaded: context.unloaded })
83
+ const schema = own.schema ?? jsonSchemaOf(meta, column, { at, context })
73
84
  return {
74
- constraints: constraintsOf(meta.constraints, at),
85
+ constraints: constraintsOf(own.constraints, at),
75
86
  kind: meta.kind,
76
87
  prefix: meta.prefix ?? null,
77
88
  schema,
89
+ schemaJson: own.schemaJson,
78
90
  states: statesOf(meta.states, at, column),
79
91
  values: meta.values === undefined ? null : [...meta.values],
80
92
  }
@@ -14,8 +14,10 @@ type SnapshotComb = {
14
14
  constraints: Record<string, boolean | number | string>
15
15
  kind: string
16
16
  prefix: string | null
17
- /** where the exact zod schema a c.json column was given is exported; null when nowhere */
17
+ /** where the exact zod schema a c.json column, or a text or number column's `schema` key, was given is exported; null when none was given */
18
18
  schema: ModuleExport | null
19
+ /** a text or number column's schema as its input JSON Schema, self-contained (each `$defs` entry inlined) and `$schema` dropped; null on c.json and when undeclared */
20
+ schemaJson: Readonly<Record<string, unknown>> | null
19
21
  states: { initial: string | null; terminal: string[] | null; transitions: Record<string, string[]> | null } | null
20
22
  values: string[] | null
21
23
  }
@@ -141,14 +141,15 @@ const TYPED_COLUMNS = {
141
141
  /** What a column's `c` helper recorded, as FieldMeta names it; a plain drizzle column's own enum values */
142
142
  function combFactsOf(
143
143
  column: SnapshotColumn,
144
- ): Pick<FieldMeta, "enumValues" | "jsonSchemaName" | "prefix" | "schemaImports" | "states"> {
144
+ ): Pick<FieldMeta, "enumValues" | "jsonSchemaName" | "prefix" | "schemaImports" | "schemaJson" | "states"> {
145
145
  const comb = column.comb
146
146
  const schema = comb?.schema ?? null
147
147
  return {
148
148
  enumValues: comb?.values ?? column.enumValues,
149
- jsonSchemaName: schema?.name ?? null,
149
+ jsonSchemaName: comb?.kind === "json" ? (schema as ModuleExport).name : null,
150
150
  prefix: comb?.prefix ?? null,
151
151
  schemaImports: schema === null ? [] : [importOf(schema)],
152
+ schemaJson: comb?.schemaJson ?? null,
152
153
  states: comb?.states ?? null,
153
154
  }
154
155
  }
@@ -172,19 +173,21 @@ function fieldOf(column: SnapshotColumn, { file, table, tables }: Build): FieldM
172
173
  `${place}: comb has no rule for a ${column.columnType} column (${column.dataType}); build it with a c helper, or as drizzle ${TYPED_COLUMNS[table.dialect]}`,
173
174
  )
174
175
  const filled = isFilled(column, kind)
176
+ const facts = combFactsOf(column)
175
177
  const constraints = fieldConstraintsOf(column.comb?.constraints ?? {}, {
176
178
  fieldName: column.key,
177
179
  filled,
178
180
  helper: kind,
179
181
  measure: measuredBy(column, kind),
180
182
  place,
183
+ values: facts.enumValues,
181
184
  })
182
185
  if (constraints.mutable && column.foreignKey === null && kind !== "ref")
183
186
  throw new Error(`${place}: mutable lets a reference move on update, and ${column.key} references nothing`)
184
187
  if (constraints.relation !== null && column.foreignKey === null)
185
188
  throw new Error(`${place}: relation names the relation of a key, and ${column.key} references nothing`)
186
189
  return {
187
- ...combFactsOf(column),
190
+ ...facts,
188
191
  autoIncrement: column.autoIncrement,
189
192
  columnType: column.columnType,
190
193
  combKind: kind,
@@ -0,0 +1,255 @@
1
+ /*
2
+ * The standard formats a column declares with `format`: each id's standard, the source its DTO checks,
3
+ * its length bounds, the transforms that run before the check, and the pinned registry a value must
4
+ * also belong to. The DTO's failure is zod's `invalid_format` issue with the id and no message, so a
5
+ * zod locale or error map translates it; the catalog carries no text a person reads but `standard`.
6
+ */
7
+ import { ipv4Source, strictIpv6Source } from "../codegen/generators/dtos/ip-patterns.ts"
8
+ import { isIanaTimeZone } from "./iana-time-zone.ts"
9
+ import { isIso3166Alpha2, isIso3166Alpha3 } from "./iso3166-1.ts"
10
+ import { isIso4217 } from "./iso4217.ts"
11
+ import { isIso639Alpha2 } from "./iso639-1.ts"
12
+ import {
13
+ CALENDARS,
14
+ FIRST_DAYS,
15
+ HOUR_CYCLES,
16
+ MEASUREMENT_SYSTEMS,
17
+ MEASUREMENT_UNITS,
18
+ NUMBERING_SYSTEMS,
19
+ } from "./uts35.ts"
20
+
21
+ /** The ids `c.text` and `c.enum` take as `format`, sorted by code unit */
22
+ export const TEXT_FORMATS = [
23
+ "base64url",
24
+ "bcp47",
25
+ "e164",
26
+ "iana-time-zone",
27
+ "ip",
28
+ "ipv4",
29
+ "ipv6",
30
+ "iso3166-1-alpha-2",
31
+ "iso3166-1-alpha-3",
32
+ "iso4217",
33
+ "iso639-1",
34
+ "kebab-slug",
35
+ "pkce-s256-challenge",
36
+ "rfc1123-hostname",
37
+ "semver",
38
+ "sha256-hex",
39
+ "sha512-hex",
40
+ "srgb-hex",
41
+ "srgb-hex-alpha",
42
+ "ulid",
43
+ "uts35-ca",
44
+ "uts35-fw",
45
+ "uts35-hc",
46
+ "uts35-ms",
47
+ "uts35-mu",
48
+ "uts35-nu",
49
+ "uuid",
50
+ "uuid-v4",
51
+ "uuid-v7",
52
+ ] as const
53
+
54
+ /** The ids `c.integer` takes as `format`, sorted by code unit */
55
+ export const INTEGER_FORMATS = ["iso8601-month", "iso8601-weekday"] as const
56
+
57
+ /** A text format's id */
58
+ export type TextFormat = (typeof TEXT_FORMATS)[number]
59
+
60
+ /** An integer format's id */
61
+ export type IntegerFormat = (typeof INTEGER_FORMATS)[number]
62
+
63
+ /** The registry predicate a text format's value must also satisfy */
64
+ type Member = "isIanaTimeZone" | "isIso3166Alpha2" | "isIso3166Alpha3" | "isIso4217" | "isIso639Alpha2"
65
+
66
+ /** What a text format checks, after its transforms, on the value its DTO is given */
67
+ export type FormatSpec = {
68
+ /** The standard and the release it follows, as a person reads it */
69
+ readonly standard: string
70
+ /** The anchored pattern the value matches, portable to RE2 and JavaScript's `u` flag */
71
+ readonly source: string
72
+ /** The fewest characters a value has */
73
+ readonly min: number
74
+ /** The most characters a value has, or null when the format sets no cap and a column declares one */
75
+ readonly max: number | null
76
+ /** The transforms that run, in order, before the check */
77
+ readonly transform: readonly ("lowercase" | "trim" | "uppercase")[]
78
+ /** The pinned registry the value must belong to, by its predicate's name, or null when the source is the whole rule */
79
+ readonly member: Member | null
80
+ /** A value of the format as written, which the format takes and a generated DTO publishes as its example */
81
+ readonly example: string
82
+ }
83
+
84
+ /** The range an integer format's value lies in */
85
+ export type IntegerSpec = {
86
+ /** The standard that numbers the values, as a person reads it */
87
+ readonly standard: string
88
+ /** The least value */
89
+ readonly min: number
90
+ /** The greatest value */
91
+ readonly max: number
92
+ }
93
+
94
+ /** The transforms a format runs before its check: trim on all, then the one case its values are written in */
95
+ type Transforms = FormatSpec["transform"]
96
+ const TRIM: Transforms = ["trim"]
97
+ const LOWERCASE: Transforms = ["trim", "lowercase"]
98
+ const UPPERCASE: Transforms = ["trim", "uppercase"]
99
+
100
+ /*
101
+ * RFC 5646 §2.1's langtag: language (with up to three extlangs), script, region, variants, extensions
102
+ * and private use, each class case-insensitive; a grandfathered tag is no langtag
103
+ */
104
+ const LANGUAGE = "(?:[A-Za-z]{2,3}(?:-[A-Za-z]{3}){0,3}|[A-Za-z]{4,8})"
105
+ const VARIANTS = "(?:-(?:[A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*"
106
+ const EXTENSIONS = "(?:-[0-9A-WYZa-wyz](?:-[A-Za-z0-9]{2,8})+)*"
107
+ const LANGTAG = `${LANGUAGE}(?:-[A-Za-z]{4})?(?:-(?:[A-Za-z]{2}|[0-9]{3}))?${VARIANTS}${EXTENSIONS}(?:-[Xx](?:-[A-Za-z0-9]{1,8})+)?`
108
+
109
+ /* semver.org 2.0.0's version pattern, its groups non-capturing */
110
+ const NUMBER = "(?:0|[1-9][0-9]*)"
111
+ const IDENTIFIER = "(?:0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)"
112
+ const BUILD = "(?:\\+[0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*)?"
113
+ const SEMVER = `${NUMBER}\\.${NUMBER}\\.${NUMBER}(?:-${IDENTIFIER}(?:\\.${IDENTIFIER})*)?${BUILD}`
114
+
115
+ const HOST_LABEL = "[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?"
116
+ const HOSTNAME = `^${HOST_LABEL}(?:\\.${HOST_LABEL})*$`
117
+ const ZONE_NAME = "^[A-Za-z0-9_+-]+(?:/[A-Za-z0-9_+-]+){0,2}$"
118
+ const HEX = "[0-9a-f]"
119
+ const BASE64URL = "[A-Za-z0-9_-]"
120
+ /* whole quads, then two or three characters: a length of 1 mod 4 encodes no byte string */
121
+ const UNPADDED = `^(?:${BASE64URL}{4})*(?:${BASE64URL}{2,3})?$`
122
+
123
+ /* the examples too long for their format's row, the digests of no bytes among them */
124
+ const PHONE = "+385911234567"
125
+ const ZONE = "Europe/Zagreb"
126
+ const IPV6 = "2001:db8::1"
127
+ const PKCE_CHALLENGE = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"
128
+ const RGBA = "#1e90ff80"
129
+ const ULID = "01hzy3k8q2m4n5p6r7s8t9v0wx"
130
+ const UUID_V7 = "01890a5d-ac96-774b-bcce-b302099a8057"
131
+ const SHA256_EMPTY = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
132
+ const SHA512_EMPTY =
133
+ "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"
134
+ const UUID_V4 = "0f8fad5b-d9cb-469f-a165-70867728950e"
135
+
136
+ /* the standards whose names would widen a row past the line */
137
+ const COUNTRY_2 = "ISO 3166-1 alpha-2 country code, Debian iso-codes v4.20.1"
138
+ const COUNTRY_3 = "ISO 3166-1 alpha-3 country code, Debian iso-codes v4.20.1"
139
+ const CURRENCY = "ISO 4217 alphabetic currency code, SIX list 2026-09-17"
140
+ const LANGUAGE_CODE = "ISO 639-1 language code, Debian iso-codes v4.20.1"
141
+ const CHALLENGE = "RFC 7636 §4.2 PKCE SHA-256 code challenge"
142
+ const SLUG = "kebab-case slug, lowercase letters and digits joined by single hyphens"
143
+ const ADDRESS = "IPv4 dotted quad or IPv6 address, RFC 4291 §2.2"
144
+ const DIGEST_256 = "SHA-256 digest, FIPS 180-4, lowercase hex"
145
+ const DIGEST_512 = "SHA-512 digest, FIPS 180-4, lowercase hex"
146
+ const COLOR_ALPHA = "sRGB color with alpha as #rrggbbaa, CSS Color 4"
147
+
148
+ /** One row of the table: a spec's fields in the order a reader scans them */
149
+ type Row = readonly [
150
+ standard: string,
151
+ source: string,
152
+ min: number,
153
+ max: number | null,
154
+ transform: Transforms,
155
+ member: Member | null,
156
+ example: string,
157
+ ]
158
+
159
+ /** The spec a row states */
160
+ const spec = ([standard, source, min, max, transform, member, example]: Row): FormatSpec => ({
161
+ example,
162
+ max,
163
+ member,
164
+ min,
165
+ source,
166
+ standard,
167
+ transform,
168
+ })
169
+
170
+ /** RFC 9562's 8-4-4-4-12 layout in lowercase hex, its version digit and variant digit given */
171
+ const uuid = (standard: string, [version, variant]: [string, string], example: string): FormatSpec =>
172
+ spec([
173
+ standard,
174
+ `^${HEX}{8}-${HEX}{4}-${version}${HEX}{3}-${variant}${HEX}{3}-${HEX}{12}$`,
175
+ 36,
176
+ 36,
177
+ LOWERCASE,
178
+ null,
179
+ example,
180
+ ])
181
+
182
+ /** A UTS #35 key's spec: an alternation of CLDR's values, the whole rule, bounded by the shortest and longest */
183
+ function uts35(key: string, values: readonly string[], example: string): FormatSpec {
184
+ const lengths = values.map((value) => value.length)
185
+ const standard = `UTS #35 ${key} key value, CLDR 49`
186
+ const [min, max] = [Math.min(...lengths), Math.max(...lengths)]
187
+ return spec([standard, `^(?:${values.join("|")})$`, min, max, LOWERCASE, null, example])
188
+ }
189
+
190
+ /** Every text format's spec, built in a function so importing the module runs nothing */
191
+ function specs(): { readonly [F in TextFormat]: FormatSpec } {
192
+ const ipv4 = ipv4Source()
193
+ const ipv6 = strictIpv6Source()
194
+ return {
195
+ base64url: spec(["RFC 4648 §5 base64url, unpadded", UNPADDED, 0, null, TRIM, null, "dGVzdA"]),
196
+ /* RFC 5646 §4.4.1's 35 is the buffer an implementation must hold, not a cap on a tag */
197
+ bcp47: spec(["BCP 47 language tag, RFC 5646 §2.1 well-formed", `^${LANGTAG}$`, 2, null, TRIM, null, "zh-Hant-TW"]),
198
+ e164: spec(["ITU-T E.164 phone number, + and up to 15 digits", "^\\+[1-9][0-9]{1,14}$", 3, 16, TRIM, null, PHONE]),
199
+ "iana-time-zone": spec(["IANA time zone name, tzdata 2026f", ZONE_NAME, 1, 64, TRIM, "isIanaTimeZone", ZONE]),
200
+ ip: spec([ADDRESS, `^(?:${ipv4}|${ipv6})$`, 2, 45, LOWERCASE, null, IPV6]),
201
+ ipv4: spec(["IPv4 address, dotted quad without leading zeros", `^${ipv4}$`, 7, 15, TRIM, null, "192.0.2.1"]),
202
+ ipv6: spec(["IPv6 address, RFC 4291 §2.2", `^(?:${ipv6})$`, 2, 45, LOWERCASE, null, IPV6]),
203
+ "iso3166-1-alpha-2": spec([COUNTRY_2, "^[A-Z]{2}$", 2, 2, UPPERCASE, "isIso3166Alpha2", "HR"]),
204
+ "iso3166-1-alpha-3": spec([COUNTRY_3, "^[A-Z]{3}$", 3, 3, UPPERCASE, "isIso3166Alpha3", "HRV"]),
205
+ iso4217: spec([CURRENCY, "^[A-Z]{3}$", 3, 3, UPPERCASE, "isIso4217", "EUR"]),
206
+ "iso639-1": spec([LANGUAGE_CODE, "^[a-z]{2}$", 2, 2, LOWERCASE, "isIso639Alpha2", "hr"]),
207
+ "kebab-slug": spec([SLUG, "^[a-z0-9]+(?:-[a-z0-9]+)*$", 1, 128, TRIM, null, "spring-sale-2026"]),
208
+ "pkce-s256-challenge": spec([CHALLENGE, `^${BASE64URL}{43}$`, 43, 43, TRIM, null, PKCE_CHALLENGE]),
209
+ "rfc1123-hostname": spec(["RFC 1123 §2.1 host name", HOSTNAME, 1, 253, LOWERCASE, null, "api.example.com"]),
210
+ semver: spec(["Semantic Versioning 2.0.0 version", `^${SEMVER}$`, 5, 256, TRIM, null, "1.4.0-rc.1"]),
211
+ "sha256-hex": spec([DIGEST_256, `^${HEX}{64}$`, 64, 64, LOWERCASE, null, SHA256_EMPTY]),
212
+ "sha512-hex": spec([DIGEST_512, `^${HEX}{128}$`, 128, 128, LOWERCASE, null, SHA512_EMPTY]),
213
+ "srgb-hex": spec(["sRGB color as #rrggbb, CSS Color 4", `^#${HEX}{6}$`, 7, 7, LOWERCASE, null, "#1e90ff"]),
214
+ "srgb-hex-alpha": spec([COLOR_ALPHA, `^#${HEX}{8}$`, 9, 9, LOWERCASE, null, RGBA]),
215
+ /* Crockford's base32 in lowercase, as generateUlid writes it; the first character holds 3 bits */
216
+ ulid: spec(["ULID, Crockford base32 in lowercase", "^[0-7][0-9a-hjkmnp-tv-z]{25}$", 26, 26, LOWERCASE, null, ULID]),
217
+ "uts35-ca": uts35("calendar (ca)", CALENDARS, "gregory"),
218
+ "uts35-fw": uts35("first day of the week (fw)", FIRST_DAYS, "mon"),
219
+ "uts35-hc": uts35("hour cycle (hc)", HOUR_CYCLES, "h23"),
220
+ "uts35-ms": uts35("measurement system (ms)", MEASUREMENT_SYSTEMS, "metric"),
221
+ "uts35-mu": uts35("measurement unit (mu)", MEASUREMENT_UNITS, "celsius"),
222
+ "uts35-nu": uts35("numbering system (nu)", NUMBERING_SYSTEMS, "latn"),
223
+ uuid: uuid("RFC 9562 UUID, lowercase hex", [HEX, HEX], UUID_V4),
224
+ "uuid-v4": uuid("RFC 9562 UUID version 4, lowercase hex", ["4", "[89ab]"], UUID_V4),
225
+ "uuid-v7": uuid("RFC 9562 UUID version 7, lowercase hex", ["7", "[89ab]"], UUID_V7),
226
+ }
227
+ }
228
+
229
+ /** Every text format's spec, by id */
230
+ export const FORMATS: { readonly [F in TextFormat]: FormatSpec } = /* @__PURE__ */ specs()
231
+
232
+ /** Every integer format's range, by id */
233
+ export const INTEGER_SPECS: { readonly [F in IntegerFormat]: IntegerSpec } = {
234
+ "iso8601-month": { max: 12, min: 1, standard: "ISO 8601 month number, 1 January to 12 December" },
235
+ "iso8601-weekday": { max: 7, min: 1, standard: "ISO 8601 §3.2.2 weekday number, 1 Monday to 7 Sunday" },
236
+ }
237
+
238
+ const MEMBERS: { readonly [M in Member]: (value: string) => boolean } = {
239
+ isIanaTimeZone,
240
+ isIso3166Alpha2,
241
+ isIso3166Alpha3,
242
+ isIso4217,
243
+ isIso639Alpha2,
244
+ }
245
+
246
+ /**
247
+ * Whether `format` takes `value` exactly as written, its transforms not run: within the format's
248
+ * length bounds, matching its source, and a member of its registry when it names one. Never throws.
249
+ */
250
+ export function formatTakes(format: TextFormat, value: string): boolean {
251
+ const { max, member, min, source } = FORMATS[format]
252
+ if (value.length < min || (max !== null && value.length > max)) return false
253
+ if (!new RegExp(source, "u").test(value)) return false
254
+ return member === null || MEMBERS[member](value)
255
+ }
@@ -0,0 +1,117 @@
1
+ // Written by `bun scripts/registries.ts` from the pinned release below: edit the script, not this file.
2
+ // IANA tzdata 2026f: https://data.iana.org/time-zones/releases/tzdata2026f.tar.gz
3
+ // every Zone and Link name of africa, antarctica, asia, australasia, europe, northamerica, southamerica, etcetera, backward; Factory dropped
4
+
5
+ // lookup-table: the 597 time zone names of tzdata 2026f, one line per area, read by isIanaTimeZone
6
+ const NAMES: ReadonlySet<string> = /* @__PURE__ */ new Set(
7
+ /* @__PURE__ */ `Africa/Abidjan Africa/Accra Africa/Addis_Ababa Africa/Algiers Africa/Asmara Africa/Asmera Africa/Bamako
8
+ Africa/Bangui Africa/Banjul Africa/Bissau Africa/Blantyre Africa/Brazzaville Africa/Bujumbura Africa/Cairo
9
+ Africa/Casablanca Africa/Ceuta Africa/Conakry Africa/Dakar Africa/Dar_es_Salaam Africa/Djibouti Africa/Douala
10
+ Africa/El_Aaiun Africa/Freetown Africa/Gaborone Africa/Harare Africa/Johannesburg Africa/Juba Africa/Kampala
11
+ Africa/Khartoum Africa/Kigali Africa/Kinshasa Africa/Lagos Africa/Libreville Africa/Lome Africa/Luanda
12
+ Africa/Lubumbashi Africa/Lusaka Africa/Malabo Africa/Maputo Africa/Maseru Africa/Mbabane Africa/Mogadishu
13
+ Africa/Monrovia Africa/Nairobi Africa/Ndjamena Africa/Niamey Africa/Nouakchott Africa/Ouagadougou
14
+ Africa/Porto-Novo Africa/Sao_Tome Africa/Timbuktu Africa/Tripoli Africa/Tunis Africa/Windhoek
15
+ America/Adak America/Anchorage America/Anguilla America/Antigua America/Araguaina
16
+ America/Argentina/Buenos_Aires America/Argentina/Catamarca America/Argentina/ComodRivadavia
17
+ America/Argentina/Cordoba America/Argentina/Jujuy America/Argentina/La_Rioja America/Argentina/Mendoza
18
+ America/Argentina/Rio_Gallegos America/Argentina/Salta America/Argentina/San_Juan America/Argentina/San_Luis
19
+ America/Argentina/Tucuman America/Argentina/Ushuaia America/Aruba America/Asuncion America/Atikokan
20
+ America/Atka America/Bahia America/Bahia_Banderas America/Barbados America/Belem America/Belize
21
+ America/Blanc-Sablon America/Boa_Vista America/Bogota America/Boise America/Buenos_Aires America/Cambridge_Bay
22
+ America/Campo_Grande America/Cancun America/Caracas America/Catamarca America/Cayenne America/Cayman
23
+ America/Chicago America/Chihuahua America/Ciudad_Juarez America/Coral_Harbour America/Cordoba
24
+ America/Costa_Rica America/Coyhaique America/Creston America/Cuiaba America/Curacao America/Danmarkshavn
25
+ America/Dawson America/Dawson_Creek America/Denver America/Detroit America/Dominica America/Edmonton
26
+ America/Eirunepe America/El_Salvador America/Ensenada America/Fort_Nelson America/Fort_Wayne America/Fortaleza
27
+ America/Glace_Bay America/Godthab America/Goose_Bay America/Grand_Turk America/Grenada America/Guadeloupe
28
+ America/Guatemala America/Guayaquil America/Guyana America/Halifax America/Havana America/Hermosillo
29
+ America/Indiana/Indianapolis America/Indiana/Knox America/Indiana/Marengo America/Indiana/Petersburg
30
+ America/Indiana/Tell_City America/Indiana/Vevay America/Indiana/Vincennes America/Indiana/Winamac
31
+ America/Indianapolis America/Inuvik America/Iqaluit America/Jamaica America/Jujuy America/Juneau
32
+ America/Kentucky/Louisville America/Kentucky/Monticello America/Knox_IN America/Kralendijk America/La_Paz
33
+ America/Lima America/Los_Angeles America/Louisville America/Lower_Princes America/Maceio America/Managua
34
+ America/Manaus America/Marigot America/Martinique America/Matamoros America/Mazatlan America/Mendoza
35
+ America/Menominee America/Merida America/Metlakatla America/Mexico_City America/Miquelon America/Moncton
36
+ America/Monterrey America/Montevideo America/Montreal America/Montserrat America/Nassau America/New_York
37
+ America/Nipigon America/Nome America/Noronha America/North_Dakota/Beulah America/North_Dakota/Center
38
+ America/North_Dakota/New_Salem America/Nuuk America/Ojinaga America/Panama America/Pangnirtung
39
+ America/Paramaribo America/Phoenix America/Port-au-Prince America/Port_of_Spain America/Porto_Acre
40
+ America/Porto_Velho America/Puerto_Rico America/Punta_Arenas America/Rainy_River America/Rankin_Inlet
41
+ America/Recife America/Regina America/Resolute America/Rio_Branco America/Rosario America/Santa_Isabel
42
+ America/Santarem America/Santiago America/Santo_Domingo America/Sao_Paulo America/Scoresbysund
43
+ America/Shiprock America/Sitka America/St_Barthelemy America/St_Johns America/St_Kitts America/St_Lucia
44
+ America/St_Thomas America/St_Vincent America/Swift_Current America/Tegucigalpa America/Thule
45
+ America/Thunder_Bay America/Tijuana America/Toronto America/Tortola America/Vancouver America/Virgin
46
+ America/Whitehorse America/Winnipeg America/Yakutat America/Yellowknife
47
+ Antarctica/Casey Antarctica/Davis Antarctica/DumontDUrville Antarctica/Macquarie Antarctica/Mawson
48
+ Antarctica/McMurdo Antarctica/Palmer Antarctica/Rothera Antarctica/South_Pole Antarctica/Syowa
49
+ Antarctica/Troll Antarctica/Vostok
50
+ Arctic/Longyearbyen
51
+ Asia/Aden Asia/Almaty Asia/Amman Asia/Anadyr Asia/Aqtau Asia/Aqtobe Asia/Ashgabat Asia/Ashkhabad Asia/Atyrau
52
+ Asia/Baghdad Asia/Bahrain Asia/Baku Asia/Bangkok Asia/Barnaul Asia/Beirut Asia/Bishkek Asia/Brunei
53
+ Asia/Calcutta Asia/Chita Asia/Choibalsan Asia/Chongqing Asia/Chungking Asia/Colombo Asia/Dacca Asia/Damascus
54
+ Asia/Dhaka Asia/Dili Asia/Dubai Asia/Dushanbe Asia/Famagusta Asia/Gaza Asia/Harbin Asia/Hebron
55
+ Asia/Ho_Chi_Minh Asia/Hong_Kong Asia/Hovd Asia/Irkutsk Asia/Istanbul Asia/Jakarta Asia/Jayapura Asia/Jerusalem
56
+ Asia/Kabul Asia/Kamchatka Asia/Karachi Asia/Kashgar Asia/Kathmandu Asia/Katmandu Asia/Khandyga Asia/Kolkata
57
+ Asia/Krasnoyarsk Asia/Kuala_Lumpur Asia/Kuching Asia/Kuwait Asia/Macao Asia/Macau Asia/Magadan Asia/Makassar
58
+ Asia/Manila Asia/Muscat Asia/Nicosia Asia/Novokuznetsk Asia/Novosibirsk Asia/Omsk Asia/Oral Asia/Phnom_Penh
59
+ Asia/Pontianak Asia/Pyongyang Asia/Qatar Asia/Qostanay Asia/Qyzylorda Asia/Rangoon Asia/Riyadh Asia/Saigon
60
+ Asia/Sakhalin Asia/Samarkand Asia/Seoul Asia/Shanghai Asia/Singapore Asia/Srednekolymsk Asia/Taipei
61
+ Asia/Tashkent Asia/Tbilisi Asia/Tehran Asia/Tel_Aviv Asia/Thimbu Asia/Thimphu Asia/Tokyo Asia/Tomsk
62
+ Asia/Ujung_Pandang Asia/Ulaanbaatar Asia/Ulan_Bator Asia/Urumqi Asia/Ust-Nera Asia/Vientiane Asia/Vladivostok
63
+ Asia/Yakutsk Asia/Yangon Asia/Yekaterinburg Asia/Yerevan
64
+ Atlantic/Azores Atlantic/Bermuda Atlantic/Canary Atlantic/Cape_Verde Atlantic/Faeroe Atlantic/Faroe
65
+ Atlantic/Jan_Mayen Atlantic/Madeira Atlantic/Reykjavik Atlantic/South_Georgia Atlantic/St_Helena
66
+ Atlantic/Stanley
67
+ Australia/ACT Australia/Adelaide Australia/Brisbane Australia/Broken_Hill Australia/Canberra Australia/Currie
68
+ Australia/Darwin Australia/Eucla Australia/Hobart Australia/LHI Australia/Lindeman Australia/Lord_Howe
69
+ Australia/Melbourne Australia/NSW Australia/North Australia/Perth Australia/Queensland Australia/South
70
+ Australia/Sydney Australia/Tasmania Australia/Victoria Australia/West Australia/Yancowinna
71
+ Brazil/Acre Brazil/DeNoronha Brazil/East Brazil/West
72
+ CET CST6CDT
73
+ Canada/Atlantic Canada/Central Canada/Eastern Canada/Mountain Canada/Newfoundland Canada/Pacific
74
+ Canada/Saskatchewan Canada/Yukon
75
+ Chile/Continental Chile/EasterIsland
76
+ Cuba EET EST EST5EDT Egypt Eire
77
+ Etc/GMT Etc/GMT+0 Etc/GMT+1 Etc/GMT+10 Etc/GMT+11 Etc/GMT+12 Etc/GMT+2 Etc/GMT+3 Etc/GMT+4 Etc/GMT+5 Etc/GMT+6
78
+ Etc/GMT+7 Etc/GMT+8 Etc/GMT+9 Etc/GMT-0 Etc/GMT-1 Etc/GMT-10 Etc/GMT-11 Etc/GMT-12 Etc/GMT-13 Etc/GMT-14
79
+ Etc/GMT-2 Etc/GMT-3 Etc/GMT-4 Etc/GMT-5 Etc/GMT-6 Etc/GMT-7 Etc/GMT-8 Etc/GMT-9 Etc/GMT0 Etc/Greenwich Etc/UCT
80
+ Etc/UTC Etc/Universal Etc/Zulu
81
+ Europe/Amsterdam Europe/Andorra Europe/Astrakhan Europe/Athens Europe/Belfast Europe/Belgrade Europe/Berlin
82
+ Europe/Bratislava Europe/Brussels Europe/Bucharest Europe/Budapest Europe/Busingen Europe/Chisinau
83
+ Europe/Copenhagen Europe/Dublin Europe/Gibraltar Europe/Guernsey Europe/Helsinki Europe/Isle_of_Man
84
+ Europe/Istanbul Europe/Jersey Europe/Kaliningrad Europe/Kiev Europe/Kirov Europe/Kyiv Europe/Lisbon
85
+ Europe/Ljubljana Europe/London Europe/Luxembourg Europe/Madrid Europe/Malta Europe/Mariehamn Europe/Minsk
86
+ Europe/Monaco Europe/Moscow Europe/Nicosia Europe/Oslo Europe/Paris Europe/Podgorica Europe/Prague Europe/Riga
87
+ Europe/Rome Europe/Samara Europe/San_Marino Europe/Sarajevo Europe/Saratov Europe/Simferopol Europe/Skopje
88
+ Europe/Sofia Europe/Stockholm Europe/Tallinn Europe/Tirane Europe/Tiraspol Europe/Ulyanovsk Europe/Uzhgorod
89
+ Europe/Vaduz Europe/Vatican Europe/Vienna Europe/Vilnius Europe/Volgograd Europe/Warsaw Europe/Zagreb
90
+ Europe/Zaporozhye Europe/Zurich
91
+ GB GB-Eire GMT GMT+0 GMT-0 GMT0 Greenwich HST Hongkong Iceland
92
+ Indian/Antananarivo Indian/Chagos Indian/Christmas Indian/Cocos Indian/Comoro Indian/Kerguelen Indian/Mahe
93
+ Indian/Maldives Indian/Mauritius Indian/Mayotte Indian/Reunion
94
+ Iran Israel Jamaica Japan Kwajalein Libya MET MST MST7MDT
95
+ Mexico/BajaNorte Mexico/BajaSur Mexico/General
96
+ NZ NZ-CHAT Navajo PRC PST8PDT
97
+ Pacific/Apia Pacific/Auckland Pacific/Bougainville Pacific/Chatham Pacific/Chuuk Pacific/Easter Pacific/Efate
98
+ Pacific/Enderbury Pacific/Fakaofo Pacific/Fiji Pacific/Funafuti Pacific/Galapagos Pacific/Gambier
99
+ Pacific/Guadalcanal Pacific/Guam Pacific/Honolulu Pacific/Johnston Pacific/Kanton Pacific/Kiritimati
100
+ Pacific/Kosrae Pacific/Kwajalein Pacific/Majuro Pacific/Marquesas Pacific/Midway Pacific/Nauru Pacific/Niue
101
+ Pacific/Norfolk Pacific/Noumea Pacific/Pago_Pago Pacific/Palau Pacific/Pitcairn Pacific/Pohnpei Pacific/Ponape
102
+ Pacific/Port_Moresby Pacific/Rarotonga Pacific/Saipan Pacific/Samoa Pacific/Tahiti Pacific/Tarawa
103
+ Pacific/Tongatapu Pacific/Truk Pacific/Wake Pacific/Wallis Pacific/Yap
104
+ Poland Portugal ROC ROK Singapore Turkey UCT
105
+ US/Alaska US/Aleutian US/Arizona US/Central US/East-Indiana US/Eastern US/Hawaii US/Indiana-Starke US/Michigan
106
+ US/Mountain US/Pacific US/Samoa
107
+ UTC Universal W-SU WET Zulu`.split(/\s/u),
108
+ )
109
+
110
+ /**
111
+ * Whether `value` names a time zone of IANA tzdata 2026f, exactly as written: a zone or a link to one, such as
112
+ * Europe/Zagreb, America/Argentina/Buenos_Aires, Etc/GMT+5, UTC or US/Pacific. Case counts, and Factory, a
113
+ * placeholder, is no zone. Any string is taken; this never throws.
114
+ */
115
+ export function isIanaTimeZone(value: string): boolean {
116
+ return NAMES.has(value)
117
+ }