{"_id":"@birtalanrobert/messaging","_rev":"4-3c54618805c94d08544a1734dcfef241","name":"@birtalanrobert/messaging","dist-tags":{"latest":"1.2.0"},"versions":{"1.0.0":{"name":"@birtalanrobert/messaging","version":"1.0.0","license":"AGPL-3.0-only","_id":"@birtalanrobert/messaging@1.0.0","maintainers":[{"name":"birtalanrobert","email":"birtalanrobert@gmail.com"}],"dist":{"shasum":"8f85d8111fa6f1b6df955bf79ebf2f8e9848ef63","tarball":"https://registry.npmjs.org/@birtalanrobert/messaging/-/messaging-1.0.0.tgz","fileCount":46,"integrity":"sha512-RKh9JeBzjHzzsR2OTjT1i+wD0VbK5Rzk9GDgAd3XtxYdZhPXscgNATE1zlOQHSkFFP7MAcvJW/+vZvxAoIuULw==","signatures":[{"sig":"MEUCIB0OQYjUhqhkeNEjOg9le5o7D4qIGMvuPfbPhVNvXaQTAiEAkZsxqN86JDR78QFUXFtNdZiQ/uJ0QF5LtLCuLrviRns=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":141690},"main":"./dist/index.js","type":"commonjs","_from":"file:birtalanrobert-messaging-1.0.0.tgz","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./nestjs":{"types":"./dist/nestjs/index.d.ts","default":"./dist/nestjs/index.js"},"./package.json":"./package.json"},"scripts":{"build":"tsc -p tsconfig.build.json","clean":"rm -rf dist *.tsbuildinfo","typecheck":"tsc -p tsconfig.json --noEmit"},"_npmUser":{"name":"birtalanrobert","email":"birtalanrobert@gmail.com"},"_resolved":"/private/var/folders/zx/7dcyg3mn6kjfyzsymzgpx1jr0000gn/T/51d1946172022fe9bf91b3dea59de7f6/birtalanrobert-messaging-1.0.0.tgz","_integrity":"sha512-RKh9JeBzjHzzsR2OTjT1i+wD0VbK5Rzk9GDgAd3XtxYdZhPXscgNATE1zlOQHSkFFP7MAcvJW/+vZvxAoIuULw==","_npmVersion":"11.13.0","description":"SMS segment counting, quiet hours, pumping detection and a message credit ledger","directories":{},"_nodeVersion":"24.16.0","dependencies":{"@birtalanrobert/tenancy":"^1.1.0","@birtalanrobert/database":"^1.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"peerDependencies":{"typeorm":"^1.0.0","@nestjs/common":"^11.0.0"},"_npmOperationalInternal":{"tmp":"tmp/messaging_1.0.0_1788425090457_0.2508599323262497","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@birtalanrobert/messaging","version":"1.0.1","license":"AGPL-3.0-only","_id":"@birtalanrobert/messaging@1.0.1","maintainers":[{"name":"birtalanrobert","email":"birtalanrobert@gmail.com"}],"dist":{"shasum":"8df74f257528cbc044ec4f47320368164086b11c","tarball":"https://registry.npmjs.org/@birtalanrobert/messaging/-/messaging-1.0.1.tgz","fileCount":46,"integrity":"sha512-jkjLPfbB5wua/NT5ynxdh5LZNsH5wdacqmR31K4CGn+FHfc0XovtmlLK2uZel0i/lsZhKX8IKvnj6nhQHk7ypA==","signatures":[{"sig":"MEQCIDOLwRKgMsChU0XPxMH+k9Fon1npRjeLjkAjEJztAbnDAiB18HusC92tyATjezEfejK5KDxX2sVnj4HvdS0ZvXWaKA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":142160},"main":"./dist/index.js","type":"commonjs","_from":"file:birtalanrobert-messaging-1.0.1.tgz","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./nestjs":{"types":"./dist/nestjs/index.d.ts","default":"./dist/nestjs/index.js"},"./package.json":"./package.json"},"scripts":{"build":"tsc -p tsconfig.build.json","clean":"rm -rf dist *.tsbuildinfo","typecheck":"tsc -p tsconfig.json --noEmit"},"_npmUser":{"name":"birtalanrobert","email":"birtalanrobert@gmail.com"},"_resolved":"/private/var/folders/zx/7dcyg3mn6kjfyzsymzgpx1jr0000gn/T/0124106bf7adf811e6926948e32a2447/birtalanrobert-messaging-1.0.1.tgz","_integrity":"sha512-jkjLPfbB5wua/NT5ynxdh5LZNsH5wdacqmR31K4CGn+FHfc0XovtmlLK2uZel0i/lsZhKX8IKvnj6nhQHk7ypA==","_npmVersion":"11.13.0","description":"SMS segment counting, quiet hours, pumping detection and a message credit ledger","directories":{},"_nodeVersion":"24.16.0","dependencies":{"@birtalanrobert/tenancy":"^1.1.0","@birtalanrobert/database":"^1.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"peerDependencies":{"typeorm":"^1.0.0","@nestjs/common":"^11.0.0"},"_npmOperationalInternal":{"tmp":"tmp/messaging_1.0.1_1788426623648_0.334700269605015","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@birtalanrobert/messaging","version":"1.1.0","license":"AGPL-3.0-only","_id":"@birtalanrobert/messaging@1.1.0","maintainers":[{"name":"birtalanrobert","email":"birtalanrobert@gmail.com"}],"dist":{"shasum":"cbaafe66f5bffc938594646448a2e2b27e4f26b5","tarball":"https://registry.npmjs.org/@birtalanrobert/messaging/-/messaging-1.1.0.tgz","fileCount":66,"integrity":"sha512-b+F/ilnVosfUFEJnmROm2Nyj+deWhfvamTN4H4fbz/Ij0KdEjPOF6O6S7ih/JS8CuBuHJ5viZbmackOXyrj5PA==","signatures":[{"sig":"MEQCIEyKa6Y/IjnC9p1ya4bVJB2dCHmnePnfR8jMZN3tqiRbAiASbFf4DnLYeCbjM+K7e+9WhMfqK6h+0WoZm8FZlrW1TQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":205789},"main":"./dist/index.js","type":"commonjs","_from":"file:birtalanrobert-messaging-1.1.0.tgz","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./nestjs":{"types":"./dist/nestjs/index.d.ts","default":"./dist/nestjs/index.js"},"./package.json":"./package.json"},"scripts":{"build":"tsc -p tsconfig.build.json","clean":"rm -rf dist *.tsbuildinfo","typecheck":"tsc -p tsconfig.json --noEmit"},"_npmUser":{"name":"birtalanrobert","email":"birtalanrobert@gmail.com"},"_resolved":"/private/var/folders/zx/7dcyg3mn6kjfyzsymzgpx1jr0000gn/T/a4d5326158c76ad9fe8686449725f2dc/birtalanrobert-messaging-1.1.0.tgz","_integrity":"sha512-b+F/ilnVosfUFEJnmROm2Nyj+deWhfvamTN4H4fbz/Ij0KdEjPOF6O6S7ih/JS8CuBuHJ5viZbmackOXyrj5PA==","_npmVersion":"11.13.0","description":"SMS segment counting, quiet hours, pumping detection and a message credit ledger","directories":{},"_nodeVersion":"24.16.0","dependencies":{"@birtalanrobert/tenancy":"^1.1.0","@birtalanrobert/database":"^1.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"peerDependencies":{"typeorm":"^1.0.0","@nestjs/common":"^11.0.0"},"_npmOperationalInternal":{"tmp":"tmp/messaging_1.1.0_1788599572148_0.8528459518579257","host":"s3://npm-registry-packages-npm-production"}},"1.2.0":{"name":"@birtalanrobert/messaging","version":"1.2.0","description":"SMS segment counting, quiet hours, pumping detection and a message credit ledger","license":"AGPL-3.0-only","repository":{"type":"git","url":"git+https://github.com/birtalanrobert/mortar.git","directory":"packages/messaging"},"publishConfig":{"access":"public"},"type":"commonjs","main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./nestjs":{"types":"./dist/nestjs/index.d.ts","default":"./dist/nestjs/index.js"},"./package.json":"./package.json"},"dependencies":{"@birtalanrobert/database":"^1.1.0","@birtalanrobert/tenancy":"^1.1.0"},"peerDependencies":{"@nestjs/common":"^11.0.0","typeorm":"^1.0.0"},"scripts":{"build":"tsc -p tsconfig.build.json","clean":"rm -rf dist *.tsbuildinfo","typecheck":"tsc -p tsconfig.json --noEmit"},"_id":"@birtalanrobert/messaging@1.2.0","bugs":{"url":"https://github.com/birtalanrobert/mortar/issues"},"homepage":"https://github.com/birtalanrobert/mortar#readme","_integrity":"sha512-jm2bvAvXhxertHVkCCy21DRqRwt3WrTjlRvrYb97GhWsSl5R0vsh/Bf9DKB61NKsMx7LJhfuRS2Ud7VNQdf64g==","_resolved":"/private/var/folders/zx/7dcyg3mn6kjfyzsymzgpx1jr0000gn/T/1f058c31b746bc00d5bf64dcb9a15c58/birtalanrobert-messaging-1.2.0.tgz","_from":"file:birtalanrobert-messaging-1.2.0.tgz","_nodeVersion":"24.16.0","_npmVersion":"11.13.0","dist":{"integrity":"sha512-jm2bvAvXhxertHVkCCy21DRqRwt3WrTjlRvrYb97GhWsSl5R0vsh/Bf9DKB61NKsMx7LJhfuRS2Ud7VNQdf64g==","shasum":"3422e1bc41a6c0b091cd25b67e5c5d847441d05f","tarball":"https://registry.npmjs.org/@birtalanrobert/messaging/-/messaging-1.2.0.tgz","fileCount":71,"unpackedSize":263632,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIEtUmMw7DTyTiZvgIDsPCiTNeC5Rq3uxuRXdtrbFWkfpAiEA545cqWasuXivMoZGPRenjGLMdVDtZUBDHYCWzK0UOp8="}]},"_npmUser":{"name":"birtalanrobert","email":"birtalanrobert@gmail.com"},"directories":{},"maintainers":[{"name":"birtalanrobert","email":"birtalanrobert@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/messaging_1.2.0_1789481275627_0.05418557118803169"},"_hasShrinkwrap":false}},"time":{"created":"2026-09-03T08:44:50.284Z","modified":"2026-09-15T14:07:55.914Z","1.0.0":"2026-09-03T08:44:50.583Z","1.0.1":"2026-09-03T09:10:23.788Z","1.1.0":"2026-09-05T09:12:52.287Z","1.2.0":"2026-09-15T14:07:55.782Z"},"license":"AGPL-3.0-only","description":"SMS segment counting, quiet hours, pumping detection and a message credit ledger","maintainers":[{"name":"birtalanrobert","email":"birtalanrobert@gmail.com"}],"readme":"# @birtalanrobert/messaging\n\nSMS segment counting, quiet hours, pumping detection and a message credit\nledger.\n\nFour things every product that sends text messages gets wrong in the same\norder: it prices a message the way a person counts characters rather than the\nway a provider counts segments; it sends at three in the morning; it pays for\ntraffic somebody manufactured; and it keeps a balance as a number nobody can\nexplain. None of that is specific to a domain, which is why it is here rather\nthan written a second time.\n\n## The pure half\n\n```ts\nimport { countSegments, isQuiet, nextAllowed, assessSmsRisk } from '@birtalanrobert/messaging';\n```\n\nNo database, no framework, no Node built-ins — a console counts segments on\nevery keystroke, and that has to work in a browser.\n\n### Segments, not characters\n\n```ts\ncountSegments('Comanda dumneavoastră este gata');\n// { encoding: 'unicode', segments: 1, remaining: 39, offenders: ['ă'] }\n```\n\nA provider charges per segment, and a single character outside the GSM 03.38\nalphabet changes the encoding for the **whole** message: 160 characters per\nsegment becomes 70. One `ș` in a Romanian sentence more than doubles what it\ncosts to send, which is why `offenders` names the characters responsible rather\nthan only reporting the encoding. \"Your ș and ț are doubling the cost\" is\nsomething a person can act on; \"this message is unicode\" is not.\n\n### Saying it in the cheap alphabet\n\n```ts\nimport { transliterateToGsm } from '@birtalanrobert/messaging';\n\ntransliterateToGsm('Cofetăria Mierla');\n// { text: 'Cofetaria Mierla', changed: true, untranslatable: [] }\n```\n\nNaming the offenders is not much use when they are in the shop's own name. One\n`ă` in `Cofetăria Mierla` moves every message that shop ever sends into UCS-2,\nand the shop cannot rename itself.\n\nTwo rules, and both matter. **Marks already in the GSM alphabet are left alone**\n— `é`, `ü`, `à`, `ñ` and `Ö` cost one place each, so \"strip every accent\" damages\na French or German name to save nothing. **Nothing is silently dropped**: Greek,\nCyrillic and ideographs have no faithful Latin equivalent, so they are kept and\nreported in `untranslatable`, and the product can say \"this still costs double\"\nrather than claim a saving it did not make.\n\nIt is not applied on anyone's behalf. A business's name is theirs — offer the\ntransliteration, show what it saves, and let them choose.\n\n### Quiet hours\n\n```ts\nisQuiet(new Date(), { from: 19, to: 9, timezone: 'Europe/Bucharest' });\nnextAllowed(new Date(), quietHours); // when it may go instead\n```\n\nThe zone is the **business's**, not the recipient's. A phone number says\nnothing about where somebody is sitting, and the business's zone is the one\nthey would have been telephoned from anyway — a client abroad gets a message at\nnine local rather than eight, which is a far smaller wrong than 03:00.\n\n### Pumping\n\n```ts\nassessSmsRisk({ phone, sentToCountryInLastHour, ... });\n```\n\nSMS pumping is fraud that costs money rather than data: a script requests\nverification messages to premium-rate ranges the attacker earns a share of. It\nis not detectable per message, only in the shape of recent traffic.\n\n## The ledger\n\n```ts\nimport {\n  MessageCreditsService,\n  messagingEntities,\n  messagingMigrations,\n} from '@birtalanrobert/messaging/nestjs';\n```\n\nRegister the entities and migrations with the database module.\n\n```ts\nawait credits.debit(tenantId, countSegments(body).segments, ticketId, 'Uncollected notice');\nawait credits.balance(tenantId); // { balance, entries }\n```\n\nA ledger rather than a counter: \"why has my balance gone down by four hundred\"\nis unanswerable against a number and obvious against a list of entries that each\nname what they were spent on. The balance is a sum over the entries rather than\na column, because a column and a list that disagree is a support conversation\nnobody can win. Entries are append-only in the database; a correction is a new\nentry with the opposite sign.\n\n`canAfford` asks a question rather than enforcing an answer. What to do when\nthere is not enough — hold, refuse, send anyway and invoice — is the owning\nproduct's decision, and this package should not make it on their behalf.\n\nThere is no foreign key from an entry to what it paid for: one product debits\nagainst a repair ticket and another against a document request, and a key to\neither is exactly what would stop the table being shared.\n","readmeFilename":"README.md","homepage":"https://github.com/birtalanrobert/mortar#readme","repository":{"type":"git","url":"git+https://github.com/birtalanrobert/mortar.git","directory":"packages/messaging"},"bugs":{"url":"https://github.com/birtalanrobert/mortar/issues"}}