npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@primocaredentgroup/customer-accounts

v0.5.6

Published

Convex component per la gestione clienti esterni del laboratorio — PrimoLabcore

Readme

@primocaredentgroup/customer-accounts

Componente Convex riusabile per l'anagrafica clienti del laboratorio: account, contatti, sedi, profili operativi, note, task, attività e richieste dal portale.

Prerequisiti

  • Node.js 20+
  • Convex ^1.43.0
  • accesso npm a @primocaredentgroup/migration-kit@^2.3.8, peer richiesto dal component source
  • un provider di autenticazione configurato nell'app host

Installazione

npm install @primocaredentgroup/customer-accounts

Registra il componente mantenendo l'handle canonico customerAccounts:

// convex/convex.config.ts
import { defineApp } from "convex/server";
import customerAccounts from "@primocaredentgroup/customer-accounts/convex.config.js";

const app = defineApp();
app.use(customerAccounts);
export default app;

Le capability di migrazione dichiarano componentName: "customerAccounts". Un alias di mount differente richiede supporto esplicito del migration kit.

Boundary host autenticato

Il browser non deve chiamare riferimenti components.customerAccounts e non deve fornire userId, authSubject, email o ruoli per autorizzarsi. L'host deriva l'identità da ctx.auth, applica il proprio RBAC e usa exposeApi come unico boundary pubblico per le operazioni CRM dello staff.

// convex/customerAccounts.ts
import {
  CustomerAccountsClient,
  exposeApi,
} from "@primocaredentgroup/customer-accounts";
import { components } from "./_generated/api.js";

export const customerAccountsClientForActor = (tokenIdentifier: string) =>
  new CustomerAccountsClient(components.customerAccounts, {
    callerTokenIdentifier: tokenIdentifier,
  });

export const {
  createAccount,
  updateAccount,
  getAccountByCustomerCode,
  paginateAccounts,
  addAccountContact,
  addAccountLocation,
  getAccountOverview,
  // ...esporta soltanto le funzioni richieste dall'app
} = exposeApi(components.customerAccounts, {
  getActorContext: async (ctx, permission) => {
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) throw new Error("Autenticazione richiesta");

    await authorizeCustomerAccountsPermission(identity, permission);
    return { tokenIdentifier: identity.tokenIdentifier };
  },
});

exposeApi espone intenzionalmente solo l'API CRM dello staff. Le operazioni portale restano nel client backend perché l'associazione utente → account è proprietà dell'host. Prima di chiamarle, l'host deve risolvere server-side l'accountId consentito e creare il client con il tokenIdentifier corrente.

const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new Error("Autenticazione richiesta");

const accountId = await resolvePortalAccountForIdentity(ctx, identity);
const client = customerAccountsClientForActor(identity.tokenIdentifier);

return await client.portal.createOrderRequest(ctx, {
  accountId,
  title: args.title,
});

Le letture di dettaglio portale usate dal client sono sempre scoped per accountId. I vecchi riferimenti raw non scoped restano disponibili soltanto per compatibilità con la 0.3 e sono deprecati.

Liste e paginazione

Le API legacy list* continuano a restituire array, ma sono bounded:

  • default 100 record;
  • massimo 200;
  • se esistono più record del limite richiesto, generano un errore esplicito che indica la corrispondente API paginate*.

Per sincronizzazioni, selettori completi e dataset non banali usa sempre le API paginate con paginationOpts Convex. Il numero di elementi per pagina deve essere compreso tra 1 e 200.

const firstPage = await client.accounts.paginate(ctx, {
  isActive: true,
  paginationOpts: { numItems: 100, cursor: null },
});

Le API paginate* usano cursori compatibili con il boundary dei componenti; trattali sempre come stringhe opache. searchAccountsPaginated usa un cursore dedicato legato ai filtri della ricerca e una finestra bounded di massimo 1.000 risultati. Per questa API gli advanced options endCursor, maximumRowsRead e maximumBytesRead non sono supportati.

Il filtro legacy tag richiede membership in un array e non può produrre una paginazione corretta con lo schema attuale. È supportato solo da listAccounts con scan bounded; non è accettato da paginateAccounts.

