@voltro/plugin-saves 0.0.0-stage → 0.87.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.
@@ -0,0 +1,79 @@
1
+ import { SaveSummarySchema as e, savesError as t } from "./errors.js";
2
+ import { Schema as n } from "effect";
3
+ //#region src/contract.ts
4
+ var r = "_voltro_saves", i = "_voltro_saves_history", a = "_voltro_saves_requests", o = "_voltro_saves_subjects", s = n.Number.pipe(n.check(n.isInt()), n.check(n.isBetween({
5
+ minimum: 0,
6
+ maximum: 2147483647
7
+ }))), c = n.Number.pipe(n.check(n.isInt()), n.check(n.isBetween({
8
+ minimum: 0,
9
+ maximum: 864e13
10
+ }))), l = n.Struct({
11
+ slot: n.String,
12
+ version: s,
13
+ schemaVersion: s,
14
+ byteLength: s,
15
+ checksum: n.String,
16
+ deviceId: n.String,
17
+ clientVersion: n.String,
18
+ savedAt: c,
19
+ summary: e,
20
+ deletedUntil: n.NullOr(c)
21
+ }), u = n.Struct({
22
+ ...l.fields,
23
+ bytes: n.Uint8ArrayFromBase64
24
+ }), d = (e) => {
25
+ if (typeof e != "string" || e.length > 120 || !/^[A-Za-z0-9][A-Za-z0-9._:-]*$/.test(e)) throw t("invalid_input");
26
+ return e;
27
+ }, f = (e, n = 0, r = 2147483647) => {
28
+ if (typeof e != "number" || !Number.isSafeInteger(e) || e < n || e > r) throw t("invalid_input");
29
+ return e;
30
+ }, p = (e, n) => {
31
+ if (typeof e != "object" || !e || Array.isArray(e)) throw t("invalid_input");
32
+ let r = Object.getPrototypeOf(e);
33
+ if (r !== Object.prototype && r !== null) throw t("invalid_input");
34
+ let i = Object.create(null);
35
+ for (let r of Reflect.ownKeys(e)) {
36
+ let a = Object.getOwnPropertyDescriptor(e, r);
37
+ if (typeof r != "string" || !n.includes(r) || !a || !a.enumerable || !("value" in a)) throw t("invalid_input");
38
+ i[r] = a.value;
39
+ }
40
+ return Object.freeze(i);
41
+ }, m = (r) => {
42
+ if (typeof r != "object" || !r || Array.isArray(r)) throw t("invalid_input");
43
+ let i = Reflect.ownKeys(r);
44
+ if (i.length > 32 || i.some((e) => typeof e != "string" || !/^[A-Za-z][A-Za-z0-9._-]{0,63}$/.test(e))) throw t("invalid_input");
45
+ let a = p(r, i);
46
+ for (let e of Object.values(a)) if (!(e === null || typeof e == "boolean" || typeof e == "string" && e.length <= 512 || typeof e == "number" && Number.isFinite(e))) throw t("invalid_input");
47
+ return Object.freeze(n.decodeUnknownSync(e)(a));
48
+ }, h = (e, n) => {
49
+ let r = p(e, [
50
+ "slot",
51
+ "requestId",
52
+ "baseVersion",
53
+ "schemaVersion",
54
+ "bytes",
55
+ "deviceId",
56
+ "clientVersion",
57
+ "summary"
58
+ ]);
59
+ if (!(r.bytes instanceof Uint8Array) || r.bytes.byteLength > n) throw t("limit_exceeded");
60
+ return Object.freeze({
61
+ slot: d(r.slot),
62
+ requestId: d(r.requestId),
63
+ baseVersion: f(r.baseVersion),
64
+ schemaVersion: f(r.schemaVersion, 1),
65
+ bytes: Uint8Array.from(r.bytes),
66
+ deviceId: d(r.deviceId),
67
+ clientVersion: d(r.clientVersion),
68
+ summary: m(r.summary)
69
+ });
70
+ }, g = (e) => {
71
+ let n = structuredClone(e);
72
+ if (n.type !== "user") throw t("forbidden");
73
+ return Object.freeze({
74
+ tenantId: d(n.tenantId),
75
+ ownerId: d(n.id)
76
+ });
77
+ };
78
+ //#endregion
79
+ export { i as SAVE_HISTORY_TABLE, a as SAVE_REQUEST_TABLE, o as SAVE_SUBJECT_TABLE, r as SAVE_TABLE, u as SaveDocumentSchema, l as SaveHeaderSchema, h as captureSavePut, p as saveData, d as saveId, f as saveInteger, g as saveOwner, m as saveSummary };
@@ -0,0 +1,26 @@
1
+ import { Schema } from 'effect';
2
+ import { YieldableError } from 'effect/Cause';
3
+
4
+ export declare class SaveConflict extends SaveConflict_base {
5
+ }
6
+
7
+ declare const SaveConflict_base: Schema.Class<SaveConflict, Schema.TaggedStruct<"SaveConflict", {
8
+ readonly serverVersion: Schema.Number;
9
+ readonly server: Schema.$Record<Schema.String, Schema.Union<readonly [Schema.String, Schema.Number, Schema.Boolean, Schema.Null]>>;
10
+ readonly client: Schema.$Record<Schema.String, Schema.Union<readonly [Schema.String, Schema.Number, Schema.Boolean, Schema.Null]>>;
11
+ }>, YieldableError>;
12
+
13
+ export declare type SaveError = SavesError | SaveConflict;
14
+
15
+ export declare class SavesError extends SavesError_base {
16
+ }
17
+
18
+ export declare const savesError: (code: SavesError["code"]) => SavesError;
19
+
20
+ declare const SavesError_base: Schema.Class<SavesError, Schema.TaggedStruct<"SavesError", {
21
+ readonly code: Schema.Literals<readonly ["invalid_input", "invalid_record", "forbidden", "not_found", "erased", "limit_exceeded", "invalid_save", "rate_limited", "unavailable", "request_conflict"]>;
22
+ }>, YieldableError>;
23
+
24
+ export declare const SaveSummarySchema: Schema.$Record<Schema.String, Schema.Union<readonly [Schema.String, Schema.Number, Schema.Boolean, Schema.Null]>>;
25
+
26
+ export { }
package/dist/errors.js ADDED
@@ -0,0 +1,25 @@
1
+ import { Schema as e } from "effect";
2
+ //#region src/errors.ts
3
+ var t = e.Record(e.String, e.Union([
4
+ e.String,
5
+ e.Number,
6
+ e.Boolean,
7
+ e.Null
8
+ ])), n = class extends e.TaggedError()("SavesError", { code: e.Literals([
9
+ "invalid_input",
10
+ "invalid_record",
11
+ "forbidden",
12
+ "not_found",
13
+ "erased",
14
+ "limit_exceeded",
15
+ "invalid_save",
16
+ "rate_limited",
17
+ "unavailable",
18
+ "request_conflict"
19
+ ]) }) {}, r = class extends e.TaggedError()("SaveConflict", {
20
+ serverVersion: e.Number,
21
+ server: t,
22
+ client: t
23
+ }) {}, i = (e) => new n({ code: e });
24
+ //#endregion
25
+ export { r as SaveConflict, t as SaveSummarySchema, n as SavesError, i as savesError };
@@ -0,0 +1,166 @@
1
+ import { ConnectionRef } from '@voltro/kv/connection';
2
+ import { Context } from 'effect';
3
+ import { Effect } from 'effect';
4
+ import { HotStoreShape } from '@voltro/kv/hot';
5
+ import { PluginPrivacyTarget } from '@voltro/protocol';
6
+ import { Schema } from 'effect';
7
+ import { Subject } from '@voltro/protocol';
8
+ import { VoltroPlugin } from '@voltro/protocol';
9
+ import { YieldableError } from 'effect/Cause';
10
+
11
+ export declare const captureSavesService: (input: SavesService) => SavesService;
12
+
13
+ /** Opaque saves convey no Wallet, Inventory, ranked-result or Room-state authority. */
14
+ export declare const defaultSaveValidator: SaveValidator;
15
+
16
+ export declare const resolveSaveValidation: (context: SaveValidationContext, validator?: SaveValidator) => void;
17
+
18
+ export declare class SaveConflict extends SaveConflict_base {
19
+ }
20
+
21
+ declare const SaveConflict_base: Schema.Class<SaveConflict, Schema.TaggedStruct<"SaveConflict", {
22
+ readonly serverVersion: Schema.Number;
23
+ readonly server: Schema.$Record<Schema.String, Schema.Union<readonly [Schema.String, Schema.Number, Schema.Boolean, Schema.Null]>>;
24
+ readonly client: Schema.$Record<Schema.String, Schema.Union<readonly [Schema.String, Schema.Number, Schema.Boolean, Schema.Null]>>;
25
+ }>, YieldableError>;
26
+
27
+ export declare interface SaveDeleteIntent {
28
+ readonly slot: string;
29
+ readonly requestId: string;
30
+ readonly baseVersion: number;
31
+ }
32
+
33
+ export declare type SaveDocument = typeof SaveDocumentSchema.Type;
34
+
35
+ declare const SaveDocumentSchema: Schema.Struct<{
36
+ readonly bytes: Schema.Uint8ArrayFromBase64;
37
+ readonly slot: Schema.String;
38
+ readonly version: Schema.Number;
39
+ readonly schemaVersion: Schema.Number;
40
+ readonly byteLength: Schema.Number;
41
+ readonly checksum: Schema.String;
42
+ readonly deviceId: Schema.String;
43
+ readonly clientVersion: Schema.String;
44
+ readonly savedAt: Schema.Number;
45
+ readonly summary: Schema.$Record<Schema.String, Schema.Union<readonly [Schema.String, Schema.Number, Schema.Boolean, Schema.Null]>>;
46
+ readonly deletedUntil: Schema.NullOr<Schema.Number>;
47
+ }>;
48
+
49
+ export declare type SaveError = SavesError | SaveConflict;
50
+
51
+ export declare type SaveHeader = typeof SaveHeaderSchema.Type;
52
+
53
+ declare const SaveHeaderSchema: Schema.Struct<{
54
+ readonly slot: Schema.String;
55
+ readonly version: Schema.Number;
56
+ readonly schemaVersion: Schema.Number;
57
+ readonly byteLength: Schema.Number;
58
+ readonly checksum: Schema.String;
59
+ readonly deviceId: Schema.String;
60
+ readonly clientVersion: Schema.String;
61
+ readonly savedAt: Schema.Number;
62
+ readonly summary: Schema.$Record<Schema.String, Schema.Union<readonly [Schema.String, Schema.Number, Schema.Boolean, Schema.Null]>>;
63
+ readonly deletedUntil: Schema.NullOr<Schema.Number>;
64
+ }>;
65
+
66
+ export declare interface SavePutIntent {
67
+ readonly slot: string;
68
+ readonly requestId: string;
69
+ readonly baseVersion: number;
70
+ readonly schemaVersion: number;
71
+ readonly bytes: Uint8Array;
72
+ readonly deviceId: string;
73
+ readonly clientVersion: string;
74
+ readonly summary: SaveSummary;
75
+ }
76
+
77
+ export declare interface SaveRollbackIntent {
78
+ readonly slot: string;
79
+ readonly requestId: string;
80
+ readonly baseVersion: number;
81
+ readonly version: number;
82
+ }
83
+
84
+ export declare class SavesError extends SavesError_base {
85
+ }
86
+
87
+ declare const SavesError_base: Schema.Class<SavesError, Schema.TaggedStruct<"SavesError", {
88
+ readonly code: Schema.Literals<readonly ["invalid_input", "invalid_record", "forbidden", "not_found", "erased", "limit_exceeded", "invalid_save", "rate_limited", "unavailable", "request_conflict"]>;
89
+ }>, YieldableError>;
90
+
91
+ export declare interface SavesOptions {
92
+ /** Server configuration, never a game/owner selector from the player. */
93
+ readonly gameId: string;
94
+ readonly slots?: readonly string[];
95
+ readonly maxBytes?: number;
96
+ readonly historyVersions?: number;
97
+ readonly maxRequests?: number;
98
+ readonly deleteGraceMs?: number;
99
+ readonly version?: string;
100
+ readonly validator?: SaveValidator;
101
+ readonly schema?: Schema.Top | undefined;
102
+ readonly schemaVersion?: number | undefined;
103
+ readonly now?: (() => number) | undefined;
104
+ }
105
+
106
+ export declare interface SavesPlugin extends VoltroPlugin {
107
+ readonly saves: () => SavesService;
108
+ }
109
+
110
+ export declare const savesPlugin: (input: SavesPluginOptions) => SavesPlugin;
111
+
112
+ export declare interface SavesPluginOptions extends SavesOptions {
113
+ readonly service?: SavesService;
114
+ /** Actual app-owned invalidation sources required by a complete replacement. */
115
+ readonly querySources?: readonly string[];
116
+ readonly connection?: ConnectionRef<'redis'>;
117
+ /** Private fixture or single-node app-owned HotStore; never disposed here. */
118
+ readonly hotStore?: HotStoreShape;
119
+ }
120
+
121
+ export declare class SavesPort extends SavesPort_base {
122
+ }
123
+
124
+ declare const SavesPort_base: Context.ServiceClass<SavesPort, "@voltro/SavesPort", SavesService>;
125
+
126
+ export declare interface SavesService {
127
+ readonly get: (subject: Subject, slot: string) => Effect.Effect<SaveDocument | null, SaveError>;
128
+ readonly list: (subject: Subject) => Effect.Effect<readonly SaveHeader[], SaveError>;
129
+ readonly history: (subject: Subject, slot: string) => Effect.Effect<readonly SaveHeader[], SaveError>;
130
+ readonly put: (subject: Subject, intent: SavePutIntent) => Effect.Effect<SaveHeader, SaveError>;
131
+ readonly rollback: (subject: Subject, intent: SaveRollbackIntent) => Effect.Effect<SaveHeader, SaveError>;
132
+ readonly remove: (subject: Subject, intent: SaveDeleteIntent) => Effect.Effect<SaveHeader, SaveError>;
133
+ readonly exportSubject: (target: PluginPrivacyTarget) => Effect.Effect<unknown, SaveError>;
134
+ readonly eraseSubject: (target: PluginPrivacyTarget) => Effect.Effect<void, SaveError>;
135
+ }
136
+
137
+ export declare const savesSystemContract: Readonly<{
138
+ slots: {
139
+ validator: {
140
+ defaultExport: string;
141
+ fixtureExport: string;
142
+ };
143
+ };
144
+ ports: {
145
+ saves: {
146
+ tagExport: string;
147
+ fakeLayerExport: string;
148
+ fixtureExport: string;
149
+ };
150
+ };
151
+ events: readonly string[];
152
+ }>;
153
+
154
+ export declare type SaveSummary = typeof SaveSummarySchema.Type;
155
+
156
+ declare const SaveSummarySchema: Schema.$Record<Schema.String, Schema.Union<readonly [Schema.String, Schema.Number, Schema.Boolean, Schema.Null]>>;
157
+
158
+ export declare interface SaveValidationContext {
159
+ readonly previous: SaveDocument | null;
160
+ readonly proposed: SavePutIntent;
161
+ readonly decoded: unknown;
162
+ }
163
+
164
+ export declare type SaveValidator = (context: SaveValidationContext) => boolean;
165
+
166
+ export { }
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ import { SaveConflict as e, SavesError as t } from "./errors.js";
2
+ import { SavesPort as n, captureSavesService as r } from "./port.js";
3
+ import { defaultSaveValidator as i, resolveSaveValidation as a } from "./strategies.js";
4
+ import { savesPlugin as o, savesSystemContract as s } from "./plugin.js";
5
+ export { e as SaveConflict, t as SavesError, n as SavesPort, r as captureSavesService, i as defaultSaveValidator, a as resolveSaveValidation, o as savesPlugin, s as savesSystemContract };
@@ -0,0 +1,82 @@
1
+ import { Schema } from 'effect';
2
+
3
+ declare const base: Schema.Struct<{
4
+ observedAt: Schema.Number;
5
+ bound: Schema.Boolean;
6
+ backend: Schema.Literals<readonly ["sql", "custom"]>;
7
+ gameId: Schema.String;
8
+ version: Schema.String;
9
+ slots: Schema.$Array<Schema.String>;
10
+ validator: Schema.Literals<readonly ["default", "custom", "app-owned"]>;
11
+ budgets: Schema.NullOr<Schema.Struct<{
12
+ readonly maxBytes: Schema.Number;
13
+ readonly historyVersions: Schema.Number;
14
+ readonly maxRequests: Schema.Number;
15
+ readonly deleteGraceMs: Schema.Number;
16
+ readonly writesPerMinute: Schema.Literal<10>;
17
+ }>>;
18
+ schemaVersion: Schema.NullOr<Schema.Number>;
19
+ rateActive: Schema.Boolean;
20
+ tables: Schema.$Array<Schema.Literals<readonly ["_voltro_saves", "_voltro_saves_history", "_voltro_saves_requests", "_voltro_saves_subjects"]>>;
21
+ offlineInstalled: Schema.Literal<false>;
22
+ signaturesInstalled: Schema.Literal<false>;
23
+ externalStorageInstalled: Schema.Literal<false>;
24
+ purgeInstalled: Schema.Literal<false>;
25
+ gmRollbackInstalled: Schema.Literal<false>;
26
+ }>;
27
+
28
+ export declare const isSavesStatusConsistent: (value: typeof base.Type) => boolean;
29
+
30
+ export declare type SavesStatus = typeof SavesStatusSchema.Type;
31
+
32
+ export declare const SavesStatusFields: {
33
+ observedAt: Schema.Number;
34
+ bound: Schema.Boolean;
35
+ backend: Schema.Literals<readonly ["sql", "custom"]>;
36
+ gameId: Schema.String;
37
+ version: Schema.String;
38
+ slots: Schema.$Array<Schema.String>;
39
+ validator: Schema.Literals<readonly ["default", "custom", "app-owned"]>;
40
+ budgets: Schema.NullOr<Schema.Struct<{
41
+ readonly maxBytes: Schema.Number;
42
+ readonly historyVersions: Schema.Number;
43
+ readonly maxRequests: Schema.Number;
44
+ readonly deleteGraceMs: Schema.Number;
45
+ readonly writesPerMinute: Schema.Literal<10>;
46
+ }>>;
47
+ schemaVersion: Schema.NullOr<Schema.Number>;
48
+ rateActive: Schema.Boolean;
49
+ tables: Schema.$Array<Schema.Literals<readonly ["_voltro_saves", "_voltro_saves_history", "_voltro_saves_requests", "_voltro_saves_subjects"]>>;
50
+ offlineInstalled: Schema.Literal<false>;
51
+ signaturesInstalled: Schema.Literal<false>;
52
+ externalStorageInstalled: Schema.Literal<false>;
53
+ purgeInstalled: Schema.Literal<false>;
54
+ gmRollbackInstalled: Schema.Literal<false>;
55
+ };
56
+
57
+ export declare const SavesStatusSchema: Schema.Struct<{
58
+ observedAt: Schema.Number;
59
+ bound: Schema.Boolean;
60
+ backend: Schema.Literals<readonly ["sql", "custom"]>;
61
+ gameId: Schema.String;
62
+ version: Schema.String;
63
+ slots: Schema.$Array<Schema.String>;
64
+ validator: Schema.Literals<readonly ["default", "custom", "app-owned"]>;
65
+ budgets: Schema.NullOr<Schema.Struct<{
66
+ readonly maxBytes: Schema.Number;
67
+ readonly historyVersions: Schema.Number;
68
+ readonly maxRequests: Schema.Number;
69
+ readonly deleteGraceMs: Schema.Number;
70
+ readonly writesPerMinute: Schema.Literal<10>;
71
+ }>>;
72
+ schemaVersion: Schema.NullOr<Schema.Number>;
73
+ rateActive: Schema.Boolean;
74
+ tables: Schema.$Array<Schema.Literals<readonly ["_voltro_saves", "_voltro_saves_history", "_voltro_saves_requests", "_voltro_saves_subjects"]>>;
75
+ offlineInstalled: Schema.Literal<false>;
76
+ signaturesInstalled: Schema.Literal<false>;
77
+ externalStorageInstalled: Schema.Literal<false>;
78
+ purgeInstalled: Schema.Literal<false>;
79
+ gmRollbackInstalled: Schema.Literal<false>;
80
+ }>;
81
+
82
+ export { }
@@ -0,0 +1,49 @@
1
+ import { SAVE_HISTORY_TABLE as e, SAVE_REQUEST_TABLE as t, SAVE_SUBJECT_TABLE as n, SAVE_TABLE as r } from "./contract.js";
2
+ import { Schema as i } from "effect";
3
+ //#region src/inspect.ts
4
+ var a = (e, t) => i.Number.pipe(i.check(i.isInt()), i.check(i.isBetween({
5
+ minimum: e,
6
+ maximum: t
7
+ }))), o = {
8
+ observedAt: a(0, 864e13),
9
+ bound: i.Boolean,
10
+ backend: i.Literals(["sql", "custom"]),
11
+ gameId: i.String,
12
+ version: i.String,
13
+ slots: i.Array(i.String),
14
+ validator: i.Literals([
15
+ "default",
16
+ "custom",
17
+ "app-owned"
18
+ ]),
19
+ budgets: i.NullOr(i.Struct({
20
+ maxBytes: a(1, 1048576),
21
+ historyVersions: a(1, 100),
22
+ maxRequests: a(1, 1e5),
23
+ deleteGraceMs: a(0, 31536e6),
24
+ writesPerMinute: i.Literal(10)
25
+ })),
26
+ schemaVersion: i.NullOr(a(1, 2147483647)),
27
+ rateActive: i.Boolean,
28
+ tables: i.Array(i.Literals([
29
+ r,
30
+ e,
31
+ t,
32
+ n
33
+ ])),
34
+ offlineInstalled: i.Literal(!1),
35
+ signaturesInstalled: i.Literal(!1),
36
+ externalStorageInstalled: i.Literal(!1),
37
+ purgeInstalled: i.Literal(!1),
38
+ gmRollbackInstalled: i.Literal(!1)
39
+ }, s = i.Struct(o), c = (e) => {
40
+ let t = (e) => /^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$/.test(e);
41
+ return !t(e.gameId) || !t(e.version) || e.slots.length < 1 || e.slots.length > 64 || new Set(e.slots).size !== e.slots.length || !e.slots.every(t) || e.rateActive && !e.bound ? !1 : e.backend === "custom" ? e.validator === "app-owned" && e.budgets === null && e.schemaVersion === null && e.tables.length === 0 && !e.rateActive && e.version !== "default.v1" : e.validator !== "app-owned" && e.budgets !== null && JSON.stringify(e.tables) === JSON.stringify([
42
+ "_voltro_saves",
43
+ "_voltro_saves_history",
44
+ "_voltro_saves_requests",
45
+ "_voltro_saves_subjects"
46
+ ]);
47
+ }, l = s.pipe(i.check(i.makeFilter(c)));
48
+ //#endregion
49
+ export { o as SavesStatusFields, l as SavesStatusSchema, c as isSavesStatusConsistent };