jfastframework 0.1.0a1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- jfastframework-0.1.0a1/.env.example +37 -0
- jfastframework-0.1.0a1/.github/workflows/ci.yml +125 -0
- jfastframework-0.1.0a1/.github/workflows/pages.yml +121 -0
- jfastframework-0.1.0a1/.github/workflows/release.yml +99 -0
- jfastframework-0.1.0a1/.gitignore +32 -0
- jfastframework-0.1.0a1/.jfast/skills/README.md +65 -0
- jfastframework-0.1.0a1/.jfast/skills/add-rag/SKILL.md +145 -0
- jfastframework-0.1.0a1/.jfast/skills/add-service/SKILL.md +125 -0
- jfastframework-0.1.0a1/.jfast/skills/build-frontend/SKILL.md +172 -0
- jfastframework-0.1.0a1/.jfast/skills/create-module/SKILL.md +126 -0
- jfastframework-0.1.0a1/.jfast/skills/create-plugin/SKILL.md +102 -0
- jfastframework-0.1.0a1/.jfast/skills/design-system/SKILL.md +90 -0
- jfastframework-0.1.0a1/.jfast/skills/respect-contracts/SKILL.md +113 -0
- jfastframework-0.1.0a1/AGENTS.md +189 -0
- jfastframework-0.1.0a1/ARCHITECTURE.md +184 -0
- jfastframework-0.1.0a1/CHANGELOG.md +577 -0
- jfastframework-0.1.0a1/PKG-INFO +471 -0
- jfastframework-0.1.0a1/PLAN-NEXT.md +603 -0
- jfastframework-0.1.0a1/PLAN.md +312 -0
- jfastframework-0.1.0a1/README.md +373 -0
- jfastframework-0.1.0a1/STATUS.md +88 -0
- jfastframework-0.1.0a1/docs/auth.md +275 -0
- jfastframework-0.1.0a1/docs/cloud.md +157 -0
- jfastframework-0.1.0a1/docs/contracts.md +274 -0
- jfastframework-0.1.0a1/docs/datastores.md +140 -0
- jfastframework-0.1.0a1/docs/deploy.md +116 -0
- jfastframework-0.1.0a1/docs/frontend.md +171 -0
- jfastframework-0.1.0a1/docs/kubernetes.md +145 -0
- jfastframework-0.1.0a1/docs/local-setup.md +228 -0
- jfastframework-0.1.0a1/docs/migrations-and-tests.md +147 -0
- jfastframework-0.1.0a1/docs/modules.md +184 -0
- jfastframework-0.1.0a1/docs/multitenancy.md +133 -0
- jfastframework-0.1.0a1/docs/plugins.md +198 -0
- jfastframework-0.1.0a1/docs/queues-and-events.md +191 -0
- jfastframework-0.1.0a1/docs/service-contract.md +156 -0
- jfastframework-0.1.0a1/docs/skills.md +103 -0
- jfastframework-0.1.0a1/docs/storage.md +174 -0
- jfastframework-0.1.0a1/docs/workspaces.md +281 -0
- jfastframework-0.1.0a1/docs-site/assets/BRAND.md +54 -0
- jfastframework-0.1.0a1/docs-site/assets/favicon.svg +17 -0
- jfastframework-0.1.0a1/docs-site/assets/mark.svg +22 -0
- jfastframework-0.1.0a1/docs-site/assets/site.css +586 -0
- jfastframework-0.1.0a1/docs-site/build.py +533 -0
- jfastframework-0.1.0a1/docs-site/check.py +139 -0
- jfastframework-0.1.0a1/examples/README.md +13 -0
- jfastframework-0.1.0a1/examples/hello/jfast.toml +9 -0
- jfastframework-0.1.0a1/examples/hello/main.py +34 -0
- jfastframework-0.1.0a1/jfast.toml +44 -0
- jfastframework-0.1.0a1/legacy/README.v0.md +45 -0
- jfastframework-0.1.0a1/legacy/commands.py +412 -0
- jfastframework-0.1.0a1/pyproject.toml +193 -0
- jfastframework-0.1.0a1/requirements.txt +8 -0
- jfastframework-0.1.0a1/scripts/smoke.sh +156 -0
- jfastframework-0.1.0a1/scripts/smoke_auth_k8s.sh +165 -0
- jfastframework-0.1.0a1/scripts/smoke_contracts.sh +147 -0
- jfastframework-0.1.0a1/scripts/smoke_docs.sh +80 -0
- jfastframework-0.1.0a1/scripts/smoke_frontend.sh +71 -0
- jfastframework-0.1.0a1/scripts/smoke_go.sh +78 -0
- jfastframework-0.1.0a1/scripts/smoke_start.sh +76 -0
- jfastframework-0.1.0a1/scripts/smoke_storage_tenancy.sh +155 -0
- jfastframework-0.1.0a1/scripts/smoke_workspace.sh +175 -0
- jfastframework-0.1.0a1/src/jfastframework/__init__.py +44 -0
- jfastframework-0.1.0a1/src/jfastframework/app.py +155 -0
- jfastframework-0.1.0a1/src/jfastframework/auth/__init__.py +56 -0
- jfastframework-0.1.0a1/src/jfastframework/auth/jwks.py +117 -0
- jfastframework-0.1.0a1/src/jfastframework/auth/oidc.py +268 -0
- jfastframework-0.1.0a1/src/jfastframework/auth/principal.py +64 -0
- jfastframework-0.1.0a1/src/jfastframework/auth/store.py +141 -0
- jfastframework-0.1.0a1/src/jfastframework/auth/tokens.py +197 -0
- jfastframework-0.1.0a1/src/jfastframework/cli/__init__.py +5 -0
- jfastframework-0.1.0a1/src/jfastframework/cli/main.py +1549 -0
- jfastframework-0.1.0a1/src/jfastframework/cli/patcher.py +160 -0
- jfastframework-0.1.0a1/src/jfastframework/cli/scaffold.py +478 -0
- jfastframework-0.1.0a1/src/jfastframework/context.py +69 -0
- jfastframework-0.1.0a1/src/jfastframework/contracts/__init__.py +36 -0
- jfastframework-0.1.0a1/src/jfastframework/contracts/_scan.py +141 -0
- jfastframework-0.1.0a1/src/jfastframework/contracts/blocking.py +425 -0
- jfastframework-0.1.0a1/src/jfastframework/contracts/checker.py +318 -0
- jfastframework-0.1.0a1/src/jfastframework/contracts/model.py +306 -0
- jfastframework-0.1.0a1/src/jfastframework/contracts/render.py +116 -0
- jfastframework-0.1.0a1/src/jfastframework/db/__init__.py +13 -0
- jfastframework-0.1.0a1/src/jfastframework/db/base.py +47 -0
- jfastframework-0.1.0a1/src/jfastframework/db/repository.py +152 -0
- jfastframework-0.1.0a1/src/jfastframework/deploy/__init__.py +17 -0
- jfastframework-0.1.0a1/src/jfastframework/deploy/compose.py +182 -0
- jfastframework-0.1.0a1/src/jfastframework/deploy/kubernetes.py +431 -0
- jfastframework-0.1.0a1/src/jfastframework/deploy/serverless.py +199 -0
- jfastframework-0.1.0a1/src/jfastframework/deploy/workspace.py +256 -0
- jfastframework-0.1.0a1/src/jfastframework/errors.py +141 -0
- jfastframework-0.1.0a1/src/jfastframework/graph.py +109 -0
- jfastframework-0.1.0a1/src/jfastframework/health.py +110 -0
- jfastframework-0.1.0a1/src/jfastframework/languages.py +123 -0
- jfastframework-0.1.0a1/src/jfastframework/middleware.py +169 -0
- jfastframework-0.1.0a1/src/jfastframework/plugins/__init__.py +17 -0
- jfastframework-0.1.0a1/src/jfastframework/plugins/base.py +162 -0
- jfastframework-0.1.0a1/src/jfastframework/plugins/builtin/__init__.py +11 -0
- jfastframework-0.1.0a1/src/jfastframework/plugins/builtin/auth.py +698 -0
- jfastframework-0.1.0a1/src/jfastframework/plugins/builtin/cache.py +148 -0
- jfastframework-0.1.0a1/src/jfastframework/plugins/builtin/database.py +149 -0
- jfastframework-0.1.0a1/src/jfastframework/plugins/builtin/events.py +306 -0
- jfastframework-0.1.0a1/src/jfastframework/plugins/builtin/gateway.py +239 -0
- jfastframework-0.1.0a1/src/jfastframework/plugins/builtin/metrics.py +162 -0
- jfastframework-0.1.0a1/src/jfastframework/plugins/builtin/mongo.py +108 -0
- jfastframework-0.1.0a1/src/jfastframework/plugins/builtin/notifications.py +324 -0
- jfastframework-0.1.0a1/src/jfastframework/plugins/builtin/observability.py +186 -0
- jfastframework-0.1.0a1/src/jfastframework/plugins/builtin/qdrant.py +104 -0
- jfastframework-0.1.0a1/src/jfastframework/plugins/builtin/queue.py +262 -0
- jfastframework-0.1.0a1/src/jfastframework/plugins/builtin/rag.py +308 -0
- jfastframework-0.1.0a1/src/jfastframework/plugins/builtin/sentry.py +74 -0
- jfastframework-0.1.0a1/src/jfastframework/plugins/builtin/storage.py +293 -0
- jfastframework-0.1.0a1/src/jfastframework/plugins/builtin/tenancy.py +222 -0
- jfastframework-0.1.0a1/src/jfastframework/plugins/builtin/web.py +209 -0
- jfastframework-0.1.0a1/src/jfastframework/plugins/registry.py +182 -0
- jfastframework-0.1.0a1/src/jfastframework/py.typed +0 -0
- jfastframework-0.1.0a1/src/jfastframework/queues/__init__.py +10 -0
- jfastframework-0.1.0a1/src/jfastframework/queues/base.py +131 -0
- jfastframework-0.1.0a1/src/jfastframework/queues/postgres.py +211 -0
- jfastframework-0.1.0a1/src/jfastframework/queues/rabbitmq.py +139 -0
- jfastframework-0.1.0a1/src/jfastframework/queues/redis.py +235 -0
- jfastframework-0.1.0a1/src/jfastframework/queues/worker.py +204 -0
- jfastframework-0.1.0a1/src/jfastframework/resources.py +241 -0
- jfastframework-0.1.0a1/src/jfastframework/secrets.py +172 -0
- jfastframework-0.1.0a1/src/jfastframework/settings.py +173 -0
- jfastframework-0.1.0a1/src/jfastframework/sql.py +45 -0
- jfastframework-0.1.0a1/src/jfastframework/storage/__init__.py +38 -0
- jfastframework-0.1.0a1/src/jfastframework/storage/base.py +198 -0
- jfastframework-0.1.0a1/src/jfastframework/storage/local.py +192 -0
- jfastframework-0.1.0a1/src/jfastframework/storage/s3.py +295 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/contracts_layered/contracts.toml.j2 +123 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/contracts_screaming/contracts.toml.j2 +125 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/frontend_react/.env.example.j2 +4 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/frontend_react/.env.j2 +4 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/frontend_react/.env.production.j2 +7 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/frontend_react/.gitignore.j2 +6 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/frontend_react/README.md.j2 +69 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/frontend_react/index.html.j2 +13 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/frontend_react/package.json.j2 +25 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/frontend_react/src/components/BaseIcon.jsx.j2 +13 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/frontend_react/src/components/CardBox.jsx.j2 +9 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/frontend_react/src/components/SectionMain.jsx.j2 +3 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/frontend_react/src/components/SectionTitleLineWithButton.jsx.j2 +18 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/frontend_react/src/layouts/LayoutAuthenticated.jsx.j2 +39 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/frontend_react/src/main.jsx.j2 +12 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/frontend_react/src/menuAside.js.j2 +15 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/frontend_react/src/router/index.jsx.j2 +20 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/frontend_react/src/services/api.js.j2 +29 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/frontend_react/src/style.css.j2 +17 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/frontend_react/src/views/HomeView.jsx.j2 +58 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/frontend_react/vite.config.js.j2 +14 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/frontend_vue/.env.example.j2 +2 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/frontend_vue/.env.j2 +4 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/frontend_vue/.env.production.j2 +7 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/frontend_vue/.gitignore.j2 +6 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/frontend_vue/README.md.j2 +69 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/frontend_vue/index.html.j2 +13 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/frontend_vue/package.json.j2 +25 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/frontend_vue/src/App.vue.j2 +7 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/frontend_vue/src/components/BaseIcon.vue.j2 +22 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/frontend_vue/src/components/CardBox.vue.j2 +5 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/frontend_vue/src/components/SectionMain.vue.j2 +5 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/frontend_vue/src/components/SectionTitleLineWithButton.vue.j2 +22 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/frontend_vue/src/layouts/LayoutAuthenticated.vue.j2 +35 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/frontend_vue/src/main.js.j2 +8 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/frontend_vue/src/menuAside.js.j2 +15 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/frontend_vue/src/router/index.js.j2 +33 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/frontend_vue/src/services/api.js.j2 +29 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/frontend_vue/src/style.css.j2 +17 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/frontend_vue/src/views/HomeView.vue.j2 +53 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/frontend_vue/vite.config.js.j2 +19 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/module_layered/__init__.py.j2 +7 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/module_layered/{{module}}/README.md.j2 +48 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/module_layered/{{module}}/__init__.py.j2 +22 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/module_layered/{{module}}/models.py.j2 +19 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/module_layered/{{module}}/repository.py.j2 +22 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/module_layered/{{module}}/router.py.j2 +80 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/module_layered/{{module}}/schemas.py.j2 +35 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/module_layered/{{module}}/service.py.j2 +39 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/module_layered/{{module}}/tests/__init__.py.j2 +7 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/module_layered/{{module}}/tests/test_{{module}}.py.j2 +103 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/module_screaming/__init__.py.j2 +7 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/module_screaming/{{module}}/README.md.j2 +66 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/module_screaming/{{module}}/__init__.py.j2 +30 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/module_screaming/{{module}}/http.py.j2 +102 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/module_screaming/{{module}}/storage.py.j2 +41 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/module_screaming/{{module}}/tests/__init__.py.j2 +6 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/module_screaming/{{module}}/tests/test_{{module}}_domain.py.j2 +45 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/module_screaming/{{module}}/tests/test_{{module}}_use_cases.py.j2 +137 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/module_screaming/{{module}}/use_cases/__init__.py.j2 +54 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/module_screaming/{{module}}/use_cases/create_{{module}}.py.j2 +33 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/module_screaming/{{module}}/use_cases/delete_{{module}}.py.j2 +10 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/module_screaming/{{module}}/use_cases/get_{{module}}.py.j2 +10 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/module_screaming/{{module}}/use_cases/list_{{module}}.py.j2 +12 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/module_screaming/{{module}}/use_cases/update_{{module}}.py.j2 +30 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/module_screaming/{{module}}/{{module}}.py.j2 +51 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/proto/proto/README.md.j2 +60 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/proto/proto/{{service_slug}}.proto.j2 +96 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/service_base/.env.example.j2 +66 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/service_base/.gitignore.j2 +10 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/service_base/README.md.j2 +101 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/service_base/alembic.ini.j2 +44 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/service_base/conftest.py.j2 +27 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/service_base/jfast.toml.j2 +122 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/service_base/main.py.j2 +30 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/service_base/migrations/env.py.j2 +105 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/service_base/migrations/script.py.mako.j2 +31 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/service_base/migrations/versions/.gitkeep.j2 +0 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/service_base/pytest.ini.j2 +6 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/service_base/requirements.txt.j2 +2 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/service_gateway/.env.example.j2 +9 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/service_gateway/README.md.j2 +56 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/service_gateway/jfast.toml.j2 +31 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/service_gateway/main.py.j2 +16 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/service_gateway/requirements.txt.j2 +1 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/service_go/.env.example.j2 +17 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/service_go/.gitignore.j2 +4 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/service_go/Dockerfile.j2 +25 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/service_go/README.md.j2 +81 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/service_go/go.mod.j2 +3 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/service_go/internal/jfast/config.go.j2 +66 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/service_go/internal/jfast/health.go.j2 +112 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/service_go/internal/jfast/jfast_test.go.j2 +167 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/service_go/internal/jfast/middleware.go.j2 +120 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/service_go/internal/jfast/problem.go.j2 +59 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/service_go/internal/modules/{{module}}/helpers.go.j2 +26 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/service_go/internal/modules/{{module}}/module.go.j2 +155 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/service_go/internal/modules/{{module}}/module_test.go.j2 +84 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/service_go/jfast.service.toml.j2 +14 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/service_go/main.go.j2 +78 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/service_web/static/app.css.j2 +116 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/service_web/templates/base.html.j2 +37 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/service_web/templates/index.html.j2 +19 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/service_web/web.py.j2 +18 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/ui_htmx/templates/{{module}}/_row.html.j2 +21 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/ui_htmx/templates/{{module}}/_rows.html.j2 +13 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/ui_htmx/templates/{{module}}/index.html.j2 +43 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/ui_htmx/{{modules_dir}}/{{module}}/web.py.j2 +65 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/view_react/src/Modulo{{View}}/Components/Modals/.gitkeep.j2 +0 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/view_react/src/Modulo{{View}}/Components/README.md.j2 +12 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/view_react/src/Modulo{{View}}/Components/Tables/.gitkeep.j2 +0 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/view_react/src/Modulo{{View}}/Pages/{{View}}View.jsx.j2 +70 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/view_react/src/Modulo{{View}}/Routes/router.jsx.j2 +10 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/view_react/src/Modulo{{View}}/Services/{{view_slug}}.service.js.j2 +34 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/view_vue/src/Modulo{{View}}/Components/Modals/.gitkeep.j2 +0 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/view_vue/src/Modulo{{View}}/Components/README.md.j2 +12 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/view_vue/src/Modulo{{View}}/Components/Tables/.gitkeep.j2 +0 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/view_vue/src/Modulo{{View}}/Pages/{{View}}View.vue.j2 +60 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/view_vue/src/Modulo{{View}}/Routes/router.js.j2 +10 -0
- jfastframework-0.1.0a1/src/jfastframework/templates/view_vue/src/Modulo{{View}}/Services/{{view_slug}}.service.js.j2 +34 -0
- jfastframework-0.1.0a1/src/jfastframework/testing/__init__.py +25 -0
- jfastframework-0.1.0a1/src/jfastframework/testing/fixtures.py +102 -0
- jfastframework-0.1.0a1/src/jfastframework/vectors/__init__.py +8 -0
- jfastframework-0.1.0a1/src/jfastframework/vectors/base.py +85 -0
- jfastframework-0.1.0a1/src/jfastframework/vectors/pgvector.py +147 -0
- jfastframework-0.1.0a1/src/jfastframework/vectors/qdrant.py +155 -0
- jfastframework-0.1.0a1/src/jfastframework/workspace.py +517 -0
- jfastframework-0.1.0a1/tests/test_app.py +105 -0
- jfastframework-0.1.0a1/tests/test_auth.py +435 -0
- jfastframework-0.1.0a1/tests/test_blocking.py +228 -0
- jfastframework-0.1.0a1/tests/test_contracts.py +276 -0
- jfastframework-0.1.0a1/tests/test_deploy.py +67 -0
- jfastframework-0.1.0a1/tests/test_edge.py +153 -0
- jfastframework-0.1.0a1/tests/test_gateway.py +185 -0
- jfastframework-0.1.0a1/tests/test_kubernetes.py +189 -0
- jfastframework-0.1.0a1/tests/test_notifications.py +141 -0
- jfastframework-0.1.0a1/tests/test_oidc.py +351 -0
- jfastframework-0.1.0a1/tests/test_patcher.py +138 -0
- jfastframework-0.1.0a1/tests/test_queue.py +266 -0
- jfastframework-0.1.0a1/tests/test_rag_store_selection.py +83 -0
- jfastframework-0.1.0a1/tests/test_redis_queue.py +272 -0
- jfastframework-0.1.0a1/tests/test_registry.py +96 -0
- jfastframework-0.1.0a1/tests/test_repository.py +153 -0
- jfastframework-0.1.0a1/tests/test_resources.py +241 -0
- jfastframework-0.1.0a1/tests/test_scaffold.py +114 -0
- jfastframework-0.1.0a1/tests/test_secrets_and_serverless.py +195 -0
- jfastframework-0.1.0a1/tests/test_settings.py +61 -0
- jfastframework-0.1.0a1/tests/test_storage.py +326 -0
- jfastframework-0.1.0a1/tests/test_tenancy.py +268 -0
- jfastframework-0.1.0a1/tests/test_web.py +107 -0
- jfastframework-0.1.0a1/tests/test_workspace.py +131 -0
- jfastframework-0.1.0a1/tests/test_workspace_deploy.py +143 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Copy to .env and fill in. Never commit .env.
|
|
2
|
+
|
|
3
|
+
# --- kernel ---
|
|
4
|
+
JFAST_APP_NAME=jfast-service
|
|
5
|
+
JFAST_ENV=local # local | dev | staging | prod
|
|
6
|
+
JFAST_DEBUG=false # true leaks exception messages to clients
|
|
7
|
+
JFAST_PORT=8000
|
|
8
|
+
|
|
9
|
+
# --- edge protections (all off unless set) ---
|
|
10
|
+
# JFAST_READINESS_TIMEOUT=2.0 # seconds one plugin gets to answer /ready
|
|
11
|
+
# JFAST_CORS_ORIGINS=["https://app.example.com"]
|
|
12
|
+
# JFAST_CORS_ALLOW_CREDENTIALS=false
|
|
13
|
+
# JFAST_TRUSTED_HOSTS=["api.example.com"]
|
|
14
|
+
# JFAST_MAX_BODY_BYTES=10485760 # 10 MiB; unset means no limit
|
|
15
|
+
# JFAST_REQUEST_TIMEOUT=30 # seconds before a request is answered 504
|
|
16
|
+
# JFAST_DOCS_URL=/docs # closed in prod unless set explicitly
|
|
17
|
+
|
|
18
|
+
# --- observability plugin ---
|
|
19
|
+
JFAST_LOG_LEVEL=INFO
|
|
20
|
+
JFAST_LOG_JSON_LOGS=true
|
|
21
|
+
|
|
22
|
+
# --- database plugin (extra: jfastframework[db]) ---
|
|
23
|
+
JFAST_DB_DSN=postgresql+asyncpg://app:CHANGEME@localhost:5432/app
|
|
24
|
+
JFAST_DB_POOL_SIZE=10
|
|
25
|
+
POSTGRES_PASSWORD=CHANGEME
|
|
26
|
+
|
|
27
|
+
# --- cache plugin (extra: jfastframework[cache]) ---
|
|
28
|
+
JFAST_CACHE_URL=redis://localhost:6379/0
|
|
29
|
+
|
|
30
|
+
# --- sentry plugin (extra: jfastframework[sentry]) ---
|
|
31
|
+
# JFAST_SENTRY_DSN=
|
|
32
|
+
JFAST_SENTRY_TRACES_SAMPLE_RATE=0.1
|
|
33
|
+
|
|
34
|
+
# --- rag plugin (extra: jfastframework[rag,db]) ---
|
|
35
|
+
JFAST_RAG_DIMENSIONS=768
|
|
36
|
+
JFAST_RAG_OLLAMA_URL=http://localhost:11434
|
|
37
|
+
JFAST_RAG_OLLAMA_MODEL=nomic-embed-text
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
check:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
fail-fast: false
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: ${{ matrix.python-version }}
|
|
22
|
+
cache: pip
|
|
23
|
+
|
|
24
|
+
- name: Install
|
|
25
|
+
# setuptools is upgraded first. It is not a dependency of this
|
|
26
|
+
# package -- hatchling does the building -- but setup-python ships one
|
|
27
|
+
# in the runner's environment, pip-audit sees it, and the version it
|
|
28
|
+
# ships has lagged behind published advisories. Upgrading is the honest
|
|
29
|
+
# fix; waiving a real advisory because it is inconvenient is not.
|
|
30
|
+
run: |
|
|
31
|
+
python -m pip install --upgrade pip setuptools wheel
|
|
32
|
+
pip install -e ".[all,dev]"
|
|
33
|
+
|
|
34
|
+
- name: Lint
|
|
35
|
+
run: ruff check src tests
|
|
36
|
+
|
|
37
|
+
- name: Format
|
|
38
|
+
run: ruff format --check src tests
|
|
39
|
+
|
|
40
|
+
- name: Types
|
|
41
|
+
run: mypy src
|
|
42
|
+
|
|
43
|
+
- name: Tests
|
|
44
|
+
run: pytest -q
|
|
45
|
+
|
|
46
|
+
# A framework shipping opinionated auth and storage defaults should audit
|
|
47
|
+
# its own dependency tree. Both are hard gates.
|
|
48
|
+
#
|
|
49
|
+
# When a finding lands on an unrelated pull request, the order is: try
|
|
50
|
+
# upgrading the package first, and only if it cannot be upgraded, waive
|
|
51
|
+
# it here explicitly -- `pip-audit --ignore-vuln PYSEC-0000-000` with a
|
|
52
|
+
# comment saying which code path makes it inapplicable. Same for bandit
|
|
53
|
+
# with `# nosec BXXX` and the reason on the line above. What must not
|
|
54
|
+
# happen is a step going yellow and being ignored, which is how a
|
|
55
|
+
# security check stops meaning anything.
|
|
56
|
+
- name: Audit dependencies
|
|
57
|
+
# --skip-editable: this project is installed from the checkout and is
|
|
58
|
+
# not on PyPI, so there is nothing to look up for it.
|
|
59
|
+
run: pip-audit --skip-editable --desc
|
|
60
|
+
|
|
61
|
+
- name: Static security scan
|
|
62
|
+
run: bandit -c pyproject.toml -r src -ll
|
|
63
|
+
|
|
64
|
+
# The generator is the part most likely to break silently: templates
|
|
65
|
+
# render fine but produce code that does not import, or a Jinja
|
|
66
|
+
# environment eats the runtime braces a Vue file needs. Both smoke
|
|
67
|
+
# scripts render real trees and run what they produce.
|
|
68
|
+
- name: Scaffold smoke
|
|
69
|
+
run: bash scripts/smoke.sh
|
|
70
|
+
|
|
71
|
+
- name: Contracts smoke
|
|
72
|
+
run: bash scripts/smoke_contracts.sh
|
|
73
|
+
|
|
74
|
+
- name: Auth and Kubernetes smoke
|
|
75
|
+
run: bash scripts/smoke_auth_k8s.sh
|
|
76
|
+
|
|
77
|
+
- name: Storage, tenancy and serverless smoke
|
|
78
|
+
run: bash scripts/smoke_storage_tenancy.sh
|
|
79
|
+
|
|
80
|
+
- name: Workspace smoke
|
|
81
|
+
run: bash scripts/smoke_workspace.sh
|
|
82
|
+
|
|
83
|
+
- name: Quickstart smoke
|
|
84
|
+
# The documented commands, run exactly as documented. Docs that have
|
|
85
|
+
# never been executed are a guess.
|
|
86
|
+
run: bash scripts/smoke_docs.sh
|
|
87
|
+
|
|
88
|
+
- name: Docs site builds and validates
|
|
89
|
+
run: |
|
|
90
|
+
pip install -e ".[docs]"
|
|
91
|
+
python docs-site/build.py --version ci --output site/ci
|
|
92
|
+
python docs-site/check.py site/ci
|
|
93
|
+
|
|
94
|
+
# Generated projects in other languages get their own job, because the
|
|
95
|
+
# point of generating them is that they build. A scaffold nobody has run
|
|
96
|
+
# is a liability that looks like a feature.
|
|
97
|
+
go:
|
|
98
|
+
runs-on: ubuntu-latest
|
|
99
|
+
steps:
|
|
100
|
+
- uses: actions/checkout@v4
|
|
101
|
+
- uses: actions/setup-python@v5
|
|
102
|
+
with:
|
|
103
|
+
python-version: "3.12"
|
|
104
|
+
cache: pip
|
|
105
|
+
- uses: actions/setup-go@v5
|
|
106
|
+
with:
|
|
107
|
+
go-version: "1.23"
|
|
108
|
+
- run: pip install -e ".[all,dev]"
|
|
109
|
+
- name: Generate, vet, test, build and run a Go service
|
|
110
|
+
run: bash scripts/smoke_go.sh
|
|
111
|
+
|
|
112
|
+
frontend:
|
|
113
|
+
runs-on: ubuntu-latest
|
|
114
|
+
steps:
|
|
115
|
+
- uses: actions/checkout@v4
|
|
116
|
+
- uses: actions/setup-python@v5
|
|
117
|
+
with:
|
|
118
|
+
python-version: "3.12"
|
|
119
|
+
cache: pip
|
|
120
|
+
- uses: actions/setup-node@v4
|
|
121
|
+
with:
|
|
122
|
+
node-version: "22"
|
|
123
|
+
- run: pip install -e ".[all,dev]"
|
|
124
|
+
- name: Generate, install and build the Vue and React frontends
|
|
125
|
+
run: bash scripts/smoke_frontend.sh
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
name: Docs site
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
tags: ["v*"]
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
# Only one deploy at a time, and never cancel one halfway: a cancelled Pages
|
|
10
|
+
# deploy can leave the site partially replaced.
|
|
11
|
+
concurrency:
|
|
12
|
+
group: pages
|
|
13
|
+
cancel-in-progress: false
|
|
14
|
+
|
|
15
|
+
permissions:
|
|
16
|
+
contents: read
|
|
17
|
+
pages: write
|
|
18
|
+
id-token: write
|
|
19
|
+
|
|
20
|
+
jobs:
|
|
21
|
+
build:
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@v4
|
|
25
|
+
with:
|
|
26
|
+
# Tags are needed to build the version picker.
|
|
27
|
+
fetch-depth: 0
|
|
28
|
+
|
|
29
|
+
- uses: actions/setup-python@v5
|
|
30
|
+
with:
|
|
31
|
+
python-version: "3.12"
|
|
32
|
+
cache: pip
|
|
33
|
+
|
|
34
|
+
- name: Install
|
|
35
|
+
run: pip install -e ".[docs]"
|
|
36
|
+
|
|
37
|
+
- name: Work out the version
|
|
38
|
+
id: version
|
|
39
|
+
run: |
|
|
40
|
+
if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
|
|
41
|
+
echo "name=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT"
|
|
42
|
+
else
|
|
43
|
+
echo "name=latest" >> "$GITHUB_OUTPUT"
|
|
44
|
+
fi
|
|
45
|
+
# Minor-series directories: v0.3 rather than v0.3.0, so a patch
|
|
46
|
+
# release replaces its predecessor instead of littering the picker.
|
|
47
|
+
VERSIONS=$(git tag --list 'v*' --sort=-v:refname \
|
|
48
|
+
| sed -E 's/^(v[0-9]+\.[0-9]+).*/\1/' | uniq | head -8 | paste -sd, -)
|
|
49
|
+
echo "all=latest${VERSIONS:+,$VERSIONS}" >> "$GITHUB_OUTPUT"
|
|
50
|
+
|
|
51
|
+
- name: Build latest
|
|
52
|
+
run: |
|
|
53
|
+
python docs-site/build.py \
|
|
54
|
+
--version latest \
|
|
55
|
+
--versions "${{ steps.version.outputs.all }}" \
|
|
56
|
+
--output site/latest
|
|
57
|
+
|
|
58
|
+
- name: Build every released version
|
|
59
|
+
# The Pages artifact replaces the whole site, so previous versions have
|
|
60
|
+
# to be rebuilt here or they vanish on the next deploy. Each one is
|
|
61
|
+
# built from its own tag, which is the point of versioned docs:
|
|
62
|
+
# someone pinned to 0.3 should read 0.3's documentation.
|
|
63
|
+
run: |
|
|
64
|
+
for MINOR in $(git tag --list 'v*' --sort=-v:refname \
|
|
65
|
+
| sed -E 's/^(v[0-9]+\.[0-9]+).*/\1/' | uniq | head -8); do
|
|
66
|
+
TAG=$(git tag --list "${MINOR}*" --sort=-v:refname | head -1)
|
|
67
|
+
echo "::group::${MINOR} (from ${TAG})"
|
|
68
|
+
rm -rf .worktree
|
|
69
|
+
git worktree add --detach .worktree "${TAG}" >/dev/null
|
|
70
|
+
if [ -f .worktree/docs-site/build.py ]; then
|
|
71
|
+
(cd .worktree && python docs-site/build.py \
|
|
72
|
+
--version "${MINOR}" \
|
|
73
|
+
--versions "${{ steps.version.outputs.all }}" \
|
|
74
|
+
--output "../site/${MINOR}")
|
|
75
|
+
else
|
|
76
|
+
echo "no docs-site/ at ${TAG}; skipping"
|
|
77
|
+
fi
|
|
78
|
+
git worktree remove --force .worktree
|
|
79
|
+
echo "::endgroup::"
|
|
80
|
+
done
|
|
81
|
+
|
|
82
|
+
- name: Check every built version
|
|
83
|
+
# A docs site fails quietly: a broken link still renders as a page.
|
|
84
|
+
run: |
|
|
85
|
+
FAILED=0
|
|
86
|
+
for DIR in site/*/; do
|
|
87
|
+
echo "checking ${DIR}"
|
|
88
|
+
python docs-site/check.py "${DIR}" || FAILED=1
|
|
89
|
+
done
|
|
90
|
+
exit "${FAILED}"
|
|
91
|
+
|
|
92
|
+
- name: Root redirect to latest
|
|
93
|
+
run: |
|
|
94
|
+
cat > site/index.html <<'HTML'
|
|
95
|
+
<!doctype html>
|
|
96
|
+
<html lang="en">
|
|
97
|
+
<head>
|
|
98
|
+
<meta charset="utf-8">
|
|
99
|
+
<title>JFastFramework</title>
|
|
100
|
+
<link rel="canonical" href="latest/">
|
|
101
|
+
<meta http-equiv="refresh" content="0; url=latest/">
|
|
102
|
+
</head>
|
|
103
|
+
<body><p><a href="latest/">JFastFramework documentation</a></p></body>
|
|
104
|
+
</html>
|
|
105
|
+
HTML
|
|
106
|
+
# Jekyll would otherwise skip files and directories starting with _.
|
|
107
|
+
touch site/.nojekyll
|
|
108
|
+
|
|
109
|
+
- uses: actions/upload-pages-artifact@v3
|
|
110
|
+
with:
|
|
111
|
+
path: site
|
|
112
|
+
|
|
113
|
+
deploy:
|
|
114
|
+
needs: build
|
|
115
|
+
runs-on: ubuntu-latest
|
|
116
|
+
environment:
|
|
117
|
+
name: github-pages
|
|
118
|
+
url: ${{ steps.deploy.outputs.page_url }}
|
|
119
|
+
steps:
|
|
120
|
+
- id: deploy
|
|
121
|
+
uses: actions/deploy-pages@v4
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Publishing runs through PyPI's trusted publishing: GitHub mints a short-lived
|
|
4
|
+
# OIDC token for this exact workflow in this exact repository, and PyPI trades
|
|
5
|
+
# it for an upload. No API token is stored anywhere, so there is none to leak
|
|
6
|
+
# and none to rotate.
|
|
7
|
+
#
|
|
8
|
+
# Registering it on PyPI means filling in, exactly:
|
|
9
|
+
#
|
|
10
|
+
# Owner JFabrizzio5
|
|
11
|
+
# Repository name JFastFramework
|
|
12
|
+
# Workflow name release.yml
|
|
13
|
+
# Environment name pypi (testpypi, on test.pypi.org)
|
|
14
|
+
#
|
|
15
|
+
# The environment names below have to match what is registered there, or the
|
|
16
|
+
# token is minted for a claim PyPI will not accept.
|
|
17
|
+
|
|
18
|
+
on:
|
|
19
|
+
workflow_dispatch:
|
|
20
|
+
inputs:
|
|
21
|
+
target:
|
|
22
|
+
description: Where to publish
|
|
23
|
+
type: choice
|
|
24
|
+
options: [testpypi, pypi]
|
|
25
|
+
default: testpypi
|
|
26
|
+
push:
|
|
27
|
+
tags: ["v*"]
|
|
28
|
+
|
|
29
|
+
permissions:
|
|
30
|
+
contents: read
|
|
31
|
+
|
|
32
|
+
jobs:
|
|
33
|
+
build:
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
steps:
|
|
36
|
+
- uses: actions/checkout@v4
|
|
37
|
+
|
|
38
|
+
- uses: actions/setup-python@v5
|
|
39
|
+
with:
|
|
40
|
+
python-version: "3.12"
|
|
41
|
+
|
|
42
|
+
- name: Build sdist and wheel
|
|
43
|
+
run: pipx run build
|
|
44
|
+
|
|
45
|
+
# --strict fails on a malformed long description, which is the failure
|
|
46
|
+
# that otherwise shows up as a blank project page after the upload has
|
|
47
|
+
# already happened and the version is burned.
|
|
48
|
+
- name: Check the metadata
|
|
49
|
+
run: pipx run twine check --strict dist/*
|
|
50
|
+
|
|
51
|
+
# A sdist that does not install is only discovered by someone else.
|
|
52
|
+
- name: Install the built wheel and run the CLI
|
|
53
|
+
run: |
|
|
54
|
+
python -m venv /tmp/probe
|
|
55
|
+
/tmp/probe/bin/pip install --quiet dist/*.whl
|
|
56
|
+
/tmp/probe/bin/jfast version
|
|
57
|
+
/tmp/probe/bin/python -c "import jfastframework; print(jfastframework.__version__)"
|
|
58
|
+
|
|
59
|
+
- uses: actions/upload-artifact@v4
|
|
60
|
+
with:
|
|
61
|
+
name: dist
|
|
62
|
+
path: dist/
|
|
63
|
+
|
|
64
|
+
testpypi:
|
|
65
|
+
needs: build
|
|
66
|
+
if: github.event_name == 'workflow_dispatch' && inputs.target == 'testpypi'
|
|
67
|
+
runs-on: ubuntu-latest
|
|
68
|
+
environment: testpypi
|
|
69
|
+
permissions:
|
|
70
|
+
id-token: write
|
|
71
|
+
steps:
|
|
72
|
+
- uses: actions/download-artifact@v4
|
|
73
|
+
with:
|
|
74
|
+
name: dist
|
|
75
|
+
path: dist/
|
|
76
|
+
|
|
77
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
78
|
+
with:
|
|
79
|
+
repository-url: https://test.pypi.org/legacy/
|
|
80
|
+
# TestPyPI keeps old uploads from experiments; re-running a build
|
|
81
|
+
# that produced identical files should not fail the job.
|
|
82
|
+
skip-existing: true
|
|
83
|
+
|
|
84
|
+
pypi:
|
|
85
|
+
needs: build
|
|
86
|
+
if: >-
|
|
87
|
+
github.event_name == 'push'
|
|
88
|
+
|| (github.event_name == 'workflow_dispatch' && inputs.target == 'pypi')
|
|
89
|
+
runs-on: ubuntu-latest
|
|
90
|
+
environment: pypi
|
|
91
|
+
permissions:
|
|
92
|
+
id-token: write
|
|
93
|
+
steps:
|
|
94
|
+
- uses: actions/download-artifact@v4
|
|
95
|
+
with:
|
|
96
|
+
name: dist
|
|
97
|
+
path: dist/
|
|
98
|
+
|
|
99
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.eggs/
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
.venv/
|
|
9
|
+
venv/
|
|
10
|
+
.python-version
|
|
11
|
+
|
|
12
|
+
# Tooling
|
|
13
|
+
.pytest_cache/
|
|
14
|
+
.ruff_cache/
|
|
15
|
+
.mypy_cache/
|
|
16
|
+
.coverage
|
|
17
|
+
htmlcov/
|
|
18
|
+
|
|
19
|
+
# Env
|
|
20
|
+
.env
|
|
21
|
+
.env.local
|
|
22
|
+
.env.*.local
|
|
23
|
+
|
|
24
|
+
# Generated deploy artifacts
|
|
25
|
+
/generated/
|
|
26
|
+
docker-compose.generated.yml
|
|
27
|
+
|
|
28
|
+
# OS
|
|
29
|
+
.DS_Store
|
|
30
|
+
|
|
31
|
+
# Generated documentation site
|
|
32
|
+
/site/
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Skills
|
|
2
|
+
|
|
3
|
+
A skill is a folder holding one `SKILL.md`: instructions for a task an agent
|
|
4
|
+
performs repeatedly in this codebase.
|
|
5
|
+
|
|
6
|
+
The point is selective loading. An agent should not read the entire manual to
|
|
7
|
+
add one module — it reads the front matter of each skill, picks the one that
|
|
8
|
+
matches, and loads only that file.
|
|
9
|
+
|
|
10
|
+
## Layout
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
.jfast/skills/
|
|
14
|
+
├── README.md
|
|
15
|
+
├── add-service/SKILL.md add a service: datastores, ports, gateway
|
|
16
|
+
├── respect-contracts/SKILL.md read the project contract, then verify against it
|
|
17
|
+
├── create-module/SKILL.md scaffold a domain module: layout, UI, wiring
|
|
18
|
+
├── create-plugin/SKILL.md build a plugin that adds a capability
|
|
19
|
+
├── build-frontend/SKILL.md UI: server-rendered HTMX, or a Vue/React SPA
|
|
20
|
+
├── add-rag/SKILL.md semantic search over pgvector or Qdrant
|
|
21
|
+
└── design-system/SKILL.md apply a DESIGN.md to generated UI
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The first decision an agent makes is usually **service or module**, and getting
|
|
25
|
+
it wrong is expensive in one direction only: a module promoted to a service
|
|
26
|
+
later is a move, while a premature service is a network hop, a second deploy
|
|
27
|
+
target and an integration test nobody asked for. `add-service` opens with that
|
|
28
|
+
question for exactly that reason.
|
|
29
|
+
|
|
30
|
+
## Format
|
|
31
|
+
|
|
32
|
+
Front matter, then the steps.
|
|
33
|
+
|
|
34
|
+
```markdown
|
|
35
|
+
---
|
|
36
|
+
name: create-module
|
|
37
|
+
description: Scaffold a domain module. Use when adding a new business entity
|
|
38
|
+
with its own table and endpoints.
|
|
39
|
+
when_to_use: The user asks for a new resource, entity, CRUD surface, or table.
|
|
40
|
+
when_not_to_use: The change fits inside an existing module, or adds a
|
|
41
|
+
cross-cutting capability (that is a plugin).
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Steps
|
|
45
|
+
1. ...
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`description` and `when_to_use` are the routing signal. Write them so an agent
|
|
49
|
+
can decide from those two lines alone whether to open the file.
|
|
50
|
+
|
|
51
|
+
## Rules
|
|
52
|
+
|
|
53
|
+
- One task per skill. If a skill has two "or else" branches, it is two skills.
|
|
54
|
+
- Give exact commands, not descriptions of commands.
|
|
55
|
+
- State the verification step. A skill that does not say how to check its own
|
|
56
|
+
output produces work nobody validated.
|
|
57
|
+
- Keep it under 150 lines. Longer means the task is not decomposed.
|
|
58
|
+
|
|
59
|
+
## Related conventions
|
|
60
|
+
|
|
61
|
+
- `DESIGN.md` next to a module or frontend describes the visual language:
|
|
62
|
+
palette, type scale, spacing, component patterns. The `design-system` skill
|
|
63
|
+
consumes it.
|
|
64
|
+
- `AGENTS.md` at the repository root holds rules that apply to every task.
|
|
65
|
+
Skills hold rules for one task. Do not duplicate between them.
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: add-rag
|
|
3
|
+
description: Enable semantic search over pgvector or Qdrant, with a local
|
|
4
|
+
Ollama embedder or a custom one.
|
|
5
|
+
when_to_use: The user wants semantic search, a knowledge base, "chat with my
|
|
6
|
+
documents", or retrieval to ground an LLM answer.
|
|
7
|
+
when_not_to_use: Exact-match or keyword search is enough — use a PostgreSQL
|
|
8
|
+
index or full-text search. RAG is not a substitute for a WHERE clause.
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## What you get
|
|
12
|
+
|
|
13
|
+
`POST /rag/documents` (ingest), `POST /rag/search`, `DELETE /rag/documents/{id}`,
|
|
14
|
+
plus `rag.store` and `rag.embedder` providers for code that needs them
|
|
15
|
+
directly.
|
|
16
|
+
|
|
17
|
+
## Known limits — say these before building on it
|
|
18
|
+
|
|
19
|
+
- Chunking is fixed-size with overlap. Fine for prose, poor for code and tables.
|
|
20
|
+
- No reranking. Top-k by cosine similarity only.
|
|
21
|
+
- No hybrid search. Pure vector, no BM25 blend.
|
|
22
|
+
- `auto_migrate` runs DDL at startup. Fine in development; move to migrations
|
|
23
|
+
before production.
|
|
24
|
+
|
|
25
|
+
If the use case needs better retrieval than that, say so rather than shipping
|
|
26
|
+
this and calling it done.
|
|
27
|
+
|
|
28
|
+
## Step 1: choose the store
|
|
29
|
+
|
|
30
|
+
| | pgvector (default) | Qdrant |
|
|
31
|
+
| --- | --- | --- |
|
|
32
|
+
| Operational cost | none — the database you already run | a second service to run and back up |
|
|
33
|
+
| Scale | comfortable to a few million chunks | far beyond |
|
|
34
|
+
| Filtering | tenant id, plus whatever SQL you write | rich indexed payload filters |
|
|
35
|
+
|
|
36
|
+
**Start with pgvector.** Move to Qdrant when you hit a wall you can name —
|
|
37
|
+
filter complexity, index build time, memory. "It might scale better" is not
|
|
38
|
+
that wall.
|
|
39
|
+
|
|
40
|
+
## Step 2: enable the plugins
|
|
41
|
+
|
|
42
|
+
pgvector:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install -e ".[rag,db]"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
```toml
|
|
49
|
+
[plugins]
|
|
50
|
+
enabled = ["observability", "database", "rag"]
|
|
51
|
+
|
|
52
|
+
[plugin.rag]
|
|
53
|
+
store = "pgvector"
|
|
54
|
+
collection = "rag_chunks"
|
|
55
|
+
dimensions = 768
|
|
56
|
+
chunk_size = 1000
|
|
57
|
+
chunk_overlap = 150
|
|
58
|
+
top_k = 5
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Qdrant:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pip install -e ".[rag,qdrant]"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```toml
|
|
68
|
+
[plugins]
|
|
69
|
+
enabled = ["observability", "qdrant", "rag"]
|
|
70
|
+
|
|
71
|
+
[plugin.rag]
|
|
72
|
+
store = "qdrant"
|
|
73
|
+
collection = "rag_chunks"
|
|
74
|
+
dimensions = 768
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Getting this pairing wrong fails at startup with the fix in the message, not at
|
|
78
|
+
the first search:
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
rag store 'qdrant' needs the 'qdrant' plugin. Add "qdrant" to [plugins].enabled.
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Step 3: match dimensions to the model
|
|
85
|
+
|
|
86
|
+
The mistake that costs an afternoon. `nomic-embed-text` is 768;
|
|
87
|
+
`mxbai-embed-large` is 1024. A mismatch fails at insert, and changing it later
|
|
88
|
+
means re-embedding everything already ingested. Decide before the first ingest.
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
ollama pull nomic-embed-text
|
|
92
|
+
curl -s localhost:11434/api/tags | jq -r '.models[].name'
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
For pgvector, the image must carry the extension — `pgvector/pgvector:pg16`,
|
|
96
|
+
which the `database` plugin already defaults to.
|
|
97
|
+
|
|
98
|
+
## Step 4: ingest and search
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
curl -X POST localhost:8000/rag/documents \
|
|
102
|
+
-H 'content-type: application/json' \
|
|
103
|
+
-d '{"document_id":"doc-1","content":"...","metadata":{"source":"manual"}}'
|
|
104
|
+
|
|
105
|
+
curl -X POST localhost:8000/rag/search \
|
|
106
|
+
-H 'content-type: application/json' \
|
|
107
|
+
-d '{"query":"how do refunds work","limit":5}'
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Re-ingesting a document replaces its chunks in both stores — pgvector deletes
|
|
111
|
+
by `document_id`, Qdrant derives point ids deterministically from
|
|
112
|
+
`(document_id, chunk_index)`.
|
|
113
|
+
|
|
114
|
+
## Swapping the embedder
|
|
115
|
+
|
|
116
|
+
Implement the protocol — a `dimensions` attribute and an async
|
|
117
|
+
`embed(texts) -> list[list[float]]` — then:
|
|
118
|
+
|
|
119
|
+
```toml
|
|
120
|
+
[plugin.rag]
|
|
121
|
+
embedder = "myapp.embeddings:OpenAIEmbedder"
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
A custom store works the same way (`store = "myapp.stores:MyStore"`); it is
|
|
125
|
+
constructed with the `AppContext`, so it can pull its client from the provider
|
|
126
|
+
registry.
|
|
127
|
+
|
|
128
|
+
## Multi-tenancy
|
|
129
|
+
|
|
130
|
+
Pass `tenant_id` on ingest and search. Both stores filter on it and index it.
|
|
131
|
+
Until row-level security lands (PLAN.md phase 2) that filter is a convention:
|
|
132
|
+
a caller that omits `tenant_id` searches across tenants. If the data is
|
|
133
|
+
tenant-sensitive, set the value server-side from the JWT — never from the
|
|
134
|
+
request body.
|
|
135
|
+
|
|
136
|
+
## Verification
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
jfast doctor
|
|
140
|
+
curl -s localhost:8000/ready | jq '.checks.rag'
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Then ingest one known document and search for a phrase inside it. If the top
|
|
144
|
+
result is not that document, the embedder or the dimensions are wrong — check
|
|
145
|
+
those before touching `chunk_size`.
|