Per operazioni idempotenti non enumerare gli account: usa getAccountByCustomerCode. customerCode viene trim-mato in creazione e lookup, conserva il case ed è l'identità stabile e immutabile del master cliente.

Tabelle isolate

| Tabella | Responsabilità | | --- | --- | | lookupValues | Lookup configurabili | | accounts | Anagrafica master cliente | | accountContacts | Contatti dell'account | | accountLocations | Sedi dell'account | | accountOperationalProfiles | Profilo operativo 1:1 | | accountNotes | Note interne | | accountTasks | Task e follow-up | | accountActivities | Timeline audit | | portalClinicExternalLinks | Associazioni clinica esterna → account | | portalOrderRequests | Richieste del portale | | portalOrderAttachments | Allegati delle richieste |

API per dominio

Il client CustomerAccountsClient raggruppa le operazioni nei domini:

  • accounts: create/update/lifecycle/get/list/paginate/search;
  • contacts e locations: lifecycle, primario unico, list/paginate;
  • operationalProfiles: upsert 1:1 e get;
  • notes, tasks, activities: timeline operativa bounded/paginata;
  • lookups: lifecycle, lookup diretto, list/paginate;
  • overview: aggregato bounded dell'account;
  • portal: link cliniche, richieste e allegati con ownership verificata.

Tutti i validator degli argomenti, documenti, pagine e ritorni sono esportati da @primocaredentgroup/customer-accounts/validators e @primocaredentgroup/customer-accounts/returns.

I campi opzionali delle mutation update possono essere:

  • omessi per lasciarli invariati;
  • valorizzati per aggiornarli;
  • impostati a null per cancellarli, dove previsto dal validator.

Il client generico non permette di creare lookup con isSystem: true: quel flag rende il record non disattivabile e richiede una capability privilegiata progettata esplicitamente dall'host. I seed dimostrativi devono creare lookup normali.

Migration kit

Il pacchetto espone una row storica verificata, con contratto v2:

  • CustomerAccount.Identity, identità originale externalId (contratto v3).

Contatti, sedi e transazioni portale sono rinviati finché non esistono chiavi esterne stabili e un contratto esplicito per le relazioni possedute dall'host.

Con migration-kit 2.3.8 o successivo configura il modulo npm esplicitamente:

// migration-kit.config.ts
export default {
  componentGlobs: [],
  modules: ["@primocaredentgroup/customer-accounts/migration"],
  dispatcher: { visibility: "internal" },
};

La capability supporta preview e commit, con batch fino a 100 record. preview non scrive. La row dichiara reconcileStrategy: "reconcile": il replay richiede il mapping kit approvato, la provenienza coerente e originali invariati. Collisioni con account operativi o gruppi sorgente discordanti vengono rifiutati; non viene eseguito un upsert per codice. I valori originali assenti restano assenti. I mapping discordanti richiedono una riconciliazione esplicita: non vanno cancellati per forzare un reimport. Il nuovo input.externalId è la chiave sorgente stabile e deve coincidere col batch; customerCode è un attributo CRM opzionale distinto.

I lockfile del pacchetto e del workspace usano migration-kit 2.3.10. La verifica npm run check:exports, inclusa in verify, importa il client e i subpath di migrazione con Node nativo. convex-helpers resta fissato a 0.1.123, compatibile con Convex 1.44.0 usato dal pacchetto; versioni successive che richiedono un peer Convex più recente vanno aggiornate insieme al relativo contratto di compatibilità.

Upgrade dalla 0.3

Prima di installare la 0.4 su un deployment esistente esegui un preflight in sola lettura e blocca il rollout se trovi:

  • customerCode vuoti, con spazi esterni o duplicati esatti;
  • customerType vuoti o con spazi esterni (la 0.4 normalizza i nuovi valori e i filtri, ma non riscrive automaticamente le righe legacy);
  • duplicati (category, code) nei lookup;
  • più profili operativi per account;
  • più contatti o sedi primarie attive per account;
  • link portale duplicati per account/clinica;
  • richieste portale in bozza create con subject, email o user id invece del tokenIdentifier canonico: il confronto ownership della 0.4 è esatto, quindi completale prima dell'upgrade oppure esegui un backfill con una mapping identità verificata;
  • metadata attività non conformi al record JSON flat supportato: primitive o array di primitive, senza oggetti annidati.

