@tearleads/client-sdk 0.1.28 → 0.1.30

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.
Files changed (43) hide show
  1. package/client/Tearleads.d.ts +5 -0
  2. package/client/Tearleads.js +10 -2
  3. package/client/Tearleads.js.map +1 -1
  4. package/data/documents/blob/shared/blobRecordCrypto.js +2 -17
  5. package/data/documents/blob/shared/blobRecordCrypto.js.map +1 -1
  6. package/data/documents/blob/shared/chunkedBlobCrypto.js +2 -2
  7. package/data/documents/blob/shared/chunkedBlobCrypto.js.map +1 -1
  8. package/data/documents/blob/shared/crypto.js +1 -1
  9. package/data/documents/blob/shared/crypto.js.map +1 -1
  10. package/data/documents/blob/shared/types.d.ts +0 -1
  11. package/data/documents/blob/shared/types.js +0 -1
  12. package/data/documents/blob/shared/types.js.map +1 -1
  13. package/data/persistence/documents/internal/attachmentStagingPersistence.js +39 -3
  14. package/data/persistence/documents/internal/attachmentStagingPersistence.js.map +1 -1
  15. package/index.d.ts +1 -0
  16. package/index.js +4 -0
  17. package/index.js.map +1 -1
  18. package/internal/api-client/ApiClient.d.ts +2 -1
  19. package/internal/api-client/ApiClient.js +2 -2
  20. package/internal/api-client/ApiClient.js.map +1 -1
  21. package/internal/api-client/apiRequestMetrics.d.ts +38 -0
  22. package/internal/api-client/apiRequestMetrics.js +97 -0
  23. package/internal/api-client/apiRequestMetrics.js.map +1 -0
  24. package/internal/api-client/apiRequestRuntime.d.ts +3 -1
  25. package/internal/api-client/apiRequestRuntime.js +9 -7
  26. package/internal/api-client/apiRequestRuntime.js.map +1 -1
  27. package/internal/api-client/index.d.ts +1 -0
  28. package/internal/api-client/index.js +1 -0
  29. package/internal/api-client/index.js.map +1 -1
  30. package/internal/api-client/measureApiRequest.js +31 -0
  31. package/internal/api-client/measureApiRequest.js.map +1 -0
  32. package/internal/crypto/blobContentMetadata.d.ts +7 -0
  33. package/internal/crypto/blobContentMetadata.js +21 -0
  34. package/internal/crypto/blobContentMetadata.js.map +1 -0
  35. package/internal/crypto/index.d.ts +1 -0
  36. package/internal/crypto/index.js +1 -0
  37. package/internal/crypto/index.js.map +1 -1
  38. package/package.json +1 -1
  39. package/stores/documents/documentStore/attachmentPersistence.js.map +1 -1
  40. package/stores/documents/documentStore/attachments.js +13 -2
  41. package/stores/documents/documentStore/attachments.js.map +1 -1
  42. package/workflows/blobs/decrypt.js +2 -2
  43. package/workflows/blobs/decrypt.js.map +1 -1
@@ -1,3 +1,4 @@
1
+ import { ApiRequestMetrics } from "../internal/api-client/index.js";
1
2
  import type { BlobStore } from "../data/blobContracts.js";
2
3
  import { type DocumentProjectorRegistryInput } from "../data/documents/documentKinds.js";
3
4
  import { type DomainScope } from "../data/domainScope.js";
@@ -23,6 +24,8 @@ import { type Runtime } from "./workflowRuntime.js";
23
24
  export type ClientDatabaseOptions = Omit<DatabaseOptions, "status">;
