@playporter/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # @playporter/core
2
+
3
+ Shared PlayPorter contracts, types and core models used by the SDK and Studio.
@@ -0,0 +1,188 @@
1
+ export declare const SUPPORTED_PLATFORMS: readonly ["discord", "telegram", "reddit", "web"];
2
+ export type Platform = (typeof SUPPORTED_PLATFORMS)[number];
3
+ export type PublicPlatform = Exclude<Platform, "web">;
4
+ export type EnvironmentName = "development" | "staging" | "production";
5
+ export type PlanName = "free" | "pro" | "business";
6
+ export interface PlanLimits {
7
+ games: number;
8
+ monthlyActivePlayers: number;
9
+ monthlyEvents: number;
10
+ analyticsRetentionDays: number;
11
+ liveOpsFlags: number;
12
+ collaborators: number;
13
+ environments: EnvironmentName[];
14
+ platforms: PublicPlatform[];
15
+ branded: boolean;
16
+ }
17
+ export declare const PLAN_LIMITS: Record<PlanName, PlanLimits>;
18
+ export type JsonPrimitive = string | number | boolean | null;
19
+ export type JsonValue = JsonPrimitive | JsonValue[] | {
20
+ [key: string]: JsonValue;
21
+ };
22
+ export interface PlayerProfile {
23
+ id: string;
24
+ displayName?: string | null;
25
+ avatarUrl?: string | null;
26
+ platform: Platform;
27
+ platformUserId: string;
28
+ isGuest: boolean;
29
+ }
30
+ export interface FeatureFlagDefinition {
31
+ key: string;
32
+ enabled: boolean;
33
+ value: JsonValue;
34
+ rolloutPercentage: number;
35
+ platforms?: Platform[];
36
+ minimumVersion?: string | null;
37
+ maximumVersion?: string | null;
38
+ }
39
+ export interface EffectiveConfig {
40
+ environment: EnvironmentName;
41
+ revision: number;
42
+ values: Record<string, JsonValue>;
43
+ flags: Record<string, JsonValue>;
44
+ fetchedAt: string;
45
+ }
46
+ export interface AnalyticsEventInput {
47
+ name: string;
48
+ occurredAt?: string;
49
+ sessionId?: string;
50
+ properties?: Record<string, JsonValue>;
51
+ }
52
+ export interface ValidationIssue {
53
+ code: string;
54
+ severity: "error" | "warning" | "info";
55
+ platform: Platform | "common";
56
+ message: string;
57
+ file?: string;
58
+ remediation?: string;
59
+ }
60
+ export interface ValidationReportData {
61
+ id?: string;
62
+ bundlePath: string;
63
+ bundleHash: string;
64
+ createdAt: string;
65
+ totalBytes: number;
66
+ fileCount: number;
67
+ platforms: PublicPlatform[];
68
+ issues: ValidationIssue[];
69
+ passed: boolean;
70
+ }
71
+ export interface ArtifactFileEntry {
72
+ path: string;
73
+ bytes: number;
74
+ sha256: string;
75
+ role: "entry" | "asset" | "runtime" | "metadata";
76
+ }
77
+ export interface ArtifactManifest {
78
+ schemaVersion: 1;
79
+ artifactId: string;
80
+ game: PlayPorterConfig["game"];
81
+ createdAt: string;
82
+ builder: {
83
+ cliVersion: string;
84
+ nodeVersion: string;
85
+ platform: string;
86
+ };
87
+ bundle: {
88
+ root: string;
89
+ entry: string;
90
+ totalBytes: number;
91
+ fileCount: number;
92
+ sha256: string;
93
+ };
94
+ validation: {
95
+ passed: boolean;
96
+ platforms: PublicPlatform[];
97
+ issueCount: number;
98
+ };
99
+ claims: {
100
+ sourceCodeIncluded: false;
101
+ localCompilation: true;
102
+ studioControlledRelease: true;
103
+ };
104
+ files: ArtifactFileEntry[];
105
+ }
106
+ export interface ArtifactSignature {
107
+ algorithm: "ed25519";
108
+ publicKeyPem: string;
109
+ signatureBase64: string;
110
+ signedAt: string;
111
+ manifestSha256: string;
112
+ }
113
+ export interface PassportAccount {
114
+ platform: Platform;
115
+ username?: string | null;
116
+ displayName?: string | null;
117
+ externalId: string;
118
+ verifiedAt?: string;
119
+ }
120
+ export interface PassportSummary {
121
+ player: PlayerProfile;
122
+ passportId: string;
123
+ linkedAccounts: PassportAccount[];
124
+ profile: Record<string, JsonValue>;
125
+ achievements: Array<{
126
+ id: string;
127
+ unlockedAt: string;
128
+ metadata?: Record<string, JsonValue>;
129
+ }>;
130
+ purchases: Array<{
131
+ id: string;
132
+ sku: string;
133
+ purchasedAt: string;
134
+ metadata?: Record<string, JsonValue>;
135
+ }>;
136
+ friends: Array<{
137
+ id: string;
138
+ displayName?: string | null;
139
+ platform?: Platform;
140
+ linkedAt: string;
141
+ }>;
142
+ preferences: Record<string, JsonValue>;
143
+ saveKeys: string[];
144
+ leaderboardEntries: Array<{
145
+ leaderboard: string;
146
+ score: number;
147
+ updatedAt: string;
148
+ }>;
149
+ }
150
+ export interface PlayPorterConfig {
151
+ schemaVersion: 1;
152
+ game: {
153
+ id?: string;
154
+ slug: string;
155
+ name: string;
156
+ version: string;
157
+ };
158
+ build: {
159
+ command: string;
160
+ output: string;
161
+ };
162
+ api: {
163
+ baseUrl: string;
164
+ projectKeyEnv: string;
165
+ };
166
+ platforms: {
167
+ discord?: {
168
+ enabled: boolean;
169
+ clientId?: string;
170
+ urlMappings?: Array<{
171
+ prefix: string;
172
+ target: string;
173
+ }>;
174
+ };
175
+ telegram?: {
176
+ enabled: boolean;
177
+ botUsername?: string;
178
+ };
179
+ reddit?: {
180
+ enabled: boolean;
181
+ appName?: string;
182
+ subreddit?: string;
183
+ postTitle?: string;
184
+ };
185
+ };
186
+ }
187
+ export declare function deterministicBucket(...parts: string[]): number;
188
+ export declare function compareSemver(left: string, right: string): number;
package/dist/index.js ADDED
@@ -0,0 +1,58 @@
1
+ export const SUPPORTED_PLATFORMS = ["discord", "telegram", "reddit", "web"];
2
+ export const PLAN_LIMITS = {
3
+ free: {
4
+ games: 1,
5
+ monthlyActivePlayers: 2_500,
6
+ monthlyEvents: 50_000,
7
+ analyticsRetentionDays: 7,
8
+ liveOpsFlags: 10,
9
+ collaborators: 1,
10
+ environments: ["development", "production"],
11
+ platforms: ["discord", "telegram", "reddit"],
12
+ branded: true,
13
+ },
14
+ pro: {
15
+ games: 10,
16
+ monthlyActivePlayers: 100_000,
17
+ monthlyEvents: 5_000_000,
18
+ analyticsRetentionDays: 90,
19
+ liveOpsFlags: 250,
20
+ collaborators: 10,
21
+ environments: ["development", "staging", "production"],
22
+ platforms: ["discord", "telegram", "reddit"],
23
+ branded: false,
24
+ },
25
+ business: {
26
+ games: Number.MAX_SAFE_INTEGER,
27
+ monthlyActivePlayers: Number.MAX_SAFE_INTEGER,
28
+ monthlyEvents: Number.MAX_SAFE_INTEGER,
29
+ analyticsRetentionDays: 365,
30
+ liveOpsFlags: Number.MAX_SAFE_INTEGER,
31
+ collaborators: Number.MAX_SAFE_INTEGER,
32
+ environments: ["development", "staging", "production"],
33
+ platforms: ["discord", "telegram", "reddit"],
34
+ branded: false,
35
+ },
36
+ };
37
+ export function deterministicBucket(...parts) {
38
+ const input = parts.join(":");
39
+ let hash = 2166136261;
40
+ for (let index = 0; index < input.length; index += 1) {
41
+ hash ^= input.charCodeAt(index);
42
+ hash = Math.imul(hash, 16777619);
43
+ }
44
+ return (hash >>> 0) % 100;
45
+ }
46
+ export function compareSemver(left, right) {
47
+ const parse = (value) => value.split(".").map((part) => Number.parseInt(part, 10) || 0);
48
+ const a = parse(left);
49
+ const b = parse(right);
50
+ const length = Math.max(a.length, b.length);
51
+ for (let index = 0; index < length; index += 1) {
52
+ const difference = (a[index] ?? 0) - (b[index] ?? 0);
53
+ if (difference !== 0)
54
+ return difference;
55
+ }
56
+ return 0;
57
+ }
58
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,17 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { compareSemver, deterministicBucket, PLAN_LIMITS } from "./index.js";
4
+ test("deterministicBucket is stable and bounded", () => {
5
+ const first = deterministicBucket("player-1", "weekend_event");
6
+ assert.equal(first, deterministicBucket("player-1", "weekend_event"));
7
+ assert.ok(first >= 0 && first < 100);
8
+ });
9
+ test("compareSemver compares numeric segments", () => {
10
+ assert.equal(compareSemver("1.2.0", "1.2"), 0);
11
+ assert.ok(compareSemver("1.10.0", "1.2.9") > 0);
12
+ assert.ok(compareSemver("2.0.0", "2.1.0") < 0);
13
+ });
14
+ test("free plan includes all three launch platforms", () => {
15
+ assert.deepEqual(PLAN_LIMITS.free.platforms, ["discord", "telegram", "reddit"]);
16
+ });
17
+ //# sourceMappingURL=index.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.js","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE7E,IAAI,CAAC,2CAA2C,EAAE,GAAG,EAAE;IACrD,MAAM,KAAK,GAAG,mBAAmB,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;IAC/D,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,mBAAmB,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC,CAAC;IACtE,MAAM,CAAC,EAAE,CAAC,KAAK,IAAI,CAAC,IAAI,KAAK,GAAG,GAAG,CAAC,CAAC;AACvC,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,yCAAyC,EAAE,GAAG,EAAE;IACnD,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;IAC/C,MAAM,CAAC,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;IAChD,MAAM,CAAC,EAAE,CAAC,aAAa,CAAC,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;AACjD,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,+CAA+C,EAAE,GAAG,EAAE;IACzD,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,SAAS,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC;AAClF,CAAC,CAAC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@playporter/core",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist",
9
+ "package.json",
10
+ "README.md"
11
+ ],
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js"
16
+ }
17
+ },
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "scripts": {
22
+ "build": "tsc -p tsconfig.json",
23
+ "typecheck": "tsc -p tsconfig.json --noEmit",
24
+ "test": "node --test dist/**/*.test.js",
25
+ "prepublishOnly": "npm run build && npm run typecheck"
26
+ }
27
+ }