La release aggiunge indici a tabelle esistenti. Sui deployment PrimoCare verificati il dataset è piccolo; per un'installazione con tabelle grandi applica il rollout staged prescritto da Convex: prima indici staged, backfill/verifica, poi indici attivi e codice che li interroga.

Non eseguire migrazioni o deploy di produzione come parte dell'installazione npm. Prima usa preview, conserva un export/backup, applica batch bounded e verifica mapping e conteggi prima/dopo. Un retry orchestrato di identità già mappate deve risultare skipped; il test di upsert con lo stesso ID riguarda la capability diretta, non sostituisce questa verifica dell'orchestratore.

Sviluppo del package

npm install
npm run verify
npm pack --dry-run

verify esegue build, test e typecheck usando i binding ufficiali tracciati nel repository. Dopo una modifica alla superficie Convex, il maintainer esegue separatamente npm run codegen, verifica il diff dei binding e solo dopo lancia verify. Il publish richiede il normale 2FA npm dell'organizzazione.

Commercial configuration (0.5.0)

client.commercial stores revisioned scope configurations and per-service rules for fastlab and centro_fresaggio. Scopes are account/company/clinic/doctor. The host must authenticate, authorize and validate every external scope/catalog reference; the component does not grant access to clinics or users. saveScope and saveRule require the revision last read (0 for creation), preventing lost updates. Rule and scope history is immutable and paginated. Empty allowlists and disabled scopes remain distinct from missing configuration. Company sharing is exclusive, even when no company rule exists; doctor rules overlay clinic rules by service.

Use @primocaredentgroup/customer-accounts/commercial for integer-cent quotes, quantity modes, phase selection, date-only validation and commission calculations. All catalog prices and phases must be loaded server-side. Explicit net zero means free; legacyFastlabNet alone interprets historical zero as absent discount. Model surcharge is added once per order. Agreed milling price is an order total; commission takes that total once and excludes separately stored shipping costs.

saveRule receives an authoritative clinical phase catalogue and a host-resolved canChangeSkipPolicies; never copy this capability from public request arguments. It requires delivery and rejects removal of protected phases without permission. Clinic configuration must not reuse internal production phase identifiers.

client.partnership manages partners, case-normalized unique referral codes, customer assignment and inclusive non-overlapping date periods. Snapshot the result of commissionSnapshot when creating an order; later changes must not rewrite old orders. Time boundaries use Europe/Rome. Mutations record actor, revision and immutable audit rows. Partner status does not itself authorize a portal member to view reports; that is a separate host permission.

This package does not own order status, uploads, patient records or production workflow. These are orchestrated by PrimoLabCore. No import or remote deployment is performed by installing the package.

Historical master migration (contract v3)

/migration exports CustomerAccount.Identity; /migration/validators exports its table-derived schema and typed batch/result contracts. Use CustomerAccountsClient.migration.loadCustomerAccountIdentity through an internal host dispatcher. The catalog requires the stable original externalId and legalName. customerCode, displayName, customerType, relationshipStatus, serviceProfiles, isActive, createdBy and updatedBy are optional originals. Missing values stay absent; false, empty arrays, whitespace and zero timestamps are preserved where valid. The CRM code is separate from the source identity. No synthetic actors, customer codes, service grants, state or activities are created. For compatibility, direct callers of the former code-based loader can still omit input.externalId when input.customerCode equals the batch source identity. New mappings use the v3 catalog; review existing mappings before switching contracts.

Incomplete imported accounts cannot operate services or be reactivated until the CRM code, display name, type, relationship status and service profiles are supplied. The regular update API can complete those fields without activation and can assign an absent CRM code after checking uniqueness. An assigned code stays immutable. Setting relationship status and reactivation remain separate explicit operations; original absent creation actors are not fabricated during completion.

A preexisting business code is never claimed or overwritten. Replay requires an approved existingConvexId mapping, matching provider provenance and identical original master fields; missing/changed optional values require reconciliation. Conflicting duplicate groups all fail before writes, independently of input order; identical duplicates share one insertion. Preview performs validation without writes. Unexpected write failures escape so the host transaction can roll back provider writes and migration identities together.

