@strapi/data-transfer 4.6.0-beta.1 → 4.6.0-beta.2

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.
Files changed (92) hide show
  1. package/{dist → lib}/engine/index.d.ts +7 -1
  2. package/{dist → lib}/engine/index.js +74 -18
  3. package/lib/engine/validation/index.d.ts +1 -0
  4. package/{dist/providers/shared → lib/engine/validation}/index.js +2 -2
  5. package/{dist/strategies → lib/engine/validation/schemas}/index.d.ts +2 -2
  6. package/{dist/strategies → lib/engine/validation/schemas}/index.js +3 -2
  7. package/lib/file/index.d.ts +1 -0
  8. package/lib/file/index.js +28 -0
  9. package/{dist/providers/local-file-destination-provider → lib/file/providers/destination}/index.d.ts +6 -6
  10. package/{dist/providers/local-file-destination-provider → lib/file/providers/destination}/index.js +7 -7
  11. package/{dist/providers/local-file-destination-provider → lib/file/providers/destination}/utils.d.ts +0 -0
  12. package/{dist/providers/local-file-destination-provider → lib/file/providers/destination}/utils.js +0 -0
  13. package/lib/file/providers/index.d.ts +2 -0
  14. package/{dist → lib/file}/providers/index.js +2 -6
  15. package/{dist/providers/local-file-source-provider → lib/file/providers/source}/index.d.ts +9 -8
  16. package/{dist/providers/local-file-source-provider → lib/file/providers/source}/index.js +33 -45
  17. package/lib/index.d.ts +4 -0
  18. package/lib/index.js +31 -0
  19. package/lib/strapi/index.d.ts +4 -0
  20. package/lib/strapi/index.js +35 -0
  21. package/lib/strapi/providers/index.d.ts +3 -0
  22. package/lib/strapi/providers/index.js +22 -0
  23. package/{dist/providers/local-strapi-destination-provider → lib/strapi/providers/local-destination}/index.d.ts +8 -7
  24. package/{dist/providers/local-strapi-destination-provider → lib/strapi/providers/local-destination}/index.js +14 -6
  25. package/{dist/providers/local-strapi-destination-provider → lib/strapi/providers/local-destination}/strategies/index.d.ts +0 -0
  26. package/{dist/providers/local-strapi-destination-provider → lib/strapi/providers/local-destination}/strategies/index.js +0 -0
  27. package/{dist/providers/local-strapi-destination-provider → lib/strapi/providers/local-destination}/strategies/restore/configuration.d.ts +1 -1
  28. package/{dist/providers/local-strapi-destination-provider → lib/strapi/providers/local-destination}/strategies/restore/configuration.js +0 -0
  29. package/{dist/providers/local-strapi-destination-provider → lib/strapi/providers/local-destination}/strategies/restore/entities.d.ts +0 -0
  30. package/{dist/providers/local-strapi-destination-provider → lib/strapi/providers/local-destination}/strategies/restore/entities.js +25 -13
  31. package/{dist/providers/local-strapi-destination-provider → lib/strapi/providers/local-destination}/strategies/restore/index.d.ts +0 -0
  32. package/{dist/providers/local-strapi-destination-provider → lib/strapi/providers/local-destination}/strategies/restore/index.js +2 -2
  33. package/{dist/providers/local-strapi-destination-provider → lib/strapi/providers/local-destination}/strategies/restore/links.d.ts +0 -0
  34. package/{dist/providers/local-strapi-destination-provider → lib/strapi/providers/local-destination}/strategies/restore/links.js +1 -1
  35. package/{dist/providers/local-strapi-source-provider → lib/strapi/providers/local-source}/assets.d.ts +0 -0
  36. package/{dist/providers/local-strapi-source-provider → lib/strapi/providers/local-source}/assets.js +0 -0
  37. package/{dist/providers/local-strapi-source-provider → lib/strapi/providers/local-source}/configuration.d.ts +0 -0
  38. package/{dist/providers/local-strapi-source-provider → lib/strapi/providers/local-source}/configuration.js +12 -13
  39. package/{dist/providers/local-strapi-source-provider → lib/strapi/providers/local-source}/entities.d.ts +0 -0
  40. package/{dist/providers/local-strapi-source-provider → lib/strapi/providers/local-source}/entities.js +11 -4
  41. package/{dist/providers/local-strapi-source-provider → lib/strapi/providers/local-source}/index.d.ts +6 -6
  42. package/{dist/providers/local-strapi-source-provider → lib/strapi/providers/local-source}/index.js +6 -6
  43. package/{dist/providers/local-strapi-source-provider → lib/strapi/providers/local-source}/links.d.ts +0 -0
  44. package/{dist/providers/local-strapi-source-provider → lib/strapi/providers/local-source}/links.js +1 -1
  45. package/lib/strapi/providers/remote-destination/index.d.ts +40 -0
  46. package/lib/strapi/providers/remote-destination/index.js +171 -0
  47. package/lib/strapi/providers/remote-destination/utils.d.ts +31 -0
  48. package/lib/strapi/providers/remote-destination/utils.js +72 -0
  49. package/{dist/providers/shared/strapi → lib/strapi/queries}/entity.d.ts +0 -0
  50. package/{dist/providers/shared/strapi → lib/strapi/queries}/entity.js +0 -0
  51. package/{dist/providers/shared/strapi → lib/strapi/queries}/index.d.ts +0 -0
  52. package/{dist/providers/shared/strapi → lib/strapi/queries}/index.js +0 -0
  53. package/{dist/providers/shared/strapi → lib/strapi/queries}/link.d.ts +1 -1
  54. package/{dist/providers/shared/strapi → lib/strapi/queries}/link.js +0 -0
  55. package/lib/strapi/register.d.ts +7 -0
  56. package/lib/strapi/register.js +13 -0
  57. package/lib/strapi/remote/constants.d.ts +1 -0
  58. package/lib/strapi/remote/constants.js +5 -0
  59. package/lib/strapi/remote/controllers/index.d.ts +1 -0
  60. package/{dist → lib/strapi/remote/controllers}/index.js +1 -2
  61. package/lib/strapi/remote/controllers/push.d.ts +25 -0
  62. package/lib/strapi/remote/controllers/push.js +95 -0
  63. package/lib/strapi/remote/handlers.d.ts +3 -0
  64. package/lib/strapi/remote/handlers.js +177 -0
  65. package/lib/strapi/remote/index.d.ts +3 -0
  66. package/lib/strapi/remote/index.js +30 -0
  67. package/lib/strapi/remote/routes.d.ts +21 -0
  68. package/lib/strapi/remote/routes.js +22 -0
  69. package/lib/utils/encryption/decrypt.d.ts +11 -0
  70. package/{dist → lib/utils}/encryption/decrypt.js +8 -0
  71. package/lib/utils/encryption/encrypt.d.ts +11 -0
  72. package/{dist → lib/utils}/encryption/encrypt.js +8 -0
  73. package/{dist → lib/utils}/encryption/index.d.ts +0 -0
  74. package/{dist → lib/utils}/encryption/index.js +0 -0
  75. package/{dist → lib}/utils/index.d.ts +1 -0
  76. package/{dist → lib}/utils/index.js +2 -1
  77. package/{dist → lib}/utils/json.d.ts +7 -0
  78. package/{dist → lib}/utils/json.js +8 -1
  79. package/{dist → lib}/utils/schema.d.ts +4 -0
  80. package/{dist → lib}/utils/schema.js +9 -2
  81. package/lib/utils/stream.d.ts +27 -0
  82. package/lib/utils/stream.js +59 -0
  83. package/package.json +19 -12
  84. package/dist/encryption/decrypt.d.ts +0 -3
  85. package/dist/encryption/encrypt.d.ts +0 -3
  86. package/dist/index.d.ts +0 -2
  87. package/dist/providers/index.d.ts +0 -4
  88. package/dist/providers/shared/index.d.ts +0 -1
  89. package/dist/providers/test-utils/index.d.ts +0 -111
  90. package/dist/providers/test-utils/index.js +0 -64
  91. package/dist/utils/stream.d.ts +0 -10
  92. package/dist/utils/stream.js +0 -39