24
25
  export interface ClientOptions {
25
26
  apiBaseUrl?: string | undefined;
27
+ /** Reuse a memory-only request counter across client lifetimes. */
28
+ apiRequestMetrics?: ApiRequestMetrics | undefined;
26
29
  blobStore?: BlobStore | undefined;
27
30
  blobStoreFactory?: BlobStoreFactory | undefined;
28
31
  database?: ClientDatabaseOptions | undefined;
@@ -58,6 +61,7 @@ export interface ClientOptions {
58
61
  }
59
62
  export declare class Tearleads {
60
63
  readonly apiVersion: ApiVersion;
64
+ readonly apiRequests: ApiRequestMetrics;
61
65
  readonly blobs: Blobs;
62
66
  readonly database: Database;
63
67
  readonly deviceFirst: DeviceFirst;
@@ -87,6 +91,7 @@ export declare class Tearleads {
87
91
  private pinLocalUserIdentity;
88
92
  private boundUserId;
89
93
  constructor(options?: ClientOptions);
94
+ private createApiClient;
90
95
  /** Rebinds the session's trust callbacks once the runtime exists. */
91
96
  private bindTrustedIdentityRuntime;
92
97
  private configureApiClientCallbacks;
@@ -1,4 +1,4 @@
1
- import { ApiClient } from "../internal/api-client/index.js";
1
+ import { ApiClient, ApiRequestMetrics } from "../internal/api-client/index.js";
2
2
  import { resolveDocumentProjectorRegistry, } from "../data/documents/documentKinds.js";
3
3
  import { createDomainScope } from "../data/domainScope.js";
4
4
  import { disposeDomainSyncCoordinator } from "../data/sync/syncCoordinator.js";
@@ -37,6 +37,7 @@ function createClientSecurityIncidentService(input) {
37
37
  }
38
38
  export class Tearleads {
39
39
  apiVersion = new ApiVersion();
40
+ apiRequests;
40
41
  blobs;
41
42
  database;
42
43
  deviceFirst;
@@ -74,7 +75,8 @@ export class Tearleads {
74
75
  };
75
76
  constructor(options = {}) {
76
77
  const apiBaseUrl = options.apiBaseUrl ?? "";
77
- this.apiClient = new ApiClient(apiBaseUrl);
78
+ this.apiRequests = options.apiRequestMetrics ?? new ApiRequestMetrics();
79
+ this.apiClient = this.createApiClient(apiBaseUrl);
78
80
  this.blobs = new Blobs(options.blobStore, options.blobStoreFactory);
79
81
  this.database = new Database(options.database);
80
82
  this.documentProjectors = resolveDocumentProjectorRegistry(options.documentProjectors);
@@ -137,6 +139,12 @@ export class Tearleads {
137
139
  this.startAutomaticIdentityProvisioning();
138
140
  }
139
141
  }
142
+ createApiClient(apiBaseUrl) {
143
+ return new ApiClient(apiBaseUrl, {
144
+ metrics: this.apiRequests,
145
+ getIdentity: () => this.identity.signingFingerprint,
146
+ });
147
+ }
140
148
  /** Rebinds the session's trust callbacks once the runtime exists. */
141
149
  bindTrustedIdentityRuntime(runtime) {
142
150
  this.retirePrincipalHistoryProtection =
@@ -1 +1 @@
1
- {"version":3,"file":"Tearleads.js","sourceRoot":"","sources":["Tearleads.ts"],"names":[],"mappings":"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","sourcesContent":["import { ApiClient } from \"@tearleads/api-client\";\nimport type { BlobStore } from \"../data/blobContracts\";\nimport {\n type DocumentProjectorRegistry,\n type DocumentProjectorRegistryInput,\n resolveDocumentProjectorRegistry,\n} from \"../data/documents/documentKinds\";\nimport { createDomainScope, type DomainScope } from \"../data/domainScope\";\nimport type { SecurityIncidentReporter } from \"../data/securityIncidents\";\nimport { disposeDomainSyncCoordinator } from \"../data/sync/syncCoordinator\";\nimport { resolveIdentityTrustDomain } from \"../data/trustedUserIdentity\";\nimport type { ProvisionedSystemContainerSpec } from \"../workflows/registration\";\nimport { ApiVersion } from \"./apiVersion\";\nimport { type BlobStoreFactory, Blobs } from \"./blobs\";\nimport {\n type ContainerContents,\n createContainerContents,\n} from \"./containerContents\";\nimport { Database, type DatabaseOptions } from \"./database\";\nimport { createDeviceFirst, type DeviceFirst } from \"./deviceFirst\";\nimport { createDocuments, type Documents } from \"./documents\";\nimport { Events } from \"./events\";\nimport {\n createIdentity,\n type Identity,\n type IdentityOptions,\n} from \"./identity\";\nimport { type Logger, logErrorToConsole } from \"./logger\";\nimport { Network } from \"./network\";\nimport { createOrganizations, type Organizations } from \"./organizations\";\nimport type { PrincipalHistoryKeyProvider } from \"./principalHistoryProtection\";\nimport { createRoot, type Root, rootRuntimeOf } from \"./root\";\nimport {\n createSecurityIncidentService,\n type SecurityIncidentListener,\n type SecurityIncidents,\n} from \"./securityIncidents\";\nimport { createSession } from \"./session\";\nimport type { Session } from \"./session/sessionTypes\";\nimport { SyncBillingGate } from \"./syncBillingGate\";\nimport { createUserIdentities, type UserIdentities } from \"./userIdentities\";\nimport {\n createRuntime,\n type InternalRuntime,\n type Runtime,\n} from \"./workflowRuntime\";\n\nfunction createClientSecurityIncidentService(input: {\n readonly apiBaseUrl: string;\n readonly database: Database;\n readonly logError: (message: string | Error, cause?: unknown) => void;\n readonly options: ClientOptions;\n}) {\n const identityTrustDomain = resolveIdentityTrustDomain({\n apiBaseUrl: input.apiBaseUrl,\n identityTrustDomain: input.options.identityTrustDomain,\n });\n return {\n identityTrustDomain,\n service: createSecurityIncidentService({\n database: input.database,\n logError: input.logError,\n onIncident: input.options.onSecurityIncident,\n trustDomain: identityTrustDomain,\n }),\n };\n}\n\nexport type ClientDatabaseOptions = Omit<DatabaseOptions, \"status\">;\n\nexport interface ClientOptions {\n apiBaseUrl?: string | undefined;\n blobStore?: BlobStore | undefined;\n blobStoreFactory?: BlobStoreFactory | undefined;\n database?: ClientDatabaseOptions | undefined;\n documentProjectors?: DocumentProjectorRegistryInput | undefined;\n events?: ReadonlyArray<unknown> | undefined;\n identity?: IdentityOptions | undefined;\n /**\n * Absolute, host-controlled API namespace for durable user-identity pins.\n * Required only when apiBaseUrl is relative and no browser origin exists.\n */\n identityTrustDomain?: string | undefined;\n identityProvisioning?: \"auto\" | \"manual\" | undefined;\n logger?: Logger | undefined;\n online?: boolean | undefined;\n /** Called after a trust-boundary verification failure is durably recorded. */\n onSecurityIncident?: SecurityIncidentListener | undefined;\n /**\n * Per-pane discriminator so documents in distinct panes derive distinct Loro\n * peer ids (e.g. the pane's local identity namespace). Omit for single-pane.\n */\n peerScope?: string | undefined;\n /** Private host key for durable history progress; omitted uses disposable session keys. */\n principalHistoryKeyProvider?: PrincipalHistoryKeyProvider | undefined;\n /** Compound policy dispatch/recovery deadline in milliseconds; defaults to 60 seconds. */\n principalMutationTimeoutMs?: number | undefined;\n /**\n * App-owned system containers born with every new organization (both\n * registration and additional-org creation provision them atomically in the\n * same server call), e.g. a trash bin. The app owns these because their slot\n * namespaces are app concepts the SDK stays agnostic to.\n */\n provisionedSystemContainers?:\n | ReadonlyArray<ProvisionedSystemContainerSpec>\n | undefined;\n}\n\nexport class Tearleads {\n readonly apiVersion = new ApiVersion();\n readonly blobs: Blobs;\n readonly database: Database;\n readonly deviceFirst: DeviceFirst;\n readonly documents: Documents;\n readonly events: Events;\n readonly containerContents: ContainerContents;\n readonly identity: Identity;\n readonly network: Network;\n readonly organizations: Organizations;\n readonly root: Root;\n readonly runtime: Runtime;\n readonly securityIncidents: SecurityIncidents;\n readonly session: Session;\n readonly syncBillingGate: SyncBillingGate;\n readonly userIdentities: UserIdentities;\n\n private readonly apiClient: ApiClient;\n private readonly documentProjectors: DocumentProjectorRegistry;\n private readonly disposeSecurityIncidents: () => void;\n private retirePrincipalHistoryProtection = () => {};\n // Tracks the storage/identity pair that owns domain-scoped runtime state.\n private domainScopeKey: string | null = null;\n // Recreated when the storage database or signing identity changes.\n private domainScopeValue: DomainScope = createDomainScope();\n private readonly logErrorHandler: (\n message: string | Error,\n cause?: unknown,\n ) => void;\n private readonly logHandler: (message: string) => void;\n private autoIdentityProvisioned = false;\n private autoIdentityProvisioningPromise: Promise<void> | null = null;\n private expiredSessionLoginPromise: Promise<boolean> | null = null;\n // Rebound to the workflow runtime once it exists; the session is created first.\n private pinLocalUserIdentity: InternalRuntime[\"pinLocalUserIdentity\"] =\n async () => {\n throw new Error(\"Trusted user identity runtime is not initialized\");\n };\n private boundUserId: InternalRuntime[\"boundUserId\"] = async () => {\n throw new Error(\"Trusted user identity runtime is not initialized\");\n };\n\n constructor(options: ClientOptions = {}) {\n const apiBaseUrl = options.apiBaseUrl ?? \"\";\n this.apiClient = new ApiClient(apiBaseUrl);\n this.blobs = new Blobs(options.blobStore, options.blobStoreFactory);\n this.database = new Database(options.database);\n this.documentProjectors = resolveDocumentProjectorRegistry(\n options.documentProjectors,\n );\n this.events = new Events(options.events, () =>\n this.apiClient.clearWriterProjectionCaches(),\n );\n this.logHandler = options.logger?.log ?? (() => undefined);\n this.logErrorHandler = options.logger?.logError ?? logErrorToConsole;\n const security = createClientSecurityIncidentService({\n apiBaseUrl,\n database: this.database,\n logError: this.logError,\n options,\n });\n this.securityIncidents = security.service.incidents;\n this.disposeSecurityIncidents = security.service.dispose;\n this.network = new Network(options.online);\n this.syncBillingGate = new SyncBillingGate();\n let session: Session | null = null;\n this.identity = createIdentity(\n options.identity,\n (signingFingerprint) =>\n this.blobs.updateIdentityNamespace(signingFingerprint),\n this.log,\n {\n bootstrapLocalRootContainer: async () => {\n if (this.database.status !== \"ready\") {\n throw new Error(\n \"identity.generate requires a ready SQLite database.\",\n );\n }\n if (!session) {\n throw new Error(\n \"identity.generate requires an initialized session.\",\n );\n }\n\n return session.bootstrapLocalRootContainer();\n },\n getUserId: () => session?.userId ?? null,\n },\n );\n session = createSession({\n api: this.apiClient,\n database: this.database,\n documentProjectors: this.documentProjectors,\n identity: this.identity,\n log: this.log,\n logError: this.logError,\n boundUserIdForSigningKey: (signingKeyFingerprint) =>\n this.boundUserId(signingKeyFingerprint),\n onUserIdentityAvailable: (userId, candidate) =>\n this.pinLocalUserIdentity(userId, candidate),\n provisionedSystemContainers: options.provisionedSystemContainers,\n reportSecurityIncident: security.service.report,\n });\n this.session = session;\n const runtime = this.createWorkflowRuntime(\n security.identityTrustDomain,\n options,\n security.service.report,\n );\n this.bindTrustedIdentityRuntime(runtime);\n this.runtime = runtime.publicRuntime;\n this.documents = createDocuments({\n getDefaultContainerId: () => this.session.containerId,\n runtime,\n });\n this.containerContents = createContainerContents(runtime);\n this.deviceFirst = createDeviceFirst(runtime, this.containerContents);\n this.organizations = createOrganizations(runtime, this.containerContents);\n this.root = createRoot(rootRuntimeOf(runtime, this.session));\n this.userIdentities = createUserIdentities({\n log: this.log,\n resolveTrustedUserIdentity: (userId) =>\n runtime.workflowInput().resolveTrustedUserIdentity(userId),\n });\n\n this.configureApiClientCallbacks();\n\n if (options.identityProvisioning === \"auto\") {\n this.startAutomaticIdentityProvisioning();\n }\n }\n\n /** Rebinds the session's trust callbacks once the runtime exists. */\n private bindTrustedIdentityRuntime(runtime: InternalRuntime): void {\n this.retirePrincipalHistoryProtection =\n runtime.retirePrincipalHistoryProtection;\n this.pinLocalUserIdentity = (userId, candidate) =>\n runtime.pinLocalUserIdentity(userId, candidate);\n this.boundUserId = (signingKeyFingerprint) =>\n runtime.boundUserId(signingKeyFingerprint);\n }\n\n private configureApiClientCallbacks(): void {\n this.apiClient.setOnError((message) => this.logError(message));\n // An authoritative host source owns connectivity. Without one, request\n // outcomes remain reachability hints. Browser and native app bindings both\n // install authoritative sources so a backend outage cannot stop retries.\n this.apiClient.setOnNetworkError(() =>\n this.network.reportReachability(false),\n );\n this.apiClient.setOnNetworkSuccess(() =>\n this.network.reportReachability(true),\n );\n this.apiClient.setOnApiVersion((version) =>\n this.apiVersion.observe(version),\n );\n this.apiClient.setOnSessionExpired(() => this.loginAfterSessionExpired());\n this.apiClient.setOnPaymentRequired((organizationId) =>\n this.syncBillingGate.notifyPaymentRequired(organizationId),\n );\n }\n\n private createWorkflowRuntime(\n identityTrustDomain: string | null,\n options: ClientOptions,\n reportSecurityIncident: SecurityIncidentReporter,\n ): InternalRuntime {\n return createRuntime({\n api: this.apiClient,\n blobs: this.blobs,\n database: this.database,\n documentProjectors: this.documentProjectors,\n events: this.events,\n getDomainScope: () => this.domainScope,\n identity: this.identity,\n identityTrustDomain,\n log: this.log,\n logError: this.logError,\n network: this.network,\n peerScope: options.peerScope ?? null,\n principalHistoryKeyProvider: options.principalHistoryKeyProvider,\n principalMutationTimeoutMs: options.principalMutationTimeoutMs,\n reportSecurityIncident,\n session: this.session,\n syncBillingGate: this.syncBillingGate,\n });\n }\n\n log = (message: string): void => {\n this.logHandler(message);\n };\n\n // Preserve the host's return value so best-effort callers can also catch\n // rejected promises from async loggers accepted by the void callback type.\n logError = (message: string | Error, cause?: unknown): void =>\n this.logErrorHandler(message, cause);\n\n /**\n * Mint a single-use ticket to authenticate the server-events websocket\n * handshake (which cannot carry the bearer header). Returns null when no\n * session is active. The caller appends it to the websocket URL.\n */\n requestWebSocketTicket(): Promise<string | null> {\n return this.apiClient.requestWebSocketTicket();\n }\n\n get domainScope(): DomainScope {\n const nextScopeKey = JSON.stringify([\n this.database.id,\n this.identity.signingFingerprint,\n ]);\n if (this.domainScopeKey !== nextScopeKey) {\n this.domainScopeKey = nextScopeKey;\n this.domainScopeValue = createDomainScope();\n }\n\n return this.domainScopeValue;\n }\n\n /**\n * Tear down background work for the active domain scope: stop the reconciler\n * and force-stop the sync coordinator pump, then drop the coordinator from\n * its registry. Hosts call this from unmount/cleanup so a pump cannot outlive\n * the React tree that created it. A later access for the same scope rebuilds\n * fresh state.\n */\n dispose(): void {\n const domainScope = this.domainScope;\n this.disposeSecurityIncidents();\n this.retirePrincipalHistoryProtection();\n this.deviceFirst.dispose();\n disposeDomainSyncCoordinator(domainScope);\n }\n\n private loginAfterSessionExpired(): Promise<boolean> {\n if (!this.session.authToken || !this.identity.signingKeyPair) {\n this.session.logout();\n return Promise.resolve(false);\n }\n\n // The API client replays requests the expired session sent, so a login is\n // their renewal only when the loaded keys authenticate that known user.\n const expiredUserId = this.session.userId;\n this.expiredSessionLoginPromise ??= this.session\n .login()\n .then(\n (renewed) =>\n renewed &&\n expiredUserId !== null &&\n this.session.userId === expiredUserId,\n )\n .finally(() => {\n this.expiredSessionLoginPromise = null;\n });\n return this.expiredSessionLoginPromise;\n }\n\n private startAutomaticIdentityProvisioning(): void {\n let unsubscribe: (() => void) | null = null;\n const stopProvisioningListener = () => {\n unsubscribe?.();\n unsubscribe = null;\n };\n const maybeProvisionIdentity = () => {\n if (this.autoIdentityProvisioned || this.identity.signingKeyPair) {\n stopProvisioningListener();\n return;\n }\n\n if (\n this.autoIdentityProvisioningPromise ||\n this.database.status !== \"ready\"\n ) {\n return;\n }\n\n this.autoIdentityProvisioningPromise = this.identity\n .generate()\n .then(() => {\n this.autoIdentityProvisioned = true;\n stopProvisioningListener();\n })\n .catch((error: unknown) => {\n try {\n this.logError(\"Automatic identity provisioning failed\", error);\n } catch {\n // Keep background provisioning failures from surfacing as unhandled rejections.\n }\n })\n .finally(() => {\n this.autoIdentityProvisioningPromise = null;\n });\n };\n\n unsubscribe = this.database.subscribe(maybeProvisionIdentity);\n void Promise.resolve().then(maybeProvisionIdentity);\n }\n}\n"]}
1
+ {"version":3,"file":"Tearleads.js","sourceRoot":"","sources":["Tearleads.ts"],"names":[],"mappings":"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","sourcesContent":["import { ApiClient, ApiRequestMetrics } from \"@tearleads/api-client\";\nimport type { BlobStore } from \"../data/blobContracts\";\nimport {\n type DocumentProjectorRegistry,\n type DocumentProjectorRegistryInput,\n resolveDocumentProjectorRegistry,\n} from \"../data/documents/documentKinds\";\nimport { createDomainScope, type DomainScope } from \"../data/domainScope\";\nimport type { SecurityIncidentReporter } from \"../data/securityIncidents\";\nimport { disposeDomainSyncCoordinator } from \"../data/sync/syncCoordinator\";\nimport { resolveIdentityTrustDomain } from \"../data/trustedUserIdentity\";\nimport type { ProvisionedSystemContainerSpec } from \"../workflows/registration\";\nimport { ApiVersion } from \"./apiVersion\";\nimport { type BlobStoreFactory, Blobs } from \"./blobs\";\nimport {\n type ContainerContents,\n createContainerContents,\n} from \"./containerContents\";\nimport { Database, type DatabaseOptions } from \"./database\";\nimport { createDeviceFirst, type DeviceFirst } from \"./deviceFirst\";\nimport { createDocuments, type Documents } from \"./documents\";\nimport { Events } from \"./events\";\nimport {\n createIdentity,\n type Identity,\n type IdentityOptions,\n} from \"./identity\";\nimport { type Logger, logErrorToConsole } from \"./logger\";\nimport { Network } from \"./network\";\nimport { createOrganizations, type Organizations } from \"./organizations\";\nimport type { PrincipalHistoryKeyProvider } from \"./principalHistoryProtection\";\nimport { createRoot, type Root, rootRuntimeOf } from \"./root\";\nimport {\n createSecurityIncidentService,\n type SecurityIncidentListener,\n type SecurityIncidents,\n} from \"./securityIncidents\";\nimport { createSession } from \"./session\";\nimport type { Session } from \"./session/sessionTypes\";\nimport { SyncBillingGate } from \"./syncBillingGate\";\nimport { createUserIdentities, type UserIdentities } from \"./userIdentities\";\nimport {\n createRuntime,\n type InternalRuntime,\n type Runtime,\n} from \"./workflowRuntime\";\n\nfunction createClientSecurityIncidentService(input: {\n readonly apiBaseUrl: string;\n readonly database: Database;\n readonly logError: (message: string | Error, cause?: unknown) => void;\n readonly options: ClientOptions;\n}) {\n const identityTrustDomain = resolveIdentityTrustDomain({\n apiBaseUrl: input.apiBaseUrl,\n identityTrustDomain: input.options.identityTrustDomain,\n });\n return {\n identityTrustDomain,\n service: createSecurityIncidentService({\n database: input.database,\n logError: input.logError,\n onIncident: input.options.onSecurityIncident,\n trustDomain: identityTrustDomain,\n }),\n };\n}\n\nexport type ClientDatabaseOptions = Omit<DatabaseOptions, \"status\">;\n\nexport interface ClientOptions {\n apiBaseUrl?: string | undefined;\n /** Reuse a memory-only request counter across client lifetimes. */\n apiRequestMetrics?: ApiRequestMetrics | undefined;\n blobStore?: BlobStore | undefined;\n blobStoreFactory?: BlobStoreFactory | undefined;\n database?: ClientDatabaseOptions | undefined;\n documentProjectors?: DocumentProjectorRegistryInput | undefined;\n events?: ReadonlyArray<unknown> | undefined;\n identity?: IdentityOptions | undefined;\n /**\n * Absolute, host-controlled API namespace for durable user-identity pins.\n * Required only when apiBaseUrl is relative and no browser origin exists.\n */\n identityTrustDomain?: string | undefined;\n identityProvisioning?: \"auto\" | \"manual\" | undefined;\n logger?: Logger | undefined;\n online?: boolean | undefined;\n /** Called after a trust-boundary verification failure is durably recorded. */\n onSecurityIncident?: SecurityIncidentListener | undefined;\n /**\n * Per-pane discriminator so documents in distinct panes derive distinct Loro\n * peer ids (e.g. the pane's local identity namespace). Omit for single-pane.\n */\n peerScope?: string | undefined;\n /** Private host key for durable history progress; omitted uses disposable session keys. */\n principalHistoryKeyProvider?: PrincipalHistoryKeyProvider | undefined;\n /** Compound policy dispatch/recovery deadline in milliseconds; defaults to 60 seconds. */\n principalMutationTimeoutMs?: number | undefined;\n /**\n * App-owned system containers born with every new organization (both\n * registration and additional-org creation provision them atomically in the\n * same server call), e.g. a trash bin. The app owns these because their slot\n * namespaces are app concepts the SDK stays agnostic to.\n */\n provisionedSystemContainers?:\n | ReadonlyArray<ProvisionedSystemContainerSpec>\n | undefined;\n}\n\nexport class Tearleads {\n readonly apiVersion = new ApiVersion();\n readonly apiRequests: ApiRequestMetrics;\n readonly blobs: Blobs;\n readonly database: Database;\n readonly deviceFirst: DeviceFirst;\n readonly documents: Documents;\n readonly events: Events;\n readonly containerContents: ContainerContents;\n readonly identity: Identity;\n readonly network: Network;\n readonly organizations: Organizations;\n readonly root: Root;\n readonly runtime: Runtime;\n readonly securityIncidents: SecurityIncidents;\n readonly session: Session;\n readonly syncBillingGate: SyncBillingGate;\n readonly userIdentities: UserIdentities;\n\n private readonly apiClient: ApiClient;\n private readonly documentProjectors: DocumentProjectorRegistry;\n private readonly disposeSecurityIncidents: () => void;\n private retirePrincipalHistoryProtection = () => {};\n // Tracks the storage/identity pair that owns domain-scoped runtime state.\n private domainScopeKey: string | null = null;\n // Recreated when the storage database or signing identity changes.\n private domainScopeValue: DomainScope = createDomainScope();\n private readonly logErrorHandler: (\n message: string | Error,\n cause?: unknown,\n ) => void;\n private readonly logHandler: (message: string) => void;\n private autoIdentityProvisioned = false;\n private autoIdentityProvisioningPromise: Promise<void> | null = null;\n private expiredSessionLoginPromise: Promise<boolean> | null = null;\n // Rebound to the workflow runtime once it exists; the session is created first.\n private pinLocalUserIdentity: InternalRuntime[\"pinLocalUserIdentity\"] =\n async () => {\n throw new Error(\"Trusted user identity runtime is not initialized\");\n };\n private boundUserId: InternalRuntime[\"boundUserId\"] = async () => {\n throw new Error(\"Trusted user identity runtime is not initialized\");\n };\n\n constructor(options: ClientOptions = {}) {\n const apiBaseUrl = options.apiBaseUrl ?? \"\";\n this.apiRequests = options.apiRequestMetrics ?? new ApiRequestMetrics();\n this.apiClient = this.createApiClient(apiBaseUrl);\n this.blobs = new Blobs(options.blobStore, options.blobStoreFactory);\n this.database = new Database(options.database);\n this.documentProjectors = resolveDocumentProjectorRegistry(\n options.documentProjectors,\n );\n this.events = new Events(options.events, () =>\n this.apiClient.clearWriterProjectionCaches(),\n );\n this.logHandler = options.logger?.log ?? (() => undefined);\n this.logErrorHandler = options.logger?.logError ?? logErrorToConsole;\n const security = createClientSecurityIncidentService({\n apiBaseUrl,\n database: this.database,\n logError: this.logError,\n options,\n });\n this.securityIncidents = security.service.incidents;\n this.disposeSecurityIncidents = security.service.dispose;\n this.network = new Network(options.online);\n this.syncBillingGate = new SyncBillingGate();\n let session: Session | null = null;\n this.identity = createIdentity(\n options.identity,\n (signingFingerprint) =>\n this.blobs.updateIdentityNamespace(signingFingerprint),\n this.log,\n {\n bootstrapLocalRootContainer: async () => {\n if (this.database.status !== \"ready\") {\n throw new Error(\n \"identity.generate requires a ready SQLite database.\",\n );\n }\n if (!session) {\n throw new Error(\n \"identity.generate requires an initialized session.\",\n );\n }\n\n return session.bootstrapLocalRootContainer();\n },\n getUserId: () => session?.userId ?? null,\n },\n );\n session = createSession({\n api: this.apiClient,\n database: this.database,\n documentProjectors: this.documentProjectors,\n identity: this.identity,\n log: this.log,\n logError: this.logError,\n boundUserIdForSigningKey: (signingKeyFingerprint) =>\n this.boundUserId(signingKeyFingerprint),\n onUserIdentityAvailable: (userId, candidate) =>\n this.pinLocalUserIdentity(userId, candidate),\n provisionedSystemContainers: options.provisionedSystemContainers,\n reportSecurityIncident: security.service.report,\n });\n this.session = session;\n const runtime = this.createWorkflowRuntime(\n security.identityTrustDomain,\n options,\n security.service.report,\n );\n this.bindTrustedIdentityRuntime(runtime);\n this.runtime = runtime.publicRuntime;\n this.documents = createDocuments({\n getDefaultContainerId: () => this.session.containerId,\n runtime,\n });\n this.containerContents = createContainerContents(runtime);\n this.deviceFirst = createDeviceFirst(runtime, this.containerContents);\n this.organizations = createOrganizations(runtime, this.containerContents);\n this.root = createRoot(rootRuntimeOf(runtime, this.session));\n this.userIdentities = createUserIdentities({\n log: this.log,\n resolveTrustedUserIdentity: (userId) =>\n runtime.workflowInput().resolveTrustedUserIdentity(userId),\n });\n\n this.configureApiClientCallbacks();\n\n if (options.identityProvisioning === \"auto\") {\n this.startAutomaticIdentityProvisioning();\n }\n }\n\n private createApiClient(apiBaseUrl: string): ApiClient {\n return new ApiClient(apiBaseUrl, {\n metrics: this.apiRequests,\n getIdentity: () => this.identity.signingFingerprint,\n });\n }\n\n /** Rebinds the session's trust callbacks once the runtime exists. */\n private bindTrustedIdentityRuntime(runtime: InternalRuntime): void {\n this.retirePrincipalHistoryProtection =\n runtime.retirePrincipalHistoryProtection;\n this.pinLocalUserIdentity = (userId, candidate) =>\n runtime.pinLocalUserIdentity(userId, candidate);\n this.boundUserId = (signingKeyFingerprint) =>\n runtime.boundUserId(signingKeyFingerprint);\n }\n\n private configureApiClientCallbacks(): void {\n this.apiClient.setOnError((message) => this.logError(message));\n // An authoritative host source owns connectivity. Without one, request\n // outcomes remain reachability hints. Browser and native app bindings both\n // install authoritative sources so a backend outage cannot stop retries.\n this.apiClient.setOnNetworkError(() =>\n this.network.reportReachability(false),\n );\n this.apiClient.setOnNetworkSuccess(() =>\n this.network.reportReachability(true),\n );\n this.apiClient.setOnApiVersion((version) =>\n this.apiVersion.observe(version),\n );\n this.apiClient.setOnSessionExpired(() => this.loginAfterSessionExpired());\n this.apiClient.setOnPaymentRequired((organizationId) =>\n this.syncBillingGate.notifyPaymentRequired(organizationId),\n );\n }\n\n private createWorkflowRuntime(\n identityTrustDomain: string | null,\n options: ClientOptions,\n reportSecurityIncident: SecurityIncidentReporter,\n ): InternalRuntime {\n return createRuntime({\n api: this.apiClient,\n blobs: this.blobs,\n database: this.database,\n documentProjectors: this.documentProjectors,\n events: this.events,\n getDomainScope: () => this.domainScope,\n identity: this.identity,\n identityTrustDomain,\n log: this.log,\n logError: this.logError,\n network: this.network,\n peerScope: options.peerScope ?? null,\n principalHistoryKeyProvider: options.principalHistoryKeyProvider,\n principalMutationTimeoutMs: options.principalMutationTimeoutMs,\n reportSecurityIncident,\n session: this.session,\n syncBillingGate: this.syncBillingGate,\n });\n }\n\n log = (message: string): void => {\n this.logHandler(message);\n };\n\n // Preserve the host's return value so best-effort callers can also catch\n // rejected promises from async loggers accepted by the void callback type.\n logError = (message: string | Error, cause?: unknown): void =>\n this.logErrorHandler(message, cause);\n\n /**\n * Mint a single-use ticket to authenticate the server-events websocket\n * handshake (which cannot carry the bearer header). Returns null when no\n * session is active. The caller appends it to the websocket URL.\n */\n requestWebSocketTicket(): Promise<string | null> {\n return this.apiClient.requestWebSocketTicket();\n }\n\n get domainScope(): DomainScope {\n const nextScopeKey = JSON.stringify([\n this.database.id,\n this.identity.signingFingerprint,\n ]);\n if (this.domainScopeKey !== nextScopeKey) {\n this.domainScopeKey = nextScopeKey;\n this.domainScopeValue = createDomainScope();\n }\n\n return this.domainScopeValue;\n }\n\n /**\n * Tear down background work for the active domain scope: stop the reconciler\n * and force-stop the sync coordinator pump, then drop the coordinator from\n * its registry. Hosts call this from unmount/cleanup so a pump cannot outlive\n * the React tree that created it. A later access for the same scope rebuilds\n * fresh state.\n */\n dispose(): void {\n const domainScope = this.domainScope;\n this.disposeSecurityIncidents();\n this.retirePrincipalHistoryProtection();\n this.deviceFirst.dispose();\n disposeDomainSyncCoordinator(domainScope);\n }\n\n private loginAfterSessionExpired(): Promise<boolean> {\n if (!this.session.authToken || !this.identity.signingKeyPair) {\n this.session.logout();\n return Promise.resolve(false);\n }\n\n // The API client replays requests the expired session sent, so a login is\n // their renewal only when the loaded keys authenticate that known user.\n const expiredUserId = this.session.userId;\n this.expiredSessionLoginPromise ??= this.session\n .login()\n .then(\n (renewed) =>\n renewed &&\n expiredUserId !== null &&\n this.session.userId === expiredUserId,\n )\n .finally(() => {\n this.expiredSessionLoginPromise = null;\n });\n return this.expiredSessionLoginPromise;\n }\n\n private startAutomaticIdentityProvisioning(): void {\n let unsubscribe: (() => void) | null = null;\n const stopProvisioningListener = () => {\n unsubscribe?.();\n unsubscribe = null;\n };\n const maybeProvisionIdentity = () => {\n if (this.autoIdentityProvisioned || this.identity.signingKeyPair) {\n stopProvisioningListener();\n return;\n }\n\n if (\n this.autoIdentityProvisioningPromise ||\n this.database.status !== \"ready\"\n ) {\n return;\n }\n\n this.autoIdentityProvisioningPromise = this.identity\n .generate()\n .then(() => {\n this.autoIdentityProvisioned = true;\n stopProvisioningListener();\n })\n .catch((error: unknown) => {\n try {\n this.logError(\"Automatic identity provisioning failed\", error);\n } catch {\n // Keep background provisioning failures from surfacing as unhandled rejections.\n }\n })\n .finally(() => {\n this.autoIdentityProvisioningPromise = null;\n });\n };\n\n unsubscribe = this.database.subscribe(maybeProvisionIdentity);\n void Promise.resolve().then(maybeProvisionIdentity);\n }\n}\n"]}
@@ -1,6 +1,6 @@
1
- import { AES_GCM_IV_BYTES, assertAesGcmIv, BLOB_ENCRYPTED_BYTES_FORMAT, BLOB_ENCRYPTED_BYTES_VERSION, CONTENT_RECORD_ENCRYPTION_SUITE, serializeKeyingCanonicalJson, toFingerprint, } from "../../../../internal/crypto/index.js";
1
+ import { AES_GCM_IV_BYTES, assertAesGcmIv, BLOB_ENCRYPTED_BYTES_FORMAT, BLOB_ENCRYPTED_BYTES_VERSION, CONTENT_RECORD_ENCRYPTION_SUITE, serializeKeyingCanonicalJson, } from "../../../../internal/crypto/index.js";
2
2
  import { bytesToBase64 } from "../../../../internal/encoding/index.js";
3
- import { BLOB_CONTENT_RECORD_AAD_DOMAIN, BLOB_CONTENT_RECORD_HKDF_SALT, BLOB_CONTENT_RECORD_KEY_INFO_DOMAIN, BLOB_CONTENT_RECORD_METADATA_HASH_DOMAIN, BLOB_CONTENT_RECORD_NONCE_DOMAIN, TEXT_ENCODER, } from "./types.js";
3
+ import { BLOB_CONTENT_RECORD_AAD_DOMAIN, BLOB_CONTENT_RECORD_HKDF_SALT, BLOB_CONTENT_RECORD_KEY_INFO_DOMAIN, BLOB_CONTENT_RECORD_NONCE_DOMAIN, TEXT_ENCODER, } from "./types.js";
4
4
  const SHA256_HEX_PATTERN = /^[0-9a-f]{64}$/;
5
5
  // The chunk layout is part of the IV family: a persisted nonce seed can
6
6
  // outlive a chunk-size change (upload resume across an app update), and the
@@ -24,21 +24,6 @@ export async function deriveBlobBaseIv(input) {
24
24
  }))));
25
25
  return digest.slice(0, AES_GCM_IV_BYTES);
26
26
  }
