@openlup/platform 0.28.0 → 0.30.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/CHANGELOG.md +369 -156
- package/README.md +220 -4
- package/dist/composition/index.d.ts +1 -0
- package/dist/composition/index.js +1 -0
- package/dist/composition/marketingContribution.d.ts +21 -0
- package/dist/composition/marketingContribution.js +29 -0
- package/dist/composition/nodeHost.d.ts +1 -1
- package/dist/composition/nodeHost.js +3 -2
- package/dist/lifecycle/appUpgrade.d.ts +52 -0
- package/dist/lifecycle/appUpgrade.js +180 -0
- package/dist/lifecycle/index.d.ts +2 -0
- package/dist/lifecycle/index.js +2 -0
- package/dist/lifecycle/postgresInstallQualification.d.ts +51 -0
- package/dist/lifecycle/postgresInstallQualification.js +74 -0
- package/dist/marketing/acquisitionCaseSchemas.d.ts +103 -0
- package/dist/marketing/acquisitionCaseSchemas.js +53 -0
- package/dist/marketing/acquisitionEvidenceSchemas.d.ts +105 -0
- package/dist/marketing/acquisitionEvidenceSchemas.js +55 -0
- package/dist/marketing/analytics/dataLayer.d.ts +18 -0
- package/dist/marketing/analytics/dataLayer.js +111 -0
- package/dist/marketing/analytics/noop.d.ts +9 -0
- package/dist/marketing/analytics/noop.js +8 -0
- package/dist/marketing/analytics/purchaseFacts.d.ts +4 -0
- package/dist/marketing/analytics/purchaseFacts.js +69 -0
- package/dist/marketing/analytics/routePolicy.d.ts +10 -0
- package/dist/marketing/analytics/routePolicy.js +59 -0
- package/dist/marketing/consentEvidence.d.ts +8 -0
- package/dist/marketing/consentEvidence.js +32 -0
- package/dist/marketing/contactLegal.d.ts +14 -0
- package/dist/marketing/contactLegal.js +19 -0
- package/dist/marketing/contracts/acquisition.d.ts +88 -0
- package/dist/marketing/contracts/acquisition.js +1 -0
- package/dist/marketing/contracts/acquisitionPorts.d.ts +49 -0
- package/dist/marketing/contracts/acquisitionPorts.js +1 -0
- package/dist/marketing/contracts/analytics.d.ts +144 -0
- package/dist/marketing/contracts/analytics.js +1 -0
- package/dist/marketing/contracts/contactLegal.d.ts +53 -0
- package/dist/marketing/contracts/contactLegal.js +1 -0
- package/dist/marketing/contracts/index.d.ts +6 -0
- package/dist/marketing/contracts/index.js +1 -0
- package/dist/marketing/contracts/prelaunch.d.ts +39 -0
- package/dist/marketing/contracts/prelaunch.js +1 -0
- package/dist/marketing/index.d.ts +16 -0
- package/dist/marketing/index.js +14 -0
- package/dist/marketing/prelaunchAcquisitionSchemas.d.ts +116 -0
- package/dist/marketing/prelaunchAcquisitionSchemas.js +51 -0
- package/dist/marketing/seo/jsonLd.d.ts +157 -0
- package/dist/marketing/seo/jsonLd.js +127 -0
- package/dist/marketing/seo/metadata.d.ts +41 -0
- package/dist/marketing/seo/metadata.js +36 -0
- package/dist/marketing/seo/serializeJsonLd.d.ts +1 -0
- package/dist/marketing/seo/serializeJsonLd.js +6 -0
- package/dist/marketing/seo/urls.d.ts +14 -0
- package/dist/marketing/seo/urls.js +23 -0
- package/dist/marketing/server/acquisitionCaseDirect.d.ts +4 -0
- package/dist/marketing/server/acquisitionCaseDirect.js +88 -0
- package/dist/marketing/server/acquisitionCaseHandlers.d.ts +28 -0
- package/dist/marketing/server/acquisitionCaseHandlers.js +40 -0
- package/dist/marketing/server/acquisitionCasePorts.d.ts +42 -0
- package/dist/marketing/server/acquisitionCasePorts.js +1 -0
- package/dist/marketing/server/acquisitionEvidenceDirect.d.ts +5 -0
- package/dist/marketing/server/acquisitionEvidenceDirect.js +100 -0
- package/dist/marketing/server/analyticsContext.d.ts +3 -0
- package/dist/marketing/server/analyticsContext.js +65 -0
- package/dist/marketing/server/analyticsContextPorts.d.ts +31 -0
- package/dist/marketing/server/analyticsContextPorts.js +1 -0
- package/dist/marketing/server/bindings.d.ts +14 -0
- package/dist/marketing/server/bindings.js +18 -0
- package/dist/marketing/server/directPorts.d.ts +26 -0
- package/dist/marketing/server/directPorts.js +1 -0
- package/dist/marketing/server/index.d.ts +14 -0
- package/dist/marketing/server/index.js +11 -0
- package/dist/marketing/server/postgres/acquisitionCaseRoutines.d.ts +10 -0
- package/dist/marketing/server/postgres/acquisitionCaseRoutines.js +204 -0
- package/dist/marketing/server/postgres/acquisitionEvidenceRoutines.d.ts +10 -0
- package/dist/marketing/server/postgres/acquisitionEvidenceRoutines.js +107 -0
- package/dist/marketing/server/postgres/index.d.ts +3 -0
- package/dist/marketing/server/postgres/index.js +3 -0
- package/dist/marketing/server/prelaunchAcquisitionDirect.d.ts +4 -0
- package/dist/marketing/server/prelaunchAcquisitionDirect.js +122 -0
- package/dist/marketing/server/privacyContributor.d.ts +66 -0
- package/dist/marketing/server/privacyContributor.js +87 -0
- package/dist/marketing/server/research/adminSurveyResponsesHandler.d.ts +9 -0
- package/dist/marketing/server/research/adminSurveyResponsesHandler.js +53 -0
- package/dist/marketing/server/research/managedSurveyResponseSubmit.d.ts +39 -0
- package/dist/marketing/server/research/managedSurveyResponseSubmit.js +102 -0
- package/dist/marketing/server/research/surveyResponseSubmitHandler.d.ts +8 -0
- package/dist/marketing/server/research/surveyResponseSubmitHandler.js +41 -0
- package/dist/marketing/validation.d.ts +2 -0
- package/dist/marketing/validation.js +3 -0
- package/dist/migrations/postgresRunner.d.ts +9 -0
- package/dist/migrations/postgresRunner.js +17 -10
- package/dist/operations/adminAlertsSchemas.d.ts +153 -0
- package/dist/operations/adminAlertsSchemas.js +69 -0
- package/dist/operations/adminAlertsView.d.ts +73 -0
- package/dist/operations/adminAlertsView.js +184 -0
- package/dist/operations/alertPagingPolicy.d.ts +15 -0
- package/dist/operations/alertPagingPolicy.js +43 -0
- package/dist/operations/alertThrottlePolicy.d.ts +3 -0
- package/dist/operations/alertThrottlePolicy.js +18 -0
- package/dist/operations/clients.d.ts +17 -0
- package/dist/operations/clients.js +32 -0
- package/dist/operations/contracts/admin.d.ts +93 -0
- package/dist/operations/contracts/admin.js +1 -0
- package/dist/operations/contracts/adminAlertsContracts.d.ts +40 -0
- package/dist/operations/contracts/adminAlertsContracts.js +1 -0
- package/dist/operations/contracts/alertHumanContext.d.ts +7 -0
- package/dist/operations/contracts/alertHumanContext.js +1 -0
- package/dist/operations/contracts/index.d.ts +6 -0
- package/dist/operations/contracts/index.js +6 -0
- package/dist/operations/contracts/observabilityContracts.d.ts +41 -0
- package/dist/operations/contracts/observabilityContracts.js +1 -0
- package/dist/operations/contracts/observabilityPorts.d.ts +18 -0
- package/dist/operations/contracts/observabilityPorts.js +1 -0
- package/dist/operations/contracts/ports.d.ts +19 -0
- package/dist/operations/contracts/ports.js +1 -0
- package/dist/operations/errors.d.ts +6 -0
- package/dist/operations/errors.js +6 -0
- package/dist/operations/index.d.ts +9 -0
- package/dist/operations/index.js +9 -0
- package/dist/operations/schemas.d.ts +138 -0
- package/dist/operations/schemas.js +104 -0
- package/dist/operations/server/adminAlertsHandlers.d.ts +16 -0
- package/dist/operations/server/adminAlertsHandlers.js +35 -0
- package/dist/operations/server/adminPipelineHandlers.d.ts +12 -0
- package/dist/operations/server/adminPipelineHandlers.js +48 -0
- package/dist/operations/server/adminSettingsHandlers.d.ts +14 -0
- package/dist/operations/server/adminSettingsHandlers.js +82 -0
- package/dist/operations/server/controlPlaneHandler.d.ts +7 -0
- package/dist/operations/server/controlPlaneHandler.js +54 -0
- package/dist/operations/server/controlPlaneStore.d.ts +12 -0
- package/dist/operations/server/controlPlaneStore.js +138 -0
- package/dist/operations/server/controlPlaneWatchdog.d.ts +7 -0
- package/dist/operations/server/controlPlaneWatchdog.js +20 -0
- package/dist/operations/server/index.d.ts +12 -0
- package/dist/operations/server/index.js +12 -0
- package/dist/operations/server/migrations.d.ts +14 -0
- package/dist/operations/server/migrations.js +7 -0
- package/dist/operations/server/platformWatchdogDecision.d.ts +34 -0
- package/dist/operations/server/platformWatchdogDecision.js +71 -0
- package/dist/operations/server/platformWatchdogDeliveryPolicy.d.ts +10 -0
- package/dist/operations/server/platformWatchdogDeliveryPolicy.js +53 -0
- package/dist/operations/server/platformWatchdogQueue.d.ts +11 -0
- package/dist/operations/server/platformWatchdogQueue.js +94 -0
- package/dist/operations/server/platformWatchdogService.d.ts +47 -0
- package/dist/operations/server/platformWatchdogService.js +105 -0
- package/dist/operations/validation.d.ts +2 -0
- package/dist/operations/validation.js +3 -0
- package/dist/portability/filesystemObjects.d.ts +12 -0
- package/dist/portability/filesystemObjects.js +191 -0
- package/dist/portability/index.d.ts +5 -0
- package/dist/portability/index.js +4 -0
- package/dist/portability/objects.d.ts +63 -0
- package/dist/portability/objects.js +138 -0
- package/dist/portability/postgresRows.d.ts +64 -0
- package/dist/portability/postgresRows.js +182 -0
- package/dist/scheduler/advisoryLock.js +20 -2
- package/dist/scheduler/nodeScheduler.d.ts +2 -2
- package/dist/scheduler/nodeScheduler.js +35 -6
- package/package.json +47 -2
- package/release-gates.json +143 -9
- package/sql/20260814010000_platform_control_plane.sql +286 -0
- package/src/composition/index.ts +1 -0
- package/src/composition/marketingContribution.ts +37 -0
- package/src/composition/nodeHost.ts +4 -3
- package/src/lifecycle/appUpgrade.ts +186 -0
- package/src/lifecycle/index.ts +19 -0
- package/src/lifecycle/postgresInstallQualification.ts +125 -0
- package/src/marketing/acquisitionCaseSchemas.ts +75 -0
- package/src/marketing/acquisitionEvidenceSchemas.ts +72 -0
- package/src/marketing/analytics/dataLayer.ts +100 -0
- package/src/marketing/analytics/noop.ts +14 -0
- package/src/marketing/analytics/purchaseFacts.ts +63 -0
- package/src/marketing/analytics/routePolicy.ts +55 -0
- package/src/marketing/consentEvidence.ts +36 -0
- package/src/marketing/contactLegal.ts +22 -0
- package/src/marketing/contracts/acquisition.ts +50 -0
- package/src/marketing/contracts/acquisitionPorts.ts +48 -0
- package/src/marketing/contracts/analytics.ts +88 -0
- package/src/marketing/contracts/contactLegal.ts +41 -0
- package/src/marketing/contracts/index.ts +17 -0
- package/src/marketing/contracts/prelaunch.ts +15 -0
- package/src/marketing/index.ts +16 -0
- package/src/marketing/prelaunchAcquisitionSchemas.ts +64 -0
- package/src/marketing/seo/jsonLd.ts +153 -0
- package/src/marketing/seo/metadata.ts +62 -0
- package/src/marketing/seo/serializeJsonLd.ts +6 -0
- package/src/marketing/seo/urls.ts +34 -0
- package/src/marketing/server/acquisitionCaseDirect.ts +67 -0
- package/src/marketing/server/acquisitionCaseHandlers.ts +57 -0
- package/src/marketing/server/acquisitionCasePorts.ts +42 -0
- package/src/marketing/server/acquisitionEvidenceDirect.ts +76 -0
- package/src/marketing/server/analyticsContext.ts +55 -0
- package/src/marketing/server/analyticsContextPorts.ts +28 -0
- package/src/marketing/server/bindings.ts +27 -0
- package/src/marketing/server/directPorts.ts +27 -0
- package/src/marketing/server/index.ts +14 -0
- package/src/marketing/server/postgres/acquisitionCaseRoutines.ts +218 -0
- package/src/marketing/server/postgres/acquisitionEvidenceRoutines.ts +103 -0
- package/src/marketing/server/postgres/index.ts +3 -0
- package/src/marketing/server/prelaunchAcquisitionDirect.ts +121 -0
- package/src/marketing/server/privacyContributor.ts +123 -0
- package/src/marketing/server/research/adminSurveyResponsesHandler.ts +78 -0
- package/src/marketing/server/research/managedSurveyResponseSubmit.ts +158 -0
- package/src/marketing/server/research/surveyResponseSubmitHandler.ts +57 -0
- package/src/marketing/validation.ts +3 -0
- package/src/migrations/postgresRunner.ts +24 -9
- package/src/operations/adminAlertsSchemas.ts +78 -0
- package/src/operations/adminAlertsView.ts +238 -0
- package/src/operations/alertPagingPolicy.ts +50 -0
- package/src/operations/alertThrottlePolicy.ts +22 -0
- package/src/operations/clients.ts +44 -0
- package/src/operations/contracts/admin.ts +24 -0
- package/src/operations/contracts/adminAlertsContracts.ts +13 -0
- package/src/operations/contracts/alertHumanContext.ts +17 -0
- package/src/operations/contracts/index.ts +6 -0
- package/src/operations/contracts/observabilityContracts.ts +40 -0
- package/src/operations/contracts/observabilityPorts.ts +30 -0
- package/src/operations/contracts/ports.ts +22 -0
- package/src/operations/errors.ts +6 -0
- package/src/operations/index.ts +9 -0
- package/src/operations/schemas.ts +122 -0
- package/src/operations/server/adminAlertsHandlers.ts +61 -0
- package/src/operations/server/adminPipelineHandlers.ts +73 -0
- package/src/operations/server/adminSettingsHandlers.ts +114 -0
- package/src/operations/server/controlPlaneHandler.ts +56 -0
- package/src/operations/server/controlPlaneStore.ts +150 -0
- package/src/operations/server/controlPlaneWatchdog.ts +28 -0
- package/src/operations/server/index.ts +12 -0
- package/src/operations/server/migrations.ts +7 -0
- package/src/operations/server/platformWatchdogDecision.ts +93 -0
- package/src/operations/server/platformWatchdogDeliveryPolicy.ts +92 -0
- package/src/operations/server/platformWatchdogQueue.ts +96 -0
- package/src/operations/server/platformWatchdogService.ts +209 -0
- package/src/operations/validation.ts +3 -0
- package/src/portability/filesystemObjects.ts +138 -0
- package/src/portability/index.ts +12 -0
- package/src/portability/objects.ts +160 -0
- package/src/portability/postgresRows.ts +169 -0
- package/src/scheduler/advisoryLock.ts +20 -5
- package/src/scheduler/nodeScheduler.ts +28 -8
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** Read-only prerequisites of the immutable legacy fresh-install bootstrap, not install readiness. */
|
|
2
|
+
export interface PostgresInstallQuery {
|
|
3
|
+
query(sql: string, values?: unknown[]): Promise<{
|
|
4
|
+
rows: Record<string, unknown>[];
|
|
5
|
+
}>;
|
|
6
|
+
}
|
|
7
|
+
export interface LegacyPostgresInstallRequest {
|
|
8
|
+
/** Expected actual login: both current_user and session_user must match. */
|
|
9
|
+
migrationLogin: string;
|
|
10
|
+
/** Legacy bootstrap requires this to be the same login as the migrator. */
|
|
11
|
+
runtimeLogin: string;
|
|
12
|
+
roleMode: "installer-managed" | "operator-provisioned";
|
|
13
|
+
}
|
|
14
|
+
export interface PostgresInstallRoleObservation {
|
|
15
|
+
name: string;
|
|
16
|
+
login: boolean;
|
|
17
|
+
superuser: boolean;
|
|
18
|
+
inherit: boolean;
|
|
19
|
+
createRole: boolean;
|
|
20
|
+
createDatabase: boolean;
|
|
21
|
+
replication: boolean;
|
|
22
|
+
bypassRls: boolean;
|
|
23
|
+
}
|
|
24
|
+
export interface PostgresInstallMembershipObservation {
|
|
25
|
+
role: string;
|
|
26
|
+
member: string;
|
|
27
|
+
grantor: string;
|
|
28
|
+
admin: boolean;
|
|
29
|
+
inherit: boolean;
|
|
30
|
+
set: boolean;
|
|
31
|
+
}
|
|
32
|
+
export type LegacyPostgresInstallBlocker = "migration_login_mismatch" | "legacy_bootstrap_requires_superuser" | "legacy_bootstrap_requires_shared_runtime_login" | "runtime_login_missing_or_not_login" | "legacy_bootstrap_requires_installer_managed_roles" | "legacy_bootstrap_role_already_exists";
|
|
33
|
+
export interface LegacyPostgresInstallRoleReport {
|
|
34
|
+
scope: "legacy-fresh-bootstrap-role-prerequisites";
|
|
35
|
+
database: string;
|
|
36
|
+
currentUser: string;
|
|
37
|
+
sessionUser: string;
|
|
38
|
+
roles: PostgresInstallRoleObservation[];
|
|
39
|
+
memberships: PostgresInstallMembershipObservation[];
|
|
40
|
+
blockers: {
|
|
41
|
+
code: LegacyPostgresInstallBlocker;
|
|
42
|
+
role?: string;
|
|
43
|
+
}[];
|
|
44
|
+
rolePrerequisitesMet: boolean;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Observes PostgreSQL 16+ legacy role prerequisites without changing the database.
|
|
48
|
+
* A true result says nothing about schema inventory, object ACLs, pooling, import safety or hosting.
|
|
49
|
+
* Query/driver errors are redacted because they can contain connection credentials.
|
|
50
|
+
*/
|
|
51
|
+
export declare function inspectLegacyPostgresInstallRoles(db: PostgresInstallQuery, request: LegacyPostgresInstallRequest): Promise<LegacyPostgresInstallRoleReport>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
const BOOTSTRAP_ROLES = ["anon", "authenticated", "platform_acquisition_owner", "platform_acquisition_runtime"];
|
|
2
|
+
// One statement observes identities and the relevant role graph in the same statement snapshot.
|
|
3
|
+
// No SQL, identifiers, credentials or catalog expectations come from the caller.
|
|
4
|
+
// Bind functions, cast types and operators independently of the caller's search_path.
|
|
5
|
+
const OBSERVE = `SELECT pg_catalog.current_database() AS database, current_user::pg_catalog.text AS "currentUser",
|
|
6
|
+
session_user::pg_catalog.text AS "sessionUser",
|
|
7
|
+
(SELECT COALESCE(pg_catalog.jsonb_agg(pg_catalog.jsonb_build_object('name', rolname, 'login', rolcanlogin,
|
|
8
|
+
'superuser', rolsuper, 'inherit', rolinherit, 'createRole', rolcreaterole,
|
|
9
|
+
'createDatabase', rolcreatedb, 'replication', rolreplication, 'bypassRls', rolbypassrls)
|
|
10
|
+
ORDER BY rolname), '[]'::pg_catalog.jsonb) FROM pg_catalog.pg_roles
|
|
11
|
+
WHERE rolname OPERATOR(pg_catalog.=) ANY($1::pg_catalog.text[]) OR rolname OPERATOR(pg_catalog.=) current_user) AS roles,
|
|
12
|
+
(SELECT COALESCE(pg_catalog.jsonb_agg(pg_catalog.jsonb_build_object('role', granted.rolname, 'member', member.rolname,
|
|
13
|
+
'grantor', grantor.rolname, 'admin', m.admin_option, 'inherit', m.inherit_option, 'set', m.set_option)
|
|
14
|
+
ORDER BY granted.rolname, member.rolname, grantor.rolname), '[]'::pg_catalog.jsonb)
|
|
15
|
+
FROM pg_catalog.pg_auth_members m
|
|
16
|
+
JOIN pg_catalog.pg_roles granted ON granted.oid OPERATOR(pg_catalog.=) m.roleid
|
|
17
|
+
JOIN pg_catalog.pg_roles member ON member.oid OPERATOR(pg_catalog.=) m.member
|
|
18
|
+
JOIN pg_catalog.pg_roles grantor ON grantor.oid OPERATOR(pg_catalog.=) m.grantor
|
|
19
|
+
WHERE granted.rolname OPERATOR(pg_catalog.=) ANY($1::pg_catalog.text[])
|
|
20
|
+
OR member.rolname OPERATOR(pg_catalog.=) ANY($1::pg_catalog.text[])) AS memberships`;
|
|
21
|
+
function isRecord(value) {
|
|
22
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
23
|
+
}
|
|
24
|
+
function validRows(value, texts, flags) {
|
|
25
|
+
return Array.isArray(value) && value.every(row => isRecord(row)
|
|
26
|
+
&& texts.every(key => typeof row[key] === "string" && row[key].length > 0)
|
|
27
|
+
&& flags.every(key => typeof row[key] === "boolean"));
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Observes PostgreSQL 16+ legacy role prerequisites without changing the database.
|
|
31
|
+
* A true result says nothing about schema inventory, object ACLs, pooling, import safety or hosting.
|
|
32
|
+
* Query/driver errors are redacted because they can contain connection credentials.
|
|
33
|
+
*/
|
|
34
|
+
export async function inspectLegacyPostgresInstallRoles(db, request) {
|
|
35
|
+
if (![request.migrationLogin, request.runtimeLogin].every(name => typeof name === "string" && name.trim().length > 0)
|
|
36
|
+
|| !["installer-managed", "operator-provisioned"].includes(request.roleMode)) {
|
|
37
|
+
throw new Error("postgres_install_request_invalid");
|
|
38
|
+
}
|
|
39
|
+
let rows;
|
|
40
|
+
try {
|
|
41
|
+
({ rows } = await db.query(OBSERVE, [[...new Set([...BOOTSTRAP_ROLES, request.migrationLogin, request.runtimeLogin])]]));
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
throw new Error("postgres_install_observation_failed");
|
|
45
|
+
}
|
|
46
|
+
const row = rows[0];
|
|
47
|
+
if (rows.length !== 1 || !row
|
|
48
|
+
|| ![row.database, row.currentUser, row.sessionUser].every(value => typeof value === "string" && value.length > 0)
|
|
49
|
+
|| !validRows(row.roles, ["name"], ["login", "superuser", "inherit", "createRole", "createDatabase", "replication", "bypassRls"])
|
|
50
|
+
|| !validRows(row.memberships, ["role", "member", "grantor"], ["admin", "inherit", "set"])) {
|
|
51
|
+
throw new Error("postgres_install_observation_invalid");
|
|
52
|
+
}
|
|
53
|
+
const roles = row.roles;
|
|
54
|
+
const blockers = [];
|
|
55
|
+
if (row.currentUser !== request.migrationLogin || row.sessionUser !== request.migrationLogin)
|
|
56
|
+
blockers.push({ code: "migration_login_mismatch" });
|
|
57
|
+
if (!roles.some(role => role.name === row.currentUser && role.superuser))
|
|
58
|
+
blockers.push({ code: "legacy_bootstrap_requires_superuser" });
|
|
59
|
+
if (row.currentUser !== request.runtimeLogin)
|
|
60
|
+
blockers.push({ code: "legacy_bootstrap_requires_shared_runtime_login" });
|
|
61
|
+
if (!roles.some(role => role.name === request.runtimeLogin && role.login))
|
|
62
|
+
blockers.push({ code: "runtime_login_missing_or_not_login" });
|
|
63
|
+
if (request.roleMode !== "installer-managed")
|
|
64
|
+
blockers.push({ code: "legacy_bootstrap_requires_installer_managed_roles" });
|
|
65
|
+
for (const name of BOOTSTRAP_ROLES) {
|
|
66
|
+
if (roles.some(role => role.name === name))
|
|
67
|
+
blockers.push({ code: "legacy_bootstrap_role_already_exists", role: name });
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
scope: "legacy-fresh-bootstrap-role-prerequisites",
|
|
71
|
+
database: row.database, currentUser: row.currentUser, sessionUser: row.sessionUser,
|
|
72
|
+
roles, memberships: row.memberships, blockers, rolePrerequisitesMet: blockers.length === 0,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { z } from "./validation.js";
|
|
2
|
+
export declare const ACQUISITION_CASE_CONTRACT_VERSION: "tester_application_v1";
|
|
3
|
+
export declare const ACQUISITION_CASE_VIEW: "acquisition_case_v1";
|
|
4
|
+
export declare const ACQUISITION_CASE_SOURCE: "tester_application";
|
|
5
|
+
export declare const acquisitionOpaqueReferenceSchema: z.ZodString;
|
|
6
|
+
export declare const acquisitionAddressReferenceSchema: z.ZodObject<{
|
|
7
|
+
source: z.ZodString;
|
|
8
|
+
reference: z.ZodString;
|
|
9
|
+
revision: z.ZodString;
|
|
10
|
+
provenance: z.ZodString;
|
|
11
|
+
}, z.core.$strict>;
|
|
12
|
+
export declare const acquisitionCaseStateSchema: z.ZodEnum<{
|
|
13
|
+
rejected: "rejected";
|
|
14
|
+
active: "active";
|
|
15
|
+
submitted: "submitted";
|
|
16
|
+
withdrawn: "withdrawn";
|
|
17
|
+
approved: "approved";
|
|
18
|
+
}>;
|
|
19
|
+
export declare const acquisitionCaseProjectionSchema: z.ZodObject<{
|
|
20
|
+
contractVersion: z.ZodLiteral<"tester_application_v1">;
|
|
21
|
+
caseRef: z.ZodString;
|
|
22
|
+
contactRef: z.ZodString;
|
|
23
|
+
sourceKind: z.ZodLiteral<"tester_application">;
|
|
24
|
+
consent: z.ZodObject<{
|
|
25
|
+
consentVersion: z.ZodString;
|
|
26
|
+
policyVersion: z.ZodString;
|
|
27
|
+
recordedAt: z.ZodString;
|
|
28
|
+
locale: z.ZodEnum<{
|
|
29
|
+
en: "en";
|
|
30
|
+
pl: "pl";
|
|
31
|
+
}>;
|
|
32
|
+
sourcePath: z.ZodLiteral<"/tester-application">;
|
|
33
|
+
}, z.core.$strict>;
|
|
34
|
+
addressReference: z.ZodNullable<z.ZodObject<{
|
|
35
|
+
source: z.ZodString;
|
|
36
|
+
reference: z.ZodString;
|
|
37
|
+
revision: z.ZodString;
|
|
38
|
+
provenance: z.ZodString;
|
|
39
|
+
}, z.core.$strict>>;
|
|
40
|
+
state: z.ZodEnum<{
|
|
41
|
+
rejected: "rejected";
|
|
42
|
+
active: "active";
|
|
43
|
+
submitted: "submitted";
|
|
44
|
+
withdrawn: "withdrawn";
|
|
45
|
+
approved: "approved";
|
|
46
|
+
}>;
|
|
47
|
+
version: z.ZodNumber;
|
|
48
|
+
createdAt: z.ZodString;
|
|
49
|
+
updatedAt: z.ZodString;
|
|
50
|
+
}, z.core.$strict>;
|
|
51
|
+
export declare const acquisitionCaseListRequestSchema: z.ZodObject<{
|
|
52
|
+
view: z.ZodLiteral<"acquisition_case_v1">;
|
|
53
|
+
cursor: z.ZodOptional<z.ZodString>;
|
|
54
|
+
limit: z.ZodDefault<z.ZodNumber>;
|
|
55
|
+
}, z.core.$strict>;
|
|
56
|
+
export declare const acquisitionCaseListResponseSchema: z.ZodObject<{
|
|
57
|
+
contractVersion: z.ZodLiteral<"tester_application_v1">;
|
|
58
|
+
cases: z.ZodArray<z.ZodObject<{
|
|
59
|
+
contractVersion: z.ZodLiteral<"tester_application_v1">;
|
|
60
|
+
caseRef: z.ZodString;
|
|
61
|
+
contactRef: z.ZodString;
|
|
62
|
+
sourceKind: z.ZodLiteral<"tester_application">;
|
|
63
|
+
consent: z.ZodObject<{
|
|
64
|
+
consentVersion: z.ZodString;
|
|
65
|
+
policyVersion: z.ZodString;
|
|
66
|
+
recordedAt: z.ZodString;
|
|
67
|
+
locale: z.ZodEnum<{
|
|
68
|
+
en: "en";
|
|
69
|
+
pl: "pl";
|
|
70
|
+
}>;
|
|
71
|
+
sourcePath: z.ZodLiteral<"/tester-application">;
|
|
72
|
+
}, z.core.$strict>;
|
|
73
|
+
addressReference: z.ZodNullable<z.ZodObject<{
|
|
74
|
+
source: z.ZodString;
|
|
75
|
+
reference: z.ZodString;
|
|
76
|
+
revision: z.ZodString;
|
|
77
|
+
provenance: z.ZodString;
|
|
78
|
+
}, z.core.$strict>>;
|
|
79
|
+
state: z.ZodEnum<{
|
|
80
|
+
rejected: "rejected";
|
|
81
|
+
active: "active";
|
|
82
|
+
submitted: "submitted";
|
|
83
|
+
withdrawn: "withdrawn";
|
|
84
|
+
approved: "approved";
|
|
85
|
+
}>;
|
|
86
|
+
version: z.ZodNumber;
|
|
87
|
+
createdAt: z.ZodString;
|
|
88
|
+
updatedAt: z.ZodString;
|
|
89
|
+
}, z.core.$strict>>;
|
|
90
|
+
nextCursor: z.ZodNullable<z.ZodString>;
|
|
91
|
+
}, z.core.$strict>;
|
|
92
|
+
export declare const acquisitionCaseActiveCountResponseSchema: z.ZodObject<{
|
|
93
|
+
activeTesterCount: z.ZodNumber;
|
|
94
|
+
}, z.core.$strict>;
|
|
95
|
+
export declare const acquisitionIdempotencyKeySchema: z.ZodString;
|
|
96
|
+
export type AcquisitionOpaqueReference = z.infer<typeof acquisitionOpaqueReferenceSchema>;
|
|
97
|
+
export type AcquisitionAddressReference = z.infer<typeof acquisitionAddressReferenceSchema>;
|
|
98
|
+
export type AcquisitionCaseState = z.infer<typeof acquisitionCaseStateSchema>;
|
|
99
|
+
export type AcquisitionCaseProjection = z.infer<typeof acquisitionCaseProjectionSchema>;
|
|
100
|
+
export type AcquisitionCaseListRequest = z.infer<typeof acquisitionCaseListRequestSchema>;
|
|
101
|
+
export type AcquisitionCaseListResponse = z.infer<typeof acquisitionCaseListResponseSchema>;
|
|
102
|
+
export type AcquisitionCaseActiveCountResponse = z.infer<typeof acquisitionCaseActiveCountResponseSchema>;
|
|
103
|
+
export type AcquisitionIdempotencyKey = z.infer<typeof acquisitionIdempotencyKeySchema>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { z } from "./validation.js";
|
|
2
|
+
export const ACQUISITION_CASE_CONTRACT_VERSION = "tester_application_v1";
|
|
3
|
+
export const ACQUISITION_CASE_VIEW = "acquisition_case_v1";
|
|
4
|
+
export const ACQUISITION_CASE_SOURCE = "tester_application";
|
|
5
|
+
export const acquisitionOpaqueReferenceSchema = z.string().trim().min(8).max(160)
|
|
6
|
+
.regex(/^[A-Za-z0-9][A-Za-z0-9._:-]*$/);
|
|
7
|
+
const dateTimeSchema = z.string().datetime({ offset: true });
|
|
8
|
+
export const acquisitionAddressReferenceSchema = z.object({
|
|
9
|
+
source: z.string().trim().min(1).max(48).regex(/^[a-z][a-z0-9_-]*$/),
|
|
10
|
+
reference: acquisitionOpaqueReferenceSchema,
|
|
11
|
+
revision: z.string().trim().min(1).max(64).regex(/^[A-Za-z0-9][A-Za-z0-9._:-]*$/),
|
|
12
|
+
provenance: z.string().trim().min(1).max(64).regex(/^[a-z][a-z0-9_-]*$/),
|
|
13
|
+
}).strict();
|
|
14
|
+
export const acquisitionCaseStateSchema = z.enum([
|
|
15
|
+
"submitted", "approved", "active", "rejected", "withdrawn",
|
|
16
|
+
]);
|
|
17
|
+
export const acquisitionCaseProjectionSchema = z.object({
|
|
18
|
+
contractVersion: z.literal(ACQUISITION_CASE_CONTRACT_VERSION),
|
|
19
|
+
caseRef: acquisitionOpaqueReferenceSchema,
|
|
20
|
+
contactRef: acquisitionOpaqueReferenceSchema,
|
|
21
|
+
sourceKind: z.literal(ACQUISITION_CASE_SOURCE),
|
|
22
|
+
consent: z.object({
|
|
23
|
+
consentVersion: z.string().min(1).max(64),
|
|
24
|
+
policyVersion: z.string().min(1).max(64),
|
|
25
|
+
recordedAt: dateTimeSchema,
|
|
26
|
+
locale: z.enum(["en", "pl"]),
|
|
27
|
+
sourcePath: z.literal("/tester-application"),
|
|
28
|
+
}).strict(),
|
|
29
|
+
addressReference: acquisitionAddressReferenceSchema.nullable(),
|
|
30
|
+
state: acquisitionCaseStateSchema,
|
|
31
|
+
version: z.number().int().positive(),
|
|
32
|
+
createdAt: dateTimeSchema,
|
|
33
|
+
updatedAt: dateTimeSchema,
|
|
34
|
+
}).strict().superRefine((value, context) => {
|
|
35
|
+
if (value.state !== "withdrawn" && value.addressReference === null) {
|
|
36
|
+
context.addIssue({ code: z.ZodIssueCode.custom, path: ["addressReference"], message: "active case requires address reference" });
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
export const acquisitionCaseListRequestSchema = z.object({
|
|
40
|
+
view: z.literal(ACQUISITION_CASE_VIEW),
|
|
41
|
+
cursor: acquisitionOpaqueReferenceSchema.optional(),
|
|
42
|
+
limit: z.number().int().min(1).max(100).default(25),
|
|
43
|
+
}).strict();
|
|
44
|
+
export const acquisitionCaseListResponseSchema = z.object({
|
|
45
|
+
contractVersion: z.literal(ACQUISITION_CASE_CONTRACT_VERSION),
|
|
46
|
+
cases: z.array(acquisitionCaseProjectionSchema).max(100),
|
|
47
|
+
nextCursor: acquisitionOpaqueReferenceSchema.nullable(),
|
|
48
|
+
}).strict();
|
|
49
|
+
export const acquisitionCaseActiveCountResponseSchema = z.object({
|
|
50
|
+
activeTesterCount: z.number().int().nonnegative(),
|
|
51
|
+
}).strict();
|
|
52
|
+
export const acquisitionIdempotencyKeySchema = z.string().trim().min(8).max(200)
|
|
53
|
+
.regex(/^[A-Za-z0-9][A-Za-z0-9._:-]*$/);
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { z } from "./validation.js";
|
|
2
|
+
export declare const ACQUISITION_SURVEY_VERSION: "acquisition_survey_v1";
|
|
3
|
+
export declare const ACQUISITION_NEWSLETTER_VERSION: "acquisition_newsletter_consent_v1";
|
|
4
|
+
export declare const acquisitionSurveySubmitSchema: z.ZodObject<{
|
|
5
|
+
view: z.ZodLiteral<"acquisition_survey_v1">;
|
|
6
|
+
caseReference: z.ZodString;
|
|
7
|
+
surveyType: z.ZodEnum<{
|
|
8
|
+
producer: "producer";
|
|
9
|
+
consumer: "consumer";
|
|
10
|
+
}>;
|
|
11
|
+
responseData: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
12
|
+
}, z.core.$strip>;
|
|
13
|
+
export declare const acquisitionSurveyEvidenceSchema: z.ZodObject<{
|
|
14
|
+
evidenceReference: z.ZodString;
|
|
15
|
+
caseReference: z.ZodString;
|
|
16
|
+
contactReference: z.ZodString;
|
|
17
|
+
surveyType: z.ZodEnum<{
|
|
18
|
+
producer: "producer";
|
|
19
|
+
consumer: "consumer";
|
|
20
|
+
}>;
|
|
21
|
+
responseDigest: z.ZodString;
|
|
22
|
+
answerCount: z.ZodNumber;
|
|
23
|
+
recordedAt: z.ZodString;
|
|
24
|
+
}, z.core.$strip>;
|
|
25
|
+
export declare const acquisitionSurveySubmitResponseSchema: z.ZodObject<{
|
|
26
|
+
contractVersion: z.ZodLiteral<"acquisition_survey_v1">;
|
|
27
|
+
evidence: z.ZodObject<{
|
|
28
|
+
evidenceReference: z.ZodString;
|
|
29
|
+
caseReference: z.ZodString;
|
|
30
|
+
contactReference: z.ZodString;
|
|
31
|
+
surveyType: z.ZodEnum<{
|
|
32
|
+
producer: "producer";
|
|
33
|
+
consumer: "consumer";
|
|
34
|
+
}>;
|
|
35
|
+
responseDigest: z.ZodString;
|
|
36
|
+
answerCount: z.ZodNumber;
|
|
37
|
+
recordedAt: z.ZodString;
|
|
38
|
+
}, z.core.$strip>;
|
|
39
|
+
replayed: z.ZodBoolean;
|
|
40
|
+
}, z.core.$strip>;
|
|
41
|
+
export declare const acquisitionSurveyListQuerySchema: z.ZodObject<{
|
|
42
|
+
view: z.ZodLiteral<"acquisition_survey_v1">;
|
|
43
|
+
surveyType: z.ZodOptional<z.ZodEnum<{
|
|
44
|
+
producer: "producer";
|
|
45
|
+
consumer: "consumer";
|
|
46
|
+
}>>;
|
|
47
|
+
limit: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
48
|
+
}, z.core.$strip>;
|
|
49
|
+
export declare const acquisitionSurveyListResponseSchema: z.ZodObject<{
|
|
50
|
+
contractVersion: z.ZodLiteral<"acquisition_survey_v1">;
|
|
51
|
+
rows: z.ZodArray<z.ZodObject<{
|
|
52
|
+
evidenceReference: z.ZodString;
|
|
53
|
+
caseReference: z.ZodString;
|
|
54
|
+
contactReference: z.ZodString;
|
|
55
|
+
surveyType: z.ZodEnum<{
|
|
56
|
+
producer: "producer";
|
|
57
|
+
consumer: "consumer";
|
|
58
|
+
}>;
|
|
59
|
+
responseDigest: z.ZodString;
|
|
60
|
+
answerCount: z.ZodNumber;
|
|
61
|
+
recordedAt: z.ZodString;
|
|
62
|
+
}, z.core.$strip>>;
|
|
63
|
+
totalCount: z.ZodNumber;
|
|
64
|
+
}, z.core.$strip>;
|
|
65
|
+
export declare const acquisitionNewsletterConsentEventSchema: z.ZodObject<{
|
|
66
|
+
contractVersion: z.ZodLiteral<"acquisition_newsletter_consent_v1">;
|
|
67
|
+
caseReference: z.ZodString;
|
|
68
|
+
contactReference: z.ZodString;
|
|
69
|
+
eventReference: z.ZodString;
|
|
70
|
+
eventType: z.ZodEnum<{
|
|
71
|
+
suppress: "suppress";
|
|
72
|
+
update: "update";
|
|
73
|
+
subscribe: "subscribe";
|
|
74
|
+
unsubscribe: "unsubscribe";
|
|
75
|
+
complaint: "complaint";
|
|
76
|
+
}>;
|
|
77
|
+
purpose: z.ZodEnum<{
|
|
78
|
+
marketing_launch_offer: "marketing_launch_offer";
|
|
79
|
+
marketing_newsletter: "marketing_newsletter";
|
|
80
|
+
}>;
|
|
81
|
+
explicitOptInEvidence: z.ZodDefault<z.ZodBoolean>;
|
|
82
|
+
occurredAt: z.ZodString;
|
|
83
|
+
}, z.core.$strict>;
|
|
84
|
+
export declare const acquisitionNewsletterConsentResponseSchema: z.ZodObject<{
|
|
85
|
+
contractVersion: z.ZodLiteral<"acquisition_newsletter_consent_v1">;
|
|
86
|
+
eventReference: z.ZodString;
|
|
87
|
+
outcome: z.ZodEnum<{
|
|
88
|
+
applied: "applied";
|
|
89
|
+
ignored: "ignored";
|
|
90
|
+
stale: "stale";
|
|
91
|
+
}>;
|
|
92
|
+
consentState: z.ZodNullable<z.ZodEnum<{
|
|
93
|
+
suppressed: "suppressed";
|
|
94
|
+
granted: "granted";
|
|
95
|
+
}>>;
|
|
96
|
+
auditReference: z.ZodString;
|
|
97
|
+
replayed: z.ZodBoolean;
|
|
98
|
+
}, z.core.$strip>;
|
|
99
|
+
export type AcquisitionSurveySubmit = z.infer<typeof acquisitionSurveySubmitSchema>;
|
|
100
|
+
export type AcquisitionSurveyEvidence = z.infer<typeof acquisitionSurveyEvidenceSchema>;
|
|
101
|
+
export type AcquisitionSurveySubmitResponse = z.infer<typeof acquisitionSurveySubmitResponseSchema>;
|
|
102
|
+
export type AcquisitionSurveyListQuery = z.infer<typeof acquisitionSurveyListQuerySchema>;
|
|
103
|
+
export type AcquisitionSurveyListResponse = z.infer<typeof acquisitionSurveyListResponseSchema>;
|
|
104
|
+
export type AcquisitionNewsletterConsentEvent = z.infer<typeof acquisitionNewsletterConsentEventSchema>;
|
|
105
|
+
export type AcquisitionNewsletterConsentResponse = z.infer<typeof acquisitionNewsletterConsentResponseSchema>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { z } from "./validation.js";
|
|
2
|
+
export const ACQUISITION_SURVEY_VERSION = "acquisition_survey_v1";
|
|
3
|
+
export const ACQUISITION_NEWSLETTER_VERSION = "acquisition_newsletter_consent_v1";
|
|
4
|
+
const caseReference = z.string().regex(/^acquisition-case:[0-9a-f-]{36}$/);
|
|
5
|
+
const contactReference = z.string().regex(/^acquisition-contact:[0-9a-f-]{36}$/);
|
|
6
|
+
const commandKey = z.string().min(8).max(200).regex(/^[A-Za-z0-9][A-Za-z0-9._:-]+$/);
|
|
7
|
+
const digest = z.string().regex(/^[0-9a-f]{64}$/);
|
|
8
|
+
export const acquisitionSurveySubmitSchema = z.object({
|
|
9
|
+
view: z.literal(ACQUISITION_SURVEY_VERSION),
|
|
10
|
+
caseReference,
|
|
11
|
+
surveyType: z.enum(["producer", "consumer"]),
|
|
12
|
+
responseData: z.record(z.string(), z.unknown()),
|
|
13
|
+
});
|
|
14
|
+
export const acquisitionSurveyEvidenceSchema = z.object({
|
|
15
|
+
evidenceReference: z.string().regex(/^acquisition-survey:[0-9a-f-]{36}$/),
|
|
16
|
+
caseReference,
|
|
17
|
+
contactReference,
|
|
18
|
+
surveyType: z.enum(["producer", "consumer"]),
|
|
19
|
+
responseDigest: digest,
|
|
20
|
+
answerCount: z.number().int().min(1).max(100),
|
|
21
|
+
recordedAt: z.string().datetime({ offset: true }),
|
|
22
|
+
});
|
|
23
|
+
export const acquisitionSurveySubmitResponseSchema = z.object({
|
|
24
|
+
contractVersion: z.literal(ACQUISITION_SURVEY_VERSION),
|
|
25
|
+
evidence: acquisitionSurveyEvidenceSchema,
|
|
26
|
+
replayed: z.boolean(),
|
|
27
|
+
});
|
|
28
|
+
export const acquisitionSurveyListQuerySchema = z.object({
|
|
29
|
+
view: z.literal(ACQUISITION_SURVEY_VERSION),
|
|
30
|
+
surveyType: z.enum(["producer", "consumer"]).optional(),
|
|
31
|
+
limit: z.coerce.number().int().min(1).max(100).default(50),
|
|
32
|
+
});
|
|
33
|
+
export const acquisitionSurveyListResponseSchema = z.object({
|
|
34
|
+
contractVersion: z.literal(ACQUISITION_SURVEY_VERSION),
|
|
35
|
+
rows: z.array(acquisitionSurveyEvidenceSchema),
|
|
36
|
+
totalCount: z.number().int().min(0),
|
|
37
|
+
});
|
|
38
|
+
export const acquisitionNewsletterConsentEventSchema = z.object({
|
|
39
|
+
contractVersion: z.literal(ACQUISITION_NEWSLETTER_VERSION),
|
|
40
|
+
caseReference,
|
|
41
|
+
contactReference,
|
|
42
|
+
eventReference: commandKey,
|
|
43
|
+
eventType: z.enum(["subscribe", "unsubscribe", "suppress", "complaint", "update"]),
|
|
44
|
+
purpose: z.enum(["marketing_launch_offer", "marketing_newsletter"]),
|
|
45
|
+
explicitOptInEvidence: z.boolean().default(false),
|
|
46
|
+
occurredAt: z.string().datetime({ offset: true }),
|
|
47
|
+
}).strict();
|
|
48
|
+
export const acquisitionNewsletterConsentResponseSchema = z.object({
|
|
49
|
+
contractVersion: z.literal(ACQUISITION_NEWSLETTER_VERSION),
|
|
50
|
+
eventReference: commandKey,
|
|
51
|
+
outcome: z.enum(["applied", "ignored", "stale"]),
|
|
52
|
+
consentState: z.enum(["granted", "suppressed"]).nullable(),
|
|
53
|
+
auditReference: z.string().regex(/^acquisition-consent:[0-9a-f-]{36}$/),
|
|
54
|
+
replayed: z.boolean(),
|
|
55
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { AnalyticsSinkAttemptMask, AnalyticsSinkAdmission, AnalyticsCookieConsent, AnalyticsDataLayerOptions } from "../contracts/analytics.js";
|
|
2
|
+
export type { AnalyticsSinkAttemptMask, AnalyticsSinkAdmission, AnalyticsCookieConsent, AnalyticsBrowserWindow, AnalyticsDataLayerOptions } from "../contracts/analytics.js";
|
|
3
|
+
import type { AnalyticsEventMap, AnalyticsEventName } from "./noop.js";
|
|
4
|
+
/** Each instance owns loader state; the host supplies browser and deployment policy. */
|
|
5
|
+
export declare function createAnalyticsDataLayer<StepName extends string = string>(options?: AnalyticsDataLayerOptions<StepName>): {
|
|
6
|
+
readCookieConsent: () => AnalyticsCookieConsent | null;
|
|
7
|
+
hasAnalyticsConsent: (consent?: AnalyticsCookieConsent | null) => boolean;
|
|
8
|
+
configuredContainerId: () => string | null;
|
|
9
|
+
subscribeToAnalyticsConsent: (listener: () => void) => () => void;
|
|
10
|
+
isAnalyticsLoaderReady: () => boolean;
|
|
11
|
+
subscribeToAnalyticsLoaderReadiness: (listener: () => void) => () => void;
|
|
12
|
+
markAnalyticsLoaderReady: () => void;
|
|
13
|
+
resetAnalyticsLoaderReadiness: () => void;
|
|
14
|
+
pushAnalyticsConsentDefault: () => void;
|
|
15
|
+
pushAnalyticsConsentUpdate: (granted: boolean) => void;
|
|
16
|
+
pushAnalyticsEvent: <K extends AnalyticsEventName>(event: K, properties: AnalyticsEventMap<StepName>[K]) => boolean;
|
|
17
|
+
emitAnalyticsEvent: <K extends AnalyticsEventName>(event: K, properties: AnalyticsEventMap<StepName>[K], attempts?: AnalyticsSinkAttemptMask) => AnalyticsSinkAdmission;
|
|
18
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { trackDeploymentEvent } from "./noop.js";
|
|
2
|
+
/** Each instance owns loader state; the host supplies browser and deployment policy. */
|
|
3
|
+
export function createAnalyticsDataLayer(options = {}) {
|
|
4
|
+
const { consentStorageKey = "", consentChangeEvent = "", consentSignalNames, eventPrefix = "", readContainerId = () => null, getWindow = () => undefined } = options;
|
|
5
|
+
const deploymentSink = options.trackDeploymentEvent ?? trackDeploymentEvent;
|
|
6
|
+
let analyticsLoaderReady = false;
|
|
7
|
+
const readinessListeners = new Set();
|
|
8
|
+
function readCookieConsent() {
|
|
9
|
+
const browser = getWindow();
|
|
10
|
+
if (!browser)
|
|
11
|
+
return null;
|
|
12
|
+
try {
|
|
13
|
+
const parsed = JSON.parse(browser.localStorage.getItem(consentStorageKey) ?? "");
|
|
14
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
15
|
+
return null;
|
|
16
|
+
const value = parsed;
|
|
17
|
+
if (typeof value.necessary !== "boolean" || typeof value.functional !== "boolean"
|
|
18
|
+
|| typeof value.analytics !== "boolean" || typeof value.marketing !== "boolean")
|
|
19
|
+
return null;
|
|
20
|
+
return { necessary: value.necessary, functional: value.functional,
|
|
21
|
+
analytics: value.analytics, marketing: value.marketing };
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function hasAnalyticsConsent(consent = readCookieConsent()) { return consent?.analytics === true; }
|
|
28
|
+
function configuredContainerId() { return readContainerId(); }
|
|
29
|
+
function subscribeToAnalyticsConsent(listener) {
|
|
30
|
+
const browser = getWindow();
|
|
31
|
+
if (!browser)
|
|
32
|
+
return () => undefined;
|
|
33
|
+
const onStorage = (event) => { if (event.key === consentStorageKey)
|
|
34
|
+
listener(); };
|
|
35
|
+
browser.addEventListener("storage", onStorage);
|
|
36
|
+
browser.addEventListener(consentChangeEvent, listener);
|
|
37
|
+
return () => {
|
|
38
|
+
browser.removeEventListener("storage", onStorage);
|
|
39
|
+
browser.removeEventListener(consentChangeEvent, listener);
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function isAnalyticsLoaderReady() { return analyticsLoaderReady; }
|
|
43
|
+
function subscribeToAnalyticsLoaderReadiness(listener) {
|
|
44
|
+
readinessListeners.add(listener);
|
|
45
|
+
return () => readinessListeners.delete(listener);
|
|
46
|
+
}
|
|
47
|
+
function markAnalyticsLoaderReady() {
|
|
48
|
+
if (analyticsLoaderReady)
|
|
49
|
+
return;
|
|
50
|
+
analyticsLoaderReady = true;
|
|
51
|
+
for (const listener of readinessListeners)
|
|
52
|
+
listener();
|
|
53
|
+
}
|
|
54
|
+
function resetAnalyticsLoaderReadiness() { analyticsLoaderReady = false; }
|
|
55
|
+
function pushAnalyticsConsentDefault() {
|
|
56
|
+
pushConsentCommand("default", "denied", "denied");
|
|
57
|
+
}
|
|
58
|
+
function pushAnalyticsConsentUpdate(granted) {
|
|
59
|
+
const analyticsState = granted ? "granted" : "denied";
|
|
60
|
+
// Read the separate advertising grant at push time; revocation denies all signals.
|
|
61
|
+
const adState = granted && readCookieConsent()?.marketing === true ? "granted" : "denied";
|
|
62
|
+
pushConsentCommand("update", analyticsState, adState);
|
|
63
|
+
}
|
|
64
|
+
function pushAnalyticsEvent(event, properties) {
|
|
65
|
+
const browser = getWindow();
|
|
66
|
+
if (!consentSignalNames || !browser || !configuredContainerId() || !hasAnalyticsConsent())
|
|
67
|
+
return false;
|
|
68
|
+
if (!browser.dataLayer) {
|
|
69
|
+
browser.dataLayer = [];
|
|
70
|
+
pushAnalyticsConsentDefault();
|
|
71
|
+
pushAnalyticsConsentUpdate(true);
|
|
72
|
+
}
|
|
73
|
+
browser.dataLayer.push({ event: `${eventPrefix}${event}`, ...properties });
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
function emitAnalyticsEvent(event, properties, attempts = { deployment: true, optional: true }) {
|
|
77
|
+
let deployment = false;
|
|
78
|
+
let optional = false;
|
|
79
|
+
if (attempts.deployment) {
|
|
80
|
+
try {
|
|
81
|
+
deployment = deploymentSink(event, properties);
|
|
82
|
+
}
|
|
83
|
+
catch { /* Sink failure never gates the product. */ }
|
|
84
|
+
}
|
|
85
|
+
if (attempts.optional) {
|
|
86
|
+
try {
|
|
87
|
+
optional = pushAnalyticsEvent(event, properties);
|
|
88
|
+
}
|
|
89
|
+
catch { /* Independent best-effort admission. */ }
|
|
90
|
+
}
|
|
91
|
+
return { deployment, optional };
|
|
92
|
+
}
|
|
93
|
+
function pushConsentCommand(action, analyticsState, adState) {
|
|
94
|
+
const browser = getWindow();
|
|
95
|
+
if (!consentSignalNames || !browser?.dataLayer)
|
|
96
|
+
return;
|
|
97
|
+
const settings = { [consentSignalNames.analytics]: analyticsState };
|
|
98
|
+
for (const name of consentSignalNames.advertising)
|
|
99
|
+
settings[name] = adState;
|
|
100
|
+
function consentCommand(..._args) {
|
|
101
|
+
// Consent commands use the array-like arguments object, not an event object.
|
|
102
|
+
// eslint-disable-next-line prefer-rest-params
|
|
103
|
+
browser?.dataLayer?.push(arguments);
|
|
104
|
+
}
|
|
105
|
+
consentCommand("consent", action, settings);
|
|
106
|
+
}
|
|
107
|
+
return { readCookieConsent, hasAnalyticsConsent, configuredContainerId, subscribeToAnalyticsConsent,
|
|
108
|
+
isAnalyticsLoaderReady, subscribeToAnalyticsLoaderReadiness, markAnalyticsLoaderReady,
|
|
109
|
+
resetAnalyticsLoaderReadiness, pushAnalyticsConsentDefault, pushAnalyticsConsentUpdate,
|
|
110
|
+
pushAnalyticsEvent, emitAnalyticsEvent };
|
|
111
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { AnalyticsEventMap, AnalyticsEventName } from "../contracts/analytics.js";
|
|
2
|
+
export type { AnalyticsStepName, AnalyticsCheckoutItem, AnalyticsPurchaseItem, AnalyticsCheckoutMode, AnalyticsPaymentWaitOutcome, AnalyticsEventMap, AnalyticsEventName } from "../contracts/analytics.js";
|
|
3
|
+
import type { AnalyticsPort } from "@openlup/core/platform-runtime";
|
|
4
|
+
export declare function initializeDeploymentAnalytics(): void;
|
|
5
|
+
export declare function trackDeploymentEvent<K extends AnalyticsEventName>(_event: K, _properties: AnalyticsEventMap[K]): boolean;
|
|
6
|
+
export declare function syncAdvertisingConsent(): void;
|
|
7
|
+
export declare function trackAdvertisingPurchase(_payload: AnalyticsEventMap["purchase"]): boolean;
|
|
8
|
+
/** Inert composition satisfies the existing platform runtime port. */
|
|
9
|
+
export declare function createNoopAnalytics(): AnalyticsPort;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export function initializeDeploymentAnalytics() { }
|
|
2
|
+
export function trackDeploymentEvent(_event, _properties) { return false; }
|
|
3
|
+
export function syncAdvertisingConsent() { }
|
|
4
|
+
export function trackAdvertisingPurchase(_payload) { return false; }
|
|
5
|
+
/** Inert composition satisfies the existing platform runtime port. */
|
|
6
|
+
export function createNoopAnalytics() {
|
|
7
|
+
return { trackPageView() { }, trackEvent() { } };
|
|
8
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { PurchaseOrder, PurchaseSuppressionReason } from "../contracts/analytics.js";
|
|
2
|
+
export type { PurchaseOrder, PurchaseSuppressionReason } from "../contracts/analytics.js";
|
|
3
|
+
import type { AnalyticsEventMap } from "./noop.js";
|
|
4
|
+
export declare function projectPaidPurchase(recap: PurchaseOrder, suppressPurchase?: (reason: PurchaseSuppressionReason) => null): AnalyticsEventMap["purchase"] | null;
|