@@ -0,0 +1,171 @@
1
+ "use strict";
2
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
3
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
4
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
5
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
6
+ };
7
+ var _RemoteStrapiDestinationProvider_instances, _RemoteStrapiDestinationProvider_streamStep;
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.createRemoteStrapiDestinationProvider = void 0;
10
+ const ws_1 = require("ws");
11
+ const uuid_1 = require("uuid");
12
+ const stream_1 = require("stream");
13
+ const utils_1 = require("./utils");
14
+ const constants_1 = require("../../remote/constants");
15
+ class RemoteStrapiDestinationProvider {
16
+ constructor(options) {
17
+ _RemoteStrapiDestinationProvider_instances.add(this);
18
+ this.name = 'destination::remote-strapi';
19
+ this.type = 'destination';
20
+ this.options = options;
21
+ this.ws = null;
22
+ this.dispatcher = null;
23
+ }
24
+ async initTransfer() {
25
+ const { strategy, restore } = this.options;
26
+ // Wait for the connection to be made to the server, then init the transfer
27
+ return new Promise((resolve, reject) => {
28
+ this.ws
29
+ ?.once('open', async () => {
30
+ const query = this.dispatcher?.dispatchCommand({
31
+ command: 'init',
32
+ params: { options: { strategy, restore }, transfer: 'push' },
33
+ });
34
+ const res = (await query);
35
+ if (!res?.transferID) {
36
+ return reject(new Error('Init failed, invalid response from the server'));
37
+ }
38
+ resolve(res.transferID);
39
+ })
40
+ .once('error', reject);
41
+ });
42
+ }
43
+ async bootstrap() {
44
+ const { url, auth } = this.options;
45
+ let ws;
46
+ if (!['https:', 'http:'].includes(url.protocol)) {
47
+ throw new Error(`Invalid protocol "${url.protocol}"`);
48
+ }
49
+ const wsProtocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
50
+ const wsUrl = `${wsProtocol}//${url.host}${url.pathname}${constants_1.TRANSFER_PATH}`;
51
+ // No auth defined, trying public access for transfer
52
+ if (!auth) {
53
+ ws = new ws_1.WebSocket(wsUrl);
54
+ }
55
+ // Common token auth, this should be the main auth method
56
+ else if (auth.type === 'token') {
57
+ const headers = { Authorization: `Bearer ${auth.token}` };
58
+ ws = new ws_1.WebSocket(wsUrl, { headers });
59
+ }
60
+ // Invalid auth method provided
61
+ else {
62
+ throw new Error('Auth method not implemented');
63
+ }
64
+ this.ws = ws;
65
+ this.dispatcher = (0, utils_1.createDispatcher)(this.ws);
66
+ const transferID = await this.initTransfer();
67
+ this.dispatcher.setTransferProperties({ id: transferID, kind: 'push' });
68
+ await this.dispatcher.dispatchTransferAction('bootstrap');
69
+ }
70
+ async close() {
71
+ await this.dispatcher?.dispatchTransferAction('close');
72
+ await new Promise((resolve) => {
73
+ const { ws } = this;
74
+ if (!ws || ws.CLOSED) {
75
+ resolve();
76
+ return;
77
+ }
78
+ ws.on('close', () => resolve()).close();
79
+ });
80
+ }
81
+ getMetadata() {
82
+ return this.dispatcher?.dispatchTransferAction('getMetadata') ?? null;
83
+ }
84
+ async beforeTransfer() {
85
+ await this.dispatcher?.dispatchTransferAction('beforeTransfer');
86
+ }
87
+ getSchemas() {
88
+ if (!this.dispatcher) {
89
+ return Promise.resolve(null);
90
+ }
91
+ return this.dispatcher.dispatchTransferAction('getSchemas');
92
+ }
93
+ createEntitiesWriteStream() {
94
+ return new stream_1.Writable({
95
+ objectMode: true,
96
+ write: async (entity, _encoding, callback) => {
97
+ const e = await __classPrivateFieldGet(this, _RemoteStrapiDestinationProvider_instances, "m", _RemoteStrapiDestinationProvider_streamStep).call(this, 'entities', entity);
98
+ callback(e);
99
+ },
100
+ });
101
+ }
102
+ createLinksWriteStream() {
103
+ return new stream_1.Writable({
104
+ objectMode: true,
105
+ write: async (link, _encoding, callback) => {
106
+ const e = await __classPrivateFieldGet(this, _RemoteStrapiDestinationProvider_instances, "m", _RemoteStrapiDestinationProvider_streamStep).call(this, 'links', link);
107
+ callback(e);
108
+ },
109
+ });
110
+ }
111
+ createConfigurationWriteStream() {
112
+ return new stream_1.Writable({
113
+ objectMode: true,
114
+ write: async (configuration, _encoding, callback) => {
115
+ const e = await __classPrivateFieldGet(this, _RemoteStrapiDestinationProvider_instances, "m", _RemoteStrapiDestinationProvider_streamStep).call(this, 'configuration', configuration);
116
+ callback(e);
117
+ },
118
+ });
119
+ }
120
+ createAssetsWriteStream() {
121
+ return new stream_1.Writable({
122
+ objectMode: true,
123
+ final: async (callback) => {
124
+ // TODO: replace this stream call by an end call
125
+ const e = await __classPrivateFieldGet(this, _RemoteStrapiDestinationProvider_instances, "m", _RemoteStrapiDestinationProvider_streamStep).call(this, 'assets', null);
126
+ callback(e);
127
+ },
128
+ write: async (asset, _encoding, callback) => {
129
+ const { filename, filepath, stats, stream } = asset;
130
+ const assetID = (0, uuid_1.v4)();
131
+ await __classPrivateFieldGet(this, _RemoteStrapiDestinationProvider_instances, "m", _RemoteStrapiDestinationProvider_streamStep).call(this, 'assets', {
132
+ action: 'start',
133
+ assetID,
134
+ data: { filename, filepath, stats },
135
+ });
136
+ for await (const chunk of stream) {
137
+ await __classPrivateFieldGet(this, _RemoteStrapiDestinationProvider_instances, "m", _RemoteStrapiDestinationProvider_streamStep).call(this, 'assets', {
138
+ action: 'stream',
139
+ assetID,
140
+ data: chunk,
141
+ });
142
+ }
143
+ await __classPrivateFieldGet(this, _RemoteStrapiDestinationProvider_instances, "m", _RemoteStrapiDestinationProvider_streamStep).call(this, 'assets', {
144
+ action: 'end',
145
+ assetID,
146
+ });
147
+ callback();
148
+ },
149
+ });
150
+ }
151
+ }
152
+ _RemoteStrapiDestinationProvider_instances = new WeakSet(), _RemoteStrapiDestinationProvider_streamStep = async function _RemoteStrapiDestinationProvider_streamStep(step, data) {
153
+ try {
154
+ await this.dispatcher?.dispatchTransferStep({ action: 'stream', step, data });
155
+ }
156
+ catch (e) {
157
+ if (e instanceof Error) {
158
+ return e;
159
+ }
160
+ if (typeof e === 'string') {
161
+ return new Error(e);
162
+ }
163
+ return new Error('Unexpected error');
164
+ }
165
+ return null;
166
+ };
167
+ const createRemoteStrapiDestinationProvider = (options) => {
168
+ return new RemoteStrapiDestinationProvider(options);
169
+ };
170
+ exports.createRemoteStrapiDestinationProvider = createRemoteStrapiDestinationProvider;
171
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,31 @@
1
+ import { WebSocket } from 'ws';
2
+ import type { client } from '../../../../types/remote/protocol';
3
+ interface IDispatcherState {
4
+ transfer?: {
5
+ kind: client.TransferKind;
6
+ id: string;
7
+ };
8
+ }
9
+ interface IDispatchOptions {
10
+ attachTransfer?: boolean;
11
+ }
12
+ declare type Dispatch<T> = Omit<T, 'transferID' | 'uuid'>;
13
+ declare const createDispatcher: (ws: WebSocket) => {
14
+ readonly transferID: string | undefined;
15
+ readonly transferKind: "push" | "pull" | undefined;
16
+ setTransferProperties: (properties: Exclude<IDispatcherState['transfer'], undefined>) => void;
17
+ dispatch: <U = null>(message: Dispatch<client.Message>, options?: IDispatchOptions) => Promise<U | null>;
18
+ dispatchCommand: <U_1 extends "end" | "init" | "status">(payload: {
19
+ command: U_1;
20
+ } & ([client.GetCommandParams<U_1>] extends [never] ? unknown : {
21
+ params: client.GetCommandParams<U_1>;
22
+ })) => Promise<null>;
23
+ dispatchTransferAction: <T>(action: client.Action['action']) => Promise<T | null>;
24
+ dispatchTransferStep: <T_1, A extends "stream" | "end" | "start" = "stream" | "end" | "start", S extends "entities" | "links" | "configuration" | "assets" = "entities" | "links" | "configuration" | "assets">(payload: {
25
+ step: S;
26
+ action: A;
27
+ } & (A extends "stream" ? {
28
+ data: client.GetTransferPushStreamData<S>;
29
+ } : unknown)) => Promise<T_1 | null>;
30
+ };
31
+ export { createDispatcher };
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createDispatcher = void 0;
4
+ const uuid_1 = require("uuid");
5
+ const createDispatcher = (ws) => {
6
+ const state = {};
7
+ const dispatch = async (message, options = {}) => {
8
+ if (!ws) {
9
+ throw new Error('No websocket connection found');
10
+ }
11
+ return new Promise((resolve, reject) => {
12
+ const uuid = (0, uuid_1.v4)();
13
+ const payload = { ...message, uuid };
14
+ if (options.attachTransfer) {
15
+ Object.assign(payload, { transferID: state.transfer?.id });
16
+ }
17
+ const stringifiedPayload = JSON.stringify(payload);
18
+ ws.send(stringifiedPayload, (error) => {
19
+ if (error) {
20
+ reject(error);
21
+ }
22
+ });
23
+ const onResponse = (raw) => {
24
+ const response = JSON.parse(raw.toString());
25
+ if (response.uuid === uuid) {
26
+ if (response.error) {
27
+ return reject(new Error(response.error.message));
28
+ }
29
+ resolve(response.data ?? null);
30
+ }
31
+ else {
32
+ ws.once('message', onResponse);
33
+ }
34
+ };
35
+ // TODO: What happens if the server sends another message (not a response to this message)
36
+ ws.once('message', onResponse);
37
+ });
38
+ };
39
+ const dispatchCommand = (payload) => {
40
+ return dispatch({ type: 'command', ...payload });
41
+ };
42
+ const dispatchTransferAction = async (action) => {
43
+ const payload = { type: 'transfer', kind: 'action', action };
44
+ return dispatch(payload, { attachTransfer: true }) ?? Promise.resolve(null);
45
+ };
46
+ const dispatchTransferStep = async (payload) => {
47
+ const message = {
48
+ type: 'transfer',
49
+ kind: 'step',
50
+ ...payload,
51
+ };
52
+ return dispatch(message, { attachTransfer: true }) ?? Promise.resolve(null);
53
+ };
54
+ const setTransferProperties = (properties) => {
55
+ state.transfer = { ...properties };
56
+ };
57
+ return {
58
+ get transferID() {
59
+ return state.transfer?.id;
60
+ },
61
+ get transferKind() {
62
+ return state.transfer?.kind;
63
+ },
64
+ setTransferProperties,
65
+ dispatch,
66
+ dispatchCommand,
67
+ dispatchTransferAction,
68
+ dispatchTransferStep,
69
+ };
70
+ };
71
+ exports.createDispatcher = createDispatcher;
72
+ //# sourceMappingURL=utils.js.map
@@ -1,4 +1,4 @@
1
- import { ILink } from '../../../../types';
1
+ import { ILink } from '../../../types';
2
2
  export declare const createLinkQuery: (strapi: Strapi.Strapi) => () => {
3
3
  generateAll: (uid: string) => AsyncGenerator<ILink>;
4
4
  generateAllForAttribute: (uid: string, fieldName: string) => AsyncGenerator<ILink>;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * This is intended to be called on Strapi register phase.
3
+ *
4
+ * It registers a transfer route in the Strapi admin router.
5
+ */
6
+ declare const register: (strapi: Strapi.Strapi) => void;
7
+ export default register;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const remote_1 = require("./remote");
4
+ /**
5
+ * This is intended to be called on Strapi register phase.
6
+ *
7
+ * It registers a transfer route in the Strapi admin router.
8
+ */
9
+ const register = (strapi) => {
10
+ remote_1.routes.registerAdminTransferRoute(strapi);
11
+ };
12
+ exports.default = register;
13
+ //# sourceMappingURL=register.js.map
@@ -0,0 +1 @@
1
+ export declare const TRANSFER_PATH = "/transfer";
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TRANSFER_PATH = void 0;
4
+ exports.TRANSFER_PATH = '/transfer';
5
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ export * from './push';
@@ -14,6 +14,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- __exportStar(require("./engine"), exports);
18
- __exportStar(require("./providers"), exports);
17
+ __exportStar(require("./push"), exports);
19
18
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,25 @@
1
+ /// <reference types="node" />
2
+ import { Writable } from 'stream-chain';
3
+ import type { IMetadata } from '../../../../types';
4
+ import type { TransferPushMessage, TransferPushStep } from '../../../../types/remote/protocol/client';
5
+ import { ILocalStrapiDestinationProviderOptions } from '../../providers';
6
+ export interface IPushController {
7
+ streams: {
8
+ [stage in TransferPushStep]?: Writable;
9
+ };
10
+ actions: {
11
+ getMetadata(): Promise<IMetadata>;
12
+ getSchemas(): Strapi.Schemas;
13
+ bootstrap(): Promise<void>;
14
+ close(): Promise<void>;
15
+ beforeTransfer(): Promise<void>;
16
+ };
17
+ transfer: {
18
+ [key in TransferPushStep]: <T extends TransferPushMessage>(value: T extends {
19
+ step: key;
20
+ data: infer U;
21
+ } ? U : never) => Promise<void>;
22
+ };
23
+ }
24
+ declare const createPushController: (options: ILocalStrapiDestinationProviderOptions) => IPushController;
25
+ export default createPushController;
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const stream_chain_1 = require("stream-chain");
4
+ const providers_1 = require("../../providers");
5
+ const createPushController = (options) => {
6
+ const provider = (0, providers_1.createLocalStrapiDestinationProvider)(options);
7
+ const streams = {};
8
+ const assets = {};
9
+ const writeAsync = (stream, data) => {
10
+ return new Promise((resolve, reject) => {
11
+ stream.write(data, (error) => {
12
+ if (error) {
13
+ reject(error);
14
+ }
15
+ resolve();
16
+ });
17
+ });
18
+ };
19
+ return {
20
+ streams,
21
+ actions: {
22
+ async getSchemas() {
23
+ return provider.getSchemas();
24
+ },
25
+ async getMetadata() {
26
+ return provider.getMetadata();
27
+ },
28
+ async bootstrap() {
29
+ return provider.bootstrap();
30
+ },
31
+ async close() {
32
+ return provider.close();
33
+ },
34
+ async beforeTransfer() {
35
+ return provider.beforeTransfer();
36
+ },
37
+ },
38
+ transfer: {
39
+ async entities(entity) {
40
+ if (!streams.entities) {
41
+ streams.entities = provider.createEntitiesWriteStream();
42
+ }
43
+ await writeAsync(streams.entities, entity);
44
+ },
45
+ async links(link) {
46
+ if (!streams.links) {
47
+ streams.links = await provider.createLinksWriteStream();
48
+ }
49
+ await writeAsync(streams.links, link);
50
+ },
51
+ async configuration(config) {
52
+ if (!streams.configuration) {
53
+ streams.configuration = await provider.createConfigurationWriteStream();
54
+ }
55
+ await writeAsync(streams.configuration, config);
56
+ },
57
+ async assets(payload) {
58
+ // TODO: close the stream upong receiving an 'end' event instead
59
+ if (payload === null) {
60
+ streams.assets?.end();
61
+ return;
62
+ }
63
+ const { action, assetID } = payload;
64
+ if (!streams.assets) {
65
+ streams.assets = await provider.createAssetsWriteStream();
66
+ }
67
+ if (action === 'start') {
68
+ assets[assetID] = { ...payload.data, stream: new stream_chain_1.PassThrough() };
69
+ writeAsync(streams.assets, assets[assetID]);
70
+ }
71
+ if (action === 'stream') {
72
+ // The buffer has gone through JSON operations and is now of shape { type: "Buffer"; data: UInt8Array }
73
+ // We need to transform it back into a Buffer instance
74
+ const rawBuffer = payload.data;
75
+ const chunk = Buffer.from(rawBuffer.data);
76
+ await writeAsync(assets[assetID].stream, chunk);
77
+ }
78
+ if (action === 'end') {
79
+ await new Promise((resolve, reject) => {
80
+ const { stream } = assets[assetID];
81
+ stream
82
+ .on('close', () => {
83
+ delete assets[assetID];
84
+ resolve();
85
+ })
86
+ .on('error', reject)
87
+ .end();
88
+ });
89
+ }
90
+ },
91
+ },
92
+ };
93
+ };
94
+ exports.default = createPushController;
95
+ //# sourceMappingURL=push.js.map
@@ -0,0 +1,3 @@
1
+ import type { Context } from 'koa';
2
+ import type { ServerOptions } from 'ws';
3
+ export declare const createTransferHandler: (options?: ServerOptions) => (ctx: Context) => Promise<void>;
@@ -0,0 +1,177 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createTransferHandler = void 0;
7
+ const crypto_1 = require("crypto");
8
+ const ws_1 = require("ws");
9
+ const push_1 = __importDefault(require("./controllers/push"));
10
+ const createTransferHandler = (options = {}) => async (ctx) => {
11
+ const upgradeHeader = (ctx.request.headers.upgrade || '')
12
+ .split(',')
13
+ .map((s) => s.trim().toLowerCase());
14
+ // Create the websocket server
15
+ const wss = new ws_1.WebSocket.Server({ ...options, noServer: true });
16
+ if (upgradeHeader.includes('websocket')) {
17
+ wss.handleUpgrade(ctx.req, ctx.request.socket, Buffer.alloc(0), (ws) => {
18
+ // Create a connection between the client & the server
19
+ wss.emit('connection', ws, ctx.req);
20
+ const state = {};
21
+ let uuid;
22
+ /**
23
+ * Format error & message to follow the remote transfer protocol
24
+ */
25
+ const callback = (e = null, data) => {
26
+ return new Promise((resolve, reject) => {
27
+ if (!uuid) {
28
+ reject(new Error('Missing uuid for this message'));
29
+ return;
30
+ }
31
+ const payload = JSON.stringify({
32
+ uuid,
33
+ data: data ?? null,
34
+ error: e
35
+ ? {
36
+ code: 'ERR',
37
+ message: e?.message,
38
+ }
39
+ : null,
40
+ });
41
+ ws.send(payload, (error) => (error ? reject(error) : resolve()));
42
+ });
43
+ };
44
+ /**
45
+ * Wrap a function call to catch errors and answer the request with the correct format
46
+ */
47
+ const answer = async (fn) => {
48
+ try {
49
+ const response = await fn();
50
+ callback(null, response);
51
+ }
52
+ catch (e) {
53
+ if (e instanceof Error) {
54
+ callback(e);
55
+ }
56
+ else if (typeof e === 'string') {
57
+ callback(new Error(e));
58
+ }
59
+ else {
60
+ callback(new Error('Unexpected error'));
61
+ }
62
+ }
63
+ };
64
+ const teardown = () => {
65
+ delete state.controller;
66
+ delete state.transfer;
67
+ return { ok: true };
68
+ };
69
+ const init = (msg) => {
70
+ if (state.controller) {
71
+ throw new Error('Transfer already in progres');
72
+ }
73
+ const { transfer } = msg.params;
74
+ // Push transfer
75
+ if (transfer === 'push') {
76
+ const { options: controllerOptions } = msg.params;
77
+ state.controller = (0, push_1.default)({
78
+ ...controllerOptions,
79
+ autoDestroy: false,
80
+ getStrapi: () => strapi,
81
+ });
82
+ }
83
+ // Pull or any other string
84
+ else {
85
+ throw new Error(`Transfer not implemented: "${transfer}"`);
86
+ }
87
+ state.transfer = { id: (0, crypto_1.randomUUID)(), kind: transfer };
88
+ return { transferID: state.transfer.id };
89
+ };
90
+ /**
91
+ * On command message (init, end, status, ...)
92
+ */
93
+ const onCommand = async (msg) => {
94
+ const { command } = msg;
95
+ if (command === 'init') {
96
+ await answer(() => init(msg));
97
+ }
98
+ if (command === 'end') {
99
+ await answer(teardown);
100
+ }
101
+ if (command === 'status') {
102
+ await callback(new Error('Command not implemented: "status"'));
103
+ }
104
+ };
105
+ const onTransferCommand = async (msg) => {
106
+ const { transferID, kind } = msg;
107
+ const { controller } = state;
108
+ // TODO: (re)move this check
109
+ // It shouldn't be possible to strart a pull transfer for now, so reaching
110
+ // this code should be impossible too, but this has been added by security
111
+ if (state.transfer?.kind === 'pull') {
112
+ return callback(new Error('Pull transfer not implemented'));
113
+ }
114
+ if (!controller) {
115
+ return callback(new Error("The transfer hasn't been initialized"));
116
+ }
117
+ if (!transferID) {
118
+ return callback(new Error('Missing transfer ID'));
119
+ }
120
+ // Action
121
+ if (kind === 'action') {
122
+ const { action } = msg;
123
+ if (!(action in controller.actions)) {
124
+ return callback(new Error(`Invalid action provided: "${action}"`));
125
+ }
126
+ await answer(() => controller.actions[action]());
127
+ }
128
+ // Transfer
129
+ else if (kind === 'step') {
130
+ // We can only have push transfer message for the moment
131
+ const message = msg;
132
+ // TODO: lock transfer process
133
+ if (message.action === 'start') {
134
+ // console.log('Starting transfer for ', message.step);
135
+ }
136
+ // Stream step
137
+ else if (message.action === 'stream') {
138
+ await answer(() => controller.transfer[message.step]?.(message.data));
139
+ }
140
+ // TODO: unlock transfer process
141
+ else if (message.action === 'end') {
142
+ // console.log('Ending transfer for ', message.step);
143
+ }
144
+ }
145
+ };
146
+ ws.on('close', () => {
147
+ teardown();
148
+ });
149
+ ws.on('error', (e) => {
150
+ teardown();
151
+ console.error(e);
152
+ });
153
+ ws.on('message', async (raw) => {
154
+ const msg = JSON.parse(raw.toString());
155
+ if (!msg.uuid) {
156
+ throw new Error('Missing uuid in message');
157
+ }
158
+ uuid = msg.uuid;
159
+ // Regular command message (init, end, status)
160
+ if (msg.type === 'command') {
161
+ await onCommand(msg);
162
+ }
163
+ // Transfer message (the transfer must be initialized first)
164
+ else if (msg.type === 'transfer') {
165
+ await onTransferCommand(msg);
166
+ }
167
+ // Invalid messages
168
+ else {
169
+ await callback(new Error('Bad request'));
170
+ }
171
+ });
172
+ });
173
+ ctx.respond = false;
174
+ }
175
+ };
176
+ exports.createTransferHandler = createTransferHandler;
177
+ //# sourceMappingURL=handlers.js.map
@@ -0,0 +1,3 @@
1
+ export * as controllers from './controllers';
2
+ export * as routes from './routes';
3
+ export * as constants from './constants';