diff --git a/Makefile b/Makefile
index d67863bd..bf9f001b 100644
--- a/Makefile
+++ b/Makefile
@@ -144,13 +144,14 @@ clean_core_js:
##############
build_packages: build_python_host build_nodejs_host build_cfw_host
deps_packages: deps_python_host deps_nodejs_host deps_cfw_host
+test_packages: test_nodejs_host test_cfw_host test_python_host
# Node.js Host
deps_nodejs_host:
cd packages/nodejs_host && yarn install
build_nodejs_host: deps_nodejs_host ${CORE_ASYNCIFY_WASM}
mkdir -p ${NODEJS_HOST_ASSETS}
- cp ${CORE_ASYNCIFY_WASM} ${NODEJS_HOST_ASSETS}/core-async.wasm # copy wasm always because cached docker artifacts can have older timestamp
+ cp ${CORE_ASYNCIFY_WASM} ${NODEJS_HOST_ASSETS}/core-async.wasm
cd packages/nodejs_host && yarn build
test_nodejs_host: build_nodejs_host ${TEST_CORE_ASYNCIFY_WASM}
cp ${TEST_CORE_ASYNCIFY_WASM} ${NODEJS_HOST_ASSETS}/test-core-async.wasm
@@ -161,7 +162,7 @@ deps_cfw_host:
cd packages/cloudflare_worker_host && yarn install
build_cfw_host: deps_cfw_host ${CORE_ASYNCIFY_WASM}
mkdir -p ${CFW_HOST_ASSETS}
- cp ${CORE_ASYNCIFY_WASM} ${CFW_HOST_ASSETS}/core-async.wasm # copy wasm always because cached docker artifacts can have older timestamp
+ cp ${CORE_ASYNCIFY_WASM} ${CFW_HOST_ASSETS}/core-async.wasm
cd packages/cloudflare_worker_host && yarn build
test_cfw_host: build_cfw_host ${TEST_CORE_ASYNCIFY_WASM}
cp ${TEST_CORE_ASYNCIFY_WASM} ${CFW_HOST_ASSETS}/test-core-async.wasm
diff --git a/core/core/src/sf_core/metrics.rs b/core/core/src/sf_core/metrics.rs
index 2ae456d2..ee129a06 100644
--- a/core/core/src/sf_core/metrics.rs
+++ b/core/core/src/sf_core/metrics.rs
@@ -28,26 +28,29 @@ impl<'a> PerformMetricsData<'a> {
pub fn get_profile(&self) -> Cow<'_, str> {
match self.profile {
Some(profile) => Cow::Borrowed(profile),
- None => Cow::Owned(
- self.profile_url
- .split('/')
- .last()
- .and_then(|b| b.strip_suffix(".profile"))
- .unwrap()
- .replace('.', "/"),
- ),
+ None => match self.profile_url.split('/').last() {
+ // treat anything from .profile until the end of the url as an extension
+ // this works for both .profile and .profile.ts extensions
+ Some(last_segment) => match last_segment.rfind(".profile") {
+ Some(ext_start) => Cow::Owned(last_segment[..ext_start].replace('.', "/")),
+ None => Cow::Borrowed("unknown"),
+ },
+ None => Cow::Borrowed("unknown"),
+ },
}
}
pub fn get_provider(&self) -> &str {
match self.provider {
Some(provider) => provider,
- None => self
- .provider_url
- .split('/')
- .last()
- .and_then(|b| b.strip_suffix(".provider.json"))
- .unwrap(),
+ None => match self.profile_url.split('/').last() {
+ // treat anything from .provider until the end of the url as an extension
+ Some(last_segment) => match last_segment.rfind(".provider") {
+ Some(ext_start) => &last_segment[..ext_start],
+ None => "unknown",
+ },
+ None => "unknown",
+ },
}
}
}
diff --git a/examples/cloudflare_worker/src/comlinks.ts b/examples/cloudflare_worker/src/comlinks.ts
index ff0493d9..d9dda53f 100644
--- a/examples/cloudflare_worker/src/comlinks.ts
+++ b/examples/cloudflare_worker/src/comlinks.ts
@@ -15,7 +15,7 @@ import mapSendSms from '../superface/communication.send-sms.twilio.map.js';
import providerTwilio from '../superface/twilio.provider.json';
// @ts-ignore
-import profileExample from '../superface/wasm-sdk.example.profile';
+import profileExample from '../superface/wasm-sdk.example.profile.ts';
// @ts-ignore
import mapExample from '../superface/wasm-sdk.example.localhost.map.js';
// @ts-ignore
@@ -28,7 +28,7 @@ export const COMLINK_IMPORTS = {
'superface/communication.send-sms.profile': new Uint8Array(profileSendSms),
'superface/communication.send-sms.twilio.map.js': new Uint8Array(mapSendSms),
'superface/twilio.provider.json': new Uint8Array(providerTwilio as any),
- 'superface/wasm-sdk.example.profile': new Uint8Array(profileExample),
+ 'superface/wasm-sdk.example.profile.ts': new Uint8Array(profileExample),
'superface/wasm-sdk.example.localhost.map.js': new Uint8Array(mapExample),
'superface/localhost.provider.json': new Uint8Array(providerLocalhost as any)
};
diff --git a/examples/comlinks/src/wasm-sdk.example.localhost.map.js b/examples/comlinks/src/wasm-sdk.example.localhost.map.js
index 689ca804..833718b5 100644
--- a/examples/comlinks/src/wasm-sdk.example.localhost.map.js
+++ b/examples/comlinks/src/wasm-sdk.example.localhost.map.js
@@ -1,4 +1,5 @@
///
+///
// @ts-check
const manifest = {
@@ -6,9 +7,8 @@ const manifest = {
provider: 'localhost'
};
-// var to hoist it like a function would be
-// can't be a function because then the type annotation doesn't work
-/** @type {Usecase<{ safety: 'safe', input: { id: AnyValue }, result: { url: AnyValue, method: AnyValue, query: AnyValue, headers: AnyValue }, error: { title: string, detail: string } }>} */
+// var to hoist it like a function would be - can't be a function because then the type annotation doesn't work
+/** @type {Example} */
var Example = ({ input, parameters, services }) => {
// @ts-ignore
__ffi.unstable.printDebug('Input:', input);
diff --git a/examples/comlinks/src/wasm-sdk.example.profile b/examples/comlinks/src/wasm-sdk.example.profile
deleted file mode 100644
index 92fa96d5..00000000
--- a/examples/comlinks/src/wasm-sdk.example.profile
+++ /dev/null
@@ -1,20 +0,0 @@
-name = "wasm-sdk/example"
-version = "0.1.0"
-
-usecase Example {
- input {
- id!
- }
-
- result {
- url!
- method!
- query!
- headers!
- }
-
- error {
- title!
- detail
- }
-}
\ No newline at end of file
diff --git a/examples/comlinks/src/wasm-sdk.example.profile.ts b/examples/comlinks/src/wasm-sdk.example.profile.ts
new file mode 100644
index 00000000..fc4d3993
--- /dev/null
+++ b/examples/comlinks/src/wasm-sdk.example.profile.ts
@@ -0,0 +1,7 @@
+///
+type Example = Usecase<{
+ safety: 'safe'
+ input: { id: AnyValue }
+ result: { url: AnyValue, method: AnyValue, query: AnyValue, headers: AnyValue }
+ error: { title: AnyValue, detail?: AnyValue }
+}>;
diff --git a/examples/run.sh b/examples/run.sh
index 956f3331..56989714 100755
--- a/examples/run.sh
+++ b/examples/run.sh
@@ -22,7 +22,7 @@ case $1 in
make build_cfw_host $MAKE_FLAGS
fi
cd "$base/cloudflare_worker"
- yarn install
+ yarn install --force
yarn dev
;;
diff --git a/packages/cloudflare_worker_host/src/index.ts b/packages/cloudflare_worker_host/src/index.ts
index 3609cc04..76ce5c9a 100644
--- a/packages/cloudflare_worker_host/src/index.ts
+++ b/packages/cloudflare_worker_host/src/index.ts
@@ -31,6 +31,10 @@ class CfwFileSystem implements FileSystem {
this.files = new HandleMap();
}
+ async exists(path: string): Promise {
+ return this.preopens[path] !== undefined;
+ }
+
async open(path: string, options: { createNew?: boolean, create?: boolean, truncate?: boolean, append?: boolean, write?: boolean, read?: boolean }): Promise {
if (options.read !== true) {
throw new WasiError(WasiErrno.EROFS);
@@ -88,7 +92,11 @@ class CfwNetwork implements Network {
try {
response = await fetch(input, init);
} catch (err: unknown) {
- throw err; // TODO: are there any errors that we need to handle here?
+ if (typeof err === 'object' && err !== null && 'message' in err) {
+ // found a `Error: Network connection lost` caused by `kj/async-io-unix.c++:186: disconnected` in the wild
+ throw new HostError(ErrorCode.NetworkError, `${err.message}`);
+ }
+ throw err;
}
if (response.status === 530) {
@@ -275,10 +283,12 @@ export type ClientPerformOptions = {
class InternalClient {
private readonly app: App;
private ready = false;
+ private readonly fileSystem: CfwFileSystem;
constructor(readonly options: ClientOptions = {}) {
+ this.fileSystem = new CfwFileSystem(options.preopens ?? {});
this.app = new App({
- fileSystem: new CfwFileSystem(options.preopens ?? {}),
+ fileSystem: this.fileSystem,
textCoder: new CfwTextCoder(),
timers: new CfwTimers(),
network: new CfwNetwork(),
@@ -324,9 +334,20 @@ class InternalClient {
const resolvedProfile = profile.replace(/\//g, '.'); // TODO: be smarter about this
const assetsPath = this.options.assetsPath ?? 'superface'; // TODO: path join? - not sure if we are going to stick with this VFS
+ let profilePath = `${assetsPath}/${resolvedProfile}.profile.ts`;
+ // migration from Comlink to TypeScript profiles
+ const profilePathComlink = `${assetsPath}/${resolvedProfile}.profile`;
+ if (
+ !(await this.fileSystem.exists(profilePath))
+ && (await this.fileSystem.exists(profilePathComlink))
+ ) {
+ profilePath = profilePathComlink;
+ }
+ profilePath = `file://${profilePath}`;
+
try {
return await this.app.perform(
- `file://${assetsPath}/${resolvedProfile}.profile`,
+ profilePath,
`file://${assetsPath}/${provider}.provider.json`,
`file://${assetsPath}/${resolvedProfile}.${provider}.map.js`,
usecase,
diff --git a/packages/javascript_common/src/app.ts b/packages/javascript_common/src/app.ts
index f3d26eda..63a384f2 100644
--- a/packages/javascript_common/src/app.ts
+++ b/packages/javascript_common/src/app.ts
@@ -1,108 +1,9 @@
-import { Asyncify } from './asyncify.js';
+import type { SecurityValuesMap } from './security.js';
+import type { AppContext, FileSystem, Network, Persistence, TextCoder, Timers, WasiContext } from './interfaces.js';
import { PerformError, UnexpectedError, UninitializedError, ValidationError, WasiErrno, WasiError } from './error.js';
-import { HandleMap } from './handle_map.js';
-import { AppContext, FileSystem, Network, Persistence, TextCoder, Timers, WasiContext } from './interfaces.js';
-import { SecurityValuesMap } from './security.js';
+import { AsyncMutex, Asyncify, HandleMap, ReadableStreamAdapter, Stream } from './lib/index.js';
import * as sf_host from './sf_host.js';
-class ReadableStreamAdapter implements Stream {
- private chunks: Uint8Array[];
- private readonly reader?: ReadableStreamDefaultReader;
- constructor(stream: ReadableStream | null) {
- this.reader = stream?.getReader();
- this.chunks = [];
- }
- async read(out: Uint8Array): Promise {
- if (this.reader === undefined) {
- return 0;
- }
-
- if (this.chunks.length === 0) {
- const readResult = await this.reader.read();
- if (readResult.value === undefined) {
- return 0;
- }
-
- this.chunks.push(readResult.value);
- }
-
- // TODO: coalesce multiple smaller chunks into one read
- let chunk = this.chunks.shift()!;
- if (chunk.byteLength > out.byteLength) {
- const remaining = chunk.subarray(out.byteLength);
- chunk = chunk.subarray(0, out.byteLength);
-
- this.chunks.unshift(remaining);
- }
-
- const count = Math.min(chunk.byteLength, out.byteLength);
- for (let i = 0; i < count; i += 1) {
- out[i] = chunk[i];
- }
-
- return count;
- }
- async write(data: Uint8Array): Promise {
- throw new Error('not implemented');
- }
- async close(): Promise {
- // TODO: what to do here?
- }
-}
-
-/** Async mutex allows us to synchronize multiple async tasks.
- *
- * For example, if a perform is in-flight but is waiting for I/O the async task is suspended. If at the same time
- * the periodic timer fires, this could cause core to be invoked twice within the same asyncify context, causing undefined behavior.
- *
- * We can avoid this by synchronizing over core.
- *
- * Note that this is not thread safe (concurrent), but merely task safe (asynchronous).
- */
-export class AsyncMutex {
- /** Promise to be awaited to synchronize between tasks. */
- private condvar: Promise;
- /** Indicator of whether the mutex is currently locked. */
- private isLocked: boolean;
- private value: T;
-
- constructor(value: T) {
- this.condvar = Promise.resolve();
- this.isLocked = false;
- this.value = value;
- }
-
- /**
- * Get the protected value without respecting the lock.
- *
- * This is unsafe, but it is needed to get access to memory in sf_host imports.
- */
- get unsafeValue(): T {
- return this.value;
- }
-
- public async withLock(fn: (value: T) => R): Promise> {
- do {
- // Under the assumption that we do not have concurrency it can never happen that two tasks
- // pass over the condition of this loop and think they both have a lock - that would imply there exists task preemption in synchronous code.
- //
- // If there ever is threading or task preemption, we will need to use other means (atomics, spinlocks).
- await this.condvar;
- } while (this.isLocked);
-
- this.isLocked = true;
- let notify: () => void;
- this.condvar = new Promise((resolve) => { notify = resolve; });
-
- try {
- return await fn(this.value);
- } finally {
- this.isLocked = false;
- notify!();
- }
- }
-}
-
function headersToMultimap(headers: Headers): Record {
const result: Record = {};
@@ -118,14 +19,6 @@ function headersToMultimap(headers: Headers): Record {
return result;
}
-type Stream = {
- /** Reads up to `out.length` bytes from the stream, returns number of bytes read or throws a `WasiError`. */
- read(out: Uint8Array): Promise;
- /** Writes up to `data.length` bytes into the stream, returns number of bytes written or throws a `WasiError`. */
- write(data: Uint8Array): Promise;
- /** Closes the stream, returns undefined or throws a `WasiError`. */
- close(): Promise;
-};
type AppCore = {
instance: WebAssembly.Instance;
asyncify: Asyncify;
diff --git a/packages/javascript_common/src/index.ts b/packages/javascript_common/src/index.ts
index 78a5c031..3b0758b9 100644
--- a/packages/javascript_common/src/index.ts
+++ b/packages/javascript_common/src/index.ts
@@ -1,6 +1,5 @@
-export { App, AsyncMutex } from './app.js';
+export { App } from './app.js';
export * from './error.js';
-export { HandleMap } from './handle_map.js';
export type { FileSystem, Network, Persistence, TextCoder, Timers, WasiContext } from './interfaces.js';
-export { SecurityValuesMap } from './security.js';
-export * from './wasm.js';
+export type { SecurityValuesMap } from './security.js';
+export { HandleMap, AsyncMutex, corePathURL } from './lib/index.js';
\ No newline at end of file
diff --git a/packages/javascript_common/src/interfaces.ts b/packages/javascript_common/src/interfaces.ts
index 5dd9a579..d05e05c7 100644
--- a/packages/javascript_common/src/interfaces.ts
+++ b/packages/javascript_common/src/interfaces.ts
@@ -8,6 +8,8 @@ export interface AppContext {
closeStream(handle: number): Promise;
}
export interface FileSystem {
+ /** Return true if the file exists (can be `stat`ed). */
+ exists(path: string): Promise;
open(path: string, options: { createNew?: boolean, create?: boolean, truncate?: boolean, append?: boolean, write?: boolean, read?: boolean }): Promise;
/** Read bytes and write them to `out`. Returns number of bytes read. */
read(handle: number, out: Uint8Array): Promise;
diff --git a/packages/javascript_common/src/lib/async_mutex.ts b/packages/javascript_common/src/lib/async_mutex.ts
new file mode 100644
index 00000000..bfc38821
--- /dev/null
+++ b/packages/javascript_common/src/lib/async_mutex.ts
@@ -0,0 +1,52 @@
+/** Async mutex allows us to synchronize multiple async tasks.
+ *
+ * For example, if a perform is in-flight but is waiting for I/O the async task is suspended. If at the same time
+ * the periodic timer fires, this could cause core to be invoked twice within the same asyncify context, causing undefined behavior.
+ *
+ * We can avoid this by synchronizing over core.
+ *
+ * Note that this is not thread safe (concurrent), but merely task safe (asynchronous).
+ */
+export class AsyncMutex {
+ /** Promise to be awaited to synchronize between tasks. */
+ private condvar: Promise;
+ /** Indicator of whether the mutex is currently locked. */
+ private isLocked: boolean;
+ private value: T;
+
+ constructor(value: T) {
+ this.condvar = Promise.resolve();
+ this.isLocked = false;
+ this.value = value;
+ }
+
+ /**
+ * Get the protected value without respecting the lock.
+ *
+ * This is unsafe, but it is needed to get access to memory in sf_host imports.
+ */
+ get unsafeValue(): T {
+ return this.value;
+ }
+
+ public async withLock(fn: (value: T) => R): Promise> {
+ do {
+ // Under the assumption that we do not have concurrency it can never happen that two tasks
+ // pass over the condition of this loop and think they both have a lock - that would imply there exists task preemption in synchronous code.
+ //
+ // If there ever is threading or task preemption, we will need to use other means (atomics, spinlocks).
+ await this.condvar;
+ } while (this.isLocked);
+
+ this.isLocked = true;
+ let notify: () => void;
+ this.condvar = new Promise((resolve) => { notify = resolve; });
+
+ try {
+ return await fn(this.value);
+ } finally {
+ this.isLocked = false;
+ notify!();
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/javascript_common/src/asyncify.ts b/packages/javascript_common/src/lib/asyncify.ts
similarity index 100%
rename from packages/javascript_common/src/asyncify.ts
rename to packages/javascript_common/src/lib/asyncify.ts
diff --git a/packages/javascript_common/src/handle_map.ts b/packages/javascript_common/src/lib/handle_map.ts
similarity index 100%
rename from packages/javascript_common/src/handle_map.ts
rename to packages/javascript_common/src/lib/handle_map.ts
diff --git a/packages/javascript_common/src/lib/index.ts b/packages/javascript_common/src/lib/index.ts
new file mode 100644
index 00000000..5c053141
--- /dev/null
+++ b/packages/javascript_common/src/lib/index.ts
@@ -0,0 +1,5 @@
+export * from './asyncify.js';
+export * from './async_mutex.js';
+export * from './handle_map.js';
+export * from './stream.js';
+export * from './wasm.js';
\ No newline at end of file
diff --git a/packages/javascript_common/src/lib/stream.ts b/packages/javascript_common/src/lib/stream.ts
new file mode 100644
index 00000000..b8e30ca1
--- /dev/null
+++ b/packages/javascript_common/src/lib/stream.ts
@@ -0,0 +1,52 @@
+export type Stream = {
+ /** Reads up to `out.length` bytes from the stream, returns number of bytes read or throws a `WasiError`. */
+ read(out: Uint8Array): Promise;
+ /** Writes up to `data.length` bytes into the stream, returns number of bytes written or throws a `WasiError`. */
+ write(data: Uint8Array): Promise;
+ /** Closes the stream, returns undefined or throws a `WasiError`. */
+ close(): Promise;
+};
+export class ReadableStreamAdapter implements Stream {
+ private chunks: Uint8Array[];
+ private readonly reader?: ReadableStreamDefaultReader;
+ constructor(stream: ReadableStream | null) {
+ this.reader = stream?.getReader();
+ this.chunks = [];
+ }
+ async read(out: Uint8Array): Promise {
+ if (this.reader === undefined) {
+ return 0;
+ }
+
+ if (this.chunks.length === 0) {
+ const readResult = await this.reader.read();
+ if (readResult.value === undefined) {
+ return 0;
+ }
+
+ this.chunks.push(readResult.value);
+ }
+
+ // TODO: coalesce multiple smaller chunks into one read
+ let chunk = this.chunks.shift()!;
+ if (chunk.byteLength > out.byteLength) {
+ const remaining = chunk.subarray(out.byteLength);
+ chunk = chunk.subarray(0, out.byteLength);
+
+ this.chunks.unshift(remaining);
+ }
+
+ const count = Math.min(chunk.byteLength, out.byteLength);
+ for (let i = 0; i < count; i += 1) {
+ out[i] = chunk[i];
+ }
+
+ return count;
+ }
+ async write(data: Uint8Array): Promise {
+ throw new Error('not implemented');
+ }
+ async close(): Promise {
+ // TODO: what to do here?
+ }
+}
\ No newline at end of file
diff --git a/packages/javascript_common/src/lib/wasm.ts b/packages/javascript_common/src/lib/wasm.ts
new file mode 100644
index 00000000..e23c1166
--- /dev/null
+++ b/packages/javascript_common/src/lib/wasm.ts
@@ -0,0 +1,5 @@
+export function corePathURL(): URL {
+ // relative to where common is symlinked
+ // up three times gets us from dist/common/lib into the outer package
+ return new URL('../../../assets/core-async.wasm', import.meta.url);
+}
diff --git a/packages/javascript_common/src/sf_host.ts b/packages/javascript_common/src/sf_host.ts
index a96ce3c7..cba1562c 100644
--- a/packages/javascript_common/src/sf_host.ts
+++ b/packages/javascript_common/src/sf_host.ts
@@ -1,6 +1,5 @@
import type { TextCoder, AppContext } from './interfaces';
-import { HandleMap } from './handle_map.js';
-import { Asyncify, AsyncifyState } from './asyncify.js';
+import { Asyncify, AsyncifyState, HandleMap } from './lib/index.js';
import { WasiErrno } from './error.js';
type AbiResult = number;
diff --git a/packages/javascript_common/src/wasm.ts b/packages/javascript_common/src/wasm.ts
deleted file mode 100644
index f2055769..00000000
--- a/packages/javascript_common/src/wasm.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export function corePathURL(): URL {
- return new URL('../../assets/core-async.wasm', import.meta.url); // path is relative to location where common is simlinked
-}
diff --git a/packages/nodejs_host/src/index.ts b/packages/nodejs_host/src/index.ts
index 18b43ead..9b63f971 100644
--- a/packages/nodejs_host/src/index.ts
+++ b/packages/nodejs_host/src/index.ts
@@ -5,7 +5,7 @@ import process from 'node:process';
import { fileURLToPath } from 'node:url';
import { WASI } from 'node:wasi';
-import { AsyncMutex } from './common/app.js';
+import { AsyncMutex } from './common/lib/index.js';
import {
App,
FileSystem,
@@ -45,6 +45,15 @@ class NodeTextCoder implements TextCoder {
class NodeFileSystem implements FileSystem {
private readonly files: HandleMap = new HandleMap();
+ async exists(path: string): Promise {
+ try {
+ await fs.stat(path);
+ return true;
+ } catch {
+ return false;
+ }
+ }
+
async open(path: string, options: { createNew?: boolean, create?: boolean, truncate?: boolean, append?: boolean, write?: boolean, read?: boolean }): Promise {
let flags = '';
@@ -223,6 +232,7 @@ class InternalClient {
private app: App;
private readyState: AsyncMutex<{ ready: boolean }>;
+ private readonly fileSystem: NodeFileSystem;
constructor(readonly options: ClientOptions = {}) {
if (options.assetsPath !== undefined) {
@@ -230,10 +240,11 @@ class InternalClient {
}
this.readyState = new AsyncMutex({ ready: false });
+ this.fileSystem = new NodeFileSystem();
this.app = new App({
network: new NodeNetwork(),
- fileSystem: new NodeFileSystem(),
+ fileSystem: this.fileSystem,
textCoder: new NodeTextCoder(),
timers: new NodeTimers(),
persistence: new NodePersistence(options.token, options.superfaceApiUrl, this.userAgent)
@@ -305,7 +316,15 @@ class InternalClient {
public async resolveProfileUrl(profile: string): Promise {
const resolvedProfile = profile.replace(/\//g, '.');
- const path = resolvePath(this.assetsPath, `${resolvedProfile}.profile`);
+ let path = resolvePath(this.assetsPath, `${resolvedProfile}.profile.ts`);
+ // migration from Comlink to TypeScript profiles
+ const pathComlink = resolvePath(this.assetsPath, `${resolvedProfile}.profile`);
+ if (
+ !(await this.fileSystem.exists(path))
+ && (await this.fileSystem.exists(pathComlink))
+ ) {
+ path = pathComlink;
+ }
return `file://${path}`;
}
diff --git a/packages/python_host/src/one_sdk/client.py b/packages/python_host/src/one_sdk/client.py
index f7334112..fea69e2e 100644
--- a/packages/python_host/src/one_sdk/client.py
+++ b/packages/python_host/src/one_sdk/client.py
@@ -21,15 +21,20 @@ def __init__(
self._assets_path = assets_path
self._core_path = CORE_PATH
self._ready = False
+ self._file_system = PythonFilesystem()
self._app = WasiApp(
- PythonFilesystem(),
+ self._file_system,
PythonNetwork(),
PythonPersistence(token, superface_api_url, WasiApp.user_agent())
)
def resolve_profile_url(self, profile: str) -> str:
resolved_profile = profile.replace('/', '.')
- path = os.path.abspath(os.path.join(self._assets_path, f"{resolved_profile}.profile"))
+ path = os.path.abspath(os.path.join(self._assets_path, f"{resolved_profile}.profile.ts"))
+ # migration from Comlink to TypeScript profiles
+ path_comlink = os.path.abspath(os.path.join(self._assets_path, f"{resolved_profile}.profile"))
+ if not self._file_system.exists(path) and self._file_system.exists(path_comlink):
+ path = path_comlink
return f"file://{path}"
diff --git a/packages/python_host/src/one_sdk/platform.py b/packages/python_host/src/one_sdk/platform.py
index bc240abc..4d048fcf 100644
--- a/packages/python_host/src/one_sdk/platform.py
+++ b/packages/python_host/src/one_sdk/platform.py
@@ -1,5 +1,6 @@
from typing import BinaryIO, List, Mapping, Optional, Union, cast
+import os.path
from datetime import datetime
from collections import defaultdict
@@ -82,7 +83,6 @@ def write(self, handle: int, data: bytes) -> int:
# TODO: map system exception to wasi
raise WasiError(WasiErrno.EINVAL) from e
-
def close(self, handle: int):
file = self._files.get(handle)
if file is None:
@@ -94,6 +94,9 @@ def close(self, handle: int):
# TODO: map system exception to wasi
raise WasiError(WasiErrno.EINVAL) from e
+ def exists(self, path: str) -> bool:
+ return os.path.exists(path)
+
class HttpResponse(BinaryIO):
def __init__(self, response):
self._response = response