d25d482dc2
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
239 lines
9.5 KiB
TypeScript
239 lines
9.5 KiB
TypeScript
import { stripVersionSuffix } from '@sim/utils/string'
|
|
import type { BlockVisibilityState } from '@/lib/core/config/block-visibility'
|
|
import { overlayBlocks, resolveOverlayBlock } from '@/blocks/custom/overlay'
|
|
import { BLOCK_META_REGISTRY, BLOCK_REGISTRY } from '@/blocks/registry-maps'
|
|
import type {
|
|
BlockCategory,
|
|
BlockConfig,
|
|
BlockMeta,
|
|
BlockTemplate,
|
|
SuggestedSkill,
|
|
} from '@/blocks/types'
|
|
import { isHiddenUnder, overlayVisibility } from '@/blocks/visibility/context'
|
|
|
|
/**
|
|
* Normalize an external block type to its registry key form: dashes become
|
|
* underscores (some external sources use either form).
|
|
*/
|
|
function normalizeType(type: string): string {
|
|
return type.replace(/-/g, '_')
|
|
}
|
|
|
|
/** Get the block config for a single block type. Falls back to the custom-block overlay. */
|
|
export function getBlock(type: string): BlockConfig | undefined {
|
|
return BLOCK_REGISTRY[type] ?? BLOCK_REGISTRY[normalizeType(type)] ?? resolveOverlayBlock(type)
|
|
}
|
|
|
|
/** Whether any registered block is an unreleased `preview` block. Static — computed once. */
|
|
const HAS_PREVIEW_BLOCKS = Object.values(BLOCK_REGISTRY).some((block) => block.preview)
|
|
|
|
/**
|
|
* True when the visibility projection cannot change any block, so accessors can
|
|
* return raw arrays untouched: no `preview` blocks exist (they must be hidden
|
|
* even with a null state) and no kill-switch entries apply.
|
|
*/
|
|
function visibilityInert(vis: BlockVisibilityState | null): boolean {
|
|
if (HAS_PREVIEW_BLOCKS) return false
|
|
return vis === null || vis.disabled.size === 0
|
|
}
|
|
|
|
/**
|
|
* Effective hidden state for discovery surfaces: static `hideFromToolbar`
|
|
* (superseded versions, disabled custom blocks) plus the per-viewer visibility
|
|
* predicate ({@link isHiddenUnder}: unrevealed `preview` blocks — fail-closed
|
|
* even without a context — and kill-switched types).
|
|
*/
|
|
function effectiveHidden(block: BlockConfig, vis: BlockVisibilityState | null): boolean {
|
|
if (block.hideFromToolbar) return true
|
|
return isHiddenUnder(vis, block)
|
|
}
|
|
|
|
/**
|
|
* Project a block through the viewer's visibility: gated blocks become shallow
|
|
* clones with `hideFromToolbar: true` (CLONE-NOT-REMOVE — gated blocks must stay
|
|
* in `getAllBlocks()` output because `.find`-by-type consumers rely on it), and
|
|
* revealed-but-not-GA preview blocks get a display " (Preview)" name suffix.
|
|
* The `!block.hideFromToolbar` guard keeps already-hidden blocks (including
|
|
* disabled custom blocks) un-cloned and never suffixed.
|
|
*/
|
|
function projectBlock(block: BlockConfig, vis: BlockVisibilityState | null): BlockConfig {
|
|
if (effectiveHidden(block, vis) && !block.hideFromToolbar) {
|
|
return { ...block, hideFromToolbar: true }
|
|
}
|
|
if (block.preview && vis?.previewTagged.has(block.type)) {
|
|
return { ...block, name: `${block.name} (Preview)` }
|
|
}
|
|
return block
|
|
}
|
|
|
|
/**
|
|
* All block configs, including any in-scope custom blocks from the overlay,
|
|
* projected through the viewer's block visibility. Execution paths are
|
|
* unaffected: they resolve via the pure {@link getBlock}.
|
|
*/
|
|
export function getAllBlocks(): BlockConfig[] {
|
|
const all = [...Object.values(BLOCK_REGISTRY), ...overlayBlocks()]
|
|
const vis = overlayVisibility()
|
|
if (visibilityInert(vis)) return all
|
|
return all.map((block) => projectBlock(block, vis))
|
|
}
|
|
|
|
/** Find the block whose `tools.access` contains the given tool id. */
|
|
export function getBlockByToolName(toolName: string): BlockConfig | undefined {
|
|
return Object.values(BLOCK_REGISTRY).find((b) => b.tools?.access?.includes(toolName))
|
|
}
|
|
|
|
/**
|
|
* Resolve the canonical (highest-version) block for a base type. Handles
|
|
* versioned variants like `confluence_v2`: callers pass `confluence` and
|
|
* receive the latest implementation. Returns the registry key alongside the
|
|
* config so callers that need the canonical type identifier avoid re-deriving
|
|
* it.
|
|
*/
|
|
function resolveLatest(baseType: string): { type: string; config: BlockConfig } | undefined {
|
|
const normalized = normalizeType(baseType)
|
|
const versionPattern = new RegExp(`^${normalized}_v(\\d+)$`)
|
|
let latestKey: string | undefined
|
|
let latestVersion = -1
|
|
for (const key of Object.keys(BLOCK_REGISTRY)) {
|
|
const match = key.match(versionPattern)
|
|
if (!match) continue
|
|
const version = Number.parseInt(match[1]!, 10)
|
|
if (version > latestVersion) {
|
|
latestVersion = version
|
|
latestKey = key
|
|
}
|
|
}
|
|
if (latestKey) return { type: latestKey, config: BLOCK_REGISTRY[latestKey]! }
|
|
const config = BLOCK_REGISTRY[normalized]
|
|
return config ? { type: normalized, config } : undefined
|
|
}
|
|
|
|
/**
|
|
* Resolve the canonical (highest-version) block for a base type. Handles
|
|
* versioned variants like `confluence_v2`: callers pass `confluence` and
|
|
* receive the latest implementation.
|
|
*/
|
|
export function getLatestBlock(baseType: string): BlockConfig | undefined {
|
|
return resolveLatest(baseType)?.config
|
|
}
|
|
|
|
/** All blocks in a given category. */
|
|
export function getBlocksByCategory(category: BlockCategory): BlockConfig[] {
|
|
return Object.values(BLOCK_REGISTRY).filter((block) => block.category === category)
|
|
}
|
|
|
|
/**
|
|
* The canonical "latest-version, toolbar-visible" set of blocks for a
|
|
* category. This is the single source of truth shared by every surface that
|
|
* extracts blocks for presentation — the toolbar, the search/mention engine,
|
|
* and the integrations catalog. A block is included when its `category`
|
|
* matches and it is not effectively hidden: not `hideFromToolbar` (superseded
|
|
* versions), not an unrevealed `preview` block, and not kill-switched for the
|
|
* viewer. Visible blocks are projected so revealed preview blocks carry their
|
|
* " (Preview)" display suffix.
|
|
*/
|
|
export function getCanonicalBlocksByCategory(category: BlockCategory): BlockConfig[] {
|
|
const vis = overlayVisibility()
|
|
const blocks = [...Object.values(BLOCK_REGISTRY), ...overlayBlocks()].filter(
|
|
(block) => block.category === category && !effectiveHidden(block, vis)
|
|
)
|
|
return visibilityInert(vis) ? blocks : blocks.map((block) => projectBlock(block, vis))
|
|
}
|
|
|
|
/** All registered block type identifiers. */
|
|
export function getAllBlockTypes(): string[] {
|
|
return Object.keys(BLOCK_REGISTRY)
|
|
}
|
|
|
|
/** Whether the given string is a registered block type. Accepts hyphens as a dash-form alias. */
|
|
export function isValidBlockType(type: string): type is string {
|
|
return (
|
|
type in BLOCK_REGISTRY ||
|
|
normalizeType(type) in BLOCK_REGISTRY ||
|
|
Boolean(resolveOverlayBlock(type))
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Get the presentation/catalog meta for a block type, resolving through the
|
|
* version suffix the same way {@link getTemplatesForBlock} does. Metas are
|
|
* keyed under the base type (e.g. `confluence`, not `confluence_v2`), so a
|
|
* versioned lookup falls back to the stripped base.
|
|
*/
|
|
export function getBlockMeta(type: string): BlockMeta | undefined {
|
|
const normalized = normalizeType(type)
|
|
return (
|
|
BLOCK_META_REGISTRY[type] ??
|
|
BLOCK_META_REGISTRY[normalized] ??
|
|
BLOCK_META_REGISTRY[stripVersionSuffix(normalized)]
|
|
)
|
|
}
|
|
|
|
/** All block metas keyed by block type. */
|
|
export function getAllBlockMeta(): Record<string, BlockMeta> {
|
|
return BLOCK_META_REGISTRY
|
|
}
|
|
|
|
/**
|
|
* A template scoped to a viewing block, enriched with `otherBlockTypes` —
|
|
* the integrations to render alongside the viewer in the icon cluster.
|
|
* Includes the template's owner block whenever the viewer is not the owner.
|
|
*/
|
|
export interface ScopedBlockTemplate extends BlockTemplate {
|
|
/** Block types (base form) to render alongside the viewing block in the icon cluster. */
|
|
otherBlockTypes: readonly string[]
|
|
}
|
|
|
|
/**
|
|
* All templates whose owner block is `type` or which list `type` in their
|
|
* `alsoIntegrations`. Each returned template carries `otherBlockTypes` —
|
|
* the non-viewing integrations (owner + other alsoIntegrations) for icon
|
|
* cluster rendering.
|
|
*/
|
|
export function getTemplatesForBlock(type: string): ScopedBlockTemplate[] {
|
|
const base = stripVersionSuffix(type)
|
|
const collected: ScopedBlockTemplate[] = []
|
|
for (const [ownerType, meta] of Object.entries(BLOCK_META_REGISTRY)) {
|
|
if (!meta.templates) continue
|
|
const ownerBase = stripVersionSuffix(ownerType)
|
|
const isOwnerMatch = ownerBase === base
|
|
for (const template of meta.templates) {
|
|
const isAlsoMatch =
|
|
template.alsoIntegrations?.includes(base) || template.alsoIntegrations?.includes(type)
|
|
if (!isOwnerMatch && !isAlsoMatch) continue
|
|
const others: string[] = []
|
|
if (!isOwnerMatch) others.push(ownerBase)
|
|
for (const also of template.alsoIntegrations ?? []) {
|
|
const alsoBase = stripVersionSuffix(also)
|
|
if (alsoBase !== base && !others.includes(alsoBase)) others.push(alsoBase)
|
|
}
|
|
collected.push({ ...template, otherBlockTypes: others })
|
|
}
|
|
}
|
|
return collected
|
|
}
|
|
|
|
/**
|
|
* Popular, ready-to-add skills for a block type. Curated skills live on the
|
|
* base integration's meta, but a versioned catalog type (e.g. `notion_v2`) has
|
|
* its own meta entry that {@link getBlockMeta} resolves first and which may omit
|
|
* skills — so fall back to the stripped base meta. Returns an empty array when
|
|
* the integration has no curated skills.
|
|
*/
|
|
export function getSuggestedSkillsForBlock(type: string): readonly SuggestedSkill[] {
|
|
const direct = getBlockMeta(type)?.skills
|
|
if (direct && direct.length > 0) return direct
|
|
const base = stripVersionSuffix(normalizeType(type))
|
|
return BLOCK_META_REGISTRY[base]?.skills ?? []
|
|
}
|
|
|
|
/**
|
|
* Raw block registry map keyed by block type. Prefer the typed accessors
|
|
* (`getBlock`, `getAllBlocks`, `getCanonicalBlocksByCategory`); this alias is
|
|
* retained for callers that need the underlying record directly.
|
|
*/
|
|
export const registry: Record<string, BlockConfig> = BLOCK_REGISTRY
|
|
|
|
export type { BlockCategory }
|