@jskit-ai/rewarded-core 0.1.120

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 ADDED
@@ -0,0 +1,149 @@
1
+ # @jskit-ai/rewarded-core
2
+
3
+ Server runtime for Rewarded unlock gates.
4
+
5
+ ## Package Shape
6
+
7
+ This package installs:
8
+
9
+ - four CRUD-owned server providers, one per persisted table
10
+ - one workflow provider for the rewarded gate API
11
+
12
+ The persisted tables are:
13
+
14
+ - `rewarded_rules`
15
+ - `rewarded_provider_configs`
16
+ - `rewarded_watch_sessions`
17
+ - `rewarded_unlock_receipts`
18
+
19
+ The package keeps the CRUD ownership strict:
20
+
21
+ - rules and provider configs are `workspace`-owned
22
+ - watch sessions and unlock receipts are `workspace_user`-owned
23
+
24
+ Every owned row carries direct owner columns. The module does not rely on inherited ownership through parent joins.
25
+
26
+ ## What It Does
27
+
28
+ The workflow provider exposes four workspace-scoped app routes:
29
+
30
+ - `GET /api/w/:workspaceSlug/rewarded/current`
31
+ - `POST /api/w/:workspaceSlug/rewarded/start`
32
+ - `POST /api/w/:workspaceSlug/rewarded/grant`
33
+ - `POST /api/w/:workspaceSlug/rewarded/close`
34
+
35
+ These are plain workflow endpoints, not CRUD JSON:API endpoints.
36
+
37
+ The flow is:
38
+
39
+ 1. `current` decides whether the gate is enabled, blocked, or already unlocked.
40
+ 2. `start` creates a watch session when a reward is required.
41
+ 3. `grant` marks the session rewarded and creates an unlock receipt.
42
+ 4. `close` closes a started session without granting access.
43
+
44
+ Day 0 is intentionally app-surface-only for the rewarded workflow. Rules and provider configs should therefore use `surface = "app"` for the active gate rows.
45
+
46
+ ## Required Data
47
+
48
+ Day-0 configuration lives in the CRUD-owned tables.
49
+
50
+ At minimum, apps need:
51
+
52
+ - a `rewarded_rules` row for the target `gateKey`
53
+ - a matching enabled `rewarded_provider_configs` row for the surface
54
+
55
+ Important config fields:
56
+
57
+ - `rewarded_rules.gate_key`
58
+ - `rewarded_rules.surface`
59
+ - `rewarded_rules.unlock_minutes`
60
+ - `rewarded_rules.cooldown_minutes`
61
+ - `rewarded_rules.daily_limit`
62
+ - `rewarded_provider_configs.surface`
63
+ - `rewarded_provider_configs.placement`
64
+ - `rewarded_provider_configs.provider`
65
+
66
+ The application selects `provider` and `placement` for its delivery adapter.
67
+ The workflow does not interpret them or choose a default provider. For the
68
+ Google application pattern use `provider = "google-publisher-tag"` and the ad unit
69
+ path as `placement`.
70
+
71
+ ## Protecting Server Features
72
+
73
+ Protected server mutations should use the exported helper:
74
+
75
+ ```js
76
+ import { requireRewardedUnlock } from "@jskit-ai/rewarded-core/server/requireRewardedUnlock";
77
+ ```
78
+
79
+ The dedicated manual page is:
80
+
81
+ - [docs/protecting-server-actions.md](docs/protecting-server-actions.md)
82
+
83
+ That page shows the exact service and provider wiring pattern.
84
+
85
+ ## Grant authorization
86
+
87
+ The application must supply capability `rewarded.grant-policy` with an
88
+ `authorizeGrant({ session, context, trx })` function. Direct service composition
89
+ supplies the same function as `authorizeGrant`. Missing policy fails at setup;
90
+ only the literal result `true` permits a grant. False, absent or other results
91
+ reject it before any watch-session update or receipt creation.
92
+
93
+ The workflow loads the session through its owned repository, then passes that
94
+ stored session, the server request context and the current transaction to policy.
95
+ It never accepts a client approval flag or replacement gate identity. The policy
96
+ runs for repeated grant requests too. Keep policy local and bounded within the
97
+ transaction; use the application's trusted eligibility or verification records.
98
+ A policy exception propagates as failure rather than silently allowing a grant.
99
+
100
+ The Google browser pattern supplies delivery only. Its callbacks are not a
101
+ server authorization implementation. The application must decide which reward
102
+ claims it accepts; the library supplies neither provider-proof verification nor
103
+ a permissive default.
104
+
105
+ ## Policy Model
106
+
107
+ This module is designed for rewarded unlocks, not for blocking all normal app use on boot.
108
+
109
+ Recommended usage:
110
+
111
+ - unlock bonus actions
112
+ - unlock extra quota
113
+ - unlock a temporary feature window
114
+
115
+ Do not treat it as a hard requirement for baseline product use unless the ad provider policy clearly allows that.
116
+
117
+ ## Install Notes
118
+
119
+ The package declares its four initial schema migrations under `migrations/`.
120
+ Run them through the application's existing database migration operation.
121
+
122
+ The package does not add day-0 settings pages automatically. Configuration can stay manual or be layered with app-specific UI later.
123
+
124
+ ## Manual V0 migration
125
+
126
+ Replace `@jskit-ai/google-rewarded-core` with `@jskit-ai/rewarded-core` and
127
+ `@jskit-ai/google-rewarded-web` with `@jskit-ai/rewarded-web`. Update imports
128
+ (`requireRewardedUnlock`, `createRewardedRuntime`, `useRewardedRuntime`),
129
+ `Rewarded*` provider names, `rewarded.*` server capabilities, `client.rewarded`
130
+ and `/api/w/:workspaceSlug/rewarded/*` routes. Register application-owned
131
+ `client.rewarded-delivery` and `rewarded.grant-policy` providers as described
132
+ in these packages. There is no implicit grant approval for older apps.
133
+
134
+ For disposable prerelease data, recreate the four tables through the new
135
+ migrations. If retaining data, stop writers, back up the database and manually
136
+ rename each `google_rewarded_*` table to `rewarded_*`, preserving IDs, owner
137
+ columns and foreign-key relationships. In the provider configuration table,
138
+ rename `ad_unit_path` to `placement` and replace `script_mode` with required
139
+ `provider` values chosen for the application; existing GPT rows use
140
+ `google-publisher-tag`. Remove the old column/default. Reconcile the application's
141
+ migration records so initial table creation is not replayed against retained
142
+ schema; verify indexes, constraints and ownership with its database tools before
143
+ resuming writers. Decide explicitly whether to retain active watch sessions and
144
+ unlock receipts. No library migration shim, alias, old-table reader or dual write
145
+ performs this conversion.
146
+
147
+ The package manifest lists application-owned inputs in both `capabilities.requires`
148
+ and `capabilities.applicationRequires`. The latter identifies who supplies them;
149
+ it does not make the runtime dependency optional or provide a default.
@@ -0,0 +1,190 @@
1
+ # Protecting Server Actions With `requireRewardedUnlock()`
2
+
3
+ This is the manual page for the server helper exported by:
4
+
5
+ ```js
6
+ @jskit-ai/rewarded-core/server/requireRewardedUnlock
7
+ ```
8
+
9
+ Use this helper when a feature is:
10
+
11
+ - allowed in principle for the actor
12
+ - but temporarily gated behind a rewarded unlock
13
+
14
+ Do not use it as a permission system.
15
+
16
+ The correct split is:
17
+
18
+ - permissions decide whether the actor may ever do the thing
19
+ - rewarded gates decide whether the actor must unlock the thing right now
20
+
21
+ ## What The Helper Does
22
+
23
+ `requireRewardedUnlock()` calls `rewarded.core.service.getCurrentState(...)` and then enforces one simple rule:
24
+
25
+ - if the gate is already unlocked, it returns the gate state
26
+ - if the gate is not configured and `requireConfigured` is not enabled, it returns the gate state
27
+ - otherwise it throws an `AppError`
28
+
29
+ This means the helper is a truth-enforcement seam for protected server operations.
30
+
31
+ The UI should still call the web runtime first for user experience, but the protected server operation must also check the gate so users cannot bypass it by calling the endpoint directly.
32
+
33
+ ## Import Path
34
+
35
+ ```js
36
+ import { requireRewardedUnlock } from "@jskit-ai/rewarded-core/server/requireRewardedUnlock";
37
+ ```
38
+
39
+ ## Basic Service Pattern
40
+
41
+ Inject `rewarded.core.service` into your own feature service, then call the helper before the protected mutation.
42
+
43
+ Example:
44
+
45
+ ```js
46
+ import { requireRewardedUnlock } from "@jskit-ai/rewarded-core/server/requireRewardedUnlock";
47
+
48
+ function createProgressLoggingService({
49
+ rewardedService,
50
+ workoutLogRepository
51
+ } = {}) {
52
+ async function logProgress(input = {}, options = {}) {
53
+ await requireRewardedUnlock(
54
+ rewardedService,
55
+ {
56
+ gateKey: "progress-logging",
57
+ workspaceSlug: input.workspaceSlug
58
+ },
59
+ {
60
+ context: options.context,
61
+ errorMessage: "Watch a rewarded ad before logging progress."
62
+ }
63
+ );
64
+
65
+ return workoutLogRepository.create(input, options);
66
+ }
67
+
68
+ return Object.freeze({
69
+ logProgress
70
+ });
71
+ }
72
+ ```
73
+
74
+ ## Provider Wiring Pattern
75
+
76
+ Inject the rewarded service from the container the same way as any other JSKIT runtime service.
77
+
78
+ Example:
79
+
80
+ ```js
81
+ app.service(
82
+ "convict.progress-logging.service",
83
+ (scope) => createProgressLoggingService({
84
+ rewardedService: scope.make("rewarded.core.service"),
85
+ workoutLogRepository: scope.make("convict.workout-log.repository")
86
+ })
87
+ );
88
+ ```
89
+
90
+ ## Exact Helper Signature
91
+
92
+ ```js
93
+ await requireRewardedUnlock(
94
+ rewardedService,
95
+ {
96
+ gateKey,
97
+ workspaceSlug
98
+ },
99
+ {
100
+ context,
101
+ requireConfigured,
102
+ errorCode,
103
+ errorMessage
104
+ }
105
+ );
106
+ ```
107
+
108
+ Arguments:
109
+
110
+ - `rewardedService`
111
+ - must expose `getCurrentState(input, { context })`
112
+ - first object
113
+ - `gateKey`: required gate identifier
114
+ - `workspaceSlug`: required for the current workspace gate
115
+ - second object
116
+ - `context`: the normal JSKIT action/service context
117
+ - `requireConfigured`: fail closed when no rule/provider config exists
118
+ - `errorCode`: optional override for the thrown `AppError.code`
119
+ - `errorMessage`: optional override for the thrown `AppError.message`
120
+
121
+ ## Default Behavior
122
+
123
+ The helper is intentionally permissive when the rewarded gate is not configured.
124
+
125
+ By default:
126
+
127
+ - `reason = "rule-not-configured"` passes
128
+ - `reason = "provider-not-configured"` passes
129
+
130
+ Why:
131
+
132
+ - day-0 apps may install the package before creating live rules/config rows
133
+ - you do not want every protected feature to break during setup or partial rollout
134
+
135
+ If you want the protected operation to fail closed until configuration exists, set:
136
+
137
+ ```js
138
+ requireConfigured: true
139
+ ```
140
+
141
+ ## When The Helper Throws
142
+
143
+ The helper throws `AppError` with `details.rewardedGate` containing the full gate state.
144
+
145
+ Important built-in failure cases:
146
+
147
+ - `reward-required`
148
+ - status: `423`
149
+ - code: `rewarded_unlock_required`
150
+ - `cooldown-active`
151
+ - status: `423`
152
+ - code: `rewarded_cooldown_active`
153
+ - `daily-limit-reached`
154
+ - status: `423`
155
+ - code: `rewarded_daily_limit_reached`
156
+ - `rule-not-configured` or `provider-not-configured` with `requireConfigured: true`
157
+ - status: `503`
158
+ - code: `rewarded_not_configured`
159
+
160
+ ## What The Helper Returns
161
+
162
+ On success, the helper returns the gate state from `rewardedService.getCurrentState(...)`.
163
+
164
+ That lets your service inspect the unlock window if it wants to log or branch on it, for example:
165
+
166
+ ```js
167
+ const gateState = await requireRewardedUnlock(...);
168
+ const unlockedUntil = gateState.unlock?.unlockedUntil || null;
169
+ ```
170
+
171
+ ## Recommended Calling Order
172
+
173
+ For a protected feature operation:
174
+
175
+ 1. run normal permission / ownership checks
176
+ 2. run `requireRewardedUnlock(...)`
177
+ 3. perform the real mutation
178
+
179
+ Do not invert that order.
180
+
181
+ ## What Not To Do
182
+
183
+ Do not:
184
+
185
+ - model rewarded gates as permissions
186
+ - skip the server check because the client already opened the gate
187
+ - rely on route visibility as the rewarded gate
188
+ - duplicate the raw `reason` checks in every feature service
189
+
190
+ Use the helper instead of open-coding that logic.
@@ -0,0 +1,27 @@
1
+ const TABLE_NAME = "rewarded_provider_configs";
2
+
3
+ exports.up = async function up(knex) {
4
+ const hasCrudTable = await knex.schema.hasTable(TABLE_NAME);
5
+ if (hasCrudTable) {
6
+ return;
7
+ }
8
+
9
+ await knex.schema.createTable(TABLE_NAME, (table) => {
10
+ table.bigIncrements("id").unsigned().primary();
11
+ table.bigInteger("workspace_id").unsigned().notNullable();
12
+ table.string("surface", 64).notNullable();
13
+ table.boolean("enabled").notNullable().defaultTo(true);
14
+ table.string("placement", 255).notNullable();
15
+ table.string("provider", 64).notNullable();
16
+ table.timestamp("created_at").notNullable().defaultTo(knex.fn.now());
17
+ table.timestamp("updated_at").notNullable().defaultTo(knex.raw("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"));
18
+ table.index(["workspace_id"], "idx_rewarded_provider_configs_workspace");
19
+ table.unique(["workspace_id","surface"], "uq_rewarded_provider_configs_workspace_surface");
20
+ table.foreign(["workspace_id"], "fk_rewarded_provider_configs_workspace").references(["id"]).inTable("workspaces").onUpdate("RESTRICT").onDelete("CASCADE");
21
+ });
22
+
23
+ };
24
+
25
+ exports.down = async function down(knex) {
26
+ await knex.schema.dropTableIfExists(TABLE_NAME);
27
+ };
@@ -0,0 +1,31 @@
1
+ const TABLE_NAME = "rewarded_rules";
2
+
3
+ exports.up = async function up(knex) {
4
+ const hasCrudTable = await knex.schema.hasTable(TABLE_NAME);
5
+ if (hasCrudTable) {
6
+ return;
7
+ }
8
+
9
+ await knex.schema.createTable(TABLE_NAME, (table) => {
10
+ table.bigIncrements("id").unsigned().primary();
11
+ table.bigInteger("workspace_id").unsigned().notNullable();
12
+ table.string("gate_key", 120).notNullable();
13
+ table.string("surface", 64).notNullable();
14
+ table.boolean("enabled").notNullable().defaultTo(true);
15
+ table.integer("unlock_minutes").unsigned().notNullable().defaultTo(30);
16
+ table.integer("cooldown_minutes").unsigned().notNullable().defaultTo(0);
17
+ table.integer("daily_limit").unsigned().nullable();
18
+ table.string("title", 160).notNullable().defaultTo("");
19
+ table.text("description").nullable();
20
+ table.timestamp("created_at").notNullable().defaultTo(knex.fn.now());
21
+ table.timestamp("updated_at").notNullable().defaultTo(knex.raw("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"));
22
+ table.index(["workspace_id"], "idx_rewarded_rules_workspace");
23
+ table.unique(["workspace_id","gate_key"], "uq_rewarded_rules_workspace_gate");
24
+ table.foreign(["workspace_id"], "fk_rewarded_rules_workspace").references(["id"]).inTable("workspaces").onUpdate("RESTRICT").onDelete("CASCADE");
25
+ });
26
+
27
+ };
28
+
29
+ exports.down = async function down(knex) {
30
+ await knex.schema.dropTableIfExists(TABLE_NAME);
31
+ };
@@ -0,0 +1,35 @@
1
+ const TABLE_NAME = "rewarded_unlock_receipts";
2
+
3
+ exports.up = async function up(knex) {
4
+ const hasCrudTable = await knex.schema.hasTable(TABLE_NAME);
5
+ if (hasCrudTable) {
6
+ return;
7
+ }
8
+
9
+ await knex.schema.createTable(TABLE_NAME, (table) => {
10
+ table.bigIncrements("id").unsigned().primary();
11
+ table.bigInteger("workspace_id").unsigned().notNullable();
12
+ table.bigInteger("user_id").unsigned().notNullable();
13
+ table.string("gate_key", 120).notNullable();
14
+ table.bigInteger("provider_config_id").unsigned().nullable();
15
+ table.bigInteger("watch_session_id").unsigned().nullable();
16
+ table.timestamp("granted_at").notNullable().defaultTo(knex.fn.now());
17
+ table.timestamp("unlocked_until").notNullable();
18
+ table.timestamp("created_at").notNullable().defaultTo(knex.fn.now());
19
+ table.index(["user_id"], "fk_rewarded_unlock_receipts_user");
20
+ table.index(["gate_key"], "idx_rewarded_unlock_receipts_gate");
21
+ table.index(["provider_config_id"], "idx_rewarded_unlock_receipts_provider_config");
22
+ table.index(["unlocked_until"], "idx_rewarded_unlock_receipts_unlocked_until");
23
+ table.index(["watch_session_id"], "idx_rewarded_unlock_receipts_watch_session");
24
+ table.index(["workspace_id","user_id"], "idx_rewarded_unlock_receipts_workspace_user");
25
+ table.foreign(["provider_config_id"], "fk_rewarded_unlock_receipts_provider_config").references(["id"]).inTable("rewarded_provider_configs").onUpdate("RESTRICT").onDelete("SET NULL");
26
+ table.foreign(["user_id"], "fk_rewarded_unlock_receipts_user").references(["id"]).inTable("users").onUpdate("RESTRICT").onDelete("CASCADE");
27
+ table.foreign(["watch_session_id"], "fk_rewarded_unlock_receipts_watch_session").references(["id"]).inTable("rewarded_watch_sessions").onUpdate("RESTRICT").onDelete("SET NULL");
28
+ table.foreign(["workspace_id"], "fk_rewarded_unlock_receipts_workspace").references(["id"]).inTable("workspaces").onUpdate("RESTRICT").onDelete("CASCADE");
29
+ });
30
+
31
+ };
32
+
33
+ exports.down = async function down(knex) {
34
+ await knex.schema.dropTableIfExists(TABLE_NAME);
35
+ };
@@ -0,0 +1,36 @@
1
+ const TABLE_NAME = "rewarded_watch_sessions";
2
+
3
+ exports.up = async function up(knex) {
4
+ const hasCrudTable = await knex.schema.hasTable(TABLE_NAME);
5
+ if (hasCrudTable) {
6
+ return;
7
+ }
8
+
9
+ await knex.schema.createTable(TABLE_NAME, (table) => {
10
+ table.bigIncrements("id").unsigned().primary();
11
+ table.bigInteger("workspace_id").unsigned().notNullable();
12
+ table.bigInteger("user_id").unsigned().notNullable();
13
+ table.string("gate_key", 120).notNullable();
14
+ table.bigInteger("provider_config_id").unsigned().nullable();
15
+ table.string("status", 32).notNullable().defaultTo("started");
16
+ table.timestamp("started_at").notNullable().defaultTo(knex.fn.now());
17
+ table.timestamp("rewarded_at").nullable();
18
+ table.timestamp("completed_at").nullable();
19
+ table.timestamp("closed_at").nullable();
20
+ table.timestamp("created_at").notNullable().defaultTo(knex.fn.now());
21
+ table.timestamp("updated_at").notNullable().defaultTo(knex.raw("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"));
22
+ table.index(["user_id"], "fk_rewarded_watch_sessions_user");
23
+ table.index(["gate_key"], "idx_rewarded_watch_sessions_gate");
24
+ table.index(["provider_config_id"], "idx_rewarded_watch_sessions_provider_config");
25
+ table.index(["status"], "idx_rewarded_watch_sessions_status");
26
+ table.index(["workspace_id","user_id"], "idx_rewarded_watch_sessions_workspace_user");
27
+ table.foreign(["provider_config_id"], "fk_rewarded_watch_sessions_provider_config").references(["id"]).inTable("rewarded_provider_configs").onUpdate("RESTRICT").onDelete("SET NULL");
28
+ table.foreign(["user_id"], "fk_rewarded_watch_sessions_user").references(["id"]).inTable("users").onUpdate("RESTRICT").onDelete("CASCADE");
29
+ table.foreign(["workspace_id"], "fk_rewarded_watch_sessions_workspace").references(["id"]).inTable("workspaces").onUpdate("RESTRICT").onDelete("CASCADE");
30
+ });
31
+
32
+ };
33
+
34
+ exports.down = async function down(knex) {
35
+ await knex.schema.dropTableIfExists(TABLE_NAME);
36
+ };
package/package.json ADDED
@@ -0,0 +1,130 @@
1
+ {
2
+ "name": "@jskit-ai/rewarded-core",
3
+ "version": "0.1.120",
4
+ "type": "module",
5
+ "exports": {
6
+ "./shared": "./src/shared/index.js",
7
+ "./server/actions": "./src/server/actions.js",
8
+ "./server/requireRewardedUnlock": "./src/server/support/requireRewardedUnlock.js"
9
+ },
10
+ "description": "Rewarded workflow runtime plus internal CRUD providers for rules, provider configs, watch sessions, and unlock receipts.",
11
+ "jskit": {
12
+ "kind": "runtime",
13
+ "capabilities": {
14
+ "provides": [
15
+ "rewarded.rules",
16
+ "rewarded.provider-configs",
17
+ "rewarded.watch-sessions",
18
+ "rewarded.unlock-receipts",
19
+ "rewarded.core"
20
+ ],
21
+ "requires": [
22
+ "runtime.actions",
23
+ "runtime.database",
24
+ "runtime.http",
25
+ "runtime.json-rest-api",
26
+ "rewarded.grant-policy"
27
+ ],
28
+ "applicationRequires": [
29
+ "rewarded.grant-policy"
30
+ ]
31
+ },
32
+ "runtime": {
33
+ "server": {
34
+ "providers": [
35
+ {
36
+ "entrypoint": "src/server/RewardedResources.js",
37
+ "export": "RewardedRulesFeature"
38
+ },
39
+ {
40
+ "entrypoint": "src/server/RewardedResources.js",
41
+ "export": "RewardedProviderConfigsFeature"
42
+ },
43
+ {
44
+ "entrypoint": "src/server/RewardedResources.js",
45
+ "export": "RewardedWatchSessionsFeature"
46
+ },
47
+ {
48
+ "entrypoint": "src/server/RewardedResources.js",
49
+ "export": "RewardedUnlockReceiptsFeature"
50
+ },
51
+ {
52
+ "entrypoint": "src/server/RewardedCoreProvider.js",
53
+ "export": "RewardedCoreFeature"
54
+ }
55
+ ]
56
+ },
57
+ "client": {
58
+ "providers": []
59
+ }
60
+ },
61
+ "metadata": {
62
+ "jskit": {
63
+ "tableOwnership": {
64
+ "tables": [
65
+ {
66
+ "tableName": "rewarded_rules",
67
+ "providerEntrypoint": "src/server/RewardedResources.js",
68
+ "ownershipFilter": "workspace"
69
+ },
70
+ {
71
+ "tableName": "rewarded_provider_configs",
72
+ "providerEntrypoint": "src/server/RewardedResources.js",
73
+ "ownershipFilter": "workspace"
74
+ },
75
+ {
76
+ "tableName": "rewarded_watch_sessions",
77
+ "providerEntrypoint": "src/server/RewardedResources.js",
78
+ "ownershipFilter": "workspace_user"
79
+ },
80
+ {
81
+ "tableName": "rewarded_unlock_receipts",
82
+ "providerEntrypoint": "src/server/RewardedResources.js",
83
+ "ownershipFilter": "workspace_user"
84
+ }
85
+ ]
86
+ }
87
+ },
88
+ "apiSummary": {
89
+ "surfaces": [
90
+ {
91
+ "subpath": "./shared",
92
+ "summary": "Exports Rewarded shared CRUD resources."
93
+ },
94
+ {
95
+ "subpath": "./server/actions",
96
+ "summary": "Exports Rewarded workflow action identifiers and validators."
97
+ },
98
+ {
99
+ "subpath": "./server/requireRewardedUnlock",
100
+ "summary": "Exports the server helper that enforces a rewarded unlock before a protected feature action continues."
101
+ }
102
+ ],
103
+ "capabilities": [
104
+ "rewarded.rules",
105
+ "rewarded.provider-configs",
106
+ "rewarded.watch-sessions",
107
+ "rewarded.unlock-receipts",
108
+ "rewarded.core"
109
+ ]
110
+ }
111
+ },
112
+ "migrations": {
113
+ "directories": [
114
+ "migrations"
115
+ ]
116
+ }
117
+ },
118
+ "dependencies": {
119
+ "@jskit-ai/crud-core": "0.1.195",
120
+ "@jskit-ai/database-runtime": "0.1.184",
121
+ "@jskit-ai/json-rest-api-core": "0.1.128",
122
+ "@jskit-ai/resource-crud-core": "0.1.126",
123
+ "@jskit-ai/workspaces-core": "0.1.162"
124
+ },
125
+ "peerDependencies": {
126
+ "@jskit-ai/auth-core": "0.1.182",
127
+ "@jskit-ai/http-runtime": "0.1.182",
128
+ "@jskit-ai/kernel": "0.1.184"
129
+ }
130
+ }
@@ -0,0 +1,44 @@
1
+ import { defineFeature } from "@jskit-ai/kernel/server/features";
2
+
3
+ import { createService } from "./service.js";
4
+ import { createRewardedActions } from "./actions.js";
5
+ import { registerRoutes } from "./registerRoutes.js";
6
+
7
+ const RewardedCoreFeature = defineFeature({
8
+ id: "rewarded.core",
9
+ domain: "rewarded",
10
+ requires: {
11
+ http: "runtime.http",
12
+ grantPolicy: "rewarded.grant-policy",
13
+ providerConfigs: "rewarded.provider-configs",
14
+ rules: "rewarded.rules",
15
+ unlockReceipts: "rewarded.unlock-receipts",
16
+ watchSessions: "rewarded.watch-sessions"
17
+ },
18
+ provides: {
19
+ rewarded: "rewarded.core"
20
+ },
21
+ setup({ http, grantPolicy, providerConfigs, rules, unlockReceipts, watchSessions }) {
22
+ if (typeof grantPolicy.authorizeGrant !== "function") {
23
+ throw new TypeError("rewarded.grant-policy requires authorizeGrant.");
24
+ }
25
+ const rewarded = createService({
26
+ authorizeGrant: (input) => grantPolicy.authorizeGrant(input),
27
+ rewardedRulesRepository: rules.repository,
28
+ rewardedProviderConfigsRepository: providerConfigs.repository,
29
+ rewardedWatchSessionsRepository: watchSessions.repository,
30
+ rewardedUnlockReceiptsRepository: unlockReceipts.repository
31
+ });
32
+ registerRoutes(http.router, {
33
+ routeOwnershipFilter: "workspace_user",
34
+ routeSurface: "app",
35
+ routeRelativePath: "rewarded"
36
+ });
37
+ return { rewarded };
38
+ },
39
+ actions({ rewarded }) {
40
+ return createRewardedActions({ rewarded });
41
+ }
42
+ });
43
+
44
+ export { RewardedCoreFeature };