Artifacts Overlay Use config files as globals and gj_artifacts as the caller-scoped overlay for saved queries, fragments, and workflows. agentic guide agentic/artifacts agentic/artifacts.md

Artifacts Overlay

Use config files as globals and gj_artifacts as the caller-scoped overlay for saved queries, fragments, and workflows.

Globals and user artifacts

GraphJin has one resolution model for saved queries, fragments, and workflows:

  1. Look for a user artifact in gj_artifacts by (kind, name) for the request user_id.
  2. Fall back to the global config file under the configured config path.

Kinds do not mask each other. A saved query named daily_report does not hide a workflow named daily_report.

Global files are the baseline:

text
config/
  queries/
    products.graphql
    fragments/
      product_fields.gql
  workflows/
    nightly-report.js

When an artifact store is configured and the request has user_id, GraphJin stores user-owned edits in gj_artifacts with visibility = "user", owner_id = <user_id>, optional account_id, source = "database", and read_only = false. Config files appear as visibility = "global", source = "config", and read_only = true.

Verified by TestUserArtifactSavedQueryOverridesGlobalOnlyForOwner serv/artifact_overlay_test.go:94
Verified by TestCatalogSnapshotMergesCallerScopedArtifacts serv/artifact_overlay_test.go:173

Configure the store

YAML
sources:
  - name: app
    kind: database
    type: postgres

artifacts:
  enabled: true
  source: app
  schema: _graphjin
  globals_path: ./config
  auto_init: true

With auto_init: true, GraphJin creates _graphjin.artifacts when the service starts. Current updates increment the live artifact row’s revision value in place; GraphJin does not create or expose artifact history rows.

One store, one bounded projection

gj_artifacts is backed by a real SQL table, but GraphJin never hand-writes SQL against it: control-plane reads and writes run back through GraphJin’s own engine under the reserved, non-forgeable __graphjin_internal_store role — the same compiled, validated query machinery that serves your app, across every supported database. Watches persist through the same store.

List and search reads are served from an in-memory nanoDB projection, so discovery costs no database round-trips. The projection is a bounded search index, not a copy of the store: per artifact, content is capped (default 32KB, tunable via artifacts.projection_content_max_bytes) and marked with content_truncated: true; oversized content_json/metadata_json are dropped from the projection rather than truncated into invalid JSON. Execution reads — running a saved query, loading a workflow — always read through to the store, so nothing functional is ever truncated. The projection refreshes immediately on GraphJin-made mutations and via a revision-gated poller (artifacts.poll_seconds) for external writes; an idle system does no projection work.

Verified by TestArtifactProjectionCapsJSONFieldsButStoreKeepsFull serv/artifact_overlay_test.go:345
Verified by TestArtifactProjectionHonorsConfiguredCap serv/artifact_overlay_test.go:395

Writes in development

Named query auto-save keeps the existing development behavior unless both conditions are true:

  • The artifact store is configured.
  • The request context has user_id.

With both conditions, named queries and captured fragments save to gj_artifacts. Without either condition, dev-mode auto-save falls back to the global queries/ and queries/fragments/ files.

Workflow writes use the same rule. gj_workflow and save_workflow write user workflow artifacts when user_id and the store are present. Otherwise they write global workflows/*.js files only in dev fallback mode.

Verified by TestSavedQueryAutoSaveUsesUserArtifactWhenConfigured serv/artifact_overlay_test.go:71
Verified by TestUserArtifactWorkflowOverridesGlobalOnlyForOwner serv/artifact_overlay_test.go:148

Reads and execution

REST saved-query routes, MCP execute_saved_query, the server-side agent, workflow scripts, gj_workflow_execution, and catalog discovery all use the merged resolver. This means a user artifact can override a global saved query or workflow for that caller without changing what another caller sees.

Artifact-backed saved queries can import artifact-backed fragments with the same #import "./fragments/name" syntax as files. Import traversal and absolute paths are rejected by the same resolver used for filesystem saved queries.

Verified by TestArtifactSavedQueryImportsArtifactFragmentsAndRejectsTraversal serv/artifact_overlay_test.go:120
Docs