gina 0.6.7 → 0.6.8
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.
- package/CHANGELOG.md +22 -0
- package/README.md +27 -26
- package/ROADMAP.md +3 -2
- package/framework/v0.6.8/VERSION +1 -0
- package/framework/{v0.6.7 → v0.6.8}/core/config.js +10 -0
- package/framework/v0.6.8/core/connectors/couchbase/lib/storage-store.js +1074 -0
- package/framework/{v0.6.7 → v0.6.8}/core/controller/controller.js +532 -22
- package/framework/v0.6.8/core/controller/controller.render-stream.js +270 -0
- package/framework/{v0.6.7 → v0.6.8}/core/gna.js +10 -2
- package/framework/{v0.6.7 → v0.6.8}/core/router.js +30 -10
- package/framework/{v0.6.7 → v0.6.8}/core/server.isaac.js +42 -7
- package/framework/{v0.6.7 → v0.6.8}/lib/cmd/storage/gc.js +6 -0
- package/framework/{v0.6.7 → v0.6.8}/lib/cmd/storage/stats.js +7 -0
- package/framework/{v0.6.7 → v0.6.8}/lib/cmd/storage/verify.js +6 -0
- package/framework/{v0.6.7 → v0.6.8}/lib/storage/src/local-cas.js +107 -9
- package/framework/v0.6.8/lib/storage/src/local-stream.js +1418 -0
- package/framework/{v0.6.7 → v0.6.8}/lib/storage/src/local.js +177 -16
- package/framework/{v0.6.7 → v0.6.8}/lib/storage/src/main.js +406 -25
- package/framework/{v0.6.7 → v0.6.8}/lib/storage/src/meta-store.js +13 -2
- package/framework/v0.6.8/lib/storage/src/s3.js +759 -0
- package/framework/{v0.6.7 → v0.6.8}/lib/storage/src/util.js +26 -1
- package/framework/{v0.6.7 → v0.6.8}/lib/storage-store.js +33 -23
- package/framework/{v0.6.7 → v0.6.8}/package.json +1 -1
- package/gna.js +4 -4
- package/llms.txt +22 -47
- package/package.json +3 -3
- package/schema/routing.json +4 -0
- package/schema/settings.json +84 -10
- package/script/check_no_local_leak.js +2 -1
- package/script/retry_lockfile_sync.js +2 -1
- package/script/soak/storage-couchbase/README.md +61 -0
- package/script/soak/storage-couchbase/probe-cas.js +77 -0
- package/script/soak/storage-couchbase/probe-store.js +101 -0
- package/types/index.d.ts +18 -0
- package/framework/v0.6.7/VERSION +0 -1
- package/framework/v0.6.7/core/controller/controller.render-stream.js +0 -169
- /package/framework/{v0.6.7 → v0.6.8}/AUTHORS +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/LICENSE +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/asset/html/nolayout.html +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/asset/html/static.html +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/asset/img/android-chrome-192x192.png +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/asset/img/android-chrome-512x512.png +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/asset/img/apple-touch-icon.png +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/asset/img/favicon-16x16.png +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/asset/img/favicon-32x32.png +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/asset/img/favicon.ico +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/asset/plugin/README.md +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/asset/plugin/dist/vendor/gina/beemaster/beemaster.css +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/asset/plugin/dist/vendor/gina/beemaster/beemaster.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/asset/plugin/dist/vendor/gina/beemaster/index.html +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/asset/plugin/dist/vendor/gina/css/gina.min.css +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/asset/plugin/dist/vendor/gina/css/gina.min.css.br +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/asset/plugin/dist/vendor/gina/css/gina.min.css.gz +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/asset/plugin/dist/vendor/gina/html/statusbar.html +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/asset/plugin/dist/vendor/gina/html/statusbar.html.br +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/asset/plugin/dist/vendor/gina/html/statusbar.html.gz +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/asset/plugin/dist/vendor/gina/inspector/have_heart_one-webfont.woff2 +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/asset/plugin/dist/vendor/gina/inspector/index.html +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/asset/plugin/dist/vendor/gina/inspector/inspector.css +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/asset/plugin/dist/vendor/gina/inspector/inspector.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/asset/plugin/dist/vendor/gina/inspector/logo.svg +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/asset/plugin/dist/vendor/gina/js/gina.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/asset/plugin/dist/vendor/gina/js/gina.min.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/asset/plugin/dist/vendor/gina/js/gina.min.js.br +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/asset/plugin/dist/vendor/gina/js/gina.min.js.gz +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/asset/plugin/dist/vendor/gina/js/gina.onload.min.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/asset/plugin/dist/vendor/gina/js/gina.onload.min.js.br +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/asset/plugin/dist/vendor/gina/js/gina.onload.min.js.gz +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/connectors/ai/index.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/connectors/ai/lib/connector.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/connectors/couchbase/index.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/connectors/couchbase/lib/connector.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/connectors/couchbase/lib/connector.v3.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/connectors/couchbase/lib/connector.v4.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/connectors/couchbase/lib/n1ql.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/connectors/couchbase/lib/session-store.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/connectors/couchbase/lib/session-store.v3.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/connectors/couchbase/lib/session-store.v4.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/connectors/duckdb/index.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/connectors/duckdb/lib/connector.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/connectors/mongodb/index.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/connectors/mongodb/lib/connector.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/connectors/mongodb/lib/job-store.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/connectors/mongodb/lib/pipeline-loader.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/connectors/mongodb/lib/session-store.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/connectors/mysql/index.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/connectors/mysql/lib/connector.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/connectors/param-redact.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/connectors/postgresql/index.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/connectors/postgresql/lib/connector.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/connectors/redis/index.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/connectors/redis/lib/connector.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/connectors/redis/lib/job-store.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/connectors/redis/lib/render-cache-store.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/connectors/redis/lib/session-store.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/connectors/scylladb/index.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/connectors/scylladb/lib/connector.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/connectors/scylladb/lib/session-store.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/connectors/sql-parser.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/connectors/sqlite/index.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/connectors/sqlite/lib/connector.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/connectors/sqlite/lib/job-store.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/connectors/sqlite/lib/session-store.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/content.encoding +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/controller/controller.framework.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/controller/controller.render-json.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/controller/controller.render-nunjucks-async.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/controller/controller.render-nunjucks.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/controller/controller.render-swig-async.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/controller/controller.render-swig.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/controller/controller.render-v1.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/controller/index.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/controller/inspector-window-emit.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/controller/release-banner.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/dev/index.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/dev/lib/class.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/dev/lib/factory.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/dev/lib/tools.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/locales/README.md +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/locales/currency.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/locales/dist/language/en.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/locales/dist/language/fr.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/locales/dist/region/en.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/locales/dist/region/fr.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/locales/index.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/mime.types +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/model/entity.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/model/index.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/model/template/entityFactory.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/model/template/index.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/README.md +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/index.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/csrf/README.md +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/csrf/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/csrf/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/security-headers/README.md +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/security-headers/coep/README.md +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/security-headers/coep/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/security-headers/coep/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/security-headers/coop/README.md +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/security-headers/coop/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/security-headers/coop/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/security-headers/corp/README.md +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/security-headers/corp/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/security-headers/corp/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/security-headers/csp/README.md +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/security-headers/csp/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/security-headers/csp/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/security-headers/hide-powered-by/README.md +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/security-headers/hide-powered-by/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/security-headers/hide-powered-by/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/security-headers/hsts/README.md +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/security-headers/hsts/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/security-headers/hsts/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/security-headers/origin-agent-cluster/README.md +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/security-headers/origin-agent-cluster/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/security-headers/origin-agent-cluster/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/security-headers/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/security-headers/referrer-policy/README.md +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/security-headers/referrer-policy/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/security-headers/referrer-policy/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/security-headers/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/security-headers/x-content-type-options/README.md +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/security-headers/x-content-type-options/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/security-headers/x-content-type-options/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/security-headers/x-dns-prefetch-control/README.md +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/security-headers/x-dns-prefetch-control/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/security-headers/x-dns-prefetch-control/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/security-headers/x-download-options/README.md +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/security-headers/x-download-options/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/security-headers/x-download-options/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/security-headers/x-frame-options/README.md +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/security-headers/x-frame-options/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/security-headers/x-frame-options/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/security-headers/x-permitted-cross-domain-policies/README.md +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/security-headers/x-permitted-cross-domain-policies/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/security-headers/x-permitted-cross-domain-policies/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/security-headers/x-xss-protection/README.md +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/security-headers/x-xss-protection/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/security-headers/x-xss-protection/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/session/README.md +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/session/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/session/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/storage/README.md +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/storage/build.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/storage/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/storage/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/validator/README.md +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/validator/build.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/validator/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/validator/src/form-validator.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/plugins/lib/validator/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/server.express.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/server.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/status.codes +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/template/_gitignore +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/template/boilerplate/bundle/config/app.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/template/boilerplate/bundle/config/connectors.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/template/boilerplate/bundle/config/routing.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/template/boilerplate/bundle/config/settings.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/template/boilerplate/bundle/config/settings.server.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/template/boilerplate/bundle/config/templates.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/template/boilerplate/bundle/config/watchers.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/template/boilerplate/bundle/controllers/controller.content.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/template/boilerplate/bundle/controllers/controller.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/template/boilerplate/bundle/controllers/setup.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/template/boilerplate/bundle/index.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/template/boilerplate/bundle/locales/en.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/template/boilerplate/bundle_namespace/controllers/controller.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/template/boilerplate/bundle_public/css/default.css +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/template/boilerplate/bundle_public/css/home.css +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/template/boilerplate/bundle_public/css/vendor/readme.md +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/template/boilerplate/bundle_public/favicon.ico +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/template/boilerplate/bundle_public/js/components/x-checklist.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/template/boilerplate/bundle_public/js/vendor/readme.md +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/template/boilerplate/bundle_public/manifest.webmanifest +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/template/boilerplate/bundle_public/readme.md +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/template/boilerplate/bundle_public/sw.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/template/boilerplate/bundle_templates/handlers/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/template/boilerplate/bundle_templates/html/content/homepage.html +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/template/boilerplate/bundle_templates/html/includes/error-msg-noscript.html +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/template/boilerplate/bundle_templates/html/includes/error-msg-outdated-browser.html +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/template/boilerplate/bundle_templates/html/includes/x-checklist.html +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/template/boilerplate/bundle_templates/html/layouts/main.html +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/template/command/gina.bat.tpl +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/template/command/gina.tpl +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/template/conf/env.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/template/conf/manifest.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/template/conf/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/template/conf/settings.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/template/conf/statics.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/template/conf/templates.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/template/error/client/json/401.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/template/error/client/json/403.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/template/error/client/json/404.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/template/error/server/html/50x.html +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/template/error/server/json/500.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/template/error/server/json/503.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/core/template/extensions/logger/config.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/helpers/console.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/helpers/context.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/helpers/data/LICENSE +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/helpers/data/README.md +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/helpers/data/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/helpers/data/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/helpers/dateFormat.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/helpers/index.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/helpers/json/LICENSE +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/helpers/json/README.md +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/helpers/json/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/helpers/json/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/helpers/path.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/helpers/plugins/README.md +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/helpers/plugins/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/helpers/plugins/src/api-error.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/helpers/plugins/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/helpers/prototypes.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/helpers/task.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/helpers/text.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/admin/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/admin/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/archiver/README.md +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/archiver/build.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/archiver/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/archiver/src/dep/jszip.min.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/archiver/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/async/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/async/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/audit/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/audit/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/audit-store.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/authn/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/authn/src/lockout.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/authn/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/authn/src/totp.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/authz-gate/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/authz-gate/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cache/README.md +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cache/build.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cache/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cache/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/aliases.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/audit/arguments.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/audit/help.txt +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/audit/verify.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/bundle/add.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/bundle/arguments.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/bundle/build.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/bundle/copy.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/bundle/cp.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/bundle/help.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/bundle/help.txt +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/bundle/inc/name-rewrite.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/bundle/list.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/bundle/man.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/bundle/mcp-start.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/bundle/mcp.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/bundle/oas.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/bundle/openapi.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/bundle/remove.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/bundle/rename.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/bundle/restart.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/bundle/rm.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/bundle/start.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/bundle/status.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/bundle/stop.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/bundle/types.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/cache/arguments.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/cache/clear.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/cache/help.txt +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/cache/stats.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/connector/add.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/connector/arguments.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/connector/help.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/connector/help.txt +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/connector/infer.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/connector/list.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/connector/migrate.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/connector/models.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/connector/remove.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/connector/rm.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/connector/test.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/container/arguments.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/container/help.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/container/help.txt +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/container/man.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/container/ps.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/container/stop.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/controller/add.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/controller/arguments.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/controller/help.txt +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/controller/inc/args.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/controller/inc/namespace.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/controller/inc/reference-rewrite.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/controller/inc/reference-scan.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/controller/inc/scaffold.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/controller/remove.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/controller/rename.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/controller/rm.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/env/add.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/env/get.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/env/help.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/env/help.txt +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/env/link-dev.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/env/list.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/env/remove.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/env/rm.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/env/set.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/env/unset.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/env/use.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/framework/add.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/framework/arguments.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/framework/build.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/framework/dot.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/framework/get.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/framework/help.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/framework/help.txt +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/framework/init.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/framework/link-node-modules.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/framework/link.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/framework/list.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/framework/man.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/framework/msg.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/framework/open.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/framework/remove.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/framework/reset.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/framework/restart.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/framework/set.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/framework/start.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/framework/status.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/framework/stop.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/framework/tail.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/framework/update.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/framework/version.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/gina-dev.1.md +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/gina-framework.1.md +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/gina.1.md +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/helper.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/i18n/add.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/i18n/arguments.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/i18n/export.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/i18n/help.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/i18n/help.txt +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/i18n/import.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/i18n/scan.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/image/_host.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/image/arguments.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/image/build.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/image/help.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/image/help.txt +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/image/list.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/image/man.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/image/rm.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/image/run.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/index.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/inspector/help.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/inspector/help.txt +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/inspector/open.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/man-render.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/minion/arguments.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/minion/help.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/minion/help.txt +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/minion/kill.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/minion/list.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/msg.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/port/help.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/port/help.txt +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/port/inc/scan.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/port/list.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/port/reset.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/port/set.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/project/add.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/project/arguments.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/project/backup.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/project/build.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/project/help.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/project/help.txt +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/project/import.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/project/list.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/project/man.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/project/move.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/project/remove.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/project/rename.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/project/restart.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/project/restore.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/project/rm.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/project/start.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/project/status.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/project/stop.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/protocol/arguments.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/protocol/help.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/protocol/help.txt +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/protocol/list.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/protocol/remove.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/protocol/set.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/scope/add.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/scope/help.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/scope/help.txt +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/scope/link-local.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/scope/link-production.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/scope/list.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/scope/remove.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/scope/rm.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/scope/use.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/secrets/arguments.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/secrets/check.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/secrets/help.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/secrets/help.txt +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/secrets/scan.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/service/help.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/service/help.txt +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/service/list.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/service/man.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/service/start.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/storage/arguments.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/storage/help.txt +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd/view/add.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd-status-format/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cmd-status-format/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/collection/README.md +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/collection/build.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/collection/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/collection/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/config.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/connector-config/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/connector-config/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/connector-error/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/connector-error/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/connector-registry/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/connector-registry/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cron/README.md +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cron/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/cron/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/domain/LICENSE +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/domain/README.md +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/domain/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/domain/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/dto/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/dto/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/dto-pipe/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/dto-pipe/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/dto-types/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/dto-types/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/generator/index.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/i18n/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/i18n/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/image-build/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/image-build/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/index.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/inherits/LICENSE +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/inherits/README.md +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/inherits/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/inherits/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/inspector-events/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/inspector-events/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/inspector-redact/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/inspector-redact/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/instrument/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/instrument/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/job/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/job/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/job-store.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/json-config-header/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/json-config-header/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/loading-state/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/loading-state/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/logger/README.md +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/logger/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/logger/src/containers/default/index.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/logger/src/containers/file/index.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/logger/src/containers/file/lib/logrotator/README.md +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/logger/src/containers/file/lib/logrotator/index.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/logger/src/containers/mq/index.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/logger/src/containers/mq/listener.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/logger/src/containers/mq/speaker.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/logger/src/helper.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/logger/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/math/index.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/mcp-dispatch/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/mcp-dispatch/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/mcp-http/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/mcp-http/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/mcp-server/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/mcp-server/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/merge/README.md +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/merge/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/merge/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/metrics/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/metrics/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/model.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/net-locality/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/net-locality/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/nunjucks-filters/README.md +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/nunjucks-filters/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/nunjucks-filters/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/nunjucks-resolver/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/nunjucks-resolver/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/proc.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/release-watch/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/release-watch/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/render-cache/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/render-cache/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/render-cache-store.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/routing/README.md +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/routing/build.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/routing/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/routing/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/routing/src/radix.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/routing-introspect/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/routing-introspect/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/secrets/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/secrets/src/backends/env.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/secrets/src/backends/file.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/secrets/src/env-file.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/secrets/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/session-store.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/shell.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/sqlite-driver.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/state.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/storage/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/swig-filters/README.md +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/swig-filters/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/swig-filters/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/swig-resolver/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/swig-resolver/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/template-loaders/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/template-loaders/src/loaders/http.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/template-loaders/src/loaders/memory.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/template-loaders/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/url/README.md +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/url/index.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/url/routing.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/uuid/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/uuid/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/validator.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/watcher/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/watcher/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/ws-framing/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/ws-framing/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/ws-query/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/ws-query/src/main.js +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/ws-session/package.json +0 -0
- /package/framework/{v0.6.7 → v0.6.8}/lib/ws-session/src/main.js +0 -0
|
@@ -0,0 +1,1418 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of the gina package.
|
|
3
|
+
* Copyright (c) 2009-2026 Rhinostone <contact@gina.io>
|
|
4
|
+
*
|
|
5
|
+
* For the full copyright and license information, please view the LICENSE
|
|
6
|
+
* file that was distributed with this source code.
|
|
7
|
+
*/
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @module lib/storage/local-stream
|
|
12
|
+
* @description The `local` adapter crossed with the `stream` strategy — large
|
|
13
|
+
* sequential media on the local filesystem, with **resumable uploads**.
|
|
14
|
+
*
|
|
15
|
+
* **The key names an ASSET, not a file**: `assets/<ulid>/original<ext>`. The
|
|
16
|
+
* per-asset directory is the namespace future renditions land in (`preview`,
|
|
17
|
+
* `thumb`, a transcoded variant); `original` is the reserved base-rendition
|
|
18
|
+
* name. No rendition API ships in this slice — the layout reserves the room.
|
|
19
|
+
* Keys stay OPAQUE: callers store what `finalize()`/`put()` returns and never
|
|
20
|
+
* parse the directory out of it.
|
|
21
|
+
*
|
|
22
|
+
* **What per-asset colocation actually buys, stated honestly.** It is
|
|
23
|
+
* OPERATIONAL grouping — one `rm -rf` removes an asset and everything derived
|
|
24
|
+
* from it, one `rsync` moves a subtree. It is NOT physical locality: a
|
|
25
|
+
* directory does not place extents, the allocator does. And gina neither
|
|
26
|
+
* prevents nor can prevent fragmentation — Node exposes no `fallocate`
|
|
27
|
+
* binding, native addons are not shipped, and `ftruncate` produces a fully
|
|
28
|
+
* SPARSE file (measured: 256MB truncate ⇒ 0 blocks on disk), so it reserves
|
|
29
|
+
* nothing and gives neither anti-fragmentation nor an early ENOSPC.
|
|
30
|
+
* Contiguity is a filesystem/volume concern (XFS extent hints and the like).
|
|
31
|
+
* The same honesty the `cas` slice applied to `F_FULLFSYNC`.
|
|
32
|
+
*
|
|
33
|
+
* **Resumable uploads: the filesystem IS the manifest.** A session lives at
|
|
34
|
+
* `<root>/.uploads/<uploadId>/` and holds exactly three kinds of entry —
|
|
35
|
+
* `data` (the file being assembled in place, written POSITIONALLY so segments
|
|
36
|
+
* may arrive out of order), `meta.json` (written ONCE at create through
|
|
37
|
+
* temp+rename, so its state is binary: present-and-parseable, or absent), and
|
|
38
|
+
* one zero-byte `<offset>-<length>.ok` marker per DURABLE range. Nothing about
|
|
39
|
+
* a session touches the metadata store, and the seam gains ZERO new verbs.
|
|
40
|
+
*
|
|
41
|
+
* The argument for that home is FATE-SHARING, not seam economy: the manifest
|
|
42
|
+
* must share crash-fate and fsync-ordering with the bytes it describes.
|
|
43
|
+
* `fsync(data)` happens-before the marker is created, which is enforceable
|
|
44
|
+
* only while both live on the same medium. A store-backed manifest (SQLite
|
|
45
|
+
* WAL, redis, couchbase) can survive a crash the bytes did not, and a manifest
|
|
46
|
+
* claiming a range the bytes lost passes the coverage check and publishes
|
|
47
|
+
* ZEROS — reintroducing, through desync, exactly the hazard the coverage check
|
|
48
|
+
* exists to close.
|
|
49
|
+
*
|
|
50
|
+
* **The zero-fill hazard, and why coverage is an INTERVAL UNION.** A hole in a
|
|
51
|
+
* sparse file reads back as zeros, not as an error (measured). So a finalise
|
|
52
|
+
* that did not verify coverage would publish a plausible, silently zero-filled
|
|
53
|
+
* object. Verifying by SUMMING marker lengths is not enough either: markers
|
|
54
|
+
* `0–8MB` and `4–12MB` sum to 16MB while `12–16MB` is uncovered. Coverage is
|
|
55
|
+
* therefore computed by sorting the markers, merging overlapping and adjacent
|
|
56
|
+
* spans, and requiring exactly ONE span equal to `[0, expectedSize)`.
|
|
57
|
+
*
|
|
58
|
+
* **Durability (`fsync: true`, the stream default).** Per segment: positional
|
|
59
|
+
* write → `fsync(data)` → create the marker. Only the DATA fsync is
|
|
60
|
+
* load-bearing; the marker's own durability is deliberately not guaranteed,
|
|
61
|
+
* because a lost marker beside present bytes costs a re-send, which is the
|
|
62
|
+
* safe direction. Measured: a process killed with SIGKILL after an un-fsynced
|
|
63
|
+
* write still yields the full bytes to a later reader — the page cache carries
|
|
64
|
+
* them — so markers stay honest across PROCESS death without fsync; only power
|
|
65
|
+
* loss or a kernel panic opens the window. The cost is a RANGE, not the single
|
|
66
|
+
* flattering figure: ~12ms per 8MB segment is ~2% of network-paced ingest at
|
|
67
|
+
* 100 Mbps, ~19% at 1 Gbps, and fsync-DOMINANT at 10 Gbps. A LAN-ingest
|
|
68
|
+
* consumer sets `fsync: false` and accepts the documented window. Inherits the
|
|
69
|
+
* `cas` slice's per-platform honesty: the parent-directory flush is
|
|
70
|
+
* best-effort, and macOS honours `fsync` as the platform defines it.
|
|
71
|
+
*
|
|
72
|
+
* **Multi-process: documented, not enforced.** Parallel segment writes are
|
|
73
|
+
* safe when the ranges are DISJOINT — measured on a POSIX-coherent local
|
|
74
|
+
* filesystem: two uncoordinated processes interleaving 1MB stripes into one
|
|
75
|
+
* shared file produced byte-exact output. On relaxed-coherence network mounts
|
|
76
|
+
* (GlusterFS and friends) that has NOT been measured; route one session's
|
|
77
|
+
* segments to one process there. Nothing in this driver enforces affinity —
|
|
78
|
+
* cross-process discovery does not exist in this layer.
|
|
79
|
+
*
|
|
80
|
+
* **No tiering, no hashing.** A multi-gigabyte strategy has no business
|
|
81
|
+
* inlining its payload into a metadata row, and the write path deliberately
|
|
82
|
+
* does not hash. Setting `inlineThreshold` or `hash` on a `stream` driver
|
|
83
|
+
* therefore warns as an ignored key rather than silently doing nothing —
|
|
84
|
+
* `capabilities.inline` and `capabilities.dedup` are both `false`.
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* // settings.json — a stream driver for large media
|
|
88
|
+
* // { "storage": { "drivers": { "media": {
|
|
89
|
+
* // "adapter": "local", "strategy": "stream",
|
|
90
|
+
* // "root": "/var/data/media", "maxObjectSize": "5GB", "chunkSize": "8MB"
|
|
91
|
+
* // } } } }
|
|
92
|
+
* var s = gina.storage('media');
|
|
93
|
+
* s.createUpload({ expectedSize: 4294967296, originalName: 'talk.mp4' },
|
|
94
|
+
* function (err, session) {
|
|
95
|
+
* if (err) { return next(err); }
|
|
96
|
+
* // session => { uploadId, chunkSize, expectedSize }
|
|
97
|
+
* });
|
|
98
|
+
*/
|
|
99
|
+
|
|
100
|
+
var fs = require('fs');
|
|
101
|
+
var nodePath = require('path');
|
|
102
|
+
|
|
103
|
+
var util = require('./util');
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Directory holding in-flight `put()` temp files, relative to the driver root.
|
|
107
|
+
* Same name as the other local strategies' — one temp namespace per root.
|
|
108
|
+
*
|
|
109
|
+
* @inner
|
|
110
|
+
* @constant
|
|
111
|
+
* @type {string}
|
|
112
|
+
*/
|
|
113
|
+
var TMP_DIR = '.tmp';
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Directory holding resumable upload sessions, relative to the driver root.
|
|
117
|
+
* A dot segment, so it is structurally uncollidable with any caller key — the
|
|
118
|
+
* key guards refuse dot segments, the same trick `.tmp` and the `.driver`
|
|
119
|
+
* stamp row already use.
|
|
120
|
+
*
|
|
121
|
+
* @inner
|
|
122
|
+
* @constant
|
|
123
|
+
* @type {string}
|
|
124
|
+
*/
|
|
125
|
+
var UPLOADS_DIR = '.uploads';
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Name of the file a session assembles bytes into, inside its own directory.
|
|
129
|
+
* Its mtime — not the directory's — is the session's liveness signal: it
|
|
130
|
+
* advances on every segment write, whereas a directory mtime only moves when
|
|
131
|
+
* entries are added or removed.
|
|
132
|
+
*
|
|
133
|
+
* @inner
|
|
134
|
+
* @constant
|
|
135
|
+
* @type {string}
|
|
136
|
+
*/
|
|
137
|
+
var DATA_FILE = 'data';
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Name of a session's manifest, inside its own directory. Written ONCE, at
|
|
141
|
+
* create, through temp+rename — never mutated, so no read-modify-write hazard
|
|
142
|
+
* exists and no crash can leave it half-written.
|
|
143
|
+
*
|
|
144
|
+
* @inner
|
|
145
|
+
* @constant
|
|
146
|
+
* @type {string}
|
|
147
|
+
*/
|
|
148
|
+
var META_FILE = 'meta.json';
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* The reserved base-rendition file name inside an asset directory.
|
|
152
|
+
*
|
|
153
|
+
* @inner
|
|
154
|
+
* @constant
|
|
155
|
+
* @type {string}
|
|
156
|
+
*/
|
|
157
|
+
var ORIGINAL = 'original';
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* A durable-range marker's file name grammar: `<offset>-<length>.ok`.
|
|
161
|
+
* Anything else in a session directory is ignored by the coverage scan.
|
|
162
|
+
*
|
|
163
|
+
* @inner
|
|
164
|
+
* @constant
|
|
165
|
+
* @type {RegExp}
|
|
166
|
+
*/
|
|
167
|
+
var MARKER_RE = /^([0-9]+)-([0-9]+)\.ok$/;
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* A driver-minted upload id: a ULID in Crockford base32, which excludes I, L,
|
|
171
|
+
* O and U. Validated BEFORE any path join — an id reaches the filesystem as a
|
|
172
|
+
* directory name, so this is a security boundary, not a formatting nicety
|
|
173
|
+
* (`findByDigest`'s posture in the cas strategy).
|
|
174
|
+
*
|
|
175
|
+
* @inner
|
|
176
|
+
* @constant
|
|
177
|
+
* @type {RegExp}
|
|
178
|
+
*/
|
|
179
|
+
var UPLOAD_ID_RE = /^[0-9A-HJKMNP-TV-Z]{26}$/;
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* A resumable upload session, as returned by `createUpload()`.
|
|
183
|
+
*
|
|
184
|
+
* @typedef {Object} StorageUploadSession
|
|
185
|
+
* @property {string} uploadId - Opaque, driver-minted session id. Hand it back to
|
|
186
|
+
* `writeSegment`/`statUpload`/`finalize`/`abortUpload`.
|
|
187
|
+
* @property {number} chunkSize - The driver's configured segment size, in bytes. Advisory:
|
|
188
|
+
* a client may send any segment size, but this is the size
|
|
189
|
+
* the write path is tuned for.
|
|
190
|
+
* @property {number} expectedSize - The declared total, echoed back.
|
|
191
|
+
*/
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* One durable byte range of a session, as reported by `statUpload()`.
|
|
195
|
+
*
|
|
196
|
+
* @typedef {Object} StorageUploadSpan
|
|
197
|
+
* @property {number} offset - First byte of the span.
|
|
198
|
+
* @property {number} length - Span length in bytes; the span covers `[offset, offset+length)`.
|
|
199
|
+
*/
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* The state of a resumable upload, as returned by `statUpload()` — the verb
|
|
203
|
+
* that makes resuming possible at all.
|
|
204
|
+
*
|
|
205
|
+
* @typedef {Object} StorageUploadState
|
|
206
|
+
* @property {number} expectedSize - The declared total, from `createUpload`.
|
|
207
|
+
* @property {?string} contentType - Client-supplied MIME type, verbatim.
|
|
208
|
+
* @property {?string} originalName - Client-supplied filename, verbatim.
|
|
209
|
+
* @property {number} createdAt - Epoch ms at session creation.
|
|
210
|
+
* @property {StorageUploadSpan[]} received - Durable ranges, merged and offset-ordered.
|
|
211
|
+
* @property {StorageUploadSpan[]} missing - The gaps within `[0, expectedSize)` — what a
|
|
212
|
+
* resuming client must still send. An HTTP
|
|
213
|
+
* protocol that reports only a contiguous
|
|
214
|
+
* offset (tus) reads `received[0]`.
|
|
215
|
+
* @property {boolean} complete - `true` when `missing` is empty, i.e. when
|
|
216
|
+
* `finalize()` would succeed.
|
|
217
|
+
*/
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Build a `local` filesystem driver with the `stream` (large sequential media,
|
|
221
|
+
* resumable) strategy.
|
|
222
|
+
*
|
|
223
|
+
* @param {string} name - Driver name, used only in error messages.
|
|
224
|
+
* @param {object} conf - Resolved driver configuration (defaults
|
|
225
|
+
* resolve in `start()`, never here — the
|
|
226
|
+
* `maxObjectSize` pattern).
|
|
227
|
+
* @param {string} conf.root - Absolute driver root. Validated at boot
|
|
228
|
+
* to be absolute and outside every
|
|
229
|
+
* web-served tree.
|
|
230
|
+
* @param {number} conf.maxObjectSize - Per-object byte ceiling, already parsed.
|
|
231
|
+
* @param {number} conf.chunkSize - Write-stream `highWaterMark`, in bytes.
|
|
232
|
+
* @param {boolean} conf.fsync - Whether segment writes and the finalise
|
|
233
|
+
* publish flush to disk (file hard, parent
|
|
234
|
+
* dir best-effort).
|
|
235
|
+
* @param {number} conf.sessionTtl - How long an untouched session survives
|
|
236
|
+
* before the sweep reclaims it, in ms.
|
|
237
|
+
* @param {number} conf.sessionSweepInterval - Sweep period in ms; `0` disables the
|
|
238
|
+
* periodic sweep (the build-time pass
|
|
239
|
+
* still runs).
|
|
240
|
+
* @param {StorageMetaStore} metaStore - Metadata backend (embedded SQLite by
|
|
241
|
+
* default). No refcount verbs required —
|
|
242
|
+
* `stream` uses the four required ones.
|
|
243
|
+
* @returns {StorageDriver} A ready driver (plus the five resumable verbs).
|
|
244
|
+
*
|
|
245
|
+
* @example
|
|
246
|
+
* var driver = createLocalStreamDriver('media', conf, metaStore);
|
|
247
|
+
*/
|
|
248
|
+
module.exports = function createLocalStreamDriver(name, conf, metaStore) {
|
|
249
|
+
|
|
250
|
+
var root = conf.root;
|
|
251
|
+
var max = conf.maxObjectSize;
|
|
252
|
+
var fsyncOn = ( conf.fsync === true );
|
|
253
|
+
var chunkSize = ( typeof conf.chunkSize === 'number' && conf.chunkSize > 0 )
|
|
254
|
+
? conf.chunkSize
|
|
255
|
+
: 0;
|
|
256
|
+
var sessionTtlMs = ( typeof conf.sessionTtl === 'number' && conf.sessionTtl > 0 )
|
|
257
|
+
? conf.sessionTtl
|
|
258
|
+
: 0;
|
|
259
|
+
var sweepIntervalMs = ( typeof conf.sessionSweepInterval === 'number' && conf.sessionSweepInterval > 0 )
|
|
260
|
+
? conf.sessionSweepInterval
|
|
261
|
+
: 0;
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Key → confined absolute path. Shared, single-sourced guard sequence —
|
|
265
|
+
* see {@link module:lib/storage/util.makeResolvePath} for the three guards
|
|
266
|
+
* and why their ORDER is load-bearing.
|
|
267
|
+
*
|
|
268
|
+
* @inner
|
|
269
|
+
* @type {function(string): {path: ?string, error: ?Error}}
|
|
270
|
+
*/
|
|
271
|
+
var resolvePath = util.makeResolvePath(name, root);
|
|
272
|
+
|
|
273
|
+
var tmpD = nodePath.join(root, TMP_DIR);
|
|
274
|
+
var uploadsD = nodePath.join(root, UPLOADS_DIR);
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* fsync a file by path — the HARD half of the durability contract: a
|
|
278
|
+
* failure here fails the operation that asked for durability.
|
|
279
|
+
*
|
|
280
|
+
* @inner
|
|
281
|
+
* @param {string} p - File path.
|
|
282
|
+
* @returns {void}
|
|
283
|
+
*/
|
|
284
|
+
var fsyncFile = function(p) {
|
|
285
|
+
var fd = fs.openSync(p, 'r');
|
|
286
|
+
try {
|
|
287
|
+
fs.fsyncSync(fd);
|
|
288
|
+
} finally {
|
|
289
|
+
fs.closeSync(fd);
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* fsync a directory — the BEST-EFFORT half: platforms and filesystems that
|
|
295
|
+
* cannot fsync a directory (Windows; some network mounts) throw here and
|
|
296
|
+
* the publish proceeds, which the module header documents rather than
|
|
297
|
+
* silently implies.
|
|
298
|
+
*
|
|
299
|
+
* @inner
|
|
300
|
+
* @param {string} p - Directory path.
|
|
301
|
+
* @returns {void}
|
|
302
|
+
*/
|
|
303
|
+
var fsyncDirBestEffort = function(p) {
|
|
304
|
+
try {
|
|
305
|
+
var fd = fs.openSync(p, 'r');
|
|
306
|
+
try {
|
|
307
|
+
fs.fsyncSync(fd);
|
|
308
|
+
} finally {
|
|
309
|
+
fs.closeSync(fd);
|
|
310
|
+
}
|
|
311
|
+
} catch (e) {
|
|
312
|
+
// platform cannot fsync a directory — best-effort by contract
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Validate an upload id and resolve its session directory.
|
|
318
|
+
*
|
|
319
|
+
* The id is charset-validated BEFORE any path join, so a traversal
|
|
320
|
+
* attempt (`../`), a dot segment or a stray separator never reaches
|
|
321
|
+
* `path.join` at all — the same posture `findByDigest` takes with a
|
|
322
|
+
* caller-supplied digest.
|
|
323
|
+
*
|
|
324
|
+
* @inner
|
|
325
|
+
* @param {*} uploadId - Caller-supplied session id, any type tolerated.
|
|
326
|
+
* @returns {{dir: ?string, error: ?Error}} Exactly one side is set.
|
|
327
|
+
*/
|
|
328
|
+
var resolveSession = function(uploadId) {
|
|
329
|
+
if ( typeof uploadId !== 'string' || !UPLOAD_ID_RE.test(uploadId) ) {
|
|
330
|
+
return { dir: null, error: new Error('[storage:' + name + '] not a valid upload id: ' + JSON.stringify(uploadId)) };
|
|
331
|
+
}
|
|
332
|
+
return { dir: nodePath.join(uploadsD, uploadId), error: null };
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Read a session manifest.
|
|
337
|
+
*
|
|
338
|
+
* Fails LOUDLY on a missing or unparseable manifest rather than assuming
|
|
339
|
+
* defaults: because the manifest is written atomically, "absent" and
|
|
340
|
+
* "present and parseable" are the only two states a crash can leave, so
|
|
341
|
+
* anything else is corruption or a stale/abandoned id — and inventing an
|
|
342
|
+
* `expectedSize` for it would let the coverage check pass over nothing.
|
|
343
|
+
*
|
|
344
|
+
* @inner
|
|
345
|
+
* @param {string} dir - Session directory.
|
|
346
|
+
* @returns {{meta: ?object, error: ?Error}} Exactly one side is set.
|
|
347
|
+
*/
|
|
348
|
+
var readManifest = function(dir) {
|
|
349
|
+
var raw;
|
|
350
|
+
try {
|
|
351
|
+
raw = fs.readFileSync(nodePath.join(dir, META_FILE), 'utf8');
|
|
352
|
+
} catch (e) {
|
|
353
|
+
return { meta: null, error: new Error('[storage:' + name + '] no such upload session (or its manifest is gone): ' + nodePath.basename(dir)) };
|
|
354
|
+
}
|
|
355
|
+
var parsed;
|
|
356
|
+
try {
|
|
357
|
+
parsed = JSON.parse(raw);
|
|
358
|
+
} catch (e) {
|
|
359
|
+
return { meta: null, error: new Error('[storage:' + name + '] the manifest for upload session `' + nodePath.basename(dir) + '` is unreadable (' + (e.message || e) + ') — the session cannot be resumed or finalised; abort it') };
|
|
360
|
+
}
|
|
361
|
+
if ( !parsed || typeof parsed !== 'object' || !Number.isInteger(parsed.expectedSize) || typeof parsed.key !== 'string' ) {
|
|
362
|
+
return { meta: null, error: new Error('[storage:' + name + '] the manifest for upload session `' + nodePath.basename(dir) + '` is malformed — the session cannot be resumed or finalised; abort it') };
|
|
363
|
+
}
|
|
364
|
+
return { meta: parsed, error: null };
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Merge byte spans into the minimal offset-ordered set covering the same
|
|
369
|
+
* bytes. ADJACENT spans merge as well as overlapping ones (`8MB-16MB`
|
|
370
|
+
* following `0MB-8MB` is one contiguous range, not two), which is what
|
|
371
|
+
* makes "exactly one span equal to `[0, expectedSize)`" the completeness
|
|
372
|
+
* test.
|
|
373
|
+
*
|
|
374
|
+
* @inner
|
|
375
|
+
* @param {StorageUploadSpan[]} spans - Unordered, possibly overlapping spans.
|
|
376
|
+
* @returns {StorageUploadSpan[]} Merged, offset-ordered.
|
|
377
|
+
*/
|
|
378
|
+
var mergeSpans = function(spans) {
|
|
379
|
+
var sorted = spans.slice().sort(function(a, b) { return a.offset - b.offset; });
|
|
380
|
+
var out = [];
|
|
381
|
+
for (var i = 0; i < sorted.length; i++) {
|
|
382
|
+
var s = sorted[i];
|
|
383
|
+
if ( s.length <= 0 ) { continue; }
|
|
384
|
+
var last = out.length ? out[out.length - 1] : null;
|
|
385
|
+
if ( last && s.offset <= last.offset + last.length ) {
|
|
386
|
+
var end = Math.max(last.offset + last.length, s.offset + s.length);
|
|
387
|
+
last.length = end - last.offset;
|
|
388
|
+
} else {
|
|
389
|
+
out.push({ offset: s.offset, length: s.length });
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return out;
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Read a session's durable ranges from its markers.
|
|
397
|
+
*
|
|
398
|
+
* The filesystem IS the manifest: one zero-byte `<offset>-<length>.ok`
|
|
399
|
+
* file per range whose bytes are durable. Entries that do not match the
|
|
400
|
+
* grammar (`data`, `meta.json`, anything an operator dropped in) are
|
|
401
|
+
* ignored.
|
|
402
|
+
*
|
|
403
|
+
* @inner
|
|
404
|
+
* @param {string} dir - Session directory.
|
|
405
|
+
* @returns {StorageUploadSpan[]} Merged, offset-ordered durable ranges.
|
|
406
|
+
*/
|
|
407
|
+
var readSpans = function(dir) {
|
|
408
|
+
var entries;
|
|
409
|
+
try {
|
|
410
|
+
entries = fs.readdirSync(dir);
|
|
411
|
+
} catch (e) {
|
|
412
|
+
return [];
|
|
413
|
+
}
|
|
414
|
+
var spans = [];
|
|
415
|
+
for (var i = 0; i < entries.length; i++) {
|
|
416
|
+
var m = MARKER_RE.exec(entries[i]);
|
|
417
|
+
if ( !m ) { continue; }
|
|
418
|
+
spans.push({ offset: parseInt(m[1], 10), length: parseInt(m[2], 10) });
|
|
419
|
+
}
|
|
420
|
+
return mergeSpans(spans);
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* The gaps in `[0, expectedSize)` left by a set of merged spans — what a
|
|
425
|
+
* resuming client still has to send.
|
|
426
|
+
*
|
|
427
|
+
* @inner
|
|
428
|
+
* @param {StorageUploadSpan[]} spans - Merged, offset-ordered durable ranges.
|
|
429
|
+
* @param {number} expectedSize - The declared total.
|
|
430
|
+
* @returns {StorageUploadSpan[]} Missing ranges, offset-ordered.
|
|
431
|
+
*/
|
|
432
|
+
var missingSpans = function(spans, expectedSize) {
|
|
433
|
+
var out = [];
|
|
434
|
+
var cursor = 0;
|
|
435
|
+
for (var i = 0; i < spans.length; i++) {
|
|
436
|
+
if ( spans[i].offset > cursor ) {
|
|
437
|
+
out.push({ offset: cursor, length: spans[i].offset - cursor });
|
|
438
|
+
}
|
|
439
|
+
cursor = Math.max(cursor, spans[i].offset + spans[i].length);
|
|
440
|
+
}
|
|
441
|
+
if ( cursor < expectedSize ) {
|
|
442
|
+
out.push({ offset: cursor, length: expectedSize - cursor });
|
|
443
|
+
}
|
|
444
|
+
return out;
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Total durable bytes across a merged span set.
|
|
449
|
+
*
|
|
450
|
+
* @inner
|
|
451
|
+
* @param {StorageUploadSpan[]} spans - Merged spans.
|
|
452
|
+
* @returns {number} Byte count.
|
|
453
|
+
*/
|
|
454
|
+
var coveredBytes = function(spans) {
|
|
455
|
+
var n = 0;
|
|
456
|
+
for (var i = 0; i < spans.length; i++) { n += spans[i].length; }
|
|
457
|
+
return n;
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Remove a session directory and everything in it. Used by `abortUpload`,
|
|
462
|
+
* by a successful `finalize`, and by the sweep.
|
|
463
|
+
*
|
|
464
|
+
* @inner
|
|
465
|
+
* @param {string} dir - Session directory.
|
|
466
|
+
* @returns {void}
|
|
467
|
+
*/
|
|
468
|
+
var dropSession = function(dir) {
|
|
469
|
+
try { fs.rmSync(dir, { recursive: true, force: true }); } catch (e) { /* raced or unreadable */ }
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* One reclamation pass: abandoned upload sessions, plus `put()` temp
|
|
474
|
+
* orphans, both age-gated at `sessionTtl`.
|
|
475
|
+
*
|
|
476
|
+
* Sessions get their own sweep rather than riding a shared one because
|
|
477
|
+
* they legitimately live for DAYS — a paused multi-gigabyte upload is not
|
|
478
|
+
* garbage — where a crashed `put()`'s temp is dead the moment its process
|
|
479
|
+
* is. Liveness is `mtime(data)`, which advances on every segment write;
|
|
480
|
+
* the session directory's own mtime only moves when entries are added or
|
|
481
|
+
* removed, so a long segment write would look idle by that measure. A
|
|
482
|
+
* session whose `data` is gone (a `finalize` that renamed the bytes away
|
|
483
|
+
* and then failed to write the row) falls back to the directory mtime, so
|
|
484
|
+
* it is reclaimable too.
|
|
485
|
+
*
|
|
486
|
+
* The temp half is the same age gate applied to `<root>/.tmp`: a crashed
|
|
487
|
+
* `put()` leaves its temp behind and no verb ever looks at it again. Every
|
|
488
|
+
* strategy now clears its own — `cas` and `sharded` at build (#B349 closed
|
|
489
|
+
* the sharded gap), `stream` here at build AND on the interval, because
|
|
490
|
+
* this pass has sessions to walk anyway. Each sweeps only its OWN root.
|
|
491
|
+
*
|
|
492
|
+
* Never throws: an unreadable directory is the next verb's problem to
|
|
493
|
+
* report properly, and a sweep that took the build down would be a worse
|
|
494
|
+
* failure than the leak it prevents.
|
|
495
|
+
*
|
|
496
|
+
* @inner
|
|
497
|
+
* @returns {{sessions: number, temps: number}} What this pass reclaimed.
|
|
498
|
+
*/
|
|
499
|
+
var sweepAbandoned = function() {
|
|
500
|
+
var out = { sessions: 0, temps: 0 };
|
|
501
|
+
if ( sessionTtlMs <= 0 ) { return out; }
|
|
502
|
+
var cutoff = Date.now() - sessionTtlMs;
|
|
503
|
+
|
|
504
|
+
try {
|
|
505
|
+
if ( fs.existsSync(uploadsD) ) {
|
|
506
|
+
var sessions = fs.readdirSync(uploadsD);
|
|
507
|
+
for (var i = 0; i < sessions.length; i++) {
|
|
508
|
+
var dir = nodePath.join(uploadsD, sessions[i]);
|
|
509
|
+
try {
|
|
510
|
+
var liveness;
|
|
511
|
+
try {
|
|
512
|
+
liveness = fs.statSync(nodePath.join(dir, DATA_FILE)).mtimeMs;
|
|
513
|
+
} catch (e) {
|
|
514
|
+
liveness = fs.statSync(dir).mtimeMs;
|
|
515
|
+
}
|
|
516
|
+
if ( liveness < cutoff ) {
|
|
517
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
518
|
+
out.sessions++;
|
|
519
|
+
}
|
|
520
|
+
} catch (e) { /* raced or unreadable — leave it */ }
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
} catch (e) { /* never blocks the build */ }
|
|
524
|
+
|
|
525
|
+
try {
|
|
526
|
+
if ( fs.existsSync(tmpD) ) {
|
|
527
|
+
var temps = fs.readdirSync(tmpD);
|
|
528
|
+
for (var t = 0; t < temps.length; t++) {
|
|
529
|
+
var tp = nodePath.join(tmpD, temps[t]);
|
|
530
|
+
try {
|
|
531
|
+
if ( fs.statSync(tp).mtimeMs < cutoff ) {
|
|
532
|
+
fs.unlinkSync(tp);
|
|
533
|
+
out.temps++;
|
|
534
|
+
}
|
|
535
|
+
} catch (e) { /* raced or unreadable — leave it */ }
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
} catch (e) { /* never blocks the build */ }
|
|
539
|
+
|
|
540
|
+
return out;
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
// Build-time pass: a bundle that crashed mid-upload has sessions and temps
|
|
544
|
+
// nothing else will ever remove, and a restart is the earliest moment we
|
|
545
|
+
// can be sure the previous process is gone.
|
|
546
|
+
sweepAbandoned();
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* The periodic sweep timer — self-contained and unref'd (the `lib/job`
|
|
550
|
+
* scheduling precedent; `lib/cron` is dormant), so it never holds the
|
|
551
|
+
* process open. Cleared by `close()`. The build-time pass alone would
|
|
552
|
+
* suffice for short-lived temps but not for sessions abandoned by a client
|
|
553
|
+
* that simply went away while the bundle kept running.
|
|
554
|
+
*
|
|
555
|
+
* @inner
|
|
556
|
+
* @type {?object}
|
|
557
|
+
*/
|
|
558
|
+
var sweepTimer = null;
|
|
559
|
+
if ( sweepIntervalMs > 0 ) {
|
|
560
|
+
sweepTimer = setInterval(sweepAbandoned, sweepIntervalMs);
|
|
561
|
+
if ( sweepTimer.unref ) { sweepTimer.unref(); }
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* What this driver can do — hoisted to a closure var so `stats()` can
|
|
566
|
+
* include it in its report without reaching back into the returned
|
|
567
|
+
* literal. Documented on the `capabilities` property below.
|
|
568
|
+
*
|
|
569
|
+
* @inner
|
|
570
|
+
* @type {{offload: boolean, ranges: boolean, dedup: boolean, resumable: boolean, inline: boolean}}
|
|
571
|
+
*/
|
|
572
|
+
var capabilities = {
|
|
573
|
+
offload : false,
|
|
574
|
+
ranges : true,
|
|
575
|
+
dedup : false,
|
|
576
|
+
resumable : true,
|
|
577
|
+
inline : false
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
return {
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Store a readable stream of known-at-the-end size and return its
|
|
584
|
+
* opaque key — the one-shot path, for content whose total the caller
|
|
585
|
+
* does not know up front or does not need to resume.
|
|
586
|
+
*
|
|
587
|
+
* Streams to a temp file inside the driver root and publishes with
|
|
588
|
+
* `rename(2)`, so a reader never observes a partial object and a
|
|
589
|
+
* crashed write leaves nothing visible. The write stream's
|
|
590
|
+
* `highWaterMark` is the configured `chunkSize`, so the syscall size is
|
|
591
|
+
* the tuned one with no hand-rolled buffer and no extra copy — memory
|
|
592
|
+
* stays bounded by the stream machinery rather than by us. With
|
|
593
|
+
* `fsync` on, the temp is flushed BEFORE the rename publishes it and
|
|
594
|
+
* the parent directory best-effort after.
|
|
595
|
+
*
|
|
596
|
+
* No tiering branch: a `stream` driver never inlines. The reported
|
|
597
|
+
* size is measured from the published file on disk, never from the
|
|
598
|
+
* client and never from an in-flight counter (#B142).
|
|
599
|
+
*
|
|
600
|
+
* @param {object} stream - Any readable stream.
|
|
601
|
+
* @param {object} [meta] - Client-supplied, untrusted.
|
|
602
|
+
* @param {string} [meta.originalName] - Filename; stored verbatim in metadata, and
|
|
603
|
+
* the only source of the path extension.
|
|
604
|
+
* @param {string} [meta.contentType] - MIME type; stored verbatim.
|
|
605
|
+
* @param {function} fn - `fn(err, {@link StoragePutResult})`.
|
|
606
|
+
* @returns {void}
|
|
607
|
+
*
|
|
608
|
+
* @example
|
|
609
|
+
* driver.put(req, { originalName: 'talk.mp4' }, function (err, res) {
|
|
610
|
+
* if (err) { return next(err); }
|
|
611
|
+
* record.assetKey = res.key; // opaque — store it, never parse it
|
|
612
|
+
* });
|
|
613
|
+
*/
|
|
614
|
+
put: function(stream, meta, fn) {
|
|
615
|
+
if ( typeof meta === 'function' ) { fn = meta; meta = {}; }
|
|
616
|
+
meta = meta || {};
|
|
617
|
+
if ( typeof fn !== 'function' ) {
|
|
618
|
+
throw new Error('[storage:' + name + '] put() requires a callback');
|
|
619
|
+
}
|
|
620
|
+
if ( !stream || typeof stream.pipe !== 'function' ) {
|
|
621
|
+
return fn(new Error('[storage:' + name + '] put() requires a readable stream'));
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
var now = new Date();
|
|
625
|
+
var id = util.ulid(now.getTime());
|
|
626
|
+
var ext = util.sanitiseExtension(meta.originalName);
|
|
627
|
+
var key = 'assets/' + id + '/' + ORIGINAL + ext;
|
|
628
|
+
var final = nodePath.join(root, key);
|
|
629
|
+
var tmp = nodePath.join(tmpD, id + '.tmp');
|
|
630
|
+
|
|
631
|
+
try {
|
|
632
|
+
fs.mkdirSync(nodePath.dirname(final), { recursive: true });
|
|
633
|
+
fs.mkdirSync(tmpD, { recursive: true });
|
|
634
|
+
} catch (mkErr) {
|
|
635
|
+
return fn(mkErr);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
var settled = false;
|
|
639
|
+
var written = 0;
|
|
640
|
+
|
|
641
|
+
var ws = fs.createWriteStream(tmp, { highWaterMark: chunkSize || undefined });
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* Abandon the put: tear both streams down, remove the temp, report.
|
|
645
|
+
*
|
|
646
|
+
* #B358 — the unlink WAITS for the write stream to close, and the
|
|
647
|
+
* callback waits with it. `createWriteStream()` opens
|
|
648
|
+
* asynchronously, so a refusal raised from the 'data' handler
|
|
649
|
+
* (`maxObjectSize`) or an immediately failing source reaches here
|
|
650
|
+
* while the open is still in the threadpool: the temp does not
|
|
651
|
+
* exist YET at this instant but lands a tick later, on the worker
|
|
652
|
+
* thread, with no event-loop turn in between for a synchronous
|
|
653
|
+
* check to see. The previous `existsSync`-gated unlink therefore
|
|
654
|
+
* read false, skipped, and orphaned the temp — measured at ~30% on
|
|
655
|
+
* a 4096-byte refusal. Deferring to 'close' also fixes the contract
|
|
656
|
+
* it broke: a caller told the put failed must not be able to
|
|
657
|
+
* observe residue afterwards. `force` absorbs the open that never
|
|
658
|
+
* happened at all, so the no-temp case costs nothing.
|
|
659
|
+
*
|
|
660
|
+
* @inner
|
|
661
|
+
* @param {*} err - The failure to report; wrapped if not an Error.
|
|
662
|
+
* @returns {void}
|
|
663
|
+
*/
|
|
664
|
+
var onError = function(err) {
|
|
665
|
+
if (settled) return;
|
|
666
|
+
settled = true;
|
|
667
|
+
try { stream.destroy(); } catch (_e) {}
|
|
668
|
+
|
|
669
|
+
var finish = function() {
|
|
670
|
+
try { fs.rmSync(tmp, { force: true }); } catch (_e) {}
|
|
671
|
+
fn(( err instanceof Error ) ? err : new Error(String(err)));
|
|
672
|
+
};
|
|
673
|
+
|
|
674
|
+
// Already closed (an autoDestroyed ws that errored) would never
|
|
675
|
+
// emit 'close' again — that arm must not wait for it.
|
|
676
|
+
if ( ws.closed || ws.destroyed ) { return finish(); }
|
|
677
|
+
ws.once('close', finish);
|
|
678
|
+
try { ws.destroy(); } catch (_e) { finish(); }
|
|
679
|
+
};
|
|
680
|
+
|
|
681
|
+
// #B143 — armed at creation, never in a later handler: Node does not
|
|
682
|
+
// replay 'error' for a listener attached after the fact, and an
|
|
683
|
+
// unlistened stream 'error' becomes an uncaughtException that takes
|
|
684
|
+
// the bundle down. Both streams need their own; `.pipe()` returns the
|
|
685
|
+
// DESTINATION, so a single chained listener would cover only one.
|
|
686
|
+
ws.on('error', onError);
|
|
687
|
+
stream.on('error', onError);
|
|
688
|
+
|
|
689
|
+
// 'close' also follows 'error' on an autoDestroyed stream, so the
|
|
690
|
+
// settled latch is what keeps a failed write from publishing.
|
|
691
|
+
ws.on('close', function() {
|
|
692
|
+
if (settled) return;
|
|
693
|
+
|
|
694
|
+
var size;
|
|
695
|
+
try {
|
|
696
|
+
size = fs.statSync(tmp).size;
|
|
697
|
+
if ( fsyncOn ) { fsyncFile(tmp); }
|
|
698
|
+
fs.renameSync(tmp, final);
|
|
699
|
+
if ( fsyncOn ) { fsyncDirBestEffort(nodePath.dirname(final)); }
|
|
700
|
+
} catch (err) {
|
|
701
|
+
return onError(err);
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
settled = true;
|
|
705
|
+
metaStore.set(key, {
|
|
706
|
+
originalName : ( typeof meta.originalName === 'string' ) ? meta.originalName : null,
|
|
707
|
+
contentType : ( typeof meta.contentType === 'string' ) ? meta.contentType : null,
|
|
708
|
+
size : size,
|
|
709
|
+
createdAt : now.getTime()
|
|
710
|
+
}, function(metaErr) {
|
|
711
|
+
if (metaErr) {
|
|
712
|
+
// The bytes are published but unindexed. Roll the object
|
|
713
|
+
// back rather than leave a file `stat()` will deny exists
|
|
714
|
+
// — a caller that got no key can never reference it.
|
|
715
|
+
try { fs.unlinkSync(final); } catch (_e) {}
|
|
716
|
+
return fn(metaErr);
|
|
717
|
+
}
|
|
718
|
+
fn(null, {
|
|
719
|
+
key : key,
|
|
720
|
+
size : size,
|
|
721
|
+
contentType : ( typeof meta.contentType === 'string' ) ? meta.contentType : null
|
|
722
|
+
});
|
|
723
|
+
});
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
stream.on('data', function(chunk) {
|
|
727
|
+
if (settled) return;
|
|
728
|
+
written += chunk.length;
|
|
729
|
+
if ( written > max ) {
|
|
730
|
+
return onError(new Error('[storage:' + name + '] object exceeds maxObjectSize (' + max + ' bytes) — refused'));
|
|
731
|
+
}
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
stream.pipe(ws);
|
|
735
|
+
},
|
|
736
|
+
|
|
737
|
+
/**
|
|
738
|
+
* Open a resumable upload session.
|
|
739
|
+
*
|
|
740
|
+
* **`expectedSize` is REQUIRED**, and that is the deliberate exclusion
|
|
741
|
+
* in this contract: without a declared total, `finalize()` cannot
|
|
742
|
+
* verify that the received ranges actually cover the object, and
|
|
743
|
+
* `statUpload()` cannot say what is MISSING — only what arrived. A
|
|
744
|
+
* caller that does not know the size uses `put()`. (The equivalent
|
|
745
|
+
* wire protocols agree in spirit: tus requires `Upload-Length` unless
|
|
746
|
+
* a server opts into the separate defer-length extension, and S3
|
|
747
|
+
* multipart simply cannot report missing parts because it never learns
|
|
748
|
+
* the total.)
|
|
749
|
+
*
|
|
750
|
+
* The declared size permits an early `maxObjectSize` refusal before
|
|
751
|
+
* any bytes move — **advisory only**: `expectedSize` is
|
|
752
|
+
* client-supplied, so the running byte counter in `writeSegment` stays
|
|
753
|
+
* the real enforcement.
|
|
754
|
+
*
|
|
755
|
+
* **The KEY is minted here**, not at finalise, and persisted in the
|
|
756
|
+
* manifest. Minting it at finalise would mean that a row write failing
|
|
757
|
+
* after the bytes are published leaves them at an address nobody
|
|
758
|
+
* holds — an unreachable orphan plus a session that can never finalise
|
|
759
|
+
* again, which is strictly worse than the row simply being retried.
|
|
760
|
+
*
|
|
761
|
+
* @param {object} meta - Session metadata.
|
|
762
|
+
* @param {number} meta.expectedSize - The object's total size in bytes. Required;
|
|
763
|
+
* a positive integer, at most `maxObjectSize`.
|
|
764
|
+
* @param {string} [meta.originalName] - Filename; stored verbatim, and the only
|
|
765
|
+
* source of the key's extension.
|
|
766
|
+
* @param {string} [meta.contentType] - MIME type; stored verbatim.
|
|
767
|
+
* @param {function} fn - `fn(err, {@link StorageUploadSession})`.
|
|
768
|
+
* @returns {void}
|
|
769
|
+
*
|
|
770
|
+
* @example
|
|
771
|
+
* driver.createUpload({ expectedSize: 4294967296, originalName: 'talk.mp4' },
|
|
772
|
+
* function (err, session) {
|
|
773
|
+
* if (err) { return next(err); }
|
|
774
|
+
* res.setHeader('x-upload-id', session.uploadId);
|
|
775
|
+
* });
|
|
776
|
+
*/
|
|
777
|
+
createUpload: function(meta, fn) {
|
|
778
|
+
if ( typeof meta === 'function' ) { fn = meta; meta = {}; }
|
|
779
|
+
meta = meta || {};
|
|
780
|
+
if ( typeof fn !== 'function' ) {
|
|
781
|
+
throw new Error('[storage:' + name + '] createUpload() requires a callback');
|
|
782
|
+
}
|
|
783
|
+
if ( !Number.isInteger(meta.expectedSize) || meta.expectedSize <= 0 ) {
|
|
784
|
+
return fn(new Error('[storage:' + name + '] createUpload() needs `expectedSize`, a positive integer byte count (got: ' + JSON.stringify(meta.expectedSize) + ') — a resumable upload of unknown total size is not supported; use put() for that'));
|
|
785
|
+
}
|
|
786
|
+
if ( meta.expectedSize > max ) {
|
|
787
|
+
return fn(new Error('[storage:' + name + '] expectedSize ' + meta.expectedSize + ' exceeds maxObjectSize (' + max + ' bytes) — refused before any bytes were transferred'));
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
var now = Date.now();
|
|
791
|
+
var id = util.ulid(now);
|
|
792
|
+
var ext = util.sanitiseExtension(meta.originalName);
|
|
793
|
+
// ONE ulid names the session AND the asset it becomes: an
|
|
794
|
+
// operational convenience (the session dir and the asset dir share
|
|
795
|
+
// a name in the same root), never something a caller may rely on —
|
|
796
|
+
// both values stay opaque.
|
|
797
|
+
var manifest = {
|
|
798
|
+
key : 'assets/' + id + '/' + ORIGINAL + ext,
|
|
799
|
+
expectedSize : meta.expectedSize,
|
|
800
|
+
originalName : ( typeof meta.originalName === 'string' ) ? meta.originalName : null,
|
|
801
|
+
contentType : ( typeof meta.contentType === 'string' ) ? meta.contentType : null,
|
|
802
|
+
createdAt : now,
|
|
803
|
+
chunkSize : chunkSize
|
|
804
|
+
};
|
|
805
|
+
var dir = nodePath.join(uploadsD, id);
|
|
806
|
+
var metaTmp = nodePath.join(dir, META_FILE + '.tmp');
|
|
807
|
+
|
|
808
|
+
try {
|
|
809
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
810
|
+
// The assembling file exists from the start, so `mtime(data)`
|
|
811
|
+
// is a valid liveness signal for the sweep from the first
|
|
812
|
+
// moment, and so a positional write can open it 'r+'.
|
|
813
|
+
fs.closeSync(fs.openSync(nodePath.join(dir, DATA_FILE), 'w'));
|
|
814
|
+
// temp+rename: a crash mid-write can only leave the manifest
|
|
815
|
+
// ABSENT, never truncated, which is what makes "missing or
|
|
816
|
+
// unparseable ⇒ fail loudly" a safe rule rather than a trap.
|
|
817
|
+
fs.writeFileSync(metaTmp, JSON.stringify(manifest));
|
|
818
|
+
if ( fsyncOn ) { fsyncFile(metaTmp); }
|
|
819
|
+
fs.renameSync(metaTmp, nodePath.join(dir, META_FILE));
|
|
820
|
+
} catch (err) {
|
|
821
|
+
dropSession(dir);
|
|
822
|
+
return fn(err);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
fn(null, { uploadId: id, chunkSize: chunkSize, expectedSize: meta.expectedSize });
|
|
826
|
+
},
|
|
827
|
+
|
|
828
|
+
/**
|
|
829
|
+
* Write one segment of a resumable upload at `offset`.
|
|
830
|
+
*
|
|
831
|
+
* Segments may arrive OUT OF ORDER and in parallel — the bytes are
|
|
832
|
+
* written positionally into the session's `data` file, so no assembly
|
|
833
|
+
* pass is needed at finalise (measured: a segment-per-file layout costs
|
|
834
|
+
* ~1.9 s/GB of extra concatenation I/O at finalise; this costs ~4 ms).
|
|
835
|
+
* Re-sending a range that already landed is idempotent, and re-sends
|
|
836
|
+
* may overlap freely.
|
|
837
|
+
*
|
|
838
|
+
* A segment whose source ends early is NOT an error: the marker
|
|
839
|
+
* records the RECEIVED length only, so the coverage union stays exact
|
|
840
|
+
* and the client re-sends the remainder. A segment that would run past
|
|
841
|
+
* `expectedSize` IS an error — that is where a lying `expectedSize` is
|
|
842
|
+
* actually stopped, since the declared value is only advisory.
|
|
843
|
+
*
|
|
844
|
+
* Durability order is load-bearing: the data is fsynced BEFORE the
|
|
845
|
+
* marker is created, so a surviving marker implies durable bytes. The
|
|
846
|
+
* marker's own durability is deliberately not guaranteed — a lost
|
|
847
|
+
* marker beside present bytes costs a re-send, which is the safe
|
|
848
|
+
* direction.
|
|
849
|
+
*
|
|
850
|
+
* @param {string} uploadId - The session id from `createUpload`.
|
|
851
|
+
* @param {number} offset - Byte offset this segment starts at; integer >= 0.
|
|
852
|
+
* @param {object} stream - Any readable stream carrying the segment's bytes.
|
|
853
|
+
* @param {function} fn - `fn(err, {offset, length, received})` — `length` is what
|
|
854
|
+
* THIS call made durable, `received` the session's total
|
|
855
|
+
* covered bytes afterwards.
|
|
856
|
+
* @returns {void}
|
|
857
|
+
*
|
|
858
|
+
* @example
|
|
859
|
+
* driver.writeSegment(uploadId, 8388608, req, function (err, r) {
|
|
860
|
+
* if (err) { return next(err); }
|
|
861
|
+
* res.setHeader('x-received', r.received);
|
|
862
|
+
* });
|
|
863
|
+
*/
|
|
864
|
+
writeSegment: function(uploadId, offset, stream, fn) {
|
|
865
|
+
if ( typeof fn !== 'function' ) {
|
|
866
|
+
throw new Error('[storage:' + name + '] writeSegment() requires a callback');
|
|
867
|
+
}
|
|
868
|
+
if ( !stream || typeof stream.pipe !== 'function' ) {
|
|
869
|
+
return fn(new Error('[storage:' + name + '] writeSegment() requires a readable stream'));
|
|
870
|
+
}
|
|
871
|
+
var s = resolveSession(uploadId);
|
|
872
|
+
if ( s.error ) { return fn(s.error); }
|
|
873
|
+
if ( !Number.isInteger(offset) || offset < 0 ) {
|
|
874
|
+
return fn(new Error('[storage:' + name + '] writeSegment(): offset must be an integer >= 0 (got: ' + JSON.stringify(offset) + ')'));
|
|
875
|
+
}
|
|
876
|
+
var man = readManifest(s.dir);
|
|
877
|
+
if ( man.error ) { return fn(man.error); }
|
|
878
|
+
if ( offset >= man.meta.expectedSize ) {
|
|
879
|
+
return fn(new Error('[storage:' + name + '] writeSegment(): offset ' + offset + ' is at or beyond the declared size (' + man.meta.expectedSize + ') for upload `' + uploadId + '`'));
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
var dataPath = nodePath.join(s.dir, DATA_FILE);
|
|
883
|
+
var remaining = man.meta.expectedSize - offset;
|
|
884
|
+
var settled = false;
|
|
885
|
+
var written = 0;
|
|
886
|
+
|
|
887
|
+
// 'r+' + `start` writes IN PLACE at the offset: no truncation, no
|
|
888
|
+
// append, and the holes a not-yet-written range leaves read back
|
|
889
|
+
// as zeros — which is exactly why finalise verifies coverage
|
|
890
|
+
// rather than trusting the file's size.
|
|
891
|
+
var ws = fs.createWriteStream(dataPath, {
|
|
892
|
+
flags : 'r+',
|
|
893
|
+
start : offset,
|
|
894
|
+
highWaterMark : chunkSize || undefined
|
|
895
|
+
});
|
|
896
|
+
|
|
897
|
+
var onError = function(err) {
|
|
898
|
+
if (settled) return;
|
|
899
|
+
settled = true;
|
|
900
|
+
try { stream.destroy(); } catch (_e) {}
|
|
901
|
+
try { ws.destroy(); } catch (_e) {}
|
|
902
|
+
// The session is NOT dropped: partial bytes with no marker are
|
|
903
|
+
// invisible to the coverage check, so the client simply
|
|
904
|
+
// re-sends this range.
|
|
905
|
+
fn(( err instanceof Error ) ? err : new Error(String(err)));
|
|
906
|
+
};
|
|
907
|
+
|
|
908
|
+
// #B143 — armed at creation on both streams.
|
|
909
|
+
ws.on('error', onError);
|
|
910
|
+
stream.on('error', onError);
|
|
911
|
+
|
|
912
|
+
ws.on('close', function() {
|
|
913
|
+
if (settled) return;
|
|
914
|
+
settled = true;
|
|
915
|
+
try {
|
|
916
|
+
if ( written > 0 ) {
|
|
917
|
+
// data first, marker second — the whole durability
|
|
918
|
+
// contract of a resumable upload is in this order.
|
|
919
|
+
if ( fsyncOn ) { fsyncFile(dataPath); }
|
|
920
|
+
fs.closeSync(fs.openSync(nodePath.join(s.dir, offset + '-' + written + '.ok'), 'w'));
|
|
921
|
+
}
|
|
922
|
+
} catch (err) {
|
|
923
|
+
return fn(err);
|
|
924
|
+
}
|
|
925
|
+
fn(null, {
|
|
926
|
+
offset : offset,
|
|
927
|
+
length : written,
|
|
928
|
+
received : coveredBytes(readSpans(s.dir))
|
|
929
|
+
});
|
|
930
|
+
});
|
|
931
|
+
|
|
932
|
+
stream.on('data', function(chunk) {
|
|
933
|
+
if (settled) return;
|
|
934
|
+
written += chunk.length;
|
|
935
|
+
if ( written > remaining ) {
|
|
936
|
+
return onError(new Error('[storage:' + name + '] writeSegment(): the segment at offset ' + offset + ' runs past the declared size (' + man.meta.expectedSize + ' bytes) for upload `' + uploadId + '` — refused'));
|
|
937
|
+
}
|
|
938
|
+
});
|
|
939
|
+
|
|
940
|
+
stream.pipe(ws);
|
|
941
|
+
},
|
|
942
|
+
|
|
943
|
+
/**
|
|
944
|
+
* The state of a resumable upload — what landed, what is missing, and
|
|
945
|
+
* whether `finalize()` would succeed.
|
|
946
|
+
*
|
|
947
|
+
* The resumable twin of `stat()`, and not optional: resuming is
|
|
948
|
+
* impossible unless a client can learn what already arrived. Read
|
|
949
|
+
* straight from the session's markers, so it is accurate across a
|
|
950
|
+
* process restart and needs no in-memory session table.
|
|
951
|
+
*
|
|
952
|
+
* @param {string} uploadId - The session id from `createUpload`.
|
|
953
|
+
* @param {function} fn - `fn(err, {@link StorageUploadState})`.
|
|
954
|
+
* @returns {void}
|
|
955
|
+
*
|
|
956
|
+
* @example
|
|
957
|
+
* driver.statUpload(uploadId, function (err, st) {
|
|
958
|
+
* if (err) { return self.throwError(404); }
|
|
959
|
+
* if (st.complete) { return driver.finalize(uploadId, done); }
|
|
960
|
+
* res.json({ missing: st.missing }); // tell the client what to re-send
|
|
961
|
+
* });
|
|
962
|
+
*/
|
|
963
|
+
statUpload: function(uploadId, fn) {
|
|
964
|
+
if ( typeof fn !== 'function' ) {
|
|
965
|
+
throw new Error('[storage:' + name + '] statUpload() requires a callback');
|
|
966
|
+
}
|
|
967
|
+
var s = resolveSession(uploadId);
|
|
968
|
+
if ( s.error ) { return fn(s.error); }
|
|
969
|
+
var man = readManifest(s.dir);
|
|
970
|
+
if ( man.error ) { return fn(man.error); }
|
|
971
|
+
|
|
972
|
+
var spans = readSpans(s.dir);
|
|
973
|
+
var missing = missingSpans(spans, man.meta.expectedSize);
|
|
974
|
+
fn(null, {
|
|
975
|
+
expectedSize : man.meta.expectedSize,
|
|
976
|
+
contentType : man.meta.contentType,
|
|
977
|
+
originalName : man.meta.originalName,
|
|
978
|
+
createdAt : man.meta.createdAt,
|
|
979
|
+
received : spans,
|
|
980
|
+
missing : missing,
|
|
981
|
+
complete : ( missing.length === 0 )
|
|
982
|
+
});
|
|
983
|
+
},
|
|
984
|
+
|
|
985
|
+
/**
|
|
986
|
+
* Publish a completed resumable upload and return its opaque key.
|
|
987
|
+
*
|
|
988
|
+
* **Coverage is verified as an INTERVAL UNION** — the markers are
|
|
989
|
+
* merged and must form exactly one span equal to `[0, expectedSize)`.
|
|
990
|
+
* Summing their lengths would not do: markers `0–8MB` and `4–12MB` sum
|
|
991
|
+
* to 16MB with `12–16MB` uncovered, and because a hole reads back as
|
|
992
|
+
* ZEROS a sum-based check would publish a plausible, silently
|
|
993
|
+
* zero-filled object. A gap is a REAL error and the session is
|
|
994
|
+
* PRESERVED so the client can complete it — never a silent publish.
|
|
995
|
+
*
|
|
996
|
+
* **Bytes are published BEFORE the metadata row.** The inverse
|
|
997
|
+
* ordering would manufacture rows without files, which the `cas`
|
|
998
|
+
* strategy's own verify classifies as loss evidence and never
|
|
999
|
+
* auto-fixes. This ordering's residue is the benign kind: an object
|
|
1000
|
+
* whose row write failed is still readable by key (`get()` falls
|
|
1001
|
+
* through to the file), merely missing `originalName`/`contentType`.
|
|
1002
|
+
*
|
|
1003
|
+
* **Idempotent, with a heal path.** If the final path already exists
|
|
1004
|
+
* this call skips assembly, (re)writes the row and cleans the session
|
|
1005
|
+
* — so a `finalize` that published the bytes and then failed on the
|
|
1006
|
+
* row is completed by simply retrying it.
|
|
1007
|
+
*
|
|
1008
|
+
* `writeSegment` must not run concurrently with `finalize` on the same
|
|
1009
|
+
* session — that is a CLIENT contract, single-actor-per-finalise. The
|
|
1010
|
+
* pre-rename size check below is its cheap detection: bytes past the
|
|
1011
|
+
* declared size are truncated away, and bytes MISSING under a full set
|
|
1012
|
+
* of markers refuse the publish outright.
|
|
1013
|
+
*
|
|
1014
|
+
* @param {string} uploadId - The session id from `createUpload`.
|
|
1015
|
+
* @param {function} fn - `fn(err, {key, size, contentType})` — the same shape
|
|
1016
|
+
* `put()` returns.
|
|
1017
|
+
* @returns {void}
|
|
1018
|
+
*
|
|
1019
|
+
* @example
|
|
1020
|
+
* driver.finalize(uploadId, function (err, res) {
|
|
1021
|
+
* if (err) { return next(err); } // a coverage gap keeps the session alive
|
|
1022
|
+
* record.assetKey = res.key;
|
|
1023
|
+
* });
|
|
1024
|
+
*/
|
|
1025
|
+
finalize: function(uploadId, fn) {
|
|
1026
|
+
if ( typeof fn !== 'function' ) {
|
|
1027
|
+
throw new Error('[storage:' + name + '] finalize() requires a callback');
|
|
1028
|
+
}
|
|
1029
|
+
var s = resolveSession(uploadId);
|
|
1030
|
+
if ( s.error ) { return fn(s.error); }
|
|
1031
|
+
var man = readManifest(s.dir);
|
|
1032
|
+
if ( man.error ) { return fn(man.error); }
|
|
1033
|
+
|
|
1034
|
+
var meta = man.meta;
|
|
1035
|
+
var key = meta.key;
|
|
1036
|
+
var r = resolvePath(key);
|
|
1037
|
+
if ( r.error ) { return fn(r.error); }
|
|
1038
|
+
var final = r.path;
|
|
1039
|
+
var dataPath = nodePath.join(s.dir, DATA_FILE);
|
|
1040
|
+
|
|
1041
|
+
/**
|
|
1042
|
+
* Write the metadata row and, on success, drop the session. On
|
|
1043
|
+
* failure the session is KEPT: the bytes are already published, so
|
|
1044
|
+
* a retry takes the heal path above and completes the row.
|
|
1045
|
+
*
|
|
1046
|
+
* @inner
|
|
1047
|
+
* @param {number} size - The published object's size in bytes.
|
|
1048
|
+
* @returns {void}
|
|
1049
|
+
*/
|
|
1050
|
+
var indexAndFinish = function(size) {
|
|
1051
|
+
metaStore.set(key, {
|
|
1052
|
+
originalName : meta.originalName,
|
|
1053
|
+
contentType : meta.contentType,
|
|
1054
|
+
size : size,
|
|
1055
|
+
createdAt : ( typeof meta.createdAt === 'number' ) ? meta.createdAt : Date.now()
|
|
1056
|
+
}, function(metaErr) {
|
|
1057
|
+
if (metaErr) { return fn(metaErr); }
|
|
1058
|
+
dropSession(s.dir);
|
|
1059
|
+
fn(null, { key: key, size: size, contentType: meta.contentType });
|
|
1060
|
+
});
|
|
1061
|
+
};
|
|
1062
|
+
|
|
1063
|
+
// Heal path: the bytes are already where they belong, so this is a
|
|
1064
|
+
// retry of a finalise whose row write failed (or a duplicate call).
|
|
1065
|
+
if ( fs.existsSync(final) ) {
|
|
1066
|
+
var healSize;
|
|
1067
|
+
try {
|
|
1068
|
+
healSize = fs.statSync(final).size;
|
|
1069
|
+
} catch (err) {
|
|
1070
|
+
return fn(err);
|
|
1071
|
+
}
|
|
1072
|
+
return indexAndFinish(healSize);
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
var spans = readSpans(s.dir);
|
|
1076
|
+
var missing = missingSpans(spans, meta.expectedSize);
|
|
1077
|
+
if ( missing.length ) {
|
|
1078
|
+
return fn(new Error('[storage:' + name + '] upload `' + uploadId + '` is incomplete — '
|
|
1079
|
+
+ missing.length + ' range(s) missing, ' + coveredBytes(missing) + ' of '
|
|
1080
|
+
+ meta.expectedSize + ' bytes (first gap at offset ' + missing[0].offset + ', '
|
|
1081
|
+
+ missing[0].length + ' bytes). The session is preserved; send the missing ranges and finalise again.'));
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
var size;
|
|
1085
|
+
try {
|
|
1086
|
+
size = fs.statSync(dataPath).size;
|
|
1087
|
+
if ( size < meta.expectedSize ) {
|
|
1088
|
+
// Markers claim coverage the bytes do not back — the one
|
|
1089
|
+
// shape that would publish zeros with a full marker set.
|
|
1090
|
+
// Refuse and preserve; nothing here can safely repair it.
|
|
1091
|
+
return fn(new Error('[storage:' + name + '] upload `' + uploadId + '` claims full coverage but its assembling file is only '
|
|
1092
|
+
+ size + ' of ' + meta.expectedSize + ' bytes — refusing to publish a short object. The session is preserved.'));
|
|
1093
|
+
}
|
|
1094
|
+
if ( size > meta.expectedSize ) {
|
|
1095
|
+
// Unreachable through this API (every segment is capped),
|
|
1096
|
+
// so this is external tampering or a client-contract
|
|
1097
|
+
// violation. Coverage proves [0, expectedSize) is durable;
|
|
1098
|
+
// drop whatever is past the declared end.
|
|
1099
|
+
fs.truncateSync(dataPath, meta.expectedSize);
|
|
1100
|
+
size = meta.expectedSize;
|
|
1101
|
+
}
|
|
1102
|
+
if ( fsyncOn ) { fsyncFile(dataPath); }
|
|
1103
|
+
fs.mkdirSync(nodePath.dirname(final), { recursive: true });
|
|
1104
|
+
fs.renameSync(dataPath, final);
|
|
1105
|
+
if ( fsyncOn ) { fsyncDirBestEffort(nodePath.dirname(final)); }
|
|
1106
|
+
} catch (err) {
|
|
1107
|
+
return fn(err);
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
indexAndFinish(size);
|
|
1111
|
+
},
|
|
1112
|
+
|
|
1113
|
+
/**
|
|
1114
|
+
* Discard a resumable upload and everything it accumulated.
|
|
1115
|
+
*
|
|
1116
|
+
* Idempotent: aborting an unknown or already-aborted session answers
|
|
1117
|
+
* `{aborted: false}` rather than erroring — the caller asked for the
|
|
1118
|
+
* session to be gone, and it is. An invalid id is still refused, since
|
|
1119
|
+
* that is a caller bug rather than a state.
|
|
1120
|
+
*
|
|
1121
|
+
* @param {string} uploadId - The session id from `createUpload`.
|
|
1122
|
+
* @param {function} [fn] - `fn(err, {aborted})`.
|
|
1123
|
+
* @returns {void}
|
|
1124
|
+
*
|
|
1125
|
+
* @example
|
|
1126
|
+
* driver.abortUpload(uploadId, function (err, r) {
|
|
1127
|
+
* // r.aborted === false when it was already gone
|
|
1128
|
+
* });
|
|
1129
|
+
*/
|
|
1130
|
+
abortUpload: function(uploadId, fn) {
|
|
1131
|
+
if ( typeof fn !== 'function' ) { fn = function() {}; }
|
|
1132
|
+
var s = resolveSession(uploadId);
|
|
1133
|
+
if ( s.error ) { return fn(s.error); }
|
|
1134
|
+
var existed = fs.existsSync(s.dir);
|
|
1135
|
+
dropSession(s.dir);
|
|
1136
|
+
fn(null, { aborted: existed });
|
|
1137
|
+
},
|
|
1138
|
+
|
|
1139
|
+
/**
|
|
1140
|
+
* Open a readable stream for `key`.
|
|
1141
|
+
*
|
|
1142
|
+
* Errors (rather than yielding `null`) on an unknown key: a caller
|
|
1143
|
+
* asking for bytes has no use for a null stream, and the existence
|
|
1144
|
+
* question belongs to `stat()`.
|
|
1145
|
+
*
|
|
1146
|
+
* @param {string} key - The opaque storage key.
|
|
1147
|
+
* @param {function} fn - `fn(err, stream)`.
|
|
1148
|
+
* @returns {void}
|
|
1149
|
+
*
|
|
1150
|
+
* @example
|
|
1151
|
+
* driver.get(key, function (err, stream) {
|
|
1152
|
+
* if (err) { return self.throwError(404); }
|
|
1153
|
+
* stream.pipe(res);
|
|
1154
|
+
* });
|
|
1155
|
+
*/
|
|
1156
|
+
get: function(key, fn) {
|
|
1157
|
+
if ( typeof fn !== 'function' ) {
|
|
1158
|
+
throw new Error('[storage:' + name + '] get() requires a callback');
|
|
1159
|
+
}
|
|
1160
|
+
var r = resolvePath(key);
|
|
1161
|
+
if ( r.error ) { return fn(r.error); }
|
|
1162
|
+
if ( !fs.existsSync(r.path) ) {
|
|
1163
|
+
return fn(util.codedError('STORAGE_NO_OBJECT', '[storage:' + name + '] no object for key `' + key + '`'));
|
|
1164
|
+
}
|
|
1165
|
+
var rs = fs.createReadStream(r.path, { highWaterMark: chunkSize || undefined });
|
|
1166
|
+
// Armed before the caller can touch it (#B143) — an fs error after
|
|
1167
|
+
// handoff is the caller's, but one raised during open is ours.
|
|
1168
|
+
var handed = false;
|
|
1169
|
+
rs.on('error', function(err) {
|
|
1170
|
+
if (handed) return;
|
|
1171
|
+
handed = true;
|
|
1172
|
+
fn(err);
|
|
1173
|
+
});
|
|
1174
|
+
rs.on('open', function() {
|
|
1175
|
+
if (handed) return;
|
|
1176
|
+
handed = true;
|
|
1177
|
+
fn(null, rs);
|
|
1178
|
+
});
|
|
1179
|
+
},
|
|
1180
|
+
|
|
1181
|
+
/**
|
|
1182
|
+
* Byte-range read — the driver half of HTTP Range serving, and the
|
|
1183
|
+
* verb this strategy exists to feed.
|
|
1184
|
+
*
|
|
1185
|
+
* `end` is INCLUSIVE, matching both the HTTP `Range` header and
|
|
1186
|
+
* `fs.createReadStream({start, end})`, so no arithmetic happens at the
|
|
1187
|
+
* boundary between them. An `end` past the last byte is CLAMPED rather
|
|
1188
|
+
* than refused (RFC 9110: a range whose start is satisfiable is
|
|
1189
|
+
* satisfiable); only a `start` at or beyond the object's size is
|
|
1190
|
+
* unsatisfiable, and that is the caller's 416.
|
|
1191
|
+
*
|
|
1192
|
+
* The framework consumes this verb through the controller's
|
|
1193
|
+
* `serveFromStorage()` facade, which answers 206/416 with
|
|
1194
|
+
* `Accept-Ranges`/`Content-Range` on both engines, gated on
|
|
1195
|
+
* `capabilities.ranges`.
|
|
1196
|
+
*
|
|
1197
|
+
* @param {string} key - The opaque storage key.
|
|
1198
|
+
* @param {number} start - First byte offset, inclusive; integer >= 0.
|
|
1199
|
+
* @param {number} end - Last byte offset, INCLUSIVE; integer >= start.
|
|
1200
|
+
* @param {function} fn - `fn(err, stream)`.
|
|
1201
|
+
* @returns {void}
|
|
1202
|
+
*
|
|
1203
|
+
* @example
|
|
1204
|
+
* // Serving `Range: bytes=0-1023`
|
|
1205
|
+
* driver.getRange(key, 0, 1023, function (err, stream) {
|
|
1206
|
+
* if (err) { return self.throwError(416); }
|
|
1207
|
+
* stream.pipe(res);
|
|
1208
|
+
* });
|
|
1209
|
+
*/
|
|
1210
|
+
getRange: function(key, start, end, fn) {
|
|
1211
|
+
if ( typeof fn !== 'function' ) {
|
|
1212
|
+
throw new Error('[storage:' + name + '] getRange() requires a callback');
|
|
1213
|
+
}
|
|
1214
|
+
if ( !Number.isInteger(start) || !Number.isInteger(end) || start < 0 || end < start ) {
|
|
1215
|
+
return fn(util.codedError('STORAGE_INVALID_RANGE', '[storage:' + name + '] getRange(): invalid range [' + start + ', ' + end + '] — both bounds must be integers with 0 <= start <= end'));
|
|
1216
|
+
}
|
|
1217
|
+
var r = resolvePath(key);
|
|
1218
|
+
if ( r.error ) { return fn(r.error); }
|
|
1219
|
+
if ( !fs.existsSync(r.path) ) {
|
|
1220
|
+
return fn(util.codedError('STORAGE_NO_OBJECT', '[storage:' + name + '] no object for key `' + key + '`'));
|
|
1221
|
+
}
|
|
1222
|
+
var size = fs.statSync(r.path).size;
|
|
1223
|
+
if ( start >= size ) {
|
|
1224
|
+
return fn(util.codedError('STORAGE_RANGE_UNSATISFIABLE', '[storage:' + name + '] getRange(): start ' + start + ' is beyond the object size (' + size + ') for key `' + key + '`'));
|
|
1225
|
+
}
|
|
1226
|
+
var rs = fs.createReadStream(r.path, { start: start, end: Math.min(end, size - 1) });
|
|
1227
|
+
var handed = false;
|
|
1228
|
+
rs.on('error', function(err) {
|
|
1229
|
+
if (handed) return;
|
|
1230
|
+
handed = true;
|
|
1231
|
+
fn(err);
|
|
1232
|
+
});
|
|
1233
|
+
rs.on('open', function() {
|
|
1234
|
+
if (handed) return;
|
|
1235
|
+
handed = true;
|
|
1236
|
+
fn(null, rs);
|
|
1237
|
+
});
|
|
1238
|
+
},
|
|
1239
|
+
|
|
1240
|
+
/**
|
|
1241
|
+
* Metadata for `key`.
|
|
1242
|
+
*
|
|
1243
|
+
* @param {string} key - The opaque storage key.
|
|
1244
|
+
* @param {function} fn - `fn(err, meta|null)`; `null` when the key is unknown.
|
|
1245
|
+
* @returns {void}
|
|
1246
|
+
*
|
|
1247
|
+
* @example
|
|
1248
|
+
* driver.stat(key, function (err, meta) {
|
|
1249
|
+
* if (!meta) { return self.throwError(404); }
|
|
1250
|
+
* res.setHeader('Content-Type', meta.contentType);
|
|
1251
|
+
* });
|
|
1252
|
+
*/
|
|
1253
|
+
stat: function(key, fn) {
|
|
1254
|
+
if ( typeof fn !== 'function' ) {
|
|
1255
|
+
throw new Error('[storage:' + name + '] stat() requires a callback');
|
|
1256
|
+
}
|
|
1257
|
+
var r = resolvePath(key);
|
|
1258
|
+
if ( r.error ) { return fn(r.error); }
|
|
1259
|
+
metaStore.get(key, function(err, m) {
|
|
1260
|
+
if (err) { return fn(err); }
|
|
1261
|
+
if (!m) { return fn(null, null); }
|
|
1262
|
+
fn(null, {
|
|
1263
|
+
originalName : m.originalName,
|
|
1264
|
+
contentType : m.contentType,
|
|
1265
|
+
size : m.size,
|
|
1266
|
+
createdAt : m.createdAt
|
|
1267
|
+
});
|
|
1268
|
+
});
|
|
1269
|
+
},
|
|
1270
|
+
|
|
1271
|
+
/**
|
|
1272
|
+
* Delete the object and its metadata row.
|
|
1273
|
+
*
|
|
1274
|
+
* The asset's own directory is removed too when nothing else is left
|
|
1275
|
+
* in it — which is the operational half of the per-asset layout, and
|
|
1276
|
+
* why a future rendition sitting beside `original` correctly keeps the
|
|
1277
|
+
* directory alive.
|
|
1278
|
+
*
|
|
1279
|
+
* @param {string} key - The opaque storage key.
|
|
1280
|
+
* @param {function} [fn] - `fn(err, existed)`.
|
|
1281
|
+
* @returns {void}
|
|
1282
|
+
*/
|
|
1283
|
+
release: function(key, fn) {
|
|
1284
|
+
if ( typeof fn !== 'function' ) { fn = function() {}; }
|
|
1285
|
+
var r = resolvePath(key);
|
|
1286
|
+
if ( r.error ) { return fn(r.error); }
|
|
1287
|
+
|
|
1288
|
+
var existed = false;
|
|
1289
|
+
try {
|
|
1290
|
+
if ( fs.existsSync(r.path) ) {
|
|
1291
|
+
fs.unlinkSync(r.path);
|
|
1292
|
+
existed = true;
|
|
1293
|
+
}
|
|
1294
|
+
} catch (err) {
|
|
1295
|
+
// ENOENT means a concurrent release won the race, which is the
|
|
1296
|
+
// outcome the caller asked for; anything else is a real failure.
|
|
1297
|
+
if ( err.code !== 'ENOENT' ) { return fn(err); }
|
|
1298
|
+
}
|
|
1299
|
+
// Only succeeds while the asset directory is empty — a rendition
|
|
1300
|
+
// beside `original` keeps it, which is the point of the layout.
|
|
1301
|
+
try { fs.rmdirSync(nodePath.dirname(r.path)); } catch (e) { /* not empty, or gone */ }
|
|
1302
|
+
|
|
1303
|
+
metaStore.remove(key, function(metaErr, metaExisted) {
|
|
1304
|
+
if (metaErr) { return fn(metaErr); }
|
|
1305
|
+
fn(null, existed || !!metaExisted);
|
|
1306
|
+
});
|
|
1307
|
+
},
|
|
1308
|
+
|
|
1309
|
+
/**
|
|
1310
|
+
* How to serve `key` — always `{kind:'path'}` here: a `stream` driver
|
|
1311
|
+
* never inlines, and `'url'` arrives with the s3 adapter. Callers must
|
|
1312
|
+
* still branch on `kind` rather than assume a path is available,
|
|
1313
|
+
* because the driver behind a key is a configuration choice.
|
|
1314
|
+
*
|
|
1315
|
+
* @param {string} key - The opaque storage key.
|
|
1316
|
+
* @param {function} fn - `fn(err, {kind: 'path', path: string})`.
|
|
1317
|
+
* @returns {void}
|
|
1318
|
+
*
|
|
1319
|
+
* @example
|
|
1320
|
+
* driver.resolve(key, function (err, r) {
|
|
1321
|
+
* if (err) { return next(err); }
|
|
1322
|
+
* if (r.kind === 'path') { return res.sendFile(r.path); }
|
|
1323
|
+
* });
|
|
1324
|
+
*/
|
|
1325
|
+
resolve: function(key, opts, fn) {
|
|
1326
|
+
// optional middle `opts` — offload response overrides, consumed
|
|
1327
|
+
// by offload-capable adapters (`s3`); accepted and IGNORED here
|
|
1328
|
+
// so one facade call site serves every driver.
|
|
1329
|
+
if ( typeof opts === 'function' ) { fn = opts; }
|
|
1330
|
+
if ( typeof fn !== 'function' ) {
|
|
1331
|
+
throw new Error('[storage:' + name + '] resolve() requires a callback');
|
|
1332
|
+
}
|
|
1333
|
+
var r = resolvePath(key);
|
|
1334
|
+
if ( r.error ) { return fn(r.error); }
|
|
1335
|
+
if ( !fs.existsSync(r.path) ) {
|
|
1336
|
+
return fn(util.codedError('STORAGE_NO_OBJECT', '[storage:' + name + '] no object for key `' + key + '`'));
|
|
1337
|
+
}
|
|
1338
|
+
fn(null, { kind: 'path', path: r.path });
|
|
1339
|
+
},
|
|
1340
|
+
|
|
1341
|
+
/**
|
|
1342
|
+
* Driver-level statistics for the operator surface (`storage:stats`
|
|
1343
|
+
* and `GET /_gina/storage/stats`): the driver's identity — strategy,
|
|
1344
|
+
* root, capabilities — plus the metadata store's aggregate counts.
|
|
1345
|
+
*
|
|
1346
|
+
* The store half is OPTIONAL seam surface: a store lacking `stats()`
|
|
1347
|
+
* reports `store: null` ("store reports no stats") rather than
|
|
1348
|
+
* erroring, so a connector-backed driver degrades instead of failing
|
|
1349
|
+
* the whole stats sweep. In-flight upload sessions are NOT counted —
|
|
1350
|
+
* they hold no rows, and reporting them is deferred with the rest of
|
|
1351
|
+
* the stream maintenance surface.
|
|
1352
|
+
*
|
|
1353
|
+
* @param {function} fn - `fn(err, {name, strategy, root, capabilities, store})` —
|
|
1354
|
+
* `store` is `{objects, refcounted, zeroRefPending, inline,
|
|
1355
|
+
* bytes}`, or `null` when the store reports no stats.
|
|
1356
|
+
* @returns {void}
|
|
1357
|
+
*
|
|
1358
|
+
* @example
|
|
1359
|
+
* driver.stats(function (err, s) {
|
|
1360
|
+
* if (err) { return next(err); }
|
|
1361
|
+
* // s.store.objects, s.store.bytes …
|
|
1362
|
+
* });
|
|
1363
|
+
*/
|
|
1364
|
+
stats: function(fn) {
|
|
1365
|
+
if ( typeof fn !== 'function' ) {
|
|
1366
|
+
throw new Error('[storage:' + name + '] stats() requires a callback');
|
|
1367
|
+
}
|
|
1368
|
+
var view = {
|
|
1369
|
+
name : name,
|
|
1370
|
+
strategy : conf.strategy,
|
|
1371
|
+
root : root,
|
|
1372
|
+
capabilities : capabilities,
|
|
1373
|
+
store : null
|
|
1374
|
+
};
|
|
1375
|
+
if ( typeof metaStore.stats !== 'function' ) {
|
|
1376
|
+
return fn(null, view);
|
|
1377
|
+
}
|
|
1378
|
+
metaStore.stats(function(err, s) {
|
|
1379
|
+
if (err) { return fn(err); }
|
|
1380
|
+
view.store = s;
|
|
1381
|
+
fn(null, view);
|
|
1382
|
+
});
|
|
1383
|
+
},
|
|
1384
|
+
|
|
1385
|
+
/**
|
|
1386
|
+
* What this driver can do. `resumable` is the stream flag — it is what
|
|
1387
|
+
* gates the five session verbs. `inline` and `dedup` are `false` BY
|
|
1388
|
+
* DESIGN here, not pending: a multi-gigabyte strategy does not inline,
|
|
1389
|
+
* and the write path deliberately does not hash.
|
|
1390
|
+
*
|
|
1391
|
+
* `offload` is `false` and measured, not assumed: no X-Accel /
|
|
1392
|
+
* X-Sendfile handling exists anywhere in either engine today, so a
|
|
1393
|
+
* caller must stream bytes itself.
|
|
1394
|
+
*
|
|
1395
|
+
* @type {{offload: boolean, ranges: boolean, dedup: boolean, resumable: boolean, inline: boolean}}
|
|
1396
|
+
*/
|
|
1397
|
+
capabilities: capabilities,
|
|
1398
|
+
|
|
1399
|
+
/**
|
|
1400
|
+
* Stop the session sweep timer and release the metadata handle.
|
|
1401
|
+
* Teardown and tests only — the runtime never calls it.
|
|
1402
|
+
*
|
|
1403
|
+
* @returns {void}
|
|
1404
|
+
*/
|
|
1405
|
+
close: function() {
|
|
1406
|
+
if ( sweepTimer ) {
|
|
1407
|
+
clearInterval(sweepTimer);
|
|
1408
|
+
sweepTimer = null;
|
|
1409
|
+
}
|
|
1410
|
+
try { metaStore.close(); } catch (e) { /* already closed */ }
|
|
1411
|
+
},
|
|
1412
|
+
|
|
1413
|
+
// test seam — the sweep has no operator door in this slice (the
|
|
1414
|
+
// `storage:gc` family stays cas-only), so this is what drives it
|
|
1415
|
+
// deterministically instead of waiting on the interval.
|
|
1416
|
+
_sweepAbandoned: sweepAbandoned
|
|
1417
|
+
};
|
|
1418
|
+
};
|