@mpgd/bridge 0.5.0 → 0.6.1

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.
@@ -1,5 +1,5 @@
1
- import { type BridgeRequest, type BridgeResponse } from './index';
2
- import { type BridgeRpcPrefix } from './orpc';
1
+ import { type BridgeRequest, type BridgeResponse } from './index.js';
2
+ import { type BridgeRpcPrefix } from './orpc.js';
3
3
  export declare const defaultCloudflarePagesBridgeEndpoint: '/api/mpgd/bridge';
4
4
  export declare const defaultCloudflarePagesGameServicesPrefix: '/api/game-services';
5
5
  export declare const defaultCloudflarePagesGameServicesForwardHeaders: readonly ['accept', 'authorization', 'content-type'];
@@ -1,5 +1,5 @@
1
- import { assertBridgeRequest, createBridgeError, } from './index';
2
- import { createBridgeRpcFetchHandler, createBridgeRpcRouter, defaultBridgeRpcEndpoint, } from './orpc';
1
+ import { assertBridgeRequest, bridgeStorageLoadProtocol, createBridgeError, } from './index.js';
2
+ import { createBridgeRpcFetchHandler, createBridgeRpcRouter, defaultBridgeRpcEndpoint, } from './orpc.js';
3
3
  export const defaultCloudflarePagesBridgeEndpoint = '/api/mpgd/bridge';
4
4
  export const defaultCloudflarePagesGameServicesPrefix = '/api/game-services';
