diff --git a/README.md b/README.md index f67bd02b3a8..4f4140a63f0 100644 --- a/README.md +++ b/README.md @@ -177,19 +177,10 @@ If you need to run the browser plugin locally, see the `README.md` in the `apps/ ##### Resetting the local database -If you need to reset the local database, to clear out test data or because it has become corrupted during development, you have two options: +If you need to reset the local database, to clear out test data or because it has become corrupted during development: -1. The slow option – rebuild in Docker - - 1. In the Docker UI (or via CLI at your preference), stop and delete the `hash-external-services` container - 2. In 'Volumes', search 'hash-external-services' and delete the volumes shown - 3. Run `yarn external-services up --wait` to rebuild the services - -2. The fast option – reset the database via the Graph API - - 1. Run the Graph API in test mode by running `yarn dev:graph:test-server` - 2. Run `yarn graph:reset-database` to reset the database - 3. **If you need to use the frontend**, you will also need to delete the rows in the `identities` table in the `dev_kratos` database, or signin will not work. You can do so via any Postgres UI or CLI. The db connection and user details are in `.env` +1. Run `yarn external-services down -v` (this will take the Docker services down and drop the volumes) +2. Run `yarn external-services up --wait` to start everything again ##### External services test mode diff --git a/apps/hash-api/src/graph/ensure-system-graph-is-initialized/migrate-ontology-types.ts b/apps/hash-api/src/graph/ensure-system-graph-is-initialized/migrate-ontology-types.ts index 694c659e621..2b94ed2bd70 100644 --- a/apps/hash-api/src/graph/ensure-system-graph-is-initialized/migrate-ontology-types.ts +++ b/apps/hash-api/src/graph/ensure-system-graph-is-initialized/migrate-ontology-types.ts @@ -2,7 +2,21 @@ import { readdir } from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; +import type { ProvidedEntityEditionProvenance } from "@blockprotocol/type-system"; +import { componentsFromVersionedUrl } from "@blockprotocol/type-system"; +import { getHashInstance } from "@local/hash-backend-utils/hash-instance"; import type { Logger } from "@local/hash-backend-utils/logger"; +import { queryDataTypes } from "@local/hash-graph-sdk/data-type"; +import { queryEntityTypes } from "@local/hash-graph-sdk/entity-type"; +import { queryPropertyTypes } from "@local/hash-graph-sdk/property-type"; +import { currentTimeInstantTemporalAxes } from "@local/hash-isomorphic-utils/graph-queries"; +import { + systemDataTypes, + systemEntityTypes, + systemLinkEntityTypes, + systemPropertyTypes, +} from "@local/hash-isomorphic-utils/ontology-type-ids"; +import type { MigrationsCompletedPropertyValueWithMetadata } from "@local/hash-isomorphic-utils/system-types/hashinstance"; import { isProdEnv } from "../../lib/env-config"; import type { ImpureGraphContext } from "../context-types"; @@ -39,6 +53,144 @@ export const migrateOntologyTypes = async (params: { dataTypeVersions: {}, }; + /** + * `migrationState` is used as a cache for "current" ontology type versions while applying + * migrations. Historically this cache was only populated by the migrations that create/update + * ontology types. When migrations are skipped on an existing instance, the cache would be empty + * and later migrations that rely on `getCurrentHashSystemEntityTypeId` (etc.) would fail. + * + * To make migrations idempotent across fresh installs and existing deployments, we hydrate the + * cache from the graph before running any migration functions. + */ + const hydrateMigrationStateFromGraph = async () => { + const entityTypeBaseUrls = [ + ...Object.values(systemEntityTypes).map(({ entityTypeBaseUrl }) => + entityTypeBaseUrl, + ), + ...Object.values(systemLinkEntityTypes).map(({ linkEntityTypeBaseUrl }) => + linkEntityTypeBaseUrl, + ), + ]; + + const propertyTypeBaseUrls = Object.values(systemPropertyTypes).map( + ({ propertyTypeBaseUrl }) => propertyTypeBaseUrl, + ); + + const dataTypeBaseUrls = Object.values(systemDataTypes).map( + ({ dataTypeBaseUrl }) => dataTypeBaseUrl, + ); + + await Promise.all([ + ...entityTypeBaseUrls.map(async (baseUrl) => { + if (migrationState.entityTypeVersions[baseUrl]) { + return; + } + + const { entityTypes } = await queryEntityTypes( + params.context.graphApi, + authentication, + { + filter: { + all: [ + { + equal: [{ path: ["baseUrl"] }, { parameter: baseUrl }], + }, + { + equal: [{ path: ["version"] }, { parameter: "latest" }], + }, + ], + }, + temporalAxes: currentTimeInstantTemporalAxes, + limit: 1, + }, + ); + + const existing = entityTypes[0]; + if (!existing) { + return; + } + + const { version } = componentsFromVersionedUrl(existing.schema.$id); + migrationState.entityTypeVersions[baseUrl] = version; + }), + ...propertyTypeBaseUrls.map(async (baseUrl) => { + if (migrationState.propertyTypeVersions[baseUrl]) { + return; + } + + const { propertyTypes } = await queryPropertyTypes( + params.context.graphApi, + authentication, + { + filter: { + all: [ + { + equal: [{ path: ["baseUrl"] }, { parameter: baseUrl }], + }, + { + equal: [{ path: ["version"] }, { parameter: "latest" }], + }, + ], + }, + temporalAxes: currentTimeInstantTemporalAxes, + limit: 1, + }, + ); + + const existing = propertyTypes[0]; + if (!existing) { + return; + } + + const { version } = componentsFromVersionedUrl(existing.schema.$id); + migrationState.propertyTypeVersions[baseUrl] = version; + }), + ...dataTypeBaseUrls.map(async (baseUrl) => { + if (migrationState.dataTypeVersions[baseUrl]) { + return; + } + + const { dataTypes } = await queryDataTypes( + params.context.graphApi, + authentication, + { + filter: { + all: [ + { + equal: [{ path: ["baseUrl"] }, { parameter: baseUrl }], + }, + { + equal: [{ path: ["version"] }, { parameter: "latest" }], + }, + ], + }, + temporalAxes: currentTimeInstantTemporalAxes, + limit: 1, + }, + ); + + const existing = dataTypes[0]; + if (!existing) { + return; + } + + const { version } = componentsFromVersionedUrl(existing.schema.$id); + migrationState.dataTypeVersions[baseUrl] = version; + }), + ]); + }; + + const migrationsCompleted: string[] = []; + + try { + const hashInstance = await getHashInstance(params.context, authentication); + migrationsCompleted.push(...(hashInstance.migrationsCompleted ?? [])); + } catch { + // HASH Instance entity not available, this may be the first time the instance is being initialized + } + + await hydrateMigrationStateFromGraph(); + for (const migrationFileName of migrationFileNames) { if (migrationFileName.endsWith(".migration.ts")) { /** @@ -57,7 +209,20 @@ export const migrateOntologyTypes = async (params: { // Expect the default export of a migration file to be of type `MigrationFunction` const migrationFunction = module.default as MigrationFunction; - /** @todo: consider persisting which migration files have been run */ + const migrationNumber = migrationFileName.split("-")[0]; + + if (!migrationNumber) { + throw new Error( + `Migration file ${migrationFileName} has an invalid name. Migration files must be formatted as '{number}-{name}.migration.ts'`, + ); + } + + if (migrationsCompleted.includes(migrationNumber)) { + params.logger.info( + `Skipping migration ${migrationFileName} as it has already been processed`, + ); + continue; + } migrationState = await migrationFunction({ ...params, @@ -65,7 +230,35 @@ export const migrateOntologyTypes = async (params: { migrationState, }); - params.logger.debug(`Processed migration ${migrationFileName}`); + migrationsCompleted.push(migrationNumber); + + params.logger.info(`Processed migration ${migrationFileName}`); } } + + const hashInstance = await getHashInstance(params.context, authentication); + + await hashInstance.entity.patch(params.context.graphApi, authentication, { + propertyPatches: [ + { + op: "add", + path: [systemPropertyTypes.migrationsCompleted.propertyTypeBaseUrl], + property: { + value: migrationsCompleted.map((migration) => ({ + value: migration, + metadata: { + dataTypeId: + "https://blockprotocol.org/@blockprotocol/types/data-type/text/v/1", + }, + })), + } satisfies MigrationsCompletedPropertyValueWithMetadata, + }, + ], + provenance: { + actorType: "machine", + origin: { + type: "migration", + }, + } satisfies ProvidedEntityEditionProvenance, + }); }; diff --git a/apps/hash-api/src/graph/ensure-system-graph-is-initialized/migrate-ontology-types/migrations/001-create-hash-system-types.migration.ts b/apps/hash-api/src/graph/ensure-system-graph-is-initialized/migrate-ontology-types/migrations/001-create-hash-system-types.migration.ts index a4b4c921ae3..f19559a383a 100644 --- a/apps/hash-api/src/graph/ensure-system-graph-is-initialized/migrate-ontology-types/migrations/001-create-hash-system-types.migration.ts +++ b/apps/hash-api/src/graph/ensure-system-graph-is-initialized/migrate-ontology-types/migrations/001-create-hash-system-types.migration.ts @@ -1,4 +1,5 @@ import { getDataTypeById } from "@local/hash-graph-sdk/data-type"; +import { getEntityTypeById } from "@local/hash-graph-sdk/entity-type"; import { fullTransactionTimeAxis } from "@local/hash-isomorphic-utils/graph-queries"; import { blockProtocolEntityTypes, @@ -25,6 +26,11 @@ const blockProtocolDataTypeIds = [ "https://blockprotocol.org/@blockprotocol/types/data-type/value/v/1", ] as const; +const blockProtocolEntityTypeIds = [ + "https://blockprotocol.org/@hash/types/entity-type/query/v/1", + "https://blockprotocol.org/@hash/types/entity-type/has-query/v/1", +] as const; + const migrate: MigrationFunction = async ({ context, authentication, @@ -1732,8 +1738,8 @@ const migrate: MigrationFunction = async ({ /** * Ensure the primitive BP data types are loaded */ - await Promise.all( - blockProtocolDataTypeIds.map(async (dataTypeId) => { + await Promise.all([ + ...blockProtocolDataTypeIds.map(async (dataTypeId) => { const existingDataType = await getDataTypeById( context.graphApi, authentication, @@ -1751,7 +1757,25 @@ const migrate: MigrationFunction = async ({ dataTypeId, }); }), - ); + ...blockProtocolEntityTypeIds.map(async (entityTypeId) => { + const existingEntityType = await getEntityTypeById( + context.graphApi, + authentication, + { + entityTypeId, + temporalAxes: fullTransactionTimeAxis, + }, + ); + + if (existingEntityType) { + return; + } + + return context.graphApi.loadExternalEntityType(authentication.actorId, { + entityTypeId, + }); + }), + ]); return migrationState; }; diff --git a/apps/hash-api/src/graph/ensure-system-graph-is-initialized/migrate-ontology-types/migrations/005-create-hash-system-entities-and-web-bots.migration.ts b/apps/hash-api/src/graph/ensure-system-graph-is-initialized/migrate-ontology-types/migrations/005-create-hash-system-entities-and-web-bots.migration.ts index 96e08c1832f..abaa4b84a0f 100644 --- a/apps/hash-api/src/graph/ensure-system-graph-is-initialized/migrate-ontology-types/migrations/005-create-hash-system-entities-and-web-bots.migration.ts +++ b/apps/hash-api/src/graph/ensure-system-graph-is-initialized/migrate-ontology-types/migrations/005-create-hash-system-entities-and-web-bots.migration.ts @@ -45,6 +45,11 @@ const migrate: MigrationFunction = async ({ migrationState, }); + const currentHashInstanceEntityTypeId = getCurrentHashSystemEntityTypeId({ + entityTypeKey: "hashInstance", + migrationState, + }); + /** * Step 1: Create the system entities associated with the 'hash' web: * 1. The HASH org entity is required to create the HASH Instance entity in Step 2 @@ -68,7 +73,9 @@ const migrate: MigrationFunction = async ({ await getHashInstance(context, systemAccountAuthentication); } catch (error) { if (error instanceof NotFoundError) { - await createHashInstance(context, systemAccountAuthentication, {}); + await createHashInstance(context, systemAccountAuthentication, { + hashInstanceEntityTypeId: currentHashInstanceEntityTypeId, + }); logger.info("Created hashInstance entity"); } else { throw error; diff --git a/apps/hash-api/src/graph/ensure-system-graph-is-initialized/migrate-ontology-types/migrations/025-add-migrations-completed-to-hash-instance.migration.ts b/apps/hash-api/src/graph/ensure-system-graph-is-initialized/migrate-ontology-types/migrations/025-add-migrations-completed-to-hash-instance.migration.ts new file mode 100644 index 00000000000..168d1f2aad2 --- /dev/null +++ b/apps/hash-api/src/graph/ensure-system-graph-is-initialized/migrate-ontology-types/migrations/025-add-migrations-completed-to-hash-instance.migration.ts @@ -0,0 +1,127 @@ +import type { EntityType } from "@blockprotocol/type-system"; +import { getWebMachineId } from "@local/hash-backend-utils/machine-actors"; +import { getEntityTypeById } from "@local/hash-graph-sdk/entity-type"; +import { createPolicy, deletePolicyById } from "@local/hash-graph-sdk/policy"; +import { currentTimeInstantTemporalAxes } from "@local/hash-isomorphic-utils/graph-queries"; +import { systemEntityTypes } from "@local/hash-isomorphic-utils/ontology-type-ids"; + +import { getOrCreateOwningWebId } from "../../system-webs-and-entities"; +import type { MigrationFunction } from "../types"; +import { + createSystemPropertyTypeIfNotExists, + getCurrentHashSystemEntityTypeId, + updateSystemEntityType, + upgradeEntitiesToNewTypeVersion, +} from "../util"; + +const migrate: MigrationFunction = async ({ + context, + authentication, + migrationState, +}) => { + const migrationsCompletedPropertyType = + await createSystemPropertyTypeIfNotExists(context, authentication, { + propertyTypeDefinition: { + title: "Migrations Completed", + description: + "The migrations that have been completed for this instance", + possibleValues: [{ primitiveDataType: "text", array: true }], + }, + webShortname: "h", + migrationState, + }); + + const currentHashInstanceEntityTypeId = getCurrentHashSystemEntityTypeId({ + entityTypeKey: "hashInstance", + migrationState, + }); + + const hashInstanceEntityType = await getEntityTypeById( + context.graphApi, + authentication, + { + entityTypeId: currentHashInstanceEntityTypeId, + temporalAxes: currentTimeInstantTemporalAxes, + }, + ); + + if (!hashInstanceEntityType) { + throw new Error( + `Could not find entity type with ID ${currentHashInstanceEntityTypeId}`, + ); + } + + const hashInstanceEntityTypeSchema = hashInstanceEntityType.schema; + + const newHashInstanceEntityTypeSchema: EntityType = { + ...hashInstanceEntityTypeSchema, + properties: { + ...hashInstanceEntityTypeSchema.properties, + [migrationsCompletedPropertyType.metadata.recordId.baseUrl]: { + $ref: migrationsCompletedPropertyType.schema.$id, + }, + }, + }; + + const { updatedEntityTypeId } = await updateSystemEntityType( + context, + authentication, + { + currentEntityTypeId: currentHashInstanceEntityTypeId, + migrationState, + newSchema: newHashInstanceEntityTypeSchema, + }, + ); + + const { webId: hashWebId } = await getOrCreateOwningWebId(context, "h"); + + const hashWebBotAccountId = await getWebMachineId(context, authentication, { + webId: hashWebId, + }).then((maybeMachineId) => { + if (!maybeMachineId) { + throw new Error( + `Failed to get web bot account ID for web ID: ${hashWebId}`, + ); + } + return maybeMachineId; + }); + + const instantiationPolicies = await Promise.all( + [updatedEntityTypeId, currentHashInstanceEntityTypeId].map( + async (entityTypeId) => + createPolicy(context.graphApi, authentication, { + name: "tmp-hash-instance-instantiate", + effect: "permit", + principal: { + type: "actor", + actorType: "machine", + id: hashWebBotAccountId, + }, + actions: ["instantiate"], + resource: { + type: "entityType", + id: entityTypeId, + }, + }), + ), + ); + + try { + await upgradeEntitiesToNewTypeVersion(context, authentication, { + entityTypeBaseUrls: [systemEntityTypes.hashInstance.entityTypeBaseUrl], + migrationState, + }); + } finally { + await Promise.all( + instantiationPolicies.map(async (policyId) => + deletePolicyById(context.graphApi, authentication, policyId, { + permanent: true, + }), + ), + ); + } + + return migrationState; +}; + +export default migrate; diff --git a/apps/hash-api/src/graph/knowledge/system-types/hash-instance.ts b/apps/hash-api/src/graph/knowledge/system-types/hash-instance.ts index 132ff83c26c..5b04d4c27e8 100644 --- a/apps/hash-api/src/graph/knowledge/system-types/hash-instance.ts +++ b/apps/hash-api/src/graph/knowledge/system-types/hash-instance.ts @@ -1,4 +1,4 @@ -import type { ActorId } from "@blockprotocol/type-system"; +import type { ActorId, VersionedUrl } from "@blockprotocol/type-system"; import { NotFoundError } from "@local/hash-backend-utils/error"; import type { HashInstance } from "@local/hash-backend-utils/hash-instance"; import { @@ -7,7 +7,6 @@ import { } from "@local/hash-backend-utils/hash-instance"; import { createPolicy, deletePolicyById } from "@local/hash-graph-sdk/policy"; import { getInstanceAdminsTeam } from "@local/hash-graph-sdk/principal/hash-instance-admins"; -import { systemEntityTypes } from "@local/hash-isomorphic-utils/ontology-type-ids"; import type { HASHInstance as HashInstanceEntity } from "@local/hash-isomorphic-utils/system-types/hashinstance"; import { logger } from "../../../logger"; @@ -24,6 +23,7 @@ import { createEntity } from "../primitive/entity"; */ export const createHashInstance: ImpureGraphFunction< { + hashInstanceEntityTypeId: VersionedUrl; pagesAreEnabled?: boolean; userSelfRegistrationIsEnabled?: boolean; userRegistrationByInviteIsEnabled?: boolean; @@ -67,7 +67,7 @@ export const createHashInstance: ImpureGraphFunction< actions: ["instantiate"], resource: { type: "entityType", - id: systemEntityTypes.hashInstance.entityTypeId, + id: params.hashInstanceEntityTypeId, }, }); @@ -109,7 +109,9 @@ export const createHashInstance: ImpureGraphFunction< }, }, }, - entityTypeIds: [systemEntityTypes.hashInstance.entityTypeId], + entityTypeIds: [ + params.hashInstanceEntityTypeId, + ] as HashInstanceEntity["entityTypeIds"], }); return getHashInstanceFromEntity({ entity }); diff --git a/apps/hash-frontend/src/middleware/return-types-as-json.ts b/apps/hash-frontend/src/middleware/return-types-as-json.ts index 3583fa9bd1d..d68803afb87 100644 --- a/apps/hash-frontend/src/middleware/return-types-as-json.ts +++ b/apps/hash-frontend/src/middleware/return-types-as-json.ts @@ -53,7 +53,19 @@ const makeGraphQlRequest = async ( method: "POST", headers: { "content-type": "application/json", cookie: cookie ?? "" }, body: JSON.stringify({ query, variables }), - }).then((resp) => resp.json()); + }) + .then((resp) => resp.json()) + .catch((err) => { + return { + data: null, + errors: [ + { + message: err.message ?? "Internal server error", + extensions: { code: "INTERNAL_SERVER_ERROR" }, + }, + ], + }; + }); return { data, errors }; }; diff --git a/libs/@local/hash-backend-utils/src/hash-instance.ts b/libs/@local/hash-backend-utils/src/hash-instance.ts index d610b334550..ae4050aae35 100644 --- a/libs/@local/hash-backend-utils/src/hash-instance.ts +++ b/libs/@local/hash-backend-utils/src/hash-instance.ts @@ -1,10 +1,10 @@ -import type { ActorEntityUuid } from "@blockprotocol/type-system"; +import { + type ActorEntityUuid, + extractBaseUrl, +} from "@blockprotocol/type-system"; import type { GraphApi } from "@local/hash-graph-client"; import { type HashEntity, queryEntities } from "@local/hash-graph-sdk/entity"; -import { - currentTimeInstantTemporalAxes, - generateVersionedUrlMatchingFilter, -} from "@local/hash-isomorphic-utils/graph-queries"; +import { currentTimeInstantTemporalAxes } from "@local/hash-isomorphic-utils/graph-queries"; import { systemEntityTypes } from "@local/hash-isomorphic-utils/ontology-type-ids"; import type { SimpleProperties } from "@local/hash-isomorphic-utils/simplify-properties"; import { simplifyProperties } from "@local/hash-isomorphic-utils/simplify-properties"; @@ -27,8 +27,10 @@ export const getHashInstanceFromEntity = ({ entity: HashEntity; }): HashInstance => { if ( - !entity.metadata.entityTypeIds.includes( - systemEntityTypes.hashInstance.entityTypeId, + !entity.metadata.entityTypeIds.some( + (entityTypeId) => + extractBaseUrl(entityTypeId) === + systemEntityTypes.hashInstance.entityTypeBaseUrl, ) ) { throw new EntityTypeMismatchError( @@ -54,10 +56,12 @@ export const getHashInstance = async ( const { entities } = await backOff( () => queryEntities(context, authentication, { - filter: generateVersionedUrlMatchingFilter( - systemEntityTypes.hashInstance.entityTypeId, - { ignoreParents: true }, - ), + filter: { + equal: [ + { path: ["type", "baseUrl"] }, + { parameter: systemEntityTypes.hashInstance.entityTypeBaseUrl }, + ], + }, temporalAxes: currentTimeInstantTemporalAxes, includeDrafts: false, includePermissions: false, diff --git a/libs/@local/hash-isomorphic-utils/src/ontology-type-ids.ts b/libs/@local/hash-isomorphic-utils/src/ontology-type-ids.ts index f1037625aea..90c60a3c817 100644 --- a/libs/@local/hash-isomorphic-utils/src/ontology-type-ids.ts +++ b/libs/@local/hash-isomorphic-utils/src/ontology-type-ids.ts @@ -99,7 +99,7 @@ export const systemEntityTypes = { "https://hash.ai/@h/types/entity-type/graph-change-notification/" as BaseUrl, }, hashInstance: { - entityTypeId: "https://hash.ai/@h/types/entity-type/hash-instance/v/1", + entityTypeId: "https://hash.ai/@h/types/entity-type/hash-instance/v/2", entityTypeBaseUrl: "https://hash.ai/@h/types/entity-type/hash-instance/" as BaseUrl, }, @@ -798,6 +798,12 @@ export const systemPropertyTypes = { propertyTypeBaseUrl: "https://hash.ai/@h/types/property-type/methodology/" as BaseUrl, }, + migrationsCompleted: { + propertyTypeId: + "https://hash.ai/@h/types/property-type/migrations-completed/v/1", + propertyTypeBaseUrl: + "https://hash.ai/@h/types/property-type/migrations-completed/" as BaseUrl, + }, nctId: { propertyTypeId: "https://hash.ai/@h/types/property-type/nct-id/v/1", propertyTypeBaseUrl: @@ -1999,9 +2005,9 @@ export const blockProtocolEntityTypes = { export const blockProtocolLinkEntityTypes = { hasQuery: { linkEntityTypeId: - "https://blockprotocol.org/@h/types/entity-type/has-query/v/1", + "https://blockprotocol.org/@hash/types/entity-type/has-query/v/1", linkEntityTypeBaseUrl: - "https://blockprotocol.org/@h/types/entity-type/has-query/" as BaseUrl, + "https://blockprotocol.org/@hash/types/entity-type/has-query/" as BaseUrl, }, } as const satisfies Record< string, @@ -2077,9 +2083,9 @@ export const blockProtocolPropertyTypes = { }, query: { propertyTypeId: - "https://blockprotocol.org/@h/types/property-type/query/v/1", + "https://blockprotocol.org/@hash/types/property-type/query/v/1", propertyTypeBaseUrl: - "https://blockprotocol.org/@h/types/property-type/query/" as BaseUrl, + "https://blockprotocol.org/@hash/types/property-type/query/" as BaseUrl, }, textualContent: { propertyTypeId: diff --git a/libs/@local/hash-isomorphic-utils/src/system-types/hashinstance.ts b/libs/@local/hash-isomorphic-utils/src/system-types/hashinstance.ts index ec3f65659b5..27b500d3f28 100644 --- a/libs/@local/hash-isomorphic-utils/src/system-types/hashinstance.ts +++ b/libs/@local/hash-isomorphic-utils/src/system-types/hashinstance.ts @@ -2,17 +2,27 @@ * This file was automatically generated – do not edit it. */ -import type { ObjectMetadata } from "@blockprotocol/type-system"; - -import type { BooleanDataType, BooleanDataTypeWithMetadata } from "./shared.js"; - -export type { BooleanDataType, BooleanDataTypeWithMetadata }; +import type { ArrayMetadata, ObjectMetadata } from "@blockprotocol/type-system"; + +import type { + BooleanDataType, + BooleanDataTypeWithMetadata, + TextDataType, + TextDataTypeWithMetadata, +} from "./shared.js"; + +export type { + BooleanDataType, + BooleanDataTypeWithMetadata, + TextDataType, + TextDataTypeWithMetadata, +}; /** * An instance of HASH. */ export type HASHInstance = { - entityTypeIds: ["https://hash.ai/@h/types/entity-type/hash-instance/v/1"]; + entityTypeIds: ["https://hash.ai/@h/types/entity-type/hash-instance/v/2"]; properties: HASHInstanceProperties; propertiesWithMetadata: HASHInstancePropertiesWithMetadata; }; @@ -25,6 +35,7 @@ export type HASHInstanceOutgoingLinksByLinkEntityTypeId = {}; * An instance of HASH. */ export type HASHInstanceProperties = { + "https://hash.ai/@h/types/property-type/migrations-completed/"?: MigrationsCompletedPropertyValue; "https://hash.ai/@h/types/property-type/org-self-registration-is-enabled/": OrgSelfRegistrationIsEnabledPropertyValue; "https://hash.ai/@h/types/property-type/pages-are-enabled/": PagesAreEnabledPropertyValue; "https://hash.ai/@h/types/property-type/user-registration-by-invitation-is-enabled/": UserRegistrationByInvitationIsEnabledPropertyValue; @@ -34,6 +45,7 @@ export type HASHInstanceProperties = { export type HASHInstancePropertiesWithMetadata = { metadata?: ObjectMetadata; value: { + "https://hash.ai/@h/types/property-type/migrations-completed/"?: MigrationsCompletedPropertyValueWithMetadata; "https://hash.ai/@h/types/property-type/org-self-registration-is-enabled/": OrgSelfRegistrationIsEnabledPropertyValueWithMetadata; "https://hash.ai/@h/types/property-type/pages-are-enabled/": PagesAreEnabledPropertyValueWithMetadata; "https://hash.ai/@h/types/property-type/user-registration-by-invitation-is-enabled/": UserRegistrationByInvitationIsEnabledPropertyValueWithMetadata; @@ -41,6 +53,16 @@ export type HASHInstancePropertiesWithMetadata = { }; }; +/** + * The migrations that have been completed for this instance + */ +export type MigrationsCompletedPropertyValue = TextDataType[]; + +export type MigrationsCompletedPropertyValueWithMetadata = { + value: TextDataTypeWithMetadata[]; + metadata?: ArrayMetadata; +}; + /** * Whether or not a user can self-register an org (note this does not apply to instance admins). */