27
- export async function blobContentMetadataHash(input) {
28
- return toFingerprint(TEXT_ENCODER.encode(serializeKeyingCanonicalJson({
29
- domain: BLOB_CONTENT_RECORD_METADATA_HASH_DOMAIN,
30
- payload: {
31
- version: BLOB_ENCRYPTED_BYTES_VERSION,
32
- recordKind: "blob_bytes",
33
- blobId: input.blobId,
34
- byteLength: input.byteLength,
35
- chunkCount: input.chunkCount,
36
- chunkSize: input.chunkSize,
37
- contentKeyEpoch: input.contentKeyEpoch,
38
- format: BLOB_ENCRYPTED_BYTES_FORMAT,
39
- },
40
- })));
41
- }
42
27
  function contentRecordDerivationPayload(input) {
43
28
  return {
44
29
  version: BLOB_ENCRYPTED_BYTES_VERSION,
@@ -1 +1 @@
1
- {"version":3,"file":"blobRecordCrypto.js","sourceRoot":"","sources":["blobRecordCrypto.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n AES_GCM_IV_BYTES,\n assertAesGcmIv,\n BLOB_ENCRYPTED_BYTES_FORMAT,\n BLOB_ENCRYPTED_BYTES_VERSION,\n CONTENT_RECORD_ENCRYPTION_SUITE,\n type KeyingCanonicalJson,\n serializeKeyingCanonicalJson,\n toFingerprint,\n} from \"@tearleads/crypto\";\nimport { bytesToBase64 } from \"@tearleads/encoding\";\nimport {\n BLOB_CONTENT_RECORD_AAD_DOMAIN,\n BLOB_CONTENT_RECORD_HKDF_SALT,\n BLOB_CONTENT_RECORD_KEY_INFO_DOMAIN,\n BLOB_CONTENT_RECORD_METADATA_HASH_DOMAIN,\n BLOB_CONTENT_RECORD_NONCE_DOMAIN,\n TEXT_ENCODER,\n} from \"./types\";\n\nconst SHA256_HEX_PATTERN = /^[0-9a-f]{64}$/;\n\n// The chunk layout is part of the IV family: a persisted nonce seed can\n// outlive a chunk-size change (upload resume across an app update), and the\n// record key does not vary with the layout, so the same seed must never\n// yield the same per-chunk IVs over two different plaintext chunkings.\nexport async function deriveBlobBaseIv(input: {\n readonly chunkSize: number;\n readonly nonceSeed: Uint8Array;\n readonly plaintextSha256: string;\n}): Promise<Uint8Array<ArrayBuffer>> {\n assertAesGcmIv(input.nonceSeed, \"Blob nonce seed is invalid\");\n if (!SHA256_HEX_PATTERN.test(input.plaintextSha256)) {\n throw new Error(\"Blob plaintext SHA-256 is invalid\");\n }\n if (!Number.isInteger(input.chunkSize) || input.chunkSize <= 0) {\n throw new Error(\"Blob chunk size is invalid\");\n }\n\n const digest = new Uint8Array(\n await crypto.subtle.digest(\n \"SHA-256\",\n TEXT_ENCODER.encode(\n serializeKeyingCanonicalJson({\n domain: BLOB_CONTENT_RECORD_NONCE_DOMAIN,\n payload: {\n chunkSize: input.chunkSize,\n nonceSeed: bytesToBase64(input.nonceSeed),\n plaintextSha256: input.plaintextSha256,\n },\n }),\n ),\n ),\n );\n return digest.slice(0, AES_GCM_IV_BYTES);\n}\n\nexport async function blobContentMetadataHash(input: {\n blobId: string;\n byteLength: number;\n chunkCount: number;\n chunkSize: number;\n contentKeyEpoch: number;\n}): Promise<string> {\n return toFingerprint(\n TEXT_ENCODER.encode(\n serializeKeyingCanonicalJson({\n domain: BLOB_CONTENT_RECORD_METADATA_HASH_DOMAIN,\n payload: {\n version: BLOB_ENCRYPTED_BYTES_VERSION,\n recordKind: \"blob_bytes\",\n blobId: input.blobId,\n byteLength: input.byteLength,\n chunkCount: input.chunkCount,\n chunkSize: input.chunkSize,\n contentKeyEpoch: input.contentKeyEpoch,\n format: BLOB_ENCRYPTED_BYTES_FORMAT,\n },\n }),\n ),\n );\n}\n\nfunction contentRecordDerivationPayload(input: {\n blobId: string;\n contentKeyEpoch: number;\n contentRecordId: string;\n organizationId: string;\n}): Record<string, KeyingCanonicalJson> {\n return {\n version: BLOB_ENCRYPTED_BYTES_VERSION,\n organizationId: input.organizationId,\n objectKind: \"blob\",\n objectId: input.blobId,\n contentKeyEpoch: input.contentKeyEpoch,\n encryptionSuite: CONTENT_RECORD_ENCRYPTION_SUITE,\n contentRecordId: input.contentRecordId,\n };\n}\n\nfunction contentRecordDerivationBytes(input: {\n blobId: string;\n contentKeyEpoch: number;\n contentRecordId: string;\n organizationId: string;\n}): Uint8Array<ArrayBuffer> {\n return TEXT_ENCODER.encode(\n serializeKeyingCanonicalJson({\n domain: BLOB_CONTENT_RECORD_KEY_INFO_DOMAIN,\n payload: contentRecordDerivationPayload(input),\n }),\n );\n}\n\nexport function contentRecordAdditionalDataBytes(input: {\n blobId: string;\n chunkCount: number;\n chunkIndex: number;\n chunkPlaintextByteLength: number;\n chunkSize: number;\n contentKeyEpoch: number;\n contentRecordId: string;\n metadataHash: string;\n nonceDomainHash: string;\n organizationId: string;\n plaintextByteLength: number;\n}): Uint8Array<ArrayBuffer> {\n return TEXT_ENCODER.encode(\n serializeKeyingCanonicalJson({\n domain: BLOB_CONTENT_RECORD_AAD_DOMAIN,\n payload: {\n ...contentRecordDerivationPayload(input),\n blobFormat: BLOB_ENCRYPTED_BYTES_FORMAT,\n blobFormatVersion: BLOB_ENCRYPTED_BYTES_VERSION,\n chunkCount: input.chunkCount,\n chunkIndex: input.chunkIndex,\n chunkPlaintextByteLength: input.chunkPlaintextByteLength,\n chunkSize: input.chunkSize,\n metadataHash: input.metadataHash,\n nonceDomainHash: input.nonceDomainHash,\n plaintextByteLength: input.plaintextByteLength,\n },\n }),\n );\n}\n\nexport async function deriveBlobContentRecordKey(input: {\n blobId: string;\n contentKeyEpoch: number;\n contentKeyMaterial: CryptoKey;\n contentRecordId: string;\n organizationId: string;\n usage: \"decrypt\" | \"encrypt\";\n}): Promise<CryptoKey> {\n return crypto.subtle.deriveKey(\n {\n name: \"HKDF\",\n hash: \"SHA-256\",\n salt: BLOB_CONTENT_RECORD_HKDF_SALT,\n info: contentRecordDerivationBytes(input),\n },\n input.contentKeyMaterial,\n { name: \"AES-GCM\", length: 256 },\n false,\n [input.usage],\n );\n}\n"]}
1
+ {"version":3,"file":"blobRecordCrypto.js","sourceRoot":"","sources":["blobRecordCrypto.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n AES_GCM_IV_BYTES,\n assertAesGcmIv,\n BLOB_ENCRYPTED_BYTES_FORMAT,\n BLOB_ENCRYPTED_BYTES_VERSION,\n CONTENT_RECORD_ENCRYPTION_SUITE,\n type KeyingCanonicalJson,\n serializeKeyingCanonicalJson,\n} from \"@tearleads/crypto\";\nimport { bytesToBase64 } from \"@tearleads/encoding\";\nimport {\n BLOB_CONTENT_RECORD_AAD_DOMAIN,\n BLOB_CONTENT_RECORD_HKDF_SALT,\n BLOB_CONTENT_RECORD_KEY_INFO_DOMAIN,\n BLOB_CONTENT_RECORD_NONCE_DOMAIN,\n TEXT_ENCODER,\n} from \"./types\";\n\nconst SHA256_HEX_PATTERN = /^[0-9a-f]{64}$/;\n\n// The chunk layout is part of the IV family: a persisted nonce seed can\n// outlive a chunk-size change (upload resume across an app update), and the\n// record key does not vary with the layout, so the same seed must never\n// yield the same per-chunk IVs over two different plaintext chunkings.\nexport async function deriveBlobBaseIv(input: {\n readonly chunkSize: number;\n readonly nonceSeed: Uint8Array;\n readonly plaintextSha256: string;\n}): Promise<Uint8Array<ArrayBuffer>> {\n assertAesGcmIv(input.nonceSeed, \"Blob nonce seed is invalid\");\n if (!SHA256_HEX_PATTERN.test(input.plaintextSha256)) {\n throw new Error(\"Blob plaintext SHA-256 is invalid\");\n }\n if (!Number.isInteger(input.chunkSize) || input.chunkSize <= 0) {\n throw new Error(\"Blob chunk size is invalid\");\n }\n\n const digest = new Uint8Array(\n await crypto.subtle.digest(\n \"SHA-256\",\n TEXT_ENCODER.encode(\n serializeKeyingCanonicalJson({\n domain: BLOB_CONTENT_RECORD_NONCE_DOMAIN,\n payload: {\n chunkSize: input.chunkSize,\n nonceSeed: bytesToBase64(input.nonceSeed),\n plaintextSha256: input.plaintextSha256,\n },\n }),\n ),\n ),\n );\n return digest.slice(0, AES_GCM_IV_BYTES);\n}\n\nfunction contentRecordDerivationPayload(input: {\n blobId: string;\n contentKeyEpoch: number;\n contentRecordId: string;\n organizationId: string;\n}): Record<string, KeyingCanonicalJson> {\n return {\n version: BLOB_ENCRYPTED_BYTES_VERSION,\n organizationId: input.organizationId,\n objectKind: \"blob\",\n objectId: input.blobId,\n contentKeyEpoch: input.contentKeyEpoch,\n encryptionSuite: CONTENT_RECORD_ENCRYPTION_SUITE,\n contentRecordId: input.contentRecordId,\n };\n}\n\nfunction contentRecordDerivationBytes(input: {\n blobId: string;\n contentKeyEpoch: number;\n contentRecordId: string;\n organizationId: string;\n}): Uint8Array<ArrayBuffer> {\n return TEXT_ENCODER.encode(\n serializeKeyingCanonicalJson({\n domain: BLOB_CONTENT_RECORD_KEY_INFO_DOMAIN,\n payload: contentRecordDerivationPayload(input),\n }),\n );\n}\n\nexport function contentRecordAdditionalDataBytes(input: {\n blobId: string;\n chunkCount: number;\n chunkIndex: number;\n chunkPlaintextByteLength: number;\n chunkSize: number;\n contentKeyEpoch: number;\n contentRecordId: string;\n metadataHash: string;\n nonceDomainHash: string;\n organizationId: string;\n plaintextByteLength: number;\n}): Uint8Array<ArrayBuffer> {\n return TEXT_ENCODER.encode(\n serializeKeyingCanonicalJson({\n domain: BLOB_CONTENT_RECORD_AAD_DOMAIN,\n payload: {\n ...contentRecordDerivationPayload(input),\n blobFormat: BLOB_ENCRYPTED_BYTES_FORMAT,\n blobFormatVersion: BLOB_ENCRYPTED_BYTES_VERSION,\n chunkCount: input.chunkCount,\n chunkIndex: input.chunkIndex,\n chunkPlaintextByteLength: input.chunkPlaintextByteLength,\n chunkSize: input.chunkSize,\n metadataHash: input.metadataHash,\n nonceDomainHash: input.nonceDomainHash,\n plaintextByteLength: input.plaintextByteLength,\n },\n }),\n );\n}\n\nexport async function deriveBlobContentRecordKey(input: {\n blobId: string;\n contentKeyEpoch: number;\n contentKeyMaterial: CryptoKey;\n contentRecordId: string;\n organizationId: string;\n usage: \"decrypt\" | \"encrypt\";\n}): Promise<CryptoKey> {\n return crypto.subtle.deriveKey(\n {\n name: \"HKDF\",\n hash: \"SHA-256\",\n salt: BLOB_CONTENT_RECORD_HKDF_SALT,\n info: contentRecordDerivationBytes(input),\n },\n input.contentKeyMaterial,\n { name: \"AES-GCM\", length: 256 },\n false,\n [input.usage],\n );\n}\n"]}
@@ -1,7 +1,7 @@
1
- import { AES_256_KEY_BYTES, AES_GCM_TAG_BYTES, assertAesGcmIv, BLOB_CHUNK_SIZE_BYTES, BLOB_ENCRYPTED_BYTES_FORMAT, BLOB_ENCRYPTED_BYTES_VERSION, bytesToHex, CONTENT_RECORD_ENCRYPTION_SUITE, computeBlobChunkCount, computeBlobEncryptedByteLength, computeContentRecordNonceDomainHash, createAesGcmIv, createIncrementalSha256, deriveBlobChunkIv, encodeBlobEnvelopeV2Header, normalizeBlobChunkSize, } from "../../../../internal/crypto/index.js";
1
+ import { AES_256_KEY_BYTES, AES_GCM_TAG_BYTES, assertAesGcmIv, BLOB_CHUNK_SIZE_BYTES, BLOB_ENCRYPTED_BYTES_FORMAT, BLOB_ENCRYPTED_BYTES_VERSION, blobContentMetadataHash, bytesToHex, CONTENT_RECORD_ENCRYPTION_SUITE, computeBlobChunkCount, computeBlobEncryptedByteLength, computeContentRecordNonceDomainHash, createAesGcmIv, createIncrementalSha256, deriveBlobChunkIv, encodeBlobEnvelopeV2Header, normalizeBlobChunkSize, } from "../../../../internal/crypto/index.js";
2
2
  import { bytesToBase64 } from "../../../../internal/encoding/index.js";
3
3
  import { importContentKeyMaterial } from "../../shared/contentRecordKeys.js";
4
- import { blobContentMetadataHash, contentRecordAdditionalDataBytes, deriveBlobBaseIv, deriveBlobContentRecordKey, } from "./blobRecordCrypto.js";
4
+ import { contentRecordAdditionalDataBytes, deriveBlobBaseIv, deriveBlobContentRecordKey, } from "./blobRecordCrypto.js";
5
5
  import { inspectBlobSource, readVerifiedBlobSourceChunk, } from "./blobSourceSnapshot.js";
6
6
  import { createEncryptedUploadParts } from "./encryptedUploadParts.js";
7
7
  export const DEFAULT_BLOB_CHUNK_SIZE_BYTES = BLOB_CHUNK_SIZE_BYTES;
@@ -1 +1 @@
1
- {"version":3,"file":"chunkedBlobCrypto.js","sourceRoot":"","sources":["chunkedBlobCrypto.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n AES_256_KEY_BYTES,\n AES_GCM_TAG_BYTES,\n assertAesGcmIv,\n BLOB_CHUNK_SIZE_BYTES,\n BLOB_ENCRYPTED_BYTES_FORMAT,\n BLOB_ENCRYPTED_BYTES_VERSION,\n bytesToHex,\n CONTENT_RECORD_ENCRYPTION_SUITE,\n computeBlobChunkCount,\n computeBlobEncryptedByteLength,\n computeContentRecordNonceDomainHash,\n createAesGcmIv,\n createIncrementalSha256,\n deriveBlobChunkIv,\n encodeBlobEnvelopeV2Header,\n normalizeBlobChunkSize,\n} from \"@tearleads/crypto\";\nimport { bytesToBase64 } from \"@tearleads/encoding\";\nimport type { BlobContentKeyBundleRequest } from \"@tearleads/validators/request\";\nimport type { BlobByteSource, BlobBytes } from \"../../../blobContracts\";\nimport { importContentKeyMaterial } from \"../../shared/contentRecordKeys\";\nimport {\n blobContentMetadataHash,\n contentRecordAdditionalDataBytes,\n deriveBlobBaseIv,\n deriveBlobContentRecordKey,\n} from \"./blobRecordCrypto\";\nimport {\n type BlobSourceSnapshot,\n inspectBlobSource,\n readVerifiedBlobSourceChunk,\n} from \"./blobSourceSnapshot\";\nimport { createEncryptedUploadParts } from \"./encryptedUploadParts\";\nimport type { BlobEncryptionPlan } from \"./types\";\n\nexport const DEFAULT_BLOB_CHUNK_SIZE_BYTES = BLOB_CHUNK_SIZE_BYTES;\n\ninterface PrepareBlobEncryptionInput {\n readonly blobId: string;\n readonly chunkSize?: number | undefined;\n readonly contentKey: Uint8Array;\n readonly contentKeyBundle: BlobContentKeyBundleRequest;\n readonly nonceSeed?: Uint8Array | undefined;\n readonly organizationId: string;\n readonly source: BlobByteSource;\n readonly sourceSnapshot?: BlobSourceSnapshot | undefined;\n}\n\ninterface BlobEncryptionContext {\n readonly baseIv: BlobBytes;\n readonly blobId: string;\n readonly chunkSize: number;\n readonly contentKeyEpoch: number;\n readonly contentRecordId: string;\n readonly headerBytes: BlobBytes;\n readonly metadataHash: string;\n readonly nonceDomainHash: string;\n readonly organizationId: string;\n readonly partCount: number;\n readonly plaintextByteLength: number;\n readonly recordKey: CryptoKey;\n}\n\nasync function createBlobEncryptionContext(\n input: PrepareBlobEncryptionInput,\n snapshot: BlobSourceSnapshot,\n): Promise<BlobEncryptionContext> {\n const chunkSize = normalizeBlobChunkSize(\n input.chunkSize ?? DEFAULT_BLOB_CHUNK_SIZE_BYTES,\n );\n const plaintextByteLength = input.source.byteLength;\n const partCount = computeBlobChunkCount(plaintextByteLength, chunkSize);\n const contentRecordId = input.blobId;\n const { contentKeyEpoch } = input.contentKeyBundle;\n const nonceSeed = new Uint8Array(input.nonceSeed ?? createAesGcmIv());\n const baseIv = await deriveBlobBaseIv({\n chunkSize,\n nonceSeed,\n plaintextSha256: snapshot.sha256,\n });\n\n const [nonceDomainHash, metadataHash] = await Promise.all([\n computeContentRecordNonceDomainHash({\n version: 1,\n organizationId: input.organizationId,\n objectKind: \"blob\",\n objectId: input.blobId,\n contentKeyEpoch,\n encryptionSuite: CONTENT_RECORD_ENCRYPTION_SUITE,\n contentRecordId,\n }),\n blobContentMetadataHash({\n blobId: input.blobId,\n byteLength: plaintextByteLength,\n chunkCount: partCount,\n chunkSize,\n contentKeyEpoch,\n }),\n ]);\n const contentKeyMaterial = await importContentKeyMaterial(input.contentKey);\n const recordKey = await deriveBlobContentRecordKey({\n blobId: input.blobId,\n contentKeyEpoch,\n contentKeyMaterial,\n contentRecordId,\n organizationId: input.organizationId,\n usage: \"encrypt\",\n });\n return {\n baseIv,\n blobId: input.blobId,\n chunkSize,\n contentKeyEpoch,\n contentRecordId,\n headerBytes: encodeBlobEnvelopeV2Header({\n blobId: input.blobId,\n byteLength: plaintextByteLength,\n chunkCount: partCount,\n chunkSize,\n contentKeyEpoch,\n contentRecordId,\n encryptionSuite: CONTENT_RECORD_ENCRYPTION_SUITE,\n format: BLOB_ENCRYPTED_BYTES_FORMAT,\n iv: bytesToBase64(baseIv),\n metadataHash,\n nonceDomainHash,\n version: BLOB_ENCRYPTED_BYTES_VERSION,\n }),\n metadataHash,\n nonceDomainHash,\n organizationId: input.organizationId,\n partCount,\n plaintextByteLength,\n recordKey,\n };\n}\n\nasync function encryptBlobChunk(\n context: BlobEncryptionContext,\n chunkIndex: number,\n plaintext: BlobBytes,\n): Promise<BlobBytes> {\n return new Uint8Array(\n await crypto.subtle.encrypt(\n {\n name: \"AES-GCM\",\n iv: deriveBlobChunkIv(context.baseIv, chunkIndex),\n additionalData: contentRecordAdditionalDataBytes({\n blobId: context.blobId,\n chunkCount: context.partCount,\n chunkIndex,\n chunkPlaintextByteLength: plaintext.byteLength,\n chunkSize: context.chunkSize,\n contentKeyEpoch: context.contentKeyEpoch,\n contentRecordId: context.contentRecordId,\n metadataHash: context.metadataHash,\n nonceDomainHash: context.nonceDomainHash,\n organizationId: context.organizationId,\n plaintextByteLength: context.plaintextByteLength,\n }),\n },\n context.recordKey,\n plaintext,\n ),\n );\n}\n\nasync function runEncryptionHashPass(\n source: BlobByteSource,\n snapshot: BlobSourceSnapshot,\n context: BlobEncryptionContext,\n): Promise<string> {\n const objectHash = createIncrementalSha256();\n objectHash.update(context.headerBytes);\n for (let chunkIndex = 0; chunkIndex < context.partCount; chunkIndex += 1) {\n const plaintext = await readVerifiedBlobSourceChunk({\n chunkIndex,\n snapshot,\n source,\n });\n objectHash.update(await encryptBlobChunk(context, chunkIndex, plaintext));\n }\n return bytesToHex(objectHash.digest());\n}\n\nfunction createBlobEncryptionPlan(input: {\n readonly context: BlobEncryptionContext;\n readonly sha256: string;\n readonly snapshot: BlobSourceSnapshot;\n readonly source: BlobByteSource;\n}): BlobEncryptionPlan {\n const { context, sha256, snapshot, source } = input;\n const byteLength = computeBlobEncryptedByteLength({\n chunkCount: context.partCount,\n headerByteLength: context.headerBytes.byteLength,\n plaintextByteLength: context.plaintextByteLength,\n });\n return {\n byteLength,\n chunkSize: context.chunkSize,\n metadataHash: context.metadataHash,\n plaintextByteLength: context.plaintextByteLength,\n plaintextSha256: snapshot.sha256,\n sha256,\n ...createEncryptedUploadParts({\n byteLength,\n chunkSize: context.chunkSize + AES_GCM_TAG_BYTES,\n header: context.headerBytes,\n async encryptChunk(chunkIndex) {\n const plaintext = await readVerifiedBlobSourceChunk({\n chunkIndex,\n snapshot,\n source,\n });\n return encryptBlobChunk(context, chunkIndex, plaintext);\n },\n }),\n };\n}\n\nexport async function prepareBlobEncryption(\n input: PrepareBlobEncryptionInput,\n): Promise<BlobEncryptionPlan> {\n if (input.contentKey.byteLength !== AES_256_KEY_BYTES) {\n throw new Error(\"Blob content key must be 32 bytes\");\n }\n if (input.nonceSeed) {\n assertAesGcmIv(input.nonceSeed, \"Blob nonce seed is invalid\");\n }\n const chunkSize = normalizeBlobChunkSize(\n input.chunkSize ?? DEFAULT_BLOB_CHUNK_SIZE_BYTES,\n );\n const snapshot =\n input.sourceSnapshot ?? (await inspectBlobSource(input.source, chunkSize));\n const context = await createBlobEncryptionContext(input, snapshot);\n if (\n snapshot.byteLength !== context.plaintextByteLength ||\n snapshot.chunkSize !== context.chunkSize\n ) {\n throw new Error(\"Blob source snapshot does not match encryption layout\");\n }\n const sha256 = await runEncryptionHashPass(input.source, snapshot, context);\n return createBlobEncryptionPlan({\n context,\n sha256,\n snapshot,\n source: input.source,\n });\n}\n"]}
1
+ {"version":3,"file":"chunkedBlobCrypto.js","sourceRoot":"","sources":["chunkedBlobCrypto.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n AES_256_KEY_BYTES,\n AES_GCM_TAG_BYTES,\n assertAesGcmIv,\n BLOB_CHUNK_SIZE_BYTES,\n BLOB_ENCRYPTED_BYTES_FORMAT,\n BLOB_ENCRYPTED_BYTES_VERSION,\n blobContentMetadataHash,\n bytesToHex,\n CONTENT_RECORD_ENCRYPTION_SUITE,\n computeBlobChunkCount,\n computeBlobEncryptedByteLength,\n computeContentRecordNonceDomainHash,\n createAesGcmIv,\n createIncrementalSha256,\n deriveBlobChunkIv,\n encodeBlobEnvelopeV2Header,\n normalizeBlobChunkSize,\n} from \"@tearleads/crypto\";\nimport { bytesToBase64 } from \"@tearleads/encoding\";\nimport type { BlobContentKeyBundleRequest } from \"@tearleads/validators/request\";\nimport type { BlobByteSource, BlobBytes } from \"../../../blobContracts\";\nimport { importContentKeyMaterial } from \"../../shared/contentRecordKeys\";\nimport {\n contentRecordAdditionalDataBytes,\n deriveBlobBaseIv,\n deriveBlobContentRecordKey,\n} from \"./blobRecordCrypto\";\nimport {\n type BlobSourceSnapshot,\n inspectBlobSource,\n readVerifiedBlobSourceChunk,\n} from \"./blobSourceSnapshot\";\nimport { createEncryptedUploadParts } from \"./encryptedUploadParts\";\nimport type { BlobEncryptionPlan } from \"./types\";\n\nexport const DEFAULT_BLOB_CHUNK_SIZE_BYTES = BLOB_CHUNK_SIZE_BYTES;\n\ninterface PrepareBlobEncryptionInput {\n readonly blobId: string;\n readonly chunkSize?: number | undefined;\n readonly contentKey: Uint8Array;\n readonly contentKeyBundle: BlobContentKeyBundleRequest;\n readonly nonceSeed?: Uint8Array | undefined;\n readonly organizationId: string;\n readonly source: BlobByteSource;\n readonly sourceSnapshot?: BlobSourceSnapshot | undefined;\n}\n\ninterface BlobEncryptionContext {\n readonly baseIv: BlobBytes;\n readonly blobId: string;\n readonly chunkSize: number;\n readonly contentKeyEpoch: number;\n readonly contentRecordId: string;\n readonly headerBytes: BlobBytes;\n readonly metadataHash: string;\n readonly nonceDomainHash: string;\n readonly organizationId: string;\n readonly partCount: number;\n readonly plaintextByteLength: number;\n readonly recordKey: CryptoKey;\n}\n\nasync function createBlobEncryptionContext(\n input: PrepareBlobEncryptionInput,\n snapshot: BlobSourceSnapshot,\n): Promise<BlobEncryptionContext> {\n const chunkSize = normalizeBlobChunkSize(\n input.chunkSize ?? DEFAULT_BLOB_CHUNK_SIZE_BYTES,\n );\n const plaintextByteLength = input.source.byteLength;\n const partCount = computeBlobChunkCount(plaintextByteLength, chunkSize);\n const contentRecordId = input.blobId;\n const { contentKeyEpoch } = input.contentKeyBundle;\n const nonceSeed = new Uint8Array(input.nonceSeed ?? createAesGcmIv());\n const baseIv = await deriveBlobBaseIv({\n chunkSize,\n nonceSeed,\n plaintextSha256: snapshot.sha256,\n });\n\n const [nonceDomainHash, metadataHash] = await Promise.all([\n computeContentRecordNonceDomainHash({\n version: 1,\n organizationId: input.organizationId,\n objectKind: \"blob\",\n objectId: input.blobId,\n contentKeyEpoch,\n encryptionSuite: CONTENT_RECORD_ENCRYPTION_SUITE,\n contentRecordId,\n }),\n blobContentMetadataHash({\n blobId: input.blobId,\n byteLength: plaintextByteLength,\n chunkCount: partCount,\n chunkSize,\n contentKeyEpoch,\n }),\n ]);\n const contentKeyMaterial = await importContentKeyMaterial(input.contentKey);\n const recordKey = await deriveBlobContentRecordKey({\n blobId: input.blobId,\n contentKeyEpoch,\n contentKeyMaterial,\n contentRecordId,\n organizationId: input.organizationId,\n usage: \"encrypt\",\n });\n return {\n baseIv,\n blobId: input.blobId,\n chunkSize,\n contentKeyEpoch,\n contentRecordId,\n headerBytes: encodeBlobEnvelopeV2Header({\n blobId: input.blobId,\n byteLength: plaintextByteLength,\n chunkCount: partCount,\n chunkSize,\n contentKeyEpoch,\n contentRecordId,\n encryptionSuite: CONTENT_RECORD_ENCRYPTION_SUITE,\n format: BLOB_ENCRYPTED_BYTES_FORMAT,\n iv: bytesToBase64(baseIv),\n metadataHash,\n nonceDomainHash,\n version: BLOB_ENCRYPTED_BYTES_VERSION,\n }),\n metadataHash,\n nonceDomainHash,\n organizationId: input.organizationId,\n partCount,\n plaintextByteLength,\n recordKey,\n };\n}\n\nasync function encryptBlobChunk(\n context: BlobEncryptionContext,\n chunkIndex: number,\n plaintext: BlobBytes,\n): Promise<BlobBytes> {\n return new Uint8Array(\n await crypto.subtle.encrypt(\n {\n name: \"AES-GCM\",\n iv: deriveBlobChunkIv(context.baseIv, chunkIndex),\n additionalData: contentRecordAdditionalDataBytes({\n blobId: context.blobId,\n chunkCount: context.partCount,\n chunkIndex,\n chunkPlaintextByteLength: plaintext.byteLength,\n chunkSize: context.chunkSize,\n contentKeyEpoch: context.contentKeyEpoch,\n contentRecordId: context.contentRecordId,\n metadataHash: context.metadataHash,\n nonceDomainHash: context.nonceDomainHash,\n organizationId: context.organizationId,\n plaintextByteLength: context.plaintextByteLength,\n }),\n },\n context.recordKey,\n plaintext,\n ),\n );\n}\n\nasync function runEncryptionHashPass(\n source: BlobByteSource,\n snapshot: BlobSourceSnapshot,\n context: BlobEncryptionContext,\n): Promise<string> {\n const objectHash = createIncrementalSha256();\n objectHash.update(context.headerBytes);\n for (let chunkIndex = 0; chunkIndex < context.partCount; chunkIndex += 1) {\n const plaintext = await readVerifiedBlobSourceChunk({\n chunkIndex,\n snapshot,\n source,\n });\n objectHash.update(await encryptBlobChunk(context, chunkIndex, plaintext));\n }\n return bytesToHex(objectHash.digest());\n}\n\nfunction createBlobEncryptionPlan(input: {\n readonly context: BlobEncryptionContext;\n readonly sha256: string;\n readonly snapshot: BlobSourceSnapshot;\n readonly source: BlobByteSource;\n}): BlobEncryptionPlan {\n const { context, sha256, snapshot, source } = input;\n const byteLength = computeBlobEncryptedByteLength({\n chunkCount: context.partCount,\n headerByteLength: context.headerBytes.byteLength,\n plaintextByteLength: context.plaintextByteLength,\n });\n return {\n byteLength,\n chunkSize: context.chunkSize,\n metadataHash: context.metadataHash,\n plaintextByteLength: context.plaintextByteLength,\n plaintextSha256: snapshot.sha256,\n sha256,\n ...createEncryptedUploadParts({\n byteLength,\n chunkSize: context.chunkSize + AES_GCM_TAG_BYTES,\n header: context.headerBytes,\n async encryptChunk(chunkIndex) {\n const plaintext = await readVerifiedBlobSourceChunk({\n chunkIndex,\n snapshot,\n source,\n });\n return encryptBlobChunk(context, chunkIndex, plaintext);\n },\n }),\n };\n}\n\nexport async function prepareBlobEncryption(\n input: PrepareBlobEncryptionInput,\n): Promise<BlobEncryptionPlan> {\n if (input.contentKey.byteLength !== AES_256_KEY_BYTES) {\n throw new Error(\"Blob content key must be 32 bytes\");\n }\n if (input.nonceSeed) {\n assertAesGcmIv(input.nonceSeed, \"Blob nonce seed is invalid\");\n }\n const chunkSize = normalizeBlobChunkSize(\n input.chunkSize ?? DEFAULT_BLOB_CHUNK_SIZE_BYTES,\n );\n const snapshot =\n input.sourceSnapshot ?? (await inspectBlobSource(input.source, chunkSize));\n const context = await createBlobEncryptionContext(input, snapshot);\n if (\n snapshot.byteLength !== context.plaintextByteLength ||\n snapshot.chunkSize !== context.chunkSize\n ) {\n throw new Error(\"Blob source snapshot does not match encryption layout\");\n }\n const sha256 = await runEncryptionHashPass(input.source, snapshot, context);\n return createBlobEncryptionPlan({\n context,\n sha256,\n snapshot,\n source: input.source,\n });\n}\n"]}
@@ -1,3 +1,3 @@
1
- export { blobContentMetadataHash, contentRecordAdditionalDataBytes, deriveBlobContentRecordKey, } from "./blobRecordCrypto.js";
1
+ export { contentRecordAdditionalDataBytes, deriveBlobContentRecordKey, } from "./blobRecordCrypto.js";
2
2
  export { DEFAULT_BLOB_CHUNK_SIZE_BYTES, prepareBlobEncryption, } from "./chunkedBlobCrypto.js";
3
3
  //# sourceMappingURL=crypto.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"crypto.js","sourceRoot":"","sources":["crypto.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EACvB,gCAAgC,EAChC,0BAA0B,GAC3B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,6BAA6B,EAC7B,qBAAqB,GACtB,MAAM,qBAAqB,CAAC","sourcesContent":["export {\n blobContentMetadataHash,\n contentRecordAdditionalDataBytes,\n deriveBlobContentRecordKey,\n} from \"./blobRecordCrypto\";\nexport {\n DEFAULT_BLOB_CHUNK_SIZE_BYTES,\n prepareBlobEncryption,\n} from \"./chunkedBlobCrypto\";\nexport type { BlobEncryptionPlan } from \"./types\";\n"]}
1
+ {"version":3,"file":"crypto.js","sourceRoot":"","sources":["crypto.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gCAAgC,EAChC,0BAA0B,GAC3B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,6BAA6B,EAC7B,qBAAqB,GACtB,MAAM,qBAAqB,CAAC","sourcesContent":["export {\n contentRecordAdditionalDataBytes,\n deriveBlobContentRecordKey,\n} from \"./blobRecordCrypto\";\nexport {\n DEFAULT_BLOB_CHUNK_SIZE_BYTES,\n prepareBlobEncryption,\n} from \"./chunkedBlobCrypto\";\nexport type { BlobEncryptionPlan } from \"./types\";\n"]}
@@ -8,7 +8,6 @@ import type { ExecSql } from "../../../sqlite/sqlSchema.js";
8
8
  import type { DocumentCreateAuthor } from "../../shared/types.js";
9
9
  export declare const BLOB_CONTENT_RECORD_KEY_INFO_DOMAIN = "tearleads.blob.content-record-key-info";
10
10
  export declare const BLOB_CONTENT_RECORD_AAD_DOMAIN = "tearleads.blob.content-record-aad";
11
- export declare const BLOB_CONTENT_RECORD_METADATA_HASH_DOMAIN = "tearleads.blob.content-record-metadata";
12
11
  export declare const BLOB_CONTENT_RECORD_NONCE_DOMAIN = "tearleads.blob.content-record-nonce";
13
12
  export declare const BLOB_CONTENT_RECORD_HKDF_SALT: Uint8Array<ArrayBuffer>;
14
13
  export declare const TEXT_ENCODER: TextEncoder;
@@ -1,6 +1,5 @@
1
1
  export const BLOB_CONTENT_RECORD_KEY_INFO_DOMAIN = "tearleads.blob.content-record-key-info";
2
2
  export const BLOB_CONTENT_RECORD_AAD_DOMAIN = "tearleads.blob.content-record-aad";
3
- export const BLOB_CONTENT_RECORD_METADATA_HASH_DOMAIN = "tearleads.blob.content-record-metadata";
4
3
  export const BLOB_CONTENT_RECORD_NONCE_DOMAIN = "tearleads.blob.content-record-nonce";
5
4
  export const BLOB_CONTENT_RECORD_HKDF_SALT = new TextEncoder().encode("tearleads.blob.content-record-hkdf-salt");
6
5
  export const TEXT_ENCODER = new TextEncoder();
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"AAmCA,MAAM,CAAC,MAAM,mCAAmC,GAC9C,wCAAwC,CAAC;AAC3C,MAAM,CAAC,MAAM,8BAA8B,GACzC,mCAAmC,CAAC;AACtC,MAAM,CAAC,MAAM,wCAAwC,GACnD,wCAAwC,CAAC;AAC3C,MAAM,CAAC,MAAM,gCAAgC,GAC3C,qCAAqC,CAAC;AACxC,MAAM,CAAC,MAAM,6BAA6B,GACxC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,yCAAyC,CAAC,CAAC;AACtE,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,WAAW,EAAE,CAAC","sourcesContent":["import type {\n RequestFailureKind,\n UploadMultipartBlobPartBytesRequest,\n} from \"@tearleads/api-client\";\nimport type {\n BlobContentKeyTarget,\n BlobEnvelopeHeaderRecord,\n BlobEnvelopeRecord,\n WriteHeader,\n} from \"@tearleads/crypto\";\nimport type {\n BlobAttachmentBindRequest,\n BlobAttachmentDetachRequest,\n BlobContentKeyBundleRequest,\n CompleteMultipartBlobStageRequest,\n InitiateMultipartBlobStageRequest,\n} from \"@tearleads/validators/request\";\nimport type {\n BlobAttachmentBindResponse,\n BlobAttachmentDetachResponse,\n BlobAttachmentSummary,\n CompleteMultipartBlobStageResponse,\n DocumentWriterProjectionResponse,\n InitiateMultipartBlobStageResponse,\n MultipartBlobStageStatusResponse,\n UploadMultipartBlobPartResponse,\n} from \"@tearleads/validators/response\";\nimport type { BlobByteSourceInput, BlobBytes } from \"../../../blobContracts\";\nimport type {\n ProjectionUserKeyResolver,\n ReferencedPrincipalPolicyWarmer,\n} from \"../../../keyingProjectionVerification\";\nimport type { ExecSql } from \"../../../sqlite/sqlSchema\";\nimport type { DocumentCreateAuthor } from \"../../shared/types\";\n\nexport const BLOB_CONTENT_RECORD_KEY_INFO_DOMAIN =\n \"tearleads.blob.content-record-key-info\";\nexport const BLOB_CONTENT_RECORD_AAD_DOMAIN =\n \"tearleads.blob.content-record-aad\";\nexport const BLOB_CONTENT_RECORD_METADATA_HASH_DOMAIN =\n \"tearleads.blob.content-record-metadata\";\nexport const BLOB_CONTENT_RECORD_NONCE_DOMAIN =\n \"tearleads.blob.content-record-nonce\";\nexport const BLOB_CONTENT_RECORD_HKDF_SALT: Uint8Array<ArrayBuffer> =\n new TextEncoder().encode(\"tearleads.blob.content-record-hkdf-salt\");\nexport const TEXT_ENCODER = new TextEncoder();\n\nexport interface BlobAttachmentRequestOptions {\n readonly expectedPaymentRequiredOrganizationId?: string | undefined;\n}\n\nexport interface BlobAttachmentApi {\n bindBlobAttachment(\n blobId: string,\n input: BlobAttachmentBindRequest,\n options?: BlobAttachmentRequestOptions,\n ): Promise<BlobAttachmentBindResponse | null>;\n clearWriterProjectionCaches?(): void;\n evictDocumentWriterProjection?(documentId: string): void;\n getRequestFailure?(input: {\n method: \"DELETE\" | \"GET\" | \"POST\" | \"PUT\";\n path: string;\n }): {\n readonly code?: string | undefined;\n readonly kind: RequestFailureKind;\n readonly message: string;\n readonly status: number | null;\n } | null;\n completeMultipartBlobStage(\n stageId: string,\n input: CompleteMultipartBlobStageRequest,\n ): Promise<CompleteMultipartBlobStageResponse | null>;\n getDocumentWriterProjection(\n documentId: string,\n ): Promise<DocumentWriterProjectionResponse | null>;\n getMultipartBlobStage(\n stageId: string,\n ): Promise<MultipartBlobStageStatusResponse | null>;\n initiateMultipartBlobStage(\n input: InitiateMultipartBlobStageRequest,\n ): Promise<InitiateMultipartBlobStageResponse | null>;\n uploadMultipartBlobPartBytes(\n stageId: string,\n partNumber: number,\n input: UploadMultipartBlobPartBytesRequest,\n ): Promise<UploadMultipartBlobPartResponse | null>;\n}\n\nexport interface BlobAttachmentDetachApi {\n detachBlobAttachment(\n blobId: string,\n bindingId: string,\n input: BlobAttachmentDetachRequest,\n options?: BlobAttachmentRequestOptions,\n ): Promise<BlobAttachmentDetachResponse | null>;\n getDocumentWriterProjection(\n documentId: string,\n ): Promise<DocumentWriterProjectionResponse | null>;\n}\n\nexport type { BlobContentKeyTarget };\n\n/**\n * Aliases of the crypto parser's own types. The parser is shared with the API,\n * so restating these shapes here would let the two drift apart silently.\n */\nexport type BlobEncryptedBytesRecord = BlobEnvelopeRecord;\n\n/**\n * The envelope as the header establishes it. Streaming readers hold this\n * before any ciphertext is read, so it has no `chunks`: an empty array there\n * would read as an empty object rather than an unread one.\n */\nexport type BlobEncryptedBytesHeader = BlobEnvelopeHeaderRecord;\n\nexport type BlobEncryptedChunk = BlobEnvelopeRecord[\"chunks\"][number];\n\nexport interface BlobSourceSnapshot {\n readonly byteLength: number;\n readonly chunkSha256: readonly string[];\n readonly chunkSize: number;\n readonly sha256: string;\n}\n\nexport interface BlobEncryptionPlan {\n readonly byteLength: number;\n readonly chunkSize: number;\n readonly metadataHash: string;\n readonly partCount: number;\n readonly plaintextByteLength: number;\n readonly plaintextSha256: string;\n readonly sha256: string;\n encryptPart(index: number): Promise<BlobBytes>;\n getPartByteLength(index: number): number;\n}\n\nexport interface DocumentManifestIdentity {\n documentId: string;\n manifestHash: string;\n organizationId: string;\n}\n\nexport interface BlobAttachmentMaterial {\n blobAccessManifestHash: string;\n contentKeyBundle: BlobContentKeyBundleRequest;\n encrypted: BlobEncryptionPlan;\n manifestIdentity: DocumentManifestIdentity;\n targetHash: string;\n targets: BlobContentKeyTarget[];\n writerProjection: DocumentWriterProjectionResponse;\n}\n\n/**\n * Progress for an in-flight multipart blob upload.\n */\nexport interface MultipartUploadProgress {\n readonly bytesTotal: number;\n readonly bytesUploaded: number;\n readonly partsCompleted: number;\n readonly partsTotal: number;\n}\n\nexport type MultipartUploadProgressListener = (\n progress: MultipartUploadProgress,\n) => void;\n\n/**\n * Fires once a multipart stage has been opened (or resumed), with its id and\n * plaintext chunk size. Callers persist both so a later attempt can reproduce\n * the same encrypted parts and resume instead of orphaning the partial upload.\n */\nexport type MultipartStageResolvedListener = (input: {\n readonly partSize: number;\n readonly stageId: string;\n}) => void | Promise<void>;\n\nexport interface UploadDocumentAttachmentInput {\n apiClient: BlobAttachmentApi;\n author: DocumentCreateAuthor;\n blobId?: string | undefined;\n bindingId?: string | undefined;\n bytes: BlobByteSourceInput;\n contentKey?: Uint8Array | undefined;\n contentKeyEpoch?: number | undefined;\n documentId: string;\n eventId?: string | undefined;\n execSql: ExecSql;\n expectedBindingId: string | null;\n isRemoteSyncBlocked?: ((organizationId: string) => boolean) | undefined;\n // A persisted nonce seed. The plaintext digest derives the envelope IV from\n // it, then each chunk derives a unique IV. Same-source retries reproduce the\n // encrypted parts; changed bytes cannot reuse their AES-GCM nonces.\n nonceSeed?: Uint8Array | undefined;\n multipart?: MultipartBlobUploadOptions | undefined;\n onMultipartProgress?: MultipartUploadProgressListener | undefined;\n onStageResolved?: MultipartStageResolvedListener | undefined;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n signedAt?: string | undefined;\n slotId: string;\n targetSecretKey: Uint8Array;\n warmReferencedPrincipalPolicies?: ReferencedPrincipalPolicyWarmer | undefined;\n writerProjection?: DocumentWriterProjectionResponse | undefined;\n}\n\nexport interface MultipartBlobUploadOptions {\n /** Exactly 5 MiB per plaintext encryption chunk; transport parts include envelope overhead. */\n readonly partSize: number;\n // The id of a previously opened stage to resume. When set, the upload refetches\n // that stage and skips parts the server already has instead of opening a new\n // one; if the stage is gone (expired), it falls back to a fresh stage.\n readonly resumeStageId?: string | undefined;\n readonly uploadConcurrency?: number | undefined;\n}\n\nexport interface UploadDocumentAttachmentResult {\n blobId: string;\n bindingId: string;\n request: BlobAttachmentBindRequest;\n response: BlobAttachmentBindResponse;\n sha256: string;\n byteLength: number;\n writeHeader: WriteHeader;\n writeHeaderHash: string;\n writerProjection: DocumentWriterProjectionResponse;\n}\n\nexport interface DetachDocumentAttachmentInput {\n apiClient: BlobAttachmentDetachApi;\n author: DocumentCreateAuthor;\n bindingId: string;\n blobId: string;\n documentId: string;\n eventId?: string | undefined;\n execSql: ExecSql;\n isRemoteSyncBlocked?: ((organizationId: string) => boolean) | undefined;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n signedAt?: string | undefined;\n slotId: string;\n stillCurrent?: (() => boolean) | undefined;\n warmReferencedPrincipalPolicies?: ReferencedPrincipalPolicyWarmer | undefined;\n writerProjection?: DocumentWriterProjectionResponse | undefined;\n}\n\nexport interface DetachDocumentAttachmentResult {\n request: BlobAttachmentDetachRequest;\n response: BlobAttachmentDetachResponse;\n writerProjection: DocumentWriterProjectionResponse;\n}\n\nexport interface DecryptDocumentAttachmentBlobInput {\n binding: BlobAttachmentSummary;\n encryptedBytes: BlobBytes;\n expectedDocumentId: string;\n expectedSlotId: string;\n execSql: ExecSql;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n targetSecretKey: Uint8Array;\n stillCurrent?: (() => boolean) | undefined;\n warmReferencedPrincipalPolicies?: ReferencedPrincipalPolicyWarmer | undefined;\n writerProjection: DocumentWriterProjectionResponse;\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"AAmCA,MAAM,CAAC,MAAM,mCAAmC,GAC9C,wCAAwC,CAAC;AAC3C,MAAM,CAAC,MAAM,8BAA8B,GACzC,mCAAmC,CAAC;AACtC,MAAM,CAAC,MAAM,gCAAgC,GAC3C,qCAAqC,CAAC;AACxC,MAAM,CAAC,MAAM,6BAA6B,GACxC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,yCAAyC,CAAC,CAAC;AACtE,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,WAAW,EAAE,CAAC","sourcesContent":["import type {\n RequestFailureKind,\n UploadMultipartBlobPartBytesRequest,\n} from \"@tearleads/api-client\";\nimport type {\n BlobContentKeyTarget,\n BlobEnvelopeHeaderRecord,\n BlobEnvelopeRecord,\n WriteHeader,\n} from \"@tearleads/crypto\";\nimport type {\n BlobAttachmentBindRequest,\n BlobAttachmentDetachRequest,\n BlobContentKeyBundleRequest,\n CompleteMultipartBlobStageRequest,\n InitiateMultipartBlobStageRequest,\n} from \"@tearleads/validators/request\";\nimport type {\n BlobAttachmentBindResponse,\n BlobAttachmentDetachResponse,\n BlobAttachmentSummary,\n CompleteMultipartBlobStageResponse,\n DocumentWriterProjectionResponse,\n InitiateMultipartBlobStageResponse,\n MultipartBlobStageStatusResponse,\n UploadMultipartBlobPartResponse,\n} from \"@tearleads/validators/response\";\nimport type { BlobByteSourceInput, BlobBytes } from \"../../../blobContracts\";\nimport type {\n ProjectionUserKeyResolver,\n ReferencedPrincipalPolicyWarmer,\n} from \"../../../keyingProjectionVerification\";\nimport type { ExecSql } from \"../../../sqlite/sqlSchema\";\nimport type { DocumentCreateAuthor } from \"../../shared/types\";\n\nexport const BLOB_CONTENT_RECORD_KEY_INFO_DOMAIN =\n \"tearleads.blob.content-record-key-info\";\nexport const BLOB_CONTENT_RECORD_AAD_DOMAIN =\n \"tearleads.blob.content-record-aad\";\nexport const BLOB_CONTENT_RECORD_NONCE_DOMAIN =\n \"tearleads.blob.content-record-nonce\";\nexport const BLOB_CONTENT_RECORD_HKDF_SALT: Uint8Array<ArrayBuffer> =\n new TextEncoder().encode(\"tearleads.blob.content-record-hkdf-salt\");\nexport const TEXT_ENCODER = new TextEncoder();\n\nexport interface BlobAttachmentRequestOptions {\n readonly expectedPaymentRequiredOrganizationId?: string | undefined;\n}\n\nexport interface BlobAttachmentApi {\n bindBlobAttachment(\n blobId: string,\n input: BlobAttachmentBindRequest,\n options?: BlobAttachmentRequestOptions,\n ): Promise<BlobAttachmentBindResponse | null>;\n clearWriterProjectionCaches?(): void;\n evictDocumentWriterProjection?(documentId: string): void;\n getRequestFailure?(input: {\n method: \"DELETE\" | \"GET\" | \"POST\" | \"PUT\";\n path: string;\n }): {\n readonly code?: string | undefined;\n readonly kind: RequestFailureKind;\n readonly message: string;\n readonly status: number | null;\n } | null;\n completeMultipartBlobStage(\n stageId: string,\n input: CompleteMultipartBlobStageRequest,\n ): Promise<CompleteMultipartBlobStageResponse | null>;\n getDocumentWriterProjection(\n documentId: string,\n ): Promise<DocumentWriterProjectionResponse | null>;\n getMultipartBlobStage(\n stageId: string,\n ): Promise<MultipartBlobStageStatusResponse | null>;\n initiateMultipartBlobStage(\n input: InitiateMultipartBlobStageRequest,\n ): Promise<InitiateMultipartBlobStageResponse | null>;\n uploadMultipartBlobPartBytes(\n stageId: string,\n partNumber: number,\n input: UploadMultipartBlobPartBytesRequest,\n ): Promise<UploadMultipartBlobPartResponse | null>;\n}\n\nexport interface BlobAttachmentDetachApi {\n detachBlobAttachment(\n blobId: string,\n bindingId: string,\n input: BlobAttachmentDetachRequest,\n options?: BlobAttachmentRequestOptions,\n ): Promise<BlobAttachmentDetachResponse | null>;\n getDocumentWriterProjection(\n documentId: string,\n ): Promise<DocumentWriterProjectionResponse | null>;\n}\n\nexport type { BlobContentKeyTarget };\n\n/**\n * Aliases of the crypto parser's own types. The parser is shared with the API,\n * so restating these shapes here would let the two drift apart silently.\n */\nexport type BlobEncryptedBytesRecord = BlobEnvelopeRecord;\n\n/**\n * The envelope as the header establishes it. Streaming readers hold this\n * before any ciphertext is read, so it has no `chunks`: an empty array there\n * would read as an empty object rather than an unread one.\n */\nexport type BlobEncryptedBytesHeader = BlobEnvelopeHeaderRecord;\n\nexport type BlobEncryptedChunk = BlobEnvelopeRecord[\"chunks\"][number];\n\nexport interface BlobSourceSnapshot {\n readonly byteLength: number;\n readonly chunkSha256: readonly string[];\n readonly chunkSize: number;\n readonly sha256: string;\n}\n\nexport interface BlobEncryptionPlan {\n readonly byteLength: number;\n readonly chunkSize: number;\n readonly metadataHash: string;\n readonly partCount: number;\n readonly plaintextByteLength: number;\n readonly plaintextSha256: string;\n readonly sha256: string;\n encryptPart(index: number): Promise<BlobBytes>;\n getPartByteLength(index: number): number;\n}\n\nexport interface DocumentManifestIdentity {\n documentId: string;\n manifestHash: string;\n organizationId: string;\n}\n\nexport interface BlobAttachmentMaterial {\n blobAccessManifestHash: string;\n contentKeyBundle: BlobContentKeyBundleRequest;\n encrypted: BlobEncryptionPlan;\n manifestIdentity: DocumentManifestIdentity;\n targetHash: string;\n targets: BlobContentKeyTarget[];\n writerProjection: DocumentWriterProjectionResponse;\n}\n\n/**\n * Progress for an in-flight multipart blob upload.\n */\nexport interface MultipartUploadProgress {\n readonly bytesTotal: number;\n readonly bytesUploaded: number;\n readonly partsCompleted: number;\n readonly partsTotal: number;\n}\n\nexport type MultipartUploadProgressListener = (\n progress: MultipartUploadProgress,\n) => void;\n\n/**\n * Fires once a multipart stage has been opened (or resumed), with its id and\n * plaintext chunk size. Callers persist both so a later attempt can reproduce\n * the same encrypted parts and resume instead of orphaning the partial upload.\n */\nexport type MultipartStageResolvedListener = (input: {\n readonly partSize: number;\n readonly stageId: string;\n}) => void | Promise<void>;\n\nexport interface UploadDocumentAttachmentInput {\n apiClient: BlobAttachmentApi;\n author: DocumentCreateAuthor;\n blobId?: string | undefined;\n bindingId?: string | undefined;\n bytes: BlobByteSourceInput;\n contentKey?: Uint8Array | undefined;\n contentKeyEpoch?: number | undefined;\n documentId: string;\n eventId?: string | undefined;\n execSql: ExecSql;\n expectedBindingId: string | null;\n isRemoteSyncBlocked?: ((organizationId: string) => boolean) | undefined;\n // A persisted nonce seed. The plaintext digest derives the envelope IV from\n // it, then each chunk derives a unique IV. Same-source retries reproduce the\n // encrypted parts; changed bytes cannot reuse their AES-GCM nonces.\n nonceSeed?: Uint8Array | undefined;\n multipart?: MultipartBlobUploadOptions | undefined;\n onMultipartProgress?: MultipartUploadProgressListener | undefined;\n onStageResolved?: MultipartStageResolvedListener | undefined;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n signedAt?: string | undefined;\n slotId: string;\n targetSecretKey: Uint8Array;\n warmReferencedPrincipalPolicies?: ReferencedPrincipalPolicyWarmer | undefined;\n writerProjection?: DocumentWriterProjectionResponse | undefined;\n}\n\nexport interface MultipartBlobUploadOptions {\n /** Exactly 5 MiB per plaintext encryption chunk; transport parts include envelope overhead. */\n readonly partSize: number;\n // The id of a previously opened stage to resume. When set, the upload refetches\n // that stage and skips parts the server already has instead of opening a new\n // one; if the stage is gone (expired), it falls back to a fresh stage.\n readonly resumeStageId?: string | undefined;\n readonly uploadConcurrency?: number | undefined;\n}\n\nexport interface UploadDocumentAttachmentResult {\n blobId: string;\n bindingId: string;\n request: BlobAttachmentBindRequest;\n response: BlobAttachmentBindResponse;\n sha256: string;\n byteLength: number;\n writeHeader: WriteHeader;\n writeHeaderHash: string;\n writerProjection: DocumentWriterProjectionResponse;\n}\n\nexport interface DetachDocumentAttachmentInput {\n apiClient: BlobAttachmentDetachApi;\n author: DocumentCreateAuthor;\n bindingId: string;\n blobId: string;\n documentId: string;\n eventId?: string | undefined;\n execSql: ExecSql;\n isRemoteSyncBlocked?: ((organizationId: string) => boolean) | undefined;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n signedAt?: string | undefined;\n slotId: string;\n stillCurrent?: (() => boolean) | undefined;\n warmReferencedPrincipalPolicies?: ReferencedPrincipalPolicyWarmer | undefined;\n writerProjection?: DocumentWriterProjectionResponse | undefined;\n}\n\nexport interface DetachDocumentAttachmentResult {\n request: BlobAttachmentDetachRequest;\n response: BlobAttachmentDetachResponse;\n writerProjection: DocumentWriterProjectionResponse;\n}\n\nexport interface DecryptDocumentAttachmentBlobInput {\n binding: BlobAttachmentSummary;\n encryptedBytes: BlobBytes;\n expectedDocumentId: string;\n expectedSlotId: string;\n execSql: ExecSql;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n targetSecretKey: Uint8Array;\n stillCurrent?: (() => boolean) | undefined;\n warmReferencedPrincipalPolicies?: ReferencedPrincipalPolicyWarmer | undefined;\n writerProjection: DocumentWriterProjectionResponse;\n}\n"]}
@@ -6,14 +6,50 @@ export async function applyStoredAttachmentRemoval(input) {
6
6
  const { localId, removal, tx } = input;
7
7
  const pendingMatch = and(eq(documentPendingAttachments.localId, localId), eq(documentPendingAttachments.slotId, removal.slotId), eq(documentPendingAttachments.storageKey, removal.storageKey));
8
8
  const localMatch = and(eq(documentAttachmentBlobProjection.localId, localId), eq(documentAttachmentBlobProjection.slotId, removal.slotId), eq(documentAttachmentBlobProjection.storageKey, removal.storageKey));
9
- await tx.delete(documentPendingAttachments).where(pendingMatch).run();
10
- // A pending-only slot leaves no detached marker to queue its bytes later.
9
+ const [pending] = await tx
10
+ .delete(documentPendingAttachments)
11
+ .where(pendingMatch)
12
+ .returning();
13
+ // A pending upload without a remote identity needs no detach marker.
11
14
  // Reclaim keeps any key that a marker or another row still references.
12
15
  await queueDocumentAttachmentStorageKeys(tx, [removal.storageKey]);
13
16
  if (removal.mode === "detach") {
17
+ const detachedAt = new Date().toISOString();
18
+ // The bind may have committed before its response (or settlement) was lost.
19
+ // Preserve its identity until sync confirms that the binding is detached.
20
+ if (pending?.uploadBlobId) {
21
+ const [marker] = await tx
22
+ .insert(documentAttachmentBlobProjection)
23
+ .values({
24
+ localId,
25
+ slotId: removal.slotId,
26
+ storageKey: removal.storageKey,
27
+ blobId: pending.uploadBlobId,
28
+ byteLength: pending.byteLength,
29
+ contentSha256: pending.contentSha256,
30
+ mimeType: pending.mimeType,
31
+ detachedAt,
32
+ updatedAt: detachedAt,
33
+ })
34
+ .onConflictDoUpdate({
35
+ target: [
36
+ documentAttachmentBlobProjection.localId,
37
+ documentAttachmentBlobProjection.slotId,
38
+ ],
39
+ set: {
40
+ blobId: pending.uploadBlobId,
41
+ detachedAt,
42
+ updatedAt: detachedAt,
43
+ },
44
+ setWhere: eq(documentAttachmentBlobProjection.storageKey, removal.storageKey),
45
+ })
46
+ .returning();
47
+ if (!marker)
48
+ throw new Error("Attachment slot changed during removal");
49
+ }
14
50
  await tx
15
51
  .update(documentAttachmentBlobProjection)
16
- .set({ detachedAt: new Date().toISOString() })
52
+ .set({ detachedAt })
17
53
  .where(and(localMatch, isNull(documentAttachmentBlobProjection.detachedAt)))
18
54
  .run();
19
55
  return;
@@ -1 +1 @@
1
- {"version":3,"file":"attachmentStagingPersistence.js","sourceRoot":"","sources":["attachmentStagingPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { and, eq, isNull } from \"drizzle-orm\";\nimport {\n documentAttachmentBlobProjection,\n documentPendingAttachments,\n} from \"../../../sqlite/schema\";\nimport type { ClientSQLiteTransactionScope } from \"../../../sqlite/sqlitePersistenceRuntime\";\nimport type { DocumentsPersistence } from \"../types\";\nimport { buildPendingAttachmentRow } from \"./attachmentRows\";\nimport { queueDocumentAttachmentStorageKeys } from \"./orphanSideRows\";\n\ntype AttachmentStaging = NonNullable<\n Parameters<\n DocumentsPersistence[\"commitDocumentMutation\"]\n >[1][\"attachmentStaging\"]\n>;\ntype AttachmentRemoval = NonNullable<\n Parameters<\n DocumentsPersistence[\"commitDocumentMutation\"]\n >[1][\"attachmentRemoval\"]\n>;\n\nexport async function applyStoredAttachmentRemoval(input: {\n localId: string;\n removal: AttachmentRemoval;\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n const { localId, removal, tx } = input;\n const pendingMatch = and(\n eq(documentPendingAttachments.localId, localId),\n eq(documentPendingAttachments.slotId, removal.slotId),\n eq(documentPendingAttachments.storageKey, removal.storageKey),\n );\n const localMatch = and(\n eq(documentAttachmentBlobProjection.localId, localId),\n eq(documentAttachmentBlobProjection.slotId, removal.slotId),\n eq(documentAttachmentBlobProjection.storageKey, removal.storageKey),\n );\n await tx.delete(documentPendingAttachments).where(pendingMatch).run();\n // A pending-only slot leaves no detached marker to queue its bytes later.\n // Reclaim keeps any key that a marker or another row still references.\n await queueDocumentAttachmentStorageKeys(tx, [removal.storageKey]);\n if (removal.mode === \"detach\") {\n await tx\n .update(documentAttachmentBlobProjection)\n .set({ detachedAt: new Date().toISOString() })\n .where(\n and(localMatch, isNull(documentAttachmentBlobProjection.detachedAt)),\n )\n .run();\n return;\n }\n await tx.delete(documentAttachmentBlobProjection).where(localMatch).run();\n}\n\nfunction assertAttachmentScope(\n localId: string,\n staging: AttachmentStaging,\n): void {\n const everyAttachmentMatchesScope = [\n ...staging.localAttachments,\n ...staging.pendingAttachments,\n ].every((attachment) => attachment.localId === localId);\n if (!everyAttachmentMatchesScope) {\n throw new Error(\"Attachment staging rows must share one document scope\");\n }\n}\n\nexport async function upsertStoredAttachmentStagingRows(input: {\n createdAt: string;\n localId: string;\n staging: AttachmentStaging;\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n const { createdAt, localId, staging, tx } = input;\n assertAttachmentScope(localId, staging);\n\n for (const attachment of staging.pendingAttachments) {\n const row = buildPendingAttachmentRow(attachment, createdAt);\n const [existing] = await tx\n .select({ storageKey: documentPendingAttachments.storageKey })\n .from(documentPendingAttachments)\n .where(\n and(\n eq(documentPendingAttachments.localId, localId),\n eq(documentPendingAttachments.slotId, attachment.slotId),\n ),\n )\n .limit(1);\n if (existing && existing.storageKey !== row.storageKey) {\n await queueDocumentAttachmentStorageKeys(tx, [existing.storageKey]);\n }\n await tx\n .insert(documentPendingAttachments)\n .values(row)\n .onConflictDoUpdate({\n target: [\n documentPendingAttachments.localId,\n documentPendingAttachments.slotId,\n ],\n set: {\n byteLength: row.byteLength,\n contentSha256: row.contentSha256,\n mimeType: row.mimeType,\n name: row.name,\n storageKey: row.storageKey,\n uploadBlobId: row.uploadBlobId,\n uploadContentKey: row.uploadContentKey,\n uploadContentKeyEpoch: row.uploadContentKeyEpoch,\n uploadIv: row.uploadIv,\n uploadPartSize: row.uploadPartSize,\n uploadPlaintextSha256: row.uploadPlaintextSha256,\n uploadStageId: row.uploadStageId,\n },\n })\n .run();\n }\n\n for (const attachment of staging.localAttachments) {\n const row = { ...attachment, updatedAt: createdAt };\n const [existing] = await tx\n .select({ storageKey: documentAttachmentBlobProjection.storageKey })\n .from(documentAttachmentBlobProjection)\n .where(\n and(\n eq(documentAttachmentBlobProjection.localId, localId),\n eq(documentAttachmentBlobProjection.slotId, attachment.slotId),\n ),\n )\n .limit(1);\n if (existing && existing.storageKey !== row.storageKey) {\n await queueDocumentAttachmentStorageKeys(tx, [existing.storageKey]);\n }\n await tx\n .insert(documentAttachmentBlobProjection)\n .values(row)\n .onConflictDoUpdate({\n target: [\n documentAttachmentBlobProjection.localId,\n documentAttachmentBlobProjection.slotId,\n ],\n set: {\n blobId: row.blobId,\n byteLength: row.byteLength,\n contentSha256: row.contentSha256,\n detachedAt: row.detachedAt,\n mimeType: row.mimeType,\n storageKey: row.storageKey,\n updatedAt: row.updatedAt,\n },\n })\n .run();\n }\n}\n"]}
1
+ {"version":3,"file":"attachmentStagingPersistence.js","sourceRoot":"","sources":["attachmentStagingPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { and, eq, isNull } from \"drizzle-orm\";\nimport {\n documentAttachmentBlobProjection,\n documentPendingAttachments,\n} from \"../../../sqlite/schema\";\nimport type { ClientSQLiteTransactionScope } from \"../../../sqlite/sqlitePersistenceRuntime\";\nimport type { DocumentsPersistence } from \"../types\";\nimport { buildPendingAttachmentRow } from \"./attachmentRows\";\nimport { queueDocumentAttachmentStorageKeys } from \"./orphanSideRows\";\n\ntype AttachmentStaging = NonNullable<\n Parameters<\n DocumentsPersistence[\"commitDocumentMutation\"]\n >[1][\"attachmentStaging\"]\n>;\ntype AttachmentRemoval = NonNullable<\n Parameters<\n DocumentsPersistence[\"commitDocumentMutation\"]\n >[1][\"attachmentRemoval\"]\n>;\n\nexport async function applyStoredAttachmentRemoval(input: {\n localId: string;\n removal: AttachmentRemoval;\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n const { localId, removal, tx } = input;\n const pendingMatch = and(\n eq(documentPendingAttachments.localId, localId),\n eq(documentPendingAttachments.slotId, removal.slotId),\n eq(documentPendingAttachments.storageKey, removal.storageKey),\n );\n const localMatch = and(\n eq(documentAttachmentBlobProjection.localId, localId),\n eq(documentAttachmentBlobProjection.slotId, removal.slotId),\n eq(documentAttachmentBlobProjection.storageKey, removal.storageKey),\n );\n const [pending] = await tx\n .delete(documentPendingAttachments)\n .where(pendingMatch)\n .returning();\n // A pending upload without a remote identity needs no detach marker.\n // Reclaim keeps any key that a marker or another row still references.\n await queueDocumentAttachmentStorageKeys(tx, [removal.storageKey]);\n if (removal.mode === \"detach\") {\n const detachedAt = new Date().toISOString();\n // The bind may have committed before its response (or settlement) was lost.\n // Preserve its identity until sync confirms that the binding is detached.\n if (pending?.uploadBlobId) {\n const [marker] = await tx\n .insert(documentAttachmentBlobProjection)\n .values({\n localId,\n slotId: removal.slotId,\n storageKey: removal.storageKey,\n blobId: pending.uploadBlobId,\n byteLength: pending.byteLength,\n contentSha256: pending.contentSha256,\n mimeType: pending.mimeType,\n detachedAt,\n updatedAt: detachedAt,\n })\n .onConflictDoUpdate({\n target: [\n documentAttachmentBlobProjection.localId,\n documentAttachmentBlobProjection.slotId,\n ],\n set: {\n blobId: pending.uploadBlobId,\n detachedAt,\n updatedAt: detachedAt,\n },\n setWhere: eq(\n documentAttachmentBlobProjection.storageKey,\n removal.storageKey,\n ),\n })\n .returning();\n if (!marker) throw new Error(\"Attachment slot changed during removal\");\n }\n await tx\n .update(documentAttachmentBlobProjection)\n .set({ detachedAt })\n .where(\n and(localMatch, isNull(documentAttachmentBlobProjection.detachedAt)),\n )\n .run();\n return;\n }\n await tx.delete(documentAttachmentBlobProjection).where(localMatch).run();\n}\n\nfunction assertAttachmentScope(\n localId: string,\n staging: AttachmentStaging,\n): void {\n const everyAttachmentMatchesScope = [\n ...staging.localAttachments,\n ...staging.pendingAttachments,\n ].every((attachment) => attachment.localId === localId);\n if (!everyAttachmentMatchesScope) {\n throw new Error(\"Attachment staging rows must share one document scope\");\n }\n}\n\nexport async function upsertStoredAttachmentStagingRows(input: {\n createdAt: string;\n localId: string;\n staging: AttachmentStaging;\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n const { createdAt, localId, staging, tx } = input;\n assertAttachmentScope(localId, staging);\n\n for (const attachment of staging.pendingAttachments) {\n const row = buildPendingAttachmentRow(attachment, createdAt);\n const [existing] = await tx\n .select({ storageKey: documentPendingAttachments.storageKey })\n .from(documentPendingAttachments)\n .where(\n and(\n eq(documentPendingAttachments.localId, localId),\n eq(documentPendingAttachments.slotId, attachment.slotId),\n ),\n )\n .limit(1);\n if (existing && existing.storageKey !== row.storageKey) {\n await queueDocumentAttachmentStorageKeys(tx, [existing.storageKey]);\n }\n await tx\n .insert(documentPendingAttachments)\n .values(row)\n .onConflictDoUpdate({\n target: [\n documentPendingAttachments.localId,\n documentPendingAttachments.slotId,\n ],\n set: {\n byteLength: row.byteLength,\n contentSha256: row.contentSha256,\n mimeType: row.mimeType,\n name: row.name,\n storageKey: row.storageKey,\n uploadBlobId: row.uploadBlobId,\n uploadContentKey: row.uploadContentKey,\n uploadContentKeyEpoch: row.uploadContentKeyEpoch,\n uploadIv: row.uploadIv,\n uploadPartSize: row.uploadPartSize,\n uploadPlaintextSha256: row.uploadPlaintextSha256,\n uploadStageId: row.uploadStageId,\n },\n })\n .run();\n }\n\n for (const attachment of staging.localAttachments) {\n const row = { ...attachment, updatedAt: createdAt };\n const [existing] = await tx\n .select({ storageKey: documentAttachmentBlobProjection.storageKey })\n .from(documentAttachmentBlobProjection)\n .where(\n and(\n eq(documentAttachmentBlobProjection.localId, localId),\n eq(documentAttachmentBlobProjection.slotId, attachment.slotId),\n ),\n )\n .limit(1);\n if (existing && existing.storageKey !== row.storageKey) {\n await queueDocumentAttachmentStorageKeys(tx, [existing.storageKey]);\n }\n await tx\n .insert(documentAttachmentBlobProjection)\n .values(row)\n .onConflictDoUpdate({\n target: [\n documentAttachmentBlobProjection.localId,\n documentAttachmentBlobProjection.slotId,\n ],\n set: {\n blobId: row.blobId,\n byteLength: row.byteLength,\n contentSha256: row.contentSha256,\n detachedAt: row.detachedAt,\n mimeType: row.mimeType,\n storageKey: row.storageKey,\n updatedAt: row.updatedAt,\n },\n })\n .run();\n }\n}\n"]}
package/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export { type ApiRequestCounts, type ApiRequestEndpointCounts, ApiRequestMetrics, type ApiRequestMetricsSnapshot, } from "./internal/api-client/index.js";
1
2
  export type { AbandonOrganizationPolicyMutationInput, AddOrganizationGroupUserInput, BlobInfo, BlobInfoAttachmentKind, BlobInfoDocumentReference, BlobInfoInput, BlobInfoList, BlobInfoSort, BlobInfoSortDirection, BlobInfoSortKey, BrowserLocalKeyringOptions, ClientDatabaseOptions, ContainerContents, ContainerContentsContextValue, ContainerContentsStore, ContainerContentsStoreOptions, ContainerDocumentLinkInput, ContainerDocumentLinks, ContainerDocumentObjectSyncState, ContainerDocumentObjectSyncStatus, ContainerDocumentQueries, ContainerDocumentSidebarRow, ContainerInfo, ContainerInfoInput, ContainerItemRow, ContainerItemSort, ContainerItemSortDirection, ContainerItemSortKey, ContainerNode, ContainerShareAccessLevel, CreateOrganizationOptions, DatabaseListener, DatabaseOptions, DatabaseSnapshot, DatabaseStatus, DeviceFirst, DeviceFirstContainerContents, DiscardUnreadableOrganizationPolicyMutationInput, DocumentAttachmentStatus, DocumentAttachmentUpload, DocumentAttributionRangesInput, DocumentAttributionRangesPage, DocumentContextValue, DocumentInfo, DocumentInfoInput, DocumentList, DocumentSort, DocumentSortDirection, DocumentSortKey, DocumentStore, DocumentSubscriptionOptions, Documents, DocumentsRuntime, DomainScope, FileSaver, Identity, IdentityGenerationResult, IdentityKeyPackage, IdentityListener, IdentityOptions, IdentitySnapshot, ImportedOrganizationUser, IndexedDbLocalKeyringManifestStoreOptions, IndexedDbWrappingKeyKeystoreOptions, LinkDocumentToContainerInput, ListDocumentsInput, LocalKeyPurpose, LocalKeyring, LocalKeyringManifest, LocalKeyringManifestFormat, LocalKeyringManifestStorage, LocalKeyringManifestStore, LocalKeyringOptions, LocalKeyringPinCode, LocalKeyringScope, LocalKeyringSession, LocalOrganizationSummary, LocalProjectionReconciledDelta, LocalProjectionSnapshot, LocalProjectionView, LocalSecretContext, LocalStorageLocalKeyringManifestStoreOptions, Logger, MergeDocumentSummary, MoveDocumentToContainerInput, NetworkListener, NetworkMode, NetworkStatusSource, NormalizedLocalKeyringScope, OpenContainerDocumentInput, OpenDocumentInput, OpenDocumentOptions, OrganizationBilling, OrganizationBillingHistory, OrganizationBillingHistoryEntry, OrganizationBillingManagementUrl, OrganizationBillingView, OrganizationContainerGrant, OrganizationContainerGrants, OrganizationDataUsage, OrganizationDirectory, OrganizationDirectoryAndGroups, OrganizationDirectoryUser, OrganizationGrantRef, OrganizationGroupContainer, OrganizationGroupContainers, OrganizationGroupDetails, OrganizationGroupMember, OrganizationGroupMembers, OrganizationGroupMutationReceipt, OrganizationGroupPolicyHistory, OrganizationGroupSummary, OrganizationPolicyGrantChange, OrganizationPolicyGroupChange,
2
3
  /** Verified organization display page with an optional older-page cursor. */
3
4
  OrganizationPolicyHistory, OrganizationPolicyHistoryEntry, OrganizationProfile, Organizations, OrganizationUserDetail, PendingWriteQueueItem, PendingWriteQueueItemStatus, PendingWriteQueueObjectKind, PendingWriteQueueOperation, PendingWriteQueueOperationKind, PendingWriteQueueOperationStatus, PersistedDocumentListener, PinCodeBrowserLocalKeyringOptions, PinCodeWrappingKeyKeystoreOptions, PurgeDocumentInput, PurgeDocumentResult, ReconciliationService, RemoveOrganizationGroupUserInput, ResolvedUserIdentity, SaveFileRequest, SecurityIncident, SecurityIncidentListener, SecurityIncidentObjectKind, SecurityIncidents, Session, SessionContext, SessionCreateOrganizationResult, SessionListener, SessionRecoverOrganizationResult, SessionRegistrationRefusal, SessionRegistrationResult, SessionSnapshot, SetActiveDocumentContainerInput, SetLinkedContainerIdsForDocument, UnlinkDocumentFromContainerInput, UnwrapLocalSecretInput, UserIdentities, UserSession, WrapLocalSecretInput, WrappedLocalSecretEnvelope, WrappedLocalSecretFormat, WrappingKeyHandle, WrappingKeyKeystore, WrappingKeyMaterialStorage, } from "./client/index.js";
package/index.js CHANGED
@@ -1,3 +1,7 @@
1
+ // Current SDK contract: unified device-first handles and atomic revision checks.
2
+ // Authority loaders take exact heads, including cached citations. Obsolete
3
+ // adapters are excluded; authored policy journals survive restart. See workflows/README.md.
4
+ export { ApiRequestMetrics, } from "./internal/api-client/index.js";
1
5
  export { Blobs, createBrowserLocalKeyring, createBrowserLocalKeyringManifestStore, createBrowserNetworkStatusSource, createContainerDocumentObjectSyncState, createDomainScope, createIndexedDbLocalKeyringManifestStore, createIndexedDbWrappingKeyKeystore, createLocalKeyring, createLocalStorageLocalKeyringManifestStore, createMemoryLocalKeyringManifestStore, createMemoryWrappingKeyKeystore, createPinCodeBrowserLocalKeyring, createPinCodeWrappingKeyKeystore, createRevenueCatPurchases, createUnavailableDirectCheckout, createUnavailablePurchases, createWebViewLocalKeyring, Database, DEFAULT_DOCUMENT_ID, DirectCheckoutUnavailableError, decodeLocalKeyringSqliteKey, Events, encodeLocalKeyringSqliteKey, enqueueReconciliationForEvents, IDENTITY_KEY_PACKAGE_FORMAT, isPinCodeWrappedLocalSecretEnvelope, LOCAL_KEYRING_MANIFEST_FORMAT, localKeyringScopeKey, Network, normalizeLocalKeyringScope, PurchaseAbortedError, PurchaseAlreadyOwnedError, PurchaseCancelledError, PurchaseIdentityPendingError, PurchaseProviderStalledError, PurchasesUnavailableError, PurgedOrganizationRecoveryBillingRequiredError, parseLocalKeyringManifest, SyncBillingGate, serializeLocalKeyringManifest, Tearleads, WRAPPED_LOCAL_SECRET_FORMAT, } from "./client/index.js";
2
6
  export { addDocumentAttachments, blobByteSourceInputLength, createBlobByteSource, createDocumentProjectorRegistry, createDocumentSignerDeviceId, DEFAULT_DOCUMENT_ACCESS_EPOCH, DEFAULT_DOCUMENT_KIND, defaultDocumentProjectorRegistry, deriveStoredDocumentTitle, ensureDocumentAttachmentStructure, getDocumentAttachments, getDocumentClientProjectionTables, getStoredDocumentTypeLabel, getUntitledDocumentTitle, initializeStoredDocumentKind, projectStoredDocumentState, readBlobByteSource, readStoredDocumentState, readStringDocumentField, resolveOpIdAttribution, sameDocumentAttachments, summarizeDocumentContributors, writerByPeerId, writeStoredDocumentFields, } from "./documents.js";
3
7
  export { createContainerContentsStore, createContainerContentsStoreState, createContainerContentsStoreSyncAgent, getOrCreateContainerContentsStore, isSystemContainerNode, requestContainerContentsDocumentPriming, subscribeToContainerContentsStore, updateContainerContentsSnapshot, updateContainerContentsStoreRuntime, } from "./stores/container-contents/index.js";