ownerUserId and listiniListId are rejected by the master input schema rather than dropped. Operational ownership/access and the master list connection are separate from historical preservation. The optional historicalOwnerEmail preserves the institutional/clinic-owner email exactly, including case, spaces, empty value or absence. It never populates ownerUserId, changes contact email, grants portal access or creates activities. It is accepted only by the historical loader; normal account create/update cannot set it. Replay rejects changed or omitted originals, including adding a value to a record previously imported without one. Commercial/portal children, lookups and storage bindings use the contracts below. No original actor is inferred to be a Core User. Releases use the existing push-to-main GitHub Action with automatic committed patch and public npm visibility; the private demo app is not published.

Historical customer children

The same module now declares CustomerContact.Identity, CustomerLocation.Identity, CustomerOperationalProfile.Identity, CustomerNote.Identity, CustomerTask.Identity and CustomerActivity.Identity. Each schema derives from its destination table and requires externalId (the original, qualified record PK) and accountExternalId (the approved CustomerAccount original source ID). Do not derive child keys from names, titles or parent pairs. The host must use the kit-resolved fields.accountExternalId.id; the provider verifies the actual account and its source provenance before writing. Deleted/inactive masters may retain their history.

All six loaders preserve original flags, text, actor strings, array order and optional externalCreationTime, including zero and absence. They never call operational CRUD, log new activities, complete tasks, reactivate accounts, grant portal access or emit notifications. A completed task requires its original completion time and actor. Conflicting active primary contacts/locations and multiple operational profiles stop; no primary is demoted and no profile is overwritten. Replay requires the approved physical ID and unchanged originals. Preview runs the same preflight without writes. A distinct source ID remains a distinct record even if its business content is equal.

Commercial and portal history

The module also declares identities for lookup values, portal clinic links, requests and attachments, commercial scopes/rules/revisions/sharing, partners, referral codes, referrals, commission periods and partner revisions: all 21 existing tables have provider loaders. This does not certify host adoption or a real import; the master historical owner email is supported; operational ownership and the master list connection remain separate.

Every new row retains its qualified source PK and original payload in migrationOriginal, resolves FK fields explicitly, and sets the technical migrationPendingActivation marker. Original enabled/isActive/status values remain unchanged. Ordinary APIs hide pending history or reject operations with CUSTOMER_HISTORY_ACTIVATION_REQUIRED; no activation endpoint is provided. Operational writes cannot overwrite pending records. Activation requires a separate, reviewed migration and must check then-current ownership and catalog compatibility.

  • scopeExternalId links a rule to CustomerCommercialScope.Identity.
  • partnerExternalId links referral codes/referrals/commission periods to CustomerPartner.Identity; referral account and partner account are separate roles.
  • orderExternalId links an attachment to CustomerPortalOrder.Identity and must match the account. Storage stays in the host; files are not uploaded by the loader.
  • Company.Identity, Clinic.Identity, Doctor.Identity are host-owned references.
  • CustomerListiniList, CustomerListiniRow, CustomerListiniService, CustomerListiniPhase identities must bind explicitly to existing catalog records.
  • CustomerPortalStorage.Identity must bind a verified host-storage file (hash, size, content type). The host validates physical targets and every approved kit mapping in the same mutation; the typed migration client requires a guard callback.

Use fields.<field>.id and ordered fields.phaseExternalIds.ids roles. Never resolve parents by names, emails or titles. Scope kinds and original scope keys must agree; source phase lists must exactly mirror the original terms, including order. Commercial amounts preserve zero; incompatible native terms stop rather than being recalculated. Referrals preserve the case of original codes; activating them would separately require review against the native case-normalizing operational API. Commission overlaps fail closed, including old long intervals; over 1000 candidate intervals requires review. Opaque partner revision JSON is retained literally; embedded snapshots are not live FKs.

The batch limit is 100. Preview performs the complete preflight without writes; exact replay requires an approved physical ID and identical originals. All conflicting source and business-key groups fail before writes. Provider writes participate in the host transaction with kit mappings. Source completeness, operational owner binding, consumer bindings and actual import rehearsal remain independent checks. Institutional/clinic-owner email is preserved in historicalOwnerEmail, not treated as an authenticated identity.