From 7a2faa42c7c839935aef635594af0b74bb7d43f1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 30 Apr 2026 03:59:02 +0000 Subject: [PATCH] chore: update schemas and regenerate all SDK clients Auto-generated by schema-propagation workflow. Source: constructive-db@3de87aec79 --- .../references/entity-type-provision.md | 6 +- .../cli-public/references/provision-table.md | 4 +- .../cli-public/references/storage-module.md | 6 +- .../references/entity-type-provision.md | 8 +- .../hooks-public/references/storage-module.md | 8 +- .../references/entity-type-provision.md | 4 +- .../orm-public/references/storage-module.md | 4 +- sdk/constructive-cli/src/admin/orm/client.ts | 28 +- .../src/admin/orm/input-types.ts | 3 + sdk/constructive-cli/src/auth/orm/client.ts | 28 +- .../src/auth/orm/input-types.ts | 3 + .../src/objects/orm/client.ts | 28 +- .../src/objects/orm/input-types.ts | 3 + sdk/constructive-cli/src/public/cli/README.md | 10 +- .../cli/commands/entity-type-provision.ts | 44 + .../src/public/cli/commands/storage-module.ts | 28 +- sdk/constructive-cli/src/public/orm/README.md | 17 +- sdk/constructive-cli/src/public/orm/client.ts | 28 +- .../src/public/orm/input-types.ts | 83 +- .../src/admin/orm/client.ts | 28 +- .../src/admin/orm/input-types.ts | 3 + .../src/admin/schema-types.ts | 2 + sdk/constructive-react/src/auth/orm/client.ts | 28 +- .../src/auth/orm/input-types.ts | 3 + .../src/auth/schema-types.ts | 2 + .../src/objects/orm/client.ts | 28 +- .../src/objects/orm/input-types.ts | 3 + .../src/objects/schema-types.ts | 2 + .../src/public/hooks/README.md | 12 +- .../src/public/orm/README.md | 17 +- .../src/public/orm/client.ts | 28 +- .../src/public/orm/input-types.ts | 83 +- .../src/public/schema-types.ts | 417 ++++--- sdk/constructive-react/src/public/types.ts | 5 +- sdk/constructive-sdk/schemas/admin.graphql | 5 + sdk/constructive-sdk/schemas/app.graphql | 5 + sdk/constructive-sdk/schemas/auth.graphql | 5 + sdk/constructive-sdk/schemas/objects.graphql | 5 + sdk/constructive-sdk/schemas/public.graphql | 1087 +++++++++-------- sdk/constructive-sdk/src/admin/orm/client.ts | 28 +- .../src/admin/orm/input-types.ts | 3 + sdk/constructive-sdk/src/auth/orm/client.ts | 28 +- .../src/auth/orm/input-types.ts | 3 + .../src/objects/orm/client.ts | 28 +- .../src/objects/orm/input-types.ts | 3 + sdk/constructive-sdk/src/public/orm/README.md | 17 +- sdk/constructive-sdk/src/public/orm/client.ts | 28 +- .../src/public/orm/input-types.ts | 83 +- sdk/migrate-client/schemas/migrate.graphql | 5 + sdk/migrate-client/src/migrate/orm/client.ts | 28 +- .../src/migrate/orm/input-types.ts | 3 + 51 files changed, 1352 insertions(+), 1016 deletions(-) diff --git a/.agents/skills/cli-public/references/entity-type-provision.md b/.agents/skills/cli-public/references/entity-type-provision.md index c599b2627..5d17a28fa 100644 --- a/.agents/skills/cli-public/references/entity-type-provision.md +++ b/.agents/skills/cli-public/references/entity-type-provision.md @@ -12,8 +12,8 @@ csdk entity-type-provision list --where.. --orderBy csdk entity-type-provision list --limit 10 --after csdk entity-type-provision find-first --where.. csdk entity-type-provision get --id -csdk entity-type-provision create --databaseId --name --prefix [--description ] [--parentEntity ] [--tableName ] [--isVisible ] [--hasLimits ] [--hasProfiles ] [--hasLevels ] [--hasStorage ] [--storageConfig ] [--skipEntityPolicies ] [--tableProvision ] [--outMembershipType ] [--outEntityTableId ] [--outEntityTableName ] [--outInstalledModules ] [--outStorageModuleId ] [--outBucketsTableId ] [--outFilesTableId ] -csdk entity-type-provision update --id [--databaseId ] [--name ] [--prefix ] [--description ] [--parentEntity ] [--tableName ] [--isVisible ] [--hasLimits ] [--hasProfiles ] [--hasLevels ] [--hasStorage ] [--storageConfig ] [--skipEntityPolicies ] [--tableProvision ] [--outMembershipType ] [--outEntityTableId ] [--outEntityTableName ] [--outInstalledModules ] [--outStorageModuleId ] [--outBucketsTableId ] [--outFilesTableId ] +csdk entity-type-provision create --databaseId --name --prefix [--description ] [--parentEntity ] [--tableName ] [--isVisible ] [--hasLimits ] [--hasProfiles ] [--hasLevels ] [--hasStorage ] [--hasInvites ] [--storageConfig ] [--skipEntityPolicies ] [--tableProvision ] [--outMembershipType ] [--outEntityTableId ] [--outEntityTableName ] [--outInstalledModules ] [--outStorageModuleId ] [--outBucketsTableId ] [--outFilesTableId ] [--outInvitesModuleId ] +csdk entity-type-provision update --id [--databaseId ] [--name ] [--prefix ] [--description ] [--parentEntity ] [--tableName ] [--isVisible ] [--hasLimits ] [--hasProfiles ] [--hasLevels ] [--hasStorage ] [--hasInvites ] [--storageConfig ] [--skipEntityPolicies ] [--tableProvision ] [--outMembershipType ] [--outEntityTableId ] [--outEntityTableName ] [--outInstalledModules ] [--outStorageModuleId ] [--outBucketsTableId ] [--outFilesTableId ] [--outInvitesModuleId ] csdk entity-type-provision delete --id ``` @@ -58,7 +58,7 @@ csdk entity-type-provision list --where.id.equalTo --orderBy ID_ASC ### Create a entityTypeProvision ```bash -csdk entity-type-provision create --databaseId --name --prefix [--description ] [--parentEntity ] [--tableName ] [--isVisible ] [--hasLimits ] [--hasProfiles ] [--hasLevels ] [--hasStorage ] [--storageConfig ] [--skipEntityPolicies ] [--tableProvision ] [--outMembershipType ] [--outEntityTableId ] [--outEntityTableName ] [--outInstalledModules ] [--outStorageModuleId ] [--outBucketsTableId ] [--outFilesTableId ] +csdk entity-type-provision create --databaseId --name --prefix [--description ] [--parentEntity ] [--tableName ] [--isVisible ] [--hasLimits ] [--hasProfiles ] [--hasLevels ] [--hasStorage ] [--hasInvites ] [--storageConfig ] [--skipEntityPolicies ] [--tableProvision ] [--outMembershipType ] [--outEntityTableId ] [--outEntityTableName ] [--outInstalledModules ] [--outStorageModuleId ] [--outBucketsTableId ] [--outFilesTableId ] [--outInvitesModuleId ] ``` ### Get a entityTypeProvision by id diff --git a/.agents/skills/cli-public/references/provision-table.md b/.agents/skills/cli-public/references/provision-table.md index 65cd73c65..9426533f5 100644 --- a/.agents/skills/cli-public/references/provision-table.md +++ b/.agents/skills/cli-public/references/provision-table.md @@ -7,7 +7,7 @@ Composable table provisioning: creates or finds a table, then creates fields (so ## Usage ```bash -csdk provision-table --input.clientMutationId --input.databaseId --input.schemaId --input.tableName --input.tableId --input.nodes --input.fields --input.policies --input.grants --input.useRls --input.indexes --input.fullTextSearches --input.uniqueConstraints +csdk provision-table --input.clientMutationId --input.databaseId --input.schemaId --input.tableName --input.tableId --input.nodes --input.fields --input.policies --input.grants --input.useRls --input.indexes --input.fullTextSearches --input.uniqueConstraints --input.description ``` ## Examples @@ -15,5 +15,5 @@ csdk provision-table --input.clientMutationId --input.databaseId ### Run provisionTable ```bash -csdk provision-table --input.clientMutationId --input.databaseId --input.schemaId --input.tableName --input.tableId --input.nodes --input.fields --input.policies --input.grants --input.useRls --input.indexes --input.fullTextSearches --input.uniqueConstraints +csdk provision-table --input.clientMutationId --input.databaseId --input.schemaId --input.tableName --input.tableId --input.nodes --input.fields --input.policies --input.grants --input.useRls --input.indexes --input.fullTextSearches --input.uniqueConstraints --input.description ``` diff --git a/.agents/skills/cli-public/references/storage-module.md b/.agents/skills/cli-public/references/storage-module.md index 808d5e77e..eb04f68c3 100644 --- a/.agents/skills/cli-public/references/storage-module.md +++ b/.agents/skills/cli-public/references/storage-module.md @@ -12,8 +12,8 @@ csdk storage-module list --where.. --orderBy csdk storage-module list --limit 10 --after csdk storage-module find-first --where.. csdk storage-module get --id -csdk storage-module create --databaseId [--schemaId ] [--privateSchemaId ] [--bucketsTableId ] [--filesTableId ] [--uploadRequestsTableId ] [--bucketsTableName ] [--filesTableName ] [--uploadRequestsTableName ] [--membershipType ] [--policies ] [--entityTableId ] [--endpoint ] [--publicUrlPrefix ] [--provider ] [--allowedOrigins ] [--uploadUrlExpirySeconds ] [--downloadUrlExpirySeconds ] [--defaultMaxFileSize ] [--maxFilenameLength ] [--cacheTtlSeconds ] -csdk storage-module update --id [--databaseId ] [--schemaId ] [--privateSchemaId ] [--bucketsTableId ] [--filesTableId ] [--uploadRequestsTableId ] [--bucketsTableName ] [--filesTableName ] [--uploadRequestsTableName ] [--membershipType ] [--policies ] [--entityTableId ] [--endpoint ] [--publicUrlPrefix ] [--provider ] [--allowedOrigins ] [--uploadUrlExpirySeconds ] [--downloadUrlExpirySeconds ] [--defaultMaxFileSize ] [--maxFilenameLength ] [--cacheTtlSeconds ] +csdk storage-module create --databaseId [--schemaId ] [--privateSchemaId ] [--bucketsTableId ] [--filesTableId ] [--uploadRequestsTableId ] [--bucketsTableName ] [--filesTableName ] [--uploadRequestsTableName ] [--membershipType ] [--policies ] [--skipDefaultPolicyTables ] [--entityTableId ] [--endpoint ] [--publicUrlPrefix ] [--provider ] [--allowedOrigins ] [--uploadUrlExpirySeconds ] [--downloadUrlExpirySeconds ] [--defaultMaxFileSize ] [--maxFilenameLength ] [--cacheTtlSeconds ] +csdk storage-module update --id [--databaseId ] [--schemaId ] [--privateSchemaId ] [--bucketsTableId ] [--filesTableId ] [--uploadRequestsTableId ] [--bucketsTableName ] [--filesTableName ] [--uploadRequestsTableName ] [--membershipType ] [--policies ] [--skipDefaultPolicyTables ] [--entityTableId ] [--endpoint ] [--publicUrlPrefix ] [--provider ] [--allowedOrigins ] [--uploadUrlExpirySeconds ] [--downloadUrlExpirySeconds ] [--defaultMaxFileSize ] [--maxFilenameLength ] [--cacheTtlSeconds ] csdk storage-module delete --id ``` @@ -58,7 +58,7 @@ csdk storage-module list --where.id.equalTo --orderBy ID_ASC ### Create a storageModule ```bash -csdk storage-module create --databaseId [--schemaId ] [--privateSchemaId ] [--bucketsTableId ] [--filesTableId ] [--uploadRequestsTableId ] [--bucketsTableName ] [--filesTableName ] [--uploadRequestsTableName ] [--membershipType ] [--policies ] [--entityTableId ] [--endpoint ] [--publicUrlPrefix ] [--provider ] [--allowedOrigins ] [--uploadUrlExpirySeconds ] [--downloadUrlExpirySeconds ] [--defaultMaxFileSize ] [--maxFilenameLength ] [--cacheTtlSeconds ] +csdk storage-module create --databaseId [--schemaId ] [--privateSchemaId ] [--bucketsTableId ] [--filesTableId ] [--uploadRequestsTableId ] [--bucketsTableName ] [--filesTableName ] [--uploadRequestsTableName ] [--membershipType ] [--policies ] [--skipDefaultPolicyTables ] [--entityTableId ] [--endpoint ] [--publicUrlPrefix ] [--provider ] [--allowedOrigins ] [--uploadUrlExpirySeconds ] [--downloadUrlExpirySeconds ] [--defaultMaxFileSize ] [--maxFilenameLength ] [--cacheTtlSeconds ] ``` ### Get a storageModule by id diff --git a/.agents/skills/hooks-public/references/entity-type-provision.md b/.agents/skills/hooks-public/references/entity-type-provision.md index 4f7203f73..453ee4650 100644 --- a/.agents/skills/hooks-public/references/entity-type-provision.md +++ b/.agents/skills/hooks-public/references/entity-type-provision.md @@ -14,8 +14,8 @@ Provisions a new membership entity type. Each INSERT creates an entity table, re ## Usage ```typescript -useEntityTypeProvisionsQuery({ selection: { fields: { id: true, databaseId: true, name: true, prefix: true, description: true, parentEntity: true, tableName: true, isVisible: true, hasLimits: true, hasProfiles: true, hasLevels: true, hasStorage: true, storageConfig: true, skipEntityPolicies: true, tableProvision: true, outMembershipType: true, outEntityTableId: true, outEntityTableName: true, outInstalledModules: true, outStorageModuleId: true, outBucketsTableId: true, outFilesTableId: true } } }) -useEntityTypeProvisionQuery({ id: '', selection: { fields: { id: true, databaseId: true, name: true, prefix: true, description: true, parentEntity: true, tableName: true, isVisible: true, hasLimits: true, hasProfiles: true, hasLevels: true, hasStorage: true, storageConfig: true, skipEntityPolicies: true, tableProvision: true, outMembershipType: true, outEntityTableId: true, outEntityTableName: true, outInstalledModules: true, outStorageModuleId: true, outBucketsTableId: true, outFilesTableId: true } } }) +useEntityTypeProvisionsQuery({ selection: { fields: { id: true, databaseId: true, name: true, prefix: true, description: true, parentEntity: true, tableName: true, isVisible: true, hasLimits: true, hasProfiles: true, hasLevels: true, hasStorage: true, hasInvites: true, storageConfig: true, skipEntityPolicies: true, tableProvision: true, outMembershipType: true, outEntityTableId: true, outEntityTableName: true, outInstalledModules: true, outStorageModuleId: true, outBucketsTableId: true, outFilesTableId: true, outInvitesModuleId: true } } }) +useEntityTypeProvisionQuery({ id: '', selection: { fields: { id: true, databaseId: true, name: true, prefix: true, description: true, parentEntity: true, tableName: true, isVisible: true, hasLimits: true, hasProfiles: true, hasLevels: true, hasStorage: true, hasInvites: true, storageConfig: true, skipEntityPolicies: true, tableProvision: true, outMembershipType: true, outEntityTableId: true, outEntityTableName: true, outInstalledModules: true, outStorageModuleId: true, outBucketsTableId: true, outFilesTableId: true, outInvitesModuleId: true } } }) useCreateEntityTypeProvisionMutation({ selection: { fields: { id: true } } }) useUpdateEntityTypeProvisionMutation({ selection: { fields: { id: true } } }) useDeleteEntityTypeProvisionMutation({}) @@ -27,7 +27,7 @@ useDeleteEntityTypeProvisionMutation({}) ```typescript const { data, isLoading } = useEntityTypeProvisionsQuery({ - selection: { fields: { id: true, databaseId: true, name: true, prefix: true, description: true, parentEntity: true, tableName: true, isVisible: true, hasLimits: true, hasProfiles: true, hasLevels: true, hasStorage: true, storageConfig: true, skipEntityPolicies: true, tableProvision: true, outMembershipType: true, outEntityTableId: true, outEntityTableName: true, outInstalledModules: true, outStorageModuleId: true, outBucketsTableId: true, outFilesTableId: true } }, + selection: { fields: { id: true, databaseId: true, name: true, prefix: true, description: true, parentEntity: true, tableName: true, isVisible: true, hasLimits: true, hasProfiles: true, hasLevels: true, hasStorage: true, hasInvites: true, storageConfig: true, skipEntityPolicies: true, tableProvision: true, outMembershipType: true, outEntityTableId: true, outEntityTableName: true, outInstalledModules: true, outStorageModuleId: true, outBucketsTableId: true, outFilesTableId: true, outInvitesModuleId: true } }, }); ``` @@ -37,5 +37,5 @@ const { data, isLoading } = useEntityTypeProvisionsQuery({ const { mutate } = useCreateEntityTypeProvisionMutation({ selection: { fields: { id: true } }, }); -mutate({ databaseId: '', name: '', prefix: '', description: '', parentEntity: '', tableName: '', isVisible: '', hasLimits: '', hasProfiles: '', hasLevels: '', hasStorage: '', storageConfig: '', skipEntityPolicies: '', tableProvision: '', outMembershipType: '', outEntityTableId: '', outEntityTableName: '', outInstalledModules: '', outStorageModuleId: '', outBucketsTableId: '', outFilesTableId: '' }); +mutate({ databaseId: '', name: '', prefix: '', description: '', parentEntity: '', tableName: '', isVisible: '', hasLimits: '', hasProfiles: '', hasLevels: '', hasStorage: '', hasInvites: '', storageConfig: '', skipEntityPolicies: '', tableProvision: '', outMembershipType: '', outEntityTableId: '', outEntityTableName: '', outInstalledModules: '', outStorageModuleId: '', outBucketsTableId: '', outFilesTableId: '', outInvitesModuleId: '' }); ``` diff --git a/.agents/skills/hooks-public/references/storage-module.md b/.agents/skills/hooks-public/references/storage-module.md index c540c425e..5b64297bc 100644 --- a/.agents/skills/hooks-public/references/storage-module.md +++ b/.agents/skills/hooks-public/references/storage-module.md @@ -7,8 +7,8 @@ React Query hooks for StorageModule data operations ## Usage ```typescript -useStorageModulesQuery({ selection: { fields: { id: true, databaseId: true, schemaId: true, privateSchemaId: true, bucketsTableId: true, filesTableId: true, uploadRequestsTableId: true, bucketsTableName: true, filesTableName: true, uploadRequestsTableName: true, membershipType: true, policies: true, entityTableId: true, endpoint: true, publicUrlPrefix: true, provider: true, allowedOrigins: true, uploadUrlExpirySeconds: true, downloadUrlExpirySeconds: true, defaultMaxFileSize: true, maxFilenameLength: true, cacheTtlSeconds: true } } }) -useStorageModuleQuery({ id: '', selection: { fields: { id: true, databaseId: true, schemaId: true, privateSchemaId: true, bucketsTableId: true, filesTableId: true, uploadRequestsTableId: true, bucketsTableName: true, filesTableName: true, uploadRequestsTableName: true, membershipType: true, policies: true, entityTableId: true, endpoint: true, publicUrlPrefix: true, provider: true, allowedOrigins: true, uploadUrlExpirySeconds: true, downloadUrlExpirySeconds: true, defaultMaxFileSize: true, maxFilenameLength: true, cacheTtlSeconds: true } } }) +useStorageModulesQuery({ selection: { fields: { id: true, databaseId: true, schemaId: true, privateSchemaId: true, bucketsTableId: true, filesTableId: true, uploadRequestsTableId: true, bucketsTableName: true, filesTableName: true, uploadRequestsTableName: true, membershipType: true, policies: true, skipDefaultPolicyTables: true, entityTableId: true, endpoint: true, publicUrlPrefix: true, provider: true, allowedOrigins: true, uploadUrlExpirySeconds: true, downloadUrlExpirySeconds: true, defaultMaxFileSize: true, maxFilenameLength: true, cacheTtlSeconds: true } } }) +useStorageModuleQuery({ id: '', selection: { fields: { id: true, databaseId: true, schemaId: true, privateSchemaId: true, bucketsTableId: true, filesTableId: true, uploadRequestsTableId: true, bucketsTableName: true, filesTableName: true, uploadRequestsTableName: true, membershipType: true, policies: true, skipDefaultPolicyTables: true, entityTableId: true, endpoint: true, publicUrlPrefix: true, provider: true, allowedOrigins: true, uploadUrlExpirySeconds: true, downloadUrlExpirySeconds: true, defaultMaxFileSize: true, maxFilenameLength: true, cacheTtlSeconds: true } } }) useCreateStorageModuleMutation({ selection: { fields: { id: true } } }) useUpdateStorageModuleMutation({ selection: { fields: { id: true } } }) useDeleteStorageModuleMutation({}) @@ -20,7 +20,7 @@ useDeleteStorageModuleMutation({}) ```typescript const { data, isLoading } = useStorageModulesQuery({ - selection: { fields: { id: true, databaseId: true, schemaId: true, privateSchemaId: true, bucketsTableId: true, filesTableId: true, uploadRequestsTableId: true, bucketsTableName: true, filesTableName: true, uploadRequestsTableName: true, membershipType: true, policies: true, entityTableId: true, endpoint: true, publicUrlPrefix: true, provider: true, allowedOrigins: true, uploadUrlExpirySeconds: true, downloadUrlExpirySeconds: true, defaultMaxFileSize: true, maxFilenameLength: true, cacheTtlSeconds: true } }, + selection: { fields: { id: true, databaseId: true, schemaId: true, privateSchemaId: true, bucketsTableId: true, filesTableId: true, uploadRequestsTableId: true, bucketsTableName: true, filesTableName: true, uploadRequestsTableName: true, membershipType: true, policies: true, skipDefaultPolicyTables: true, entityTableId: true, endpoint: true, publicUrlPrefix: true, provider: true, allowedOrigins: true, uploadUrlExpirySeconds: true, downloadUrlExpirySeconds: true, defaultMaxFileSize: true, maxFilenameLength: true, cacheTtlSeconds: true } }, }); ``` @@ -30,5 +30,5 @@ const { data, isLoading } = useStorageModulesQuery({ const { mutate } = useCreateStorageModuleMutation({ selection: { fields: { id: true } }, }); -mutate({ databaseId: '', schemaId: '', privateSchemaId: '', bucketsTableId: '', filesTableId: '', uploadRequestsTableId: '', bucketsTableName: '', filesTableName: '', uploadRequestsTableName: '', membershipType: '', policies: '', entityTableId: '', endpoint: '', publicUrlPrefix: '', provider: '', allowedOrigins: '', uploadUrlExpirySeconds: '', downloadUrlExpirySeconds: '', defaultMaxFileSize: '', maxFilenameLength: '', cacheTtlSeconds: '' }); +mutate({ databaseId: '', schemaId: '', privateSchemaId: '', bucketsTableId: '', filesTableId: '', uploadRequestsTableId: '', bucketsTableName: '', filesTableName: '', uploadRequestsTableName: '', membershipType: '', policies: '', skipDefaultPolicyTables: '', entityTableId: '', endpoint: '', publicUrlPrefix: '', provider: '', allowedOrigins: '', uploadUrlExpirySeconds: '', downloadUrlExpirySeconds: '', defaultMaxFileSize: '', maxFilenameLength: '', cacheTtlSeconds: '' }); ``` diff --git a/.agents/skills/orm-public/references/entity-type-provision.md b/.agents/skills/orm-public/references/entity-type-provision.md index 80ce2cf23..9cf6437a1 100644 --- a/.agents/skills/orm-public/references/entity-type-provision.md +++ b/.agents/skills/orm-public/references/entity-type-provision.md @@ -16,7 +16,7 @@ Provisions a new membership entity type. Each INSERT creates an entity table, re ```typescript db.entityTypeProvision.findMany({ select: { id: true } }).execute() db.entityTypeProvision.findOne({ id: '', select: { id: true } }).execute() -db.entityTypeProvision.create({ data: { databaseId: '', name: '', prefix: '', description: '', parentEntity: '', tableName: '', isVisible: '', hasLimits: '', hasProfiles: '', hasLevels: '', hasStorage: '', storageConfig: '', skipEntityPolicies: '', tableProvision: '', outMembershipType: '', outEntityTableId: '', outEntityTableName: '', outInstalledModules: '', outStorageModuleId: '', outBucketsTableId: '', outFilesTableId: '' }, select: { id: true } }).execute() +db.entityTypeProvision.create({ data: { databaseId: '', name: '', prefix: '', description: '', parentEntity: '', tableName: '', isVisible: '', hasLimits: '', hasProfiles: '', hasLevels: '', hasStorage: '', hasInvites: '', storageConfig: '', skipEntityPolicies: '', tableProvision: '', outMembershipType: '', outEntityTableId: '', outEntityTableName: '', outInstalledModules: '', outStorageModuleId: '', outBucketsTableId: '', outFilesTableId: '', outInvitesModuleId: '' }, select: { id: true } }).execute() db.entityTypeProvision.update({ where: { id: '' }, data: { databaseId: '' }, select: { id: true } }).execute() db.entityTypeProvision.delete({ where: { id: '' } }).execute() ``` @@ -35,7 +35,7 @@ const items = await db.entityTypeProvision.findMany({ ```typescript const item = await db.entityTypeProvision.create({ - data: { databaseId: '', name: '', prefix: '', description: '', parentEntity: '', tableName: '', isVisible: '', hasLimits: '', hasProfiles: '', hasLevels: '', hasStorage: '', storageConfig: '', skipEntityPolicies: '', tableProvision: '', outMembershipType: '', outEntityTableId: '', outEntityTableName: '', outInstalledModules: '', outStorageModuleId: '', outBucketsTableId: '', outFilesTableId: '' }, + data: { databaseId: '', name: '', prefix: '', description: '', parentEntity: '', tableName: '', isVisible: '', hasLimits: '', hasProfiles: '', hasLevels: '', hasStorage: '', hasInvites: '', storageConfig: '', skipEntityPolicies: '', tableProvision: '', outMembershipType: '', outEntityTableId: '', outEntityTableName: '', outInstalledModules: '', outStorageModuleId: '', outBucketsTableId: '', outFilesTableId: '', outInvitesModuleId: '' }, select: { id: true } }).execute(); ``` diff --git a/.agents/skills/orm-public/references/storage-module.md b/.agents/skills/orm-public/references/storage-module.md index b43d92c44..f2e6ea92e 100644 --- a/.agents/skills/orm-public/references/storage-module.md +++ b/.agents/skills/orm-public/references/storage-module.md @@ -9,7 +9,7 @@ ORM operations for StorageModule records ```typescript db.storageModule.findMany({ select: { id: true } }).execute() db.storageModule.findOne({ id: '', select: { id: true } }).execute() -db.storageModule.create({ data: { databaseId: '', schemaId: '', privateSchemaId: '', bucketsTableId: '', filesTableId: '', uploadRequestsTableId: '', bucketsTableName: '', filesTableName: '', uploadRequestsTableName: '', membershipType: '', policies: '', entityTableId: '', endpoint: '', publicUrlPrefix: '', provider: '', allowedOrigins: '', uploadUrlExpirySeconds: '', downloadUrlExpirySeconds: '', defaultMaxFileSize: '', maxFilenameLength: '', cacheTtlSeconds: '' }, select: { id: true } }).execute() +db.storageModule.create({ data: { databaseId: '', schemaId: '', privateSchemaId: '', bucketsTableId: '', filesTableId: '', uploadRequestsTableId: '', bucketsTableName: '', filesTableName: '', uploadRequestsTableName: '', membershipType: '', policies: '', skipDefaultPolicyTables: '', entityTableId: '', endpoint: '', publicUrlPrefix: '', provider: '', allowedOrigins: '', uploadUrlExpirySeconds: '', downloadUrlExpirySeconds: '', defaultMaxFileSize: '', maxFilenameLength: '', cacheTtlSeconds: '' }, select: { id: true } }).execute() db.storageModule.update({ where: { id: '' }, data: { databaseId: '' }, select: { id: true } }).execute() db.storageModule.delete({ where: { id: '' } }).execute() ``` @@ -28,7 +28,7 @@ const items = await db.storageModule.findMany({ ```typescript const item = await db.storageModule.create({ - data: { databaseId: '', schemaId: '', privateSchemaId: '', bucketsTableId: '', filesTableId: '', uploadRequestsTableId: '', bucketsTableName: '', filesTableName: '', uploadRequestsTableName: '', membershipType: '', policies: '', entityTableId: '', endpoint: '', publicUrlPrefix: '', provider: '', allowedOrigins: '', uploadUrlExpirySeconds: '', downloadUrlExpirySeconds: '', defaultMaxFileSize: '', maxFilenameLength: '', cacheTtlSeconds: '' }, + data: { databaseId: '', schemaId: '', privateSchemaId: '', bucketsTableId: '', filesTableId: '', uploadRequestsTableId: '', bucketsTableName: '', filesTableName: '', uploadRequestsTableName: '', membershipType: '', policies: '', skipDefaultPolicyTables: '', entityTableId: '', endpoint: '', publicUrlPrefix: '', provider: '', allowedOrigins: '', uploadUrlExpirySeconds: '', downloadUrlExpirySeconds: '', defaultMaxFileSize: '', maxFilenameLength: '', cacheTtlSeconds: '' }, select: { id: true } }).execute(); ``` diff --git a/sdk/constructive-cli/src/admin/orm/client.ts b/sdk/constructive-cli/src/admin/orm/client.ts index a1d5c74d8..39fb07eed 100644 --- a/sdk/constructive-cli/src/admin/orm/client.ts +++ b/sdk/constructive-cli/src/admin/orm/client.ts @@ -31,16 +31,13 @@ export class FetchAdapter implements GraphQLAdapter { constructor( private endpoint: string, headers?: Record, - fetchFn?: typeof globalThis.fetch, + fetchFn?: typeof globalThis.fetch ) { this.headers = headers ?? {}; this.fetchFn = fetchFn ?? createFetch(); } - async execute( - document: string, - variables?: Record, - ): Promise> { + async execute(document: string, variables?: Record): Promise> { const response = await this.fetchFn(this.endpoint, { method: 'POST', headers: { @@ -58,9 +55,7 @@ export class FetchAdapter implements GraphQLAdapter { return { ok: false, data: null, - errors: [ - { message: `HTTP ${response.status}: ${response.statusText}` }, - ], + errors: [{ message: `HTTP ${response.status}: ${response.statusText}` }], }; } @@ -120,7 +115,7 @@ export interface OrmClientConfig { export class GraphQLRequestError extends Error { constructor( public readonly errors: GraphQLError[], - public readonly data: unknown = null, + public readonly data: unknown = null ) { const messages = errors.map((e) => e.message).join('; '); super(`GraphQL Error: ${messages}`); @@ -135,22 +130,13 @@ export class OrmClient { if (config.adapter) { this.adapter = config.adapter; } else if (config.endpoint) { - this.adapter = new FetchAdapter( - config.endpoint, - config.headers, - config.fetch, - ); + this.adapter = new FetchAdapter(config.endpoint, config.headers, config.fetch); } else { - throw new Error( - 'OrmClientConfig requires either an endpoint or a custom adapter', - ); + throw new Error('OrmClientConfig requires either an endpoint or a custom adapter'); } } - async execute( - document: string, - variables?: Record, - ): Promise> { + async execute(document: string, variables?: Record): Promise> { return this.adapter.execute(document, variables); } diff --git a/sdk/constructive-cli/src/admin/orm/input-types.ts b/sdk/constructive-cli/src/admin/orm/input-types.ts index 806b6b042..3f1ab47a3 100644 --- a/sdk/constructive-cli/src/admin/orm/input-types.ts +++ b/sdk/constructive-cli/src/admin/orm/input-types.ts @@ -3615,6 +3615,8 @@ export interface RequestUploadUrlPayload { deduplicated: boolean; /** Presigned URL expiry time (null if deduplicated) */ expiresAt?: string | null; + /** File status — 'pending' for fresh uploads, 'ready' or 'processed' for deduplicated files. Clients can use this to know immediately whether the file is usable. */ + status: string; } export type RequestUploadUrlPayloadSelect = { uploadUrl?: boolean; @@ -3622,6 +3624,7 @@ export type RequestUploadUrlPayloadSelect = { key?: boolean; deduplicated?: boolean; expiresAt?: boolean; + status?: boolean; }; export interface ConfirmUploadPayload { /** The confirmed file ID */ diff --git a/sdk/constructive-cli/src/auth/orm/client.ts b/sdk/constructive-cli/src/auth/orm/client.ts index a1d5c74d8..39fb07eed 100644 --- a/sdk/constructive-cli/src/auth/orm/client.ts +++ b/sdk/constructive-cli/src/auth/orm/client.ts @@ -31,16 +31,13 @@ export class FetchAdapter implements GraphQLAdapter { constructor( private endpoint: string, headers?: Record, - fetchFn?: typeof globalThis.fetch, + fetchFn?: typeof globalThis.fetch ) { this.headers = headers ?? {}; this.fetchFn = fetchFn ?? createFetch(); } - async execute( - document: string, - variables?: Record, - ): Promise> { + async execute(document: string, variables?: Record): Promise> { const response = await this.fetchFn(this.endpoint, { method: 'POST', headers: { @@ -58,9 +55,7 @@ export class FetchAdapter implements GraphQLAdapter { return { ok: false, data: null, - errors: [ - { message: `HTTP ${response.status}: ${response.statusText}` }, - ], + errors: [{ message: `HTTP ${response.status}: ${response.statusText}` }], }; } @@ -120,7 +115,7 @@ export interface OrmClientConfig { export class GraphQLRequestError extends Error { constructor( public readonly errors: GraphQLError[], - public readonly data: unknown = null, + public readonly data: unknown = null ) { const messages = errors.map((e) => e.message).join('; '); super(`GraphQL Error: ${messages}`); @@ -135,22 +130,13 @@ export class OrmClient { if (config.adapter) { this.adapter = config.adapter; } else if (config.endpoint) { - this.adapter = new FetchAdapter( - config.endpoint, - config.headers, - config.fetch, - ); + this.adapter = new FetchAdapter(config.endpoint, config.headers, config.fetch); } else { - throw new Error( - 'OrmClientConfig requires either an endpoint or a custom adapter', - ); + throw new Error('OrmClientConfig requires either an endpoint or a custom adapter'); } } - async execute( - document: string, - variables?: Record, - ): Promise> { + async execute(document: string, variables?: Record): Promise> { return this.adapter.execute(document, variables); } diff --git a/sdk/constructive-cli/src/auth/orm/input-types.ts b/sdk/constructive-cli/src/auth/orm/input-types.ts index ba12312ee..eb5c48d84 100644 --- a/sdk/constructive-cli/src/auth/orm/input-types.ts +++ b/sdk/constructive-cli/src/auth/orm/input-types.ts @@ -2440,6 +2440,8 @@ export interface RequestUploadUrlPayload { deduplicated: boolean; /** Presigned URL expiry time (null if deduplicated) */ expiresAt?: string | null; + /** File status — 'pending' for fresh uploads, 'ready' or 'processed' for deduplicated files. Clients can use this to know immediately whether the file is usable. */ + status: string; } export type RequestUploadUrlPayloadSelect = { uploadUrl?: boolean; @@ -2447,6 +2449,7 @@ export type RequestUploadUrlPayloadSelect = { key?: boolean; deduplicated?: boolean; expiresAt?: boolean; + status?: boolean; }; export interface ConfirmUploadPayload { /** The confirmed file ID */ diff --git a/sdk/constructive-cli/src/objects/orm/client.ts b/sdk/constructive-cli/src/objects/orm/client.ts index a1d5c74d8..39fb07eed 100644 --- a/sdk/constructive-cli/src/objects/orm/client.ts +++ b/sdk/constructive-cli/src/objects/orm/client.ts @@ -31,16 +31,13 @@ export class FetchAdapter implements GraphQLAdapter { constructor( private endpoint: string, headers?: Record, - fetchFn?: typeof globalThis.fetch, + fetchFn?: typeof globalThis.fetch ) { this.headers = headers ?? {}; this.fetchFn = fetchFn ?? createFetch(); } - async execute( - document: string, - variables?: Record, - ): Promise> { + async execute(document: string, variables?: Record): Promise> { const response = await this.fetchFn(this.endpoint, { method: 'POST', headers: { @@ -58,9 +55,7 @@ export class FetchAdapter implements GraphQLAdapter { return { ok: false, data: null, - errors: [ - { message: `HTTP ${response.status}: ${response.statusText}` }, - ], + errors: [{ message: `HTTP ${response.status}: ${response.statusText}` }], }; } @@ -120,7 +115,7 @@ export interface OrmClientConfig { export class GraphQLRequestError extends Error { constructor( public readonly errors: GraphQLError[], - public readonly data: unknown = null, + public readonly data: unknown = null ) { const messages = errors.map((e) => e.message).join('; '); super(`GraphQL Error: ${messages}`); @@ -135,22 +130,13 @@ export class OrmClient { if (config.adapter) { this.adapter = config.adapter; } else if (config.endpoint) { - this.adapter = new FetchAdapter( - config.endpoint, - config.headers, - config.fetch, - ); + this.adapter = new FetchAdapter(config.endpoint, config.headers, config.fetch); } else { - throw new Error( - 'OrmClientConfig requires either an endpoint or a custom adapter', - ); + throw new Error('OrmClientConfig requires either an endpoint or a custom adapter'); } } - async execute( - document: string, - variables?: Record, - ): Promise> { + async execute(document: string, variables?: Record): Promise> { return this.adapter.execute(document, variables); } diff --git a/sdk/constructive-cli/src/objects/orm/input-types.ts b/sdk/constructive-cli/src/objects/orm/input-types.ts index e1684b49e..5a7d7952c 100644 --- a/sdk/constructive-cli/src/objects/orm/input-types.ts +++ b/sdk/constructive-cli/src/objects/orm/input-types.ts @@ -829,6 +829,8 @@ export interface RequestUploadUrlPayload { deduplicated: boolean; /** Presigned URL expiry time (null if deduplicated) */ expiresAt?: string | null; + /** File status — 'pending' for fresh uploads, 'ready' or 'processed' for deduplicated files. Clients can use this to know immediately whether the file is usable. */ + status: string; } export type RequestUploadUrlPayloadSelect = { uploadUrl?: boolean; @@ -836,6 +838,7 @@ export type RequestUploadUrlPayloadSelect = { key?: boolean; deduplicated?: boolean; expiresAt?: boolean; + status?: boolean; }; export interface ConfirmUploadPayload { /** The confirmed file ID */ diff --git a/sdk/constructive-cli/src/public/cli/README.md b/sdk/constructive-cli/src/public/cli/README.md index bd441e0d3..d09274ad8 100644 --- a/sdk/constructive-cli/src/public/cli/README.md +++ b/sdk/constructive-cli/src/public/cli/README.md @@ -2409,7 +2409,8 @@ CRUD operations for StorageModule records. | `filesTableName` | String | | `uploadRequestsTableName` | String | | `membershipType` | Int | -| `policies` | String | +| `policies` | JSON | +| `skipDefaultPolicyTables` | String | | `entityTableId` | UUID | | `endpoint` | String | | `publicUrlPrefix` | String | @@ -2422,7 +2423,7 @@ CRUD operations for StorageModule records. | `cacheTtlSeconds` | Int | **Required create fields:** `databaseId` -**Optional create fields (backend defaults):** `schemaId`, `privateSchemaId`, `bucketsTableId`, `filesTableId`, `uploadRequestsTableId`, `bucketsTableName`, `filesTableName`, `uploadRequestsTableName`, `membershipType`, `policies`, `entityTableId`, `endpoint`, `publicUrlPrefix`, `provider`, `allowedOrigins`, `uploadUrlExpirySeconds`, `downloadUrlExpirySeconds`, `defaultMaxFileSize`, `maxFilenameLength`, `cacheTtlSeconds` +**Optional create fields (backend defaults):** `schemaId`, `privateSchemaId`, `bucketsTableId`, `filesTableId`, `uploadRequestsTableId`, `bucketsTableName`, `filesTableName`, `uploadRequestsTableName`, `membershipType`, `policies`, `skipDefaultPolicyTables`, `entityTableId`, `endpoint`, `publicUrlPrefix`, `provider`, `allowedOrigins`, `uploadUrlExpirySeconds`, `downloadUrlExpirySeconds`, `defaultMaxFileSize`, `maxFilenameLength`, `cacheTtlSeconds` ### `entity-type-provision` @@ -2453,6 +2454,7 @@ CRUD operations for EntityTypeProvision records. | `hasProfiles` | Boolean | | `hasLevels` | Boolean | | `hasStorage` | Boolean | +| `hasInvites` | Boolean | | `storageConfig` | JSON | | `skipEntityPolicies` | Boolean | | `tableProvision` | JSON | @@ -2463,9 +2465,10 @@ CRUD operations for EntityTypeProvision records. | `outStorageModuleId` | UUID | | `outBucketsTableId` | UUID | | `outFilesTableId` | UUID | +| `outInvitesModuleId` | UUID | **Required create fields:** `databaseId`, `name`, `prefix` -**Optional create fields (backend defaults):** `description`, `parentEntity`, `tableName`, `isVisible`, `hasLimits`, `hasProfiles`, `hasLevels`, `hasStorage`, `storageConfig`, `skipEntityPolicies`, `tableProvision`, `outMembershipType`, `outEntityTableId`, `outEntityTableName`, `outInstalledModules`, `outStorageModuleId`, `outBucketsTableId`, `outFilesTableId` +**Optional create fields (backend defaults):** `description`, `parentEntity`, `tableName`, `isVisible`, `hasLimits`, `hasProfiles`, `hasLevels`, `hasStorage`, `hasInvites`, `storageConfig`, `skipEntityPolicies`, `tableProvision`, `outMembershipType`, `outEntityTableId`, `outEntityTableName`, `outInstalledModules`, `outStorageModuleId`, `outBucketsTableId`, `outFilesTableId`, `outInvitesModuleId` ### `webauthn-credentials-module` @@ -4950,6 +4953,7 @@ Composable table provisioning: creates or finds a table, then creates fields (so | `--input.indexes` | JSON | | `--input.fullTextSearches` | JSON | | `--input.uniqueConstraints` | JSON | + | `--input.description` | String | ### `send-verification-email` diff --git a/sdk/constructive-cli/src/public/cli/commands/entity-type-provision.ts b/sdk/constructive-cli/src/public/cli/commands/entity-type-provision.ts index 2933c4241..409659ddd 100644 --- a/sdk/constructive-cli/src/public/cli/commands/entity-type-provision.ts +++ b/sdk/constructive-cli/src/public/cli/commands/entity-type-provision.ts @@ -28,6 +28,7 @@ const fieldSchema: FieldSchema = { hasProfiles: 'boolean', hasLevels: 'boolean', hasStorage: 'boolean', + hasInvites: 'boolean', storageConfig: 'json', skipEntityPolicies: 'boolean', tableProvision: 'json', @@ -38,6 +39,7 @@ const fieldSchema: FieldSchema = { outStorageModuleId: 'uuid', outBucketsTableId: 'uuid', outFilesTableId: 'uuid', + outInvitesModuleId: 'uuid', }; const usage = '\nentity-type-provision \n\nCommands:\n list List entityTypeProvision records\n find-first Find first matching entityTypeProvision record\n get Get a entityTypeProvision by ID\n create Create a new entityTypeProvision\n update Update an existing entityTypeProvision\n delete Delete a entityTypeProvision\n\nList Options:\n --limit Max number of records to return (forward pagination)\n --last Number of records from the end (backward pagination)\n --after Cursor for forward pagination\n --before Cursor for backward pagination\n --offset Number of records to skip\n --select Comma-separated list of fields to return\n --where.. Filter (dot-notation, e.g. --where.name.equalTo foo)\n --condition.. Condition filter (dot-notation)\n --orderBy Comma-separated ordering values (e.g. NAME_ASC,CREATED_AT_DESC)\n\nFind-First Options:\n --select Comma-separated list of fields to return\n --where.. Filter (dot-notation, e.g. --where.status.equalTo active)\n --condition.. Condition filter (dot-notation)\n\n --help, -h Show this help message\n'; @@ -102,6 +104,7 @@ async function handleList(argv: Partial>, _prompter: Inq hasProfiles: true, hasLevels: true, hasStorage: true, + hasInvites: true, storageConfig: true, skipEntityPolicies: true, tableProvision: true, @@ -112,6 +115,7 @@ async function handleList(argv: Partial>, _prompter: Inq outStorageModuleId: true, outBucketsTableId: true, outFilesTableId: true, + outInvitesModuleId: true, }; const findManyArgs = parseFindManyArgs< FindManyArgs< @@ -148,6 +152,7 @@ async function handleFindFirst(argv: Partial>, _prompter hasProfiles: true, hasLevels: true, hasStorage: true, + hasInvites: true, storageConfig: true, skipEntityPolicies: true, tableProvision: true, @@ -158,6 +163,7 @@ async function handleFindFirst(argv: Partial>, _prompter outStorageModuleId: true, outBucketsTableId: true, outFilesTableId: true, + outInvitesModuleId: true, }; const findFirstArgs = parseFindFirstArgs< FindFirstArgs & { @@ -202,6 +208,7 @@ async function handleGet(argv: Partial>, prompter: Inqui hasProfiles: true, hasLevels: true, hasStorage: true, + hasInvites: true, storageConfig: true, skipEntityPolicies: true, tableProvision: true, @@ -212,6 +219,7 @@ async function handleGet(argv: Partial>, prompter: Inqui outStorageModuleId: true, outBucketsTableId: true, outFilesTableId: true, + outInvitesModuleId: true, }, }) .execute(); @@ -301,6 +309,13 @@ async function handleCreate(argv: Partial>, prompter: In required: false, skipPrompt: true, }, + { + type: 'boolean', + name: 'hasInvites', + message: 'hasInvites', + required: false, + skipPrompt: true, + }, { type: 'json', name: 'storageConfig', @@ -371,6 +386,13 @@ async function handleCreate(argv: Partial>, prompter: In required: false, skipPrompt: true, }, + { + type: 'text', + name: 'outInvitesModuleId', + message: 'outInvitesModuleId', + required: false, + skipPrompt: true, + }, ]); const answers = coerceAnswers(rawAnswers, fieldSchema); const cleanedData = stripUndefined( @@ -392,6 +414,7 @@ async function handleCreate(argv: Partial>, prompter: In hasProfiles: cleanedData.hasProfiles, hasLevels: cleanedData.hasLevels, hasStorage: cleanedData.hasStorage, + hasInvites: cleanedData.hasInvites, storageConfig: cleanedData.storageConfig, skipEntityPolicies: cleanedData.skipEntityPolicies, tableProvision: cleanedData.tableProvision, @@ -402,6 +425,7 @@ async function handleCreate(argv: Partial>, prompter: In outStorageModuleId: cleanedData.outStorageModuleId, outBucketsTableId: cleanedData.outBucketsTableId, outFilesTableId: cleanedData.outFilesTableId, + outInvitesModuleId: cleanedData.outInvitesModuleId, }, select: { id: true, @@ -416,6 +440,7 @@ async function handleCreate(argv: Partial>, prompter: In hasProfiles: true, hasLevels: true, hasStorage: true, + hasInvites: true, storageConfig: true, skipEntityPolicies: true, tableProvision: true, @@ -426,6 +451,7 @@ async function handleCreate(argv: Partial>, prompter: In outStorageModuleId: true, outBucketsTableId: true, outFilesTableId: true, + outInvitesModuleId: true, }, }) .execute(); @@ -521,6 +547,13 @@ async function handleUpdate(argv: Partial>, prompter: In required: false, skipPrompt: true, }, + { + type: 'boolean', + name: 'hasInvites', + message: 'hasInvites', + required: false, + skipPrompt: true, + }, { type: 'json', name: 'storageConfig', @@ -591,6 +624,13 @@ async function handleUpdate(argv: Partial>, prompter: In required: false, skipPrompt: true, }, + { + type: 'text', + name: 'outInvitesModuleId', + message: 'outInvitesModuleId', + required: false, + skipPrompt: true, + }, ]); const answers = coerceAnswers(rawAnswers, fieldSchema); const cleanedData = stripUndefined(answers, fieldSchema) as EntityTypeProvisionPatch; @@ -612,6 +652,7 @@ async function handleUpdate(argv: Partial>, prompter: In hasProfiles: cleanedData.hasProfiles, hasLevels: cleanedData.hasLevels, hasStorage: cleanedData.hasStorage, + hasInvites: cleanedData.hasInvites, storageConfig: cleanedData.storageConfig, skipEntityPolicies: cleanedData.skipEntityPolicies, tableProvision: cleanedData.tableProvision, @@ -622,6 +663,7 @@ async function handleUpdate(argv: Partial>, prompter: In outStorageModuleId: cleanedData.outStorageModuleId, outBucketsTableId: cleanedData.outBucketsTableId, outFilesTableId: cleanedData.outFilesTableId, + outInvitesModuleId: cleanedData.outInvitesModuleId, }, select: { id: true, @@ -636,6 +678,7 @@ async function handleUpdate(argv: Partial>, prompter: In hasProfiles: true, hasLevels: true, hasStorage: true, + hasInvites: true, storageConfig: true, skipEntityPolicies: true, tableProvision: true, @@ -646,6 +689,7 @@ async function handleUpdate(argv: Partial>, prompter: In outStorageModuleId: true, outBucketsTableId: true, outFilesTableId: true, + outInvitesModuleId: true, }, }) .execute(); diff --git a/sdk/constructive-cli/src/public/cli/commands/storage-module.ts b/sdk/constructive-cli/src/public/cli/commands/storage-module.ts index f19a60af8..edf9a0d07 100644 --- a/sdk/constructive-cli/src/public/cli/commands/storage-module.ts +++ b/sdk/constructive-cli/src/public/cli/commands/storage-module.ts @@ -27,7 +27,8 @@ const fieldSchema: FieldSchema = { filesTableName: 'string', uploadRequestsTableName: 'string', membershipType: 'int', - policies: 'string', + policies: 'json', + skipDefaultPolicyTables: 'string', entityTableId: 'uuid', endpoint: 'string', publicUrlPrefix: 'string', @@ -102,6 +103,7 @@ async function handleList(argv: Partial>, _prompter: Inq uploadRequestsTableName: true, membershipType: true, policies: true, + skipDefaultPolicyTables: true, entityTableId: true, endpoint: true, publicUrlPrefix: true, @@ -144,6 +146,7 @@ async function handleFindFirst(argv: Partial>, _prompter uploadRequestsTableName: true, membershipType: true, policies: true, + skipDefaultPolicyTables: true, entityTableId: true, endpoint: true, publicUrlPrefix: true, @@ -198,6 +201,7 @@ async function handleGet(argv: Partial>, prompter: Inqui uploadRequestsTableName: true, membershipType: true, policies: true, + skipDefaultPolicyTables: true, entityTableId: true, endpoint: true, publicUrlPrefix: true, @@ -293,12 +297,19 @@ async function handleCreate(argv: Partial>, prompter: In skipPrompt: true, }, { - type: 'text', + type: 'json', name: 'policies', message: 'policies', required: false, skipPrompt: true, }, + { + type: 'text', + name: 'skipDefaultPolicyTables', + message: 'skipDefaultPolicyTables', + required: false, + skipPrompt: true, + }, { type: 'text', name: 'entityTableId', @@ -390,6 +401,7 @@ async function handleCreate(argv: Partial>, prompter: In uploadRequestsTableName: cleanedData.uploadRequestsTableName, membershipType: cleanedData.membershipType, policies: cleanedData.policies, + skipDefaultPolicyTables: cleanedData.skipDefaultPolicyTables, entityTableId: cleanedData.entityTableId, endpoint: cleanedData.endpoint, publicUrlPrefix: cleanedData.publicUrlPrefix, @@ -414,6 +426,7 @@ async function handleCreate(argv: Partial>, prompter: In uploadRequestsTableName: true, membershipType: true, policies: true, + skipDefaultPolicyTables: true, entityTableId: true, endpoint: true, publicUrlPrefix: true, @@ -515,12 +528,19 @@ async function handleUpdate(argv: Partial>, prompter: In skipPrompt: true, }, { - type: 'text', + type: 'json', name: 'policies', message: 'policies', required: false, skipPrompt: true, }, + { + type: 'text', + name: 'skipDefaultPolicyTables', + message: 'skipDefaultPolicyTables', + required: false, + skipPrompt: true, + }, { type: 'text', name: 'entityTableId', @@ -612,6 +632,7 @@ async function handleUpdate(argv: Partial>, prompter: In uploadRequestsTableName: cleanedData.uploadRequestsTableName, membershipType: cleanedData.membershipType, policies: cleanedData.policies, + skipDefaultPolicyTables: cleanedData.skipDefaultPolicyTables, entityTableId: cleanedData.entityTableId, endpoint: cleanedData.endpoint, publicUrlPrefix: cleanedData.publicUrlPrefix, @@ -636,6 +657,7 @@ async function handleUpdate(argv: Partial>, prompter: In uploadRequestsTableName: true, membershipType: true, policies: true, + skipDefaultPolicyTables: true, entityTableId: true, endpoint: true, publicUrlPrefix: true, diff --git a/sdk/constructive-cli/src/public/orm/README.md b/sdk/constructive-cli/src/public/orm/README.md index 74e83c23e..9d3f602c4 100644 --- a/sdk/constructive-cli/src/public/orm/README.md +++ b/sdk/constructive-cli/src/public/orm/README.md @@ -2733,7 +2733,8 @@ CRUD operations for StorageModule records. | `filesTableName` | String | Yes | | `uploadRequestsTableName` | String | Yes | | `membershipType` | Int | Yes | -| `policies` | String | Yes | +| `policies` | JSON | Yes | +| `skipDefaultPolicyTables` | String | Yes | | `entityTableId` | UUID | Yes | | `endpoint` | String | Yes | | `publicUrlPrefix` | String | Yes | @@ -2749,13 +2750,13 @@ CRUD operations for StorageModule records. ```typescript // List all storageModule records -const items = await db.storageModule.findMany({ select: { id: true, databaseId: true, schemaId: true, privateSchemaId: true, bucketsTableId: true, filesTableId: true, uploadRequestsTableId: true, bucketsTableName: true, filesTableName: true, uploadRequestsTableName: true, membershipType: true, policies: true, entityTableId: true, endpoint: true, publicUrlPrefix: true, provider: true, allowedOrigins: true, uploadUrlExpirySeconds: true, downloadUrlExpirySeconds: true, defaultMaxFileSize: true, maxFilenameLength: true, cacheTtlSeconds: true } }).execute(); +const items = await db.storageModule.findMany({ select: { id: true, databaseId: true, schemaId: true, privateSchemaId: true, bucketsTableId: true, filesTableId: true, uploadRequestsTableId: true, bucketsTableName: true, filesTableName: true, uploadRequestsTableName: true, membershipType: true, policies: true, skipDefaultPolicyTables: true, entityTableId: true, endpoint: true, publicUrlPrefix: true, provider: true, allowedOrigins: true, uploadUrlExpirySeconds: true, downloadUrlExpirySeconds: true, defaultMaxFileSize: true, maxFilenameLength: true, cacheTtlSeconds: true } }).execute(); // Get one by id -const item = await db.storageModule.findOne({ id: '', select: { id: true, databaseId: true, schemaId: true, privateSchemaId: true, bucketsTableId: true, filesTableId: true, uploadRequestsTableId: true, bucketsTableName: true, filesTableName: true, uploadRequestsTableName: true, membershipType: true, policies: true, entityTableId: true, endpoint: true, publicUrlPrefix: true, provider: true, allowedOrigins: true, uploadUrlExpirySeconds: true, downloadUrlExpirySeconds: true, defaultMaxFileSize: true, maxFilenameLength: true, cacheTtlSeconds: true } }).execute(); +const item = await db.storageModule.findOne({ id: '', select: { id: true, databaseId: true, schemaId: true, privateSchemaId: true, bucketsTableId: true, filesTableId: true, uploadRequestsTableId: true, bucketsTableName: true, filesTableName: true, uploadRequestsTableName: true, membershipType: true, policies: true, skipDefaultPolicyTables: true, entityTableId: true, endpoint: true, publicUrlPrefix: true, provider: true, allowedOrigins: true, uploadUrlExpirySeconds: true, downloadUrlExpirySeconds: true, defaultMaxFileSize: true, maxFilenameLength: true, cacheTtlSeconds: true } }).execute(); // Create -const created = await db.storageModule.create({ data: { databaseId: '', schemaId: '', privateSchemaId: '', bucketsTableId: '', filesTableId: '', uploadRequestsTableId: '', bucketsTableName: '', filesTableName: '', uploadRequestsTableName: '', membershipType: '', policies: '', entityTableId: '', endpoint: '', publicUrlPrefix: '', provider: '', allowedOrigins: '', uploadUrlExpirySeconds: '', downloadUrlExpirySeconds: '', defaultMaxFileSize: '', maxFilenameLength: '', cacheTtlSeconds: '' }, select: { id: true } }).execute(); +const created = await db.storageModule.create({ data: { databaseId: '', schemaId: '', privateSchemaId: '', bucketsTableId: '', filesTableId: '', uploadRequestsTableId: '', bucketsTableName: '', filesTableName: '', uploadRequestsTableName: '', membershipType: '', policies: '', skipDefaultPolicyTables: '', entityTableId: '', endpoint: '', publicUrlPrefix: '', provider: '', allowedOrigins: '', uploadUrlExpirySeconds: '', downloadUrlExpirySeconds: '', defaultMaxFileSize: '', maxFilenameLength: '', cacheTtlSeconds: '' }, select: { id: true } }).execute(); // Update const updated = await db.storageModule.update({ where: { id: '' }, data: { databaseId: '' }, select: { id: true } }).execute(); @@ -2784,6 +2785,7 @@ CRUD operations for EntityTypeProvision records. | `hasProfiles` | Boolean | Yes | | `hasLevels` | Boolean | Yes | | `hasStorage` | Boolean | Yes | +| `hasInvites` | Boolean | Yes | | `storageConfig` | JSON | Yes | | `skipEntityPolicies` | Boolean | Yes | | `tableProvision` | JSON | Yes | @@ -2794,18 +2796,19 @@ CRUD operations for EntityTypeProvision records. | `outStorageModuleId` | UUID | Yes | | `outBucketsTableId` | UUID | Yes | | `outFilesTableId` | UUID | Yes | +| `outInvitesModuleId` | UUID | Yes | **Operations:** ```typescript // List all entityTypeProvision records -const items = await db.entityTypeProvision.findMany({ select: { id: true, databaseId: true, name: true, prefix: true, description: true, parentEntity: true, tableName: true, isVisible: true, hasLimits: true, hasProfiles: true, hasLevels: true, hasStorage: true, storageConfig: true, skipEntityPolicies: true, tableProvision: true, outMembershipType: true, outEntityTableId: true, outEntityTableName: true, outInstalledModules: true, outStorageModuleId: true, outBucketsTableId: true, outFilesTableId: true } }).execute(); +const items = await db.entityTypeProvision.findMany({ select: { id: true, databaseId: true, name: true, prefix: true, description: true, parentEntity: true, tableName: true, isVisible: true, hasLimits: true, hasProfiles: true, hasLevels: true, hasStorage: true, hasInvites: true, storageConfig: true, skipEntityPolicies: true, tableProvision: true, outMembershipType: true, outEntityTableId: true, outEntityTableName: true, outInstalledModules: true, outStorageModuleId: true, outBucketsTableId: true, outFilesTableId: true, outInvitesModuleId: true } }).execute(); // Get one by id -const item = await db.entityTypeProvision.findOne({ id: '', select: { id: true, databaseId: true, name: true, prefix: true, description: true, parentEntity: true, tableName: true, isVisible: true, hasLimits: true, hasProfiles: true, hasLevels: true, hasStorage: true, storageConfig: true, skipEntityPolicies: true, tableProvision: true, outMembershipType: true, outEntityTableId: true, outEntityTableName: true, outInstalledModules: true, outStorageModuleId: true, outBucketsTableId: true, outFilesTableId: true } }).execute(); +const item = await db.entityTypeProvision.findOne({ id: '', select: { id: true, databaseId: true, name: true, prefix: true, description: true, parentEntity: true, tableName: true, isVisible: true, hasLimits: true, hasProfiles: true, hasLevels: true, hasStorage: true, hasInvites: true, storageConfig: true, skipEntityPolicies: true, tableProvision: true, outMembershipType: true, outEntityTableId: true, outEntityTableName: true, outInstalledModules: true, outStorageModuleId: true, outBucketsTableId: true, outFilesTableId: true, outInvitesModuleId: true } }).execute(); // Create -const created = await db.entityTypeProvision.create({ data: { databaseId: '', name: '', prefix: '', description: '', parentEntity: '', tableName: '', isVisible: '', hasLimits: '', hasProfiles: '', hasLevels: '', hasStorage: '', storageConfig: '', skipEntityPolicies: '', tableProvision: '', outMembershipType: '', outEntityTableId: '', outEntityTableName: '', outInstalledModules: '', outStorageModuleId: '', outBucketsTableId: '', outFilesTableId: '' }, select: { id: true } }).execute(); +const created = await db.entityTypeProvision.create({ data: { databaseId: '', name: '', prefix: '', description: '', parentEntity: '', tableName: '', isVisible: '', hasLimits: '', hasProfiles: '', hasLevels: '', hasStorage: '', hasInvites: '', storageConfig: '', skipEntityPolicies: '', tableProvision: '', outMembershipType: '', outEntityTableId: '', outEntityTableName: '', outInstalledModules: '', outStorageModuleId: '', outBucketsTableId: '', outFilesTableId: '', outInvitesModuleId: '' }, select: { id: true } }).execute(); // Update const updated = await db.entityTypeProvision.update({ where: { id: '' }, data: { databaseId: '' }, select: { id: true } }).execute(); diff --git a/sdk/constructive-cli/src/public/orm/client.ts b/sdk/constructive-cli/src/public/orm/client.ts index a1d5c74d8..39fb07eed 100644 --- a/sdk/constructive-cli/src/public/orm/client.ts +++ b/sdk/constructive-cli/src/public/orm/client.ts @@ -31,16 +31,13 @@ export class FetchAdapter implements GraphQLAdapter { constructor( private endpoint: string, headers?: Record, - fetchFn?: typeof globalThis.fetch, + fetchFn?: typeof globalThis.fetch ) { this.headers = headers ?? {}; this.fetchFn = fetchFn ?? createFetch(); } - async execute( - document: string, - variables?: Record, - ): Promise> { + async execute(document: string, variables?: Record): Promise> { const response = await this.fetchFn(this.endpoint, { method: 'POST', headers: { @@ -58,9 +55,7 @@ export class FetchAdapter implements GraphQLAdapter { return { ok: false, data: null, - errors: [ - { message: `HTTP ${response.status}: ${response.statusText}` }, - ], + errors: [{ message: `HTTP ${response.status}: ${response.statusText}` }], }; } @@ -120,7 +115,7 @@ export interface OrmClientConfig { export class GraphQLRequestError extends Error { constructor( public readonly errors: GraphQLError[], - public readonly data: unknown = null, + public readonly data: unknown = null ) { const messages = errors.map((e) => e.message).join('; '); super(`GraphQL Error: ${messages}`); @@ -135,22 +130,13 @@ export class OrmClient { if (config.adapter) { this.adapter = config.adapter; } else if (config.endpoint) { - this.adapter = new FetchAdapter( - config.endpoint, - config.headers, - config.fetch, - ); + this.adapter = new FetchAdapter(config.endpoint, config.headers, config.fetch); } else { - throw new Error( - 'OrmClientConfig requires either an endpoint or a custom adapter', - ); + throw new Error('OrmClientConfig requires either an endpoint or a custom adapter'); } } - async execute( - document: string, - variables?: Record, - ): Promise> { + async execute(document: string, variables?: Record): Promise> { return this.adapter.execute(document, variables); } diff --git a/sdk/constructive-cli/src/public/orm/input-types.ts b/sdk/constructive-cli/src/public/orm/input-types.ts index 888d00b99..6f3337645 100644 --- a/sdk/constructive-cli/src/public/orm/input-types.ts +++ b/sdk/constructive-cli/src/public/orm/input-types.ts @@ -1361,7 +1361,8 @@ export interface StorageModule { filesTableName?: string | null; uploadRequestsTableName?: string | null; membershipType?: number | null; - policies?: string[] | null; + policies?: Record | null; + skipDefaultPolicyTables?: string[] | null; entityTableId?: string | null; endpoint?: string | null; publicUrlPrefix?: string | null; @@ -1389,8 +1390,8 @@ export interface EntityTypeProvision { /** The database to provision this entity type in. Required. */ databaseId?: string | null; /** - * Human-readable name for this membership type, e.g. 'Data Room Member', 'Team Channel Member'. Required. - * Stored in the membership_types registry table. + * Human-readable name for this entity type, e.g. 'Data Room', 'Team Channel'. Required. + * Stored in the entity_types registry table. */ name?: string | null; /** @@ -1400,7 +1401,7 @@ export interface EntityTypeProvision { * Must be unique per database — the (database_id, prefix) constraint ensures graceful ON CONFLICT DO NOTHING. */ prefix?: string | null; - /** Description of this membership type. Stored in the membership_types registry table. Defaults to empty string. */ + /** Description of this entity type. Stored in the entity_types registry table. Defaults to empty string. */ description?: string | null; /** * Prefix of the parent entity type. The trigger resolves this to a membership_type integer @@ -1451,6 +1452,16 @@ export interface EntityTypeProvision { * Storage tables get owner_id FK to the entity table, so files are owned by the entity. */ hasStorage?: boolean | null; + /** + * Whether to provision invites_module for this type. Defaults to false. + * When true, the trigger inserts a row into invites_module which in turn + * (via insert_invites_module BEFORE INSERT) creates {prefix}_invites and + * {prefix}_claimed_invites tables plus the submit_{prefix}_invite_code() function. + * Symmetric counterpart of has_storage. Re-provisioning is idempotent: the + * UNIQUE (database_id, membership_type) constraint on invites_module combined with + * ON CONFLICT DO NOTHING in the fan-out makes repeated INSERTs safe. + */ + hasInvites?: boolean | null; /** * Optional jsonb object for storage module configuration and initial bucket seeding. * Only used when has_storage = true; ignored otherwise. NULL = use defaults. @@ -1467,8 +1478,21 @@ export interface EntityTypeProvision { * - allowed_mime_types (text[]) whitelist of MIME types (null = any) * - max_file_size (bigint) max file size in bytes (null = use scope default) * - allowed_origins (text[]) per-bucket CORS override + * - provisions (jsonb object) optional: customize storage tables + * with additional nodes, fields, grants, and policies. + * Keyed by table role: "files", "buckets", "upload_requests". + * Each value uses the same shape as table_provision: + * { nodes, fields, grants, use_rls, policies }. Fanned out + * to secure_table_provision targeting the corresponding table. + * When a key includes policies[], those REPLACE the default + * storage policies for that table; tables without a key still + * get defaults. Missing "data" on policy entries is auto-populated + * with storage-specific defaults (same as table_provision). + * Example: add SearchBm25 for full-text search on files: + * {"provisions": {"files": {"nodes": [{"$type": + * "SearchBm25", "data": {"source_fields": ["description"]}}]}}} * Example: - * storage_config := '{"buckets": [{"name": "documents", "is_public": false, "allowed_mime_types": ["application/pdf"]}]}'::jsonb + * storage_config := '{"buckets": [{"name": "documents", "is_public": false, "allowed_mime_types": ["application/pdf"]}], "provisions": {"files": {"nodes": [{"$type": "SearchBm25", "data": {"source_fields": ["description"]}}]}}}'::jsonb */ storageConfig?: Record | null; /** @@ -1519,7 +1543,7 @@ export interface EntityTypeProvision { tableProvision?: Record | null; /** * Output: the auto-assigned integer membership type ID. Populated by the trigger after successful provisioning. - * This is the ID used in membership_types, memberships_module, and all module tables. + * This is the ID used in entity_types, memberships_module, and all module tables. */ outMembershipType?: number | null; /** @@ -1540,6 +1564,12 @@ export interface EntityTypeProvision { outBucketsTableId?: string | null; /** Output: the UUID of the generated files table (e.g. data_room_files). Populated by the trigger when has_storage=true. */ outFilesTableId?: string | null; + /** + * Output: the UUID of the invites_module row created for this entity type. Populated by the trigger when has_invites=true. + * NULL when has_invites=false, or when re-provisioning hits ON CONFLICT DO NOTHING + * (i.e. the invites_module row was created in a previous run). + */ + outInvitesModuleId?: string | null; } /** Config row for the webauthn_credentials_module, which provisions the per-user WebAuthn/passkey credentials table (public key, counter, transports, device type, backup state) mirroring crypto_addresses_module. The sibling webauthn_auth_module holds RP config and the registration/sign-in challenge state. */ export interface WebauthnCredentialsModule { @@ -5321,6 +5351,7 @@ export type StorageModuleSelect = { uploadRequestsTableName?: boolean; membershipType?: boolean; policies?: boolean; + skipDefaultPolicyTables?: boolean; entityTableId?: boolean; endpoint?: boolean; publicUrlPrefix?: boolean; @@ -5366,6 +5397,7 @@ export type EntityTypeProvisionSelect = { hasProfiles?: boolean; hasLevels?: boolean; hasStorage?: boolean; + hasInvites?: boolean; storageConfig?: boolean; skipEntityPolicies?: boolean; tableProvision?: boolean; @@ -5376,6 +5408,7 @@ export type EntityTypeProvisionSelect = { outStorageModuleId?: boolean; outBucketsTableId?: boolean; outFilesTableId?: boolean; + outInvitesModuleId?: boolean; database?: { select: DatabaseSelect; }; @@ -9422,7 +9455,9 @@ export interface StorageModuleFilter { /** Filter by the object’s `membershipType` field. */ membershipType?: IntFilter; /** Filter by the object’s `policies` field. */ - policies?: StringListFilter; + policies?: JSONFilter; + /** Filter by the object’s `skipDefaultPolicyTables` field. */ + skipDefaultPolicyTables?: StringListFilter; /** Filter by the object’s `entityTableId` field. */ entityTableId?: UUIDFilter; /** Filter by the object’s `endpoint` field. */ @@ -9491,6 +9526,8 @@ export interface EntityTypeProvisionFilter { hasLevels?: BooleanFilter; /** Filter by the object’s `hasStorage` field. */ hasStorage?: BooleanFilter; + /** Filter by the object’s `hasInvites` field. */ + hasInvites?: BooleanFilter; /** Filter by the object’s `storageConfig` field. */ storageConfig?: JSONFilter; /** Filter by the object’s `skipEntityPolicies` field. */ @@ -9511,6 +9548,8 @@ export interface EntityTypeProvisionFilter { outBucketsTableId?: UUIDFilter; /** Filter by the object’s `outFilesTableId` field. */ outFilesTableId?: UUIDFilter; + /** Filter by the object’s `outInvitesModuleId` field. */ + outInvitesModuleId?: UUIDFilter; /** Checks for all expressions in this list. */ and?: EntityTypeProvisionFilter[]; /** Checks for any expressions in this list. */ @@ -13014,6 +13053,8 @@ export type StorageModuleOrderBy = | 'MEMBERSHIP_TYPE_DESC' | 'POLICIES_ASC' | 'POLICIES_DESC' + | 'SKIP_DEFAULT_POLICY_TABLES_ASC' + | 'SKIP_DEFAULT_POLICY_TABLES_DESC' | 'ENTITY_TABLE_ID_ASC' | 'ENTITY_TABLE_ID_DESC' | 'ENDPOINT_ASC' @@ -13062,6 +13103,8 @@ export type EntityTypeProvisionOrderBy = | 'HAS_LEVELS_DESC' | 'HAS_STORAGE_ASC' | 'HAS_STORAGE_DESC' + | 'HAS_INVITES_ASC' + | 'HAS_INVITES_DESC' | 'STORAGE_CONFIG_ASC' | 'STORAGE_CONFIG_DESC' | 'SKIP_ENTITY_POLICIES_ASC' @@ -13081,7 +13124,9 @@ export type EntityTypeProvisionOrderBy = | 'OUT_BUCKETS_TABLE_ID_ASC' | 'OUT_BUCKETS_TABLE_ID_DESC' | 'OUT_FILES_TABLE_ID_ASC' - | 'OUT_FILES_TABLE_ID_DESC'; + | 'OUT_FILES_TABLE_ID_DESC' + | 'OUT_INVITES_MODULE_ID_ASC' + | 'OUT_INVITES_MODULE_ID_DESC'; export type WebauthnCredentialsModuleOrderBy = | 'NATURAL' | 'PRIMARY_KEY_ASC' @@ -16466,7 +16511,8 @@ export interface CreateStorageModuleInput { filesTableName?: string; uploadRequestsTableName?: string; membershipType?: number; - policies?: string[]; + policies?: Record; + skipDefaultPolicyTables?: string[]; entityTableId?: string; endpoint?: string; publicUrlPrefix?: string; @@ -16490,7 +16536,8 @@ export interface StorageModulePatch { filesTableName?: string | null; uploadRequestsTableName?: string | null; membershipType?: number | null; - policies?: string[] | null; + policies?: Record | null; + skipDefaultPolicyTables?: string[] | null; entityTableId?: string | null; endpoint?: string | null; publicUrlPrefix?: string | null; @@ -16525,6 +16572,7 @@ export interface CreateEntityTypeProvisionInput { hasProfiles?: boolean; hasLevels?: boolean; hasStorage?: boolean; + hasInvites?: boolean; storageConfig?: Record; skipEntityPolicies?: boolean; tableProvision?: Record; @@ -16535,6 +16583,7 @@ export interface CreateEntityTypeProvisionInput { outStorageModuleId?: string; outBucketsTableId?: string; outFilesTableId?: string; + outInvitesModuleId?: string; }; } export interface EntityTypeProvisionPatch { @@ -16549,6 +16598,7 @@ export interface EntityTypeProvisionPatch { hasProfiles?: boolean | null; hasLevels?: boolean | null; hasStorage?: boolean | null; + hasInvites?: boolean | null; storageConfig?: Record | null; skipEntityPolicies?: boolean | null; tableProvision?: Record | null; @@ -16559,6 +16609,7 @@ export interface EntityTypeProvisionPatch { outStorageModuleId?: string | null; outBucketsTableId?: string | null; outFilesTableId?: string | null; + outInvitesModuleId?: string | null; } export interface UpdateEntityTypeProvisionInput { clientMutationId?: string; @@ -18535,6 +18586,7 @@ export interface ProvisionTableInput { indexes?: Record; fullTextSearches?: Record; uniqueConstraints?: Record; + description?: string; } export interface SendVerificationEmailInput { clientMutationId?: string; @@ -22806,7 +22858,9 @@ export interface StorageModuleFilter { /** Filter by the object’s `membershipType` field. */ membershipType?: IntFilter; /** Filter by the object’s `policies` field. */ - policies?: StringListFilter; + policies?: JSONFilter; + /** Filter by the object’s `skipDefaultPolicyTables` field. */ + skipDefaultPolicyTables?: StringListFilter; /** Filter by the object’s `entityTableId` field. */ entityTableId?: UUIDFilter; /** Filter by the object’s `endpoint` field. */ @@ -22876,6 +22930,8 @@ export interface EntityTypeProvisionFilter { hasLevels?: BooleanFilter; /** Filter by the object’s `hasStorage` field. */ hasStorage?: BooleanFilter; + /** Filter by the object’s `hasInvites` field. */ + hasInvites?: BooleanFilter; /** Filter by the object’s `storageConfig` field. */ storageConfig?: JSONFilter; /** Filter by the object’s `skipEntityPolicies` field. */ @@ -22896,6 +22952,8 @@ export interface EntityTypeProvisionFilter { outBucketsTableId?: UUIDFilter; /** Filter by the object’s `outFilesTableId` field. */ outFilesTableId?: UUIDFilter; + /** Filter by the object’s `outInvitesModuleId` field. */ + outInvitesModuleId?: UUIDFilter; /** Checks for all expressions in this list. */ and?: EntityTypeProvisionFilter[]; /** Checks for any expressions in this list. */ @@ -25755,6 +25813,8 @@ export interface RequestUploadUrlPayload { deduplicated: boolean; /** Presigned URL expiry time (null if deduplicated) */ expiresAt?: string | null; + /** File status — 'pending' for fresh uploads, 'ready' or 'processed' for deduplicated files. Clients can use this to know immediately whether the file is usable. */ + status: string; } export type RequestUploadUrlPayloadSelect = { uploadUrl?: boolean; @@ -25762,6 +25822,7 @@ export type RequestUploadUrlPayloadSelect = { key?: boolean; deduplicated?: boolean; expiresAt?: boolean; + status?: boolean; }; export interface ConfirmUploadPayload { /** The confirmed file ID */ diff --git a/sdk/constructive-react/src/admin/orm/client.ts b/sdk/constructive-react/src/admin/orm/client.ts index a1d5c74d8..39fb07eed 100644 --- a/sdk/constructive-react/src/admin/orm/client.ts +++ b/sdk/constructive-react/src/admin/orm/client.ts @@ -31,16 +31,13 @@ export class FetchAdapter implements GraphQLAdapter { constructor( private endpoint: string, headers?: Record, - fetchFn?: typeof globalThis.fetch, + fetchFn?: typeof globalThis.fetch ) { this.headers = headers ?? {}; this.fetchFn = fetchFn ?? createFetch(); } - async execute( - document: string, - variables?: Record, - ): Promise> { + async execute(document: string, variables?: Record): Promise> { const response = await this.fetchFn(this.endpoint, { method: 'POST', headers: { @@ -58,9 +55,7 @@ export class FetchAdapter implements GraphQLAdapter { return { ok: false, data: null, - errors: [ - { message: `HTTP ${response.status}: ${response.statusText}` }, - ], + errors: [{ message: `HTTP ${response.status}: ${response.statusText}` }], }; } @@ -120,7 +115,7 @@ export interface OrmClientConfig { export class GraphQLRequestError extends Error { constructor( public readonly errors: GraphQLError[], - public readonly data: unknown = null, + public readonly data: unknown = null ) { const messages = errors.map((e) => e.message).join('; '); super(`GraphQL Error: ${messages}`); @@ -135,22 +130,13 @@ export class OrmClient { if (config.adapter) { this.adapter = config.adapter; } else if (config.endpoint) { - this.adapter = new FetchAdapter( - config.endpoint, - config.headers, - config.fetch, - ); + this.adapter = new FetchAdapter(config.endpoint, config.headers, config.fetch); } else { - throw new Error( - 'OrmClientConfig requires either an endpoint or a custom adapter', - ); + throw new Error('OrmClientConfig requires either an endpoint or a custom adapter'); } } - async execute( - document: string, - variables?: Record, - ): Promise> { + async execute(document: string, variables?: Record): Promise> { return this.adapter.execute(document, variables); } diff --git a/sdk/constructive-react/src/admin/orm/input-types.ts b/sdk/constructive-react/src/admin/orm/input-types.ts index 806b6b042..3f1ab47a3 100644 --- a/sdk/constructive-react/src/admin/orm/input-types.ts +++ b/sdk/constructive-react/src/admin/orm/input-types.ts @@ -3615,6 +3615,8 @@ export interface RequestUploadUrlPayload { deduplicated: boolean; /** Presigned URL expiry time (null if deduplicated) */ expiresAt?: string | null; + /** File status — 'pending' for fresh uploads, 'ready' or 'processed' for deduplicated files. Clients can use this to know immediately whether the file is usable. */ + status: string; } export type RequestUploadUrlPayloadSelect = { uploadUrl?: boolean; @@ -3622,6 +3624,7 @@ export type RequestUploadUrlPayloadSelect = { key?: boolean; deduplicated?: boolean; expiresAt?: boolean; + status?: boolean; }; export interface ConfirmUploadPayload { /** The confirmed file ID */ diff --git a/sdk/constructive-react/src/admin/schema-types.ts b/sdk/constructive-react/src/admin/schema-types.ts index d33c677a7..176b675ae 100644 --- a/sdk/constructive-react/src/admin/schema-types.ts +++ b/sdk/constructive-react/src/admin/schema-types.ts @@ -3884,6 +3884,8 @@ export interface RequestUploadUrlPayload { deduplicated: boolean; /** Presigned URL expiry time (null if deduplicated) */ expiresAt?: string | null; + /** File status — 'pending' for fresh uploads, 'ready' or 'processed' for deduplicated files. Clients can use this to know immediately whether the file is usable. */ + status: string; } export interface ConfirmUploadPayload { /** The confirmed file ID */ diff --git a/sdk/constructive-react/src/auth/orm/client.ts b/sdk/constructive-react/src/auth/orm/client.ts index a1d5c74d8..39fb07eed 100644 --- a/sdk/constructive-react/src/auth/orm/client.ts +++ b/sdk/constructive-react/src/auth/orm/client.ts @@ -31,16 +31,13 @@ export class FetchAdapter implements GraphQLAdapter { constructor( private endpoint: string, headers?: Record, - fetchFn?: typeof globalThis.fetch, + fetchFn?: typeof globalThis.fetch ) { this.headers = headers ?? {}; this.fetchFn = fetchFn ?? createFetch(); } - async execute( - document: string, - variables?: Record, - ): Promise> { + async execute(document: string, variables?: Record): Promise> { const response = await this.fetchFn(this.endpoint, { method: 'POST', headers: { @@ -58,9 +55,7 @@ export class FetchAdapter implements GraphQLAdapter { return { ok: false, data: null, - errors: [ - { message: `HTTP ${response.status}: ${response.statusText}` }, - ], + errors: [{ message: `HTTP ${response.status}: ${response.statusText}` }], }; } @@ -120,7 +115,7 @@ export interface OrmClientConfig { export class GraphQLRequestError extends Error { constructor( public readonly errors: GraphQLError[], - public readonly data: unknown = null, + public readonly data: unknown = null ) { const messages = errors.map((e) => e.message).join('; '); super(`GraphQL Error: ${messages}`); @@ -135,22 +130,13 @@ export class OrmClient { if (config.adapter) { this.adapter = config.adapter; } else if (config.endpoint) { - this.adapter = new FetchAdapter( - config.endpoint, - config.headers, - config.fetch, - ); + this.adapter = new FetchAdapter(config.endpoint, config.headers, config.fetch); } else { - throw new Error( - 'OrmClientConfig requires either an endpoint or a custom adapter', - ); + throw new Error('OrmClientConfig requires either an endpoint or a custom adapter'); } } - async execute( - document: string, - variables?: Record, - ): Promise> { + async execute(document: string, variables?: Record): Promise> { return this.adapter.execute(document, variables); } diff --git a/sdk/constructive-react/src/auth/orm/input-types.ts b/sdk/constructive-react/src/auth/orm/input-types.ts index ba12312ee..eb5c48d84 100644 --- a/sdk/constructive-react/src/auth/orm/input-types.ts +++ b/sdk/constructive-react/src/auth/orm/input-types.ts @@ -2440,6 +2440,8 @@ export interface RequestUploadUrlPayload { deduplicated: boolean; /** Presigned URL expiry time (null if deduplicated) */ expiresAt?: string | null; + /** File status — 'pending' for fresh uploads, 'ready' or 'processed' for deduplicated files. Clients can use this to know immediately whether the file is usable. */ + status: string; } export type RequestUploadUrlPayloadSelect = { uploadUrl?: boolean; @@ -2447,6 +2449,7 @@ export type RequestUploadUrlPayloadSelect = { key?: boolean; deduplicated?: boolean; expiresAt?: boolean; + status?: boolean; }; export interface ConfirmUploadPayload { /** The confirmed file ID */ diff --git a/sdk/constructive-react/src/auth/schema-types.ts b/sdk/constructive-react/src/auth/schema-types.ts index 4894391fc..84ea522c5 100644 --- a/sdk/constructive-react/src/auth/schema-types.ts +++ b/sdk/constructive-react/src/auth/schema-types.ts @@ -1628,6 +1628,8 @@ export interface RequestUploadUrlPayload { deduplicated: boolean; /** Presigned URL expiry time (null if deduplicated) */ expiresAt?: string | null; + /** File status — 'pending' for fresh uploads, 'ready' or 'processed' for deduplicated files. Clients can use this to know immediately whether the file is usable. */ + status: string; } export interface ConfirmUploadPayload { /** The confirmed file ID */ diff --git a/sdk/constructive-react/src/objects/orm/client.ts b/sdk/constructive-react/src/objects/orm/client.ts index a1d5c74d8..39fb07eed 100644 --- a/sdk/constructive-react/src/objects/orm/client.ts +++ b/sdk/constructive-react/src/objects/orm/client.ts @@ -31,16 +31,13 @@ export class FetchAdapter implements GraphQLAdapter { constructor( private endpoint: string, headers?: Record, - fetchFn?: typeof globalThis.fetch, + fetchFn?: typeof globalThis.fetch ) { this.headers = headers ?? {}; this.fetchFn = fetchFn ?? createFetch(); } - async execute( - document: string, - variables?: Record, - ): Promise> { + async execute(document: string, variables?: Record): Promise> { const response = await this.fetchFn(this.endpoint, { method: 'POST', headers: { @@ -58,9 +55,7 @@ export class FetchAdapter implements GraphQLAdapter { return { ok: false, data: null, - errors: [ - { message: `HTTP ${response.status}: ${response.statusText}` }, - ], + errors: [{ message: `HTTP ${response.status}: ${response.statusText}` }], }; } @@ -120,7 +115,7 @@ export interface OrmClientConfig { export class GraphQLRequestError extends Error { constructor( public readonly errors: GraphQLError[], - public readonly data: unknown = null, + public readonly data: unknown = null ) { const messages = errors.map((e) => e.message).join('; '); super(`GraphQL Error: ${messages}`); @@ -135,22 +130,13 @@ export class OrmClient { if (config.adapter) { this.adapter = config.adapter; } else if (config.endpoint) { - this.adapter = new FetchAdapter( - config.endpoint, - config.headers, - config.fetch, - ); + this.adapter = new FetchAdapter(config.endpoint, config.headers, config.fetch); } else { - throw new Error( - 'OrmClientConfig requires either an endpoint or a custom adapter', - ); + throw new Error('OrmClientConfig requires either an endpoint or a custom adapter'); } } - async execute( - document: string, - variables?: Record, - ): Promise> { + async execute(document: string, variables?: Record): Promise> { return this.adapter.execute(document, variables); } diff --git a/sdk/constructive-react/src/objects/orm/input-types.ts b/sdk/constructive-react/src/objects/orm/input-types.ts index e1684b49e..5a7d7952c 100644 --- a/sdk/constructive-react/src/objects/orm/input-types.ts +++ b/sdk/constructive-react/src/objects/orm/input-types.ts @@ -829,6 +829,8 @@ export interface RequestUploadUrlPayload { deduplicated: boolean; /** Presigned URL expiry time (null if deduplicated) */ expiresAt?: string | null; + /** File status — 'pending' for fresh uploads, 'ready' or 'processed' for deduplicated files. Clients can use this to know immediately whether the file is usable. */ + status: string; } export type RequestUploadUrlPayloadSelect = { uploadUrl?: boolean; @@ -836,6 +838,7 @@ export type RequestUploadUrlPayloadSelect = { key?: boolean; deduplicated?: boolean; expiresAt?: boolean; + status?: boolean; }; export interface ConfirmUploadPayload { /** The confirmed file ID */ diff --git a/sdk/constructive-react/src/objects/schema-types.ts b/sdk/constructive-react/src/objects/schema-types.ts index 1c861704b..c49783d13 100644 --- a/sdk/constructive-react/src/objects/schema-types.ts +++ b/sdk/constructive-react/src/objects/schema-types.ts @@ -607,6 +607,8 @@ export interface RequestUploadUrlPayload { deduplicated: boolean; /** Presigned URL expiry time (null if deduplicated) */ expiresAt?: string | null; + /** File status — 'pending' for fresh uploads, 'ready' or 'processed' for deduplicated files. Clients can use this to know immediately whether the file is usable. */ + status: string; } export interface ConfirmUploadPayload { /** The confirmed file ID */ diff --git a/sdk/constructive-react/src/public/hooks/README.md b/sdk/constructive-react/src/public/hooks/README.md index 9c1a3e829..77bbb1467 100644 --- a/sdk/constructive-react/src/public/hooks/README.md +++ b/sdk/constructive-react/src/public/hooks/README.md @@ -2162,20 +2162,20 @@ create({ blueprintId: '', databaseId: '', schemaId: '', status ```typescript // List all storageModules const { data, isLoading } = useStorageModulesQuery({ - selection: { fields: { id: true, databaseId: true, schemaId: true, privateSchemaId: true, bucketsTableId: true, filesTableId: true, uploadRequestsTableId: true, bucketsTableName: true, filesTableName: true, uploadRequestsTableName: true, membershipType: true, policies: true, entityTableId: true, endpoint: true, publicUrlPrefix: true, provider: true, allowedOrigins: true, uploadUrlExpirySeconds: true, downloadUrlExpirySeconds: true, defaultMaxFileSize: true, maxFilenameLength: true, cacheTtlSeconds: true } }, + selection: { fields: { id: true, databaseId: true, schemaId: true, privateSchemaId: true, bucketsTableId: true, filesTableId: true, uploadRequestsTableId: true, bucketsTableName: true, filesTableName: true, uploadRequestsTableName: true, membershipType: true, policies: true, skipDefaultPolicyTables: true, entityTableId: true, endpoint: true, publicUrlPrefix: true, provider: true, allowedOrigins: true, uploadUrlExpirySeconds: true, downloadUrlExpirySeconds: true, defaultMaxFileSize: true, maxFilenameLength: true, cacheTtlSeconds: true } }, }); // Get one storageModule const { data: item } = useStorageModuleQuery({ id: '', - selection: { fields: { id: true, databaseId: true, schemaId: true, privateSchemaId: true, bucketsTableId: true, filesTableId: true, uploadRequestsTableId: true, bucketsTableName: true, filesTableName: true, uploadRequestsTableName: true, membershipType: true, policies: true, entityTableId: true, endpoint: true, publicUrlPrefix: true, provider: true, allowedOrigins: true, uploadUrlExpirySeconds: true, downloadUrlExpirySeconds: true, defaultMaxFileSize: true, maxFilenameLength: true, cacheTtlSeconds: true } }, + selection: { fields: { id: true, databaseId: true, schemaId: true, privateSchemaId: true, bucketsTableId: true, filesTableId: true, uploadRequestsTableId: true, bucketsTableName: true, filesTableName: true, uploadRequestsTableName: true, membershipType: true, policies: true, skipDefaultPolicyTables: true, entityTableId: true, endpoint: true, publicUrlPrefix: true, provider: true, allowedOrigins: true, uploadUrlExpirySeconds: true, downloadUrlExpirySeconds: true, defaultMaxFileSize: true, maxFilenameLength: true, cacheTtlSeconds: true } }, }); // Create a storageModule const { mutate: create } = useCreateStorageModuleMutation({ selection: { fields: { id: true } }, }); -create({ databaseId: '', schemaId: '', privateSchemaId: '', bucketsTableId: '', filesTableId: '', uploadRequestsTableId: '', bucketsTableName: '', filesTableName: '', uploadRequestsTableName: '', membershipType: '', policies: '', entityTableId: '', endpoint: '', publicUrlPrefix: '', provider: '', allowedOrigins: '', uploadUrlExpirySeconds: '', downloadUrlExpirySeconds: '', defaultMaxFileSize: '', maxFilenameLength: '', cacheTtlSeconds: '' }); +create({ databaseId: '', schemaId: '', privateSchemaId: '', bucketsTableId: '', filesTableId: '', uploadRequestsTableId: '', bucketsTableName: '', filesTableName: '', uploadRequestsTableName: '', membershipType: '', policies: '', skipDefaultPolicyTables: '', entityTableId: '', endpoint: '', publicUrlPrefix: '', provider: '', allowedOrigins: '', uploadUrlExpirySeconds: '', downloadUrlExpirySeconds: '', defaultMaxFileSize: '', maxFilenameLength: '', cacheTtlSeconds: '' }); ``` ### EntityTypeProvision @@ -2183,20 +2183,20 @@ create({ databaseId: '', schemaId: '', privateSchemaId: '', bu ```typescript // List all entityTypeProvisions const { data, isLoading } = useEntityTypeProvisionsQuery({ - selection: { fields: { id: true, databaseId: true, name: true, prefix: true, description: true, parentEntity: true, tableName: true, isVisible: true, hasLimits: true, hasProfiles: true, hasLevels: true, hasStorage: true, storageConfig: true, skipEntityPolicies: true, tableProvision: true, outMembershipType: true, outEntityTableId: true, outEntityTableName: true, outInstalledModules: true, outStorageModuleId: true, outBucketsTableId: true, outFilesTableId: true } }, + selection: { fields: { id: true, databaseId: true, name: true, prefix: true, description: true, parentEntity: true, tableName: true, isVisible: true, hasLimits: true, hasProfiles: true, hasLevels: true, hasStorage: true, hasInvites: true, storageConfig: true, skipEntityPolicies: true, tableProvision: true, outMembershipType: true, outEntityTableId: true, outEntityTableName: true, outInstalledModules: true, outStorageModuleId: true, outBucketsTableId: true, outFilesTableId: true, outInvitesModuleId: true } }, }); // Get one entityTypeProvision const { data: item } = useEntityTypeProvisionQuery({ id: '', - selection: { fields: { id: true, databaseId: true, name: true, prefix: true, description: true, parentEntity: true, tableName: true, isVisible: true, hasLimits: true, hasProfiles: true, hasLevels: true, hasStorage: true, storageConfig: true, skipEntityPolicies: true, tableProvision: true, outMembershipType: true, outEntityTableId: true, outEntityTableName: true, outInstalledModules: true, outStorageModuleId: true, outBucketsTableId: true, outFilesTableId: true } }, + selection: { fields: { id: true, databaseId: true, name: true, prefix: true, description: true, parentEntity: true, tableName: true, isVisible: true, hasLimits: true, hasProfiles: true, hasLevels: true, hasStorage: true, hasInvites: true, storageConfig: true, skipEntityPolicies: true, tableProvision: true, outMembershipType: true, outEntityTableId: true, outEntityTableName: true, outInstalledModules: true, outStorageModuleId: true, outBucketsTableId: true, outFilesTableId: true, outInvitesModuleId: true } }, }); // Create a entityTypeProvision const { mutate: create } = useCreateEntityTypeProvisionMutation({ selection: { fields: { id: true } }, }); -create({ databaseId: '', name: '', prefix: '', description: '', parentEntity: '', tableName: '', isVisible: '', hasLimits: '', hasProfiles: '', hasLevels: '', hasStorage: '', storageConfig: '', skipEntityPolicies: '', tableProvision: '', outMembershipType: '', outEntityTableId: '', outEntityTableName: '', outInstalledModules: '', outStorageModuleId: '', outBucketsTableId: '', outFilesTableId: '' }); +create({ databaseId: '', name: '', prefix: '', description: '', parentEntity: '', tableName: '', isVisible: '', hasLimits: '', hasProfiles: '', hasLevels: '', hasStorage: '', hasInvites: '', storageConfig: '', skipEntityPolicies: '', tableProvision: '', outMembershipType: '', outEntityTableId: '', outEntityTableName: '', outInstalledModules: '', outStorageModuleId: '', outBucketsTableId: '', outFilesTableId: '', outInvitesModuleId: '' }); ``` ### WebauthnCredentialsModule diff --git a/sdk/constructive-react/src/public/orm/README.md b/sdk/constructive-react/src/public/orm/README.md index 74e83c23e..9d3f602c4 100644 --- a/sdk/constructive-react/src/public/orm/README.md +++ b/sdk/constructive-react/src/public/orm/README.md @@ -2733,7 +2733,8 @@ CRUD operations for StorageModule records. | `filesTableName` | String | Yes | | `uploadRequestsTableName` | String | Yes | | `membershipType` | Int | Yes | -| `policies` | String | Yes | +| `policies` | JSON | Yes | +| `skipDefaultPolicyTables` | String | Yes | | `entityTableId` | UUID | Yes | | `endpoint` | String | Yes | | `publicUrlPrefix` | String | Yes | @@ -2749,13 +2750,13 @@ CRUD operations for StorageModule records. ```typescript // List all storageModule records -const items = await db.storageModule.findMany({ select: { id: true, databaseId: true, schemaId: true, privateSchemaId: true, bucketsTableId: true, filesTableId: true, uploadRequestsTableId: true, bucketsTableName: true, filesTableName: true, uploadRequestsTableName: true, membershipType: true, policies: true, entityTableId: true, endpoint: true, publicUrlPrefix: true, provider: true, allowedOrigins: true, uploadUrlExpirySeconds: true, downloadUrlExpirySeconds: true, defaultMaxFileSize: true, maxFilenameLength: true, cacheTtlSeconds: true } }).execute(); +const items = await db.storageModule.findMany({ select: { id: true, databaseId: true, schemaId: true, privateSchemaId: true, bucketsTableId: true, filesTableId: true, uploadRequestsTableId: true, bucketsTableName: true, filesTableName: true, uploadRequestsTableName: true, membershipType: true, policies: true, skipDefaultPolicyTables: true, entityTableId: true, endpoint: true, publicUrlPrefix: true, provider: true, allowedOrigins: true, uploadUrlExpirySeconds: true, downloadUrlExpirySeconds: true, defaultMaxFileSize: true, maxFilenameLength: true, cacheTtlSeconds: true } }).execute(); // Get one by id -const item = await db.storageModule.findOne({ id: '', select: { id: true, databaseId: true, schemaId: true, privateSchemaId: true, bucketsTableId: true, filesTableId: true, uploadRequestsTableId: true, bucketsTableName: true, filesTableName: true, uploadRequestsTableName: true, membershipType: true, policies: true, entityTableId: true, endpoint: true, publicUrlPrefix: true, provider: true, allowedOrigins: true, uploadUrlExpirySeconds: true, downloadUrlExpirySeconds: true, defaultMaxFileSize: true, maxFilenameLength: true, cacheTtlSeconds: true } }).execute(); +const item = await db.storageModule.findOne({ id: '', select: { id: true, databaseId: true, schemaId: true, privateSchemaId: true, bucketsTableId: true, filesTableId: true, uploadRequestsTableId: true, bucketsTableName: true, filesTableName: true, uploadRequestsTableName: true, membershipType: true, policies: true, skipDefaultPolicyTables: true, entityTableId: true, endpoint: true, publicUrlPrefix: true, provider: true, allowedOrigins: true, uploadUrlExpirySeconds: true, downloadUrlExpirySeconds: true, defaultMaxFileSize: true, maxFilenameLength: true, cacheTtlSeconds: true } }).execute(); // Create -const created = await db.storageModule.create({ data: { databaseId: '', schemaId: '', privateSchemaId: '', bucketsTableId: '', filesTableId: '', uploadRequestsTableId: '', bucketsTableName: '', filesTableName: '', uploadRequestsTableName: '', membershipType: '', policies: '', entityTableId: '', endpoint: '', publicUrlPrefix: '', provider: '', allowedOrigins: '', uploadUrlExpirySeconds: '', downloadUrlExpirySeconds: '', defaultMaxFileSize: '', maxFilenameLength: '', cacheTtlSeconds: '' }, select: { id: true } }).execute(); +const created = await db.storageModule.create({ data: { databaseId: '', schemaId: '', privateSchemaId: '', bucketsTableId: '', filesTableId: '', uploadRequestsTableId: '', bucketsTableName: '', filesTableName: '', uploadRequestsTableName: '', membershipType: '', policies: '', skipDefaultPolicyTables: '', entityTableId: '', endpoint: '', publicUrlPrefix: '', provider: '', allowedOrigins: '', uploadUrlExpirySeconds: '', downloadUrlExpirySeconds: '', defaultMaxFileSize: '', maxFilenameLength: '', cacheTtlSeconds: '' }, select: { id: true } }).execute(); // Update const updated = await db.storageModule.update({ where: { id: '' }, data: { databaseId: '' }, select: { id: true } }).execute(); @@ -2784,6 +2785,7 @@ CRUD operations for EntityTypeProvision records. | `hasProfiles` | Boolean | Yes | | `hasLevels` | Boolean | Yes | | `hasStorage` | Boolean | Yes | +| `hasInvites` | Boolean | Yes | | `storageConfig` | JSON | Yes | | `skipEntityPolicies` | Boolean | Yes | | `tableProvision` | JSON | Yes | @@ -2794,18 +2796,19 @@ CRUD operations for EntityTypeProvision records. | `outStorageModuleId` | UUID | Yes | | `outBucketsTableId` | UUID | Yes | | `outFilesTableId` | UUID | Yes | +| `outInvitesModuleId` | UUID | Yes | **Operations:** ```typescript // List all entityTypeProvision records -const items = await db.entityTypeProvision.findMany({ select: { id: true, databaseId: true, name: true, prefix: true, description: true, parentEntity: true, tableName: true, isVisible: true, hasLimits: true, hasProfiles: true, hasLevels: true, hasStorage: true, storageConfig: true, skipEntityPolicies: true, tableProvision: true, outMembershipType: true, outEntityTableId: true, outEntityTableName: true, outInstalledModules: true, outStorageModuleId: true, outBucketsTableId: true, outFilesTableId: true } }).execute(); +const items = await db.entityTypeProvision.findMany({ select: { id: true, databaseId: true, name: true, prefix: true, description: true, parentEntity: true, tableName: true, isVisible: true, hasLimits: true, hasProfiles: true, hasLevels: true, hasStorage: true, hasInvites: true, storageConfig: true, skipEntityPolicies: true, tableProvision: true, outMembershipType: true, outEntityTableId: true, outEntityTableName: true, outInstalledModules: true, outStorageModuleId: true, outBucketsTableId: true, outFilesTableId: true, outInvitesModuleId: true } }).execute(); // Get one by id -const item = await db.entityTypeProvision.findOne({ id: '', select: { id: true, databaseId: true, name: true, prefix: true, description: true, parentEntity: true, tableName: true, isVisible: true, hasLimits: true, hasProfiles: true, hasLevels: true, hasStorage: true, storageConfig: true, skipEntityPolicies: true, tableProvision: true, outMembershipType: true, outEntityTableId: true, outEntityTableName: true, outInstalledModules: true, outStorageModuleId: true, outBucketsTableId: true, outFilesTableId: true } }).execute(); +const item = await db.entityTypeProvision.findOne({ id: '', select: { id: true, databaseId: true, name: true, prefix: true, description: true, parentEntity: true, tableName: true, isVisible: true, hasLimits: true, hasProfiles: true, hasLevels: true, hasStorage: true, hasInvites: true, storageConfig: true, skipEntityPolicies: true, tableProvision: true, outMembershipType: true, outEntityTableId: true, outEntityTableName: true, outInstalledModules: true, outStorageModuleId: true, outBucketsTableId: true, outFilesTableId: true, outInvitesModuleId: true } }).execute(); // Create -const created = await db.entityTypeProvision.create({ data: { databaseId: '', name: '', prefix: '', description: '', parentEntity: '', tableName: '', isVisible: '', hasLimits: '', hasProfiles: '', hasLevels: '', hasStorage: '', storageConfig: '', skipEntityPolicies: '', tableProvision: '', outMembershipType: '', outEntityTableId: '', outEntityTableName: '', outInstalledModules: '', outStorageModuleId: '', outBucketsTableId: '', outFilesTableId: '' }, select: { id: true } }).execute(); +const created = await db.entityTypeProvision.create({ data: { databaseId: '', name: '', prefix: '', description: '', parentEntity: '', tableName: '', isVisible: '', hasLimits: '', hasProfiles: '', hasLevels: '', hasStorage: '', hasInvites: '', storageConfig: '', skipEntityPolicies: '', tableProvision: '', outMembershipType: '', outEntityTableId: '', outEntityTableName: '', outInstalledModules: '', outStorageModuleId: '', outBucketsTableId: '', outFilesTableId: '', outInvitesModuleId: '' }, select: { id: true } }).execute(); // Update const updated = await db.entityTypeProvision.update({ where: { id: '' }, data: { databaseId: '' }, select: { id: true } }).execute(); diff --git a/sdk/constructive-react/src/public/orm/client.ts b/sdk/constructive-react/src/public/orm/client.ts index a1d5c74d8..39fb07eed 100644 --- a/sdk/constructive-react/src/public/orm/client.ts +++ b/sdk/constructive-react/src/public/orm/client.ts @@ -31,16 +31,13 @@ export class FetchAdapter implements GraphQLAdapter { constructor( private endpoint: string, headers?: Record, - fetchFn?: typeof globalThis.fetch, + fetchFn?: typeof globalThis.fetch ) { this.headers = headers ?? {}; this.fetchFn = fetchFn ?? createFetch(); } - async execute( - document: string, - variables?: Record, - ): Promise> { + async execute(document: string, variables?: Record): Promise> { const response = await this.fetchFn(this.endpoint, { method: 'POST', headers: { @@ -58,9 +55,7 @@ export class FetchAdapter implements GraphQLAdapter { return { ok: false, data: null, - errors: [ - { message: `HTTP ${response.status}: ${response.statusText}` }, - ], + errors: [{ message: `HTTP ${response.status}: ${response.statusText}` }], }; } @@ -120,7 +115,7 @@ export interface OrmClientConfig { export class GraphQLRequestError extends Error { constructor( public readonly errors: GraphQLError[], - public readonly data: unknown = null, + public readonly data: unknown = null ) { const messages = errors.map((e) => e.message).join('; '); super(`GraphQL Error: ${messages}`); @@ -135,22 +130,13 @@ export class OrmClient { if (config.adapter) { this.adapter = config.adapter; } else if (config.endpoint) { - this.adapter = new FetchAdapter( - config.endpoint, - config.headers, - config.fetch, - ); + this.adapter = new FetchAdapter(config.endpoint, config.headers, config.fetch); } else { - throw new Error( - 'OrmClientConfig requires either an endpoint or a custom adapter', - ); + throw new Error('OrmClientConfig requires either an endpoint or a custom adapter'); } } - async execute( - document: string, - variables?: Record, - ): Promise> { + async execute(document: string, variables?: Record): Promise> { return this.adapter.execute(document, variables); } diff --git a/sdk/constructive-react/src/public/orm/input-types.ts b/sdk/constructive-react/src/public/orm/input-types.ts index 888d00b99..6f3337645 100644 --- a/sdk/constructive-react/src/public/orm/input-types.ts +++ b/sdk/constructive-react/src/public/orm/input-types.ts @@ -1361,7 +1361,8 @@ export interface StorageModule { filesTableName?: string | null; uploadRequestsTableName?: string | null; membershipType?: number | null; - policies?: string[] | null; + policies?: Record | null; + skipDefaultPolicyTables?: string[] | null; entityTableId?: string | null; endpoint?: string | null; publicUrlPrefix?: string | null; @@ -1389,8 +1390,8 @@ export interface EntityTypeProvision { /** The database to provision this entity type in. Required. */ databaseId?: string | null; /** - * Human-readable name for this membership type, e.g. 'Data Room Member', 'Team Channel Member'. Required. - * Stored in the membership_types registry table. + * Human-readable name for this entity type, e.g. 'Data Room', 'Team Channel'. Required. + * Stored in the entity_types registry table. */ name?: string | null; /** @@ -1400,7 +1401,7 @@ export interface EntityTypeProvision { * Must be unique per database — the (database_id, prefix) constraint ensures graceful ON CONFLICT DO NOTHING. */ prefix?: string | null; - /** Description of this membership type. Stored in the membership_types registry table. Defaults to empty string. */ + /** Description of this entity type. Stored in the entity_types registry table. Defaults to empty string. */ description?: string | null; /** * Prefix of the parent entity type. The trigger resolves this to a membership_type integer @@ -1451,6 +1452,16 @@ export interface EntityTypeProvision { * Storage tables get owner_id FK to the entity table, so files are owned by the entity. */ hasStorage?: boolean | null; + /** + * Whether to provision invites_module for this type. Defaults to false. + * When true, the trigger inserts a row into invites_module which in turn + * (via insert_invites_module BEFORE INSERT) creates {prefix}_invites and + * {prefix}_claimed_invites tables plus the submit_{prefix}_invite_code() function. + * Symmetric counterpart of has_storage. Re-provisioning is idempotent: the + * UNIQUE (database_id, membership_type) constraint on invites_module combined with + * ON CONFLICT DO NOTHING in the fan-out makes repeated INSERTs safe. + */ + hasInvites?: boolean | null; /** * Optional jsonb object for storage module configuration and initial bucket seeding. * Only used when has_storage = true; ignored otherwise. NULL = use defaults. @@ -1467,8 +1478,21 @@ export interface EntityTypeProvision { * - allowed_mime_types (text[]) whitelist of MIME types (null = any) * - max_file_size (bigint) max file size in bytes (null = use scope default) * - allowed_origins (text[]) per-bucket CORS override + * - provisions (jsonb object) optional: customize storage tables + * with additional nodes, fields, grants, and policies. + * Keyed by table role: "files", "buckets", "upload_requests". + * Each value uses the same shape as table_provision: + * { nodes, fields, grants, use_rls, policies }. Fanned out + * to secure_table_provision targeting the corresponding table. + * When a key includes policies[], those REPLACE the default + * storage policies for that table; tables without a key still + * get defaults. Missing "data" on policy entries is auto-populated + * with storage-specific defaults (same as table_provision). + * Example: add SearchBm25 for full-text search on files: + * {"provisions": {"files": {"nodes": [{"$type": + * "SearchBm25", "data": {"source_fields": ["description"]}}]}}} * Example: - * storage_config := '{"buckets": [{"name": "documents", "is_public": false, "allowed_mime_types": ["application/pdf"]}]}'::jsonb + * storage_config := '{"buckets": [{"name": "documents", "is_public": false, "allowed_mime_types": ["application/pdf"]}], "provisions": {"files": {"nodes": [{"$type": "SearchBm25", "data": {"source_fields": ["description"]}}]}}}'::jsonb */ storageConfig?: Record | null; /** @@ -1519,7 +1543,7 @@ export interface EntityTypeProvision { tableProvision?: Record | null; /** * Output: the auto-assigned integer membership type ID. Populated by the trigger after successful provisioning. - * This is the ID used in membership_types, memberships_module, and all module tables. + * This is the ID used in entity_types, memberships_module, and all module tables. */ outMembershipType?: number | null; /** @@ -1540,6 +1564,12 @@ export interface EntityTypeProvision { outBucketsTableId?: string | null; /** Output: the UUID of the generated files table (e.g. data_room_files). Populated by the trigger when has_storage=true. */ outFilesTableId?: string | null; + /** + * Output: the UUID of the invites_module row created for this entity type. Populated by the trigger when has_invites=true. + * NULL when has_invites=false, or when re-provisioning hits ON CONFLICT DO NOTHING + * (i.e. the invites_module row was created in a previous run). + */ + outInvitesModuleId?: string | null; } /** Config row for the webauthn_credentials_module, which provisions the per-user WebAuthn/passkey credentials table (public key, counter, transports, device type, backup state) mirroring crypto_addresses_module. The sibling webauthn_auth_module holds RP config and the registration/sign-in challenge state. */ export interface WebauthnCredentialsModule { @@ -5321,6 +5351,7 @@ export type StorageModuleSelect = { uploadRequestsTableName?: boolean; membershipType?: boolean; policies?: boolean; + skipDefaultPolicyTables?: boolean; entityTableId?: boolean; endpoint?: boolean; publicUrlPrefix?: boolean; @@ -5366,6 +5397,7 @@ export type EntityTypeProvisionSelect = { hasProfiles?: boolean; hasLevels?: boolean; hasStorage?: boolean; + hasInvites?: boolean; storageConfig?: boolean; skipEntityPolicies?: boolean; tableProvision?: boolean; @@ -5376,6 +5408,7 @@ export type EntityTypeProvisionSelect = { outStorageModuleId?: boolean; outBucketsTableId?: boolean; outFilesTableId?: boolean; + outInvitesModuleId?: boolean; database?: { select: DatabaseSelect; }; @@ -9422,7 +9455,9 @@ export interface StorageModuleFilter { /** Filter by the object’s `membershipType` field. */ membershipType?: IntFilter; /** Filter by the object’s `policies` field. */ - policies?: StringListFilter; + policies?: JSONFilter; + /** Filter by the object’s `skipDefaultPolicyTables` field. */ + skipDefaultPolicyTables?: StringListFilter; /** Filter by the object’s `entityTableId` field. */ entityTableId?: UUIDFilter; /** Filter by the object’s `endpoint` field. */ @@ -9491,6 +9526,8 @@ export interface EntityTypeProvisionFilter { hasLevels?: BooleanFilter; /** Filter by the object’s `hasStorage` field. */ hasStorage?: BooleanFilter; + /** Filter by the object’s `hasInvites` field. */ + hasInvites?: BooleanFilter; /** Filter by the object’s `storageConfig` field. */ storageConfig?: JSONFilter; /** Filter by the object’s `skipEntityPolicies` field. */ @@ -9511,6 +9548,8 @@ export interface EntityTypeProvisionFilter { outBucketsTableId?: UUIDFilter; /** Filter by the object’s `outFilesTableId` field. */ outFilesTableId?: UUIDFilter; + /** Filter by the object’s `outInvitesModuleId` field. */ + outInvitesModuleId?: UUIDFilter; /** Checks for all expressions in this list. */ and?: EntityTypeProvisionFilter[]; /** Checks for any expressions in this list. */ @@ -13014,6 +13053,8 @@ export type StorageModuleOrderBy = | 'MEMBERSHIP_TYPE_DESC' | 'POLICIES_ASC' | 'POLICIES_DESC' + | 'SKIP_DEFAULT_POLICY_TABLES_ASC' + | 'SKIP_DEFAULT_POLICY_TABLES_DESC' | 'ENTITY_TABLE_ID_ASC' | 'ENTITY_TABLE_ID_DESC' | 'ENDPOINT_ASC' @@ -13062,6 +13103,8 @@ export type EntityTypeProvisionOrderBy = | 'HAS_LEVELS_DESC' | 'HAS_STORAGE_ASC' | 'HAS_STORAGE_DESC' + | 'HAS_INVITES_ASC' + | 'HAS_INVITES_DESC' | 'STORAGE_CONFIG_ASC' | 'STORAGE_CONFIG_DESC' | 'SKIP_ENTITY_POLICIES_ASC' @@ -13081,7 +13124,9 @@ export type EntityTypeProvisionOrderBy = | 'OUT_BUCKETS_TABLE_ID_ASC' | 'OUT_BUCKETS_TABLE_ID_DESC' | 'OUT_FILES_TABLE_ID_ASC' - | 'OUT_FILES_TABLE_ID_DESC'; + | 'OUT_FILES_TABLE_ID_DESC' + | 'OUT_INVITES_MODULE_ID_ASC' + | 'OUT_INVITES_MODULE_ID_DESC'; export type WebauthnCredentialsModuleOrderBy = | 'NATURAL' | 'PRIMARY_KEY_ASC' @@ -16466,7 +16511,8 @@ export interface CreateStorageModuleInput { filesTableName?: string; uploadRequestsTableName?: string; membershipType?: number; - policies?: string[]; + policies?: Record; + skipDefaultPolicyTables?: string[]; entityTableId?: string; endpoint?: string; publicUrlPrefix?: string; @@ -16490,7 +16536,8 @@ export interface StorageModulePatch { filesTableName?: string | null; uploadRequestsTableName?: string | null; membershipType?: number | null; - policies?: string[] | null; + policies?: Record | null; + skipDefaultPolicyTables?: string[] | null; entityTableId?: string | null; endpoint?: string | null; publicUrlPrefix?: string | null; @@ -16525,6 +16572,7 @@ export interface CreateEntityTypeProvisionInput { hasProfiles?: boolean; hasLevels?: boolean; hasStorage?: boolean; + hasInvites?: boolean; storageConfig?: Record; skipEntityPolicies?: boolean; tableProvision?: Record; @@ -16535,6 +16583,7 @@ export interface CreateEntityTypeProvisionInput { outStorageModuleId?: string; outBucketsTableId?: string; outFilesTableId?: string; + outInvitesModuleId?: string; }; } export interface EntityTypeProvisionPatch { @@ -16549,6 +16598,7 @@ export interface EntityTypeProvisionPatch { hasProfiles?: boolean | null; hasLevels?: boolean | null; hasStorage?: boolean | null; + hasInvites?: boolean | null; storageConfig?: Record | null; skipEntityPolicies?: boolean | null; tableProvision?: Record | null; @@ -16559,6 +16609,7 @@ export interface EntityTypeProvisionPatch { outStorageModuleId?: string | null; outBucketsTableId?: string | null; outFilesTableId?: string | null; + outInvitesModuleId?: string | null; } export interface UpdateEntityTypeProvisionInput { clientMutationId?: string; @@ -18535,6 +18586,7 @@ export interface ProvisionTableInput { indexes?: Record; fullTextSearches?: Record; uniqueConstraints?: Record; + description?: string; } export interface SendVerificationEmailInput { clientMutationId?: string; @@ -22806,7 +22858,9 @@ export interface StorageModuleFilter { /** Filter by the object’s `membershipType` field. */ membershipType?: IntFilter; /** Filter by the object’s `policies` field. */ - policies?: StringListFilter; + policies?: JSONFilter; + /** Filter by the object’s `skipDefaultPolicyTables` field. */ + skipDefaultPolicyTables?: StringListFilter; /** Filter by the object’s `entityTableId` field. */ entityTableId?: UUIDFilter; /** Filter by the object’s `endpoint` field. */ @@ -22876,6 +22930,8 @@ export interface EntityTypeProvisionFilter { hasLevels?: BooleanFilter; /** Filter by the object’s `hasStorage` field. */ hasStorage?: BooleanFilter; + /** Filter by the object’s `hasInvites` field. */ + hasInvites?: BooleanFilter; /** Filter by the object’s `storageConfig` field. */ storageConfig?: JSONFilter; /** Filter by the object’s `skipEntityPolicies` field. */ @@ -22896,6 +22952,8 @@ export interface EntityTypeProvisionFilter { outBucketsTableId?: UUIDFilter; /** Filter by the object’s `outFilesTableId` field. */ outFilesTableId?: UUIDFilter; + /** Filter by the object’s `outInvitesModuleId` field. */ + outInvitesModuleId?: UUIDFilter; /** Checks for all expressions in this list. */ and?: EntityTypeProvisionFilter[]; /** Checks for any expressions in this list. */ @@ -25755,6 +25813,8 @@ export interface RequestUploadUrlPayload { deduplicated: boolean; /** Presigned URL expiry time (null if deduplicated) */ expiresAt?: string | null; + /** File status — 'pending' for fresh uploads, 'ready' or 'processed' for deduplicated files. Clients can use this to know immediately whether the file is usable. */ + status: string; } export type RequestUploadUrlPayloadSelect = { uploadUrl?: boolean; @@ -25762,6 +25822,7 @@ export type RequestUploadUrlPayloadSelect = { key?: boolean; deduplicated?: boolean; expiresAt?: boolean; + status?: boolean; }; export interface ConfirmUploadPayload { /** The confirmed file ID */ diff --git a/sdk/constructive-react/src/public/schema-types.ts b/sdk/constructive-react/src/public/schema-types.ts index fe153da77..a808414e0 100644 --- a/sdk/constructive-react/src/public/schema-types.ts +++ b/sdk/constructive-react/src/public/schema-types.ts @@ -1835,6 +1835,8 @@ export type StorageModuleOrderBy = | 'MEMBERSHIP_TYPE_DESC' | 'POLICIES_ASC' | 'POLICIES_DESC' + | 'SKIP_DEFAULT_POLICY_TABLES_ASC' + | 'SKIP_DEFAULT_POLICY_TABLES_DESC' | 'ENTITY_TABLE_ID_ASC' | 'ENTITY_TABLE_ID_DESC' | 'ENDPOINT_ASC' @@ -1884,6 +1886,8 @@ export type EntityTypeProvisionOrderBy = | 'HAS_LEVELS_DESC' | 'HAS_STORAGE_ASC' | 'HAS_STORAGE_DESC' + | 'HAS_INVITES_ASC' + | 'HAS_INVITES_DESC' | 'STORAGE_CONFIG_ASC' | 'STORAGE_CONFIG_DESC' | 'SKIP_ENTITY_POLICIES_ASC' @@ -1903,7 +1907,9 @@ export type EntityTypeProvisionOrderBy = | 'OUT_BUCKETS_TABLE_ID_ASC' | 'OUT_BUCKETS_TABLE_ID_DESC' | 'OUT_FILES_TABLE_ID_ASC' - | 'OUT_FILES_TABLE_ID_DESC'; + | 'OUT_FILES_TABLE_ID_DESC' + | 'OUT_INVITES_MODULE_ID_ASC' + | 'OUT_INVITES_MODULE_ID_DESC'; /** Methods to use when ordering `WebauthnCredentialsModule`. */ export type WebauthnCredentialsModuleOrderBy = | 'NATURAL' @@ -8952,7 +8958,9 @@ export interface StorageModuleFilter { /** Filter by the object’s `membershipType` field. */ membershipType?: IntFilter; /** Filter by the object’s `policies` field. */ - policies?: StringListFilter; + policies?: JSONFilter; + /** Filter by the object’s `skipDefaultPolicyTables` field. */ + skipDefaultPolicyTables?: StringListFilter; /** Filter by the object’s `entityTableId` field. */ entityTableId?: UUIDFilter; /** Filter by the object’s `endpoint` field. */ @@ -9031,6 +9039,8 @@ export interface EntityTypeProvisionFilter { hasLevels?: BooleanFilter; /** Filter by the object’s `hasStorage` field. */ hasStorage?: BooleanFilter; + /** Filter by the object’s `hasInvites` field. */ + hasInvites?: BooleanFilter; /** Filter by the object’s `storageConfig` field. */ storageConfig?: JSONFilter; /** Filter by the object’s `skipEntityPolicies` field. */ @@ -9051,6 +9061,8 @@ export interface EntityTypeProvisionFilter { outBucketsTableId?: UUIDFilter; /** Filter by the object’s `outFilesTableId` field. */ outFilesTableId?: UUIDFilter; + /** Filter by the object’s `outInvitesModuleId` field. */ + outInvitesModuleId?: UUIDFilter; /** Checks for all expressions in this list. */ and?: EntityTypeProvisionFilter[]; /** Checks for any expressions in this list. */ @@ -10083,6 +10095,7 @@ export interface ProvisionTableInput { indexes?: unknown; fullTextSearches?: unknown; uniqueConstraints?: unknown; + description?: string; } export interface SendVerificationEmailInput { clientMutationId?: string; @@ -12272,6 +12285,64 @@ export interface ForeignKeyConstraintInput { createdAt?: string; updatedAt?: string; } +export interface CreateTableInput { + clientMutationId?: string; + /** The `Table` to be created by this mutation. */ + table: TableInput; +} +/** An input for mutations affecting `Table` */ +export interface TableInput { + id?: string; + databaseId?: string; + schemaId: string; + name: string; + label?: string; + description?: string; + smartTags?: unknown; + category?: ObjectCategory; + module?: string; + scope?: number; + useRls?: boolean; + timestamps?: boolean; + peoplestamps?: boolean; + pluralName?: string; + singularName?: string; + tags?: string[]; + inheritsId?: string; + createdAt?: string; + updatedAt?: string; +} +export interface CreateStorageModuleInput { + clientMutationId?: string; + /** The `StorageModule` to be created by this mutation. */ + storageModule: StorageModuleInput; +} +/** An input for mutations affecting `StorageModule` */ +export interface StorageModuleInput { + id?: string; + databaseId: string; + schemaId?: string; + privateSchemaId?: string; + bucketsTableId?: string; + filesTableId?: string; + uploadRequestsTableId?: string; + bucketsTableName?: string; + filesTableName?: string; + uploadRequestsTableName?: string; + membershipType?: number; + policies?: unknown; + skipDefaultPolicyTables?: string[]; + entityTableId?: string; + endpoint?: string; + publicUrlPrefix?: string; + provider?: string; + allowedOrigins?: string[]; + uploadUrlExpirySeconds?: number; + downloadUrlExpirySeconds?: number; + defaultMaxFileSize?: string; + maxFilenameLength?: number; + cacheTtlSeconds?: number; +} export interface CreateEntityTypeProvisionInput { clientMutationId?: string; /** The `EntityTypeProvision` to be created by this mutation. */ @@ -12284,8 +12355,8 @@ export interface EntityTypeProvisionInput { /** The database to provision this entity type in. Required. */ databaseId: string; /** - * Human-readable name for this membership type, e.g. 'Data Room Member', 'Team Channel Member'. Required. - * Stored in the membership_types registry table. + * Human-readable name for this entity type, e.g. 'Data Room', 'Team Channel'. Required. + * Stored in the entity_types registry table. */ name: string; /** @@ -12295,7 +12366,7 @@ export interface EntityTypeProvisionInput { * Must be unique per database — the (database_id, prefix) constraint ensures graceful ON CONFLICT DO NOTHING. */ prefix: string; - /** Description of this membership type. Stored in the membership_types registry table. Defaults to empty string. */ + /** Description of this entity type. Stored in the entity_types registry table. Defaults to empty string. */ description?: string; /** * Prefix of the parent entity type. The trigger resolves this to a membership_type integer @@ -12346,6 +12417,16 @@ export interface EntityTypeProvisionInput { * Storage tables get owner_id FK to the entity table, so files are owned by the entity. */ hasStorage?: boolean; + /** + * Whether to provision invites_module for this type. Defaults to false. + * When true, the trigger inserts a row into invites_module which in turn + * (via insert_invites_module BEFORE INSERT) creates {prefix}_invites and + * {prefix}_claimed_invites tables plus the submit_{prefix}_invite_code() function. + * Symmetric counterpart of has_storage. Re-provisioning is idempotent: the + * UNIQUE (database_id, membership_type) constraint on invites_module combined with + * ON CONFLICT DO NOTHING in the fan-out makes repeated INSERTs safe. + */ + hasInvites?: boolean; /** * Optional jsonb object for storage module configuration and initial bucket seeding. * Only used when has_storage = true; ignored otherwise. NULL = use defaults. @@ -12362,8 +12443,21 @@ export interface EntityTypeProvisionInput { * - allowed_mime_types (text[]) whitelist of MIME types (null = any) * - max_file_size (bigint) max file size in bytes (null = use scope default) * - allowed_origins (text[]) per-bucket CORS override + * - provisions (jsonb object) optional: customize storage tables + * with additional nodes, fields, grants, and policies. + * Keyed by table role: "files", "buckets", "upload_requests". + * Each value uses the same shape as table_provision: + * { nodes, fields, grants, use_rls, policies }. Fanned out + * to secure_table_provision targeting the corresponding table. + * When a key includes policies[], those REPLACE the default + * storage policies for that table; tables without a key still + * get defaults. Missing "data" on policy entries is auto-populated + * with storage-specific defaults (same as table_provision). + * Example: add SearchBm25 for full-text search on files: + * {"provisions": {"files": {"nodes": [{"$type": + * "SearchBm25", "data": {"source_fields": ["description"]}}]}}} * Example: - * storage_config := '{"buckets": [{"name": "documents", "is_public": false, "allowed_mime_types": ["application/pdf"]}]}'::jsonb + * storage_config := '{"buckets": [{"name": "documents", "is_public": false, "allowed_mime_types": ["application/pdf"]}], "provisions": {"files": {"nodes": [{"$type": "SearchBm25", "data": {"source_fields": ["description"]}}]}}}'::jsonb */ storageConfig?: unknown; /** @@ -12414,7 +12508,7 @@ export interface EntityTypeProvisionInput { tableProvision?: unknown; /** * Output: the auto-assigned integer membership type ID. Populated by the trigger after successful provisioning. - * This is the ID used in membership_types, memberships_module, and all module tables. + * This is the ID used in entity_types, memberships_module, and all module tables. */ outMembershipType?: number; /** @@ -12435,63 +12529,12 @@ export interface EntityTypeProvisionInput { outBucketsTableId?: string; /** Output: the UUID of the generated files table (e.g. data_room_files). Populated by the trigger when has_storage=true. */ outFilesTableId?: string; -} -export interface CreateStorageModuleInput { - clientMutationId?: string; - /** The `StorageModule` to be created by this mutation. */ - storageModule: StorageModuleInput; -} -/** An input for mutations affecting `StorageModule` */ -export interface StorageModuleInput { - id?: string; - databaseId: string; - schemaId?: string; - privateSchemaId?: string; - bucketsTableId?: string; - filesTableId?: string; - uploadRequestsTableId?: string; - bucketsTableName?: string; - filesTableName?: string; - uploadRequestsTableName?: string; - membershipType?: number; - policies?: string[]; - entityTableId?: string; - endpoint?: string; - publicUrlPrefix?: string; - provider?: string; - allowedOrigins?: string[]; - uploadUrlExpirySeconds?: number; - downloadUrlExpirySeconds?: number; - defaultMaxFileSize?: string; - maxFilenameLength?: number; - cacheTtlSeconds?: number; -} -export interface CreateTableInput { - clientMutationId?: string; - /** The `Table` to be created by this mutation. */ - table: TableInput; -} -/** An input for mutations affecting `Table` */ -export interface TableInput { - id?: string; - databaseId?: string; - schemaId: string; - name: string; - label?: string; - description?: string; - smartTags?: unknown; - category?: ObjectCategory; - module?: string; - scope?: number; - useRls?: boolean; - timestamps?: boolean; - peoplestamps?: boolean; - pluralName?: string; - singularName?: string; - tags?: string[]; - inheritsId?: string; - createdAt?: string; - updatedAt?: string; + /** + * Output: the UUID of the invites_module row created for this entity type. Populated by the trigger when has_invites=true. + * NULL when has_invites=false, or when re-provisioning hits ON CONFLICT DO NOTHING + * (i.e. the invites_module row was created in a previous run). + */ + outInvitesModuleId?: string; } export interface CreateRelationProvisionInput { clientMutationId?: string; @@ -15005,6 +15048,66 @@ export interface ForeignKeyConstraintPatch { createdAt?: string; updatedAt?: string; } +export interface UpdateTableInput { + clientMutationId?: string; + id: string; + /** An object where the defined keys will be set on the `Table` being updated. */ + tablePatch: TablePatch; +} +/** Represents an update to a `Table`. Fields that are set will be updated. */ +export interface TablePatch { + id?: string; + databaseId?: string; + schemaId?: string; + name?: string; + label?: string; + description?: string; + smartTags?: unknown; + category?: ObjectCategory; + module?: string; + scope?: number; + useRls?: boolean; + timestamps?: boolean; + peoplestamps?: boolean; + pluralName?: string; + singularName?: string; + tags?: string[]; + inheritsId?: string; + createdAt?: string; + updatedAt?: string; +} +export interface UpdateStorageModuleInput { + clientMutationId?: string; + id: string; + /** An object where the defined keys will be set on the `StorageModule` being updated. */ + storageModulePatch: StorageModulePatch; +} +/** Represents an update to a `StorageModule`. Fields that are set will be updated. */ +export interface StorageModulePatch { + id?: string; + databaseId?: string; + schemaId?: string; + privateSchemaId?: string; + bucketsTableId?: string; + filesTableId?: string; + uploadRequestsTableId?: string; + bucketsTableName?: string; + filesTableName?: string; + uploadRequestsTableName?: string; + membershipType?: number; + policies?: unknown; + skipDefaultPolicyTables?: string[]; + entityTableId?: string; + endpoint?: string; + publicUrlPrefix?: string; + provider?: string; + allowedOrigins?: string[]; + uploadUrlExpirySeconds?: number; + downloadUrlExpirySeconds?: number; + defaultMaxFileSize?: string; + maxFilenameLength?: number; + cacheTtlSeconds?: number; +} export interface UpdateEntityTypeProvisionInput { clientMutationId?: string; /** Unique identifier for this provision row. */ @@ -15019,8 +15122,8 @@ export interface EntityTypeProvisionPatch { /** The database to provision this entity type in. Required. */ databaseId?: string; /** - * Human-readable name for this membership type, e.g. 'Data Room Member', 'Team Channel Member'. Required. - * Stored in the membership_types registry table. + * Human-readable name for this entity type, e.g. 'Data Room', 'Team Channel'. Required. + * Stored in the entity_types registry table. */ name?: string; /** @@ -15030,7 +15133,7 @@ export interface EntityTypeProvisionPatch { * Must be unique per database — the (database_id, prefix) constraint ensures graceful ON CONFLICT DO NOTHING. */ prefix?: string; - /** Description of this membership type. Stored in the membership_types registry table. Defaults to empty string. */ + /** Description of this entity type. Stored in the entity_types registry table. Defaults to empty string. */ description?: string; /** * Prefix of the parent entity type. The trigger resolves this to a membership_type integer @@ -15081,6 +15184,16 @@ export interface EntityTypeProvisionPatch { * Storage tables get owner_id FK to the entity table, so files are owned by the entity. */ hasStorage?: boolean; + /** + * Whether to provision invites_module for this type. Defaults to false. + * When true, the trigger inserts a row into invites_module which in turn + * (via insert_invites_module BEFORE INSERT) creates {prefix}_invites and + * {prefix}_claimed_invites tables plus the submit_{prefix}_invite_code() function. + * Symmetric counterpart of has_storage. Re-provisioning is idempotent: the + * UNIQUE (database_id, membership_type) constraint on invites_module combined with + * ON CONFLICT DO NOTHING in the fan-out makes repeated INSERTs safe. + */ + hasInvites?: boolean; /** * Optional jsonb object for storage module configuration and initial bucket seeding. * Only used when has_storage = true; ignored otherwise. NULL = use defaults. @@ -15097,8 +15210,21 @@ export interface EntityTypeProvisionPatch { * - allowed_mime_types (text[]) whitelist of MIME types (null = any) * - max_file_size (bigint) max file size in bytes (null = use scope default) * - allowed_origins (text[]) per-bucket CORS override + * - provisions (jsonb object) optional: customize storage tables + * with additional nodes, fields, grants, and policies. + * Keyed by table role: "files", "buckets", "upload_requests". + * Each value uses the same shape as table_provision: + * { nodes, fields, grants, use_rls, policies }. Fanned out + * to secure_table_provision targeting the corresponding table. + * When a key includes policies[], those REPLACE the default + * storage policies for that table; tables without a key still + * get defaults. Missing "data" on policy entries is auto-populated + * with storage-specific defaults (same as table_provision). + * Example: add SearchBm25 for full-text search on files: + * {"provisions": {"files": {"nodes": [{"$type": + * "SearchBm25", "data": {"source_fields": ["description"]}}]}}} * Example: - * storage_config := '{"buckets": [{"name": "documents", "is_public": false, "allowed_mime_types": ["application/pdf"]}]}'::jsonb + * storage_config := '{"buckets": [{"name": "documents", "is_public": false, "allowed_mime_types": ["application/pdf"]}], "provisions": {"files": {"nodes": [{"$type": "SearchBm25", "data": {"source_fields": ["description"]}}]}}}'::jsonb */ storageConfig?: unknown; /** @@ -15149,7 +15275,7 @@ export interface EntityTypeProvisionPatch { tableProvision?: unknown; /** * Output: the auto-assigned integer membership type ID. Populated by the trigger after successful provisioning. - * This is the ID used in membership_types, memberships_module, and all module tables. + * This is the ID used in entity_types, memberships_module, and all module tables. */ outMembershipType?: number; /** @@ -15170,65 +15296,12 @@ export interface EntityTypeProvisionPatch { outBucketsTableId?: string; /** Output: the UUID of the generated files table (e.g. data_room_files). Populated by the trigger when has_storage=true. */ outFilesTableId?: string; -} -export interface UpdateStorageModuleInput { - clientMutationId?: string; - id: string; - /** An object where the defined keys will be set on the `StorageModule` being updated. */ - storageModulePatch: StorageModulePatch; -} -/** Represents an update to a `StorageModule`. Fields that are set will be updated. */ -export interface StorageModulePatch { - id?: string; - databaseId?: string; - schemaId?: string; - privateSchemaId?: string; - bucketsTableId?: string; - filesTableId?: string; - uploadRequestsTableId?: string; - bucketsTableName?: string; - filesTableName?: string; - uploadRequestsTableName?: string; - membershipType?: number; - policies?: string[]; - entityTableId?: string; - endpoint?: string; - publicUrlPrefix?: string; - provider?: string; - allowedOrigins?: string[]; - uploadUrlExpirySeconds?: number; - downloadUrlExpirySeconds?: number; - defaultMaxFileSize?: string; - maxFilenameLength?: number; - cacheTtlSeconds?: number; -} -export interface UpdateTableInput { - clientMutationId?: string; - id: string; - /** An object where the defined keys will be set on the `Table` being updated. */ - tablePatch: TablePatch; -} -/** Represents an update to a `Table`. Fields that are set will be updated. */ -export interface TablePatch { - id?: string; - databaseId?: string; - schemaId?: string; - name?: string; - label?: string; - description?: string; - smartTags?: unknown; - category?: ObjectCategory; - module?: string; - scope?: number; - useRls?: boolean; - timestamps?: boolean; - peoplestamps?: boolean; - pluralName?: string; - singularName?: string; - tags?: string[]; - inheritsId?: string; - createdAt?: string; - updatedAt?: string; + /** + * Output: the UUID of the invites_module row created for this entity type. Populated by the trigger when has_invites=true. + * NULL when has_invites=false, or when re-provisioning hits ON CONFLICT DO NOTHING + * (i.e. the invites_module row was created in a previous run). + */ + outInvitesModuleId?: string; } export interface UpdateRelationProvisionInput { clientMutationId?: string; @@ -15944,17 +16017,17 @@ export interface DeleteForeignKeyConstraintInput { clientMutationId?: string; id: string; } -export interface DeleteEntityTypeProvisionInput { +export interface DeleteTableInput { clientMutationId?: string; - /** Unique identifier for this provision row. */ id: string; } export interface DeleteStorageModuleInput { clientMutationId?: string; id: string; } -export interface DeleteTableInput { +export interface DeleteEntityTypeProvisionInput { clientMutationId?: string; + /** Unique identifier for this provision row. */ id: string; } export interface DeleteRelationProvisionInput { @@ -16794,10 +16867,10 @@ export interface ForeignKeyConstraintConnection { pageInfo: PageInfo; totalCount: number; } -/** A connection to a list of `EntityTypeProvision` values. */ -export interface EntityTypeProvisionConnection { - nodes: EntityTypeProvision[]; - edges: EntityTypeProvisionEdge[]; +/** A connection to a list of `Table` values. */ +export interface TableConnection { + nodes: Table[]; + edges: TableEdge[]; pageInfo: PageInfo; totalCount: number; } @@ -16808,10 +16881,10 @@ export interface StorageModuleConnection { pageInfo: PageInfo; totalCount: number; } -/** A connection to a list of `Table` values. */ -export interface TableConnection { - nodes: Table[]; - edges: TableEdge[]; +/** A connection to a list of `EntityTypeProvision` values. */ +export interface EntityTypeProvisionConnection { + nodes: EntityTypeProvision[]; + edges: EntityTypeProvisionEdge[]; pageInfo: PageInfo; totalCount: number; } @@ -17683,11 +17756,11 @@ export interface CreateForeignKeyConstraintPayload { foreignKeyConstraint?: ForeignKeyConstraint | null; foreignKeyConstraintEdge?: ForeignKeyConstraintEdge | null; } -export interface CreateEntityTypeProvisionPayload { +export interface CreateTablePayload { clientMutationId?: string | null; - /** The `EntityTypeProvision` that was created by this mutation. */ - entityTypeProvision?: EntityTypeProvision | null; - entityTypeProvisionEdge?: EntityTypeProvisionEdge | null; + /** The `Table` that was created by this mutation. */ + table?: Table | null; + tableEdge?: TableEdge | null; } export interface CreateStorageModulePayload { clientMutationId?: string | null; @@ -17695,11 +17768,11 @@ export interface CreateStorageModulePayload { storageModule?: StorageModule | null; storageModuleEdge?: StorageModuleEdge | null; } -export interface CreateTablePayload { +export interface CreateEntityTypeProvisionPayload { clientMutationId?: string | null; - /** The `Table` that was created by this mutation. */ - table?: Table | null; - tableEdge?: TableEdge | null; + /** The `EntityTypeProvision` that was created by this mutation. */ + entityTypeProvision?: EntityTypeProvision | null; + entityTypeProvisionEdge?: EntityTypeProvisionEdge | null; } export interface CreateRelationProvisionPayload { clientMutationId?: string | null; @@ -18355,11 +18428,11 @@ export interface UpdateForeignKeyConstraintPayload { foreignKeyConstraint?: ForeignKeyConstraint | null; foreignKeyConstraintEdge?: ForeignKeyConstraintEdge | null; } -export interface UpdateEntityTypeProvisionPayload { +export interface UpdateTablePayload { clientMutationId?: string | null; - /** The `EntityTypeProvision` that was updated by this mutation. */ - entityTypeProvision?: EntityTypeProvision | null; - entityTypeProvisionEdge?: EntityTypeProvisionEdge | null; + /** The `Table` that was updated by this mutation. */ + table?: Table | null; + tableEdge?: TableEdge | null; } export interface UpdateStorageModulePayload { clientMutationId?: string | null; @@ -18367,11 +18440,11 @@ export interface UpdateStorageModulePayload { storageModule?: StorageModule | null; storageModuleEdge?: StorageModuleEdge | null; } -export interface UpdateTablePayload { +export interface UpdateEntityTypeProvisionPayload { clientMutationId?: string | null; - /** The `Table` that was updated by this mutation. */ - table?: Table | null; - tableEdge?: TableEdge | null; + /** The `EntityTypeProvision` that was updated by this mutation. */ + entityTypeProvision?: EntityTypeProvision | null; + entityTypeProvisionEdge?: EntityTypeProvisionEdge | null; } export interface UpdateRelationProvisionPayload { clientMutationId?: string | null; @@ -19027,11 +19100,11 @@ export interface DeleteForeignKeyConstraintPayload { foreignKeyConstraint?: ForeignKeyConstraint | null; foreignKeyConstraintEdge?: ForeignKeyConstraintEdge | null; } -export interface DeleteEntityTypeProvisionPayload { +export interface DeleteTablePayload { clientMutationId?: string | null; - /** The `EntityTypeProvision` that was deleted by this mutation. */ - entityTypeProvision?: EntityTypeProvision | null; - entityTypeProvisionEdge?: EntityTypeProvisionEdge | null; + /** The `Table` that was deleted by this mutation. */ + table?: Table | null; + tableEdge?: TableEdge | null; } export interface DeleteStorageModulePayload { clientMutationId?: string | null; @@ -19039,11 +19112,11 @@ export interface DeleteStorageModulePayload { storageModule?: StorageModule | null; storageModuleEdge?: StorageModuleEdge | null; } -export interface DeleteTablePayload { +export interface DeleteEntityTypeProvisionPayload { clientMutationId?: string | null; - /** The `Table` that was deleted by this mutation. */ - table?: Table | null; - tableEdge?: TableEdge | null; + /** The `EntityTypeProvision` that was deleted by this mutation. */ + entityTypeProvision?: EntityTypeProvision | null; + entityTypeProvisionEdge?: EntityTypeProvisionEdge | null; } export interface DeleteRelationProvisionPayload { clientMutationId?: string | null; @@ -19086,6 +19159,8 @@ export interface RequestUploadUrlPayload { deduplicated: boolean; /** Presigned URL expiry time (null if deduplicated) */ expiresAt?: string | null; + /** File status — 'pending' for fresh uploads, 'ready' or 'processed' for deduplicated files. Clients can use this to know immediately whether the file is usable. */ + status: string; } export interface ConfirmUploadPayload { /** The confirmed file ID */ @@ -19792,11 +19867,11 @@ export interface ForeignKeyConstraintEdge { /** The `ForeignKeyConstraint` at the end of the edge. */ node?: ForeignKeyConstraint | null; } -/** A `EntityTypeProvision` edge in the connection. */ -export interface EntityTypeProvisionEdge { +/** A `Table` edge in the connection. */ +export interface TableEdge { cursor?: string | null; - /** The `EntityTypeProvision` at the end of the edge. */ - node?: EntityTypeProvision | null; + /** The `Table` at the end of the edge. */ + node?: Table | null; } /** A `StorageModule` edge in the connection. */ export interface StorageModuleEdge { @@ -19804,11 +19879,11 @@ export interface StorageModuleEdge { /** The `StorageModule` at the end of the edge. */ node?: StorageModule | null; } -/** A `Table` edge in the connection. */ -export interface TableEdge { +/** A `EntityTypeProvision` edge in the connection. */ +export interface EntityTypeProvisionEdge { cursor?: string | null; - /** The `Table` at the end of the edge. */ - node?: Table | null; + /** The `EntityTypeProvision` at the end of the edge. */ + node?: EntityTypeProvision | null; } /** A `RelationProvision` edge in the connection. */ export interface RelationProvisionEdge { diff --git a/sdk/constructive-react/src/public/types.ts b/sdk/constructive-react/src/public/types.ts index f687bd63c..1dc3c17b8 100644 --- a/sdk/constructive-react/src/public/types.ts +++ b/sdk/constructive-react/src/public/types.ts @@ -878,7 +878,8 @@ export interface StorageModule { filesTableName: string | null; uploadRequestsTableName: string | null; membershipType: number | null; - policies: string[] | null; + policies: unknown | null; + skipDefaultPolicyTables: string[] | null; entityTableId: string | null; endpoint: string | null; publicUrlPrefix: string | null; @@ -903,6 +904,7 @@ export interface EntityTypeProvision { hasProfiles: boolean | null; hasLevels: boolean | null; hasStorage: boolean | null; + hasInvites: boolean | null; storageConfig: unknown | null; skipEntityPolicies: boolean | null; tableProvision: unknown | null; @@ -913,6 +915,7 @@ export interface EntityTypeProvision { outStorageModuleId: string | null; outBucketsTableId: string | null; outFilesTableId: string | null; + outInvitesModuleId: string | null; } export interface WebauthnCredentialsModule { id: string | null; diff --git a/sdk/constructive-sdk/schemas/admin.graphql b/sdk/constructive-sdk/schemas/admin.graphql index 53bddfd60..1c41f4834 100644 --- a/sdk/constructive-sdk/schemas/admin.graphql +++ b/sdk/constructive-sdk/schemas/admin.graphql @@ -9222,6 +9222,11 @@ type RequestUploadUrlPayload { """Presigned URL expiry time (null if deduplicated)""" expiresAt: Datetime + + """ + File status — 'pending' for fresh uploads, 'ready' or 'processed' for deduplicated files. Clients can use this to know immediately whether the file is usable. + """ + status: String! } input ConfirmUploadInput { diff --git a/sdk/constructive-sdk/schemas/app.graphql b/sdk/constructive-sdk/schemas/app.graphql index aa0eb7052..9f1e36a1d 100644 --- a/sdk/constructive-sdk/schemas/app.graphql +++ b/sdk/constructive-sdk/schemas/app.graphql @@ -193,6 +193,11 @@ type RequestUploadUrlPayload { """Presigned URL expiry time (null if deduplicated)""" expiresAt: Datetime + + """ + File status — 'pending' for fresh uploads, 'ready' or 'processed' for deduplicated files. Clients can use this to know immediately whether the file is usable. + """ + status: String! } """ diff --git a/sdk/constructive-sdk/schemas/auth.graphql b/sdk/constructive-sdk/schemas/auth.graphql index 840b89316..f1a5922e9 100644 --- a/sdk/constructive-sdk/schemas/auth.graphql +++ b/sdk/constructive-sdk/schemas/auth.graphql @@ -4378,6 +4378,11 @@ type RequestUploadUrlPayload { """Presigned URL expiry time (null if deduplicated)""" expiresAt: Datetime + + """ + File status — 'pending' for fresh uploads, 'ready' or 'processed' for deduplicated files. Clients can use this to know immediately whether the file is usable. + """ + status: String! } input ConfirmUploadInput { diff --git a/sdk/constructive-sdk/schemas/objects.graphql b/sdk/constructive-sdk/schemas/objects.graphql index 817de1a7f..c5b7b8027 100644 --- a/sdk/constructive-sdk/schemas/objects.graphql +++ b/sdk/constructive-sdk/schemas/objects.graphql @@ -1887,6 +1887,11 @@ type RequestUploadUrlPayload { """Presigned URL expiry time (null if deduplicated)""" expiresAt: Datetime + + """ + File status — 'pending' for fresh uploads, 'ready' or 'processed' for deduplicated files. Clients can use this to know immediately whether the file is usable. + """ + status: String! } input ConfirmUploadInput { diff --git a/sdk/constructive-sdk/schemas/public.graphql b/sdk/constructive-sdk/schemas/public.graphql index d7c13332e..c5753ff28 100644 --- a/sdk/constructive-sdk/schemas/public.graphql +++ b/sdk/constructive-sdk/schemas/public.graphql @@ -14414,7 +14414,10 @@ input StorageModuleFilter { membershipType: IntFilter """Filter by the object’s `policies` field.""" - policies: StringListFilter + policies: JSONFilter + + """Filter by the object’s `skipDefaultPolicyTables` field.""" + skipDefaultPolicyTables: StringListFilter """Filter by the object’s `entityTableId` field.""" entityTableId: UUIDFilter @@ -14534,6 +14537,9 @@ input EntityTypeProvisionFilter { """Filter by the object’s `hasStorage` field.""" hasStorage: BooleanFilter + """Filter by the object’s `hasInvites` field.""" + hasInvites: BooleanFilter + """Filter by the object’s `storageConfig` field.""" storageConfig: JSONFilter @@ -14564,6 +14570,9 @@ input EntityTypeProvisionFilter { """Filter by the object’s `outFilesTableId` field.""" outFilesTableId: UUIDFilter + """Filter by the object’s `outInvitesModuleId` field.""" + outInvitesModuleId: UUIDFilter + """Checks for all expressions in this list.""" and: [EntityTypeProvisionFilter!] @@ -20892,7 +20901,8 @@ type StorageModule { filesTableName: String! uploadRequestsTableName: String! membershipType: Int - policies: [String] + policies: JSON + skipDefaultPolicyTables: [String]! entityTableId: UUID endpoint: String publicUrlPrefix: String @@ -20964,6 +20974,8 @@ enum StorageModuleOrderBy { MEMBERSHIP_TYPE_DESC POLICIES_ASC POLICIES_DESC + SKIP_DEFAULT_POLICY_TABLES_ASC + SKIP_DEFAULT_POLICY_TABLES_DESC ENTITY_TABLE_ID_ASC ENTITY_TABLE_ID_DESC ENDPOINT_ASC @@ -21023,8 +21035,8 @@ type EntityTypeProvision { databaseId: UUID! """ - Human-readable name for this membership type, e.g. 'Data Room Member', 'Team Channel Member'. Required. - Stored in the membership_types registry table. + Human-readable name for this entity type, e.g. 'Data Room', 'Team Channel'. Required. + Stored in the entity_types registry table. """ name: String! @@ -21037,7 +21049,7 @@ type EntityTypeProvision { prefix: String! """ - Description of this membership type. Stored in the membership_types registry table. Defaults to empty string. + Description of this entity type. Stored in the entity_types registry table. Defaults to empty string. """ description: String! @@ -21097,6 +21109,17 @@ type EntityTypeProvision { """ hasStorage: Boolean! + """ + Whether to provision invites_module for this type. Defaults to false. + When true, the trigger inserts a row into invites_module which in turn + (via insert_invites_module BEFORE INSERT) creates {prefix}_invites and + {prefix}_claimed_invites tables plus the submit_{prefix}_invite_code() function. + Symmetric counterpart of has_storage. Re-provisioning is idempotent: the + UNIQUE (database_id, membership_type) constraint on invites_module combined with + ON CONFLICT DO NOTHING in the fan-out makes repeated INSERTs safe. + """ + hasInvites: Boolean! + """ Optional jsonb object for storage module configuration and initial bucket seeding. Only used when has_storage = true; ignored otherwise. NULL = use defaults. @@ -21113,8 +21136,21 @@ type EntityTypeProvision { - allowed_mime_types (text[]) whitelist of MIME types (null = any) - max_file_size (bigint) max file size in bytes (null = use scope default) - allowed_origins (text[]) per-bucket CORS override + - provisions (jsonb object) optional: customize storage tables + with additional nodes, fields, grants, and policies. + Keyed by table role: "files", "buckets", "upload_requests". + Each value uses the same shape as table_provision: + { nodes, fields, grants, use_rls, policies }. Fanned out + to secure_table_provision targeting the corresponding table. + When a key includes policies[], those REPLACE the default + storage policies for that table; tables without a key still + get defaults. Missing "data" on policy entries is auto-populated + with storage-specific defaults (same as table_provision). + Example: add SearchBm25 for full-text search on files: + {"provisions": {"files": {"nodes": [{"$type": + "SearchBm25", "data": {"source_fields": ["description"]}}]}}} Example: - storage_config := '{"buckets": [{"name": "documents", "is_public": false, "allowed_mime_types": ["application/pdf"]}]}'::jsonb + storage_config := '{"buckets": [{"name": "documents", "is_public": false, "allowed_mime_types": ["application/pdf"]}], "provisions": {"files": {"nodes": [{"$type": "SearchBm25", "data": {"source_fields": ["description"]}}]}}}'::jsonb """ storageConfig: JSON @@ -21168,7 +21204,7 @@ type EntityTypeProvision { """ Output: the auto-assigned integer membership type ID. Populated by the trigger after successful provisioning. - This is the ID used in membership_types, memberships_module, and all module tables. + This is the ID used in entity_types, memberships_module, and all module tables. """ outMembershipType: Int @@ -21204,6 +21240,13 @@ type EntityTypeProvision { """ outFilesTableId: UUID + """ + Output: the UUID of the invites_module row created for this entity type. Populated by the trigger when has_invites=true. + NULL when has_invites=false, or when re-provisioning hits ON CONFLICT DO NOTHING + (i.e. the invites_module row was created in a previous run). + """ + outInvitesModuleId: UUID + """ Reads a single `Database` that is related to this `EntityTypeProvision`. """ @@ -21248,6 +21291,8 @@ enum EntityTypeProvisionOrderBy { HAS_LEVELS_DESC HAS_STORAGE_ASC HAS_STORAGE_DESC + HAS_INVITES_ASC + HAS_INVITES_DESC STORAGE_CONFIG_ASC STORAGE_CONFIG_DESC SKIP_ENTITY_POLICIES_ASC @@ -21268,6 +21313,8 @@ enum EntityTypeProvisionOrderBy { OUT_BUCKETS_TABLE_ID_DESC OUT_FILES_TABLE_ID_ASC OUT_FILES_TABLE_ID_DESC + OUT_INVITES_MODULE_ID_ASC + OUT_INVITES_MODULE_ID_DESC } type RateLimitsModule { @@ -27429,6 +27476,7 @@ input ProvisionTableInput { indexes: JSON fullTextSearches: JSON uniqueConstraints: JSON + description: String } """The output of our `sendVerificationEmail` mutation.""" @@ -33454,293 +33502,6 @@ input ForeignKeyConstraintInput { updatedAt: Datetime } -"""The output of our create `EntityTypeProvision` mutation.""" -type CreateEntityTypeProvisionPayload { - """ - The exact same `clientMutationId` that was provided in the mutation input, - unchanged and unused. May be used by a client to track mutations. - """ - clientMutationId: String - - """The `EntityTypeProvision` that was created by this mutation.""" - entityTypeProvision: EntityTypeProvision - - """ - Our root query field type. Allows us to run any query from our mutation payload. - """ - query: Query - - """An edge for our `EntityTypeProvision`. May be used by Relay 1.""" - entityTypeProvisionEdge( - """The method to use when ordering `EntityTypeProvision`.""" - orderBy: [EntityTypeProvisionOrderBy!]! = [PRIMARY_KEY_ASC] - ): EntityTypeProvisionEdge -} - -"""All input for the create `EntityTypeProvision` mutation.""" -input CreateEntityTypeProvisionInput { - """ - An arbitrary string value with no semantic meaning. Will be included in the - payload verbatim. May be used to track mutations by the client. - """ - clientMutationId: String - - """The `EntityTypeProvision` to be created by this mutation.""" - entityTypeProvision: EntityTypeProvisionInput! -} - -"""An input for mutations affecting `EntityTypeProvision`""" -input EntityTypeProvisionInput { - """Unique identifier for this provision row.""" - id: UUID - - """The database to provision this entity type in. Required.""" - databaseId: UUID! - - """ - Human-readable name for this membership type, e.g. 'Data Room Member', 'Team Channel Member'. Required. - Stored in the membership_types registry table. - """ - name: String! - - """ - SQL prefix used for table and module naming, e.g. 'data_room', 'team_channel'. Required. - Drives entity table name (prefix || 's' by default), module labels (permissions_module:prefix), - and membership table names (prefix_memberships, prefix_members, etc.). - Must be unique per database — the (database_id, prefix) constraint ensures graceful ON CONFLICT DO NOTHING. - """ - prefix: String! - - """ - Description of this membership type. Stored in the membership_types registry table. Defaults to empty string. - """ - description: String - - """ - Prefix of the parent entity type. The trigger resolves this to a membership_type integer - by looking up memberships_module WHERE prefix = parent_entity. - Defaults to 'org' (the organization-level type). For nested types, set to the parent's prefix - (e.g. 'data_room' for a team_channel nested under data_room). - The parent type must already be provisioned before this INSERT. - """ - parentEntity: String - - """ - Override the entity table name. When NULL (default), the table name is derived as prefix || 's' - (e.g. prefix 'data_room' produces table 'data_rooms'). - Set this when the pluralization rule doesn't apply (e.g. prefix 'staff' should produce 'staff' not 'staffs'). - """ - tableName: String - - """ - Whether members of the parent entity can see child entities. Defaults to true. - When true: a SELECT policy allows parent members to list child entities (e.g. org members can see all data rooms). - When false: only direct members of the entity itself can see it (private entity mode). - Controls whether the parent_member SELECT policy is created on the entity table. - Only meaningful on the defaults path — ignored (no-op) when table_provision is non-NULL or - skip_entity_policies=true, since no default policies are being applied in those cases. - """ - isVisible: Boolean - - """ - Whether to apply limits_module security for this type. Defaults to false. - The limits_module table structure is always created (memberships_module requires it), - but when false, no RLS policies are applied to the limits tables. - Set to true if this entity type needs configurable resource limits per membership. - """ - hasLimits: Boolean - - """ - Whether to provision profiles_module for this type. Defaults to false. - Profiles provide named permission roles (e.g. 'Editor', 'Viewer') with pre-configured permission bitmasks. - When true, creates profile tables and applies profiles security. - """ - hasProfiles: Boolean - - """ - Whether to provision levels_module for this type. Defaults to false. - Levels provide gamification/achievement tracking for members. - When true, creates level steps, achievements, and level tables with security. - """ - hasLevels: Boolean - - """ - Whether to provision storage_module for this type. Defaults to false. - When true, creates {prefix}_buckets, {prefix}_files, and {prefix}_upload_requests tables - with entity-scoped RLS (AuthzEntityMembership) using the entity's membership_type. - Storage tables get owner_id FK to the entity table, so files are owned by the entity. - """ - hasStorage: Boolean - - """ - Optional jsonb object for storage module configuration and initial bucket seeding. - Only used when has_storage = true; ignored otherwise. NULL = use defaults. - Recognized keys (all optional): - - upload_url_expiry_seconds (integer) presigned PUT URL expiry override - - download_url_expiry_seconds (integer) presigned GET URL expiry override - - default_max_file_size (bigint) global max file size in bytes for this scope - - allowed_origins (text[]) default CORS origins for all buckets in this scope - - buckets (jsonb[]) array of initial bucket definitions to seed - Each bucket in the buckets array recognizes: - - name (text, required) bucket name e.g. 'documents' - - description (text) human-readable description - - is_public (boolean) whether files are publicly readable (default false) - - allowed_mime_types (text[]) whitelist of MIME types (null = any) - - max_file_size (bigint) max file size in bytes (null = use scope default) - - allowed_origins (text[]) per-bucket CORS override - Example: - storage_config := '{"buckets": [{"name": "documents", "is_public": false, "allowed_mime_types": ["application/pdf"]}]}'::jsonb - """ - storageConfig: JSON - - """ - Escape hatch: when true, apply zero RLS policies to the entity table. Defaults to false. - Use this only when you want the entity table provisioned with zero policies (e.g. because you - plan to insert secure_table_provision rows yourself later). In most cases, prefer leaving this - false and either accepting the five defaults (table_provision=NULL) or overriding them via - table_provision. - Defaults (applied when table_provision IS NULL and skip_entity_policies=false): - - SELECT (parent_member): parent entity members can see child entities (only when is_visible=true) - - SELECT (self_member): direct members of the entity can see it - - INSERT: create_entity permission on the parent entity - - UPDATE: admin_entity permission on the entity itself - - DELETE: owner of the entity can delete it - """ - skipEntityPolicies: Boolean - - """ - Single jsonb object describing the full security setup to apply to the entity table. - Uses the same vocabulary as metaschema_modules_public.provision_table() and blueprint tables[] - entries, so an entity table is configured the same way an ordinary blueprint table is. - Defaults to NULL; when non-NULL, the five default policies are implicitly replaced by - table_provision.policies[] (is_visible becomes a no-op on this path). - Recognized keys (all optional): - - use_rls (boolean, default true) - - nodes (jsonb array of {"$type","data"} Data* module entries) - - fields (jsonb array of field objects: name,type,is_required,default,min,max,regexp,index) - - grants (jsonb array of grant objects; each with roles[] and privileges[]) - - policies (jsonb array of policy objects; each with $type, privileges, data, name, role, permissive) - The trigger forwards all setup (nodes/fields/grants/policies) as a single secure_table_provision row - against the newly created entity table. - Example — override with two SELECT policies: - table_provision := jsonb_build_object( - 'policies', jsonb_build_array( - jsonb_build_object( - '$type', 'AuthzEntityMembership', - 'privileges', jsonb_build_array('select'), - 'data', jsonb_build_object('entity_field', 'id', 'membership_type', 3), - 'name', 'self_member' - ), - jsonb_build_object( - '$type', 'AuthzDirectOwner', - 'privileges', jsonb_build_array('select', 'update'), - 'data', jsonb_build_object('owner_field', 'owner_id') - ) - ) - ) - """ - tableProvision: JSON - - """ - Output: the auto-assigned integer membership type ID. Populated by the trigger after successful provisioning. - This is the ID used in membership_types, memberships_module, and all module tables. - """ - outMembershipType: Int - - """ - Output: the UUID of the created entity table. Populated by the trigger. - Use this to reference the entity table in subsequent relation_provision or secure_table_provision rows. - """ - outEntityTableId: UUID - - """ - Output: the name of the created entity table (e.g. 'data_rooms'). Populated by the trigger. - """ - outEntityTableName: String - - """ - Output: array of installed module labels (e.g. ARRAY['permissions_module:data_room', 'memberships_module:data_room', 'invites_module:data_room']). - Populated by the trigger. Useful for verifying which modules were provisioned. - """ - outInstalledModules: [String] - - """ - Output: the UUID of the storage_module row created for this entity type. Populated by the trigger when has_storage=true. - """ - outStorageModuleId: UUID - - """ - Output: the UUID of the generated buckets table (e.g. data_room_buckets). Populated by the trigger when has_storage=true. - """ - outBucketsTableId: UUID - - """ - Output: the UUID of the generated files table (e.g. data_room_files). Populated by the trigger when has_storage=true. - """ - outFilesTableId: UUID -} - -"""The output of our create `StorageModule` mutation.""" -type CreateStorageModulePayload { - """ - The exact same `clientMutationId` that was provided in the mutation input, - unchanged and unused. May be used by a client to track mutations. - """ - clientMutationId: String - - """The `StorageModule` that was created by this mutation.""" - storageModule: StorageModule - - """ - Our root query field type. Allows us to run any query from our mutation payload. - """ - query: Query - - """An edge for our `StorageModule`. May be used by Relay 1.""" - storageModuleEdge( - """The method to use when ordering `StorageModule`.""" - orderBy: [StorageModuleOrderBy!]! = [PRIMARY_KEY_ASC] - ): StorageModuleEdge -} - -"""All input for the create `StorageModule` mutation.""" -input CreateStorageModuleInput { - """ - An arbitrary string value with no semantic meaning. Will be included in the - payload verbatim. May be used to track mutations by the client. - """ - clientMutationId: String - - """The `StorageModule` to be created by this mutation.""" - storageModule: StorageModuleInput! -} - -"""An input for mutations affecting `StorageModule`""" -input StorageModuleInput { - id: UUID - databaseId: UUID! - schemaId: UUID - privateSchemaId: UUID - bucketsTableId: UUID - filesTableId: UUID - uploadRequestsTableId: UUID - bucketsTableName: String - filesTableName: String - uploadRequestsTableName: String - membershipType: Int - policies: [String] - entityTableId: UUID - endpoint: String - publicUrlPrefix: String - provider: String - allowedOrigins: [String] - uploadUrlExpirySeconds: Int - downloadUrlExpirySeconds: Int - defaultMaxFileSize: BigInt - maxFilenameLength: Int - cacheTtlSeconds: Int -} - """The output of our create `Table` mutation.""" type CreateTablePayload { """ @@ -33799,6 +33560,325 @@ input TableInput { updatedAt: Datetime } +"""The output of our create `StorageModule` mutation.""" +type CreateStorageModulePayload { + """ + The exact same `clientMutationId` that was provided in the mutation input, + unchanged and unused. May be used by a client to track mutations. + """ + clientMutationId: String + + """The `StorageModule` that was created by this mutation.""" + storageModule: StorageModule + + """ + Our root query field type. Allows us to run any query from our mutation payload. + """ + query: Query + + """An edge for our `StorageModule`. May be used by Relay 1.""" + storageModuleEdge( + """The method to use when ordering `StorageModule`.""" + orderBy: [StorageModuleOrderBy!]! = [PRIMARY_KEY_ASC] + ): StorageModuleEdge +} + +"""All input for the create `StorageModule` mutation.""" +input CreateStorageModuleInput { + """ + An arbitrary string value with no semantic meaning. Will be included in the + payload verbatim. May be used to track mutations by the client. + """ + clientMutationId: String + + """The `StorageModule` to be created by this mutation.""" + storageModule: StorageModuleInput! +} + +"""An input for mutations affecting `StorageModule`""" +input StorageModuleInput { + id: UUID + databaseId: UUID! + schemaId: UUID + privateSchemaId: UUID + bucketsTableId: UUID + filesTableId: UUID + uploadRequestsTableId: UUID + bucketsTableName: String + filesTableName: String + uploadRequestsTableName: String + membershipType: Int + policies: JSON + skipDefaultPolicyTables: [String] + entityTableId: UUID + endpoint: String + publicUrlPrefix: String + provider: String + allowedOrigins: [String] + uploadUrlExpirySeconds: Int + downloadUrlExpirySeconds: Int + defaultMaxFileSize: BigInt + maxFilenameLength: Int + cacheTtlSeconds: Int +} + +"""The output of our create `EntityTypeProvision` mutation.""" +type CreateEntityTypeProvisionPayload { + """ + The exact same `clientMutationId` that was provided in the mutation input, + unchanged and unused. May be used by a client to track mutations. + """ + clientMutationId: String + + """The `EntityTypeProvision` that was created by this mutation.""" + entityTypeProvision: EntityTypeProvision + + """ + Our root query field type. Allows us to run any query from our mutation payload. + """ + query: Query + + """An edge for our `EntityTypeProvision`. May be used by Relay 1.""" + entityTypeProvisionEdge( + """The method to use when ordering `EntityTypeProvision`.""" + orderBy: [EntityTypeProvisionOrderBy!]! = [PRIMARY_KEY_ASC] + ): EntityTypeProvisionEdge +} + +"""All input for the create `EntityTypeProvision` mutation.""" +input CreateEntityTypeProvisionInput { + """ + An arbitrary string value with no semantic meaning. Will be included in the + payload verbatim. May be used to track mutations by the client. + """ + clientMutationId: String + + """The `EntityTypeProvision` to be created by this mutation.""" + entityTypeProvision: EntityTypeProvisionInput! +} + +"""An input for mutations affecting `EntityTypeProvision`""" +input EntityTypeProvisionInput { + """Unique identifier for this provision row.""" + id: UUID + + """The database to provision this entity type in. Required.""" + databaseId: UUID! + + """ + Human-readable name for this entity type, e.g. 'Data Room', 'Team Channel'. Required. + Stored in the entity_types registry table. + """ + name: String! + + """ + SQL prefix used for table and module naming, e.g. 'data_room', 'team_channel'. Required. + Drives entity table name (prefix || 's' by default), module labels (permissions_module:prefix), + and membership table names (prefix_memberships, prefix_members, etc.). + Must be unique per database — the (database_id, prefix) constraint ensures graceful ON CONFLICT DO NOTHING. + """ + prefix: String! + + """ + Description of this entity type. Stored in the entity_types registry table. Defaults to empty string. + """ + description: String + + """ + Prefix of the parent entity type. The trigger resolves this to a membership_type integer + by looking up memberships_module WHERE prefix = parent_entity. + Defaults to 'org' (the organization-level type). For nested types, set to the parent's prefix + (e.g. 'data_room' for a team_channel nested under data_room). + The parent type must already be provisioned before this INSERT. + """ + parentEntity: String + + """ + Override the entity table name. When NULL (default), the table name is derived as prefix || 's' + (e.g. prefix 'data_room' produces table 'data_rooms'). + Set this when the pluralization rule doesn't apply (e.g. prefix 'staff' should produce 'staff' not 'staffs'). + """ + tableName: String + + """ + Whether members of the parent entity can see child entities. Defaults to true. + When true: a SELECT policy allows parent members to list child entities (e.g. org members can see all data rooms). + When false: only direct members of the entity itself can see it (private entity mode). + Controls whether the parent_member SELECT policy is created on the entity table. + Only meaningful on the defaults path — ignored (no-op) when table_provision is non-NULL or + skip_entity_policies=true, since no default policies are being applied in those cases. + """ + isVisible: Boolean + + """ + Whether to apply limits_module security for this type. Defaults to false. + The limits_module table structure is always created (memberships_module requires it), + but when false, no RLS policies are applied to the limits tables. + Set to true if this entity type needs configurable resource limits per membership. + """ + hasLimits: Boolean + + """ + Whether to provision profiles_module for this type. Defaults to false. + Profiles provide named permission roles (e.g. 'Editor', 'Viewer') with pre-configured permission bitmasks. + When true, creates profile tables and applies profiles security. + """ + hasProfiles: Boolean + + """ + Whether to provision levels_module for this type. Defaults to false. + Levels provide gamification/achievement tracking for members. + When true, creates level steps, achievements, and level tables with security. + """ + hasLevels: Boolean + + """ + Whether to provision storage_module for this type. Defaults to false. + When true, creates {prefix}_buckets, {prefix}_files, and {prefix}_upload_requests tables + with entity-scoped RLS (AuthzEntityMembership) using the entity's membership_type. + Storage tables get owner_id FK to the entity table, so files are owned by the entity. + """ + hasStorage: Boolean + + """ + Whether to provision invites_module for this type. Defaults to false. + When true, the trigger inserts a row into invites_module which in turn + (via insert_invites_module BEFORE INSERT) creates {prefix}_invites and + {prefix}_claimed_invites tables plus the submit_{prefix}_invite_code() function. + Symmetric counterpart of has_storage. Re-provisioning is idempotent: the + UNIQUE (database_id, membership_type) constraint on invites_module combined with + ON CONFLICT DO NOTHING in the fan-out makes repeated INSERTs safe. + """ + hasInvites: Boolean + + """ + Optional jsonb object for storage module configuration and initial bucket seeding. + Only used when has_storage = true; ignored otherwise. NULL = use defaults. + Recognized keys (all optional): + - upload_url_expiry_seconds (integer) presigned PUT URL expiry override + - download_url_expiry_seconds (integer) presigned GET URL expiry override + - default_max_file_size (bigint) global max file size in bytes for this scope + - allowed_origins (text[]) default CORS origins for all buckets in this scope + - buckets (jsonb[]) array of initial bucket definitions to seed + Each bucket in the buckets array recognizes: + - name (text, required) bucket name e.g. 'documents' + - description (text) human-readable description + - is_public (boolean) whether files are publicly readable (default false) + - allowed_mime_types (text[]) whitelist of MIME types (null = any) + - max_file_size (bigint) max file size in bytes (null = use scope default) + - allowed_origins (text[]) per-bucket CORS override + - provisions (jsonb object) optional: customize storage tables + with additional nodes, fields, grants, and policies. + Keyed by table role: "files", "buckets", "upload_requests". + Each value uses the same shape as table_provision: + { nodes, fields, grants, use_rls, policies }. Fanned out + to secure_table_provision targeting the corresponding table. + When a key includes policies[], those REPLACE the default + storage policies for that table; tables without a key still + get defaults. Missing "data" on policy entries is auto-populated + with storage-specific defaults (same as table_provision). + Example: add SearchBm25 for full-text search on files: + {"provisions": {"files": {"nodes": [{"$type": + "SearchBm25", "data": {"source_fields": ["description"]}}]}}} + Example: + storage_config := '{"buckets": [{"name": "documents", "is_public": false, "allowed_mime_types": ["application/pdf"]}], "provisions": {"files": {"nodes": [{"$type": "SearchBm25", "data": {"source_fields": ["description"]}}]}}}'::jsonb + """ + storageConfig: JSON + + """ + Escape hatch: when true, apply zero RLS policies to the entity table. Defaults to false. + Use this only when you want the entity table provisioned with zero policies (e.g. because you + plan to insert secure_table_provision rows yourself later). In most cases, prefer leaving this + false and either accepting the five defaults (table_provision=NULL) or overriding them via + table_provision. + Defaults (applied when table_provision IS NULL and skip_entity_policies=false): + - SELECT (parent_member): parent entity members can see child entities (only when is_visible=true) + - SELECT (self_member): direct members of the entity can see it + - INSERT: create_entity permission on the parent entity + - UPDATE: admin_entity permission on the entity itself + - DELETE: owner of the entity can delete it + """ + skipEntityPolicies: Boolean + + """ + Single jsonb object describing the full security setup to apply to the entity table. + Uses the same vocabulary as metaschema_modules_public.provision_table() and blueprint tables[] + entries, so an entity table is configured the same way an ordinary blueprint table is. + Defaults to NULL; when non-NULL, the five default policies are implicitly replaced by + table_provision.policies[] (is_visible becomes a no-op on this path). + Recognized keys (all optional): + - use_rls (boolean, default true) + - nodes (jsonb array of {"$type","data"} Data* module entries) + - fields (jsonb array of field objects: name,type,is_required,default,min,max,regexp,index) + - grants (jsonb array of grant objects; each with roles[] and privileges[]) + - policies (jsonb array of policy objects; each with $type, privileges, data, name, role, permissive) + The trigger forwards all setup (nodes/fields/grants/policies) as a single secure_table_provision row + against the newly created entity table. + Example — override with two SELECT policies: + table_provision := jsonb_build_object( + 'policies', jsonb_build_array( + jsonb_build_object( + '$type', 'AuthzEntityMembership', + 'privileges', jsonb_build_array('select'), + 'data', jsonb_build_object('entity_field', 'id', 'membership_type', 3), + 'name', 'self_member' + ), + jsonb_build_object( + '$type', 'AuthzDirectOwner', + 'privileges', jsonb_build_array('select', 'update'), + 'data', jsonb_build_object('owner_field', 'owner_id') + ) + ) + ) + """ + tableProvision: JSON + + """ + Output: the auto-assigned integer membership type ID. Populated by the trigger after successful provisioning. + This is the ID used in entity_types, memberships_module, and all module tables. + """ + outMembershipType: Int + + """ + Output: the UUID of the created entity table. Populated by the trigger. + Use this to reference the entity table in subsequent relation_provision or secure_table_provision rows. + """ + outEntityTableId: UUID + + """ + Output: the name of the created entity table (e.g. 'data_rooms'). Populated by the trigger. + """ + outEntityTableName: String + + """ + Output: array of installed module labels (e.g. ARRAY['permissions_module:data_room', 'memberships_module:data_room', 'invites_module:data_room']). + Populated by the trigger. Useful for verifying which modules were provisioned. + """ + outInstalledModules: [String] + + """ + Output: the UUID of the storage_module row created for this entity type. Populated by the trigger when has_storage=true. + """ + outStorageModuleId: UUID + + """ + Output: the UUID of the generated buckets table (e.g. data_room_buckets). Populated by the trigger when has_storage=true. + """ + outBucketsTableId: UUID + + """ + Output: the UUID of the generated files table (e.g. data_room_files). Populated by the trigger when has_storage=true. + """ + outFilesTableId: UUID + + """ + Output: the UUID of the invites_module row created for this entity type. Populated by the trigger when has_invites=true. + NULL when has_invites=false, or when re-provisioning hits ON CONFLICT DO NOTHING + (i.e. the invites_module row was created in a previous run). + """ + outInvitesModuleId: UUID +} + """The output of our create `RelationProvision` mutation.""" type CreateRelationProvisionPayload { """ @@ -40614,6 +40694,136 @@ input ForeignKeyConstraintPatch { updatedAt: Datetime } +"""The output of our update `Table` mutation.""" +type UpdateTablePayload { + """ + The exact same `clientMutationId` that was provided in the mutation input, + unchanged and unused. May be used by a client to track mutations. + """ + clientMutationId: String + + """The `Table` that was updated by this mutation.""" + table: Table + + """ + Our root query field type. Allows us to run any query from our mutation payload. + """ + query: Query + + """An edge for our `Table`. May be used by Relay 1.""" + tableEdge( + """The method to use when ordering `Table`.""" + orderBy: [TableOrderBy!]! = [PRIMARY_KEY_ASC] + ): TableEdge +} + +"""All input for the `updateTable` mutation.""" +input UpdateTableInput { + """ + An arbitrary string value with no semantic meaning. Will be included in the + payload verbatim. May be used to track mutations by the client. + """ + clientMutationId: String + id: UUID! + + """ + An object where the defined keys will be set on the `Table` being updated. + """ + tablePatch: TablePatch! +} + +""" +Represents an update to a `Table`. Fields that are set will be updated. +""" +input TablePatch { + id: UUID + databaseId: UUID + schemaId: UUID + name: String + label: String + description: String + smartTags: JSON + category: ObjectCategory + module: String + scope: Int + useRls: Boolean + timestamps: Boolean + peoplestamps: Boolean + pluralName: String + singularName: String + tags: [String] + inheritsId: UUID + createdAt: Datetime + updatedAt: Datetime +} + +"""The output of our update `StorageModule` mutation.""" +type UpdateStorageModulePayload { + """ + The exact same `clientMutationId` that was provided in the mutation input, + unchanged and unused. May be used by a client to track mutations. + """ + clientMutationId: String + + """The `StorageModule` that was updated by this mutation.""" + storageModule: StorageModule + + """ + Our root query field type. Allows us to run any query from our mutation payload. + """ + query: Query + + """An edge for our `StorageModule`. May be used by Relay 1.""" + storageModuleEdge( + """The method to use when ordering `StorageModule`.""" + orderBy: [StorageModuleOrderBy!]! = [PRIMARY_KEY_ASC] + ): StorageModuleEdge +} + +"""All input for the `updateStorageModule` mutation.""" +input UpdateStorageModuleInput { + """ + An arbitrary string value with no semantic meaning. Will be included in the + payload verbatim. May be used to track mutations by the client. + """ + clientMutationId: String + id: UUID! + + """ + An object where the defined keys will be set on the `StorageModule` being updated. + """ + storageModulePatch: StorageModulePatch! +} + +""" +Represents an update to a `StorageModule`. Fields that are set will be updated. +""" +input StorageModulePatch { + id: UUID + databaseId: UUID + schemaId: UUID + privateSchemaId: UUID + bucketsTableId: UUID + filesTableId: UUID + uploadRequestsTableId: UUID + bucketsTableName: String + filesTableName: String + uploadRequestsTableName: String + membershipType: Int + policies: JSON + skipDefaultPolicyTables: [String] + entityTableId: UUID + endpoint: String + publicUrlPrefix: String + provider: String + allowedOrigins: [String] + uploadUrlExpirySeconds: Int + downloadUrlExpirySeconds: Int + defaultMaxFileSize: BigInt + maxFilenameLength: Int + cacheTtlSeconds: Int +} + """The output of our update `EntityTypeProvision` mutation.""" type UpdateEntityTypeProvisionPayload { """ @@ -40665,8 +40875,8 @@ input EntityTypeProvisionPatch { databaseId: UUID """ - Human-readable name for this membership type, e.g. 'Data Room Member', 'Team Channel Member'. Required. - Stored in the membership_types registry table. + Human-readable name for this entity type, e.g. 'Data Room', 'Team Channel'. Required. + Stored in the entity_types registry table. """ name: String @@ -40679,7 +40889,7 @@ input EntityTypeProvisionPatch { prefix: String """ - Description of this membership type. Stored in the membership_types registry table. Defaults to empty string. + Description of this entity type. Stored in the entity_types registry table. Defaults to empty string. """ description: String @@ -40739,6 +40949,17 @@ input EntityTypeProvisionPatch { """ hasStorage: Boolean + """ + Whether to provision invites_module for this type. Defaults to false. + When true, the trigger inserts a row into invites_module which in turn + (via insert_invites_module BEFORE INSERT) creates {prefix}_invites and + {prefix}_claimed_invites tables plus the submit_{prefix}_invite_code() function. + Symmetric counterpart of has_storage. Re-provisioning is idempotent: the + UNIQUE (database_id, membership_type) constraint on invites_module combined with + ON CONFLICT DO NOTHING in the fan-out makes repeated INSERTs safe. + """ + hasInvites: Boolean + """ Optional jsonb object for storage module configuration and initial bucket seeding. Only used when has_storage = true; ignored otherwise. NULL = use defaults. @@ -40755,8 +40976,21 @@ input EntityTypeProvisionPatch { - allowed_mime_types (text[]) whitelist of MIME types (null = any) - max_file_size (bigint) max file size in bytes (null = use scope default) - allowed_origins (text[]) per-bucket CORS override + - provisions (jsonb object) optional: customize storage tables + with additional nodes, fields, grants, and policies. + Keyed by table role: "files", "buckets", "upload_requests". + Each value uses the same shape as table_provision: + { nodes, fields, grants, use_rls, policies }. Fanned out + to secure_table_provision targeting the corresponding table. + When a key includes policies[], those REPLACE the default + storage policies for that table; tables without a key still + get defaults. Missing "data" on policy entries is auto-populated + with storage-specific defaults (same as table_provision). + Example: add SearchBm25 for full-text search on files: + {"provisions": {"files": {"nodes": [{"$type": + "SearchBm25", "data": {"source_fields": ["description"]}}]}}} Example: - storage_config := '{"buckets": [{"name": "documents", "is_public": false, "allowed_mime_types": ["application/pdf"]}]}'::jsonb + storage_config := '{"buckets": [{"name": "documents", "is_public": false, "allowed_mime_types": ["application/pdf"]}], "provisions": {"files": {"nodes": [{"$type": "SearchBm25", "data": {"source_fields": ["description"]}}]}}}'::jsonb """ storageConfig: JSON @@ -40810,7 +41044,7 @@ input EntityTypeProvisionPatch { """ Output: the auto-assigned integer membership type ID. Populated by the trigger after successful provisioning. - This is the ID used in membership_types, memberships_module, and all module tables. + This is the ID used in entity_types, memberships_module, and all module tables. """ outMembershipType: Int @@ -40845,135 +41079,13 @@ input EntityTypeProvisionPatch { Output: the UUID of the generated files table (e.g. data_room_files). Populated by the trigger when has_storage=true. """ outFilesTableId: UUID -} -"""The output of our update `StorageModule` mutation.""" -type UpdateStorageModulePayload { - """ - The exact same `clientMutationId` that was provided in the mutation input, - unchanged and unused. May be used by a client to track mutations. """ - clientMutationId: String - - """The `StorageModule` that was updated by this mutation.""" - storageModule: StorageModule - + Output: the UUID of the invites_module row created for this entity type. Populated by the trigger when has_invites=true. + NULL when has_invites=false, or when re-provisioning hits ON CONFLICT DO NOTHING + (i.e. the invites_module row was created in a previous run). """ - Our root query field type. Allows us to run any query from our mutation payload. - """ - query: Query - - """An edge for our `StorageModule`. May be used by Relay 1.""" - storageModuleEdge( - """The method to use when ordering `StorageModule`.""" - orderBy: [StorageModuleOrderBy!]! = [PRIMARY_KEY_ASC] - ): StorageModuleEdge -} - -"""All input for the `updateStorageModule` mutation.""" -input UpdateStorageModuleInput { - """ - An arbitrary string value with no semantic meaning. Will be included in the - payload verbatim. May be used to track mutations by the client. - """ - clientMutationId: String - id: UUID! - - """ - An object where the defined keys will be set on the `StorageModule` being updated. - """ - storageModulePatch: StorageModulePatch! -} - -""" -Represents an update to a `StorageModule`. Fields that are set will be updated. -""" -input StorageModulePatch { - id: UUID - databaseId: UUID - schemaId: UUID - privateSchemaId: UUID - bucketsTableId: UUID - filesTableId: UUID - uploadRequestsTableId: UUID - bucketsTableName: String - filesTableName: String - uploadRequestsTableName: String - membershipType: Int - policies: [String] - entityTableId: UUID - endpoint: String - publicUrlPrefix: String - provider: String - allowedOrigins: [String] - uploadUrlExpirySeconds: Int - downloadUrlExpirySeconds: Int - defaultMaxFileSize: BigInt - maxFilenameLength: Int - cacheTtlSeconds: Int -} - -"""The output of our update `Table` mutation.""" -type UpdateTablePayload { - """ - The exact same `clientMutationId` that was provided in the mutation input, - unchanged and unused. May be used by a client to track mutations. - """ - clientMutationId: String - - """The `Table` that was updated by this mutation.""" - table: Table - - """ - Our root query field type. Allows us to run any query from our mutation payload. - """ - query: Query - - """An edge for our `Table`. May be used by Relay 1.""" - tableEdge( - """The method to use when ordering `Table`.""" - orderBy: [TableOrderBy!]! = [PRIMARY_KEY_ASC] - ): TableEdge -} - -"""All input for the `updateTable` mutation.""" -input UpdateTableInput { - """ - An arbitrary string value with no semantic meaning. Will be included in the - payload verbatim. May be used to track mutations by the client. - """ - clientMutationId: String - id: UUID! - - """ - An object where the defined keys will be set on the `Table` being updated. - """ - tablePatch: TablePatch! -} - -""" -Represents an update to a `Table`. Fields that are set will be updated. -""" -input TablePatch { - id: UUID - databaseId: UUID - schemaId: UUID - name: String - label: String - description: String - smartTags: JSON - category: ObjectCategory - module: String - scope: Int - useRls: Boolean - timestamps: Boolean - peoplestamps: Boolean - pluralName: String - singularName: String - tags: [String] - inheritsId: UUID - createdAt: Datetime - updatedAt: Datetime + outInvitesModuleId: UUID } """The output of our update `RelationProvision` mutation.""" @@ -44947,38 +45059,36 @@ input DeleteForeignKeyConstraintInput { id: UUID! } -"""The output of our delete `EntityTypeProvision` mutation.""" -type DeleteEntityTypeProvisionPayload { +"""The output of our delete `Table` mutation.""" +type DeleteTablePayload { """ The exact same `clientMutationId` that was provided in the mutation input, unchanged and unused. May be used by a client to track mutations. """ clientMutationId: String - """The `EntityTypeProvision` that was deleted by this mutation.""" - entityTypeProvision: EntityTypeProvision + """The `Table` that was deleted by this mutation.""" + table: Table """ Our root query field type. Allows us to run any query from our mutation payload. """ query: Query - """An edge for our `EntityTypeProvision`. May be used by Relay 1.""" - entityTypeProvisionEdge( - """The method to use when ordering `EntityTypeProvision`.""" - orderBy: [EntityTypeProvisionOrderBy!]! = [PRIMARY_KEY_ASC] - ): EntityTypeProvisionEdge + """An edge for our `Table`. May be used by Relay 1.""" + tableEdge( + """The method to use when ordering `Table`.""" + orderBy: [TableOrderBy!]! = [PRIMARY_KEY_ASC] + ): TableEdge } -"""All input for the `deleteEntityTypeProvision` mutation.""" -input DeleteEntityTypeProvisionInput { +"""All input for the `deleteTable` mutation.""" +input DeleteTableInput { """ An arbitrary string value with no semantic meaning. Will be included in the payload verbatim. May be used to track mutations by the client. """ clientMutationId: String - - """Unique identifier for this provision row.""" id: UUID! } @@ -45015,36 +45125,38 @@ input DeleteStorageModuleInput { id: UUID! } -"""The output of our delete `Table` mutation.""" -type DeleteTablePayload { +"""The output of our delete `EntityTypeProvision` mutation.""" +type DeleteEntityTypeProvisionPayload { """ The exact same `clientMutationId` that was provided in the mutation input, unchanged and unused. May be used by a client to track mutations. """ clientMutationId: String - """The `Table` that was deleted by this mutation.""" - table: Table + """The `EntityTypeProvision` that was deleted by this mutation.""" + entityTypeProvision: EntityTypeProvision """ Our root query field type. Allows us to run any query from our mutation payload. """ query: Query - """An edge for our `Table`. May be used by Relay 1.""" - tableEdge( - """The method to use when ordering `Table`.""" - orderBy: [TableOrderBy!]! = [PRIMARY_KEY_ASC] - ): TableEdge + """An edge for our `EntityTypeProvision`. May be used by Relay 1.""" + entityTypeProvisionEdge( + """The method to use when ordering `EntityTypeProvision`.""" + orderBy: [EntityTypeProvisionOrderBy!]! = [PRIMARY_KEY_ASC] + ): EntityTypeProvisionEdge } -"""All input for the `deleteTable` mutation.""" -input DeleteTableInput { +"""All input for the `deleteEntityTypeProvision` mutation.""" +input DeleteEntityTypeProvisionInput { """ An arbitrary string value with no semantic meaning. Will be included in the payload verbatim. May be used to track mutations by the client. """ clientMutationId: String + + """Unique identifier for this provision row.""" id: UUID! } @@ -45255,6 +45367,11 @@ type RequestUploadUrlPayload { """Presigned URL expiry time (null if deduplicated)""" expiresAt: Datetime + + """ + File status — 'pending' for fresh uploads, 'ready' or 'processed' for deduplicated files. Clients can use this to know immediately whether the file is usable. + """ + status: String! } input ConfirmUploadInput { @@ -48622,8 +48739,8 @@ type Query { orderBy: [ForeignKeyConstraintOrderBy!] = [PRIMARY_KEY_ASC] ): ForeignKeyConstraintConnection - """Reads and enables pagination through a set of `EntityTypeProvision`.""" - entityTypeProvisions( + """Reads and enables pagination through a set of `Table`.""" + tables( """Only read the first `n` values of the set.""" first: Int @@ -48645,11 +48762,11 @@ type Query { """ A filter to be used in determining which values should be returned by the collection. """ - where: EntityTypeProvisionFilter + where: TableFilter - """The method to use when ordering `EntityTypeProvision`.""" - orderBy: [EntityTypeProvisionOrderBy!] = [PRIMARY_KEY_ASC] - ): EntityTypeProvisionConnection + """The method to use when ordering `Table`.""" + orderBy: [TableOrderBy!] = [PRIMARY_KEY_ASC] + ): TableConnection """Reads and enables pagination through a set of `StorageModule`.""" storageModules( @@ -48680,8 +48797,8 @@ type Query { orderBy: [StorageModuleOrderBy!] = [PRIMARY_KEY_ASC] ): StorageModuleConnection - """Reads and enables pagination through a set of `Table`.""" - tables( + """Reads and enables pagination through a set of `EntityTypeProvision`.""" + entityTypeProvisions( """Only read the first `n` values of the set.""" first: Int @@ -48703,11 +48820,11 @@ type Query { """ A filter to be used in determining which values should be returned by the collection. """ - where: TableFilter + where: EntityTypeProvisionFilter - """The method to use when ordering `Table`.""" - orderBy: [TableOrderBy!] = [PRIMARY_KEY_ASC] - ): TableConnection + """The method to use when ordering `EntityTypeProvision`.""" + orderBy: [EntityTypeProvisionOrderBy!] = [PRIMARY_KEY_ASC] + ): EntityTypeProvisionConnection """Reads and enables pagination through a set of `RelationProvision`.""" relationProvisions( @@ -50053,13 +50170,13 @@ type Mutation { input: CreateForeignKeyConstraintInput! ): CreateForeignKeyConstraintPayload - """Creates a single `EntityTypeProvision`.""" - createEntityTypeProvision( + """Creates a single `Table`.""" + createTable( """ The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields. """ - input: CreateEntityTypeProvisionInput! - ): CreateEntityTypeProvisionPayload + input: CreateTableInput! + ): CreateTablePayload """Creates a single `StorageModule`.""" createStorageModule( @@ -50069,13 +50186,13 @@ type Mutation { input: CreateStorageModuleInput! ): CreateStorageModulePayload - """Creates a single `Table`.""" - createTable( + """Creates a single `EntityTypeProvision`.""" + createEntityTypeProvision( """ The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields. """ - input: CreateTableInput! - ): CreateTablePayload + input: CreateEntityTypeProvisionInput! + ): CreateEntityTypeProvisionPayload """Creates a single `RelationProvision`.""" createRelationProvision( @@ -50985,13 +51102,13 @@ type Mutation { input: UpdateForeignKeyConstraintInput! ): UpdateForeignKeyConstraintPayload - """Updates a single `EntityTypeProvision` using a unique key and a patch.""" - updateEntityTypeProvision( + """Updates a single `Table` using a unique key and a patch.""" + updateTable( """ The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields. """ - input: UpdateEntityTypeProvisionInput! - ): UpdateEntityTypeProvisionPayload + input: UpdateTableInput! + ): UpdateTablePayload """Updates a single `StorageModule` using a unique key and a patch.""" updateStorageModule( @@ -51001,13 +51118,13 @@ type Mutation { input: UpdateStorageModuleInput! ): UpdateStorageModulePayload - """Updates a single `Table` using a unique key and a patch.""" - updateTable( + """Updates a single `EntityTypeProvision` using a unique key and a patch.""" + updateEntityTypeProvision( """ The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields. """ - input: UpdateTableInput! - ): UpdateTablePayload + input: UpdateEntityTypeProvisionInput! + ): UpdateEntityTypeProvisionPayload """Updates a single `RelationProvision` using a unique key and a patch.""" updateRelationProvision( @@ -51881,13 +51998,13 @@ type Mutation { input: DeleteForeignKeyConstraintInput! ): DeleteForeignKeyConstraintPayload - """Deletes a single `EntityTypeProvision` using a unique key.""" - deleteEntityTypeProvision( + """Deletes a single `Table` using a unique key.""" + deleteTable( """ The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields. """ - input: DeleteEntityTypeProvisionInput! - ): DeleteEntityTypeProvisionPayload + input: DeleteTableInput! + ): DeleteTablePayload """Deletes a single `StorageModule` using a unique key.""" deleteStorageModule( @@ -51897,13 +52014,13 @@ type Mutation { input: DeleteStorageModuleInput! ): DeleteStorageModulePayload - """Deletes a single `Table` using a unique key.""" - deleteTable( + """Deletes a single `EntityTypeProvision` using a unique key.""" + deleteEntityTypeProvision( """ The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields. """ - input: DeleteTableInput! - ): DeleteTablePayload + input: DeleteEntityTypeProvisionInput! + ): DeleteEntityTypeProvisionPayload """Deletes a single `RelationProvision` using a unique key.""" deleteRelationProvision( diff --git a/sdk/constructive-sdk/src/admin/orm/client.ts b/sdk/constructive-sdk/src/admin/orm/client.ts index a1d5c74d8..39fb07eed 100644 --- a/sdk/constructive-sdk/src/admin/orm/client.ts +++ b/sdk/constructive-sdk/src/admin/orm/client.ts @@ -31,16 +31,13 @@ export class FetchAdapter implements GraphQLAdapter { constructor( private endpoint: string, headers?: Record, - fetchFn?: typeof globalThis.fetch, + fetchFn?: typeof globalThis.fetch ) { this.headers = headers ?? {}; this.fetchFn = fetchFn ?? createFetch(); } - async execute( - document: string, - variables?: Record, - ): Promise> { + async execute(document: string, variables?: Record): Promise> { const response = await this.fetchFn(this.endpoint, { method: 'POST', headers: { @@ -58,9 +55,7 @@ export class FetchAdapter implements GraphQLAdapter { return { ok: false, data: null, - errors: [ - { message: `HTTP ${response.status}: ${response.statusText}` }, - ], + errors: [{ message: `HTTP ${response.status}: ${response.statusText}` }], }; } @@ -120,7 +115,7 @@ export interface OrmClientConfig { export class GraphQLRequestError extends Error { constructor( public readonly errors: GraphQLError[], - public readonly data: unknown = null, + public readonly data: unknown = null ) { const messages = errors.map((e) => e.message).join('; '); super(`GraphQL Error: ${messages}`); @@ -135,22 +130,13 @@ export class OrmClient { if (config.adapter) { this.adapter = config.adapter; } else if (config.endpoint) { - this.adapter = new FetchAdapter( - config.endpoint, - config.headers, - config.fetch, - ); + this.adapter = new FetchAdapter(config.endpoint, config.headers, config.fetch); } else { - throw new Error( - 'OrmClientConfig requires either an endpoint or a custom adapter', - ); + throw new Error('OrmClientConfig requires either an endpoint or a custom adapter'); } } - async execute( - document: string, - variables?: Record, - ): Promise> { + async execute(document: string, variables?: Record): Promise> { return this.adapter.execute(document, variables); } diff --git a/sdk/constructive-sdk/src/admin/orm/input-types.ts b/sdk/constructive-sdk/src/admin/orm/input-types.ts index 806b6b042..3f1ab47a3 100644 --- a/sdk/constructive-sdk/src/admin/orm/input-types.ts +++ b/sdk/constructive-sdk/src/admin/orm/input-types.ts @@ -3615,6 +3615,8 @@ export interface RequestUploadUrlPayload { deduplicated: boolean; /** Presigned URL expiry time (null if deduplicated) */ expiresAt?: string | null; + /** File status — 'pending' for fresh uploads, 'ready' or 'processed' for deduplicated files. Clients can use this to know immediately whether the file is usable. */ + status: string; } export type RequestUploadUrlPayloadSelect = { uploadUrl?: boolean; @@ -3622,6 +3624,7 @@ export type RequestUploadUrlPayloadSelect = { key?: boolean; deduplicated?: boolean; expiresAt?: boolean; + status?: boolean; }; export interface ConfirmUploadPayload { /** The confirmed file ID */ diff --git a/sdk/constructive-sdk/src/auth/orm/client.ts b/sdk/constructive-sdk/src/auth/orm/client.ts index a1d5c74d8..39fb07eed 100644 --- a/sdk/constructive-sdk/src/auth/orm/client.ts +++ b/sdk/constructive-sdk/src/auth/orm/client.ts @@ -31,16 +31,13 @@ export class FetchAdapter implements GraphQLAdapter { constructor( private endpoint: string, headers?: Record, - fetchFn?: typeof globalThis.fetch, + fetchFn?: typeof globalThis.fetch ) { this.headers = headers ?? {}; this.fetchFn = fetchFn ?? createFetch(); } - async execute( - document: string, - variables?: Record, - ): Promise> { + async execute(document: string, variables?: Record): Promise> { const response = await this.fetchFn(this.endpoint, { method: 'POST', headers: { @@ -58,9 +55,7 @@ export class FetchAdapter implements GraphQLAdapter { return { ok: false, data: null, - errors: [ - { message: `HTTP ${response.status}: ${response.statusText}` }, - ], + errors: [{ message: `HTTP ${response.status}: ${response.statusText}` }], }; } @@ -120,7 +115,7 @@ export interface OrmClientConfig { export class GraphQLRequestError extends Error { constructor( public readonly errors: GraphQLError[], - public readonly data: unknown = null, + public readonly data: unknown = null ) { const messages = errors.map((e) => e.message).join('; '); super(`GraphQL Error: ${messages}`); @@ -135,22 +130,13 @@ export class OrmClient { if (config.adapter) { this.adapter = config.adapter; } else if (config.endpoint) { - this.adapter = new FetchAdapter( - config.endpoint, - config.headers, - config.fetch, - ); + this.adapter = new FetchAdapter(config.endpoint, config.headers, config.fetch); } else { - throw new Error( - 'OrmClientConfig requires either an endpoint or a custom adapter', - ); + throw new Error('OrmClientConfig requires either an endpoint or a custom adapter'); } } - async execute( - document: string, - variables?: Record, - ): Promise> { + async execute(document: string, variables?: Record): Promise> { return this.adapter.execute(document, variables); } diff --git a/sdk/constructive-sdk/src/auth/orm/input-types.ts b/sdk/constructive-sdk/src/auth/orm/input-types.ts index ba12312ee..eb5c48d84 100644 --- a/sdk/constructive-sdk/src/auth/orm/input-types.ts +++ b/sdk/constructive-sdk/src/auth/orm/input-types.ts @@ -2440,6 +2440,8 @@ export interface RequestUploadUrlPayload { deduplicated: boolean; /** Presigned URL expiry time (null if deduplicated) */ expiresAt?: string | null; + /** File status — 'pending' for fresh uploads, 'ready' or 'processed' for deduplicated files. Clients can use this to know immediately whether the file is usable. */ + status: string; } export type RequestUploadUrlPayloadSelect = { uploadUrl?: boolean; @@ -2447,6 +2449,7 @@ export type RequestUploadUrlPayloadSelect = { key?: boolean; deduplicated?: boolean; expiresAt?: boolean; + status?: boolean; }; export interface ConfirmUploadPayload { /** The confirmed file ID */ diff --git a/sdk/constructive-sdk/src/objects/orm/client.ts b/sdk/constructive-sdk/src/objects/orm/client.ts index a1d5c74d8..39fb07eed 100644 --- a/sdk/constructive-sdk/src/objects/orm/client.ts +++ b/sdk/constructive-sdk/src/objects/orm/client.ts @@ -31,16 +31,13 @@ export class FetchAdapter implements GraphQLAdapter { constructor( private endpoint: string, headers?: Record, - fetchFn?: typeof globalThis.fetch, + fetchFn?: typeof globalThis.fetch ) { this.headers = headers ?? {}; this.fetchFn = fetchFn ?? createFetch(); } - async execute( - document: string, - variables?: Record, - ): Promise> { + async execute(document: string, variables?: Record): Promise> { const response = await this.fetchFn(this.endpoint, { method: 'POST', headers: { @@ -58,9 +55,7 @@ export class FetchAdapter implements GraphQLAdapter { return { ok: false, data: null, - errors: [ - { message: `HTTP ${response.status}: ${response.statusText}` }, - ], + errors: [{ message: `HTTP ${response.status}: ${response.statusText}` }], }; } @@ -120,7 +115,7 @@ export interface OrmClientConfig { export class GraphQLRequestError extends Error { constructor( public readonly errors: GraphQLError[], - public readonly data: unknown = null, + public readonly data: unknown = null ) { const messages = errors.map((e) => e.message).join('; '); super(`GraphQL Error: ${messages}`); @@ -135,22 +130,13 @@ export class OrmClient { if (config.adapter) { this.adapter = config.adapter; } else if (config.endpoint) { - this.adapter = new FetchAdapter( - config.endpoint, - config.headers, - config.fetch, - ); + this.adapter = new FetchAdapter(config.endpoint, config.headers, config.fetch); } else { - throw new Error( - 'OrmClientConfig requires either an endpoint or a custom adapter', - ); + throw new Error('OrmClientConfig requires either an endpoint or a custom adapter'); } } - async execute( - document: string, - variables?: Record, - ): Promise> { + async execute(document: string, variables?: Record): Promise> { return this.adapter.execute(document, variables); } diff --git a/sdk/constructive-sdk/src/objects/orm/input-types.ts b/sdk/constructive-sdk/src/objects/orm/input-types.ts index e1684b49e..5a7d7952c 100644 --- a/sdk/constructive-sdk/src/objects/orm/input-types.ts +++ b/sdk/constructive-sdk/src/objects/orm/input-types.ts @@ -829,6 +829,8 @@ export interface RequestUploadUrlPayload { deduplicated: boolean; /** Presigned URL expiry time (null if deduplicated) */ expiresAt?: string | null; + /** File status — 'pending' for fresh uploads, 'ready' or 'processed' for deduplicated files. Clients can use this to know immediately whether the file is usable. */ + status: string; } export type RequestUploadUrlPayloadSelect = { uploadUrl?: boolean; @@ -836,6 +838,7 @@ export type RequestUploadUrlPayloadSelect = { key?: boolean; deduplicated?: boolean; expiresAt?: boolean; + status?: boolean; }; export interface ConfirmUploadPayload { /** The confirmed file ID */ diff --git a/sdk/constructive-sdk/src/public/orm/README.md b/sdk/constructive-sdk/src/public/orm/README.md index 74e83c23e..9d3f602c4 100644 --- a/sdk/constructive-sdk/src/public/orm/README.md +++ b/sdk/constructive-sdk/src/public/orm/README.md @@ -2733,7 +2733,8 @@ CRUD operations for StorageModule records. | `filesTableName` | String | Yes | | `uploadRequestsTableName` | String | Yes | | `membershipType` | Int | Yes | -| `policies` | String | Yes | +| `policies` | JSON | Yes | +| `skipDefaultPolicyTables` | String | Yes | | `entityTableId` | UUID | Yes | | `endpoint` | String | Yes | | `publicUrlPrefix` | String | Yes | @@ -2749,13 +2750,13 @@ CRUD operations for StorageModule records. ```typescript // List all storageModule records -const items = await db.storageModule.findMany({ select: { id: true, databaseId: true, schemaId: true, privateSchemaId: true, bucketsTableId: true, filesTableId: true, uploadRequestsTableId: true, bucketsTableName: true, filesTableName: true, uploadRequestsTableName: true, membershipType: true, policies: true, entityTableId: true, endpoint: true, publicUrlPrefix: true, provider: true, allowedOrigins: true, uploadUrlExpirySeconds: true, downloadUrlExpirySeconds: true, defaultMaxFileSize: true, maxFilenameLength: true, cacheTtlSeconds: true } }).execute(); +const items = await db.storageModule.findMany({ select: { id: true, databaseId: true, schemaId: true, privateSchemaId: true, bucketsTableId: true, filesTableId: true, uploadRequestsTableId: true, bucketsTableName: true, filesTableName: true, uploadRequestsTableName: true, membershipType: true, policies: true, skipDefaultPolicyTables: true, entityTableId: true, endpoint: true, publicUrlPrefix: true, provider: true, allowedOrigins: true, uploadUrlExpirySeconds: true, downloadUrlExpirySeconds: true, defaultMaxFileSize: true, maxFilenameLength: true, cacheTtlSeconds: true } }).execute(); // Get one by id -const item = await db.storageModule.findOne({ id: '', select: { id: true, databaseId: true, schemaId: true, privateSchemaId: true, bucketsTableId: true, filesTableId: true, uploadRequestsTableId: true, bucketsTableName: true, filesTableName: true, uploadRequestsTableName: true, membershipType: true, policies: true, entityTableId: true, endpoint: true, publicUrlPrefix: true, provider: true, allowedOrigins: true, uploadUrlExpirySeconds: true, downloadUrlExpirySeconds: true, defaultMaxFileSize: true, maxFilenameLength: true, cacheTtlSeconds: true } }).execute(); +const item = await db.storageModule.findOne({ id: '', select: { id: true, databaseId: true, schemaId: true, privateSchemaId: true, bucketsTableId: true, filesTableId: true, uploadRequestsTableId: true, bucketsTableName: true, filesTableName: true, uploadRequestsTableName: true, membershipType: true, policies: true, skipDefaultPolicyTables: true, entityTableId: true, endpoint: true, publicUrlPrefix: true, provider: true, allowedOrigins: true, uploadUrlExpirySeconds: true, downloadUrlExpirySeconds: true, defaultMaxFileSize: true, maxFilenameLength: true, cacheTtlSeconds: true } }).execute(); // Create -const created = await db.storageModule.create({ data: { databaseId: '', schemaId: '', privateSchemaId: '', bucketsTableId: '', filesTableId: '', uploadRequestsTableId: '', bucketsTableName: '', filesTableName: '', uploadRequestsTableName: '', membershipType: '', policies: '', entityTableId: '', endpoint: '', publicUrlPrefix: '', provider: '', allowedOrigins: '', uploadUrlExpirySeconds: '', downloadUrlExpirySeconds: '', defaultMaxFileSize: '', maxFilenameLength: '', cacheTtlSeconds: '' }, select: { id: true } }).execute(); +const created = await db.storageModule.create({ data: { databaseId: '', schemaId: '', privateSchemaId: '', bucketsTableId: '', filesTableId: '', uploadRequestsTableId: '', bucketsTableName: '', filesTableName: '', uploadRequestsTableName: '', membershipType: '', policies: '', skipDefaultPolicyTables: '', entityTableId: '', endpoint: '', publicUrlPrefix: '', provider: '', allowedOrigins: '', uploadUrlExpirySeconds: '', downloadUrlExpirySeconds: '', defaultMaxFileSize: '', maxFilenameLength: '', cacheTtlSeconds: '' }, select: { id: true } }).execute(); // Update const updated = await db.storageModule.update({ where: { id: '' }, data: { databaseId: '' }, select: { id: true } }).execute(); @@ -2784,6 +2785,7 @@ CRUD operations for EntityTypeProvision records. | `hasProfiles` | Boolean | Yes | | `hasLevels` | Boolean | Yes | | `hasStorage` | Boolean | Yes | +| `hasInvites` | Boolean | Yes | | `storageConfig` | JSON | Yes | | `skipEntityPolicies` | Boolean | Yes | | `tableProvision` | JSON | Yes | @@ -2794,18 +2796,19 @@ CRUD operations for EntityTypeProvision records. | `outStorageModuleId` | UUID | Yes | | `outBucketsTableId` | UUID | Yes | | `outFilesTableId` | UUID | Yes | +| `outInvitesModuleId` | UUID | Yes | **Operations:** ```typescript // List all entityTypeProvision records -const items = await db.entityTypeProvision.findMany({ select: { id: true, databaseId: true, name: true, prefix: true, description: true, parentEntity: true, tableName: true, isVisible: true, hasLimits: true, hasProfiles: true, hasLevels: true, hasStorage: true, storageConfig: true, skipEntityPolicies: true, tableProvision: true, outMembershipType: true, outEntityTableId: true, outEntityTableName: true, outInstalledModules: true, outStorageModuleId: true, outBucketsTableId: true, outFilesTableId: true } }).execute(); +const items = await db.entityTypeProvision.findMany({ select: { id: true, databaseId: true, name: true, prefix: true, description: true, parentEntity: true, tableName: true, isVisible: true, hasLimits: true, hasProfiles: true, hasLevels: true, hasStorage: true, hasInvites: true, storageConfig: true, skipEntityPolicies: true, tableProvision: true, outMembershipType: true, outEntityTableId: true, outEntityTableName: true, outInstalledModules: true, outStorageModuleId: true, outBucketsTableId: true, outFilesTableId: true, outInvitesModuleId: true } }).execute(); // Get one by id -const item = await db.entityTypeProvision.findOne({ id: '', select: { id: true, databaseId: true, name: true, prefix: true, description: true, parentEntity: true, tableName: true, isVisible: true, hasLimits: true, hasProfiles: true, hasLevels: true, hasStorage: true, storageConfig: true, skipEntityPolicies: true, tableProvision: true, outMembershipType: true, outEntityTableId: true, outEntityTableName: true, outInstalledModules: true, outStorageModuleId: true, outBucketsTableId: true, outFilesTableId: true } }).execute(); +const item = await db.entityTypeProvision.findOne({ id: '', select: { id: true, databaseId: true, name: true, prefix: true, description: true, parentEntity: true, tableName: true, isVisible: true, hasLimits: true, hasProfiles: true, hasLevels: true, hasStorage: true, hasInvites: true, storageConfig: true, skipEntityPolicies: true, tableProvision: true, outMembershipType: true, outEntityTableId: true, outEntityTableName: true, outInstalledModules: true, outStorageModuleId: true, outBucketsTableId: true, outFilesTableId: true, outInvitesModuleId: true } }).execute(); // Create -const created = await db.entityTypeProvision.create({ data: { databaseId: '', name: '', prefix: '', description: '', parentEntity: '', tableName: '', isVisible: '', hasLimits: '', hasProfiles: '', hasLevels: '', hasStorage: '', storageConfig: '', skipEntityPolicies: '', tableProvision: '', outMembershipType: '', outEntityTableId: '', outEntityTableName: '', outInstalledModules: '', outStorageModuleId: '', outBucketsTableId: '', outFilesTableId: '' }, select: { id: true } }).execute(); +const created = await db.entityTypeProvision.create({ data: { databaseId: '', name: '', prefix: '', description: '', parentEntity: '', tableName: '', isVisible: '', hasLimits: '', hasProfiles: '', hasLevels: '', hasStorage: '', hasInvites: '', storageConfig: '', skipEntityPolicies: '', tableProvision: '', outMembershipType: '', outEntityTableId: '', outEntityTableName: '', outInstalledModules: '', outStorageModuleId: '', outBucketsTableId: '', outFilesTableId: '', outInvitesModuleId: '' }, select: { id: true } }).execute(); // Update const updated = await db.entityTypeProvision.update({ where: { id: '' }, data: { databaseId: '' }, select: { id: true } }).execute(); diff --git a/sdk/constructive-sdk/src/public/orm/client.ts b/sdk/constructive-sdk/src/public/orm/client.ts index a1d5c74d8..39fb07eed 100644 --- a/sdk/constructive-sdk/src/public/orm/client.ts +++ b/sdk/constructive-sdk/src/public/orm/client.ts @@ -31,16 +31,13 @@ export class FetchAdapter implements GraphQLAdapter { constructor( private endpoint: string, headers?: Record, - fetchFn?: typeof globalThis.fetch, + fetchFn?: typeof globalThis.fetch ) { this.headers = headers ?? {}; this.fetchFn = fetchFn ?? createFetch(); } - async execute( - document: string, - variables?: Record, - ): Promise> { + async execute(document: string, variables?: Record): Promise> { const response = await this.fetchFn(this.endpoint, { method: 'POST', headers: { @@ -58,9 +55,7 @@ export class FetchAdapter implements GraphQLAdapter { return { ok: false, data: null, - errors: [ - { message: `HTTP ${response.status}: ${response.statusText}` }, - ], + errors: [{ message: `HTTP ${response.status}: ${response.statusText}` }], }; } @@ -120,7 +115,7 @@ export interface OrmClientConfig { export class GraphQLRequestError extends Error { constructor( public readonly errors: GraphQLError[], - public readonly data: unknown = null, + public readonly data: unknown = null ) { const messages = errors.map((e) => e.message).join('; '); super(`GraphQL Error: ${messages}`); @@ -135,22 +130,13 @@ export class OrmClient { if (config.adapter) { this.adapter = config.adapter; } else if (config.endpoint) { - this.adapter = new FetchAdapter( - config.endpoint, - config.headers, - config.fetch, - ); + this.adapter = new FetchAdapter(config.endpoint, config.headers, config.fetch); } else { - throw new Error( - 'OrmClientConfig requires either an endpoint or a custom adapter', - ); + throw new Error('OrmClientConfig requires either an endpoint or a custom adapter'); } } - async execute( - document: string, - variables?: Record, - ): Promise> { + async execute(document: string, variables?: Record): Promise> { return this.adapter.execute(document, variables); } diff --git a/sdk/constructive-sdk/src/public/orm/input-types.ts b/sdk/constructive-sdk/src/public/orm/input-types.ts index 888d00b99..6f3337645 100644 --- a/sdk/constructive-sdk/src/public/orm/input-types.ts +++ b/sdk/constructive-sdk/src/public/orm/input-types.ts @@ -1361,7 +1361,8 @@ export interface StorageModule { filesTableName?: string | null; uploadRequestsTableName?: string | null; membershipType?: number | null; - policies?: string[] | null; + policies?: Record | null; + skipDefaultPolicyTables?: string[] | null; entityTableId?: string | null; endpoint?: string | null; publicUrlPrefix?: string | null; @@ -1389,8 +1390,8 @@ export interface EntityTypeProvision { /** The database to provision this entity type in. Required. */ databaseId?: string | null; /** - * Human-readable name for this membership type, e.g. 'Data Room Member', 'Team Channel Member'. Required. - * Stored in the membership_types registry table. + * Human-readable name for this entity type, e.g. 'Data Room', 'Team Channel'. Required. + * Stored in the entity_types registry table. */ name?: string | null; /** @@ -1400,7 +1401,7 @@ export interface EntityTypeProvision { * Must be unique per database — the (database_id, prefix) constraint ensures graceful ON CONFLICT DO NOTHING. */ prefix?: string | null; - /** Description of this membership type. Stored in the membership_types registry table. Defaults to empty string. */ + /** Description of this entity type. Stored in the entity_types registry table. Defaults to empty string. */ description?: string | null; /** * Prefix of the parent entity type. The trigger resolves this to a membership_type integer @@ -1451,6 +1452,16 @@ export interface EntityTypeProvision { * Storage tables get owner_id FK to the entity table, so files are owned by the entity. */ hasStorage?: boolean | null; + /** + * Whether to provision invites_module for this type. Defaults to false. + * When true, the trigger inserts a row into invites_module which in turn + * (via insert_invites_module BEFORE INSERT) creates {prefix}_invites and + * {prefix}_claimed_invites tables plus the submit_{prefix}_invite_code() function. + * Symmetric counterpart of has_storage. Re-provisioning is idempotent: the + * UNIQUE (database_id, membership_type) constraint on invites_module combined with + * ON CONFLICT DO NOTHING in the fan-out makes repeated INSERTs safe. + */ + hasInvites?: boolean | null; /** * Optional jsonb object for storage module configuration and initial bucket seeding. * Only used when has_storage = true; ignored otherwise. NULL = use defaults. @@ -1467,8 +1478,21 @@ export interface EntityTypeProvision { * - allowed_mime_types (text[]) whitelist of MIME types (null = any) * - max_file_size (bigint) max file size in bytes (null = use scope default) * - allowed_origins (text[]) per-bucket CORS override + * - provisions (jsonb object) optional: customize storage tables + * with additional nodes, fields, grants, and policies. + * Keyed by table role: "files", "buckets", "upload_requests". + * Each value uses the same shape as table_provision: + * { nodes, fields, grants, use_rls, policies }. Fanned out + * to secure_table_provision targeting the corresponding table. + * When a key includes policies[], those REPLACE the default + * storage policies for that table; tables without a key still + * get defaults. Missing "data" on policy entries is auto-populated + * with storage-specific defaults (same as table_provision). + * Example: add SearchBm25 for full-text search on files: + * {"provisions": {"files": {"nodes": [{"$type": + * "SearchBm25", "data": {"source_fields": ["description"]}}]}}} * Example: - * storage_config := '{"buckets": [{"name": "documents", "is_public": false, "allowed_mime_types": ["application/pdf"]}]}'::jsonb + * storage_config := '{"buckets": [{"name": "documents", "is_public": false, "allowed_mime_types": ["application/pdf"]}], "provisions": {"files": {"nodes": [{"$type": "SearchBm25", "data": {"source_fields": ["description"]}}]}}}'::jsonb */ storageConfig?: Record | null; /** @@ -1519,7 +1543,7 @@ export interface EntityTypeProvision { tableProvision?: Record | null; /** * Output: the auto-assigned integer membership type ID. Populated by the trigger after successful provisioning. - * This is the ID used in membership_types, memberships_module, and all module tables. + * This is the ID used in entity_types, memberships_module, and all module tables. */ outMembershipType?: number | null; /** @@ -1540,6 +1564,12 @@ export interface EntityTypeProvision { outBucketsTableId?: string | null; /** Output: the UUID of the generated files table (e.g. data_room_files). Populated by the trigger when has_storage=true. */ outFilesTableId?: string | null; + /** + * Output: the UUID of the invites_module row created for this entity type. Populated by the trigger when has_invites=true. + * NULL when has_invites=false, or when re-provisioning hits ON CONFLICT DO NOTHING + * (i.e. the invites_module row was created in a previous run). + */ + outInvitesModuleId?: string | null; } /** Config row for the webauthn_credentials_module, which provisions the per-user WebAuthn/passkey credentials table (public key, counter, transports, device type, backup state) mirroring crypto_addresses_module. The sibling webauthn_auth_module holds RP config and the registration/sign-in challenge state. */ export interface WebauthnCredentialsModule { @@ -5321,6 +5351,7 @@ export type StorageModuleSelect = { uploadRequestsTableName?: boolean; membershipType?: boolean; policies?: boolean; + skipDefaultPolicyTables?: boolean; entityTableId?: boolean; endpoint?: boolean; publicUrlPrefix?: boolean; @@ -5366,6 +5397,7 @@ export type EntityTypeProvisionSelect = { hasProfiles?: boolean; hasLevels?: boolean; hasStorage?: boolean; + hasInvites?: boolean; storageConfig?: boolean; skipEntityPolicies?: boolean; tableProvision?: boolean; @@ -5376,6 +5408,7 @@ export type EntityTypeProvisionSelect = { outStorageModuleId?: boolean; outBucketsTableId?: boolean; outFilesTableId?: boolean; + outInvitesModuleId?: boolean; database?: { select: DatabaseSelect; }; @@ -9422,7 +9455,9 @@ export interface StorageModuleFilter { /** Filter by the object’s `membershipType` field. */ membershipType?: IntFilter; /** Filter by the object’s `policies` field. */ - policies?: StringListFilter; + policies?: JSONFilter; + /** Filter by the object’s `skipDefaultPolicyTables` field. */ + skipDefaultPolicyTables?: StringListFilter; /** Filter by the object’s `entityTableId` field. */ entityTableId?: UUIDFilter; /** Filter by the object’s `endpoint` field. */ @@ -9491,6 +9526,8 @@ export interface EntityTypeProvisionFilter { hasLevels?: BooleanFilter; /** Filter by the object’s `hasStorage` field. */ hasStorage?: BooleanFilter; + /** Filter by the object’s `hasInvites` field. */ + hasInvites?: BooleanFilter; /** Filter by the object’s `storageConfig` field. */ storageConfig?: JSONFilter; /** Filter by the object’s `skipEntityPolicies` field. */ @@ -9511,6 +9548,8 @@ export interface EntityTypeProvisionFilter { outBucketsTableId?: UUIDFilter; /** Filter by the object’s `outFilesTableId` field. */ outFilesTableId?: UUIDFilter; + /** Filter by the object’s `outInvitesModuleId` field. */ + outInvitesModuleId?: UUIDFilter; /** Checks for all expressions in this list. */ and?: EntityTypeProvisionFilter[]; /** Checks for any expressions in this list. */ @@ -13014,6 +13053,8 @@ export type StorageModuleOrderBy = | 'MEMBERSHIP_TYPE_DESC' | 'POLICIES_ASC' | 'POLICIES_DESC' + | 'SKIP_DEFAULT_POLICY_TABLES_ASC' + | 'SKIP_DEFAULT_POLICY_TABLES_DESC' | 'ENTITY_TABLE_ID_ASC' | 'ENTITY_TABLE_ID_DESC' | 'ENDPOINT_ASC' @@ -13062,6 +13103,8 @@ export type EntityTypeProvisionOrderBy = | 'HAS_LEVELS_DESC' | 'HAS_STORAGE_ASC' | 'HAS_STORAGE_DESC' + | 'HAS_INVITES_ASC' + | 'HAS_INVITES_DESC' | 'STORAGE_CONFIG_ASC' | 'STORAGE_CONFIG_DESC' | 'SKIP_ENTITY_POLICIES_ASC' @@ -13081,7 +13124,9 @@ export type EntityTypeProvisionOrderBy = | 'OUT_BUCKETS_TABLE_ID_ASC' | 'OUT_BUCKETS_TABLE_ID_DESC' | 'OUT_FILES_TABLE_ID_ASC' - | 'OUT_FILES_TABLE_ID_DESC'; + | 'OUT_FILES_TABLE_ID_DESC' + | 'OUT_INVITES_MODULE_ID_ASC' + | 'OUT_INVITES_MODULE_ID_DESC'; export type WebauthnCredentialsModuleOrderBy = | 'NATURAL' | 'PRIMARY_KEY_ASC' @@ -16466,7 +16511,8 @@ export interface CreateStorageModuleInput { filesTableName?: string; uploadRequestsTableName?: string; membershipType?: number; - policies?: string[]; + policies?: Record; + skipDefaultPolicyTables?: string[]; entityTableId?: string; endpoint?: string; publicUrlPrefix?: string; @@ -16490,7 +16536,8 @@ export interface StorageModulePatch { filesTableName?: string | null; uploadRequestsTableName?: string | null; membershipType?: number | null; - policies?: string[] | null; + policies?: Record | null; + skipDefaultPolicyTables?: string[] | null; entityTableId?: string | null; endpoint?: string | null; publicUrlPrefix?: string | null; @@ -16525,6 +16572,7 @@ export interface CreateEntityTypeProvisionInput { hasProfiles?: boolean; hasLevels?: boolean; hasStorage?: boolean; + hasInvites?: boolean; storageConfig?: Record; skipEntityPolicies?: boolean; tableProvision?: Record; @@ -16535,6 +16583,7 @@ export interface CreateEntityTypeProvisionInput { outStorageModuleId?: string; outBucketsTableId?: string; outFilesTableId?: string; + outInvitesModuleId?: string; }; } export interface EntityTypeProvisionPatch { @@ -16549,6 +16598,7 @@ export interface EntityTypeProvisionPatch { hasProfiles?: boolean | null; hasLevels?: boolean | null; hasStorage?: boolean | null; + hasInvites?: boolean | null; storageConfig?: Record | null; skipEntityPolicies?: boolean | null; tableProvision?: Record | null; @@ -16559,6 +16609,7 @@ export interface EntityTypeProvisionPatch { outStorageModuleId?: string | null; outBucketsTableId?: string | null; outFilesTableId?: string | null; + outInvitesModuleId?: string | null; } export interface UpdateEntityTypeProvisionInput { clientMutationId?: string; @@ -18535,6 +18586,7 @@ export interface ProvisionTableInput { indexes?: Record; fullTextSearches?: Record; uniqueConstraints?: Record; + description?: string; } export interface SendVerificationEmailInput { clientMutationId?: string; @@ -22806,7 +22858,9 @@ export interface StorageModuleFilter { /** Filter by the object’s `membershipType` field. */ membershipType?: IntFilter; /** Filter by the object’s `policies` field. */ - policies?: StringListFilter; + policies?: JSONFilter; + /** Filter by the object’s `skipDefaultPolicyTables` field. */ + skipDefaultPolicyTables?: StringListFilter; /** Filter by the object’s `entityTableId` field. */ entityTableId?: UUIDFilter; /** Filter by the object’s `endpoint` field. */ @@ -22876,6 +22930,8 @@ export interface EntityTypeProvisionFilter { hasLevels?: BooleanFilter; /** Filter by the object’s `hasStorage` field. */ hasStorage?: BooleanFilter; + /** Filter by the object’s `hasInvites` field. */ + hasInvites?: BooleanFilter; /** Filter by the object’s `storageConfig` field. */ storageConfig?: JSONFilter; /** Filter by the object’s `skipEntityPolicies` field. */ @@ -22896,6 +22952,8 @@ export interface EntityTypeProvisionFilter { outBucketsTableId?: UUIDFilter; /** Filter by the object’s `outFilesTableId` field. */ outFilesTableId?: UUIDFilter; + /** Filter by the object’s `outInvitesModuleId` field. */ + outInvitesModuleId?: UUIDFilter; /** Checks for all expressions in this list. */ and?: EntityTypeProvisionFilter[]; /** Checks for any expressions in this list. */ @@ -25755,6 +25813,8 @@ export interface RequestUploadUrlPayload { deduplicated: boolean; /** Presigned URL expiry time (null if deduplicated) */ expiresAt?: string | null; + /** File status — 'pending' for fresh uploads, 'ready' or 'processed' for deduplicated files. Clients can use this to know immediately whether the file is usable. */ + status: string; } export type RequestUploadUrlPayloadSelect = { uploadUrl?: boolean; @@ -25762,6 +25822,7 @@ export type RequestUploadUrlPayloadSelect = { key?: boolean; deduplicated?: boolean; expiresAt?: boolean; + status?: boolean; }; export interface ConfirmUploadPayload { /** The confirmed file ID */ diff --git a/sdk/migrate-client/schemas/migrate.graphql b/sdk/migrate-client/schemas/migrate.graphql index 8b142b135..6f8ca5cfa 100644 --- a/sdk/migrate-client/schemas/migrate.graphql +++ b/sdk/migrate-client/schemas/migrate.graphql @@ -1118,6 +1118,11 @@ type RequestUploadUrlPayload { """Presigned URL expiry time (null if deduplicated)""" expiresAt: Datetime + + """ + File status — 'pending' for fresh uploads, 'ready' or 'processed' for deduplicated files. Clients can use this to know immediately whether the file is usable. + """ + status: String! } input ConfirmUploadInput { diff --git a/sdk/migrate-client/src/migrate/orm/client.ts b/sdk/migrate-client/src/migrate/orm/client.ts index a1d5c74d8..39fb07eed 100644 --- a/sdk/migrate-client/src/migrate/orm/client.ts +++ b/sdk/migrate-client/src/migrate/orm/client.ts @@ -31,16 +31,13 @@ export class FetchAdapter implements GraphQLAdapter { constructor( private endpoint: string, headers?: Record, - fetchFn?: typeof globalThis.fetch, + fetchFn?: typeof globalThis.fetch ) { this.headers = headers ?? {}; this.fetchFn = fetchFn ?? createFetch(); } - async execute( - document: string, - variables?: Record, - ): Promise> { + async execute(document: string, variables?: Record): Promise> { const response = await this.fetchFn(this.endpoint, { method: 'POST', headers: { @@ -58,9 +55,7 @@ export class FetchAdapter implements GraphQLAdapter { return { ok: false, data: null, - errors: [ - { message: `HTTP ${response.status}: ${response.statusText}` }, - ], + errors: [{ message: `HTTP ${response.status}: ${response.statusText}` }], }; } @@ -120,7 +115,7 @@ export interface OrmClientConfig { export class GraphQLRequestError extends Error { constructor( public readonly errors: GraphQLError[], - public readonly data: unknown = null, + public readonly data: unknown = null ) { const messages = errors.map((e) => e.message).join('; '); super(`GraphQL Error: ${messages}`); @@ -135,22 +130,13 @@ export class OrmClient { if (config.adapter) { this.adapter = config.adapter; } else if (config.endpoint) { - this.adapter = new FetchAdapter( - config.endpoint, - config.headers, - config.fetch, - ); + this.adapter = new FetchAdapter(config.endpoint, config.headers, config.fetch); } else { - throw new Error( - 'OrmClientConfig requires either an endpoint or a custom adapter', - ); + throw new Error('OrmClientConfig requires either an endpoint or a custom adapter'); } } - async execute( - document: string, - variables?: Record, - ): Promise> { + async execute(document: string, variables?: Record): Promise> { return this.adapter.execute(document, variables); } diff --git a/sdk/migrate-client/src/migrate/orm/input-types.ts b/sdk/migrate-client/src/migrate/orm/input-types.ts index fafdcbb0a..35fc08ebf 100644 --- a/sdk/migrate-client/src/migrate/orm/input-types.ts +++ b/sdk/migrate-client/src/migrate/orm/input-types.ts @@ -544,6 +544,8 @@ export interface RequestUploadUrlPayload { deduplicated: boolean; /** Presigned URL expiry time (null if deduplicated) */ expiresAt?: string | null; + /** File status — 'pending' for fresh uploads, 'ready' or 'processed' for deduplicated files. Clients can use this to know immediately whether the file is usable. */ + status: string; } export type RequestUploadUrlPayloadSelect = { uploadUrl?: boolean; @@ -551,6 +553,7 @@ export type RequestUploadUrlPayloadSelect = { key?: boolean; deduplicated?: boolean; expiresAt?: boolean; + status?: boolean; }; export interface ConfirmUploadPayload { /** The confirmed file ID */