5
5
  export const defaultCloudflarePagesGameServicesForwardHeaders = [
@@ -137,7 +137,10 @@ export function createMpgdCloudflarePagesBridgeHandler() {
137
137
  case 'leaderboard.open':
138
138
  return createBridgeError(input.bridgeRequest.id, 'CLOUDFLARE_PAGES_LEADERBOARD_OPEN_UNAVAILABLE', 'Cloudflare Pages host does not provide a native leaderboard UI.');
139
139
  case 'storage.load':
140
- return ok(input.bridgeRequest, null);
140
+ return ok(input.bridgeRequest, {
141
+ __mpgdBridgeProtocol: bridgeStorageLoadProtocol,
142
+ found: false,
143
+ });
141
144
  case 'storage.save':
142
145
  return ok(input.bridgeRequest, {
143
146
  saved: false,
package/dist/index.d.ts CHANGED
@@ -23,6 +23,18 @@ export type BridgeResponse<TData = unknown> = {
23
23
  readonly retryable: boolean;
24
24
  };
25
25
  };
26
+ export declare const bridgeStorageLoadProtocol: 'mpgd.storage.load.v1';
27
+ export type BridgeStorageLoadData = {
28
+ readonly __mpgdBridgeProtocol: typeof bridgeStorageLoadProtocol;
29
+ readonly found: false;
30
+ } | {
31
+ readonly __mpgdBridgeProtocol: typeof bridgeStorageLoadProtocol;
32
+ readonly found: true;
33
+ readonly value: unknown;
34
+ };
26
35
  export declare const assertBridgeRequest: (input: unknown) => BridgeRequest<unknown>;
27
36
  export declare const assertBridgeResponse: (input: unknown) => BridgeResponse<unknown>;
37
+ export declare function decodeBridgeStorageLoadData(input: unknown): {
38
+ readonly value: unknown;
39
+ } | null;
28
40
  export declare function createBridgeError(id: string, code: string, message: string, retryable?: boolean): BridgeResponse;
package/dist/index.js CHANGED
@@ -1,46 +1,47 @@
1
- import * as _a from "typia/lib/internal/_assertGuard";
1
+ import * as _assertGuard_1 from "typia/lib/internal/_assertGuard";
2
2
  import typia from 'typia';
3
+ export const bridgeStorageLoadProtocol = 'mpgd.storage.load.v1';
3
4
  export const assertBridgeRequest = (() => {
4
5
  const _iv0 = new Set(["ads.preload", "ads.showInterstitial", "ads.showRewarded", "commerce.getEntitlements", "commerce.getProducts", "commerce.purchase", "commerce.restore", "identity.getPlayer", "identity.getSession", "identity.requestUpgrade", "leaderboard.open", "leaderboard.submitScore", "notifications.getStatus", "notifications.requestSubscription", "presentation.getLaunchIntent", "presentation.requestGameSurface", "runtime.getCapabilities", "share.readInboundShare", "share.share", "storage.load", "storage.save"]);
5
6
  const _av0 = new Set(["ads.preload", "ads.showInterstitial", "ads.showRewarded", "commerce.getEntitlements", "commerce.getProducts", "commerce.purchase", "commerce.restore", "identity.getPlayer", "identity.getSession", "identity.requestUpgrade", "leaderboard.open", "leaderboard.submitScore", "notifications.getStatus", "notifications.requestSubscription", "presentation.getLaunchIntent", "presentation.requestGameSurface", "runtime.getCapabilities", "share.readInboundShare", "share.share", "storage.load", "storage.save"]);
6
7
  const _ae0 = "(\"ads.preload\" | \"ads.showInterstitial\" | \"ads.showRewarded\" | \"commerce.getEntitlements\" | \"commerce.getProducts\" | \"commerce.purchase\" | \"commerce.restore\" | \"identity.getPlayer\" | \"identity.getSession\" | \"identity.requestUpgrade\" | \"leaderboard.open\" | \"leaderboard.submitScore\" | \"notifications.getStatus\" | \"notifications.requestSubscription\" | \"presentation.getLaunchIntent\" | \"presentation.requestGameSurface\" | \"runtime.getCapabilities\" | \"share.readInboundShare\" | \"share.share\" | \"storage.load\" | \"storage.save\")";
7
- const _ao0 = (input, _path, _exceptionable = true) => ("string" === typeof input.id || _a._assertGuard(_exceptionable, {
8
+ const _ao0 = (input, _path, _exceptionable = true) => ("string" === typeof input.id || _assertGuard_1._assertGuard(_exceptionable, {
8
9
  method: "typia.createAssert",
9
10
  path: _path + ".id",
10
11
  expected: "string",
11
12
  value: input.id
12
- }, _errorFactory)) && (true === _av0.has(input.method) || _a._assertGuard(_exceptionable, {
13
+ }, _errorFactory)) && (true === _av0.has(input.method) || _assertGuard_1._assertGuard(_exceptionable, {
13
14
  method: "typia.createAssert",
14
15
  path: _path + ".method",
15
16
  expected: _ae0,
16
17
  value: input.method
17
- }, _errorFactory)) && true && (("object" === typeof input.meta && null !== input.meta || _a._assertGuard(_exceptionable, {
18
+ }, _errorFactory)) && true && (("object" === typeof input.meta && null !== input.meta || _assertGuard_1._assertGuard(_exceptionable, {
18
19
  method: "typia.createAssert",
19
20
  path: _path + ".meta",
20
21
  expected: "{ readonly target: string; readonly appVersion: string; readonly buildId: string; readonly sentAt: string; }",
21
22
  value: input.meta
22
- }, _errorFactory)) && _ao1(input.meta, _path + ".meta", true && _exceptionable) || _a._assertGuard(_exceptionable, {
23
+ }, _errorFactory)) && _ao1(input.meta, _path + ".meta", true && _exceptionable) || _assertGuard_1._assertGuard(_exceptionable, {
23
24
  method: "typia.createAssert",
24
25
  path: _path + ".meta",
25
26
  expected: "{ readonly target: string; readonly appVersion: string; readonly buildId: string; readonly sentAt: string; }",
26
27
  value: input.meta
27
28
  }, _errorFactory));
28
- const _ao1 = (input, _path, _exceptionable = true) => ("string" === typeof input.target || _a._assertGuard(_exceptionable, {
29
+ const _ao1 = (input, _path, _exceptionable = true) => ("string" === typeof input.target || _assertGuard_1._assertGuard(_exceptionable, {
29
30
  method: "typia.createAssert",
30
31
  path: _path + ".target",
31
32
  expected: "string",
32
33
  value: input.target
33
- }, _errorFactory)) && ("string" === typeof input.appVersion || _a._assertGuard(_exceptionable, {
34
+ }, _errorFactory)) && ("string" === typeof input.appVersion || _assertGuard_1._assertGuard(_exceptionable, {
34
35
  method: "typia.createAssert",
35
36
  path: _path + ".appVersion",
36
37
  expected: "string",
37
38
  value: input.appVersion
38
- }, _errorFactory)) && ("string" === typeof input.buildId || _a._assertGuard(_exceptionable, {
39
+ }, _errorFactory)) && ("string" === typeof input.buildId || _assertGuard_1._assertGuard(_exceptionable, {
39
40
  method: "typia.createAssert",
40
41
  path: _path + ".buildId",
41
42
  expected: "string",
42
43
  value: input.buildId
43
- }, _errorFactory)) && ("string" === typeof input.sentAt || _a._assertGuard(_exceptionable, {
44
+ }, _errorFactory)) && ("string" === typeof input.sentAt || _assertGuard_1._assertGuard(_exceptionable, {
44
45
  method: "typia.createAssert",
45
46
  path: _path + ".sentAt",
46
47
  expected: "string",
@@ -53,12 +54,12 @@ export const assertBridgeRequest = (() => {
53
54
  return (input, errorFactory) => {
54
55
  if (false === __is(input)) {
55
56
  _errorFactory = errorFactory;
56
- ((input, _path, _exceptionable = true) => ("object" === typeof input && null !== input || _a._assertGuard(true, {
57
+ ((input, _path, _exceptionable = true) => ("object" === typeof input && null !== input || _assertGuard_1._assertGuard(true, {
57
58
  method: "typia.createAssert",
58
59
  path: _path + "",
59
60
  expected: "BridgeRequest<unknown>",
60
61
  value: input
61
- }, _errorFactory)) && _ao0(input, _path + "", true) || _a._assertGuard(true, {
62
+ }, _errorFactory)) && _ao0(input, _path + "", true) || _assertGuard_1._assertGuard(true, {
62
63
  method: "typia.createAssert",
63
64
  path: _path + "",
64
65
  expected: "BridgeRequest<unknown>",
@@ -71,46 +72,46 @@ export const assertBridgeRequest = (() => {
71
72
  export const assertBridgeResponse = (() => {
72
73
  const _ip0 = input => "string" === typeof input["id"];
73
74
  const _ae0 = "({ readonly id: string; readonly ok: false; readonly error: { readonly code: string; readonly message: string; readonly retryable: boolean; }; } | { readonly id: string; readonly ok: true; readonly data: unknown; })";
74
- const _ap0 = (input, _path, _exceptionable = true) => "string" === typeof input["id"] || _a._assertGuard(_exceptionable, {
75
+ const _ap0 = (input, _path, _exceptionable = true) => "string" === typeof input["id"] || _assertGuard_1._assertGuard(_exceptionable, {
75
76
  method: "typia.createAssert",
76
77
  path: _path + ".id",
77
78
  expected: "string",
78
79
  value: input["id"]
79
80
  }, _errorFactory);
80
81
  const _ae1 = "({ readonly id: string; readonly ok: true; readonly data: unknown; } | { readonly id: string; readonly ok: false; readonly error: { readonly code: string; readonly message: string; readonly retryable: boolean; }; })";
81
- const _ao0 = (input, _path, _exceptionable = true) => _ap0(input, _path, true && _exceptionable) && (true === input.ok || _a._assertGuard(_exceptionable, {
82
+ const _ao0 = (input, _path, _exceptionable = true) => _ap0(input, _path, true && _exceptionable) && (true === input.ok || _assertGuard_1._assertGuard(_exceptionable, {
82
83
  method: "typia.createAssert",
83
84
  path: _path + ".ok",
84
85
  expected: "true",
85
86
  value: input.ok
86
87
  }, _errorFactory)) && true;
87
- const _ao1 = (input, _path, _exceptionable = true) => _ap0(input, _path, true && _exceptionable) && (false === input.ok || _a._assertGuard(_exceptionable, {
88
+ const _ao1 = (input, _path, _exceptionable = true) => _ap0(input, _path, true && _exceptionable) && (false === input.ok || _assertGuard_1._assertGuard(_exceptionable, {
88
89
  method: "typia.createAssert",
89
90
  path: _path + ".ok",
90
91
  expected: "false",
91
92
  value: input.ok
92
- }, _errorFactory)) && (("object" === typeof input.error && null !== input.error || _a._assertGuard(_exceptionable, {
93
+ }, _errorFactory)) && (("object" === typeof input.error && null !== input.error || _assertGuard_1._assertGuard(_exceptionable, {
93
94
  method: "typia.createAssert",
94
95
  path: _path + ".error",
95
96
  expected: "{ readonly code: string; readonly message: string; readonly retryable: boolean; }",
96
97
  value: input.error
97
- }, _errorFactory)) && _ao2(input.error, _path + ".error", true && _exceptionable) || _a._assertGuard(_exceptionable, {
98
+ }, _errorFactory)) && _ao2(input.error, _path + ".error", true && _exceptionable) || _assertGuard_1._assertGuard(_exceptionable, {
98
99
  method: "typia.createAssert",
99
100
  path: _path + ".error",
100
101
  expected: "{ readonly code: string; readonly message: string; readonly retryable: boolean; }",
101
102
  value: input.error
102
103
  }, _errorFactory));
103
- const _ao2 = (input, _path, _exceptionable = true) => ("string" === typeof input.code || _a._assertGuard(_exceptionable, {
104
+ const _ao2 = (input, _path, _exceptionable = true) => ("string" === typeof input.code || _assertGuard_1._assertGuard(_exceptionable, {
104
105
  method: "typia.createAssert",
105
106
  path: _path + ".code",
106
107
  expected: "string",
107
108
  value: input.code
108
- }, _errorFactory)) && ("string" === typeof input.message || _a._assertGuard(_exceptionable, {
109
+ }, _errorFactory)) && ("string" === typeof input.message || _assertGuard_1._assertGuard(_exceptionable, {
109
110
  method: "typia.createAssert",
110
111
  path: _path + ".message",
111
112
  expected: "string",
112
113
  value: input.message
113
- }, _errorFactory)) && ("boolean" === typeof input.retryable || _a._assertGuard(_exceptionable, {
114
+ }, _errorFactory)) && ("boolean" === typeof input.retryable || _assertGuard_1._assertGuard(_exceptionable, {
114
115
  method: "typia.createAssert",
115
116
  path: _path + ".retryable",
116
117
  expected: "boolean",
@@ -122,7 +123,7 @@ export const assertBridgeResponse = (() => {
122
123
  else if (false === input.ok)
123
124
  return _ao1(input, _path, true && _exceptionable);
124
125
  else
125
- return _a._assertGuard(_exceptionable, {
126
+ return _assertGuard_1._assertGuard(_exceptionable, {
126
127
  method: "typia.createAssert",
127
128
  path: _path,
128
129
  expected: _ae1,
@@ -145,12 +146,12 @@ export const assertBridgeResponse = (() => {
145
146
  return (input, errorFactory) => {
146
147
  if (false === __is(input)) {
147
148
  _errorFactory = errorFactory;
148
- ((input, _path, _exceptionable = true) => ("object" === typeof input && null !== input || _a._assertGuard(true, {
149
+ ((input, _path, _exceptionable = true) => ("object" === typeof input && null !== input || _assertGuard_1._assertGuard(true, {
149
150
  method: "typia.createAssert",
150
151
  path: _path + "",
151
152
  expected: _ae0,
152
153
  value: input
153
- }, _errorFactory)) && _au0(input, _path + "", true) || _a._assertGuard(true, {
154
+ }, _errorFactory)) && _au0(input, _path + "", true) || _assertGuard_1._assertGuard(true, {
154
155
  method: "typia.createAssert",
155
156
  path: _path + "",
156
157
  expected: _ae0,
@@ -160,6 +161,30 @@ export const assertBridgeResponse = (() => {
160
161
  return input;
161
162
  };
162
163
  })();
164
+ export function decodeBridgeStorageLoadData(input) {
165
+ if (input === null) {
166
+ return null;
167
+ }
168
+ if (input === undefined) {
169
+ throw new Error('Storage bridge load returned an invalid response.');
170
+ }
171
+ if (typeof input !== 'object' ||
172
+ !(input !== null && '__mpgdBridgeProtocol' in input) ||
173
+ input.__mpgdBridgeProtocol !== bridgeStorageLoadProtocol) {
174
+ return { value: input };
175
+ }
176
+ const response = input;
177
+ if (response.found === false) {
178
+ return null;
179
+ }
180
+ if (response.found !== true) {
181
+ throw new Error('Storage bridge load returned an invalid response.');
182
+ }
183
+ if (!Object.prototype.hasOwnProperty.call(response, 'value')) {
184
+ throw new Error('Storage bridge load returned an invalid response.');
185
+ }
186
+ return { value: response.value };
187
+ }
163
188
  export function createBridgeError(id, code, message, retryable = false) {
164
189
  return {
165
190
  id,
@@ -0,0 +1,12 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import { type BridgeRpcPrefix, type BridgeRpcRouter } from './orpc.js';
3
+ export interface CreateBridgeRpcNodeHandlerOptions {
4
+ readonly maxBodySize?: number;
5
+ readonly prefix?: BridgeRpcPrefix;
6
+ }
7
+ export type BridgeRpcNodeHandler = (request: IncomingMessage, response: ServerResponse) => Promise<boolean>;
8
+ /**
9
+ * Creates a bridge transport that writes oRPC responses directly to Node HTTP.
10
+ * The boolean result is false when another route should handle the request.
11
+ */
12
+ export declare function createBridgeRpcNodeHandler(router: BridgeRpcRouter, options?: CreateBridgeRpcNodeHandlerOptions): BridgeRpcNodeHandler;
@@ -0,0 +1,52 @@
1
+ import { BodyLimitHandlerPlugin, RPCHandler } from '@orpc/server/node';
2
+ import { defaultBridgeRpcEndpoint } from './orpc.js';
3
+ /**
4
+ * Creates a bridge transport that writes oRPC responses directly to Node HTTP.
5
+ * The boolean result is false when another route should handle the request.
6
+ */
7
+ export function createBridgeRpcNodeHandler(router, options = {}) {
8
+ const handler = new RPCHandler(router, {
9
+ plugins: [
10
+ new BodyLimitHandlerPlugin({
11
+ maxBodySize: options.maxBodySize ?? 1000000,
12
+ }),
13
+ ],
14
+ });
15
+ const prefix = options.prefix ?? defaultBridgeRpcEndpoint;
16
+ return async (request, response) => {
17
+ try {
18
+ const result = await handler.handle(request, response, {
19
+ prefix,
20
+ context: {
21
+ request,
22
+ },
23
+ });
24
+ return result.matched;
25
+ }
26
+ catch (error) {
27
+ console.error('Bridge RPC internal error:', error);
28
+ finishInternalErrorResponse(response);
29
+ return true;
30
+ }
31
+ };
32
+ }
33
+ function finishInternalErrorResponse(response) {
34
+ if (response.headersSent) {
35
+ if (!response.writableEnded) {
36
+ response.destroy();
37
+ }
38
+ return;
39
+ }
40
+ if (response.destroyed) {
41
+ return;
42
+ }
43
+ const payload = JSON.stringify({
44
+ error: 'BRIDGE_RPC_INTERNAL_ERROR',
45
+ });
46
+ response.statusCode = 500;
47
+ response.setHeader('cache-control', 'no-store');
48
+ response.setHeader('content-length', Buffer.byteLength(payload));
49
+ response.setHeader('content-type', 'application/json; charset=utf-8');
50
+ response.setHeader('x-content-type-options', 'nosniff');
51
+ response.end(payload);
52
+ }
package/dist/orpc.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { StandardUrl } from '@orpc/client/standard';
2
2
  import { type RouterContractClient } from '@orpc/contract';
3
- import { type BridgeRequest, type BridgeResponse } from './index';
3
+ import { type BridgeRequest, type BridgeResponse } from './index.js';
4
4
  export type BridgeRpcEndpoint = StandardUrl;
5
5
  export type BridgeRpcPrefix = `/${string}`;
6
6
  export declare const defaultBridgeRpcEndpoint = "/api/mpgd/rpc";
package/dist/orpc.js CHANGED
@@ -3,7 +3,7 @@ import { RPCLink } from '@orpc/client/fetch';
3
3
  import { oc, type as orpcType } from '@orpc/contract';
4
4
  import { implement } from '@orpc/server';
5
5
  import { RPCHandler } from '@orpc/server/fetch';
6
- import { assertBridgeRequest, createBridgeError, } from './index';
6
+ import { assertBridgeRequest, createBridgeError, } from './index.js';
7
7
  export const defaultBridgeRpcEndpoint = '/api/mpgd/rpc';
8
8
  export const bridgeRpcContract = oc.router({
9
9
  request: oc.input(orpcType()).output(orpcType()),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mpgd/bridge",
3
- "version": "0.5.0",
3
+ "version": "0.6.1",
4
4
  "description": "Typed native bridge protocol for mpgd platform adapters.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -33,6 +33,10 @@
33
33
  "types": "./dist/orpc.d.ts",
34
34
  "default": "./dist/orpc.js"
35
35
  },
36
+ "./orpc/node": {
37
+ "types": "./dist/orpc-node.d.ts",
38
+ "default": "./dist/orpc-node.js"
39
+ },
36
40
  "./cloudflare-pages": {
37
41
  "types": "./dist/cloudflare-pages.d.ts",
38
42
  "default": "./dist/cloudflare-pages.js"
@@ -45,11 +49,12 @@
45
49
  "@orpc/client": "2.0.0-beta.14",
46
50
  "@orpc/contract": "2.0.0-beta.14",
47
51
  "@orpc/server": "2.0.0-beta.14",
48
- "typia": "rc"
52
+ "typia": "13.0.2"
49
53
  },
50
54
  "devDependencies": {
51
- "ttsc": "0.16.9",
52
- "typescript": "7.0.1-rc"
55
+ "@types/node": "^24.0.0",
56
+ "ttsc": "0.18.4",
57
+ "typescript": "7.0.2"
53
58
  },
54
59
  "main": "./dist/index.js",
55
60
  "types": "./dist/index.d.ts",
@@ -61,6 +66,6 @@
61
66
  "lint": "ttsc --noEmit",
62
67
  "format": "ttsc format",
63
68
  "fix": "ttsc fix",
64
- "test": "cd ../.. && node tools/run-ttsx.mjs packages/bridge/src/bridge-protocol.test.ts && node tools/run-ttsx.mjs packages/bridge/src/orpc.test.ts && node tools/run-ttsx.mjs packages/bridge/src/cloudflare-pages.test.ts"
69
+ "test": "cd ../.. && node tools/run-ttsx.mjs packages/bridge/src/bridge-protocol.test.ts && node tools/run-ttsx.mjs packages/bridge/src/orpc.test.ts && node tools/run-ttsx.mjs packages/bridge/src/orpc-node.test.ts && node tools/run-ttsx.mjs packages/bridge/src/cloudflare-pages.test.ts && node packages/bridge/test/orpc-node-dist-import.mjs"
65
70
  }
66
71
  }