@omega.js/manager 0.1.0
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/.claude-plugin/marketplace.json +14 -0
- package/LICENSE +98 -0
- package/README.md +165 -0
- package/bin/omega +2 -0
- package/bin/omg +2 -0
- package/claude-plugin/.claude-plugin/plugin.json +11 -0
- package/claude-plugin/.mcp.json +9 -0
- package/claude-plugin/README.md +139 -0
- package/claude-plugin/hooks/gate/mark.sh +58 -0
- package/claude-plugin/hooks/gate/run.sh +97 -0
- package/claude-plugin/hooks/guard/run.sh +249 -0
- package/claude-plugin/hooks/hooks.json +63 -0
- package/claude-plugin/hooks/inject/run.sh +127 -0
- package/claude-plugin/hooks/lib/omega-gate.sh +20 -0
- package/claude-plugin/hooks/lib/omega-scope.sh +41 -0
- package/claude-plugin/hooks/lib/omega-skills.sh +209 -0
- package/claude-plugin/hooks/quality/run.sh +132 -0
- package/claude-plugin/hooks/shape/run.sh +45 -0
- package/claude-plugin/mcp-router-launch.js +29 -0
- package/claude-plugin/skills/README.md +48 -0
- package/claude-plugin/skills/accessibility/SKILL.md +31 -0
- package/claude-plugin/skills/analytics/SKILL.md +30 -0
- package/claude-plugin/skills/backend/SKILL.md +25 -0
- package/claude-plugin/skills/brandcheck/SKILL.md +29 -0
- package/claude-plugin/skills/browser/SKILL.md +64 -0
- package/claude-plugin/skills/client/SKILL.md +24 -0
- package/claude-plugin/skills/desktop/SKILL.md +25 -0
- package/claude-plugin/skills/extension/SKILL.md +25 -0
- package/claude-plugin/skills/main/SKILL.md +54 -0
- package/claude-plugin/skills/manager/SKILL.md +25 -0
- package/claude-plugin/skills/seo/SKILL.md +36 -0
- package/claude-plugin/skills/theme/SKILL.md +33 -0
- package/claude-plugin/skills/web/SKILL.md +29 -0
- package/dist/cli-run.js +34 -0
- package/dist/cli.js +29 -0
- package/dist/commands/build.js +17 -0
- package/dist/commands/clean.js +16 -0
- package/dist/commands/company.js +46 -0
- package/dist/commands/deploy.js +143 -0
- package/dist/commands/dev.js +437 -0
- package/dist/commands/devlog.js +25 -0
- package/dist/commands/help.js +48 -0
- package/dist/commands/manage.js +53 -0
- package/dist/commands/migrate.js +76 -0
- package/dist/commands/onboard.js +31 -0
- package/dist/commands/pipeline.js +319 -0
- package/dist/commands/test.js +380 -0
- package/dist/commands/update.js +128 -0
- package/dist/commands/version.js +8 -0
- package/dist/company-init.js +167 -0
- package/dist/company.js +283 -0
- package/dist/config.js +1359 -0
- package/dist/devlog/index.js +226 -0
- package/dist/devlog/lib/collect-commits.js +136 -0
- package/dist/devlog/lib/generate-post.js +201 -0
- package/dist/devlog/lib/ghostii.js +122 -0
- package/dist/devlog/lib/project-map.js +98 -0
- package/dist/devlog/lib/publish-website.js +88 -0
- package/dist/index.js +45 -0
- package/dist/lib/agents-md.js +201 -0
- package/dist/lib/analytics-secret.js +22 -0
- package/dist/lib/argv.js +21 -0
- package/dist/lib/auth-admin.js +120 -0
- package/dist/lib/automation-client.js +210 -0
- package/dist/lib/backend-marketing.js +49 -0
- package/dist/lib/brand.js +240 -0
- package/dist/lib/bundle-id.js +53 -0
- package/dist/lib/claude-settings.js +125 -0
- package/dist/lib/company-scaffold.js +213 -0
- package/dist/lib/company.js +227 -0
- package/dist/lib/config-flow.js +350 -0
- package/dist/lib/config-write.js +41 -0
- package/dist/lib/custom-target.js +75 -0
- package/dist/lib/domain-utils.js +19 -0
- package/dist/lib/duration.js +39 -0
- package/dist/lib/env-order.js +236 -0
- package/dist/lib/env-secret.js +78 -0
- package/dist/lib/firestore-rest.js +326 -0
- package/dist/lib/framework-bin.js +156 -0
- package/dist/lib/gitignore.js +63 -0
- package/dist/lib/google-auth.js +495 -0
- package/dist/lib/google-token.js +66 -0
- package/dist/lib/jwt.js +31 -0
- package/dist/lib/legacy-oauth.js +59 -0
- package/dist/lib/node-version.js +127 -0
- package/dist/lib/owner-plan.js +78 -0
- package/dist/lib/package-scripts.js +152 -0
- package/dist/lib/preflight.js +470 -0
- package/dist/lib/product-create.js +144 -0
- package/dist/lib/run-command.js +74 -0
- package/dist/lib/run-gates.js +70 -0
- package/dist/lib/run-output.js +42 -0
- package/dist/lib/run-summary.js +384 -0
- package/dist/lib/scaffold.js +427 -0
- package/dist/lib/service-input.js +207 -0
- package/dist/lib/service-runner.js +466 -0
- package/dist/lib/stale.js +24 -0
- package/dist/lib/target-selection.js +155 -0
- package/dist/lib/verb-fanout.js +128 -0
- package/dist/lib/verify-live.js +188 -0
- package/dist/manage.js +311 -0
- package/dist/omega-bin.js +7 -0
- package/dist/onboard.js +532 -0
- package/dist/services/account/ensure/users.js +259 -0
- package/dist/services/account/index.js +97 -0
- package/dist/services/account/lib/backend-client.js +107 -0
- package/dist/services/account/lib/password.js +36 -0
- package/dist/services/account/lib/resolve-password.js +100 -0
- package/dist/services/advertising/ensure/sites.js +101 -0
- package/dist/services/advertising/index.js +101 -0
- package/dist/services/advertising/lib/adsense-api.js +41 -0
- package/dist/services/ai/ensure/keys.js +21 -0
- package/dist/services/ai/index.js +36 -0
- package/dist/services/analytics/ensure/google-firebase-link.js +230 -0
- package/dist/services/analytics/ensure/google-streams.js +256 -0
- package/dist/services/analytics/ensure/meta-pixel.js +22 -0
- package/dist/services/analytics/ensure/tiktok-pixel.js +22 -0
- package/dist/services/analytics/index.js +134 -0
- package/dist/services/analytics/lib/analytics-api.js +156 -0
- package/dist/services/analytics/lib/meta-api.js +83 -0
- package/dist/services/analytics/lib/pixel-account.js +78 -0
- package/dist/services/analytics/lib/pixel-provision.js +152 -0
- package/dist/services/analytics/lib/pixel-specs.js +57 -0
- package/dist/services/analytics/lib/pixel-token.js +88 -0
- package/dist/services/analytics/lib/property-flow.js +77 -0
- package/dist/services/analytics/lib/tiktok-api.js +157 -0
- package/dist/services/analytics/lib/tiktok-auth.js +153 -0
- package/dist/services/assets/ensure/logo-gen.js +68 -0
- package/dist/services/assets/index.js +109 -0
- package/dist/services/assets/lib/assets-config.js +168 -0
- package/dist/services/assets/lib/brandmark-api.js +160 -0
- package/dist/services/assets/lib/derived.js +113 -0
- package/dist/services/assets/lib/font-loader.js +67 -0
- package/dist/services/assets/lib/reconcile.js +122 -0
- package/dist/services/assets/lib/reset.js +118 -0
- package/dist/services/assets/lib/svg-logo-generator.js +154 -0
- package/dist/services/assets/lib/svg-to-black.js +32 -0
- package/dist/services/assets/write/favicons.js +82 -0
- package/dist/services/assets/write/icons.js +64 -0
- package/dist/services/assets/write/process.js +96 -0
- package/dist/services/assets/write/reconcile.js +30 -0
- package/dist/services/assets/write/social-icons.js +108 -0
- package/dist/services/assets/write/templates.js +345 -0
- package/dist/services/bookmark/ensure/sync.js +255 -0
- package/dist/services/bookmark/index.js +17 -0
- package/dist/services/campaigns/ensure/contact-person.js +40 -0
- package/dist/services/campaigns/ensure/custom-fields.js +84 -0
- package/dist/services/campaigns/ensure/domain-auth.js +92 -0
- package/dist/services/campaigns/ensure/event-webhook.js +89 -0
- package/dist/services/campaigns/ensure/link-branding.js +187 -0
- package/dist/services/campaigns/ensure/list.js +50 -0
- package/dist/services/campaigns/ensure/segments.js +109 -0
- package/dist/services/campaigns/ensure/sender-identity.js +153 -0
- package/dist/services/campaigns/ensure/unsubscribe-groups.js +116 -0
- package/dist/services/campaigns/index.js +79 -0
- package/dist/services/campaigns/lib/dns-sync.js +81 -0
- package/dist/services/campaigns/lib/segment-query.js +138 -0
- package/dist/services/campaigns/lib/sendgrid-api.js +287 -0
- package/dist/services/captcha/ensure/site-key.js +83 -0
- package/dist/services/captcha/index.js +78 -0
- package/dist/services/captcha/lib/console-url.js +45 -0
- package/dist/services/captcha/lib/recaptcha-api.js +36 -0
- package/dist/services/certificates/ensure/api-key.js +22 -0
- package/dist/services/certificates/ensure/bundle-ids.js +115 -0
- package/dist/services/certificates/ensure/certificates.js +187 -0
- package/dist/services/certificates/ensure/profiles.js +143 -0
- package/dist/services/certificates/index.js +225 -0
- package/dist/services/certificates/lib/apple-api.js +183 -0
- package/dist/services/certificates/lib/certificate-manager.js +252 -0
- package/dist/services/certificates/lib/identifier-manager.js +107 -0
- package/dist/services/certificates/lib/keychain.js +135 -0
- package/dist/services/certificates/lib/manual-walkthrough.js +165 -0
- package/dist/services/certificates/lib/profile-manager.js +115 -0
- package/dist/services/chat/data/baseline-knowledge.md +96 -0
- package/dist/services/chat/ensure/chat.js +106 -0
- package/dist/services/chat/ensure/user.js +45 -0
- package/dist/services/chat/index.js +139 -0
- package/dist/services/chat/lib/baseline-knowledge.js +137 -0
- package/dist/services/cloud/ensure/authentication.js +267 -0
- package/dist/services/cloud/ensure/billing.js +120 -0
- package/dist/services/cloud/ensure/cloud-messaging.js +105 -0
- package/dist/services/cloud/ensure/database.js +60 -0
- package/dist/services/cloud/ensure/firestore.js +79 -0
- package/dist/services/cloud/ensure/functions.js +49 -0
- package/dist/services/cloud/ensure/hosting.js +398 -0
- package/dist/services/cloud/ensure/oauth-consent.js +248 -0
- package/dist/services/cloud/ensure/project-settings.js +72 -0
- package/dist/services/cloud/ensure/sdk-config.js +95 -0
- package/dist/services/cloud/ensure/service-account.js +152 -0
- package/dist/services/cloud/ensure/services.js +103 -0
- package/dist/services/cloud/ensure/storage.js +47 -0
- package/dist/services/cloud/index.js +130 -0
- package/dist/services/cloud/lib/access-heal.js +130 -0
- package/dist/services/cloud/lib/firebase-api.js +662 -0
- package/dist/services/cloud/lib/project-flow.js +102 -0
- package/dist/services/directory/ensure/entry.js +48 -0
- package/dist/services/directory/index.js +64 -0
- package/dist/services/directory/lib/blocks.js +104 -0
- package/dist/services/disperse/index.js +45 -0
- package/dist/services/disperse/write/certs.js +117 -0
- package/dist/services/domain/ensure/nameservers.js +179 -0
- package/dist/services/domain/index.js +69 -0
- package/dist/services/domain/lib/namecheap-api.js +167 -0
- package/dist/services/domain/lib/registrars.js +46 -0
- package/dist/services/domain/lib/whitelist-walkthrough.js +76 -0
- package/dist/services/edge/ensure/cache-rules.js +105 -0
- package/dist/services/edge/ensure/dns-records.js +304 -0
- package/dist/services/edge/ensure/email-routing.js +292 -0
- package/dist/services/edge/ensure/rules-configuration.js +98 -0
- package/dist/services/edge/ensure/rules-managed-transforms.js +101 -0
- package/dist/services/edge/ensure/rules-redirect.js +107 -0
- package/dist/services/edge/ensure/rules-response-headers.js +90 -0
- package/dist/services/edge/ensure/rules-security.js +142 -0
- package/dist/services/edge/ensure/speed-scheduled-tests.js +98 -0
- package/dist/services/edge/ensure/workers.js +219 -0
- package/dist/services/edge/ensure/zone-settings.js +108 -0
- package/dist/services/edge/ensure/zone.js +183 -0
- package/dist/services/edge/index.js +73 -0
- package/dist/services/edge/lib/cloudflare-api.js +84 -0
- package/dist/services/edge/lib/dns-records-helpers.js +518 -0
- package/dist/services/edge/lib/read-cache.js +16 -0
- package/dist/services/edge/lib/ruleset-helper.js +108 -0
- package/dist/services/edge/workers/omega-api-proxy.js +43 -0
- package/dist/services/email/data/baseline-filter.md +4 -0
- package/dist/services/email/data/baseline-knowledge.md +86 -0
- package/dist/services/email/ensure/agent.js +107 -0
- package/dist/services/email/ensure/user.js +45 -0
- package/dist/services/email/index.js +138 -0
- package/dist/services/email/lib/baseline.js +82 -0
- package/dist/services/email/lib/knowledge-file.js +53 -0
- package/dist/services/forms/ensure/form.js +46 -0
- package/dist/services/forms/ensure/user.js +45 -0
- package/dist/services/forms/index.js +138 -0
- package/dist/services/migrations/ensure/notifications.js +189 -0
- package/dist/services/migrations/ensure/orders.js +34 -0
- package/dist/services/migrations/ensure/payment-provider.js +132 -0
- package/dist/services/migrations/ensure/payments-intents.js +34 -0
- package/dist/services/migrations/ensure/state-retirement.js +266 -0
- package/dist/services/migrations/ensure/targets-rename.js +124 -0
- package/dist/services/migrations/ensure/users.js +1176 -0
- package/dist/services/migrations/index.js +101 -0
- package/dist/services/migrations/lib/attribution-touch.js +64 -0
- package/dist/services/migrations/lib/ensure-metadata.js +144 -0
- package/dist/services/migrations/lib/migration-runner.js +473 -0
- package/dist/services/migrations/lib/sanitize-strings.js +112 -0
- package/dist/services/migrations/lib/schema-validator.js +154 -0
- package/dist/services/monitoring/ensure/dsn.js +54 -0
- package/dist/services/monitoring/ensure/projects.js +135 -0
- package/dist/services/monitoring/index.js +54 -0
- package/dist/services/monitoring/lib/sentry-api.js +94 -0
- package/dist/services/newsletter/ensure/custom-fields.js +90 -0
- package/dist/services/newsletter/ensure/publication.js +94 -0
- package/dist/services/newsletter/ensure/segments.js +140 -0
- package/dist/services/newsletter/ensure/webhook.js +108 -0
- package/dist/services/newsletter/index.js +64 -0
- package/dist/services/newsletter/lib/beehiiv-api.js +124 -0
- package/dist/services/newsletter/lib/segment-automation.js +577 -0
- package/dist/services/payment/ensure/chargebee-account.js +30 -0
- package/dist/services/payment/ensure/chargebee-products.js +359 -0
- package/dist/services/payment/ensure/chargebee-webhook.js +113 -0
- package/dist/services/payment/ensure/paypal-account.js +36 -0
- package/dist/services/payment/ensure/paypal-products.js +374 -0
- package/dist/services/payment/ensure/paypal-webhook.js +128 -0
- package/dist/services/payment/ensure/stripe-account.js +93 -0
- package/dist/services/payment/ensure/stripe-disputes.js +56 -0
- package/dist/services/payment/ensure/stripe-products.js +245 -0
- package/dist/services/payment/ensure/stripe-radar.js +74 -0
- package/dist/services/payment/ensure/stripe-webhook.js +130 -0
- package/dist/services/payment/index.js +163 -0
- package/dist/services/payment/lib/chargebee-api.js +338 -0
- package/dist/services/payment/lib/payment-utils.js +116 -0
- package/dist/services/payment/lib/paypal-api.js +354 -0
- package/dist/services/payment/lib/provider-setup.js +134 -0
- package/dist/services/payment/lib/stripe-api.js +152 -0
- package/dist/services/repo/ensure/org.js +70 -0
- package/dist/services/repo/ensure/pages.js +72 -0
- package/dist/services/repo/ensure/repo.js +90 -0
- package/dist/services/repo/index.js +52 -0
- package/dist/services/repo/lib/github-api.js +196 -0
- package/dist/services/search/ensure/ga-link.js +57 -0
- package/dist/services/search/ensure/property.js +137 -0
- package/dist/services/search/ensure/sitemaps.js +70 -0
- package/dist/services/search/index.js +61 -0
- package/dist/services/search/lib/search-console-api.js +92 -0
- package/dist/services/seo/ensure/github-repos.js +271 -0
- package/dist/services/seo/index.js +55 -0
- package/dist/services/seo/lib/gh-api.js +161 -0
- package/dist/services/seo/templates/developer-tool/.github/workflows/maintenance.yml +25 -0
- package/dist/services/seo/templates/developer-tool/.nvmrc +1 -0
- package/dist/services/seo/templates/developer-tool/_README.md.js +121 -0
- package/dist/services/seo/templates/developer-tool/_package.json.js +33 -0
- package/dist/services/seo/templates/developer-tool/src/index.js +120 -0
- package/dist/services/seo/templates/index.js +57 -0
- package/dist/services/server/ensure/brands.js +63 -0
- package/dist/services/server/index.js +42 -0
- package/dist/services/testing/ensure/target-checks.js +97 -0
- package/dist/services/testing/index.js +11 -0
- package/dist/services/testing/lib/checks.js +467 -0
- package/dist/services/update/index.js +20 -0
- package/dist/services/update/lib/cache.js +59 -0
- package/dist/services/update/lib/fingerprint.js +157 -0
- package/dist/services/update/write/targets.js +206 -0
- package/dist/services/workspace/ensure/agents.js +42 -0
- package/dist/services/workspace/ensure/claude-settings.js +34 -0
- package/dist/services/workspace/ensure/config.js +54 -0
- package/dist/services/workspace/ensure/defaults.js +60 -0
- package/dist/services/workspace/ensure/env-keys.js +55 -0
- package/dist/services/workspace/ensure/env-order.js +66 -0
- package/dist/services/workspace/ensure/env-rules.js +97 -0
- package/dist/services/workspace/ensure/gitignore.js +18 -0
- package/dist/services/workspace/ensure/scripts.js +126 -0
- package/dist/services/workspace/ensure/structure.js +97 -0
- package/dist/services/workspace/ensure/translation-sdk.js +148 -0
- package/dist/services/workspace/ensure/workflows.js +54 -0
- package/dist/services/workspace/index.js +8 -0
- package/dist/vendor/account/engine.js +182 -0
- package/dist/vendor/account/features.js +220 -0
- package/dist/vendor/account/index.js +53 -0
- package/dist/vendor/account/schema.js +272 -0
- package/dist/vendor/account/subscription.js +38 -0
- package/dist/vendor/config/company.js +31 -0
- package/dist/vendor/config/defaults.js +173 -0
- package/dist/vendor/config/demo.js +18 -0
- package/dist/vendor/config/desktop-artifacts.js +110 -0
- package/dist/vendor/config/edit.js +769 -0
- package/dist/vendor/config/env-delivery.js +145 -0
- package/dist/vendor/config/env-rules.js +93 -0
- package/dist/vendor/config/env-schema.js +1078 -0
- package/dist/vendor/config/env.js +445 -0
- package/dist/vendor/config/hooks.js +97 -0
- package/dist/vendor/config/index.js +237 -0
- package/dist/vendor/config/instances.js +208 -0
- package/dist/vendor/config/load.js +490 -0
- package/dist/vendor/config/merge.js +68 -0
- package/dist/vendor/config/order.js +139 -0
- package/dist/vendor/config/ports.js +374 -0
- package/dist/vendor/config/providers.js +32 -0
- package/dist/vendor/config/repo.js +142 -0
- package/dist/vendor/config/retired-keys.js +430 -0
- package/dist/vendor/config/schema.js +1610 -0
- package/dist/vendor/config/secrets.js +50 -0
- package/dist/vendor/config/seed.js +34 -0
- package/dist/vendor/config/site-global.js +205 -0
- package/dist/vendor/config/validate.js +554 -0
- package/dist/vendor/config/winback.js +61 -0
- package/dist/vendor/devkit/attach-log-file.js +262 -0
- package/dist/vendor/devkit/certs.js +199 -0
- package/dist/vendor/devkit/ci-workflows.js +520 -0
- package/dist/vendor/devkit/cli-router.js +156 -0
- package/dist/vendor/devkit/command-path.js +46 -0
- package/dist/vendor/devkit/deploy-record.js +180 -0
- package/dist/vendor/devkit/flows.js +317 -0
- package/dist/vendor/devkit/local-https.js +360 -0
- package/dist/vendor/devkit/local.js +1905 -0
- package/dist/vendor/devkit/logger.js +128 -0
- package/dist/vendor/devkit/omega-bin.js +345 -0
- package/dist/vendor/devkit/prompt.js +187 -0
- package/dist/vendor/devkit/safe-install.js +18 -0
- package/dist/vendor/devkit/stop-signals.js +28 -0
- package/dist/vendor/devkit/test/scope.js +162 -0
- package/dist/vendor/devkit/translate/cache.js +84 -0
- package/dist/vendor/devkit/translate/engine.js +243 -0
- package/dist/vendor/devkit/translate/index.js +49 -0
- package/dist/vendor/devkit/translate/languages.js +134 -0
- package/dist/vendor/devkit/translate/providers.js +188 -0
- package/dist/vendor/devkit/update.js +569 -0
- package/docs/AGENTS.md +200 -0
- package/docs/account.md +45 -0
- package/docs/advertising.md +46 -0
- package/docs/ai.md +34 -0
- package/docs/analytics.md +69 -0
- package/docs/assets.md +57 -0
- package/docs/bookmark.md +26 -0
- package/docs/brand.md +151 -0
- package/docs/campaigns.md +96 -0
- package/docs/captcha.md +46 -0
- package/docs/certificates.md +68 -0
- package/docs/chat.md +47 -0
- package/docs/cloud.md +110 -0
- package/docs/company.md +64 -0
- package/docs/directory.md +140 -0
- package/docs/disperse.md +46 -0
- package/docs/domain.md +56 -0
- package/docs/edge.md +234 -0
- package/docs/email.md +42 -0
- package/docs/forms.md +48 -0
- package/docs/index.md +109 -0
- package/docs/migration.md +203 -0
- package/docs/migrations.md +56 -0
- package/docs/monitoring.md +41 -0
- package/docs/newsletter.md +48 -0
- package/docs/payment.md +83 -0
- package/docs/repo.md +48 -0
- package/docs/search.md +44 -0
- package/docs/seo.md +34 -0
- package/docs/server.md +37 -0
- package/docs/shared/agent-docs.md +89 -0
- package/docs/shared/analytics.md +612 -0
- package/docs/shared/brands.md +51 -0
- package/docs/shared/breaking-changes.md +497 -0
- package/docs/shared/config.md +1387 -0
- package/docs/shared/deploys.md +215 -0
- package/docs/shared/icons.md +201 -0
- package/docs/shared/local-dev.md +147 -0
- package/docs/shared/logging.md +202 -0
- package/docs/shared/monitoring.md +153 -0
- package/docs/shared/publishing.md +183 -0
- package/docs/shared/rulings.md +34 -0
- package/docs/shared/testing.md +147 -0
- package/docs/shared/theming.md +604 -0
- package/docs/shared/translation.md +291 -0
- package/docs/shared/updates.md +61 -0
- package/docs/testing.md +34 -0
- package/docs/update.md +36 -0
- package/docs/workspace.md +64 -0
- package/package.json +88 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* features.js — the ONE reading of the features contract
|
|
3
|
+
* ([#647](https://github.com/Omega-JS-Stack/omega/issues/647)).
|
|
4
|
+
*
|
|
5
|
+
* A feature is DEFINED once, in the config's top-level `features` catalog:
|
|
6
|
+
*
|
|
7
|
+
* features: {
|
|
8
|
+
* saves: { name: 'Saves', icon: 'feather', definition: '…',
|
|
9
|
+
* usage: { pace: 'daily', mirror: ['teams'] } },
|
|
10
|
+
* support: { name: 'Priority support', icon: 'headset' },
|
|
11
|
+
* }
|
|
12
|
+
*
|
|
13
|
+
* A `usage` block makes the entry COUNTED (metered per user); an entry without
|
|
14
|
+
* one is a PERK, never counted. Each product then names only its VALUE:
|
|
15
|
+
* `features: { saves: 100, support: true }` — a number on a counted feature
|
|
16
|
+
* (the MONTHLY limit, -1 unlimited), true/false/a string on a perk.
|
|
17
|
+
*
|
|
18
|
+
* This module is the shared home of every derivation both sides make from
|
|
19
|
+
* those two halves plus a user's stored counters — the backend's `consume`
|
|
20
|
+
* gate and the browser's account page must never disagree about what a user's
|
|
21
|
+
* limit is:
|
|
22
|
+
* - the EFFECTIVE limit, where an admin-written `usage.overrides.<feature>`
|
|
23
|
+
* on the user doc wins over the plan's number;
|
|
24
|
+
* - the DAY share, `ceil(limit / days in this month)`, so a monthly quota
|
|
25
|
+
* cannot be burned on day one (pacing is the default; `pace: false` opts
|
|
26
|
+
* out to a plain monthly counter);
|
|
27
|
+
* - the two counters a counted feature carries per user, month and day.
|
|
28
|
+
*
|
|
29
|
+
* -1 is the unlimited sentinel everywhere it can appear: a limit, a day share,
|
|
30
|
+
* and a remaining count.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
// Where a user's counters and overrides live on the account document.
|
|
34
|
+
const USAGE_KEY = 'usage';
|
|
35
|
+
const OVERRIDES_KEY = 'overrides';
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Is this catalog entry COUNTED? A `usage` block is what meters a feature;
|
|
39
|
+
* everything else is a perk the plan either includes or does not.
|
|
40
|
+
* @param {object} entry - a top-level `features` catalog entry
|
|
41
|
+
* @returns {boolean}
|
|
42
|
+
*/
|
|
43
|
+
function isCountedFeature(entry) {
|
|
44
|
+
return !!(entry && typeof entry === 'object' && entry.usage && typeof entry.usage === 'object' && !Array.isArray(entry.usage));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Is this feature paced by day? Pacing is the DEFAULT on every counted
|
|
49
|
+
* feature; `usage: { pace: false }` opts out to a plain monthly counter.
|
|
50
|
+
* @param {object} entry - a catalog entry
|
|
51
|
+
* @returns {boolean}
|
|
52
|
+
*/
|
|
53
|
+
function isPacedFeature(entry) {
|
|
54
|
+
return isCountedFeature(entry) && entry.usage.pace !== false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The document kinds a counted feature's counters mirror onto, declared in the
|
|
59
|
+
* catalog rather than at the call site.
|
|
60
|
+
* @param {object} entry - a catalog entry
|
|
61
|
+
* @returns {string[]} the declared kinds, or []
|
|
62
|
+
*/
|
|
63
|
+
function featureMirrors(entry) {
|
|
64
|
+
return isCountedFeature(entry) && Array.isArray(entry.usage.mirror) ? entry.usage.mirror : [];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Days in the month the given date falls in.
|
|
69
|
+
* @param {Date} [date] - defaults to now
|
|
70
|
+
* @returns {number}
|
|
71
|
+
*/
|
|
72
|
+
function daysInMonth(date) {
|
|
73
|
+
const d = date || new Date();
|
|
74
|
+
return new Date(d.getFullYear(), d.getMonth() + 1, 0).getDate();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* A month limit's share of ONE day: ceil(limit / days in this month), so a
|
|
79
|
+
* quota of 100 in a 31-day month allows 4 a day and never 100 on the 1st.
|
|
80
|
+
* Unlimited stays unlimited; nothing stays nothing.
|
|
81
|
+
* @param {number} limit - the effective MONTHLY limit (-1 unlimited)
|
|
82
|
+
* @param {Date} [date] - the day being paced, defaults to now
|
|
83
|
+
* @returns {number} the day's share (-1 unlimited)
|
|
84
|
+
*/
|
|
85
|
+
function dayShare(limit, date) {
|
|
86
|
+
if (!Number.isFinite(limit) || limit === 0) return 0;
|
|
87
|
+
if (limit < 0) return -1;
|
|
88
|
+
return Math.ceil(limit / daysInMonth(date));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* The per-user override for a feature, when the account carries one. Written
|
|
93
|
+
* by admins only (the user doc's `usage` key is a framework field the security
|
|
94
|
+
* rules deny a client), and it WINS over the plan's number — extra credits
|
|
95
|
+
* granted to one account, not a second pricing tier.
|
|
96
|
+
* @param {object} account - the user document
|
|
97
|
+
* @param {string} id - feature id
|
|
98
|
+
* @returns {number|null} the override, or null when there is none
|
|
99
|
+
*/
|
|
100
|
+
function featureOverride(account, id) {
|
|
101
|
+
const value = account && account[USAGE_KEY] && account[USAGE_KEY][OVERRIDES_KEY]
|
|
102
|
+
? account[USAGE_KEY][OVERRIDES_KEY][id]
|
|
103
|
+
: undefined;
|
|
104
|
+
|
|
105
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* What a product promises for one feature: its raw value from the product's
|
|
110
|
+
* `features` map. A product that never names the feature promises nothing.
|
|
111
|
+
* @param {object} product - a `payment.products` entry
|
|
112
|
+
* @param {string} id - feature id
|
|
113
|
+
* @returns {*} the value, or undefined
|
|
114
|
+
*/
|
|
115
|
+
function productFeatureValue(product, id) {
|
|
116
|
+
const values = product && product.features;
|
|
117
|
+
return values && typeof values === 'object' && !Array.isArray(values) ? values[id] : undefined;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* The counters a user carries for one feature. The stored shape is
|
|
122
|
+
* `usage.<id>.{ monthly, daily, total, last }` — monthly and daily are the two
|
|
123
|
+
* the gate reads, total never resets, last records when it moved.
|
|
124
|
+
* @param {object} account - the user document
|
|
125
|
+
* @param {string} id - feature id
|
|
126
|
+
* @returns {{ monthly: number, daily: number, total: number }}
|
|
127
|
+
*/
|
|
128
|
+
function featureCounters(account, id) {
|
|
129
|
+
const stored = (account && account[USAGE_KEY] && account[USAGE_KEY][id]) || {};
|
|
130
|
+
const read = (key) => (typeof stored[key] === 'number' && Number.isFinite(stored[key]) ? stored[key] : 0);
|
|
131
|
+
|
|
132
|
+
return { monthly: read('monthly'), daily: read('daily'), total: read('total') };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* How many of a limit are left, given what is used. Unlimited stays unlimited.
|
|
137
|
+
* @param {number} limit - the limit (-1 unlimited)
|
|
138
|
+
* @param {number} used - the counter
|
|
139
|
+
* @returns {number} what remains (-1 unlimited), never below zero
|
|
140
|
+
*/
|
|
141
|
+
function remaining(limit, used) {
|
|
142
|
+
if (limit < 0) return -1;
|
|
143
|
+
return Math.max(0, limit - used);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Resolve ONE feature against a catalog, a product and an account: everything
|
|
148
|
+
* a gate or an account page needs to speak about it in one object.
|
|
149
|
+
*
|
|
150
|
+
* @param {string} id - feature id
|
|
151
|
+
* @param {object} options
|
|
152
|
+
* @param {object} [options.catalog] - the top-level `features` catalog
|
|
153
|
+
* @param {object} [options.product] - the account's resolved product
|
|
154
|
+
* @param {object} [options.account] - the user document (counters + overrides)
|
|
155
|
+
* @param {Date} [options.now] - the day being paced, defaults to now
|
|
156
|
+
* @returns {object} { id, name, icon, definition, counted, paced, mirror,
|
|
157
|
+
* value, limit, planLimit, override, used, left, total,
|
|
158
|
+
* day: { limit, used, left } }
|
|
159
|
+
*/
|
|
160
|
+
function resolveFeature(id, options) {
|
|
161
|
+
options = options || {};
|
|
162
|
+
|
|
163
|
+
const entry = (options.catalog || {})[id] || null;
|
|
164
|
+
const counted = isCountedFeature(entry);
|
|
165
|
+
const value = productFeatureValue(options.product, id);
|
|
166
|
+
const counters = featureCounters(options.account, id);
|
|
167
|
+
const override = featureOverride(options.account, id);
|
|
168
|
+
|
|
169
|
+
const planLimit = counted && typeof value === 'number' ? value : 0;
|
|
170
|
+
const limit = counted && override !== null ? override : planLimit;
|
|
171
|
+
const paced = isPacedFeature(entry);
|
|
172
|
+
const dayLimit = paced ? dayShare(limit, options.now) : -1;
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
id: id,
|
|
176
|
+
name: (entry && entry.name) || id,
|
|
177
|
+
icon: (entry && entry.icon) || null,
|
|
178
|
+
definition: (entry && entry.definition) || null,
|
|
179
|
+
counted: counted,
|
|
180
|
+
paced: paced,
|
|
181
|
+
mirror: featureMirrors(entry),
|
|
182
|
+
value: counted ? limit : value,
|
|
183
|
+
limit: limit,
|
|
184
|
+
planLimit: planLimit,
|
|
185
|
+
override: override,
|
|
186
|
+
used: counters.monthly,
|
|
187
|
+
left: remaining(limit, counters.monthly),
|
|
188
|
+
total: counters.total,
|
|
189
|
+
day: {
|
|
190
|
+
limit: dayLimit,
|
|
191
|
+
used: counters.daily,
|
|
192
|
+
left: remaining(dayLimit, counters.daily),
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Resolve every feature the catalog defines, in CATALOG order — the row order
|
|
199
|
+
* every surface renders (pricing rows, account bars).
|
|
200
|
+
* @param {object} options - same options as resolveFeature
|
|
201
|
+
* @returns {object[]} one resolved feature per catalog entry
|
|
202
|
+
*/
|
|
203
|
+
function resolveFeatures(options) {
|
|
204
|
+
options = options || {};
|
|
205
|
+
|
|
206
|
+
return Object.keys(options.catalog || {}).map((id) => resolveFeature(id, options));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
module.exports = {
|
|
210
|
+
isCountedFeature,
|
|
211
|
+
isPacedFeature,
|
|
212
|
+
featureMirrors,
|
|
213
|
+
featureOverride,
|
|
214
|
+
featureCounters,
|
|
215
|
+
productFeatureValue,
|
|
216
|
+
daysInMonth,
|
|
217
|
+
dayShare,
|
|
218
|
+
resolveFeature,
|
|
219
|
+
resolveFeatures,
|
|
220
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @omega.js/account — the single source of truth for the OMEGA user/account schema.
|
|
3
|
+
*
|
|
4
|
+
* Today the account shape lives in two drifted places: @omega.js/backend's
|
|
5
|
+
* src/manager/helpers/user.js (canonical — schema engine + tokens) and
|
|
6
|
+
* @omega.js/client's DEFAULT_ACCOUNT in modules/auth.js (hand-rolled deep-merge,
|
|
7
|
+
* missing flags/activity/personal branches and `everPaid`). This package
|
|
8
|
+
* unifies them; each framework becomes a thin wrapper (@omega.js/backend keeps its
|
|
9
|
+
* `new User(Manager, settings).properties` API, @omega.js/client keeps
|
|
10
|
+
* `resolveAccount(rawData, firebaseUser)`).
|
|
11
|
+
*
|
|
12
|
+
* resolveAccount(data, options):
|
|
13
|
+
* options.generators — { uuid, randomId, apiKey } value generators for the
|
|
14
|
+
* '$uuid'/'$randomId'/'$apiKey' schema tokens. @omega.js/backend injects real ones
|
|
15
|
+
* (uuid v4, Utilities().randomId, uid-generator); the frontend passes none
|
|
16
|
+
* and the fields resolve to null (real values always come from the backend).
|
|
17
|
+
* options.user — optional auth-user overlay ({ uid, email }): fills
|
|
18
|
+
* account.auth identity when the doc doesn't carry it (@omega.js/client's
|
|
19
|
+
* resolveAccount(rawData, firebaseUser) semantic).
|
|
20
|
+
*/
|
|
21
|
+
const USER_SCHEMA = require('./schema.js');
|
|
22
|
+
const { resolve } = require('./engine.js');
|
|
23
|
+
const resolveSubscription = require('./subscription.js');
|
|
24
|
+
const features = require('./features.js');
|
|
25
|
+
|
|
26
|
+
function resolveAccount(data, options) {
|
|
27
|
+
options = options || {};
|
|
28
|
+
|
|
29
|
+
// Time context — byte-identical to @omega.js/backend's node-powertools timestamps:
|
|
30
|
+
// powertools.timestamp(d, { output: 'string' }) === d.toISOString(),
|
|
31
|
+
// powertools.timestamp(d, { output: 'unix' }) === floor(ms / 1000).
|
|
32
|
+
const now = new Date();
|
|
33
|
+
const ctx = {
|
|
34
|
+
generators: options.generators || {},
|
|
35
|
+
now: now.toISOString(),
|
|
36
|
+
nowUNIX: Math.floor(now.getTime() / 1000),
|
|
37
|
+
oldDate: new Date(0).toISOString(),
|
|
38
|
+
oldDateUNIX: 0,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const account = resolve(USER_SCHEMA, data || {}, ctx);
|
|
42
|
+
|
|
43
|
+
// Auth-user overlay (frontend semantic): identity from the signed-in Firebase
|
|
44
|
+
// user when the stored doc doesn't already carry it
|
|
45
|
+
if (options.user) {
|
|
46
|
+
account.auth.uid = account.auth.uid || options.user.uid || null;
|
|
47
|
+
account.auth.email = account.auth.email || options.user.email || null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return account;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = { USER_SCHEMA, resolveAccount, resolveSubscription, ...features };
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* USER_SCHEMA — the canonical OMEGA user/account schema (pure data, no logic).
|
|
3
|
+
*
|
|
4
|
+
* Extracted verbatim from @omega.js/backend's src/manager/helpers/user.js, which is
|
|
5
|
+
* the authoritative shape (@omega.js/client's DEFAULT_ACCOUNT had drifted from it).
|
|
6
|
+
*
|
|
7
|
+
* Each leaf field is { type, default, nullable }
|
|
8
|
+
* Special keys:
|
|
9
|
+
* $passthrough — preserve all existing keys from input, don't strip unknowns
|
|
10
|
+
* $template — shape applied to every dynamic key in a $passthrough object
|
|
11
|
+
* '$template' — (string value) reference to parent's $template
|
|
12
|
+
* '$timestamp' — shorthand for { timestamp, timestampUNIX } defaulting to epoch
|
|
13
|
+
* '$timestamp:now' — same but defaults to current time
|
|
14
|
+
* '$uuid', '$randomId', '$apiKey', '$oldDate' — resolved at runtime (generators
|
|
15
|
+
* are injected by the host — see engine.js; absent generators resolve to null)
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* One attribution TOUCH — a single visit's campaign context, shared by
|
|
20
|
+
* `attribution.first` and `attribution.last`
|
|
21
|
+
* ([#384](https://github.com/Omega-JS-Stack/omega/issues/384)). `tags` holds the
|
|
22
|
+
* utm set and `clickIds` the ad-platform click ids (fbclid/gclid/…), both
|
|
23
|
+
* passthrough because the platforms keep adding params. The schema is read-only
|
|
24
|
+
* to the engine, so one object serves both slots.
|
|
25
|
+
*/
|
|
26
|
+
const ATTRIBUTION_TOUCH = {
|
|
27
|
+
tags: { $passthrough: true },
|
|
28
|
+
clickIds: { $passthrough: true },
|
|
29
|
+
referrer: { type: 'string', default: null, nullable: true },
|
|
30
|
+
url: { type: 'string', default: null, nullable: true },
|
|
31
|
+
page: { type: 'string', default: null, nullable: true },
|
|
32
|
+
timestamp: { type: 'string', default: null, nullable: true },
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const USER_SCHEMA = {
|
|
36
|
+
auth: {
|
|
37
|
+
uid: { type: 'string', default: null, nullable: true },
|
|
38
|
+
email: { type: 'string', default: null, nullable: true },
|
|
39
|
+
temporary: { type: 'boolean', default: false },
|
|
40
|
+
},
|
|
41
|
+
subscription: {
|
|
42
|
+
product: {
|
|
43
|
+
id: { type: 'string', default: 'basic' },
|
|
44
|
+
name: { type: 'string', default: 'Basic' },
|
|
45
|
+
},
|
|
46
|
+
status: { type: 'string', default: 'active' },
|
|
47
|
+
expires: '$timestamp',
|
|
48
|
+
trial: {
|
|
49
|
+
claimed: { type: 'boolean', default: false },
|
|
50
|
+
expires: '$timestamp',
|
|
51
|
+
// How the trial ENDED: 'converted' | 'lapsed' | null (still running, or never
|
|
52
|
+
// answered). `claimed` says only that a trial happened — a converted trial and a
|
|
53
|
+
// lapsed one carry identical dates — so this is the one stored conversion signal.
|
|
54
|
+
// Stamped by the trial-lapse sweep once the provider confirms which it was.
|
|
55
|
+
outcome: { type: 'string', default: null, nullable: true },
|
|
56
|
+
},
|
|
57
|
+
cancellation: {
|
|
58
|
+
pending: { type: 'boolean', default: false },
|
|
59
|
+
date: '$timestamp',
|
|
60
|
+
},
|
|
61
|
+
payment: {
|
|
62
|
+
provider: { type: 'string', default: null, nullable: true },
|
|
63
|
+
orderId: { type: 'string', default: null, nullable: true },
|
|
64
|
+
resourceId: { type: 'string', default: null, nullable: true },
|
|
65
|
+
frequency: { type: 'string', default: null, nullable: true },
|
|
66
|
+
price: { type: 'number', default: 0 },
|
|
67
|
+
startDate: '$timestamp',
|
|
68
|
+
updatedBy: {
|
|
69
|
+
event: {
|
|
70
|
+
name: { type: 'string', default: null, nullable: true },
|
|
71
|
+
id: { type: 'string', default: null, nullable: true },
|
|
72
|
+
},
|
|
73
|
+
date: '$timestamp',
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
// The discount riding the subscription right now
|
|
77
|
+
// ([#325](https://github.com/Omega-JS-Stack/omega/issues/325)). Shaped as a
|
|
78
|
+
// discount-codes validate() RESULT — the one shape the whole payment stack
|
|
79
|
+
// already speaks — so the billing card reads exactly what the apply route
|
|
80
|
+
// wrote, and neither side learns a second spelling. `valid: false` (the
|
|
81
|
+
// default) IS "no discount": absent and denied read the same everywhere.
|
|
82
|
+
//
|
|
83
|
+
// Both shapes are always present and default to 0, unlike a fresh validate()
|
|
84
|
+
// result which omits the one it is not: this node is MERGED onto a document
|
|
85
|
+
// that may already carry a discount, and a percent claim landing on a stored
|
|
86
|
+
// amount would otherwise be read as the older, wrong number.
|
|
87
|
+
//
|
|
88
|
+
// `source` is what makes it safe to read as a CLAIM. Today only the winback
|
|
89
|
+
// claim writes this node; a code typed at checkout will set the same node
|
|
90
|
+
// when checkout starts writing it, and a winback pitch suppressed by someone
|
|
91
|
+
// else's promo is an offer silently withheld — so the node says which system
|
|
92
|
+
// applied it ('winback' | 'checkout'), and only 'winback' is the claimed
|
|
93
|
+
// signal the cancel flow gates on.
|
|
94
|
+
//
|
|
95
|
+
// `resourceId` is the subscription the discount was applied TO, stamped at
|
|
96
|
+
// claim time ([#333](https://github.com/Omega-JS-Stack/omega/issues/333)).
|
|
97
|
+
// It is what makes the node clearable: the unified webhook write carries no
|
|
98
|
+
// discount key, so a merge preserves this node forever, and a customer who
|
|
99
|
+
// churned and resubscribed carried a spent claim into the NEW subscription,
|
|
100
|
+
// where `source: 'winback'` reads as "already claimed" and silently retires
|
|
101
|
+
// an offer the backend would grant again. The webhook pipeline compares the
|
|
102
|
+
// stamp against the subscription each event is about and CLEARS the node
|
|
103
|
+
// (back to these defaults) on a mismatch: a new subscription is a clean
|
|
104
|
+
// slate. An unstamped node (written before the stamp existed) is read as
|
|
105
|
+
// belonging to the subscription it is found on, because nothing can prove
|
|
106
|
+
// otherwise and no live discount is ever taken away on a guess, and the
|
|
107
|
+
// pipeline stamps it there, so it clears on the next resubscribe like any
|
|
108
|
+
// other.
|
|
109
|
+
discount: {
|
|
110
|
+
valid: { type: 'boolean', default: false },
|
|
111
|
+
code: { type: 'string', default: null, nullable: true },
|
|
112
|
+
percent: { type: 'number', default: 0 },
|
|
113
|
+
amount: { type: 'number', default: 0 },
|
|
114
|
+
duration: { type: 'string', default: null, nullable: true },
|
|
115
|
+
source: { type: 'string', default: null, nullable: true },
|
|
116
|
+
resourceId: { type: 'string', default: null, nullable: true },
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
roles: {
|
|
120
|
+
$passthrough: true,
|
|
121
|
+
admin: { type: 'boolean', default: false },
|
|
122
|
+
betaTester: { type: 'boolean', default: false },
|
|
123
|
+
developer: { type: 'boolean', default: false },
|
|
124
|
+
},
|
|
125
|
+
flags: {
|
|
126
|
+
$passthrough: true,
|
|
127
|
+
signupProcessed: { type: 'boolean', default: false },
|
|
128
|
+
},
|
|
129
|
+
affiliate: {
|
|
130
|
+
code: { type: 'string', default: '$randomId' },
|
|
131
|
+
referrals: { type: 'array', default: [] },
|
|
132
|
+
},
|
|
133
|
+
metadata: {
|
|
134
|
+
created: '$timestamp:now',
|
|
135
|
+
updated: '$timestamp:now',
|
|
136
|
+
},
|
|
137
|
+
activity: {
|
|
138
|
+
geolocation: {
|
|
139
|
+
ip: { type: 'string', default: null, nullable: true },
|
|
140
|
+
continent: { type: 'string', default: null, nullable: true },
|
|
141
|
+
country: { type: 'string', default: null, nullable: true },
|
|
142
|
+
region: { type: 'string', default: null, nullable: true },
|
|
143
|
+
city: { type: 'string', default: null, nullable: true },
|
|
144
|
+
latitude: { type: 'number', default: 0 },
|
|
145
|
+
longitude: { type: 'number', default: 0 },
|
|
146
|
+
},
|
|
147
|
+
client: {
|
|
148
|
+
language: { type: 'string', default: null, nullable: true },
|
|
149
|
+
mobile: { type: 'boolean', default: false },
|
|
150
|
+
device: { type: 'string', default: null, nullable: true },
|
|
151
|
+
platform: { type: 'string', default: null, nullable: true },
|
|
152
|
+
browser: { type: 'string', default: null, nullable: true },
|
|
153
|
+
vendor: { type: 'string', default: null, nullable: true },
|
|
154
|
+
runtime: { type: 'string', default: null, nullable: true },
|
|
155
|
+
userAgent: { type: 'string', default: null, nullable: true },
|
|
156
|
+
url: { type: 'string', default: null, nullable: true },
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
api: {
|
|
160
|
+
clientId: { type: 'string', default: '$uuid' },
|
|
161
|
+
privateKey: { type: 'string', default: '$apiKey' },
|
|
162
|
+
},
|
|
163
|
+
// The documents this account owns, by KIND: `{ teams: ['team-abc'] }`
|
|
164
|
+
// ([#647](https://github.com/Omega-JS-Stack/omega/issues/647)). A counted
|
|
165
|
+
// feature's catalog entry names the kinds its counters mirror onto
|
|
166
|
+
// (`usage: { mirror: ['teams'] }`), and this is what resolves a kind to real
|
|
167
|
+
// document paths — so a mirror is declared once in config instead of
|
|
168
|
+
// re-derived at every call site. Server-written like `usage`: a client that
|
|
169
|
+
// could write it could point another account's counters at its own doc.
|
|
170
|
+
owns: {
|
|
171
|
+
$passthrough: true,
|
|
172
|
+
},
|
|
173
|
+
usage: {
|
|
174
|
+
$passthrough: true,
|
|
175
|
+
// Admin-granted extra credits, feature id → number
|
|
176
|
+
// ([#647](https://github.com/Omega-JS-Stack/omega/issues/647)). Declared
|
|
177
|
+
// HERE so the sibling `$template` never resolves it as a counter block:
|
|
178
|
+
// it is a map of limits, not a map of counts. `usage` is a framework field
|
|
179
|
+
// the security rules deny every client, so an override can only ever be
|
|
180
|
+
// server-written — which is what makes it trustworthy as a limit.
|
|
181
|
+
overrides: { $passthrough: true },
|
|
182
|
+
$template: {
|
|
183
|
+
monthly: { type: 'number', default: 0 },
|
|
184
|
+
daily: { type: 'number', default: 0 },
|
|
185
|
+
total: { type: 'number', default: 0 },
|
|
186
|
+
// When the counter last moved. The old `id` field went with the retired
|
|
187
|
+
// `increment(name, value, { id })` option (#647): nothing writes it now,
|
|
188
|
+
// and a schema field nothing fills reads as a fact that is always null.
|
|
189
|
+
last: {
|
|
190
|
+
timestamp: { type: 'string', default: '$oldDate' },
|
|
191
|
+
timestampUNIX: { type: 'number', default: 0 },
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
personal: {
|
|
196
|
+
birthday: '$timestamp',
|
|
197
|
+
gender: { type: 'string', default: null, nullable: true },
|
|
198
|
+
location: {
|
|
199
|
+
country: { type: 'string', default: null, nullable: true },
|
|
200
|
+
region: { type: 'string', default: null, nullable: true },
|
|
201
|
+
city: { type: 'string', default: null, nullable: true },
|
|
202
|
+
postalCode: { type: 'string', default: null, nullable: true },
|
|
203
|
+
street: { type: 'string', default: null, nullable: true },
|
|
204
|
+
},
|
|
205
|
+
name: {
|
|
206
|
+
first: { type: 'string', default: null, nullable: true },
|
|
207
|
+
last: { type: 'string', default: null, nullable: true },
|
|
208
|
+
},
|
|
209
|
+
company: {
|
|
210
|
+
name: { type: 'string', default: null, nullable: true },
|
|
211
|
+
position: { type: 'string', default: null, nullable: true },
|
|
212
|
+
},
|
|
213
|
+
telephone: {
|
|
214
|
+
countryCode: { type: 'number', default: 0 },
|
|
215
|
+
national: { type: 'number', default: 0 },
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
connections: {
|
|
219
|
+
$passthrough: true,
|
|
220
|
+
},
|
|
221
|
+
attribution: {
|
|
222
|
+
affiliate: {
|
|
223
|
+
code: { type: 'string', default: null, nullable: true },
|
|
224
|
+
timestamp: { type: 'string', default: null, nullable: true },
|
|
225
|
+
url: { type: 'string', default: null, nullable: true },
|
|
226
|
+
page: { type: 'string', default: null, nullable: true },
|
|
227
|
+
},
|
|
228
|
+
// The touch model: `first` is the visit that first brought the user here,
|
|
229
|
+
// written once and never overwritten; `last` is the newest TAGGED visit. No
|
|
230
|
+
// expiry — the timestamps carry any read-time lookback window. Replaces the
|
|
231
|
+
// single `utm` blob outright (no dual-read).
|
|
232
|
+
first: ATTRIBUTION_TOUCH,
|
|
233
|
+
last: ATTRIBUTION_TOUCH,
|
|
234
|
+
},
|
|
235
|
+
// The tracking-consent snapshot the client captured, stored verbatim: passthrough
|
|
236
|
+
// because this layer never interprets it — the consent module owns its shape. NOT
|
|
237
|
+
// the same thing as `consent` below, which is the legal/marketing decision.
|
|
238
|
+
trackingConsent: {
|
|
239
|
+
$passthrough: true,
|
|
240
|
+
},
|
|
241
|
+
consent: {
|
|
242
|
+
legal: {
|
|
243
|
+
status: { type: 'string', default: 'revoked' },
|
|
244
|
+
grantedAt: {
|
|
245
|
+
timestamp: { type: 'string', default: null, nullable: true },
|
|
246
|
+
timestampUNIX: { type: 'number', default: null, nullable: true },
|
|
247
|
+
source: { type: 'string', default: null, nullable: true },
|
|
248
|
+
ip: { type: 'string', default: null, nullable: true },
|
|
249
|
+
text: { type: 'string', default: null, nullable: true },
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
marketing: {
|
|
253
|
+
status: { type: 'string', default: 'revoked' },
|
|
254
|
+
grantedAt: {
|
|
255
|
+
timestamp: { type: 'string', default: null, nullable: true },
|
|
256
|
+
timestampUNIX: { type: 'number', default: null, nullable: true },
|
|
257
|
+
source: { type: 'string', default: null, nullable: true },
|
|
258
|
+
ip: { type: 'string', default: null, nullable: true },
|
|
259
|
+
text: { type: 'string', default: null, nullable: true },
|
|
260
|
+
},
|
|
261
|
+
revokedAt: {
|
|
262
|
+
timestamp: { type: 'string', default: null, nullable: true },
|
|
263
|
+
timestampUNIX: { type: 'number', default: null, nullable: true },
|
|
264
|
+
source: { type: 'string', default: null, nullable: true },
|
|
265
|
+
ip: { type: 'string', default: null, nullable: true },
|
|
266
|
+
text: { type: 'string', default: null, nullable: true },
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
module.exports = USER_SCHEMA;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* resolveSubscription — extracted verbatim from @omega.js/backend's User.resolveSubscription
|
|
3
|
+
* (the superset: @omega.js/client's drifted copy lacked `everPaid`).
|
|
4
|
+
*
|
|
5
|
+
* Resolves calculated subscription fields that require derivation logic.
|
|
6
|
+
* Raw data (product.id, status, trial, cancellation) is on the account directly.
|
|
7
|
+
* Returns: { plan, active, trialing, cancelling, everPaid }
|
|
8
|
+
* - plan: the plan ID the user effectively has access to RIGHT NOW ('basic' if cancelled/suspended)
|
|
9
|
+
* - active: user has active access (active, trialing, or cancelling)
|
|
10
|
+
* - trialing: user is in an active trial (status is 'active' but trial hasn't expired)
|
|
11
|
+
* - cancelling: cancellation is pending (status is 'active' but cancellation.pending is true)
|
|
12
|
+
* - everPaid: user has had a paid subscription at some point (payment.startDate exists)
|
|
13
|
+
*/
|
|
14
|
+
function resolveSubscription(account) {
|
|
15
|
+
const subscription = (account?.subscription || account?.properties?.subscription) || {};
|
|
16
|
+
const productId = subscription.product?.id || 'basic';
|
|
17
|
+
|
|
18
|
+
let trialing = false;
|
|
19
|
+
let cancelling = false;
|
|
20
|
+
|
|
21
|
+
if (productId !== 'basic' && subscription.status === 'active') {
|
|
22
|
+
trialing = !!(subscription.trial?.claimed
|
|
23
|
+
&& subscription.trial?.expires?.timestampUNIX > Math.floor(Date.now() / 1000));
|
|
24
|
+
cancelling = !trialing && !!subscription.cancellation?.pending;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const active = (productId !== 'basic' && subscription.status === 'active');
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
plan: active ? productId : 'basic',
|
|
31
|
+
active,
|
|
32
|
+
trialing,
|
|
33
|
+
cancelling,
|
|
34
|
+
everPaid: (subscription.payment?.startDate?.timestampUNIX || 0) > 0,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = resolveSubscription;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The company layer's ONE discovery rule.
|
|
3
|
+
*
|
|
4
|
+
* A brand managed by a company workspace carries `.omega/company.json` at its
|
|
5
|
+
* root (stamped idempotently by company manage runs) pointing at the company
|
|
6
|
+
* root. Everything that cascades — the config chain (load.js), the .env chain
|
|
7
|
+
* (env.js), owner hooks (hooks.js) — resolves the company root through here,
|
|
8
|
+
* so the stamp is read exactly one way.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('node:fs');
|
|
12
|
+
const path = require('node:path');
|
|
13
|
+
|
|
14
|
+
// A brand's pointer at its company root.
|
|
15
|
+
const COMPANY_MARKER = path.join('.omega', 'company.json');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The company root a brand points at via its .omega/company.json marker.
|
|
19
|
+
* @param {string} brandRoot
|
|
20
|
+
* @returns {string|null} Absolute company root, or null when unstamped/unreadable.
|
|
21
|
+
*/
|
|
22
|
+
function readCompanyRoot(brandRoot) {
|
|
23
|
+
try {
|
|
24
|
+
const marker = JSON.parse(fs.readFileSync(path.join(brandRoot, COMPANY_MARKER), 'utf8'));
|
|
25
|
+
return typeof marker.root === 'string' && marker.root ? marker.root : null;
|
|
26
|
+
} catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = { readCompanyRoot, COMPANY_MARKER };
|