mirror of
https://github.com/HeyPuter/puter.git
synced 2026-08-25 23:46:51 +00:00
@@ -15,7 +15,7 @@ npm install @heyputer/puter.js
|
||||
#### ES Modules
|
||||
|
||||
```js
|
||||
import puter from '@heyputer/puterjs';
|
||||
import '@heyputer/puter.js';
|
||||
```
|
||||
|
||||
#### CommonJS
|
||||
@@ -34,6 +34,8 @@ Include Puter.js directly in your HTML via CDN in the `<head>` section:
|
||||
|
||||
## Usage Example
|
||||
|
||||
After importing, you can use the global `puter` object:
|
||||
|
||||
```js
|
||||
// Print a message
|
||||
puter.print('Hello from Puter.js!');
|
||||
|
||||
Vendored
+479
@@ -0,0 +1,479 @@
|
||||
declare global {
|
||||
interface Window {
|
||||
puter: Puter;
|
||||
}
|
||||
}
|
||||
|
||||
declare namespace Puter {
|
||||
// Main Puter interface
|
||||
interface Puter {
|
||||
// Properties
|
||||
appID: string;
|
||||
env: 'app' | 'web' | 'gui';
|
||||
|
||||
// Utility methods
|
||||
print(text: string, options?: { code?: boolean }): void;
|
||||
randName(separator?: string): string;
|
||||
exit(statusCode?: number): void;
|
||||
|
||||
// Sub-modules
|
||||
ai: AI;
|
||||
apps: Apps;
|
||||
auth: Auth;
|
||||
drivers: Drivers;
|
||||
fs: FileSystem;
|
||||
hosting: Hosting;
|
||||
kv: KeyValue;
|
||||
net: Networking;
|
||||
perms: Permissions;
|
||||
ui: UI;
|
||||
workers: Workers;
|
||||
}
|
||||
|
||||
// AI Module
|
||||
interface AI {
|
||||
chat(prompt: string, options?: ChatOptions): Promise<ChatResponse>;
|
||||
chat(prompt: string, testMode?: boolean, options?: ChatOptions): Promise<ChatResponse>;
|
||||
chat(prompt: string, imageURL?: string, testMode?: boolean, options?: ChatOptions): Promise<ChatResponse>;
|
||||
chat(prompt: string, imageURLArray?: string[], testMode?: boolean, options?: ChatOptions): Promise<ChatResponse>;
|
||||
chat(messages: ChatMessage[], testMode?: boolean, options?: ChatOptions): Promise<ChatResponse>;
|
||||
|
||||
img2txt(image: string | File | Blob, testMode?: boolean): Promise<string>;
|
||||
|
||||
txt2img(prompt: string, testMode?: boolean): Promise<HTMLImageElement>;
|
||||
txt2img(prompt: string, options?: Txt2ImgOptions): Promise<HTMLImageElement>;
|
||||
|
||||
txt2speech(text: string): Promise<HTMLAudioElement>;
|
||||
txt2speech(text: string, options?: Txt2SpeechOptions): Promise<HTMLAudioElement>;
|
||||
txt2speech(text: string, language?: string): Promise<HTMLAudioElement>;
|
||||
txt2speech(text: string, language?: string, voice?: string): Promise<HTMLAudioElement>;
|
||||
txt2speech(text: string, language?: string, voice?: string, engine?: string): Promise<HTMLAudioElement>;
|
||||
}
|
||||
|
||||
interface ChatOptions {
|
||||
model?: string;
|
||||
stream?: boolean;
|
||||
max_tokens?: number;
|
||||
temperature?: number;
|
||||
tools?: ToolDefinition[];
|
||||
}
|
||||
|
||||
interface ToolDefinition {
|
||||
type: 'function';
|
||||
function: {
|
||||
name: string;
|
||||
description: string;
|
||||
parameters: object;
|
||||
strict?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
interface ChatMessage {
|
||||
role: 'system' | 'assistant' | 'user' | 'function' | 'tool';
|
||||
content: string | ContentObject[];
|
||||
tool_call_id?: string;
|
||||
}
|
||||
|
||||
interface ContentObject {
|
||||
type: 'text' | 'file';
|
||||
text?: string;
|
||||
puter_path?: string;
|
||||
}
|
||||
|
||||
interface ChatResponse {
|
||||
message: {
|
||||
role: string;
|
||||
content: string;
|
||||
tool_calls?: ToolCall[];
|
||||
};
|
||||
}
|
||||
|
||||
interface ToolCall {
|
||||
id: string;
|
||||
function: {
|
||||
name: string;
|
||||
arguments: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface Txt2ImgOptions {
|
||||
model?: 'gpt-image-1' | 'gemini-2.5-flash-image-preview' | 'dall-e-3';
|
||||
quality?: 'high' | 'medium' | 'low' | 'hd' | 'standard';
|
||||
input_image?: string;
|
||||
input_image_mime_type?: string;
|
||||
}
|
||||
|
||||
interface Txt2SpeechOptions {
|
||||
language?: string;
|
||||
voice?: string;
|
||||
engine?: 'standard' | 'neural' | 'generative';
|
||||
}
|
||||
|
||||
// Apps Module
|
||||
interface Apps {
|
||||
create(name: string, indexURL: string): Promise<App>;
|
||||
create(name: string, indexURL: string, title?: string): Promise<App>;
|
||||
create(name: string, indexURL: string, title?: string, description?: string): Promise<App>;
|
||||
create(options: CreateAppOptions): Promise<App>;
|
||||
|
||||
delete(name: string): Promise<App>;
|
||||
get(name: string, options?: GetAppOptions): Promise<App>;
|
||||
list(options?: ListAppOptions): Promise<App[]>;
|
||||
update(name: string, attributes: UpdateAppAttributes): Promise<App>;
|
||||
}
|
||||
|
||||
interface CreateAppOptions {
|
||||
name: string;
|
||||
indexURL: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
icon?: string;
|
||||
maximizeOnStart?: boolean;
|
||||
filetypeAssociations?: string[];
|
||||
}
|
||||
|
||||
interface GetAppOptions {
|
||||
stats_period?: StatsPeriod;
|
||||
icon_size?: null | 16 | 32 | 64 | 128 | 256 | 512;
|
||||
}
|
||||
|
||||
interface ListAppOptions extends GetAppOptions { }
|
||||
|
||||
interface UpdateAppAttributes {
|
||||
name?: string;
|
||||
indexURL?: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
icon?: string;
|
||||
maximizeOnStart?: boolean;
|
||||
filetypeAssociations?: string[];
|
||||
}
|
||||
|
||||
type StatsPeriod = 'all' | 'today' | 'yesterday' | '7d' | '30d' | 'this_month' | 'last_month' | 'this_year' | 'last_year' | 'month_to_date' | 'year_to_date' | 'last_12_months';
|
||||
|
||||
interface App {
|
||||
uid: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
description: string;
|
||||
title: string;
|
||||
maximize_on_start: boolean;
|
||||
index_url: string;
|
||||
created_at: string;
|
||||
background: boolean;
|
||||
filetype_associations: string[];
|
||||
open_count: number;
|
||||
user_count: number;
|
||||
}
|
||||
|
||||
// Auth Module
|
||||
interface Auth {
|
||||
signIn(options?: { attempt_temp_user_creation?: boolean }): Promise<boolean>;
|
||||
signOut(): void;
|
||||
isSignedIn(): boolean;
|
||||
getUser(): Promise<User>;
|
||||
}
|
||||
|
||||
interface User {
|
||||
uuid: string;
|
||||
username: string;
|
||||
email_confirmed: boolean;
|
||||
}
|
||||
|
||||
// Drivers Module
|
||||
interface Drivers {
|
||||
call(interface: string, driver: string, method: string, args?: object): Promise<any>;
|
||||
}
|
||||
|
||||
// FileSystem Module
|
||||
interface FileSystem {
|
||||
copy(source: string, destination: string, options?: CopyOptions): Promise<FSItem>;
|
||||
delete(path: string, options?: DeleteOptions): Promise<void>;
|
||||
getReadURL(path: string, expiresIn?: number): Promise<string>;
|
||||
mkdir(path: string, options?: MkdirOptions): Promise<FSItem>;
|
||||
move(source: string, destination: string, options?: MoveOptions): Promise<FSItem>;
|
||||
read(path: string, options?: ReadOptions): Promise<Blob>;
|
||||
readdir(path: string, options?: ReaddirOptions): Promise<FSItem[]>;
|
||||
readdir(options?: ReaddirOptions): Promise<FSItem[]>;
|
||||
rename(path: string, newName: string): Promise<FSItem>;
|
||||
space(): Promise<SpaceInfo>;
|
||||
stat(path: string): Promise<FSItem>;
|
||||
upload(items: FileList | File[] | Blob[], dirPath?: string, options?: object): Promise<FSItem[]>;
|
||||
write(path: string, data?: string | File | Blob, options?: WriteOptions): Promise<FSItem>;
|
||||
}
|
||||
|
||||
interface CopyOptions {
|
||||
overwrite?: boolean;
|
||||
dedupeName?: boolean;
|
||||
newName?: string;
|
||||
}
|
||||
|
||||
interface DeleteOptions {
|
||||
recursive?: boolean;
|
||||
descendantsOnly?: boolean;
|
||||
}
|
||||
|
||||
interface MkdirOptions {
|
||||
overwrite?: boolean;
|
||||
dedupeName?: boolean;
|
||||
createMissingParents?: boolean;
|
||||
}
|
||||
|
||||
interface MoveOptions extends CopyOptions {
|
||||
createMissingParents?: boolean;
|
||||
}
|
||||
|
||||
interface ReadOptions {
|
||||
offset?: number;
|
||||
byte_count?: number;
|
||||
}
|
||||
|
||||
interface ReaddirOptions {
|
||||
path?: string;
|
||||
uid?: string;
|
||||
}
|
||||
|
||||
interface WriteOptions {
|
||||
overwrite?: boolean;
|
||||
dedupeName?: boolean;
|
||||
createMissingParents?: boolean;
|
||||
}
|
||||
|
||||
interface SpaceInfo {
|
||||
capacity: number;
|
||||
used: number;
|
||||
}
|
||||
|
||||
interface FSItem {
|
||||
id: string;
|
||||
uid: string;
|
||||
name: string;
|
||||
path: string;
|
||||
is_dir: boolean;
|
||||
parent_id: string;
|
||||
parent_uid: string;
|
||||
created: number;
|
||||
modified: number;
|
||||
accessed: number;
|
||||
size: number | null;
|
||||
writable: boolean;
|
||||
read(): Promise<Blob>;
|
||||
readdir(): Promise<FSItem[]>;
|
||||
}
|
||||
|
||||
// Hosting Module
|
||||
interface Hosting {
|
||||
create(subdomain: string, dirPath?: string): Promise<Subdomain>;
|
||||
delete(subdomain: string): Promise<boolean>;
|
||||
get(subdomain: string): Promise<Subdomain>;
|
||||
list(): Promise<Subdomain[]>;
|
||||
update(subdomain: string, dirPath?: string): Promise<Subdomain>;
|
||||
}
|
||||
|
||||
interface Subdomain {
|
||||
uid: string;
|
||||
subdomain: string;
|
||||
root_dir: FSItem;
|
||||
}
|
||||
|
||||
// KeyValue Module
|
||||
interface KeyValue {
|
||||
set(key: string, value: string | number | boolean | object | any[]): Promise<boolean>;
|
||||
get(key: string): Promise<any>;
|
||||
del(key: string): Promise<boolean>;
|
||||
incr(key: string, amount?: number): Promise<number>;
|
||||
decr(key: string, amount?: number): Promise<number>;
|
||||
list(pattern?: string, returnValues?: boolean): Promise<string[] | KeyValuePair[]>;
|
||||
list(returnValues?: boolean): Promise<string[] | KeyValuePair[]>;
|
||||
flush(): Promise<boolean>;
|
||||
}
|
||||
|
||||
interface KeyValuePair {
|
||||
key: string;
|
||||
value: any;
|
||||
}
|
||||
|
||||
// Networking Module
|
||||
interface Networking {
|
||||
fetch(url: string, options?: RequestInit): Promise<Response>;
|
||||
Socket: typeof Socket;
|
||||
tls: {
|
||||
TLSSocket: typeof TLSSocket;
|
||||
};
|
||||
}
|
||||
|
||||
class Socket {
|
||||
constructor(hostname: string, port: number);
|
||||
write(data: ArrayBuffer | Uint8Array | string): void;
|
||||
close(): void;
|
||||
on(event: 'open', callback: () => void): void;
|
||||
on(event: 'data', callback: (buffer: Uint8Array) => void): void;
|
||||
on(event: 'error', callback: (reason: string) => void): void;
|
||||
on(event: 'close', callback: (hadError: boolean) => void): void;
|
||||
}
|
||||
|
||||
class TLSSocket extends Socket {
|
||||
constructor(hostname: string, port: number);
|
||||
}
|
||||
|
||||
// Permissions Module
|
||||
interface Permissions {
|
||||
grantApp(app_uid: string, permissionString: string): Promise<object>;
|
||||
grantAppAnyUser(app_uid: string, permissionString: string): Promise<object>;
|
||||
grantGroup(group_uid: string, permissionString: string): Promise<object>;
|
||||
grantOrigin(origin: string, permissionString: string): Promise<object>;
|
||||
grantUser(username: string, permissionString: string): Promise<object>;
|
||||
revokeApp(app_uid: string, permissionString: string): Promise<object>;
|
||||
revokeAppAnyUser(app_uid: string, permissionString: string): Promise<object>;
|
||||
revokeGroup(group_uid: string, permissionString: string): Promise<object>;
|
||||
revokeOrigin(origin: string, permissionString: string): Promise<object>;
|
||||
revokeUser(username: string, permissionString: string): Promise<object>;
|
||||
}
|
||||
|
||||
// UI Module
|
||||
interface UI {
|
||||
alert(message?: string, buttons?: AlertButton[]): Promise<string>;
|
||||
prompt(message?: string, defaultValue?: string): Promise<string | null>;
|
||||
authenticateWithPuter(): Promise<boolean>;
|
||||
contextMenu(options: ContextMenuOptions): void;
|
||||
createWindow(options?: WindowOptions): void;
|
||||
exit(statusCode?: number): void;
|
||||
getLanguage(): Promise<string>;
|
||||
hideSpinner(): void;
|
||||
launchApp(appName?: string, args?: object): Promise<AppConnection>;
|
||||
launchApp(options: LaunchAppOptions): Promise<AppConnection>;
|
||||
on(eventName: 'localeChanged', handler: (data: { language: string }) => void): void;
|
||||
on(eventName: 'themeChanged', handler: (data: ThemeData) => void): void;
|
||||
onItemsOpened(handler: (items: FSItem[]) => void): void;
|
||||
onLaunchedWithItems(handler: (items: FSItem[]) => void): void;
|
||||
onWindowClose(handler: () => void): void;
|
||||
parentApp(): AppConnection | null;
|
||||
setMenubar(options: MenubarOptions): void;
|
||||
setWindowHeight(height: number): void;
|
||||
setWindowPosition(x: number, y: number): void;
|
||||
setWindowSize(width: number, height: number): void;
|
||||
setWindowTitle(title: string): void;
|
||||
setWindowWidth(width: number): void;
|
||||
setWindowX(x: number): void;
|
||||
setWindowY(y: number): void;
|
||||
showColorPicker(defaultColor?: string): Promise<string>;
|
||||
showColorPicker(options?: object): Promise<string>;
|
||||
showDirectoryPicker(options?: { multiple?: boolean }): Promise<FSItem | FSItem[]>;
|
||||
showFontPicker(defaultFont?: string): Promise<{ fontFamily: string }>;
|
||||
showFontPicker(options?: object): Promise<{ fontFamily: string }>;
|
||||
showOpenFilePicker(options?: FilePickerOptions): Promise<FSItem | FSItem[]>;
|
||||
showSaveFilePicker(data?: any, defaultFileName?: string): Promise<FSItem>;
|
||||
showSpinner(): void;
|
||||
socialShare(url: string, message?: string, options?: { left?: number; top?: number }): void;
|
||||
wasLaunchedWithItems(): boolean;
|
||||
}
|
||||
|
||||
interface AlertButton {
|
||||
label: string;
|
||||
value?: string;
|
||||
type?: 'primary' | 'success' | 'info' | 'warning' | 'danger';
|
||||
}
|
||||
|
||||
interface ContextMenuOptions {
|
||||
items: (ContextMenuItem | '-')[];
|
||||
}
|
||||
|
||||
interface ContextMenuItem {
|
||||
label: string;
|
||||
action?: () => void;
|
||||
icon?: string;
|
||||
icon_active?: string;
|
||||
disabled?: boolean;
|
||||
items?: (ContextMenuItem | '-')[];
|
||||
}
|
||||
|
||||
interface WindowOptions {
|
||||
center?: boolean;
|
||||
content?: string;
|
||||
disable_parent_window?: boolean;
|
||||
has_head?: boolean;
|
||||
height?: number;
|
||||
is_resizable?: boolean;
|
||||
show_in_taskbar?: boolean;
|
||||
title?: string;
|
||||
width?: number;
|
||||
}
|
||||
|
||||
interface LaunchAppOptions {
|
||||
name?: string;
|
||||
args?: object;
|
||||
}
|
||||
|
||||
interface ThemeData {
|
||||
palette: {
|
||||
primaryHue: number;
|
||||
primarySaturation: string;
|
||||
primaryLightness: string;
|
||||
primaryAlpha: number;
|
||||
primaryColor: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface MenubarOptions {
|
||||
items: MenuItem[];
|
||||
}
|
||||
|
||||
interface MenuItem {
|
||||
label: string;
|
||||
action?: () => void;
|
||||
items?: MenuItem[];
|
||||
}
|
||||
|
||||
interface FilePickerOptions {
|
||||
multiple?: boolean;
|
||||
accept?: string | string[];
|
||||
}
|
||||
|
||||
interface AppConnection {
|
||||
usesSDK: boolean;
|
||||
on(eventName: 'message', handler: (message: any) => void): void;
|
||||
on(eventName: 'close', handler: (data: { appInstanceID: string }) => void): void;
|
||||
off(eventName: string, handler: Function): void;
|
||||
postMessage(message: any): void;
|
||||
close(): void;
|
||||
}
|
||||
|
||||
// Workers Module
|
||||
interface Workers {
|
||||
create(workerName: string, filePath: string): Promise<WorkerDeployment>;
|
||||
delete(workerName: string): Promise<boolean>;
|
||||
exec(workerURL: string, options?: WorkerExecOptions): Promise<Response>;
|
||||
get(workerName: string): Promise<WorkerInfo>;
|
||||
list(): Promise<WorkerInfo[]>;
|
||||
}
|
||||
|
||||
interface WorkerDeployment {
|
||||
success: boolean;
|
||||
url: string;
|
||||
errors: any[];
|
||||
}
|
||||
|
||||
interface WorkerExecOptions extends RequestInit {
|
||||
method?: string;
|
||||
headers?: object;
|
||||
body?: string | object;
|
||||
cache?: RequestCache;
|
||||
credentials?: RequestCredentials;
|
||||
mode?: RequestMode;
|
||||
redirect?: RequestRedirect;
|
||||
referrer?: string;
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
|
||||
interface WorkerInfo {
|
||||
name: string;
|
||||
url: string;
|
||||
file_path: string;
|
||||
file_uid: string;
|
||||
created_at: string;
|
||||
}
|
||||
}
|
||||
|
||||
declare const puter: Puter.Puter;
|
||||
|
||||
export = Puter;
|
||||
@@ -2,7 +2,13 @@
|
||||
"name": "@heyputer/puterjs",
|
||||
"version": "2.0.0",
|
||||
"description": "Puter.js - A JavaScript library for interacting with Puter services.",
|
||||
"main": "index.js",
|
||||
"main": "src/index.js",
|
||||
"types": "index.d.ts",
|
||||
"typings": "index.d.ts",
|
||||
"files": [
|
||||
"src/index.js",
|
||||
"index.d.ts"
|
||||
],
|
||||
"type": "module",
|
||||
"publishConfig": {
|
||||
"registry": "https://registry.npmjs.org/"
|
||||
@@ -11,13 +17,17 @@
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/HeyPuter/puter.git"
|
||||
},
|
||||
"keywords": [
|
||||
"puter",
|
||||
"puter.js",
|
||||
"puterjs"
|
||||
],
|
||||
"scripts": {
|
||||
"start-server": "npx http-server --cors -c-1",
|
||||
"start-webpack": "webpack && webpack --output-filename puter.dev.js --watch --devtool source-map",
|
||||
"start": "concurrently \"npm run start-server\" \"npm run start-webpack\"",
|
||||
"build": "webpack && { echo \"// Copyright 2024-present Puter Technologies Inc. All rights reserved.\"; echo \"// Generated on $(date '+%Y-%m-%d %H:%M')\n\"; cat ./dist/puter.js; } > temp && mv temp ./dist/puter.js"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "Puter Technologies Inc.",
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
@@ -25,6 +35,7 @@
|
||||
"webpack-cli": "^5.1.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@heyputer/kv.js": "^0.1.92"
|
||||
"@heyputer/kv.js": "^0.1.92",
|
||||
"@heyputer/putility": "^1.0.3"
|
||||
}
|
||||
}
|
||||
@@ -11,15 +11,15 @@ import { fileURLToPath } from 'url';
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
export default {
|
||||
entry: './src/index.js',
|
||||
output: {
|
||||
filename: 'puter.js',
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'globalThis.PUTER_ORIGIN': JSON.stringify(process.env.PUTER_ORIGIN || 'https://puter.com'),
|
||||
'globalThis.PUTER_API_ORIGIN': JSON.stringify(process.env.PUTER_API_ORIGIN || 'https://api.puter.com'),
|
||||
}),
|
||||
],
|
||||
entry: './src/index.js',
|
||||
output: {
|
||||
filename: 'puter.js',
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'globalThis.PUTER_ORIGIN': JSON.stringify(process.env.PUTER_ORIGIN || 'https://puter.com'),
|
||||
'globalThis.PUTER_API_ORIGIN': JSON.stringify(process.env.PUTER_API_ORIGIN || 'https://api.puter.com'),
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@heyputer/putility",
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.3",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
@@ -10,4 +10,4 @@
|
||||
},
|
||||
"author": "Puter Technologies Inc.",
|
||||
"license": "AGPL-3.0-only"
|
||||
}
|
||||
}
|
||||
@@ -1,60 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2024-present Puter Technologies Inc.
|
||||
*
|
||||
*
|
||||
* This file is part of Puter.
|
||||
*
|
||||
*
|
||||
* Puter is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const { AdvancedBase } = require("../AdvancedBase");
|
||||
const { TService } = require("../concepts/Service");
|
||||
const { TeePromise } = require("../libs/promise");
|
||||
|
||||
const mkstatus = name => {
|
||||
const c = class {
|
||||
get label () { return name }
|
||||
describe () { return name }
|
||||
}
|
||||
c.name = `Status${
|
||||
name[0].toUpperCase() + name.slice(1)
|
||||
}`
|
||||
return c;
|
||||
}
|
||||
const { AdvancedBase } = require('../AdvancedBase');
|
||||
const { TService } = require('../concepts/Service');
|
||||
|
||||
const StatusEnum = {
|
||||
Registering: 'registering',
|
||||
Pending: 'pending',
|
||||
Initializing: 'initializing',
|
||||
Running: 'running',
|
||||
};
|
||||
class ServiceManager extends AdvancedBase {
|
||||
static StatusRegistering = mkstatus('registering');
|
||||
static StatusPending = class StatusPending {
|
||||
constructor ({ waiting_for }) {
|
||||
this.waiting_for = waiting_for;
|
||||
}
|
||||
get label () { return 'waiting'; }
|
||||
// TODO: trait?
|
||||
describe () {
|
||||
return `waiting for: ${this.waiting_for.join(', ')}`
|
||||
}
|
||||
}
|
||||
static StatusInitializing = mkstatus('initializing');
|
||||
static StatusRunning = class StatusRunning {
|
||||
constructor ({ start_ts }) {
|
||||
this.start_ts = start_ts;
|
||||
}
|
||||
get label () { return 'running'; }
|
||||
describe () {
|
||||
return `running (since ${this.start_ts})`;
|
||||
}
|
||||
}
|
||||
constructor ({ context } = {}) {
|
||||
constructor({ context } = {}) {
|
||||
super();
|
||||
|
||||
this.context = context;
|
||||
@@ -68,7 +41,7 @@ class ServiceManager extends AdvancedBase {
|
||||
// initialized; mapped like: waiting_[dependency] = Set(dependents)
|
||||
this.waiting_ = {};
|
||||
}
|
||||
async register (name, factory, options = {}) {
|
||||
async register(name, factory, options = {}) {
|
||||
await new Promise(rslv => setTimeout(rslv, 0));
|
||||
|
||||
const ins = factory.create({
|
||||
@@ -78,25 +51,25 @@ class ServiceManager extends AdvancedBase {
|
||||
const entry = {
|
||||
name,
|
||||
instance: ins,
|
||||
status: new this.constructor.StatusRegistering(),
|
||||
status: StatusEnum.Registering,
|
||||
};
|
||||
this.services_l_.push(entry);
|
||||
this.services_m_[name] = entry;
|
||||
|
||||
await this.maybe_init_(name);
|
||||
}
|
||||
info (name) {
|
||||
info(name) {
|
||||
return this.services_m_[name];
|
||||
}
|
||||
get (name) {
|
||||
get(name) {
|
||||
const info = this.services_m_[name];
|
||||
if ( ! info ) throw new Error(`Service not registered: ${name}`);
|
||||
if ( ! (info.status instanceof this.constructor.StatusRunning ) ) {
|
||||
if ( info.status !== StatusEnum.Running ) {
|
||||
return undefined;
|
||||
}
|
||||
return info.instance;
|
||||
}
|
||||
async aget (name) {
|
||||
async aget(name) {
|
||||
await this.wait_for_init([name]);
|
||||
return this.get(name);
|
||||
}
|
||||
@@ -105,7 +78,7 @@ class ServiceManager extends AdvancedBase {
|
||||
* Wait for the specified list of services to be initialized.
|
||||
* @param {*} depends - list of services to wait for
|
||||
*/
|
||||
async wait_for_init (depends) {
|
||||
async wait_for_init(depends) {
|
||||
let check;
|
||||
|
||||
await new Promise(rslv => {
|
||||
@@ -134,7 +107,7 @@ class ServiceManager extends AdvancedBase {
|
||||
});
|
||||
};
|
||||
|
||||
get_waiting_for_ (depends) {
|
||||
get_waiting_for_(depends) {
|
||||
const waiting_for = [];
|
||||
for ( const depend of depends ) {
|
||||
const depend_entry = this.services_m_[depend];
|
||||
@@ -142,14 +115,14 @@ class ServiceManager extends AdvancedBase {
|
||||
waiting_for.push(depend);
|
||||
continue;
|
||||
}
|
||||
if ( ! (depend_entry.status instanceof this.constructor.StatusRunning) ) {
|
||||
if ( ( depend_entry.status !== StatusEnum.Running ) ) {
|
||||
waiting_for.push(depend);
|
||||
}
|
||||
}
|
||||
return waiting_for;
|
||||
}
|
||||
|
||||
async maybe_init_ (name) {
|
||||
async maybe_init_(name) {
|
||||
const entry = this.services_m_[name];
|
||||
const depends = entry.instance.as(TService).get_depends();
|
||||
const waiting_for = this.get_waiting_for_(depends);
|
||||
@@ -160,34 +133,33 @@ class ServiceManager extends AdvancedBase {
|
||||
}
|
||||
|
||||
for ( const dependency of waiting_for ) {
|
||||
/** @type Set */
|
||||
const waiting_set = this.waiting_[dependency] ||
|
||||
(this.waiting_[dependency] = new Set());
|
||||
waiting_set.add(name);
|
||||
if ( !this.waiting_[dependency] ) {
|
||||
this.waiting_[dependency] = new Set();
|
||||
}
|
||||
this.waiting_[dependency].add(name);
|
||||
}
|
||||
|
||||
entry.status = new this.constructor.StatusPending(
|
||||
{ waiting_for });
|
||||
entry.status = StatusEnum.Pending;
|
||||
entry.statusWaitingFor = waiting_for;
|
||||
}
|
||||
|
||||
// called when a service has all of its dependencies initialized
|
||||
// and is ready to be initialized itself
|
||||
async init_service_ (name, modifiers = {}) {
|
||||
async init_service_(name, modifiers = {}) {
|
||||
const entry = this.services_m_[name];
|
||||
entry.status = new this.constructor.StatusInitializing();
|
||||
entry.status = StatusEnum.Initializing;
|
||||
|
||||
const service_impl = entry.instance.as(TService);
|
||||
await service_impl.init();
|
||||
entry.status = new this.constructor.StatusRunning({
|
||||
start_ts: new Date(),
|
||||
});
|
||||
entry.status = StatusEnum.Running;
|
||||
entry.statusStartTS = new Date();
|
||||
/** @type Set */
|
||||
const maybe_ready_set = this.waiting_[name];
|
||||
const promises = [];
|
||||
if ( maybe_ready_set ) {
|
||||
for ( const dependent of maybe_ready_set.values() ) {
|
||||
promises.push(this.maybe_init_(dependent, {
|
||||
no_init_listeners: true
|
||||
no_init_listeners: true,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user