@simpleplatform/sdk 1.0.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/LICENSE +201 -0
- package/README.md +309 -0
- package/dist/ai.d.ts +239 -0
- package/dist/ai.js +265 -0
- package/dist/build.js +161 -0
- package/dist/graphql.d.ts +33 -0
- package/dist/graphql.js +56 -0
- package/dist/host.d.ts +28 -0
- package/dist/host.js +148 -0
- package/dist/http.d.ts +26 -0
- package/dist/http.js +42 -0
- package/dist/index.d.ts +51 -0
- package/dist/index.js +165 -0
- package/dist/internal/global.d.ts +9 -0
- package/dist/internal/global.js +29 -0
- package/dist/internal/memory.d.ts +103 -0
- package/dist/internal/memory.js +152 -0
- package/dist/internal/polyfills.d.ts +52 -0
- package/dist/internal/polyfills.js +150 -0
- package/dist/security.d.ts +174 -0
- package/dist/security.js +60 -0
- package/dist/settings.d.ts +13 -0
- package/dist/settings.js +25 -0
- package/dist/storage.d.ts +34 -0
- package/dist/storage.js +72 -0
- package/dist/types.d.ts +81 -0
- package/dist/types.js +1 -0
- package/dist/worker-override.js +86 -0
- package/package.json +55 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Context } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Executes a GraphQL query with variables and returns the data directly.
|
|
4
|
+
* This function communicates with the host system's database action and handles
|
|
5
|
+
* JSON marshaling, response processing, and error handling internally.
|
|
6
|
+
*
|
|
7
|
+
* @param query The GraphQL query string to execute.
|
|
8
|
+
* @param variables Query variables as a map or object.
|
|
9
|
+
* @param context The execution context for the query.
|
|
10
|
+
* @returns A promise that resolves with the GraphQL query result data.
|
|
11
|
+
* @throws Will throw an error if the query fails or the host returns an error.
|
|
12
|
+
*/
|
|
13
|
+
export declare function execute<T = any>(query: string, variables: any, context: Context): Promise<T>;
|
|
14
|
+
/**
|
|
15
|
+
* Executes a GraphQL mutation operation.
|
|
16
|
+
* It will throw an error if a query operation is passed.
|
|
17
|
+
*
|
|
18
|
+
* @param mutation The GraphQL mutation string.
|
|
19
|
+
* @param variables The variables for the mutation.
|
|
20
|
+
* @param context The execution context.
|
|
21
|
+
* @returns A promise that resolves with the mutation result.
|
|
22
|
+
*/
|
|
23
|
+
export declare function mutate<T = any>(mutation: string, variables: any, context: Context): Promise<T>;
|
|
24
|
+
/**
|
|
25
|
+
* Executes a GraphQL query operation.
|
|
26
|
+
* It will throw an error if a mutation operation is passed.
|
|
27
|
+
*
|
|
28
|
+
* @param query The GraphQL query string.
|
|
29
|
+
* @param variables The variables for the query.
|
|
30
|
+
* @param context The execution context.
|
|
31
|
+
* @returns A promise that resolves with the query result.
|
|
32
|
+
*/
|
|
33
|
+
export declare function query<T = any>(query: string, variables: any, context: Context): Promise<T>;
|
package/dist/graphql.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { execute as hostExecute } from './host';
|
|
2
|
+
/**
|
|
3
|
+
* Executes a GraphQL query with variables and returns the data directly.
|
|
4
|
+
* This function communicates with the host system's database action and handles
|
|
5
|
+
* JSON marshaling, response processing, and error handling internally.
|
|
6
|
+
*
|
|
7
|
+
* @param query The GraphQL query string to execute.
|
|
8
|
+
* @param variables Query variables as a map or object.
|
|
9
|
+
* @param context The execution context for the query.
|
|
10
|
+
* @returns A promise that resolves with the GraphQL query result data.
|
|
11
|
+
* @throws Will throw an error if the query fails or the host returns an error.
|
|
12
|
+
*/
|
|
13
|
+
export async function execute(query, variables, context) {
|
|
14
|
+
if (!query) {
|
|
15
|
+
throw new Error('query is required for GraphQL execution');
|
|
16
|
+
}
|
|
17
|
+
const response = await hostExecute('action:db/execute', { query, variables }, context);
|
|
18
|
+
if (!response.ok) {
|
|
19
|
+
// eslint-disable-next-line no-console
|
|
20
|
+
console.log('[GraphQL] response.error:', JSON.stringify(response.error, null, 2));
|
|
21
|
+
// eslint-disable-next-line no-console
|
|
22
|
+
console.log('[GraphQL] response.data:', JSON.stringify(response.data, null, 2));
|
|
23
|
+
throw new Error(response.error?.message ?? 'GraphQL query failed');
|
|
24
|
+
}
|
|
25
|
+
return response.data;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Executes a GraphQL mutation operation.
|
|
29
|
+
* It will throw an error if a query operation is passed.
|
|
30
|
+
*
|
|
31
|
+
* @param mutation The GraphQL mutation string.
|
|
32
|
+
* @param variables The variables for the mutation.
|
|
33
|
+
* @param context The execution context.
|
|
34
|
+
* @returns A promise that resolves with the mutation result.
|
|
35
|
+
*/
|
|
36
|
+
export async function mutate(mutation, variables, context) {
|
|
37
|
+
if (!mutation.trim().startsWith('mutation')) {
|
|
38
|
+
throw new Error('A query was passed to the `mutate` method. Use the `query` method instead.');
|
|
39
|
+
}
|
|
40
|
+
return execute(mutation, variables, context);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Executes a GraphQL query operation.
|
|
44
|
+
* It will throw an error if a mutation operation is passed.
|
|
45
|
+
*
|
|
46
|
+
* @param query The GraphQL query string.
|
|
47
|
+
* @param variables The variables for the query.
|
|
48
|
+
* @param context The execution context.
|
|
49
|
+
* @returns A promise that resolves with the query result.
|
|
50
|
+
*/
|
|
51
|
+
export async function query(query, variables, context) {
|
|
52
|
+
if (query.trim().startsWith('mutation')) {
|
|
53
|
+
throw new Error('A mutation was passed to the `query` method. Use the `mutate` method instead.');
|
|
54
|
+
}
|
|
55
|
+
return execute(query, variables, context);
|
|
56
|
+
}
|
package/dist/host.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Context, SimpleResponse } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Executes an action synchronously on the host system and returns the response.
|
|
4
|
+
*
|
|
5
|
+
* NOTE: The memory model for this execution is that the entire WASM instance is
|
|
6
|
+
* ephemeral. Memory is allocated for this single execution and then discarded
|
|
7
|
+
* when the instance is terminated. Therefore, a manual memory reset is not required.
|
|
8
|
+
*
|
|
9
|
+
* This function has two internal implementations selected at build time:
|
|
10
|
+
* 1. ASYNC_BUILD = true: For browsers, uses Asyncify to pause/resume execution.
|
|
11
|
+
* 2. ASYNC_BUILD = false: For Elixir, uses a synchronous call-and-get-result pattern.
|
|
12
|
+
*/
|
|
13
|
+
export declare function execute<T = any>(actionName: string, params: any, context: Context): SimpleResponse<T>;
|
|
14
|
+
/**
|
|
15
|
+
* Executes an action asynchronously on the host system (fire-and-forget).
|
|
16
|
+
*/
|
|
17
|
+
export declare function executeAsync(actionName: string, params: any, context: Context): void;
|
|
18
|
+
/**
|
|
19
|
+
* Retrieves the initial context payload from the host. This function orchestrates
|
|
20
|
+
* the "pull" mechanism required by the execution environment.
|
|
21
|
+
*
|
|
22
|
+
* It first asks the host for the payload size, then allocates memory within
|
|
23
|
+
* the WASM module, and finally asks the host to write the payload into the
|
|
24
|
+
* allocated buffer.
|
|
25
|
+
*
|
|
26
|
+
* @returns A Uint8Array containing the context payload.
|
|
27
|
+
*/
|
|
28
|
+
export declare function getContext(): Uint8Array;
|
package/dist/host.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { allocate, deallocate, decoder, readBufferSlice, stringToPtr } from './internal/memory';
|
|
2
|
+
/**
|
|
3
|
+
* Executes an action synchronously on the host system and returns the response.
|
|
4
|
+
*
|
|
5
|
+
* NOTE: The memory model for this execution is that the entire WASM instance is
|
|
6
|
+
* ephemeral. Memory is allocated for this single execution and then discarded
|
|
7
|
+
* when the instance is terminated. Therefore, a manual memory reset is not required.
|
|
8
|
+
*
|
|
9
|
+
* This function has two internal implementations selected at build time:
|
|
10
|
+
* 1. ASYNC_BUILD = true: For browsers, uses Asyncify to pause/resume execution.
|
|
11
|
+
* 2. ASYNC_BUILD = false: For Elixir, uses a synchronous call-and-get-result pattern.
|
|
12
|
+
*/
|
|
13
|
+
export function execute(actionName, params, context) {
|
|
14
|
+
// We must track the pointers for the parameters so we can free them after the call.
|
|
15
|
+
// They are declared here to be accessible in the `finally` block.
|
|
16
|
+
let actionNamePtr = 0;
|
|
17
|
+
let actionNameLen = 0;
|
|
18
|
+
let paramsPtr = 0;
|
|
19
|
+
let paramsLen = 0;
|
|
20
|
+
let contextPtr = 0;
|
|
21
|
+
let contextLen = 0;
|
|
22
|
+
try {
|
|
23
|
+
const paramsJSON = JSON.stringify(params ?? null);
|
|
24
|
+
const contextJSON = JSON.stringify(context);
|
|
25
|
+
[actionNamePtr, actionNameLen] = stringToPtr(actionName);
|
|
26
|
+
[paramsPtr, paramsLen] = stringToPtr(paramsJSON);
|
|
27
|
+
[contextPtr, contextLen] = stringToPtr(contextJSON);
|
|
28
|
+
if (__ASYNC_BUILD__) {
|
|
29
|
+
// --- ASYNCIFY-AWARE PATH (FOR BROWSER) ---
|
|
30
|
+
let responsePtr = 0;
|
|
31
|
+
let responseLen = 0;
|
|
32
|
+
try {
|
|
33
|
+
// State 2 is "Rewinding". This check implements the stateful "double call"
|
|
34
|
+
// pattern required by Asyncify.
|
|
35
|
+
if (asyncify_get_state() === 2) {
|
|
36
|
+
// This is the second call, during the rewind. We just need to stop the
|
|
37
|
+
// rewind so that normal execution can resume.
|
|
38
|
+
asyncify_stop_rewind();
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
// This is the first call. Call the host to start the async operation.
|
|
42
|
+
// The host will then trigger the unwind.
|
|
43
|
+
__host.call(actionNamePtr, actionNameLen, paramsPtr, paramsLen, contextPtr, contextLen);
|
|
44
|
+
}
|
|
45
|
+
// --- EXECUTION PAUSES HERE OR CONTINUES AFTER REWIND IS STOPPED ---
|
|
46
|
+
// The host has placed the response in memory. Read it using pointers
|
|
47
|
+
// retrieved from the Javy plugin.
|
|
48
|
+
responsePtr = __wasm.get_response_ptr();
|
|
49
|
+
responseLen = __wasm.get_response_len();
|
|
50
|
+
const resultBytes = readBufferSlice(responsePtr, responseLen);
|
|
51
|
+
const resultJSON = decoder.decode(resultBytes);
|
|
52
|
+
// Immediately clear the response buffer pointers in the Javy plugin to
|
|
53
|
+
// prevent reading stale data on subsequent nested calls.
|
|
54
|
+
__wasm.clear_response_buffer();
|
|
55
|
+
return JSON.parse(resultJSON);
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
// 1. Free the result buffer allocated by the host.
|
|
59
|
+
if (responsePtr > 0) {
|
|
60
|
+
deallocate(responsePtr, responseLen);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
// --- SYNCHRONOUS PATH (FOR ELIXIR BACKEND) ---
|
|
66
|
+
let resultPtr = 0;
|
|
67
|
+
let resultLen = 0;
|
|
68
|
+
try {
|
|
69
|
+
// 1. Make the synchronous call. The host now holds the result.
|
|
70
|
+
__host.call(actionNamePtr, actionNameLen, paramsPtr, paramsLen, contextPtr, contextLen);
|
|
71
|
+
// 2. Ask the host for the size of the result.
|
|
72
|
+
resultLen = __host.getExecutionResultSize();
|
|
73
|
+
if (resultLen === 0) {
|
|
74
|
+
return { error: { message: 'Host returned an empty result.' }, ok: false };
|
|
75
|
+
}
|
|
76
|
+
// 3. Allocate memory inside WASM for the result.
|
|
77
|
+
resultPtr = allocate(resultLen);
|
|
78
|
+
// 4. Ask the host to write the result into our buffer.
|
|
79
|
+
__host.getExecutionResult(resultPtr);
|
|
80
|
+
// 5. Read the result from our buffer and return it.
|
|
81
|
+
const resultBytes = readBufferSlice(resultPtr, resultLen);
|
|
82
|
+
const resultJSON = decoder.decode(resultBytes);
|
|
83
|
+
return JSON.parse(resultJSON);
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
// Free the result buffer allocated by us.
|
|
87
|
+
if (resultPtr > 0) {
|
|
88
|
+
deallocate(resultPtr, resultLen);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
// 2. Free the parameter buffers we allocated before the host call.
|
|
95
|
+
if (actionNamePtr > 0) {
|
|
96
|
+
deallocate(actionNamePtr, actionNameLen);
|
|
97
|
+
}
|
|
98
|
+
if (paramsPtr > 0) {
|
|
99
|
+
deallocate(paramsPtr, paramsLen);
|
|
100
|
+
}
|
|
101
|
+
if (contextPtr > 0) {
|
|
102
|
+
deallocate(contextPtr, contextLen);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Executes an action asynchronously on the host system (fire-and-forget).
|
|
108
|
+
*/
|
|
109
|
+
export function executeAsync(actionName, params, context) {
|
|
110
|
+
const paramsJSON = JSON.stringify(params ?? null);
|
|
111
|
+
const contextJSON = JSON.stringify(context);
|
|
112
|
+
const [actionNamePtr, actionNameLen] = stringToPtr(actionName);
|
|
113
|
+
const [paramsPtr, paramsLen] = stringToPtr(paramsJSON);
|
|
114
|
+
const [contextPtr, contextLen] = stringToPtr(contextJSON);
|
|
115
|
+
// Make the asynchronous (fire-and-forget) call to the host.
|
|
116
|
+
__host.cast(actionNamePtr, actionNameLen, paramsPtr, paramsLen, contextPtr, contextLen);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Retrieves the initial context payload from the host. This function orchestrates
|
|
120
|
+
* the "pull" mechanism required by the execution environment.
|
|
121
|
+
*
|
|
122
|
+
* It first asks the host for the payload size, then allocates memory within
|
|
123
|
+
* the WASM module, and finally asks the host to write the payload into the
|
|
124
|
+
* allocated buffer.
|
|
125
|
+
*
|
|
126
|
+
* @returns A Uint8Array containing the context payload.
|
|
127
|
+
*/
|
|
128
|
+
export function getContext() {
|
|
129
|
+
// Ask the host for the size of the data.
|
|
130
|
+
const size = __host.getContextSize();
|
|
131
|
+
if (size === 0) {
|
|
132
|
+
return new Uint8Array(0);
|
|
133
|
+
}
|
|
134
|
+
// Allocate memory for the data inside the WASM module's buffer.
|
|
135
|
+
const ptr = allocate(size);
|
|
136
|
+
let buffer;
|
|
137
|
+
try {
|
|
138
|
+
// Ask the host to write the data into the allocated memory region.
|
|
139
|
+
__host.getContext(ptr);
|
|
140
|
+
// Now that the data is in our memory, create a slice to read it.
|
|
141
|
+
buffer = readBufferSlice(ptr, size);
|
|
142
|
+
}
|
|
143
|
+
finally {
|
|
144
|
+
// Free the memory we allocated now that the data is in a JS-owned buffer.
|
|
145
|
+
deallocate(ptr, size);
|
|
146
|
+
}
|
|
147
|
+
return buffer;
|
|
148
|
+
}
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Context } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Represents the configuration for an HTTP request.
|
|
4
|
+
*/
|
|
5
|
+
export interface HttpRequest {
|
|
6
|
+
body?: any;
|
|
7
|
+
headers?: Record<string, string>;
|
|
8
|
+
method?: 'DELETE' | 'GET' | 'PATCH' | 'POST' | 'PUT';
|
|
9
|
+
url: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function del<T = any>(url: string, headers: Record<string, string>, context: Context): Promise<T>;
|
|
12
|
+
/**
|
|
13
|
+
* Executes an HTTP request and returns the response data directly.
|
|
14
|
+
* This provides an ergonomic API for HTTP operations by handling host communication
|
|
15
|
+
* and error checking internally.
|
|
16
|
+
*
|
|
17
|
+
* @param request The HTTP request configuration.
|
|
18
|
+
* @param context The execution context for the request.
|
|
19
|
+
* @returns A promise that resolves with the HTTP response data.
|
|
20
|
+
* @throws Will throw an error if the request fails or the host returns an error.
|
|
21
|
+
*/
|
|
22
|
+
export declare function fetch<T = any>(request: HttpRequest, context: Context): Promise<T>;
|
|
23
|
+
export declare function get<T = any>(url: string, headers: Record<string, string>, context: Context): Promise<T>;
|
|
24
|
+
export declare function patch<T = any>(url: string, body: any, headers: Record<string, string>, context: Context): Promise<T>;
|
|
25
|
+
export declare function post<T = any>(url: string, body: any, headers: Record<string, string>, context: Context): Promise<T>;
|
|
26
|
+
export declare function put<T = any>(url: string, body: any, headers: Record<string, string>, context: Context): Promise<T>;
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { execute as hostExecute } from './host';
|
|
2
|
+
export async function del(url, headers, context) {
|
|
3
|
+
return fetch({ headers, method: 'DELETE', url }, context);
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Executes an HTTP request and returns the response data directly.
|
|
7
|
+
* This provides an ergonomic API for HTTP operations by handling host communication
|
|
8
|
+
* and error checking internally.
|
|
9
|
+
*
|
|
10
|
+
* @param request The HTTP request configuration.
|
|
11
|
+
* @param context The execution context for the request.
|
|
12
|
+
* @returns A promise that resolves with the HTTP response data.
|
|
13
|
+
* @throws Will throw an error if the request fails or the host returns an error.
|
|
14
|
+
*/
|
|
15
|
+
export async function fetch(request, context) {
|
|
16
|
+
if (!request.url) {
|
|
17
|
+
throw new Error('URL is required for HTTP request');
|
|
18
|
+
}
|
|
19
|
+
const hostRequest = {
|
|
20
|
+
body: request.body ? JSON.stringify(request.body) : undefined,
|
|
21
|
+
headers: request.headers,
|
|
22
|
+
method: request.method ?? 'GET',
|
|
23
|
+
url: request.url,
|
|
24
|
+
};
|
|
25
|
+
const response = await hostExecute('action:http/fetch', hostRequest, context);
|
|
26
|
+
if (!response.ok) {
|
|
27
|
+
throw new Error(response.error?.message ?? 'HTTP request failed');
|
|
28
|
+
}
|
|
29
|
+
return response.data;
|
|
30
|
+
}
|
|
31
|
+
export async function get(url, headers, context) {
|
|
32
|
+
return fetch({ headers, method: 'GET', url }, context);
|
|
33
|
+
}
|
|
34
|
+
export async function patch(url, body, headers, context) {
|
|
35
|
+
return fetch({ body, headers, method: 'PATCH', url }, context);
|
|
36
|
+
}
|
|
37
|
+
export async function post(url, body, headers, context) {
|
|
38
|
+
return fetch({ body, headers, method: 'POST', url }, context);
|
|
39
|
+
}
|
|
40
|
+
export async function put(url, body, headers, context) {
|
|
41
|
+
return fetch({ body, headers, method: 'PUT', url }, context);
|
|
42
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { Context, SimpleRequest, SimpleResponse } from './types';
|
|
2
|
+
import './internal/global';
|
|
3
|
+
export * from './storage';
|
|
4
|
+
export * from './types';
|
|
5
|
+
/**
|
|
6
|
+
* Defines the signature for a user's action handler.
|
|
7
|
+
*/
|
|
8
|
+
export type Handler<TResult = any> = (request: Request) => Promise<TResult> | TResult;
|
|
9
|
+
/**
|
|
10
|
+
* Represents an incoming action request with ergonomic access to its data.
|
|
11
|
+
* This class will be exposed as `simple.Request`.
|
|
12
|
+
*/
|
|
13
|
+
export declare class Request {
|
|
14
|
+
readonly context: Context;
|
|
15
|
+
readonly headers: Record<string, any>;
|
|
16
|
+
private readonly rawData;
|
|
17
|
+
constructor(simpleRequest: SimpleRequest);
|
|
18
|
+
/** Returns the raw data string from the request. */
|
|
19
|
+
data(): string;
|
|
20
|
+
/** Parses the request data JSON into a new object. */
|
|
21
|
+
parse<T>(): T;
|
|
22
|
+
}
|
|
23
|
+
declare function execute<T = any>(actionName: string, params: any, context: Context): SimpleResponse<T>;
|
|
24
|
+
declare function executeAsync(actionName: string, params: any, context: Context): void;
|
|
25
|
+
/**
|
|
26
|
+
* The main entry point for a Simple Logic action.
|
|
27
|
+
*
|
|
28
|
+
* This function provides a transparent developer experience by detecting the
|
|
29
|
+
* execution environment and choosing the optimal strategy.
|
|
30
|
+
*
|
|
31
|
+
* - In a synchronous environment (like the Elixir backend), it executes the
|
|
32
|
+
* handler directly. With QuickJS's event loop enabled, it can now correctly
|
|
33
|
+
* await async handlers and their host calls.
|
|
34
|
+
* - In an asynchronous, constrained environment (like the browser), it
|
|
35
|
+
* automatically offloads the *entire bundled application script* to an
|
|
36
|
+
* unconstrained script worker for execution.
|
|
37
|
+
*
|
|
38
|
+
* @returns In most environments, this function returns `Promise<void>` as it
|
|
39
|
+
* signals completion via a fire-and-forget host call. However, when executed
|
|
40
|
+
* inside the script worker (`__IS_WORKER_BUILD__`), it returns the `Promise`
|
|
41
|
+
* from the user's handler. This is critical for allowing the script worker
|
|
42
|
+
* to correctly `await` the completion of the user's async logic.
|
|
43
|
+
*/
|
|
44
|
+
declare function handle(handler: Handler): Promise<any>;
|
|
45
|
+
declare const simple: {
|
|
46
|
+
Execute: typeof execute;
|
|
47
|
+
ExecuteAsync: typeof executeAsync;
|
|
48
|
+
Handle: typeof handle;
|
|
49
|
+
Request: typeof Request;
|
|
50
|
+
};
|
|
51
|
+
export default simple;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import * as host from './host';
|
|
2
|
+
import './internal/global';
|
|
3
|
+
export * from './storage';
|
|
4
|
+
export * from './types';
|
|
5
|
+
/**
|
|
6
|
+
* Represents an incoming action request with ergonomic access to its data.
|
|
7
|
+
* This class will be exposed as `simple.Request`.
|
|
8
|
+
*/
|
|
9
|
+
export class Request {
|
|
10
|
+
constructor(simpleRequest) {
|
|
11
|
+
this.context = simpleRequest.context;
|
|
12
|
+
this.headers = simpleRequest.headers;
|
|
13
|
+
this.rawData = simpleRequest.data ?? '';
|
|
14
|
+
}
|
|
15
|
+
/** Returns the raw data string from the request. */
|
|
16
|
+
data() {
|
|
17
|
+
return this.rawData;
|
|
18
|
+
}
|
|
19
|
+
/** Parses the request data JSON into a new object. */
|
|
20
|
+
parse() {
|
|
21
|
+
if (!this.rawData) {
|
|
22
|
+
throw new Error('no data to parse');
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
return JSON.parse(this.rawData);
|
|
26
|
+
}
|
|
27
|
+
catch (e) {
|
|
28
|
+
throw new Error(`failed to parse data: ${e.message}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function execute(actionName, params, context) {
|
|
33
|
+
return host.execute(actionName, params, context);
|
|
34
|
+
}
|
|
35
|
+
// --- Internal Implementations ---
|
|
36
|
+
function executeAsync(actionName, params, context) {
|
|
37
|
+
return host.executeAsync(actionName, params, context);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The main entry point for a Simple Logic action.
|
|
41
|
+
*
|
|
42
|
+
* This function provides a transparent developer experience by detecting the
|
|
43
|
+
* execution environment and choosing the optimal strategy.
|
|
44
|
+
*
|
|
45
|
+
* - In a synchronous environment (like the Elixir backend), it executes the
|
|
46
|
+
* handler directly. With QuickJS's event loop enabled, it can now correctly
|
|
47
|
+
* await async handlers and their host calls.
|
|
48
|
+
* - In an asynchronous, constrained environment (like the browser), it
|
|
49
|
+
* automatically offloads the *entire bundled application script* to an
|
|
50
|
+
* unconstrained script worker for execution.
|
|
51
|
+
*
|
|
52
|
+
* @returns In most environments, this function returns `Promise<void>` as it
|
|
53
|
+
* signals completion via a fire-and-forget host call. However, when executed
|
|
54
|
+
* inside the script worker (`__IS_WORKER_BUILD__`), it returns the `Promise`
|
|
55
|
+
* from the user's handler. This is critical for allowing the script worker
|
|
56
|
+
* to correctly `await` the completion of the user's async logic.
|
|
57
|
+
*/
|
|
58
|
+
async function handle(handler) {
|
|
59
|
+
// Context 2: We are inside the unconstrained script worker.
|
|
60
|
+
// This is the highest-priority check.
|
|
61
|
+
if (typeof __IS_WORKER_BUILD__ !== 'undefined' && __IS_WORKER_BUILD__) {
|
|
62
|
+
// The wrapper (`build.js`) is responsible for the try/catch block.
|
|
63
|
+
// The job of `handle` here is ONLY to parse the input and pass the
|
|
64
|
+
// resulting promise to the wrapper via the secure channel.
|
|
65
|
+
const inputText = readInputFromHost();
|
|
66
|
+
if (!inputText) {
|
|
67
|
+
// If setup fails, we must put a rejected promise on the channel.
|
|
68
|
+
const error = new Error('no input payload provided by the host environment');
|
|
69
|
+
if (globalThis.__SIMPLE_PROMISE_CHANNEL__) {
|
|
70
|
+
;
|
|
71
|
+
globalThis.__SIMPLE_PROMISE_CHANNEL__.promise = Promise.reject(error);
|
|
72
|
+
}
|
|
73
|
+
// Re-throwing ensures the worker's main catch block can see the error too.
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
const simpleReq = JSON.parse(inputText);
|
|
77
|
+
const request = new Request(simpleReq);
|
|
78
|
+
const resultPromise = Promise.resolve(handler(request));
|
|
79
|
+
if (!globalThis.__SIMPLE_PROMISE_CHANNEL__) {
|
|
80
|
+
throw new Error('CRITICAL: Script worker context is missing the promise channel.');
|
|
81
|
+
}
|
|
82
|
+
// This is the key: place the promise on the channel and exit synchronously.
|
|
83
|
+
;
|
|
84
|
+
globalThis.__SIMPLE_PROMISE_CHANNEL__.promise = resultPromise;
|
|
85
|
+
return; // DO NOT return the promise here.
|
|
86
|
+
}
|
|
87
|
+
// All other contexts (WASM Loader, Elixir Backend) are handled below.
|
|
88
|
+
let context;
|
|
89
|
+
try {
|
|
90
|
+
const inputText = readInputFromHost();
|
|
91
|
+
if (!inputText) {
|
|
92
|
+
returnError('no input payload provided by the host environment', undefined);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const simpleReq = JSON.parse(inputText);
|
|
96
|
+
context = simpleReq.context;
|
|
97
|
+
// Context 1: This is the initial WASM loader running in the browser.
|
|
98
|
+
// Its only job is to start the script worker.
|
|
99
|
+
if (__ASYNC_BUILD__) {
|
|
100
|
+
// --- ASYNC PATH (BROWSER) ---
|
|
101
|
+
// We are in the constrained WASM environment. We cannot run the async
|
|
102
|
+
// handler here. Instead, we use the pre-bundled user script string
|
|
103
|
+
// and send it to the host to be run in an unconstrained worker.
|
|
104
|
+
if (typeof __USER_SCRIPT_BUNDLE__ === 'undefined') {
|
|
105
|
+
throw new TypeError('CRITICAL: __USER_SCRIPT_BUNDLE__ is not defined in async build. Check the build script.');
|
|
106
|
+
}
|
|
107
|
+
const payload = { request: simpleReq };
|
|
108
|
+
const result = await host.execute('runtime/script:execute', {
|
|
109
|
+
payload,
|
|
110
|
+
script: __USER_SCRIPT_BUNDLE__,
|
|
111
|
+
}, context);
|
|
112
|
+
if (!result.ok) {
|
|
113
|
+
throw new Error(result.error?.message ?? 'Unconstrained script execution failed');
|
|
114
|
+
}
|
|
115
|
+
returnSuccess(result.data, context);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
// Context 3: This is the synchronous path for the Elixir backend.
|
|
119
|
+
const request = new Request(simpleReq);
|
|
120
|
+
const resultPromise = Promise.resolve(handler(request));
|
|
121
|
+
const result = await resultPromise;
|
|
122
|
+
returnSuccess(result, context);
|
|
123
|
+
}
|
|
124
|
+
catch (e) {
|
|
125
|
+
// This catch block is ONLY for the non-worker paths.
|
|
126
|
+
// Worker errors are handled by the try/catch in `script.worker.ts`.
|
|
127
|
+
returnError(e.message, context);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Transparently reads the initial payload from the host.
|
|
132
|
+
* This function is designed to work in two contexts:
|
|
133
|
+
* 1. Inside a WASM module, where it reads from the host via ABI calls.
|
|
134
|
+
* 2. Inside a standard JS script worker, where the host places the payload
|
|
135
|
+
* on the global scope as `__SIMPLE_INITIAL_PAYLOAD__`.
|
|
136
|
+
*/
|
|
137
|
+
function readInputFromHost() {
|
|
138
|
+
if (globalThis.__SIMPLE_INITIAL_PAYLOAD__) {
|
|
139
|
+
// This global variable is set by the script.worker.ts before executing the user's bundle.
|
|
140
|
+
return JSON.stringify(globalThis.__SIMPLE_INITIAL_PAYLOAD__.request);
|
|
141
|
+
}
|
|
142
|
+
// Otherwise, we are in the main WASM module and must read from the host ABI.
|
|
143
|
+
const buffer = host.getContext();
|
|
144
|
+
return new TextDecoder().decode(buffer);
|
|
145
|
+
}
|
|
146
|
+
function returnError(message, context) {
|
|
147
|
+
const response = { data: null, errors: [message], ok: false };
|
|
148
|
+
// Provide a minimal, safe context if the original is not available.
|
|
149
|
+
const safeContext = context ?? { logic: { execution_id: 'unknown' } };
|
|
150
|
+
host.executeAsync('__done__', response, safeContext);
|
|
151
|
+
}
|
|
152
|
+
function returnSuccess(data, context) {
|
|
153
|
+
// With the async handle function, we no longer need to check for promises here.
|
|
154
|
+
const response = { data, errors: [], ok: true };
|
|
155
|
+
host.executeAsync('__done__', response, context);
|
|
156
|
+
}
|
|
157
|
+
// --- The Default Export Object ---
|
|
158
|
+
// This creates the single `simple` object that users will import.
|
|
159
|
+
const simple = {
|
|
160
|
+
Execute: execute,
|
|
161
|
+
ExecuteAsync: executeAsync,
|
|
162
|
+
Handle: handle,
|
|
163
|
+
Request,
|
|
164
|
+
};
|
|
165
|
+
export default simple;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file This script is the first to be imported by the SDK. Its purpose is to
|
|
3
|
+
* polyfill the global scope of the JavaScript runtime with necessary APIs that
|
|
4
|
+
* may be missing or non-compliant in a minimal environment like Javy/QuickJS.
|
|
5
|
+
*
|
|
6
|
+
* This ensures that all other modules within the SDK can reliably use standard
|
|
7
|
+
* APIs like `TextEncoder` and `TextDecoder` as if they were in a browser,
|
|
8
|
+
* making the SDK portable and self-sufficient.
|
|
9
|
+
*/
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @file This script is the first to be imported by the SDK. Its purpose is to
|
|
4
|
+
* polyfill the global scope of the JavaScript runtime with necessary APIs that
|
|
5
|
+
* may be missing or non-compliant in a minimal environment like Javy/QuickJS.
|
|
6
|
+
*
|
|
7
|
+
* This ensures that all other modules within the SDK can reliably use standard
|
|
8
|
+
* APIs like `TextEncoder` and `TextDecoder` as if they were in a browser,
|
|
9
|
+
* making the SDK portable and self-sufficient.
|
|
10
|
+
*/
|
|
11
|
+
// We need polyfills in ANY environment that is not the dedicated script worker.
|
|
12
|
+
// The script worker is a modern browser environment with these APIs built-in.
|
|
13
|
+
// The Javy/QuickJS environment inside the main WASM module, however, is minimal
|
|
14
|
+
// and needs them for host communication.
|
|
15
|
+
if (typeof __IS_WORKER_BUILD__ === 'undefined' || !__IS_WORKER_BUILD__) {
|
|
16
|
+
// Use `require` to ensure the import is contained within the conditional
|
|
17
|
+
// block, making it easy for the bundler to tree-shake.
|
|
18
|
+
// eslint-disable-next-line ts/no-require-imports
|
|
19
|
+
const { TextDecoder: PolyfillDecoder, TextEncoder: PolyfillEncoder } = require('./polyfills');
|
|
20
|
+
// Check if TextEncoder is not available on the global object.
|
|
21
|
+
if (typeof globalThis.TextEncoder === 'undefined') {
|
|
22
|
+
// If it's missing, we attach our robust polyfill to the global scope.
|
|
23
|
+
globalThis.TextEncoder = PolyfillEncoder;
|
|
24
|
+
}
|
|
25
|
+
// Do the same for TextDecoder.
|
|
26
|
+
if (typeof globalThis.TextDecoder === 'undefined') {
|
|
27
|
+
globalThis.TextDecoder = PolyfillDecoder;
|
|
28
|
+
}
|
|
29
|
+
}
|