@doubling/types
v0.7.0
Published
Shared Firestore document interfaces, MIME / path helpers, and CRDT constants consumed by web/, sync/, and functions/. Single source of truth so consumers cannot drift on a document shape or a content-hash encoding.
Readme
@doubling/types
Shared TypeScript interfaces and pure helpers consumed by web/, sync/, and functions/ (verified via each package's package.json dependency and import … from '@doubling/types' sites). Single source of truth for Firestore document shapes (file/folder entities, org membership, teams, user profiles), file-extension to MIME mappings, storage-path helpers, the file-body Y.Text key, and the content-hash helper.
functions/ installs a staged copy of this package as file:./vendor/types so Cloud Run's reinstall, which sees only the uploaded functions/ tree, can resolve it without a registry publish. Restage with npm run stage:types-for-functions after rebuilding types/. functions/ still keeps a local mirror of OrgMember (see functions/src/index.ts) pending a later migration of that shape.
Lives at the repo root as an npm workspace (@doubling/types). The compiled output is shipped at dist/, regenerated on npm install via the prepare script and rebuilt explicitly via npm run build.
Why this exists
Before this package, web/ and sync/ each kept their own copy of:
- File / folder document shapes (web as a TS interface, sync via JSDoc).
- The
EXT_TO_MIMEtable mapping extensions to MIME types. mimeTypeFromExt,isTextMimeType,extFromPath,storagePathFor.
The duplication was load-bearing for shipping (web and sync run in different environments and the byte-coercion helpers are necessarily environment-specific), but the static parts drifted. New text extensions that landed on one side and not the other caused the daemon to upload with application/octet-stream while the web's eager-load path skipped the file because the mime didn't match. Adding @doubling/types collapses both copies for the static parts. The content hash later joined them: three hand-rolled SHA-256 hex encodings could drift and stay green, so the helper lives here too. Env-specific byte coercion (toBytes, contentByteSize) stays in each consumer.
Layout
src/crdt-store.ts:UpdateRecord,Snapshot,UpdateStore,isAfterBaseline,isVisibleAfterFirestoreBaseline,firestoreBaselineInclusiveMillis,isReconnectFromCache,compactionPublishedCoveringBaseline(covering also requiresremainingRowsandrefusedFutureRowsto be absent or a finite zero),isTransientResyncError,snapshotReloadUnchanged, andupdateStoreConformance. The CRDT update-store contract plus the behavioural cases bothweb/andsync/run against their own backends. Contract comparison is the full record id. Firestore replay also keeps the rest of the baseline millisecond because auto-ids are random and a losing fold can leave a lower-id row from that millisecond in the log. It lives here because it is the only dependency-free part of the CRDT stack (types overUint8Arrayandstring, since a store treats updates as opaque bytes) and because a package with shared runtime deps cannot be shared at all under this repo's nested install strategy (DOU-379).src/crdt-text.ts:YTEXT_KEY, theY.Textname the file body lives in.src/content-hash.ts:contentBytesandcomputeContentHash(Web Crypto, async). Shared SHA-256 hex of the UTF-8 bytes.src/content-hash-node.ts: sync Node adapter, exported as@doubling/types/nodeso a browser bundle never pullsnode:crypto.src/firestore.ts:FileEntity,FolderEntity,Entity,OrgMembership,OrgMember,Team,TeamMember,UserProfile,Scope.src/mime.ts:EXT_TO_MIME,mimeTypeFromExt,isTextMimeType.src/paths.ts:extFromPath,storagePathFor.src/index.ts: barrel export. The Node hash adapter is not on the barrel.
Adding a new extension
Edit EXT_TO_MIME in src/mime.ts. That's it. Both web/ and sync/ pick up the new entry on the next tsc build. No need to touch any other file.
Build
npm run -w types buildEmits dist/index.js plus dist/*.d.ts. The dist/ directory is gitignored; CI runs the build automatically via the prepare script on npm install (prepack re-runs tsc before packaging). Only dist/ and README.md are shipped in the published tarball (see the files field in package.json).
Publish
Published to the public npm registry as @doubling/types. The .github/workflows/publish-types.yml workflow (Publish Types to npm) fires on every published GitHub Release, gated on the @doubling/types@ tag prefix so a sibling sync release does not trigger it. It runs npm ci, npm run build -w types, skips cleanly if types/package.json's version is already on npm, then npm publish --access public via npm Trusted Publishing (OIDC — no NPM_TOKEN). workflow_dispatch is exposed for bootstrap and transient-failure recovery. Version bumps are cut by Changesets (see CHANGELOG.md).
The package is published because @doubling/compound-sync is itself a published npm package that depends on @doubling/types at runtime, so the dependency must resolve from the registry outside the monorepo.
Docs
DESIGN.md— detailed design (exported symbols, path scheme, trade-offs).- Root
../DESIGN.md§5 — canonical data model. ../docs/features/sync/index.md— shared-schema bullet in the sync feature docs.
