@yolk-sdk/emulators 0.1.0-canary.96
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.
- package/LICENSE +21 -0
- package/README.md +2135 -0
- package/dist/anthropic.d.mts +269 -0
- package/dist/anthropic.d.mts.map +1 -0
- package/dist/anthropic.mjs +177 -0
- package/dist/anthropic.mjs.map +1 -0
- package/dist/chat-completions.d.mts +288 -0
- package/dist/chat-completions.d.mts.map +1 -0
- package/dist/chat-completions.mjs +451 -0
- package/dist/chat-completions.mjs.map +1 -0
- package/dist/codex.d.mts +265 -0
- package/dist/codex.d.mts.map +1 -0
- package/dist/codex.mjs +199 -0
- package/dist/codex.mjs.map +1 -0
- package/dist/dropbox/api.d.mts +63 -0
- package/dist/dropbox/api.d.mts.map +1 -0
- package/dist/dropbox/api.mjs +565 -0
- package/dist/dropbox/api.mjs.map +1 -0
- package/dist/dropbox/state.d.mts +136 -0
- package/dist/dropbox/state.d.mts.map +1 -0
- package/dist/dropbox/state.mjs +209 -0
- package/dist/dropbox/state.mjs.map +1 -0
- package/dist/dropbox.d.mts +79 -0
- package/dist/dropbox.d.mts.map +1 -0
- package/dist/dropbox.mjs +124 -0
- package/dist/dropbox.mjs.map +1 -0
- package/dist/email-fixtures.d.mts +36 -0
- package/dist/email-fixtures.d.mts.map +1 -0
- package/dist/email-fixtures.mjs +1080 -0
- package/dist/email-fixtures.mjs.map +1 -0
- package/dist/email.d.mts +160 -0
- package/dist/email.d.mts.map +1 -0
- package/dist/email.mjs +608 -0
- package/dist/email.mjs.map +1 -0
- package/dist/emulator-compose.d.mts +40 -0
- package/dist/emulator-compose.d.mts.map +1 -0
- package/dist/emulator-compose.mjs +91 -0
- package/dist/emulator-compose.mjs.map +1 -0
- package/dist/emulator-http.d.mts +53 -0
- package/dist/emulator-http.d.mts.map +1 -0
- package/dist/emulator-http.mjs +117 -0
- package/dist/emulator-http.mjs.map +1 -0
- package/dist/emulator-kernel.d.mts +176 -0
- package/dist/emulator-kernel.d.mts.map +1 -0
- package/dist/emulator-kernel.mjs +413 -0
- package/dist/emulator-kernel.mjs.map +1 -0
- package/dist/fixture-route.d.mts +129 -0
- package/dist/fixture-route.d.mts.map +1 -0
- package/dist/fixture-route.mjs +257 -0
- package/dist/fixture-route.mjs.map +1 -0
- package/dist/fortnox/api.d.mts +92 -0
- package/dist/fortnox/api.d.mts.map +1 -0
- package/dist/fortnox/api.mjs +750 -0
- package/dist/fortnox/api.mjs.map +1 -0
- package/dist/fortnox/state.d.mts +533 -0
- package/dist/fortnox/state.d.mts.map +1 -0
- package/dist/fortnox/state.mjs +612 -0
- package/dist/fortnox/state.mjs.map +1 -0
- package/dist/fortnox.d.mts +128 -0
- package/dist/fortnox.d.mts.map +1 -0
- package/dist/fortnox.mjs +403 -0
- package/dist/fortnox.mjs.map +1 -0
- package/dist/gateway-evaluate-recordings.d.mts +12 -0
- package/dist/gateway-evaluate-recordings.d.mts.map +1 -0
- package/dist/gateway-evaluate-recordings.mjs +128 -0
- package/dist/gateway-evaluate-recordings.mjs.map +1 -0
- package/dist/gateway.d.mts +272 -0
- package/dist/gateway.d.mts.map +1 -0
- package/dist/gateway.mjs +400 -0
- package/dist/gateway.mjs.map +1 -0
- package/dist/github/api.d.mts +63 -0
- package/dist/github/api.d.mts.map +1 -0
- package/dist/github/api.mjs +527 -0
- package/dist/github/api.mjs.map +1 -0
- package/dist/github/state.d.mts +272 -0
- package/dist/github/state.d.mts.map +1 -0
- package/dist/github/state.mjs +303 -0
- package/dist/github/state.mjs.map +1 -0
- package/dist/github.d.mts +77 -0
- package/dist/github.d.mts.map +1 -0
- package/dist/github.mjs +180 -0
- package/dist/github.mjs.map +1 -0
- package/dist/google/calendar.d.mts +10 -0
- package/dist/google/calendar.d.mts.map +1 -0
- package/dist/google/calendar.mjs +252 -0
- package/dist/google/calendar.mjs.map +1 -0
- package/dist/google/drive.d.mts +8 -0
- package/dist/google/drive.d.mts.map +1 -0
- package/dist/google/drive.mjs +252 -0
- package/dist/google/drive.mjs.map +1 -0
- package/dist/google/gmail.d.mts +10 -0
- package/dist/google/gmail.d.mts.map +1 -0
- package/dist/google/gmail.mjs +591 -0
- package/dist/google/gmail.mjs.map +1 -0
- package/dist/google/shared.d.mts +107 -0
- package/dist/google/shared.d.mts.map +1 -0
- package/dist/google/shared.mjs +146 -0
- package/dist/google/shared.mjs.map +1 -0
- package/dist/google/state.d.mts +385 -0
- package/dist/google/state.d.mts.map +1 -0
- package/dist/google/state.mjs +496 -0
- package/dist/google/state.mjs.map +1 -0
- package/dist/google.d.mts +75 -0
- package/dist/google.d.mts.map +1 -0
- package/dist/google.mjs +210 -0
- package/dist/google.mjs.map +1 -0
- package/dist/linkedin-search/api.d.mts +45 -0
- package/dist/linkedin-search/api.d.mts.map +1 -0
- package/dist/linkedin-search/api.mjs +181 -0
- package/dist/linkedin-search/api.mjs.map +1 -0
- package/dist/linkedin-search/state.d.mts +123 -0
- package/dist/linkedin-search/state.d.mts.map +1 -0
- package/dist/linkedin-search/state.mjs +261 -0
- package/dist/linkedin-search/state.mjs.map +1 -0
- package/dist/linkedin-search.d.mts +69 -0
- package/dist/linkedin-search.d.mts.map +1 -0
- package/dist/linkedin-search.mjs +158 -0
- package/dist/linkedin-search.mjs.map +1 -0
- package/dist/mcp/api.d.mts +44 -0
- package/dist/mcp/api.d.mts.map +1 -0
- package/dist/mcp/api.mjs +557 -0
- package/dist/mcp/api.mjs.map +1 -0
- package/dist/mcp/recordings.d.mts +40 -0
- package/dist/mcp/recordings.d.mts.map +1 -0
- package/dist/mcp/recordings.mjs +2390 -0
- package/dist/mcp/recordings.mjs.map +1 -0
- package/dist/mcp/state.d.mts +71 -0
- package/dist/mcp/state.d.mts.map +1 -0
- package/dist/mcp/state.mjs +83 -0
- package/dist/mcp/state.mjs.map +1 -0
- package/dist/mcp.d.mts +84 -0
- package/dist/mcp.d.mts.map +1 -0
- package/dist/mcp.mjs +241 -0
- package/dist/mcp.mjs.map +1 -0
- package/dist/messages.d.mts +183 -0
- package/dist/messages.d.mts.map +1 -0
- package/dist/messages.mjs +532 -0
- package/dist/messages.mjs.map +1 -0
- package/dist/microsoft/api.d.mts +47 -0
- package/dist/microsoft/api.d.mts.map +1 -0
- package/dist/microsoft/api.mjs +178 -0
- package/dist/microsoft/api.mjs.map +1 -0
- package/dist/microsoft/calendar.d.mts +30 -0
- package/dist/microsoft/calendar.d.mts.map +1 -0
- package/dist/microsoft/calendar.mjs +271 -0
- package/dist/microsoft/calendar.mjs.map +1 -0
- package/dist/microsoft/drive.d.mts +36 -0
- package/dist/microsoft/drive.d.mts.map +1 -0
- package/dist/microsoft/drive.mjs +298 -0
- package/dist/microsoft/drive.mjs.map +1 -0
- package/dist/microsoft/graph.d.mts +198 -0
- package/dist/microsoft/graph.d.mts.map +1 -0
- package/dist/microsoft/graph.mjs +264 -0
- package/dist/microsoft/graph.mjs.map +1 -0
- package/dist/microsoft/mail.d.mts +48 -0
- package/dist/microsoft/mail.d.mts.map +1 -0
- package/dist/microsoft/mail.mjs +488 -0
- package/dist/microsoft/mail.mjs.map +1 -0
- package/dist/microsoft/state.d.mts +480 -0
- package/dist/microsoft/state.d.mts.map +1 -0
- package/dist/microsoft/state.mjs +625 -0
- package/dist/microsoft/state.mjs.map +1 -0
- package/dist/microsoft.d.mts +168 -0
- package/dist/microsoft.d.mts.map +1 -0
- package/dist/microsoft.mjs +483 -0
- package/dist/microsoft.mjs.map +1 -0
- package/dist/node.d.mts +47 -0
- package/dist/node.d.mts.map +1 -0
- package/dist/node.mjs +178 -0
- package/dist/node.mjs.map +1 -0
- package/dist/notion/api.d.mts +51 -0
- package/dist/notion/api.d.mts.map +1 -0
- package/dist/notion/api.mjs +507 -0
- package/dist/notion/api.mjs.map +1 -0
- package/dist/notion/state.d.mts +329 -0
- package/dist/notion/state.d.mts.map +1 -0
- package/dist/notion/state.mjs +432 -0
- package/dist/notion/state.mjs.map +1 -0
- package/dist/notion.d.mts +72 -0
- package/dist/notion.d.mts.map +1 -0
- package/dist/notion.mjs +127 -0
- package/dist/notion.mjs.map +1 -0
- package/dist/openai.d.mts +165 -0
- package/dist/openai.d.mts.map +1 -0
- package/dist/openai.mjs +141 -0
- package/dist/openai.mjs.map +1 -0
- package/dist/opencode-recordings.d.mts +7 -0
- package/dist/opencode-recordings.d.mts.map +1 -0
- package/dist/opencode-recordings.mjs +222 -0
- package/dist/opencode-recordings.mjs.map +1 -0
- package/dist/opencode.d.mts +70 -0
- package/dist/opencode.d.mts.map +1 -0
- package/dist/opencode.mjs +217 -0
- package/dist/opencode.mjs.map +1 -0
- package/dist/r2-fixtures.d.mts +36 -0
- package/dist/r2-fixtures.d.mts.map +1 -0
- package/dist/r2-fixtures.mjs +245 -0
- package/dist/r2-fixtures.mjs.map +1 -0
- package/dist/r2-guard.d.mts +43 -0
- package/dist/r2-guard.d.mts.map +1 -0
- package/dist/r2-guard.mjs +215 -0
- package/dist/r2-guard.mjs.map +1 -0
- package/dist/r2.d.mts +166 -0
- package/dist/r2.d.mts.map +1 -0
- package/dist/r2.mjs +517 -0
- package/dist/r2.mjs.map +1 -0
- package/dist/responses.d.mts +231 -0
- package/dist/responses.d.mts.map +1 -0
- package/dist/responses.mjs +556 -0
- package/dist/responses.mjs.map +1 -0
- package/dist/route-evidence.d.mts +45 -0
- package/dist/route-evidence.d.mts.map +1 -0
- package/dist/route-evidence.mjs +56 -0
- package/dist/route-evidence.mjs.map +1 -0
- package/dist/router.d.mts +93 -0
- package/dist/router.d.mts.map +1 -0
- package/dist/router.mjs +259 -0
- package/dist/router.mjs.map +1 -0
- package/dist/stateful-core.d.mts +17 -0
- package/dist/stateful-core.d.mts.map +1 -0
- package/dist/stateful-core.mjs +41 -0
- package/dist/stateful-core.mjs.map +1 -0
- package/dist/stateful-emulator.d.mts +565 -0
- package/dist/stateful-emulator.d.mts.map +1 -0
- package/dist/stateful-emulator.mjs +1228 -0
- package/dist/stateful-emulator.mjs.map +1 -0
- package/dist/stateful-secrets.d.mts +84 -0
- package/dist/stateful-secrets.d.mts.map +1 -0
- package/dist/stateful-secrets.mjs +216 -0
- package/dist/stateful-secrets.mjs.map +1 -0
- package/dist/subscription-usage-recordings.d.mts +9 -0
- package/dist/subscription-usage-recordings.d.mts.map +1 -0
- package/dist/subscription-usage-recordings.mjs +53 -0
- package/dist/subscription-usage-recordings.mjs.map +1 -0
- package/dist/subscription-usage.d.mts +53 -0
- package/dist/subscription-usage.d.mts.map +1 -0
- package/dist/subscription-usage.mjs +21 -0
- package/dist/subscription-usage.mjs.map +1 -0
- package/dist/telegram/api.d.mts +34 -0
- package/dist/telegram/api.d.mts.map +1 -0
- package/dist/telegram/api.mjs +257 -0
- package/dist/telegram/api.mjs.map +1 -0
- package/dist/telegram/state.d.mts +128 -0
- package/dist/telegram/state.d.mts.map +1 -0
- package/dist/telegram/state.mjs +154 -0
- package/dist/telegram/state.mjs.map +1 -0
- package/dist/telegram.d.mts +62 -0
- package/dist/telegram.d.mts.map +1 -0
- package/dist/telegram.mjs +127 -0
- package/dist/telegram.mjs.map +1 -0
- package/dist/todoist/api.d.mts +55 -0
- package/dist/todoist/api.d.mts.map +1 -0
- package/dist/todoist/api.mjs +415 -0
- package/dist/todoist/api.mjs.map +1 -0
- package/dist/todoist/state.d.mts +275 -0
- package/dist/todoist/state.d.mts.map +1 -0
- package/dist/todoist/state.mjs +345 -0
- package/dist/todoist/state.mjs.map +1 -0
- package/dist/todoist.d.mts +68 -0
- package/dist/todoist.d.mts.map +1 -0
- package/dist/todoist.mjs +181 -0
- package/dist/todoist.mjs.map +1 -0
- package/dist/xai.d.mts +267 -0
- package/dist/xai.d.mts.map +1 -0
- package/dist/xai.mjs +252 -0
- package/dist/xai.mjs.map +1 -0
- package/package.json +157 -0
- package/src/anthropic.ts +289 -0
- package/src/chat-completions.ts +924 -0
- package/src/codex.ts +296 -0
- package/src/dropbox/api.ts +1084 -0
- package/src/dropbox/state.ts +364 -0
- package/src/dropbox.ts +203 -0
- package/src/email-fixtures.ts +1297 -0
- package/src/email.ts +1108 -0
- package/src/emulator-compose.ts +147 -0
- package/src/emulator-http.ts +184 -0
- package/src/emulator-kernel.ts +844 -0
- package/src/fixture-route.ts +561 -0
- package/src/fortnox/api.ts +1352 -0
- package/src/fortnox/state.ts +798 -0
- package/src/fortnox.ts +801 -0
- package/src/gateway-evaluate-recordings.ts +153 -0
- package/src/gateway.ts +530 -0
- package/src/github/api.ts +986 -0
- package/src/github/state.ts +439 -0
- package/src/github.ts +271 -0
- package/src/google/calendar.ts +486 -0
- package/src/google/drive.ts +471 -0
- package/src/google/gmail.ts +1011 -0
- package/src/google/shared.ts +321 -0
- package/src/google/state.ts +686 -0
- package/src/google.ts +298 -0
- package/src/linkedin-search/api.ts +363 -0
- package/src/linkedin-search/state.ts +373 -0
- package/src/linkedin-search.ts +241 -0
- package/src/mcp/api.ts +995 -0
- package/src/mcp/recordings.ts +2665 -0
- package/src/mcp/state.ts +125 -0
- package/src/mcp.ts +319 -0
- package/src/messages.ts +844 -0
- package/src/microsoft/api.ts +426 -0
- package/src/microsoft/calendar.ts +491 -0
- package/src/microsoft/drive.ts +541 -0
- package/src/microsoft/graph.ts +550 -0
- package/src/microsoft/mail.ts +833 -0
- package/src/microsoft/state.ts +822 -0
- package/src/microsoft.ts +982 -0
- package/src/node.ts +293 -0
- package/src/notion/api.ts +976 -0
- package/src/notion/state.ts +512 -0
- package/src/notion.ts +210 -0
- package/src/openai.ts +198 -0
- package/src/opencode-recordings.ts +257 -0
- package/src/opencode.ts +271 -0
- package/src/r2-fixtures.ts +295 -0
- package/src/r2-guard.ts +323 -0
- package/src/r2.ts +890 -0
- package/src/responses.ts +901 -0
- package/src/route-evidence.ts +90 -0
- package/src/router.ts +490 -0
- package/src/stateful-core.ts +70 -0
- package/src/stateful-emulator.ts +2571 -0
- package/src/stateful-secrets.ts +299 -0
- package/src/subscription-usage-recordings.ts +77 -0
- package/src/subscription-usage.ts +68 -0
- package/src/telegram/api.ts +437 -0
- package/src/telegram/state.ts +229 -0
- package/src/telegram.ts +227 -0
- package/src/todoist/api.ts +736 -0
- package/src/todoist/state.ts +456 -0
- package/src/todoist.ts +316 -0
- package/src/xai.ts +341 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.mjs","names":[],"sources":["../../src/fortnox/state.ts"],"sourcesContent":["/**\n * Fortnox emulator state: the typed entities, the seed input, the default seed, and the\n * account-variance profiles (internal; re-exported by `src/fortnox.ts`).\n *\n * Entity shapes and the default entities follow the synthetic Fortnox conformance fixtures, copied\n * as data (the same customer and document numbers as `fortnoxConformanceFixtureSeeds`), never\n * imported from SDK code. Where no fixture records a value, the default is synthesized and says\n * so below: customer 1002, most invoice rows, and the company information have no fixture.\n *\n * @experimental\n */\nimport { Result, Struct } from 'effect'\nimport * as Schema from 'effect/Schema'\n\nconst DiscountType = Schema.Literals(['PERCENT', 'AMOUNT'])\n\nconst rowFields = {\n RowId: Schema.Int,\n AccountNumber: Schema.NullOr(Schema.Int),\n ArticleNumber: Schema.String,\n ContributionPercent: Schema.String,\n ContributionValue: Schema.String,\n CostCenter: Schema.String,\n DeliveredQuantity: Schema.String,\n Description: Schema.String,\n Discount: Schema.Finite,\n DiscountType,\n HouseWork: Schema.Boolean,\n Price: Schema.Finite,\n PriceExcludingVAT: Schema.Finite,\n Project: Schema.String,\n Total: Schema.Finite,\n TotalExcludingVAT: Schema.Finite,\n Unit: Schema.String,\n VAT: Schema.Finite,\n VATCode: Schema.String\n}\n\n/** A stored invoice row, in the single-invoice wire shape. Totals are derived. */\nexport const FortnoxEmulatorInvoiceRow = Schema.Struct(rowFields)\n\nexport type FortnoxEmulatorInvoiceRow = typeof FortnoxEmulatorInvoiceRow.Type\n\nconst EmailInformation = Schema.Struct({\n EmailAddressFrom: Schema.String,\n EmailAddressTo: Schema.String,\n EmailAddressCC: Schema.String,\n EmailAddressBCC: Schema.String,\n EmailSubject: Schema.String,\n EmailBody: Schema.String\n})\n\nconst invoiceFields = {\n DocumentNumber: Schema.String,\n CustomerNumber: Schema.String,\n CustomerName: Schema.String,\n Balance: Schema.Finite,\n Booked: Schema.Boolean,\n Cancelled: Schema.Boolean,\n Comments: Schema.String,\n CostCenter: Schema.String,\n Credit: Schema.String,\n Currency: Schema.String,\n CurrencyRate: Schema.Finite,\n CurrencyUnit: Schema.Finite,\n DueDate: Schema.NullOr(Schema.String),\n EmailInformation,\n ExternalInvoiceReference1: Schema.String,\n ExternalInvoiceReference2: Schema.String,\n FinalPayDate: Schema.NullOr(Schema.String),\n Gross: Schema.Finite,\n InvoiceDate: Schema.String,\n InvoiceRows: Schema.Array(FortnoxEmulatorInvoiceRow),\n InvoiceType: Schema.String,\n Net: Schema.Finite,\n NotCompleted: Schema.Boolean,\n NoxFinans: Schema.Boolean,\n OCR: Schema.String,\n OurReference: Schema.String,\n Project: Schema.String,\n Sent: Schema.Boolean,\n TermsOfPayment: Schema.String,\n Total: Schema.Finite,\n TotalToPay: Schema.Finite,\n TotalVAT: Schema.Finite,\n VoucherNumber: Schema.NullOr(Schema.Int),\n VoucherSeries: Schema.NullOr(Schema.String),\n VoucherYear: Schema.NullOr(Schema.Int),\n WayOfDelivery: Schema.String,\n YourReference: Schema.String\n}\n\n/** A stored customer invoice with its rows. */\nexport const FortnoxEmulatorInvoice = Schema.Struct(invoiceFields)\n\nexport type FortnoxEmulatorInvoice = typeof FortnoxEmulatorInvoice.Type\n\nconst customerFields = {\n CustomerNumber: Schema.String,\n Name: Schema.String,\n Active: Schema.Boolean,\n Address1: Schema.String,\n Address2: Schema.String,\n City: Schema.String,\n Comments: Schema.String,\n CostCenter: Schema.String,\n Country: Schema.String,\n CountryCode: Schema.String,\n Currency: Schema.String,\n Email: Schema.String,\n EmailInvoice: Schema.String,\n OrganisationNumber: Schema.String,\n OurReference: Schema.String,\n Phone1: Schema.String,\n Phone2: Schema.String,\n TermsOfPayment: Schema.String,\n Type: Schema.String,\n VATNumber: Schema.String,\n VATType: Schema.String,\n YourReference: Schema.String,\n ZipCode: Schema.String\n}\n\n/** A stored customer. `Country` is read-only (derived from `CountryCode`). */\nexport const FortnoxEmulatorCustomer = Schema.Struct(customerFields)\n\nexport type FortnoxEmulatorCustomer = typeof FortnoxEmulatorCustomer.Type\n\nconst companyFields = {\n CompanyName: Schema.String,\n OrganizationNumber: Schema.String,\n DatabaseNumber: Schema.Int,\n Address: Schema.String,\n City: Schema.String,\n ZipCode: Schema.String,\n CountryCode: Schema.String,\n VisitAddress: Schema.String,\n VisitCity: Schema.String,\n VisitZipCode: Schema.String,\n VisitCountryCode: Schema.String\n}\n\n/** The company the credential belongs to (`GET /3/companyinformation`). */\nexport const FortnoxEmulatorCompany = Schema.Struct(companyFields)\n\nexport type FortnoxEmulatorCompany = typeof FortnoxEmulatorCompany.Type\n\n/** A recorded (never delivered) invoice email. */\nexport const FortnoxEmulatorOutboxEntry = Schema.Struct({\n DocumentNumber: Schema.String,\n EmailAddressTo: Schema.String,\n EmailAddressCC: Schema.String,\n EmailAddressBCC: Schema.String,\n EmailSubject: Schema.String,\n /** ISO timestamp from the emulator clock. */\n sentAt: Schema.String\n})\n\nexport type FortnoxEmulatorOutboxEntry = typeof FortnoxEmulatorOutboxEntry.Type\n\nconst Counter = Schema.Int.check(Schema.isGreaterThanOrEqualTo(1))\n\nconst Counters = Schema.Struct({\n /** Next RowId handed out; RowIds are regenerated on every row update. */\n nextRowId: Counter,\n /** Next DocumentNumber handed out by `POST /3/invoices`. */\n nextDocumentNumber: Counter\n})\n\n/** The whole emulator state (JSON-compatible; what `snapshot()` returns). */\nexport const FortnoxEmulatorStateSchema = Schema.Struct({\n company: FortnoxEmulatorCompany,\n customers: Schema.Array(FortnoxEmulatorCustomer),\n invoices: Schema.Array(FortnoxEmulatorInvoice),\n outbox: Schema.Array(FortnoxEmulatorOutboxEntry),\n counters: Counters\n})\n\n/**\n * The emulator state. The container is mutable (route handlers replace whole entity lists);\n * every entity is replaced, never edited in place.\n */\nexport type FortnoxEmulatorState = {\n company: FortnoxEmulatorCompany\n customers: ReadonlyArray<FortnoxEmulatorCustomer>\n invoices: ReadonlyArray<FortnoxEmulatorInvoice>\n outbox: ReadonlyArray<FortnoxEmulatorOutboxEntry>\n counters: typeof Counters.Type\n}\n\n/** A seeded invoice row: the writable row fields; totals and `VATCode` are derived. */\nexport const FortnoxEmulatorInvoiceRowSeed = Schema.Struct({\n RowId: Schema.optionalKey(Schema.Int),\n AccountNumber: Schema.optionalKey(Schema.NullOr(Schema.Int)),\n ArticleNumber: Schema.optionalKey(Schema.String),\n CostCenter: Schema.optionalKey(Schema.String),\n DeliveredQuantity: Schema.optionalKey(Schema.String),\n Description: Schema.optionalKey(Schema.String),\n Discount: Schema.optionalKey(Schema.Finite),\n DiscountType: Schema.optionalKey(DiscountType),\n Price: Schema.optionalKey(Schema.Finite),\n Project: Schema.optionalKey(Schema.String),\n Unit: Schema.optionalKey(Schema.String),\n VAT: Schema.optionalKey(Schema.Finite)\n})\n\nexport type FortnoxEmulatorInvoiceRowSeed = typeof FortnoxEmulatorInvoiceRowSeed.Type\n\nconst { InvoiceRows: _invoiceRows, ...invoiceScalarFields } = invoiceFields\n\n/**\n * A seeded invoice: `DocumentNumber` and `CustomerNumber` are required; everything else has a\n * synthetic default. Totals (`Net`, `Gross`, `TotalVAT`, `Total`, `TotalToPay`) are always\n * computed from the rows; `Balance` defaults to `Total`.\n */\nexport const FortnoxEmulatorInvoiceSeed = Schema.Struct({\n ...Struct.mapOmit(invoiceScalarFields, ['DocumentNumber', 'CustomerNumber'], Schema.optionalKey),\n InvoiceRows: Schema.optionalKey(Schema.Array(FortnoxEmulatorInvoiceRowSeed))\n})\n\nexport type FortnoxEmulatorInvoiceSeed = typeof FortnoxEmulatorInvoiceSeed.Type\n\n/** A seeded customer: `CustomerNumber` and `Name` are required; `Country` follows `CountryCode`. */\nexport const FortnoxEmulatorCustomerSeed = Schema.Struct(\n Struct.mapOmit(customerFields, ['CustomerNumber', 'Name'], Schema.optionalKey)\n)\n\nexport type FortnoxEmulatorCustomerSeed = typeof FortnoxEmulatorCustomerSeed.Type\n\n/** Account-variance profiles for the default seed. */\nexport const FortnoxEmulatorProfile = Schema.Literals([\n 'default',\n 'empty-company',\n 'no-booked-invoices'\n])\n\nexport type FortnoxEmulatorProfile = typeof FortnoxEmulatorProfile.Type\n\n/**\n * A typed seed. Start from `profile` (default `'default'`, the fixture entities); `company`,\n * `customers`, and `invoices`, when given, replace that part of the profile.\n */\nexport const FortnoxEmulatorSeed = Schema.Struct({\n profile: Schema.optionalKey(FortnoxEmulatorProfile),\n company: Schema.optionalKey(FortnoxEmulatorCompany),\n customers: Schema.optionalKey(Schema.Array(FortnoxEmulatorCustomerSeed)),\n invoices: Schema.optionalKey(Schema.Array(FortnoxEmulatorInvoiceSeed))\n})\n\nexport type FortnoxEmulatorSeed = typeof FortnoxEmulatorSeed.Type\n\nconst strict = { onExcessProperty: 'error' } as const\n\nconst decodeSeedInput = Schema.decodeUnknownResult(FortnoxEmulatorSeed, strict)\n\nconst decodeStateInput = Schema.decodeUnknownResult(FortnoxEmulatorStateSchema, strict)\n\nconst issueMessage = (issue: Schema.SchemaError['issue']): string =>\n new Schema.SchemaError(issue).message\n\n/** `CountryCode` to the Swedish country name Fortnox derives `Country` from (a small table). */\nexport const fortnoxEmulatorCountries = new Map([\n ['SE', 'Sverige'],\n ['NO', 'Norge'],\n ['DK', 'Danmark'],\n ['FI', 'Finland'],\n ['DE', 'Tyskland'],\n ['GB', 'Storbritannien'],\n ['US', 'USA']\n])\n\n/** VAT percent to Fortnox sales VAT code. */\nexport const fortnoxEmulatorVatCodes = new Map([\n [25, 'MP1'],\n [12, 'MP2'],\n [6, 'MP3'],\n [0, 'MF']\n])\n\nexport const round2 = (value: number): number => Math.round(value * 100) / 100\n\n/** Row total: `Price × DeliveredQuantity`, less a percent (or amount) discount. */\nexport const rowTotal = (\n row: Pick<FortnoxEmulatorInvoiceRow, 'Price' | 'DeliveredQuantity' | 'Discount' | 'DiscountType'>\n): number => {\n const gross = row.Price * Number(row.DeliveredQuantity)\n\n return round2(\n row.DiscountType === 'AMOUNT' ? gross - row.Discount : (gross * (100 - row.Discount)) / 100\n )\n}\n\ntype RowInput = Pick<\n FortnoxEmulatorInvoiceRow,\n | 'RowId'\n | 'AccountNumber'\n | 'ArticleNumber'\n | 'CostCenter'\n | 'DeliveredQuantity'\n | 'Description'\n | 'Discount'\n | 'DiscountType'\n | 'Price'\n | 'Project'\n | 'Unit'\n | 'VAT'\n>\n\n/** A full row (fixture key order) with derived totals and VAT code. */\nexport const buildRow = (input: RowInput): FortnoxEmulatorInvoiceRow => {\n const total = rowTotal(input)\n\n return {\n AccountNumber: input.AccountNumber,\n ArticleNumber: input.ArticleNumber,\n ContributionPercent: '0',\n ContributionValue: '0',\n CostCenter: input.CostCenter,\n DeliveredQuantity: input.DeliveredQuantity,\n Description: input.Description,\n Discount: input.Discount,\n DiscountType: input.DiscountType,\n HouseWork: false,\n Price: input.Price,\n PriceExcludingVAT: input.Price,\n Project: input.Project,\n RowId: input.RowId,\n Total: total,\n TotalExcludingVAT: total,\n Unit: input.Unit,\n VAT: input.VAT,\n VATCode: fortnoxEmulatorVatCodes.get(input.VAT) ?? ''\n }\n}\n\n/** Defaults for a new row (no positional match). */\nexport const newRowDefaults: Omit<RowInput, 'RowId'> = {\n AccountNumber: 3001,\n ArticleNumber: '',\n CostCenter: '',\n DeliveredQuantity: '1.00',\n Description: '',\n Discount: 0,\n DiscountType: 'PERCENT',\n Price: 0,\n Project: '',\n Unit: '',\n VAT: 25\n}\n\n/** Fortnox-style quantity: two decimals (`\"2.00\"`). */\nexport const formatQuantity = (value: number): string => value.toFixed(2)\n\nexport type InvoiceTotals = Pick<\n FortnoxEmulatorInvoice,\n 'Net' | 'Gross' | 'TotalVAT' | 'Total' | 'TotalToPay'\n>\n\n/**\n * Invoice totals from the rows: `Net = Gross = Σ row totals`, `TotalVAT = Σ row total × VAT%`,\n * and `Total = TotalToPay = Net + TotalVAT` rounded to whole kronor (half up), matching the\n * synthetic row-discount fixture (1150 + 287.5 → 1438).\n */\nexport const invoiceTotals = (rows: ReadonlyArray<FortnoxEmulatorInvoiceRow>): InvoiceTotals => {\n const net = round2(rows.reduce((sum, row) => sum + row.Total, 0))\n const vat = round2(rows.reduce((sum, row) => sum + (row.Total * row.VAT) / 100, 0))\n const total = Math.round(net + vat)\n\n return { Net: net, Gross: net, TotalVAT: vat, Total: total, TotalToPay: total }\n}\n\n// Default entities. Fixture-derived: customer 1001 (empty-string fixture) and the invoice headers\n// of 101-105 (list, payment-filter, row-discount, and email fixtures). Where two fixtures disagree\n// the more specific one wins: invoice 103 (and its two rows) follows the row-discount fixture, not\n// the list fixture (its Balance, DueDate, InvoiceDate, and OCR differ there). Synthesized: customer\n// 1002, the rows of invoices 101, 102, 104, and 105 (chosen so their computed totals equal the\n// fixture totals), the company information (no fixture), and every default `customerFromSeed` and\n// `invoiceFromSeed` fill in for a field the seed omits.\n\nconst exampleCustomer: FortnoxEmulatorCustomerSeed = {\n CustomerNumber: '1001',\n Name: 'Example Customer AB',\n Active: true,\n Address1: 'Exempelgatan 1',\n Address2: '',\n City: 'Exempelstad',\n Comments: 'Synthetic note: invoice monthly by email.',\n CostCenter: '',\n CountryCode: 'SE',\n Currency: 'SEK',\n Email: 'billing@example.test',\n EmailInvoice: 'billing@example.test',\n OrganisationNumber: '000000-0000',\n OurReference: '',\n Phone1: '',\n Phone2: '',\n TermsOfPayment: '30',\n Type: 'COMPANY',\n VATNumber: '',\n VATType: 'SEVAT',\n YourReference: '',\n ZipCode: '000 00'\n}\n\nconst exampleTrading: FortnoxEmulatorCustomerSeed = {\n CustomerNumber: '1002',\n Name: 'Example Trading AB',\n Active: true,\n Address1: 'Exempelvägen 2',\n City: 'Exempelstad',\n Comments: '',\n CountryCode: 'SE',\n Currency: 'SEK',\n Email: 'accounts@example.test',\n EmailInvoice: 'accounts@example.test',\n OrganisationNumber: '000000-0001',\n TermsOfPayment: '30',\n Type: 'COMPANY',\n VATType: 'SEVAT',\n ZipCode: '000 01'\n}\n\nconst singleRow = (description: string, price: number): FortnoxEmulatorInvoiceRowSeed => ({\n AccountNumber: 3001,\n Description: description,\n DeliveredQuantity: '1.00',\n Price: price,\n VAT: 25,\n Discount: 0,\n DiscountType: 'PERCENT'\n})\n\nconst defaultInvoices: ReadonlyArray<FortnoxEmulatorInvoiceSeed> = [\n {\n DocumentNumber: '101',\n CustomerNumber: '1001',\n Balance: 0,\n Booked: true,\n DueDate: '2026-09-30',\n FinalPayDate: '2026-09-20',\n InvoiceDate: '2026-08-31',\n OCR: '10151',\n Sent: true,\n VoucherNumber: 12,\n VoucherSeries: 'B',\n VoucherYear: 3,\n InvoiceRows: [singleRow('Monthly service (synthetic)', 1000)]\n },\n {\n DocumentNumber: '102',\n CustomerNumber: '1002',\n Balance: 3750,\n Booked: true,\n DueDate: '2026-09-30',\n InvoiceDate: '2026-08-31',\n OCR: '10250',\n Sent: true,\n VoucherNumber: 13,\n VoucherSeries: 'B',\n VoucherYear: 3,\n InvoiceRows: [singleRow('Project delivery (synthetic)', 3000)]\n },\n {\n DocumentNumber: '103',\n CustomerNumber: '1001',\n Booked: false,\n DueDate: '2026-10-31',\n InvoiceDate: '2026-09-30',\n OCR: '10350',\n InvoiceRows: [\n {\n RowId: 1,\n AccountNumber: 3001,\n Description: 'Consulting hours (synthetic)',\n DeliveredQuantity: '2.00',\n Unit: 'h',\n Price: 500,\n VAT: 25,\n Discount: 5,\n DiscountType: 'PERCENT'\n },\n {\n RowId: 2,\n AccountNumber: 3001,\n Description: 'Travel (synthetic)',\n DeliveredQuantity: '1.00',\n Price: 250,\n VAT: 25,\n Discount: 0,\n DiscountType: 'PERCENT'\n }\n ]\n },\n {\n DocumentNumber: '104',\n CustomerNumber: '1001',\n Balance: 625,\n Booked: true,\n DueDate: '2026-10-31',\n InvoiceDate: '2026-09-30',\n VoucherNumber: 14,\n VoucherSeries: 'B',\n VoucherYear: 3,\n EmailInformation: {\n EmailAddressFrom: 'invoices@example.test',\n EmailAddressTo: 'billing@example.test',\n EmailAddressCC: '',\n EmailAddressBCC: '',\n EmailSubject: 'Invoice {no} from Example AB',\n EmailBody: 'Synthetic invoice email body.'\n },\n InvoiceRows: [singleRow('Support hours (synthetic)', 500)]\n },\n {\n DocumentNumber: '105',\n CustomerNumber: '1001',\n Booked: false,\n DueDate: '2026-09-15',\n InvoiceDate: '2026-08-16',\n InvoiceRows: [singleRow('Workshop (synthetic)', 1000)]\n }\n]\n\nconst defaultCompany: FortnoxEmulatorCompany = {\n CompanyName: 'Example AB',\n OrganizationNumber: '000000-0002',\n DatabaseNumber: 100001,\n Address: 'Exempelgatan 10',\n City: 'Exempelstad',\n ZipCode: '000 02',\n CountryCode: 'SE',\n VisitAddress: 'Exempelgatan 10',\n VisitCity: 'Exempelstad',\n VisitZipCode: '000 02',\n VisitCountryCode: 'SE'\n}\n\ntype ProfileEntities = {\n readonly company: FortnoxEmulatorCompany\n readonly customers: ReadonlyArray<FortnoxEmulatorCustomerSeed>\n readonly invoices: ReadonlyArray<FortnoxEmulatorInvoiceSeed>\n}\n\nconst unbooked = (invoice: FortnoxEmulatorInvoiceSeed): FortnoxEmulatorInvoiceSeed => {\n const { Balance: _balance, FinalPayDate: _finalPayDate, ...rest } = invoice\n\n return { ...rest, Booked: false, VoucherNumber: null, VoucherSeries: null, VoucherYear: null }\n}\n\nconst profileEntities = (profile: FortnoxEmulatorProfile): ProfileEntities => {\n switch (profile) {\n case 'default':\n return {\n company: defaultCompany,\n customers: [exampleCustomer, exampleTrading],\n invoices: defaultInvoices\n }\n case 'empty-company':\n return { company: defaultCompany, customers: [], invoices: [] }\n case 'no-booked-invoices':\n return {\n company: defaultCompany,\n customers: [exampleCustomer, exampleTrading],\n invoices: defaultInvoices.map(unbooked)\n }\n }\n}\n\n/** Numeric-aware order for Fortnox numbers (`\"999\" < \"1001\"`). */\nexport const compareNumbers = (left: string, right: string): number =>\n left.localeCompare(right, 'en', { numeric: true })\n\nconst customerFromSeed = (seed: FortnoxEmulatorCustomerSeed): FortnoxEmulatorCustomer => {\n const countryCode = seed.CountryCode ?? 'SE'\n\n return {\n CustomerNumber: seed.CustomerNumber,\n Name: seed.Name,\n Active: seed.Active ?? true,\n Address1: seed.Address1 ?? '',\n Address2: seed.Address2 ?? '',\n City: seed.City ?? '',\n Comments: seed.Comments ?? '',\n CostCenter: seed.CostCenter ?? '',\n Country: seed.Country ?? fortnoxEmulatorCountries.get(countryCode) ?? '',\n CountryCode: countryCode,\n Currency: seed.Currency ?? 'SEK',\n Email: seed.Email ?? '',\n EmailInvoice: seed.EmailInvoice ?? '',\n OrganisationNumber: seed.OrganisationNumber ?? '',\n OurReference: seed.OurReference ?? '',\n Phone1: seed.Phone1 ?? '',\n Phone2: seed.Phone2 ?? '',\n TermsOfPayment: seed.TermsOfPayment ?? '30',\n Type: seed.Type ?? 'COMPANY',\n VATNumber: seed.VATNumber ?? '',\n VATType: seed.VATType ?? 'SEVAT',\n YourReference: seed.YourReference ?? '',\n ZipCode: seed.ZipCode ?? ''\n }\n}\n\n/** Default invoice email for a customer (the email fixture's sender, subject, and body). */\nexport const defaultEmailInformation = (\n customer: FortnoxEmulatorCustomer | undefined,\n company: FortnoxEmulatorCompany\n): FortnoxEmulatorInvoice['EmailInformation'] => ({\n EmailAddressFrom: 'invoices@example.test',\n EmailAddressTo: customer?.EmailInvoice ?? '',\n EmailAddressCC: '',\n EmailAddressBCC: '',\n EmailSubject: `Invoice {no} from ${company.CompanyName}`,\n EmailBody: 'Synthetic invoice email body.'\n})\n\nexport type RowIdSource = { next: number }\n\nconst rowFromSeed = (\n seed: FortnoxEmulatorInvoiceRowSeed,\n ids: RowIdSource\n): FortnoxEmulatorInvoiceRow =>\n buildRow({\n ...newRowDefaults,\n ...seed,\n DeliveredQuantity: formatQuantity(Number(seed.DeliveredQuantity ?? '1')),\n RowId: seed.RowId ?? ids.next++\n })\n\n/** Build a stored invoice from a seed; row ids come from `ids` unless the seed names them. */\nexport const invoiceFromSeed = (\n seed: FortnoxEmulatorInvoiceSeed,\n customer: FortnoxEmulatorCustomer | undefined,\n company: FortnoxEmulatorCompany,\n ids: RowIdSource\n): FortnoxEmulatorInvoice => {\n const rows = (seed.InvoiceRows ?? []).map(row => rowFromSeed(row, ids))\n const totals = invoiceTotals(rows)\n\n return {\n DocumentNumber: seed.DocumentNumber,\n CustomerNumber: seed.CustomerNumber,\n CustomerName: seed.CustomerName ?? customer?.Name ?? '',\n Balance: seed.Balance ?? totals.Total,\n Booked: seed.Booked ?? false,\n Cancelled: seed.Cancelled ?? false,\n Comments: seed.Comments ?? '',\n CostCenter: seed.CostCenter ?? '',\n Credit: seed.Credit ?? 'false',\n Currency: seed.Currency ?? customer?.Currency ?? 'SEK',\n CurrencyRate: seed.CurrencyRate ?? 1,\n CurrencyUnit: seed.CurrencyUnit ?? 1,\n DueDate: seed.DueDate ?? null,\n EmailInformation: seed.EmailInformation ?? defaultEmailInformation(customer, company),\n ExternalInvoiceReference1: seed.ExternalInvoiceReference1 ?? '',\n ExternalInvoiceReference2: seed.ExternalInvoiceReference2 ?? '',\n FinalPayDate: seed.FinalPayDate ?? null,\n Gross: totals.Gross,\n InvoiceDate: seed.InvoiceDate ?? '2026-09-30',\n InvoiceRows: rows,\n InvoiceType: seed.InvoiceType ?? 'INVOICE',\n Net: totals.Net,\n NotCompleted: seed.NotCompleted ?? false,\n NoxFinans: seed.NoxFinans ?? false,\n OCR: seed.OCR ?? '',\n OurReference: seed.OurReference ?? '',\n Project: seed.Project ?? '',\n Sent: seed.Sent ?? false,\n TermsOfPayment: seed.TermsOfPayment ?? customer?.TermsOfPayment ?? '30',\n Total: totals.Total,\n TotalToPay: totals.TotalToPay,\n TotalVAT: totals.TotalVAT,\n VoucherNumber: seed.VoucherNumber ?? null,\n VoucherSeries: seed.VoucherSeries ?? null,\n VoucherYear: seed.VoucherYear ?? null,\n WayOfDelivery: seed.WayOfDelivery ?? '',\n YourReference: seed.YourReference ?? ''\n }\n}\n\nconst duplicate = (values: ReadonlyArray<string | number>): string | number | undefined =>\n values.find((value, index) => values.indexOf(value) !== index)\n\nconst quantityProblem = (rows: ReadonlyArray<FortnoxEmulatorInvoiceRowSeed>) =>\n rows.find(\n row => row.DeliveredQuantity !== undefined && !Number.isFinite(Number(row.DeliveredQuantity))\n )\n\n/** Integrity problems a decoded seed can still have (duplicates, dangling references). */\nconst seedProblem = (entities: ProfileEntities): string | undefined => {\n const customerNumbers = entities.customers.map(customer => customer.CustomerNumber)\n const documentNumbers = entities.invoices.map(invoice => invoice.DocumentNumber)\n\n const rowIds = entities.invoices.flatMap(invoice =>\n (invoice.InvoiceRows ?? []).flatMap(row => (row.RowId === undefined ? [] : [row.RowId]))\n )\n\n const duplicateCustomer = duplicate(customerNumbers)\n const duplicateInvoice = duplicate(documentNumbers)\n const duplicateRow = duplicate(rowIds)\n\n if (duplicateCustomer !== undefined) return `duplicate CustomerNumber ${duplicateCustomer}`\n\n if (duplicateInvoice !== undefined) return `duplicate DocumentNumber ${duplicateInvoice}`\n\n if (duplicateRow !== undefined) return `duplicate RowId ${duplicateRow}`\n\n const dangling = entities.invoices.find(\n invoice => !customerNumbers.includes(invoice.CustomerNumber)\n )\n\n if (dangling !== undefined) {\n return `invoice ${dangling.DocumentNumber} references missing customer ${dangling.CustomerNumber}`\n }\n\n const badQuantity = entities.invoices.find(\n invoice => quantityProblem(invoice.InvoiceRows ?? []) !== undefined\n )\n\n return badQuantity === undefined\n ? undefined\n : `invoice ${badQuantity.DocumentNumber} has a non-numeric DeliveredQuantity`\n}\n\nconst nextNumber = (numbers: ReadonlyArray<string>): number =>\n numbers.reduce((max, value) => {\n const parsed = Number(value)\n\n return Number.isSafeInteger(parsed) && parsed >= max ? parsed + 1 : max\n }, 1)\n\n/** Build the emulator state for a decoded seed; a string is an integrity problem. */\nconst stateFromSeed = (seed: FortnoxEmulatorSeed): FortnoxEmulatorState | string => {\n const profile = profileEntities(seed.profile ?? 'default')\n\n const entities: ProfileEntities = {\n company: seed.company ?? profile.company,\n customers: seed.customers ?? profile.customers,\n invoices: seed.invoices ?? profile.invoices\n }\n\n const problem = seedProblem(entities)\n\n if (problem !== undefined) {\n return problem\n }\n\n const customers = entities.customers\n .map(customerFromSeed)\n .sort((left, right) => compareNumbers(left.CustomerNumber, right.CustomerNumber))\n\n const explicitRowIds = entities.invoices.flatMap(invoice =>\n (invoice.InvoiceRows ?? []).flatMap(row => (row.RowId === undefined ? [] : [row.RowId]))\n )\n\n const ids: RowIdSource = { next: Math.max(0, ...explicitRowIds) + 1 }\n\n const invoices = entities.invoices\n .map(invoice =>\n invoiceFromSeed(\n invoice,\n customers.find(customer => customer.CustomerNumber === invoice.CustomerNumber),\n entities.company,\n ids\n )\n )\n .sort((left, right) => compareNumbers(left.DocumentNumber, right.DocumentNumber))\n\n return {\n company: entities.company,\n customers,\n invoices,\n outbox: [],\n counters: {\n nextRowId: ids.next,\n nextDocumentNumber: nextNumber(invoices.map(invoice => invoice.DocumentNumber))\n }\n }\n}\n\n/** Decode and build a seed; a string is the reason it is invalid. */\nexport const buildSeedState = (input: unknown): FortnoxEmulatorState | string => {\n const decoded = decodeSeedInput(input)\n\n return Result.isFailure(decoded)\n ? issueMessage(decoded.failure.issue)\n : stateFromSeed(decoded.success)\n}\n\n/** Decode a full state (a restored snapshot); a string is the reason it is invalid. */\nexport const decodeState = (input: unknown): FortnoxEmulatorState | string => {\n const decoded = decodeStateInput(input)\n\n if (Result.isFailure(decoded)) {\n return issueMessage(decoded.failure.issue)\n }\n\n return { ...decoded.success }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { EmulatorEvidence, EmulatorRouteEvidence, emulatorEvidenceHeader } from "./route-evidence.mjs";
|
|
2
|
+
import { FortnoxEmulatorCompany, FortnoxEmulatorCustomer, FortnoxEmulatorCustomerSeed, FortnoxEmulatorInvoice, FortnoxEmulatorInvoiceRow, FortnoxEmulatorInvoiceRowSeed, FortnoxEmulatorInvoiceSeed, FortnoxEmulatorOutboxEntry, FortnoxEmulatorProfile, FortnoxEmulatorSeed, FortnoxEmulatorState, FortnoxEmulatorStateSchema } from "./fortnox/state.mjs";
|
|
3
|
+
import { FortnoxEmulatorQuirk, FortnoxEmulatorQuirks, fortnoxEmulatorBasePath, fortnoxEmulatorErrorCodes, fortnoxEmulatorQuirks } from "./fortnox/api.mjs";
|
|
4
|
+
import * as Schema from "effect/Schema";
|
|
5
|
+
|
|
6
|
+
//#region src/fortnox.d.ts
|
|
7
|
+
/** Origin the connector calls; also the default origin of `@url` links. */
|
|
8
|
+
declare const fortnoxEmulatorDefaultOrigin = "https://api.fortnox.se";
|
|
9
|
+
/**
|
|
10
|
+
* Route evidence manifest: every emulated Fortnox route, whether it writes, and the conformance
|
|
11
|
+
* cases whose (currently synthetic, unverified) wire claims it follows. Kept in sync with the
|
|
12
|
+
* handlers by construction (both come from one route table).
|
|
13
|
+
*/
|
|
14
|
+
declare const fortnoxEmulatorRoutes: ReadonlyArray<EmulatorRouteEvidence>;
|
|
15
|
+
/** Optional fault filter; an omitted field matches every request. `path` ending in `*` is a prefix. */
|
|
16
|
+
declare const FortnoxFaultMatch: Schema.Struct<{
|
|
17
|
+
readonly method: Schema.optionalKey<Schema.String>;
|
|
18
|
+
readonly path: Schema.optionalKey<Schema.String>;
|
|
19
|
+
}>;
|
|
20
|
+
type FortnoxFaultMatch = typeof FortnoxFaultMatch.Type;
|
|
21
|
+
/**
|
|
22
|
+
* A wire fault: answer matching requests with this status, headers, and body (for example 429
|
|
23
|
+
* with `retry-after`) instead of the route's successful answer, so nothing is written. A request
|
|
24
|
+
* the route refuses is answered by the route and uses up no fault. The body defaults to a
|
|
25
|
+
* Fortnox `ErrorInformation`. `count` limits how many requests it answers (omitted: all).
|
|
26
|
+
*
|
|
27
|
+
* The Gateway emulator's rules apply: statuses that cannot carry a body (1xx, 204, 205) and
|
|
28
|
+
* redirects (3xx) are rejected, as are invalid header names or values, a `location` header, and
|
|
29
|
+
* framing headers (`content-length`, `transfer-encoding`, `connection`, `keep-alive`, `upgrade`).
|
|
30
|
+
*/
|
|
31
|
+
declare const FortnoxFault: Schema.Struct<{
|
|
32
|
+
readonly kind: Schema.Literal<"status">;
|
|
33
|
+
readonly status: Schema.Int;
|
|
34
|
+
readonly headers: Schema.optionalKey<Schema.$Record<Schema.String, Schema.String>>;
|
|
35
|
+
readonly body: Schema.optionalKey<Schema.Codec<Schema.Json, Schema.Json, never, never>>;
|
|
36
|
+
readonly match: Schema.optionalKey<Schema.Struct<{
|
|
37
|
+
readonly method: Schema.optionalKey<Schema.String>;
|
|
38
|
+
readonly path: Schema.optionalKey<Schema.String>;
|
|
39
|
+
}>>;
|
|
40
|
+
readonly count: Schema.optionalKey<Schema.Int>;
|
|
41
|
+
}>;
|
|
42
|
+
type FortnoxFault = typeof FortnoxFault.Type;
|
|
43
|
+
declare const FortnoxEmulatorInputInvalid_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }>) => import("effect/Cause").YieldableError & {
|
|
44
|
+
readonly _tag: "FortnoxEmulatorInputInvalid";
|
|
45
|
+
} & Readonly<A>;
|
|
46
|
+
/** Invalid emulator input from the JS API: a seed, a fault, or an option. A programmer error. */
|
|
47
|
+
declare class FortnoxEmulatorInputInvalid extends FortnoxEmulatorInputInvalid_base<{
|
|
48
|
+
readonly input: 'seed' | 'fault' | 'option';
|
|
49
|
+
readonly reason: string;
|
|
50
|
+
}> {
|
|
51
|
+
get message(): string;
|
|
52
|
+
}
|
|
53
|
+
type FortnoxLedgerEntry = {
|
|
54
|
+
/** 1-based arrival order since the last ledger clear or reset. */readonly seq: number;
|
|
55
|
+
readonly method: string; /** Concrete request path, for example `/3/invoices/103`. */
|
|
56
|
+
readonly path: string; /** Path template of the matched route, for example `/3/invoices/{DocumentNumber}`. */
|
|
57
|
+
readonly route?: string;
|
|
58
|
+
readonly query: Readonly<Record<string, string>>; /** Parsed JSON request body, when there was one. */
|
|
59
|
+
readonly body?: Schema.Json;
|
|
60
|
+
readonly status: number; /** Evidence of the matched route; `unknown-route` for requests that failed closed. */
|
|
61
|
+
readonly evidence: EmulatorEvidence | 'unknown-route'; /** Set when a fault answered the request. */
|
|
62
|
+
readonly fault?: 'status';
|
|
63
|
+
/**
|
|
64
|
+
* Set when the emulator could not build or produce the response, or a route handler threw; the
|
|
65
|
+
* request was answered with a 500 `ErrorInformation` (still evidence-tagged) and no fault was
|
|
66
|
+
* used up.
|
|
67
|
+
*/
|
|
68
|
+
readonly responseError?: string;
|
|
69
|
+
};
|
|
70
|
+
type FortnoxFaultState = {
|
|
71
|
+
readonly id: number;
|
|
72
|
+
readonly fault: FortnoxFault; /** Remaining matching requests; `undefined` for an unlimited fault. */
|
|
73
|
+
readonly remaining: number | undefined;
|
|
74
|
+
readonly applied: number;
|
|
75
|
+
};
|
|
76
|
+
type FortnoxRouteCoverage = EmulatorRouteEvidence & {
|
|
77
|
+
/** Ledger requests answered by this route since the last ledger clear or reset. */readonly requests: number;
|
|
78
|
+
};
|
|
79
|
+
type FortnoxCoverage = {
|
|
80
|
+
readonly routes: ReadonlyArray<FortnoxRouteCoverage>; /** Ledger requests to unknown routes (failed closed). */
|
|
81
|
+
readonly unknownRouteRequests: number;
|
|
82
|
+
};
|
|
83
|
+
type FortnoxEmulatorOptions = {
|
|
84
|
+
/** Typed seed; defaults to the fixture entities (`profile: 'default'`). */readonly seed?: FortnoxEmulatorSeed; /** Clock in epoch milliseconds. Defaults to `Date.now`. */
|
|
85
|
+
readonly now?: () => number; /** Drill knobs (tests only): flip one observed quirk to prove the cases catch it. */
|
|
86
|
+
readonly quirks?: FortnoxEmulatorQuirks; /** Origin of `@url` links in responses. Defaults to `https://api.fortnox.se`. */
|
|
87
|
+
readonly baseUrl?: string;
|
|
88
|
+
};
|
|
89
|
+
type FortnoxEmulator = {
|
|
90
|
+
/** The fetch handler (the `/3` API plus the `/_emulate/*` control plane). Never rejects. */readonly fetch: (request: Request) => Promise<Response>; /** Origin of `@url` links in responses. */
|
|
91
|
+
readonly baseUrl: string;
|
|
92
|
+
readonly ledger: {
|
|
93
|
+
readonly entries: () => ReadonlyArray<FortnoxLedgerEntry>;
|
|
94
|
+
readonly clear: () => void;
|
|
95
|
+
};
|
|
96
|
+
readonly faults: {
|
|
97
|
+
/** Add a fault; throws `FortnoxEmulatorInputInvalid` for an invalid fault. */readonly add: (fault: FortnoxFault) => FortnoxFaultState;
|
|
98
|
+
readonly list: () => ReadonlyArray<FortnoxFaultState>;
|
|
99
|
+
readonly clear: () => void;
|
|
100
|
+
}; /** Restore the current seed and clear the ledger and faults. */
|
|
101
|
+
readonly reset: () => Promise<void>;
|
|
102
|
+
/**
|
|
103
|
+
* Replace the state with a new seed, which becomes what `reset` restores. Rejects with
|
|
104
|
+
* `FortnoxEmulatorInputInvalid` for an invalid seed.
|
|
105
|
+
*/
|
|
106
|
+
readonly seed: (seed: FortnoxEmulatorSeed) => Promise<void>; /** A deep copy of the current state (entities, outbox, counters). */
|
|
107
|
+
readonly snapshot: () => FortnoxEmulatorState;
|
|
108
|
+
readonly coverage: () => FortnoxCoverage; /** Close the core runtime. Later requests answer 503. Idempotent. */
|
|
109
|
+
readonly close: () => Promise<void>;
|
|
110
|
+
};
|
|
111
|
+
/**
|
|
112
|
+
* Create a stateful Fortnox emulator on the `@emulators/core` custom runtime. Each call has its
|
|
113
|
+
* own state, ledger, and faults. Rejects with `FortnoxEmulatorInputInvalid` for an invalid seed
|
|
114
|
+
* or `baseUrl`.
|
|
115
|
+
*
|
|
116
|
+
* Requests to `/3/*` need `Authorization: Bearer <non-empty>` (missing: 401 `ErrorInformation`);
|
|
117
|
+
* the token is never checked, stored, forwarded to the core, or ledgered. Precedence per request:
|
|
118
|
+
* route match (unknown routes fail closed with a 404 `ErrorInformation`), authorization, JSON
|
|
119
|
+
* body parsing, then the stateful route's plan (its query allowlist, then its handler on a draft
|
|
120
|
+
* of the state), then the first matching fault, then the commit. A request the route refuses (any
|
|
121
|
+
* answer that is not 2xx, such as a 404 for an unknown id or a 400 for an invalid state) uses up
|
|
122
|
+
* no fault; a faulted request writes nothing. Every response from an unverified route carries
|
|
123
|
+
* `x-emulator-evidence: unverified`.
|
|
124
|
+
*/
|
|
125
|
+
declare const makeFortnoxEmulator: (options?: FortnoxEmulatorOptions) => Promise<FortnoxEmulator>;
|
|
126
|
+
//#endregion
|
|
127
|
+
export { type EmulatorEvidence, type EmulatorRouteEvidence, FortnoxCoverage, FortnoxEmulator, FortnoxEmulatorCompany, FortnoxEmulatorCustomer, FortnoxEmulatorCustomerSeed, FortnoxEmulatorInputInvalid, FortnoxEmulatorInvoice, FortnoxEmulatorInvoiceRow, FortnoxEmulatorInvoiceRowSeed, FortnoxEmulatorInvoiceSeed, FortnoxEmulatorOptions, FortnoxEmulatorOutboxEntry, FortnoxEmulatorProfile, type FortnoxEmulatorQuirk, type FortnoxEmulatorQuirks, FortnoxEmulatorSeed, type FortnoxEmulatorState, FortnoxEmulatorStateSchema, FortnoxFault, FortnoxFaultMatch, FortnoxFaultState, FortnoxLedgerEntry, FortnoxRouteCoverage, emulatorEvidenceHeader, fortnoxEmulatorBasePath, fortnoxEmulatorDefaultOrigin, fortnoxEmulatorErrorCodes, fortnoxEmulatorQuirks, fortnoxEmulatorRoutes, makeFortnoxEmulator };
|
|
128
|
+
//# sourceMappingURL=fortnox.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fortnox.d.mts","names":[],"sources":["../src/fortnox.ts"],"mappings":";;;;;;;cA0Ea,4BAAA;AAA4B;AAOzC;;;;AAPyC,cAO5B,qBAAA,EAAuB,aAAa,CAAC,qBAAA;AAOlD;AAAA,cAAa,iBAAA,EAAiB,MAAA,CAAA,MAAA;EAAA;;;KAKlB,iBAAA,UAA2B,iBAAA,CAAkB,IAAI;;;;;;;;;;;cAYhD,YAAA,EAAY,MAAA,CAAA,MAAA;EAAA;;;;;;;;;;KASb,YAAA,UAAsB,YAAA,CAAa,IAAI;AAAA,cAAA,gCAAA;;;;cAGtC,2BAAA,SAAoC,gCAAA;EAAA,SACtC,KAAA;EAAA,SACA,MAAA;AAAA;EAAA,IAEI,OAAA,CAAA;AAAA;AAAA,KAKH,kBAAA;6EAED,GAAA;EAAA,SACA,MAAA;WAEA,IAAA;WAEA,KAAA;EAAA,SACA,KAAA,EAAO,QAAA,CAAS,MAAA;WAEhB,IAAA,GAAO,MAAA,CAAO,IAAA;EAAA,SACd,MAAA,UAhCc;EAAA,SAkCd,QAAA,EAAU,gBAAA,oBAlCI;EAAA,SAoCd,KAAA;EApCc;;;;;EAAA,SA0Cd,aAAA;AAAA;AAAA,KAGC,iBAAA;EAAA,SACD,EAAA;EAAA,SACA,KAAA,EAAO,YAAY;WAEnB,SAAA;EAAA,SACA,OAAA;AAAA;AAAA,KAGC,oBAAA,GAAuB,qBAAqB;8FAE7C,QAAQ;AAAA;AAAA,KAGP,eAAA;EAAA,SACD,MAAA,EAAQ,aAAa,CAAC,oBAAA;WAEtB,oBAAA;AAAA;AAAA,KAGC,sBAAA;sFAED,IAAA,GAAO,mBAAA;WAEP,GAAA;WAEA,MAAA,GAAS,qBAAqB;WAE9B,OAAA;AAAA;AAAA,KAGC,eAAA;uGAED,KAAA,GAAQ,OAAA,EAAS,OAAA,KAAY,OAAA,CAAQ,QAAA;WAErC,OAAA;EAAA,SACA,MAAA;IAAA,SACE,OAAA,QAAe,aAAA,CAAc,kBAAA;IAAA,SAC7B,KAAA;EAAA;EAAA,SAEF,MAAA;2FAEE,GAAA,GAAM,KAAA,EAAO,YAAA,KAAiB,iBAAA;IAAA,SAC9B,IAAA,QAAY,aAAA,CAAc,iBAAA;IAAA,SAC1B,KAAA;EAAA,GA/EW;EAAA,SAkFb,KAAA,QAAa,OAAA;EAlFU;AAAiB;AAAA;;EAAjB,SAuFvB,IAAA,GAAO,IAAA,EAAM,mBAAA,KAAwB,OAAA;WAErC,QAAA,QAAgB,oBAAA;EAAA,SAChB,QAAA,QAAgB,eAAA;WAEhB,KAAA,QAAa,OAAA;AAAA;;;;;;;;;;;;;;;cAwIX,mBAAA,GACX,OAAA,GAAS,sBAAA,KACR,OAAA,CAAQ,eAAA"}
|
package/dist/fortnox.mjs
ADDED
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
import { EmulatorHeaderRecord, EmulatorResponseStatus, emulatorJobHeader } from "./emulator-http.mjs";
|
|
2
|
+
import { emulatorEvidenceHeader } from "./route-evidence.mjs";
|
|
3
|
+
import { FortnoxEmulatorCompany, FortnoxEmulatorCustomer, FortnoxEmulatorCustomerSeed, FortnoxEmulatorInvoice, FortnoxEmulatorInvoiceRow, FortnoxEmulatorInvoiceRowSeed, FortnoxEmulatorInvoiceSeed, FortnoxEmulatorOutboxEntry, FortnoxEmulatorProfile, FortnoxEmulatorSeed, FortnoxEmulatorStateSchema, buildSeedState, decodeState } from "./fortnox/state.mjs";
|
|
4
|
+
import { errorInformation, fortnoxApiRoutes, fortnoxEmulatorBasePath, fortnoxEmulatorErrorCodes, fortnoxEmulatorQuirks, matchFortnoxRoute, parseJsonText, registerFortnoxApi } from "./fortnox/api.mjs";
|
|
5
|
+
import { Data, Predicate, Result } from "effect";
|
|
6
|
+
import * as Schema from "effect/Schema";
|
|
7
|
+
//#region src/fortnox.ts
|
|
8
|
+
/** Origin the connector calls; also the default origin of `@url` links. */
|
|
9
|
+
const fortnoxEmulatorDefaultOrigin = "https://api.fortnox.se";
|
|
10
|
+
/**
|
|
11
|
+
* Route evidence manifest: every emulated Fortnox route, whether it writes, and the conformance
|
|
12
|
+
* cases whose (currently synthetic, unverified) wire claims it follows. Kept in sync with the
|
|
13
|
+
* handlers by construction (both come from one route table).
|
|
14
|
+
*/
|
|
15
|
+
const fortnoxEmulatorRoutes = fortnoxApiRoutes.map(({ handler: _handler, queryKeys: _queryKeys, ...route }) => route);
|
|
16
|
+
const FaultCount = Schema.Int.check(Schema.isGreaterThanOrEqualTo(1));
|
|
17
|
+
/** Optional fault filter; an omitted field matches every request. `path` ending in `*` is a prefix. */
|
|
18
|
+
const FortnoxFaultMatch = Schema.Struct({
|
|
19
|
+
method: Schema.optionalKey(Schema.String),
|
|
20
|
+
path: Schema.optionalKey(Schema.String)
|
|
21
|
+
});
|
|
22
|
+
/**
|
|
23
|
+
* A wire fault: answer matching requests with this status, headers, and body (for example 429
|
|
24
|
+
* with `retry-after`) instead of the route's successful answer, so nothing is written. A request
|
|
25
|
+
* the route refuses is answered by the route and uses up no fault. The body defaults to a
|
|
26
|
+
* Fortnox `ErrorInformation`. `count` limits how many requests it answers (omitted: all).
|
|
27
|
+
*
|
|
28
|
+
* The Gateway emulator's rules apply: statuses that cannot carry a body (1xx, 204, 205) and
|
|
29
|
+
* redirects (3xx) are rejected, as are invalid header names or values, a `location` header, and
|
|
30
|
+
* framing headers (`content-length`, `transfer-encoding`, `connection`, `keep-alive`, `upgrade`).
|
|
31
|
+
*/
|
|
32
|
+
const FortnoxFault = Schema.Struct({
|
|
33
|
+
kind: Schema.Literal("status"),
|
|
34
|
+
status: EmulatorResponseStatus,
|
|
35
|
+
headers: Schema.optionalKey(EmulatorHeaderRecord),
|
|
36
|
+
body: Schema.optionalKey(Schema.Json),
|
|
37
|
+
match: Schema.optionalKey(FortnoxFaultMatch),
|
|
38
|
+
count: Schema.optionalKey(FaultCount)
|
|
39
|
+
});
|
|
40
|
+
/** Invalid emulator input from the JS API: a seed, a fault, or an option. A programmer error. */
|
|
41
|
+
var FortnoxEmulatorInputInvalid = class extends Data.TaggedError("FortnoxEmulatorInputInvalid") {
|
|
42
|
+
get message() {
|
|
43
|
+
return `Invalid Fortnox emulator ${this.input}: ${this.reason}`;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const strict = { onExcessProperty: "error" };
|
|
47
|
+
const decodeFault = Schema.decodeUnknownResult(FortnoxFault, strict);
|
|
48
|
+
const decodeFaultList = Schema.decodeUnknownResult(Schema.Union([FortnoxFault, Schema.Struct({ faults: Schema.Array(FortnoxFault) })]), strict);
|
|
49
|
+
const issueMessage = (issue) => new Schema.SchemaError(issue).message;
|
|
50
|
+
const jsonResponse = (status, body, headers = {}) => {
|
|
51
|
+
const responseHeaders = new Headers(headers);
|
|
52
|
+
responseHeaders.set("content-type", "application/json");
|
|
53
|
+
return new Response(JSON.stringify(body), {
|
|
54
|
+
status,
|
|
55
|
+
headers: responseHeaders
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
const controlError = (status, message, headers = {}) => jsonResponse(status, { error: {
|
|
59
|
+
message,
|
|
60
|
+
type: "emulator_error"
|
|
61
|
+
} }, headers);
|
|
62
|
+
const codes = fortnoxEmulatorErrorCodes;
|
|
63
|
+
const defaultFaultBody = (status) => ({ ErrorInformation: status === 429 ? {
|
|
64
|
+
error: 1,
|
|
65
|
+
message: "Synthetic: too many requests.",
|
|
66
|
+
code: codes.rateLimited
|
|
67
|
+
} : {
|
|
68
|
+
error: 1,
|
|
69
|
+
message: `Synthetic upstream error (${status}).`,
|
|
70
|
+
code: codes.upstreamError
|
|
71
|
+
} });
|
|
72
|
+
const bearerPattern = /^bearer\s+\S+/i;
|
|
73
|
+
const pathMatches = (pattern, path) => pattern.endsWith("*") ? path.startsWith(pattern.slice(0, -1)) : pattern === path;
|
|
74
|
+
const faultMatches = (fault, method, path) => (fault.match?.method === void 0 || fault.match.method.toUpperCase() === method) && (fault.match?.path === void 0 || pathMatches(fault.match.path, path));
|
|
75
|
+
const readText = (request) => request.text().then((text) => text, () => void 0);
|
|
76
|
+
const validOrigin = (input) => {
|
|
77
|
+
if (!URL.canParse(input)) return;
|
|
78
|
+
const url = new URL(input);
|
|
79
|
+
const bare = (url.pathname === "/" || url.pathname === "") && url.search === "" && url.hash === "" && url.username === "" && url.password === "";
|
|
80
|
+
return (url.protocol === "http:" || url.protocol === "https:") && bare ? url.origin : void 0;
|
|
81
|
+
};
|
|
82
|
+
const withEvidence = (response, evidence) => {
|
|
83
|
+
const headers = new Headers(response.headers);
|
|
84
|
+
if (evidence === "unverified") headers.set(emulatorEvidenceHeader, "unverified");
|
|
85
|
+
return new Response(response.body, {
|
|
86
|
+
status: response.status,
|
|
87
|
+
statusText: response.statusText,
|
|
88
|
+
headers
|
|
89
|
+
});
|
|
90
|
+
};
|
|
91
|
+
const defaultQuirks = {
|
|
92
|
+
stickyRowDiscount: true,
|
|
93
|
+
emptyStringClears: false,
|
|
94
|
+
paymentFiltersIncludeUnbooked: false
|
|
95
|
+
};
|
|
96
|
+
/** The evidence-tagged fallback when a matched API route cannot build or produce its response. */
|
|
97
|
+
const responseFailed = () => errorInformation(500, codes.upstreamError, "Synthetic: the emulator could not build the response.");
|
|
98
|
+
const isControlPath = (path) => path === "/_emulate" || path.startsWith("/_emulate/");
|
|
99
|
+
/**
|
|
100
|
+
* Create a stateful Fortnox emulator on the `@emulators/core` custom runtime. Each call has its
|
|
101
|
+
* own state, ledger, and faults. Rejects with `FortnoxEmulatorInputInvalid` for an invalid seed
|
|
102
|
+
* or `baseUrl`.
|
|
103
|
+
*
|
|
104
|
+
* Requests to `/3/*` need `Authorization: Bearer <non-empty>` (missing: 401 `ErrorInformation`);
|
|
105
|
+
* the token is never checked, stored, forwarded to the core, or ledgered. Precedence per request:
|
|
106
|
+
* route match (unknown routes fail closed with a 404 `ErrorInformation`), authorization, JSON
|
|
107
|
+
* body parsing, then the stateful route's plan (its query allowlist, then its handler on a draft
|
|
108
|
+
* of the state), then the first matching fault, then the commit. A request the route refuses (any
|
|
109
|
+
* answer that is not 2xx, such as a 404 for an unknown id or a 400 for an invalid state) uses up
|
|
110
|
+
* no fault; a faulted request writes nothing. Every response from an unverified route carries
|
|
111
|
+
* `x-emulator-evidence: unverified`.
|
|
112
|
+
*/
|
|
113
|
+
const makeFortnoxEmulator = async (options = {}) => {
|
|
114
|
+
const initial = buildSeedState(options.seed ?? {});
|
|
115
|
+
if (Predicate.isString(initial)) throw new FortnoxEmulatorInputInvalid({
|
|
116
|
+
input: "seed",
|
|
117
|
+
reason: initial
|
|
118
|
+
});
|
|
119
|
+
const linkOrigin = validOrigin(options.baseUrl ?? "https://api.fortnox.se");
|
|
120
|
+
if (linkOrigin === void 0) throw new FortnoxEmulatorInputInvalid({
|
|
121
|
+
input: "option",
|
|
122
|
+
reason: "baseUrl must be an http(s) origin without path, query, hash, or credentials"
|
|
123
|
+
});
|
|
124
|
+
const env = {
|
|
125
|
+
now: options.now ?? (() => Date.now()),
|
|
126
|
+
linkOrigin,
|
|
127
|
+
quirks: {
|
|
128
|
+
...defaultQuirks,
|
|
129
|
+
...options.quirks
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
const core = await import("@emulators/core");
|
|
133
|
+
/**
|
|
134
|
+
* Fault decisions of the core requests in flight, by job id (asked by the route once it would
|
|
135
|
+
* answer successfully). Job ids never reset, so a ledger clear never makes two jobs share one.
|
|
136
|
+
*/
|
|
137
|
+
const jobs = /* @__PURE__ */ new Map();
|
|
138
|
+
let nextJobId = 1;
|
|
139
|
+
const definition = core.defineEmulator({
|
|
140
|
+
name: "fortnox",
|
|
141
|
+
cors: false,
|
|
142
|
+
state: () => initial,
|
|
143
|
+
validateSeed: (value) => {
|
|
144
|
+
const decoded = decodeState(value);
|
|
145
|
+
if (Predicate.isString(decoded)) throw new FortnoxEmulatorInputInvalid({
|
|
146
|
+
input: "seed",
|
|
147
|
+
reason: decoded
|
|
148
|
+
});
|
|
149
|
+
return decoded;
|
|
150
|
+
},
|
|
151
|
+
setup: ({ app, state }) => registerFortnoxApi(app, state, env, (jobId) => jobId === void 0 ? false : jobs.get(jobId)?.() ?? false)
|
|
152
|
+
});
|
|
153
|
+
const runtime = await core.createCustomRuntime(definition, { seed: initial });
|
|
154
|
+
let baseline = initial;
|
|
155
|
+
let entries = [];
|
|
156
|
+
let faultStates = [];
|
|
157
|
+
let nextSeq = 1;
|
|
158
|
+
let nextFaultId = 1;
|
|
159
|
+
let closed = false;
|
|
160
|
+
const snapshotEntry = (entry) => ({
|
|
161
|
+
...entry,
|
|
162
|
+
query: { ...entry.query }
|
|
163
|
+
});
|
|
164
|
+
const snapshotFault = (state) => ({ ...state });
|
|
165
|
+
const clearLedger = () => {
|
|
166
|
+
entries = [];
|
|
167
|
+
nextSeq = 1;
|
|
168
|
+
};
|
|
169
|
+
const addFault = (input) => {
|
|
170
|
+
const decoded = decodeFault(input);
|
|
171
|
+
if (Result.isFailure(decoded)) return issueMessage(decoded.failure.issue);
|
|
172
|
+
const state = {
|
|
173
|
+
id: nextFaultId++,
|
|
174
|
+
fault: decoded.success,
|
|
175
|
+
remaining: decoded.success.count,
|
|
176
|
+
applied: 0
|
|
177
|
+
};
|
|
178
|
+
faultStates.push(state);
|
|
179
|
+
return snapshotFault(state);
|
|
180
|
+
};
|
|
181
|
+
const takeFault = (method, path) => faultStates.find((state) => (state.remaining === void 0 || state.remaining > 0) && faultMatches(state.fault, method, path));
|
|
182
|
+
/** Build the fault's response first: a response that cannot be built must not consume it. */
|
|
183
|
+
const applyFault = (state) => {
|
|
184
|
+
const headers = new Headers(state.fault.headers ?? {});
|
|
185
|
+
const body = state.fault.body ?? defaultFaultBody(state.fault.status);
|
|
186
|
+
if (!headers.has("content-type")) headers.set("content-type", Predicate.isString(body) ? "text/plain" : "application/json");
|
|
187
|
+
const response = new Response(Predicate.isString(body) ? body : JSON.stringify(body), {
|
|
188
|
+
status: state.fault.status,
|
|
189
|
+
headers
|
|
190
|
+
});
|
|
191
|
+
state.applied += 1;
|
|
192
|
+
if (state.remaining !== void 0) state.remaining -= 1;
|
|
193
|
+
return response;
|
|
194
|
+
};
|
|
195
|
+
const snapshot = () => runtime.snapshot().state;
|
|
196
|
+
const restore = (state) => {
|
|
197
|
+
const current = runtime.snapshot();
|
|
198
|
+
return runtime.restore({
|
|
199
|
+
...current,
|
|
200
|
+
state
|
|
201
|
+
});
|
|
202
|
+
};
|
|
203
|
+
const reset = async () => {
|
|
204
|
+
clearLedger();
|
|
205
|
+
faultStates = [];
|
|
206
|
+
await restore(baseline);
|
|
207
|
+
};
|
|
208
|
+
const reseed = async (input) => {
|
|
209
|
+
const next = buildSeedState(input);
|
|
210
|
+
if (Predicate.isString(next)) return next;
|
|
211
|
+
await restore(next);
|
|
212
|
+
baseline = next;
|
|
213
|
+
return next;
|
|
214
|
+
};
|
|
215
|
+
const coverage = () => ({
|
|
216
|
+
routes: fortnoxEmulatorRoutes.map((route) => ({
|
|
217
|
+
...route,
|
|
218
|
+
requests: entries.filter((entry) => entry.route === route.path && entry.method.toUpperCase() === route.method).length
|
|
219
|
+
})),
|
|
220
|
+
unknownRouteRequests: entries.filter((entry) => entry.evidence === "unknown-route").length
|
|
221
|
+
});
|
|
222
|
+
/**
|
|
223
|
+
* Authorization, body parsing, then the core route (without the credential), which asks for the
|
|
224
|
+
* fault only once it would answer successfully.
|
|
225
|
+
*/
|
|
226
|
+
const routed = async (request, url, entry) => {
|
|
227
|
+
if (!bearerPattern.test(request.headers.get("authorization") ?? "")) return errorInformation(401, codes.unauthorized, "Synthetic: missing or invalid authorization.");
|
|
228
|
+
const text = await readText(request);
|
|
229
|
+
if (text === void 0) return errorInformation(400, codes.invalidBody, "Synthetic: the request body is unreadable.");
|
|
230
|
+
if (text !== "") {
|
|
231
|
+
const json = parseJsonText(text);
|
|
232
|
+
if (json === void 0) return errorInformation(400, codes.invalidBody, "Synthetic: the request body is not valid JSON.");
|
|
233
|
+
entry.body = json;
|
|
234
|
+
}
|
|
235
|
+
const method = request.method.toUpperCase();
|
|
236
|
+
const hasBody = text !== "" && method !== "GET" && method !== "HEAD";
|
|
237
|
+
const jobId = String(nextJobId++);
|
|
238
|
+
const headers = new Headers({
|
|
239
|
+
accept: "application/json",
|
|
240
|
+
[emulatorJobHeader]: jobId
|
|
241
|
+
});
|
|
242
|
+
if (hasBody) headers.set("content-type", "application/json");
|
|
243
|
+
const init = {
|
|
244
|
+
method,
|
|
245
|
+
headers
|
|
246
|
+
};
|
|
247
|
+
if (hasBody) init.body = text;
|
|
248
|
+
const job = {};
|
|
249
|
+
jobs.set(jobId, () => {
|
|
250
|
+
const fault = takeFault(method, url.pathname);
|
|
251
|
+
if (fault === void 0) return false;
|
|
252
|
+
try {
|
|
253
|
+
job.faultAnswer = applyFault(fault);
|
|
254
|
+
} catch (error) {
|
|
255
|
+
entry.responseError = "the emulator could not build or produce the response";
|
|
256
|
+
throw error;
|
|
257
|
+
}
|
|
258
|
+
entry.fault = "status";
|
|
259
|
+
return true;
|
|
260
|
+
});
|
|
261
|
+
const response = await runtime.fetch(new Request(new URL(`${url.pathname}${url.search}`, runtime.baseUrl), init)).finally(() => jobs.delete(jobId));
|
|
262
|
+
if (job.faultAnswer !== void 0) return job.faultAnswer;
|
|
263
|
+
if (response.headers.has("x-emulator-handler-failed")) {
|
|
264
|
+
entry.responseError ??= "the route handler failed";
|
|
265
|
+
return responseFailed();
|
|
266
|
+
}
|
|
267
|
+
return response;
|
|
268
|
+
};
|
|
269
|
+
const emulatedApi = async (request, url) => {
|
|
270
|
+
const route = matchFortnoxRoute(request.method, url.pathname);
|
|
271
|
+
const entry = {
|
|
272
|
+
seq: nextSeq++,
|
|
273
|
+
method: request.method,
|
|
274
|
+
path: url.pathname,
|
|
275
|
+
query: Object.fromEntries(url.searchParams),
|
|
276
|
+
status: 0,
|
|
277
|
+
evidence: route?.evidence ?? "unknown-route"
|
|
278
|
+
};
|
|
279
|
+
entries.push(entry);
|
|
280
|
+
if (route === void 0) {
|
|
281
|
+
entry.status = 404;
|
|
282
|
+
return errorInformation(404, codes.unknownRoute, "Synthetic: no emulated Fortnox route.");
|
|
283
|
+
}
|
|
284
|
+
entry.route = route.path;
|
|
285
|
+
const tagged = withEvidence(await routed(request, url, entry).catch(() => {
|
|
286
|
+
entry.responseError = "the emulator could not build or produce the response";
|
|
287
|
+
return responseFailed();
|
|
288
|
+
}), route.evidence);
|
|
289
|
+
entry.status = tagged.status;
|
|
290
|
+
return tagged;
|
|
291
|
+
};
|
|
292
|
+
const controlPlane = async (request, path) => {
|
|
293
|
+
const method = request.method;
|
|
294
|
+
const allow = (methods) => controlError(405, "method not allowed", { allow: methods });
|
|
295
|
+
const jsonBody = async () => {
|
|
296
|
+
const text = await readText(request);
|
|
297
|
+
return text === void 0 ? void 0 : parseJsonText(text);
|
|
298
|
+
};
|
|
299
|
+
switch (path) {
|
|
300
|
+
case "/_emulate/ledger":
|
|
301
|
+
if (method === "GET") return jsonResponse(200, { entries: entries.map(snapshotEntry) });
|
|
302
|
+
if (method === "DELETE") {
|
|
303
|
+
const cleared = entries.length;
|
|
304
|
+
clearLedger();
|
|
305
|
+
return jsonResponse(200, { cleared });
|
|
306
|
+
}
|
|
307
|
+
return allow("GET, DELETE");
|
|
308
|
+
case "/_emulate/faults": {
|
|
309
|
+
if (method === "GET") return jsonResponse(200, { faults: faultStates.map(snapshotFault) });
|
|
310
|
+
if (method === "DELETE") {
|
|
311
|
+
const cleared = faultStates.length;
|
|
312
|
+
faultStates = [];
|
|
313
|
+
return jsonResponse(200, { cleared });
|
|
314
|
+
}
|
|
315
|
+
if (method !== "POST") return allow("GET, POST, DELETE");
|
|
316
|
+
const decoded = decodeFaultList(await jsonBody());
|
|
317
|
+
if (Result.isFailure(decoded)) return controlError(400, `invalid fault: ${issueMessage(decoded.failure.issue)}`);
|
|
318
|
+
return jsonResponse(201, { faults: ("faults" in decoded.success ? decoded.success.faults : [decoded.success]).map((fault) => addFault(fault)) });
|
|
319
|
+
}
|
|
320
|
+
case "/_emulate/reset":
|
|
321
|
+
if (method !== "POST") return allow("POST");
|
|
322
|
+
await reset();
|
|
323
|
+
return jsonResponse(200, { reset: true });
|
|
324
|
+
case "/_emulate/state":
|
|
325
|
+
if (method !== "GET") return allow("GET");
|
|
326
|
+
return jsonResponse(200, {
|
|
327
|
+
state: snapshot(),
|
|
328
|
+
faults: faultStates.map(snapshotFault),
|
|
329
|
+
ledgerEntries: entries.length
|
|
330
|
+
});
|
|
331
|
+
case "/_emulate/seed": {
|
|
332
|
+
if (method !== "POST") return allow("POST");
|
|
333
|
+
const next = await reseed(await jsonBody() ?? null);
|
|
334
|
+
if (Predicate.isString(next)) return controlError(400, `invalid seed: ${next}`);
|
|
335
|
+
return jsonResponse(200, {
|
|
336
|
+
seeded: true,
|
|
337
|
+
customers: next.customers.length,
|
|
338
|
+
invoices: next.invoices.length
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
case "/_emulate/coverage":
|
|
342
|
+
if (method !== "GET") return allow("GET");
|
|
343
|
+
return jsonResponse(200, coverage());
|
|
344
|
+
default: return controlError(404, "unknown control-plane route");
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
/**
|
|
348
|
+
* Last resort when handling itself fails (for example an unparseable request URL): API
|
|
349
|
+
* requests answer a Fortnox `ErrorInformation` 500, tagged with the matched route's evidence;
|
|
350
|
+
* control-plane requests answer an emulator error.
|
|
351
|
+
*/
|
|
352
|
+
const lastResort = (request) => {
|
|
353
|
+
const path = URL.canParse(request.url) ? new URL(request.url).pathname : void 0;
|
|
354
|
+
if (path !== void 0 && isControlPath(path)) return controlError(500, "emulator failed to handle the request");
|
|
355
|
+
const route = path === void 0 ? void 0 : matchFortnoxRoute(request.method, path);
|
|
356
|
+
return route === void 0 ? responseFailed() : withEvidence(responseFailed(), route.evidence);
|
|
357
|
+
};
|
|
358
|
+
const handle = async (request) => {
|
|
359
|
+
if (closed) return errorInformation(503, codes.upstreamError, "Synthetic: the emulator is closed.");
|
|
360
|
+
const url = new URL(request.url);
|
|
361
|
+
return isControlPath(url.pathname) ? controlPlane(request, url.pathname) : emulatedApi(request, url);
|
|
362
|
+
};
|
|
363
|
+
return {
|
|
364
|
+
fetch: (request) => handle(request).catch(() => lastResort(request)),
|
|
365
|
+
baseUrl: linkOrigin,
|
|
366
|
+
ledger: {
|
|
367
|
+
entries: () => entries.map(snapshotEntry),
|
|
368
|
+
clear: clearLedger
|
|
369
|
+
},
|
|
370
|
+
faults: {
|
|
371
|
+
add: (fault) => {
|
|
372
|
+
const added = addFault(fault);
|
|
373
|
+
if (Predicate.isString(added)) throw new FortnoxEmulatorInputInvalid({
|
|
374
|
+
input: "fault",
|
|
375
|
+
reason: added
|
|
376
|
+
});
|
|
377
|
+
return added;
|
|
378
|
+
},
|
|
379
|
+
list: () => faultStates.map(snapshotFault),
|
|
380
|
+
clear: () => {
|
|
381
|
+
faultStates = [];
|
|
382
|
+
}
|
|
383
|
+
},
|
|
384
|
+
reset,
|
|
385
|
+
seed: async (input) => {
|
|
386
|
+
const next = await reseed(input);
|
|
387
|
+
if (Predicate.isString(next)) throw new FortnoxEmulatorInputInvalid({
|
|
388
|
+
input: "seed",
|
|
389
|
+
reason: next
|
|
390
|
+
});
|
|
391
|
+
},
|
|
392
|
+
snapshot,
|
|
393
|
+
coverage,
|
|
394
|
+
close: () => {
|
|
395
|
+
closed = true;
|
|
396
|
+
return runtime.close();
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
};
|
|
400
|
+
//#endregion
|
|
401
|
+
export { FortnoxEmulatorCompany, FortnoxEmulatorCustomer, FortnoxEmulatorCustomerSeed, FortnoxEmulatorInputInvalid, FortnoxEmulatorInvoice, FortnoxEmulatorInvoiceRow, FortnoxEmulatorInvoiceRowSeed, FortnoxEmulatorInvoiceSeed, FortnoxEmulatorOutboxEntry, FortnoxEmulatorProfile, FortnoxEmulatorSeed, FortnoxEmulatorStateSchema, FortnoxFault, FortnoxFaultMatch, emulatorEvidenceHeader, fortnoxEmulatorBasePath, fortnoxEmulatorDefaultOrigin, fortnoxEmulatorErrorCodes, fortnoxEmulatorQuirks, fortnoxEmulatorRoutes, makeFortnoxEmulator };
|
|
402
|
+
|
|
403
|
+
//# sourceMappingURL=fortnox.mjs.map
|