@owlmeans/marketing-consent-postgres 0.1.18-rc.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/README.md +15 -0
- package/agent-meta/manifest.json +16 -0
- package/agent-meta/skills/marketing-consent-postgres/SKILL.md +83 -0
- package/build/helper.d.ts +12 -0
- package/build/helper.d.ts.map +1 -0
- package/build/helper.js +16 -0
- package/build/helper.js.map +1 -0
- package/build/index.d.ts +3 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +3 -0
- package/build/index.js.map +1 -0
- package/build/resource.d.ts +19 -0
- package/build/resource.d.ts.map +1 -0
- package/build/resource.js +31 -0
- package/build/resource.js.map +1 -0
- package/package.json +50 -0
- package/src/helper.ts +25 -0
- package/src/index.ts +2 -0
- package/src/resource.ts +47 -0
- package/tests/context.ts +92 -0
- package/tests/resource.spec.ts +121 -0
- package/tests/schema.spec.ts +85 -0
- package/tsconfig.json +11 -0
package/README.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!-- owlmeans:agent-guidance:start -->
|
|
2
|
+
## Agent guidance
|
|
3
|
+
|
|
4
|
+
This package ships embedded agent skills under `agent-meta/`. After installing your
|
|
5
|
+
`@owlmeans/*` packages, run the OwlMeans agent-skills installer to place them into
|
|
6
|
+
your project's skill store (`.agents/skills/`):
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
npx @owlmeans/agent-skills@^0.1.18-rc.33
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
The embedded files are version-matched to this package release. Do not edit them
|
|
13
|
+
directly — they are regenerated on each publish. To contribute guidance edits,
|
|
14
|
+
open a PR against the source monorepo.
|
|
15
|
+
<!-- owlmeans:agent-guidance:end -->
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 2,
|
|
3
|
+
"package": "@owlmeans/marketing-consent-postgres",
|
|
4
|
+
"version": "0.1.18-rc.0",
|
|
5
|
+
"generatedAt": "2026-09-22T17:37:10.922Z",
|
|
6
|
+
"canonicalRepo": "https://github.com/owlmeans/common",
|
|
7
|
+
"entries": [
|
|
8
|
+
{
|
|
9
|
+
"kind": "skill",
|
|
10
|
+
"name": "marketing-consent-postgres",
|
|
11
|
+
"category": "package-specific",
|
|
12
|
+
"file": "skills/marketing-consent-postgres/SKILL.md",
|
|
13
|
+
"canonicalPath": ".agents/skills/marketing-consent-postgres/SKILL.md"
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: marketing-consent-postgres
|
|
3
|
+
description: How to use @owlmeans/marketing-consent-postgres — the Postgres extension that registers the two @owlmeans/server-marketing-consent resources (marketing-consent-state, marketing-consent-log) for a GENERATED TARGET PROJECT. Auto-invoked when wiring marketing-consent storage into a target project's Postgres backend, or touching the state/log table shape.
|
|
4
|
+
user-invocable: false
|
|
5
|
+
---
|
|
6
|
+
<!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
|
|
7
|
+
|
|
8
|
+
# @owlmeans/marketing-consent-postgres
|
|
9
|
+
|
|
10
|
+
**Layer:** Infra extension
|
|
11
|
+
**Install:** `"@owlmeans/marketing-consent-postgres": "^0.1.18-rc.0"` in `dependencies` (peers `pg`, `ajv`)
|
|
12
|
+
|
|
13
|
+
The Postgres counterpart to `@owlmeans/server-marketing-consent`'s two resources. This package is
|
|
14
|
+
for a **generated target project**, which runs Postgres — the platform's own equivalent for
|
|
15
|
+
projects that stayed on Mongo is `@owlmeans/marketing-consent-mongo` (a separate, not-yet-built
|
|
16
|
+
package); Viable itself runs Mongo and never needs this one.
|
|
17
|
+
|
|
18
|
+
## Key Exports
|
|
19
|
+
|
|
20
|
+
| Export | Description |
|
|
21
|
+
|--------|-------------|
|
|
22
|
+
| `makeMarketingConsentStatePostgres(dbAlias?, serviceAlias?)` | `PostgresResource<MarketingConsentStateRecord>` at `RES_MARKETING_CONSENT_STATE` |
|
|
23
|
+
| `makeMarketingConsentLogPostgres(dbAlias?, serviceAlias?)` | `PostgresResource<MarketingConsentLogRecord>` at `RES_MARKETING_CONSENT_LOG` |
|
|
24
|
+
| `appendMarketingConsentPostgres(ctx, opts?)` | Registers both, each guarded by `ctx.hasResource(...)` — safe to call more than once |
|
|
25
|
+
|
|
26
|
+
## The schema is imported, never redeclared
|
|
27
|
+
|
|
28
|
+
Both makers set `resource.schema` to `MarketingConsentStateSchema` / `MarketingConsentLogSchema`
|
|
29
|
+
from `@owlmeans/server-marketing-consent` — this package owns no AJV schema of its own. The two
|
|
30
|
+
record shapes are shared with the (not yet built) `@owlmeans/marketing-consent-mongo` sibling;
|
|
31
|
+
duplicating the schema here would let the two extensions drift apart from what
|
|
32
|
+
`MarketingConsentService` actually reads and writes.
|
|
33
|
+
|
|
34
|
+
## `decisions` / `terms` / `documents` / `notices` are one jsonb column each — never split
|
|
35
|
+
|
|
36
|
+
`MarketingConsentStateRecord.decisions` is an ARRAY, never an object keyed by consent key —
|
|
37
|
+
`server-marketing-consent`'s own skill explains why: a dotted key like `"marketing.email"` is read
|
|
38
|
+
as a PATH by Postgres jsonb operators, so an object-keyed shape breaks the moment a second key is
|
|
39
|
+
added. This package does nothing to make that safe by itself — it relies on
|
|
40
|
+
`@owlmeans/postgres-resource`'s **default** schema-to-table rule: any `array` of objects (or any
|
|
41
|
+
nested `object` property) compiles to a single opaque `jsonb` column, never a child table and never
|
|
42
|
+
one column per field. No `pg:` override is applied here, and none is needed — `tests/schema.spec.ts`
|
|
43
|
+
asserts this directly against `schemaToTableSpec`, which is the one test that would catch a
|
|
44
|
+
regression here (in this package, in `postgres-resource`'s compiler, or in a future schema edit
|
|
45
|
+
that accidentally reshapes `decisions` into something path-addressable).
|
|
46
|
+
|
|
47
|
+
## Indexes
|
|
48
|
+
|
|
49
|
+
- `idx_mc_state_subject` — `{ columns: ['subject'], unique: true }`: one state row per subject,
|
|
50
|
+
the same invariant `subjectKey(subject)` being the record's own `id` already gives on the Mongo
|
|
51
|
+
side.
|
|
52
|
+
- `idx_mc_state_user` — `{ columns: ['userId'] }`: lookups across a user's profiles/entities.
|
|
53
|
+
- `idx_mc_log_subject_key` — `{ columns: ['subject', 'key', 'decidedAt'] }`: a subject's history
|
|
54
|
+
for one consent key, newest first.
|
|
55
|
+
- `idx_mc_log_user` — `{ columns: ['userId', 'decidedAt'] }`: a user's whole consent/terms history.
|
|
56
|
+
|
|
57
|
+
## Wiring
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { appendMarketingConsentPostgres } from '@owlmeans/marketing-consent-postgres'
|
|
61
|
+
import { appendMarketingConsentService } from '@owlmeans/server-marketing-consent'
|
|
62
|
+
|
|
63
|
+
appendMarketingConsentPostgres(context) // register the two Postgres resources first
|
|
64
|
+
appendMarketingConsentService(context) // the service resolves them by alias, lazily, per call
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Registration order relative to the service append only matters for the resources needing to exist
|
|
68
|
+
by the first `status`/`save`/`terms` call — `server-marketing-consent`'s own skill covers this.
|
|
69
|
+
|
|
70
|
+
## Testing
|
|
71
|
+
|
|
72
|
+
`bun test ./tests` — `tests/schema.spec.ts` needs no live database (the jsonb-shape assertion
|
|
73
|
+
above); `tests/resource.spec.ts` is env-gated on `POSTGRES_URL` exactly like
|
|
74
|
+
`@owlmeans/postgres-resource`'s own integration suites (`@owlmeans/test-integration`'s
|
|
75
|
+
`postgresGate()`), and skips cleanly with no server reachable.
|
|
76
|
+
|
|
77
|
+
## Related
|
|
78
|
+
|
|
79
|
+
- `@owlmeans/server-marketing-consent` — the service, the record types, the AJV schemas this
|
|
80
|
+
package imports (read its skill first)
|
|
81
|
+
- `@owlmeans/postgres-resource` — the resource factory, the schema-to-table compiler, `PgIndexSpec`
|
|
82
|
+
- `@owlmeans/marketing-consent-mongo` — the Mongo counterpart for the platform's own database (not
|
|
83
|
+
yet built)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { BasicContext } from '@owlmeans/context';
|
|
2
|
+
export interface MarketingConsentPostgresOptions {
|
|
3
|
+
dbAlias?: string;
|
|
4
|
+
serviceAlias?: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Registers the two Postgres resources this package owns, once. A context that already has
|
|
8
|
+
* either resource registered — a repeated call from an app that composes contexts in more than
|
|
9
|
+
* one place — is left alone, which is also what keeps this idempotent across a hot reload.
|
|
10
|
+
*/
|
|
11
|
+
export declare const appendMarketingConsentPostgres: (ctx: BasicContext<any>, opts?: MarketingConsentPostgresOptions) => void;
|
|
12
|
+
//# sourceMappingURL=helper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helper.d.ts","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAKrD,MAAM,WAAW,+BAA+B;IAC9C,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED;;;;GAIG;AACH,eAAO,MAAM,8BAA8B,QACpC,YAAY,CAAC,GAAG,CAAC,SAAS,+BAA+B,KAC7D,IAOF,CAAA"}
|
package/build/helper.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { RES_MARKETING_CONSENT_LOG, RES_MARKETING_CONSENT_STATE } from '@owlmeans/server-marketing-consent';
|
|
2
|
+
import { makeMarketingConsentLogPostgres, makeMarketingConsentStatePostgres } from './resource.js';
|
|
3
|
+
/**
|
|
4
|
+
* Registers the two Postgres resources this package owns, once. A context that already has
|
|
5
|
+
* either resource registered — a repeated call from an app that composes contexts in more than
|
|
6
|
+
* one place — is left alone, which is also what keeps this idempotent across a hot reload.
|
|
7
|
+
*/
|
|
8
|
+
export const appendMarketingConsentPostgres = (ctx, opts) => {
|
|
9
|
+
if (!ctx.hasResource(RES_MARKETING_CONSENT_STATE)) {
|
|
10
|
+
ctx.registerResource(makeMarketingConsentStatePostgres(opts?.dbAlias, opts?.serviceAlias));
|
|
11
|
+
}
|
|
12
|
+
if (!ctx.hasResource(RES_MARKETING_CONSENT_LOG)) {
|
|
13
|
+
ctx.registerResource(makeMarketingConsentLogPostgres(opts?.dbAlias, opts?.serviceAlias));
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=helper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helper.js","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,yBAAyB,EAAE,2BAA2B,EAAE,MAAM,oCAAoC,CAAA;AAE3G,OAAO,EAAE,+BAA+B,EAAE,iCAAiC,EAAE,MAAM,eAAe,CAAA;AAOlG;;;;GAIG;AACH,MAAM,CAAC,MAAM,8BAA8B,GAAG,CAC5C,GAAsB,EAAE,IAAsC,EACxD,EAAE;IACR,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,2BAA2B,CAAC,EAAE,CAAC;QAClD,GAAG,CAAC,gBAAgB,CAAC,iCAAiC,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,CAAC,CAAC,CAAA;IAC5F,CAAC;IACD,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,yBAAyB,CAAC,EAAE,CAAC;QAChD,GAAG,CAAC,gBAAgB,CAAC,+BAA+B,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,CAAC,CAAC,CAAA;IAC1F,CAAC;AACH,CAAC,CAAA"}
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAA;AAC7B,cAAc,aAAa,CAAA"}
|
package/build/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAA;AAC7B,cAAc,aAAa,CAAA"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { PostgresResource } from '@owlmeans/postgres-resource';
|
|
2
|
+
import type { MarketingConsentLogRecord, MarketingConsentStateRecord } from '@owlmeans/server-marketing-consent';
|
|
3
|
+
/**
|
|
4
|
+
* One row per subject. `decisions`/`terms` compile to a single `jsonb` column each — the AJV
|
|
5
|
+
* schema declares them as a nested array/object with no `pg:` override, and
|
|
6
|
+
* `schemaToTableSpec`'s default rule for "any array of objects, or a nested object" is exactly
|
|
7
|
+
* that: one opaque `jsonb` column, never a child table or one column per field. That is load
|
|
8
|
+
* bearing here, not incidental — `decisions` holds dotted keys such as `"marketing.email"`, and
|
|
9
|
+
* flattening it into columns or a keyed jsonb path would make that key readable as a PATH by
|
|
10
|
+
* Postgres' own jsonb operators (see `@owlmeans/server-marketing-consent`'s `model.ts`). This
|
|
11
|
+
* maker never reshapes the record; it only imports the schema and adds the indexes below.
|
|
12
|
+
*/
|
|
13
|
+
export declare const makeMarketingConsentStatePostgres: (dbAlias?: string, serviceAlias?: string) => PostgresResource<MarketingConsentStateRecord>;
|
|
14
|
+
/**
|
|
15
|
+
* Append-only evidence log — same jsonb-column reasoning as the state table above applies to
|
|
16
|
+
* its `documents`/`notices` properties.
|
|
17
|
+
*/
|
|
18
|
+
export declare const makeMarketingConsentLogPostgres: (dbAlias?: string, serviceAlias?: string) => PostgresResource<MarketingConsentLogRecord>;
|
|
19
|
+
//# sourceMappingURL=resource.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AAKnE,OAAO,KAAK,EAAE,yBAAyB,EAAE,2BAA2B,EAAE,MAAM,oCAAoC,CAAA;AAEhH;;;;;;;;;GASG;AACH,eAAO,MAAM,iCAAiC,aAClC,MAAM,iBAAiB,MAAM,KACtC,gBAAgB,CAAC,2BAA2B,CAS9C,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,+BAA+B,aAChC,MAAM,iBAAiB,MAAM,KACtC,gBAAgB,CAAC,yBAAyB,CAS5C,CAAA"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { makePostgresResource } from '@owlmeans/postgres-resource';
|
|
2
|
+
import { MarketingConsentLogSchema, MarketingConsentStateSchema, RES_MARKETING_CONSENT_LOG, RES_MARKETING_CONSENT_STATE } from '@owlmeans/server-marketing-consent';
|
|
3
|
+
/**
|
|
4
|
+
* One row per subject. `decisions`/`terms` compile to a single `jsonb` column each — the AJV
|
|
5
|
+
* schema declares them as a nested array/object with no `pg:` override, and
|
|
6
|
+
* `schemaToTableSpec`'s default rule for "any array of objects, or a nested object" is exactly
|
|
7
|
+
* that: one opaque `jsonb` column, never a child table or one column per field. That is load
|
|
8
|
+
* bearing here, not incidental — `decisions` holds dotted keys such as `"marketing.email"`, and
|
|
9
|
+
* flattening it into columns or a keyed jsonb path would make that key readable as a PATH by
|
|
10
|
+
* Postgres' own jsonb operators (see `@owlmeans/server-marketing-consent`'s `model.ts`). This
|
|
11
|
+
* maker never reshapes the record; it only imports the schema and adds the indexes below.
|
|
12
|
+
*/
|
|
13
|
+
export const makeMarketingConsentStatePostgres = (dbAlias, serviceAlias) => {
|
|
14
|
+
const resource = makePostgresResource(RES_MARKETING_CONSENT_STATE, dbAlias, serviceAlias);
|
|
15
|
+
resource.schema = MarketingConsentStateSchema;
|
|
16
|
+
resource.index('idx_mc_state_subject', { columns: ['subject'], unique: true });
|
|
17
|
+
resource.index('idx_mc_state_user', { columns: ['userId'] });
|
|
18
|
+
return resource;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Append-only evidence log — same jsonb-column reasoning as the state table above applies to
|
|
22
|
+
* its `documents`/`notices` properties.
|
|
23
|
+
*/
|
|
24
|
+
export const makeMarketingConsentLogPostgres = (dbAlias, serviceAlias) => {
|
|
25
|
+
const resource = makePostgresResource(RES_MARKETING_CONSENT_LOG, dbAlias, serviceAlias);
|
|
26
|
+
resource.schema = MarketingConsentLogSchema;
|
|
27
|
+
resource.index('idx_mc_log_subject_key', { columns: ['subject', 'key', 'decidedAt'] });
|
|
28
|
+
resource.index('idx_mc_log_user', { columns: ['userId', 'decidedAt'] });
|
|
29
|
+
return resource;
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=resource.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource.js","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAA;AAElE,OAAO,EACL,yBAAyB,EAAE,2BAA2B,EACtD,yBAAyB,EAAE,2BAA2B,EACvD,MAAM,oCAAoC,CAAA;AAG3C;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,iCAAiC,GAAG,CAC/C,OAAgB,EAAE,YAAqB,EACQ,EAAE;IACjD,MAAM,QAAQ,GAAG,oBAAoB,CACnC,2BAA2B,EAAE,OAAO,EAAE,YAAY,CACnD,CAAA;IACD,QAAQ,CAAC,MAAM,GAAG,2BAA2B,CAAA;IAC7C,QAAQ,CAAC,KAAK,CAAC,sBAAsB,EAAE,EAAE,OAAO,EAAE,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAA;IAC9E,QAAQ,CAAC,KAAK,CAAC,mBAAmB,EAAE,EAAE,OAAO,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;IAE5D,OAAO,QAAQ,CAAA;AACjB,CAAC,CAAA;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAG,CAC7C,OAAgB,EAAE,YAAqB,EACM,EAAE;IAC/C,MAAM,QAAQ,GAAG,oBAAoB,CACnC,yBAAyB,EAAE,OAAO,EAAE,YAAY,CACjD,CAAA;IACD,QAAQ,CAAC,MAAM,GAAG,yBAAyB,CAAA;IAC3C,QAAQ,CAAC,KAAK,CAAC,wBAAwB,EAAE,EAAE,OAAO,EAAE,CAAC,SAAS,EAAE,KAAK,EAAE,WAAW,CAAC,EAAE,CAAC,CAAA;IACtF,QAAQ,CAAC,KAAK,CAAC,iBAAiB,EAAE,EAAE,OAAO,EAAE,CAAC,QAAQ,EAAE,WAAW,CAAC,EAAE,CAAC,CAAA;IAEvE,OAAO,QAAQ,CAAA;AACjB,CAAC,CAAA"}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@owlmeans/marketing-consent-postgres",
|
|
3
|
+
"version": "0.1.18-rc.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"description": "Postgres-backed resources for @owlmeans/marketing-consent — the state and log tables of a generated target project, compiled from the shared AJV schema in @owlmeans/server-marketing-consent.",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc -b",
|
|
9
|
+
"dev": "sleep 2 && nodemon -e ts,tsx,json --watch src --exec \"tsc -p ./tsconfig.json\"",
|
|
10
|
+
"watch": "tsc -b -w --preserveWatchOutput --pretty",
|
|
11
|
+
"test": "bun test ./tests"
|
|
12
|
+
},
|
|
13
|
+
"main": "build/index.js",
|
|
14
|
+
"module": "build/index.js",
|
|
15
|
+
"types": "build/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"import": "./build/index.js",
|
|
19
|
+
"require": "./build/index.js",
|
|
20
|
+
"default": "./build/index.js",
|
|
21
|
+
"module": "./build/index.js",
|
|
22
|
+
"types": "./build/index.d.ts"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@owlmeans/context": "^0.1.18-rc.29",
|
|
27
|
+
"@owlmeans/marketing-consent": "^0.1.18-rc.0",
|
|
28
|
+
"@owlmeans/postgres-resource": "^0.1.18-rc.31",
|
|
29
|
+
"@owlmeans/resource": "^0.1.18-rc.31",
|
|
30
|
+
"@owlmeans/server-marketing-consent": "^0.1.18-rc.0"
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"ajv": "*",
|
|
34
|
+
"pg": "*"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@owlmeans/dep-config": "workspace:*",
|
|
38
|
+
"@owlmeans/test": "^0.1.18-rc.29",
|
|
39
|
+
"@owlmeans/test-integration": "^0.1.18-rc.26",
|
|
40
|
+
"@types/bun": "^1.4.0",
|
|
41
|
+
"@types/pg": "^8.20.4",
|
|
42
|
+
"ajv": "^8.17.1",
|
|
43
|
+
"nodemon": "^3.1.14",
|
|
44
|
+
"pg": "^8.22.0",
|
|
45
|
+
"typescript": "^7.0.2"
|
|
46
|
+
},
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public"
|
|
49
|
+
}
|
|
50
|
+
}
|
package/src/helper.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { BasicContext } from '@owlmeans/context'
|
|
2
|
+
import { RES_MARKETING_CONSENT_LOG, RES_MARKETING_CONSENT_STATE } from '@owlmeans/server-marketing-consent'
|
|
3
|
+
|
|
4
|
+
import { makeMarketingConsentLogPostgres, makeMarketingConsentStatePostgres } from './resource.js'
|
|
5
|
+
|
|
6
|
+
export interface MarketingConsentPostgresOptions {
|
|
7
|
+
dbAlias?: string
|
|
8
|
+
serviceAlias?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Registers the two Postgres resources this package owns, once. A context that already has
|
|
13
|
+
* either resource registered — a repeated call from an app that composes contexts in more than
|
|
14
|
+
* one place — is left alone, which is also what keeps this idempotent across a hot reload.
|
|
15
|
+
*/
|
|
16
|
+
export const appendMarketingConsentPostgres = (
|
|
17
|
+
ctx: BasicContext<any>, opts?: MarketingConsentPostgresOptions
|
|
18
|
+
): void => {
|
|
19
|
+
if (!ctx.hasResource(RES_MARKETING_CONSENT_STATE)) {
|
|
20
|
+
ctx.registerResource(makeMarketingConsentStatePostgres(opts?.dbAlias, opts?.serviceAlias))
|
|
21
|
+
}
|
|
22
|
+
if (!ctx.hasResource(RES_MARKETING_CONSENT_LOG)) {
|
|
23
|
+
ctx.registerResource(makeMarketingConsentLogPostgres(opts?.dbAlias, opts?.serviceAlias))
|
|
24
|
+
}
|
|
25
|
+
}
|
package/src/index.ts
ADDED
package/src/resource.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { makePostgresResource } from '@owlmeans/postgres-resource'
|
|
2
|
+
import type { PostgresResource } from '@owlmeans/postgres-resource'
|
|
3
|
+
import {
|
|
4
|
+
MarketingConsentLogSchema, MarketingConsentStateSchema,
|
|
5
|
+
RES_MARKETING_CONSENT_LOG, RES_MARKETING_CONSENT_STATE
|
|
6
|
+
} from '@owlmeans/server-marketing-consent'
|
|
7
|
+
import type { MarketingConsentLogRecord, MarketingConsentStateRecord } from '@owlmeans/server-marketing-consent'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* One row per subject. `decisions`/`terms` compile to a single `jsonb` column each — the AJV
|
|
11
|
+
* schema declares them as a nested array/object with no `pg:` override, and
|
|
12
|
+
* `schemaToTableSpec`'s default rule for "any array of objects, or a nested object" is exactly
|
|
13
|
+
* that: one opaque `jsonb` column, never a child table or one column per field. That is load
|
|
14
|
+
* bearing here, not incidental — `decisions` holds dotted keys such as `"marketing.email"`, and
|
|
15
|
+
* flattening it into columns or a keyed jsonb path would make that key readable as a PATH by
|
|
16
|
+
* Postgres' own jsonb operators (see `@owlmeans/server-marketing-consent`'s `model.ts`). This
|
|
17
|
+
* maker never reshapes the record; it only imports the schema and adds the indexes below.
|
|
18
|
+
*/
|
|
19
|
+
export const makeMarketingConsentStatePostgres = (
|
|
20
|
+
dbAlias?: string, serviceAlias?: string
|
|
21
|
+
): PostgresResource<MarketingConsentStateRecord> => {
|
|
22
|
+
const resource = makePostgresResource<MarketingConsentStateRecord>(
|
|
23
|
+
RES_MARKETING_CONSENT_STATE, dbAlias, serviceAlias
|
|
24
|
+
)
|
|
25
|
+
resource.schema = MarketingConsentStateSchema
|
|
26
|
+
resource.index('idx_mc_state_subject', { columns: ['subject'], unique: true })
|
|
27
|
+
resource.index('idx_mc_state_user', { columns: ['userId'] })
|
|
28
|
+
|
|
29
|
+
return resource
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Append-only evidence log — same jsonb-column reasoning as the state table above applies to
|
|
34
|
+
* its `documents`/`notices` properties.
|
|
35
|
+
*/
|
|
36
|
+
export const makeMarketingConsentLogPostgres = (
|
|
37
|
+
dbAlias?: string, serviceAlias?: string
|
|
38
|
+
): PostgresResource<MarketingConsentLogRecord> => {
|
|
39
|
+
const resource = makePostgresResource<MarketingConsentLogRecord>(
|
|
40
|
+
RES_MARKETING_CONSENT_LOG, dbAlias, serviceAlias
|
|
41
|
+
)
|
|
42
|
+
resource.schema = MarketingConsentLogSchema
|
|
43
|
+
resource.index('idx_mc_log_subject_key', { columns: ['subject', 'key', 'decidedAt'] })
|
|
44
|
+
resource.index('idx_mc_log_user', { columns: ['userId', 'decidedAt'] })
|
|
45
|
+
|
|
46
|
+
return resource
|
|
47
|
+
}
|
package/tests/context.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { postgresGate, randomNamespace } from '@owlmeans/test-integration'
|
|
2
|
+
import type { IntegrationGate, PostgresEnv } from '@owlmeans/test-integration'
|
|
3
|
+
import { PgAutoSync, resetDeclarations } from '@owlmeans/postgres-resource'
|
|
4
|
+
import { config, makeServerContext } from '@owlmeans/server-context'
|
|
5
|
+
import type { ServerConfig, ServerContext } from '@owlmeans/server-context'
|
|
6
|
+
import { Pool } from 'pg'
|
|
7
|
+
|
|
8
|
+
import { appendPostgres } from '@owlmeans/postgres'
|
|
9
|
+
import type { PostgresService } from '@owlmeans/postgres'
|
|
10
|
+
import { RES_MARKETING_CONSENT_LOG, RES_MARKETING_CONSENT_STATE } from '@owlmeans/server-marketing-consent'
|
|
11
|
+
|
|
12
|
+
import { appendMarketingConsentPostgres } from '../src/index.js'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Env-gated integration harness, the same shape `@owlmeans/postgres`'s own tests use: a real
|
|
16
|
+
* `ServerContext` against a throwaway schema, dropped in `teardown()`. Lives here rather than
|
|
17
|
+
* importing the sibling package's own `tests/context.ts`, which is test-only source that no
|
|
18
|
+
* package exports.
|
|
19
|
+
*/
|
|
20
|
+
export const gate: IntegrationGate<PostgresEnv> = postgresGate()
|
|
21
|
+
|
|
22
|
+
const url = (): string => gate.env.POSTGRES_URL as string
|
|
23
|
+
|
|
24
|
+
export const raw = async <R>(fn: (pool: Pool) => Promise<R>): Promise<R> => {
|
|
25
|
+
const pool = new Pool({ connectionString: url(), max: 1 })
|
|
26
|
+
try {
|
|
27
|
+
return await fn(pool)
|
|
28
|
+
} finally {
|
|
29
|
+
await pool.end().catch(() => undefined)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface Booted {
|
|
34
|
+
context: ServerContext<ServerConfig>
|
|
35
|
+
pg: PostgresService
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface Suite {
|
|
39
|
+
schema: string
|
|
40
|
+
boot: () => Promise<Booted>
|
|
41
|
+
teardown: () => Promise<void>
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const makeSuite = (label: string): Suite => {
|
|
45
|
+
const prefix = process.env.POSTGRES_TEST_DB_PREFIX ?? 'omt'
|
|
46
|
+
const schema = randomNamespace(`${prefix}_${label}`)
|
|
47
|
+
const pools: Pool[] = []
|
|
48
|
+
|
|
49
|
+
const boot = async (): Promise<Booted> => {
|
|
50
|
+
/** A fresh declaration per suite run — this spec simulates a freshly started process. */
|
|
51
|
+
resetDeclarations(RES_MARKETING_CONSENT_STATE)
|
|
52
|
+
resetDeclarations(RES_MARKETING_CONSENT_LOG)
|
|
53
|
+
|
|
54
|
+
const cfg: ServerConfig = config('mc-pg-test', {
|
|
55
|
+
dbs: [{
|
|
56
|
+
service: 'postgres', alias: 'postgres', host: '127.0.0.1', schema,
|
|
57
|
+
meta: { url: url(), max: 2, retries: 3, retryDelayMillis: 200, autoSync: PgAutoSync.Full }
|
|
58
|
+
}]
|
|
59
|
+
} as Partial<ServerConfig>)
|
|
60
|
+
|
|
61
|
+
const context = makeServerContext(cfg) as ServerContext<ServerConfig>
|
|
62
|
+
appendPostgres(context)
|
|
63
|
+
appendMarketingConsentPostgres(context)
|
|
64
|
+
|
|
65
|
+
context.configure()
|
|
66
|
+
const pg = context.service<PostgresService>('postgres')
|
|
67
|
+
try {
|
|
68
|
+
await context.init()
|
|
69
|
+
} finally {
|
|
70
|
+
for (const pool of Object.values(pg.clients ?? {})) {
|
|
71
|
+
if (!pools.includes(pool)) {
|
|
72
|
+
pools.push(pool)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return { context, pg }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const teardown = async (): Promise<void> => {
|
|
81
|
+
if (gate.skip) {
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
await raw(async pool => { await pool.query(`DROP SCHEMA IF EXISTS "${schema}" CASCADE`) })
|
|
85
|
+
.catch(error => console.error(`marketing-consent-postgres test teardown (${schema}):`, error))
|
|
86
|
+
while (pools.length > 0) {
|
|
87
|
+
await pools.pop()?.end().catch(() => undefined)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return { schema, boot, teardown }
|
|
92
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from 'bun:test'
|
|
2
|
+
import { RecordExists } from '@owlmeans/resource'
|
|
3
|
+
import type { PostgresResource } from '@owlmeans/postgres-resource'
|
|
4
|
+
import {
|
|
5
|
+
RES_MARKETING_CONSENT_LOG, RES_MARKETING_CONSENT_STATE
|
|
6
|
+
} from '@owlmeans/server-marketing-consent'
|
|
7
|
+
import type {
|
|
8
|
+
MarketingConsentLogRecord, MarketingConsentStateRecord
|
|
9
|
+
} from '@owlmeans/server-marketing-consent'
|
|
10
|
+
|
|
11
|
+
import { appendMarketingConsentPostgres } from '../src/index.js'
|
|
12
|
+
import type { Booted } from './context.js'
|
|
13
|
+
import { gate, makeSuite } from './context.js'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Env-gated against a real Postgres — skips cleanly (never fails) when `POSTGRES_URL` isn't
|
|
17
|
+
* reachable, the same gate `@owlmeans/postgres-resource`'s own integration suites use.
|
|
18
|
+
*/
|
|
19
|
+
const suite = makeSuite('resource')
|
|
20
|
+
const it = gate.skip ? test.skip : test
|
|
21
|
+
|
|
22
|
+
describe('@owlmeans/marketing-consent-postgres — resources against a real context', () => {
|
|
23
|
+
if (gate.skip) {
|
|
24
|
+
test.skip(gate.reason ?? 'postgres gate closed', () => {})
|
|
25
|
+
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let booted: Booted
|
|
30
|
+
let state: PostgresResource<MarketingConsentStateRecord>
|
|
31
|
+
let log: PostgresResource<MarketingConsentLogRecord>
|
|
32
|
+
|
|
33
|
+
beforeAll(async () => {
|
|
34
|
+
booted = await suite.boot()
|
|
35
|
+
state = booted.context.resource<PostgresResource<MarketingConsentStateRecord>>(
|
|
36
|
+
RES_MARKETING_CONSENT_STATE
|
|
37
|
+
)
|
|
38
|
+
log = booted.context.resource<PostgresResource<MarketingConsentLogRecord>>(
|
|
39
|
+
RES_MARKETING_CONSENT_LOG
|
|
40
|
+
)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
afterAll(async () => {
|
|
44
|
+
await suite.teardown()
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('reconciles both tables at boot without error', () => {
|
|
48
|
+
/** `beforeAll` already drove `context.init()` — reaching here at all is the assertion. */
|
|
49
|
+
expect(state.table.qualified).toBe(`"${suite.schema}"."marketing_consent_state"`)
|
|
50
|
+
expect(log.table.qualified).toBe(`"${suite.schema}"."marketing_consent_log"`)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('stores a dotted decision key verbatim and reads it back', async () => {
|
|
54
|
+
const record = await state.create({
|
|
55
|
+
subject: 'ent-1|user-1|',
|
|
56
|
+
userId: 'user-1',
|
|
57
|
+
entityId: 'ent-1',
|
|
58
|
+
decisions: [
|
|
59
|
+
{
|
|
60
|
+
key: 'marketing.email', granted: true, revisedAt: '2026-01-01T00:00:00.000Z',
|
|
61
|
+
mode: 'opt-in', decidedAt: '2026-01-01T00:00:00.000Z', source: 'settings'
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
key: 'trackers.advertising', granted: false, revisedAt: '2026-01-01T00:00:00.000Z',
|
|
65
|
+
mode: 'opt-out', decidedAt: '2026-01-01T00:00:00.000Z', source: 'settings'
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
createdAt: '2026-01-01T00:00:00.000Z',
|
|
69
|
+
updatedAt: '2026-01-01T00:00:00.000Z'
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
const loaded = await state.get(record.id as string)
|
|
73
|
+
/** Verbatim array, not folded into an object keyed by the dotted consent key. */
|
|
74
|
+
expect(loaded.decisions).toEqual([
|
|
75
|
+
{
|
|
76
|
+
key: 'marketing.email', granted: true, revisedAt: '2026-01-01T00:00:00.000Z',
|
|
77
|
+
mode: 'opt-in', decidedAt: '2026-01-01T00:00:00.000Z', source: 'settings'
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
key: 'trackers.advertising', granted: false, revisedAt: '2026-01-01T00:00:00.000Z',
|
|
81
|
+
mode: 'opt-out', decidedAt: '2026-01-01T00:00:00.000Z', source: 'settings'
|
|
82
|
+
}
|
|
83
|
+
])
|
|
84
|
+
expect(Array.isArray(loaded.decisions)).toBe(true)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('rejects a duplicate subject through the unique index', async () => {
|
|
88
|
+
await state.create({
|
|
89
|
+
subject: 'ent-2|user-2|', userId: 'user-2', decisions: [],
|
|
90
|
+
createdAt: '2026-01-01T00:00:00.000Z', updatedAt: '2026-01-01T00:00:00.000Z'
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
await expect(state.create({
|
|
94
|
+
subject: 'ent-2|user-2|', userId: 'user-2', decisions: [],
|
|
95
|
+
createdAt: '2026-01-02T00:00:00.000Z', updatedAt: '2026-01-02T00:00:00.000Z'
|
|
96
|
+
})).rejects.toThrow(RecordExists)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('appends a log row untouched by state writes', async () => {
|
|
100
|
+
const row = await log.create({
|
|
101
|
+
subject: 'ent-3|user-3|', userId: 'user-3', kind: 'consent', key: 'marketing.email',
|
|
102
|
+
granted: true, mode: 'opt-in', decidedAt: '2026-01-01T00:00:00.000Z', source: 'settings'
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
expect(await log.get(row.id as string)).toMatchObject({ key: 'marketing.email', granted: true })
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('is a no-op the second time it registers on the same context', () => {
|
|
109
|
+
const before = booted.context.resource<PostgresResource<MarketingConsentStateRecord>>(
|
|
110
|
+
RES_MARKETING_CONSENT_STATE
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
appendMarketingConsentPostgres(booted.context)
|
|
114
|
+
|
|
115
|
+
const after = booted.context.resource<PostgresResource<MarketingConsentStateRecord>>(
|
|
116
|
+
RES_MARKETING_CONSENT_STATE
|
|
117
|
+
)
|
|
118
|
+
/** Same registered instance — the second call never re-registered a resource. */
|
|
119
|
+
expect(after).toBe(before)
|
|
120
|
+
})
|
|
121
|
+
})
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import { getDeclaration, resetDeclarations, schemaToTableSpec } from '@owlmeans/postgres-resource'
|
|
3
|
+
import {
|
|
4
|
+
MarketingConsentLogSchema, MarketingConsentStateSchema,
|
|
5
|
+
RES_MARKETING_CONSENT_LOG, RES_MARKETING_CONSENT_STATE
|
|
6
|
+
} from '@owlmeans/server-marketing-consent'
|
|
7
|
+
|
|
8
|
+
import { makeMarketingConsentLogPostgres, makeMarketingConsentStatePostgres } from '../src/resource.js'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* No live database needed — `schemaToTableSpec` is the same compiler `init()` runs against a
|
|
12
|
+
* real connection, so this is the one test that would have caught the CRITICAL risk this
|
|
13
|
+
* package exists to avoid: `decisions`/`terms` silently expanding into per-field columns or a
|
|
14
|
+
* child table instead of staying one opaque `jsonb` blob each.
|
|
15
|
+
*/
|
|
16
|
+
describe('@owlmeans/marketing-consent-postgres — schema shape', () => {
|
|
17
|
+
test('the maker assigns the imported schema, not a copy', () => {
|
|
18
|
+
resetDeclarations(RES_MARKETING_CONSENT_STATE)
|
|
19
|
+
resetDeclarations(RES_MARKETING_CONSENT_LOG)
|
|
20
|
+
|
|
21
|
+
const state = makeMarketingConsentStatePostgres()
|
|
22
|
+
const log = makeMarketingConsentLogPostgres()
|
|
23
|
+
|
|
24
|
+
expect(state.schema).toBe(MarketingConsentStateSchema)
|
|
25
|
+
expect(log.schema).toBe(MarketingConsentLogSchema)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('decisions and terms compile to one jsonb column each, never a child table', () => {
|
|
29
|
+
const spec = schemaToTableSpec(
|
|
30
|
+
RES_MARKETING_CONSENT_STATE, MarketingConsentStateSchema, 'app', 'marketing_consent_state', true
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
const decisions = spec.byProperty.decisions
|
|
34
|
+
const terms = spec.byProperty.terms
|
|
35
|
+
|
|
36
|
+
expect(decisions.jsonb).toBe(true)
|
|
37
|
+
expect(decisions.sqlType).toBe('jsonb')
|
|
38
|
+
/** Not a native Postgres array column — the whole array is one opaque jsonb value. */
|
|
39
|
+
expect(decisions.array).toBe(false)
|
|
40
|
+
|
|
41
|
+
expect(terms.jsonb).toBe(true)
|
|
42
|
+
expect(terms.sqlType).toBe('jsonb')
|
|
43
|
+
expect(terms.array).toBe(false)
|
|
44
|
+
|
|
45
|
+
/** Exactly the schema's own top-level properties — nothing expanded into extra columns. */
|
|
46
|
+
expect(spec.columns.map(column => column.property).sort()).toEqual([
|
|
47
|
+
'createdAt', 'decisions', 'entityId', 'gpc', 'id', 'profileId', 'subject', 'terms',
|
|
48
|
+
'updatedAt', 'userId'
|
|
49
|
+
])
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
test('the log table\'s documents/notices are also single jsonb columns', () => {
|
|
53
|
+
const spec = schemaToTableSpec(
|
|
54
|
+
RES_MARKETING_CONSENT_LOG, MarketingConsentLogSchema, 'app', 'marketing_consent_log', true
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
expect(spec.byProperty.documents.jsonb).toBe(true)
|
|
58
|
+
expect(spec.byProperty.documents.array).toBe(false)
|
|
59
|
+
expect(spec.byProperty.notices.jsonb).toBe(true)
|
|
60
|
+
expect(spec.byProperty.notices.array).toBe(false)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test('the makers declare the indexes the API sketch promises', () => {
|
|
64
|
+
resetDeclarations(RES_MARKETING_CONSENT_STATE)
|
|
65
|
+
resetDeclarations(RES_MARKETING_CONSENT_LOG)
|
|
66
|
+
makeMarketingConsentStatePostgres()
|
|
67
|
+
makeMarketingConsentLogPostgres()
|
|
68
|
+
|
|
69
|
+
const stateIndexes = getDeclaration(RES_MARKETING_CONSENT_STATE).indexes
|
|
70
|
+
expect(stateIndexes).toContainEqual(
|
|
71
|
+
expect.objectContaining({ name: 'idx_mc_state_subject', columns: ['subject'], unique: true })
|
|
72
|
+
)
|
|
73
|
+
expect(stateIndexes).toContainEqual(
|
|
74
|
+
expect.objectContaining({ name: 'idx_mc_state_user', columns: ['userId'] })
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
const logIndexes = getDeclaration(RES_MARKETING_CONSENT_LOG).indexes
|
|
78
|
+
expect(logIndexes).toContainEqual(
|
|
79
|
+
expect.objectContaining({ name: 'idx_mc_log_subject_key', columns: ['subject', 'key', 'decidedAt'] })
|
|
80
|
+
)
|
|
81
|
+
expect(logIndexes).toContainEqual(
|
|
82
|
+
expect.objectContaining({ name: 'idx_mc_log_user', columns: ['userId', 'decidedAt'] })
|
|
83
|
+
)
|
|
84
|
+
})
|
|
85
|
+
})
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": [
|
|
3
|
+
"@owlmeans/dep-config/tsconfig.base.json",
|
|
4
|
+
"@owlmeans/dep-config/tsconfig.node.json"
|
|
5
|
+
],
|
|
6
|
+
"compilerOptions": {
|
|
7
|
+
"rootDir": "./src/",
|
|
8
|
+
"outDir": "./build/"
|
|
9
|
+
},
|
|
10
|
+
"exclude": ["./dist/**/*", "./build/**/*", "./tests/**/*", "./*.ts"]
|
|
11
|
+
}
|