{"_id":"@deathbycode/civitas-id-sweden","_rev":"5-e655800c2c628ef6253ab7833d5ca82f","name":"@deathbycode/civitas-id-sweden","dist-tags":{"latest":"2.1.0"},"versions":{"1.0.0":{"name":"@deathbycode/civitas-id-sweden","version":"1.0.0","keywords":["sweden","personnummer","samordningsnummer","organisationsnummer","swedish-id","identity","validation","luhn","typescript"],"license":"MIT","_id":"@deathbycode/civitas-id-sweden@1.0.0","maintainers":[{"name":"deathbycode","email":"mattias.carlsson01@gmail.com"}],"homepage":"https://github.com/crippledgeek/civitas-id#readme","bugs":{"url":"https://github.com/crippledgeek/civitas-id/issues"},"dist":{"shasum":"6abe0a66bf01822713dfd3d9f65fcc5f597fdcd1","tarball":"https://registry.npmjs.org/@deathbycode/civitas-id-sweden/-/civitas-id-sweden-1.0.0.tgz","fileCount":11,"integrity":"sha512-sx3qJhb9AXk8pMSKLWFkRRVU7srM7jlekUr6sWEkPFZJjtJCYEhcWNQnOoTFCjL60h4O5uHzL1DdS1MwGO89Pg==","signatures":[{"sig":"MEUCIQCMXSIPfKwSWktk7mTcTn8RfLkOIluuuWA05Ipi68QXFwIgdbPrx9wkfE3OdB3YZzFGWx4b8ylAkRqvGq9tSEL/IYI=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":165273},"type":"module","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./testing":{"types":"./dist/testing.d.ts","import":"./dist/testing.js"}},"gitHead":"4dfe5ba2b22160b023d064c94d179ec24d8334be","scripts":{"test":"vitest run","build":"tsup","typecheck":"tsc --noEmit"},"_npmUser":{"name":"deathbycode","email":"mattias.carlsson01@gmail.com"},"repository":{"url":"git+https://github.com/crippledgeek/civitas-id.git","type":"git","directory":"packages/sweden"},"_npmVersion":"10.9.2","description":"Swedish identity number validation — personnummer, samordningsnummer, organisationsnummer","directories":{},"_nodeVersion":"22.14.0","dependencies":{"@deathbycode/civitas-id-core":"workspace:*"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"*","vitest":"*","typescript":"*","@types/node":"^25.3.3"},"_npmOperationalInternal":{"tmp":"tmp/civitas-id-sweden_1.0.0_1772471840311_0.688430163501021","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@deathbycode/civitas-id-sweden","version":"1.0.1","keywords":["sweden","personnummer","samordningsnummer","organisationsnummer","swedish-id","identity","validation","luhn","typescript"],"license":"MIT","_id":"@deathbycode/civitas-id-sweden@1.0.1","maintainers":[{"name":"deathbycode","email":"mattias.carlsson01@gmail.com"}],"homepage":"https://github.com/crippledgeek/civitas-id#readme","bugs":{"url":"https://github.com/crippledgeek/civitas-id/issues"},"dist":{"shasum":"26942cd470aed33b06bd4c14f2b4afe8f595a9cc","tarball":"https://registry.npmjs.org/@deathbycode/civitas-id-sweden/-/civitas-id-sweden-1.0.1.tgz","fileCount":12,"integrity":"sha512-06yOqT4BUT0yzRrWC1/dRzxfbQpnDNH6VAN+p0xSeBmo3ix8s9IApXDlsH94KSzCV7iZV/bA8bCYUmvbs5pWmw==","signatures":[{"sig":"MEYCIQClXaQiQtrHttFi5BOdP3BPKlvN50lG1P9jT8fyHI1HDQIhANCMByFBi3ugLE8a3jvo4km6lQSZUVHzO5Ffn6l3MgB8","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@deathbycode%2fcivitas-id-sweden@1.0.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":188416},"type":"module","_from":"file:deathbycode-civitas-id-sweden-1.0.1.tgz","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./testing":{"types":"./dist/testing.d.ts","import":"./dist/testing.js"}},"scripts":{"test":"vitest run","build":"tsup","typecheck":"tsc --noEmit"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:740db332-20f6-496b-bd2c-dbe1a936bdb7"}},"_resolved":"/tmp/9266254db3bcf08f1eff8d372ce7dc8d/deathbycode-civitas-id-sweden-1.0.1.tgz","_integrity":"sha512-06yOqT4BUT0yzRrWC1/dRzxfbQpnDNH6VAN+p0xSeBmo3ix8s9IApXDlsH94KSzCV7iZV/bA8bCYUmvbs5pWmw==","repository":{"url":"git+https://github.com/crippledgeek/civitas-id.git","type":"git","directory":"packages/sweden"},"_npmVersion":"11.11.0","description":"Swedish identity number validation — personnummer, samordningsnummer, organisationsnummer","directories":{},"_nodeVersion":"22.22.0","dependencies":{"@deathbycode/civitas-id-core":"1.0.1"},"publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"tsup":"*","vitest":"*","typescript":"*","@types/node":"^25.3.3"},"_npmOperationalInternal":{"tmp":"tmp/civitas-id-sweden_1.0.1_1772473143864_0.32705413806313177","host":"s3://npm-registry-packages-npm-production"}},"1.0.2":{"name":"@deathbycode/civitas-id-sweden","version":"1.0.2","keywords":["sweden","personnummer","samordningsnummer","organisationsnummer","swedish-id","identity","validation","luhn","typescript"],"license":"MIT","_id":"@deathbycode/civitas-id-sweden@1.0.2","maintainers":[{"name":"deathbycode","email":"mattias.carlsson01@gmail.com"}],"homepage":"https://github.com/crippledgeek/civitas-id#readme","bugs":{"url":"https://github.com/crippledgeek/civitas-id/issues"},"dist":{"shasum":"38194fef1bc37e0430d6a78e93475e418d414963","tarball":"https://registry.npmjs.org/@deathbycode/civitas-id-sweden/-/civitas-id-sweden-1.0.2.tgz","fileCount":12,"integrity":"sha512-z3eBHoY+cloNgnkE1+BSMqIjYGipszMfFf8lR9K3uiSHvBNsw5ACXNJ6N77DQEES4v3wPoDCDr0pRLJzZU90Kw==","signatures":[{"sig":"MEUCIGG6N3y2rfc3f9GGSeipArFN9xZlre0Yt4FEPcYkR2dfAiEAiHNOMFxKTjBIDDWvLjeNloxZyPn2PKrlu8A/j1/eQTI=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@deathbycode%2fcivitas-id-sweden@1.0.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":188416},"type":"module","_from":"file:deathbycode-civitas-id-sweden-1.0.2.tgz","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./testing":{"types":"./dist/testing.d.ts","import":"./dist/testing.js"}},"scripts":{"test":"vitest run","build":"tsup","typecheck":"tsc --noEmit"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:740db332-20f6-496b-bd2c-dbe1a936bdb7"}},"_resolved":"/tmp/f1474762d8f25697e1b43b02df74f46e/deathbycode-civitas-id-sweden-1.0.2.tgz","_integrity":"sha512-z3eBHoY+cloNgnkE1+BSMqIjYGipszMfFf8lR9K3uiSHvBNsw5ACXNJ6N77DQEES4v3wPoDCDr0pRLJzZU90Kw==","repository":{"url":"git+https://github.com/crippledgeek/civitas-id.git","type":"git","directory":"packages/sweden"},"_npmVersion":"11.12.1","description":"Swedish identity number validation — personnummer, samordningsnummer, organisationsnummer","directories":{},"_nodeVersion":"22.22.1","dependencies":{"@deathbycode/civitas-id-core":"1.0.2"},"publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"tsup":"*","vitest":"*","typescript":"*","@types/node":"^25.3.3"},"_npmOperationalInternal":{"tmp":"tmp/civitas-id-sweden_1.0.2_1774990153265_0.8619927133052303","host":"s3://npm-registry-packages-npm-production"}},"2.0.0":{"name":"@deathbycode/civitas-id-sweden","version":"2.0.0","keywords":["sweden","personnummer","samordningsnummer","organisationsnummer","swedish-id","identity","validation","luhn","typescript"],"license":"MIT","_id":"@deathbycode/civitas-id-sweden@2.0.0","maintainers":[{"name":"deathbycode","email":"mattias.carlsson01@gmail.com"}],"homepage":"https://github.com/crippledgeek/civitas-id#readme","bugs":{"url":"https://github.com/crippledgeek/civitas-id/issues"},"dist":{"shasum":"5e8bb4f390763cf06c4762a0c151f1517d4185a5","tarball":"https://registry.npmjs.org/@deathbycode/civitas-id-sweden/-/civitas-id-sweden-2.0.0.tgz","fileCount":12,"integrity":"sha512-RFiZBg6YQrL3LSgoSHRPvjvJTeRhITloY3Vkx+gcFRvks1YniVte4EBATi3n2XaU3FrKZYtq69hd1i2Y2/SPYg==","signatures":[{"sig":"MEYCIQCOKKdIPsZO/cX5hI8r5fQ1hSBh5j5AW9XgE3TIrzfFQQIhAP4pGc+s4i+WKj7BxzoDArHAtjoNtIUxthO6wXnj9rh/","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@deathbycode%2fcivitas-id-sweden@2.0.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":201562},"type":"module","_from":"file:deathbycode-civitas-id-sweden-2.0.0.tgz","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./testing":{"types":"./dist/testing.d.ts","import":"./dist/testing.js"}},"scripts":{"test":"vitest run","build":"tsup","typecheck":"tsc --noEmit"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:740db332-20f6-496b-bd2c-dbe1a936bdb7"}},"_resolved":"/tmp/b1bb74264f49303959be5b7e5e611b2a/deathbycode-civitas-id-sweden-2.0.0.tgz","_integrity":"sha512-RFiZBg6YQrL3LSgoSHRPvjvJTeRhITloY3Vkx+gcFRvks1YniVte4EBATi3n2XaU3FrKZYtq69hd1i2Y2/SPYg==","repository":{"url":"git+https://github.com/crippledgeek/civitas-id.git","type":"git","directory":"packages/sweden"},"_npmVersion":"11.11.0","description":"Swedish identity number validation — personnummer, samordningsnummer, organisationsnummer","directories":{},"_nodeVersion":"24.14.1","dependencies":{"@deathbycode/civitas-id-core":"2.0.0"},"publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"tsup":"*","vitest":"*","fast-check":"^4.7.0","typescript":"*","@types/node":"^25.3.3","@fast-check/vitest":"^0.3.0"},"_npmOperationalInternal":{"tmp":"tmp/civitas-id-sweden_2.0.0_1778443182015_0.7825646467724197","host":"s3://npm-registry-packages-npm-production"}},"2.1.0":{"name":"@deathbycode/civitas-id-sweden","version":"2.1.0","description":"Swedish identity number validation — personnummer, samordningsnummer, organisationsnummer","license":"MIT","type":"module","keywords":["sweden","personnummer","samordningsnummer","organisationsnummer","swedish-id","identity","validation","luhn","typescript"],"homepage":"https://github.com/crippledgeek/civitas-id#readme","repository":{"type":"git","url":"git+https://github.com/crippledgeek/civitas-id.git","directory":"packages/sweden"},"bugs":{"url":"https://github.com/crippledgeek/civitas-id/issues"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./testing":{"types":"./dist/testing.d.ts","import":"./dist/testing.js"}},"dependencies":{"@deathbycode/civitas-id-core":"2.1.0"},"publishConfig":{"access":"public","provenance":true},"devDependencies":{"@fast-check/vitest":"^0.3.0","@types/node":"^25.3.3","fast-check":"^4.7.0","tsup":"*","typescript":"*","vitest":"*"},"scripts":{"build":"tsup","test":"vitest run","typecheck":"tsc --noEmit"},"_id":"@deathbycode/civitas-id-sweden@2.1.0","_integrity":"sha512-t+1aD2vLBkVJ4v07/JmhEqSbbTHzejHEpFkItifP/XI7HMnSv0rdN51zZW8qjbPgLPME/JjskdHlbBWRiKd23w==","_resolved":"/tmp/4b71bf470f1c29fe2b4b029e2fb39337/deathbycode-civitas-id-sweden-2.1.0.tgz","_from":"file:deathbycode-civitas-id-sweden-2.1.0.tgz","_nodeVersion":"24.15.0","_npmVersion":"11.12.1","dist":{"integrity":"sha512-t+1aD2vLBkVJ4v07/JmhEqSbbTHzejHEpFkItifP/XI7HMnSv0rdN51zZW8qjbPgLPME/JjskdHlbBWRiKd23w==","shasum":"60cccee070f355bec5cdcba4f1a924f486fdda66","tarball":"https://registry.npmjs.org/@deathbycode/civitas-id-sweden/-/civitas-id-sweden-2.1.0.tgz","fileCount":12,"unpackedSize":224790,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@deathbycode%2fcivitas-id-sweden@2.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQC0E0NZMrxkCaUVKwmDPjfnPF1J8gVOU7LuH+dKWV1SnQIgHTtsoP14HqcdFmCPZfdtK3yuHDIoRk9PMKrfAF8JTL0="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:740db332-20f6-496b-bd2c-dbe1a936bdb7"}},"directories":{},"maintainers":[{"name":"deathbycode","email":"mattias.carlsson01@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/civitas-id-sweden_2.1.0_1779207813214_0.1345931353177947"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-02T17:17:20.199Z","modified":"2026-05-19T16:23:33.702Z","1.0.0":"2026-03-02T17:17:20.464Z","1.0.1":"2026-03-02T17:39:04.023Z","1.0.2":"2026-03-31T20:49:13.429Z","2.0.0":"2026-05-10T19:59:42.223Z","2.1.0":"2026-05-19T16:23:33.399Z"},"bugs":{"url":"https://github.com/crippledgeek/civitas-id/issues"},"license":"MIT","homepage":"https://github.com/crippledgeek/civitas-id#readme","keywords":["sweden","personnummer","samordningsnummer","organisationsnummer","swedish-id","identity","validation","luhn","typescript"],"repository":{"type":"git","url":"git+https://github.com/crippledgeek/civitas-id.git","directory":"packages/sweden"},"description":"Swedish identity number validation — personnummer, samordningsnummer, organisationsnummer","maintainers":[{"name":"deathbycode","email":"mattias.carlsson01@gmail.com"}],"readme":"# Civitas ID\n\n[![TypeScript](https://img.shields.io/badge/TypeScript-5.7%2B-blue)](https://www.typescriptlang.org/)\n[![pnpm](https://img.shields.io/badge/pnpm-10.30%2B-blue)](https://pnpm.io/)\n[![License](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)\n[![Tests](https://img.shields.io/badge/Tests-passing-brightgreen)](packages/sweden/test)\n\nA comprehensive TypeScript library for validating and working with Swedish personal identification numbers (personnummer), coordination numbers (samordningsnummer), and organisation numbers (organisationsnummer).\n\n## v2.0.0 (breaking)\n\n- `getAge` / `isAdult` / `isChild` on Swedish ID classes now anchor to **Europe/Stockholm** civil time when called without a clock argument. v1 anchored to UTC and returned the wrong age during the 1–2 hour window between Stockholm and UTC midnight every birthday.\n- Leap-day birthdays (born 29 February) now attain age on **28 February** in non-leap years per **Lag (1930:173) §1**. v1 reported them as one year younger on 28 February.\n- **Removed** from `@deathbycode/civitas-id-core`: `LocalDate.now()`, `LocalDate.prototype.age()`, `IdValidator`, `ValidationResult`, `IdFormat`. Replacement: `computeAge(birth, today, resolver)` with the country package's `AnniversaryResolver`. The Sweden ID classes apply this internally; consumers using `LocalDate.now()` or `localDate.age(ref)` directly must migrate.\n- Public method signatures of `PersonalId` / `CoordinationId` / `OrganisationId` are unchanged.\n\n## Table of Contents\n\n- [Overview](#overview)\n- [Features](#features)\n- [Installation](#installation)\n- [Usage Examples](#usage-examples)\n- [Error Handling](#error-handling)\n- [Format Options](#format-options)\n- [Type Guards & Discriminated Unions](#type-guards--discriminated-unions)\n- [Test Utilities (Fakers)](#test-utilities-fakers)\n- [Architecture](#architecture)\n- [Core Utilities](#core-utilities)\n- [Requirements](#requirements)\n- [Building the Project](#building-the-project)\n- [API Design](#api-design)\n- [Swedish Organisation Number Format](#swedish-organisation-number-format)\n- [Organisation Forms](#organisation-forms)\n- [License](#license)\n\n## Overview\n\nThis library provides comprehensive functionality for working with Swedish official identification numbers according to official rules and formats from Skatteverket and Bolagsverket.\n\n## Features\n\n### Personal Identification\n- **Personal Numbers (personnummer)**: Validation and formatting of Swedish personal ID numbers\n- **Coordination Numbers (samordningsnummer)**: Support for coordination numbers assigned to individuals without Swedish personal numbers\n- Support for different formats (10-digit, 12-digit, with or without separators)\n- Handling of personal numbers from different time periods (19th, 20th, and 21st centuries)\n- Birth date extraction and age calculation\n- Gender determination (male/female)\n\n### Organisation Identification\n- **Organisation Numbers (organisationsnummer)**: Validation and formatting of Swedish organisation numbers\n- **Organisation Form Detection**: Automatic detection of organisation type (e.g., Aktiebolag, Ekonomiska foreningar, etc.)\n  - Support for all 33 official Swedish organisation forms\n  - Distinction between legal persons (juridisk person) and physical persons (enskild firma)\n- **Hybrid Format Support**: Accepts both official 10-digit format and legacy 12-digit format for compatibility\n  - **Input**: Accepts `556012-3456` (10-digit) or `165560123456` (12-digit legacy)\n  - **Output**: Always returns official 10-digit format\n\n### Additional Features\n- Checksum validation using the Luhn algorithm\n- Type-safe API with discriminated unions and type guards\n- Stockholm civil-time anchoring for legally correct age computation under Swedish statute\n- Lag (1930:173) §1 leap-day rule for 29 February birthdays\n- Zero runtime dependencies\n- Test utilities (fakers) for generating valid test data\n\n## Installation\n\n```bash\nnpm install @deathbycode/civitas-id-sweden\n# or\npnpm add @deathbycode/civitas-id-sweden\n```\n\n## Usage Examples\n\n### Personal Numbers\n\n```typescript\nimport { PersonalId } from \"@deathbycode/civitas-id-sweden\";\n\n// Parse and validate a personal number (returns undefined on failure)\nconst personalId = PersonalId.parse(\"202407132394\");\n\nif (personalId) {\n  const birthDate = personalId.getBirthDate(); // LocalDate { year: 2024, month: 7, day: 13 }\n  const longFormat = personalId.longFormat();    // \"202407132394\"\n}\n\n// Or use parseOrThrow() when you expect valid input\nconst id = PersonalId.parseOrThrow(\"202407132394\");\nconst birthDate = id.getBirthDate();\n\n// Format in different ways\nid.longFormat();                  // \"202407132394\"\nid.shortFormat();                 // \"2407132394\"\nid.shortFormatWithSeparator();    // \"240713-2394\"\n\n// Check gender\nid.isMale();    // true or false\nid.isFemale();  // true or false\n\n// Check age (anchored to Europe/Stockholm civil time when called with no arg)\nid.getAge();    // current age in years\nid.isAdult();   // true if 18+\nid.isChild();   // true if under 18\n\n// Check if valid\nPersonalId.isValid(\"202407132394\"); // true\n```\n\n### Coordination Numbers\n\n```typescript\nimport { CoordinationId } from \"@deathbycode/civitas-id-sweden\";\n\n// Parse a coordination number (returns undefined on failure)\nconst coordId = CoordinationId.parse(\"198206822390\");\n\n// Or use parseOrThrow() when you expect valid input\nconst id = CoordinationId.parseOrThrow(\"198206822390\");\n\n// Coordination numbers have birth day + 60\nconst birthDate = id.getBirthDate(); // 1982-06-22 (not 1982-06-82)\n```\n\n### Organisation Numbers\n\n```typescript\nimport { OrganisationId, OrganisationNumberType } from \"@deathbycode/civitas-id-sweden\";\n\n// Parse an organisation number (accepts both 10 and 12-digit formats)\nconst orgId = OrganisationId.parse(\"556012-3456\");\n\n// Or use parseOrThrow() when you expect valid input\nconst id = OrganisationId.parseOrThrow(\"556012-3456\");\n\n// Get the organisation form\nconst form = id.getOrganisationForm();\nconsole.log(form.code);        // 56\nconsole.log(form.description); // \"Ovriga aktiebolag\"\n\n// Check if legal or physical person\nid.isLegalPerson();   // true for companies\nid.isPhysicalPerson(); // true for sole proprietors\n\n// Format (always outputs 10-digit official format)\nid.longFormatWithSeparator(); // \"556012-3456\"\n\n// Validate with type checking\nOrganisationId.isValid(\"556012-3456\", OrganisationNumberType.LEGAL_PERSON);\n```\n\n### Parsing Any Swedish ID Type\n\nWhen you don't know the specific type of Swedish ID, use the unified parsing methods:\n\n```typescript\nimport {\n  SwedishOfficialId,\n  PersonalId,\n  CoordinationId,\n  OrganisationId,\n  InvalidIdNumberError,\n  isPersonalId,\n  isCoordinationId,\n  isOrganisationId,\n} from \"@deathbycode/civitas-id-sweden\";\n\n// Parse any Swedish ID type (returns undefined on failure)\nconst result = SwedishOfficialId.parseAny(\"202407132394\");\n\nif (result) {\n  // Use type guards to narrow the type\n  if (isPersonalId(result)) {\n    console.log(\"Personal ID:\", result.getBirthDate());\n  } else if (isCoordinationId(result)) {\n    console.log(\"Coordination ID:\", result.getBirthDate());\n  } else if (isOrganisationId(result)) {\n    console.log(\"Organisation ID:\", result.getOrganisationForm());\n  }\n}\n\n// Or use parseAnyOrThrow() when you expect valid input\ntry {\n  const id = SwedishOfficialId.parseAnyOrThrow(\"202407132394\");\n\n  // Use discriminated union with switch\n  switch (id.type) {\n    case \"PERSONAL\":\n      console.log(id.getBirthDate());\n      break;\n    case \"COORDINATION\":\n      console.log(id.getBirthDate());\n      break;\n    case \"ORGANISATION\":\n      console.log(id.getOrganisationForm());\n      break;\n  }\n} catch (e) {\n  if (e instanceof InvalidIdNumberError) {\n    console.error(\"Invalid Swedish ID:\", e.message);\n  } else {\n    throw e;\n  }\n}\n\n// Validate any Swedish ID type\nSwedishOfficialId.isValid(\"202407132394\"); // true\n```\n\n**Parsing Priority:**\nThe unified parser attempts to parse in this order:\n1. Personal number (personnummer)\n2. Coordination number (samordningsnummer)\n3. Organisation number (organisationsnummer)\n\n## Error Handling\n\nAll failures from the throwing APIs (`parseOrThrow`, `parseAnyOrThrow`, `format`) throw `InvalidIdNumberError`, which extends the standard `Error` class and supports error cause chaining via `ErrorOptions`. Non-throwing parse helpers (`parse`, `parseAny`) return `undefined` on failure:\n\n```typescript\nimport { PersonalId, InvalidIdNumberError } from \"@deathbycode/civitas-id-sweden\";\n\n// Catch and inspect parsing errors\ntry {\n  const id = PersonalId.parseOrThrow(\"invalid-input\");\n} catch (e) {\n  if (e instanceof InvalidIdNumberError) {\n    console.error(e.message); // \"Invalid personal ID: invalid-input\"\n  }\n}\n\n// Error cause chaining — wrap lower-level errors\ntry {\n  processId(input);\n} catch (e) {\n  throw new InvalidIdNumberError(\"Failed to process ID\", { cause: e });\n}\n```\n\n\n## Format Options\n\nPersonal and coordination IDs support six output formats via the `PnrFormat` union. The separator character is age-sensitive: `-` for persons under 100, `+` for centenarians.\n\n| PnrFormat Value | Pattern | Example (born 1990-05-15) | Example (born 1890-05-15) |\n|-----------------|---------|---------------------------|---------------------------|\n| `LONG_FORMAT` | `YYYYMMDDXXXX` | `199005151239` | `189005151239` |\n| `LONG_FORMAT_WITH_STANDARD_SEPARATOR` | `YYYYMMDD-XXXX` | `19900515-1239` | `18900515-1239` |\n| `LONG_FORMAT_WITH_SEPARATOR` | `YYYYMMDD-XXXX` or `YYYYMMDD+XXXX` | `19900515-1239` | `18900515+1239` |\n| `SHORT_FORMAT` | `YYMMDDXXXX` | `9005151239` | `9005151239` |\n| `SHORT_FORMAT_WITH_STANDARD_SEPARATOR` | `YYMMDD-XXXX` | `900515-1239` | `900515-1239` |\n| `SHORT_FORMAT_WITH_SEPARATOR` | `YYMMDD-XXXX` or `YYMMDD+XXXX` | `900515-1239` | `900515+1239` |\n\n```typescript\nimport { PersonalId, PnrFormat } from \"@deathbycode/civitas-id-sweden\";\n\nconst id = PersonalId.parseOrThrow(\"199005151239\");\n\n// Convenience methods (use standard separator)\nid.longFormat();               // \"199005151239\"\nid.shortFormat();              // \"9005151239\"\nid.longFormatWithSeparator();  // \"19900515-1239\"\nid.shortFormatWithSeparator(); // \"900515-1239\"\n\n// Explicit format selection\nid.formatted(PnrFormat.LONG_FORMAT_WITH_SEPARATOR);  // \"19900515-1239\" (or +)\nid.formatted(PnrFormat.SHORT_FORMAT_WITH_SEPARATOR); // \"900515-1239\" (or +)\n```\n\n> **Organisation IDs** also accept `PnrFormat` in their `formatted()` method, but long and short variants produce identical output since organisation numbers are always 10 digits (the internal `16` century prefix is always stripped).\n\n## Type Guards & Discriminated Unions\n\n`SwedishOfficialId` is a union type with a `type` discriminant field:\n\n```typescript\ntype SwedishOfficialId = PersonalId | CoordinationId | OrganisationId;\n```\n\nEach variant carries a `type` field: `\"PERSONAL\"`, `\"COORDINATION\"`, or `\"ORGANISATION\"`.\n\n### Type guard functions\n\nFour type guard functions are exported for narrowing:\n\n```typescript\nimport {\n  SwedishOfficialId,\n  isPersonalId,\n  isCoordinationId,\n  isOrganisationId,\n  isPersonOfficialId,\n} from \"@deathbycode/civitas-id-sweden\";\n\nconst id = SwedishOfficialId.parseAnyOrThrow(input);\n\n// Narrow to a specific type\nif (isPersonalId(id)) {\n  id.getBirthDate(); // PersonalId methods available\n}\n\nif (isCoordinationId(id)) {\n  id.getBirthDate(); // CoordinationId methods available\n}\n\nif (isOrganisationId(id)) {\n  id.getOrganisationForm(); // OrganisationId methods available\n}\n\n// Narrow to person types (PersonalId | CoordinationId)\nif (isPersonOfficialId(id)) {\n  id.getBirthDate(); // shared person methods available\n  id.getAge();\n  id.isMale();\n}\n```\n\n### Switch on discriminant\n\n```typescript\nswitch (id.type) {\n  case \"PERSONAL\":\n    console.log(\"Personal ID:\", id.getBirthDate());\n    break;\n  case \"COORDINATION\":\n    console.log(\"Coordination ID:\", id.getBirthDate());\n    break;\n  case \"ORGANISATION\":\n    console.log(\"Organisation ID:\", id.getOrganisationForm());\n    break;\n}\n```\n\n## Test Utilities (Fakers)\n\nThe library includes test utilities for generating valid test data, available via a separate subpath export to keep them out of production bundles:\n\n```typescript\nimport { PersonalIdFaker } from \"@deathbycode/civitas-id-sweden/testing\";\n```\n\n### Personal ID Faker\n\n```typescript\nimport { PersonalIdFaker } from \"@deathbycode/civitas-id-sweden/testing\";\nimport { LocalDate } from \"@deathbycode/civitas-id-core\";\n\n// Generate random valid personal ID\nconst randomId = PersonalIdFaker.create();\n\n// Generate with specific birth date\nconst specificId = PersonalIdFaker.create(LocalDate.of(1990, 5, 15));\n\n// Generate with specific date components\nconst id = PersonalIdFaker.createFor(1990, 5, 15);\n\n// Generate gender-specific IDs\nconst male = PersonalIdFaker.createMale();\nconst female = PersonalIdFaker.createFemale();\n\n// Generate centenarian (100+ years old)\nconst centenarian = PersonalIdFaker.createCentenarian();\n```\n\n### Coordination ID Faker\n\n```typescript\nimport { CoordinationIdFaker } from \"@deathbycode/civitas-id-sweden/testing\";\n\n// Generate random coordination ID\nconst randomId = CoordinationIdFaker.create();\n\n// Gender-specific and centenarian methods also available\nconst male = CoordinationIdFaker.createMale();\nconst female = CoordinationIdFaker.createFemale();\nconst centenarian = CoordinationIdFaker.createCentenarian();\n```\n\n### Organisation ID Faker\n\n```typescript\nimport { SwedishOrganisationIdFaker } from \"@deathbycode/civitas-id-sweden/testing\";\n\n// Generate random organisation ID (legal person)\nconst legalPerson = SwedishOrganisationIdFaker.create();\n\n// Generate specific types\nconst legal = SwedishOrganisationIdFaker.createLegalPerson();\nconst physical = SwedishOrganisationIdFaker.createPhysicalPerson();\n```\n\n### Swedish Official ID Faker\n\n```typescript\nimport { SwedishOfficialIdFaker } from \"@deathbycode/civitas-id-sweden/testing\";\n\n// Generate random Swedish ID (PersonalId, CoordinationId, or OrganisationId)\nconst randomId = SwedishOfficialIdFaker.create();\n\n// Generate multiple IDs at once\nconst ids = SwedishOfficialIdFaker.createMany(10);\n```\n\n**Note:** All fakers generate cryptographically secure random IDs using `crypto.getRandomValues()` and ensure proper Luhn checksum validation.\n\n## Architecture\n\n### Multi-Country Design\n\nThe library is architected for multi-country support with a clear separation of concerns:\n\n```\ncivitas-id/\n├── @deathbycode/civitas-id-core          — Generic interfaces and utilities\n│   ├── OfficialId, PersonOfficialId, OrganisationOfficialId interfaces\n│   ├── Generic Luhn algorithm implementation\n│   └── LocalDate value object\n└── @deathbycode/civitas-id-sweden        — Swedish implementations\n    ├── PersonalId, CoordinationId, OrganisationId\n    ├── Swedish-specific validation and formatting\n    └── @deathbycode/civitas-id-sweden/testing — Test fakers for generating Swedish IDs\n```\n\n**Design Principles:**\n- **Core package**: Contains only country-agnostic interfaces and truly generic utilities\n- **Country packages**: Implement core interfaces with country-specific validation rules\n- **Test utilities**: Available via `@deathbycode/civitas-id-sweden/testing` subpath to keep production bundles clean\n- **Discriminated unions**: Type-safe ID hierarchy using TypeScript discriminated unions with `type` field\n- **Idiomatic TypeScript**: Default generic parameters, exhaustiveness guards, discriminated union results, const object singletons\n\n**Extensibility:**\nThe architecture is ready for additional countries (Norway, Finland, Denmark, etc.) following the same pattern. Each country module is self-contained and independent.\n\n## Core Utilities\n\n### LocalDate\n\nA minimal, immutable date value object from `@deathbycode/civitas-id-core`. No external date library dependencies.\n\n```typescript\nimport { LocalDate, computeAge } from \"@deathbycode/civitas-id-core\";\n\n// Factories\nconst date = LocalDate.of(1990, 5, 15);  // from components\nconst parsed = LocalDate.parse(\"1990-05-15\"); // from ISO string\n\n// Read-only properties\ndate.year;   // 1990\ndate.month;  // 5\ndate.day;    // 15\n\n// Methods\ndate.isValid();                   // true if the date exists in the calendar\ndate.equals(other);               // structural equality\ndate.toString();                  // \"1990-05-15\"\n```\n\n`LocalDate` deliberately does **not** expose `now()` or `age()` — clock access and age computation are jurisdiction-bound. Sweden's `getAge`/`isAdult`/`isChild` methods automatically anchor to `Europe/Stockholm` when called with no argument; the `todayInSweden()` clock helper and `swedishAnniversaryResolver` (implementing Lag (1930:173) §1) are used internally and are not part of the public surface.\n\n**Clock injection:** Methods like `getAge()` on Swedish ID objects accept an optional clock function `() => LocalDate` so tests can control the current date. The default anchor is `todayInSweden()` (legally correct for Swedish civil age):\n\n```typescript\nconst id = PersonalId.parseOrThrow(\"199005151239\");\nid.getAge(() => LocalDate.of(2026, 1, 1)); // 35 — deterministic for tests\nid.getAge();                                 // anchored to Europe/Stockholm civil date\n```\n\n### LuhnAlgorithm\n\nA `const` object implementing the standard Luhn (mod-10) checksum algorithm, from `@deathbycode/civitas-id-core`:\n\n```typescript\nimport { LuhnAlgorithm } from \"@deathbycode/civitas-id-core\";\n\n// Calculate the check digit for a digit string\nLuhnAlgorithm.calculateCheckDigit(\"7992739871\"); // 3\n\n// Verify a complete number (last digit is the check digit)\nLuhnAlgorithm.isChecksumValid(\"79927398713\"); // true\n```\n\nBoth methods accept an optional `maxDigits` parameter to limit validation to the last N digits of the input. The Swedish variant uses `maxDigits=10` internally so that 12-digit personal numbers (`YYYYMMDDXXXX`) are validated on only the 10-digit suffix (`YYMMDDXXXX`).\n\n## Requirements\n\n- Node.js 18+\n- TypeScript 5.7+ (for consumers using TypeScript)\n\n## Building the Project\n\n```bash\n# Install dependencies\npnpm install\n\n# Build all packages\npnpm -r build\n\n# Run all tests\npnpm -r test\n\n# Type check\npnpm -r exec tsc --noEmit\n\n# Lint\npnpm biome check .\n```\n\n## API Design\n\nThe library provides a safe API with two parsing approaches:\n\n| Method | Return Type | Use Case |\n|--------|-------------|----------|\n| `parse(string)` | `T \\| undefined` | Uncertain input (user-provided, external APIs) |\n| `parseOrThrow(string)` | `T` | Expected valid input (database, known data) |\n| `isValid(string)` | `boolean` | Validation only |\n\n### Format Methods\n\nAll ID types implement the `OfficialId` interface and share a common set of format methods:\n\n| Method | PersonalId (1990-05-15) | OrganisationId (556012-3456) |\n|--------|-------------------------|------------------------------|\n| `longFormat()` | `\"199005151239\"` | `\"5560123456\"` |\n| `shortFormat()` | `\"9005151239\"` | `\"5560123456\"` |\n| `longFormatWithSeparator()` | `\"19900515-1239\"` | `\"556012-3456\"` |\n| `shortFormatWithSeparator()` | `\"900515-1239\"` | `\"556012-3456\"` |\n| `formatted(PnrFormat)` | *(see [Format Options](#format-options))* | *(long = short for org IDs)* |\n\n> For organisation IDs, long and short variants produce identical output because organisation numbers are always 10 digits.\n\nThe base `SwedishOfficialId` namespace provides unified parsing when the ID type is unknown:\n\n| Method | Return Type | Use Case |\n|--------|-------------|----------|\n| `parseAny(string)` | `SwedishOfficialId \\| undefined` | Parse any type (uncertain input) |\n| `parseAnyOrThrow(string)` | `SwedishOfficialId` | Parse any type (expected valid) |\n\n## Swedish Organisation Number Format\n\n### Official Standard: 10-Digit Format Only\n\n**Swedish organisation numbers are officially ALWAYS 10 digits.**\n\nFormat: `NNNNNN-NNNN` where:\n- Positions 1-2: Legal entity group code (organisation form)\n- Position 3: Always >= 2 (to distinguish from personal numbers)\n- Position 10: Check digit (Luhn algorithm)\n\n### The 12-Digit Convention\n\nThe \"16\" prefix (e.g., `16NNNNNNNNNN`) is **not an official standard** and stems from legacy IT systems using \"16\" as a placeholder century.\n\n**Civitas-ID implementation:**\n- **Input**: Accepts both 10-digit (`556012-3456`) and 12-digit (`165560123456`) for legacy compatibility\n- **Output**: Always returns the official 10-digit format\n\n```typescript\n// Both inputs work\nconst org1 = OrganisationId.parseOrThrow(\"556012-3456\");   // 10-digit\nconst org2 = OrganisationId.parseOrThrow(\"165560123456\");   // 12-digit legacy\n\n// Output is always 10-digit\norg1.longFormat();              // \"5560123456\"\norg2.longFormat();              // \"5560123456\"\norg1.longFormatWithSeparator(); // \"556012-3456\"\norg2.longFormatWithSeparator(); // \"556012-3456\"\n```\n\n## Organisation Forms\n\nThe library supports all 33 official Swedish organisation forms. The organisation form is automatically extracted from the first two digits of the organisation number.\n\n| Code | Organisation Form (Swedish) | Description |\n|------|----------------------------|-------------|\n| **0** | **Ingen organisationsform** | **Physical person (not a legal entity)** |\n| 21 | Enkla bolag | Simple companies |\n| 22 | Partrederier | Shipping partnerships |\n| 31 | Handelsbolag, kommanditbolag | Trading partnerships, limited partnerships |\n| 32 | Gruvbolag | Mining companies |\n| 41 | Bankaktiebolag | Banking companies |\n| 42 | Forsakringsaktiebolag | Insurance companies |\n| 43 | Europabolag | European companies (SE) |\n| 49 | Ovriga aktiebolag | Other limited companies *(most common)* |\n| 51 | Ekonomiska foreningar | Economic associations |\n| 53 | Bostadsrattsforeningar | Tenant-ownership associations |\n| 54 | Kooperativ Hyresrattsforening | Cooperative rental associations |\n| 55 | Europakooperativ, EGTS och Eric-konsortier | European cooperatives, EGTC, Eric consortia |\n| 61 | Ideella foreningar | Non-profit associations |\n| 62 | Samfalligheter | Joint property management associations |\n| 63 | Registrerat trossamfund | Registered religious communities |\n| 71 | Familjestiftelser | Family foundations |\n| 72 | Ovriga stiftelser och fonder | Other foundations and funds |\n| 81 | Statliga enheter | State entities |\n| 82 | Kommuner | Municipalities |\n| 83 | Kommunalforbund | Municipal federations |\n| 84 | Regioner | Regions |\n| 85 | Allmanna forsakringskassor | General insurance funds |\n| 87 | Offentliga korporationer och anstalter | Public corporations and agencies |\n| 88 | Hypoteksforeningar | Mortgage associations |\n| 89 | Regionala statliga myndigheter | Regional state authorities |\n| 91 | Oskiftade dodsbon | Undivided estates |\n| 92 | Omsesidiga forsakringsbolag | Mutual insurance companies |\n| 93 | Sparbanker | Savings banks |\n| 94 | Understodsforeningar och Forsakringsforeningar | Benefit societies and insurance associations |\n| 95 | Arbetsloshetskassor | Unemployment insurance funds |\n| 96 | Utlandska juridiska personer | Foreign legal entities |\n| 98 | Ovriga svenska juridiska personer | Other Swedish legal entities (special legislation) |\n| 99 | Juridisk form ej utredd | Legal form not determined |\n\n**Total: 34 forms** (1 physical person code + 33 legal entity forms)\n\n**Source:** Swedish Companies Registration Office ([Bolagsverket](https://bolagsverket.se))\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n## Official References\n\n- **Skatteverket (Swedish Tax Agency)**: [https://www.skatteverket.se](https://www.skatteverket.se)\n- **Bolagsverket (Companies Registration Office)**: [https://www.bolagsverket.se](https://www.bolagsverket.se)\n- **SCB (Statistics Sweden)**: [https://www.scb.se](https://www.scb.se)\n","readmeFilename":"README.md"}