From c57b7ba0700e075c57093cf72f1e7d2857c42b17 Mon Sep 17 00:00:00 2001 From: Robert Fidler Date: Wed, 14 Oct 2026 12:57:40 -0400 Subject: [PATCH 1/2] [monorepo] Use canonical object wrapping with Oxfmt collapse --- .syncpackrc.json | 11 +- .vscode/extensions.json | 4 +- .vscode/settings.json | 25 +- apps/icon-lab/scripts/extract-assets.ts | 27 +- apps/icon-lab/scripts/generate-embeddings.ts | 11 +- apps/icon-lab/src/App.tsx | 10 +- .../src/components/DraftMappingBrowser.tsx | 9 +- .../src/components/EmbeddingScatter.tsx | 7 +- apps/icon-lab/src/shared/catalog.test.ts | 7 +- .../src/shared/mappingFeedback.test.ts | 24 +- apps/icon-lab/src/shared/math.ts | 5 +- apps/icon-lab/src/shared/types.ts | 24 +- apps/icon-lab/tsconfig.json | 4 +- apps/icon-lab/vite.config.ts | 21 +- apps/issue-tracker/src/app/IssueTracker.tsx | 127 +- apps/issue-tracker/src/app/auth.ts | 4 +- apps/issue-tracker/src/app/profiles.ts | 35 +- .../src/components/FormControls.tsx | 15 +- apps/issue-tracker/src/ontology/config.ts | 27 +- .../src/ontology/generated/live.ts | 5 +- .../src/ontology/generated/types.ts | 42 +- apps/issue-tracker/src/ontology/ontology.ts | 568 +----- apps/issue-tracker/src/ontology/user.ts | 9 +- apps/issue-tracker/src/router.tsx | 5 +- apps/issue-tracker/src/routes/__root.tsx | 10 +- .../routes/api.remote-ontology.$endpoint.ts | 12 +- .../src/routes/auth.callback.tsx | 11 +- apps/issue-tracker/src/routes/index.tsx | 5 +- .../src/routes/project.$projectId.tsx | 5 +- apps/issue-tracker/src/server/auth.ts | 28 +- apps/issue-tracker/tsconfig.json | 4 +- apps/issue-tracker/vite.config.ts | 7 +- .../src/components/CommandBar.tsx | 15 +- .../src/ontology/OntologyProvider.tsx | 17 +- apps/ontology-explorer/src/router.tsx | 5 +- apps/ontology-explorer/src/routes/__root.tsx | 9 +- .../src/routes/action-types.$actionType.tsx | 4 +- apps/ontology-explorer/src/routes/index.tsx | 4 +- .../src/routes/link-types.$linkType.tsx | 4 +- .../src/routes/object-types.$objectType.tsx | 4 +- apps/ontology-explorer/tsconfig.json | 4 +- apps/remote-notes/src/notes/NotesViews.tsx | 24 +- .../src/ontology/generated/live.ts | 5 +- .../src/ontology/generated/types.ts | 38 +- apps/remote-notes/src/ontology/ontology.ts | 34 +- apps/remote-notes/src/router.tsx | 5 +- .../routes/api.remote-ontology.$endpoint.ts | 10 +- apps/remote-notes/src/routes/index.tsx | 4 +- .../remote-notes/src/routes/notes.$noteId.tsx | 4 +- apps/remote-notes/tsconfig.json | 4 +- .../src/app/TaskManager.tsx | 61 +- .../src/app/salesforce.ts | 25 +- .../src/ontology/config.ts | 7 +- .../src/ontology/generated/types.ts | 41 +- .../src/ontology/ontology.test.ts | 10 +- .../src/ontology/ontology.ts | 1773 ++++------------- apps/salesforce-task-manager/src/router.tsx | 5 +- .../src/routes/__root.tsx | 9 +- .../src/routes/auth.callback.tsx | 5 +- apps/salesforce-task-manager/tsconfig.json | 4 +- apps/salesforce-task-manager/vite.config.ts | 30 +- old/README.md | 10 +- old/apollo/src/Apollo.test.ts | 10 +- old/apollo/src/Apollo.ts | 103 +- old/apollo/tsconfig.json | 4 +- .../src/schema/ExecutableFoundrySchema.ts | 11 +- .../src/schema/FoundrySchema.ts | 4 +- .../src/schema/ListTypes.ts | 12 +- .../src/schema/admin/UserQueryFields.ts | 10 +- .../src/schema/admin/UserType.ts | 16 +- .../src/schema/filters/StringFilterType.ts | 28 +- .../src/schema/nodes/NodeField.ts | 4 +- .../src/schema/nodes/NodeHandlers.ts | 4 +- .../src/schema/nodes/NodeInterface.ts | 11 +- .../schema/ontology-meta/ActionTypeType.ts | 11 +- .../src/schema/ontology/ActionField.ts | 4 +- .../src/schema/ontology/ObjectArgumentType.ts | 4 +- .../src/schema/ontology/ObjectLinkField.ts | 15 +- .../src/schema/ontology/ObjectListField.ts | 20 +- .../src/schema/ontology/ObjectListStep.ts | 23 +- .../src/schema/ontology/ObjectListTypes.ts | 14 +- .../schema/ontology/ObjectPropertyField.ts | 4 +- .../src/schema/ontology/ObjectQueryField.ts | 10 +- .../schema/ontology/ObjectSearchQueryField.ts | 9 +- .../schema/ontology/ObjectSetFilterType.ts | 16 +- .../src/schema/ontology/ObjectSetLinkField.ts | 10 +- .../src/schema/ontology/ObjectSetType.ts | 6 +- .../src/schema/ontology/ObjectType.ts | 7 +- .../schema/ontology/ObjectUserLinkField.ts | 4 +- .../schema/ontology/OntologyQueryFields.ts | 4 +- .../src/schema/ontology/getObjectLoader.ts | 6 +- .../src/schema/utils/Schemas.ts | 5 +- old/foundry-graphql-schema/tsconfig.json | 5 +- old/foundry-graphql-server/src/index.ts | 10 +- old/foundry-graphql-server/tsconfig.json | 5 +- old/foundry-graphql-server/vite.config.ts | 7 +- old/foundry-graphql/src/relay-local/index.ts | 7 +- old/foundry-graphql/tsconfig.json | 5 +- old/osdk-react-todo-app/src/App.tsx | 11 +- old/osdk-react-todo-app/src/main.tsx | 20 +- old/osdk-react-todo-app/vite.config.ts | 4 +- old/osdk-react/src/ai/useChat.ts | 10 +- .../src/attachments/useAttachmentMetadata.ts | 5 +- .../src/attachments/useUploadAttachment.ts | 5 +- old/osdk-react/src/ontology/ObjectList.ts | 4 +- .../src/ontology/object-set/ObjectSet.ts | 6 +- old/osdk-react/src/useAction.ts | 6 +- old/osdk-react/src/useAggregations.ts | 9 +- old/osdk-react/src/useLiveObjectSet.ts | 4 +- old/osdk-react/src/useObject.ts | 4 +- old/osdk-react/src/useObjects.ts | 20 +- old/osdk-utils/src/ActionError.ts | 12 +- old/osdk-utils/tsconfig.json | 5 +- .../createBetterAuthConnectionAdapter.test.ts | 44 +- .../src/createBetterAuthConnectionAdapter.ts | 15 +- packages/better-auth/src/partyStack.test.ts | 72 +- packages/better-auth/src/partyStack.ts | 10 +- packages/better-auth/src/partyStackClient.ts | 4 +- .../better-auth/src/sessionSelection.test.ts | 8 +- packages/better-auth/tsconfig.json | 5 +- packages/blobs/src/createBlobManager.ts | 25 +- .../blobs/src/gc/collectBlobGarbage.test.ts | 51 +- packages/blobs/src/gc/collectBlobGarbage.ts | 8 +- packages/blobs/src/index.test.ts | 200 +- .../src/metadata/measureBlobDimensions.ts | 5 +- .../blobs/src/store/createBlobStore.test.ts | 144 +- packages/blobs/src/store/createBlobStore.ts | 52 +- packages/blobs/src/types.ts | 13 +- packages/blobs/tsconfig.json | 5 +- packages/build-config/README.md | 4 +- packages/build-config/src/index.test.ts | 6 +- packages/build-config/src/index.ts | 4 +- packages/build-config/tsconfig.json | 4 +- packages/build-config/tsconfig/app.json | 4 +- packages/build-config/tsconfig/react.json | 6 +- .../src/createConnectionManager.test.ts | 69 +- .../src/createConnectionManager.ts | 19 +- .../src/createConnectionMonitor.ts | 4 +- packages/connections/src/types.ts | 8 +- .../src/withHttpRetryHandling.test.ts | 74 +- .../connections/src/withHttpRetryHandling.ts | 8 +- packages/connections/tsconfig.json | 5 +- .../src/LockBroadcastCoordination.test.ts | 36 +- .../src/LockBroadcastCoordination.ts | 41 +- .../src/SingleProcessCoordination.test.ts | 88 +- .../coordination/src/effection/runInScope.ts | 10 +- .../src/effection/useBroadcastChannel.test.ts | 16 +- .../src/effection/useMessagePort.ts | 5 +- .../src/effection/useWebLock.test.ts | 14 +- .../coordination/src/effection/useWebLock.ts | 15 +- .../src/effection/waitForAbort.ts | 4 +- packages/coordination/src/host-core.ts | 4 +- packages/coordination/src/internal.ts | 6 +- .../SharedWorkerCoordination.test.ts | 49 +- .../SharedWorkerCoordinationClient.ts | 9 +- .../SharedWorkerCoordinationHost.ts | 15 +- packages/coordination/src/types.test.ts | 52 +- packages/coordination/tsconfig.json | 5 +- packages/db-indexeddb-persistence/README.md | 5 +- ...dexedDBPersistenceAdapter.browser.bench.ts | 17 +- .../src/IndexedDBPersistenceAdapter.test.ts | 167 +- .../src/IndexedDBPersistenceAdapter.ts | 100 +- .../src/contracts/persistenceContract.ts | 282 +-- .../src/indexeddb-persistence.ts | 11 +- .../db-indexeddb-persistence/tsconfig.json | 5 +- packages/durable-object-ontology/README.md | 6 +- .../fixtures/worker.ts | 33 +- packages/durable-object-ontology/src/index.ts | 48 +- .../test/index.test.ts | 11 +- .../durable-object-ontology/test/worker.ts | 80 +- .../durable-object-ontology/vite.config.ts | 8 +- .../wrangler.fixture.jsonc | 23 +- .../durable-object-ontology/wrangler.jsonc | 23 +- .../errors/src/UnauthenticatedError.test.ts | 7 +- packages/errors/src/UnauthenticatedError.ts | 5 +- packages/errors/tsconfig.json | 5 +- .../src/createExpoBrowserAuthentication.ts | 4 +- .../expo-runtime/src/createExpoRuntime.ts | 12 +- packages/expo-runtime/tsconfig.json | 5 +- packages/foundry-client/src/client.test.ts | 5 +- packages/foundry-client/src/client.ts | 28 +- .../src/getTokenDetails.test.ts | 7 +- packages/foundry-client/tsconfig.json | 5 +- .../src/useObjectSetWatcherConnection.test.ts | 17 +- .../src/useObjectSetWatcherConnection.ts | 17 +- .../src/useObjectSetWatcherManager.ts | 6 +- packages/foundry-ontology/README.md | 5 +- .../adapter/convertLoadSubsetOptions.test.ts | 20 +- .../src/adapter/convertLoadSubsetOptions.ts | 11 +- ...reateFoundryOntologyBackendAdapter.test.ts | 402 +--- .../createFoundryOntologyBackendAdapter.ts | 85 +- .../adapter/foundryActionValidation.test.ts | 134 +- .../src/adapter/foundryActionValidation.ts | 28 +- .../src/adapter/foundryCodec.test.ts | 60 +- .../src/adapter/foundryCodec.ts | 31 +- .../src/adapter/foundryMediaId.ts | 14 +- .../adapter/objectCollectionOptions.test.ts | 361 +--- .../src/adapter/objectCollectionOptions.ts | 42 +- .../applyAttachmentConstraintOverrides.ts | 19 +- .../src/config/connectionPull.test.ts | 81 +- .../foundry-ontology/src/config/index.test.ts | 78 +- packages/foundry-ontology/src/config/index.ts | 18 +- .../foundry-ontology/src/connection.test.ts | 54 +- packages/foundry-ontology/src/connection.ts | 18 +- .../createFoundryBackendInstallation.test.ts | 49 +- .../createFoundryBackendInstallation.ts | 9 +- .../src/meta/actionTypeCollectionOptions.ts | 20 +- .../src/meta/actionTypeIdQuery.test.ts | 58 +- ...convertActionTypeLoadSubsetOptions.test.ts | 75 +- .../convertActionTypeLoadSubsetOptions.ts | 31 +- .../src/meta/convertMetaActionType.test.ts | 528 +---- .../src/meta/convertMetaActionType.ts | 163 +- .../src/meta/convertMetaLinkType.test.ts | 12 +- .../src/meta/convertMetaObjectType.test.ts | 72 +- .../src/meta/convertMetaObjectType.ts | 14 +- .../meta/convertMetaQueryFunctionType.test.ts | 32 +- .../src/meta/convertMetaQueryFunctionType.ts | 5 +- .../src/meta/convertMetaTypeDef.ts | 32 +- .../meta/convertOmsActionParameterMetadata.ts | 53 +- ...eFoundryMetaOntologyBackendAdapter.test.ts | 5 +- .../src/meta/entityCollectionOptions.test.ts | 14 +- .../src/meta/entityCollectionOptions.ts | 14 +- .../meta/loadActionTypeOmsMetadata.test.ts | 72 +- .../src/meta/loadActionTypeOmsMetadata.ts | 32 +- .../queryFunctionTypeCollectionOptions.ts | 5 +- .../foundry-ontology/src/stagedWrites.test.ts | 29 +- packages/foundry-ontology/src/stagedWrites.ts | 25 +- .../users/createFoundryAdminOntologyRoute.ts | 23 +- .../createFoundryUsersIntegration.test.ts | 29 +- .../users/createFoundryUsersIntegration.ts | 6 +- .../foundry-ontology/src/users/foundryUser.ts | 26 +- .../src/users/userCollectionOptions.test.ts | 5 +- .../src/users/userCollectionOptions.ts | 8 +- packages/icons-blueprint/src/index.ts | 11 +- packages/icons-blueprint/src/react.test.tsx | 11 +- packages/icons-expo/src/index.test.ts | 15 +- .../icons-salesforce-lightning/src/index.ts | 11 +- .../icons-salesforce-lightning/tsconfig.json | 5 +- packages/icons/src/index.test.ts | 10 +- packages/icons/tsconfig.json | 5 +- .../src/NodeFileSystemBlobBytesStore.test.ts | 12 +- .../src/NodeFileSystemBlobBytesStore.ts | 4 +- .../node-runtime/src/NodeSecretStore.test.ts | 16 +- .../createNodeBrowserAuthentication.test.ts | 20 +- .../src/createNodeBrowserAuthentication.ts | 8 +- .../src/createNodeRuntime.test.ts | 39 +- .../node-runtime/src/createNodeRuntime.ts | 26 +- packages/node-runtime/tsconfig.json | 5 +- ...createClientCredentialsOAuthClient.test.ts | 19 +- .../src/createClientCredentialsOAuthClient.ts | 31 +- .../oauth/src/createPublicOAuthClient.test.ts | 11 +- packages/oauth/src/createPublicOAuthClient.ts | 50 +- packages/oauth/src/storage.ts | 4 +- packages/oauth/src/types.ts | 5 +- packages/oauth/tsconfig.json | 5 +- .../src/OntologyDevtools.test.tsx | 57 +- .../src/OntologyDevtools.tsx | 67 +- packages/ontology/src/cli/generate.ts | 9 +- packages/ontology/src/cli/index.test.ts | 11 +- packages/ontology/src/cli/index.ts | 4 +- packages/ontology/src/cli/pull.test.ts | 70 +- .../src/examples/blog.live-queries.ts | 12 +- packages/ontology/src/examples/blog.ts | 203 +- .../ontology/src/examples/generated/live.ts | 5 +- .../ontology/src/examples/generated/types.ts | 32 +- packages/ontology/src/generate/builders.ts | 9 +- packages/ontology/src/generate/constants.ts | 13 +- packages/ontology/src/generate/live.test.ts | 8 +- packages/ontology/src/generate/live.ts | 15 +- .../ontology/src/generate/ontology.test.ts | 34 +- packages/ontology/src/generate/ontology.ts | 34 +- packages/ontology/src/generate/types.test.ts | 30 +- packages/ontology/src/generate/types.ts | 30 +- .../src/generate/utils/buildJsDocs.ts | 5 +- packages/ontology/src/infer.test.ts | 33 +- packages/ontology/src/infer.ts | 11 +- .../createMetaOntologyConfiguration.test.ts | 12 +- .../createMetaOntologyConfiguration.ts | 5 +- .../createOntologyBackendInstallation.test.ts | 141 +- .../createOntologyBackendInstallation.ts | 49 +- packages/ontology/src/ir/generated/types.ts | 138 +- packages/ontology/src/ir/schema.ts | 331 +-- packages/ontology/src/ir/validate.test.ts | 206 +- packages/ontology/src/ir/validate.ts | 59 +- packages/ontology/src/json/index.test.ts | 70 +- packages/ontology/src/lenses.test.ts | 32 +- packages/ontology/src/lenses.ts | 6 +- .../ontology/src/live/LiveOntology.test.ts | 141 +- packages/ontology/src/live/LiveOntology.ts | 15 +- .../src/live/OntologyBackendAdapter.ts | 18 +- .../actions/createLiveOntologyAction.test.ts | 88 +- .../live/actions/createLiveOntologyAction.ts | 6 +- .../live/actions/createLiveOntologyActions.ts | 25 +- .../actions/prepareActionParameters.test.ts | 75 +- .../live/actions/prepareActionParameters.ts | 98 +- .../attachments/attachmentMetadata.test.ts | 46 +- .../src/live/attachments/attachmentSources.ts | 29 +- .../createLiveOntologyAttachments.test.ts | 109 +- .../createLiveOntologyAttachments.ts | 51 +- packages/ontology/src/live/expression.test.ts | 133 +- packages/ontology/src/live/expression.ts | 54 +- .../src/live/mutators/OntologyEdits.test.ts | 38 +- .../src/live/mutators/OntologyEdits.ts | 21 +- .../src/live/mutators/createMutatorTx.test.ts | 114 +- .../src/live/mutators/createMutatorTx.ts | 4 +- .../src/live/mutators/runOptimisticAction.ts | 6 +- ...createLiveOntologyObjectCollection.test.ts | 82 +- .../live/outbox/createOntologyOutbox.test.ts | 374 +--- .../src/live/outbox/createOntologyOutbox.ts | 34 +- .../src/live/outbox/leaderExecutor.ts | 22 +- .../ontology/src/live/outbox/outboxService.ts | 17 +- .../src/live/outbox/projectionManager.ts | 5 +- .../ontology/src/live/outbox/repository.ts | 17 +- packages/ontology/src/live/outbox/service.ts | 16 +- packages/ontology/src/live/outbox/types.ts | 13 +- .../createLocalMetaOntologyBackendAdapter.ts | 5 +- packages/ontology/src/meta/generated/live.ts | 5 +- packages/ontology/src/meta/generated/types.ts | 138 +- packages/ontology/src/meta/ontology.test.ts | 14 +- packages/ontology/src/meta/ontology.ts | 13 +- packages/ontology/src/meta/pull.ts | 8 +- packages/ontology/src/utils/links.test.ts | 36 +- packages/ontology/src/utils/links.ts | 41 +- packages/ontology/src/utils/paths.test.ts | 12 +- packages/ontology/src/utils/targets.ts | 5 +- packages/ontology/src/utils/types.test.ts | 38 +- packages/ontology/src/utils/uncertain.test.ts | 9 +- packages/ontology/src/utils/uncertain.ts | 18 +- packages/ontology/src/utils/values.ts | 6 +- packages/ontology/tsconfig.json | 5 +- packages/remote-ontology/src/client.test.ts | 174 +- packages/remote-ontology/src/client.ts | 62 +- packages/remote-ontology/src/errors.test.ts | 26 +- packages/remote-ontology/src/errors.ts | 10 +- packages/remote-ontology/src/http.ts | 8 +- packages/remote-ontology/src/protocol.test.ts | 164 +- packages/remote-ontology/src/protocol.ts | 70 +- .../src/securedOntology.test.ts | 178 +- .../remote-ontology/src/securedOntology.ts | 114 +- packages/remote-ontology/src/server.test.ts | 254 +-- packages/remote-ontology/src/server.ts | 121 +- packages/remote-ontology/tsconfig.json | 5 +- .../createPersistedCollectionCoordinator.ts | 51 +- .../src/utils/createLocalCollection.test.ts | 116 +- packages/runtime/src/utils/defineRuntime.ts | 6 +- packages/runtime/tsconfig.json | 5 +- .../scripts/create-local-session.mjs | 5 +- packages/salesforce-client/src/client.test.ts | 146 +- packages/salesforce-client/src/client.ts | 69 +- packages/salesforce-client/src/types.ts | 29 +- packages/salesforce-client/tsconfig.json | 5 +- packages/salesforce-ontology/README.md | 38 +- .../salesforce-ontology/src/actionMetadata.ts | 27 +- .../adapter/convertLoadSubsetOptions.test.ts | 25 +- .../src/adapter/convertLoadSubsetOptions.ts | 5 +- ...teSalesforceOntologyBackendAdapter.test.ts | 199 +- .../createSalesforceOntologyBackendAdapter.ts | 9 +- .../adapter/objectCollectionOptions.test.ts | 58 +- .../src/adapter/objectCollectionOptions.ts | 20 +- .../src/adapter/salesforceCodec.test.ts | 12 +- .../src/adapter/salesforceCodec.ts | 5 +- .../src/config/index.test.ts | 80 +- .../salesforce-ontology/src/config/index.ts | 14 +- .../src/connection.test.ts | 79 +- .../salesforce-ontology/src/connection.ts | 54 +- ...reateSalesforceBackendInstallation.test.ts | 37 +- .../createSalesforceBackendInstallation.ts | 16 +- .../meta/actionTypeCollectionOptions.test.ts | 139 +- .../src/meta/actionTypeCollectionOptions.ts | 16 +- .../src/meta/convertMetaActionType.test.ts | 98 +- .../src/meta/convertMetaActionType.ts | 5 +- .../src/meta/convertMetaLoadSubsetOptions.ts | 33 +- .../src/meta/convertMetaObjectType.test.ts | 22 +- .../meta/convertMetaQueryFunctionType.test.ts | 26 +- .../src/meta/convertMetaTypeDef.ts | 51 +- ...ateSalesforceMetaOntologyBackendAdapter.ts | 5 +- .../src/meta/entityCollectionOptions.test.ts | 61 +- ...queryFunctionTypeCollectionOptions.test.ts | 50 +- .../meta/synthesizeCrudActionTypes.test.ts | 78 +- .../src/meta/synthesizeCrudActionTypes.ts | 54 +- packages/salesforce-ontology/tsconfig.json | 5 +- packages/sqlite-ontology/src/attachments.ts | 32 +- packages/sqlite-ontology/src/index.test.ts | 231 +-- packages/sqlite-ontology/src/index.ts | 74 +- packages/sqlite-ontology/src/testing/index.ts | 214 +- packages/sqlite-ontology/tsconfig.json | 5 +- .../tanstack-start-build-config/README.md | 8 +- .../tanstack-start-build-config/tsconfig.json | 4 +- .../tsconfig/app.json | 4 +- .../src/proxy.test.ts | 6 +- .../vite-plugin-worktree-dev/tsconfig.json | 4 +- .../web-runtime/src/OPFSBlobBytesStore.ts | 8 +- .../createWebBrowserAuthentication.test.ts | 16 +- .../src/createWebBrowserAuthentication.ts | 13 +- packages/web-runtime/src/createWebRuntime.ts | 18 +- packages/web-runtime/tsconfig.e2e.json | 6 +- packages/web-runtime/tsconfig.json | 5 +- specs/2026-06-16-action-drafts/README.md | 15 +- .../outbox-mutators.md | 17 +- specs/2026-07-26-coordination/README.md | 7 +- .../README.md | 21 +- .../README.md | 53 +- specs/2026-08-13-ontology-lenses/README.md | 39 +- .../README.md | 21 +- .../README.md | 39 +- .../README.md | 5 +- vite.config.ts | 5 +- 407 files changed, 3196 insertions(+), 13610 deletions(-) diff --git a/.syncpackrc.json b/.syncpackrc.json index b95466f7..9424a0d7 100644 --- a/.syncpackrc.json +++ b/.syncpackrc.json @@ -23,14 +23,7 @@ "dependencies": ["@tanstack/db", "@tanstack/db-*", "@tanstack/*-db", "@tanstack/*-db-*"], "range": "" }, - { - "label": "Allow TypeScript patch updates", - "dependencies": ["typescript"], - "range": "~" - }, - { - "label": "Use caret ranges for other dependencies", - "range": "^" - } + { "label": "Allow TypeScript patch updates", "dependencies": ["typescript"], "range": "~" }, + { "label": "Use caret ranges for other dependencies", "range": "^" } ] } diff --git a/.vscode/extensions.json b/.vscode/extensions.json index f7af5a79..1605c91e 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,3 +1 @@ -{ - "recommendations": ["VoidZero.vite-plus-extension-pack", "TypeScriptTeam.native-preview"] -} +{ "recommendations": ["VoidZero.vite-plus-extension-pack", "TypeScriptTeam.native-preview"] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 6b4f9984..785b2af2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,27 +1,14 @@ { - "files.associations": { - "*.json": "jsonc", - "package.json": "json" - }, + "files.associations": { "*.json": "jsonc", "package.json": "json" }, "explorer.compactFolders": false, "editor.defaultFormatter": "oxc.oxc-vscode", - "[javascript]": { - "editor.defaultFormatter": "oxc.oxc-vscode" - }, - "[javascriptreact]": { - "editor.defaultFormatter": "oxc.oxc-vscode" - }, - "[typescript]": { - "editor.defaultFormatter": "oxc.oxc-vscode" - }, - "[typescriptreact]": { - "editor.defaultFormatter": "oxc.oxc-vscode" - }, + "[javascript]": { "editor.defaultFormatter": "oxc.oxc-vscode" }, + "[javascriptreact]": { "editor.defaultFormatter": "oxc.oxc-vscode" }, + "[typescript]": { "editor.defaultFormatter": "oxc.oxc-vscode" }, + "[typescriptreact]": { "editor.defaultFormatter": "oxc.oxc-vscode" }, "editor.formatOnSave": true, "editor.formatOnSaveMode": "file", - "editor.codeActionsOnSave": { - "source.fixAll.oxc": "explicit" - }, + "editor.codeActionsOnSave": { "source.fixAll.oxc": "explicit" }, "oxc.disableNestedConfig": true, "oxc.fmt.disableNestedConfig": true, "search.exclude": { diff --git a/apps/icon-lab/scripts/extract-assets.ts b/apps/icon-lab/scripts/extract-assets.ts index 2d15fe1e..6ac27a0a 100644 --- a/apps/icon-lab/scripts/extract-assets.ts +++ b/apps/icon-lab/scripts/extract-assets.ts @@ -359,11 +359,7 @@ async function attachLocalSfSymbolAssets( return { ...icon, textOnly: undefined, - asset: { - archive: archiveName, - path: assetPath, - format: extension, - }, + asset: { archive: archiveName, path: assetPath, format: extension }, }; }) ); @@ -374,11 +370,7 @@ async function attachLocalSfSymbolAssets( ); } await writeFile(archivePath, zipSync(files, { level: 6 })); - return { - icons: withAssets, - bytes: (await stat(archivePath)).size, - assetCount, - }; + return { icons: withAssets, bytes: (await stat(archivePath)).size, assetCount }; } async function buildCatalogFromSnapshot( @@ -408,11 +400,7 @@ async function buildCatalogFromSnapshot( name, concept: mappedConcepts?.[0], concepts: mappedConcepts, - asset: { - archive: archiveName, - path: assetPath, - format: "svg" as const, - }, + asset: { archive: archiveName, path: assetPath, format: "svg" as const }, labels, }; }); @@ -440,14 +428,7 @@ async function main(): Promise { : (Object.fromEntries( Object.entries(configuredSources).map(([provider, source]) => { const version = packageVersion(source.package); - return [ - provider, - { - ...source, - version, - archiveUrl: npmTarball(source.package, version), - }, - ]; + return [provider, { ...source, version, archiveUrl: npmTarball(source.package, version) }]; }) ) as Sources); diff --git a/apps/icon-lab/scripts/generate-embeddings.ts b/apps/icon-lab/scripts/generate-embeddings.ts index d4da6bd7..1d4bc696 100644 --- a/apps/icon-lab/scripts/generate-embeddings.ts +++ b/apps/icon-lab/scripts/generate-embeddings.ts @@ -437,16 +437,9 @@ async function main(): Promise { ) : undefined; const match = forced - ? { - icon: forced, - score: cosineSimilarity(vectors.get(anchor.id)!, vectors.get(forced.id)!), - } + ? { icon: forced, score: cosineSimilarity(vectors.get(anchor.id)!, vectors.get(forced.id)!) } : bestMatch(anchor, candidates, vectors, textVectors, gramIndexes.get(provider)!); - providers[provider] = { - id: match.icon.id, - name: match.icon.name, - score: match.score, - }; + providers[provider] = { id: match.icon.id, name: match.icon.name, score: match.score }; } const scores = Object.values(providers) diff --git a/apps/icon-lab/src/App.tsx b/apps/icon-lab/src/App.tsx index 449e50a5..bfd4f7f8 100644 --- a/apps/icon-lab/src/App.tsx +++ b/apps/icon-lab/src/App.tsx @@ -118,16 +118,10 @@ export function App() { const seedIcon = catalog.icons.find((icon) => icon.id === neighborSeed); return catalog.icons .filter((icon) => icon.id !== neighborSeed && embeddingStore.has(icon.id)) - .map((icon) => ({ - icon, - score: embeddingStore.similarity(neighborSeed, icon.id), - })) + .map((icon) => ({ icon, score: embeddingStore.similarity(neighborSeed, icon.id) })) .sort((a, b) => b.score - a.score) .slice(0, 24) - .map((row) => ({ - ...row, - sameProvider: row.icon.provider === seedIcon?.provider, - })); + .map((row) => ({ ...row, sameProvider: row.icon.provider === seedIcon?.provider })); }, [neighborSeed, catalog, embeddingStore]); const scatterPoints = useMemo(() => { diff --git a/apps/icon-lab/src/components/DraftMappingBrowser.tsx b/apps/icon-lab/src/components/DraftMappingBrowser.tsx index 73e504aa..14bab9d1 100644 --- a/apps/icon-lab/src/components/DraftMappingBrowser.tsx +++ b/apps/icon-lab/src/components/DraftMappingBrowser.tsx @@ -265,9 +265,7 @@ export function DraftMappingBrowser({ catalog, draft }: { catalog: CatalogFile; function exportFeedback() { const blob = new Blob( [JSON.stringify(createMappingFeedbackFile(feedback, undefined, confirmedConcepts), null, 2)], - { - type: "application/json", - } + { type: "application/json" } ); const url = URL.createObjectURL(blob); const link = document.createElement("a"); @@ -476,10 +474,7 @@ export function DraftMappingBrowser({ catalog, draft }: { catalog: CatalogFile; const providerFeedback = feedbackByKey.get( mappingFeedbackKey(selected.concept, providerName) ); - const decisions: Array<{ - value: MappingFeedbackDecision; - label: string; - }> = [ + const decisions: Array<{ value: MappingFeedbackDecision; label: string }> = [ { value: "approve", label: "Approve" }, { value: "reject", label: "Reject" }, ]; diff --git a/apps/icon-lab/src/components/EmbeddingScatter.tsx b/apps/icon-lab/src/components/EmbeddingScatter.tsx index 3121c959..db44525e 100644 --- a/apps/icon-lab/src/components/EmbeddingScatter.tsx +++ b/apps/icon-lab/src/components/EmbeddingScatter.tsx @@ -32,12 +32,7 @@ export function EmbeddingScatter({ } const xs = filtered.map((p) => p.x); const ys = filtered.map((p) => p.y); - return { - minX: Math.min(...xs), - maxX: Math.max(...xs), - minY: Math.min(...ys), - maxY: Math.max(...ys), - }; + return { minX: Math.min(...xs), maxX: Math.max(...xs), minY: Math.min(...ys), maxY: Math.max(...ys) }; }, [filtered]); const width = 960; diff --git a/apps/icon-lab/src/shared/catalog.test.ts b/apps/icon-lab/src/shared/catalog.test.ts index a4bbaeb7..ef79e2dd 100644 --- a/apps/icon-lab/src/shared/catalog.test.ts +++ b/apps/icon-lab/src/shared/catalog.test.ts @@ -8,12 +8,7 @@ const appRoot = path.resolve(import.meta.dirname, "../.."); describe("generated full icon catalog", () => { it("contains complete provider sets in compact archives", async () => { - const minimums = { - blueprint: 700, - lucide: 2_000, - material: 16_000, - salesforce: 1_700, - } as const; + const minimums = { blueprint: 700, lucide: 2_000, material: 16_000, salesforce: 1_700 } as const; for (const provider of Object.keys(minimums) as Array) { const zip = unzipSync( new Uint8Array(await readFile(path.join(appRoot, `public/icon-sets/${provider}.zip`))) diff --git a/apps/icon-lab/src/shared/mappingFeedback.test.ts b/apps/icon-lab/src/shared/mappingFeedback.test.ts index d12f13b3..c1065730 100644 --- a/apps/icon-lab/src/shared/mappingFeedback.test.ts +++ b/apps/icon-lab/src/shared/mappingFeedback.test.ts @@ -27,11 +27,7 @@ describe("mapping feedback", () => { }, }); - expect( - loadMappingFeedback({ - getItem: (key) => values.get(key) ?? null, - }) - ).toEqual([feedback]); + expect(loadMappingFeedback({ getItem: (key) => values.get(key) ?? null })).toEqual([feedback]); expect(values.has(MappingFeedbackStorageKey)).toBe(true); }); @@ -50,14 +46,7 @@ describe("mapping feedback", () => { it("drops abandoned replacement feedback without a selected icon", () => { expect( loadMappingFeedback({ - getItem: () => - JSON.stringify([ - { - ...feedback, - decision: "replace", - replacementName: "", - }, - ]), + getItem: () => JSON.stringify([{ ...feedback, decision: "replace", replacementName: "" }]), }) ).toEqual([]); }); @@ -68,11 +57,10 @@ describe("mapping feedback", () => { setItem: (key, value) => values.set(key, value), }); - expect( - loadMappingConfirmations({ - getItem: (key) => values.get(key) ?? null, - }) - ).toEqual(["airplane", "warning"]); + expect(loadMappingConfirmations({ getItem: (key) => values.get(key) ?? null })).toEqual([ + "airplane", + "warning", + ]); expect(values.has(MappingConfirmationsStorageKey)).toBe(true); }); diff --git a/apps/icon-lab/src/shared/math.ts b/apps/icon-lab/src/shared/math.ts index 704e53e2..e1b06f47 100644 --- a/apps/icon-lab/src/shared/math.ts +++ b/apps/icon-lab/src/shared/math.ts @@ -60,10 +60,7 @@ export function projectTo2d(vectors: number[][]): Array<{ x: number; y: number } const axisA = powerIteration(centered, dims, 24); const axisB = powerIteration(deflate(centered, axisA), dims, 24); - return centered.map((vector) => ({ - x: dot(vector, axisA), - y: dot(vector, axisB), - })); + return centered.map((vector) => ({ x: dot(vector, axisA), y: dot(vector, axisB) })); } function dot(a: number[], b: number[]): number { diff --git a/apps/icon-lab/src/shared/types.ts b/apps/icon-lab/src/shared/types.ts index 02f4f295..a74bcc50 100644 --- a/apps/icon-lab/src/shared/types.ts +++ b/apps/icon-lab/src/shared/types.ts @@ -9,11 +9,7 @@ export interface CatalogIcon { /** All existing concepts using this asset (providers may intentionally reuse glyphs). */ concepts?: string[]; /** Asset stored inside a compact provider archive. */ - asset?: { - archive: string; - path: string; - format: "svg" | "png"; - }; + asset?: { archive: string; path: string; format: "svg" | "png" }; /** True when we only have a name (no redistributable glyph). */ textOnly?: boolean; /** Synonyms / alternate labels used for text embedding. */ @@ -60,28 +56,14 @@ export interface MappingPairScore { export interface MappingAuditFile { generatedAt: string; pairs: MappingPairScore[]; - conceptScores: Array<{ - concept: string; - meanScore: number; - minScore: number; - pairCount: number; - }>; + conceptScores: Array<{ concept: string; meanScore: number; minScore: number; pairCount: number }>; } export interface DraftMapping { concept: string; blueprintId: string; status: "existing" | "generated"; - providers: Partial< - Record< - IconProvider, - { - id: string; - name: string; - score: number; - } - > - >; + providers: Partial>; meanScore: number; minScore: number; } diff --git a/apps/icon-lab/tsconfig.json b/apps/icon-lab/tsconfig.json index 56a73bfb..0df0d8e0 100644 --- a/apps/icon-lab/tsconfig.json +++ b/apps/icon-lab/tsconfig.json @@ -12,9 +12,7 @@ "resolveJsonModule": true, "isolatedModules": true, "jsx": "react-jsx", - "paths": { - "@/*": ["./src/*"] - }, + "paths": { "@/*": ["./src/*"] }, "types": ["node", "vite/client"] }, "include": ["src/**/*.ts", "src/**/*.tsx", "vite.config.ts", "scripts/**/*.ts"], diff --git a/apps/icon-lab/vite.config.ts b/apps/icon-lab/vite.config.ts index e0a42087..404d5198 100644 --- a/apps/icon-lab/vite.config.ts +++ b/apps/icon-lab/vite.config.ts @@ -54,10 +54,7 @@ function generatedDataPlugin(): Plugin { }); request.on("end", async () => { try { - const parsed = JSON.parse(body) as { - version?: unknown; - feedback?: unknown; - }; + const parsed = JSON.parse(body) as { version?: unknown; feedback?: unknown }; if (parsed.version !== 1 || !Array.isArray(parsed.feedback)) { throw new Error("Invalid mapping feedback file"); } @@ -93,11 +90,7 @@ function generatedDataPlugin(): Plugin { missingData = true; continue; } - this.emitFile({ - type: "asset", - fileName: `generated-data/${fileName}`, - source, - }); + this.emitFile({ type: "asset", fileName: `generated-data/${fileName}`, source }); } if (missingData) { this.warn( @@ -152,14 +145,8 @@ export default defineAppConfig({ }, }, - server: { - host: true, - }, + server: { host: true }, test: { environment: "node" }, plugins: lazyPlugins(() => [react(), tailwindcss(), generatedDataPlugin()]), - resolve: { - alias: { - "@": path.resolve(rootDir, "src"), - }, - }, + resolve: { alias: { "@": path.resolve(rootDir, "src") } }, }); diff --git a/apps/issue-tracker/src/app/IssueTracker.tsx b/apps/issue-tracker/src/app/IssueTracker.tsx index 33db9927..3bc2780b 100644 --- a/apps/issue-tracker/src/app/IssueTracker.tsx +++ b/apps/issue-tracker/src/app/IssueTracker.tsx @@ -61,33 +61,18 @@ const ISSUE_SECTIONS = [ { status: "In Progress", label: "In progress" }, { status: "Open", label: "Open" }, { status: "Completed", label: "Done" }, -] as const satisfies ReadonlyArray<{ - status: IssueStatus; - label: string; -}>; +] as const satisfies ReadonlyArray<{ status: IssueStatus; label: string }>; const kanbanKeyboardCoordinates: KeyboardCoordinateGetter = (event, { currentCoordinates }) => { switch (event.code) { case "ArrowLeft": - return { - ...currentCoordinates, - x: currentCoordinates.x - 300, - }; + return { ...currentCoordinates, x: currentCoordinates.x - 300 }; case "ArrowRight": - return { - ...currentCoordinates, - x: currentCoordinates.x + 300, - }; + return { ...currentCoordinates, x: currentCoordinates.x + 300 }; case "ArrowUp": - return { - ...currentCoordinates, - y: currentCoordinates.y - 80, - }; + return { ...currentCoordinates, y: currentCoordinates.y - 80 }; case "ArrowDown": - return { - ...currentCoordinates, - y: currentCoordinates.y + 80, - }; + return { ...currentCoordinates, y: currentCoordinates.y + 80 }; default: return undefined; } @@ -342,10 +327,7 @@ function formatDate(value: unknown) { if (!(input instanceof Date) && typeof input !== "string" && typeof input !== "number") return ""; const date = input instanceof Date ? input : new Date(input); if (Number.isNaN(date.getTime())) return ""; - return new Intl.DateTimeFormat("en", { - month: "short", - day: "numeric", - }).format(date); + return new Intl.DateTimeFormat("en", { month: "short", day: "numeric" }).format(date); } function formatIssueIdentifier(issueId: string) { @@ -533,11 +515,7 @@ function ProjectForm({ function handleSubmit(event: FormEvent) { event.preventDefault(); if (!title.trim()) return; - onSave({ - title: title.trim(), - description: description.trim(), - color, - }); + onSave({ title: title.trim(), description: description.trim(), color }); } return ( @@ -597,10 +575,7 @@ function ProjectForm({ type IssueAttachment = NonNullable[number]; -type PendingAttachment = { - attachment: IssueAttachment; - name: string; -}; +type PendingAttachment = { attachment: IssueAttachment; name: string }; function TagInput({ id, @@ -691,20 +666,13 @@ function AttachmentPicker({ const additions = await Promise.all( Array.from(files).map(async (file) => { const result = await ontology.attachments.create(file, { - target: { - kind: "actionParameter", - actionType, - parameter: "attachments", - }, + target: { kind: "actionParameter", actionType, parameter: "attachments" }, eager: true, }); if (result.isMaterialized) { await result.isMaterialized; } - return { - attachment: result.attachment, - name: file.name, - }; + return { attachment: result.attachment, name: file.name }; }) ); onChange([...attachments, ...additions]); @@ -917,10 +885,7 @@ function AttachmentCard({ attachment: IssueAttachment; ontology: TrackerOntology; }) { - const [preview, setPreview] = useState<{ - url: string; - metadata: AttachmentMetadata; - }>(); + const [preview, setPreview] = useState<{ url: string; metadata: AttachmentMetadata }>(); const [failed, setFailed] = useState(false); useEffect(() => { @@ -1072,9 +1037,7 @@ function IssueDetails({ <> {project.projectTitle} @@ -1099,9 +1062,7 @@ function IssueDetails({ { - void deleteIssue({ - issue: issue.issueId, - }); + void deleteIssue({ issue: issue.issueId }); onClose(); }} > @@ -1203,16 +1164,9 @@ function IssueDetails({ ); } -type IssueRow = Issue & { - projectTitle?: string; - projectColor?: string; -}; +type IssueRow = Issue & { projectTitle?: string; projectColor?: string }; -type CommandIssue = { - issueId: string; - issueTitle?: string; - issueStatus?: string; -}; +type CommandIssue = { issueId: string; issueTitle?: string; issueStatus?: string }; function KanbanCard({ issue, @@ -1225,9 +1179,7 @@ function KanbanCard({ }) { const { attributes, isDragging, listeners, setNodeRef, transform } = useDraggable({ id: issue.issueId, - data: { - status: issue.issueStatus, - }, + data: { status: issue.issueStatus }, }); return ( @@ -1283,9 +1235,7 @@ function KanbanCard({ {issue.projectTitle} @@ -1375,12 +1325,8 @@ function KanbanBoard({ onStatusChange: (issue: IssueRow, status: IssueStatus) => void; }) { const sensors = useSensors( - useSensor(PointerSensor, { - activationConstraint: { distance: 6 }, - }), - useSensor(KeyboardSensor, { - coordinateGetter: kanbanKeyboardCoordinates, - }) + useSensor(PointerSensor, { activationConstraint: { distance: 6 } }), + useSensor(KeyboardSensor, { coordinateGetter: kanbanKeyboardCoordinates }) ); function handleDragEnd(event: DragEndEvent) { @@ -1476,9 +1422,7 @@ function CommandPalette({ icon: ( ), run: () => onOpenProject(project.projectId), @@ -1726,10 +1670,7 @@ function InitialProfileConnection({ onConnected }: { onConnected: (profile: Conn } onConnected( kind === "sqlite" - ? await connectSqliteProfile({ - username: values.username!, - password: values.password!, - }) + ? await connectSqliteProfile({ username: values.username!, password: values.password! }) : await connectFoundryProfile() ); } catch (cause) { @@ -1847,10 +1788,7 @@ function ConnectedIssueTracker({ } const profile = kind === "sqlite" - ? await connectSqliteProfile({ - username: values.username!, - password: values.password!, - }) + ? await connectSqliteProfile({ username: values.username!, password: values.password! }) : await connectFoundryProfile(); setProfiles((current) => (signedOut ? [profile] : [...current, profile])); setActiveProfileId(profile.id); @@ -1891,9 +1829,7 @@ function ConnectedIssueTracker({ const { data: allIssueIds } = useLiveQuery({ query: (q) => - q.from({ Issue: ontology.objects.Issue }).select(({ Issue }) => ({ - issueId: Issue.issueId, - })), + q.from({ Issue: ontology.objects.Issue }).select(({ Issue }) => ({ issueId: Issue.issueId })), }); const { data: issues, isLoading: issuesLoading } = useLiveQuery({ @@ -1952,10 +1888,7 @@ function ConnectedIssueTracker({ function openProject(projectId: string) { setSelectedProjectId(projectId); - void navigate({ - to: "/project/$projectId", - params: { projectId }, - }); + void navigate({ to: "/project/$projectId", params: { projectId } }); } function showAllIssues() { @@ -2109,9 +2042,7 @@ function ConnectedIssueTracker({ key={project.projectId} label="project" onDelete={() => { - void deleteProject({ - project: project.projectId, - }); + void deleteProject({ project: project.projectId }); if (selectedProjectId === project.projectId) { showAllIssues(); } @@ -2360,9 +2291,7 @@ function ConnectedIssueTracker({ issues={issues} onCreateIssue={openCreateIssue} onDeleteIssue={(issueId) => { - void deleteIssue({ - issue: issueId, - }); + void deleteIssue({ issue: issueId }); if (selectedIssueId === issueId) { setSelectedIssueId(null); } @@ -2441,9 +2370,7 @@ function ConnectedIssueTracker({ className="contents" label="issue" onDelete={() => { - void deleteIssue({ - issue: issue.issueId, - }); + void deleteIssue({ issue: issue.issueId }); if (selectedIssueId === issue.issueId) { setSelectedIssueId(null); } diff --git a/apps/issue-tracker/src/app/auth.ts b/apps/issue-tracker/src/app/auth.ts index f6549f14..52350222 100644 --- a/apps/issue-tracker/src/app/auth.ts +++ b/apps/issue-tracker/src/app/auth.ts @@ -2,6 +2,4 @@ import { partyStackClient } from "@party-stack/better-auth"; import { createAuthClient } from "better-auth/client"; import { oauthPopupClient } from "better-auth/client/plugins"; -export const authClient = createAuthClient({ - plugins: [oauthPopupClient(), partyStackClient()], -}); +export const authClient = createAuthClient({ plugins: [oauthPopupClient(), partyStackClient()] }); diff --git a/apps/issue-tracker/src/app/profiles.ts b/apps/issue-tracker/src/app/profiles.ts index f4fa9949..d82a0c0d 100644 --- a/apps/issue-tracker/src/app/profiles.ts +++ b/apps/issue-tracker/src/app/profiles.ts @@ -32,10 +32,7 @@ interface ProfileHandle { ontologyId: string; } -const writes = { - defaultMode: "outbox" as const, - defaultVisibility: "optimistic" as const, -}; +const writes = { defaultMode: "outbox" as const, defaultVisibility: "optimistic" as const }; function requiredEnv(name: string, value: string | undefined): string { if (!value) { @@ -59,10 +56,7 @@ const FOUNDRY_REDIRECT_URL = const handles = new Map(); let foundryOAuthCompletion: Promise | undefined; -const foundryUsers = createFoundryUsersIntegration({ - objectType: "User", - lens: foundryUserToUser, -}); +const foundryUsers = createFoundryUsersIntegration({ objectType: "User", lens: foundryUserToUser }); const foundryInstallation = await createFoundryBackendInstallation({ baseUrl: FOUNDRY_URL, @@ -97,9 +91,7 @@ const foundryInstallation = await createFoundryBackendInstallation({ const sqliteInstallation = await createOntologyBackendInstallation({ installationId: "issue-tracker-sqlite", - connections: createBetterAuthConnectionAdapter({ - client: authClient, - }), + connections: createBetterAuthConnectionAdapter({ client: authClient }), runtime: createWebRuntime, routes: [ { @@ -138,12 +130,7 @@ async function openProfile(options: { userId: options.userId, ontologyId: options.ontologyId, }); - return { - id, - kind: options.kind, - label: options.label, - ontology, - }; + return { id, kind: options.kind, label: options.label, ontology }; } const foundryProfile = (userId: string) => @@ -180,9 +167,7 @@ export async function connectSqliteProfile(credentials: { } export async function connectGoogleProfile(): Promise { - const result = await sqliteInstallation.authentication.signIn.popup({ - provider: "google", - }); + const result = await sqliteInstallation.authentication.signIn.popup({ provider: "google" }); if (result.error) { throw new Error(result.error.message ?? "Google sign-in failed."); } @@ -242,15 +227,9 @@ export function subscribeProfileConnection( const connections = handle.installation.connections; const subscription = connections.subscribeChanges( () => { - listener( - connections.get(handle.userId)?.state ?? { - status: "inactive", - } - ); + listener(connections.get(handle.userId)?.state ?? { status: "inactive" }); }, - { - where: (connection) => eq(connection.userId, handle.userId), - } + { where: (connection) => eq(connection.userId, handle.userId) } ); return () => subscription.unsubscribe(); } diff --git a/apps/issue-tracker/src/components/FormControls.tsx b/apps/issue-tracker/src/components/FormControls.tsx index 1424c75b..960dcc8e 100644 --- a/apps/issue-tracker/src/components/FormControls.tsx +++ b/apps/issue-tracker/src/components/FormControls.tsx @@ -11,10 +11,7 @@ const sizeClassNames: Record = { default: "h-9 px-3 text-sm", }; -type SharedControlProps = { - controlSize?: ControlSize; - fullWidth?: boolean; -}; +type SharedControlProps = { controlSize?: ControlSize; fullWidth?: boolean }; export function FormInput({ className = "", @@ -39,20 +36,14 @@ export function FormSelect({ fullWidth = true, style, ...props -}: ComponentProps<"select"> & - SharedControlProps & { - containerClassName?: string; - }) { +}: ComponentProps<"select"> & SharedControlProps & { containerClassName?: string }) { return ( , "ir"> ): Promise> { - return createLiveOntology({ ...opts, ir: ontology }); + return createLiveOntology({ + ...opts, + ir: ontology, + }); } diff --git a/apps/issue-tracker/src/ontology/generated/types.ts b/apps/issue-tracker/src/ontology/generated/types.ts index 9a851139..c73eeb6e 100644 --- a/apps/issue-tracker/src/ontology/generated/types.ts +++ b/apps/issue-tracker/src/ontology/generated/types.ts @@ -51,7 +51,9 @@ export type User = { avatar?: v.attachment<"image/png" | "image/jpeg" | "image/gif" | "image/svg+xml">; }; -export type IssueTrackerOntologyContext = { user: string } & Record; +export type IssueTrackerOntologyContext = { + user: string; +} & Record; export type CreateIssueParameters = { completedAt?: v.timestamp | null; attachments?: Array> | null; @@ -71,8 +73,12 @@ export type CreateProjectParameters = { "__uuid_7af1b7e1-9b8f-470d-bcf1-e02376d353c1"?: string; __now?: v.timestamp; }; -export type DeleteIssueParameters = { issue: string }; -export type DeleteProjectParameters = { project: string }; +export type DeleteIssueParameters = { + issue: string; +}; +export type DeleteProjectParameters = { + project: string; +}; export type UpdateIssueParameters = { completedAt?: v.timestamp | null; attachments?: Array> | null; @@ -94,14 +100,30 @@ export type UpdateProjectParameters = { }; export type IssueTrackerOntology = { context: IssueTrackerOntologyContext; - objectTypes: { Issue: Issue; Project: Project; User: User }; + objectTypes: { + Issue: Issue; + Project: Project; + User: User; + }; actionTypes: { - createIssue: { parameters: CreateIssueParameters }; - createProject: { parameters: CreateProjectParameters }; - deleteIssue: { parameters: DeleteIssueParameters }; - deleteProject: { parameters: DeleteProjectParameters }; - updateIssue: { parameters: UpdateIssueParameters }; - updateProject: { parameters: UpdateProjectParameters }; + createIssue: { + parameters: CreateIssueParameters; + }; + createProject: { + parameters: CreateProjectParameters; + }; + deleteIssue: { + parameters: DeleteIssueParameters; + }; + deleteProject: { + parameters: DeleteProjectParameters; + }; + updateIssue: { + parameters: UpdateIssueParameters; + }; + updateProject: { + parameters: UpdateProjectParameters; + }; }; queryFunctionTypes: Record; }; diff --git a/apps/issue-tracker/src/ontology/ontology.ts b/apps/issue-tracker/src/ontology/ontology.ts index e0c6333d..50ed038b 100644 --- a/apps/issue-tracker/src/ontology/ontology.ts +++ b/apps/issue-tracker/src/ontology/ontology.ts @@ -10,27 +10,40 @@ export default defineOntology({ pluralDisplayName: "Issues", primaryKey: "issueId", title: "issueTitle", - icon: { name: "ticket", meta: { blueprint: { name: "issue" } } }, + icon: { + name: "ticket", + meta: { + blueprint: { + name: "issue", + }, + }, + }, color: "#2D72D2", properties: [ { name: "issueCompletedAt", displayName: "Issue Completed At", - type: o.optional({ type: o.timestamp({}) }), + type: o.optional({ + type: o.timestamp({}), + }), description: "Timestamp when the issue entered the Completed state; empty while the issue is not completed.", }, { name: "issueStatus", displayName: "Issue Status", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), description: "Current workflow state of the issue: Open, In Progress, Waiting, or Completed.", }, { name: "issueUpdatedAt", displayName: "Issue Updated At", - type: o.optional({ type: o.timestamp({}) }), + type: o.optional({ + type: o.timestamp({}), + }), description: "Timestamp of the most recent update made to the issue.", }, { @@ -42,28 +55,52 @@ export default defineOntology({ { name: "issueLabels", displayName: "Issue Labels", - type: o.optional({ type: o.list({ elementType: o.string({}) }) }), + type: o.optional({ + type: o.list({ + elementType: o.string({}), + }), + }), + }, + { + name: "createdBy", + displayName: "Created by", + type: o.optional({ + type: o.string({}), + }), }, - { name: "createdBy", displayName: "Created by", type: o.optional({ type: o.string({}) }) }, { name: "issueTitle", displayName: "Issue Title", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), description: "Short, human-readable summary used to identify the issue in lists and workflows.", }, - { name: "assignee", displayName: "Assignee", type: o.optional({ type: o.string({}) }) }, + { + name: "assignee", + displayName: "Assignee", + type: o.optional({ + type: o.string({}), + }), + }, { name: "issueAttachments", displayName: "Issue Attachments", type: o.optional({ type: o.list({ elementType: o.attachment({ - meta: { foundry: { kind: "attachment" } }, + meta: { + foundry: { + kind: "attachment", + }, + }, constraint: { content: { kind: "image", - value: { mediaTypes: ["image/png", "image/jpeg"] }, + value: { + mediaTypes: ["image/png", "image/jpeg"], + }, }, }, }), @@ -74,19 +111,25 @@ export default defineOntology({ { name: "projectId", displayName: "Project ID", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), description: "Identifier of the optional project that groups this issue.", }, { name: "issueCreatedAt", displayName: "Issue Created At", - type: o.optional({ type: o.timestamp({}) }), + type: o.optional({ + type: o.timestamp({}), + }), description: "Timestamp when the issue was created through the operational workflow.", }, { name: "issueDescription", displayName: "Issue Description", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), description: "Detailed context, requirements, or notes explaining the work represented by the issue.", }, @@ -100,25 +143,38 @@ export default defineOntology({ pluralDisplayName: "Projects", primaryKey: "projectId", title: "projectTitle", - icon: { name: "project", meta: { blueprint: { name: "projects" } } }, + icon: { + name: "project", + meta: { + blueprint: { + name: "projects", + }, + }, + }, color: "#2D72D2", properties: [ { name: "projectUpdatedAt", displayName: "Project Updated At", - type: o.optional({ type: o.timestamp({}) }), + type: o.optional({ + type: o.timestamp({}), + }), description: "Timestamp of the most recent update made to the project.", }, { name: "projectDescription", displayName: "Project Description", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), description: "Summary of the project's purpose, scope, or intended outcome.", }, { name: "projectColor", displayName: "Project Color", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), description: "Display color used to visually distinguish the project, stored as a hexadecimal color value such as #2D72D2.", }, @@ -131,13 +187,17 @@ export default defineOntology({ { name: "projectCreatedAt", displayName: "Project Created At", - type: o.optional({ type: o.timestamp({}) }), + type: o.optional({ + type: o.timestamp({}), + }), description: "Timestamp when the project was created through the operational workflow.", }, { name: "projectTitle", displayName: "Project Title", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), description: "Short, human-readable name used to identify the project.", }, ], @@ -150,10 +210,32 @@ export default defineOntology({ pluralDisplayName: "Users", primaryKey: "id", properties: [ - { name: "id", displayName: "ID", type: o.string({}) }, - { name: "givenName", displayName: "Given name", type: o.optional({ type: o.string({}) }) }, - { name: "familyName", displayName: "Family name", type: o.optional({ type: o.string({}) }) }, - { name: "email", displayName: "Email", type: o.optional({ type: o.string({}) }) }, + { + name: "id", + displayName: "ID", + type: o.string({}), + }, + { + name: "givenName", + displayName: "Given name", + type: o.optional({ + type: o.string({}), + }), + }, + { + name: "familyName", + displayName: "Family name", + type: o.optional({ + type: o.string({}), + }), + }, + { + name: "email", + displayName: "Email", + type: o.optional({ + type: o.string({}), + }), + }, { name: "avatar", displayName: "Avatar", @@ -176,8 +258,16 @@ export default defineOntology({ linkTypes: [ { id: "ri.ontology.main.relation.0a932a4d-9f7d-4874-980c-ef326adbc825", - source: { objectType: "Project", name: "project", displayName: "Project" }, - target: { objectType: "Issue", name: "issues", displayName: "Issue" }, + source: { + objectType: "Project", + name: "project", + displayName: "Project", + }, + target: { + objectType: "Issue", + name: "issues", + displayName: "Issue", + }, foreignKey: "projectId", cardinality: "one", }, @@ -190,7 +280,9 @@ export default defineOntology({ { name: "completedAt", displayName: "Issue Completed At", - type: o.optional({ type: o.timestamp({}) }), + type: o.optional({ + type: o.timestamp({}), + }), description: "Completion timestamp when the issue is already completed.", }, { @@ -199,11 +291,17 @@ export default defineOntology({ type: o.optional({ type: o.list({ elementType: o.attachment({ - meta: { foundry: { kind: "attachment" } }, + meta: { + foundry: { + kind: "attachment", + }, + }, constraint: { content: { kind: "image", - value: { mediaTypes: ["image/png", "image/jpeg"] }, + value: { + mediaTypes: ["image/png", "image/jpeg"], + }, }, }, }), @@ -214,21 +312,37 @@ export default defineOntology({ { name: "issueLabels", displayName: "Issue Labels", - type: o.optional({ type: o.list({ elementType: o.string({}) }) }), + type: o.optional({ + type: o.list({ + elementType: o.string({}), + }), + }), }, { name: "project", displayName: "Project", - type: o.optional({ type: o.objectReference({ objectType: "Project" }) }), + type: o.optional({ + type: o.objectReference({ + objectType: "Project", + }), + }), description: "Optional project used to group the issue.", }, { name: "description", displayName: "Issue Description", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), description: "Detailed context or requirements for the issue.", }, - { name: "assignee", displayName: "Assignee", type: o.optional({ type: o.string({}) }) }, + { + name: "assignee", + displayName: "Assignee", + type: o.optional({ + type: o.string({}), + }), + }, { name: "title", displayName: "Issue Title", @@ -241,15 +355,29 @@ export default defineOntology({ type: o.string({ constraint: o.StringConstraint.enum({ options: [ - { value: "Open", label: "Open" }, - { value: "In Progress", label: "In Progress" }, - { value: "Waiting", label: "Waiting" }, - { value: "Completed", label: "Completed" }, + { + value: "Open", + label: "Open", + }, + { + value: "In Progress", + label: "In Progress", + }, + { + value: "Waiting", + label: "Waiting", + }, + { + value: "Completed", + label: "Completed", + }, ], }), }), description: "Initial workflow state for the issue.", - defaultValue: o.Expression.literal({ value: "Open" }), + defaultValue: o.Expression.literal({ + value: "Open", + }), }, { name: "__uuid_0df17cad-fc40-4f4b-b755-dfccb968d615", @@ -270,12 +398,21 @@ export default defineOntology({ values: [ { property: ["issueCompletedAt"], - value: o.Expression.inputReference({ name: "completedAt" }), + value: o.Expression.inputReference({ + name: "completedAt", + }), + }, + { + property: ["issueStatus"], + value: o.Expression.inputReference({ + name: "status", + }), }, - { property: ["issueStatus"], value: o.Expression.inputReference({ name: "status" }) }, { property: ["issueUpdatedAt"], - value: o.Expression.inputReference({ name: "__now" }), + value: o.Expression.inputReference({ + name: "__now", + }), }, { property: ["issueId"], @@ -283,16 +420,35 @@ export default defineOntology({ name: "__uuid_0df17cad-fc40-4f4b-b755-dfccb968d615", }), }, - { property: ["createdBy"], value: o.Expression.contextReference({ name: "user" }) }, + { + property: ["createdBy"], + value: o.Expression.contextReference({ + name: "user", + }), + }, { property: ["issueLabels"], - value: o.Expression.inputReference({ name: "issueLabels" }), + value: o.Expression.inputReference({ + name: "issueLabels", + }), + }, + { + property: ["issueTitle"], + value: o.Expression.inputReference({ + name: "title", + }), + }, + { + property: ["assignee"], + value: o.Expression.inputReference({ + name: "assignee", + }), }, - { property: ["issueTitle"], value: o.Expression.inputReference({ name: "title" }) }, - { property: ["assignee"], value: o.Expression.inputReference({ name: "assignee" }) }, { property: ["issueAttachments"], - value: o.Expression.inputReference({ name: "attachments" }), + value: o.Expression.inputReference({ + name: "attachments", + }), }, { property: ["projectId"], @@ -300,7 +456,12 @@ export default defineOntology({ source: { kind: "objectLookup", value: { - reference: { kind: "inputReference", value: { name: "project" } }, + reference: { + kind: "inputReference", + value: { + name: "project", + }, + }, }, }, path: ["projectId"], @@ -308,11 +469,15 @@ export default defineOntology({ }, { property: ["issueCreatedAt"], - value: o.Expression.inputReference({ name: "__now" }), + value: o.Expression.inputReference({ + name: "__now", + }), }, { property: ["issueDescription"], - value: o.Expression.inputReference({ name: "description" }), + value: o.Expression.inputReference({ + name: "description", + }), }, ], }), @@ -339,7 +504,9 @@ export default defineOntology({ { name: "description", displayName: "Project Description", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), description: "Summary of the project's purpose and scope.", }, { @@ -367,13 +534,22 @@ export default defineOntology({ values: [ { property: ["projectUpdatedAt"], - value: o.Expression.inputReference({ name: "__now" }), + value: o.Expression.inputReference({ + name: "__now", + }), }, { property: ["projectDescription"], - value: o.Expression.inputReference({ name: "description" }), + value: o.Expression.inputReference({ + name: "description", + }), + }, + { + property: ["projectColor"], + value: o.Expression.inputReference({ + name: "color", + }), }, - { property: ["projectColor"], value: o.Expression.inputReference({ name: "color" }) }, { property: ["projectId"], value: o.Expression.inputReference({ @@ -382,9 +558,16 @@ export default defineOntology({ }, { property: ["projectCreatedAt"], - value: o.Expression.inputReference({ name: "__now" }), + value: o.Expression.inputReference({ + name: "__now", + }), + }, + { + property: ["projectTitle"], + value: o.Expression.inputReference({ + name: "title", + }), }, - { property: ["projectTitle"], value: o.Expression.inputReference({ name: "title" }) }, ], }), ], @@ -398,11 +581,19 @@ export default defineOntology({ { name: "issue", displayName: "Issue", - type: o.objectReference({ objectType: "Issue" }), + type: o.objectReference({ + objectType: "Issue", + }), description: "Issue to permanently delete.", }, ], - logic: [o.ActionLogicStep.deleteObject({ object: { name: "issue" } })], + logic: [ + o.ActionLogicStep.deleteObject({ + object: { + name: "issue", + }, + }), + ], description: "Permanently deletes a selected issue.", }, { @@ -412,11 +603,19 @@ export default defineOntology({ { name: "project", displayName: "Project", - type: o.objectReference({ objectType: "Project" }), + type: o.objectReference({ + objectType: "Project", + }), description: "Project to permanently delete.", }, ], - logic: [o.ActionLogicStep.deleteObject({ object: { name: "project" } })], + logic: [ + o.ActionLogicStep.deleteObject({ + object: { + name: "project", + }, + }), + ], description: "Permanently deletes a selected project. Existing issues are not deleted.", }, { @@ -426,12 +625,21 @@ export default defineOntology({ { name: "completedAt", displayName: "Issue Completed At", - type: o.optional({ type: o.timestamp({}) }), + type: o.optional({ + type: o.timestamp({}), + }), description: "Completion timestamp; clear this value when the issue is not completed.", defaultValue: o.Expression.getAt({ source: { kind: "objectLookup", - value: { reference: { kind: "inputReference", value: { name: "issue" } } }, + value: { + reference: { + kind: "inputReference", + value: { + name: "issue", + }, + }, + }, }, path: ["issueCompletedAt"], }), @@ -442,11 +650,17 @@ export default defineOntology({ type: o.optional({ type: o.list({ elementType: o.attachment({ - meta: { foundry: { kind: "attachment" } }, + meta: { + foundry: { + kind: "attachment", + }, + }, constraint: { content: { kind: "image", - value: { mediaTypes: ["image/png", "image/jpeg"] }, + value: { + mediaTypes: ["image/png", "image/jpeg"], + }, }, }, }), @@ -456,7 +670,14 @@ export default defineOntology({ defaultValue: o.Expression.getAt({ source: { kind: "objectLookup", - value: { reference: { kind: "inputReference", value: { name: "issue" } } }, + value: { + reference: { + kind: "inputReference", + value: { + name: "issue", + }, + }, + }, }, path: ["issueAttachments"], }), @@ -464,17 +685,30 @@ export default defineOntology({ { name: "issue", displayName: "Issue", - type: o.objectReference({ objectType: "Issue" }), + type: o.objectReference({ + objectType: "Issue", + }), description: "Issue to update.", }, { name: "issueLabels", displayName: "Issue Labels", - type: o.optional({ type: o.list({ elementType: o.string({}) }) }), + type: o.optional({ + type: o.list({ + elementType: o.string({}), + }), + }), defaultValue: o.Expression.getAt({ source: { kind: "objectLookup", - value: { reference: { kind: "inputReference", value: { name: "issue" } } }, + value: { + reference: { + kind: "inputReference", + value: { + name: "issue", + }, + }, + }, }, path: ["issueLabels"], }), @@ -482,12 +716,21 @@ export default defineOntology({ { name: "description", displayName: "Issue Description", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), description: "Detailed context or requirements for the issue.", defaultValue: o.Expression.getAt({ source: { kind: "objectLookup", - value: { reference: { kind: "inputReference", value: { name: "issue" } } }, + value: { + reference: { + kind: "inputReference", + value: { + name: "issue", + }, + }, + }, }, path: ["issueDescription"], }), @@ -495,12 +738,23 @@ export default defineOntology({ { name: "project", displayName: "Project", - type: o.optional({ type: o.objectReference({ objectType: "Project" }) }), + type: o.optional({ + type: o.objectReference({ + objectType: "Project", + }), + }), description: "Optional project used to group the issue.", defaultValue: o.Expression.getAt({ source: { kind: "objectLookup", - value: { reference: { kind: "inputReference", value: { name: "issue" } } }, + value: { + reference: { + kind: "inputReference", + value: { + name: "issue", + }, + }, + }, }, path: ["projectId"], }), @@ -508,11 +762,20 @@ export default defineOntology({ { name: "assignee", displayName: "Assignee", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), defaultValue: o.Expression.getAt({ source: { kind: "objectLookup", - value: { reference: { kind: "inputReference", value: { name: "issue" } } }, + value: { + reference: { + kind: "inputReference", + value: { + name: "issue", + }, + }, + }, }, path: ["assignee"], }), @@ -525,7 +788,14 @@ export default defineOntology({ defaultValue: o.Expression.getAt({ source: { kind: "objectLookup", - value: { reference: { kind: "inputReference", value: { name: "issue" } } }, + value: { + reference: { + kind: "inputReference", + value: { + name: "issue", + }, + }, + }, }, path: ["issueTitle"], }), @@ -536,10 +806,22 @@ export default defineOntology({ type: o.string({ constraint: o.StringConstraint.enum({ options: [ - { value: "Open", label: "Open" }, - { value: "In Progress", label: "In Progress" }, - { value: "Waiting", label: "Waiting" }, - { value: "Completed", label: "Completed" }, + { + value: "Open", + label: "Open", + }, + { + value: "In Progress", + label: "In Progress", + }, + { + value: "Waiting", + label: "Waiting", + }, + { + value: "Completed", + label: "Completed", + }, ], }), }), @@ -547,7 +829,14 @@ export default defineOntology({ defaultValue: o.Expression.getAt({ source: { kind: "objectLookup", - value: { reference: { kind: "inputReference", value: { name: "issue" } } }, + value: { + reference: { + kind: "inputReference", + value: { + name: "issue", + }, + }, + }, }, path: ["issueStatus"], }), @@ -561,26 +850,51 @@ export default defineOntology({ ], logic: [ o.ActionLogicStep.updateObject({ - object: { name: "issue" }, + object: { + name: "issue", + }, values: [ { property: ["issueCompletedAt"], - value: o.Expression.inputReference({ name: "completedAt" }), + value: o.Expression.inputReference({ + name: "completedAt", + }), + }, + { + property: ["issueStatus"], + value: o.Expression.inputReference({ + name: "status", + }), }, - { property: ["issueStatus"], value: o.Expression.inputReference({ name: "status" }) }, { property: ["issueUpdatedAt"], - value: o.Expression.inputReference({ name: "__now" }), + value: o.Expression.inputReference({ + name: "__now", + }), }, { property: ["issueLabels"], - value: o.Expression.inputReference({ name: "issueLabels" }), + value: o.Expression.inputReference({ + name: "issueLabels", + }), + }, + { + property: ["issueTitle"], + value: o.Expression.inputReference({ + name: "title", + }), + }, + { + property: ["assignee"], + value: o.Expression.inputReference({ + name: "assignee", + }), }, - { property: ["issueTitle"], value: o.Expression.inputReference({ name: "title" }) }, - { property: ["assignee"], value: o.Expression.inputReference({ name: "assignee" }) }, { property: ["issueAttachments"], - value: o.Expression.inputReference({ name: "attachments" }), + value: o.Expression.inputReference({ + name: "attachments", + }), }, { property: ["projectId"], @@ -588,7 +902,12 @@ export default defineOntology({ source: { kind: "objectLookup", value: { - reference: { kind: "inputReference", value: { name: "project" } }, + reference: { + kind: "inputReference", + value: { + name: "project", + }, + }, }, }, path: ["projectId"], @@ -596,7 +915,9 @@ export default defineOntology({ }, { property: ["issueDescription"], - value: o.Expression.inputReference({ name: "description" }), + value: o.Expression.inputReference({ + name: "description", + }), }, ], }), @@ -622,7 +943,14 @@ export default defineOntology({ defaultValue: o.Expression.getAt({ source: { kind: "objectLookup", - value: { reference: { kind: "inputReference", value: { name: "project" } } }, + value: { + reference: { + kind: "inputReference", + value: { + name: "project", + }, + }, + }, }, path: ["projectColor"], }), @@ -630,18 +958,29 @@ export default defineOntology({ { name: "project", displayName: "Project", - type: o.objectReference({ objectType: "Project" }), + type: o.objectReference({ + objectType: "Project", + }), description: "Project to update.", }, { name: "description", displayName: "Project Description", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), description: "Summary of the project's purpose and scope.", defaultValue: o.Expression.getAt({ source: { kind: "objectLookup", - value: { reference: { kind: "inputReference", value: { name: "project" } } }, + value: { + reference: { + kind: "inputReference", + value: { + name: "project", + }, + }, + }, }, path: ["projectDescription"], }), @@ -654,7 +993,14 @@ export default defineOntology({ defaultValue: o.Expression.getAt({ source: { kind: "objectLookup", - value: { reference: { kind: "inputReference", value: { name: "project" } } }, + value: { + reference: { + kind: "inputReference", + value: { + name: "project", + }, + }, + }, }, path: ["projectTitle"], }), @@ -668,18 +1014,34 @@ export default defineOntology({ ], logic: [ o.ActionLogicStep.updateObject({ - object: { name: "project" }, + object: { + name: "project", + }, values: [ { property: ["projectUpdatedAt"], - value: o.Expression.inputReference({ name: "__now" }), + value: o.Expression.inputReference({ + name: "__now", + }), }, { property: ["projectDescription"], - value: o.Expression.inputReference({ name: "description" }), + value: o.Expression.inputReference({ + name: "description", + }), + }, + { + property: ["projectColor"], + value: o.Expression.inputReference({ + name: "color", + }), + }, + { + property: ["projectTitle"], + value: o.Expression.inputReference({ + name: "title", + }), }, - { property: ["projectColor"], value: o.Expression.inputReference({ name: "color" }) }, - { property: ["projectTitle"], value: o.Expression.inputReference({ name: "title" }) }, ], }), ], @@ -689,6 +1051,14 @@ export default defineOntology({ ], queryFunctionTypes: [], contextType: o.struct({ - fields: [{ name: "user", displayName: "User", type: o.objectReference({ objectType: "User" }) }], + fields: [ + { + name: "user", + displayName: "User", + type: o.objectReference({ + objectType: "User", + }), + }, + ], }), }); diff --git a/apps/issue-tracker/src/ontology/user.ts b/apps/issue-tracker/src/ontology/user.ts index f3193ce1..81c08b75 100644 --- a/apps/issue-tracker/src/ontology/user.ts +++ b/apps/issue-tracker/src/ontology/user.ts @@ -2,7 +2,12 @@ import { o, type Lens } from "@party-stack/ontology"; export const foundryUserToUser = { operations: [ - o.LensOp.move({ from: ["profilePicture"], to: ["avatar"] }), - o.LensOp.select({ properties: ["id", "givenName", "familyName", "email", "avatar"] }), + o.LensOp.move({ + from: ["profilePicture"], + to: ["avatar"], + }), + o.LensOp.select({ + properties: ["id", "givenName", "familyName", "email", "avatar"], + }), ], } satisfies Lens; diff --git a/apps/issue-tracker/src/router.tsx b/apps/issue-tracker/src/router.tsx index 1f3f4c97..b51991e3 100644 --- a/apps/issue-tracker/src/router.tsx +++ b/apps/issue-tracker/src/router.tsx @@ -2,7 +2,10 @@ import { createRouter } from "@tanstack/react-router"; import { routeTree } from "./routeTree.gen"; export function getRouter() { - return createRouter({ routeTree, scrollRestoration: true }); + return createRouter({ + routeTree, + scrollRestoration: true, + }); } declare module "@tanstack/react-router" { diff --git a/apps/issue-tracker/src/routes/__root.tsx b/apps/issue-tracker/src/routes/__root.tsx index fb1d325f..959ca160 100644 --- a/apps/issue-tracker/src/routes/__root.tsx +++ b/apps/issue-tracker/src/routes/__root.tsx @@ -7,9 +7,15 @@ export const Route = createRootRoute({ head: () => ({ meta: [ { charSet: "utf-8" }, - { name: "viewport", content: "width=device-width, initial-scale=1" }, + { + name: "viewport", + content: "width=device-width, initial-scale=1", + }, { title: "Issue tracker" }, - { name: "description", content: "Live issue and project tracking" }, + { + name: "description", + content: "Live issue and project tracking", + }, ], }), component: RootComponent, diff --git a/apps/issue-tracker/src/routes/api.remote-ontology.$endpoint.ts b/apps/issue-tracker/src/routes/api.remote-ontology.$endpoint.ts index 4d71b5eb..e74b5eb8 100644 --- a/apps/issue-tracker/src/routes/api.remote-ontology.$endpoint.ts +++ b/apps/issue-tracker/src/routes/api.remote-ontology.$endpoint.ts @@ -73,11 +73,15 @@ const remoteServer = createRemoteOntologyServer<{ user: string }, IssueTrackerOn }); async function handleRequest(request: Request): Promise { - const selected = await auth.api.getSession({ headers: request.headers }); + const selected = await auth.api.getSession({ + headers: request.headers, + }); if (!selected) { return new Response("Authentication required.", { status: 401, - headers: { "www-authenticate": 'Session realm="Party Stack Issue Tracker"' }, + headers: { + "www-authenticate": 'Session realm="Party Stack Issue Tracker"', + }, }); } const seeded = issueTrackerUsers.find((user) => user.email === selected.user.email); @@ -91,7 +95,9 @@ async function handleRequest(request: Request): Promise { email: selected.user.email, }) ); - contexts.set(request, { user: selected.user.id }); + contexts.set(request, { + user: selected.user.id, + }); return remoteServer.handleRequest(request); } diff --git a/apps/issue-tracker/src/routes/auth.callback.tsx b/apps/issue-tracker/src/routes/auth.callback.tsx index 80dbb2d3..e356f30a 100644 --- a/apps/issue-tracker/src/routes/auth.callback.tsx +++ b/apps/issue-tracker/src/routes/auth.callback.tsx @@ -2,7 +2,9 @@ import { createFileRoute, useNavigate } from "@tanstack/react-router"; import { useEffect, useState } from "react"; import { completeFoundryOAuthRedirect } from "../app/profiles"; -export const Route = createFileRoute("/auth/callback")({ component: FoundryOAuthCallback }); +export const Route = createFileRoute("/auth/callback")({ + component: FoundryOAuthCallback, +}); function FoundryOAuthCallback() { const navigate = useNavigate(); @@ -11,7 +13,12 @@ function FoundryOAuthCallback() { useEffect(() => { if (window.opener) return; void completeFoundryOAuthRedirect(window.location.href) - .then(() => navigate({ to: "/", replace: true })) + .then(() => + navigate({ + to: "/", + replace: true, + }) + ) .catch((cause: unknown) => { setError(cause instanceof Error ? cause.message : String(cause)); }); diff --git a/apps/issue-tracker/src/routes/index.tsx b/apps/issue-tracker/src/routes/index.tsx index 8d93d080..18229d5a 100644 --- a/apps/issue-tracker/src/routes/index.tsx +++ b/apps/issue-tracker/src/routes/index.tsx @@ -5,4 +5,7 @@ const IssueTracker = lazy(() => import("../app/IssueTracker").then(({ IssueTracker }) => ({ default: IssueTracker })) ); -export const Route = createFileRoute("/")({ ssr: false, component: IssueTracker }); +export const Route = createFileRoute("/")({ + ssr: false, + component: IssueTracker, +}); diff --git a/apps/issue-tracker/src/routes/project.$projectId.tsx b/apps/issue-tracker/src/routes/project.$projectId.tsx index eb12319e..219dd871 100644 --- a/apps/issue-tracker/src/routes/project.$projectId.tsx +++ b/apps/issue-tracker/src/routes/project.$projectId.tsx @@ -5,7 +5,10 @@ const IssueTracker = lazy(() => import("../app/IssueTracker").then(({ IssueTracker }) => ({ default: IssueTracker })) ); -export const Route = createFileRoute("/project/$projectId")({ ssr: false, component: ProjectRoute }); +export const Route = createFileRoute("/project/$projectId")({ + ssr: false, + component: ProjectRoute, +}); function ProjectRoute() { const { projectId } = Route.useParams(); diff --git a/apps/issue-tracker/src/server/auth.ts b/apps/issue-tracker/src/server/auth.ts index 10b5da43..af737ab5 100644 --- a/apps/issue-tracker/src/server/auth.ts +++ b/apps/issue-tracker/src/server/auth.ts @@ -35,12 +35,26 @@ const authOptions = { process.env.BETTER_AUTH_URL ?? "http://localhost:3000", secret: process.env.BETTER_AUTH_SECRET ?? "party-stack-issue-tracker-development-secret-change-me", - emailAndPassword: { enabled: true, minPasswordLength: 3 }, + emailAndPassword: { + enabled: true, + minPasswordLength: 3, + }, socialProviders: googleClientId && googleClientSecret - ? { google: { clientId: googleClientId, clientSecret: googleClientSecret } } + ? { + google: { + clientId: googleClientId, + clientSecret: googleClientSecret, + }, + } : {}, - plugins: [partyStack({ maximumSessions: 10 }), oauthPopup(), tanstackStartCookies()], + plugins: [ + partyStack({ + maximumSessions: 10, + }), + oauthPopup(), + tanstackStartCookies(), + ], } satisfies BetterAuthOptions; await (await getMigrations(authOptions)).runMigrations(); @@ -50,5 +64,11 @@ export const auth = betterAuth(authOptions); const findUser = database.prepare(`SELECT "id" FROM "user" WHERE "email" = ?`); for (const user of issueTrackerUsers) { if (findUser.get(user.email)) continue; - await auth.api.signUpEmail({ body: { email: user.email, password: user.password, name: user.name } }); + await auth.api.signUpEmail({ + body: { + email: user.email, + password: user.password, + name: user.name, + }, + }); } diff --git a/apps/issue-tracker/tsconfig.json b/apps/issue-tracker/tsconfig.json index 0ed031d1..2bd79e3a 100644 --- a/apps/issue-tracker/tsconfig.json +++ b/apps/issue-tracker/tsconfig.json @@ -1,4 +1,6 @@ { "extends": "@party-stack/tanstack-start-build-config/tsconfig.json", - "compilerOptions": { "allowJs": true } + "compilerOptions": { + "allowJs": true + } } diff --git a/apps/issue-tracker/vite.config.ts b/apps/issue-tracker/vite.config.ts index 7c71b26d..9096be8a 100644 --- a/apps/issue-tracker/vite.config.ts +++ b/apps/issue-tracker/vite.config.ts @@ -1,3 +1,8 @@ import { defineAppConfig } from "@party-stack/tanstack-start-build-config"; -export default defineAppConfig({ server: { port: 3000 }, ssr: { external: ["better-sqlite3"] } }); +export default defineAppConfig({ + server: { port: 3000 }, + ssr: { + external: ["better-sqlite3"], + }, +}); diff --git a/apps/ontology-explorer/src/components/CommandBar.tsx b/apps/ontology-explorer/src/components/CommandBar.tsx index 47084f46..e5c997ad 100644 --- a/apps/ontology-explorer/src/components/CommandBar.tsx +++ b/apps/ontology-explorer/src/components/CommandBar.tsx @@ -38,17 +38,20 @@ export function CommandBar() { const { data: objectTypes } = useLiveQuery( (q) => - q - .from({ ot: metaOntology.objects.ObjectType }) - .select(({ ot }) => ({ name: ot.name, displayName: ot.displayName })), + q.from({ ot: metaOntology.objects.ObjectType }).select(({ ot }) => ({ + name: ot.name, + displayName: ot.displayName, + })), [] ); const { data: linkTypes } = useLiveQuery( (q) => - q - .from({ lt: metaOntology.objects.LinkType }) - .select(({ lt }) => ({ id: lt.id, sourceName: lt.source, targetName: lt.target })), + q.from({ lt: metaOntology.objects.LinkType }).select(({ lt }) => ({ + id: lt.id, + sourceName: lt.source, + targetName: lt.target, + })), [] ); diff --git a/apps/ontology-explorer/src/ontology/OntologyProvider.tsx b/apps/ontology-explorer/src/ontology/OntologyProvider.tsx index c7fba3d4..0993b881 100644 --- a/apps/ontology-explorer/src/ontology/OntologyProvider.tsx +++ b/apps/ontology-explorer/src/ontology/OntologyProvider.tsx @@ -26,8 +26,12 @@ export function OntologyProvider({ children }: { children: ReactNode }) { const meta = use( useMemo(() => { const client = getClient(); - const metaBackendAdapter = createFoundryMetaOntologyBackendAdapter({ client }); - return createMetaLiveOntology({ backend: () => metaBackendAdapter }); + const metaBackendAdapter = createFoundryMetaOntologyBackendAdapter({ + client, + }); + return createMetaLiveOntology({ + backend: () => metaBackendAdapter, + }); }, []) ); @@ -73,10 +77,15 @@ function OntologyInner({ meta, children }: { meta: LiveOntology; c } as unknown as OntologyIR; const client = getClient(); - const backend = createFoundryOntologyBackend({ client }); + const backend = createFoundryOntologyBackend({ + client, + }); let active = true; let ontology: LiveOntology | undefined; - void createLiveOntology({ ir, backend }).then( + void createLiveOntology({ + ir, + backend, + }).then( (created) => { if (!active) { void created.cleanup(); diff --git a/apps/ontology-explorer/src/router.tsx b/apps/ontology-explorer/src/router.tsx index 70b51bed..6a3e3271 100644 --- a/apps/ontology-explorer/src/router.tsx +++ b/apps/ontology-explorer/src/router.tsx @@ -2,7 +2,10 @@ import { createRouter } from "@tanstack/react-router"; import { routeTree } from "./routeTree.gen"; export function getRouter() { - const router = createRouter({ routeTree, scrollRestoration: true }); + const router = createRouter({ + routeTree, + scrollRestoration: true, + }); return router; } diff --git a/apps/ontology-explorer/src/routes/__root.tsx b/apps/ontology-explorer/src/routes/__root.tsx index 10265d45..b99362c3 100644 --- a/apps/ontology-explorer/src/routes/__root.tsx +++ b/apps/ontology-explorer/src/routes/__root.tsx @@ -80,7 +80,10 @@ function Sidebar() { (q) => q .from({ ot: meta.objects.ObjectType }) - .select(({ ot }) => ({ name: ot.name, displayName: ot.displayName })) + .select(({ ot }) => ({ + name: ot.name, + displayName: ot.displayName, + })) .orderBy(({ ot }) => ot.displayName, "asc"), [] ); @@ -89,7 +92,9 @@ function Sidebar() { (q) => q .from({ lt: meta.objects.LinkType }) - .select(({ lt }) => ({ id: lt.id })) + .select(({ lt }) => ({ + id: lt.id, + })) .orderBy(({ lt }) => lt.id, "asc"), [] ); diff --git a/apps/ontology-explorer/src/routes/action-types.$actionType.tsx b/apps/ontology-explorer/src/routes/action-types.$actionType.tsx index aaaa9492..4b36ebc3 100644 --- a/apps/ontology-explorer/src/routes/action-types.$actionType.tsx +++ b/apps/ontology-explorer/src/routes/action-types.$actionType.tsx @@ -4,7 +4,9 @@ import { TypeDefLabel } from "../components/TypeDefLabel"; import { useOntology } from "../ontology/OntologyProvider"; import type { ActionParameterDef, ActionLogicStep } from "@party-stack/ontology"; -export const Route = createFileRoute("/action-types/$actionType")({ component: ActionTypeDetail }); +export const Route = createFileRoute("/action-types/$actionType")({ + component: ActionTypeDetail, +}); function ActionTypeDetail() { const { meta: metaOntology } = useOntology(); diff --git a/apps/ontology-explorer/src/routes/index.tsx b/apps/ontology-explorer/src/routes/index.tsx index 84188129..7b6e59ae 100644 --- a/apps/ontology-explorer/src/routes/index.tsx +++ b/apps/ontology-explorer/src/routes/index.tsx @@ -3,7 +3,9 @@ import { createFileRoute, Link } from "@tanstack/react-router"; import { useOntology } from "../ontology/OntologyProvider"; import type { MetaLinkType } from "@party-stack/ontology"; -export const Route = createFileRoute("/")({ component: Dashboard }); +export const Route = createFileRoute("/")({ + component: Dashboard, +}); function Dashboard() { const { meta } = useOntology(); diff --git a/apps/ontology-explorer/src/routes/link-types.$linkType.tsx b/apps/ontology-explorer/src/routes/link-types.$linkType.tsx index bf277caf..65ed6abe 100644 --- a/apps/ontology-explorer/src/routes/link-types.$linkType.tsx +++ b/apps/ontology-explorer/src/routes/link-types.$linkType.tsx @@ -3,7 +3,9 @@ import { createFileRoute, Link } from "@tanstack/react-router"; import { useOntology } from "../ontology/OntologyProvider"; import type { MetaLinkType } from "@party-stack/ontology"; -export const Route = createFileRoute("/link-types/$linkType")({ component: LinkTypeDetail }); +export const Route = createFileRoute("/link-types/$linkType")({ + component: LinkTypeDetail, +}); function LinkTypeDetail() { const { meta: metaOntology } = useOntology(); diff --git a/apps/ontology-explorer/src/routes/object-types.$objectType.tsx b/apps/ontology-explorer/src/routes/object-types.$objectType.tsx index 9b12b802..643e80d4 100644 --- a/apps/ontology-explorer/src/routes/object-types.$objectType.tsx +++ b/apps/ontology-explorer/src/routes/object-types.$objectType.tsx @@ -18,7 +18,9 @@ import type { PropertyDef, TypeDef } from "@party-stack/ontology"; const objectTableFeatures = tableFeatures({ rowSortingFeature, columnSizingFeature, columnResizingFeature }); -export const Route = createFileRoute("/object-types/$objectType")({ component: ObjectTypeDetail }); +export const Route = createFileRoute("/object-types/$objectType")({ + component: ObjectTypeDetail, +}); function ObjectTypeDetail() { const { meta, data } = useOntology(); diff --git a/apps/ontology-explorer/tsconfig.json b/apps/ontology-explorer/tsconfig.json index 8e6e8a5c..1bcb8923 100644 --- a/apps/ontology-explorer/tsconfig.json +++ b/apps/ontology-explorer/tsconfig.json @@ -1 +1,3 @@ -{ "extends": "@party-stack/tanstack-start-build-config/tsconfig.json" } +{ + "extends": "@party-stack/tanstack-start-build-config/tsconfig.json" +} diff --git a/apps/remote-notes/src/notes/NotesViews.tsx b/apps/remote-notes/src/notes/NotesViews.tsx index 57ca5648..8601decc 100644 --- a/apps/remote-notes/src/notes/NotesViews.tsx +++ b/apps/remote-notes/src/notes/NotesViews.tsx @@ -66,7 +66,9 @@ function createClientOntology(userEmail: string) { createHttpRemoteOntologyTransport({ url: "/api/remote-ontology/", ir, - headers: () => ({ "x-demo-user-email": userEmail }), + headers: () => ({ + "x-demo-user-email": userEmail, + }), }), }); return createRemoteNotesLiveOntology({ @@ -74,8 +76,13 @@ function createClientOntology(userEmail: string) { id: "remote-notes", runtime: createWebRuntime, persistObjects: true, - writes: { defaultMode: "outbox", defaultVisibility: "optimistic" }, - context: { user: userEmail }, + writes: { + defaultMode: "outbox", + defaultVisibility: "optimistic", + }, + context: { + user: userEmail, + }, }); } @@ -178,7 +185,11 @@ function SelectChevron() { ); } -type NotesLayoutProps = { selectedNoteId?: string; showEditor: boolean; hideListOnMobile?: boolean }; +type NotesLayoutProps = { + selectedNoteId?: string; + showEditor: boolean; + hideListOnMobile?: boolean; +}; export function NotesHomePage() { return ; @@ -448,7 +459,10 @@ function NoteEditor({ }, } satisfies attachment; - await ontology.actions.createNoteAttachment({ note: noteId, attachment: createdAttachment }); + await ontology.actions.createNoteAttachment({ + note: noteId, + attachment: createdAttachment, + }); return markdownImage(fileAltText(file), attachmentContentUrl(attachmentForUrl, userEmail)); } diff --git a/apps/remote-notes/src/ontology/generated/live.ts b/apps/remote-notes/src/ontology/generated/live.ts index c90dc858..7a632c34 100644 --- a/apps/remote-notes/src/ontology/generated/live.ts +++ b/apps/remote-notes/src/ontology/generated/live.ts @@ -8,5 +8,8 @@ import type { CreateLiveOntologyOpts } from "@party-stack/ontology"; export async function createRemoteNotesLiveOntology< Context extends Record = Record, >(opts: Omit, "ir">): Promise> { - return createLiveOntology({ ...opts, ir: ontology }); + return createLiveOntology({ + ...opts, + ir: ontology, + }); } diff --git a/apps/remote-notes/src/ontology/generated/types.ts b/apps/remote-notes/src/ontology/generated/types.ts index 83cac806..b8394aad 100644 --- a/apps/remote-notes/src/ontology/generated/types.ts +++ b/apps/remote-notes/src/ontology/generated/types.ts @@ -19,9 +19,20 @@ export type NoteAttachment = { createdAt: v.timestamp; }; -export type CreateNoteParameters = { id?: string; title: string; bodyMarkdown: string; ownerEmail?: string }; -export type UpdateNoteParameters = { note: string; title?: string | null; bodyMarkdown?: string | null }; -export type DeleteNoteParameters = { note: string }; +export type CreateNoteParameters = { + id?: string; + title: string; + bodyMarkdown: string; + ownerEmail?: string; +}; +export type UpdateNoteParameters = { + note: string; + title?: string | null; + bodyMarkdown?: string | null; +}; +export type DeleteNoteParameters = { + note: string; +}; export type CreateNoteAttachmentParameters = { id?: string; note: string; @@ -29,12 +40,23 @@ export type CreateNoteAttachmentParameters = { attachment: v.attachment; }; export type RemoteNotesOntology = { - objectTypes: { Note: Note; NoteAttachment: NoteAttachment }; + objectTypes: { + Note: Note; + NoteAttachment: NoteAttachment; + }; actionTypes: { - createNote: { parameters: CreateNoteParameters }; - updateNote: { parameters: UpdateNoteParameters }; - deleteNote: { parameters: DeleteNoteParameters }; - createNoteAttachment: { parameters: CreateNoteAttachmentParameters }; + createNote: { + parameters: CreateNoteParameters; + }; + updateNote: { + parameters: UpdateNoteParameters; + }; + deleteNote: { + parameters: DeleteNoteParameters; + }; + createNoteAttachment: { + parameters: CreateNoteAttachmentParameters; + }; }; queryFunctionTypes: Record; }; diff --git a/apps/remote-notes/src/ontology/ontology.ts b/apps/remote-notes/src/ontology/ontology.ts index ea3b88cb..98f88267 100644 --- a/apps/remote-notes/src/ontology/ontology.ts +++ b/apps/remote-notes/src/ontology/ontology.ts @@ -38,7 +38,12 @@ export const notesOntology = { name: "createNote", displayName: "Create Note", parameters: [ - { name: "id", displayName: "ID", type: o.string({}), defaultValue: o.Expression.uuid({}) }, + { + name: "id", + displayName: "ID", + type: o.string({}), + defaultValue: o.Expression.uuid({}), + }, { name: "title", displayName: "Title", type: o.string({}) }, { name: "bodyMarkdown", displayName: "Body", type: o.string({}) }, { @@ -62,8 +67,14 @@ export const notesOntology = { property: ["bodyMarkdown"], value: o.Expression.inputReference({ name: "bodyMarkdown" }), }, - { property: ["createdAt"], value: o.Expression.now({}) }, - { property: ["updatedAt"], value: o.Expression.now({}) }, + { + property: ["createdAt"], + value: o.Expression.now({}), + }, + { + property: ["updatedAt"], + value: o.Expression.now({}), + }, ], }), ], @@ -85,7 +96,10 @@ export const notesOntology = { property: ["bodyMarkdown"], value: o.Expression.inputReference({ name: "bodyMarkdown" }), }, - { property: ["updatedAt"], value: o.Expression.now({}) }, + { + property: ["updatedAt"], + value: o.Expression.now({}), + }, ], }), ], @@ -134,12 +148,20 @@ export const notesOntology = { property: ["attachment"], value: o.Expression.inputReference({ name: "attachment" }), }, - { property: ["createdAt"], value: o.Expression.now({}) }, + { + property: ["createdAt"], + value: o.Expression.now({}), + }, ], }), o.ActionLogicStep.updateObject({ object: { name: "note" }, - values: [{ property: ["updatedAt"], value: o.Expression.now({}) }], + values: [ + { + property: ["updatedAt"], + value: o.Expression.now({}), + }, + ], }), ], }, diff --git a/apps/remote-notes/src/router.tsx b/apps/remote-notes/src/router.tsx index 1f3f4c97..b51991e3 100644 --- a/apps/remote-notes/src/router.tsx +++ b/apps/remote-notes/src/router.tsx @@ -2,7 +2,10 @@ import { createRouter } from "@tanstack/react-router"; import { routeTree } from "./routeTree.gen"; export function getRouter() { - return createRouter({ routeTree, scrollRestoration: true }); + return createRouter({ + routeTree, + scrollRestoration: true, + }); } declare module "@tanstack/react-router" { diff --git a/apps/remote-notes/src/routes/api.remote-ontology.$endpoint.ts b/apps/remote-notes/src/routes/api.remote-ontology.$endpoint.ts index c9a2a7ec..41c2328d 100644 --- a/apps/remote-notes/src/routes/api.remote-ontology.$endpoint.ts +++ b/apps/remote-notes/src/routes/api.remote-ontology.$endpoint.ts @@ -42,7 +42,9 @@ const remoteServer = createRemoteOntologyServer ({ user: getDemoEmail(request) }), + getContext: (request) => ({ + user: getDemoEmail(request), + }), policy: { clientContext: "forward", baseObjectTypeQueries: { @@ -63,7 +65,11 @@ const remoteServer = createRemoteOntologyServer Task.CreatedDate, "desc") .limit(50) - .select(({ Task }) => ({ ...Task })), + .select(({ Task }) => ({ + ...Task, + })), [data] ); const [taskDialog, setTaskDialog] = useState(); @@ -81,7 +83,11 @@ function ConnectedTaskManager({ ontologies }: { ontologies: SalesforceOntologies const [searching, setSearching] = useState(false); const [actionTypes, setActionTypes] = useState([]); const devtools = useMemo( - () => createOntologyDevtoolsPlugin({ ontology: data, metaOntology: meta }), + () => + createOntologyDevtoolsPlugin({ + ontology: data, + metaOntology: meta, + }), [data, meta] ); @@ -99,7 +105,10 @@ function ConnectedTaskManager({ ontologies }: { ontologies: SalesforceOntologies setSaving(true); try { if (taskDialog) { - await data.actions.updateTask({ recordId: taskDialog.Id, ...input }); + await data.actions.updateTask({ + recordId: taskDialog.Id, + ...input, + }); } else { await data.actions.createTask(input); } @@ -118,7 +127,9 @@ function ConnectedTaskManager({ ontologies }: { ontologies: SalesforceOntologies return; } try { - await data.actions.deleteTask({ recordId: task.Id }); + await data.actions.deleteTask({ + recordId: task.Id, + }); } catch (reason) { setError(reason instanceof Error ? reason.message : String(reason)); } @@ -130,7 +141,10 @@ function ConnectedTaskManager({ ontologies }: { ontologies: SalesforceOntologies } setMovingTaskId(task.Id); try { - await data.actions.updateTask({ recordId: task.Id, Status: status }); + await data.actions.updateTask({ + recordId: task.Id, + Status: status, + }); setError(undefined); } catch (reason) { setError(reason instanceof Error ? reason.message : String(reason)); @@ -154,7 +168,9 @@ function ConnectedTaskManager({ ontologies }: { ontologies: SalesforceOntologies setSearching(true); try { const rows = await queryOnce((query) => { - const from = query.from({ ActionType: meta.objects.ActionType }); + const from = query.from({ + ActionType: meta.objects.ActionType, + }); if (exact && normalized) { return from.where(({ ActionType }) => eq(ActionType.name, normalized)); } @@ -175,7 +191,13 @@ function ConnectedTaskManager({ ontologies }: { ontologies: SalesforceOntologies const taskType = ontology.objectTypes.find((objectType) => objectType.name === "Task"); const statusOptions = enumOptions("Status"); const priorityOptions = enumOptions("Priority"); - const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 6 } })); + const sensors = useSensors( + useSensor(PointerSensor, { + activationConstraint: { + distance: 6, + }, + }) + ); return ( <> @@ -304,7 +326,12 @@ function ConnectedTaskManager({ ontologies }: { ontologies: SalesforceOntologies ) : null} - + ); } @@ -324,7 +351,9 @@ function TaskColumn({ status: Task["Status"]; tasks: Task[]; }) { - const { isOver, setNodeRef } = useDroppable({ id: status }); + const { isOver, setNodeRef } = useDroppable({ + id: status, + }); return (
@@ -358,7 +387,9 @@ function TaskCard({ onEdit: (task: Task) => void; task: Task; }) { - const { attributes, isDragging, listeners, setNodeRef, transform } = useDraggable({ id: task.Id }); + const { attributes, isDragging, listeners, setNodeRef, transform } = useDraggable({ + id: task.Id, + }); return (
void; onSubmit: (event: FormEvent) => void; - priorityOptions: Array<{ label: string; value: string }>; + priorityOptions: Array<{ + label: string; + value: string; + }>; saving: boolean; - statusOptions: Array<{ label: string; value: string }>; + statusOptions: Array<{ + label: string; + value: string; + }>; task: Task | null; }) { const dialog = useRef(null); diff --git a/apps/salesforce-task-manager/src/app/salesforce.ts b/apps/salesforce-task-manager/src/app/salesforce.ts index 4279068a..afeed1ac 100644 --- a/apps/salesforce-task-manager/src/app/salesforce.ts +++ b/apps/salesforce-task-manager/src/app/salesforce.ts @@ -60,7 +60,12 @@ const installation = await createSalesforceBackendInstallation({ // defaultMode: "outbox", defaultVisibility: "optimistic", }, - crudActionTypes: [{ objectType: "Task", operations: ["create", "update", "delete"] }], + crudActionTypes: [ + { + objectType: "Task", + operations: ["create", "update", "delete"], + }, + ], }), ], }); @@ -77,17 +82,25 @@ async function openOntologies(userId: string): Promise { userId, ontologyId: SALESFORCE_TASK_MANAGER_ONTOLOGY_ID, }), - installation.openMetaOntology({ userId, ontologyId: SALESFORCE_TASK_MANAGER_ONTOLOGY_ID }), + installation.openMetaOntology({ + userId, + ontologyId: SALESFORCE_TASK_MANAGER_ONTOLOGY_ID, + }), ]); return { data, meta, userId }; } async function connectWithDevelopmentCredentials() { - const response = await fetch("/__salesforce/dev-session", { method: "POST" }); + const response = await fetch("/__salesforce/dev-session", { + method: "POST", + }); if (!response.ok) { throw new Error(`Development Salesforce session failed (${response.status}).`); } - const result = (await response.json()) as { accessToken?: unknown; userId?: unknown }; + const result = (await response.json()) as { + accessToken?: unknown; + userId?: unknown; + }; if (typeof result.accessToken !== "string" || typeof result.userId !== "string") { throw new Error("Development Salesforce session returned an invalid response."); } @@ -114,7 +127,9 @@ export async function restoreSalesforceOntologies(): Promise { const connection = useDevelopmentCredentials ? await connectWithDevelopmentCredentials() - : await installation.authentication.signIn.oauth({ browserPresentation: "popup" }); + : await installation.authentication.signIn.oauth({ + browserPresentation: "popup", + }); return openOntologies(connection.userId); } diff --git a/apps/salesforce-task-manager/src/ontology/config.ts b/apps/salesforce-task-manager/src/ontology/config.ts index 9efe1a7b..8e23ba20 100644 --- a/apps/salesforce-task-manager/src/ontology/config.ts +++ b/apps/salesforce-task-manager/src/ontology/config.ts @@ -19,7 +19,12 @@ export default createSalesforceOntologyPullConfig({ flowActionTypeNames: ["sales_sfa_flows__CreateSalesLead"], standardActionTypeNames: ["confirmSalesMeeting"], standardQueryFunctionTypeNames: ["getAvailableMeetingTimes"], - crudActionTypes: [{ objectType: "Task", operations: ["create", "update", "delete"] }], + crudActionTypes: [ + { + objectType: "Task", + operations: ["create", "update", "delete"], + }, + ], transformPulledOntology: projectTaskManagerOntology, connection: { userId: settings.userId, diff --git a/apps/salesforce-task-manager/src/ontology/generated/types.ts b/apps/salesforce-task-manager/src/ontology/generated/types.ts index 87ae76b2..1a01b376 100644 --- a/apps/salesforce-task-manager/src/ontology/generated/types.ts +++ b/apps/salesforce-task-manager/src/ontology/generated/types.ts @@ -17,10 +17,18 @@ export type Task = { LastModifiedById: string; }; -export type User = { Id: string; Username: string; Name: string; Email: string }; +export type User = { + Id: string; + Username: string; + Name: string; + Email: string; +}; export type SalesforceTaskManagerOntologyContext = Record; -export type ConfirmSalesMeetingParameters = { meetingRequestId: string; meetingTime: string }; +export type ConfirmSalesMeetingParameters = { + meetingRequestId: string; + meetingTime: string; +}; export type CreateTaskParameters = { WhoId?: string | null; WhatId?: string | null; @@ -502,7 +510,9 @@ export type CreateTaskParameters = { Customer_POC__c?: string | null; Mission__c?: string | null; }; -export type DeleteTaskParameters = { recordId: string }; +export type DeleteTaskParameters = { + recordId: string; +}; export type SalesSfaFlowsCreateSalesLeadParameters = { leadRecord?: { Id?: string; @@ -1127,13 +1137,26 @@ export type GetAvailableMeetingTimesReturn = { }; export type SalesforceTaskManagerOntology = { context: SalesforceTaskManagerOntologyContext; - objectTypes: { Task: Task; User: User }; + objectTypes: { + Task: Task; + User: User; + }; actionTypes: { - confirmSalesMeeting: { parameters: ConfirmSalesMeetingParameters }; - createTask: { parameters: CreateTaskParameters }; - deleteTask: { parameters: DeleteTaskParameters }; - sales_sfa_flows__CreateSalesLead: { parameters: SalesSfaFlowsCreateSalesLeadParameters }; - updateTask: { parameters: UpdateTaskParameters }; + confirmSalesMeeting: { + parameters: ConfirmSalesMeetingParameters; + }; + createTask: { + parameters: CreateTaskParameters; + }; + deleteTask: { + parameters: DeleteTaskParameters; + }; + sales_sfa_flows__CreateSalesLead: { + parameters: SalesSfaFlowsCreateSalesLeadParameters; + }; + updateTask: { + parameters: UpdateTaskParameters; + }; }; queryFunctionTypes: { getAvailableMeetingTimes: { diff --git a/apps/salesforce-task-manager/src/ontology/ontology.test.ts b/apps/salesforce-task-manager/src/ontology/ontology.test.ts index d789cde1..ab9ba7c5 100644 --- a/apps/salesforce-task-manager/src/ontology/ontology.test.ts +++ b/apps/salesforce-task-manager/src/ontology/ontology.test.ts @@ -10,7 +10,9 @@ describe("generated Salesforce task-manager ontology", () => { expect(task.title).toBe("Subject"); expect(task.properties.find((property) => property.name === "CreatedById")?.type).toEqual({ kind: "objectReference", - value: { objectType: "User" }, + value: { + objectType: "User", + }, }); expect(task.properties.find((property) => String(property.name) === "AccountId")).toBeUndefined(); expect(ontology.actionTypes.map((action) => action.name).sort()).toEqual([ @@ -21,7 +23,11 @@ describe("generated Salesforce task-manager ontology", () => { "updateTask", ]); expect(ontology.actionTypes.find((action) => action.name === "createTask")?.meta).toEqual({ - salesforce: { kind: "crud", objectType: "Task", operation: "create" }, + salesforce: { + kind: "crud", + objectType: "Task", + operation: "create", + }, }); expect(ontology.queryFunctionTypes.map((query) => query.name)).toEqual(["getAvailableMeetingTimes"]); }); diff --git a/apps/salesforce-task-manager/src/ontology/ontology.ts b/apps/salesforce-task-manager/src/ontology/ontology.ts index c6a110d5..08bb1dec 100644 --- a/apps/salesforce-task-manager/src/ontology/ontology.ts +++ b/apps/salesforce-task-manager/src/ontology/ontology.ts @@ -11,12 +11,24 @@ export default defineOntology({ primaryKey: "Id", title: "Subject", properties: [ - { name: "Id", displayName: "Activity ID", type: o.string({}) }, - { name: "Subject", displayName: "Subject", type: o.optional({ type: o.string({}) }) }, + { + name: "Id", + displayName: "Activity ID", + type: o.string({}), + }, + { + name: "Subject", + displayName: "Subject", + type: o.optional({ + type: o.string({}), + }), + }, { name: "ActivityDate", displayName: "Due Date Only", - type: o.optional({ type: o.date({}) }), + type: o.optional({ + type: o.date({}), + }), }, { name: "Status", @@ -24,11 +36,26 @@ export default defineOntology({ type: o.string({ constraint: o.StringConstraint.enum({ options: [ - { value: "Not Started", label: "Not Started" }, - { value: "In Progress", label: "In Progress" }, - { value: "Completed", label: "Completed" }, - { value: "Waiting on someone else", label: "Waiting on someone else" }, - { value: "Deferred", label: "Deferred" }, + { + value: "Not Started", + label: "Not Started", + }, + { + value: "In Progress", + label: "In Progress", + }, + { + value: "Completed", + label: "Completed", + }, + { + value: "Waiting on someone else", + label: "Waiting on someone else", + }, + { + value: "Deferred", + label: "Deferred", + }, ], }), }), @@ -39,27 +66,62 @@ export default defineOntology({ type: o.string({ constraint: o.StringConstraint.enum({ options: [ - { value: "High", label: "High" }, - { value: "Normal", label: "Normal" }, - { value: "Low", label: "Low" }, + { + value: "High", + label: "High", + }, + { + value: "Normal", + label: "Normal", + }, + { + value: "Low", + label: "Low", + }, ], }), }), }, - { name: "OwnerId", displayName: "Assigned To ID", type: o.string({}) }, - { name: "Description", displayName: "Description", type: o.optional({ type: o.string({}) }) }, - { name: "IsClosed", displayName: "Closed", type: o.boolean({}) }, - { name: "CreatedDate", displayName: "Created Date", type: o.timestamp({}) }, + { + name: "OwnerId", + displayName: "Assigned To ID", + type: o.string({}), + }, + { + name: "Description", + displayName: "Description", + type: o.optional({ + type: o.string({}), + }), + }, + { + name: "IsClosed", + displayName: "Closed", + type: o.boolean({}), + }, + { + name: "CreatedDate", + displayName: "Created Date", + type: o.timestamp({}), + }, { name: "CreatedById", displayName: "Created By ID", - type: o.objectReference({ objectType: "User" }), + type: o.objectReference({ + objectType: "User", + }), + }, + { + name: "LastModifiedDate", + displayName: "Last Modified Date", + type: o.timestamp({}), }, - { name: "LastModifiedDate", displayName: "Last Modified Date", type: o.timestamp({}) }, { name: "LastModifiedById", displayName: "Last Modified By ID", - type: o.objectReference({ objectType: "User" }), + type: o.objectReference({ + objectType: "User", + }), }, ], }, @@ -70,32 +132,69 @@ export default defineOntology({ primaryKey: "Id", title: "Name", properties: [ - { name: "Id", displayName: "User ID", type: o.string({}) }, - { name: "Username", displayName: "Username", type: o.string({}) }, - { name: "Name", displayName: "Full Name", type: o.string({}) }, - { name: "Email", displayName: "Email", type: o.string({}) }, + { + name: "Id", + displayName: "User ID", + type: o.string({}), + }, + { + name: "Username", + displayName: "Username", + type: o.string({}), + }, + { + name: "Name", + displayName: "Full Name", + type: o.string({}), + }, + { + name: "Email", + displayName: "Email", + type: o.string({}), + }, ], }, ], linkTypes: [ { id: "salesforce:link:Task.CreatedById", - source: { objectType: "Task", name: "tasks", displayName: "Tasks" }, - target: { objectType: "User", name: "CreatedBy", displayName: "Created By ID" }, + source: { + objectType: "Task", + name: "tasks", + displayName: "Tasks", + }, + target: { + objectType: "User", + name: "CreatedBy", + displayName: "Created By ID", + }, foreignKey: "CreatedById", cardinality: "many", }, { id: "salesforce:link:Task.LastModifiedById", - source: { objectType: "Task", name: "tasks", displayName: "Tasks" }, - target: { objectType: "User", name: "LastModifiedBy", displayName: "Last Modified By ID" }, + source: { + objectType: "Task", + name: "tasks", + displayName: "Tasks", + }, + target: { + objectType: "User", + name: "LastModifiedBy", + displayName: "Last Modified By ID", + }, foreignKey: "LastModifiedById", cardinality: "many", }, ], actionTypes: [ { - meta: { salesforce: { kind: "standard", apiName: "confirmSalesMeeting" } }, + meta: { + salesforce: { + kind: "standard", + apiName: "confirmSalesMeeting", + }, + }, name: "confirmSalesMeeting", displayName: "Confirm Sales Meeting", parameters: [ @@ -118,17 +217,43 @@ export default defineOntology({ description: "Confirms a meeting time that’s part of a request to Get Meeting Time Slots.", }, { - meta: { salesforce: { kind: "crud", objectType: "Task", operation: "create" } }, + meta: { + salesforce: { + kind: "crud", + objectType: "Task", + operation: "create", + }, + }, name: "createTask", displayName: "Create Task", parameters: [ - { name: "WhoId", displayName: "Name ID", type: o.optional({ type: o.string({}) }) }, - { name: "WhatId", displayName: "Related To ID", type: o.optional({ type: o.string({}) }) }, - { name: "Subject", displayName: "Subject", type: o.optional({ type: o.string({}) }) }, + { + name: "WhoId", + displayName: "Name ID", + type: o.optional({ + type: o.string({}), + }), + }, + { + name: "WhatId", + displayName: "Related To ID", + type: o.optional({ + type: o.string({}), + }), + }, + { + name: "Subject", + displayName: "Subject", + type: o.optional({ + type: o.string({}), + }), + }, { name: "ActivityDate", displayName: "Due Date Only", - type: o.optional({ type: o.date({}) }), + type: o.optional({ + type: o.date({}), + }), }, { name: "Status", @@ -137,11 +262,26 @@ export default defineOntology({ type: o.string({ constraint: o.StringConstraint.enum({ options: [ - { value: "Not Started", label: "Not Started" }, - { value: "In Progress", label: "In Progress" }, - { value: "Completed", label: "Completed" }, - { value: "Waiting on someone else", label: "Waiting on someone else" }, - { value: "Deferred", label: "Deferred" }, + { + value: "Not Started", + label: "Not Started", + }, + { + value: "In Progress", + label: "In Progress", + }, + { + value: "Completed", + label: "Completed", + }, + { + value: "Waiting on someone else", + label: "Waiting on someone else", + }, + { + value: "Deferred", + label: "Deferred", + }, ], }), }), @@ -154,20 +294,43 @@ export default defineOntology({ type: o.string({ constraint: o.StringConstraint.enum({ options: [ - { value: "High", label: "High" }, - { value: "Normal", label: "Normal" }, - { value: "Low", label: "Low" }, + { + value: "High", + label: "High", + }, + { + value: "Normal", + label: "Normal", + }, + { + value: "Low", + label: "Low", + }, ], }), }), }), }, - { name: "OwnerId", displayName: "Assigned To ID", type: o.optional({ type: o.string({}) }) }, - { name: "Description", displayName: "Description", type: o.optional({ type: o.string({}) }) }, + { + name: "OwnerId", + displayName: "Assigned To ID", + type: o.optional({ + type: o.string({}), + }), + }, + { + name: "Description", + displayName: "Description", + type: o.optional({ + type: o.string({}), + }), + }, { name: "CallDurationInSeconds", displayName: "Call Duration", - type: o.optional({ type: o.integer({}) }), + type: o.optional({ + type: o.integer({}), + }), }, { name: "CallType", @@ -176,9 +339,18 @@ export default defineOntology({ type: o.string({ constraint: o.StringConstraint.enum({ options: [ - { value: "Internal", label: "Internal" }, - { value: "Inbound", label: "Inbound" }, - { value: "Outbound", label: "Outbound" }, + { + value: "Internal", + label: "Internal", + }, + { + value: "Inbound", + label: "Inbound", + }, + { + value: "Outbound", + label: "Outbound", + }, ], }), }), @@ -187,37 +359,51 @@ export default defineOntology({ { name: "CallDisposition", displayName: "Call Result", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), }, { name: "CallObject", displayName: "Call Object Identifier", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), }, { name: "ReminderDateTime", displayName: "Reminder Date/Time", - type: o.optional({ type: o.timestamp({}) }), + type: o.optional({ + type: o.timestamp({}), + }), }, { name: "IsReminderSet", displayName: "Reminder Set", - type: o.optional({ type: o.boolean({}) }), + type: o.optional({ + type: o.boolean({}), + }), }, { name: "IsRecurrence", displayName: "Create Recurring Series of Tasks", - type: o.optional({ type: o.boolean({}) }), + type: o.optional({ + type: o.boolean({}), + }), }, { name: "RecurrenceStartDateOnly", displayName: "Recurrence Start", - type: o.optional({ type: o.date({}) }), + type: o.optional({ + type: o.date({}), + }), }, { name: "RecurrenceEndDateOnly", displayName: "Recurrence End", - type: o.optional({ type: o.date({}) }), + type: o.optional({ + type: o.date({}), + }), }, { name: "RecurrenceTimeZoneSidKey", @@ -394,7 +580,10 @@ export default defineOntology({ value: "Pacific/Saipan", label: "(GMT+10:00) Chamorro Standard Time (Pacific/Saipan)", }, - { value: "Pacific/Truk", label: "(GMT+10:00) Chuuk Time (Pacific/Truk)" }, + { + value: "Pacific/Truk", + label: "(GMT+10:00) Chuuk Time (Pacific/Truk)", + }, { value: "Australia/Adelaide", label: "(GMT+09:30) Australian Central Standard Time (Australia/Adelaide)", @@ -411,7 +600,10 @@ export default defineOntology({ value: "Asia/Chita", label: "(GMT+09:00) Yakutsk Standard Time (Asia/Chita)", }, - { value: "Asia/Dili", label: "(GMT+09:00) East Timor Time (Asia/Dili)" }, + { + value: "Asia/Dili", + label: "(GMT+09:00) East Timor Time (Asia/Dili)", + }, { value: "Asia/Jayapura", label: "(GMT+09:00) Eastern Indonesia Time (Asia/Jayapura)", @@ -556,7 +748,10 @@ export default defineOntology({ value: "Indian/Christmas", label: "(GMT+07:00) Christmas Island Time (Indian/Christmas)", }, - { value: "Asia/Yangon", label: "(GMT+06:30) Myanmar Time (Asia/Yangon)" }, + { + value: "Asia/Yangon", + label: "(GMT+06:30) Myanmar Time (Asia/Yangon)", + }, { value: "Indian/Cocos", label: "(GMT+06:30) Cocos Islands Time (Indian/Cocos)", @@ -1249,7 +1444,10 @@ export default defineOntology({ value: "Atlantic/St_Helena", label: "(GMT+00:00) Greenwich Mean Time (Atlantic/St_Helena)", }, - { value: "GMT", label: "(GMT+00:00) Greenwich Mean Time (GMT)" }, + { + value: "GMT", + label: "(GMT+00:00) Greenwich Mean Time (GMT)", + }, { value: "America/Nuuk", label: "(GMT-01:00) West Greenland Summer Time (America/Nuuk)", @@ -1902,7 +2100,10 @@ export default defineOntology({ value: "Pacific/Midway", label: "(GMT-11:00) Samoa Standard Time (Pacific/Midway)", }, - { value: "Pacific/Niue", label: "(GMT-11:00) Niue Time (Pacific/Niue)" }, + { + value: "Pacific/Niue", + label: "(GMT-11:00) Niue Time (Pacific/Niue)", + }, { value: "Pacific/Pago_Pago", label: "(GMT-11:00) Samoa Standard Time (Pacific/Pago_Pago)", @@ -1919,13 +2120,34 @@ export default defineOntology({ type: o.string({ constraint: o.StringConstraint.enum({ options: [ - { value: "RecursDaily", label: "Recurs Daily" }, - { value: "RecursEveryWeekday", label: "Recurs Every Weekday" }, - { value: "RecursMonthly", label: "Recurs Monthly" }, - { value: "RecursMonthlyNth", label: "Recurs Monthly Nth" }, - { value: "RecursWeekly", label: "Recurs Weekly" }, - { value: "RecursYearly", label: "Recurs Yearly" }, - { value: "RecursYearlyNth", label: "Recurs Yearly Nth" }, + { + value: "RecursDaily", + label: "Recurs Daily", + }, + { + value: "RecursEveryWeekday", + label: "Recurs Every Weekday", + }, + { + value: "RecursMonthly", + label: "Recurs Monthly", + }, + { + value: "RecursMonthlyNth", + label: "Recurs Monthly Nth", + }, + { + value: "RecursWeekly", + label: "Recurs Weekly", + }, + { + value: "RecursYearly", + label: "Recurs Yearly", + }, + { + value: "RecursYearlyNth", + label: "Recurs Yearly Nth", + }, ], }), }), @@ -1934,17 +2156,23 @@ export default defineOntology({ { name: "RecurrenceInterval", displayName: "Recurrence Interval", - type: o.optional({ type: o.integer({}) }), + type: o.optional({ + type: o.integer({}), + }), }, { name: "RecurrenceDayOfWeekMask", displayName: "Recurrence Day of Week Mask", - type: o.optional({ type: o.integer({}) }), + type: o.optional({ + type: o.integer({}), + }), }, { name: "RecurrenceDayOfMonth", displayName: "Recurrence Day of Month", - type: o.optional({ type: o.integer({}) }), + type: o.optional({ + type: o.integer({}), + }), }, { name: "RecurrenceInstance", @@ -1953,11 +2181,26 @@ export default defineOntology({ type: o.string({ constraint: o.StringConstraint.enum({ options: [ - { value: "First", label: "1st" }, - { value: "Second", label: "2nd" }, - { value: "Third", label: "3rd" }, - { value: "Fourth", label: "4th" }, - { value: "Last", label: "last" }, + { + value: "First", + label: "1st", + }, + { + value: "Second", + label: "2nd", + }, + { + value: "Third", + label: "3rd", + }, + { + value: "Fourth", + label: "4th", + }, + { + value: "Last", + label: "last", + }, ], }), }), @@ -1970,18 +2213,54 @@ export default defineOntology({ type: o.string({ constraint: o.StringConstraint.enum({ options: [ - { value: "January", label: "January" }, - { value: "February", label: "February" }, - { value: "March", label: "March" }, - { value: "April", label: "April" }, - { value: "May", label: "May" }, - { value: "June", label: "June" }, - { value: "July", label: "July" }, - { value: "August", label: "August" }, - { value: "September", label: "September" }, - { value: "October", label: "October" }, - { value: "November", label: "November" }, - { value: "December", label: "December" }, + { + value: "January", + label: "January", + }, + { + value: "February", + label: "February", + }, + { + value: "March", + label: "March", + }, + { + value: "April", + label: "April", + }, + { + value: "May", + label: "May", + }, + { + value: "June", + label: "June", + }, + { + value: "July", + label: "July", + }, + { + value: "August", + label: "August", + }, + { + value: "September", + label: "September", + }, + { + value: "October", + label: "October", + }, + { + value: "November", + label: "November", + }, + { + value: "December", + label: "December", + }, ], }), }), @@ -1994,12 +2273,18 @@ export default defineOntology({ type: o.string({ constraint: o.StringConstraint.enum({ options: [ - { value: "RecurrenceRegenerateAfterDueDate", label: "After due date" }, + { + value: "RecurrenceRegenerateAfterDueDate", + label: "After due date", + }, { value: "RecurrenceRegenerateAfterToday", label: "After date completed", }, - { value: "RecurrenceRegenerated", label: "(Task Closed)" }, + { + value: "RecurrenceRegenerated", + label: "(Task Closed)", + }, ], }), }), @@ -2012,12 +2297,30 @@ export default defineOntology({ type: o.string({ constraint: o.StringConstraint.enum({ options: [ - { value: "Task", label: "Task" }, - { value: "Email", label: "Email" }, - { value: "ListEmail", label: "List Email" }, - { value: "Cadence", label: "Cadence" }, - { value: "Call", label: "Call" }, - { value: "LinkedIn", label: "LinkedIn" }, + { + value: "Task", + label: "Task", + }, + { + value: "Email", + label: "Email", + }, + { + value: "ListEmail", + label: "List Email", + }, + { + value: "Cadence", + label: "Cadence", + }, + { + value: "Call", + label: "Call", + }, + { + value: "LinkedIn", + label: "LinkedIn", + }, ], }), }), @@ -2030,9 +2333,18 @@ export default defineOntology({ type: o.string({ constraint: o.StringConstraint.enum({ options: [ - { value: "FTX", label: "FTX" }, - { value: "Deployment", label: "Deployment" }, - { value: "Customer Demo", label: "Customer Demo" }, + { + value: "FTX", + label: "FTX", + }, + { + value: "Deployment", + label: "Deployment", + }, + { + value: "Customer Demo", + label: "Customer Demo", + }, ], }), }), @@ -2041,124 +2353,234 @@ export default defineOntology({ { name: "Customer_POC__c", displayName: "Customer POC", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), + }, + { + name: "Mission__c", + displayName: "Mission", + type: o.optional({ + type: o.string({}), + }), }, - { name: "Mission__c", displayName: "Mission", type: o.optional({ type: o.string({}) }) }, ], logic: [ o.ActionLogicStep.createObject({ objectType: "Task", values: [ - { property: ["WhoId"], value: o.Expression.inputReference({ name: "WhoId" }) }, - { property: ["WhatId"], value: o.Expression.inputReference({ name: "WhatId" }) }, - { property: ["Subject"], value: o.Expression.inputReference({ name: "Subject" }) }, + { + property: ["WhoId"], + value: o.Expression.inputReference({ + name: "WhoId", + }), + }, + { + property: ["WhatId"], + value: o.Expression.inputReference({ + name: "WhatId", + }), + }, + { + property: ["Subject"], + value: o.Expression.inputReference({ + name: "Subject", + }), + }, { property: ["ActivityDate"], - value: o.Expression.inputReference({ name: "ActivityDate" }), + value: o.Expression.inputReference({ + name: "ActivityDate", + }), + }, + { + property: ["Status"], + value: o.Expression.inputReference({ + name: "Status", + }), + }, + { + property: ["Priority"], + value: o.Expression.inputReference({ + name: "Priority", + }), + }, + { + property: ["OwnerId"], + value: o.Expression.inputReference({ + name: "OwnerId", + }), }, - { property: ["Status"], value: o.Expression.inputReference({ name: "Status" }) }, - { property: ["Priority"], value: o.Expression.inputReference({ name: "Priority" }) }, - { property: ["OwnerId"], value: o.Expression.inputReference({ name: "OwnerId" }) }, { property: ["Description"], - value: o.Expression.inputReference({ name: "Description" }), + value: o.Expression.inputReference({ + name: "Description", + }), }, { property: ["CallDurationInSeconds"], - value: o.Expression.inputReference({ name: "CallDurationInSeconds" }), + value: o.Expression.inputReference({ + name: "CallDurationInSeconds", + }), + }, + { + property: ["CallType"], + value: o.Expression.inputReference({ + name: "CallType", + }), }, - { property: ["CallType"], value: o.Expression.inputReference({ name: "CallType" }) }, { property: ["CallDisposition"], - value: o.Expression.inputReference({ name: "CallDisposition" }), + value: o.Expression.inputReference({ + name: "CallDisposition", + }), }, { property: ["CallObject"], - value: o.Expression.inputReference({ name: "CallObject" }), + value: o.Expression.inputReference({ + name: "CallObject", + }), }, { property: ["ReminderDateTime"], - value: o.Expression.inputReference({ name: "ReminderDateTime" }), + value: o.Expression.inputReference({ + name: "ReminderDateTime", + }), }, { property: ["IsReminderSet"], - value: o.Expression.inputReference({ name: "IsReminderSet" }), + value: o.Expression.inputReference({ + name: "IsReminderSet", + }), }, { property: ["IsRecurrence"], - value: o.Expression.inputReference({ name: "IsRecurrence" }), + value: o.Expression.inputReference({ + name: "IsRecurrence", + }), }, { property: ["RecurrenceStartDateOnly"], - value: o.Expression.inputReference({ name: "RecurrenceStartDateOnly" }), + value: o.Expression.inputReference({ + name: "RecurrenceStartDateOnly", + }), }, { property: ["RecurrenceEndDateOnly"], - value: o.Expression.inputReference({ name: "RecurrenceEndDateOnly" }), + value: o.Expression.inputReference({ + name: "RecurrenceEndDateOnly", + }), }, { property: ["RecurrenceTimeZoneSidKey"], - value: o.Expression.inputReference({ name: "RecurrenceTimeZoneSidKey" }), + value: o.Expression.inputReference({ + name: "RecurrenceTimeZoneSidKey", + }), }, { property: ["RecurrenceType"], - value: o.Expression.inputReference({ name: "RecurrenceType" }), + value: o.Expression.inputReference({ + name: "RecurrenceType", + }), }, { property: ["RecurrenceInterval"], - value: o.Expression.inputReference({ name: "RecurrenceInterval" }), + value: o.Expression.inputReference({ + name: "RecurrenceInterval", + }), }, { property: ["RecurrenceDayOfWeekMask"], - value: o.Expression.inputReference({ name: "RecurrenceDayOfWeekMask" }), + value: o.Expression.inputReference({ + name: "RecurrenceDayOfWeekMask", + }), }, { property: ["RecurrenceDayOfMonth"], - value: o.Expression.inputReference({ name: "RecurrenceDayOfMonth" }), + value: o.Expression.inputReference({ + name: "RecurrenceDayOfMonth", + }), }, { property: ["RecurrenceInstance"], - value: o.Expression.inputReference({ name: "RecurrenceInstance" }), + value: o.Expression.inputReference({ + name: "RecurrenceInstance", + }), }, { property: ["RecurrenceMonthOfYear"], - value: o.Expression.inputReference({ name: "RecurrenceMonthOfYear" }), + value: o.Expression.inputReference({ + name: "RecurrenceMonthOfYear", + }), }, { property: ["RecurrenceRegeneratedType"], - value: o.Expression.inputReference({ name: "RecurrenceRegeneratedType" }), + value: o.Expression.inputReference({ + name: "RecurrenceRegeneratedType", + }), }, { property: ["TaskSubtype"], - value: o.Expression.inputReference({ name: "TaskSubtype" }), + value: o.Expression.inputReference({ + name: "TaskSubtype", + }), }, { property: ["Event_Type__c"], - value: o.Expression.inputReference({ name: "Event_Type__c" }), + value: o.Expression.inputReference({ + name: "Event_Type__c", + }), }, { property: ["Customer_POC__c"], - value: o.Expression.inputReference({ name: "Customer_POC__c" }), + value: o.Expression.inputReference({ + name: "Customer_POC__c", + }), }, { property: ["Mission__c"], - value: o.Expression.inputReference({ name: "Mission__c" }), + value: o.Expression.inputReference({ + name: "Mission__c", + }), }, ], }), ], }, { - meta: { salesforce: { kind: "crud", objectType: "Task", operation: "delete" } }, + meta: { + salesforce: { + kind: "crud", + objectType: "Task", + operation: "delete", + }, + }, name: "deleteTask", displayName: "Delete Task", parameters: [ - { name: "recordId", displayName: "Task ID", type: o.objectReference({ objectType: "Task" }) }, + { + name: "recordId", + displayName: "Task ID", + type: o.objectReference({ + objectType: "Task", + }), + }, + ], + logic: [ + o.ActionLogicStep.deleteObject({ + object: { + name: "recordId", + }, + }), ], - logic: [o.ActionLogicStep.deleteObject({ object: { name: "recordId" } })], }, { - meta: { salesforce: { kind: "flow", apiName: "sales_sfa_flows__CreateSalesLead" } }, + meta: { + salesforce: { + kind: "flow", + apiName: "sales_sfa_flows__CreateSalesLead", + }, + }, name: "sales_sfa_flows__CreateSalesLead", displayName: "Create Sales Lead Record", parameters: [ @@ -2171,27 +2593,37 @@ export default defineOntology({ { name: "Id", displayName: "Lead ID", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), }, { name: "IsDeleted", displayName: "Deleted", - type: o.optional({ type: o.boolean({}) }), + type: o.optional({ + type: o.boolean({}), + }), }, { name: "MasterRecordId", displayName: "Master Record ID", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), }, { name: "LastName", displayName: "Last Name", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), }, { name: "FirstName", displayName: "First Name", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), }, { name: "Salutation", @@ -2200,12 +2632,30 @@ export default defineOntology({ type: o.string({ constraint: o.StringConstraint.enum({ options: [ - { value: "Mr.", label: "Mr." }, - { value: "Ms.", label: "Ms." }, - { value: "Mrs.", label: "Mrs." }, - { value: "Dr.", label: "Dr." }, - { value: "Prof.", label: "Prof." }, - { value: "Mx.", label: "Mx." }, + { + value: "Mr.", + label: "Mr.", + }, + { + value: "Ms.", + label: "Ms.", + }, + { + value: "Mrs.", + label: "Mrs.", + }, + { + value: "Dr.", + label: "Dr.", + }, + { + value: "Prof.", + label: "Prof.", + }, + { + value: "Mx.", + label: "Mx.", + }, ], }), }), @@ -2214,52 +2664,72 @@ export default defineOntology({ { name: "Name", displayName: "Full Name", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), }, { name: "Title", displayName: "Title", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), }, { name: "Company", displayName: "Company", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), }, { name: "Street", displayName: "Street", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), }, { name: "City", displayName: "City", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), }, { name: "State", displayName: "State/Province", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), }, { name: "PostalCode", displayName: "Zip/Postal Code", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), }, { name: "Country", displayName: "Country", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), }, { name: "Latitude", displayName: "Latitude", - type: o.optional({ type: o.double({}) }), + type: o.optional({ + type: o.double({}), + }), }, { name: "Longitude", displayName: "Longitude", - type: o.optional({ type: o.double({}) }), + type: o.optional({ + type: o.double({}), + }), }, { name: "GeocodeAccuracy", @@ -2268,17 +2738,50 @@ export default defineOntology({ type: o.string({ constraint: o.StringConstraint.enum({ options: [ - { value: "Address", label: "Address" }, - { value: "NearAddress", label: "Near Address" }, - { value: "Block", label: "Block" }, - { value: "Street", label: "Street" }, - { value: "ExtendedZip", label: "Extended Zip" }, - { value: "Zip", label: "Zip" }, - { value: "Neighborhood", label: "Neighborhood" }, - { value: "City", label: "City" }, - { value: "County", label: "County" }, - { value: "State", label: "State" }, - { value: "Unknown", label: "Unknown" }, + { + value: "Address", + label: "Address", + }, + { + value: "NearAddress", + label: "Near Address", + }, + { + value: "Block", + label: "Block", + }, + { + value: "Street", + label: "Street", + }, + { + value: "ExtendedZip", + label: "Extended Zip", + }, + { + value: "Zip", + label: "Zip", + }, + { + value: "Neighborhood", + label: "Neighborhood", + }, + { + value: "City", + label: "City", + }, + { + value: "County", + label: "County", + }, + { + value: "State", + label: "State", + }, + { + value: "Unknown", + label: "Unknown", + }, ], }), }), @@ -2287,27 +2790,37 @@ export default defineOntology({ { name: "Phone", displayName: "Phone", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), }, { name: "Email", displayName: "Email", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), }, { name: "Website", displayName: "Website", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), }, { name: "PhotoUrl", displayName: "Photo URL", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), }, { name: "Description", displayName: "Description", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), }, { name: "LeadSource", @@ -2316,7 +2829,10 @@ export default defineOntology({ type: o.string({ constraint: o.StringConstraint.enum({ options: [ - { value: "Advertisement", label: "Advertisement" }, + { + value: "Advertisement", + label: "Advertisement", + }, { value: "Employee Referral", label: "Employee Referral", @@ -2325,8 +2841,14 @@ export default defineOntology({ value: "External Referral", label: "External Referral", }, - { value: "Partner", label: "Partner" }, - { value: "Public Relations", label: "Public Relations" }, + { + value: "Partner", + label: "Partner", + }, + { + value: "Public Relations", + label: "Public Relations", + }, { value: "Seminar - Internal", label: "Seminar - Internal", @@ -2335,10 +2857,22 @@ export default defineOntology({ value: "Seminar - Partner", label: "Seminar - Partner", }, - { value: "Trade Show", label: "Trade Show" }, - { value: "Web", label: "Web" }, - { value: "Word of mouth", label: "Word of mouth" }, - { value: "Other", label: "Other" }, + { + value: "Trade Show", + label: "Trade Show", + }, + { + value: "Web", + label: "Web", + }, + { + value: "Word of mouth", + label: "Word of mouth", + }, + { + value: "Other", + label: "Other", + }, ], }), }), @@ -2351,11 +2885,26 @@ export default defineOntology({ type: o.string({ constraint: o.StringConstraint.enum({ options: [ - { value: "New", label: "New" }, - { value: "Contacted", label: "Contacted" }, - { value: "Nurturing", label: "Nurturing" }, - { value: "Qualified", label: "Qualified" }, - { value: "Unqualified", label: "Unqualified" }, + { + value: "New", + label: "New", + }, + { + value: "Contacted", + label: "Contacted", + }, + { + value: "Nurturing", + label: "Nurturing", + }, + { + value: "Qualified", + label: "Qualified", + }, + { + value: "Unqualified", + label: "Unqualified", + }, ], }), }), @@ -2368,41 +2917,134 @@ export default defineOntology({ type: o.string({ constraint: o.StringConstraint.enum({ options: [ - { value: "Agriculture", label: "Agriculture" }, - { value: "Apparel", label: "Apparel" }, - { value: "Banking", label: "Banking" }, - { value: "Biotechnology", label: "Biotechnology" }, - { value: "Chemicals", label: "Chemicals" }, - { value: "Communications", label: "Communications" }, - { value: "Construction", label: "Construction" }, - { value: "Consulting", label: "Consulting" }, - { value: "Education", label: "Education" }, - { value: "Electronics", label: "Electronics" }, - { value: "Energy", label: "Energy" }, - { value: "Engineering", label: "Engineering" }, - { value: "Entertainment", label: "Entertainment" }, - { value: "Environmental", label: "Environmental" }, - { value: "Finance", label: "Finance" }, - { value: "Food & Beverage", label: "Food & Beverage" }, - { value: "Government", label: "Government" }, - { value: "Healthcare", label: "Healthcare" }, - { value: "Hospitality", label: "Hospitality" }, - { value: "Insurance", label: "Insurance" }, - { value: "Machinery", label: "Machinery" }, - { value: "Manufacturing", label: "Manufacturing" }, - { value: "Media", label: "Media" }, - { value: "Not For Profit", label: "Not For Profit" }, - { value: "Other", label: "Other" }, - { value: "Recreation", label: "Recreation" }, - { value: "Retail", label: "Retail" }, - { value: "Shipping", label: "Shipping" }, - { value: "Technology", label: "Technology" }, + { + value: "Agriculture", + label: "Agriculture", + }, + { + value: "Apparel", + label: "Apparel", + }, + { + value: "Banking", + label: "Banking", + }, + { + value: "Biotechnology", + label: "Biotechnology", + }, + { + value: "Chemicals", + label: "Chemicals", + }, + { + value: "Communications", + label: "Communications", + }, + { + value: "Construction", + label: "Construction", + }, + { + value: "Consulting", + label: "Consulting", + }, + { + value: "Education", + label: "Education", + }, + { + value: "Electronics", + label: "Electronics", + }, + { + value: "Energy", + label: "Energy", + }, + { + value: "Engineering", + label: "Engineering", + }, + { + value: "Entertainment", + label: "Entertainment", + }, + { + value: "Environmental", + label: "Environmental", + }, + { + value: "Finance", + label: "Finance", + }, + { + value: "Food & Beverage", + label: "Food & Beverage", + }, + { + value: "Government", + label: "Government", + }, + { + value: "Healthcare", + label: "Healthcare", + }, + { + value: "Hospitality", + label: "Hospitality", + }, + { + value: "Insurance", + label: "Insurance", + }, + { + value: "Machinery", + label: "Machinery", + }, + { + value: "Manufacturing", + label: "Manufacturing", + }, + { + value: "Media", + label: "Media", + }, + { + value: "Not For Profit", + label: "Not For Profit", + }, + { + value: "Other", + label: "Other", + }, + { + value: "Recreation", + label: "Recreation", + }, + { + value: "Retail", + label: "Retail", + }, + { + value: "Shipping", + label: "Shipping", + }, + { + value: "Technology", + label: "Technology", + }, { value: "Telecommunications", label: "Telecommunications", }, - { value: "Transportation", label: "Transportation" }, - { value: "Utilities", label: "Utilities" }, + { + value: "Transportation", + label: "Transportation", + }, + { + value: "Utilities", + label: "Utilities", + }, ], }), }), @@ -2415,9 +3057,18 @@ export default defineOntology({ type: o.string({ constraint: o.StringConstraint.enum({ options: [ - { value: "Hot", label: "Hot" }, - { value: "Warm", label: "Warm" }, - { value: "Cold", label: "Cold" }, + { + value: "Hot", + label: "Hot", + }, + { + value: "Warm", + label: "Warm", + }, + { + value: "Cold", + label: "Cold", + }, ], }), }), @@ -2426,127 +3077,181 @@ export default defineOntology({ { name: "AnnualRevenue", displayName: "Annual Revenue", - type: o.optional({ type: o.double({}) }), + type: o.optional({ + type: o.double({}), + }), }, { name: "NumberOfEmployees", displayName: "Employees", - type: o.optional({ type: o.integer({}) }), + type: o.optional({ + type: o.integer({}), + }), }, { name: "OwnerId", displayName: "Owner ID", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), }, { name: "HasOptedOutOfEmail", displayName: "Email Opt Out", - type: o.optional({ type: o.boolean({}) }), + type: o.optional({ + type: o.boolean({}), + }), }, { name: "IsConverted", displayName: "Converted", - type: o.optional({ type: o.boolean({}) }), + type: o.optional({ + type: o.boolean({}), + }), }, { name: "ConvertedDate", displayName: "Converted Date", - type: o.optional({ type: o.date({}) }), + type: o.optional({ + type: o.date({}), + }), }, { name: "ConvertedAccountId", displayName: "Converted Account ID", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), }, { name: "ConvertedContactId", displayName: "Converted Contact ID", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), }, { name: "ConvertedOpportunityId", displayName: "Converted Opportunity ID", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), }, { name: "IsUnreadByOwner", displayName: "Unread By Owner", - type: o.optional({ type: o.boolean({}) }), + type: o.optional({ + type: o.boolean({}), + }), }, { name: "CreatedDate", displayName: "Created Date", - type: o.optional({ type: o.timestamp({}) }), + type: o.optional({ + type: o.timestamp({}), + }), }, { name: "CreatedById", displayName: "Created By ID", - type: o.optional({ type: o.objectReference({ objectType: "User" }) }), + type: o.optional({ + type: o.objectReference({ + objectType: "User", + }), + }), }, { name: "LastModifiedDate", displayName: "Last Modified Date", - type: o.optional({ type: o.timestamp({}) }), + type: o.optional({ + type: o.timestamp({}), + }), }, { name: "LastModifiedById", displayName: "Last Modified By ID", - type: o.optional({ type: o.objectReference({ objectType: "User" }) }), + type: o.optional({ + type: o.objectReference({ + objectType: "User", + }), + }), }, { name: "SystemModstamp", displayName: "System Modstamp", - type: o.optional({ type: o.timestamp({}) }), + type: o.optional({ + type: o.timestamp({}), + }), }, { name: "LastActivityDate", displayName: "Last Activity", - type: o.optional({ type: o.date({}) }), + type: o.optional({ + type: o.date({}), + }), }, { name: "LastViewedDate", displayName: "Last Viewed Date", - type: o.optional({ type: o.timestamp({}) }), + type: o.optional({ + type: o.timestamp({}), + }), }, { name: "LastReferencedDate", displayName: "Last Referenced Date", - type: o.optional({ type: o.timestamp({}) }), + type: o.optional({ + type: o.timestamp({}), + }), }, { name: "Jigsaw", displayName: "Data.com Key", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), }, { name: "JigsawContactId", displayName: "Jigsaw Contact ID", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), }, { name: "EmailBouncedReason", displayName: "Email Bounced Reason", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), }, { name: "EmailBouncedDate", displayName: "Email Bounced Date", - type: o.optional({ type: o.timestamp({}) }), + type: o.optional({ + type: o.timestamp({}), + }), }, { name: "IndividualId", displayName: "Individual ID", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), }, { name: "ActionCadenceId", displayName: "Cadence ID", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), }, { name: "ActionCadenceAssigneeId", displayName: "Cadence Assignee ID", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), }, { name: "ActionCadenceState", @@ -2555,12 +3260,30 @@ export default defineOntology({ type: o.string({ constraint: o.StringConstraint.enum({ options: [ - { value: "Running", label: "Running" }, - { value: "Complete", label: "Complete" }, - { value: "Initializing", label: "Initializing" }, - { value: "Paused", label: "Paused" }, - { value: "Processing", label: "Processing" }, - { value: "Error", label: "Error" }, + { + value: "Running", + label: "Running", + }, + { + value: "Complete", + label: "Complete", + }, + { + value: "Initializing", + label: "Initializing", + }, + { + value: "Paused", + label: "Paused", + }, + { + value: "Processing", + label: "Processing", + }, + { + value: "Error", + label: "Error", + }, ], }), }), @@ -2569,32 +3292,44 @@ export default defineOntology({ { name: "ScheduledResumeDateTime", displayName: "Cadence Resume Time", - type: o.optional({ type: o.timestamp({}) }), + type: o.optional({ + type: o.timestamp({}), + }), }, { name: "ActiveTrackerCount", displayName: "Total Cadences", - type: o.optional({ type: o.integer({}) }), + type: o.optional({ + type: o.integer({}), + }), }, { name: "FirstCallDateTime", displayName: "First Call Date", - type: o.optional({ type: o.timestamp({}) }), + type: o.optional({ + type: o.timestamp({}), + }), }, { name: "FirstEmailDateTime", displayName: "First Email Date", - type: o.optional({ type: o.timestamp({}) }), + type: o.optional({ + type: o.timestamp({}), + }), }, { name: "ActivityMetricId", displayName: "Activity Metric ID", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), }, { name: "ActivityMetricRollupId", displayName: "Activity Metric Rollup ID", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), }, ], }), @@ -2607,18 +3342,50 @@ export default defineOntology({ "Searches for an existing lead record. If one isn’t found, creates a new lead record.", }, { - meta: { salesforce: { kind: "crud", objectType: "Task", operation: "update" } }, + meta: { + salesforce: { + kind: "crud", + objectType: "Task", + operation: "update", + }, + }, name: "updateTask", displayName: "Update Task", parameters: [ - { name: "recordId", displayName: "Task ID", type: o.objectReference({ objectType: "Task" }) }, - { name: "WhoId", displayName: "Name ID", type: o.optional({ type: o.string({}) }) }, - { name: "WhatId", displayName: "Related To ID", type: o.optional({ type: o.string({}) }) }, - { name: "Subject", displayName: "Subject", type: o.optional({ type: o.string({}) }) }, + { + name: "recordId", + displayName: "Task ID", + type: o.objectReference({ + objectType: "Task", + }), + }, + { + name: "WhoId", + displayName: "Name ID", + type: o.optional({ + type: o.string({}), + }), + }, + { + name: "WhatId", + displayName: "Related To ID", + type: o.optional({ + type: o.string({}), + }), + }, + { + name: "Subject", + displayName: "Subject", + type: o.optional({ + type: o.string({}), + }), + }, { name: "ActivityDate", displayName: "Due Date Only", - type: o.optional({ type: o.date({}) }), + type: o.optional({ + type: o.date({}), + }), }, { name: "Status", @@ -2627,11 +3394,26 @@ export default defineOntology({ type: o.string({ constraint: o.StringConstraint.enum({ options: [ - { value: "Not Started", label: "Not Started" }, - { value: "In Progress", label: "In Progress" }, - { value: "Completed", label: "Completed" }, - { value: "Waiting on someone else", label: "Waiting on someone else" }, - { value: "Deferred", label: "Deferred" }, + { + value: "Not Started", + label: "Not Started", + }, + { + value: "In Progress", + label: "In Progress", + }, + { + value: "Completed", + label: "Completed", + }, + { + value: "Waiting on someone else", + label: "Waiting on someone else", + }, + { + value: "Deferred", + label: "Deferred", + }, ], }), }), @@ -2644,20 +3426,43 @@ export default defineOntology({ type: o.string({ constraint: o.StringConstraint.enum({ options: [ - { value: "High", label: "High" }, - { value: "Normal", label: "Normal" }, - { value: "Low", label: "Low" }, + { + value: "High", + label: "High", + }, + { + value: "Normal", + label: "Normal", + }, + { + value: "Low", + label: "Low", + }, ], }), }), }), }, - { name: "OwnerId", displayName: "Assigned To ID", type: o.optional({ type: o.string({}) }) }, - { name: "Description", displayName: "Description", type: o.optional({ type: o.string({}) }) }, + { + name: "OwnerId", + displayName: "Assigned To ID", + type: o.optional({ + type: o.string({}), + }), + }, + { + name: "Description", + displayName: "Description", + type: o.optional({ + type: o.string({}), + }), + }, { name: "CallDurationInSeconds", displayName: "Call Duration", - type: o.optional({ type: o.integer({}) }), + type: o.optional({ + type: o.integer({}), + }), }, { name: "CallType", @@ -2666,9 +3471,18 @@ export default defineOntology({ type: o.string({ constraint: o.StringConstraint.enum({ options: [ - { value: "Internal", label: "Internal" }, - { value: "Inbound", label: "Inbound" }, - { value: "Outbound", label: "Outbound" }, + { + value: "Internal", + label: "Internal", + }, + { + value: "Inbound", + label: "Inbound", + }, + { + value: "Outbound", + label: "Outbound", + }, ], }), }), @@ -2677,32 +3491,44 @@ export default defineOntology({ { name: "CallDisposition", displayName: "Call Result", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), }, { name: "CallObject", displayName: "Call Object Identifier", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), }, { name: "ReminderDateTime", displayName: "Reminder Date/Time", - type: o.optional({ type: o.timestamp({}) }), + type: o.optional({ + type: o.timestamp({}), + }), }, { name: "IsReminderSet", displayName: "Reminder Set", - type: o.optional({ type: o.boolean({}) }), + type: o.optional({ + type: o.boolean({}), + }), }, { name: "RecurrenceStartDateOnly", displayName: "Recurrence Start", - type: o.optional({ type: o.date({}) }), + type: o.optional({ + type: o.date({}), + }), }, { name: "RecurrenceEndDateOnly", displayName: "Recurrence End", - type: o.optional({ type: o.date({}) }), + type: o.optional({ + type: o.date({}), + }), }, { name: "RecurrenceTimeZoneSidKey", @@ -2879,7 +3705,10 @@ export default defineOntology({ value: "Pacific/Saipan", label: "(GMT+10:00) Chamorro Standard Time (Pacific/Saipan)", }, - { value: "Pacific/Truk", label: "(GMT+10:00) Chuuk Time (Pacific/Truk)" }, + { + value: "Pacific/Truk", + label: "(GMT+10:00) Chuuk Time (Pacific/Truk)", + }, { value: "Australia/Adelaide", label: "(GMT+09:30) Australian Central Standard Time (Australia/Adelaide)", @@ -2896,7 +3725,10 @@ export default defineOntology({ value: "Asia/Chita", label: "(GMT+09:00) Yakutsk Standard Time (Asia/Chita)", }, - { value: "Asia/Dili", label: "(GMT+09:00) East Timor Time (Asia/Dili)" }, + { + value: "Asia/Dili", + label: "(GMT+09:00) East Timor Time (Asia/Dili)", + }, { value: "Asia/Jayapura", label: "(GMT+09:00) Eastern Indonesia Time (Asia/Jayapura)", @@ -3041,7 +3873,10 @@ export default defineOntology({ value: "Indian/Christmas", label: "(GMT+07:00) Christmas Island Time (Indian/Christmas)", }, - { value: "Asia/Yangon", label: "(GMT+06:30) Myanmar Time (Asia/Yangon)" }, + { + value: "Asia/Yangon", + label: "(GMT+06:30) Myanmar Time (Asia/Yangon)", + }, { value: "Indian/Cocos", label: "(GMT+06:30) Cocos Islands Time (Indian/Cocos)", @@ -3734,7 +4569,10 @@ export default defineOntology({ value: "Atlantic/St_Helena", label: "(GMT+00:00) Greenwich Mean Time (Atlantic/St_Helena)", }, - { value: "GMT", label: "(GMT+00:00) Greenwich Mean Time (GMT)" }, + { + value: "GMT", + label: "(GMT+00:00) Greenwich Mean Time (GMT)", + }, { value: "America/Nuuk", label: "(GMT-01:00) West Greenland Summer Time (America/Nuuk)", @@ -4387,7 +5225,10 @@ export default defineOntology({ value: "Pacific/Midway", label: "(GMT-11:00) Samoa Standard Time (Pacific/Midway)", }, - { value: "Pacific/Niue", label: "(GMT-11:00) Niue Time (Pacific/Niue)" }, + { + value: "Pacific/Niue", + label: "(GMT-11:00) Niue Time (Pacific/Niue)", + }, { value: "Pacific/Pago_Pago", label: "(GMT-11:00) Samoa Standard Time (Pacific/Pago_Pago)", @@ -4404,13 +5245,34 @@ export default defineOntology({ type: o.string({ constraint: o.StringConstraint.enum({ options: [ - { value: "RecursDaily", label: "Recurs Daily" }, - { value: "RecursEveryWeekday", label: "Recurs Every Weekday" }, - { value: "RecursMonthly", label: "Recurs Monthly" }, - { value: "RecursMonthlyNth", label: "Recurs Monthly Nth" }, - { value: "RecursWeekly", label: "Recurs Weekly" }, - { value: "RecursYearly", label: "Recurs Yearly" }, - { value: "RecursYearlyNth", label: "Recurs Yearly Nth" }, + { + value: "RecursDaily", + label: "Recurs Daily", + }, + { + value: "RecursEveryWeekday", + label: "Recurs Every Weekday", + }, + { + value: "RecursMonthly", + label: "Recurs Monthly", + }, + { + value: "RecursMonthlyNth", + label: "Recurs Monthly Nth", + }, + { + value: "RecursWeekly", + label: "Recurs Weekly", + }, + { + value: "RecursYearly", + label: "Recurs Yearly", + }, + { + value: "RecursYearlyNth", + label: "Recurs Yearly Nth", + }, ], }), }), @@ -4419,17 +5281,23 @@ export default defineOntology({ { name: "RecurrenceInterval", displayName: "Recurrence Interval", - type: o.optional({ type: o.integer({}) }), + type: o.optional({ + type: o.integer({}), + }), }, { name: "RecurrenceDayOfWeekMask", displayName: "Recurrence Day of Week Mask", - type: o.optional({ type: o.integer({}) }), + type: o.optional({ + type: o.integer({}), + }), }, { name: "RecurrenceDayOfMonth", displayName: "Recurrence Day of Month", - type: o.optional({ type: o.integer({}) }), + type: o.optional({ + type: o.integer({}), + }), }, { name: "RecurrenceInstance", @@ -4438,11 +5306,26 @@ export default defineOntology({ type: o.string({ constraint: o.StringConstraint.enum({ options: [ - { value: "First", label: "1st" }, - { value: "Second", label: "2nd" }, - { value: "Third", label: "3rd" }, - { value: "Fourth", label: "4th" }, - { value: "Last", label: "last" }, + { + value: "First", + label: "1st", + }, + { + value: "Second", + label: "2nd", + }, + { + value: "Third", + label: "3rd", + }, + { + value: "Fourth", + label: "4th", + }, + { + value: "Last", + label: "last", + }, ], }), }), @@ -4455,18 +5338,54 @@ export default defineOntology({ type: o.string({ constraint: o.StringConstraint.enum({ options: [ - { value: "January", label: "January" }, - { value: "February", label: "February" }, - { value: "March", label: "March" }, - { value: "April", label: "April" }, - { value: "May", label: "May" }, - { value: "June", label: "June" }, - { value: "July", label: "July" }, - { value: "August", label: "August" }, - { value: "September", label: "September" }, - { value: "October", label: "October" }, - { value: "November", label: "November" }, - { value: "December", label: "December" }, + { + value: "January", + label: "January", + }, + { + value: "February", + label: "February", + }, + { + value: "March", + label: "March", + }, + { + value: "April", + label: "April", + }, + { + value: "May", + label: "May", + }, + { + value: "June", + label: "June", + }, + { + value: "July", + label: "July", + }, + { + value: "August", + label: "August", + }, + { + value: "September", + label: "September", + }, + { + value: "October", + label: "October", + }, + { + value: "November", + label: "November", + }, + { + value: "December", + label: "December", + }, ], }), }), @@ -4479,12 +5398,18 @@ export default defineOntology({ type: o.string({ constraint: o.StringConstraint.enum({ options: [ - { value: "RecurrenceRegenerateAfterDueDate", label: "After due date" }, + { + value: "RecurrenceRegenerateAfterDueDate", + label: "After due date", + }, { value: "RecurrenceRegenerateAfterToday", label: "After date completed", }, - { value: "RecurrenceRegenerated", label: "(Task Closed)" }, + { + value: "RecurrenceRegenerated", + label: "(Task Closed)", + }, ], }), }), @@ -4497,9 +5422,18 @@ export default defineOntology({ type: o.string({ constraint: o.StringConstraint.enum({ options: [ - { value: "FTX", label: "FTX" }, - { value: "Deployment", label: "Deployment" }, - { value: "Customer Demo", label: "Customer Demo" }, + { + value: "FTX", + label: "FTX", + }, + { + value: "Deployment", + label: "Deployment", + }, + { + value: "Customer Demo", + label: "Customer Demo", + }, ], }), }), @@ -4508,100 +5442,185 @@ export default defineOntology({ { name: "Customer_POC__c", displayName: "Customer POC", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), + }, + { + name: "Mission__c", + displayName: "Mission", + type: o.optional({ + type: o.string({}), + }), }, - { name: "Mission__c", displayName: "Mission", type: o.optional({ type: o.string({}) }) }, ], logic: [ o.ActionLogicStep.updateObject({ - object: { name: "recordId" }, + object: { + name: "recordId", + }, values: [ - { property: ["WhoId"], value: o.Expression.inputReference({ name: "WhoId" }) }, - { property: ["WhatId"], value: o.Expression.inputReference({ name: "WhatId" }) }, - { property: ["Subject"], value: o.Expression.inputReference({ name: "Subject" }) }, + { + property: ["WhoId"], + value: o.Expression.inputReference({ + name: "WhoId", + }), + }, + { + property: ["WhatId"], + value: o.Expression.inputReference({ + name: "WhatId", + }), + }, + { + property: ["Subject"], + value: o.Expression.inputReference({ + name: "Subject", + }), + }, { property: ["ActivityDate"], - value: o.Expression.inputReference({ name: "ActivityDate" }), + value: o.Expression.inputReference({ + name: "ActivityDate", + }), + }, + { + property: ["Status"], + value: o.Expression.inputReference({ + name: "Status", + }), + }, + { + property: ["Priority"], + value: o.Expression.inputReference({ + name: "Priority", + }), + }, + { + property: ["OwnerId"], + value: o.Expression.inputReference({ + name: "OwnerId", + }), }, - { property: ["Status"], value: o.Expression.inputReference({ name: "Status" }) }, - { property: ["Priority"], value: o.Expression.inputReference({ name: "Priority" }) }, - { property: ["OwnerId"], value: o.Expression.inputReference({ name: "OwnerId" }) }, { property: ["Description"], - value: o.Expression.inputReference({ name: "Description" }), + value: o.Expression.inputReference({ + name: "Description", + }), }, { property: ["CallDurationInSeconds"], - value: o.Expression.inputReference({ name: "CallDurationInSeconds" }), + value: o.Expression.inputReference({ + name: "CallDurationInSeconds", + }), + }, + { + property: ["CallType"], + value: o.Expression.inputReference({ + name: "CallType", + }), }, - { property: ["CallType"], value: o.Expression.inputReference({ name: "CallType" }) }, { property: ["CallDisposition"], - value: o.Expression.inputReference({ name: "CallDisposition" }), + value: o.Expression.inputReference({ + name: "CallDisposition", + }), }, { property: ["CallObject"], - value: o.Expression.inputReference({ name: "CallObject" }), + value: o.Expression.inputReference({ + name: "CallObject", + }), }, { property: ["ReminderDateTime"], - value: o.Expression.inputReference({ name: "ReminderDateTime" }), + value: o.Expression.inputReference({ + name: "ReminderDateTime", + }), }, { property: ["IsReminderSet"], - value: o.Expression.inputReference({ name: "IsReminderSet" }), + value: o.Expression.inputReference({ + name: "IsReminderSet", + }), }, { property: ["RecurrenceStartDateOnly"], - value: o.Expression.inputReference({ name: "RecurrenceStartDateOnly" }), + value: o.Expression.inputReference({ + name: "RecurrenceStartDateOnly", + }), }, { property: ["RecurrenceEndDateOnly"], - value: o.Expression.inputReference({ name: "RecurrenceEndDateOnly" }), + value: o.Expression.inputReference({ + name: "RecurrenceEndDateOnly", + }), }, { property: ["RecurrenceTimeZoneSidKey"], - value: o.Expression.inputReference({ name: "RecurrenceTimeZoneSidKey" }), + value: o.Expression.inputReference({ + name: "RecurrenceTimeZoneSidKey", + }), }, { property: ["RecurrenceType"], - value: o.Expression.inputReference({ name: "RecurrenceType" }), + value: o.Expression.inputReference({ + name: "RecurrenceType", + }), }, { property: ["RecurrenceInterval"], - value: o.Expression.inputReference({ name: "RecurrenceInterval" }), + value: o.Expression.inputReference({ + name: "RecurrenceInterval", + }), }, { property: ["RecurrenceDayOfWeekMask"], - value: o.Expression.inputReference({ name: "RecurrenceDayOfWeekMask" }), + value: o.Expression.inputReference({ + name: "RecurrenceDayOfWeekMask", + }), }, { property: ["RecurrenceDayOfMonth"], - value: o.Expression.inputReference({ name: "RecurrenceDayOfMonth" }), + value: o.Expression.inputReference({ + name: "RecurrenceDayOfMonth", + }), }, { property: ["RecurrenceInstance"], - value: o.Expression.inputReference({ name: "RecurrenceInstance" }), + value: o.Expression.inputReference({ + name: "RecurrenceInstance", + }), }, { property: ["RecurrenceMonthOfYear"], - value: o.Expression.inputReference({ name: "RecurrenceMonthOfYear" }), + value: o.Expression.inputReference({ + name: "RecurrenceMonthOfYear", + }), }, { property: ["RecurrenceRegeneratedType"], - value: o.Expression.inputReference({ name: "RecurrenceRegeneratedType" }), + value: o.Expression.inputReference({ + name: "RecurrenceRegeneratedType", + }), }, { property: ["Event_Type__c"], - value: o.Expression.inputReference({ name: "Event_Type__c" }), + value: o.Expression.inputReference({ + name: "Event_Type__c", + }), }, { property: ["Customer_POC__c"], - value: o.Expression.inputReference({ name: "Customer_POC__c" }), + value: o.Expression.inputReference({ + name: "Customer_POC__c", + }), }, { property: ["Mission__c"], - value: o.Expression.inputReference({ name: "Mission__c" }), + value: o.Expression.inputReference({ + name: "Mission__c", + }), }, ], }), @@ -4616,77 +5635,99 @@ export default defineOntology({ { name: "meetingOwnerId", displayName: "Meeting Owner ID", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), description: "Optional. The ID of the Salesforce Inbox user who owns the meeting. If no owner is specified, the ID of the current user is used.", }, { name: "attendeeEmailAddresses", displayName: "Attendee Email Addresses", - type: o.list({ elementType: o.string({}) }), + type: o.list({ + elementType: o.string({}), + }), description: "A list of the meeting attendees’ email addresses, excluding the meeting owner.", }, { name: "metadataTargetId", displayName: "Metadata Target ID", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), description: "Optional. The target record ID associated with a lead, contact, or person account. This ID typically belongs to the attendee and is used to connect the meeting with the Action Cadence Step Tracker used by the agent to engage with the target.", }, { name: "metadataOrchestrationId", displayName: "Metadata Orchestration ID", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), description: "Optional. The ID of the this action’s orchestration. The ID can be an Action Cadence Step Tracker or other orchestration. The ID associates the meeting with the Action Cadence Step Tracker used by the agent to engage with the target.", }, { name: "startDate", displayName: "Start Date", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), description: "The start date in ISO date-time format for when to begin looking for available meeting time slots.", }, { name: "endDate", displayName: "End Date", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), description: "The end date in ISO date-time format for when to stop looking for available meeting time slots.", }, { name: "timeZone", displayName: "Time Zone", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), description: "Optional. The TimeZoneSidKey used to look up available meeting time slots. When not specified, the value is the same TimeZoneSidKey as the Meeting Owner.", }, { name: "numberOfTimeSlots", displayName: "Max Number of Time Slots", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), description: "Optional. The maximum number of meeting time slots to look for. When no number is set, the default value is 3.", }, { name: "limitPerDay", displayName: "Limit Per Day", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), description: "Optional. The maximum number of meeting time slots to find for a given date. When no number is set, value defaults to 1.", }, { name: "meetingName", displayName: "Meeting Name", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), description: "Optional. The name of the scheduled meeting. When no name is entered, the default value is “Meeting with attendee”.", }, { name: "meetingDescription", displayName: "Meeting Description", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), description: "Optional. A description of the scheduled meeting.", }, ], @@ -4695,34 +5736,44 @@ export default defineOntology({ { name: "timeSlots", displayName: "Time Slots", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), description: "The list of available time slots for booking a meeting from the Salesforce Inbox user’s calendar.", }, { name: "epochTimes", displayName: "Epoch Times", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), description: "The epoch time values in milliseconds corresponding to the available time slots.", }, { name: "meetingRequestId", displayName: "Meeting Request ID", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), description: "The ID of the meeting request associated with the group of generated time slots.", }, { name: "outputErrorMessage", displayName: "Error Message", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), description: "The error message returned by the Get Available Meeting Times action.", }, { name: "outputErrorCode", displayName: "Error Code", - type: o.optional({ type: o.string({}) }), + type: o.optional({ + type: o.string({}), + }), description: "The error code returned by the Get Available Meeting Times action.", }, ], diff --git a/apps/salesforce-task-manager/src/router.tsx b/apps/salesforce-task-manager/src/router.tsx index 1f3f4c97..b51991e3 100644 --- a/apps/salesforce-task-manager/src/router.tsx +++ b/apps/salesforce-task-manager/src/router.tsx @@ -2,7 +2,10 @@ import { createRouter } from "@tanstack/react-router"; import { routeTree } from "./routeTree.gen"; export function getRouter() { - return createRouter({ routeTree, scrollRestoration: true }); + return createRouter({ + routeTree, + scrollRestoration: true, + }); } declare module "@tanstack/react-router" { diff --git a/apps/salesforce-task-manager/src/routes/__root.tsx b/apps/salesforce-task-manager/src/routes/__root.tsx index 946d5e65..6c42ff2e 100644 --- a/apps/salesforce-task-manager/src/routes/__root.tsx +++ b/apps/salesforce-task-manager/src/routes/__root.tsx @@ -7,8 +7,13 @@ export const Route = createRootRoute({ head: () => ({ meta: [ { charSet: "utf-8" }, - { name: "viewport", content: "width=device-width, initial-scale=1" }, - { title: "Salesforce Task Manager" }, + { + name: "viewport", + content: "width=device-width, initial-scale=1", + }, + { + title: "Salesforce Task Manager", + }, ], }), component: RootComponent, diff --git a/apps/salesforce-task-manager/src/routes/auth.callback.tsx b/apps/salesforce-task-manager/src/routes/auth.callback.tsx index 984b2b36..2a30b147 100644 --- a/apps/salesforce-task-manager/src/routes/auth.callback.tsx +++ b/apps/salesforce-task-manager/src/routes/auth.callback.tsx @@ -2,7 +2,10 @@ import { createFileRoute, useNavigate } from "@tanstack/react-router"; import { useEffect, useState } from "react"; import { completeSalesforceOAuthRedirect } from "../app/salesforce"; -export const Route = createFileRoute("/auth/callback")({ ssr: false, component: OAuthCallback }); +export const Route = createFileRoute("/auth/callback")({ + ssr: false, + component: OAuthCallback, +}); function OAuthCallback() { const navigate = useNavigate(); diff --git a/apps/salesforce-task-manager/tsconfig.json b/apps/salesforce-task-manager/tsconfig.json index 8e6e8a5c..1bcb8923 100644 --- a/apps/salesforce-task-manager/tsconfig.json +++ b/apps/salesforce-task-manager/tsconfig.json @@ -1 +1,3 @@ -{ "extends": "@party-stack/tanstack-start-build-config/tsconfig.json" } +{ + "extends": "@party-stack/tanstack-start-build-config/tsconfig.json" +} diff --git a/apps/salesforce-task-manager/vite.config.ts b/apps/salesforce-task-manager/vite.config.ts index 1d69db4f..b96db6a7 100644 --- a/apps/salesforce-task-manager/vite.config.ts +++ b/apps/salesforce-task-manager/vite.config.ts @@ -25,7 +25,9 @@ function salesforceDevSessionPlugin(env: Record): Plugin { `${loginUrl.replace(/\/+$/, "")}/services/oauth2/token`, { method: "POST", - headers: { "Content-Type": "application/x-www-form-urlencoded" }, + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, body: new URLSearchParams({ grant_type: "client_credentials", client_id: clientId, @@ -33,15 +35,24 @@ function salesforceDevSessionPlugin(env: Record): Plugin { }), } ); - const token = (await tokenResponse.json()) as { access_token?: unknown }; + const token = (await tokenResponse.json()) as { + access_token?: unknown; + }; if (!tokenResponse.ok || typeof token.access_token !== "string") { throw new Error(`Salesforce token endpoint returned ${tokenResponse.status}.`); } const userResponse = await fetch( `${loginUrl.replace(/\/+$/, "")}/services/oauth2/userinfo`, - { headers: { Authorization: `Bearer ${token.access_token}` } } + { + headers: { + Authorization: `Bearer ${token.access_token}`, + }, + } ); - const user = (await userResponse.json()) as { user_id?: unknown; sub?: unknown }; + const user = (await userResponse.json()) as { + user_id?: unknown; + sub?: unknown; + }; const userId = typeof user.user_id === "string" ? user.user_id @@ -53,12 +64,19 @@ function salesforceDevSessionPlugin(env: Record): Plugin { } response.setHeader("Cache-Control", "no-store"); response.setHeader("Content-Type", "application/json"); - response.end(JSON.stringify({ accessToken: token.access_token, userId })); + response.end( + JSON.stringify({ + accessToken: token.access_token, + userId, + }) + ); } catch (error) { response.statusCode = 502; response.setHeader("Content-Type", "application/json"); response.end( - JSON.stringify({ error: error instanceof Error ? error.message : String(error) }) + JSON.stringify({ + error: error instanceof Error ? error.message : String(error), + }) ); } }); diff --git a/old/README.md b/old/README.md index 03e975d4..19ec3329 100644 --- a/old/README.md +++ b/old/README.md @@ -25,7 +25,10 @@ function App() { hasNextPage, isFetchingNextPage, fetchNextPage, - } = useObjects(Task, { $orderBy: { completedAt: "asc", createdAt: "desc" }, $pageSize: 10 }); + } = useObjects(Task, { + $orderBy: { completedAt: "asc", createdAt: "desc" }, + $pageSize: 10, + }); // Subscribe to real-time updates. useLiveObjectSet(Task); @@ -38,7 +41,10 @@ function App() { // Submit Actions. addTask( { title: newTaskTitle }, - { onSuccess: () => setNewTaskTitle(""), onError: (error) => console.error(error) } + { + onSuccess: () => setNewTaskTitle(""), + onError: (error) => console.error(error), + } ); }; diff --git a/old/apollo/src/Apollo.test.ts b/old/apollo/src/Apollo.test.ts index c4d44662..b6816906 100644 --- a/old/apollo/src/Apollo.test.ts +++ b/old/apollo/src/Apollo.test.ts @@ -10,7 +10,10 @@ describe("createHelmChartProductManifest", () => { helmChartName: "oci://ghcr.io/valinor-enterprises/streamline-studio-chart", helmChartVersion: "2026.22.2-031979fc", artifacts: [ - { type: "oci", uri: "ghcr.io/valinor-enterprises/streamline-studio:2026.22.2-031979fc" }, + { + type: "oci", + uri: "ghcr.io/valinor-enterprises/streamline-studio:2026.22.2-031979fc", + }, ], productDependencies: [ { @@ -37,7 +40,10 @@ describe("createHelmChartProductManifest", () => { "helm-repository-url": "oci://ghcr.io/valinor-enterprises/streamline-studio-chart", }, artifacts: [ - { type: "oci", uri: "ghcr.io/valinor-enterprises/streamline-studio:2026.22.2-031979fc" }, + { + type: "oci", + uri: "ghcr.io/valinor-enterprises/streamline-studio:2026.22.2-031979fc", + }, ], "product-dependencies": [ { diff --git a/old/apollo/src/Apollo.ts b/old/apollo/src/Apollo.ts index 88066068..be3ceb00 100644 --- a/old/apollo/src/Apollo.ts +++ b/old/apollo/src/Apollo.ts @@ -14,8 +14,15 @@ const VERSION = "0.538.0"; const DEFAULT_GRAPHQL_PATH = "/graphql-gateway/api/graphql"; export type ApolloAuth = - | { type: "static"; token: string } - | { type: "service-user"; clientId: string; clientSecret: string }; + | { + type: "static"; + token: string; + } + | { + type: "service-user"; + clientId: string; + clientSecret: string; + }; export type RolloutStrategy = "manageRollout" | "applyChangesNoWait"; @@ -56,7 +63,9 @@ export interface HelmChartProductManifest { }>; "rollout-strategy"?: RolloutStrategy; "resource-readiness-timeout"?: string; - "image-pre-pull-config"?: { timeout: string }; + "image-pre-pull-config"?: { + timeout: string; + }; } & Record; } @@ -75,7 +84,9 @@ export interface CreateHelmChartProductManifestOptions { interface GraphqlResponse { data?: TData; - errors?: Array<{ message: string }>; + errors?: Array<{ + message: string; + }>; } interface ApolloProductReleaseCreated { @@ -92,7 +103,13 @@ interface ApolloProductReleaseFailedToDeserializeManifest { interface ApolloProductReleaseRejected { __typename: "ApolloProductReleaseRejected"; mavenCoordinate: string; - rejectionReasons: Array<{ message: string; metadata: Array<{ key: string; value: string }> }>; + rejectionReasons: Array<{ + message: string; + metadata: Array<{ + key: string; + value: string; + }>; + }>; } type ApolloCreateProductReleaseResult = @@ -105,15 +122,22 @@ export async function getAccessToken(opts: { apolloUrl: string; auth: ApolloAuth return opts.auth.token; } - const response = await fetch(Urls.extend(opts.apolloUrl, { pathname: "/multipass/api/oauth2/token" }), { - method: "POST", - body: new URLSearchParams({ - grant_type: "client_credentials", - client_id: opts.auth.clientId, - client_secret: opts.auth.clientSecret, + const response = await fetch( + Urls.extend(opts.apolloUrl, { + pathname: "/multipass/api/oauth2/token", }), - headers: { "Content-Type": "application/x-www-form-urlencoded" }, - }); + { + method: "POST", + body: new URLSearchParams({ + grant_type: "client_credentials", + client_id: opts.auth.clientId, + client_secret: opts.auth.clientSecret, + }), + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + } + ); if (!response.ok) { throw new Error(`Failed to get Apollo token: ${response.status} ${response.statusText}`); @@ -125,7 +149,11 @@ export async function getAccessToken(opts: { apolloUrl: string; auth: ApolloAuth } function getAuthFromLegacyOpts(opts: { clientId: string; clientSecret: string }): ApolloAuth { - return { type: "service-user", clientId: opts.clientId, clientSecret: opts.clientSecret }; + return { + type: "service-user", + clientId: opts.clientId, + clientSecret: opts.clientSecret, + }; } export async function ensureBinary(opts: { @@ -138,8 +166,15 @@ export async function ensureBinary(opts: { apolloUrl: string; auth: ApolloAuth } export async function ensureBinary( opts: - | { apolloUrl: string; clientId: string; clientSecret: string } - | { apolloUrl: string; auth: ApolloAuth } + | { + apolloUrl: string; + clientId: string; + clientSecret: string; + } + | { + apolloUrl: string; + auth: ApolloAuth; + } ): Promise { const auth = "auth" in opts ? opts.auth : getAuthFromLegacyOpts(opts); const token = await getAccessToken({ apolloUrl: opts.apolloUrl, auth }); @@ -167,7 +202,9 @@ export async function ensureBinary( // TODO: figure out how to pin version. pathname: Pathnames.join("assets/dyn/apollo-cli/bin", distribution, "apollo-cli"), }).toString(), - headers: { Authorization: `Bearer ${token}` }, + headers: { + Authorization: `Bearer ${token}`, + }, }; }, path.join(import.meta.dirname, "..", "node_modules", ".cache", "apollo") @@ -206,7 +243,10 @@ async function executeApolloCli(opts: { cwd?: string; secrets?: string[]; }): Promise<{ stdout: string; stderr: string }> { - const cliPath = await ensureBinary({ apolloUrl: opts.apolloUrl, auth: opts.auth }); + const cliPath = await ensureBinary({ + apolloUrl: opts.apolloUrl, + auth: opts.auth, + }); try { return await execFileAsync(cliPath, opts.args, { cwd: opts.cwd, encoding: "utf8" }); @@ -248,8 +288,14 @@ async function executeGraphql(opts: { }), { method: "POST", - body: JSON.stringify({ query: opts.query, variables: opts.variables }), - headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" }, + body: JSON.stringify({ + query: opts.query, + variables: opts.variables, + }), + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, } ); @@ -316,7 +362,9 @@ export function createHelmChartProductManifest( "maximum-version": dependency.maximumVersion, ...(dependency.recommendedVersion === undefined ? {} - : { "recommended-version": dependency.recommendedVersion }), + : { + "recommended-version": dependency.recommendedVersion, + }), ...(dependency.optional === undefined ? {} : { optional: dependency.optional }), })); } @@ -330,7 +378,9 @@ export function createHelmChartProductManifest( } if (opts.imagePrePullConfigTimeout !== undefined) { - extensions["image-pre-pull-config"] = { timeout: opts.imagePrePullConfigTimeout }; + extensions["image-pre-pull-config"] = { + timeout: opts.imagePrePullConfigTimeout, + }; } return { @@ -414,7 +464,9 @@ export async function createHelmChartProductRelease( await writeHelmChartProductManifest(manifest, manifestPath); const data = await executeGraphql<{ - apollo: { createProductReleaseFromManifestV2: ApolloCreateProductReleaseResult }; + apollo: { + createProductReleaseFromManifestV2: ApolloCreateProductReleaseResult; + }; }>({ apolloUrl: opts.apolloUrl, auth: opts.auth, @@ -491,7 +543,10 @@ export async function searchProductReleases(opts: { exclusionCriteria: opts.exclusionCriteria ?? [], pageSize: opts.pageSize ?? 50, }), - headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" }, + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, } ); diff --git a/old/apollo/tsconfig.json b/old/apollo/tsconfig.json index eba09560..14fb4eec 100644 --- a/old/apollo/tsconfig.json +++ b/old/apollo/tsconfig.json @@ -1 +1,3 @@ -{ "extends": "@party-stack/build-config/tsconfig.json" } +{ + "extends": "@party-stack/build-config/tsconfig.json" +} diff --git a/old/foundry-graphql-schema/src/schema/ExecutableFoundrySchema.ts b/old/foundry-graphql-schema/src/schema/ExecutableFoundrySchema.ts index 9ab1869a..4f70dd5b 100644 --- a/old/foundry-graphql-schema/src/schema/ExecutableFoundrySchema.ts +++ b/old/foundry-graphql-schema/src/schema/ExecutableFoundrySchema.ts @@ -25,7 +25,11 @@ async function create( const context = async (token: string): Promise => { const requestClient = createRequestClient?.(token) ?? client; const userId = getUserIdFromToken(await requestClient.__osdkClientContext.tokenProvider()); - return { client: requestClient, ontologyRid, userId }; + return { + client: requestClient, + ontologyRid, + userId, + }; }; return { schema, context }; } @@ -76,4 +80,7 @@ function createExecutor(executableFoundrySchema: ExecutableFoundrySchema): Found }; } -export const ExecutableFoundrySchema = { create, createExecutor }; +export const ExecutableFoundrySchema = { + create, + createExecutor, +}; diff --git a/old/foundry-graphql-schema/src/schema/FoundrySchema.ts b/old/foundry-graphql-schema/src/schema/FoundrySchema.ts index 4380d387..9ee8121c 100644 --- a/old/foundry-graphql-schema/src/schema/FoundrySchema.ts +++ b/old/foundry-graphql-schema/src/schema/FoundrySchema.ts @@ -77,4 +77,6 @@ function create(ontology: OntologyFullMetadata): GraphQLSchema { }); } -export const FoundrySchema = { create }; +export const FoundrySchema = { + create, +}; diff --git a/old/foundry-graphql-schema/src/schema/ListTypes.ts b/old/foundry-graphql-schema/src/schema/ListTypes.ts index 12ddee0c..4c553731 100644 --- a/old/foundry-graphql-schema/src/schema/ListTypes.ts +++ b/old/foundry-graphql-schema/src/schema/ListTypes.ts @@ -36,7 +36,10 @@ function createPageType( description: `A list of ${pluralDisplayName}.`, type: Schemas.list(getTypeReference(getEdgeTypeName(typeName)) as GraphQLObjectType), }, - pageInfo: { description: "Information about pagination for this page.", type: PageInfoType }, + pageInfo: { + description: "Information about pagination for this page.", + type: PageInfoType, + }, })), }); } @@ -72,4 +75,9 @@ function getEdgeTypeName(typeName: string): string { return `${typeName}Edge`; } -export const ListTypes = { createPageType, getPageTypeReference, createEdgeType, PageInfoType }; +export const ListTypes = { + createPageType, + getPageTypeReference, + createEdgeType, + PageInfoType, +}; diff --git a/old/foundry-graphql-schema/src/schema/admin/UserQueryFields.ts b/old/foundry-graphql-schema/src/schema/admin/UserQueryFields.ts index 3fb82647..0c929abf 100644 --- a/old/foundry-graphql-schema/src/schema/admin/UserQueryFields.ts +++ b/old/foundry-graphql-schema/src/schema/admin/UserQueryFields.ts @@ -28,7 +28,11 @@ function create(getTypeReference: GetTypeReference): NamedGraphQLFieldConfig[] { objectFieldSpec>( { description: "Get a single User.", - args: { id: { type: Schemas.required(GraphQLString) } }, + args: { + id: { + type: Schemas.required(GraphQLString), + }, + }, type: UserType.getReference(getTypeReference), plan: (_$parent, $args) => { return loadOne($args.getRaw("id"), context(), UserLoader); @@ -41,4 +45,6 @@ function create(getTypeReference: GetTypeReference): NamedGraphQLFieldConfig[] { return [viewer, user]; } -export const UserQueryFields = { create }; +export const UserQueryFields = { + create, +}; diff --git a/old/foundry-graphql-schema/src/schema/admin/UserType.ts b/old/foundry-graphql-schema/src/schema/admin/UserType.ts index 9512195d..16a8e0c2 100644 --- a/old/foundry-graphql-schema/src/schema/admin/UserType.ts +++ b/old/foundry-graphql-schema/src/schema/admin/UserType.ts @@ -41,8 +41,14 @@ function create(typeRegistry: TypeRegistry): GraphQLObjectType { description: "The unique username for the User.", type: Schemas.required(GraphQLString), }, - givenName: { description: "The given name of the User.", type: GraphQLString }, - familyName: { description: "The family name (last name) of the User.", type: GraphQLString }, + givenName: { + description: "The given name of the User.", + type: GraphQLString, + }, + familyName: { + description: "The family name (last name) of the User.", + type: GraphQLString, + }, email: { description: "The email at which to contact a User. Multiple Users may have the same email address.", @@ -67,4 +73,8 @@ function getReference(getTypeReference: GetTypeReference): GraphQLObjectType { return getTypeReference(TYPE_NAME) as GraphQLObjectType; } -export const UserType = { create, getReference, NODE_ID_HANDLER }; +export const UserType = { + create, + getReference, + NODE_ID_HANDLER, +}; diff --git a/old/foundry-graphql-schema/src/schema/filters/StringFilterType.ts b/old/foundry-graphql-schema/src/schema/filters/StringFilterType.ts index d4dc8e95..18b0d3db 100644 --- a/old/foundry-graphql-schema/src/schema/filters/StringFilterType.ts +++ b/old/foundry-graphql-schema/src/schema/filters/StringFilterType.ts @@ -10,9 +10,17 @@ export interface StringFilter { } function toObjectSetFilter(field: PropertyApiName, filter: StringFilter): SearchJsonQueryV2 | undefined { if (filter.eq) { - return { type: "eq", field, value: filter.eq }; + return { + type: "eq", + field, + value: filter.eq, + }; } else if (filter.contains) { - return { type: "containsAllTermsInOrderPrefixLastTerm", field, value: filter.contains }; + return { + type: "containsAllTermsInOrderPrefixLastTerm", + field, + value: filter.contains, + }; } else { return undefined; } @@ -24,8 +32,14 @@ function create(): GraphQLInputObjectType { description: "A filter for a String field.", isOneOf: true, fields: { - eq: { description: "The field must be equal to the provided value.", type: GraphQLString }, - contains: { description: "The field must contain the provided substring.", type: GraphQLString }, + eq: { + description: "The field must be equal to the provided value.", + type: GraphQLString, + }, + contains: { + description: "The field must contain the provided substring.", + type: GraphQLString, + }, }, }); } @@ -34,4 +48,8 @@ function getReference(getTypeReference: GetTypeReference): GraphQLInputObjectTyp return getTypeReference(TYPE_NAME) as GraphQLInputObjectType; } -export const StringFilterType = { create, getReference, toObjectSetFilter }; +export const StringFilterType = { + create, + getReference, + toObjectSetFilter, +}; diff --git a/old/foundry-graphql-schema/src/schema/nodes/NodeField.ts b/old/foundry-graphql-schema/src/schema/nodes/NodeField.ts index 9b593757..b44aa628 100644 --- a/old/foundry-graphql-schema/src/schema/nodes/NodeField.ts +++ b/old/foundry-graphql-schema/src/schema/nodes/NodeField.ts @@ -27,4 +27,6 @@ function create(getTypeReference: GetTypeReference, handlers: NodeIdHandler[]): return ["node", field]; } -export const NodeField = { create }; +export const NodeField = { + create, +}; diff --git a/old/foundry-graphql-schema/src/schema/nodes/NodeHandlers.ts b/old/foundry-graphql-schema/src/schema/nodes/NodeHandlers.ts index b1483856..8febc761 100644 --- a/old/foundry-graphql-schema/src/schema/nodes/NodeHandlers.ts +++ b/old/foundry-graphql-schema/src/schema/nodes/NodeHandlers.ts @@ -40,4 +40,6 @@ function createBasicHandler( }; } -export const NodeHandlers = { createBasicHandler }; +export const NodeHandlers = { + createBasicHandler, +}; diff --git a/old/foundry-graphql-schema/src/schema/nodes/NodeInterface.ts b/old/foundry-graphql-schema/src/schema/nodes/NodeInterface.ts index 3d168ee0..b9fe4534 100644 --- a/old/foundry-graphql-schema/src/schema/nodes/NodeInterface.ts +++ b/old/foundry-graphql-schema/src/schema/nodes/NodeInterface.ts @@ -17,7 +17,10 @@ function create(): GraphQLInterfaceType { name: TYPE_NAME, description: "A record with a global id.", fields: { - [FIELD_NAME]: { description: "The global id of the record.", type: Schemas.required(GraphQLID) }, + [FIELD_NAME]: { + description: "The global id of the record.", + type: Schemas.required(GraphQLID), + }, }, }); } @@ -26,4 +29,8 @@ function getReference(getTypeReference: GetTypeReference): GraphQLInterfaceType return getTypeReference(TYPE_NAME) as GraphQLInterfaceType; } -export const NodeInterface = { create, getReference, FIELD_NAME }; +export const NodeInterface = { + create, + getReference, + FIELD_NAME, +}; diff --git a/old/foundry-graphql-schema/src/schema/ontology-meta/ActionTypeType.ts b/old/foundry-graphql-schema/src/schema/ontology-meta/ActionTypeType.ts index 3c11e887..ff9bd006 100644 --- a/old/foundry-graphql-schema/src/schema/ontology-meta/ActionTypeType.ts +++ b/old/foundry-graphql-schema/src/schema/ontology-meta/ActionTypeType.ts @@ -35,7 +35,10 @@ function create(typeRegistry: TypeRegistry): GraphQLObjectType { description: "The display name of the Action type.", type: Schemas.required(GraphQLString), }, - description: { description: "The description of the Action type.", type: GraphQLString }, + description: { + description: "The description of the Action type.", + type: GraphQLString, + }, }, interfaces: typeRegistry.use((getTypeReference) => [ NodeInterface.getReference(getTypeReference), @@ -48,4 +51,8 @@ function getReference(getTypeReference: GetTypeReference): GraphQLObjectType { return getTypeReference(TYPE_NAME) as GraphQLObjectType; } -export const UserType = { create, getReference, NODE_ID_HANDLER }; +export const UserType = { + create, + getReference, + NODE_ID_HANDLER, +}; diff --git a/old/foundry-graphql-schema/src/schema/ontology/ActionField.ts b/old/foundry-graphql-schema/src/schema/ontology/ActionField.ts index 4a08b9ec..babf6021 100644 --- a/old/foundry-graphql-schema/src/schema/ontology/ActionField.ts +++ b/old/foundry-graphql-schema/src/schema/ontology/ActionField.ts @@ -144,4 +144,6 @@ function create( return Result.ok([fieldName, field]); } -export const ActionField = { create }; +export const ActionField = { + create, +}; diff --git a/old/foundry-graphql-schema/src/schema/ontology/ObjectArgumentType.ts b/old/foundry-graphql-schema/src/schema/ontology/ObjectArgumentType.ts index 8263a3d7..e7970b5a 100644 --- a/old/foundry-graphql-schema/src/schema/ontology/ObjectArgumentType.ts +++ b/old/foundry-graphql-schema/src/schema/ontology/ObjectArgumentType.ts @@ -31,4 +31,6 @@ function get(objectType: ObjectTypeV2): GraphQLInputType { return getScalarType(objectType); } -export const ObjectArgumentType = { get }; +export const ObjectArgumentType = { + get, +}; diff --git a/old/foundry-graphql-schema/src/schema/ontology/ObjectLinkField.ts b/old/foundry-graphql-schema/src/schema/ontology/ObjectLinkField.ts index 42198895..483f0279 100644 --- a/old/foundry-graphql-schema/src/schema/ontology/ObjectLinkField.ts +++ b/old/foundry-graphql-schema/src/schema/ontology/ObjectLinkField.ts @@ -53,8 +53,15 @@ function create( $primaryKey, (primaryKey): ObjectSet => ({ type: "filter", - objectSet: { type: "base", objectType: linkType.objectTypeApiName }, - where: { type: "eq", field: foreignKeyPropertyApiName, value: primaryKey }, + objectSet: { + type: "base", + objectType: linkType.objectTypeApiName, + }, + where: { + type: "eq", + field: foreignKeyPropertyApiName, + value: primaryKey, + }, }), true ); @@ -69,4 +76,6 @@ function create( return [linkType.apiName, field]; } -export const ObjectLinkField = { create }; +export const ObjectLinkField = { + create, +}; diff --git a/old/foundry-graphql-schema/src/schema/ontology/ObjectListField.ts b/old/foundry-graphql-schema/src/schema/ontology/ObjectListField.ts index 9c95868c..172271e9 100644 --- a/old/foundry-graphql-schema/src/schema/ontology/ObjectListField.ts +++ b/old/foundry-graphql-schema/src/schema/ontology/ObjectListField.ts @@ -17,9 +17,15 @@ function create( description: `A list of ${objectType.pluralDisplayName}.`, // TODO: args args: { - after: { type: GraphQLString }, - first: { type: GraphQLInt }, - orderBy: { type: ObjectListTypes.getOrderByTypeReference(getTypeReference, objectType) }, + after: { + type: GraphQLString, + }, + first: { + type: GraphQLInt, + }, + orderBy: { + type: ObjectListTypes.getOrderByTypeReference(getTypeReference, objectType), + }, }, type: ObjectListTypes.getPageTypeReference(getTypeReference, objectType), plan: ($objectSet, $args) => @@ -28,7 +34,9 @@ function create( $args.getRaw() as Step<{ first: Maybe; after: Maybe; - orderBy: Maybe<{ fields: { field: PropertyApiName; direction: "asc" | "desc" }[] }>; + orderBy: Maybe<{ + fields: { field: PropertyApiName; direction: "asc" | "desc" }[]; + }>; }> ), }, @@ -38,4 +46,6 @@ function create( return ["list", field]; } -export const ObjectListField = { create }; +export const ObjectListField = { + create, +}; diff --git a/old/foundry-graphql-schema/src/schema/ontology/ObjectListStep.ts b/old/foundry-graphql-schema/src/schema/ontology/ObjectListStep.ts index 41453e31..19a34c6a 100644 --- a/old/foundry-graphql-schema/src/schema/ontology/ObjectListStep.ts +++ b/old/foundry-graphql-schema/src/schema/ontology/ObjectListStep.ts @@ -25,7 +25,10 @@ export interface ObjectListArgs { } export class LoadedObjectStep extends Step { - static $$export = { moduleName: "@bobbyfidz/oql-graphql-schema", exportName: "LoadedObjectStep" }; + static $$export = { + moduleName: "@bobbyfidz/oql-graphql-schema", + exportName: "LoadedObjectStep", + }; isSyncAndSafe = true; @@ -59,7 +62,10 @@ export class LoadedObjectStep extends Step { } class ObjectListStep extends Step { - static $$export = { moduleName: "@bobbyfidz/oql-graphql-schema", exportName: "ObjectListStep" }; + static $$export = { + moduleName: "@bobbyfidz/oql-graphql-schema", + exportName: "ObjectListStep", + }; #properties = new Set(); @@ -100,7 +106,10 @@ class ObjectListStep extends Step { } as LoadObjectSetRequestV2 ); - return { data: data as TypedOntologyObject[], nextPageToken }; + return { + data: data as TypedOntologyObject[], + nextPageToken, + }; }); } @@ -118,7 +127,9 @@ export function objectListConnection( $args: Step<{ first: Maybe; after: Maybe; - orderBy: Maybe<{ fields: { field: PropertyApiName; direction: "asc" | "desc" }[] }>; + orderBy: Maybe<{ + fields: { field: PropertyApiName; direction: "asc" | "desc" }[]; + }>; }> ) { const objectListStep = new ObjectListStep( @@ -139,7 +150,9 @@ export function objectListConnection( endCursor: objectListStep.nextPageToken(), }), edges: each(objectListStep.items(), (item) => - object({ node: new LoadedObjectStep(item, objectListStep) }) + object({ + node: new LoadedObjectStep(item, objectListStep), + }) ), }); } diff --git a/old/foundry-graphql-schema/src/schema/ontology/ObjectListTypes.ts b/old/foundry-graphql-schema/src/schema/ontology/ObjectListTypes.ts index 9620d82c..0333d22d 100644 --- a/old/foundry-graphql-schema/src/schema/ontology/ObjectListTypes.ts +++ b/old/foundry-graphql-schema/src/schema/ontology/ObjectListTypes.ts @@ -28,8 +28,14 @@ function getPageTypeReference( const OrderingDirectionType = new GraphQLEnumType({ name: "OrderingDirection", values: { - asc: { value: "asc", description: "Ascending order" }, - desc: { value: "desc", description: "Descending order" }, + asc: { + value: "asc", + description: "Ascending order", + }, + desc: { + value: "desc", + description: "Descending order", + }, }, }); @@ -80,7 +86,9 @@ function createOrderByType(typeRegistry: TypeRegistry, objectType: ObjectTypeV2) name: `${objectType.apiName}OrderBy`, isOneOf: true, fields: typeRegistry.use((getTypeReference) => ({ - fields: { type: Schemas.list(getFieldOrderingTypeReference(getTypeReference, objectType)) }, + fields: { + type: Schemas.list(getFieldOrderingTypeReference(getTypeReference, objectType)), + }, })), }); } diff --git a/old/foundry-graphql-schema/src/schema/ontology/ObjectPropertyField.ts b/old/foundry-graphql-schema/src/schema/ontology/ObjectPropertyField.ts index 2b893d67..9953e360 100644 --- a/old/foundry-graphql-schema/src/schema/ontology/ObjectPropertyField.ts +++ b/old/foundry-graphql-schema/src/schema/ontology/ObjectPropertyField.ts @@ -97,4 +97,6 @@ function create( }); } -export const ObjectPropertyField = { create }; +export const ObjectPropertyField = { + create, +}; diff --git a/old/foundry-graphql-schema/src/schema/ontology/ObjectQueryField.ts b/old/foundry-graphql-schema/src/schema/ontology/ObjectQueryField.ts index 83c96d7f..6616bc97 100644 --- a/old/foundry-graphql-schema/src/schema/ontology/ObjectQueryField.ts +++ b/old/foundry-graphql-schema/src/schema/ontology/ObjectQueryField.ts @@ -17,7 +17,11 @@ function create(getTypeReference: GetTypeReference, objectType: ObjectTypeV2): N { description: `Get a single ${objectType.displayName}.`, // TODO: deprecation - args: { [objectType.primaryKey]: { type: Schemas.required(ObjectArgumentType.get(objectType)) } }, + args: { + [objectType.primaryKey]: { + type: Schemas.required(ObjectArgumentType.get(objectType)), + }, + }, type: ObjectType.getReference(getTypeReference, objectType), plan: (_$query, $args) => { return loadOne($args.getRaw(objectType.primaryKey), context(), getObjectLoader(objectType)); @@ -29,4 +33,6 @@ function create(getTypeReference: GetTypeReference, objectType: ObjectTypeV2): N return [fieldName, field]; } -export const ObjectQueryField = { create }; +export const ObjectQueryField = { + create, +}; diff --git a/old/foundry-graphql-schema/src/schema/ontology/ObjectSearchQueryField.ts b/old/foundry-graphql-schema/src/schema/ontology/ObjectSearchQueryField.ts index 62f6c80b..39ecf6da 100644 --- a/old/foundry-graphql-schema/src/schema/ontology/ObjectSearchQueryField.ts +++ b/old/foundry-graphql-schema/src/schema/ontology/ObjectSearchQueryField.ts @@ -25,7 +25,10 @@ function create(getTypeReference: GetTypeReference, objectType: ObjectTypeV2): N return lambda( $args.getRaw(), (args: { where: ObjectSetFilter }) => { - let objectSet: ObjectSet = { type: "base", objectType: objectType.apiName }; + let objectSet: ObjectSet = { + type: "base", + objectType: objectType.apiName, + }; if (args.where) { const filter = ObjectSetFilterType.toObjectSetFilter(objectType, args.where); if (filter) { @@ -44,4 +47,6 @@ function create(getTypeReference: GetTypeReference, objectType: ObjectTypeV2): N return [fieldName, field]; } -export const ObjectSearchQueryField = { create }; +export const ObjectSearchQueryField = { + create, +}; diff --git a/old/foundry-graphql-schema/src/schema/ontology/ObjectSetFilterType.ts b/old/foundry-graphql-schema/src/schema/ontology/ObjectSetFilterType.ts index a88abc72..886687b2 100644 --- a/old/foundry-graphql-schema/src/schema/ontology/ObjectSetFilterType.ts +++ b/old/foundry-graphql-schema/src/schema/ontology/ObjectSetFilterType.ts @@ -52,10 +52,12 @@ function create(typeRegistry: TypeRegistry, objectType: ObjectTypeFullMetadata): .map(([propertyApiName, property]) => { const filterType = getFilterType(getTypeReference, property); return filterType - ? ([propertyApiName, { type: filterType }] satisfies [ - string, - GraphQLInputFieldConfig, - ]) + ? ([ + propertyApiName, + { + type: filterType, + }, + ] satisfies [string, GraphQLInputFieldConfig]) : undefined; }) .filter((propertyFilterField) => propertyFilterField !== undefined) @@ -72,4 +74,8 @@ function getReference(getTypeReference: GetTypeReference, objectType: ObjectType return getTypeReference(getName(objectType)) as GraphQLInputObjectType; } -export const ObjectSetFilterType = { create, getReference, toObjectSetFilter }; +export const ObjectSetFilterType = { + create, + getReference, + toObjectSetFilter, +}; diff --git a/old/foundry-graphql-schema/src/schema/ontology/ObjectSetLinkField.ts b/old/foundry-graphql-schema/src/schema/ontology/ObjectSetLinkField.ts index f12b2ce1..1f14b139 100644 --- a/old/foundry-graphql-schema/src/schema/ontology/ObjectSetLinkField.ts +++ b/old/foundry-graphql-schema/src/schema/ontology/ObjectSetLinkField.ts @@ -19,7 +19,11 @@ function create( plan: ($objectSet) => { return lambda( $objectSet, - (objectSet): ObjectSet => ({ type: "searchAround", link: linkType.apiName, objectSet }), + (objectSet): ObjectSet => ({ + type: "searchAround", + link: linkType.apiName, + objectSet, + }), true ); }, @@ -30,4 +34,6 @@ function create( return [fieldName, field]; } -export const ObjectSetLinkField = { create }; +export const ObjectSetLinkField = { + create, +}; diff --git a/old/foundry-graphql-schema/src/schema/ontology/ObjectSetType.ts b/old/foundry-graphql-schema/src/schema/ontology/ObjectSetType.ts index 28013671..6cca9a55 100644 --- a/old/foundry-graphql-schema/src/schema/ontology/ObjectSetType.ts +++ b/old/foundry-graphql-schema/src/schema/ontology/ObjectSetType.ts @@ -34,4 +34,8 @@ function getReferenceByName( ): GraphQLObjectType { return getTypeReference(`${apiName}Set`) as GraphQLObjectType; } -export const ObjectSetType = { create, getReference, getReferenceByName }; +export const ObjectSetType = { + create, + getReference, + getReferenceByName, +}; diff --git a/old/foundry-graphql-schema/src/schema/ontology/ObjectType.ts b/old/foundry-graphql-schema/src/schema/ontology/ObjectType.ts index c4efb972..6fa9ff09 100644 --- a/old/foundry-graphql-schema/src/schema/ontology/ObjectType.ts +++ b/old/foundry-graphql-schema/src/schema/ontology/ObjectType.ts @@ -93,4 +93,9 @@ function getReferenceByName( return getTypeReference(apiName) as GraphQLObjectType; } -export const ObjectType = { create, getReference, getReferenceByName, getNodeIdHandler }; +export const ObjectType = { + create, + getReference, + getReferenceByName, + getNodeIdHandler, +}; diff --git a/old/foundry-graphql-schema/src/schema/ontology/ObjectUserLinkField.ts b/old/foundry-graphql-schema/src/schema/ontology/ObjectUserLinkField.ts index 64e708cb..77c0551f 100644 --- a/old/foundry-graphql-schema/src/schema/ontology/ObjectUserLinkField.ts +++ b/old/foundry-graphql-schema/src/schema/ontology/ObjectUserLinkField.ts @@ -28,4 +28,6 @@ function create( return [fieldName, field]; } -export const ObjectUserLinkField = { create }; +export const ObjectUserLinkField = { + create, +}; diff --git a/old/foundry-graphql-schema/src/schema/ontology/OntologyQueryFields.ts b/old/foundry-graphql-schema/src/schema/ontology/OntologyQueryFields.ts index ff252717..aac4f48d 100644 --- a/old/foundry-graphql-schema/src/schema/ontology/OntologyQueryFields.ts +++ b/old/foundry-graphql-schema/src/schema/ontology/OntologyQueryFields.ts @@ -18,4 +18,6 @@ function create( return [...objectQueryFields, ...objectSearchQueryFields]; } -export const OntologyQueryFields = { create }; +export const OntologyQueryFields = { + create, +}; diff --git a/old/foundry-graphql-schema/src/schema/ontology/getObjectLoader.ts b/old/foundry-graphql-schema/src/schema/ontology/getObjectLoader.ts index 4659f137..c90bdb4f 100644 --- a/old/foundry-graphql-schema/src/schema/ontology/getObjectLoader.ts +++ b/old/foundry-graphql-schema/src/schema/ontology/getObjectLoader.ts @@ -31,7 +31,11 @@ export function getObjectLoader(objectType: ObjectTypeV2): ObjectLoader { context.ontologyRid, objectType.apiName, { - where: { type: "in", field: objectType.primaryKey, value: ids as PrimaryKeyValue[] }, + where: { + type: "in", + field: objectType.primaryKey, + value: ids as PrimaryKeyValue[], + }, pageSize: ids.length, select: attributes as PropertyApiName[], excludeRid: true, diff --git a/old/foundry-graphql-schema/src/schema/utils/Schemas.ts b/old/foundry-graphql-schema/src/schema/utils/Schemas.ts index 3063b4f5..f73cb5b8 100644 --- a/old/foundry-graphql-schema/src/schema/utils/Schemas.ts +++ b/old/foundry-graphql-schema/src/schema/utils/Schemas.ts @@ -8,4 +8,7 @@ function list(type: T): GraphQLList> { return new GraphQLList(required(type)); } -export const Schemas = { required, list }; +export const Schemas = { + required, + list, +}; diff --git a/old/foundry-graphql-schema/tsconfig.json b/old/foundry-graphql-schema/tsconfig.json index 3e4cc408..1326a774 100644 --- a/old/foundry-graphql-schema/tsconfig.json +++ b/old/foundry-graphql-schema/tsconfig.json @@ -1 +1,4 @@ -{ "$schema": "https://json.schemastore.org/tsconfig", "extends": "@party-stack/build-config/tsconfig.json" } +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@party-stack/build-config/tsconfig.json" +} diff --git a/old/foundry-graphql-server/src/index.ts b/old/foundry-graphql-server/src/index.ts index 3485362a..3cdf563d 100644 --- a/old/foundry-graphql-server/src/index.ts +++ b/old/foundry-graphql-server/src/index.ts @@ -10,7 +10,15 @@ export async function start() { const serv = grafserv({ schema, - preset: { grafast: { context, explain: true }, grafserv: { graphiql: true } }, + preset: { + grafast: { + context, + explain: true, + }, + grafserv: { + graphiql: true, + }, + }, }); const server = createServer(); diff --git a/old/foundry-graphql-server/tsconfig.json b/old/foundry-graphql-server/tsconfig.json index 3e4cc408..1326a774 100644 --- a/old/foundry-graphql-server/tsconfig.json +++ b/old/foundry-graphql-server/tsconfig.json @@ -1 +1,4 @@ -{ "$schema": "https://json.schemastore.org/tsconfig", "extends": "@party-stack/build-config/tsconfig.json" } +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@party-stack/build-config/tsconfig.json" +} diff --git a/old/foundry-graphql-server/vite.config.ts b/old/foundry-graphql-server/vite.config.ts index 9dc57f15..ab8a3068 100644 --- a/old/foundry-graphql-server/vite.config.ts +++ b/old/foundry-graphql-server/vite.config.ts @@ -5,7 +5,12 @@ export default defineLibraryConfig({ tasks: { dev: { command: "tsx watch src/index.ts", - dependsOn: [{ task: "build", from: ["dependencies", "devDependencies"] }], + dependsOn: [ + { + task: "build", + from: ["dependencies", "devDependencies"], + }, + ], cache: false, }, }, diff --git a/old/foundry-graphql/src/relay-local/index.ts b/old/foundry-graphql/src/relay-local/index.ts index ec4c7649..33cd7bcf 100644 --- a/old/foundry-graphql/src/relay-local/index.ts +++ b/old/foundry-graphql/src/relay-local/index.ts @@ -12,7 +12,12 @@ export function createNetwork(client: Client): ReturnType const executor = await executorPromise; const token = await client.__osdkClientContext.tokenProvider(); // oxlint-disable-next-line typescript/no-unsafe-assignment - const result = await executor({ query: request.text, operationName: request.name, variables, token }); + const result = await executor({ + query: request.text, + operationName: request.name, + variables, + token, + }); // oxlint-disable-next-line typescript/no-unsafe-return return result; }; diff --git a/old/foundry-graphql/tsconfig.json b/old/foundry-graphql/tsconfig.json index 3e4cc408..1326a774 100644 --- a/old/foundry-graphql/tsconfig.json +++ b/old/foundry-graphql/tsconfig.json @@ -1 +1,4 @@ -{ "$schema": "https://json.schemastore.org/tsconfig", "extends": "@party-stack/build-config/tsconfig.json" } +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@party-stack/build-config/tsconfig.json" +} diff --git a/old/osdk-react-todo-app/src/App.tsx b/old/osdk-react-todo-app/src/App.tsx index a7a5279f..2fa2bce5 100644 --- a/old/osdk-react-todo-app/src/App.tsx +++ b/old/osdk-react-todo-app/src/App.tsx @@ -18,7 +18,10 @@ function App() { hasNextPage, isFetchingNextPage, fetchNextPage, - } = useObjects(Task, { $orderBy: { completedAt: "asc", createdAt: "desc" }, $pageSize: 10 }); + } = useObjects(Task, { + $orderBy: { completedAt: "asc", createdAt: "desc" }, + $pageSize: 10, + }); useLiveObjectSet(Task); const { mutate: addTask, isPending } = useAction(createTask); @@ -44,7 +47,11 @@ function App() { const { data: { $count: totalTodos }, - } = useAggregations(Task, { $select: { $count: "unordered" } }); + } = useAggregations(Task, { + $select: { + $count: "unordered", + }, + }); return (
diff --git a/old/osdk-react-todo-app/src/main.tsx b/old/osdk-react-todo-app/src/main.tsx index 4ae94c67..5d67fbab 100644 --- a/old/osdk-react-todo-app/src/main.tsx +++ b/old/osdk-react-todo-app/src/main.tsx @@ -15,10 +15,22 @@ const queryClient = new QueryClient(); const router = createBrowserRouter( [ - { path: "/", element: }, - { path: "/login", element: }, - { path: "/auth/callback", element: }, - { path: "/task/:taskId", element: }, + { + path: "/", + element: , + }, + { + path: "/login", + element: , + }, + { + path: "/auth/callback", + element: , + }, + { + path: "/task/:taskId", + element: , + }, ], { basename: import.meta.env.BASE_URL } ); diff --git a/old/osdk-react-todo-app/vite.config.ts b/old/osdk-react-todo-app/vite.config.ts index a137ebe5..04237a4c 100644 --- a/old/osdk-react-todo-app/vite.config.ts +++ b/old/osdk-react-todo-app/vite.config.ts @@ -4,4 +4,6 @@ import react from "@vitejs/plugin-react-swc"; import { lazyPlugins } from "vite-plus"; // https://vite.dev/config/ -export default defineAppConfig({ plugins: lazyPlugins(() => [react(), tailwindcss()]) }); +export default defineAppConfig({ + plugins: lazyPlugins(() => [react(), tailwindcss()]), +}); diff --git a/old/osdk-react/src/ai/useChat.ts b/old/osdk-react/src/ai/useChat.ts index 483c28da..7eecde10 100644 --- a/old/osdk-react/src/ai/useChat.ts +++ b/old/osdk-react/src/ai/useChat.ts @@ -29,7 +29,9 @@ export function useChat({ type: "ready" }); + >({ + type: "ready", + }); const { data: exchanges } = useSuspenseQuery({ queryFn: async () => { const content = await Contents.get(client, agentRid, opts.sessionRid, { preview: true }); @@ -47,7 +49,11 @@ export function useChat { const attachment = await Attachments.upload(client, file, { filename: file.name }); - return { ...attachment, sizeBytes: Number(attachment.sizeBytes) }; + return { + ...attachment, + sizeBytes: Number(attachment.sizeBytes), + }; }, }); } diff --git a/old/osdk-react/src/ontology/ObjectList.ts b/old/osdk-react/src/ontology/ObjectList.ts index dc90ffab..aa1764e5 100644 --- a/old/osdk-react/src/ontology/ObjectList.ts +++ b/old/osdk-react/src/ontology/ObjectList.ts @@ -32,4 +32,6 @@ function getComparator( }; } -export const ObjectList = { getComparator }; +export const ObjectList = { + getComparator, +}; diff --git a/old/osdk-react/src/ontology/object-set/ObjectSet.ts b/old/osdk-react/src/ontology/object-set/ObjectSet.ts index 3371c651..adb1c5c6 100644 --- a/old/osdk-react/src/ontology/object-set/ObjectSet.ts +++ b/old/osdk-react/src/ontology/object-set/ObjectSet.ts @@ -40,4 +40,8 @@ function toOSDK( return objectSet.filter ? objectSetClient.where(objectSet.filter) : objectSetClient; } -export const ObjectSet = { normalize, contains, toOSDK }; +export const ObjectSet = { + normalize, + contains, + toOSDK, +}; diff --git a/old/osdk-react/src/useAction.ts b/old/osdk-react/src/useAction.ts index b2909907..f4c766c2 100644 --- a/old/osdk-react/src/useAction.ts +++ b/old/osdk-react/src/useAction.ts @@ -42,7 +42,11 @@ export function useAction>( createdObjectsPromise, modifiedObjectsPromise, ]); - return { createdObjects, modifiedObjects, deletedObjects: result.deletedObjects ?? [] }; + return { + createdObjects, + modifiedObjects, + deletedObjects: result.deletedObjects ?? [], + }; }, onSuccess: (data, variables, onMutateResult, context) => { const observation: OntologyObservation = { diff --git a/old/osdk-react/src/useAggregations.ts b/old/osdk-react/src/useAggregations.ts index 15fb1951..f97b6dc0 100644 --- a/old/osdk-react/src/useAggregations.ts +++ b/old/osdk-react/src/useAggregations.ts @@ -24,7 +24,9 @@ export function useAggregations< R extends AggregationsResults, >( type: T, - opts: { $where?: WhereClause } & AggregateOptsThatErrorsAndDisallowsOrderingWithMultipleGroupBy, + opts: { + $where?: WhereClause; + } & AggregateOptsThatErrorsAndDisallowsOrderingWithMultipleGroupBy, queryOpts?: Omit< UseSuspenseQueryOptions | null, Error, R>, "queryKey" | "queryFn" | "initialData" | "initialDataUpdatedAt" @@ -32,7 +34,10 @@ export function useAggregations< ): UseSuspenseQueryResult> { const { client } = useOsdkContext(); const { $where, ...aggregateOpts } = opts; - const objectSet: ObjectSet = { type, filter: $where }; + const objectSet: ObjectSet = { + type, + filter: $where, + }; return useSuspenseQuery({ ...queryOpts, queryFn: () => diff --git a/old/osdk-react/src/useLiveObjectSet.ts b/old/osdk-react/src/useLiveObjectSet.ts index 4dbc833a..1d675e86 100644 --- a/old/osdk-react/src/useLiveObjectSet.ts +++ b/old/osdk-react/src/useLiveObjectSet.ts @@ -14,7 +14,9 @@ export type LiveObjectSetState = { status: "connecting" | "connected" | "error"; export function useLiveObjectSet(type: T): LiveObjectSetState { const { client } = useOsdkContext(); const queryClient = useQueryClient(); - const [state, setState] = React.useState({ status: "connecting" }); + const [state, setState] = React.useState({ + status: "connecting", + }); React.useEffect(() => { // oxlint-disable-next-line typescript/no-explicit-any const objectSet = client(type as any) as ObjectSet; diff --git a/old/osdk-react/src/useObject.ts b/old/osdk-react/src/useObject.ts index 718df33d..e41483e1 100644 --- a/old/osdk-react/src/useObject.ts +++ b/old/osdk-react/src/useObject.ts @@ -29,7 +29,9 @@ export function useObject | null>( } const objectType = await client.fetchMetadata(type); const result = await (client(type) as ObjectSet) - .where({ [objectType.primaryKeyApiName]: primaryKey } as WhereClause) + .where({ + [objectType.primaryKeyApiName]: primaryKey, + } as WhereClause) .fetchPage({ $pageSize: 1 }); return result.data[0] ?? null; }, diff --git a/old/osdk-react/src/useObjects.ts b/old/osdk-react/src/useObjects.ts index 4c532d2a..a93ac10f 100644 --- a/old/osdk-react/src/useObjects.ts +++ b/old/osdk-react/src/useObjects.ts @@ -18,7 +18,11 @@ const QUERY_KEY_PREFIX = ["osdk", "objects"]; export function useObjects( type: T, - opts: { $where?: WhereClause; $orderBy: ObjectSetOrderBy; $pageSize?: number }, + opts: { + $where?: WhereClause; + $orderBy: ObjectSetOrderBy; + $pageSize?: number; + }, queryOpts?: Omit< UseSuspenseInfiniteQueryOptions, | "queryKey" @@ -33,10 +37,15 @@ export function useObjects( ): Omit< UseSuspenseInfiniteQueryResult, "data" | "fetchPreviousPage" | "isFetchingPreviousPage" | "hasPreviousPage" | "isFetchPreviousPageError" -> & { data: Osdk[] } { +> & { + data: Osdk[]; +} { const { client } = useOsdkContext(); const queryClient = useQueryClient(); - const objectList: ObjectList = { objectSet: { type, filter: opts.$where }, orderBy: opts.$orderBy }; + const objectList: ObjectList = { + objectSet: { type, filter: opts.$where }, + orderBy: opts.$orderBy, + }; const { data, ...rest } = useSuspenseInfiniteQuery({ ...queryOpts, queryFn: ({ pageParam: $nextPageToken }) => { @@ -47,7 +56,10 @@ export function useObjects( $nextPageToken, }); void result.then((page) => { - const observation: OntologyObservation = { knownObjects: page.data, deletedObjects: [] }; + const observation: OntologyObservation = { + knownObjects: page.data, + deletedObjects: [], + }; updateObjectQueries(queryClient, observation); }); return result; diff --git a/old/osdk-utils/src/ActionError.ts b/old/osdk-utils/src/ActionError.ts index 9f713424..d178ab4e 100644 --- a/old/osdk-utils/src/ActionError.ts +++ b/old/osdk-utils/src/ActionError.ts @@ -10,13 +10,21 @@ export type ObjectAlreadyExistsError = Omit & { export type InvalidParameterValueError = Omit & { errorName: "InvalidParameterValue"; errorCode: "INVALID_ARGUMENT"; - parameters: { parameterBaseType: string; parameterId: string; parameterValue: unknown }; + parameters: { + parameterBaseType: string; + parameterId: string; + parameterValue: unknown; + }; }; export type ParametersNotFoundError = Omit & { errorName: "ParametersNotFound"; errorCode: "INVALID_ARGUMENT"; - parameters: { actionType: string; unknownParameterIds: string[]; configuredParameterIds: string[] }; + parameters: { + actionType: string; + unknownParameterIds: string[]; + configuredParameterIds: string[]; + }; }; export type KnownActionError = diff --git a/old/osdk-utils/tsconfig.json b/old/osdk-utils/tsconfig.json index 3e4cc408..1326a774 100644 --- a/old/osdk-utils/tsconfig.json +++ b/old/osdk-utils/tsconfig.json @@ -1 +1,4 @@ -{ "$schema": "https://json.schemastore.org/tsconfig", "extends": "@party-stack/build-config/tsconfig.json" } +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@party-stack/build-config/tsconfig.json" +} diff --git a/packages/better-auth/src/createBetterAuthConnectionAdapter.test.ts b/packages/better-auth/src/createBetterAuthConnectionAdapter.test.ts index 3be5b8cd..9c9a2c29 100644 --- a/packages/better-auth/src/createBetterAuthConnectionAdapter.test.ts +++ b/packages/better-auth/src/createBetterAuthConnectionAdapter.test.ts @@ -45,16 +45,34 @@ describe("createBetterAuthConnectionAdapter", () => { }, }, ]; - const listDeviceSessions = vi.fn(() => Promise.resolve({ data: sessions, error: null })); - const revoke = vi.fn(() => Promise.resolve({ data: { status: true }, error: null })); + const listDeviceSessions = vi.fn(() => + Promise.resolve({ + data: sessions, + error: null, + }) + ); + const revoke = vi.fn(() => + Promise.resolve({ + data: { status: true }, + error: null, + }) + ); const email = vi.fn((_input: { email: string; password: string }) => { void _input; - return Promise.resolve({ data: { user: sessions[0]!.user }, error: null }); + return Promise.resolve({ + data: { + user: sessions[0]!.user, + }, + error: null, + }); }); const signIn = Object.assign(() => undefined, { email }); let notifySessionChanged = () => Promise.resolve(); const client = { - multiSession: { listDeviceSessions, revoke }, + multiSession: { + listDeviceSessions, + revoke, + }, signIn, partyStack: { subscribe(listener) { @@ -65,7 +83,9 @@ describe("createBetterAuthConnectionAdapter", () => { }, }, } satisfies BetterAuthConnectionClient; - const adapter = await createBetterAuthConnectionAdapter({ client })({ + const adapter = await createBetterAuthConnectionAdapter({ + client, + })({ installationId: "better-auth", runtime: {} as never, }); @@ -82,7 +102,10 @@ describe("createBetterAuthConnectionAdapter", () => { if (!first?.session.egress) { throw new Error("Expected Better Auth egress."); } - const egress = first.session.egress({ fetch, createWebSocket }); + const egress = first.session.egress({ + fetch, + createWebSocket, + }); await egress.fetch(new Request("https://app.example/ontology")); const request = fetch.mock.calls[0]?.[0]; expect(request).toBeInstanceOf(Request); @@ -103,13 +126,18 @@ describe("createBetterAuthConnectionAdapter", () => { connect, disconnect: () => Promise.resolve(), }); - const result = await authentication.signIn.email({ email: "ada@example.com", password: "ada" }); + const result = await authentication.signIn.email({ + email: "ada@example.com", + password: "ada", + }); await notifySessionChanged(); expect(result.data?.user.id).toBe("user-1"); expect(connect).toHaveBeenCalledTimes(2); expect(connected.map(({ connection }) => connection.userId)).toEqual(["user-1", "user-2"]); await restored[1]!.session.disconnect(); - expect(revoke).toHaveBeenCalledWith({ sessionToken: "selector-2" }); + expect(revoke).toHaveBeenCalledWith({ + sessionToken: "selector-2", + }); }); }); diff --git a/packages/better-auth/src/createBetterAuthConnectionAdapter.ts b/packages/better-auth/src/createBetterAuthConnectionAdapter.ts index 3f4241d2..c661e973 100644 --- a/packages/better-auth/src/createBetterAuthConnectionAdapter.ts +++ b/packages/better-auth/src/createBetterAuthConnectionAdapter.ts @@ -61,7 +61,10 @@ export function createBetterAuthConnectionAdapter { it("provides the multi-session server API", () => { - const plugin = partyStack({ maximumSessions: 10 }); + const plugin = partyStack({ + maximumSessions: 10, + }); expect(plugin.id).toBe("party-stack"); expect(plugin.endpoints.listDeviceSessions).toBeDefined(); @@ -15,13 +17,26 @@ describe("partyStack", () => { }); it("makes normal getSession calls request-scoped", async () => { - const database = { user: [], session: [], account: [], verification: [] }; + const database = { + user: [], + session: [], + account: [], + verification: [], + }; const auth = betterAuth({ baseURL: "http://localhost:3000", secret: "party-stack-test-secret-party-stack-test-secret", database: memoryAdapter(database), - emailAndPassword: { enabled: true, minPasswordLength: 3 }, - session: { cookieCache: { enabled: true, maxAge: 300 } }, + emailAndPassword: { + enabled: true, + minPasswordLength: 3, + }, + session: { + cookieCache: { + enabled: true, + maxAge: 300, + }, + }, plugins: [partyStack()], }); const cookies = new Map(); @@ -43,8 +58,15 @@ describe("partyStack", () => { const response = await auth.handler( new Request("http://localhost:3000/api/auth/sign-up/email", { method: "POST", - headers: { "content-type": "application/json", cookie: cookieHeader() }, - body: JSON.stringify({ email, password: "password", name }), + headers: { + "content-type": "application/json", + cookie: cookieHeader(), + }, + body: JSON.stringify({ + email, + password: "password", + name, + }), }) ); expect(response.status).toBe(200); @@ -53,23 +75,49 @@ describe("partyStack", () => { await signUp("ada@example.com", "Ada"); await signUp("grace@example.com", "Grace"); - const headers = new Headers({ cookie: cookieHeader() }); - const sessions = await auth.api.listDeviceSessions({ headers }); + const headers = new Headers({ + cookie: cookieHeader(), + }); + const sessions = await auth.api.listDeviceSessions({ + headers, + }); const ada = sessions.find(({ user }) => user.email === "ada@example.com"); expect(ada).toBeDefined(); - expect((await auth.api.getSession({ headers }))?.user.email).toBe("grace@example.com"); + expect( + ( + await auth.api.getSession({ + headers, + }) + )?.user.email + ).toBe("grace@example.com"); headers.set(PARTY_STACK_SESSION_HEADER, ada!.session.id); - expect((await auth.api.getSession({ headers }))?.user.email).toBe("ada@example.com"); + expect( + ( + await auth.api.getSession({ + headers, + }) + )?.user.email + ).toBe("ada@example.com"); headers.delete(PARTY_STACK_SESSION_HEADER); headers.set( "sec-websocket-protocol", [createPartyStackSessionProtocol(ada!.session.id), "ontology"].join(", ") ); - expect((await auth.api.getSession({ headers }))?.user.email).toBe("ada@example.com"); + expect( + ( + await auth.api.getSession({ + headers, + }) + )?.user.email + ).toBe("ada@example.com"); headers.set("sec-websocket-protocol", createPartyStackSessionProtocol("missing-session")); - expect(await auth.api.getSession({ headers })).toBeNull(); + expect( + await auth.api.getSession({ + headers, + }) + ).toBeNull(); }); }); diff --git a/packages/better-auth/src/partyStack.ts b/packages/better-auth/src/partyStack.ts index 0c7421a7..b265c8b6 100644 --- a/packages/better-auth/src/partyStack.ts +++ b/packages/better-auth/src/partyStack.ts @@ -5,7 +5,9 @@ import { getPartyStackSessionId } from "./sessionSelection.js"; import type { BetterAuthPlugin } from "better-auth"; export type PartyStackOptions = MultiSessionConfig; -export type PartyStackPlugin = Omit, "id"> & { id: "party-stack" }; +export type PartyStackPlugin = Omit, "id"> & { + id: "party-stack"; +}; export function partyStack(options?: PartyStackOptions): PartyStackPlugin { const plugin = multiSession(options); @@ -55,7 +57,11 @@ export function partyStack(options?: PartyStackOptions): PartyStackPlugin { setRequestCookie(selectedHeaders, sessionToken.name, signedToken); break; } - return { context: { headers: selectedHeaders } }; + return { + context: { + headers: selectedHeaders, + }, + }; }), }, ], diff --git a/packages/better-auth/src/partyStackClient.ts b/packages/better-auth/src/partyStackClient.ts index 9ff4ff9e..adc500af 100644 --- a/packages/better-auth/src/partyStackClient.ts +++ b/packages/better-auth/src/partyStackClient.ts @@ -45,7 +45,9 @@ export function partyStackClient() { }, }, ], - getActions: () => ({ partyStack: partyStackActions }), + getActions: () => ({ + partyStack: partyStackActions, + }), } satisfies BetterAuthClientPlugin; } diff --git a/packages/better-auth/src/sessionSelection.test.ts b/packages/better-auth/src/sessionSelection.test.ts index ea141af2..4421922f 100644 --- a/packages/better-auth/src/sessionSelection.test.ts +++ b/packages/better-auth/src/sessionSelection.test.ts @@ -8,14 +8,18 @@ import { describe("session selection", () => { it("selects HTTP sessions from the Party Stack header", () => { - const headers = new Headers({ [PARTY_STACK_SESSION_HEADER]: "session-1" }); + const headers = new Headers({ + [PARTY_STACK_SESSION_HEADER]: "session-1", + }); expect(getPartyStackSessionId(headers)).toBe("session-1"); }); it("selects WebSocket sessions from offered protocols", () => { const protocol = createPartyStackSessionProtocol("session-1"); - const headers = new Headers({ "sec-websocket-protocol": [protocol, "ontology"].join(", ") }); + const headers = new Headers({ + "sec-websocket-protocol": [protocol, "ontology"].join(", "), + }); expect(getPartyStackSessionId(headers)).toBe("session-1"); expect(withoutPartyStackSessionProtocols([protocol, "ontology"])).toEqual(["ontology"]); diff --git a/packages/better-auth/tsconfig.json b/packages/better-auth/tsconfig.json index 3e4cc408..1326a774 100644 --- a/packages/better-auth/tsconfig.json +++ b/packages/better-auth/tsconfig.json @@ -1 +1,4 @@ -{ "$schema": "https://json.schemastore.org/tsconfig", "extends": "@party-stack/build-config/tsconfig.json" } +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@party-stack/build-config/tsconfig.json" +} diff --git a/packages/blobs/src/createBlobManager.ts b/packages/blobs/src/createBlobManager.ts index 6cdb3da8..69b80ecc 100644 --- a/packages/blobs/src/createBlobManager.ts +++ b/packages/blobs/src/createBlobManager.ts @@ -37,7 +37,9 @@ function waitForAbort(signal: AbortSignal): Operation { abort(); return () => undefined; } - signal.addEventListener("abort", abort, { once: true }); + signal.addEventListener("abort", abort, { + once: true, + }); return () => signal.removeEventListener("abort", abort); }); } @@ -66,7 +68,9 @@ function* useBlobLeader(store: BlobStore, wake: Signal, gcTime: num return; } - const wakes = yield* batch({ maxTime: GC_WAKE_BATCH_TIME })(wake); + const wakes = yield* batch({ + maxTime: GC_WAKE_BATCH_TIME, + })(wake); while (true) { const now = Date.now(); yield* until( @@ -162,7 +166,10 @@ function toPartialMetadata(ref: BlobMetadataRecord): PartialBlobMetadata { export function createBlobManager(options: BlobManagerOptions): BlobManager { const gcTime = normalizeGCTime(options.gcTime); const wake = createSignal(); - const store = createBlobStore({ runtime: options.runtime, onCacheChanged: () => wake.send() }); + const store = createBlobStore({ + runtime: options.runtime, + onCacheChanged: () => wake.send(), + }); const lifetime = run(() => useManagerLifetime(options, store, wake, gcTime)); void lifetime.catch(() => undefined); @@ -238,7 +245,9 @@ export function createBlobManager(options: BlobManagerOptions): BlobManager { const unresolved = selection.filter((field) => !isResolved(record, field)); if (unresolved.length > 0) { try { - localBlob ??= await readBytes(id, { meta: metadataOptions.meta }); + localBlob ??= await readBytes(id, { + meta: metadataOptions.meta, + }); record = await mergeBlobMetadata(id, record, localBlob, unresolved, true); } catch (error) { if (remoteMetadataError !== undefined) { @@ -267,8 +276,12 @@ export function createBlobManager(options: BlobManagerOptions): BlobManager { const blob = await options.remote.read(remoteId, readOptions); const name = typeof ref?.name === "string" ? ref.name : undefined; const cached = name - ? new File([blob], name, { type: ref?.type || blob.type }) - : new Blob([blob], { type: ref?.type || blob.type }); + ? new File([blob], name, { + type: ref?.type || blob.type, + }) + : new Blob([blob], { + type: ref?.type || blob.type, + }); await store.cache(ref?.id ?? id, cached); return blob; }; diff --git a/packages/blobs/src/gc/collectBlobGarbage.test.ts b/packages/blobs/src/gc/collectBlobGarbage.test.ts index b604f1a3..74eb8e48 100644 --- a/packages/blobs/src/gc/collectBlobGarbage.test.ts +++ b/packages/blobs/src/gc/collectBlobGarbage.test.ts @@ -20,16 +20,25 @@ function ref(id: string, overrides: Partial = {}): TestBlobRecor function setup(refs: TestBlobRecord[]) { const blobBytes = new MemoryBlobBytesStore(new Map(refs.map(({ id }) => [id, new Blob([id])]))); - const coordination = new SingleProcessCoordination({ scope: `blob-gc-${crypto.randomUUID()}` }); + const coordination = new SingleProcessCoordination({ + scope: `blob-gc-${crypto.randomUUID()}`, + }); const store = createBlobStore({ - runtime: { owner: "user-1", namespace: "ontology-1", blobBytes, coordination }, + runtime: { + owner: "user-1", + namespace: "ontology-1", + blobBytes, + coordination, + }, }); return { blobBytes, coordination, store }; } async function insertRefs(store: ReturnType, refs: TestBlobRecord[]): Promise { for (const value of refs) { - await store.collection.insert(value, { optimistic: false }).isPersisted.promise; + await store.collection.insert(value, { + optimistic: false, + }).isPersisted.promise; } } @@ -37,9 +46,16 @@ describe("collectBlobGarbage", () => { it("purges completed remote-backed refs older than the cutoff", async () => { const refs = [ ref("expired", { updatedAt: 10 }), - ref("persisted", { state: "persisted", remoteId: "remote-persisted", updatedAt: 10 }), + ref("persisted", { + state: "persisted", + remoteId: "remote-persisted", + updatedAt: 10, + }), ref("recent", { updatedAt: 100 }), - ref("staged", { state: "staged", updatedAt: 10 }), + ref("staged", { + state: "staged", + updatedAt: 10, + }), ref("failed", { updatedAt: 10, operation: { @@ -53,7 +69,10 @@ describe("collectBlobGarbage", () => { const { blobBytes, coordination, store } = setup(refs); try { await insertRefs(store, refs); - await collectBlobGarbage(store, { cutoff: 100, now: 200 }); + await collectBlobGarbage(store, { + cutoff: 100, + now: 200, + }); await expect(store.find("expired")).resolves.toBeUndefined(); await expect(store.find("persisted")).resolves.toBeUndefined(); @@ -73,10 +92,19 @@ describe("collectBlobGarbage", () => { const now = 2 * day; const refs = [ ...Array.from({ length: BLOB_GC_BATCH_SIZE - 1 }, (_, index) => - ref(`filler-${index}`, { size: 100 * BLOB_GC_SIZE_UNIT_BYTES, updatedAt: 0 }) + ref(`filler-${index}`, { + size: 100 * BLOB_GC_SIZE_UNIT_BYTES, + updatedAt: 0, + }) ), - ref("large-day-old", { size: 10 * BLOB_GC_SIZE_UNIT_BYTES, updatedAt: day }), - ref("tiny-two-days-old", { size: 1024, updatedAt: 0 }), + ref("large-day-old", { + size: 10 * BLOB_GC_SIZE_UNIT_BYTES, + updatedAt: day, + }), + ref("tiny-two-days-old", { + size: 1024, + updatedAt: 0, + }), ]; const { coordination, store } = setup(refs); try { @@ -87,7 +115,10 @@ describe("collectBlobGarbage", () => { purged.push(id); await purge(id, options); }); - await collectBlobGarbage(store, { cutoff: now, now }); + await collectBlobGarbage(store, { + cutoff: now, + now, + }); expect(purged.slice(0, BLOB_GC_BATCH_SIZE)).toContain("large-day-old"); expect(purged.slice(0, BLOB_GC_BATCH_SIZE)).not.toContain("tiny-two-days-old"); diff --git a/packages/blobs/src/gc/collectBlobGarbage.ts b/packages/blobs/src/gc/collectBlobGarbage.ts index 1b2104e6..dd8f9ea3 100644 --- a/packages/blobs/src/gc/collectBlobGarbage.ts +++ b/packages/blobs/src/gc/collectBlobGarbage.ts @@ -72,12 +72,16 @@ export async function collectBlobGarbage(store: BlobStore, opts: CollectBlobGarb ), "desc" ) - .select(({ blob }) => ({ id: blob.id })) + .select(({ blob }) => ({ + id: blob.id, + })) .limit(BLOB_GC_BATCH_SIZE) ); for (const { id } of refs) { throwIfAborted(opts.signal); - await store.purge(id, { signal: opts.signal }); + await store.purge(id, { + signal: opts.signal, + }); } if (refs.length < BLOB_GC_BATCH_SIZE) return; } diff --git a/packages/blobs/src/index.test.ts b/packages/blobs/src/index.test.ts index a4289432..d850e953 100644 --- a/packages/blobs/src/index.test.ts +++ b/packages/blobs/src/index.test.ts @@ -12,7 +12,13 @@ import { createBlobManager } from "./index.js"; import type { BlobManagerOptions, BlobRemoteSource } from "./types.js"; const unexpectedRemote: BlobRemoteSource = { - metadata: (id) => Promise.resolve({ id, size: 0, type: "", name: "" }), + metadata: (id) => + Promise.resolve({ + id, + size: 0, + type: "", + name: "", + }), read: () => Promise.reject(new Error("unexpected remote blob read")), }; @@ -37,7 +43,9 @@ function setup( const coordination = options.runtime?.coordination ?? options.coordination ?? - new SingleProcessCoordination({ scope: "blob-manager-test" }); + new SingleProcessCoordination({ + scope: "blob-manager-test", + }); const runtime: RuntimeAdapter = options.runtime ?? { owner: "user-1", namespace: "ontology-1", @@ -71,7 +79,12 @@ describe("createBlobManager", () => { vi.spyOn(Date, "now").mockReturnValue(100); const { bytes, manager } = setup(); - await manager.stage("attachment-1", new File(["hello"], "hello.txt", { type: "text/plain" })); + await manager.stage( + "attachment-1", + new File(["hello"], "hello.txt", { + type: "text/plain", + }) + ); expect(manager.collection.get("attachment-1")).toMatchObject({ id: "attachment-1", @@ -93,10 +106,19 @@ describe("createBlobManager", () => { it("calculates selected dimensions from local bytes before remote metadata", async () => { const metadata = vi.fn(unexpectedRemote.metadata); - const { manager } = setup({ remote: { metadata, read: unexpectedRemote.read } }); + const { manager } = setup({ + remote: { + metadata, + read: unexpectedRemote.read, + }, + }); await manager.stage("image-1", pngBlob(320, 200)); - await expect(manager.metadata("image-1", { select: ["dimensions"] })).resolves.toMatchObject({ + await expect( + manager.metadata("image-1", { + select: ["dimensions"], + }) + ).resolves.toMatchObject({ dimensions: { width: 320, height: 200 }, }); expect(metadata).not.toHaveBeenCalled(); @@ -122,34 +144,65 @@ describe("createBlobManager", () => { it("pushes down metadata gaps and caches extra fields", async () => { const metadata = vi.fn((_id: string, options?: { select?: readonly string[] }) => { expect(options?.select).toEqual(["dimensions"]); - return Promise.resolve({ size: 5, dimensions: { width: 800, height: 600 } }); + return Promise.resolve({ + size: 5, + dimensions: { width: 800, height: 600 }, + }); }); const { manager } = setup({ - remote: { metadata, read: () => Promise.reject(new Error("unexpected remote blob read")) }, + remote: { + metadata, + read: () => Promise.reject(new Error("unexpected remote blob read")), + }, }); - await expect(manager.metadata("image-1", { select: ["dimensions"] })).resolves.toEqual({ + await expect( + manager.metadata("image-1", { + select: ["dimensions"], + }) + ).resolves.toEqual({ id: "image-1", size: 5, dimensions: { width: 800, height: 600 }, }); - await expect(manager.metadata("image-1", { select: ["size"] })).resolves.toMatchObject({ size: 5 }); + await expect( + manager.metadata("image-1", { + select: ["size"], + }) + ).resolves.toMatchObject({ size: 5 }); expect(metadata).toHaveBeenCalledOnce(); await manager.cleanup(); }); it("persists null as resolved unavailable metadata", async () => { - const metadata = vi.fn(() => Promise.resolve({ name: undefined, dimensions: null })); + const metadata = vi.fn(() => + Promise.resolve({ + name: undefined, + dimensions: null, + }) + ); const { manager } = setup({ - remote: { metadata, read: () => Promise.reject(new Error("unexpected remote blob read")) }, + remote: { + metadata, + read: () => Promise.reject(new Error("unexpected remote blob read")), + }, }); - await expect(manager.metadata("image-1", { select: ["name", "dimensions"] })).resolves.toEqual({ + await expect( + manager.metadata("image-1", { + select: ["name", "dimensions"], + }) + ).resolves.toEqual({ id: "image-1", name: null, dimensions: null, }); - expect(manager.collection.get("image-1")).toMatchObject({ name: null, dimensions: null }); - await manager.metadata("image-1", { select: ["name", "dimensions"] }); + expect(manager.collection.get("image-1")).toMatchObject({ + name: null, + dimensions: null, + }); + await manager.metadata("image-1", { + select: ["name", "dimensions"], + }); expect(metadata).toHaveBeenCalledOnce(); await manager.cleanup(); }); @@ -157,10 +210,17 @@ describe("createBlobManager", () => { it("falls back to remote bytes when selected metadata is unavailable", async () => { const read = vi.fn(() => Promise.resolve(pngBlob(640, 480))); const { manager } = setup({ - remote: { metadata: () => Promise.reject(new Error("metadata unavailable")), read }, + remote: { + metadata: () => Promise.reject(new Error("metadata unavailable")), + read, + }, }); - await expect(manager.metadata("image-1", { select: ["dimensions"] })).resolves.toMatchObject({ + await expect( + manager.metadata("image-1", { + select: ["dimensions"], + }) + ).resolves.toMatchObject({ dimensions: { width: 640, height: 480 }, }); expect(read).toHaveBeenCalledOnce(); @@ -178,7 +238,9 @@ describe("createBlobManager", () => { }); const error = await manager - .metadata("image-1", { select: ["dimensions"] }) + .metadata("image-1", { + select: ["dimensions"], + }) .catch((caught: unknown) => caught); expect(error).toBeInstanceOf(AggregateError); expect((error as AggregateError).errors).toEqual([metadataError, expect.any(Error)]); @@ -187,10 +249,18 @@ describe("createBlobManager", () => { it("pulls remote bytes through the local cache", async () => { let reads = 0; - const remoteBlob = new Blob(["remote"], { type: "text/plain" }); + const remoteBlob = new Blob(["remote"], { + type: "text/plain", + }); const { manager } = setup({ remote: { - metadata: (id) => Promise.resolve({ id, size: 6, type: "text/plain", name: "remote.txt" }), + metadata: (id) => + Promise.resolve({ + id, + size: 6, + type: "text/plain", + name: "remote.txt", + }), read: () => { reads += 1; return Promise.resolve(remoteBlob); @@ -206,8 +276,15 @@ describe("createBlobManager", () => { it("does not fetch metadata while pulling remote bytes", async () => { const metadata = vi.fn(unexpectedRemote.metadata); - const remoteBlob = new Blob(["remote"], { type: "image/png" }); - const { manager } = setup({ remote: { metadata, read: () => Promise.resolve(remoteBlob) } }); + const remoteBlob = new Blob(["remote"], { + type: "image/png", + }); + const { manager } = setup({ + remote: { + metadata, + read: () => Promise.resolve(remoteBlob), + }, + }); await expect(manager.read("remote-id")).resolves.toBe(remoteBlob); expect(metadata).not.toHaveBeenCalled(); @@ -226,7 +303,10 @@ describe("createBlobManager", () => { it("does not remote-fetch missing local-only staged bytes", async () => { const remoteBlob = vi.fn(() => Promise.resolve(new Blob(["unexpected"]))); const { bytes, manager } = setup({ - remote: { metadata: unexpectedRemote.metadata, read: remoteBlob }, + remote: { + metadata: unexpectedRemote.metadata, + read: remoteBlob, + }, }); await manager.stage("local-id", new Blob(["hello"])); await bytes.delete("local-id"); @@ -240,10 +320,20 @@ describe("createBlobManager", () => { let reads = 0; const { bytes, manager } = setup({ remote: { - metadata: (id) => Promise.resolve({ id, size: 6, type: "text/plain", name: "remote.txt" }), + metadata: (id) => + Promise.resolve({ + id, + size: 6, + type: "text/plain", + name: "remote.txt", + }), read: () => { reads += 1; - return Promise.resolve(new Blob(["remote"], { type: "text/plain" })); + return Promise.resolve( + new Blob(["remote"], { + type: "text/plain", + }) + ); }, }, gcTime: 0, @@ -263,12 +353,25 @@ describe("createBlobManager", () => { it("coordinates client-only writes through the host service", async () => { const bytes = new MemoryBlobBytesStore(); - const coordination = new SingleProcessCoordination({ scope: "coordinated-blob-clients" }); - const host = setup({ bytes, coordination }).manager; - const client = setup({ bytes, coordination: asClient(coordination) }).manager; + const coordination = new SingleProcessCoordination({ + scope: "coordinated-blob-clients", + }); + const host = setup({ + bytes, + coordination, + }).manager; + const client = setup({ + bytes, + coordination: asClient(coordination), + }).manager; await expect( - client.stage("local-id", new File(["hello"], "hello.txt", { type: "text/plain" })) + client.stage( + "local-id", + new File(["hello"], "hello.txt", { + type: "text/plain", + }) + ) ).resolves.toBeUndefined(); await expect(host.metadata("local-id")).resolves.toMatchObject({ id: "local-id", @@ -285,14 +388,31 @@ describe("createBlobManager", () => { it("wakes GC only on the coordination leader", async () => { const bytes = new MemoryBlobBytesStore(); - const coordination = new SingleProcessCoordination({ scope: "leader-only-blob-gc" }); - const host = setup({ bytes, coordination, gcTime: 0 }).manager; + const coordination = new SingleProcessCoordination({ + scope: "leader-only-blob-gc", + }); + const host = setup({ + bytes, + coordination, + gcTime: 0, + }).manager; const client = setup({ bytes, coordination: asClient(coordination), remote: { - metadata: (id) => Promise.resolve({ id, size: 6, type: "text/plain", name: "remote.txt" }), - read: () => Promise.resolve(new Blob(["remote"], { type: "text/plain" })), + metadata: (id) => + Promise.resolve({ + id, + size: 6, + type: "text/plain", + name: "remote.txt", + }), + read: () => + Promise.resolve( + new Blob(["remote"], { + type: "text/plain", + }) + ), }, gcTime: 0, }).manager; @@ -308,11 +428,19 @@ describe("createBlobManager", () => { }); it("cancels leader work and closes handlers during cleanup", async () => { - const coordination = new SingleProcessCoordination({ scope: "blob-cleanup" }); + const coordination = new SingleProcessCoordination({ + scope: "blob-cleanup", + }); const { manager } = setup({ coordination, remote: { - metadata: (id) => Promise.resolve({ id, size: 6, type: "text/plain", name: "remote.txt" }), + metadata: (id) => + Promise.resolve({ + id, + size: 6, + type: "text/plain", + name: "remote.txt", + }), read: () => Promise.resolve(new Blob(["remote"])), }, }); @@ -323,7 +451,9 @@ describe("createBlobManager", () => { coordination .service(BLOB_COORDINATION_SERVICE) .methods.find({ id: "remote-id" }) - ).rejects.toMatchObject({ code: "SERVICE_UNAVAILABLE" }); + ).rejects.toMatchObject({ + code: "SERVICE_UNAVAILABLE", + }); await coordination.close(); }); }); diff --git a/packages/blobs/src/metadata/measureBlobDimensions.ts b/packages/blobs/src/metadata/measureBlobDimensions.ts index ef72395c..47c4f1b4 100644 --- a/packages/blobs/src/metadata/measureBlobDimensions.ts +++ b/packages/blobs/src/metadata/measureBlobDimensions.ts @@ -6,5 +6,8 @@ export async function measureBlobDimensions(blob: Blob): Promise if (!dimensions) { throw new Error(`Unable to determine dimensions for blob type "${blob.type || "(empty)"}".`); } - return { width: dimensions.width, height: dimensions.height }; + return { + width: dimensions.width, + height: dimensions.height, + }; } diff --git a/packages/blobs/src/store/createBlobStore.test.ts b/packages/blobs/src/store/createBlobStore.test.ts index a0866b4c..60ad13f9 100644 --- a/packages/blobs/src/store/createBlobStore.test.ts +++ b/packages/blobs/src/store/createBlobStore.test.ts @@ -51,9 +51,16 @@ function asClient(coordination: Coordination): Coordination { function setup(blobs = new Map()) { const bytes = new TestBlobBytesStore(blobs); - const coordination = new SingleProcessCoordination({ scope: "blob-store-test" }); + const coordination = new SingleProcessCoordination({ + scope: "blob-store-test", + }); const store = createBlobStore({ - runtime: { owner: "user-1", namespace: "ontology-1", blobBytes: bytes, coordination }, + runtime: { + owner: "user-1", + namespace: "ontology-1", + blobBytes: bytes, + coordination, + }, }); return { bytes, coordination, store }; } @@ -62,10 +69,21 @@ describe("createBlobStore", () => { it("ties staged bytes to collection metadata", async () => { const { bytes, store } = setup(); - const ref = await store.stage("local-id", new File(["hello"], "hello.txt", { type: "text/plain" })); + const ref = await store.stage( + "local-id", + new File(["hello"], "hello.txt", { + type: "text/plain", + }) + ); - expect(ref).toMatchObject({ id: "local-id", name: "hello.txt" }); - expect(store.collection.get("local-id")).toMatchObject({ state: "staged", operation: undefined }); + expect(ref).toMatchObject({ + id: "local-id", + name: "hello.txt", + }); + expect(store.collection.get("local-id")).toMatchObject({ + state: "staged", + operation: undefined, + }); await expect(bytes.read("local-id").then((blob) => blob.text())).resolves.toBe("hello"); await store.cleanup(); }); @@ -75,11 +93,19 @@ describe("createBlobStore", () => { const ref = await store.cache( "remote-id", - new File(["remote"], "remote.txt", { type: "text/plain" }) + new File(["remote"], "remote.txt", { + type: "text/plain", + }) ); - expect(ref).toMatchObject({ id: "remote-id", name: "remote.txt" }); - expect(store.collection.get("remote-id")).toMatchObject({ state: "cached", operation: undefined }); + expect(ref).toMatchObject({ + id: "remote-id", + name: "remote.txt", + }); + expect(store.collection.get("remote-id")).toMatchObject({ + state: "cached", + operation: undefined, + }); await expect(store.read("remote-id").then((blob) => blob.text())).resolves.toBe("remote"); await store.cleanup(); }); @@ -99,7 +125,9 @@ describe("createBlobStore", () => { query .from({ blob: store.collection }) .where(({ blob }) => eq(blob.state, "persisted")) - .select(({ blob }) => ({ id: blob.id })) + .select(({ blob }) => ({ + id: blob.id, + })) ) ).resolves.toEqual([expect.objectContaining({ id: "local-id" })]); await expect(store.read("remote-id").then((blob) => blob.text())).resolves.toBe("hello"); @@ -112,7 +140,9 @@ describe("createBlobStore", () => { await Promise.all([store.read("local-id"), store.bindRemoteId("local-id", "remote-id")]); - await expect(store.find("local-id")).resolves.toMatchObject({ state: "persisted" }); + await expect(store.find("local-id")).resolves.toMatchObject({ + state: "persisted", + }); await store.cleanup(); }); @@ -124,7 +154,11 @@ describe("createBlobStore", () => { const ref = await store.find("local-id"); expect(ref?.state).toBeUndefined(); - expect(ref?.operation).toMatchObject({ kind: "stage", status: "failed", error: "disk full" }); + expect(ref?.operation).toMatchObject({ + kind: "stage", + status: "failed", + error: "disk full", + }); expect(ref?.operation?.operationId).toEqual(expect.any(String)); await store.cleanup(); }); @@ -138,7 +172,11 @@ describe("createBlobStore", () => { await expect(store.find("remote-id")).resolves.toMatchObject({ state: "persisted", - operation: { kind: "cache", status: "failed", error: "disk full" }, + operation: { + kind: "cache", + status: "failed", + error: "disk full", + }, }); await expect(store.read("remote-id")).rejects.toBeInstanceOf(BlobBytesUnavailableError); await expect(bytes.read("remote-id")).rejects.toThrow("not found"); @@ -147,21 +185,43 @@ describe("createBlobStore", () => { it("mints operation ids and fences stale write requests", async () => { const { bytes, store } = setup(); - const metadata = { type: "text/plain", size: 5, name: "blob.txt" }; - const first = await store.beginWrite({ id: "local-id", kind: "stage", metadata }); + const metadata = { + type: "text/plain", + size: 5, + name: "blob.txt", + }; + const first = await store.beginWrite({ + id: "local-id", + kind: "stage", + metadata, + }); await expectTaskRejection( - store.beginWrite({ id: "local-id", kind: "stage", metadata }), + store.beginWrite({ + id: "local-id", + kind: "stage", + metadata, + }), "BLOB_OPERATION_CONFLICT" ); - await store.failWrite({ ...first, error: "first write stopped" }); + await store.failWrite({ + ...first, + error: "first write stopped", + }); - const second = await store.beginWrite({ id: "local-id", kind: "stage", metadata }); + const second = await store.beginWrite({ + id: "local-id", + kind: "stage", + metadata, + }); expect(second.operationId).not.toBe(first.operationId); await bytes.write(second.id, new Blob(["fresh"], { type: "text/plain" })); await expectTaskRejection(store.commitWrite(first), "BLOB_OPERATION_STALE"); await expectTaskRejection( - store.failWrite({ ...first, error: "late failure" }), + store.failWrite({ + ...first, + error: "late failure", + }), "BLOB_OPERATION_STALE" ); await expect(bytes.read(second.id).then((blob) => blob.text())).resolves.toBe("fresh"); @@ -171,7 +231,10 @@ describe("createBlobStore", () => { size: 5, type: "text/plain", }); - expect(store.collection.get("local-id")).toMatchObject({ state: "staged", operation: undefined }); + expect(store.collection.get("local-id")).toMatchObject({ + state: "staged", + operation: undefined, + }); await store.cleanup(); }); @@ -202,18 +265,27 @@ describe("createBlobStore", () => { id: "local-id", type: "text/plain", size: 7, - operation: { kind: "stage", status: "pending", operationId: "interrupted-stage" }, + operation: { + kind: "stage", + status: "pending", + operationId: "interrupted-stage", + }, createdAt: timestamp, updatedAt: timestamp, }; - await store.collection.insert(ref, { optimistic: false }).isPersisted.promise; + await store.collection.insert(ref, { + optimistic: false, + }).isPersisted.promise; await coordination.runAsLeader(({ signal }) => store.recoverAsLeader(signal)); await expect(bytes.read("local-id")).rejects.toThrow("not found"); const recovered = await store.find("local-id"); expect(recovered?.state).toBeUndefined(); - expect(recovered?.operation).toMatchObject({ kind: "stage", status: "failed" }); + expect(recovered?.operation).toMatchObject({ + kind: "stage", + status: "failed", + }); expect(recovered?.operation?.status === "failed" ? recovered.operation.error : undefined).toContain( "interrupted before completion" ); @@ -237,7 +309,11 @@ describe("createBlobStore", () => { type: "text/plain", size: 7, state: "persisted", - operation: { kind: "cache", status: "pending", operationId: "interrupted-cache" }, + operation: { + kind: "cache", + status: "pending", + operationId: "interrupted-cache", + }, createdAt: timestamp, updatedAt: timestamp, }, @@ -250,7 +326,10 @@ describe("createBlobStore", () => { const recovered = await store.find("remote-id"); expect(recovered).toMatchObject({ state: "persisted", - operation: { kind: "cache", status: "failed" }, + operation: { + kind: "cache", + status: "failed", + }, }); expect(recovered?.operation?.status === "failed" ? recovered.operation.error : undefined).toContain( "interrupted before completion" @@ -267,7 +346,11 @@ describe("createBlobStore", () => { type: "text/plain", size: 6, state: "cached", - operation: { kind: "purge", status: "pending", operationId: "interrupted-purge" }, + operation: { + kind: "purge", + status: "pending", + operationId: "interrupted-purge", + }, createdAt: timestamp, updatedAt: timestamp, }, @@ -323,7 +406,10 @@ describe("createBlobStore", () => { await expect(bytes.read("stable").then((blob) => blob.text())).resolves.toBe("stable"); await expect(bytes.read("failed").then((blob) => blob.text())).resolves.toBe("existing"); await expect(store.find("failed")).resolves.toMatchObject({ - operation: { status: "failed", error: "previous failure" }, + operation: { + status: "failed", + error: "previous failure", + }, }); await store.cleanup(); }); @@ -337,7 +423,11 @@ describe("createBlobStore", () => { await expect(store.find("local-id")).resolves.toMatchObject({ state: "staged", - operation: { kind: "purge", status: "failed", error: "busy" }, + operation: { + kind: "purge", + status: "failed", + error: "busy", + }, }); await store.cleanup(); }); diff --git a/packages/blobs/src/store/createBlobStore.ts b/packages/blobs/src/store/createBlobStore.ts index 09753aff..cb644379 100644 --- a/packages/blobs/src/store/createBlobStore.ts +++ b/packages/blobs/src/store/createBlobStore.ts @@ -211,7 +211,9 @@ export function createBlobStore(options: CreateBlobStoreOptions): BlobStore { const deleteRef = async (id: string): Promise => { if (!collection.has(id)) return; - await collection.delete(id, { optimistic: false }).isPersisted.promise; + await collection.delete(id, { + optimistic: false, + }).isPersisted.promise; }; const applyMetadata = (draft: BlobMetadataRecord, metadata: PartialBlobMetadata): void => { @@ -240,7 +242,10 @@ export function createBlobStore(options: CreateBlobStoreOptions): BlobStore { const currentOperation = async ( input: BlobOperationInput - ): Promise<{ ref: BlobWriteRecord; operation: Extract }> => { + ): Promise<{ + ref: BlobWriteRecord; + operation: Extract; + }> => { const ref = await findRef(input.id); const operation = ref?.operation; if ( @@ -284,7 +289,11 @@ export function createBlobStore(options: CreateBlobStoreOptions): BlobStore { name: input.metadata.name ?? (typeof existing?.name === "string" ? existing.name : null), state: input.kind === "cache" ? (existing?.state ?? "persisted") : existing?.state, - operation: { kind: input.kind, status: "pending", operationId }, + operation: { + kind: input.kind, + status: "pending", + operationId, + }, lastAccessedAt: existing?.lastAccessedAt, createdAt: existing?.createdAt ?? timestamp, updatedAt: timestamp, @@ -296,7 +305,10 @@ export function createBlobStore(options: CreateBlobStoreOptions): BlobStore { activeOperationIds.delete(operationId); throw error; } - return { id: ref.id, operationId }; + return { + id: ref.id, + operationId, + }; }, async commitWrite(input) { @@ -434,7 +446,11 @@ export function createBlobStore(options: CreateBlobStoreOptions): BlobStore { activeOperationIds.add(operationId); try { await patchRef(ref.id, (draft, updatedAt) => { - draft.operation = { kind: "purge", status: "pending", operationId }; + draft.operation = { + kind: "purge", + status: "pending", + operationId, + }; draft.updatedAt = updatedAt; }); await bytes.delete(ref.id); @@ -467,7 +483,10 @@ export function createBlobStore(options: CreateBlobStoreOptions): BlobStore { query .from({ blob: collection }) .where(({ blob }) => eq(blob.operation!.status, "pending")) - .select(({ blob }) => ({ id: blob.id, operation: blob.operation })) + .select(({ blob }) => ({ + id: blob.id, + operation: blob.operation, + })) )) as Array>; for (const pending of refs) { @@ -537,14 +556,21 @@ export function createBlobStore(options: CreateBlobStoreOptions): BlobStore { const operation = await client.methods.beginWrite({ id, kind, - metadata: { type: blob.type, size: blob.size, name: blobName(blob) }, + metadata: { + type: blob.type, + size: blob.size, + name: blobName(blob), + }, }); try { await bytes.write(operation.id, blob); return await client.methods.commitWrite(operation); } catch (error) { try { - await client.methods.failWrite({ ...operation, error: errorMessage(error) }); + await client.methods.failWrite({ + ...operation, + error: errorMessage(error), + }); } catch { // A stale failure must not delete bytes belonging to a // newer operation. Preserve the original operation error. @@ -582,7 +608,9 @@ export function createBlobStore(options: CreateBlobStoreOptions): BlobStore { try { blob = await bytes.read(resolvedId); } catch (cause) { - throw new BlobBytesUnavailableError(resolvedId, { cause }); + throw new BlobBytesUnavailableError(resolvedId, { + cause, + }); } if (ref) { await client.methods.touch({ id: ref.id }); @@ -590,7 +618,11 @@ export function createBlobStore(options: CreateBlobStoreOptions): BlobStore { return blob; }, - bindRemoteId: (localId, remoteId) => client.methods.bindRemoteId({ localId, remoteId }), + bindRemoteId: (localId, remoteId) => + client.methods.bindRemoteId({ + localId, + remoteId, + }), purge: (id, callOptions) => client.methods.purge({ id }, callOptions), async recoverAsLeader(signal) { if (!isCoordinationHost(options.runtime.coordination)) { diff --git a/packages/blobs/src/types.ts b/packages/blobs/src/types.ts index 749ce4b9..e98ffbdc 100644 --- a/packages/blobs/src/types.ts +++ b/packages/blobs/src/types.ts @@ -4,8 +4,17 @@ import type { Collection } from "@tanstack/db"; export type BlobState = "staged" | "persisted" | "cached"; export type BlobOperation = - | { kind: "stage" | "cache" | "purge"; status: "pending"; operationId: string } - | { kind: "stage" | "cache" | "purge"; status: "failed"; operationId: string; error: string }; + | { + kind: "stage" | "cache" | "purge"; + status: "pending"; + operationId: string; + } + | { + kind: "stage" | "cache" | "purge"; + status: "failed"; + operationId: string; + error: string; + }; export interface PartialBlobMetadata { size?: number; diff --git a/packages/blobs/tsconfig.json b/packages/blobs/tsconfig.json index 3e4cc408..1326a774 100644 --- a/packages/blobs/tsconfig.json +++ b/packages/blobs/tsconfig.json @@ -1 +1,4 @@ -{ "$schema": "https://json.schemastore.org/tsconfig", "extends": "@party-stack/build-config/tsconfig.json" } +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@party-stack/build-config/tsconfig.json" +} diff --git a/packages/build-config/README.md b/packages/build-config/README.md index 67a7da2e..3a7537b9 100644 --- a/packages/build-config/README.md +++ b/packages/build-config/README.md @@ -9,7 +9,9 @@ export default defineLibraryConfig(); ``` ```json -{ "extends": "@party-stack/build-config/tsconfig.json" } +{ + "extends": "@party-stack/build-config/tsconfig.json" +} ``` React and Expo libraries use `@party-stack/build-config/react/tsconfig.json` for diff --git a/packages/build-config/src/index.test.ts b/packages/build-config/src/index.test.ts index 927fa6ff..560a8f85 100644 --- a/packages/build-config/src/index.test.ts +++ b/packages/build-config/src/index.test.ts @@ -54,7 +54,11 @@ describe("library config composition", () => { expect(config.run?.tasks?.check).toMatchObject({ dependsOn: [{ task: "build", from: ["dependencies", "devDependencies"] }], }); - expect(config.pack).toMatchObject({ root: "src", outDir: "lib", deps: { neverBundle: [/\.css$/] } }); + expect(config.pack).toMatchObject({ + root: "src", + outDir: "lib", + deps: { neverBundle: [/\.css$/] }, + }); expect(config.test).toMatchObject({ include: ["test/**/*.test.ts"], clearMocks: false }); }); diff --git a/packages/build-config/src/index.ts b/packages/build-config/src/index.ts index 885963c9..889fc6ab 100644 --- a/packages/build-config/src/index.ts +++ b/packages/build-config/src/index.ts @@ -7,7 +7,9 @@ type TaskOptions = Exclude; /** Override individual task options without repeating their command or prerequisites. */ export type LibraryConfig = Omit & { - run?: Omit & { tasks?: Record> }; + run?: Omit & { + tasks?: Record>; + }; }; /** diff --git a/packages/build-config/tsconfig.json b/packages/build-config/tsconfig.json index 41cd54be..c2ea6e31 100644 --- a/packages/build-config/tsconfig.json +++ b/packages/build-config/tsconfig.json @@ -1 +1,3 @@ -{ "extends": "./tsconfig/library.json" } +{ + "extends": "./tsconfig/library.json" +} diff --git a/packages/build-config/tsconfig/app.json b/packages/build-config/tsconfig/app.json index 5c31f3da..5dcf68ea 100644 --- a/packages/build-config/tsconfig/app.json +++ b/packages/build-config/tsconfig/app.json @@ -6,7 +6,9 @@ "noUncheckedIndexedAccess": false, "lib": ["DOM", "DOM.Iterable", "ESNext"], "types": ["node", "vite/client"], - "paths": { "@/*": ["${configDir}/src/*"] } + "paths": { + "@/*": ["${configDir}/src/*"] + } }, "include": ["${configDir}/src/**/*.ts", "${configDir}/src/**/*.tsx", "${configDir}/vite.config.ts"] } diff --git a/packages/build-config/tsconfig/react.json b/packages/build-config/tsconfig/react.json index 6f305c17..d77fe459 100644 --- a/packages/build-config/tsconfig/react.json +++ b/packages/build-config/tsconfig/react.json @@ -1,5 +1,9 @@ { "extends": "./library.json", - "compilerOptions": { "module": "ESNext", "moduleResolution": "bundler", "jsx": "react-jsx" }, + "compilerOptions": { + "module": "ESNext", + "moduleResolution": "bundler", + "jsx": "react-jsx" + }, "include": ["${configDir}/src/**/*.ts", "${configDir}/src/**/*.tsx"] } diff --git a/packages/connections/src/createConnectionManager.test.ts b/packages/connections/src/createConnectionManager.test.ts index 9f773140..97bb0f61 100644 --- a/packages/connections/src/createConnectionManager.test.ts +++ b/packages/connections/src/createConnectionManager.test.ts @@ -12,13 +12,23 @@ describe("createConnectionManager", () => { createWebSocket: () => Promise.reject(new Error("unexpected websocket")), }; const disconnect = vi.fn(() => Promise.resolve()); - const adapter: BackendConnectionAdapter<{ signIn(userId: string): Promise }> = { + const adapter: BackendConnectionAdapter<{ + signIn(userId: string): Promise; + }> = { name: "test", createAuthenticationClient: (controller) => ({ signIn: (userId) => controller.connect({ - connection: { userId, state: { status: "active" } }, - session: { disconnect, egress: () => handlers }, + connection: { + userId, + state: { + status: "active", + }, + }, + session: { + disconnect, + egress: () => handlers, + }, }), }), restoreConnections: () => Promise.resolve([]), @@ -33,7 +43,9 @@ describe("createConnectionManager", () => { await manager.authentication.signIn("user-1"); const connection = manager.connections.get("user-1")!; - expect(connection).toMatchObject({ userId: "user-1" }); + expect(connection).toMatchObject({ + userId: "user-1", + }); expect(connection.state.status).toBe("active"); expect(manager.connections.get("user-1")).toEqual(connection); @@ -48,14 +60,21 @@ describe("createConnectionManager", () => { await Promise.resolve(); expect(disconnect).toHaveBeenCalledOnce(); expect(manager.connections.get("user-1")?.state.status).toBe("inactive"); - expect(listener).toHaveBeenLastCalledWith({ status: "inactive" }); + expect(listener).toHaveBeenLastCalledWith({ + status: "inactive", + }); unsubscribe(); await monitor.reportUnauthenticated(unauthenticated("Session expired.")); - expect(monitor.state).toEqual({ status: "needs-auth", error: "Session expired." }); + expect(monitor.state).toEqual({ + status: "needs-auth", + error: "Session expired.", + }); await manager.forget("user-1"); expect(manager.connections.get("user-1")).toBeUndefined(); - expect(monitor.state).toEqual({ status: "inactive" }); + expect(monitor.state).toEqual({ + status: "inactive", + }); await manager.cleanup(); await runtime.cleanup?.(); @@ -108,7 +127,9 @@ describe("createConnectionManager", () => { }); it("marks expired non-refreshable connections as needing authentication", async () => { - const adapter: BackendConnectionAdapter<{ signIn(): Promise }> = { + const adapter: BackendConnectionAdapter<{ + signIn(): Promise; + }> = { name: "expiring", createAuthenticationClient: (controller) => ({ signIn: () => @@ -117,10 +138,15 @@ describe("createConnectionManager", () => { userId: "user-1", state: { status: "active", - expiration: { expiresAt: Date.now() - 1, refreshable: false }, + expiration: { + expiresAt: Date.now() - 1, + refreshable: false, + }, }, }, - session: { disconnect: () => Promise.resolve() }, + session: { + disconnect: () => Promise.resolve(), + }, }), }), restoreConnections: () => Promise.resolve([]), @@ -148,10 +174,15 @@ describe("createConnectionManager", () => { userId: "user-1", state: { status: "active" as const, - expiration: { expiresAt: Date.now() + 60 * 60 * 1_000, refreshable: true }, + expiration: { + expiresAt: Date.now() + 60 * 60 * 1_000, + refreshable: true, + }, }, }, - session: { disconnect: () => Promise.resolve() }, + session: { + disconnect: () => Promise.resolve(), + }, }) ); const adapter: BackendConnectionAdapter = { @@ -164,10 +195,16 @@ describe("createConnectionManager", () => { userId: "user-1", state: { status: "active", - expiration: { expiresAt: Date.now() - 1, refreshable: true }, + expiration: { + expiresAt: Date.now() - 1, + refreshable: true, + }, }, }, - session: { refresh, disconnect: () => Promise.resolve() }, + session: { + refresh, + disconnect: () => Promise.resolve(), + }, }, ]), }; @@ -183,7 +220,9 @@ describe("createConnectionManager", () => { }); expect(manager.connections.get("user-1")?.state).toMatchObject({ status: "active", - expiration: { refreshable: true }, + expiration: { + refreshable: true, + }, }); await manager.cleanup(); await runtime.cleanup?.(); diff --git a/packages/connections/src/createConnectionManager.ts b/packages/connections/src/createConnectionManager.ts index 01acb674..342e0794 100644 --- a/packages/connections/src/createConnectionManager.ts +++ b/packages/connections/src/createConnectionManager.ts @@ -33,7 +33,10 @@ export interface CreateConnectionManagerOptions( options: CreateConnectionManagerOptions ): Promise> { - const context = { installationId: options.installationId, runtime: options.runtime }; + const context = { + installationId: options.installationId, + runtime: options.runtime, + }; const adapter: BackendConnectionAdapter = await options.adapter(context); const baseEgressHandlers = options.egressHandlers ?? createDefaultConnectionEgressHandlers(); const connections = createLocalCollection({ @@ -65,13 +68,19 @@ export async function createConnectionManager { await releaseSession(userId); await updateConnection(userId, (connection) => { - connection.state = { status: "needs-auth", error: error.message }; + connection.state = { + status: "needs-auth", + error: error.message, + }; }); }; const applyConnection = async ( connection: Connection ): Promise> => { - const record: Connection = { ...connection, state: connection.state }; + const record: Connection = { + ...connection, + state: connection.state, + }; const current = connections.get(connection.userId); const transaction = current ? connections.update(connection.userId, { optimistic: false }, (draft) => { @@ -203,7 +212,9 @@ export async function createConnectionManager { listener(getState()); }, - { where: (connection) => eq(connection.userId, userId) } + { + where: (connection) => eq(connection.userId, userId), + } ); return () => subscription.unsubscribe(); }, diff --git a/packages/connections/src/types.ts b/packages/connections/src/types.ts index 0a40ff6f..cee2b58b 100644 --- a/packages/connections/src/types.ts +++ b/packages/connections/src/types.ts @@ -4,7 +4,13 @@ import type { Collection } from "@tanstack/db"; export type ConnectionState = | { status: "pending" } - | { status: "active"; expiration?: { expiresAt: number; refreshable: boolean } } + | { + status: "active"; + expiration?: { + expiresAt: number; + refreshable: boolean; + }; + } | { status: "inactive" } | { status: "needs-auth"; error?: string } | { status: "error"; error: string }; diff --git a/packages/connections/src/withHttpRetryHandling.test.ts b/packages/connections/src/withHttpRetryHandling.test.ts index 388551c5..b7115df3 100644 --- a/packages/connections/src/withHttpRetryHandling.test.ts +++ b/packages/connections/src/withHttpRetryHandling.test.ts @@ -9,11 +9,27 @@ describe("withHttpRetryHandling", () => { vi.useFakeTimers(); const fetch = vi .fn() - .mockResolvedValueOnce(new Response(null, { status: 429, headers: { "retry-after": "1" } })) - .mockResolvedValueOnce(new Response(null, { status: 204 })); + .mockResolvedValueOnce( + new Response(null, { + status: 429, + headers: { + "retry-after": "1", + }, + }) + ) + .mockResolvedValueOnce( + new Response(null, { + status: 204, + }) + ); const handlers = withHttpRetryHandling( - { fetch, createWebSocket: () => Promise.reject(new Error("Unexpected WebSocket.")) }, - { maxDelayMs: 100 } + { + fetch, + createWebSocket: () => Promise.reject(new Error("Unexpected WebSocket.")), + }, + { + maxDelayMs: 100, + } ); const responsePromise = handlers.fetch(new Request("https://example.com/data")); @@ -29,11 +45,24 @@ describe("withHttpRetryHandling", () => { it("retries rate-limited requests regardless of method", async () => { const fetch = vi .fn() - .mockResolvedValueOnce(new Response(null, { status: 429 })) - .mockResolvedValueOnce(new Response(null, { status: 204 })); + .mockResolvedValueOnce( + new Response(null, { + status: 429, + }) + ) + .mockResolvedValueOnce( + new Response(null, { + status: 204, + }) + ); const handlers = withHttpRetryHandling( - { fetch, createWebSocket: () => Promise.reject(new Error("Unexpected WebSocket.")) }, - { baseDelayMs: 0 } + { + fetch, + createWebSocket: () => Promise.reject(new Error("Unexpected WebSocket.")), + }, + { + baseDelayMs: 0, + } ); const response = await handlers.fetch(new Request("https://example.com/action", { method: "POST" })); @@ -43,7 +72,13 @@ describe("withHttpRetryHandling", () => { }); it("does not retry non-429 responses", async () => { - const fetch = vi.fn(() => Promise.resolve(new Response(null, { status: 500 }))); + const fetch = vi.fn(() => + Promise.resolve( + new Response(null, { + status: 500, + }) + ) + ); const handlers = withHttpRetryHandling({ fetch, createWebSocket: () => Promise.reject(new Error("Unexpected WebSocket.")), @@ -58,11 +93,24 @@ describe("withHttpRetryHandling", () => { it("retries service-unavailable responses", async () => { const fetch = vi .fn() - .mockResolvedValueOnce(new Response(null, { status: 503 })) - .mockResolvedValueOnce(new Response(null, { status: 204 })); + .mockResolvedValueOnce( + new Response(null, { + status: 503, + }) + ) + .mockResolvedValueOnce( + new Response(null, { + status: 204, + }) + ); const handlers = withHttpRetryHandling( - { fetch, createWebSocket: () => Promise.reject(new Error("Unexpected WebSocket.")) }, - { baseDelayMs: 0 } + { + fetch, + createWebSocket: () => Promise.reject(new Error("Unexpected WebSocket.")), + }, + { + baseDelayMs: 0, + } ); const response = await handlers.fetch(new Request("https://example.com/data")); diff --git a/packages/connections/src/withHttpRetryHandling.ts b/packages/connections/src/withHttpRetryHandling.ts index 6af6d8e5..458d7c30 100644 --- a/packages/connections/src/withHttpRetryHandling.ts +++ b/packages/connections/src/withHttpRetryHandling.ts @@ -15,7 +15,9 @@ function sleep(milliseconds: number, signal: AbortSignal): Promise { reject( signal.reason instanceof Error ? signal.reason - : new Error("Sleep was aborted.", { cause: signal.reason }) + : new Error("Sleep was aborted.", { + cause: signal.reason, + }) ); }; const timeout = setTimeout(() => { @@ -26,7 +28,9 @@ function sleep(milliseconds: number, signal: AbortSignal): Promise { abort(); return; } - signal.addEventListener("abort", abort, { once: true }); + signal.addEventListener("abort", abort, { + once: true, + }); }); } diff --git a/packages/connections/tsconfig.json b/packages/connections/tsconfig.json index 3e4cc408..1326a774 100644 --- a/packages/connections/tsconfig.json +++ b/packages/connections/tsconfig.json @@ -1 +1,4 @@ -{ "$schema": "https://json.schemastore.org/tsconfig", "extends": "@party-stack/build-config/tsconfig.json" } +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@party-stack/build-config/tsconfig.json" +} diff --git a/packages/coordination/src/LockBroadcastCoordination.test.ts b/packages/coordination/src/LockBroadcastCoordination.test.ts index d335ff0b..6355fe70 100644 --- a/packages/coordination/src/LockBroadcastCoordination.test.ts +++ b/packages/coordination/src/LockBroadcastCoordination.test.ts @@ -7,7 +7,9 @@ type TestService = { double(input: { value: number }): Promise; wait(input: { value: number }): Promise; }; - events: { changed: { value: number } }; + events: { + changed: { value: number }; + }; }; class LocalBroadcastChannel extends EventTarget implements BroadcastChannel { @@ -31,7 +33,9 @@ class LocalBroadcastChannel extends EventTarget implements BroadcastChannel { if (channel === this || channel.closed) { continue; } - const event = new MessageEvent("message", { data: structuredClone(message) }); + const event = new MessageEvent("message", { + data: structuredClone(message), + }); queueMicrotask(() => { channel.dispatchEvent(event); channel.onmessage?.call(channel, event); @@ -71,7 +75,10 @@ class LocalLockManager implements LockManager { if (options.signal?.aborted) { throw options.signal.reason ?? new DOMException("The operation was aborted.", "AbortError"); } - return grant({ mode: options.mode ?? "exclusive", name }); + return grant({ + mode: options.mode ?? "exclusive", + name, + }); }); this.tails.set( name, @@ -81,7 +88,10 @@ class LocalLockManager implements LockManager { } query(): Promise { - return Promise.resolve({ held: [], pending: [] }); + return Promise.resolve({ + held: [], + pending: [], + }); } } @@ -97,7 +107,9 @@ beforeEach(() => { }); Object.defineProperty(globalThis, "navigator", { configurable: true, - value: { locks: new LocalLockManager() }, + value: { + locks: new LocalLockManager(), + }, }); restoreWebApis = () => { LocalBroadcastChannel.channels.clear(); @@ -257,7 +269,11 @@ describe("LockBroadcastCoordination", () => { await expect(follower.service("test.v1").methods.double({ value: 4 })).resolves.toBe(8); server.events.publish("changed", { value: 9 }); - await vi.waitFor(() => expect(listener).toHaveBeenCalledWith({ value: 9 })); + await vi.waitFor(() => + expect(listener).toHaveBeenCalledWith({ + value: 9, + }) + ); await leader.close(); await follower.close(); @@ -490,7 +506,9 @@ describe("LockBroadcastCoordination", () => { cleaned.resolve(); }); // oxlint-disable-next-line vitest/valid-expect -- Register before shutdown; awaited below after shutdown triggers rejection. - const operationResult = expect(operation).rejects.toMatchObject({ code: "CLOSED" }); + const operationResult = expect(operation).rejects.toMatchObject({ + code: "CLOSED", + }); await started.promise; const closing = leader.close(); @@ -572,7 +590,9 @@ describe("LockBroadcastCoordination", () => { controller.abort(); - await expect(request).rejects.toMatchObject({ name: "AbortError" }); + await expect(request).rejects.toMatchObject({ + name: "AbortError", + }); await expect(aborted.promise).resolves.toBeUndefined(); await first.close(); await second.close(); diff --git a/packages/coordination/src/LockBroadcastCoordination.ts b/packages/coordination/src/LockBroadcastCoordination.ts index 1b49059a..e2f64777 100644 --- a/packages/coordination/src/LockBroadcastCoordination.ts +++ b/packages/coordination/src/LockBroadcastCoordination.ts @@ -230,7 +230,11 @@ export class LockBroadcastCoordination extends BaseCoordination implements Coord yield* useWebLock(`party-stack.coordination:${this.scope}:leader`); const scope = yield* useScope(); const signal = yield* useAbortSignal(); - const term: LeadershipTerm = { id: randomId(), scope, signal }; + const term: LeadershipTerm = { + id: randomId(), + scope, + signal, + }; this.term = term; for (const waiter of this.leadershipWaiters) { waiter.resolve(term); @@ -418,11 +422,16 @@ export class LockBroadcastCoordination extends BaseCoordination implements Coord const timeout = this.requestTimeoutMs; const result = yield* race([ (function* () { - return { type: "response" as const, value: yield* until(response) }; + return { + type: "response" as const, + value: yield* until(response), + }; })(), (function* () { yield* sleep(timeout); - return { type: "timeout" as const }; + return { + type: "timeout" as const, + }; })(), ]); if (result.type === "timeout") { @@ -549,12 +558,23 @@ export class LockBroadcastCoordination extends BaseCoordination implements Coord path: [], }) .then( - (result) => this.completeIncoming(key, request, term, { ok: true, result }), + (result) => + this.completeIncoming(key, request, term, { + ok: true, + result, + }), (error: unknown) => - this.completeIncoming(key, request, term, { ok: false, error: serializeError(error) }) + this.completeIncoming(key, request, term, { + ok: false, + error: serializeError(error), + }) ) .finally(removeTermAbort); - this.incoming.set(key, { controller, term, completion }); + this.incoming.set(key, { + controller, + term, + completion, + }); void completion; } @@ -564,7 +584,10 @@ export class LockBroadcastCoordination extends BaseCoordination implements Coord term: LeadershipTerm, outcome: | { readonly ok: true; readonly result: unknown } - | { readonly ok: false; readonly error: ReturnType } + | { + readonly ok: false; + readonly error: ReturnType; + } ): void { this.incoming.delete(key); if (this.closed || this.term !== term || term.signal.aborted) { @@ -673,7 +696,9 @@ export class LockBroadcastCoordination extends BaseCoordination implements Coord if (signal.aborted) { abort(); } else { - signal.addEventListener("abort", abort, { once: true }); + signal.addEventListener("abort", abort, { + once: true, + }); void waiter.promise.then( () => signal.removeEventListener("abort", abort), () => signal.removeEventListener("abort", abort) diff --git a/packages/coordination/src/SingleProcessCoordination.test.ts b/packages/coordination/src/SingleProcessCoordination.test.ts index 637ca915..71732815 100644 --- a/packages/coordination/src/SingleProcessCoordination.test.ts +++ b/packages/coordination/src/SingleProcessCoordination.test.ts @@ -12,7 +12,9 @@ type TestService = { pause(input: { value: string }): Promise; nested(input: { value: number }): Promise; }; - events: { changed: { value: string } }; + events: { + changed: { value: string }; + }; }; function handlers( @@ -38,7 +40,9 @@ function promiseWithResolvers() { describe("SingleProcessCoordination", () => { it("has the host role and returns stable typed clients", async () => { - const coordination = new SingleProcessCoordination({ scope: "roles" }); + const coordination = new SingleProcessCoordination({ + scope: "roles", + }); const first = coordination.service("test.v1"); const second = coordination.service("test.v1"); @@ -53,7 +57,9 @@ describe("SingleProcessCoordination", () => { }); it("fans out typed events and isolates subscribers", async () => { - const coordination = new SingleProcessCoordination({ scope: "events" }); + const coordination = new SingleProcessCoordination({ + scope: "events", + }); const client = coordination.service("test.v1"); const first = vi.fn(() => { throw new Error("listener failure"); @@ -73,7 +79,9 @@ describe("SingleProcessCoordination", () => { }); it("runs one FIFO lane per service", async () => { - const coordination = new SingleProcessCoordination({ scope: "lanes" }); + const coordination = new SingleProcessCoordination({ + scope: "lanes", + }); const gate = promiseWithResolvers(); const order: string[] = []; let active = 0; @@ -112,7 +120,9 @@ describe("SingleProcessCoordination", () => { }); it("rejects cyclic cross-service calls", async () => { - const cyclic = new SingleProcessCoordination({ scope: "cyclic" }); + const cyclic = new SingleProcessCoordination({ + scope: "cyclic", + }); cyclic.serve("a.v1", { ...handlers(), nested: (_input, context) => @@ -130,7 +140,9 @@ describe("SingleProcessCoordination", () => { }); it("executes a finite same-service nested call reentrantly", async () => { - const coordination = new SingleProcessCoordination({ scope: "reentrant" }); + const coordination = new SingleProcessCoordination({ + scope: "reentrant", + }); coordination.serve("test.v1", { ...handlers(), nested: ({ value }, context) => @@ -146,21 +158,29 @@ describe("SingleProcessCoordination", () => { }); it("rejects duplicate, unavailable, and closed services", async () => { - const coordination = new SingleProcessCoordination({ scope: "registry" }); + const coordination = new SingleProcessCoordination({ + scope: "registry", + }); const server = coordination.serve("test.v1", handlers()); expect(() => coordination.serve("test.v1", handlers())).toThrowError(CoordinationServiceError); await expect( coordination.service("missing.v1").methods.echo({ value: "no" }) - ).rejects.toMatchObject({ code: "SERVICE_UNAVAILABLE" }); + ).rejects.toMatchObject({ + code: "SERVICE_UNAVAILABLE", + }); await server.close(); await expect( coordination.service("test.v1").methods.echo({ value: "no" }) - ).rejects.toMatchObject({ code: "SERVICE_UNAVAILABLE" }); + ).rejects.toMatchObject({ + code: "SERVICE_UNAVAILABLE", + }); await coordination.close(); }); it("cancels active work and waits for cleanup", async () => { - const coordination = new SingleProcessCoordination({ scope: "cancel" }); + const coordination = new SingleProcessCoordination({ + scope: "cancel", + }); const started = promiseWithResolvers(); const cleaned = promiseWithResolvers(); coordination.serve("test.v1", { @@ -180,13 +200,17 @@ describe("SingleProcessCoordination", () => { .methods.pause({ value: "wait" }, { signal: controller.signal }); await started.promise; controller.abort(); - await expect(call).rejects.toMatchObject({ name: "AbortError" }); + await expect(call).rejects.toMatchObject({ + name: "AbortError", + }); await expect(cleaned.promise).resolves.toBeUndefined(); await coordination.close(); }); it("closes active and queued service work deterministically", async () => { - const coordination = new SingleProcessCoordination({ scope: "service-close" }); + const coordination = new SingleProcessCoordination({ + scope: "service-close", + }); const started = promiseWithResolvers(); const cleaned = promiseWithResolvers(); const server = coordination.serve("test.v1", { @@ -201,12 +225,20 @@ describe("SingleProcessCoordination", () => { }, }); const client = coordination.service("test.v1"); - const active = client.methods.pause({ value: "active" }); - const queued = client.methods.pause({ value: "queued" }); + const active = client.methods.pause({ + value: "active", + }); + const queued = client.methods.pause({ + value: "queued", + }); // oxlint-disable-next-line vitest/valid-expect -- Register before shutdown; awaited below after shutdown triggers rejection. - const activeResult = expect(active).rejects.toMatchObject({ code: "SERVICE_CLOSED" }); + const activeResult = expect(active).rejects.toMatchObject({ + code: "SERVICE_CLOSED", + }); // oxlint-disable-next-line vitest/valid-expect -- Register before shutdown; awaited below after shutdown triggers rejection. - const queuedResult = expect(queued).rejects.toMatchObject({ code: "SERVICE_CLOSED" }); + const queuedResult = expect(queued).rejects.toMatchObject({ + code: "SERVICE_CLOSED", + }); await started.promise; await server.close(); @@ -218,26 +250,34 @@ describe("SingleProcessCoordination", () => { }); it("does not start already-cancelled leader work", async () => { - const coordination = new SingleProcessCoordination({ scope: "cancelled-leader" }); + const coordination = new SingleProcessCoordination({ + scope: "cancelled-leader", + }); const controller = new AbortController(); const callback = vi.fn(); controller.abort(); - await expect(coordination.runAsLeader(callback, { signal: controller.signal })).rejects.toMatchObject( - { name: "AbortError" } - ); + await expect( + coordination.runAsLeader(callback, { + signal: controller.signal, + }) + ).rejects.toMatchObject({ name: "AbortError" }); expect(callback).not.toHaveBeenCalled(); await coordination.close(); }); it("aborts leader work and waits for its cleanup on close", async () => { - const coordination = new SingleProcessCoordination({ scope: "leader" }); + const coordination = new SingleProcessCoordination({ + scope: "leader", + }); const started = promiseWithResolvers(); const cleaned = promiseWithResolvers(); const leader = coordination.runAsLeader(async ({ signal }) => { started.resolve(); await new Promise((resolve) => - signal.addEventListener("abort", () => resolve(), { once: true }) + signal.addEventListener("abort", () => resolve(), { + once: true, + }) ); await Promise.resolve(); cleaned.resolve(); @@ -248,7 +288,9 @@ describe("SingleProcessCoordination", () => { await coordination.close(); await expect(cleaned.promise).resolves.toBeUndefined(); - await expect(leader).rejects.toMatchObject({ code: "CLOSED" }); + await expect(leader).rejects.toMatchObject({ + code: "CLOSED", + }); await expect( coordination.service("test.v1").methods.echo({ value: "closed" }) ).rejects.toMatchObject({ code: "CLOSED" }); diff --git a/packages/coordination/src/effection/runInScope.ts b/packages/coordination/src/effection/runInScope.ts index e8ad2d8e..d57a49be 100644 --- a/packages/coordination/src/effection/runInScope.ts +++ b/packages/coordination/src/effection/runInScope.ts @@ -13,9 +13,15 @@ export function runInScope( .map(waitForAbort); try { const value = aborts.length === 0 ? yield* operation() : yield* race([operation(), ...aborts]); - return { ok: true as const, value }; + return { + ok: true as const, + value, + }; } catch (error) { - return { ok: false as const, error: normalizeError(error) }; + return { + ok: false as const, + error: normalizeError(error), + }; } }); return task.then((result) => { diff --git a/packages/coordination/src/effection/useBroadcastChannel.test.ts b/packages/coordination/src/effection/useBroadcastChannel.test.ts index a162c5dc..2cc81c69 100644 --- a/packages/coordination/src/effection/useBroadcastChannel.test.ts +++ b/packages/coordination/src/effection/useBroadcastChannel.test.ts @@ -23,7 +23,9 @@ class TestBroadcastChannel extends EventTarget implements BroadcastChannel { if (channel === this || channel.closed) { continue; } - const event = new MessageEvent("message", { data: structuredClone(message) }); + const event = new MessageEvent("message", { + data: structuredClone(message), + }); queueMicrotask(() => { channel.dispatchEvent(event); channel.onmessage?.call(channel, event); @@ -65,8 +67,12 @@ describe("useBroadcastChannel", () => { it("provides a scoped native event stream", async () => { const received = vi.fn(); await run(function* () { - const sender = yield* useBroadcastChannel<{ value: number }>("resource-channel"); - const receiver = yield* useBroadcastChannel<{ value: number }>("resource-channel"); + const sender = yield* useBroadcastChannel<{ + value: number; + }>("resource-channel"); + const receiver = yield* useBroadcastChannel<{ + value: number; + }>("resource-channel"); void (yield* spawn(function* () { for (const event of yield* each(receiver)) { received(event.data); @@ -76,7 +82,9 @@ describe("useBroadcastChannel", () => { sender.postMessage({ value: 42 }); yield* until( vi.waitFor(() => { - expect(received).toHaveBeenCalledWith({ value: 42 }); + expect(received).toHaveBeenCalledWith({ + value: 42, + }); }) ); }); diff --git a/packages/coordination/src/effection/useMessagePort.ts b/packages/coordination/src/effection/useMessagePort.ts index 27bff1ad..0fb4140f 100644 --- a/packages/coordination/src/effection/useMessagePort.ts +++ b/packages/coordination/src/effection/useMessagePort.ts @@ -28,7 +28,10 @@ export function useMessagePort( port.addEventListener("messageerror", close); port.addEventListener("close", close); port.start?.(); - const resourceHandle: MessagePortResource = { port, [Symbol.iterator]: messages[Symbol.iterator] }; + const resourceHandle: MessagePortResource = { + port, + [Symbol.iterator]: messages[Symbol.iterator], + }; try { yield* race([closed.operation, provide(resourceHandle)]); } finally { diff --git a/packages/coordination/src/effection/useWebLock.test.ts b/packages/coordination/src/effection/useWebLock.test.ts index 967e9318..f233994d 100644 --- a/packages/coordination/src/effection/useWebLock.test.ts +++ b/packages/coordination/src/effection/useWebLock.test.ts @@ -22,7 +22,10 @@ class TestLockManager implements LockManager { if (options.signal?.aborted) { throw options.signal.reason ?? new DOMException("The operation was aborted.", "AbortError"); } - return grant({ mode: options.mode ?? "exclusive", name }); + return grant({ + mode: options.mode ?? "exclusive", + name, + }); }); this.tails.set( name, @@ -32,7 +35,10 @@ class TestLockManager implements LockManager { } query(): Promise { - return Promise.resolve({ held: [], pending: [] }); + return Promise.resolve({ + held: [], + pending: [], + }); } } @@ -42,7 +48,9 @@ beforeEach(() => { navigatorDescriptor = Object.getOwnPropertyDescriptor(globalThis, "navigator"); Object.defineProperty(globalThis, "navigator", { configurable: true, - value: { locks: new TestLockManager() }, + value: { + locks: new TestLockManager(), + }, }); }); diff --git a/packages/coordination/src/effection/useWebLock.ts b/packages/coordination/src/effection/useWebLock.ts index a40b5fd9..5e75041d 100644 --- a/packages/coordination/src/effection/useWebLock.ts +++ b/packages/coordination/src/effection/useWebLock.ts @@ -14,10 +14,17 @@ export function useWebLock(name: string, options: UseWebLockOptions = {}): Opera const hold = new Promise((resolve) => { release = resolve; }); - const request = navigator.locks.request(name, { ...options, signal }, async (lock) => { - acquired.resolve(lock); - await hold; - }); + const request = navigator.locks.request( + name, + { + ...options, + signal, + }, + async (lock) => { + acquired.resolve(lock); + await hold; + } + ); const task = yield* spawn(function* () { try { yield* until(request); diff --git a/packages/coordination/src/effection/waitForAbort.ts b/packages/coordination/src/effection/waitForAbort.ts index d14a1fbd..d78c3a2e 100644 --- a/packages/coordination/src/effection/waitForAbort.ts +++ b/packages/coordination/src/effection/waitForAbort.ts @@ -8,7 +8,9 @@ export function waitForAbort(signal: AbortSignal): Operation { abort(); return () => undefined; } - signal.addEventListener("abort", abort, { once: true }); + signal.addEventListener("abort", abort, { + once: true, + }); return () => signal.removeEventListener("abort", abort); }, "wait for abort"); } diff --git a/packages/coordination/src/host-core.ts b/packages/coordination/src/host-core.ts index 24647c0b..2ae222e4 100644 --- a/packages/coordination/src/host-core.ts +++ b/packages/coordination/src/host-core.ts @@ -183,7 +183,9 @@ export class HostCore { abort(); break; } - signal.addEventListener("abort", abort, { once: true }); + signal.addEventListener("abort", abort, { + once: true, + }); } invocation.removeQueuedAbort = () => { for (const signal of signals) { diff --git a/packages/coordination/src/internal.ts b/packages/coordination/src/internal.ts index 623ffc11..9ced9c89 100644 --- a/packages/coordination/src/internal.ts +++ b/packages/coordination/src/internal.ts @@ -109,7 +109,11 @@ export function serializeError(reason: unknown): SerializedError { error instanceof CoordinationError || error instanceof CoordinationTaskRejectedError ? error.code : undefined; - return { name: error.name, message: error.message, code }; + return { + name: error.name, + message: error.message, + code, + }; } const coordinationCodes = new Set([ diff --git a/packages/coordination/src/shared-worker/SharedWorkerCoordination.test.ts b/packages/coordination/src/shared-worker/SharedWorkerCoordination.test.ts index dd612da8..24719873 100644 --- a/packages/coordination/src/shared-worker/SharedWorkerCoordination.test.ts +++ b/packages/coordination/src/shared-worker/SharedWorkerCoordination.test.ts @@ -10,7 +10,9 @@ type TestService = { fail(input: undefined): Promise; wait(input: undefined): Promise; }; - events: { changed: { value: string } }; + events: { + changed: { value: string }; + }; }; class TestMessagePort implements CoordinationMessagePort { @@ -63,7 +65,10 @@ class TestMessagePort implements CoordinationMessagePort { } } -function messageChannel(): { port1: TestMessagePort; port2: TestMessagePort } { +function messageChannel(): { + port1: TestMessagePort; + port2: TestMessagePort; +} { const port1 = new TestMessagePort(); const port2 = new TestMessagePort(); port1.peer = port2; @@ -136,7 +141,9 @@ describe("SharedWorker coordination", () => { }); it("handshakes, invokes typed services, and fans out events", async () => { - const host = new SharedWorkerCoordinationHost({ scope: "shared" }); + const host = new SharedWorkerCoordinationHost({ + scope: "shared", + }); const server = serve(host); const channel = messageChannel(); host.connect(channel.port1); @@ -152,7 +159,11 @@ describe("SharedWorker coordination", () => { "hello" ); server.events.publish("changed", { value: "one" }); - await vi.waitFor(() => expect(listener).toHaveBeenCalledWith({ value: "one" })); + await vi.waitFor(() => + expect(listener).toHaveBeenCalledWith({ + value: "one", + }) + ); await expect(client.service("test.v1").methods.fail(undefined)).rejects.toMatchObject({ name: "RangeError", message: "handler failed", @@ -163,7 +174,9 @@ describe("SharedWorker coordination", () => { }); it("rejects pending calls on disconnect and reconnects with a new identity", async () => { - const host = new SharedWorkerCoordinationHost({ scope: "reconnect" }); + const host = new SharedWorkerCoordinationHost({ + scope: "reconnect", + }); serve(host); const firstChannel = messageChannel(); const disconnect = host.connect(firstChannel.port1); @@ -179,7 +192,9 @@ describe("SharedWorker coordination", () => { disconnect(); - await expect(pending).rejects.toMatchObject({ code: "DISCONNECTED" }); + await expect(pending).rejects.toMatchObject({ + code: "DISCONNECTED", + }); const secondChannel = messageChannel(); host.connect(secondChannel.port1); const second = new SharedWorkerCoordinationClient({ @@ -197,7 +212,9 @@ describe("SharedWorker coordination", () => { }); it("rejects protocol and scope mismatches during handshake", async () => { - const host = new SharedWorkerCoordinationHost({ scope: "host-scope" }); + const host = new SharedWorkerCoordinationHost({ + scope: "host-scope", + }); const channel = messageChannel(); host.connect(channel.port1); const client = new SharedWorkerCoordinationClient({ @@ -208,7 +225,9 @@ describe("SharedWorker coordination", () => { await expect( client.service("test.v1").methods.echo({ value: "no" }) - ).rejects.toMatchObject({ code: "PROTOCOL_MISMATCH" }); + ).rejects.toMatchObject({ + code: "PROTOCOL_MISMATCH", + }); await client.close(); await host.close(); }); @@ -229,7 +248,9 @@ describe("SharedWorker coordination", () => { }); it("aborts worker handlers when the client cancels", async () => { - const host = new SharedWorkerCoordinationHost({ scope: "cancel" }); + const host = new SharedWorkerCoordinationHost({ + scope: "cancel", + }); const started = promiseWithResolvers(); const aborted = promiseWithResolvers(); host.serve("test.v1", { @@ -251,14 +272,16 @@ describe("SharedWorker coordination", () => { requestTimeoutMs: 100, }); const controller = new AbortController(); - const request = client - .service("test.v1") - .methods.wait(undefined, { signal: controller.signal }); + const request = client.service("test.v1").methods.wait(undefined, { + signal: controller.signal, + }); await started.promise; controller.abort(); - await expect(request).rejects.toMatchObject({ name: "AbortError" }); + await expect(request).rejects.toMatchObject({ + name: "AbortError", + }); await expect(aborted.promise).resolves.toBeUndefined(); await client.close(); await host.close(); diff --git a/packages/coordination/src/shared-worker/SharedWorkerCoordinationClient.ts b/packages/coordination/src/shared-worker/SharedWorkerCoordinationClient.ts index 23755e47..87018492 100644 --- a/packages/coordination/src/shared-worker/SharedWorkerCoordinationClient.ts +++ b/packages/coordination/src/shared-worker/SharedWorkerCoordinationClient.ts @@ -161,11 +161,16 @@ export class SharedWorkerCoordinationClient extends BaseCoordination { const timeout = this.requestTimeoutMs; const result = yield* race([ (function* () { - return { type: "response" as const, value: yield* until(response) }; + return { + type: "response" as const, + value: yield* until(response), + }; })(), (function* () { yield* sleep(timeout); - return { type: "timeout" as const }; + return { + type: "timeout" as const, + }; })(), ]); if (result.type === "timeout") { diff --git a/packages/coordination/src/shared-worker/SharedWorkerCoordinationHost.ts b/packages/coordination/src/shared-worker/SharedWorkerCoordinationHost.ts index 9c962197..d4f51df4 100644 --- a/packages/coordination/src/shared-worker/SharedWorkerCoordinationHost.ts +++ b/packages/coordination/src/shared-worker/SharedWorkerCoordinationHost.ts @@ -319,10 +319,16 @@ export class SharedWorkerCoordinationHost extends BaseCoordination implements Co path: [], }) ); - this.respond(connection, request, { ok: true, result }); + this.respond(connection, request, { + ok: true, + result, + }); } catch (error) { if (signal.aborted) return; - this.respond(connection, request, { ok: false, error: serializeError(error) }); + this.respond(connection, request, { + ok: false, + error: serializeError(error), + }); } } @@ -331,7 +337,10 @@ export class SharedWorkerCoordinationHost extends BaseCoordination implements Co request: WorkerRequest, outcome: | { readonly ok: true; readonly result: unknown } - | { readonly ok: false; readonly error: ReturnType } + | { + readonly ok: false; + readonly error: ReturnType; + } ): void { if (connection.disconnected || this.closed || connection.connectionId !== request.connectionId) { return; diff --git a/packages/coordination/src/types.test.ts b/packages/coordination/src/types.test.ts index 570d45e4..230ef4b9 100644 --- a/packages/coordination/src/types.test.ts +++ b/packages/coordination/src/types.test.ts @@ -11,14 +11,28 @@ type TypedService = { add(input: { left: number; right: number }): Promise; label(input: { id: string }): Promise; }; - events: { changed: { id: string; revision: number }; reset: undefined }; + events: { + changed: { id: string; revision: number }; + reset: undefined; + }; }; function acceptsPlatformWorkerTypes(worker: SharedWorker, port: MessagePort): void { - const client = new SharedWorkerCoordinationClient({ scope: "typed-worker", worker }); - const fromPort = new SharedWorkerCoordinationClient({ scope: "typed-worker", worker: port }); - const fromFactory = new SharedWorkerCoordinationClient({ scope: "typed-worker", worker: () => worker }); - const host = new SharedWorkerCoordinationHost({ scope: "typed-worker" }); + const client = new SharedWorkerCoordinationClient({ + scope: "typed-worker", + worker, + }); + const fromPort = new SharedWorkerCoordinationClient({ + scope: "typed-worker", + worker: port, + }); + const fromFactory = new SharedWorkerCoordinationClient({ + scope: "typed-worker", + worker: () => worker, + }); + const host = new SharedWorkerCoordinationHost({ + scope: "typed-worker", + }); host.connect(port); void client; void fromPort; @@ -27,24 +41,38 @@ function acceptsPlatformWorkerTypes(worker: SharedWorker, port: MessagePort): vo void acceptsPlatformWorkerTypes; it("maps service method, handler, and event types", async () => { - const coordination = new SingleProcessCoordination({ scope: "types" }); + const coordination = new SingleProcessCoordination({ + scope: "types", + }); const client = coordination.service("typed.v1"); expectTypeOf(client).toMatchTypeOf>(); - expectTypeOf(client.methods.add).parameter(0).toEqualTypeOf<{ left: number; right: number }>(); + expectTypeOf(client.methods.add).parameter(0).toEqualTypeOf<{ + left: number; + right: number; + }>(); expectTypeOf(client.methods.add).returns.toEqualTypeOf>(); const server = coordination.serve("typed.v1", { add: (input, context) => { - expectTypeOf(input).toEqualTypeOf<{ left: number; right: number }>(); + expectTypeOf(input).toEqualTypeOf<{ + left: number; + right: number; + }>(); expectTypeOf(context).toEqualTypeOf(); return input.left + input.right; }, label: ({ id }) => id, }); client.events.subscribe("changed", (event) => { - expectTypeOf(event).toEqualTypeOf<{ id: string; revision: number }>(); + expectTypeOf(event).toEqualTypeOf<{ + id: string; + revision: number; + }>(); + }); + server.events.publish("changed", { + id: "one", + revision: 1, }); - server.events.publish("changed", { id: "one", revision: 1 }); const assertInvalidCalls = async () => { // @ts-expect-error Unknown methods are rejected. @@ -57,7 +85,9 @@ it("maps service method, handler, and event types", async () => { // @ts-expect-error Event payload is inferred. server.events.publish("changed", { id: "one" }); // @ts-expect-error Every service method needs a handler. - coordination.serve("incomplete.v1", { add: ({ left, right }) => left + right }); + coordination.serve("incomplete.v1", { + add: ({ left, right }) => left + right, + }); }; void assertInvalidCalls; diff --git a/packages/coordination/tsconfig.json b/packages/coordination/tsconfig.json index 3e4cc408..1326a774 100644 --- a/packages/coordination/tsconfig.json +++ b/packages/coordination/tsconfig.json @@ -1 +1,4 @@ -{ "$schema": "https://json.schemastore.org/tsconfig", "extends": "@party-stack/build-config/tsconfig.json" } +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@party-stack/build-config/tsconfig.json" +} diff --git a/packages/db-indexeddb-persistence/README.md b/packages/db-indexeddb-persistence/README.md index 88d4b255..3e539806 100644 --- a/packages/db-indexeddb-persistence/README.md +++ b/packages/db-indexeddb-persistence/README.md @@ -53,7 +53,10 @@ const tasks = createCollection( }), persistence, schemaVersion: 1, - initialRender: { strategy: "network-first", networkTimeoutMs: 3_000 }, + initialRender: { + strategy: "network-first", + networkTimeoutMs: 3_000, + }, }) ); diff --git a/packages/db-indexeddb-persistence/src/IndexedDBPersistenceAdapter.browser.bench.ts b/packages/db-indexeddb-persistence/src/IndexedDBPersistenceAdapter.browser.bench.ts index 66e85f84..3abda690 100644 --- a/packages/db-indexeddb-persistence/src/IndexedDBPersistenceAdapter.browser.bench.ts +++ b/packages/db-indexeddb-persistence/src/IndexedDBPersistenceAdapter.browser.bench.ts @@ -17,7 +17,12 @@ import type { LoadSubsetOptions } from "@tanstack/db"; import type { PersistedIndexSpec, PersistedTx } from "@tanstack/db-sqlite-persistence-core"; const ROW_COUNT = 10_000; -const BENCH_OPTIONS = { iterations: 5, time: 1_000, warmupTime: 100, warmupIterations: 5 }; +const BENCH_OPTIONS = { + iterations: 5, + time: 1_000, + warmupTime: 100, + warmupIterations: 5, +}; interface BenchItem { [key: string]: unknown; @@ -65,7 +70,9 @@ function queryOptions(build: (query: ReturnType) => unkn } function indexSpec(field: string): PersistedIndexSpec { - return { expressionSql: [JSON.stringify(new IR.PropRef([field]))] }; + return { + expressionSql: [JSON.stringify(new IR.PropRef([field]))], + }; } function buildRows(): BenchItem[] { @@ -84,7 +91,11 @@ function seedTransaction(rows: BenchItem[]): PersistedTx { term: 1, seq: 1, rowVersion: 1, - mutations: rows.map((value) => ({ type: "insert", key: value.id, value })), + mutations: rows.map((value) => ({ + type: "insert", + key: value.id, + value, + })), }; } diff --git a/packages/db-indexeddb-persistence/src/IndexedDBPersistenceAdapter.test.ts b/packages/db-indexeddb-persistence/src/IndexedDBPersistenceAdapter.test.ts index 8525a894..99d6883e 100644 --- a/packages/db-indexeddb-persistence/src/IndexedDBPersistenceAdapter.test.ts +++ b/packages/db-indexeddb-persistence/src/IndexedDBPersistenceAdapter.test.ts @@ -88,7 +88,13 @@ function tx( rowVersion: number, mutations: PersistedTx["mutations"] ): PersistedTx { - return { txId, term: 1, seq: rowVersion, rowVersion, mutations }; + return { + txId, + term: 1, + seq: rowVersion, + rowVersion, + mutations, + }; } function indexSpec(path: string[]): PersistedIndexSpec { @@ -96,7 +102,9 @@ function indexSpec(path: string[]): PersistedIndexSpec { } function expressionIndexSpec(expression: IR.BasicExpression): PersistedIndexSpec { - return { expressionSql: [JSON.stringify(expression)] }; + return { + expressionSql: [JSON.stringify(expression)], + }; } async function seed(adapter: IndexedDBPersistenceAdapter, items: Item[]): Promise { @@ -105,7 +113,11 @@ async function seed(adapter: IndexedDBPersistenceAdapter, items: Item[]): Promis tx( "seed", 1, - items.map((value) => ({ type: "insert", key: value.id, value })) + items.map((value) => ({ + type: "insert", + key: value.id, + value, + })) ) ); } @@ -875,7 +887,9 @@ describe("IndexedDBPersistenceAdapter", () => { it("persists a local-only TanStack DB collection across instances", async () => { const name = databaseName(); - const firstAdapter = new IndexedDBPersistenceAdapter({ databaseName: name }); + const firstAdapter = new IndexedDBPersistenceAdapter({ + databaseName: name, + }); const createItems = (adapter: IndexedDBPersistenceAdapter) => createCollection( persistedCollectionOptions({ @@ -887,15 +901,24 @@ describe("IndexedDBPersistenceAdapter", () => { const first = createItems(firstAdapter); await first.preload(); - const transaction = first.insert({ id: "one", status: "open", priority: 1 }); + const transaction = first.insert({ + id: "one", + status: "open", + priority: 1, + }); await transaction.isPersisted.promise; await first.cleanup(); firstAdapter.close(); - const secondAdapter = new IndexedDBPersistenceAdapter({ databaseName: name }); + const secondAdapter = new IndexedDBPersistenceAdapter({ + databaseName: name, + }); const second = createItems(secondAdapter); await second.preload(); - expect(second.get("one")).toMatchObject({ status: "open", priority: 1 }); + expect(second.get("one")).toMatchObject({ + status: "open", + priority: 1, + }); await second.cleanup(); secondAdapter.close(); }); @@ -910,15 +933,25 @@ describe("IndexedDBPersistenceAdapter", () => { persistence: { adapter }, }) ); - const firstAdapter = new IndexedDBPersistenceAdapter({ databaseName: name }); + const firstAdapter = new IndexedDBPersistenceAdapter({ + databaseName: name, + }); const first = createItems(firstAdapter); await first.preload(); - await first.insert({ id: "one", status: "executing", priority: 1 }, { optimistic: false }).isPersisted - .promise; + await first.insert( + { + id: "one", + status: "executing", + priority: 1, + }, + { optimistic: false } + ).isPersisted.promise; await first.cleanup(); firstAdapter.close(); - const secondAdapter = new IndexedDBPersistenceAdapter({ databaseName: name }); + const secondAdapter = new IndexedDBPersistenceAdapter({ + databaseName: name, + }); const second = createItems(secondAdapter); await second.preload(); await second.update("one", { optimistic: false }, (draft) => { @@ -926,7 +959,9 @@ describe("IndexedDBPersistenceAdapter", () => { }).isPersisted.promise; expect(second.has("one")).toBe(true); - await second.delete("one", { optimistic: false }).isPersisted.promise; + await second.delete("one", { + optimistic: false, + }).isPersisted.promise; expect(second.has("one")).toBe(false); await second.cleanup(); @@ -935,7 +970,9 @@ describe("IndexedDBPersistenceAdapter", () => { it("round-trips Temporal PlainDate and Instant values", async () => { const name = databaseName(); - const first = new IndexedDBPersistenceAdapter({ databaseName: name }); + const first = new IndexedDBPersistenceAdapter({ + databaseName: name, + }); await seed(first, [ { id: "temporal", @@ -946,7 +983,9 @@ describe("IndexedDBPersistenceAdapter", () => { ]); first.close(); - const second = new IndexedDBPersistenceAdapter({ databaseName: name }); + const second = new IndexedDBPersistenceAdapter({ + databaseName: name, + }); const [row] = await second.loadSubset("items", {}); expect(row?.value.date).toBeInstanceOf(Temporal.PlainDate); @@ -957,7 +996,9 @@ describe("IndexedDBPersistenceAdapter", () => { }); it("uses range indexes for Temporal PlainDate and Instant values", async () => { - const adapter = new IndexedDBPersistenceAdapter({ databaseName: databaseName() }); + const adapter = new IndexedDBPersistenceAdapter({ + databaseName: databaseName(), + }); await seed(adapter, [ { id: "earlier", @@ -994,7 +1035,9 @@ describe("IndexedDBPersistenceAdapter", () => { }); it("falls back to all rows when no persisted index matches", async () => { - const adapter = new IndexedDBPersistenceAdapter({ databaseName: databaseName() }); + const adapter = new IndexedDBPersistenceAdapter({ + databaseName: databaseName(), + }); await seed(adapter, [ { id: "open", status: "open", priority: 1 }, { id: "closed", status: "closed", priority: 2 }, @@ -1010,7 +1053,9 @@ describe("IndexedDBPersistenceAdapter", () => { }); it("uses an equality index to return candidate rows", async () => { - const adapter = new IndexedDBPersistenceAdapter({ databaseName: databaseName() }); + const adapter = new IndexedDBPersistenceAdapter({ + databaseName: databaseName(), + }); await seed(adapter, [ { id: "open-1", status: "open", priority: 1 }, { id: "open-2", status: "open", priority: 2 }, @@ -1028,7 +1073,9 @@ describe("IndexedDBPersistenceAdapter", () => { }); it("uses range indexes for homogeneous supported values", async () => { - const adapter = new IndexedDBPersistenceAdapter({ databaseName: databaseName() }); + const adapter = new IndexedDBPersistenceAdapter({ + databaseName: databaseName(), + }); await seed(adapter, [ { id: "low", priority: 1 }, { id: "middle", priority: 5 }, @@ -1046,7 +1093,9 @@ describe("IndexedDBPersistenceAdapter", () => { }); it("intersects indexed AND predicates", async () => { - const adapter = new IndexedDBPersistenceAdapter({ databaseName: databaseName() }); + const adapter = new IndexedDBPersistenceAdapter({ + databaseName: databaseName(), + }); await seed(adapter, [ { id: "open-low", status: "open", priority: 1 }, { id: "open-high", status: "open", priority: 10 }, @@ -1067,7 +1116,9 @@ describe("IndexedDBPersistenceAdapter", () => { }); it("uses cursor.whereFrom as an index candidate predicate", async () => { - const adapter = new IndexedDBPersistenceAdapter({ databaseName: databaseName() }); + const adapter = new IndexedDBPersistenceAdapter({ + databaseName: databaseName(), + }); await seed(adapter, [ { id: "low", priority: 1 }, { id: "middle", priority: 5 }, @@ -1088,7 +1139,9 @@ describe("IndexedDBPersistenceAdapter", () => { }); it("intersects indexed where and cursor.whereFrom predicates", async () => { - const adapter = new IndexedDBPersistenceAdapter({ databaseName: databaseName() }); + const adapter = new IndexedDBPersistenceAdapter({ + databaseName: databaseName(), + }); await seed(adapter, [ { id: "open-low", status: "open", priority: 1 }, { id: "open-high", status: "open", priority: 10 }, @@ -1112,7 +1165,9 @@ describe("IndexedDBPersistenceAdapter", () => { }); it("keeps indexed where candidates when cursor.whereFrom is not indexed", async () => { - const adapter = new IndexedDBPersistenceAdapter({ databaseName: databaseName() }); + const adapter = new IndexedDBPersistenceAdapter({ + databaseName: databaseName(), + }); await seed(adapter, [ { id: "open-low", status: "open", priority: 1 }, { id: "open-high", status: "open", priority: 10 }, @@ -1135,7 +1190,9 @@ describe("IndexedDBPersistenceAdapter", () => { }); it("unions indexed IN values", async () => { - const adapter = new IndexedDBPersistenceAdapter({ databaseName: databaseName() }); + const adapter = new IndexedDBPersistenceAdapter({ + databaseName: databaseName(), + }); await seed(adapter, [ { id: "open", status: "open", priority: 1 }, { id: "pending", status: "pending", priority: 2 }, @@ -1153,7 +1210,9 @@ describe("IndexedDBPersistenceAdapter", () => { }); it("uses literal prefixes for LIKE and leaves wildcard matching residual", async () => { - const adapter = new IndexedDBPersistenceAdapter({ databaseName: databaseName() }); + const adapter = new IndexedDBPersistenceAdapter({ + databaseName: databaseName(), + }); await seed(adapter, [ { id: "hello", name: "hello world", priority: 1 }, { id: "help", name: "helper", priority: 2 }, @@ -1171,7 +1230,9 @@ describe("IndexedDBPersistenceAdapter", () => { }); it("uses normalized string entries for prefix ILIKE", async () => { - const adapter = new IndexedDBPersistenceAdapter({ databaseName: databaseName() }); + const adapter = new IndexedDBPersistenceAdapter({ + databaseName: databaseName(), + }); await seed(adapter, [ { id: "hello", name: "Hello World", priority: 1 }, { id: "helium", name: "hELium", priority: 2 }, @@ -1189,7 +1250,9 @@ describe("IndexedDBPersistenceAdapter", () => { }); it("indexes null and undefined separately", async () => { - const adapter = new IndexedDBPersistenceAdapter({ databaseName: databaseName() }); + const adapter = new IndexedDBPersistenceAdapter({ + databaseName: databaseName(), + }); await seed(adapter, [ { id: "null", nullable: null, priority: 1 }, { id: "missing", priority: 2 }, @@ -1212,7 +1275,9 @@ describe("IndexedDBPersistenceAdapter", () => { }); it("unions fully indexed OR branches", async () => { - const adapter = new IndexedDBPersistenceAdapter({ databaseName: databaseName() }); + const adapter = new IndexedDBPersistenceAdapter({ + databaseName: databaseName(), + }); await seed(adapter, [ { id: "open-low", status: "open", priority: 1 }, { id: "open-high", status: "open", priority: 10 }, @@ -1233,7 +1298,9 @@ describe("IndexedDBPersistenceAdapter", () => { }); it("falls back to all rows for NOT predicates", async () => { - const adapter = new IndexedDBPersistenceAdapter({ databaseName: databaseName() }); + const adapter = new IndexedDBPersistenceAdapter({ + databaseName: databaseName(), + }); await seed(adapter, [ { id: "open", status: "open", priority: 1 }, { id: "closed", status: "closed", priority: 2 }, @@ -1250,7 +1317,9 @@ describe("IndexedDBPersistenceAdapter", () => { }); it("matches indexes on supported computed expressions", async () => { - const adapter = new IndexedDBPersistenceAdapter({ databaseName: databaseName() }); + const adapter = new IndexedDBPersistenceAdapter({ + databaseName: databaseName(), + }); await seed(adapter, [ { id: "hello", name: "HELLO", priority: 1 }, { id: "world", name: "WORLD", priority: 2 }, @@ -1271,7 +1340,9 @@ describe("IndexedDBPersistenceAdapter", () => { }); it("uses type-tagged equality lookups for mixed indexes", async () => { - const adapter = new IndexedDBPersistenceAdapter({ databaseName: databaseName() }); + const adapter = new IndexedDBPersistenceAdapter({ + databaseName: databaseName(), + }); await seed(adapter, [ { id: "number", mixed: 1, priority: 1 }, { id: "string", mixed: "1", priority: 2 }, @@ -1293,7 +1364,9 @@ describe("IndexedDBPersistenceAdapter", () => { }); it("keeps boolean, number, bigint, and string index values distinct", async () => { - const adapter = new IndexedDBPersistenceAdapter({ databaseName: databaseName() }); + const adapter = new IndexedDBPersistenceAdapter({ + databaseName: databaseName(), + }); await seed(adapter, [ { id: "boolean", mixed: true, priority: 1 }, { id: "number", mixed: 1, priority: 2 }, @@ -1327,7 +1400,9 @@ describe("IndexedDBPersistenceAdapter", () => { }); it("falls back to all rows for mixed-type ranges", async () => { - const adapter = new IndexedDBPersistenceAdapter({ databaseName: databaseName() }); + const adapter = new IndexedDBPersistenceAdapter({ + databaseName: databaseName(), + }); await seed(adapter, [ { id: "number", mixed: 10, priority: 1 }, { id: "string", mixed: "10", priority: 2 }, @@ -1344,7 +1419,9 @@ describe("IndexedDBPersistenceAdapter", () => { }); it("rebuilds persisted indexes when rows change", async () => { - const adapter = new IndexedDBPersistenceAdapter({ databaseName: databaseName() }); + const adapter = new IndexedDBPersistenceAdapter({ + databaseName: databaseName(), + }); await seed(adapter, [ { id: "one", status: "open", priority: 1 }, { id: "two", status: "closed", priority: 2 }, @@ -1353,7 +1430,11 @@ describe("IndexedDBPersistenceAdapter", () => { await adapter.applyCommittedTx( "items", tx("update", 2, [ - { type: "update", key: "two", value: { id: "two", status: "open", priority: 2 } }, + { + type: "update", + key: "two", + value: { id: "two", status: "open", priority: 2 }, + }, ]) ); @@ -1367,7 +1448,9 @@ describe("IndexedDBPersistenceAdapter", () => { }); it("falls back to a full scan after an index is removed", async () => { - const adapter = new IndexedDBPersistenceAdapter({ databaseName: databaseName() }); + const adapter = new IndexedDBPersistenceAdapter({ + databaseName: databaseName(), + }); await seed(adapter, [ { id: "open", status: "open", priority: 1 }, { id: "closed", status: "closed", priority: 2 }, @@ -1385,9 +1468,15 @@ describe("IndexedDBPersistenceAdapter", () => { }); it("deduplicates committed transaction ids and restores stream position", async () => { - const adapter = new IndexedDBPersistenceAdapter({ databaseName: databaseName() }); + const adapter = new IndexedDBPersistenceAdapter({ + databaseName: databaseName(), + }); const committed = tx("same-id", 1, [ - { type: "insert", key: "one", value: { id: "one", status: "open", priority: 1 } }, + { + type: "insert", + key: "one", + value: { id: "one", status: "open", priority: 1 }, + }, ]); await adapter.applyCommittedTx("items", committed); @@ -1403,7 +1492,9 @@ describe("IndexedDBPersistenceAdapter", () => { }); it("uses range indexes when omitted values are nullish", async () => { - const adapter = new IndexedDBPersistenceAdapter({ databaseName: databaseName() }); + const adapter = new IndexedDBPersistenceAdapter({ + databaseName: databaseName(), + }); await seed(adapter, [ { id: "missing", priority: 0 }, { id: "open", status: "open", priority: 1 }, diff --git a/packages/db-indexeddb-persistence/src/IndexedDBPersistenceAdapter.ts b/packages/db-indexeddb-persistence/src/IndexedDBPersistenceAdapter.ts index d9f6fcee..b3ebd34b 100644 --- a/packages/db-indexeddb-persistence/src/IndexedDBPersistenceAdapter.ts +++ b/packages/db-indexeddb-persistence/src/IndexedDBPersistenceAdapter.ts @@ -97,14 +97,25 @@ interface IndexEntryRecord { } interface IndexedDBPersistenceDB extends DBSchema { - rows: { key: string; value: RowRecord; indexes: { collectionId: string; metadata: [string, number] } }; + rows: { + key: string; + value: RowRecord; + indexes: { collectionId: string; metadata: [string, number] }; + }; transactions: { key: string; value: TransactionRecord; indexes: { collectionId: string; version: [string, number]; position: [string, number, number] }; }; - collectionMetadata: { key: string; value: CollectionMetadataRecord; indexes: { collectionId: string } }; - streams: { key: string; value: StreamRecord }; + collectionMetadata: { + key: string; + value: CollectionMetadataRecord; + indexes: { collectionId: string }; + }; + streams: { + key: string; + value: StreamRecord; + }; indexDefinitions: { key: [string, string]; value: IndexDefinitionRecord; @@ -390,7 +401,10 @@ function expressionsMatch(indexed: IR.BasicExpression, queried: IR.BasicExpressi function buildIndexRecords( definition: Omit, rows: readonly RowRecord[] -): { definition: IndexDefinitionRecord; entries: IndexEntryRecord[] } { +): { + definition: IndexDefinitionRecord; + entries: IndexEntryRecord[]; +} { const evaluate = compileSingleRowExpression(definition.expression); const entries: IndexEntryRecord[] = []; const valueTypeCounts: Partial> = {}; @@ -567,7 +581,11 @@ function inPlan( firstDefinition ??= definition; ranges.push(exactRange(definition, encoded)); } - return { kind: "lookup", definition: firstDefinition, ranges }; + return { + kind: "lookup", + definition: firstDefinition, + ranges, + }; } function nullPlan( @@ -577,7 +595,16 @@ function nullPlan( ): IndexPlan | undefined { const definition = matchingDefinition(definitions, toExpression(field), valueType); return definition - ? { kind: "lookup", definition, ranges: [exactRange(definition, { type: valueType, value: 0 })] } + ? { + kind: "lookup", + definition, + ranges: [ + exactRange(definition, { + type: valueType, + value: 0, + }), + ], + } : undefined; } @@ -1126,7 +1153,10 @@ export class IndexedDBPersistenceAdapter implements PersistenceAdapter { const children = current.children ?? []; const estimated = await Promise.all( - children.map(async (child) => ({ child, count: await estimatePlan(child) })) + children.map(async (child) => ({ + child, + count: await estimatePlan(child), + })) ); estimated.sort((left, right) => left.count - right.count); const [first, ...rest] = estimated; @@ -1405,21 +1435,19 @@ export class IndexedDBPersistenceAdapter implements PersistenceAdapter { count--; cursor = await cursor.continue(); } - await transaction - .objectStore(STREAMS) - .put({ - ...stream, - collectionId, - latestTerm: Math.max(stream?.latestTerm ?? 0, committed.term), - latestSeq: - committed.term > (stream?.latestTerm ?? 0) - ? committed.seq - : committed.term === (stream?.latestTerm ?? 0) - ? Math.max(stream?.latestSeq ?? 0, committed.seq) - : (stream?.latestSeq ?? 0), - latestRowVersion: nextRowVersion, - replayFloor, - }); + await transaction.objectStore(STREAMS).put({ + ...stream, + collectionId, + latestTerm: Math.max(stream?.latestTerm ?? 0, committed.term), + latestSeq: + committed.term > (stream?.latestTerm ?? 0) + ? committed.seq + : committed.term === (stream?.latestTerm ?? 0) + ? Math.max(stream?.latestSeq ?? 0, committed.seq) + : (stream?.latestSeq ?? 0), + latestRowVersion: nextRowVersion, + replayFloor, + }); await transaction.done; } catch (error) { try { @@ -1441,7 +1469,10 @@ export class IndexedDBPersistenceAdapter implements PersistenceAdapter { .index(BY_COLLECTION) .getAll(collectionId); await transaction.done; - return records.map(({ key, value }) => ({ key, value: decodePersistedValue(value) })); + return records.map(({ key, value }) => ({ + key, + value: decodePersistedValue(value), + })); } async scanRows(collectionId: string, options?: PersistedRowScanOptions): Promise { @@ -1548,7 +1579,11 @@ export class IndexedDBPersistenceAdapter implements PersistenceAdapter { } const rows = await transaction.objectStore(ROWS).index(BY_COLLECTION).getAll(collectionId); const built = buildIndexRecords( - { collectionId, signature, expression }, + { + collectionId, + signature, + expression, + }, rows.map((row) => ({ ...row, value: decodePersistedValue(row.value) as Record, @@ -1587,7 +1622,14 @@ export class IndexedDBPersistenceAdapter implements PersistenceAdapter { const { database, epoch } = await this.collectionDatabase(collectionId); const stream = await database.get(STREAMS, collectionId); this.assertSchema(stream, collectionId, epoch); - return stream ?? { collectionId, latestTerm: 0, latestSeq: 0, latestRowVersion: 0 }; + return ( + stream ?? { + collectionId, + latestTerm: 0, + latestSeq: 0, + latestRowVersion: 0, + } + ); } close(): void { @@ -1630,13 +1672,17 @@ export class IndexedDBConnection { this.databasePromise ??= openDB(this.options.databaseName, DATABASE_VERSION, { upgrade(database, oldVersion, _newVersion, transaction) { if (oldVersion < 1) { - const rows = database.createObjectStore(ROWS, { keyPath: "id" }); + const rows = database.createObjectStore(ROWS, { + keyPath: "id", + }); rows.createIndex(BY_COLLECTION, "collectionId"); const transactions = database.createObjectStore(TRANSACTIONS, { keyPath: "id" }); transactions.createIndex(BY_COLLECTION, "collectionId"); const metadata = database.createObjectStore(COLLECTION_METADATA, { keyPath: "id" }); metadata.createIndex(BY_COLLECTION, "collectionId"); - database.createObjectStore(STREAMS, { keyPath: "collectionId" }); + database.createObjectStore(STREAMS, { + keyPath: "collectionId", + }); const definitions = database.createObjectStore(INDEX_DEFINITIONS, { keyPath: ["collectionId", "signature"], diff --git a/packages/db-indexeddb-persistence/src/contracts/persistenceContract.ts b/packages/db-indexeddb-persistence/src/contracts/persistenceContract.ts index 1ac3d326..09011f8d 100644 --- a/packages/db-indexeddb-persistence/src/contracts/persistenceContract.ts +++ b/packages/db-indexeddb-persistence/src/contracts/persistenceContract.ts @@ -43,10 +43,30 @@ export function runPersistenceContract( const collectionId = `todos`; const rows: Array = [ - { id: `1`, title: `Task Alpha`, createdAt: `2026-01-01T00:00:00.000Z`, score: 10 }, - { id: `2`, title: `Task Beta`, createdAt: `2026-01-02T00:00:00.000Z`, score: 20 }, - { id: `3`, title: `Other`, createdAt: `2026-01-03T00:00:00.000Z`, score: 15 }, - { id: `4`, title: `Task Gamma`, createdAt: `2026-01-04T00:00:00.000Z`, score: 25 }, + { + id: `1`, + title: `Task Alpha`, + createdAt: `2026-01-01T00:00:00.000Z`, + score: 10, + }, + { + id: `2`, + title: `Task Beta`, + createdAt: `2026-01-02T00:00:00.000Z`, + score: 20, + }, + { + id: `3`, + title: `Other`, + createdAt: `2026-01-03T00:00:00.000Z`, + score: 15, + }, + { + id: `4`, + title: `Task Gamma`, + createdAt: `2026-01-04T00:00:00.000Z`, + score: 25, + }, ]; await adapter.applyCommittedTx(collectionId, { @@ -54,7 +74,11 @@ export function runPersistenceContract( term: 1, seq: 1, rowVersion: 1, - mutations: rows.map((row) => ({ type: `insert` as const, key: row.id, value: row })), + mutations: rows.map((row) => ({ + type: `insert` as const, + key: row.id, + value: row, + })), }); const filtered = await adapter.loadSubset(collectionId, { @@ -71,7 +95,10 @@ export function runPersistenceContract( orderBy: [ { expression: new IR.PropRef([`score`]), - compareOptions: { direction: `desc`, nulls: `last` }, + compareOptions: { + direction: `desc`, + nulls: `last`, + }, }, ], }); @@ -79,7 +106,12 @@ export function runPersistenceContract( expect(filtered).toEqual([ { key: `2`, - value: { id: `2`, title: `Task Beta`, createdAt: `2026-01-02T00:00:00.000Z`, score: 20 }, + value: { + id: `2`, + title: `Task Beta`, + createdAt: `2026-01-02T00:00:00.000Z`, + score: 20, + }, }, ]); @@ -232,7 +264,12 @@ export function runPersistenceContract( expect(updated).toEqual([ { key: `1`, - value: { id: `1`, title: `Updated`, createdAt: `2026-01-01T00:00:00.000Z`, score: 11 }, + value: { + id: `1`, + title: `Updated`, + createdAt: `2026-01-01T00:00:00.000Z`, + score: 11, + }, }, ]); @@ -326,8 +363,19 @@ export function runPersistenceContract( { type: `insert`, key: `1`, - value: { id: `1`, title: `Tracked`, createdAt: `2026-01-01T00:00:00.000Z`, score: 1 }, - metadata: { queryCollection: { owners: { q1: true } } }, + value: { + id: `1`, + title: `Tracked`, + createdAt: `2026-01-01T00:00:00.000Z`, + score: 1, + }, + metadata: { + queryCollection: { + owners: { + q1: true, + }, + }, + }, metadataChanged: true, }, ], @@ -350,8 +398,19 @@ export function runPersistenceContract( expect(rows).toEqual([ { key: `1`, - value: { id: `1`, title: `Tracked`, createdAt: `2026-01-01T00:00:00.000Z`, score: 1 }, - metadata: { queryCollection: { owners: { q1: true } } }, + value: { + id: `1`, + title: `Tracked`, + createdAt: `2026-01-01T00:00:00.000Z`, + score: 1, + }, + metadata: { + queryCollection: { + owners: { + q1: true, + }, + }, + }, }, ]); @@ -379,11 +438,22 @@ export function runPersistenceContract( { type: `insert`, key: `2`, - value: { id: `2`, title: `Bad`, createdAt: `2026-01-01T00:00:00.000Z`, score: 2 }, + value: { + id: `2`, + title: `Bad`, + createdAt: `2026-01-01T00:00:00.000Z`, + score: 2, + }, }, ], collectionMetadataMutations: [ - { type: `set`, key: `broken`, value: { invalid: unserializable } }, + { + type: `set`, + key: `broken`, + value: { + invalid: unserializable, + }, + }, ], }) ).rejects.toThrow(Error); @@ -417,7 +487,9 @@ export function runPersistenceContract( createdAt: `2026-01-01T00:00:00.000Z`, score: 1, }, - metadata: { owner: `before` }, + metadata: { + owner: `before`, + }, metadataChanged: true, }, ], @@ -452,12 +524,21 @@ export function runPersistenceContract( createdAt: `2026-01-02T00:00:00.000Z`, score: 2, }, - metadata: { owner: `after` }, + metadata: { + owner: `after`, + }, metadataChanged: true, }, ], collectionMetadataMutations: [ - { type: `set`, key: `electric:resume`, value: { kind: `reset`, updatedAt: 2 } }, + { + type: `set`, + key: `electric:resume`, + value: { + kind: `reset`, + updatedAt: 2, + }, + }, ], }); @@ -470,17 +551,27 @@ export function runPersistenceContract( createdAt: `2026-01-02T00:00:00.000Z`, score: 2, }, - metadata: { owner: `after` }, + metadata: { + owner: `after`, + }, }, ]); expect(await adapter.loadCollectionMetadata?.(collectionId)).toEqual([ - { key: `electric:resume`, value: { kind: `reset`, updatedAt: 2 } }, + { + key: `electric:resume`, + value: { + kind: `reset`, + updatedAt: 2, + }, + }, ]); }); it(`returns pullSince deltas and requiresFullReload when threshold is exceeded`, async () => { - const { adapter } = registerContractHarness({ pullSinceReloadThreshold: 1 }); + const { adapter } = registerContractHarness({ + pullSinceReloadThreshold: 1, + }); const collectionId = `todos`; await adapter.applyCommittedTx(collectionId, { @@ -492,12 +583,22 @@ export function runPersistenceContract( { type: `insert`, key: `1`, - value: { id: `1`, title: `One`, createdAt: `2026-01-01T00:00:00.000Z`, score: 1 }, + value: { + id: `1`, + title: `One`, + createdAt: `2026-01-01T00:00:00.000Z`, + score: 1, + }, }, { type: `insert`, key: `2`, - value: { id: `2`, title: `Two`, createdAt: `2026-01-02T00:00:00.000Z`, score: 2 }, + value: { + id: `2`, + title: `Two`, + createdAt: `2026-01-02T00:00:00.000Z`, + score: 2, + }, }, ], }); @@ -510,7 +611,12 @@ export function runPersistenceContract( { type: `delete`, key: `1`, - value: { id: `1`, title: `One`, createdAt: `2026-01-01T00:00:00.000Z`, score: 1 }, + value: { + id: `1`, + title: `One`, + createdAt: `2026-01-01T00:00:00.000Z`, + score: 1, + }, }, ], }); @@ -549,19 +655,43 @@ export function runPersistenceContract( { type: `insert`, key: `1`, - value: { id: `1`, title: `Tracked`, createdAt: `2026-01-01T00:00:00.000Z`, score: 1 }, - metadata: { queryCollection: { owners: { q1: true } } }, + value: { + id: `1`, + title: `Tracked`, + createdAt: `2026-01-01T00:00:00.000Z`, + score: 1, + }, + metadata: { + queryCollection: { + owners: { + q1: true, + }, + }, + }, metadataChanged: true, }, ], }); - const scannedRows = await adapter.scanRows?.(collectionId, { metadataOnly: true }); + const scannedRows = await adapter.scanRows?.(collectionId, { + metadataOnly: true, + }); expect(scannedRows).toEqual([ { key: `1`, - value: { id: `1`, title: `Tracked`, createdAt: `2026-01-01T00:00:00.000Z`, score: 1 }, - metadata: { queryCollection: { owners: { q1: true } } }, + value: { + id: `1`, + title: `Tracked`, + createdAt: `2026-01-01T00:00:00.000Z`, + score: 1, + }, + metadata: { + queryCollection: { + owners: { + q1: true, + }, + }, + }, }, ]); @@ -572,10 +702,27 @@ export function runPersistenceContract( rowVersion: 2, mutations: [], rowMetadataMutations: [ - { type: `set`, key: `1`, value: { queryCollection: { owners: { q2: true } } } }, + { + type: `set`, + key: `1`, + value: { + queryCollection: { + owners: { + q2: true, + }, + }, + }, + }, ], collectionMetadataMutations: [ - { type: `set`, key: `electric:resume`, value: { kind: `reset`, updatedAt: 2 } }, + { + type: `set`, + key: `electric:resume`, + value: { + kind: `reset`, + updatedAt: 2, + }, + }, ], }); @@ -591,10 +738,27 @@ export function runPersistenceContract( changedRows: [], deletedKeys: [], rowMetadataMutations: [ - { type: `set`, key: `1`, value: { queryCollection: { owners: { q2: true } } } }, + { + type: `set`, + key: `1`, + value: { + queryCollection: { + owners: { + q2: true, + }, + }, + }, + }, ], collectionMetadataMutations: [ - { type: `set`, key: `electric:resume`, value: { kind: `reset`, updatedAt: 2 } }, + { + type: `set`, + key: `electric:resume`, + value: { + kind: `reset`, + updatedAt: 2, + }, + }, ], }, ]); @@ -613,22 +777,42 @@ export function runPersistenceContract( { type: `insert`, key: `a`, - value: { id: `a`, title: `A`, createdAt: `2026-01-01T00:00:00.000Z`, score: 10 }, + value: { + id: `a`, + title: `A`, + createdAt: `2026-01-01T00:00:00.000Z`, + score: 10, + }, }, { type: `insert`, key: `b`, - value: { id: `b`, title: `B`, createdAt: `2026-01-02T00:00:00.000Z`, score: 10 }, + value: { + id: `b`, + title: `B`, + createdAt: `2026-01-02T00:00:00.000Z`, + score: 10, + }, }, { type: `insert`, key: `c`, - value: { id: `c`, title: `C`, createdAt: `2026-01-03T00:00:00.000Z`, score: 12 }, + value: { + id: `c`, + title: `C`, + createdAt: `2026-01-03T00:00:00.000Z`, + score: 12, + }, }, { type: `insert`, key: `d`, - value: { id: `d`, title: `D`, createdAt: `2026-01-04T00:00:00.000Z`, score: 13 }, + value: { + id: `d`, + title: `D`, + createdAt: `2026-01-04T00:00:00.000Z`, + score: 13, + }, }, ], }); @@ -637,7 +821,10 @@ export function runPersistenceContract( orderBy: [ { expression: new IR.PropRef([`score`]), - compareOptions: { direction: `asc`, nulls: `last` }, + compareOptions: { + direction: `asc`, + nulls: `last`, + }, }, ], limit: 1, @@ -691,7 +878,12 @@ export function runPersistenceContract( orderBy: [ { expression: new IR.PropRef([`title`]), - compareOptions: { direction, nulls: `last`, stringSort: `custom`, compare }, + compareOptions: { + direction, + nulls: `last`, + stringSort: `custom`, + compare, + }, }, ], }); @@ -713,12 +905,22 @@ export function runPersistenceContract( { type: `insert`, key: 1, - value: { id: 1, title: `Numeric`, createdAt: `2026-01-01T00:00:00.000Z`, score: 1 }, + value: { + id: 1, + title: `Numeric`, + createdAt: `2026-01-01T00:00:00.000Z`, + score: 1, + }, }, { type: `insert`, key: `1`, - value: { id: `1`, title: `String`, createdAt: `2026-01-01T00:00:00.000Z`, score: 2 }, + value: { + id: `1`, + title: `String`, + createdAt: `2026-01-01T00:00:00.000Z`, + score: 2, + }, }, ], }); diff --git a/packages/db-indexeddb-persistence/src/indexeddb-persistence.ts b/packages/db-indexeddb-persistence/src/indexeddb-persistence.ts index cd0cb962..f2ac0f1a 100644 --- a/packages/db-indexeddb-persistence/src/indexeddb-persistence.ts +++ b/packages/db-indexeddb-persistence/src/indexeddb-persistence.ts @@ -13,7 +13,10 @@ export type IndexedDBSchemaMismatchPolicy = export type IndexedDBPersistenceOptions = Omit< IndexedDBPersistenceAdapterOptions, "schemaVersion" | "schemaMismatchPolicy" -> & { coordinator?: PersistedCollectionCoordinator; schemaMismatchPolicy?: IndexedDBSchemaMismatchPolicy }; +> & { + coordinator?: PersistedCollectionCoordinator; + schemaMismatchPolicy?: IndexedDBSchemaMismatchPolicy; +}; /** Create shared persistence with the same collection resolver as TanStack's SQLite factories. */ export function createIndexedDBPersistence( @@ -33,7 +36,11 @@ export function createIndexedDBPersistence( : (schemaMismatchPolicy ?? (mode === "sync-present" ? "sync-present-reset" : "sync-absent-error")); const adapter = new IndexedDBPersistenceAdapter( - { ...baseOptions, schemaVersion, schemaMismatchPolicy: policy }, + { + ...baseOptions, + schemaVersion, + schemaMismatchPolicy: policy, + }, connection ); // Without a supplied coordinator, TanStack creates a collection-local default. diff --git a/packages/db-indexeddb-persistence/tsconfig.json b/packages/db-indexeddb-persistence/tsconfig.json index 3e4cc408..1326a774 100644 --- a/packages/db-indexeddb-persistence/tsconfig.json +++ b/packages/db-indexeddb-persistence/tsconfig.json @@ -1 +1,4 @@ -{ "$schema": "https://json.schemastore.org/tsconfig", "extends": "@party-stack/build-config/tsconfig.json" } +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@party-stack/build-config/tsconfig.json" +} diff --git a/packages/durable-object-ontology/README.md b/packages/durable-object-ontology/README.md index 20f3282c..be882611 100644 --- a/packages/durable-object-ontology/README.md +++ b/packages/durable-object-ontology/README.md @@ -25,7 +25,11 @@ const backend = createDurableObjectOntologyBackend({ ontologyId: "primary", }); -const ontology = await createLiveOntology({ ir, context: { user: authenticatedUserId }, backend }); +const ontology = await createLiveOntology({ + ir, + context: { user: authenticatedUserId }, + backend, +}); ``` Each Durable Object SQLite database must host exactly one ontology. The factory diff --git a/packages/durable-object-ontology/fixtures/worker.ts b/packages/durable-object-ontology/fixtures/worker.ts index 89b30106..580ff243 100644 --- a/packages/durable-object-ontology/fixtures/worker.ts +++ b/packages/durable-object-ontology/fixtures/worker.ts @@ -23,7 +23,9 @@ export class OntologyFixture extends DurableObject { } const ontology = await createLiveOntology({ ir: sqliteOntologyConformanceIR, - context: { user: request.headers.get("x-user-id") ?? "local-user" }, + context: { + user: request.headers.get("x-user-id") ?? "local-user", + }, backend: () => createDurableObjectOntologyBackendAdapter({ ir: sqliteOntologyConformanceIR, @@ -35,15 +37,30 @@ export class OntologyFixture extends DurableObject { }); try { if (segments[0] === "notes" && request.method === "POST") { - const body = (await request.json()) as { id?: unknown; title?: unknown }; + const body = (await request.json()) as { + id?: unknown; + title?: unknown; + }; if (typeof body.id !== "string" || typeof body.title !== "string") { - return json({ error: "id and title must be strings." }, 400); + return json( + { + error: "id and title must be strings.", + }, + 400 + ); } - await ontology.actions.createNote!({ id: body.id, title: body.title }); + await ontology.actions.createNote!({ + id: body.id, + title: body.title, + }); } if (segments[0] === "notes" && (request.method === "GET" || request.method === "POST")) { await queryOnce((query) => - query.from({ note: ontology.objects.Note! }).select(({ note }) => note) + query + .from({ + note: ontology.objects.Note!, + }) + .select(({ note }) => note) ); return json({ ontologyId, @@ -72,7 +89,11 @@ const worker: ExportedHandler = { segments[2] !== "ontologies" || (ontologyId !== "primary" && ontologyId !== "secondary") ) { - return Promise.resolve(json({ usage: "/cells/{cell}/ontologies/{primary|secondary}/notes" })); + return Promise.resolve( + json({ + usage: "/cells/{cell}/ontologies/{primary|secondary}/notes", + }) + ); } const stub = environment.CELLS.get( environment.CELLS.idFromName(JSON.stringify([segments[1], ontologyId])) diff --git a/packages/durable-object-ontology/src/index.ts b/packages/durable-object-ontology/src/index.ts index 65f721cb..eb8116d3 100644 --- a/packages/durable-object-ontology/src/index.ts +++ b/packages/durable-object-ontology/src/index.ts @@ -71,7 +71,9 @@ export function createDurableObjectSQLiteDatabase( export interface R2ObjectBodyLike { arrayBuffer(): Promise; - httpMetadata?: { contentType?: string }; + httpMetadata?: { + contentType?: string; + }; } export interface R2ListedObjectLike { @@ -88,7 +90,11 @@ export interface R2BucketLike { put( key: string, value: Blob | ArrayBuffer, - options?: { httpMetadata?: { contentType?: string } } + options?: { + httpMetadata?: { + contentType?: string; + }; + } ): Promise; get(key: string): Promise; delete(keys: string | readonly string[]): Promise; @@ -127,7 +133,10 @@ async function r2Prefix(options: { installationId: string; ontologyId: string }) async function deleteR2Prefix(bucket: R2BucketLike, prefix: string): Promise { let cursor: string | undefined; do { - const listed = await bucket.list({ prefix, ...(cursor ? { cursor } : {}) }); + const listed = await bucket.list({ + prefix, + ...(cursor ? { cursor } : {}), + }); const keys = listed.objects.map((object) => object.key); if (keys.length > 0) { await bucket.delete(keys); @@ -171,7 +180,9 @@ export class R2AttachmentBytesStore { write(id: string, blob: Blob): Promise { return this.track(async () => { await this.options.bucket.put(await this.key(id), blob, { - httpMetadata: { contentType: blob.type || "application/octet-stream" }, + httpMetadata: { + contentType: blob.type || "application/octet-stream", + }, }); }); } @@ -233,8 +244,14 @@ interface DurableObjectOntologyBackendCommonOptions { } type DurableObjectOntologyAttachmentOptions = - | { attachmentStorage?: "r2"; bucket: R2BucketLike } - | { attachmentStorage: "sqlite" | SQLiteAttachmentStorageOptions; bucket?: R2BucketLike }; + | { + attachmentStorage?: "r2"; + bucket: R2BucketLike; + } + | { + attachmentStorage: "sqlite" | SQLiteAttachmentStorageOptions; + bucket?: R2BucketLike; + }; type DurableObjectOntologyBackendBaseOptions = DurableObjectOntologyBackendCommonOptions & DurableObjectOntologyAttachmentOptions; @@ -251,7 +268,9 @@ function resolveAttachmentStorage(options: DurableObjectOntologyBackendBaseOptio return { storage: undefined }; } if (options.attachmentStorage && typeof options.attachmentStorage === "object") { - return { storage: options.attachmentStorage }; + return { + storage: options.attachmentStorage, + }; } if (!options.bucket) { throw new Error("R2 attachment storage requires a bucket."); @@ -261,7 +280,14 @@ function resolveAttachmentStorage(options: DurableObjectOntologyBackendBaseOptio installationId: options.installationId, ontologyId: options.ontologyId, }); - return { storage: { external: { bytes: ownedR2 } }, ownedR2 }; + return { + storage: { + external: { + bytes: ownedR2, + }, + }, + ownedR2, + }; } export function createDurableObjectOntologyBackendAdapter( @@ -295,5 +321,9 @@ export type CreateDurableObjectOntologyBackendOptions = DurableObjectOntologyBac export function createDurableObjectOntologyBackend( options: CreateDurableObjectOntologyBackendOptions ): OntologyBackendAdapterProvider { - return (ir) => createDurableObjectOntologyBackendAdapter({ ...options, ir }); + return (ir) => + createDurableObjectOntologyBackendAdapter({ + ...options, + ir, + }); } diff --git a/packages/durable-object-ontology/test/index.test.ts b/packages/durable-object-ontology/test/index.test.ts index 14b17873..904a9e5d 100644 --- a/packages/durable-object-ontology/test/index.test.ts +++ b/packages/durable-object-ontology/test/index.test.ts @@ -33,7 +33,11 @@ describe("Durable Object ontology", () => { ) .toArray() ); - expect(rows).toEqual([{ name: "party_stack_sqlite_Note" }]); + expect(rows).toEqual([ + { + name: "party_stack_sqlite_Note", + }, + ]); }); it("isolates ontologies by Durable Object", async () => { @@ -57,7 +61,10 @@ describe("Durable Object ontology", () => { }); it("quiesces the backend and deletes installation-scoped R2 objects", async () => { - await expect(createStub().runR2Destruction()).resolves.toEqual({ quiesced: true, r2Empty: true }); + await expect(createStub().runR2Destruction()).resolves.toEqual({ + quiesced: true, + r2Empty: true, + }); }); it("deletes whole Durable Object SQL storage explicitly", async () => { diff --git a/packages/durable-object-ontology/test/worker.ts b/packages/durable-object-ontology/test/worker.ts index f5149c93..25b573ca 100644 --- a/packages/durable-object-ontology/test/worker.ts +++ b/packages/durable-object-ontology/test/worker.ts @@ -54,10 +54,17 @@ export class DurableObjectOntologyTest extends DurableObject { }); try { if (id && title) { - await ontology.actions.createNote!({ id, title }); + await ontology.actions.createNote!({ + id, + title, + }); } await queryOnce((query) => - query.from({ note: ontology.objects.Note! }).select(({ note }) => note) + query + .from({ + note: ontology.objects.Note!, + }) + .select(({ note }) => note) ); return [...ontology.objects.Note!.values()].map((note) => String(note.title)); } finally { @@ -65,7 +72,11 @@ export class DurableObjectOntologyTest extends DurableObject { } } - async runR2Backend(): Promise<{ content: string; type: string; sqlBytesAreExternal: boolean }> { + async runR2Backend(): Promise<{ + content: string; + type: string; + sqlBytesAreExternal: boolean; + }> { const ontology = await createLiveOntology({ ir: sqliteOntologyConformanceIR, backend: () => @@ -79,10 +90,21 @@ export class DurableObjectOntologyTest extends DurableObject { }); try { const created = await ontology.attachments.create( - new Blob(["r2-content"], { type: "text/plain" }), - { target: { kind: "objectProperty", objectType: "Asset", property: "attachment" } } + new Blob(["r2-content"], { + type: "text/plain", + }), + { + target: { + kind: "objectProperty", + objectType: "Asset", + property: "attachment", + }, + } ); - await ontology.actions.createAsset!({ id: "asset-1", attachment: created.attachment }); + await ontology.actions.createAsset!({ + id: "asset-1", + attachment: created.attachment, + }); const blob = await ontology.attachments.blob(created.attachment); const row = this.database .prepare( @@ -90,7 +112,12 @@ export class DurableObjectOntologyTest extends DurableObject { FROM party_stack_attachments WHERE id = ?` ) - .get(created.attachment.id) as { bytes: unknown; storage_key: string | null } | undefined; + .get(created.attachment.id) as + | { + bytes: unknown; + storage_key: string | null; + } + | undefined; return { content: await blob.text(), type: blob.type, @@ -114,24 +141,44 @@ export class DurableObjectOntologyTest extends DurableObject { }), }); try { - const created = await ontology.attachments.create(new Blob(["inline"], { type: "text/plain" }), { - target: { kind: "objectProperty", objectType: "Asset", property: "attachment" }, + const created = await ontology.attachments.create( + new Blob(["inline"], { + type: "text/plain", + }), + { + target: { + kind: "objectProperty", + objectType: "Asset", + property: "attachment", + }, + } + ); + await ontology.actions.createAsset!({ + id: "inline-asset", + attachment: created.attachment, }); - await ontology.actions.createAsset!({ id: "inline-asset", attachment: created.attachment }); const row = this.database .prepare( `SELECT bytes, storage_key FROM party_stack_attachments WHERE id = ?` ) - .get(created.attachment.id) as { bytes: unknown; storage_key: string | null } | undefined; + .get(created.attachment.id) as + | { + bytes: unknown; + storage_key: string | null; + } + | undefined; return row?.bytes !== null && row?.storage_key === null; } finally { await ontology.cleanup(); } } - async runR2Destruction(): Promise<{ quiesced: boolean; r2Empty: boolean }> { + async runR2Destruction(): Promise<{ + quiesced: boolean; + r2Empty: boolean; + }> { await this.runR2Backend(); let quiesced = false; const installationId = this.ctx.id.toString(); @@ -149,8 +196,13 @@ export class DurableObjectOntologyTest extends DurableObject { installationId, ontologyId: "ontology", }); - const listed = await this.env.BLOBS.list({ prefix: await scopedStore.prefix }); - return { quiesced, r2Empty: listed.objects.length === 0 }; + const listed = await this.env.BLOBS.list({ + prefix: await scopedStore.prefix, + }); + return { + quiesced, + r2Empty: listed.objects.length === 0, + }; } async destroyStorage(): Promise { diff --git a/packages/durable-object-ontology/vite.config.ts b/packages/durable-object-ontology/vite.config.ts index f2d47a07..bebc1ed9 100644 --- a/packages/durable-object-ontology/vite.config.ts +++ b/packages/durable-object-ontology/vite.config.ts @@ -16,5 +16,11 @@ export default defineLibraryConfig({ // https://github.com/cloudflare/workers-sdk/pull/16163 fsModuleCache: false, }, - plugins: [cloudflareTest({ wrangler: { configPath: "./wrangler.jsonc" } })], + plugins: [ + cloudflareTest({ + wrangler: { + configPath: "./wrangler.jsonc", + }, + }), + ], }); diff --git a/packages/durable-object-ontology/wrangler.fixture.jsonc b/packages/durable-object-ontology/wrangler.fixture.jsonc index 9719a2f0..770df1e7 100644 --- a/packages/durable-object-ontology/wrangler.fixture.jsonc +++ b/packages/durable-object-ontology/wrangler.fixture.jsonc @@ -3,7 +3,24 @@ "name": "party-stack-durable-object-ontology-fixture", "main": "fixtures/worker.ts", "compatibility_date": "2026-08-27", - "durable_objects": { "bindings": [{ "name": "CELLS", "class_name": "OntologyFixture" }] }, - "r2_buckets": [{ "binding": "BLOBS", "bucket_name": "party-stack-ontology-fixture" }], - "migrations": [{ "tag": "v1", "new_sqlite_classes": ["OntologyFixture"] }], + "durable_objects": { + "bindings": [ + { + "name": "CELLS", + "class_name": "OntologyFixture", + }, + ], + }, + "r2_buckets": [ + { + "binding": "BLOBS", + "bucket_name": "party-stack-ontology-fixture", + }, + ], + "migrations": [ + { + "tag": "v1", + "new_sqlite_classes": ["OntologyFixture"], + }, + ], } diff --git a/packages/durable-object-ontology/wrangler.jsonc b/packages/durable-object-ontology/wrangler.jsonc index 05f767c4..3a13d18c 100644 --- a/packages/durable-object-ontology/wrangler.jsonc +++ b/packages/durable-object-ontology/wrangler.jsonc @@ -3,7 +3,24 @@ "name": "party-stack-durable-object-ontology-test", "main": "test/worker.ts", "compatibility_date": "2026-08-27", - "durable_objects": { "bindings": [{ "name": "ONTOLOGY", "class_name": "DurableObjectOntologyTest" }] }, - "r2_buckets": [{ "binding": "BLOBS", "bucket_name": "party-stack-ontology-test" }], - "migrations": [{ "tag": "v1", "new_sqlite_classes": ["DurableObjectOntologyTest"] }], + "durable_objects": { + "bindings": [ + { + "name": "ONTOLOGY", + "class_name": "DurableObjectOntologyTest", + }, + ], + }, + "r2_buckets": [ + { + "binding": "BLOBS", + "bucket_name": "party-stack-ontology-test", + }, + ], + "migrations": [ + { + "tag": "v1", + "new_sqlite_classes": ["DurableObjectOntologyTest"], + }, + ], } diff --git a/packages/errors/src/UnauthenticatedError.test.ts b/packages/errors/src/UnauthenticatedError.test.ts index cc8d4666..358cedc1 100644 --- a/packages/errors/src/UnauthenticatedError.test.ts +++ b/packages/errors/src/UnauthenticatedError.test.ts @@ -21,6 +21,11 @@ describe("UnauthenticatedError", () => { message: "Session expired.", }) ).toBe(true); - expect(isUnauthenticatedError({ name: "UnauthenticatedError", code: "forbidden" })).toBe(false); + expect( + isUnauthenticatedError({ + name: "UnauthenticatedError", + code: "forbidden", + }) + ).toBe(false); }); }); diff --git a/packages/errors/src/UnauthenticatedError.ts b/packages/errors/src/UnauthenticatedError.ts index d0294fe8..f676ef49 100644 --- a/packages/errors/src/UnauthenticatedError.ts +++ b/packages/errors/src/UnauthenticatedError.ts @@ -19,6 +19,9 @@ export function isUnauthenticatedError(error: unknown): error is Unauthenticated if (typeof error !== "object" || error === null) { return false; } - const candidate = error as { name?: unknown; code?: unknown }; + const candidate = error as { + name?: unknown; + code?: unknown; + }; return candidate.name === UNAUTHENTICATED_ERROR_NAME && candidate.code === UNAUTHENTICATED_ERROR_CODE; } diff --git a/packages/errors/tsconfig.json b/packages/errors/tsconfig.json index 3e4cc408..1326a774 100644 --- a/packages/errors/tsconfig.json +++ b/packages/errors/tsconfig.json @@ -1 +1,4 @@ -{ "$schema": "https://json.schemastore.org/tsconfig", "extends": "@party-stack/build-config/tsconfig.json" } +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@party-stack/build-config/tsconfig.json" +} diff --git a/packages/expo-runtime/src/createExpoBrowserAuthentication.ts b/packages/expo-runtime/src/createExpoBrowserAuthentication.ts index f48f6291..f43e32b7 100644 --- a/packages/expo-runtime/src/createExpoBrowserAuthentication.ts +++ b/packages/expo-runtime/src/createExpoBrowserAuthentication.ts @@ -8,7 +8,9 @@ export function createExpoBrowserAuthentication(): BrowserAuthentication { async open(authorizationUrl) { const result = await openAuthSessionAsync(authorizationUrl, redirectUrl); if (result.type === "success") { - return { callbackUrl: result.url }; + return { + callbackUrl: result.url, + }; } throw new BrowserAuthenticationCancelledError(); }, diff --git a/packages/expo-runtime/src/createExpoRuntime.ts b/packages/expo-runtime/src/createExpoRuntime.ts index e8a966a4..465cbd20 100644 --- a/packages/expo-runtime/src/createExpoRuntime.ts +++ b/packages/expo-runtime/src/createExpoRuntime.ts @@ -17,9 +17,15 @@ export const createExpoRuntime: (owner: string, namespace: string) => Promise { it("rejects cross-origin authenticated requests before loading credentials", async () => { const tokenProvider = vi.fn(() => Promise.resolve("token")); - const client = createClient({ baseUrl: "https://foundry.example.com", tokenProvider }); + const client = createClient({ + baseUrl: "https://foundry.example.com", + tokenProvider, + }); await expect(client.fetch("https://example.com/api")).rejects.toThrow( 'Egress not allowed for origin "https://example.com".' diff --git a/packages/foundry-client/src/client.ts b/packages/foundry-client/src/client.ts index 6df2fde4..b6b9fe3a 100644 --- a/packages/foundry-client/src/client.ts +++ b/packages/foundry-client/src/client.ts @@ -23,24 +23,42 @@ function normalizeBaseUrl(baseUrl: string): string { export function createClient(context: Optional): Client { const baseUrl = normalizeBaseUrl(context.baseUrl); - const fetchImpl = context.fetch ?? createFoundryFetch({ baseUrl, tokenProvider: context.tokenProvider }); + const fetchImpl = + context.fetch ?? + createFoundryFetch({ + baseUrl, + tokenProvider: context.tokenProvider, + }); return { baseUrl, fetch: createFetchOrThrow(fetchImpl), tokenProvider: context.tokenProvider, createWebSocket: context.createWebSocket ?? - createFoundryWebSocket({ baseUrl, tokenProvider: context.tokenProvider }), + createFoundryWebSocket({ + baseUrl, + tokenProvider: context.tokenProvider, + }), }; } export function createOntologyClient(context: Optional): OntologyClient { - return { ...createClient(context), ontologyRid: context.ontologyRid }; + return { + ...createClient(context), + ontologyRid: context.ontologyRid, + }; } export function fromOsdkClient(client: SharedClient): OntologyClient { const context = client.__osdkClientContext; - const ontologyRid = (context as unknown as { ontologyRid: string }).ontologyRid; + const ontologyRid = ( + context as unknown as { + ontologyRid: string; + } + ).ontologyRid; invariant(ontologyRid, "Ontology rid not found on OSDK client, this should never happen."); - return { ...context, ontologyRid }; + return { + ...context, + ontologyRid, + }; } diff --git a/packages/foundry-client/src/getTokenDetails.test.ts b/packages/foundry-client/src/getTokenDetails.test.ts index d65709d7..e88b7494 100644 --- a/packages/foundry-client/src/getTokenDetails.test.ts +++ b/packages/foundry-client/src/getTokenDetails.test.ts @@ -10,7 +10,12 @@ describe("getTokenDetails", () => { const encodedSubject = btoa(String.fromCharCode(...subject)); const token = [ base64url.encode(JSON.stringify({ alg: "none" })), - base64url.encode(JSON.stringify({ sub: encodedSubject, exp: 1_700_000_000 })), + base64url.encode( + JSON.stringify({ + sub: encodedSubject, + exp: 1_700_000_000, + }) + ), "", ].join("."); diff --git a/packages/foundry-client/tsconfig.json b/packages/foundry-client/tsconfig.json index 3e4cc408..1326a774 100644 --- a/packages/foundry-client/tsconfig.json +++ b/packages/foundry-client/tsconfig.json @@ -1 +1,4 @@ -{ "$schema": "https://json.schemastore.org/tsconfig", "extends": "@party-stack/build-config/tsconfig.json" } +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@party-stack/build-config/tsconfig.json" +} diff --git a/packages/foundry-object-set-watcher/src/useObjectSetWatcherConnection.test.ts b/packages/foundry-object-set-watcher/src/useObjectSetWatcherConnection.test.ts index a78e1959..52c7909c 100644 --- a/packages/foundry-object-set-watcher/src/useObjectSetWatcherConnection.test.ts +++ b/packages/foundry-object-set-watcher/src/useObjectSetWatcherConnection.test.ts @@ -4,9 +4,20 @@ import type { StreamMessage } from "@osdk/foundry.ontologies"; describe("convertSubscriptionMessage", () => { it.each([ - { type: "objectSetChanged", id: "released-subscription", updates: [] }, - { type: "refreshObjectSet", id: "released-subscription", objectType: "ExampleObject" }, - { type: "subscriptionClosed", id: "released-subscription" }, + { + type: "objectSetChanged", + id: "released-subscription", + updates: [], + }, + { + type: "refreshObjectSet", + id: "released-subscription", + objectType: "ExampleObject", + }, + { + type: "subscriptionClosed", + id: "released-subscription", + }, ] as StreamMessage[])("ignores a late $type message after unsubscribe", (message) => { expect(convertSubscriptionMessage(message, new Map())).toBeUndefined(); }); diff --git a/packages/foundry-object-set-watcher/src/useObjectSetWatcherConnection.ts b/packages/foundry-object-set-watcher/src/useObjectSetWatcherConnection.ts index 99699590..f2f6b18d 100644 --- a/packages/foundry-object-set-watcher/src/useObjectSetWatcherConnection.ts +++ b/packages/foundry-object-set-watcher/src/useObjectSetWatcherConnection.ts @@ -77,17 +77,28 @@ export function convertSubscriptionMessage( case "objectSetChanged": { const subscriptionId = externalSubscriptionIdsToInternal?.get(message.id); if (!subscriptionId) return undefined; - return { type: "change", subscriptionId, updates: filterChangeMessages(message.updates) }; + return { + type: "change", + subscriptionId, + updates: filterChangeMessages(message.updates), + }; } case "refreshObjectSet": { const subscriptionId = externalSubscriptionIdsToInternal?.get(message.id); if (!subscriptionId) return undefined; - return { type: "refresh", subscriptionId, objectType: message.objectType }; + return { + type: "refresh", + subscriptionId, + objectType: message.objectType, + }; } case "subscriptionClosed": { const subscriptionId = externalSubscriptionIdsToInternal?.get(message.id); if (!subscriptionId) return undefined; - return { type: "state", updates: [{ subscriptionId, status: "closed" }] }; + return { + type: "state", + updates: [{ subscriptionId, status: "closed" }], + }; } default: return undefined; diff --git a/packages/foundry-object-set-watcher/src/useObjectSetWatcherManager.ts b/packages/foundry-object-set-watcher/src/useObjectSetWatcherManager.ts index 293a08ed..0d515b5e 100644 --- a/packages/foundry-object-set-watcher/src/useObjectSetWatcherManager.ts +++ b/packages/foundry-object-set-watcher/src/useObjectSetWatcherManager.ts @@ -97,7 +97,11 @@ export function useObjectSetWatcherManager(client: OntologyClient): Operation { const expression = gt(new IR.PropRef(["createdAt"]), cursor); const filter = convertLoadSubsetFilter(expression); - expect(filter).toMatchObject({ type: "gt", value: "2026-07-27T12:00:00Z" }); + expect(filter).toMatchObject({ + type: "gt", + value: "2026-07-27T12:00:00Z", + }); expect( compileSingleRowExpression(expression)({ createdAt: Temporal.Instant.from("2026-07-27T12:00:01Z"), @@ -177,7 +180,10 @@ describe("convertLoadSubsetFilter", () => { }); expect( convertLoadSubsetFilter(like(new IR.PropRef(["name"]), "path\\\\name%")) - ).toMatchObject({ type: "wildcard", value: "path\\\\name*" }); + ).toMatchObject({ + type: "wildcard", + value: "path\\\\name*", + }); }); it("uses contains pushdowns for each wildcard-delimited term", () => { @@ -245,7 +251,10 @@ describe("convertLoadSubsetFilter", () => { expect(filter).toEqual({ type: "wildcard", - propertyIdentifier: { type: "property", apiName: "title" }, + propertyIdentifier: { + type: "property", + apiName: "title", + }, value: "issue?one*two*", }); }); @@ -253,6 +262,9 @@ describe("convertLoadSubsetFilter", () => { it.each([ilike, like])("treats patterns containing only percent wildcards as unfiltered", (operator) => { const filter = convertLoadSubsetFilter(operator(new IR.PropRef(["title"]), "%%")); - expect(filter).toEqual({ type: "and", value: [] }); + expect(filter).toEqual({ + type: "and", + value: [], + }); }); }); diff --git a/packages/foundry-ontology/src/adapter/convertLoadSubsetOptions.ts b/packages/foundry-ontology/src/adapter/convertLoadSubsetOptions.ts index 528ab047..3f830d57 100644 --- a/packages/foundry-ontology/src/adapter/convertLoadSubsetOptions.ts +++ b/packages/foundry-ontology/src/adapter/convertLoadSubsetOptions.ts @@ -97,7 +97,11 @@ function convertLikeFilter(field: FieldPath, value: string): PushdownFilter { }) .join(""); - return pushdown({ type: "wildcard", propertyIdentifier, value: wildcard }); + return pushdown({ + type: "wildcard", + propertyIdentifier, + value: wildcard, + }); } function convertQueryValue(value: unknown): unknown { @@ -116,7 +120,10 @@ function convertQueryValue(value: unknown): unknown { function fieldPathToPropertyIdentifier(fieldPath: FieldPath): PropertyIdentifier { if (fieldPath.length === 1) { - return { type: "property", apiName: fieldPath[0]! as PropertyApiName }; + return { + type: "property", + apiName: fieldPath[0]! as PropertyApiName, + }; } else if (fieldPath.length === 2) { return { type: "structField", diff --git a/packages/foundry-ontology/src/adapter/createFoundryOntologyBackendAdapter.test.ts b/packages/foundry-ontology/src/adapter/createFoundryOntologyBackendAdapter.test.ts index 2154f7e8..5559f0ca 100644 --- a/packages/foundry-ontology/src/adapter/createFoundryOntologyBackendAdapter.test.ts +++ b/packages/foundry-ontology/src/adapter/createFoundryOntologyBackendAdapter.test.ts @@ -9,17 +9,28 @@ import { import { encodeFoundryMediaId } from "./foundryMediaId.js"; import type { OntologyClient } from "@party-stack/foundry-client"; -const mediaMocks = vi.hoisted(() => ({ metadata: vi.fn(), uploadMedia: vi.fn() })); -const attachmentMocks = vi.hoisted(() => ({ get: vi.fn(), upload: vi.fn(), uploadWithRid: vi.fn() })); +const mediaMocks = vi.hoisted(() => ({ + metadata: vi.fn(), + uploadMedia: vi.fn(), +})); +const attachmentMocks = vi.hoisted(() => ({ + get: vi.fn(), + upload: vi.fn(), + uploadWithRid: vi.fn(), +})); const ontologyMocks = vi.hoisted(() => ({ applyWithOverrides: vi.fn(), getActionType: vi.fn(), getMediaContent: vi.fn(), getMediaMetadata: vi.fn(), })); -const metadataMocks = vi.hoisted(() => ({ bulkLoadOntologyEntities: vi.fn() })); +const metadataMocks = vi.hoisted(() => ({ + bulkLoadOntologyEntities: vi.fn(), +})); -vi.mock("@osdk/foundry.mediasets", () => ({ MediaSets: mediaMocks })); +vi.mock("@osdk/foundry.mediasets", () => ({ + MediaSets: mediaMocks, +})); vi.mock("@osdk/client.unstable", () => ({ bulkLoadOntologyEntities: metadataMocks.bulkLoadOntologyEntities, })); @@ -27,14 +38,20 @@ vi.mock("@osdk/foundry.ontologies", async (importOriginal) => { const original = await importOriginal(); return { ...original, - Actions: { ...original.Actions, applyWithOverrides: ontologyMocks.applyWithOverrides }, + Actions: { + ...original.Actions, + applyWithOverrides: ontologyMocks.applyWithOverrides, + }, Attachments: { ...original.Attachments, get: attachmentMocks.get, upload: attachmentMocks.upload, uploadWithRid: attachmentMocks.uploadWithRid, }, - ActionTypesV2: { ...original.ActionTypesV2, get: ontologyMocks.getActionType }, + ActionTypesV2: { + ...original.ActionTypesV2, + get: ontologyMocks.getActionType, + }, MediaReferenceProperties: { ...original.MediaReferenceProperties, getMediaContent: ontologyMocks.getMediaContent, @@ -49,15 +66,29 @@ beforeEach(() => { describe("isFoundryNotFoundError", () => { it("recognizes Foundry HTTP and API not-found errors", () => { - expect(isFoundryNotFoundError({ statusCode: 404 })).toBe(true); - expect(isFoundryNotFoundError({ errorCode: "NOT_FOUND" })).toBe(true); - expect(isFoundryNotFoundError({ statusCode: 500 })).toBe(false); + expect( + isFoundryNotFoundError({ + statusCode: 404, + }) + ).toBe(true); + expect( + isFoundryNotFoundError({ + errorCode: "NOT_FOUND", + }) + ).toBe(true); + expect( + isFoundryNotFoundError({ + statusCode: 500, + }) + ).toBe(false); }); }); describe("Foundry action execution time overrides", () => { const adapter = createFoundryOntologyBackendAdapter({ - client: { ontologyRid: "ri.ontology.main.1" } as OntologyClient, + client: { + ontologyRid: "ri.ontology.main.1", + } as OntologyClient, ir: { types: [], objectTypes: [], @@ -81,7 +112,9 @@ describe("Foundry action execution time overrides", () => { }, }); const isoInstant = "2026-09-17T20:21:00Z"; - const temporalLike = { toString: () => isoInstant }; + const temporalLike = { + toString: () => isoInstant, + }; it.each([ ["Temporal.Instant", Temporal.Instant.from(isoInstant)], @@ -92,13 +125,18 @@ describe("Foundry action execution time overrides", () => { ontologyMocks.applyWithOverrides.mockResolvedValue({ operationId: "operation-1", validation: { result: "VALID" }, - edits: { type: "edits", edits: [] }, + edits: { + type: "edits", + edits: [], + }, }); await adapter.applyAction("setTimestamp", { __now: value }, { objects: {} }); expect(ontologyMocks.applyWithOverrides.mock.calls[0]?.[3]).toMatchObject({ - overrides: { actionExecutionTime: isoInstant }, + overrides: { + actionExecutionTime: isoInstant, + }, }); }); @@ -121,15 +159,27 @@ describe("Foundry action API names", () => { ontologyMocks.applyWithOverrides.mockResolvedValue({ operationId: "operation-1", validation: { result: "VALID" }, - edits: { type: "edits", edits: [] }, + edits: { + type: "edits", + edits: [], + }, }); const adapter = createFoundryOntologyBackendAdapter({ - client: { ontologyRid: "ri.ontology.main.1" } as OntologyClient, + client: { + ontologyRid: "ri.ontology.main.1", + } as OntologyClient, ir: { types: [], objectTypes: [], linkTypes: [], - actionTypes: [{ name, displayName: "Action", parameters: [], logic: [] }], + actionTypes: [ + { + name, + displayName: "Action", + parameters: [], + logic: [], + }, + ], queryFunctionTypes: [], }, live: false, @@ -150,11 +200,24 @@ describe("Foundry live behavior", () => { displayName: "Task", pluralDisplayName: "Tasks", primaryKey: "id", - properties: [{ name: "id", displayName: "ID", type: o.string({}) }], + properties: [ + { + name: "id", + displayName: "ID", + type: o.string({}), + }, + ], }, ], linkTypes: [], - actionTypes: [{ name: "updateTask", displayName: "Update task", parameters: [], logic: [] }], + actionTypes: [ + { + name: "updateTask", + displayName: "Update task", + parameters: [], + logic: [], + }, + ], queryFunctionTypes: [], }; @@ -162,14 +225,24 @@ describe("Foundry live behavior", () => { ontologyMocks.applyWithOverrides.mockResolvedValue({ operationId: "operation-1", validation: { result: "VALID" }, - edits: { type: "edits", edits: [{ type: "modifyObject", objectType: "Task" }] }, + edits: { + type: "edits", + edits: [ + { + type: "modifyObject", + objectType: "Task", + }, + ], + }, }); }); it("waits for edited collections by default", async () => { const awaitOperationId = vi.fn(() => Promise.resolve()); const adapter = createFoundryOntologyBackendAdapter({ - client: { ontologyRid: "ri.ontology.main.1" } as OntologyClient, + client: { + ontologyRid: "ri.ontology.main.1", + } as OntologyClient, ir, }); @@ -177,7 +250,13 @@ describe("Foundry live behavior", () => { await adapter.applyAction( "updateTask", {}, - { objects: { Task: { utils: { awaitOperationId } } as never } } + { + objects: { + Task: { + utils: { awaitOperationId }, + } as never, + }, + } ); expect(awaitOperationId).toHaveBeenCalledWith("operation-1"); @@ -186,7 +265,9 @@ describe("Foundry live behavior", () => { it("returns after backend acceptance when live is false", async () => { const awaitOperationId = vi.fn(() => Promise.resolve()); const adapter = createFoundryOntologyBackendAdapter({ - client: { ontologyRid: "ri.ontology.main.1" } as OntologyClient, + client: { + ontologyRid: "ri.ontology.main.1", + } as OntologyClient, ir, live: false, }); @@ -195,7 +276,13 @@ describe("Foundry live behavior", () => { await adapter.applyAction( "updateTask", {}, - { objects: { Task: { utils: { awaitOperationId } } as never } } + { + objects: { + Task: { + utils: { awaitOperationId }, + } as never, + }, + } ); expect(awaitOperationId).not.toHaveBeenCalled(); @@ -203,56 +290,104 @@ describe("Foundry live behavior", () => { it("forwards live through the backend provider", async () => { const backend = createFoundryOntologyBackend({ - client: { ontologyRid: "ri.ontology.main.1" } as OntologyClient, + client: { + ontologyRid: "ri.ontology.main.1", + } as OntologyClient, live: false, }); - await expect(backend(ir, {})).resolves.toMatchObject({ name: "foundry", live: false }); + await expect(backend(ir, {})).resolves.toMatchObject({ + name: "foundry", + live: false, + }); }); }); describe("Foundry attachments", () => { - const attachmentType = o.attachment({ meta: { foundry: { kind: "attachment" } } }); + const attachmentType = o.attachment({ + meta: { foundry: { kind: "attachment" } }, + }); const adapter = createFoundryOntologyBackendAdapter({ - client: { ontologyRid: "ri.ontology.main.1" } as OntologyClient, - ir: { types: [], objectTypes: [], linkTypes: [], actionTypes: [], queryFunctionTypes: [] }, + client: { + ontologyRid: "ri.ontology.main.1", + } as OntologyClient, + ir: { + types: [], + objectTypes: [], + linkTypes: [], + actionTypes: [], + queryFunctionTypes: [], + }, }); const materializeAttachment = adapter.attachments!.materializeAttachment!; const target = attachmentType.value; const blob = new Blob(["attachment"], { type: "text/plain" }); it("lets Foundry assign a canonical RID for an opaque local ID", async () => { - attachmentMocks.upload.mockResolvedValue({ rid: "ri.attachments.main.attachment.remote" }); + attachmentMocks.upload.mockResolvedValue({ + rid: "ri.attachments.main.attachment.remote", + }); await expect( - materializeAttachment({ id: "local-id", type: "text/plain" }, blob, { target }) - ).resolves.toEqual({ id: "ri.attachments.main.attachment.remote", type: "text/plain" }); - expect(attachmentMocks.upload).toHaveBeenCalledWith(expect.anything(), blob, { filename: "" }); + materializeAttachment( + { + id: "local-id", + type: "text/plain", + }, + blob, + { target } + ) + ).resolves.toEqual({ + id: "ri.attachments.main.attachment.remote", + type: "text/plain", + }); + expect(attachmentMocks.upload).toHaveBeenCalledWith(expect.anything(), blob, { + filename: "", + }); expect(attachmentMocks.get).not.toHaveBeenCalled(); expect(attachmentMocks.uploadWithRid).not.toHaveBeenCalled(); }); it("preserves an existing Foundry attachment RID", async () => { - attachmentMocks.get.mockResolvedValue({ rid: "ri.attachments.main.attachment.existing" }); + attachmentMocks.get.mockResolvedValue({ + rid: "ri.attachments.main.attachment.existing", + }); await expect( - materializeAttachment({ id: "ri.attachments.main.attachment.existing" }, blob, { target }) + materializeAttachment( + { + id: "ri.attachments.main.attachment.existing", + }, + blob, + { target } + ) ).resolves.toBeUndefined(); expect(attachmentMocks.upload).not.toHaveBeenCalled(); expect(attachmentMocks.uploadWithRid).not.toHaveBeenCalled(); }); it("uploads a missing preassigned Foundry attachment RID", async () => { - attachmentMocks.get.mockRejectedValue({ statusCode: 404 }); + attachmentMocks.get.mockRejectedValue({ + statusCode: 404, + }); await expect( - materializeAttachment({ id: "ri.attachments.main.attachment.missing" }, blob, { target }) + materializeAttachment( + { + id: "ri.attachments.main.attachment.missing", + }, + blob, + { target } + ) ).resolves.toBeUndefined(); expect(attachmentMocks.uploadWithRid).toHaveBeenCalledWith( expect.anything(), "ri.attachments.main.attachment.missing", blob, - { filename: "", preview: true } + { + filename: "", + preview: true, + } ); }); @@ -261,7 +396,13 @@ describe("Foundry attachments", () => { attachmentMocks.get.mockRejectedValue(error); await expect( - materializeAttachment({ id: "ri.attachments.main.attachment.existing" }, blob, { target }) + materializeAttachment( + { + id: "ri.attachments.main.attachment.existing", + }, + blob, + { target } + ) ).rejects.toBe(error); expect(attachmentMocks.upload).not.toHaveBeenCalled(); expect(attachmentMocks.uploadWithRid).not.toHaveBeenCalled(); @@ -276,11 +417,18 @@ describe("Foundry media attachments", () => { }; const mediaReference = { mimeType: "image/png", - reference: { type: "mediaSetViewItem" as const, mediaSetViewItem: mediaId }, + reference: { + type: "mediaSetViewItem" as const, + mediaSetViewItem: mediaId, + }, }; - const mediaType = o.attachment({ meta: { foundry: { kind: "media" } } }); + const mediaType = o.attachment({ + meta: { foundry: { kind: "media" } }, + }); const adapter = createFoundryOntologyBackendAdapter({ - client: { ontologyRid: "ri.ontology.main.1" } as OntologyClient, + client: { + ontologyRid: "ri.ontology.main.1", + } as OntologyClient, ir: { types: [], objectTypes: [], @@ -290,8 +438,16 @@ describe("Foundry media attachments", () => { name: "createMedia", displayName: "Create Media", parameters: [ - { name: "media", displayName: "Media", type: mediaType }, - { name: "caption", displayName: "Caption", type: o.optional({ type: o.string({}) }) }, + { + name: "media", + displayName: "Media", + type: mediaType, + }, + { + name: "caption", + displayName: "Caption", + type: o.optional({ type: o.string({}) }), + }, ], logic: [], }, @@ -305,7 +461,13 @@ describe("Foundry media attachments", () => { it("routes media through action attachment uploads", () => { expect( - attachments.canMaterializeAttachment?.({ id: "local-id", type: "image/png" }, { target }) + attachments.canMaterializeAttachment?.( + { + id: "local-id", + type: "image/png", + }, + { target } + ) ).toBe(false); }); @@ -314,29 +476,60 @@ describe("Foundry media attachments", () => { ...mediaReference, reference: { ...mediaReference.reference, - mediaSetViewItem: { ...mediaId, token: "temporary-token" }, + mediaSetViewItem: { + ...mediaId, + token: "temporary-token", + }, }, }; mediaMocks.uploadMedia.mockResolvedValue(temporaryReference); ontologyMocks.applyWithOverrides.mockResolvedValue({ operationId: "operation-1", validation: { result: "VALID" }, - edits: { type: "edits", edits: [] }, + edits: { + type: "edits", + edits: [], + }, + }); + const blob = new Blob(["image"], { + type: "image/png", }); - const blob = new Blob(["image"], { type: "image/png" }); - const attachment = { id: "local-id", name: "image.png", type: "image/png" }; + const attachment = { + id: "local-id", + name: "image.png", + type: "image/png", + }; await expect( adapter.applyAction( "createMedia", { media: attachment, caption: null }, - { objects: {}, attachmentUploads: [{ attachment, target, blob }] } + { + objects: {}, + attachmentUploads: [ + { + attachment, + target, + blob, + }, + ], + } ) ).resolves.toEqual({ - attachmentIdMappings: [{ localId: "local-id", remoteId: encodeFoundryMediaId(mediaId) }], + attachmentIdMappings: [ + { + localId: "local-id", + remoteId: encodeFoundryMediaId(mediaId), + }, + ], }); expect(ontologyMocks.applyWithOverrides.mock.calls[0]?.[3]).toMatchObject({ - request: { parameters: { media: temporaryReference, caption: null } }, + request: { + parameters: { + media: temporaryReference, + caption: null, + }, + }, }); }); @@ -345,7 +538,10 @@ describe("Foundry media attachments", () => { validation: { result: "INVALID", submissionCriteria: [ - { result: "INVALID", configuredFailureMessage: "Only administrators may submit." }, + { + result: "INVALID", + configuredFailureMessage: "Only administrators may submit.", + }, ], parameters: {}, }, @@ -354,22 +550,40 @@ describe("Foundry media attachments", () => { await expect( adapter.validateAction!( "createMedia", - { media: { id: encodeFoundryMediaId(mediaId), type: "image/png" } }, - { objects: {} } + { + media: { + id: encodeFoundryMediaId(mediaId), + type: "image/png", + }, + }, + { + objects: {}, + } ) ).resolves.toEqual({ certain: true, - value: { kind: "err", value: [{ message: "Only administrators may submit." }] }, + value: { + kind: "err", + value: [{ message: "Only administrators may submit." }], + }, }); const request = ontologyMocks.applyWithOverrides.mock.calls[0]?.[3] as unknown as { - request: { options: { mode: string } }; + request: { + options: { + mode: string; + }; + }; }; - expect(request.request.options).toEqual({ mode: "VALIDATE_ONLY" }); + expect(request.request.options).toEqual({ + mode: "VALIDATE_ONLY", + }); expect(mediaMocks.uploadMedia).not.toHaveBeenCalled(); }); it("proves impossible submission criteria without validating incomplete parameters", async () => { - ontologyMocks.getActionType.mockResolvedValue({ rid: "ri.action-type.main.action-type.1" }); + ontologyMocks.getActionType.mockResolvedValue({ + rid: "ri.action-type.main.action-type.1", + }); metadataMocks.bulkLoadOntologyEntities.mockResolvedValue({ actionTypes: [ { @@ -384,12 +598,18 @@ describe("Foundry media attachments", () => { comparison: { left: { type: "staticValue", - staticValue: { type: "string", string: "user" }, + staticValue: { + type: "string", + string: "user", + }, }, operator: "EQUALS", right: { type: "staticValue", - staticValue: { type: "string", string: "admin" }, + staticValue: { + type: "string", + string: "admin", + }, }, }, }, @@ -410,13 +630,23 @@ describe("Foundry media attachments", () => { adapter.validateActionDraft!( "createMedia", {}, - { objects: {}, context: { user: "user-1" }, knownParameters: [] } + { + objects: {}, + context: { + user: "user-1", + }, + knownParameters: [], + } ) ).resolves.toEqual({ certain: true, value: { kind: "err", - value: [{ message: "Impossible submission criterion: Only administrators may submit." }], + value: [ + { + message: "Impossible submission criterion: Only administrators may submit.", + }, + ], }, }); expect(ontologyMocks.applyWithOverrides).not.toHaveBeenCalled(); @@ -424,8 +654,17 @@ describe("Foundry media attachments", () => { it("reports uncertain draft validation without a context user", async () => { await expect( - adapter.validateActionDraft!("createMedia", {}, { objects: {}, knownParameters: [] }) - ).resolves.toEqual({ certain: false }); + adapter.validateActionDraft!( + "createMedia", + {}, + { + objects: {}, + knownParameters: [], + } + ) + ).resolves.toEqual({ + certain: false, + }); expect(ontologyMocks.getActionType).not.toHaveBeenCalled(); expect(metadataMocks.bulkLoadOntologyEntities).not.toHaveBeenCalled(); }); @@ -435,13 +674,24 @@ describe("Foundry media attachments", () => { adapter.validateActionDraft!( "createMedia", {}, - { objects: {}, context: { user: "user-1" }, knownParameters: ["media"] } + { + objects: {}, + context: { + user: "user-1", + }, + knownParameters: ["media"], + } ) ).resolves.toEqual({ certain: true, value: { kind: "err", - value: [{ message: 'Required action parameter "media" is missing.', path: ["media"] }], + value: [ + { + message: 'Required action parameter "media" is missing.', + path: ["media"], + }, + ], }, }); expect(metadataMocks.bulkLoadOntologyEntities).not.toHaveBeenCalled(); @@ -453,7 +703,11 @@ describe("Foundry media attachments", () => { const attachment = { id, type: "image/png", - source: { objectType: "Task", primaryKey: "task-1", property: "media" }, + source: { + objectType: "Task", + primaryKey: "task-1", + property: "media", + }, }; ontologyMocks.getMediaContent.mockResolvedValue(new Response(new Blob(["image"]))); ontologyMocks.getMediaMetadata.mockResolvedValue({ @@ -481,7 +735,10 @@ describe("Foundry media attachments", () => { }); it("pushes image dimension selection into media set metadata", async () => { - const attachment = { id: encodeFoundryMediaId(mediaId), type: "image/png" }; + const attachment = { + id: encodeFoundryMediaId(mediaId), + type: "image/png", + }; mediaMocks.metadata.mockResolvedValue({ type: "imagery", sizeBytes: 5, @@ -504,9 +761,14 @@ describe("Foundry media attachments", () => { }); it("resolves inline media type without a Foundry request", async () => { - const attachment = { id: encodeFoundryMediaId(mediaId), type: "image/png" }; + const attachment = { + id: encodeFoundryMediaId(mediaId), + type: "image/png", + }; - await expect(getAttachmentMetadata(attachment, ["type"])).resolves.toEqual({ type: "image/png" }); + await expect(getAttachmentMetadata(attachment, ["type"])).resolves.toEqual({ + type: "image/png", + }); expect(mediaMocks.metadata).not.toHaveBeenCalled(); expect(ontologyMocks.getMediaMetadata).not.toHaveBeenCalled(); }); diff --git a/packages/foundry-ontology/src/adapter/createFoundryOntologyBackendAdapter.ts b/packages/foundry-ontology/src/adapter/createFoundryOntologyBackendAdapter.ts index 179de8d1..a6589d5c 100644 --- a/packages/foundry-ontology/src/adapter/createFoundryOntologyBackendAdapter.ts +++ b/packages/foundry-ontology/src/adapter/createFoundryOntologyBackendAdapter.ts @@ -45,7 +45,10 @@ export function isFoundryNotFoundError(error: unknown): boolean { if (typeof error !== "object" || error === null) { return false; } - const foundryError = error as { statusCode?: unknown; errorCode?: unknown }; + const foundryError = error as { + statusCode?: unknown; + errorCode?: unknown; + }; return foundryError.statusCode === 404 || foundryError.errorCode === "NOT_FOUND"; } @@ -61,7 +64,9 @@ export interface FoundryUsersIntegration { } type CollectionWithUtils = Collection, string | number, ObjectCollectionUtils>; -type ApplyActionResult = Awaited> & { operationId?: string }; +type ApplyActionResult = Awaited> & { + operationId?: string; +}; function serializeOverrideValue(value: unknown): string { if (typeof value === "string") { @@ -139,7 +144,10 @@ function prepareFoundryActionInvocation(options: { codec: ReturnType; }): { parameters: Record; - overrides: { uniqueIdentifierLinkIdValues: Record; actionExecutionTime?: string }; + overrides: { + uniqueIdentifierLinkIdValues: Record; + actionExecutionTime?: string; + }; } { const actionType = options.ir.actionTypes.find((candidate) => candidate.name === options.name); if (!actionType) { @@ -175,7 +183,13 @@ function prepareFoundryActionInvocation(options: { } } - return { parameters, overrides: { uniqueIdentifierLinkIdValues, actionExecutionTime } }; + return { + parameters, + overrides: { + uniqueIdentifierLinkIdValues, + actionExecutionTime, + }, + }; } export interface CreateFoundryOntologyBackendAdapterOptions { @@ -213,7 +227,10 @@ export function createFoundryOntologyBackendAdapter( const filename = getAttachmentName(blob) ?? ""; if (!isFoundryAttachmentRid(attachment.id)) { const uploaded = await Attachments.upload(opts.client, blob, { filename }); - return { ...attachment, id: uploaded.rid }; + return { + ...attachment, + id: uploaded.rid, + }; } try { await Attachments.get(opts.client, attachment.id); @@ -223,7 +240,10 @@ export function createFoundryOntologyBackendAdapter( throw error; } } - await Attachments.uploadWithRid(opts.client, attachment.id, blob, { filename, preview: true }); + await Attachments.uploadWithRid(opts.client, attachment.id, blob, { + filename, + preview: true, + }); }, getAttachmentContent: async (attachment) => { const userContent = await opts.users?.getAttachmentContent?.(opts.client, attachment); @@ -292,7 +312,12 @@ export function createFoundryOntologyBackendAdapter( source.property, { preview: true } ); - return { ...result, size: Number(info.sizeBytes), type: info.mediaType, name: info.path }; + return { + ...result, + size: Number(info.sizeBytes), + type: info.mediaType, + name: info.path, + }; } const metadata = await Attachments.get(opts.client, attachment.id); return { @@ -323,7 +348,12 @@ export function createFoundryOntologyBackendAdapter( }); }, validateAction: async (name: string, parameters: Record) => { - const invocation = prepareFoundryActionInvocation({ ir: opts.ir, name, parameters, codec }); + const invocation = prepareFoundryActionInvocation({ + ir: opts.ir, + name, + parameters, + codec, + }); let result: ApplyActionResult; try { @@ -332,7 +362,12 @@ export function createFoundryOntologyBackendAdapter( opts.client.ontologyRid, toFoundryActionTypeName(name), { - request: { options: { mode: "VALIDATE_ONLY" }, parameters: invocation.parameters }, + request: { + options: { + mode: "VALIDATE_ONLY", + }, + parameters: invocation.parameters, + }, overrides: invocation.overrides, }, // oxlint-disable-next-line typescript/no-unsafe-argument @@ -358,8 +393,14 @@ export function createFoundryOntologyBackendAdapter( certain: true, value: result.validation.result === "VALID" - ? { kind: "ok", value: undefined } - : { kind: "err", value: getFoundryValidationIssues(result) }, + ? { + kind: "ok", + value: undefined, + } + : { + kind: "err", + value: getFoundryValidationIssues(result), + }, }; }, validateActionDraft: async ( @@ -458,7 +499,10 @@ export function createFoundryOntologyBackendAdapter( toFoundryActionTypeName(name), { request: { - options: { mode: "VALIDATE_AND_EXECUTE", returnEdits: "ALL_V2_WITH_DELETIONS" }, + options: { + mode: "VALIDATE_AND_EXECUTE", + returnEdits: "ALL_V2_WITH_DELETIONS", + }, parameters: invocation.parameters, }, overrides: invocation.overrides, @@ -497,7 +541,9 @@ export function createFoundryOntologyBackendAdapter( if (attachmentIdMappings.length === 0) { return undefined; } - return { attachmentIdMappings }; + return { + attachmentIdMappings, + }; }, runQueryFunction: async (name, parameters) => { const queryFunctionType = opts.ir.queryFunctionTypes.find((candidate) => candidate.name === name); @@ -531,9 +577,16 @@ export function createFoundryOntologyBackendAdapter( export type CreateFoundryOntologyBackendOptions< Context extends Record = Record, > = ( - | { client: OntologyClient } - | { createClient: (ir: OntologyIR, context: Context) => OntologyClient | Promise } -) & { users?: FoundryUsersIntegration; live?: boolean }; + | { + client: OntologyClient; + } + | { + createClient: (ir: OntologyIR, context: Context) => OntologyClient | Promise; + } +) & { + users?: FoundryUsersIntegration; + live?: boolean; +}; export function createFoundryOntologyBackend< Context extends Record = Record, diff --git a/packages/foundry-ontology/src/adapter/foundryActionValidation.test.ts b/packages/foundry-ontology/src/adapter/foundryActionValidation.test.ts index 9f0eed44..e440d950 100644 --- a/packages/foundry-ontology/src/adapter/foundryActionValidation.test.ts +++ b/packages/foundry-ontology/src/adapter/foundryActionValidation.test.ts @@ -7,56 +7,103 @@ import { import type { Condition, ConditionValue } from "@osdk/client.unstable"; import type { OntologyClient } from "@party-stack/foundry-client"; -const adminMocks = vi.hoisted(() => ({ listGroupMemberships: vi.fn() })); +const adminMocks = vi.hoisted(() => ({ + listGroupMemberships: vi.fn(), +})); vi.mock("@osdk/foundry.admin", async (importOriginal) => { const original = await importOriginal(); return { ...original, - GroupMemberships: { ...original.GroupMemberships, list: adminMocks.listGroupMemberships }, + GroupMemberships: { + ...original.GroupMemberships, + list: adminMocks.listGroupMemberships, + }, }; }); function staticString(value: string): ConditionValue { - return { type: "staticValue", staticValue: { type: "string", string: value } }; + return { + type: "staticValue", + staticValue: { + type: "string", + string: value, + }, + }; } function parameter(name: string): ConditionValue { - return { type: "parameterId", parameterId: name }; + return { + type: "parameterId", + parameterId: name, + }; } function currentUserGroupIds(): ConditionValue { return { type: "userProperty", userProperty: { - userId: { type: "currentUser", currentUser: {} }, - propertyValue: { type: "groupIds", groupIds: {} }, + userId: { + type: "currentUser", + currentUser: {}, + }, + propertyValue: { + type: "groupIds", + groupIds: {}, + }, }, }; } function staticStringList(...values: string[]): ConditionValue { - return { type: "staticValue", staticValue: { type: "stringList", stringList: { strings: values } } }; + return { + type: "staticValue", + staticValue: { + type: "stringList", + stringList: { + strings: values, + }, + }, + }; } function equals(left: ConditionValue, right: ConditionValue): Condition { - return { type: "comparison", comparison: { left, operator: "EQUALS", right } }; + return { + type: "comparison", + comparison: { + left, + operator: "EQUALS", + right, + }, + }; } function and(...conditions: Condition[]): Condition { - return { type: "and", and: { conditions } }; + return { + type: "and", + and: { conditions }, + }; } function or(...conditions: Condition[]): Condition { - return { type: "or", or: { conditions } }; + return { + type: "or", + or: { conditions }, + }; } function not(condition: Condition): Condition { - return { type: "not", not: { condition } }; + return { + type: "not", + not: { condition }, + }; } function alwaysTrue(): Condition { - return { type: "true", true: {} }; + return { + type: "true", + true: {}, + }; } function knownFalse(): Condition { @@ -68,7 +115,10 @@ function unknown(): Condition { } function criteria(rules: Record): FoundrySubmissionCriterion[] { - return Object.entries(rules).map(([failureMessage, condition]) => ({ condition, failureMessage })); + return Object.entries(rules).map(([failureMessage, condition]) => ({ + condition, + failureMessage, + })); } function validate(rules: Record) { @@ -86,9 +136,16 @@ describe("validateFoundryActionDraftCriteria", () => { }); it("treats an AND as impossible when any branch is known false", async () => { - await expect(validate({ "AND cannot pass": and(knownFalse(), unknown()) })).resolves.toEqual({ + await expect( + validate({ + "AND cannot pass": and(knownFalse(), unknown()), + }) + ).resolves.toEqual({ certain: true, - value: { kind: "err", value: [{ message: "Impossible submission criterion: AND cannot pass" }] }, + value: { + kind: "err", + value: [{ message: "Impossible submission criterion: AND cannot pass" }], + }, }); }); @@ -100,21 +157,34 @@ describe("validateFoundryActionDraftCriteria", () => { }) ).resolves.toEqual({ certain: true, - value: { kind: "err", value: [{ message: "Impossible submission criterion: OR cannot pass" }] }, + value: { + kind: "err", + value: [{ message: "Impossible submission criterion: OR cannot pass" }], + }, }); }); it("does not treat NOT of an uncertain condition as impossible", async () => { await expect( - validate({ "NOT might pass": not(unknown()), "NOT cannot pass": not(alwaysTrue()) }) + validate({ + "NOT might pass": not(unknown()), + "NOT cannot pass": not(alwaysTrue()), + }) ).resolves.toEqual({ certain: true, - value: { kind: "err", value: [{ message: "Impossible submission criterion: NOT cannot pass" }] }, + value: { + kind: "err", + value: [{ message: "Impossible submission criterion: NOT cannot pass" }], + }, }); }); it("returns uncertain when no criterion is provably impossible", async () => { - await expect(validate({ "Only administrators may submit.": unknown() })).resolves.toEqual({ + await expect( + validate({ + "Only administrators may submit.": unknown(), + }) + ).resolves.toEqual({ certain: false, }); }); @@ -123,7 +193,9 @@ describe("validateFoundryActionDraftCriteria", () => { await expect( validateFoundryActionDraftCriteria({ client: {} as OntologyClient, - criteria: criteria({ "A value is required.": unknown() }), + criteria: criteria({ + "A value is required.": unknown(), + }), userId: "user-1", parameters: {}, knownParameters: new Set(["formInput"]), @@ -139,8 +211,13 @@ describe("validateFoundryActionDraftCriteria", () => { it("exhausts transitive group-membership pages for the context user", async () => { adminMocks.listGroupMemberships - .mockResolvedValueOnce({ data: [{ groupId: "regular-users" }], nextPageToken: "next-page" }) - .mockResolvedValueOnce({ data: [{ groupId: "another-group" }] }); + .mockResolvedValueOnce({ + data: [{ groupId: "regular-users" }], + nextPageToken: "next-page", + }) + .mockResolvedValueOnce({ + data: [{ groupId: "another-group" }], + }); await expect( validate({ @@ -157,7 +234,11 @@ describe("validateFoundryActionDraftCriteria", () => { certain: true, value: { kind: "err", - value: [{ message: "Impossible submission criterion: Only administrators may submit." }], + value: [ + { + message: "Impossible submission criterion: Only administrators may submit.", + }, + ], }, }); expect(adminMocks.listGroupMemberships).toHaveBeenNthCalledWith(1, expect.anything(), "user-1", { @@ -195,6 +276,11 @@ describe("getFoundryValidationIssues", () => { }, }, }) - ).toEqual([{ message: "Enter a valid email address.", path: ["email"] }]); + ).toEqual([ + { + message: "Enter a valid email address.", + path: ["email"], + }, + ]); }); }); diff --git a/packages/foundry-ontology/src/adapter/foundryActionValidation.ts b/packages/foundry-ontology/src/adapter/foundryActionValidation.ts index 6111e2a1..a0d8f7f3 100644 --- a/packages/foundry-ontology/src/adapter/foundryActionValidation.ts +++ b/packages/foundry-ontology/src/adapter/foundryActionValidation.ts @@ -46,7 +46,10 @@ export async function loadFoundrySubmissionCriteria(options: { toFoundryActionTypeName(options.actionTypeName) ); const response = await bulkLoadOntologyEntities( - { ...options.client, servicePath: "ontology-metadata/api" }, + { + ...options.client, + servicePath: "ontology-metadata/api", + }, options.userId, { actionTypes: [{ rid: actionType.rid }], @@ -61,7 +64,10 @@ export async function loadFoundrySubmissionCriteria(options: { const loaded = response.actionTypes[0]; invariant(loaded, `Foundry action type "${options.actionTypeName}" was not returned by metadata.`); return Object.values(loaded.actionType.actionTypeLogic.validation.actionTypeLevelValidation.rules).map( - (rule) => ({ condition: rule.condition, failureMessage: rule.displayMetadata.failureMessage }) + (rule) => ({ + condition: rule.condition, + failureMessage: rule.displayMetadata.failureMessage, + }) ); } @@ -107,7 +113,11 @@ async function loadGroupIds(client: OntologyClient, userId: string): Promise - GroupMemberships.list(client, userId, { pageSize, pageToken, transitive: true }), + GroupMemberships.list(client, userId, { + pageSize, + pageToken, + transitive: true, + }), (page) => page.nextPageToken, (page) => page.data.map((membership) => membership.groupId), 1_000 @@ -342,7 +352,12 @@ export async function validateFoundryActionDraftCriteria(options: { .map(({ criterion }) => ({ message: `Impossible submission criterion: ${criterion.failureMessage}`, })); - return errors.length > 0 ? certain({ kind: "err", value: errors }) : uncertain(); + return errors.length > 0 + ? certain({ + kind: "err", + value: errors, + }) + : uncertain(); } function collectRegexIssue( @@ -351,7 +366,10 @@ function collectRegexIssue( path: readonly (string | number)[] ): void { if (constraint.configuredFailureMessage) { - issues.push({ message: constraint.configuredFailureMessage, path }); + issues.push({ + message: constraint.configuredFailureMessage, + path, + }); } } diff --git a/packages/foundry-ontology/src/adapter/foundryCodec.test.ts b/packages/foundry-ontology/src/adapter/foundryCodec.test.ts index 2f3e8144..5146212d 100644 --- a/packages/foundry-ontology/src/adapter/foundryCodec.test.ts +++ b/packages/foundry-ontology/src/adapter/foundryCodec.test.ts @@ -52,13 +52,24 @@ describe("createFoundryCodec", () => { expect( codec.decodeObject("Document", { id: "doc-1", - file: { rid: "ri.attachments.main.attachment.1" }, + file: { + rid: "ri.attachments.main.attachment.1", + }, optionalFile: {}, }) - ).toEqual({ id: "doc-1", file: { id: "ri.attachments.main.attachment.1" }, optionalFile: undefined }); - expect(() => codec.decodeObject("Document", { id: "doc-2", file: {} })).toThrow( - "Invalid required Foundry attachment value" - ); + ).toEqual({ + id: "doc-1", + file: { + id: "ri.attachments.main.attachment.1", + }, + optionalFile: undefined, + }); + expect(() => + codec.decodeObject("Document", { + id: "doc-2", + file: {}, + }) + ).toThrow("Invalid required Foundry attachment value"); }); it("decodes media references into serializable attachment pointers", () => { @@ -75,13 +86,25 @@ describe("createFoundryCodec", () => { { name: "file", displayName: "File", - type: o.attachment({ meta: { foundry: { kind: "media" } } }), + type: o.attachment({ + meta: { + foundry: { + kind: "media", + }, + }, + }), }, { name: "optionalMedia", displayName: "Optional media", type: o.optional({ - type: o.attachment({ meta: { foundry: { kind: "media" } } }), + type: o.attachment({ + meta: { + foundry: { + kind: "media", + }, + }, + }), }), }, ], @@ -108,13 +131,28 @@ describe("createFoundryCodec", () => { type: "image/png", }; - expect(codec.decodeObject("Document", { id: "doc-1", file: reference, optionalMedia: {} })).toEqual({ + expect( + codec.decodeObject("Document", { + id: "doc-1", + file: reference, + optionalMedia: {}, + }) + ).toEqual({ id: "doc-1", file: attachment, optionalMedia: undefined, }); - expect(codec.encodeValue(o.attachment({ meta: { foundry: { kind: "media" } } }), attachment)).toEqual( - reference - ); + expect( + codec.encodeValue( + o.attachment({ + meta: { + foundry: { + kind: "media", + }, + }, + }), + attachment + ) + ).toEqual(reference); }); }); diff --git a/packages/foundry-ontology/src/adapter/foundryCodec.ts b/packages/foundry-ontology/src/adapter/foundryCodec.ts index 1e2721f2..00da580c 100644 --- a/packages/foundry-ontology/src/adapter/foundryCodec.ts +++ b/packages/foundry-ontology/src/adapter/foundryCodec.ts @@ -25,7 +25,9 @@ export interface FoundryCodec { export function createFoundryCodec( ir: OntologyIR, - options?: { resolveMediaReference?: (id: string) => MediaReference | undefined } + options?: { + resolveMediaReference?: (id: string) => MediaReference | undefined; + } ): FoundryCodec { const objectTypes = new Map(ir.objectTypes.map((objectType) => [objectType.name, objectType])); const namedTypes = new Map(ir.types.map((type) => [type.name, type.type])); @@ -51,7 +53,10 @@ export function createFoundryCodec( const decodeValue = ( type: TypeDef, value: unknown, - context: { editHistory: boolean; optional: boolean } = { editHistory: false, optional: false } + context: { editHistory: boolean; optional: boolean } = { + editHistory: false, + optional: false, + } ): unknown => { const resolvedType = resolveType(type); @@ -63,7 +68,10 @@ export function createFoundryCodec( ) { return undefined; } - return decodeValue(resolvedType.value.type, value, { ...context, optional: true }); + return decodeValue(resolvedType.value.type, value, { + ...context, + optional: true, + }); } if (value === undefined || value === null) { @@ -136,7 +144,12 @@ export function createFoundryCodec( const property = objectType.properties.find((candidate) => candidate.name === key); return [ key, - property ? decodeValue(property.type, value, { editHistory, optional: false }) : value, + property + ? decodeValue(property.type, value, { + editHistory, + optional: false, + }) + : value, ]; }) ); @@ -270,7 +283,10 @@ function unwrapEditHistoryValue(type: TypeDef, value: unknown): unknown { value ); if (match) { - return { lat: Number(match[1]), lon: Number(match[2]) }; + return { + lat: Number(match[1]), + lon: Number(match[2]), + }; } } if ( @@ -330,7 +346,10 @@ function decodeMediaReference(value: unknown): unknown { if (!decodeFoundryMediaId(id)) { return undefined; } - return { id, type: mediaReference.mimeType }; + return { + id, + type: mediaReference.mimeType, + }; } function encodeAttachment( diff --git a/packages/foundry-ontology/src/adapter/foundryMediaId.ts b/packages/foundry-ontology/src/adapter/foundryMediaId.ts index 1bb0bae4..4efea573 100644 --- a/packages/foundry-ontology/src/adapter/foundryMediaId.ts +++ b/packages/foundry-ontology/src/adapter/foundryMediaId.ts @@ -29,7 +29,11 @@ export function decodeFoundryMediaId(value: string): FoundryMediaId | undefined ) { return; } - return { mediaSetRid, mediaSetViewRid, mediaItemRid }; + return { + mediaSetRid, + mediaSetViewRid, + mediaItemRid, + }; } export function mediaReferenceToFoundryMediaId(value: MediaReference): string { @@ -37,5 +41,11 @@ export function mediaReferenceToFoundryMediaId(value: MediaReference): string { } export function foundryMediaIdToReference(id: FoundryMediaId, mimeType: string): MediaReference { - return { mimeType, reference: { type: "mediaSetViewItem", mediaSetViewItem: id } }; + return { + mimeType, + reference: { + type: "mediaSetViewItem", + mediaSetViewItem: id, + }, + }; } diff --git a/packages/foundry-ontology/src/adapter/objectCollectionOptions.test.ts b/packages/foundry-ontology/src/adapter/objectCollectionOptions.test.ts index c0c36645..b29da9df 100644 --- a/packages/foundry-ontology/src/adapter/objectCollectionOptions.test.ts +++ b/packages/foundry-ontology/src/adapter/objectCollectionOptions.test.ts @@ -14,8 +14,13 @@ const mockState = vi.hoisted(() => ({ })); vi.mock("@osdk/foundry.ontologies", () => ({ - OntologyObjectsV2: { get: mockState.getObject, search: mockState.search }, - ObjectTypesV2: { getEditsHistory: mockState.getEditsHistory }, + OntologyObjectsV2: { + get: mockState.getObject, + search: mockState.search, + }, + ObjectTypesV2: { + getEditsHistory: mockState.getEditsHistory, + }, })); vi.mock("@party-stack/foundry-object-set-watcher", () => ({ @@ -53,7 +58,10 @@ function createSyncHarness( | undefined; const begin = vi.fn(() => { if (pendingTransaction) throw new Error("A sync transaction is already active."); - pendingTransaction = { writes: [], collectionMetadata: new Map() }; + pendingTransaction = { + writes: [], + collectionMetadata: new Map(), + }; }); const commit = vi.fn(() => { if (!pendingTransaction) throw new Error("No sync transaction is active."); @@ -111,7 +119,11 @@ function createSyncHarness( delete: vi.fn(), list: () => [...collectionMetadata].map(([key, value]) => ({ key, value })), }, - row: { get: vi.fn(), set: vi.fn(), delete: vi.fn() }, + row: { + get: vi.fn(), + set: vi.fn(), + delete: vi.fn(), + }, }, truncate, write: (mutation: Record) => { @@ -189,7 +201,10 @@ describe("objectCollectionOptions", () => { expect(mockState.getObjectSetWatcherManager).not.toHaveBeenCalled(); await harness.loadSubset({}); - expect(harness.syncedData.get(1)).toEqual({ employeeId: 1, name: "Ada" }); + expect(harness.syncedData.get(1)).toEqual({ + employeeId: 1, + name: "Ada", + }); harness.cleanup(); expect(mockState.unsubscribe).not.toHaveBeenCalled(); @@ -204,7 +219,13 @@ describe("objectCollectionOptions", () => { actionTypeRid: "action-1", userId: "user-1", timestamp: "2099-03-12T12:00:00.000Z", - edit: { type: "createEdit", properties: { employeeId: 2, name: "Grace" } }, + edit: { + type: "createEdit", + properties: { + employeeId: 2, + name: "Grace", + }, + }, }, ], nextPageToken: undefined, @@ -213,13 +234,19 @@ describe("objectCollectionOptions", () => { await expect(harness.utils.awaitOperationId("op-non-live")).resolves.toBeUndefined(); expect(mockState.getObjectSetWatcherManager).not.toHaveBeenCalled(); - expect(harness.syncedData.get(2)).toEqual({ employeeId: 2, name: "Grace" }); + expect(harness.syncedData.get(2)).toEqual({ + employeeId: 2, + name: "Grace", + }); harness.cleanup(); }); it("keeps identical abortable subset loads independent", async () => { - const requests: Array<{ resolve: (value: unknown) => void; reject: (reason: unknown) => void }> = []; + const requests: Array<{ + resolve: (value: unknown) => void; + reject: (reason: unknown) => void; + }> = []; const fetchImpl = vi.fn((_input: RequestInfo | URL, init?: RequestInit) => { return new Promise((resolve, reject) => { const request = { resolve, reject }; @@ -244,8 +271,14 @@ describe("objectCollectionOptions", () => { const controllerA = new AbortController(); const controllerB = new AbortController(); - const requestA = harness.loadSubset({ limit: 1, signal: controllerA.signal }); - const requestB = harness.loadSubset({ limit: 1, signal: controllerB.signal }); + const requestA = harness.loadSubset({ + limit: 1, + signal: controllerA.signal, + }); + const requestB = harness.loadSubset({ + limit: 1, + signal: controllerB.signal, + }); expect(requestA).not.toBe(requestB); expect(mockState.search).toHaveBeenCalledTimes(2); @@ -258,7 +291,9 @@ describe("objectCollectionOptions", () => { await expect(requestA).rejects.toMatchObject({ name: "AbortError" }); await expect(requestB).resolves.toBeUndefined(); - expect(harness.syncedData.get("employee-b")).toMatchObject({ name: "Request B" }); + expect(harness.syncedData.get("employee-b")).toMatchObject({ + name: "Request B", + }); harness.cleanup(); }); @@ -274,14 +309,23 @@ describe("objectCollectionOptions", () => { edit: { type: "modifyEdit", previousProperties: {}, - properties: { employeeId: 1, name: "Updated Employee" }, + properties: { + employeeId: 1, + name: "Updated Employee", + }, }, }, ], nextPageToken: undefined, }); - const harness = createSyncHarness([{ employeeId: 1, name: "Before", department: "Engineering" }]); + const harness = createSyncHarness([ + { + employeeId: 1, + name: "Before", + department: "Engineering", + }, + ]); mockState.subscribeCallback?.({ type: "state", status: "open" }); @@ -309,7 +353,10 @@ describe("objectCollectionOptions", () => { actionTypeRid: "action-1", userId: "user-1", timestamp: "2099-03-12T12:01:00.000Z", - edit: { type: "deleteEdit", previousProperties: {} }, + edit: { + type: "deleteEdit", + previousProperties: {}, + }, }, ], nextPageToken: undefined, @@ -333,12 +380,17 @@ describe("objectCollectionOptions", () => { mockState.getEditsHistory.mockResolvedValue({ data: [ { - objectPrimaryKey: { employeeId: 2 }, + objectPrimaryKey: { + employeeId: 2, + }, operationId: "op-hydrated-delete", actionTypeRid: "action-1", userId: "user-1", timestamp: "2099-03-12T12:01:00.000Z", - edit: { type: "deleteEdit", previousProperties: {} }, + edit: { + type: "deleteEdit", + previousProperties: {}, + }, }, ], nextPageToken: undefined, @@ -347,13 +399,22 @@ describe("objectCollectionOptions", () => { // Persisted on-demand hydration happens after the source sync closure // captures its initial state. - harness.syncedData.set(2, { employeeId: 2, name: "Persisted stale object" }); - mockState.subscribeCallback?.({ type: "state", status: "open" }); + harness.syncedData.set(2, { + employeeId: 2, + name: "Persisted stale object", + }); + mockState.subscribeCallback?.({ + type: "state", + status: "open", + }); await vi.waitFor(() => { expect(harness.syncedData.has(2)).toBe(false); }); - expect(harness.writes).toContainEqual({ type: "delete", primaryKey: 2 }); + expect(harness.writes).toContainEqual({ + type: "delete", + primaryKey: 2, + }); harness.cleanup(); }); @@ -366,7 +427,13 @@ describe("objectCollectionOptions", () => { actionTypeRid: "action-1", userId: "user-1", timestamp: "2099-03-12T12:00:00.000Z", - edit: { type: "createEdit", properties: { employeeId: 5, name: "Employee Five" } }, + edit: { + type: "createEdit", + properties: { + employeeId: 5, + name: "Employee Five", + }, + }, }, ], nextPageToken: undefined, @@ -379,7 +446,13 @@ describe("objectCollectionOptions", () => { actionTypeRid: "action-1", userId: "user-1", timestamp: "2099-03-12T12:00:00.000Z", - edit: { type: "createEdit", properties: { employeeId: 5, name: "Employee Five" } }, + edit: { + type: "createEdit", + properties: { + employeeId: 5, + name: "Employee Five", + }, + }, }, ], nextPageToken: undefined, @@ -392,7 +465,10 @@ describe("objectCollectionOptions", () => { actionTypeRid: "action-1", userId: "user-1", timestamp: "2099-03-12T12:01:00.000Z", - edit: { type: "deleteEdit", previousProperties: {} }, + edit: { + type: "deleteEdit", + previousProperties: {}, + }, }, ], nextPageToken: undefined, @@ -408,7 +484,13 @@ describe("objectCollectionOptions", () => { mockState.subscribeCallback?.({ type: "change", - updates: [{ type: "object", state: "REMOVED", object: { employeeId: 5 } }], + updates: [ + { + type: "object", + state: "REMOVED", + object: { employeeId: 5 }, + }, + ], }); await vi.waitFor(() => { @@ -433,7 +515,10 @@ describe("objectCollectionOptions", () => { edit: { type: "modifyEdit", previousProperties: {}, - properties: { employeeId: 1, name: "Employee One" }, + properties: { + employeeId: 1, + name: "Employee One", + }, }, }, ], @@ -450,7 +535,10 @@ describe("objectCollectionOptions", () => { edit: { type: "modifyEdit", previousProperties: {}, - properties: { employeeId: 1, name: "Employee One" }, + properties: { + employeeId: 1, + name: "Employee One", + }, }, }, { @@ -462,7 +550,10 @@ describe("objectCollectionOptions", () => { edit: { type: "modifyEdit", previousProperties: {}, - properties: { employeeId: 2, name: "Employee Two" }, + properties: { + employeeId: 2, + name: "Employee Two", + }, }, }, ], @@ -485,8 +576,14 @@ describe("objectCollectionOptions", () => { await vi.waitFor(() => { expect(mockState.getEditsHistory).toHaveBeenCalledTimes(2); - expect(harness.syncedData.get(1)).toEqual({ employeeId: 1, name: "Employee One" }); - expect(harness.syncedData.get(2)).toEqual({ employeeId: 2, name: "Employee Two" }); + expect(harness.syncedData.get(1)).toEqual({ + employeeId: 1, + name: "Employee One", + }); + expect(harness.syncedData.get(2)).toEqual({ + employeeId: 2, + name: "Employee Two", + }); }); expect(mockState.getObject).not.toHaveBeenCalled(); @@ -505,7 +602,10 @@ describe("objectCollectionOptions", () => { edit: { type: "modifyEdit", previousProperties: {}, - properties: { employeeId: 1, name: "Employee One" }, + properties: { + employeeId: 1, + name: "Employee One", + }, }, }; const downtimeEntry = { @@ -517,11 +617,17 @@ describe("objectCollectionOptions", () => { edit: { type: "modifyEdit", previousProperties: {}, - properties: { employeeId: 2, name: "Employee Two" }, + properties: { + employeeId: 2, + name: "Employee Two", + }, }, }; const collectionMetadata = new Map(); - mockState.getEditsHistory.mockResolvedValue({ data: [firstEntry], nextPageToken: undefined }); + mockState.getEditsHistory.mockResolvedValue({ + data: [firstEntry], + nextPageToken: undefined, + }); const first = createSyncHarness([], { collectionMetadata }); mockState.subscribeCallback?.({ type: "state", status: "open" }); @@ -534,9 +640,14 @@ describe("objectCollectionOptions", () => { timestamp: string; seenEntryKeysAtTimestamp: string[]; }; - expect(persistedCursor).toMatchObject({ version: 1, timestamp }); + expect(persistedCursor).toMatchObject({ + version: 1, + timestamp, + }); expect(persistedCursor.seenEntryKeysAtTimestamp).toHaveLength(1); - expect(first.transactions.at(-1)).toMatchObject({ writes: [{ type: "insert", primaryKey: 1 }] }); + expect(first.transactions.at(-1)).toMatchObject({ + writes: [{ type: "insert", primaryKey: 1 }], + }); expect(first.transactions.at(-1)?.collectionMetadata.size).toBe(1); const reloadedObjects = [...first.syncedData.values()]; @@ -555,11 +666,17 @@ describe("objectCollectionOptions", () => { }); expect(second.writes).toEqual([{ type: "insert", primaryKey: 2 }]); expect(mockState.getEditsHistory.mock.calls[0]?.[3]).toMatchObject({ - filters: { type: "timestampFilter", startTime: timestamp }, + filters: { + type: "timestampFilter", + startTime: timestamp, + }, }); expect( - ([...collectionMetadata.values()][0] as { seenEntryKeysAtTimestamp: string[] }) - .seenEntryKeysAtTimestamp + ( + [...collectionMetadata.values()][0] as { + seenEntryKeysAtTimestamp: string[]; + } + ).seenEntryKeysAtTimestamp ).toHaveLength(2); second.cleanup(); @@ -575,7 +692,10 @@ describe("objectCollectionOptions", () => { actionTypeRid: "action-1", userId: "user-1", timestamp, - edit: { type: "deleteEdit", previousProperties: {} }, + edit: { + type: "deleteEdit", + previousProperties: {}, + }, }, ], nextPageToken: undefined, @@ -587,10 +707,23 @@ describe("objectCollectionOptions", () => { await vi.waitFor(() => { expect(harness.transactions).toHaveLength(2); }); - expect(harness.writes).toEqual([{ type: "delete", primaryKey: 404 }]); - expect(harness.transactions.at(-1)?.writes).toEqual([{ type: "delete", primaryKey: 404 }]); + expect(harness.writes).toEqual([ + { + type: "delete", + primaryKey: 404, + }, + ]); + expect(harness.transactions.at(-1)?.writes).toEqual([ + { + type: "delete", + primaryKey: 404, + }, + ]); expect(harness.transactions.at(-1)?.collectionMetadata.size).toBe(1); - expect([...harness.collectionMetadata.values()][0]).toMatchObject({ version: 1, timestamp }); + expect([...harness.collectionMetadata.values()][0]).toMatchObject({ + version: 1, + timestamp, + }); harness.cleanup(); }); @@ -675,7 +808,10 @@ describe("objectCollectionOptions", () => { properties: { employeeId: 7, attachments: [ - { type: "attachment", attachment: "ri.attachments.main.attachment.7" }, + { + type: "attachment", + attachment: "ri.attachments.main.attachment.7", + }, ], name: "Employee Seven", }, @@ -699,7 +835,13 @@ describe("objectCollectionOptions", () => { name: "attachments", displayName: "Attachments", type: o.list({ - elementType: o.attachment({ meta: { foundry: { kind: "attachment" } } }), + elementType: o.attachment({ + meta: { + foundry: { + kind: "attachment", + }, + }, + }), }), }, ], @@ -750,7 +892,13 @@ describe("objectCollectionOptions", () => { timestamp: "2099-03-12T12:00:00.000Z", edit: { type: "createEdit", - properties: { employeeId: 8, logoMedia: { type: "mediaReference", mediaReference } }, + properties: { + employeeId: 8, + logoMedia: { + type: "mediaReference", + mediaReference, + }, + }, }, }, ], @@ -770,7 +918,13 @@ describe("objectCollectionOptions", () => { name: "logoMedia", displayName: "Logo media", type: o.optional({ - type: o.attachment({ meta: { foundry: { kind: "media" } } }), + type: o.attachment({ + meta: { + foundry: { + kind: "media", + }, + }, + }), }), }, ], @@ -865,7 +1019,12 @@ describe("objectCollectionOptions", () => { mockState.getEditsHistory.mockResolvedValue({ data: [ { - objectPrimaryKey: { employeeId: { type: "stringValue", value: "employee-4" } }, + objectPrimaryKey: { + employeeId: { + type: "stringValue", + value: "employee-4", + }, + }, operationId: "op-1", actionTypeRid: "action-1", userId: "user-1", @@ -873,7 +1032,10 @@ describe("objectCollectionOptions", () => { edit: { type: "createEdit", properties: { - employeeId: { type: "stringValue", value: "employee-4" }, + employeeId: { + type: "stringValue", + value: "employee-4", + }, name: "Employee Four", }, }, @@ -920,7 +1082,10 @@ describe("objectCollectionOptions", () => { it("triggers history catch-up on ADDED_OR_UPDATED watcher events", async () => { mockState.getEditsHistory - .mockResolvedValueOnce({ data: [], nextPageToken: undefined }) + .mockResolvedValueOnce({ + data: [], + nextPageToken: undefined, + }) .mockResolvedValueOnce({ data: [ { @@ -929,7 +1094,13 @@ describe("objectCollectionOptions", () => { actionTypeRid: "action-1", userId: "user-1", timestamp: "2099-03-12T12:05:00.000Z", - edit: { type: "createEdit", properties: { employeeId: 10, name: "New Employee" } }, + edit: { + type: "createEdit", + properties: { + employeeId: 10, + name: "New Employee", + }, + }, }, ], nextPageToken: undefined, @@ -956,7 +1127,10 @@ describe("objectCollectionOptions", () => { await vi.waitFor(() => { expect(mockState.getEditsHistory).toHaveBeenCalledTimes(2); - expect(harness.syncedData.get(10)).toEqual({ employeeId: 10, name: "New Employee" }); + expect(harness.syncedData.get(10)).toEqual({ + employeeId: 10, + name: "New Employee", + }); }); harness.cleanup(); @@ -984,12 +1158,21 @@ describe("objectCollectionOptions", () => { await vi.waitFor(() => { expect(mockState.getEditsHistory).toHaveBeenCalledTimes(1); - expect(harness.syncedData.get(12)).toEqual({ employeeId: 12, name: "Direct Employee" }); + expect(harness.syncedData.get(12)).toEqual({ + employeeId: 12, + name: "Direct Employee", + }); }); mockState.subscribeCallback?.({ type: "change", - updates: [{ type: "object", state: "REMOVED", object: { employeeId: 12 } }], + updates: [ + { + type: "object", + state: "REMOVED", + object: { employeeId: 12 }, + }, + ], }); await vi.waitFor(() => { @@ -1008,18 +1191,30 @@ describe("objectCollectionOptions", () => { it("fully reconciles after websocket reconnect when edit history is unavailable", async () => { const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); mockState.getEditsHistory.mockRejectedValue(new Error("Edit history is not enabled")); - mockState.search.mockResolvedValue({ data: [], nextPageToken: undefined }); + mockState.search.mockResolvedValue({ + data: [], + nextPageToken: undefined, + }); const harness = createSyncHarness(); - mockState.subscribeCallback?.({ type: "state", status: "open" }); + mockState.subscribeCallback?.({ + type: "state", + status: "open", + }); await vi.waitFor(() => { expect(mockState.search).toHaveBeenCalledOnce(); }); // Simulate stale persisted state becoming visible while the watcher is // disconnected and therefore unable to report its deletion. - harness.syncedData.set(30, { employeeId: 30, name: "Stale Employee" }); - mockState.subscribeCallback?.({ type: "state", status: "open" }); + harness.syncedData.set(30, { + employeeId: 30, + name: "Stale Employee", + }); + mockState.subscribeCallback?.({ + type: "state", + status: "open", + }); await vi.waitFor(() => { expect(mockState.search).toHaveBeenCalledTimes(2); @@ -1044,7 +1239,10 @@ describe("objectCollectionOptions", () => { timestamp: "2099-03-12T12:10:00.000Z", edit: { type: "createEdit", - properties: { employeeId: 20, name: "Uncommitted History Employee" }, + properties: { + employeeId: 20, + name: "Uncommitted History Employee", + }, }, }, ], @@ -1104,7 +1302,9 @@ describe("objectCollectionOptions", () => { const commitApplied = new Promise((resolve) => { resolveCommit = resolve; }); - const harness = createSyncHarness([], { commitReceipt: () => commitApplied }); + const harness = createSyncHarness([], { + commitReceipt: () => commitApplied, + }); const operationPromise = harness.utils.awaitOperationId("op-12"); await vi.waitFor(() => { @@ -1123,7 +1323,10 @@ describe("objectCollectionOptions", () => { }); await expect(operationPromise).resolves.toBeUndefined(); - expect(harness.syncedData.get(13)).toEqual({ employeeId: 13, name: "Direct Operation Employee" }); + expect(harness.syncedData.get(13)).toEqual({ + employeeId: 13, + name: "Direct Operation Employee", + }); expect(mockState.getEditsHistory).toHaveBeenCalledTimes(1); resolveCommit(); @@ -1141,7 +1344,13 @@ describe("objectCollectionOptions", () => { actionTypeRid: "action-1", userId: "user-1", timestamp: "2099-03-12T12:06:00.000Z", - edit: { type: "createEdit", properties: { employeeId: 11, name: "Operation Eleven" } }, + edit: { + type: "createEdit", + properties: { + employeeId: 11, + name: "Operation Eleven", + }, + }, }, ], nextPageToken: undefined, @@ -1151,12 +1360,17 @@ describe("objectCollectionOptions", () => { const commitApplied = new Promise((resolve) => { resolveCommit = resolve; }); - const harness = createSyncHarness([], { commitReceipt: () => commitApplied }); + const harness = createSyncHarness([], { + commitReceipt: () => commitApplied, + }); await expect(harness.utils.awaitOperationId("op-11")).resolves.toBeUndefined(); expect(mockState.getEditsHistory).toHaveBeenCalledTimes(1); - expect(harness.syncedData.get(11)).toEqual({ employeeId: 11, name: "Operation Eleven" }); + expect(harness.syncedData.get(11)).toEqual({ + employeeId: 11, + name: "Operation Eleven", + }); resolveCommit(); await commitApplied; @@ -1179,7 +1393,11 @@ describe("objectCollectionOptions", () => { }); const searchOptions = mockState.search.mock.calls[0]?.[3] as - | { where?: unknown; select?: string[]; selectV2?: unknown[] } + | { + where?: unknown; + select?: string[]; + selectV2?: unknown[]; + } | undefined; expect(searchOptions).toMatchObject({ where: { @@ -1190,11 +1408,17 @@ describe("objectCollectionOptions", () => { propertyIdentifier: { type: "property", apiName: "status" }, value: "open", }, - { type: "gt", propertyIdentifier: { type: "property", apiName: "priority" }, value: 5 }, + { + type: "gt", + propertyIdentifier: { type: "property", apiName: "priority" }, + value: 5, + }, ], }, }); - expect(searchOptions).toMatchObject({ select: ["employeeId", "name", "status", "priority"] }); + expect(searchOptions).toMatchObject({ + select: ["employeeId", "name", "status", "priority"], + }); expect(searchOptions?.selectV2).toEqual([]); expect(harness.syncedData.has(2)).toBe(true); @@ -1217,7 +1441,10 @@ describe("objectCollectionOptions", () => { orderBy: [ { expression: new IR.PropRef(["priority"]), - compareOptions: { direction: "asc", nulls: "last" }, + compareOptions: { + direction: "asc", + nulls: "last", + }, }, ], offset: 2, @@ -1226,7 +1453,9 @@ describe("objectCollectionOptions", () => { expect(mockState.search.mock.calls[0]?.[3]).toMatchObject({ pageSize: 4, - orderBy: { fields: [{ field: "priority", direction: "asc" }] }, + orderBy: { + fields: [{ field: "priority", direction: "asc" }], + }, }); expect([...harness.syncedData.keys()]).toEqual([3, 4]); diff --git a/packages/foundry-ontology/src/adapter/objectCollectionOptions.ts b/packages/foundry-ontology/src/adapter/objectCollectionOptions.ts index 0e6128ec..d48bd3a6 100644 --- a/packages/foundry-ontology/src/adapter/objectCollectionOptions.ts +++ b/packages/foundry-ontology/src/adapter/objectCollectionOptions.ts @@ -106,7 +106,10 @@ function deserializeEditHistoryCursor(value: unknown): EditHistoryCursor | undef ) { return undefined; } - return { timestamp: value.timestamp, seenEntryKeysAtTimestamp: new Set(value.seenEntryKeysAtTimestamp) }; + return { + timestamp: value.timestamp, + seenEntryKeysAtTimestamp: new Set(value.seenEntryKeysAtTimestamp), + }; } function editHistoryCursorsEqual(left: EditHistoryCursor, right: EditHistoryCursor): boolean { @@ -166,7 +169,9 @@ function getPrimaryKeyValue( if (values.length !== 1) { throw new Error("Foundry object collections currently only support single-field primary keys."); } - const value = decodeEditObject({ [primaryKeyProperty]: values[0] })[primaryKeyProperty]; + const value = decodeEditObject({ + [primaryKeyProperty]: values[0], + })[primaryKeyProperty]; if (typeof value !== "string" && typeof value !== "number") { throw new Error("Foundry object collections currently only support string or number primary keys."); } @@ -212,7 +217,10 @@ async function fetchFoundryObjects( ? { ...client, fetch: (input: RequestInfo | URL, init?: RequestInit) => - client.fetch(input, { ...init, signal: opts.signal }), + client.fetch(input, { + ...init, + signal: opts.signal, + }), } : client; const results = await AsyncIterable.toArray( @@ -366,7 +374,12 @@ function createSyncConfig( const existing = exists ? params.collection.get(key) : undefined; write({ type: exists ? "update" : "insert", - value: existing ? { ...existing, ...object } : object, + value: existing + ? { + ...existing, + ...object, + } + : object, }); }; @@ -391,10 +404,17 @@ function createSyncConfig( const decodeEditProperties = ( properties: Record, primaryKey: string | number - ): FoundryObject => decodeEditObject({ ...properties, [primaryKeyProperty]: primaryKey }); + ): FoundryObject => + decodeEditObject({ + ...properties, + [primaryKeyProperty]: primaryKey, + }); const decodeWatcherObject = (object: FoundryObject, primaryKey: string | number): FoundryObject => - decodeObject({ ...object, [primaryKeyProperty]: primaryKey }); + decodeObject({ + ...object, + [primaryKeyProperty]: primaryKey, + }); const applyDirectWatcherUpdates = async (updates: ObjectSetUpdate[]): Promise => { const pendingMutations = new Map< @@ -477,7 +497,10 @@ function createSyncConfig( for await (const entry of AsyncIterable.fromPagination( (pageSize, pageToken: string | undefined) => fetchEditHistoryPage({ - filters: { type: "timestampFilter", startTime: nextEditHistoryCursor.timestamp }, + filters: { + type: "timestampFilter", + startTime: nextEditHistoryCursor.timestamp, + }, includeAllPreviousProperties: true, pageSize, pageToken, @@ -567,7 +590,10 @@ function createSyncConfig( begin(); truncate(); for (const object of objects) { - write({ type: "insert", value: object }); + write({ + type: "insert", + value: object, + }); } const receipt = commit(); diff --git a/packages/foundry-ontology/src/config/applyAttachmentConstraintOverrides.ts b/packages/foundry-ontology/src/config/applyAttachmentConstraintOverrides.ts index 52c27cb6..8f7cd2b6 100644 --- a/packages/foundry-ontology/src/config/applyAttachmentConstraintOverrides.ts +++ b/packages/foundry-ontology/src/config/applyAttachmentConstraintOverrides.ts @@ -14,13 +14,26 @@ export interface FoundryAttachmentConstraintOverride { function withAttachmentConstraint(type: TypeDef, constraint: AttachmentConstraint): TypeDef { switch (type.kind) { case "attachment": - return { ...type, value: { ...type.value, constraint } }; + return { + ...type, + value: { + ...type.value, + constraint, + }, + }; case "optional": - return { ...type, value: { type: withAttachmentConstraint(type.value.type, constraint) } }; + return { + ...type, + value: { + type: withAttachmentConstraint(type.value.type, constraint), + }, + }; case "list": return { ...type, - value: { elementType: withAttachmentConstraint(type.value.elementType, constraint) }, + value: { + elementType: withAttachmentConstraint(type.value.elementType, constraint), + }, }; default: throw new Error( diff --git a/packages/foundry-ontology/src/config/connectionPull.test.ts b/packages/foundry-ontology/src/config/connectionPull.test.ts index 56413209..015567ba 100644 --- a/packages/foundry-ontology/src/config/connectionPull.test.ts +++ b/packages/foundry-ontology/src/config/connectionPull.test.ts @@ -10,16 +10,34 @@ vi.mock("../installation/createFoundryBackendInstallation.js", () => mocks); import { createFoundryOntologyPullSource } from "./index.js"; -function installation(connections: Array<{ userId: string; state: { status: "active" } }> = []) { +function installation( + connections: Array<{ + userId: string; + state: { status: "active" }; + }> = [] +) { const oauth = vi.fn(() => - Promise.resolve({ userId: "oauth-user", state: { status: "active" as const } }) + Promise.resolve({ + userId: "oauth-user", + state: { status: "active" as const }, + }) ); const apiToken = vi.fn(() => - Promise.resolve({ userId: "token-user", state: { status: "active" as const } }) + Promise.resolve({ + userId: "token-user", + state: { status: "active" as const }, + }) ); return { - connections: { values: () => connections.values() }, - authentication: { signIn: { oauth, apiToken } }, + connections: { + values: () => connections.values(), + }, + authentication: { + signIn: { + oauth, + apiToken, + }, + }, oauth, apiToken, }; @@ -37,12 +55,19 @@ describe("Foundry connection-backed pull", () => { const source = createFoundryOntologyPullSource({ baseUrl: "https://foundry.example", ontologyRid: "ri.ontology.main", - connection: { oauth: { clientId: "client", redirectUrl: "http://127.0.0.1:9876/callback" } }, + connection: { + oauth: { + clientId: "client", + redirectUrl: "http://127.0.0.1:9876/callback", + }, + }, }); await source.createInstallation({ runtime }); - expect(mocks.createFoundryOntologyRoute).toHaveBeenCalledWith({ ontologyId: "ri.ontology.main" }); + expect(mocks.createFoundryOntologyRoute).toHaveBeenCalledWith({ + ontologyId: "ri.ontology.main", + }); expect(mocks.createFoundryBackendInstallation).toHaveBeenCalledWith({ installationId: "foundry-pull:https://foundry.example:ri.ontology.main:api:use-ontologies-read,offline_access,ontology:view-object-type,ontology:view-unredacted-action-type", @@ -66,11 +91,21 @@ describe("Foundry connection-backed pull", () => { }); it("restores the sole active connection", async () => { - const backendInstallation = installation([{ userId: "restored-user", state: { status: "active" } }]); + const backendInstallation = installation([ + { + userId: "restored-user", + state: { status: "active" }, + }, + ]); const source = createFoundryOntologyPullSource({ baseUrl: "https://foundry.example", ontologyRid: "ri.ontology.main", - connection: { oauth: { clientId: "client", redirectUrl: "http://127.0.0.1:9876/callback" } }, + connection: { + oauth: { + clientId: "client", + redirectUrl: "http://127.0.0.1:9876/callback", + }, + }, }); await expect(source.resolveConnection(backendInstallation as never)).resolves.toMatchObject({ @@ -84,7 +119,12 @@ describe("Foundry connection-backed pull", () => { const source = createFoundryOntologyPullSource({ baseUrl: "https://foundry.example", ontologyRid: "ri.ontology.main", - connection: { oauth: { clientId: "client", redirectUrl: "http://127.0.0.1:9876/callback" } }, + connection: { + oauth: { + clientId: "client", + redirectUrl: "http://127.0.0.1:9876/callback", + }, + }, }); await expect(source.resolveConnection(backendInstallation as never)).resolves.toMatchObject({ @@ -98,7 +138,9 @@ describe("Foundry connection-backed pull", () => { const source = createFoundryOntologyPullSource({ baseUrl: "https://foundry.example", ontologyRid: "ri.ontology.main", - connection: { token: "token" }, + connection: { + token: "token", + }, }); await expect(source.resolveConnection(backendInstallation as never)).resolves.toMatchObject({ @@ -110,13 +152,24 @@ describe("Foundry connection-backed pull", () => { it("rejects ambiguous restored users", async () => { const backendInstallation = installation([ - { userId: "one", state: { status: "active" } }, - { userId: "two", state: { status: "active" } }, + { + userId: "one", + state: { status: "active" }, + }, + { + userId: "two", + state: { status: "active" }, + }, ]); const source = createFoundryOntologyPullSource({ baseUrl: "https://foundry.example", ontologyRid: "ri.ontology.main", - connection: { oauth: { clientId: "client", redirectUrl: "http://127.0.0.1:9876/callback" } }, + connection: { + oauth: { + clientId: "client", + redirectUrl: "http://127.0.0.1:9876/callback", + }, + }, }); await expect(source.resolveConnection(backendInstallation as never)).rejects.toThrow( diff --git a/packages/foundry-ontology/src/config/index.test.ts b/packages/foundry-ontology/src/config/index.test.ts index 051c2861..1506e081 100644 --- a/packages/foundry-ontology/src/config/index.test.ts +++ b/packages/foundry-ontology/src/config/index.test.ts @@ -6,7 +6,9 @@ function createSource(options: Partial) return createFoundryOntologyPullSource({ baseUrl: "https://foundry.example", ontologyRid: "ri.ontology.main", - connection: { token: "token" }, + connection: { + token: "token", + }, ...options, }); } @@ -26,7 +28,13 @@ describe("createFoundryOntologyPullSource", () => { { name: "media", displayName: "Media", - type: o.attachment({ meta: { foundry: { kind: "media" } } }), + type: o.attachment({ + meta: { + foundry: { + kind: "media", + }, + }, + }), }, ], }, @@ -41,7 +49,13 @@ describe("createFoundryOntologyPullSource", () => { name: "media", displayName: "Media", type: o.optional({ - type: o.attachment({ meta: { foundry: { kind: "media" } } }), + type: o.attachment({ + meta: { + foundry: { + kind: "media", + }, + }, + }), }), }, ], @@ -51,24 +65,53 @@ describe("createFoundryOntologyPullSource", () => { queryFunctionTypes: [], }; const constraint = { - content: o.AttachmentContentConstraint.image({ mediaTypes: ["image/png", "image/jpeg"] }), + content: o.AttachmentContentConstraint.image({ + mediaTypes: ["image/png", "image/jpeg"], + }), }; const transformed = await createSource({ attachmentConstraints: [ - { target: { kind: "objectProperty", objectType: "Task", property: "media" }, constraint }, { - target: { kind: "actionParameter", actionType: "createTask", parameter: "media" }, + target: { + kind: "objectProperty", + objectType: "Task", + property: "media", + }, + constraint, + }, + { + target: { + kind: "actionParameter", + actionType: "createTask", + parameter: "media", + }, constraint, }, ], }).transformPulledOntology!(ir); expect(transformed.objectTypes[0]?.properties[1]?.type).toEqual( - o.attachment({ constraint, meta: { foundry: { kind: "media" } } }) + o.attachment({ + constraint, + meta: { + foundry: { + kind: "media", + }, + }, + }) ); expect(transformed.actionTypes[0]?.parameters[0]?.type).toEqual( - o.optional({ type: o.attachment({ constraint, meta: { foundry: { kind: "media" } } }) }) + o.optional({ + type: o.attachment({ + constraint, + meta: { + foundry: { + kind: "media", + }, + }, + }), + }) ); }); @@ -103,7 +146,9 @@ describe("createFoundryOntologyPullSource", () => { values: [ { property: ["createdBy"], - value: o.Expression.contextReference({ name: "user" }), + value: o.Expression.contextReference({ + name: "user", + }), }, ], }), @@ -118,8 +163,13 @@ describe("createFoundryOntologyPullSource", () => { objectType: "User", lens: { operations: [ - o.LensOp.move({ from: ["profilePicture"], to: ["avatar"] }), - o.LensOp.select({ properties: ["id", "avatar"] }), + o.LensOp.move({ + from: ["profilePicture"], + to: ["avatar"], + }), + o.LensOp.select({ + properties: ["id", "avatar"], + }), ], }, }, @@ -129,7 +179,11 @@ describe("createFoundryOntologyPullSource", () => { expect(transformed.contextType).toEqual( o.struct({ fields: [ - { name: "user", displayName: "User", type: o.objectReference({ objectType: "User" }) }, + { + name: "user", + displayName: "User", + type: o.objectReference({ objectType: "User" }), + }, ], }) ); diff --git a/packages/foundry-ontology/src/config/index.ts b/packages/foundry-ontology/src/config/index.ts index d29129ce..651d5f6e 100644 --- a/packages/foundry-ontology/src/config/index.ts +++ b/packages/foundry-ontology/src/config/index.ts @@ -37,7 +37,10 @@ export interface CreateFoundryOntologyPullSourceOptions { ontologyRid: string; connection: FoundryOntologyPullConnectionOptions; attachmentConstraints?: FoundryAttachmentConstraintOverride[]; - users?: { objectType: string; lens: Lens }; + users?: { + objectType: string; + lens: Lens; + }; } function addConfiguredUser( @@ -77,9 +80,18 @@ export function createFoundryOntologyPullSource( runtime, connections: { token: options.connection.token, - oauth: options.connection.oauth ? { ...options.connection.oauth, scopes } : undefined, + oauth: options.connection.oauth + ? { + ...options.connection.oauth, + scopes, + } + : undefined, }, - routes: [createFoundryOntologyRoute({ ontologyId: options.ontologyRid })], + routes: [ + createFoundryOntologyRoute({ + ontologyId: options.ontologyRid, + }), + ], }); }, async resolveConnection(installation) { diff --git a/packages/foundry-ontology/src/connection.test.ts b/packages/foundry-ontology/src/connection.test.ts index 12f4c3eb..dbd25918 100644 --- a/packages/foundry-ontology/src/connection.test.ts +++ b/packages/foundry-ontology/src/connection.test.ts @@ -30,12 +30,18 @@ describe("createFoundryConnectionAdapter", () => { baseUrl: "https://foundry.example.com", token, }); - const adapter = await adapterProvider({ installationId: "foundry", runtime: {} as never }); + const adapter = await adapterProvider({ + installationId: "foundry", + runtime: {} as never, + }); const [established] = await adapter.restoreConnections(); if (!established?.session.egress) { throw new Error("Expected Foundry egress wrapper."); } - const egress = established.session.egress({ fetch, createWebSocket }); + const egress = established.session.egress({ + fetch, + createWebSocket, + }); await egress.fetch(new Request("https://foundry.example.com/api/data")); const request = fetch.mock.calls[0]![0] as Request; @@ -49,7 +55,9 @@ describe("createFoundryConnectionAdapter", () => { it("exposes API-token authentication through the connection controller", async () => { const token = foundryToken(); - const adapter = await createFoundryConnectionAdapter({ baseUrl: "https://foundry.example.com" })({ + const adapter = await createFoundryConnectionAdapter({ + baseUrl: "https://foundry.example.com", + })({ installationId: "foundry", runtime: {} as never, }); @@ -62,7 +70,11 @@ describe("createFoundryConnectionAdapter", () => { const connection = await authentication.signIn.apiToken({ token }); expect(connection.state.status).toBe("active"); - expect(connect).toHaveBeenCalledWith(expect.objectContaining({ connection })); + expect(connect).toHaveBeenCalledWith( + expect.objectContaining({ + connection, + }) + ); }); it("composes server-only client credentials authentication", async () => { @@ -71,13 +83,24 @@ describe("createFoundryConnectionAdapter", () => { const fetch = vi.fn((input, init) => { tokenRequest = new Request(input, init); return Promise.resolve( - Response.json({ access_token: token, token_type: "Bearer", expires_in: 3600 }) + Response.json({ + access_token: token, + token_type: "Bearer", + expires_in: 3600, + }) ); }); const adapter = await createFoundryConnectionAdapter({ baseUrl: "https://foundry.example.com", - clientCredentials: { clientId: "client", clientSecret: "secret", fetch }, - })({ installationId: "foundry", runtime: {} as never }); + clientCredentials: { + clientId: "client", + clientSecret: "secret", + fetch, + }, + })({ + installationId: "foundry", + runtime: {} as never, + }); const connect = vi.fn(() => Promise.resolve()); const authentication = adapter.createAuthenticationClient({ connect, @@ -87,7 +110,11 @@ describe("createFoundryConnectionAdapter", () => { const connection = await authentication.signIn.clientCredentials(); expect(connection.state.status).toBe("active"); - expect(connect).toHaveBeenCalledWith(expect.objectContaining({ connection })); + expect(connect).toHaveBeenCalledWith( + expect.objectContaining({ + connection, + }) + ); expect(fetch).toHaveBeenCalledOnce(); expect(tokenRequest?.url).toBe("https://foundry.example.com/multipass/api/oauth2/token"); expect(tokenRequest?.headers.get("authorization")).toBe("Basic Y2xpZW50OnNlY3JldA=="); @@ -138,7 +165,10 @@ describe("createFoundryConnectionAdapter", () => { }) ), }, - })({ installationId: "foundry", runtime }); + })({ + installationId: "foundry", + runtime, + }); const connect = vi.fn(() => Promise.resolve()); const authentication = adapter.createAuthenticationClient({ connect, @@ -148,7 +178,11 @@ describe("createFoundryConnectionAdapter", () => { const connection = await authentication.signIn.oauth(); expect(connection.state.status).toBe("active"); - expect(connect).toHaveBeenCalledWith(expect.objectContaining({ connection })); + expect(connect).toHaveBeenCalledWith( + expect.objectContaining({ + connection, + }) + ); expect([...values.values()].some((value) => value.includes("refresh"))).toBe(true); await adapter.cleanup?.(); diff --git a/packages/foundry-ontology/src/connection.ts b/packages/foundry-ontology/src/connection.ts index eb71340a..4c0b757e 100644 --- a/packages/foundry-ontology/src/connection.ts +++ b/packages/foundry-ontology/src/connection.ts @@ -66,7 +66,13 @@ const DEFAULT_SCOPES = [ function activeState(expiresAt: number | undefined, refreshable: boolean): Connection<"active">["state"] { return expiresAt === undefined ? { status: "active" } - : { status: "active", expiration: { expiresAt, refreshable } }; + : { + status: "active", + expiration: { + expiresAt, + refreshable, + }, + }; } function createFoundryEgressWrapper(options: { @@ -85,7 +91,10 @@ function createFoundryEgressWrapper(options: { createWebSocket: (url, protocols) => handlers.createWebSocket(url, protocols), }); return withHttpAuthenticationErrorHandling( - withHttpRetryHandling({ fetch: (request) => fetch(request), createWebSocket }) + withHttpRetryHandling({ + fetch: (request) => fetch(request), + createWebSocket, + }) ); }; } @@ -149,7 +158,10 @@ async function createFoundryConnectionAdapterInstance( throw new Error("Foundry API token connections are not configured."); } const { userId, expiresAt } = getTokenDetails(token); - const connection: Connection<"active"> = { userId, state: activeState(expiresAt, false) }; + const connection: Connection<"active"> = { + userId, + state: activeState(expiresAt, false), + }; const session: ConnectionSession = { disconnect() { if (token === options.token) { diff --git a/packages/foundry-ontology/src/installation/createFoundryBackendInstallation.test.ts b/packages/foundry-ontology/src/installation/createFoundryBackendInstallation.test.ts index ee2f429d..5a32c458 100644 --- a/packages/foundry-ontology/src/installation/createFoundryBackendInstallation.test.ts +++ b/packages/foundry-ontology/src/installation/createFoundryBackendInstallation.test.ts @@ -3,7 +3,13 @@ import * as metaAdapter from "../meta/createFoundryMetaOntologyBackendAdapter.js import { createFoundryOntologyRoute } from "./createFoundryBackendInstallation.js"; import type { OntologyIR } from "@party-stack/ontology"; -const ir: OntologyIR = { types: [], objectTypes: [], linkTypes: [], actionTypes: [], queryFunctionTypes: [] }; +const ir: OntologyIR = { + types: [], + objectTypes: [], + linkTypes: [], + actionTypes: [], + queryFunctionTypes: [], +}; describe("createFoundryOntologyRoute", () => { it.each([30_000, false, undefined] as const)( @@ -17,7 +23,10 @@ describe("createFoundryOntologyRoute", () => { try { const configuration = await route.configureMeta!({ - connection: { userId: "user-1", state: { status: "active" } }, + connection: { + userId: "user-1", + state: { status: "active" }, + }, egress: { fetch: globalThis.fetch, createWebSocket: () => Promise.reject(new Error("not used")), @@ -40,33 +49,45 @@ describe("createFoundryOntologyRoute", () => { ); it("creates a metadata-only route when IR is omitted", () => { - const route = createFoundryOntologyRoute({ ontologyId: "ri.ontology.main" })( - "https://foundry.example" - ); + const route = createFoundryOntologyRoute({ + ontologyId: "ri.ontology.main", + })("https://foundry.example"); expect(route.configure === undefined).toBe(true); expect(route.configureMeta !== undefined).toBe(true); }); it("defers IR metadata projection to the installation", () => { - const route = createFoundryOntologyRoute({ ontologyId: "ri.ontology.main", ir })( - "https://foundry.example" - ); + const route = createFoundryOntologyRoute({ + ontologyId: "ri.ontology.main", + ir, + })("https://foundry.example"); expect(route.configure !== undefined).toBe(true); expect(route.configureMeta === undefined).toBe(true); }); it("forwards live to configured ontology backends", async () => { - const route = createFoundryOntologyRoute({ ontologyId: "ri.ontology.main", ir, live: false })( - "https://foundry.example" - ); + const route = createFoundryOntologyRoute({ + ontologyId: "ri.ontology.main", + ir, + live: false, + })("https://foundry.example"); const configuration = await route.configure!({ - connection: { userId: "user-1", state: { status: "active" } }, - egress: { fetch: globalThis.fetch, createWebSocket: () => Promise.reject(new Error("not used")) }, + connection: { + userId: "user-1", + state: { status: "active" }, + }, + egress: { + fetch: globalThis.fetch, + createWebSocket: () => Promise.reject(new Error("not used")), + }, ontologyId: "ri.ontology.main", } as never); - await expect(configuration.backend(ir, {})).resolves.toMatchObject({ name: "foundry", live: false }); + await expect(configuration.backend(ir, {})).resolves.toMatchObject({ + name: "foundry", + live: false, + }); }); }); diff --git a/packages/foundry-ontology/src/installation/createFoundryBackendInstallation.ts b/packages/foundry-ontology/src/installation/createFoundryBackendInstallation.ts index 933f49a5..a0a2d828 100644 --- a/packages/foundry-ontology/src/installation/createFoundryBackendInstallation.ts +++ b/packages/foundry-ontology/src/installation/createFoundryBackendInstallation.ts @@ -56,7 +56,10 @@ function configureFoundryMeta( const client = createConnectionOntologyClient(baseUrl, options.ontologyId, options.egress); return createMetaOntologyConfiguration({ backend: () => - createFoundryMetaOntologyBackendAdapter({ client, refetchInterval: metaRefetchInterval }), + createFoundryMetaOntologyBackendAdapter({ + client, + refetchInterval: metaRefetchInterval, + }), }); } @@ -71,7 +74,9 @@ export function createFoundryOntologyRoute(options: { metaRefetchInterval?: number | false; }): FoundryOntologyRoute { return (baseUrl) => { - const route: OntologyRoute = { matches: (ontologyId) => ontologyId === options.ontologyId }; + const route: OntologyRoute = { + matches: (ontologyId) => ontologyId === options.ontologyId, + }; const ir = options.ir; if (ir) { route.configure = ({ connection, egress }) => { diff --git a/packages/foundry-ontology/src/meta/actionTypeCollectionOptions.ts b/packages/foundry-ontology/src/meta/actionTypeCollectionOptions.ts index d2429cfe..e81271e0 100644 --- a/packages/foundry-ontology/src/meta/actionTypeCollectionOptions.ts +++ b/packages/foundry-ontology/src/meta/actionTypeCollectionOptions.ts @@ -44,7 +44,13 @@ async function searchActionTypes( ActionTypesV2.search( client, client.ontologyRid, - { where, orderBy, pageSize, pageToken, fuzziness: { type: "off" } }, + { + where, + orderBy, + pageSize, + pageToken, + fuzziness: { type: "off" }, + }, { preview: true } ), (page) => page.nextPageToken, @@ -73,7 +79,11 @@ async function loadActionTypesFullMetadata( await ActionTypesFullMetadata.getFullMetadataBatch( client, client.ontologyRid, - { requests: batch.map((actionType) => ({ actionType: actionType.apiName })) }, + { + requests: batch.map((actionType) => ({ + actionType: actionType.apiName, + })), + }, { preview: true } ) ).data, @@ -85,7 +95,11 @@ async function loadActionTypesFullMetadata( } return actionTypes.map( - (actionType) => fullMetadataByApiName.get(actionType.apiName) ?? { actionType, fullLogicRules: [] } + (actionType) => + fullMetadataByApiName.get(actionType.apiName) ?? { + actionType, + fullLogicRules: [], + } ); } diff --git a/packages/foundry-ontology/src/meta/actionTypeIdQuery.test.ts b/packages/foundry-ontology/src/meta/actionTypeIdQuery.test.ts index d3206f80..1321bac8 100644 --- a/packages/foundry-ontology/src/meta/actionTypeIdQuery.test.ts +++ b/packages/foundry-ontology/src/meta/actionTypeIdQuery.test.ts @@ -10,13 +10,18 @@ const mocks = vi.hoisted(() => ({ searchActionTypes: vi.fn(), })); -vi.mock("@osdk/client.unstable", () => ({ bulkLoadOntologyEntities: mocks.bulkLoadOntologyEntities })); +vi.mock("@osdk/client.unstable", () => ({ + bulkLoadOntologyEntities: mocks.bulkLoadOntologyEntities, +})); vi.mock("@osdk/foundry.ontologies", async (importOriginal) => { const original = await importOriginal(); return { ...original, - ActionTypesV2: { ...original.ActionTypesV2, search: mocks.searchActionTypes }, + ActionTypesV2: { + ...original.ActionTypesV2, + search: mocks.searchActionTypes, + }, ActionTypesFullMetadata: { ...original.ActionTypesFullMetadata, getFullMetadataBatch: mocks.getFullMetadataBatch, @@ -35,7 +40,9 @@ beforeEach(() => { mocks.bulkLoadOntologyEntities.mockReset(); mocks.getFullMetadataBatch.mockReset(); mocks.searchActionTypes.mockReset(); - mocks.bulkLoadOntologyEntities.mockResolvedValue({ actionTypes: [] }); + mocks.bulkLoadOntologyEntities.mockResolvedValue({ + actionTypes: [], + }); }); describe("Foundry ActionType metadata ID queries", () => { @@ -69,7 +76,10 @@ describe("Foundry ActionType metadata ID queries", () => { expect.anything(), "ri.ontology.main.ontology.example", expect.objectContaining({ - where: { type: "actionTypeRid", value: "ri.actions.main.action-type.create-task" }, + where: { + type: "actionTypeRid", + value: "ri.actions.main.action-type.create-task", + }, }), { preview: true } ); @@ -92,22 +102,50 @@ describe("Foundry ActionType metadata ID queries", () => { status: "ACTIVE", parameters: {}, rid: "ri.actions.main.action-type.create-task", - operations: [{ type: "createObject", objectTypeApiName: "Task" }], + operations: [ + { + type: "createObject", + objectTypeApiName: "Task", + }, + ], }; - mocks.searchActionTypes.mockResolvedValue({ data: [actionType], nextPageToken: undefined }); - mocks.getFullMetadataBatch.mockResolvedValue({ data: [{ actionType, fullLogicRules: [] }] }); + mocks.searchActionTypes.mockResolvedValue({ + data: [actionType], + nextPageToken: undefined, + }); + mocks.getFullMetadataBatch.mockResolvedValue({ + data: [ + { + actionType, + fullLogicRules: [], + }, + ], + }); const meta = await createMetaLiveOntology({ - backend: () => createFoundryMetaOntologyBackendAdapter({ client }), + backend: () => + createFoundryMetaOntologyBackendAdapter({ + client, + }), persistObjects: false, }); try { - await queryOnce((q) => q.from({ ActionType: meta.objects.ActionType })); + await queryOnce((q) => + q.from({ + ActionType: meta.objects.ActionType, + }) + ); expect(mocks.getFullMetadataBatch).toHaveBeenCalledWith( expect.anything(), "ri.ontology.main.ontology.example", - { requests: [{ actionType: "create-task" }] }, + { + requests: [ + { + actionType: "create-task", + }, + ], + }, { preview: true } ); } finally { diff --git a/packages/foundry-ontology/src/meta/convertActionTypeLoadSubsetOptions.test.ts b/packages/foundry-ontology/src/meta/convertActionTypeLoadSubsetOptions.test.ts index 8753c36d..30eb9627 100644 --- a/packages/foundry-ontology/src/meta/convertActionTypeLoadSubsetOptions.test.ts +++ b/packages/foundry-ontology/src/meta/convertActionTypeLoadSubsetOptions.test.ts @@ -11,7 +11,10 @@ describe("convertActionTypeLoadSubsetFilter", () => { convertActionTypeLoadSubsetFilter( eq(new IR.PropRef(["id"]), "ri.actions.main.action-type.create-task") ) - ).toEqual({ type: "actionTypeRid", value: "ri.actions.main.action-type.create-task" }); + ).toEqual({ + type: "actionTypeRid", + value: "ri.actions.main.action-type.create-task", + }); }); it("converts name equality to an exact apiName predicate with kebab-case", () => { @@ -32,7 +35,10 @@ describe("convertActionTypeLoadSubsetFilter", () => { expect(filter).toEqual({ type: "actionTypeApiName", - value: { type: "exact", value: "create-warranty-submission-for-b-b-streamline" }, + value: { + type: "exact", + value: "create-warranty-submission-for-b-b-streamline", + }, }); }); @@ -44,8 +50,14 @@ describe("convertActionTypeLoadSubsetFilter", () => { expect(filter).toEqual({ type: "or", value: [ - { type: "actionTypeApiName", value: { type: "exact", value: "create-task" } }, - { type: "actionTypeApiName", value: { type: "exact", value: "complete-task" } }, + { + type: "actionTypeApiName", + value: { type: "exact", value: "create-task" }, + }, + { + type: "actionTypeApiName", + value: { type: "exact", value: "complete-task" }, + }, ], }); }); @@ -53,11 +65,17 @@ describe("convertActionTypeLoadSubsetFilter", () => { it("converts displayName like/ilike to contains predicates", () => { expect( convertActionTypeLoadSubsetFilter(like(new IR.PropRef(["displayName"]), "%Token%")) - ).toEqual({ type: "actionTypeDisplayName", value: { type: "contains", value: "Token" } }); + ).toEqual({ + type: "actionTypeDisplayName", + value: { type: "contains", value: "Token" }, + }); expect( convertActionTypeLoadSubsetFilter(ilike(new IR.PropRef(["displayName"]), "%token%")) - ).toEqual({ type: "actionTypeDisplayName", value: { type: "contains", value: "token" } }); + ).toEqual({ + type: "actionTypeDisplayName", + value: { type: "contains", value: "token" }, + }); }); it("rejects LIKE predicates for ID fields", () => { @@ -76,9 +94,18 @@ describe("convertActionTypeLoadSubsetFilter", () => { ).toEqual({ type: "and", value: [ - { type: "actionTypeApiName", value: { type: "contains", value: "streamline" } }, - { type: "actionTypeApiName", value: { type: "contains", value: "create" } }, - { type: "actionTypeApiName", value: { type: "contains", value: "token" } }, + { + type: "actionTypeApiName", + value: { type: "contains", value: "streamline" }, + }, + { + type: "actionTypeApiName", + value: { type: "contains", value: "create" }, + }, + { + type: "actionTypeApiName", + value: { type: "contains", value: "token" }, + }, ], }); }); @@ -100,11 +127,20 @@ describe("convertActionTypeLoadSubsetFilter", () => { { type: "or", value: [ - { type: "actionTypeApiName", value: { type: "exact", value: "create-task" } }, - { type: "actionTypeApiName", value: { type: "exact", value: "delete-task" } }, + { + type: "actionTypeApiName", + value: { type: "exact", value: "create-task" }, + }, + { + type: "actionTypeApiName", + value: { type: "exact", value: "delete-task" }, + }, ], }, - { type: "actionTypeDisplayName", value: { type: "contains", value: "Task" } }, + { + type: "actionTypeDisplayName", + value: { type: "contains", value: "Task" }, + }, ], }); }); @@ -128,13 +164,19 @@ describe("convertActionTypeLoadSubsetFilter", () => { compareOptions: { direction: "desc", nulls: "last" }, }, ]) - ).toEqual({ field: "actionTypeDisplayName", direction: "desc" }); + ).toEqual({ + field: "actionTypeDisplayName", + direction: "desc", + }); }); it("does not push down unsupported or multiple order fields", () => { expect( convertActionTypeLoadSubsetOrderBy([ - { expression: new IR.PropRef(["name"]), compareOptions: { direction: "asc", nulls: "last" } }, + { + expression: new IR.PropRef(["name"]), + compareOptions: { direction: "asc", nulls: "last" }, + }, ]) ).toBeUndefined(); @@ -144,7 +186,10 @@ describe("convertActionTypeLoadSubsetFilter", () => { expression: new IR.PropRef(["displayName"]), compareOptions: { direction: "asc", nulls: "last" }, }, - { expression: new IR.PropRef(["name"]), compareOptions: { direction: "asc", nulls: "last" } }, + { + expression: new IR.PropRef(["name"]), + compareOptions: { direction: "asc", nulls: "last" }, + }, ]) ).toBeUndefined(); }); diff --git a/packages/foundry-ontology/src/meta/convertActionTypeLoadSubsetOptions.ts b/packages/foundry-ontology/src/meta/convertActionTypeLoadSubsetOptions.ts index 717f8526..6d05c65b 100644 --- a/packages/foundry-ontology/src/meta/convertActionTypeLoadSubsetOptions.ts +++ b/packages/foundry-ontology/src/meta/convertActionTypeLoadSubsetOptions.ts @@ -18,9 +18,9 @@ const LIKE_FIELDS = { displayName: FILTER_FIELDS.displayName, } as const satisfies Pick; -const ORDER_BY_FIELDS = { displayName: "actionTypeDisplayName" } as const satisfies Partial< - Record ->; +const ORDER_BY_FIELDS = { + displayName: "actionTypeDisplayName", +} as const satisfies Partial>; function getFilterField(field: FieldPath): keyof typeof FILTER_FIELDS { const name = field.join("."); @@ -41,7 +41,10 @@ function getLikeField(field: FieldPath): keyof typeof LIKE_FIELDS { function eq(field: FieldPath, value: unknown): ActionTypeSearchJsonQueryV2 { const name = getFilterField(field); if (name === "id") { - return { type: "actionTypeRid", value: value as string }; + return { + type: "actionTypeRid", + value: value as string, + }; } return { type: FILTER_FIELDS[name], @@ -57,7 +60,10 @@ function like(field: FieldPath, value: string): ActionTypeSearchJsonQueryV2 { const terms = value.split("%").filter(Boolean); const queries: ActionTypeSearchJsonQueryV2[] = terms.map((term) => ({ type: LIKE_FIELDS[name], - value: { type: "contains", value: name === "name" ? toFoundryActionTypeName(term) : term }, + value: { + type: "contains", + value: name === "name" ? toFoundryActionTypeName(term) : term, + }, })); return queries.length === 1 ? queries[0]! : { type: "and", value: queries }; @@ -69,8 +75,14 @@ export function convertActionTypeLoadSubsetFilter( return ( parseWhereExpression(filter, { handlers: { - and: (...filters: ActionTypeSearchJsonQueryV2[]) => ({ type: "and", value: filters }), - or: (...filters: ActionTypeSearchJsonQueryV2[]) => ({ type: "or", value: filters }), + and: (...filters: ActionTypeSearchJsonQueryV2[]) => ({ + type: "and", + value: filters, + }), + or: (...filters: ActionTypeSearchJsonQueryV2[]) => ({ + type: "or", + value: filters, + }), eq, in: (field: FieldPath, values: unknown[]) => ({ type: "or", @@ -93,5 +105,8 @@ export function convertActionTypeLoadSubsetOrderBy( return undefined; } - return { field: ORDER_BY_FIELDS[name as keyof typeof ORDER_BY_FIELDS], direction }; + return { + field: ORDER_BY_FIELDS[name as keyof typeof ORDER_BY_FIELDS], + direction, + }; } diff --git a/packages/foundry-ontology/src/meta/convertMetaActionType.test.ts b/packages/foundry-ontology/src/meta/convertMetaActionType.test.ts index 386cd572..d1c7be61 100644 --- a/packages/foundry-ontology/src/meta/convertMetaActionType.test.ts +++ b/packages/foundry-ontology/src/meta/convertMetaActionType.test.ts @@ -22,8 +22,19 @@ function actionType(parameters: Record): ActionTypeFu function omsIconMetadata(locator: string, color: string): ActionTypeOmsMetadata { return { actionType: { - actionTypeLogic: { validation: { parameterValidations: {} } }, - metadata: { displayMetadata: { icon: { type: "blueprint", blueprint: { locator, color } } } }, + actionTypeLogic: { + validation: { + parameterValidations: {}, + }, + }, + metadata: { + displayMetadata: { + icon: { + type: "blueprint", + blueprint: { locator, color }, + }, + }, + }, }, propertyApiNamesByParameter: new Map(), } as unknown as ActionTypeOmsMetadata; @@ -32,7 +43,11 @@ function omsIconMetadata(locator: string, color: string): ActionTypeOmsMetadata function objectParameter(objectType: string, required: boolean): ActionParameterV2 { return { displayName: objectType, - dataType: { type: "object", objectTypeApiName: objectType, objectApiName: objectType }, + dataType: { + type: "object", + objectTypeApiName: objectType, + objectApiName: objectType, + }, required, typeClasses: [], }; @@ -46,8 +61,16 @@ function structListParameter(): ActionParameterV2 { subType: { type: "struct", fields: [ - { name: "code", fieldType: { type: "string" }, required: true }, - { name: "enabled", fieldType: { type: "boolean" }, required: true }, + { + name: "code", + fieldType: { type: "string" }, + required: true, + }, + { + name: "enabled", + fieldType: { type: "boolean" }, + required: true, + }, ], }, }, @@ -65,14 +88,22 @@ function structListField(parameterId: string, field: string): never { } function structField(parameterId: string, field: string): never { - return { type: "structParameterFieldValue", parameterId, structParameterFieldApiName: field } as never; + return { + type: "structParameterFieldValue", + parameterId, + structParameterFieldApiName: field, + } as never; } function omsActionMetadata( parameterName: string, allowedValues: Record, prefill?: Record, - resolvedProperty?: { parameterId: string; propertyTypeId: string; propertyApiName: string } + resolvedProperty?: { + parameterId: string; + propertyTypeId: string; + propertyApiName: string; + } ): never { return { actionType: { @@ -80,7 +111,14 @@ function omsActionMetadata( validation: { parameterValidations: { [parameterName]: { - defaultValidation: { display: { prefill }, validation: { allowedValues } }, + defaultValidation: { + display: { + prefill, + }, + validation: { + allowedValues, + }, + }, }, }, }, @@ -105,7 +143,10 @@ function omsOneOf(values: Array<{ label: string; value: string }>, allowed = fal oneOf: { labelledValues: values.map(({ label, value }) => ({ label, - value: { type: "string", string: value }, + value: { + type: "string", + string: value, + }, })), otherValueAllowed: { allowed }, }, @@ -116,7 +157,15 @@ function omsOneOf(values: Array<{ label: string; value: string }>, allowed = fal function omsTextRegex(regex: string): Record { return { type: "text", - text: { type: "text", text: { regex: { regex, failureMessage: "Invalid value." } } }, + text: { + type: "text", + text: { + regex: { + regex, + failureMessage: "Invalid value.", + }, + }, + }, }; } @@ -132,7 +181,12 @@ describe("convertFoundryMetaActionType parameter validation", () => { expect( convertFoundryMetaActionType(actionType({}), omsIconMetadata("person", "#2d72d2")) ).toMatchObject({ - icon: { name: "person", meta: { blueprint: { name: "person" } } }, + icon: { + name: "person", + meta: { + blueprint: { name: "person" }, + }, + }, color: "#2d72d2", }); }); @@ -142,14 +196,21 @@ describe("convertFoundryMetaActionType parameter validation", () => { actionType({ amount: { displayName: "Amount", - dataType: { type: "decimal", precision: 38, scale: 18 }, + dataType: { + type: "decimal", + precision: 38, + scale: 18, + }, required: true, typeClasses: [], }, }) ); - expect(result.parameters[0]?.type).toEqual({ kind: "string", value: {} }); + expect(result.parameters[0]?.type).toEqual({ + kind: "string", + value: {}, + }); }); it("preserves object references nested inside array structs", () => { @@ -190,7 +251,10 @@ describe("convertFoundryMetaActionType parameter validation", () => { { name: "part", displayName: "part", - type: { kind: "objectReference", value: { objectType: "Part" } }, + type: { + kind: "objectReference", + value: { objectType: "Part" }, + }, }, ], }, @@ -262,7 +326,14 @@ describe("convertFoundryMetaActionType parameter validation", () => { expect(result.parameters[0]?.type).toEqual({ kind: "string", - value: { suggestions: [{ value: "open", label: "Open" }] }, + value: { + suggestions: [ + { + value: "open", + label: "Open", + }, + ], + }, }); }); @@ -274,7 +345,14 @@ describe("convertFoundryMetaActionType parameter validation", () => { dataType: { type: "string" }, required: false, typeClasses: [], - validation: { defaultValidation: { allowedValues: { type: "text", regex: "^\\d{5}$" } } }, + validation: { + defaultValidation: { + allowedValues: { + type: "text", + regex: "^\\d{5}$", + }, + }, + }, }, }) ); @@ -284,7 +362,12 @@ describe("convertFoundryMetaActionType parameter validation", () => { value: { type: { kind: "string", - value: { constraint: { kind: "regex", value: { regex: "^\\d{5}$" } } }, + value: { + constraint: { + kind: "regex", + value: { regex: "^\\d{5}$" }, + }, + }, }, }, }); @@ -312,7 +395,10 @@ describe("convertFoundryMetaActionType parameter validation", () => { }) ); - expect(result.parameters[0]?.type).toEqual({ kind: "ref", value: { name: "PostalCode" } }); + expect(result.parameters[0]?.type).toEqual({ + kind: "ref", + value: { name: "PostalCode" }, + }); }); it("maps Foundry current user arguments to the canonical user context", () => { @@ -321,7 +407,11 @@ describe("convertFoundryMetaActionType parameter validation", () => { { type: "createObject", objectTypeApiName: "Task", - propertyArguments: { createdBy: { type: "currentUser" } }, + propertyArguments: { + createdBy: { + type: "currentUser", + }, + }, structPropertyArguments: {}, } as never, ]; @@ -334,7 +424,12 @@ describe("convertFoundryMetaActionType parameter validation", () => { values: [ { property: ["createdBy"], - value: { kind: "contextReference", value: { name: "user" } }, + value: { + kind: "contextReference", + value: { + name: "user", + }, + }, }, ], }, @@ -345,7 +440,9 @@ describe("convertFoundryMetaActionType parameter validation", () => { describe("convertFoundryMetaActionType mutation targets", () => { it("requires an optional object-reference update target", () => { - const metadata = actionType({ survey: objectParameter("HospitalSurveyDev", false) }); + const metadata = actionType({ + survey: objectParameter("HospitalSurveyDev", false), + }); metadata.fullLogicRules = [ { type: "modifyObject", @@ -368,8 +465,15 @@ describe("convertFoundryMetaActionType mutation targets", () => { }); it("requires an optional object-reference delete target", () => { - const metadata = actionType({ survey: objectParameter("HospitalSurveyDev", false) }); - metadata.fullLogicRules = [{ type: "deleteObject", objectToDelete: "survey" } as never]; + const metadata = actionType({ + survey: objectParameter("HospitalSurveyDev", false), + }); + metadata.fullLogicRules = [ + { + type: "deleteObject", + objectToDelete: "survey", + } as never, + ]; expect(convertFoundryMetaActionType(metadata).parameters[0]?.type).toEqual({ kind: "objectReference", @@ -378,7 +482,9 @@ describe("convertFoundryMetaActionType mutation targets", () => { }); it("leaves an already-required mutation target unchanged", () => { - const metadata = actionType({ survey: objectParameter("HospitalSurveyDev", true) }); + const metadata = actionType({ + survey: objectParameter("HospitalSurveyDev", true), + }); metadata.fullLogicRules = [ { type: "modifyObject", @@ -403,34 +509,59 @@ describe("convertFoundryMetaActionType mutation targets", () => { { type: "modifyObject", objectToModify: "survey", - propertyArguments: { relatedSurvey: { type: "parameterId", parameterId: "relatedSurvey" } }, + propertyArguments: { + relatedSurvey: { + type: "parameterId", + parameterId: "relatedSurvey", + }, + }, structPropertyArguments: {}, } as never, ]; expect(convertFoundryMetaActionType(metadata).parameters[1]?.type).toEqual({ kind: "optional", - value: { type: { kind: "objectReference", value: { objectType: "HospitalSurveyDev" } } }, + value: { + type: { + kind: "objectReference", + value: { objectType: "HospitalSurveyDev" }, + }, + }, }); }); it("preserves optional scalar property parameters", () => { const metadata = actionType({ survey: objectParameter("HospitalSurveyDev", true), - status: { displayName: "Status", dataType: { type: "string" }, required: false, typeClasses: [] }, + status: { + displayName: "Status", + dataType: { type: "string" }, + required: false, + typeClasses: [], + }, }); metadata.fullLogicRules = [ { type: "modifyObject", objectToModify: "survey", - propertyArguments: { status: { type: "parameterId", parameterId: "status" } }, + propertyArguments: { + status: { + type: "parameterId", + parameterId: "status", + }, + }, structPropertyArguments: {}, } as never, ]; expect(convertFoundryMetaActionType(metadata).parameters[1]?.type).toEqual({ kind: "optional", - value: { type: { kind: "string", value: {} } }, + value: { + type: { + kind: "string", + value: {}, + }, + }, }); }); @@ -453,7 +584,10 @@ describe("convertFoundryMetaActionType mutation targets", () => { }, structPropertyArguments: {}, }, - { type: "deleteObject", objectToDelete: "surveyToDelete" }, + { + type: "deleteObject", + objectToDelete: "surveyToDelete", + }, ] as never; const result = convertFoundryMetaActionType(metadata); @@ -503,7 +637,11 @@ describe("convertFoundryMetaActionType struct assignments", () => { entries: structListParameter(), record: { displayName: "Record", - dataType: { type: "object", objectTypeApiName: "Record", objectApiName: "record" }, + dataType: { + type: "object", + objectTypeApiName: "Record", + objectApiName: "record", + }, required: true, typeClasses: [], }, @@ -520,7 +658,12 @@ describe("convertFoundryMetaActionType struct assignments", () => { value: { kind: "map", value: { - source: { kind: "inputReference", value: { name: "entries" } }, + source: { + kind: "inputReference", + value: { + name: "entries", + }, + }, binding: "item", body: { kind: "struct", @@ -533,7 +676,9 @@ describe("convertFoundryMetaActionType struct assignments", () => { value: { source: { kind: "localReference", - value: { name: "item" }, + value: { + name: "item", + }, }, path: ["code"], }, @@ -546,7 +691,9 @@ describe("convertFoundryMetaActionType struct assignments", () => { value: { source: { kind: "localReference", - value: { name: "item" }, + value: { + name: "item", + }, }, path: ["enabled"], }, @@ -570,7 +717,13 @@ describe("convertFoundryMetaActionType struct assignments", () => { displayName: "Details", dataType: { type: "struct", - fields: [{ name: "code", fieldType: { type: "string" }, required: true }], + fields: [ + { + name: "code", + fieldType: { type: "string" }, + required: true, + }, + ], }, required: true, typeClasses: [], @@ -581,7 +734,11 @@ describe("convertFoundryMetaActionType struct assignments", () => { type: "createObject", objectTypeApiName: "Record", propertyArguments: {}, - structPropertyArguments: { details: { code: structField("details", "code") } }, + structPropertyArguments: { + details: { + code: structField("details", "code"), + }, + }, } as never, ]; @@ -594,7 +751,10 @@ describe("convertFoundryMetaActionType struct assignments", () => { value: { kind: "getAt", value: { - source: { kind: "inputReference", value: { name: "details" } }, + source: { + kind: "inputReference", + value: { name: "details" }, + }, path: ["code"], }, }, @@ -608,14 +768,19 @@ describe("convertFoundryMetaActionType struct assignments", () => { it.each([ { name: "partial", - fields: { code: structListField("entries", "code") }, + fields: { + code: structListField("entries", "code"), + }, expectedFields: [ { name: "code", value: { kind: "getAt", value: { - source: { kind: "localReference", value: { name: "item" } }, + source: { + kind: "localReference", + value: { name: "item" }, + }, path: ["code"], }, }, @@ -634,7 +799,10 @@ describe("convertFoundryMetaActionType struct assignments", () => { value: { kind: "getAt", value: { - source: { kind: "localReference", value: { name: "item" } }, + source: { + kind: "localReference", + value: { name: "item" }, + }, path: ["code"], }, }, @@ -644,7 +812,10 @@ describe("convertFoundryMetaActionType struct assignments", () => { value: { kind: "getAt", value: { - source: { kind: "localReference", value: { name: "item" } }, + source: { + kind: "localReference", + value: { name: "item" }, + }, path: ["enabled"], }, }, @@ -652,7 +823,9 @@ describe("convertFoundryMetaActionType struct assignments", () => { ], }, ])("converts a $name list mapping to a map expression", ({ fields, expectedFields }) => { - const metadata = actionType({ entries: structListParameter() }); + const metadata = actionType({ + entries: structListParameter(), + }); metadata.fullLogicRules = [ { type: "createObject", @@ -671,9 +844,19 @@ describe("convertFoundryMetaActionType struct assignments", () => { value: { kind: "map", value: { - source: { kind: "inputReference", value: { name: "entries" } }, + source: { + kind: "inputReference", + value: { + name: "entries", + }, + }, binding: "item", - body: { kind: "struct", value: { fields: expectedFields } }, + body: { + kind: "struct", + value: { + fields: expectedFields, + }, + }, }, }, }, @@ -684,7 +867,10 @@ describe("convertFoundryMetaActionType struct assignments", () => { }); it("rejects mappings driven by multiple list parameters", () => { - const metadata = actionType({ entries: structListParameter(), otherEntries: structListParameter() }); + const metadata = actionType({ + entries: structListParameter(), + otherEntries: structListParameter(), + }); metadata.fullLogicRules = [ { type: "createObject", @@ -711,7 +897,13 @@ describe("convertFoundryMetaActionType struct assignments", () => { displayName: "Details", dataType: { type: "struct", - fields: [{ name: "enabled", fieldType: { type: "boolean" }, required: true }], + fields: [ + { + name: "enabled", + fieldType: { type: "boolean" }, + required: true, + }, + ], }, required: true, typeClasses: [], @@ -741,7 +933,11 @@ describe("convertFoundryMetaActionType struct assignments", () => { entries: structListParameter(), record: { displayName: "Record", - dataType: { type: "object", objectTypeApiName: "Record", objectApiName: "record" }, + dataType: { + type: "object", + objectTypeApiName: "Record", + objectApiName: "record", + }, required: true, typeClasses: [], }, @@ -756,7 +952,9 @@ describe("convertFoundryMetaActionType struct assignments", () => { code: structListField("entries", "code"), enabled: structListField("entries", "enabled"), }, - summaries: { identifier: structListField("entries", "code") }, + summaries: { + identifier: structListField("entries", "code"), + }, }, }, { @@ -781,8 +979,16 @@ describe("convertFoundryMetaActionType struct assignments", () => { pluralDisplayName: "Records", primaryKey: "id", properties: [ - { name: "id", displayName: "ID", type: { kind: "string", value: {} } }, - { name: "entries", displayName: "Entries", type: converted.parameters[0]!.type }, + { + name: "id", + displayName: "ID", + type: { kind: "string", value: {} }, + }, + { + name: "entries", + displayName: "Entries", + type: converted.parameters[0]!.type, + }, { name: "summaries", displayName: "Summaries", @@ -799,7 +1005,10 @@ describe("convertFoundryMetaActionType struct assignments", () => { { name: "identifier", displayName: "Identifier", - type: { kind: "string", value: {} }, + type: { + kind: "string", + value: {}, + }, }, ], }, @@ -838,7 +1047,13 @@ describe("convertFoundryMetaActionType OMS string constraints", () => { { label: "United States", value: "US" }, { label: "Canada", value: "CA" }, ]), - { type: "staticValue", staticValue: { type: "string", string: "US" } } + { + type: "staticValue", + staticValue: { + type: "string", + string: "US", + }, + } ) ); @@ -856,7 +1071,10 @@ describe("convertFoundryMetaActionType OMS string constraints", () => { }, }, }); - expect(result.parameters[0]?.defaultValue).toEqual({ kind: "literal", value: { value: "US" } }); + expect(result.parameters[0]?.defaultValue).toEqual({ + kind: "literal", + value: { value: "US" }, + }); }); it("preserves open OMS one-of values as optional string suggestions", () => { @@ -869,7 +1087,11 @@ describe("convertFoundryMetaActionType OMS string constraints", () => { }, claim: { displayName: "Claim", - dataType: { type: "object", objectTypeApiName: "Claim", objectApiName: "claim" }, + dataType: { + type: "object", + objectTypeApiName: "Claim", + objectApiName: "claim", + }, required: true, typeClasses: [], }, @@ -878,7 +1100,12 @@ describe("convertFoundryMetaActionType OMS string constraints", () => { { type: "modifyObject", objectToModify: "claim", - propertyArguments: { country: { type: "parameterId", parameterId: "country" } }, + propertyArguments: { + country: { + type: "parameterId", + parameterId: "country", + }, + }, structPropertyArguments: {}, } as never, ]; @@ -896,9 +1123,16 @@ describe("convertFoundryMetaActionType OMS string constraints", () => { ), { type: "objectParameterPropertyValue", - objectParameterPropertyValue: { parameterId: "claim", propertyTypeId: "country" }, + objectParameterPropertyValue: { + parameterId: "claim", + propertyTypeId: "country", + }, }, - { propertyTypeId: "country", parameterId: "claim", propertyApiName: "country" } + { + propertyTypeId: "country", + parameterId: "claim", + propertyApiName: "country", + } ) ); @@ -909,9 +1143,18 @@ describe("convertFoundryMetaActionType OMS string constraints", () => { kind: "string", value: { suggestions: [ - { value: "US", label: "United States" }, - { value: "MX", label: "Mexico" }, - { value: "CA", label: "Canada" }, + { + value: "US", + label: "United States", + }, + { + value: "MX", + label: "Mexico", + }, + { + value: "CA", + label: "Canada", + }, ], }, }, @@ -922,7 +1165,12 @@ describe("convertFoundryMetaActionType OMS string constraints", () => { value: { source: { kind: "objectLookup", - value: { reference: { kind: "inputReference", value: { name: "claim" } } }, + value: { + reference: { + kind: "inputReference", + value: { name: "claim" }, + }, + }, }, path: ["country"], }, @@ -950,7 +1198,14 @@ describe("convertFoundryMetaActionType OMS string constraints", () => { value: { constraint: { kind: "enum", - value: { options: [{ value: "US", label: "United States" }] }, + value: { + options: [ + { + value: "US", + label: "United States", + }, + ], + }, }, }, }, @@ -963,7 +1218,10 @@ describe("convertFoundryMetaActionType OMS string constraints", () => { actionType({ countries: { displayName: "Countries", - dataType: { type: "array", subType: { type: "string" } }, + dataType: { + type: "array", + subType: { type: "string" }, + }, required: true, typeClasses: [], }, @@ -973,7 +1231,16 @@ describe("convertFoundryMetaActionType OMS string constraints", () => { expect(result.parameters[0]?.type).toMatchObject({ kind: "list", - value: { elementType: { kind: "string", value: { constraint: { kind: "enum" } } } }, + value: { + elementType: { + kind: "string", + value: { + constraint: { + kind: "enum", + }, + }, + }, + }, }); }); @@ -992,7 +1259,12 @@ describe("convertFoundryMetaActionType OMS string constraints", () => { expect(result.parameters[0]?.type).toEqual({ kind: "string", - value: { constraint: { kind: "regex", value: { regex: "^\\d{5}$" } } }, + value: { + constraint: { + kind: "regex", + value: { regex: "^\\d{5}$" }, + }, + }, }); }); @@ -1008,7 +1280,12 @@ describe("convertFoundryMetaActionType OMS string constraints", () => { defaultValidation: { allowedValues: { type: "oneOf", - options: [{ displayName: "Public option", value: "public" }], + options: [ + { + displayName: "Public option", + value: "public", + }, + ], otherValuesAllowed: false, }, }, @@ -1023,7 +1300,14 @@ describe("convertFoundryMetaActionType OMS string constraints", () => { value: { constraint: { kind: "enum", - value: { options: [{ value: "public", label: "Public option" }] }, + value: { + options: [ + { + value: "public", + label: "Public option", + }, + ], + }, }, }, }); @@ -1094,7 +1378,10 @@ describe("convertFoundryMetaActionType OMS struct field validations", () => { type: { kind: "optional", value: { - type: { kind: "string", value: { constraint: { kind: "enum", value: { options } } } }, + type: { + kind: "string", + value: { constraint: { kind: "enum", value: { options } } }, + }, }, }, }, @@ -1103,7 +1390,12 @@ describe("convertFoundryMetaActionType OMS struct field validations", () => { displayName: "category", type: { kind: "optional", - value: { type: { kind: "string", value: { suggestions: options } } }, + value: { + type: { + kind: "string", + value: { suggestions: options }, + }, + }, }, }, { @@ -1114,7 +1406,12 @@ describe("convertFoundryMetaActionType OMS struct field validations", () => { value: { type: { kind: "list", - value: { elementType: { kind: "string", value: { suggestions: options } } }, + value: { + elementType: { + kind: "string", + value: { suggestions: options }, + }, + }, }, }, }, @@ -1124,7 +1421,9 @@ describe("convertFoundryMetaActionType OMS struct field validations", () => { displayName: "code", type: { kind: "string", - value: { constraint: { kind: "regex", value: { regex: "^[A-Z]+$" } } }, + value: { + constraint: { kind: "regex", value: { regex: "^[A-Z]+$" } }, + }, }, }, ]); @@ -1170,7 +1469,11 @@ describe("convertFoundryMetaActionType OMS defaults", () => { const metadata = actionType({ assignee: { displayName: "Assignee", - dataType: { type: "object", objectTypeApiName: "User", objectApiName: "User" }, + dataType: { + type: "object", + objectTypeApiName: "User", + objectApiName: "User", + }, required: true, typeClasses: [], }, @@ -1180,7 +1483,12 @@ describe("convertFoundryMetaActionType OMS defaults", () => { required: false, typeClasses: [], }, - notes: { displayName: "Notes", dataType: { type: "string" }, required: false, typeClasses: [] }, + notes: { + displayName: "Notes", + dataType: { type: "string" }, + required: false, + typeClasses: [], + }, }); const result = convertFoundryMetaActionType(metadata, { actionType: { @@ -1205,7 +1513,10 @@ describe("convertFoundryMetaActionType OMS defaults", () => { display: { prefill: { type: "staticValue", - staticValue: { type: "string", string: "from-foundry" }, + staticValue: { + type: "string", + string: "from-foundry", + }, }, }, }, @@ -1220,7 +1531,9 @@ describe("convertFoundryMetaActionType OMS defaults", () => { } as never); expect(result.parameters).toMatchObject([ - { name: "assignee" }, + { + name: "assignee", + }, { name: "assigneeName", defaultValue: { @@ -1228,20 +1541,36 @@ describe("convertFoundryMetaActionType OMS defaults", () => { value: { source: { kind: "objectLookup", - value: { reference: { kind: "inputReference", value: { name: "assignee" } } }, + value: { + reference: { + kind: "inputReference", + value: { name: "assignee" }, + }, + }, }, path: ["displayName"], }, }, }, - { name: "notes", defaultValue: { kind: "literal", value: { value: "from-foundry" } } }, + { + name: "notes", + defaultValue: { + kind: "literal", + value: { + value: "from-foundry", + }, + }, + }, ]); }); it("omits object-query prefills", () => { const objectSet = { objectSet: { - startingObjectSet: { type: "base", base: { objectTypeId: "jfubb6is.user" } }, + startingObjectSet: { + type: "base", + base: { objectTypeId: "jfubb6is.user" }, + }, transforms: [ { type: "propertyFilter", @@ -1249,7 +1578,12 @@ describe("convertFoundryMetaActionType OMS defaults", () => { type: "exactMatch", exactMatch: { propertyId: "email", - terms: [{ type: "string", string: "owner@example.com" }], + terms: [ + { + type: "string", + string: "owner@example.com", + }, + ], }, }, }, @@ -1259,7 +1593,14 @@ describe("convertFoundryMetaActionType OMS defaults", () => { type: "parameterizedExactMatch", parameterizedExactMatch: { propertyId: "department", - terms: [{ type: "unresolved", unresolved: { parameterId: "departments" } }], + terms: [ + { + type: "unresolved", + unresolved: { + parameterId: "departments", + }, + }, + ], }, }, }, @@ -1270,7 +1611,9 @@ describe("convertFoundryMetaActionType OMS defaults", () => { type: "staticValue", staticValue: { type: "stringList", - stringList: { strings: ["Engineering", "Operations"] }, + stringList: { + strings: ["Engineering", "Operations"], + }, }, }, }, @@ -1279,7 +1622,11 @@ describe("convertFoundryMetaActionType OMS defaults", () => { actionType({ assignee: { displayName: "Assignee", - dataType: { type: "object", objectTypeApiName: "User", objectApiName: "jfubb6is.user" }, + dataType: { + type: "object", + objectTypeApiName: "User", + objectApiName: "jfubb6is.user", + }, required: false, typeClasses: [], }, @@ -1294,7 +1641,9 @@ describe("convertFoundryMetaActionType OMS defaults", () => { display: { prefill: { type: "objectQueryPrefill", - objectQueryPrefill: { objectSet }, + objectQueryPrefill: { + objectSet, + }, }, }, }, @@ -1321,7 +1670,10 @@ describe("convertFoundryMetaActionType OMS defaults", () => { }, tags: { displayName: "Tags", - dataType: { type: "array", subType: { type: "string" } }, + dataType: { + type: "array", + subType: { type: "string" }, + }, required: false, typeClasses: [], }, @@ -1338,7 +1690,9 @@ describe("convertFoundryMetaActionType OMS defaults", () => { type: "staticValue", staticValue: { type: "date", - date: { dateValue: "2026-08-31" }, + date: { + dateValue: "2026-08-31", + }, }, }, }, @@ -1351,7 +1705,9 @@ describe("convertFoundryMetaActionType OMS defaults", () => { type: "staticValue", staticValue: { type: "stringList", - stringList: { strings: ["priority", "customer"] }, + stringList: { + strings: ["priority", "customer"], + }, }, }, }, diff --git a/packages/foundry-ontology/src/meta/convertMetaActionType.ts b/packages/foundry-ontology/src/meta/convertMetaActionType.ts index c4e7783b..8988e02f 100644 --- a/packages/foundry-ontology/src/meta/convertMetaActionType.ts +++ b/packages/foundry-ontology/src/meta/convertMetaActionType.ts @@ -38,18 +38,35 @@ function maybeOptional(type: TypeDef, required: boolean): TypeDef { } function stringType(constraint?: StringConstraint, suggestions?: StringSuggestion[]): TypeDef { - return { kind: "string", value: { constraint, suggestions } }; + return { + kind: "string", + value: { + constraint, + suggestions, + }, + }; } function applyStringConstraintFallback(type: TypeDef, constraint: StringConstraint | undefined): TypeDef { if (!constraint) return type; switch (type.kind) { case "string": - return type.value.constraint ? type : { ...type, value: { ...type.value, constraint } }; + return type.value.constraint + ? type + : { + ...type, + value: { + ...type.value, + constraint, + }, + }; case "optional": return { ...type, - value: { ...type.value, type: applyStringConstraintFallback(type.value.type, constraint) }, + value: { + ...type.value, + type: applyStringConstraintFallback(type.value.type, constraint), + }, }; case "list": return { @@ -68,11 +85,22 @@ function applyStringSuggestionsFallback(type: TypeDef, suggestions: StringSugges if (!suggestions) return type; switch (type.kind) { case "string": - return type.value.suggestions ? type : { ...type, value: { ...type.value, suggestions } }; + return type.value.suggestions + ? type + : { + ...type, + value: { + ...type.value, + suggestions, + }, + }; case "optional": return { ...type, - value: { ...type.value, type: applyStringSuggestionsFallback(type.value.type, suggestions) }, + value: { + ...type.value, + type: applyStringSuggestionsFallback(type.value.type, suggestions), + }, }; case "list": return { @@ -169,7 +197,10 @@ function geopointType(): TypeDef { } function attachmentType(type: "attachment" | "media"): TypeDef { - return { kind: "attachment", value: { meta: foundryAttachmentMeta(type) } }; + return { + kind: "attachment", + value: { meta: foundryAttachmentMeta(type) }, + }; } function createUniqueIdentifierParameterName(linkId: string): string { @@ -190,7 +221,13 @@ function convertActionParameterType( ): TypeDef { const allowedValues = validation?.defaultValidation.allowedValues; if (allowedValues?.type === "valueType") { - return maybeOptional({ kind: "ref", value: { name: allowedValues.apiName } }, required); + return maybeOptional( + { + kind: "ref", + value: { name: allowedValues.apiName }, + }, + required + ); } const baseType: TypeDef = (() => { @@ -229,9 +266,15 @@ function convertActionParameterType( case "marking": return stringType(); case "vector": - return { kind: "list", value: { elementType: doubleType() } }; + return { + kind: "list", + value: { elementType: doubleType() }, + }; case "array": - return { kind: "list", value: { elementType: convertActionParameterType(type.subType) } }; + return { + kind: "list", + value: { elementType: convertActionParameterType(type.subType) }, + }; case "struct": return { kind: "struct", @@ -244,7 +287,10 @@ function convertActionParameterType( }, }; case "object": - return { kind: "objectReference", value: { objectType: type.objectTypeApiName } }; + return { + kind: "objectReference", + value: { objectType: type.objectTypeApiName }, + }; case "objectType": case "interfaceObject": case "objectSet": @@ -269,10 +315,18 @@ function convertActionParameterStringConstraint( const value: unknown = option.value; return typeof value === "string" ? [{ value, label: option.displayName }] : []; }); - return options.length > 0 ? { kind: "enum", value: { options } } : undefined; + return options.length > 0 + ? { + kind: "enum", + value: { options }, + } + : undefined; } if (allowedValues?.type === "text" && allowedValues.regex) { - return { kind: "regex", value: { regex: allowedValues.regex } }; + return { + kind: "regex", + value: { regex: allowedValues.regex }, + }; } return undefined; } @@ -286,7 +340,14 @@ function convertActionParameterStringSuggestions( } const suggestions = allowedValues.options.flatMap((option) => { const value: unknown = option.value; - return typeof value === "string" ? [{ value, label: option.displayName }] : []; + return typeof value === "string" + ? [ + { + value, + label: option.displayName, + }, + ] + : []; }); return suggestions.length > 0 ? suggestions : undefined; } @@ -318,7 +379,10 @@ function convertOntologyDataType(type: OntologyDataType, required = true): TypeD return attachmentType("media"); case "array": case "set": - return { kind: "list", value: { elementType: convertOntologyDataType(type.itemType) } }; + return { + kind: "list", + value: { elementType: convertOntologyDataType(type.itemType) }, + }; case "struct": return { kind: "struct", @@ -331,7 +395,10 @@ function convertOntologyDataType(type: OntologyDataType, required = true): TypeD }, }; case "object": - return { kind: "objectReference", value: { objectType: type.objectTypeApiName } }; + return { + kind: "objectReference", + value: { objectType: type.objectTypeApiName }, + }; default: return stringType(); } @@ -403,7 +470,10 @@ function createSyntheticParameters(actionType: ActionTypeFullMetadata): { name, displayName: `Generated UUID ${index + 1}`, type: { kind: "string", value: {} }, - defaultValue: { kind: "uuid", value: {} }, + defaultValue: { + kind: "uuid", + value: {}, + }, }) ); @@ -412,7 +482,10 @@ function createSyntheticParameters(actionType: ActionTypeFullMetadata): { name: nowParameterName, displayName: "Current time", type: { kind: "timestamp", value: {} }, - defaultValue: { kind: "now", value: {} }, + defaultValue: { + kind: "now", + value: {}, + }, }); } @@ -448,16 +521,27 @@ export function getFoundryActionOverrideParameterMapping(actionType: ActionTypeD } function inputReference(name: string): Expression { - return { kind: "inputReference", value: { name } }; + return { + kind: "inputReference", + value: { name }, + }; } function getAtExpression(source: Expression, path: string[]): Expression { - return { kind: "getAt", value: { source, path } }; + return { + kind: "getAt", + value: { source, path }, + }; } function objectField(parameterName: string, path: string[]): Expression { return getAtExpression( - { kind: "objectLookup", value: { reference: inputReference(parameterName) } }, + { + kind: "objectLookup", + value: { + reference: inputReference(parameterName), + }, + }, path ); } @@ -521,16 +605,30 @@ function convertLogicRuleArgument( const parameterName = argument.linkId ? syntheticParameters.uniqueIdentifierParametersByLinkId.get(argument.linkId) : undefined; - return parameterName ? inputReference(parameterName) : { kind: "uuid", value: {} }; + return parameterName + ? inputReference(parameterName) + : { + kind: "uuid", + value: {}, + }; } case "currentTime": return syntheticParameters.nowParameterName ? inputReference(syntheticParameters.nowParameterName) - : { kind: "now", value: {} }; + : { + kind: "now", + value: {}, + }; case "staticValue": - return { kind: "literal", value: { value: decodeLiteralValue(argument.value) } }; + return { + kind: "literal", + value: { value: decodeLiteralValue(argument.value) }, + }; case "currentUser": - return { kind: "contextReference", value: { name: FOUNDRY_CURRENT_USER_CONTEXT_PATH[0]! } }; + return { + kind: "contextReference", + value: { name: FOUNDRY_CURRENT_USER_CONTEXT_PATH[0]! }, + }; default: throw new Error(`Unsupported Foundry action argument "${argument.type}".`); } @@ -623,7 +721,9 @@ function convertStructPropertyAssignments( ? getAtExpression( { kind: "localReference", - value: { name: FOUNDRY_LIST_ITEM_BINDING }, + value: { + name: FOUNDRY_LIST_ITEM_BINDING, + }, }, [argument.structParameterFieldApiName] ) @@ -658,12 +758,21 @@ function convertLogicStep( return { kind: "updateObject", value: { - object: { name: rule.objectToModify }, + object: { + name: rule.objectToModify, + }, values: convertAssignments(rule, parameters, syntheticParameters), }, }; case "deleteObject": - return { kind: "deleteObject", value: { object: { name: rule.objectToDelete } } }; + return { + kind: "deleteObject", + value: { + object: { + name: rule.objectToDelete, + }, + }, + }; default: return null; } diff --git a/packages/foundry-ontology/src/meta/convertMetaLinkType.test.ts b/packages/foundry-ontology/src/meta/convertMetaLinkType.test.ts index 58374611..b594db11 100644 --- a/packages/foundry-ontology/src/meta/convertMetaLinkType.test.ts +++ b/packages/foundry-ontology/src/meta/convertMetaLinkType.test.ts @@ -70,8 +70,16 @@ describe("convertFoundryMetaLinkTypes", () => { expect(result).toEqual([ { id: linkRid, - source: { objectType: "Post", name: "posts", displayName: "Posts" }, - target: { objectType: "Author", name: "author", displayName: "Author" }, + source: { + objectType: "Post", + name: "posts", + displayName: "Posts", + }, + target: { + objectType: "Author", + name: "author", + displayName: "Author", + }, foreignKey: "authorId", cardinality: "many", }, diff --git a/packages/foundry-ontology/src/meta/convertMetaObjectType.test.ts b/packages/foundry-ontology/src/meta/convertMetaObjectType.test.ts index 6201d5b3..079fb04f 100644 --- a/packages/foundry-ontology/src/meta/convertMetaObjectType.test.ts +++ b/packages/foundry-ontology/src/meta/convertMetaObjectType.test.ts @@ -46,27 +46,49 @@ describe("convertFoundryMetaObjectType", () => { name: "Employee", primaryKey: "id", title: "fullName", - icon: { name: "person", meta: { blueprint: { name: "person" } } }, + icon: { + name: "person", + meta: { + blueprint: { + name: "person", + }, + }, + }, color: "#2d72d2", }); expect(result.properties).toEqual([ expect.objectContaining({ id: "ri.ontology.main.property.employee-id", name: "id", - type: { kind: "string", value: {} }, + type: { + kind: "string", + value: {}, + }, }), expect.objectContaining({ id: "ri.ontology.main.property.employee-name", name: "fullName", displayName: "Full name", - type: { kind: "optional", value: { type: { kind: "string", value: {} } } }, + type: { + kind: "optional", + value: { + type: { + kind: "string", + value: {}, + }, + }, + }, }), ]); }); it("omits unknown Blueprint icons without inventing a semantic match", () => { const source = objectType(); - source.icon = { type: "blueprint", name: "vendor-only-icon", color: "#000000" }; + source.icon = { + type: "blueprint", + name: "vendor-only-icon", + color: "#000000", + }; expect( convertFoundryMetaObjectType({ @@ -76,7 +98,10 @@ describe("convertFoundryMetaObjectType", () => { implementsInterfaces2: {}, sharedPropertyTypeMapping: {}, }) - ).toMatchObject({ icon: undefined, color: "#000000" }); + ).toMatchObject({ + icon: undefined, + color: "#000000", + }); }); it("treats non-primary attachment and value-type properties as optional", () => { @@ -105,11 +130,27 @@ describe("convertFoundryMetaObjectType", () => { expect(result.properties.find((property) => property.name === "profileImage")?.type).toEqual({ kind: "optional", - value: { type: { kind: "attachment", value: { meta: { foundry: { kind: "attachment" } } } } }, + value: { + type: { + kind: "attachment", + value: { + meta: { + foundry: { + kind: "attachment", + }, + }, + }, + }, + }, }); expect(result.properties.find((property) => property.name === "departmentCode")?.type).toEqual({ kind: "optional", - value: { type: { kind: "ref", value: { name: "DepartmentCode" } } }, + value: { + type: { + kind: "ref", + value: { name: "DepartmentCode" }, + }, + }, }); }); @@ -164,7 +205,13 @@ describe("convertFoundryMetaObjectType", () => { kind: "struct", value: { fields: [ - { name: "city", type: { kind: "optional", value: { type: { kind: "string" } } } }, + { + name: "city", + type: { + kind: "optional", + value: { type: { kind: "string" } }, + }, + }, { name: "coordinates", type: { @@ -178,7 +225,9 @@ describe("convertFoundryMetaObjectType", () => { name: "latitude", type: { kind: "optional", - value: { type: { kind: "double" } }, + value: { + type: { kind: "double" }, + }, }, }, ], @@ -209,6 +258,9 @@ describe("convertFoundryMetaObjectType", () => { const result = convertFoundryMetaObjectType(metadata); - expect(result.properties[0]?.type).toEqual({ kind: "string", value: {} }); + expect(result.properties[0]?.type).toEqual({ + kind: "string", + value: {}, + }); }); }); diff --git a/packages/foundry-ontology/src/meta/convertMetaObjectType.ts b/packages/foundry-ontology/src/meta/convertMetaObjectType.ts index 51044972..a2c4877c 100644 --- a/packages/foundry-ontology/src/meta/convertMetaObjectType.ts +++ b/packages/foundry-ontology/src/meta/convertMetaObjectType.ts @@ -34,7 +34,17 @@ function convertFoundryObjectProperty( name, displayName: property.displayName ?? name, description: property.description, - deprecated: property.status?.type === "deprecated" ? { message: property.status.message } : undefined, - type: required ? type : { kind: "optional", value: { type } }, + deprecated: + property.status?.type === "deprecated" + ? { + message: property.status.message, + } + : undefined, + type: required + ? type + : { + kind: "optional", + value: { type }, + }, }; } diff --git a/packages/foundry-ontology/src/meta/convertMetaQueryFunctionType.test.ts b/packages/foundry-ontology/src/meta/convertMetaQueryFunctionType.test.ts index 80674f21..ec0b39c7 100644 --- a/packages/foundry-ontology/src/meta/convertMetaQueryFunctionType.test.ts +++ b/packages/foundry-ontology/src/meta/convertMetaQueryFunctionType.test.ts @@ -20,7 +20,10 @@ describe("convertFoundryMetaQueryFunctionType", () => { queryType( { apiKey: { - dataType: { type: "union", unionTypes: [{ type: "string" }, { type: "null" }] }, + dataType: { + type: "union", + unionTypes: [{ type: "string" }, { type: "null" }], + }, required: false, }, countries: { @@ -36,10 +39,18 @@ describe("convertFoundryMetaQueryFunctionType", () => { ); expect(result.parameters.map(({ type }) => type)).toEqual([ - { kind: "optional", value: { type: { kind: "string", value: {} } } }, { kind: "optional", - value: { type: { kind: "list", value: { elementType: { kind: "string", value: {} } } } }, + value: { type: { kind: "string", value: {} } }, + }, + { + kind: "optional", + value: { + type: { + kind: "list", + value: { elementType: { kind: "string", value: {} } }, + }, + }, }, ]); }); @@ -96,7 +107,12 @@ describe("convertFoundryMetaQueryFunctionType", () => { displayName: "address2", type: { kind: "optional", - value: { type: { kind: "string", value: {} } }, + value: { + type: { + kind: "string", + value: {}, + }, + }, }, }, ], @@ -112,7 +128,13 @@ describe("convertFoundryMetaQueryFunctionType", () => { it("preserves unions with multiple non-null variants", () => { const result = convertFoundryMetaQueryFunctionType( - queryType({}, { type: "union", unionTypes: [{ type: "string" }, { type: "integer" }] }) + queryType( + {}, + { + type: "union", + unionTypes: [{ type: "string" }, { type: "integer" }], + } + ) ); expect(result.returnType).toEqual({ diff --git a/packages/foundry-ontology/src/meta/convertMetaQueryFunctionType.ts b/packages/foundry-ontology/src/meta/convertMetaQueryFunctionType.ts index 628fb606..f8349b6d 100644 --- a/packages/foundry-ontology/src/meta/convertMetaQueryFunctionType.ts +++ b/packages/foundry-ontology/src/meta/convertMetaQueryFunctionType.ts @@ -30,7 +30,10 @@ function timestampType(): TypeDef { } function attachmentType(type: "attachment" | "media"): TypeDef { - return { kind: "attachment", value: { meta: { type } } }; + return { + kind: "attachment", + value: { meta: { type } }, + }; } function maybeOptional(type: TypeDef, required: boolean): TypeDef { diff --git a/packages/foundry-ontology/src/meta/convertMetaTypeDef.ts b/packages/foundry-ontology/src/meta/convertMetaTypeDef.ts index cee92cc4..35166a91 100644 --- a/packages/foundry-ontology/src/meta/convertMetaTypeDef.ts +++ b/packages/foundry-ontology/src/meta/convertMetaTypeDef.ts @@ -129,10 +129,17 @@ export function convertFoundryObjectPropertyType(type: ObjectPropertyType): Type case "mediaReference": return { kind: "attachment", - value: { meta: foundryAttachmentMeta(type.type === "attachment" ? "attachment" : "media") }, + value: { + meta: foundryAttachmentMeta(type.type === "attachment" ? "attachment" : "media"), + }, }; case "vector": - return { kind: "list", value: { elementType: { kind: "double", value: {} } } }; + return { + kind: "list", + value: { + elementType: { kind: "double", value: {} }, + }, + }; case "array": return { kind: "list", @@ -146,7 +153,9 @@ export function convertFoundryObjectPropertyType(type: ObjectPropertyType): Type case "struct": return { kind: "struct", - value: { fields: type.structFieldTypes.map(convertFoundryStructField) }, + value: { + fields: type.structFieldTypes.map(convertFoundryStructField), + }, }; } } @@ -155,7 +164,12 @@ export function convertFoundryStructField(field: StructFieldType): PropertyDef { return { name: field.apiName, displayName: field.apiName, - type: { kind: "optional", value: { type: convertFoundryObjectPropertyType(field.dataType) } }, + type: { + kind: "optional", + value: { + type: convertFoundryObjectPropertyType(field.dataType), + }, + }, }; } @@ -166,13 +180,19 @@ function extractStringConstraint(constraints: ValueTypeConstraint[]): StringCons .filter((option): option is string => typeof option === "string") .map((value) => ({ value })); if (options.length > 0) { - return { kind: "enum", value: { options } }; + return { + kind: "enum", + value: { options }, + }; } } const regexConstraint = constraints.find((constraint) => constraint.type === "regex"); if (regexConstraint) { - return { kind: "regex", value: { regex: regexConstraint.pattern } }; + return { + kind: "regex", + value: { regex: regexConstraint.pattern }, + }; } return undefined; diff --git a/packages/foundry-ontology/src/meta/convertOmsActionParameterMetadata.ts b/packages/foundry-ontology/src/meta/convertOmsActionParameterMetadata.ts index d77194dd..3ffb66a7 100644 --- a/packages/foundry-ontology/src/meta/convertOmsActionParameterMetadata.ts +++ b/packages/foundry-ontology/src/meta/convertOmsActionParameterMetadata.ts @@ -71,7 +71,12 @@ function convertDefaultValue( if (defaultValue.type === "staticValue") { const value = convertFoundryStaticValue(defaultValue.staticValue); - return value === undefined ? undefined : { kind: "literal", value: { value } }; + return value === undefined + ? undefined + : { + kind: "literal", + value: { value }, + }; } if (defaultValue.type === "objectParameterPropertyValue") { @@ -87,7 +92,14 @@ function convertDefaultValue( value: { source: { kind: "objectLookup", - value: { reference: { kind: "inputReference", value: { name: parameterName } } }, + value: { + reference: { + kind: "inputReference", + value: { + name: parameterName, + }, + }, + }, }, path: [propertyApiName], }, @@ -111,9 +123,12 @@ function getOmsActionParameterAllowedValues( return validation?.defaultValidation.validation?.allowedValues; } -function getOmsOneOf( - allowedValues: AllowedParameterValues | undefined -): { options: StringSuggestion[]; otherValuesAllowed: boolean } | undefined { +function getOmsOneOf(allowedValues: AllowedParameterValues | undefined): + | { + options: StringSuggestion[]; + otherValuesAllowed: boolean; + } + | undefined { if (allowedValues?.type !== "oneOf") return undefined; if (allowedValues.oneOf.type !== "oneOf") { return undefined; @@ -122,9 +137,21 @@ function getOmsOneOf( const options = oneOf.labelledValues.flatMap((entry) => { const value = convertFoundryStaticValue(entry.value); - return typeof value === "string" ? [{ value, label: entry.label }] : []; + return typeof value === "string" + ? [ + { + value, + label: entry.label, + }, + ] + : []; }); - return options.length > 0 ? { options, otherValuesAllowed: oneOf.otherValueAllowed.allowed } : undefined; + return options.length > 0 + ? { + options, + otherValuesAllowed: oneOf.otherValueAllowed.allowed, + } + : undefined; } export function convertOmsActionParameterStringConstraint( @@ -137,7 +164,10 @@ export function convertOmsActionParameterStringConstraint( if (allowedValues?.type === "oneOf") { const oneOf = getOmsOneOf(allowedValues); return oneOf && !oneOf.otherValuesAllowed - ? { kind: "enum", value: { options: oneOf.options } } + ? { + kind: "enum", + value: { options: oneOf.options }, + } : undefined; } @@ -146,7 +176,12 @@ export function convertOmsActionParameterStringConstraint( return undefined; } const regex = allowedValues.text.text.regex?.regex; - return regex ? { kind: "regex", value: { regex } } : undefined; + return regex + ? { + kind: "regex", + value: { regex }, + } + : undefined; } return undefined; diff --git a/packages/foundry-ontology/src/meta/createFoundryMetaOntologyBackendAdapter.test.ts b/packages/foundry-ontology/src/meta/createFoundryMetaOntologyBackendAdapter.test.ts index 65ed3021..4b35cb2e 100644 --- a/packages/foundry-ontology/src/meta/createFoundryMetaOntologyBackendAdapter.test.ts +++ b/packages/foundry-ontology/src/meta/createFoundryMetaOntologyBackendAdapter.test.ts @@ -5,7 +5,10 @@ import type { OntologyClient } from "@party-stack/foundry-client"; vi.mock("@tanstack/query-db-collection", async (importOriginal) => { const original = await importOriginal(); - return { ...original, queryCollectionOptions: vi.fn(original.queryCollectionOptions) }; + return { + ...original, + queryCollectionOptions: vi.fn(original.queryCollectionOptions), + }; }); const client: OntologyClient = { diff --git a/packages/foundry-ontology/src/meta/entityCollectionOptions.test.ts b/packages/foundry-ontology/src/meta/entityCollectionOptions.test.ts index f40e1f29..16df8fad 100644 --- a/packages/foundry-ontology/src/meta/entityCollectionOptions.test.ts +++ b/packages/foundry-ontology/src/meta/entityCollectionOptions.test.ts @@ -5,13 +5,18 @@ import { createFoundryMetaOntologyBackendAdapter } from "./createFoundryMetaOnto import type { ObjectTypeV2 } from "@osdk/foundry.ontologies"; import type { OntologyClient } from "@party-stack/foundry-client"; -const mocks = vi.hoisted(() => ({ getFullMetadata: vi.fn() })); +const mocks = vi.hoisted(() => ({ + getFullMetadata: vi.fn(), +})); vi.mock("@osdk/foundry.ontologies", async (importOriginal) => { const original = await importOriginal(); return { ...original, - OntologiesV2: { ...original.OntologiesV2, getFullMetadata: mocks.getFullMetadata }, + OntologiesV2: { + ...original.OntologiesV2, + getFullMetadata: mocks.getFullMetadata, + }, }; }); @@ -85,7 +90,10 @@ describe("Foundry ObjectType metadata queries", () => { name: "Employee", title: "fullName", properties: [ - expect.objectContaining({ id: "ri.ontology.main.property.employee-id", name: "id" }), + expect.objectContaining({ + id: "ri.ontology.main.property.employee-id", + name: "id", + }), expect.objectContaining({ id: "ri.ontology.main.property.employee-name", name: "fullName", diff --git a/packages/foundry-ontology/src/meta/entityCollectionOptions.ts b/packages/foundry-ontology/src/meta/entityCollectionOptions.ts index ef46f74c..ee7600ae 100644 --- a/packages/foundry-ontology/src/meta/entityCollectionOptions.ts +++ b/packages/foundry-ontology/src/meta/entityCollectionOptions.ts @@ -83,9 +83,11 @@ export function linkTypeCollectionOptions(metadata: MetaEntityCollection): Ontol }) as unknown as OntologyCollectionOptions; } -async function loadFoundryMetaOntology( - client: OntologyClient -): Promise<{ objectTypes: MetaObjectType[]; valueTypes: MetaValueType[]; linkTypes: MetaLinkType[] }> { +async function loadFoundryMetaOntology(client: OntologyClient): Promise<{ + objectTypes: MetaObjectType[]; + valueTypes: MetaValueType[]; + linkTypes: MetaLinkType[]; +}> { const ontology = await OntologiesV2.getFullMetadata(client, client.ontologyRid); const objectTypeMetadata = Object.values(ontology.objectTypes); @@ -93,5 +95,9 @@ async function loadFoundryMetaOntology( const valueTypes = Object.values(ontology.valueTypes).map(convertFoundryMetaValueType); const linkTypes = convertFoundryMetaLinkTypes(objectTypeMetadata); - return { objectTypes, valueTypes, linkTypes }; + return { + objectTypes, + valueTypes, + linkTypes, + }; } diff --git a/packages/foundry-ontology/src/meta/loadActionTypeOmsMetadata.test.ts b/packages/foundry-ontology/src/meta/loadActionTypeOmsMetadata.test.ts index 17b981e1..27686733 100644 --- a/packages/foundry-ontology/src/meta/loadActionTypeOmsMetadata.test.ts +++ b/packages/foundry-ontology/src/meta/loadActionTypeOmsMetadata.test.ts @@ -1,9 +1,13 @@ import { beforeEach, describe, expect, it, vi } from "vite-plus/test"; import type { OntologyClient } from "@party-stack/foundry-client"; -const { bulkLoadOntologyEntities } = vi.hoisted(() => ({ bulkLoadOntologyEntities: vi.fn() })); +const { bulkLoadOntologyEntities } = vi.hoisted(() => ({ + bulkLoadOntologyEntities: vi.fn(), +})); -vi.mock("@osdk/client.unstable", () => ({ bulkLoadOntologyEntities })); +vi.mock("@osdk/client.unstable", () => ({ + bulkLoadOntologyEntities, +})); import { convertOmsActionParameterStringConstraint, @@ -28,7 +32,9 @@ describe("loadActionTypeOmsMetadata", () => { actionTypeLogic: { validation: { parameterValidations: {} } }, metadata: { parameters: {} }, }; - bulkLoadOntologyEntities.mockResolvedValue({ actionTypes: [{ actionType }, null] }); + bulkLoadOntologyEntities.mockResolvedValue({ + actionTypes: [{ actionType }, null], + }); const result = await loadActionTypeOmsMetadata(client, ["ri.action.one", "ri.action.two"]); @@ -50,7 +56,10 @@ describe("loadActionTypeOmsMetadata", () => { typeGroups: [], } ); - expect(result.get("ri.action.one")).toEqual({ actionType, propertyApiNamesByParameter: new Map() }); + expect(result.get("ri.action.one")).toEqual({ + actionType, + propertyApiNamesByParameter: new Map(), + }); expect(result.has("ri.action.two")).toBe(false); }); @@ -70,14 +79,22 @@ describe("loadActionTypeOmsMetadata", () => { labelledValues: [ { label: "United States", - value: { type: "string", string: "US" }, + value: { + type: "string", + string: "US", + }, }, { label: "Canada", - value: { type: "string", string: "CA" }, + value: { + type: "string", + string: "CA", + }, }, ], - otherValueAllowed: { allowed: true }, + otherValueAllowed: { + allowed: true, + }, }, }, }, @@ -115,8 +132,14 @@ describe("loadActionTypeOmsMetadata", () => { ).toBeDefined(); expect(convertOmsActionParameterStringConstraint(loaded, "country")).toBeUndefined(); expect(convertOmsActionParameterStringSuggestions(loaded, "country")).toEqual([ - { value: "US", label: "United States" }, - { value: "CA", label: "Canada" }, + { + value: "US", + label: "United States", + }, + { + value: "CA", + label: "Canada", + }, ]); }); @@ -144,19 +167,34 @@ describe("loadActionTypeOmsMetadata", () => { metadata: { parameters: { project: { - type: { type: "objectReference", objectReference: { objectTypeId: "project" } }, + type: { + type: "objectReference", + objectReference: { + objectTypeId: "project", + }, + }, }, }, }, }; bulkLoadOntologyEntities - .mockResolvedValueOnce({ actionTypes: [{ actionType, ontologyVersion: "42" }] }) + .mockResolvedValueOnce({ + actionTypes: [ + { + actionType, + ontologyVersion: "42", + }, + ], + }) .mockResolvedValueOnce({ objectTypes: [ { objectType: { propertyTypes: { - "ri.property.project-color": { id: "project-color", apiName: "projectColor" }, + "ri.property.project-color": { + id: "project-color", + apiName: "projectColor", + }, }, }, }, @@ -172,8 +210,14 @@ describe("loadActionTypeOmsMetadata", () => { expect.objectContaining({ objectTypes: [ { - identifier: { type: "objectTypeId", objectTypeId: "project" }, - versionReference: { type: "ontologyVersion", ontologyVersion: "42" }, + identifier: { + type: "objectTypeId", + objectTypeId: "project", + }, + versionReference: { + type: "ontologyVersion", + ontologyVersion: "42", + }, }, ], }) diff --git a/packages/foundry-ontology/src/meta/loadActionTypeOmsMetadata.ts b/packages/foundry-ontology/src/meta/loadActionTypeOmsMetadata.ts index 57789f82..a2eb03de 100644 --- a/packages/foundry-ontology/src/meta/loadActionTypeOmsMetadata.ts +++ b/packages/foundry-ontology/src/meta/loadActionTypeOmsMetadata.ts @@ -48,7 +48,9 @@ export async function loadActionTypeOmsMetadata( async (rids) => { try { const response = await bulkLoadOntologyEntities(context, undefined, { - actionTypes: rids.map((rid) => ({ rid })), + actionTypes: rids.map((rid) => ({ + rid, + })), datasourceTypes: [], linkTypes: [], objectTypes: [], @@ -88,7 +90,10 @@ export async function loadActionTypeOmsMetadata( ); if (!objectTypeId) continue; - const dependency = { objectTypeId, ontologyVersion }; + const dependency = { + objectTypeId, + ontologyVersion, + }; dependencies.set(objectTypeDependencyKey(dependency), dependency); } } @@ -104,8 +109,14 @@ export async function loadActionTypeOmsMetadata( datasourceTypes: [], linkTypes: [], objectTypes: batch.map(({ objectTypeId, ontologyVersion }) => ({ - identifier: { type: "objectTypeId", objectTypeId }, - versionReference: { type: "ontologyVersion", ontologyVersion }, + identifier: { + type: "objectTypeId", + objectTypeId, + }, + versionReference: { + type: "ontologyVersion", + ontologyVersion, + }, })), loadRedacted: true, includeObjectTypesWithoutSearchableDatasources: true, @@ -144,7 +155,10 @@ export async function loadActionTypeOmsMetadata( const objectTypeId = getActionParameterObjectTypeId(response.actionType, parameterId); if (!objectTypeId) continue; const objectType = objectTypes.get( - objectTypeDependencyKey({ objectTypeId, ontologyVersion: response.ontologyVersion }) + objectTypeDependencyKey({ + objectTypeId, + ontologyVersion: response.ontologyVersion, + }) ); const propertyApiName = Object.values(objectType?.propertyTypes ?? {}).find( (property) => property.id === propertyTypeId @@ -156,7 +170,13 @@ export async function loadActionTypeOmsMetadata( propertyApiNamesByParameter.set(parameterId, properties); } - return [rid, { actionType: response.actionType, propertyApiNamesByParameter }]; + return [ + rid, + { + actionType: response.actionType, + propertyApiNamesByParameter, + }, + ]; }) ); } diff --git a/packages/foundry-ontology/src/meta/queryFunctionTypeCollectionOptions.ts b/packages/foundry-ontology/src/meta/queryFunctionTypeCollectionOptions.ts index a8ea7b07..c67b71b6 100644 --- a/packages/foundry-ontology/src/meta/queryFunctionTypeCollectionOptions.ts +++ b/packages/foundry-ontology/src/meta/queryFunctionTypeCollectionOptions.ts @@ -18,7 +18,10 @@ async function listQueryFunctionTypes(opts: QueryFunctionTypeCollectionOpts): Pr return AsyncIterable.toArray( AsyncIterable.fromPagination( (pageSize, pageToken: string | undefined) => - QueryTypes.list(opts.client, opts.client.ontologyRid, { pageSize, pageToken }), + QueryTypes.list(opts.client, opts.client.ontologyRid, { + pageSize, + pageToken, + }), (page) => page.nextPageToken, (page) => page.data, 100 diff --git a/packages/foundry-ontology/src/stagedWrites.test.ts b/packages/foundry-ontology/src/stagedWrites.test.ts index c5d16f71..b7c04e64 100644 --- a/packages/foundry-ontology/src/stagedWrites.test.ts +++ b/packages/foundry-ontology/src/stagedWrites.test.ts @@ -12,7 +12,13 @@ const ir: OntologyIR = { displayName: "Task", pluralDisplayName: "Tasks", primaryKey: "id", - properties: [{ name: "id", displayName: "ID", type: o.string({}) }], + properties: [ + { + name: "id", + displayName: "ID", + type: o.string({}), + }, + ], }, ], linkTypes: [], @@ -42,16 +48,29 @@ describe("runFoundryOntologyMutator", () => { ir, client, mutator: async ({ tx }) => { - await tx.mutate.Task!.create({ id: "task-1" }); - await tx.mutate.Task!.update("task-1", { title: "Updated" }); + await tx.mutate.Task!.create({ + id: "task-1", + }); + await tx.mutate.Task!.update("task-1", { + title: "Updated", + }); }, args: {}, - objectTypes: { Task: "TaskToken" }, + objectTypes: { + Task: "TaskToken", + }, }); expect(calls).toEqual([ ["create", "TaskToken", { id: "task-1" }], - ["update", { $apiName: "Task", $primaryKey: "task-1" }, { title: "Updated" }], + [ + "update", + { + $apiName: "Task", + $primaryKey: "task-1", + }, + { title: "Updated" }, + ], ]); }); }); diff --git a/packages/foundry-ontology/src/stagedWrites.ts b/packages/foundry-ontology/src/stagedWrites.ts index a3a172fe..07ecccd1 100644 --- a/packages/foundry-ontology/src/stagedWrites.ts +++ b/packages/foundry-ontology/src/stagedWrites.ts @@ -14,7 +14,10 @@ import type { InitialQueryBuilder } from "@tanstack/db"; export interface FoundryStagedWriteClient { create(objectType: unknown, object: Record): Promise; update( - object: { $apiName: string; $primaryKey: string | number }, + object: { + $apiName: string; + $primaryKey: string | number; + }, changes: Record ): Promise; delete(object: { $apiName: string; $primaryKey: string | number }): Promise; @@ -75,7 +78,9 @@ export function createFoundryStagedWriteMutatorTx( return { create: async (object: Record) => { await options.client.create( - options.objectTypes?.[objectType] ?? { $apiName: objectType }, + options.objectTypes?.[objectType] ?? { + $apiName: objectType, + }, object ); }, @@ -84,12 +89,18 @@ export function createFoundryStagedWriteMutatorTx( changes: Record | OntologyPropertyChange[] ) => { await options.client.update( - { $apiName: objectType, $primaryKey: key }, + { + $apiName: objectType, + $primaryKey: key, + }, changesToRecord(changes) ); }, delete: async (key: string | number) => { - await options.client.delete({ $apiName: objectType, $primaryKey: key }); + await options.client.delete({ + $apiName: objectType, + $primaryKey: key, + }); }, }; }, @@ -114,5 +125,9 @@ export async function runFoundryOntologyMutator(options: { client: options.client, objectTypes: options.objectTypes, }); - await options.mutator({ tx, args: options.args, context: options.context ?? {} }); + await options.mutator({ + tx, + args: options.args, + context: options.context ?? {}, + }); } diff --git a/packages/foundry-ontology/src/users/createFoundryAdminOntologyRoute.ts b/packages/foundry-ontology/src/users/createFoundryAdminOntologyRoute.ts index 51810bff..bc79029f 100644 --- a/packages/foundry-ontology/src/users/createFoundryAdminOntologyRoute.ts +++ b/packages/foundry-ontology/src/users/createFoundryAdminOntologyRoute.ts @@ -8,7 +8,9 @@ import { foundryUserObjectType } from "./foundryUser.js"; const foundryUserIdentityLens = { operations: [ - o.LensOp.select({ properties: foundryUserObjectType.properties.map((property) => property.name) }), + o.LensOp.select({ + properties: foundryUserObjectType.properties.map((property) => property.name), + }), ], }; @@ -20,16 +22,29 @@ export const foundryAdminOntology = defineOntology({ queryFunctionTypes: [], contextType: o.struct({ fields: [ - { name: "user", displayName: "User", type: o.objectReference({ objectType: "FoundryUser" }) }, + { + name: "user", + displayName: "User", + type: o.objectReference({ + objectType: "FoundryUser", + }), + }, ], }), }); -export function createFoundryAdminOntologyRoute(options: { ontologyId?: string } = {}): FoundryOntologyRoute { +export function createFoundryAdminOntologyRoute( + options: { + ontologyId?: string; + } = {} +): FoundryOntologyRoute { const ontologyId = options.ontologyId ?? "admin"; return createFoundryOntologyRoute({ ontologyId, ir: foundryAdminOntology, - users: createFoundryUsersIntegration({ objectType: "FoundryUser", lens: foundryUserIdentityLens }), + users: createFoundryUsersIntegration({ + objectType: "FoundryUser", + lens: foundryUserIdentityLens, + }), }); } diff --git a/packages/foundry-ontology/src/users/createFoundryUsersIntegration.test.ts b/packages/foundry-ontology/src/users/createFoundryUsersIntegration.test.ts index ee84648c..0ce3a3b7 100644 --- a/packages/foundry-ontology/src/users/createFoundryUsersIntegration.test.ts +++ b/packages/foundry-ontology/src/users/createFoundryUsersIntegration.test.ts @@ -3,16 +3,35 @@ import { describe, expect, it, vi } from "vite-plus/test"; import { createFoundryUsersIntegration } from "./createFoundryUsersIntegration.js"; import { foundryUserProfilePictureAttachment } from "./foundryUser.js"; -const mocks = vi.hoisted(() => ({ profilePicture: vi.fn() })); +const mocks = vi.hoisted(() => ({ + profilePicture: vi.fn(), +})); -vi.mock("@osdk/foundry.admin", () => ({ Users: { profilePicture: mocks.profilePicture } })); +vi.mock("@osdk/foundry.admin", () => ({ + Users: { + profilePicture: mocks.profilePicture, + }, +})); -const identityLens = { operations: [o.LensOp.select({ properties: ["id"] })] } satisfies Lens; +const identityLens = { + operations: [ + o.LensOp.select({ + properties: ["id"], + }), + ], +} satisfies Lens; describe("createFoundryUsersIntegration", () => { it("represents a missing profile picture as an empty blob", async () => { - mocks.profilePicture.mockResolvedValue(new Response(null, { status: 204 })); - const integration = createFoundryUsersIntegration({ objectType: "User", lens: identityLens }); + mocks.profilePicture.mockResolvedValue( + new Response(null, { + status: 204, + }) + ); + const integration = createFoundryUsersIntegration({ + objectType: "User", + lens: identityLens, + }); await expect( integration.getAttachmentContent!( diff --git a/packages/foundry-ontology/src/users/createFoundryUsersIntegration.ts b/packages/foundry-ontology/src/users/createFoundryUsersIntegration.ts index a1539d73..e66c43df 100644 --- a/packages/foundry-ontology/src/users/createFoundryUsersIntegration.ts +++ b/packages/foundry-ontology/src/users/createFoundryUsersIntegration.ts @@ -29,7 +29,11 @@ export function createFoundryUsersIntegration(options: { const userId = decodeFoundryUserProfilePictureAttachment(attachment.id); return Promise.resolve( userId - ? { id: attachment.id, type: attachment.type, name: `${userId} profile picture` } + ? { + id: attachment.id, + type: attachment.type, + name: `${userId} profile picture`, + } : undefined ); }, diff --git a/packages/foundry-ontology/src/users/foundryUser.ts b/packages/foundry-ontology/src/users/foundryUser.ts index d00afea3..58c7cfe1 100644 --- a/packages/foundry-ontology/src/users/foundryUser.ts +++ b/packages/foundry-ontology/src/users/foundryUser.ts @@ -9,9 +9,21 @@ export const foundryUserObjectType: ObjectTypeDef = { properties: [ { name: "id", displayName: "ID", type: o.string({}) }, { name: "username", displayName: "Username", type: o.string({}) }, - { name: "givenName", displayName: "Given name", type: o.optional({ type: o.string({}) }) }, - { name: "familyName", displayName: "Family name", type: o.optional({ type: o.string({}) }) }, - { name: "email", displayName: "Email", type: o.optional({ type: o.string({}) }) }, + { + name: "givenName", + displayName: "Given name", + type: o.optional({ type: o.string({}) }), + }, + { + name: "familyName", + displayName: "Family name", + type: o.optional({ type: o.string({}) }), + }, + { + name: "email", + displayName: "Email", + type: o.optional({ type: o.string({}) }), + }, { name: "profilePicture", displayName: "Profile picture", @@ -42,7 +54,9 @@ const PROFILE_PICTURE_CACHE_WINDOW_MS = 60 * 60 * 1_000; */ export function foundryUserProfilePictureAttachment(userId: string): v.attachment { const cacheBucket = Math.floor(Date.now() / PROFILE_PICTURE_CACHE_WINDOW_MS); - return { id: `${PROFILE_PICTURE_PREFIX}${encodeURIComponent(userId)}:${cacheBucket}` }; + return { + id: `${PROFILE_PICTURE_PREFIX}${encodeURIComponent(userId)}:${cacheBucket}`, + }; } export function decodeFoundryUserProfilePictureAttachment(attachmentId: string): string | undefined { @@ -62,5 +76,7 @@ export function decodeFoundryUserProfilePictureAttachment(attachmentId: string): } export function createFoundryUserObjectType(objectType: string, lens: Lens): ObjectTypeDef { - return applyLensToObjectType(foundryUserObjectType, lens, { name: objectType }); + return applyLensToObjectType(foundryUserObjectType, lens, { + name: objectType, + }); } diff --git a/packages/foundry-ontology/src/users/userCollectionOptions.test.ts b/packages/foundry-ontology/src/users/userCollectionOptions.test.ts index 0ea1d7bf..c552e532 100644 --- a/packages/foundry-ontology/src/users/userCollectionOptions.test.ts +++ b/packages/foundry-ontology/src/users/userCollectionOptions.test.ts @@ -13,7 +13,10 @@ describe("convertQuery", () => { ilike(new IR.PropRef(["familyName"]), "alex%") ); - expect(convertQuery({ where }, identityLens)).toEqual({ type: "search", query: "alex" }); + expect(convertQuery({ where }, identityLens)).toEqual({ + type: "search", + query: "alex", + }); }); it("falls back to listing when OR search terms differ", () => { diff --git a/packages/foundry-ontology/src/users/userCollectionOptions.ts b/packages/foundry-ontology/src/users/userCollectionOptions.ts index d2ff1d89..64ddd697 100644 --- a/packages/foundry-ontology/src/users/userCollectionOptions.ts +++ b/packages/foundry-ontology/src/users/userCollectionOptions.ts @@ -21,7 +21,13 @@ export interface UserCollectionOpts { export function userCollectionOptions({ client, lens }: UserCollectionOpts) { const project = (user: User) => - applyLensToObject({ ...user, profilePicture: foundryUserProfilePictureAttachment(user.id) }, lens); + applyLensToObject( + { + ...user, + profilePicture: foundryUserProfilePictureAttachment(user.id), + }, + lens + ); return queryCollectionOptions>({ queryClient: new QueryClient(), getKey: (user) => user.id as string, diff --git a/packages/icons-blueprint/src/index.ts b/packages/icons-blueprint/src/index.ts index 40e48df7..b783f238 100644 --- a/packages/icons-blueprint/src/index.ts +++ b/packages/icons-blueprint/src/index.ts @@ -132,7 +132,9 @@ for (const [name, blueprintName] of Object.entries(BlueprintIconNames)) { } export interface BlueprintIconMeta extends Record { - blueprint: { name: string }; + blueprint: { + name: string; + }; } export function fromBlueprintIconName(name: string): IconDescriptor | undefined { @@ -141,7 +143,12 @@ export function fromBlueprintIconName(name: string): IconDescriptor | undefined if (!canonicalName || universalNames.length !== 1) { return undefined; } - return { name: canonicalName, meta: { blueprint: { name } } satisfies BlueprintIconMeta }; + return { + name: canonicalName, + meta: { + blueprint: { name }, + } satisfies BlueprintIconMeta, + }; } export function toBlueprintIconName(icon: IconDescriptor): string | undefined { diff --git a/packages/icons-blueprint/src/react.test.tsx b/packages/icons-blueprint/src/react.test.tsx index 52557936..ccaa12fd 100644 --- a/packages/icons-blueprint/src/react.test.tsx +++ b/packages/icons-blueprint/src/react.test.tsx @@ -7,12 +7,19 @@ describe("BlueprintIcon", () => { const element = BlueprintIcon({ name: "add", size: 20, title: "Add" }); expect(element.type).toBe(Icon); - expect(element.props).toMatchObject({ icon: "plus", size: 20, title: "Add" }); + expect(element.props).toMatchObject({ + icon: "plus", + size: 20, + title: "Add", + }); }); it("passes unsupported mappings to Blueprint as empty icons", () => { const element = BlueprintIcon({ name: "ticket" }); - expect(element).toMatchObject({ type: Icon, props: { icon: undefined } }); + expect(element).toMatchObject({ + type: Icon, + props: { icon: undefined }, + }); }); }); diff --git a/packages/icons-expo/src/index.test.ts b/packages/icons-expo/src/index.test.ts index e240cd62..9cb91388 100644 --- a/packages/icons-expo/src/index.test.ts +++ b/packages/icons-expo/src/index.test.ts @@ -3,13 +3,22 @@ import { getExpoSymbol, MaterialSymbolArchiveNames } from "./index.js"; describe("Expo icon adapter", () => { it("maps universal concepts to typed native symbols only", () => { - expect(getExpoSymbol("airplane")).toEqual({ ios: "airplane", android: "flight" }); - expect(getExpoSymbol("ticket")).toEqual({ ios: "ticket", android: "confirmation_number" }); + expect(getExpoSymbol("airplane")).toEqual({ + ios: "airplane", + android: "flight", + }); + expect(getExpoSymbol("ticket")).toEqual({ + ios: "ticket", + android: "confirmation_number", + }); expect(getExpoSymbol("ticket")).not.toHaveProperty("web"); }); it("uses reviewed native mappings without filling explicit gaps", () => { - expect(getExpoSymbol("activity")).toEqual({ ios: "waveform.path.ecg", android: "ecg_heart" }); + expect(getExpoSymbol("activity")).toEqual({ + ios: "waveform.path.ecg", + android: "ecg_heart", + }); expect(getExpoSymbol("rocket")).toEqual({ android: "rocket_launch" }); expect(MaterialSymbolArchiveNames.activity).toBe("ecg-heart-outline"); expect(MaterialSymbolArchiveNames["minus-circle"]).toBe("do-not-disturb-on-outline-rounded"); diff --git a/packages/icons-salesforce-lightning/src/index.ts b/packages/icons-salesforce-lightning/src/index.ts index 2b044b2e..644f69e6 100644 --- a/packages/icons-salesforce-lightning/src/index.ts +++ b/packages/icons-salesforce-lightning/src/index.ts @@ -162,7 +162,9 @@ export function getSalesforceActionIconName(apiName: string): SalesforceLightnin } export interface SalesforceLightningIconMeta extends Record { - salesforce: { name: string }; + salesforce: { + name: string; + }; } export function fromSalesforceLightningIconName(name: string): IconDescriptor | undefined { @@ -175,7 +177,12 @@ export function fromSalesforceLightningIconName(name: string): IconDescriptor | if (!canonicalName) { return undefined; } - return { name: canonicalName, meta: { salesforce: { name } } satisfies SalesforceLightningIconMeta }; + return { + name: canonicalName, + meta: { + salesforce: { name }, + } satisfies SalesforceLightningIconMeta, + }; } export function toSalesforceLightningIconName(icon: IconDescriptor): string | undefined { diff --git a/packages/icons-salesforce-lightning/tsconfig.json b/packages/icons-salesforce-lightning/tsconfig.json index 3e4cc408..1326a774 100644 --- a/packages/icons-salesforce-lightning/tsconfig.json +++ b/packages/icons-salesforce-lightning/tsconfig.json @@ -1 +1,4 @@ -{ "$schema": "https://json.schemastore.org/tsconfig", "extends": "@party-stack/build-config/tsconfig.json" } +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@party-stack/build-config/tsconfig.json" +} diff --git a/packages/icons/src/index.test.ts b/packages/icons/src/index.test.ts index 15f5f4c1..865836ec 100644 --- a/packages/icons/src/index.test.ts +++ b/packages/icons/src/index.test.ts @@ -10,8 +10,14 @@ describe("universal icon vocabulary", () => { }); it("keeps provider data in namespaced meta", () => { - const icon: IconDescriptor = { name: "airplane", meta: { example: { name: "plane" } } }; + const icon: IconDescriptor = { + name: "airplane", + meta: { example: { name: "plane" } }, + }; - expect(icon).toEqual({ name: "airplane", meta: { example: { name: "plane" } } }); + expect(icon).toEqual({ + name: "airplane", + meta: { example: { name: "plane" } }, + }); }); }); diff --git a/packages/icons/tsconfig.json b/packages/icons/tsconfig.json index 3e4cc408..1326a774 100644 --- a/packages/icons/tsconfig.json +++ b/packages/icons/tsconfig.json @@ -1 +1,4 @@ -{ "$schema": "https://json.schemastore.org/tsconfig", "extends": "@party-stack/build-config/tsconfig.json" } +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@party-stack/build-config/tsconfig.json" +} diff --git a/packages/node-runtime/src/NodeFileSystemBlobBytesStore.test.ts b/packages/node-runtime/src/NodeFileSystemBlobBytesStore.test.ts index a70120e9..333b6bf3 100644 --- a/packages/node-runtime/src/NodeFileSystemBlobBytesStore.test.ts +++ b/packages/node-runtime/src/NodeFileSystemBlobBytesStore.test.ts @@ -9,7 +9,9 @@ const directories: string[] = []; afterEach(async () => { await Promise.all( directories.splice(0).map(async (directory) => { - const store = new NodeFileSystemBlobBytesStore({ directory }); + const store = new NodeFileSystemBlobBytesStore({ + directory, + }); await store.clear(); }) ); @@ -19,7 +21,9 @@ describe("NodeFileSystemBlobBytesStore", () => { it("round-trips bytes and treats ids as opaque values", async () => { const directory = await mkdtemp(join(tmpdir(), "party-stack-blobs-")); directories.push(directory); - const store = new NodeFileSystemBlobBytesStore({ directory }); + const store = new NodeFileSystemBlobBytesStore({ + directory, + }); await store.write("../../outside", new Blob(["hello"])); @@ -30,7 +34,9 @@ describe("NodeFileSystemBlobBytesStore", () => { it("deletes missing and existing blobs idempotently", async () => { const directory = await mkdtemp(join(tmpdir(), "party-stack-blobs-")); directories.push(directory); - const store = new NodeFileSystemBlobBytesStore({ directory }); + const store = new NodeFileSystemBlobBytesStore({ + directory, + }); await store.write("one", new Blob(["one"])); await store.delete("one"); diff --git a/packages/node-runtime/src/NodeFileSystemBlobBytesStore.ts b/packages/node-runtime/src/NodeFileSystemBlobBytesStore.ts index 99e1856e..c78fc2eb 100644 --- a/packages/node-runtime/src/NodeFileSystemBlobBytesStore.ts +++ b/packages/node-runtime/src/NodeFileSystemBlobBytesStore.ts @@ -35,7 +35,9 @@ export class NodeFileSystemBlobBytesStore implements BlobBytesStore { return new Blob([await readFile(this.#path(id))]); } catch (error) { if (error instanceof Error && "code" in error && error.code === "ENOENT") { - throw new Error(`Blob bytes not found for "${id}".`, { cause: error }); + throw new Error(`Blob bytes not found for "${id}".`, { + cause: error, + }); } throw error; } diff --git a/packages/node-runtime/src/NodeSecretStore.test.ts b/packages/node-runtime/src/NodeSecretStore.test.ts index 2d6aef48..721127da 100644 --- a/packages/node-runtime/src/NodeSecretStore.test.ts +++ b/packages/node-runtime/src/NodeSecretStore.test.ts @@ -2,7 +2,9 @@ import { beforeEach, describe, expect, it, vi } from "vite-plus/test"; import { NodeSecretStore } from "./NodeSecretStore.js"; import type { SecretStorageBackend } from "cross-keychain"; -const keychain = vi.hoisted(() => ({ getAllBackends: vi.fn() })); +const keychain = vi.hoisted(() => ({ + getAllBackends: vi.fn(), +})); vi.mock("cross-keychain", () => keychain); @@ -44,7 +46,9 @@ describe("NodeSecretStore", () => { const backend = memoryBackend(); keychain.getAllBackends.mockResolvedValue([backend]); const setPassword = vi.spyOn(backend, "setPassword"); - const store = new NodeSecretStore({ service: "party-stack:test:secrets" }); + const store = new NodeSecretStore({ + service: "party-stack:test:secrets", + }); await store.set("tokens/client:id", "secret"); @@ -60,14 +64,18 @@ describe("NodeSecretStore", () => { it("rejects file-only keychain availability", async () => { keychain.getAllBackends.mockResolvedValue([memoryBackend("file")]); - const store = new NodeSecretStore({ service: "party-stack:test:secrets" }); + const store = new NodeSecretStore({ + service: "party-stack:test:secrets", + }); await expect(store.get("token")).rejects.toThrow("No OS keychain is available"); }); it("selects the Linux Secret Service lazily", async () => { keychain.getAllBackends.mockResolvedValue([memoryBackend("secret-service")]); - const store = new NodeSecretStore({ service: "party-stack:test:secrets" }); + const store = new NodeSecretStore({ + service: "party-stack:test:secrets", + }); expect(keychain.getAllBackends).not.toHaveBeenCalled(); await expect(store.get("token")).resolves.toBeUndefined(); diff --git a/packages/node-runtime/src/createNodeBrowserAuthentication.test.ts b/packages/node-runtime/src/createNodeBrowserAuthentication.test.ts index 87944e31..4a82e4aa 100644 --- a/packages/node-runtime/src/createNodeBrowserAuthentication.test.ts +++ b/packages/node-runtime/src/createNodeBrowserAuthentication.test.ts @@ -30,8 +30,12 @@ describe("createNodeBrowserAuthentication", () => { const response = await fetch(`${redirectUrl}?code=code&state=state`); responseText = await response.text(); }); - const authentication = createNodeBrowserAuthentication({ openUrl }); - const session = authentication.start({ redirectUrl }); + const authentication = createNodeBrowserAuthentication({ + openUrl, + }); + const session = authentication.start({ + redirectUrl, + }); await expect(session.open("https://auth.example/authorize")).resolves.toEqual({ callbackUrl: `${redirectUrl}?code=code&state=state`, @@ -54,7 +58,9 @@ describe("createNodeBrowserAuthentication", () => { return Promise.resolve(); }, }); - const session = authentication.start({ redirectUrl: `http://127.0.0.1:${port}/callback` }); + const session = authentication.start({ + redirectUrl: `http://127.0.0.1:${port}/callback`, + }); const result = session.open("https://auth.example/authorize"); await didOpen; @@ -66,8 +72,10 @@ describe("createNodeBrowserAuthentication", () => { it("rejects non-loopback redirects", () => { const authentication = createNodeBrowserAuthentication(); - expect(() => authentication.start({ redirectUrl: "https://app.example/callback" })).toThrow( - "requires an http:// loopback redirect URL" - ); + expect(() => + authentication.start({ + redirectUrl: "https://app.example/callback", + }) + ).toThrow("requires an http:// loopback redirect URL"); }); }); diff --git a/packages/node-runtime/src/createNodeBrowserAuthentication.ts b/packages/node-runtime/src/createNodeBrowserAuthentication.ts index 8ce5c8c9..fa61ad1d 100644 --- a/packages/node-runtime/src/createNodeBrowserAuthentication.ts +++ b/packages/node-runtime/src/createNodeBrowserAuthentication.ts @@ -72,7 +72,13 @@ export function createNodeBrowserAuthentication( settled = true; const callbackUrl = context.req.url; setImmediate(() => { - void finish().then(() => resolve({ callbackUrl }), reject); + void finish().then( + () => + resolve({ + callbackUrl, + }), + reject + ); }); return context.html(` diff --git a/packages/node-runtime/src/createNodeRuntime.test.ts b/packages/node-runtime/src/createNodeRuntime.test.ts index 3ea36d69..d7f90f30 100644 --- a/packages/node-runtime/src/createNodeRuntime.test.ts +++ b/packages/node-runtime/src/createNodeRuntime.test.ts @@ -16,7 +16,12 @@ const directories: string[] = []; afterEach(async () => { await Promise.all( - directories.splice(0).map((directory) => rm(directory, { recursive: true, force: true })) + directories.splice(0).map((directory) => + rm(directory, { + recursive: true, + force: true, + }) + ) ); }); @@ -40,7 +45,12 @@ describe("createNodeRuntime", () => { createLocalCollection< { id: string; instant: Temporal.Instant; nested: { dates: Temporal.PlainDate[] } }, string - >({ name: "temporal", getKey: (item) => item.id, runtime, schemaVersion: 1 }); + >({ + name: "temporal", + getKey: (item) => item.id, + runtime, + schemaVersion: 1, + }); const firstRuntime = await provider("owner", "temporal"); const first = open(firstRuntime); const collectionId = first.id; @@ -117,11 +127,16 @@ describe("createNodeRuntime", () => { it("reopens persisted SQLite collections", async () => { const directory = await mkdtemp(join(tmpdir(), "party-stack-runtime-")); directories.push(directory); - const provider = createNodeRuntimeWithOptions({ dataDirectory: directory }); + const provider = createNodeRuntimeWithOptions({ + dataDirectory: directory, + }); const firstRuntime = await provider("owner", "namespace"); const first = createItems(firstRuntime); await first.preload(); - await first.insert({ id: "one", title: "Persisted" }).isPersisted.promise; + await first.insert({ + id: "one", + title: "Persisted", + }).isPersisted.promise; await first.cleanup(); await firstRuntime.cleanup?.(); @@ -129,7 +144,10 @@ describe("createNodeRuntime", () => { const second = createItems(secondRuntime); await second.preload(); - expect(second.get("one")).toMatchObject({ id: "one", title: "Persisted" }); + expect(second.get("one")).toMatchObject({ + id: "one", + title: "Persisted", + }); await second.cleanup(); await secondRuntime.destroy?.(); }); @@ -139,7 +157,12 @@ describe("createNodeRuntime", () => { directories.push(directory); const runtime = await createNodeRuntimeWithOptions({ dataDirectory: directory })("owner", "schemas"); const open = (name: string, schemaVersion: number) => - createLocalCollection({ name, schemaVersion, runtime, getKey: (item) => item.id }); + createLocalCollection({ + name, + schemaVersion, + runtime, + getKey: (item) => item.id, + }); const tasks = open("tasks", 2); const settings = open("settings", 7); try { @@ -180,7 +203,9 @@ describe("createNodeRuntime", () => { it("isolates runtime namespaces", async () => { const directory = await mkdtemp(join(tmpdir(), "party-stack-runtime-")); directories.push(directory); - const provider = createNodeRuntimeWithOptions({ dataDirectory: directory }); + const provider = createNodeRuntimeWithOptions({ + dataDirectory: directory, + }); const first = await provider("owner", "one"); const second = await provider("owner", "two"); diff --git a/packages/node-runtime/src/createNodeRuntime.ts b/packages/node-runtime/src/createNodeRuntime.ts index 19964935..49ae6cfa 100644 --- a/packages/node-runtime/src/createNodeRuntime.ts +++ b/packages/node-runtime/src/createNodeRuntime.ts @@ -36,26 +36,40 @@ export function createNodeRuntimeWithOptions(options: CreateNodeRuntimeOptions): return defineRuntime(async (owner, namespace) => { const scope = `party-stack:${owner}:${namespace}`; const directory = join(options.dataDirectory ?? defaultDataDirectory(), runtimeId(owner, namespace)); - await mkdir(directory, { recursive: true }); + await mkdir(directory, { + recursive: true, + }); const database = new Database(join(directory, "collections.sqlite")); - const persistence = createNodeSQLitePersistence({ database }); - const coordination = new SingleProcessCoordination({ scope }); - const blobBytes = new NodeFileSystemBlobBytesStore({ directory: join(directory, "blobs") }); + const persistence = createNodeSQLitePersistence({ + database, + }); + const coordination = new SingleProcessCoordination({ + scope, + }); + const blobBytes = new NodeFileSystemBlobBytesStore({ + directory: join(directory, "blobs"), + }); return { owner, namespace, blobBytes, browserAuthentication: createNodeBrowserAuthentication(), - secrets: new NodeSecretStore({ service: `party-stack:${runtimeId(owner, namespace)}:secrets` }), + secrets: new NodeSecretStore({ + service: `party-stack:${runtimeId(owner, namespace)}:secrets`, + }), coordination, persistence, cleanup: async () => { await coordination.close(); database.close(); }, - destroy: () => rm(directory, { recursive: true, force: true }), + destroy: () => + rm(directory, { + recursive: true, + force: true, + }), }; }); } diff --git a/packages/node-runtime/tsconfig.json b/packages/node-runtime/tsconfig.json index 3e4cc408..1326a774 100644 --- a/packages/node-runtime/tsconfig.json +++ b/packages/node-runtime/tsconfig.json @@ -1 +1,4 @@ -{ "$schema": "https://json.schemastore.org/tsconfig", "extends": "@party-stack/build-config/tsconfig.json" } +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@party-stack/build-config/tsconfig.json" +} diff --git a/packages/oauth/src/createClientCredentialsOAuthClient.test.ts b/packages/oauth/src/createClientCredentialsOAuthClient.test.ts index ed6b2d7c..1997768a 100644 --- a/packages/oauth/src/createClientCredentialsOAuthClient.test.ts +++ b/packages/oauth/src/createClientCredentialsOAuthClient.test.ts @@ -7,7 +7,11 @@ describe("createClientCredentialsOAuthClient", () => { const fetch = vi.fn(async (input, init) => { const request = new Request(input, init); requestBody = new URLSearchParams(await request.text()); - return Response.json({ access_token: "access-token", expires_in: 3600, token_type: "Bearer" }); + return Response.json({ + access_token: "access-token", + expires_in: 3600, + token_type: "Bearer", + }); }); const client = createClientCredentialsOAuthClient({ clientId: "client", @@ -22,7 +26,9 @@ describe("createClientCredentialsOAuthClient", () => { await expect(client.getAccessToken()).resolves.toBe("access-token"); await expect(client.getSession()).resolves.toMatchObject({ userId: "user-1", - expiration: { refreshable: true }, + expiration: { + refreshable: true, + }, }); expect(fetch).toHaveBeenCalledOnce(); @@ -39,8 +45,13 @@ describe("createClientCredentialsOAuthClient", () => { requests.push(request); return Promise.resolve( request.url.endsWith("/revoke") - ? new Response(null, { status: 200 }) - : Response.json({ access_token: "access-token", expires_in: 3600 }) + ? new Response(null, { + status: 200, + }) + : Response.json({ + access_token: "access-token", + expires_in: 3600, + }) ); }); const client = createClientCredentialsOAuthClient({ diff --git a/packages/oauth/src/createClientCredentialsOAuthClient.ts b/packages/oauth/src/createClientCredentialsOAuthClient.ts index b0de46a3..d5b336fa 100644 --- a/packages/oauth/src/createClientCredentialsOAuthClient.ts +++ b/packages/oauth/src/createClientCredentialsOAuthClient.ts @@ -51,14 +51,20 @@ export function createClientCredentialsOAuthClient( parameters.set("client_id", options.clientId); parameters.set("client_secret", options.clientSecret); } - return new Request(endpoint, { method: "POST", headers, body: parameters }); + return new Request(endpoint, { + method: "POST", + headers, + body: parameters, + }); }; const requestToken = async (): Promise => { if (cleaned) { throw new Error("OAuth client has been cleaned up."); } - const body = new URLSearchParams({ grant_type: "client_credentials" }); + const body = new URLSearchParams({ + grant_type: "client_credentials", + }); if (options.scopes?.length) { body.set("scope", options.scopes.join(" ")); } @@ -99,7 +105,13 @@ export function createClientCredentialsOAuthClient( token = await tokenRequest; return token.expiresAt === undefined ? { userId: token.userId } - : { userId: token.userId, expiration: { expiresAt: token.expiresAt, refreshable: true } }; + : { + userId: token.userId, + expiration: { + expiresAt: token.expiresAt, + refreshable: true, + }, + }; }; return { @@ -115,7 +127,13 @@ export function createClientCredentialsOAuthClient( } return token.expiresAt === undefined ? { userId: token.userId } - : { userId: token.userId, expiration: { expiresAt: token.expiresAt, refreshable: true } }; + : { + userId: token.userId, + expiration: { + expiresAt: token.expiresAt, + refreshable: true, + }, + }; }, refresh, async revoke() { @@ -127,7 +145,10 @@ export function createClientCredentialsOAuthClient( const response = await fetchImpl( authenticatedRequest( options.revocationEndpoint, - new URLSearchParams({ token: accessToken, token_type_hint: "access_token" }) + new URLSearchParams({ + token: accessToken, + token_type_hint: "access_token", + }) ) ); if (!response.ok) { diff --git a/packages/oauth/src/createPublicOAuthClient.test.ts b/packages/oauth/src/createPublicOAuthClient.test.ts index cea60f00..1aa5c7bf 100644 --- a/packages/oauth/src/createPublicOAuthClient.test.ts +++ b/packages/oauth/src/createPublicOAuthClient.test.ts @@ -36,7 +36,9 @@ describe("createPublicOAuthClient", () => { const request = new Request(input, init); const body = new URLSearchParams(await request.text()); if (request.url.endsWith("/revoke")) { - return new Response(null, { status: 200 }); + return new Response(null, { + status: 200, + }); } const refresh = body.get("grant_type") === "refresh_token"; return Response.json({ @@ -146,7 +148,12 @@ describe("createPublicOAuthClient", () => { token_type: "Bearer", expires_in: 0, }) - : Response.json({ error: "invalid_grant" }, { status: 400 }) + : Response.json( + { + error: "invalid_grant", + }, + { status: 400 } + ) ); }); const client = await createPublicOAuthClient({ diff --git a/packages/oauth/src/createPublicOAuthClient.ts b/packages/oauth/src/createPublicOAuthClient.ts index c12d24a2..f9b20c2a 100644 --- a/packages/oauth/src/createPublicOAuthClient.ts +++ b/packages/oauth/src/createPublicOAuthClient.ts @@ -34,7 +34,9 @@ interface StoredTokenSet { const PENDING_MAX_AGE_MS = 10 * 60 * 1_000; interface OAuthCoordinationService { - methods: { refresh(input: { userId: string }): Promise }; + methods: { + refresh(input: { userId: string }): Promise; + }; events: Record; } @@ -43,7 +45,10 @@ function sessionFromTokens(userId: string, tokens: StoredTokenSet): OAuthSession ? { userId } : { userId, - expiration: { expiresAt: tokens.expiresAt, refreshable: tokens.refreshToken !== undefined }, + expiration: { + expiresAt: tokens.expiresAt, + refreshable: tokens.refreshToken !== undefined, + }, }; } @@ -75,9 +80,14 @@ export async function createPublicOAuthClient( token_endpoint: options.authorizationServer.tokenEndpoint, revocation_endpoint: options.authorizationServer.revocationEndpoint, }; - const client: oauth.Client = { client_id: options.clientId, token_endpoint_auth_method: "none" }; + const client: oauth.Client = { + client_id: options.clientId, + token_endpoint_auth_method: "none", + }; const clientAuthentication = oauth.None(); - const requestOptions = { [oauth.customFetch]: options.fetch ?? globalThis.fetch.bind(globalThis) }; + const requestOptions = { + [oauth.customFetch]: options.fetch ?? globalThis.fetch.bind(globalThis), + }; const accessTokens = new Map(); let cleaned = false; @@ -94,7 +104,11 @@ export async function createPublicOAuthClient( const existing = sessions.get(userId); if (!existing) { const transaction = sessions.insert( - { userId, secretKey, createdAt: Date.now() }, + { + userId, + secretKey, + createdAt: Date.now(), + }, { optimistic: false } ); await transaction.isPersisted.promise; @@ -103,7 +117,9 @@ export async function createPublicOAuthClient( await secrets.set(secretKey, JSON.stringify(tokens)); } catch (error) { if (!existing && sessions.get(userId)) { - await sessions.delete(userId, { optimistic: false }).isPersisted.promise; + await sessions.delete(userId, { + optimistic: false, + }).isPersisted.promise; } throw error; } @@ -124,14 +140,18 @@ export async function createPublicOAuthClient( const record = sessions.get(userId); if (!record) return; await secrets.delete(record.secretKey); - await sessions.delete(userId, { optimistic: false }).isPersisted.promise; + await sessions.delete(userId, { + optimistic: false, + }).isPersisted.promise; accessTokens.delete(userId); }; const removePending = async (state: string, secretKey: string) => { await secrets.delete(secretKey); if (!pending.get(state)) return; - const transaction = pending.delete(state, { optimistic: false }); + const transaction = pending.delete(state, { + optimistic: false, + }); await transaction.isPersisted.promise; }; const cleanupStalePending = async () => { @@ -241,7 +261,9 @@ export async function createPublicOAuthClient( const refresh = async (userId: string): Promise => { let session: OAuthSession; try { - session = await refreshCoordination.methods.refresh({ userId }); + session = await refreshCoordination.methods.refresh({ + userId, + }); } catch (error) { if (error instanceof Error && error.name === "UnauthenticatedError") { throw unauthenticated(error.message); @@ -273,7 +295,11 @@ export async function createPublicOAuthClient( const codeVerifier = oauth.generateRandomCodeVerifier(); const codeChallenge = await oauth.calculatePKCECodeChallenge(codeVerifier); const transaction = pending.insert( - { state, secretKey, createdAt: Date.now() }, + { + state, + secretKey, + createdAt: Date.now(), + }, { optimistic: false } ); await transaction.isPersisted.promise; @@ -311,7 +337,9 @@ export async function createPublicOAuthClient( const tokens = await readTokens(record); if (!tokens) { if ((record.createdAt ?? 0) < Date.now() - PENDING_MAX_AGE_MS) { - await sessions.delete(record.userId, { optimistic: false }).isPersisted.promise; + await sessions.delete(record.userId, { + optimistic: false, + }).isPersisted.promise; } continue; } diff --git a/packages/oauth/src/storage.ts b/packages/oauth/src/storage.ts index 16e9e332..b040805d 100644 --- a/packages/oauth/src/storage.ts +++ b/packages/oauth/src/storage.ts @@ -49,7 +49,9 @@ export async function resolveOAuthSecretStore(options: { }, async delete(key) { if (!records.get(key)) return; - const transaction = records.delete(key, { optimistic: false }); + const transaction = records.delete(key, { + optimistic: false, + }); await transaction.isPersisted.promise; }, cleanup: () => records.cleanup(), diff --git a/packages/oauth/src/types.ts b/packages/oauth/src/types.ts index 4cf11e77..43f117be 100644 --- a/packages/oauth/src/types.ts +++ b/packages/oauth/src/types.ts @@ -9,7 +9,10 @@ export interface OAuthAuthorizationServer { export interface OAuthSession { userId: string; - expiration?: { expiresAt: number; refreshable: boolean }; + expiration?: { + expiresAt: number; + refreshable: boolean; + }; } export interface PublicOAuthSignInOptions { diff --git a/packages/oauth/tsconfig.json b/packages/oauth/tsconfig.json index 3e4cc408..1326a774 100644 --- a/packages/oauth/tsconfig.json +++ b/packages/oauth/tsconfig.json @@ -1 +1,4 @@ -{ "$schema": "https://json.schemastore.org/tsconfig", "extends": "@party-stack/build-config/tsconfig.json" } +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@party-stack/build-config/tsconfig.json" +} diff --git a/packages/ontology-devtools/src/OntologyDevtools.test.tsx b/packages/ontology-devtools/src/OntologyDevtools.test.tsx index c62e0c0c..ed6b9812 100644 --- a/packages/ontology-devtools/src/OntologyDevtools.test.tsx +++ b/packages/ontology-devtools/src/OntologyDevtools.test.tsx @@ -48,7 +48,10 @@ describe("createOntologyDevtoolsPlugin", () => { if (typeof plugin.name !== "function") { throw new Error("Expected the plugin name property to be a function"); } - const title = plugin.name({} as HTMLElement, { devtoolsOpen: true, theme: "light" }); + const title = plugin.name({} as HTMLElement, { + devtoolsOpen: true, + theme: "light", + }); expect(title.type).toBe(OntologyDevtoolsPluginName); expect(title.props).toMatchObject({ theme: "light" }); }); @@ -62,14 +65,22 @@ describe("createOntologyDevtoolsPlugin", () => { defaultOpen: true, }); - expect(plugin).toMatchObject({ id: "custom-ontology", defaultOpen: true }); + expect(plugin).toMatchObject({ + id: "custom-ontology", + defaultOpen: true, + }); if (typeof plugin.render !== "function") { throw new Error("Expected a plugin renderer."); } - const panel = plugin.render({} as HTMLElement, { devtoolsOpen: true, theme: "light" }); + const panel = plugin.render({} as HTMLElement, { + devtoolsOpen: true, + theme: "light", + }); const Component = panel.type as (props: typeof panel.props) => ReactElement; const ontologyPanel = Component(panel.props); - expect(ontologyPanel.props).toMatchObject({ metaOntology }); + expect(ontologyPanel.props).toMatchObject({ + metaOntology, + }); }); it("exports the branded shell trigger separately", () => { @@ -87,7 +98,11 @@ describe("OntologyIcon", () => { expect(isValidElement(icon)).toBe(true); expect(icon).toMatchObject({ type: LucideIcon, - props: { className: "object-icon", name: "ticket", style: undefined }, + props: { + className: "object-icon", + name: "ticket", + style: undefined, + }, }); }); @@ -112,8 +127,16 @@ describe("layoutSchema", () => { linkTypes: [ { id: "project-tasks", - source: { objectType: "Project", name: "tasks", displayName: "Tasks" }, - target: { objectType: "Task", name: "project", displayName: "Project" }, + source: { + objectType: "Project", + name: "tasks", + displayName: "Tasks", + }, + target: { + objectType: "Task", + name: "project", + displayName: "Project", + }, foreignKey: "projectId", cardinality: "many", }, @@ -184,7 +207,10 @@ describe("layoutSchema", () => { expect(target).toBeDefined(); expect(touchesBoundary(points[0]!, source!)).toBe(true); expect(touchesBoundary(points.at(-1)!, target!)).toBe(true); - expect(points.at(-1)).not.toEqual({ x: target!.x + 115, y: target!.y + 88 }); + expect(points.at(-1)).not.toEqual({ + x: target!.x + 115, + y: target!.y + 88, + }); }); it("turns routed points into a rounded SVG path", () => { @@ -203,7 +229,14 @@ describe("property presentation", () => { expect( typeDisplayName({ kind: "optional", - value: { type: { kind: "list", value: { elementType: { kind: "attachment", value: {} } } } }, + value: { + type: { + kind: "list", + value: { + elementType: { kind: "attachment", value: {} }, + }, + }, + }, }) ).toBe("List of Attachment (optional)"); expect(typeDisplayName({ kind: "ref", value: { name: "EventTime" } })).toBe("EventTime"); @@ -222,7 +255,11 @@ describe("getOutboxActivity", () => { return { id: "entry", sequence: 1, - request: { actionTypeName: "save", idempotencyKey: "key", parameters: {} }, + request: { + actionTypeName: "save", + idempotencyKey: "key", + parameters: {}, + }, status: "queued", createdAt: 0, updatedAt: 0, diff --git a/packages/ontology-devtools/src/OntologyDevtools.tsx b/packages/ontology-devtools/src/OntologyDevtools.tsx index ab016836..b2658c4f 100644 --- a/packages/ontology-devtools/src/OntologyDevtools.tsx +++ b/packages/ontology-devtools/src/OntologyDevtools.tsx @@ -139,7 +139,11 @@ function PartyStackLogo({ theme }: OntologyDevtoolsChromeProps) { return (