14 Commits

Author SHA1 Message Date
d258bcc919 separated JavaScript and TypeScript export,
added AGENTS.md for AI bot
2026-02-13 09:54:08 +01:00
fab8efd780 added bin/apilite-files for generate best-practce files 2026-02-09 07:33:17 +01:00
746b06e317 added response all result for special call __HELP__ 2025-10-16 01:39:59 +02:00
3fa131e1b2 fixed API name for export in template 2025-10-16 01:30:46 +02:00
c0fd7b3fe5 added apiName 2025-10-13 23:36:34 +02:00
dab07e55ec fixed typo 2025-06-15 19:05:30 +02:00
a10e864ba3 transformed spaces to tabs 2025-06-12 08:28:15 +02:00
9d45bb5ceb update example of typescript in README 2025-06-12 08:26:58 +02:00
f1e6e92f46 added instruction of installation into README 2025-06-12 08:22:21 +02:00
2d3f8bfdd4 added list of method for HTML view 2025-06-12 07:54:37 +02:00
8502508a11 Merge branch 'main' of https://gitea.tpsoft.org/TPsoft.org/APIlite 2025-06-01 19:20:11 +02:00
a59044102c added format and endpoint into constructor,
fixed processing of parameters in methods,
added return for HTML manual,
chaged TypeScript code to class object export as default
2025-06-01 19:19:48 +02:00
b8ef58e132 added code types into README 2025-05-28 18:12:54 +02:00
2f7f63b620 removed require ext-pdo 2025-05-28 18:07:35 +02:00
9 changed files with 677 additions and 136 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/composer.lock
/vendor

37
AGENTS.md Normal file
View File

@ -0,0 +1,37 @@
# AGENTS.md
## Project overview
APIlite is a lightweight PHP library. A class extends `TPsoft\APIlite\APIlite`, public methods become API actions, requests are processed automatically, and responses are returned as JSON.
The project also generates frontend clients:
- JavaScript client: `format=javascript` (`.js`)
- TypeScript client: `format=typescript` (`.ts`, real typed output)
## Core files
- `src/APIlite.php`: runtime, routing, docs JSON/HTML/client generation
- `src/help.tpl.php`: HTML documentation template
- `src/javascript.tpl.php`: JavaScript client template
- `src/typescript.tpl.php`: TypeScript client template
- `bin/apilite-files`: helper script for bootstrapping files in consumer projects
## Working rules
- Keep API behavior backward compatible unless explicitly requested.
- Maintain both generated clients:
- JavaScript stays plain JS.
- TypeScript stays typed (interfaces + typed method signatures).
- If you change output field names in JSON help, update README and templates consistently.
- Prefer small, focused changes over broad rewrites.
## Verification checklist
- Run syntax checks on edited PHP files:
- `php -l src/APIlite.php`
- `php -l src/help.tpl.php`
- `php -l src/javascript.tpl.php`
- `php -l src/typescript.tpl.php`
- `php -l bin/apilite-files`
- If client generation changes, test with:
- `php test/APIcalculator.php --javascript`
- `php test/APIcalculator.php --typescript`
## Documentation rule
When output format names/flags/URLs change, update `README.md` in the same change set.

140
README.md
View File

@ -2,11 +2,49 @@
A set of tools to simplify the work of creating backend APIs for your frontend projects. Includes tutorials, patterns and practical examples for creating projects based on REST APIs. A set of tools to simplify the work of creating backend APIs for your frontend projects. Includes tutorials, patterns and practical examples for creating projects based on REST APIs.
## Installation
Download source code and add to your project
```php
<?php
require_once __DIR__ . '/APIlite/src/APIlite.php';
```
or use composer
```bash
composer require tpsoft/apilite
```
and add to your project
```php
<?php
require __DIR__ . '/vendor/autoload.php';
class YourAPI extends \TPsoft\APIlite\APIlite {
/**
* My first method for greetings.
*
* @param string $name Your name
* @param int $age Your age
* @return string Greetings
*/
public function myFirstMethod(string $name, int $age): string
{
return 'Hi, I`m '.$name.' and I`m '.$age.' years old.';
}
}
```
## Basic usage ## Basic usage
For example, we create an API for calculator. So we create class `APIcalculator` and store in file `test/APIcalculator.php` where we defined each actions for API as public method. For example, we create an API for calculator. So we create class `APIcalculator` and store in file `test/APIcalculator.php` where we defined each actions for API as public method.
``` ```php
<?php <?php
/** /**
@ -90,10 +128,11 @@ It is important to extend the `APIcalculator` class with the `\TPsoft\APIlite\AP
When you run this subfile through the webserver, you will see the JSON documentation in the browser When you run this subfile through the webserver, you will see the JSON documentation in the browser
``` ```js
{ {
"name": "APIcalculator", "name": "APIcalculator",
"html_version": "http://localhost/APIlite/test/APIcalculator.php?format=html", "html_version": "http://localhost/APIlite/test/APIcalculator.php?format=html",
"javascript_version": "http://localhost/APIlite/test/APIcalculator.php?format=javascript",
"typescript_version": "http://localhost/APIlite/test/APIcalculator.php?format=typescript", "typescript_version": "http://localhost/APIlite/test/APIcalculator.php?format=typescript",
"actions": [ "actions": [
{ {
@ -192,84 +231,33 @@ and there is also an HTML version available
<img src="test/Example.APIcalculator.png" /> <img src="test/Example.APIcalculator.png" />
To connect to the API from TypeScript (e.g. Vue application) it is possible to download the backend script You can also download generated frontend clients:
``` * JavaScript: `?format=javascript`
/** * TypeScript (typed for Vue/TS projects): `?format=typescript`
* Generated by APIlite
* https://gitea.tpsoft.org/TPsoft.org/APIlite
*
* 2025-05-28 15:44:07 */
export const backend = { JavaScript usage example:
endpont: window.location.origin + "APIcalculator.php",
/* ---------------------------------------------------- ```js
* General API call import backend from './backend.js';
*/
call(method, data, callback) { backend.add(1, 2).then((response) => {
var xhttp = new XMLHttpRequest(); console.log(response.data);
xhttp.withCredentials = true;
xhttp.onreadystatechange = function() {
if (this.readyState == 4) {
if (this.status == 200) {
if (callback != null) callback(JSON.parse(this.responseText));
} else {
if (callback != null) callback({'status': 'ERROR', 'message': 'HTTP STATUS ' + this.status});
}
}
}
var form_data = new FormData();
Object.keys(data).forEach(key => {
let val = data[key];
if (typeof val == 'object') val = JSON.stringify(val);
form_data.append(key, val);
}); });
xhttp.open('POST', this.endpont + '?action=' + method);
xhttp.send(form_data);
},
callPromise(method, data) {
return new Promise((resolve, reject) => {
this.call(method, data, function(response) {
if (response.status == 'OK') {
resolve(response.data);
} else {
reject(response.msg);
}
});
})
},
/* ----------------------------------------------------
* API actions
*/
help() {
return this.callPromise('__HELP__', {});
},
add(a, b) {
return this.callPromise('add', {a: a, b: b});
},
subtract(a, b) {
return this.callPromise('subtract', {a: a, b: b});
},
multiply(a, b) {
return this.callPromise('multiply', {a: a, b: b});
},
divide(a, b) {
return this.callPromise('divide', {a: a, b: b});
},
};
``` ```
These outputs can also be generated in the command line as follows TypeScript usage example (Vue + TS):
* for HTML`$> php APIcalculator.php --html` ```ts
* for TypeScript`$> php APIcalculator.php --typescript` import backend from './backend';
backend.add(1, 2).then((response) => {
console.log(response.data); // typed value based on PHP return type
});
```
These outputs can also be generated in command line:
* HTML: `$> php APIcalculator.php --html`
* JavaScript: `$> php APIcalculator.php --javascript > backend.js` (`--js` alias is available)
* TypeScript: `$> php APIcalculator.php --typescript > backend.ts`

129
bin/apilite-files Normal file
View File

@ -0,0 +1,129 @@
#!/usr/bin/env php
<?php
include $_composer_autoload_path ?? __DIR__ . '/../vendor/autoload.php';
echo " APIlite files\n\n";
$project_root = findProjectRoot(__DIR__);
if (!$project_root) {
echo "⛔ Project root not found\n";
exit(1);
}
$composer_arr = json_decode(file_get_contents($project_root . '/composer.json'), true);
echo "Project root: {$project_root}\n";
$source_dirs = array_values($composer_arr['autoload']['psr-4']);
foreach ($source_dirs as $index => $source_dir) {
echo "➡️ [$index]: $project_root/$source_dir\n";
}
$selected = readline("❔ Select source dir [default: 0]: ");
$selected = empty($selected) ? 0 : $selected;
if (!isset($source_dirs[$selected])) {
echo "⛔ Invalid selection\n";
exit(2);
}
$source_dir = $project_root . '/' . $source_dirs[$selected];
$source_dir = rtrim($source_dir, '/') . '/';
$namespaces = array_keys($composer_arr['autoload']['psr-4']);
$namespace = $namespaces[$selected];
echo "✔️ Source dir: {$source_dir} with namespace: {$namespace}\n";
// Copy files
echo " Copy files\n";
copyFile($source_dir . 'API.php', '<' . '?php
namespace ' . rtrim($namespace, '\\') . ';
use TPsoft\APIlite\APIlite;
class API extends APIlite {
}
');
copyFile($project_root . '/scripts/buildTypeScript.php', '<' . '?php
require __DIR__ . \'/../vendor/autoload.php\';
ob_start();
$backend_api = new TPsoft\BugreportBackend\API(\'typescript\', \'import.meta.env.VITE_BACKENDAPI_URL\', \'backend\');
$output = ob_get_contents();
ob_end_clean();
$ts_path = realpath(__DIR__ . \'/../../frontend/src\').\'/backend.ts\';
$suc = file_put_contents($ts_path, $output);
if ($suc === false) {
echo "✗ TypeScript store into file failed\n";
exit(2);
}
echo "✓ TypeScript backend script created\n";
');
copyFile($project_root . '/public/API.php', '<' . '?php
require_once __DIR__.\'/../src/API.php\';
new \\' . $namespace . 'API();
');
// Change composer.json
if (!isset($composer_arr['scripts']['build'])) {
$composer_arr['scripts']['build'] = 'php scripts/buildTypeScript.php';
}
$composer_json = json_encode($composer_arr, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$composer_json = preg_replace_callback(
'/^( +)/m',
fn($m) => str_repeat("\t", intdiv(strlen($m[1]), 4)),
$composer_json
);
$suc = file_put_contents($project_root . '/composer.json', $composer_json);
if ($suc) {
echo "✅ File changed: $project_root/composer.json\n";
} else {
echo "🔴 Failed to change file: $project_root/composer.json\n";
}
/**
* Functions
*/
function findProjectRoot(string $startDir): ?string
{
$dir = realpath($startDir);
if (!$dir) return null;
while ($dir && $dir !== dirname($dir)) {
if (is_file($dir . '/vendor/autoload.php')) {
return $dir;
}
$dir = dirname($dir);
}
return null;
}
function copyFile(string $destination, string $content): bool
{
$dir = dirname($destination);
if (!file_exists($dir)) {
mkdir($dir, 0777, true);
}
if (file_exists($destination)) {
echo "⚠️ File already exists: $destination\n";
$confirm = readline("❔ Overwrite? (y - yes, other - no): ");
if ($confirm != 'y') {
return false;
}
}
$suc = file_put_contents($destination, $content) !== false;
if ($suc) {
echo "✅ File created: $destination\n";
} else {
echo "🔴 Failed to create file: $destination\n";
}
return $suc;
}

View File

@ -9,6 +9,7 @@
"rest", "rest",
"json", "json",
"php", "php",
"javascript",
"typescript" "typescript"
], ],
"authors": [ "authors": [
@ -26,12 +27,14 @@
} }
], ],
"require": { "require": {
"php": ">=8.2", "php": ">=8.2"
"ext-pdo": "*"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"TPsoft\\APIlite\\": "src/" "TPsoft\\APIlite\\": "src/"
} }
} },
"bin": [
"bin/apilite-files"
]
} }

View File

@ -18,23 +18,48 @@ class APIlite
{ {
private string $apiName = ''; private string $apiName = '';
private string $endpoint = '';
private $methods = array(); private $methods = array();
public function __construct() public function __construct(?string $format = null, ?string $endpoint = null, ?string $apiName = null)
{ {
register_shutdown_function(array($this, '_shutdownHandler')); register_shutdown_function(array($this, '_shutdownHandler'));
$this->endpoint = $endpoint ?? $this->getCurrentUrl();
$this->apiName = $apiName ?? '';
$this->analyzeClass(); $this->analyzeClass();
if (isset($_REQUEST['action'])) { if (isset($_REQUEST['action'])) {
$this->doAction($_REQUEST['action']); $this->doAction($_REQUEST['action']);
} else { } else {
global $argv; global $argv;
if (isset($_REQUEST['format'])) {
$format = $_REQUEST['format'];
}
if (isset($argv)) {
$switches = array_map('strtolower', is_array($argv) ? $argv : []); $switches = array_map('strtolower', is_array($argv) ? $argv : []);
if ((isset($_REQUEST['format']) && $_REQUEST['format'] == 'html') || in_array('--html', $switches)) { if (in_array('--html', $switches)) {
$format = 'html';
}
if (in_array('--javascript', $switches) || in_array('--js', $switches)) {
$format = 'javascript';
}
if (in_array('--typescript', $switches)) {
$format = 'typescript';
}
}
switch ($format) {
case 'html':
$this->printHelpHTML(); $this->printHelpHTML();
} elseif ((isset($_REQUEST['format']) && $_REQUEST['format'] == 'typescript') || in_array('--typescript', $switches)) { break;
case 'javascript':
case 'js':
$this->printHelpJavascript();
break;
case 'typescript':
$this->printHelpTypescript(); $this->printHelpTypescript();
} else { break;
default:
$this->printHelpJSON(); $this->printHelpJSON();
break;
} }
} }
} }
@ -50,7 +75,9 @@ class APIlite
private function analyzeClass(): bool private function analyzeClass(): bool
{ {
$refClass = new \ReflectionClass($this); $refClass = new \ReflectionClass($this);
$this->apiName = $refClass->getName(); if (strlen($this->apiName) <= 0) {
$this->apiName = $refClass->getShortName();
}
$this->methods = array(); $this->methods = array();
foreach ($refClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $ref_method) { foreach ($refClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $ref_method) {
$method_name = $ref_method->getName(); $method_name = $ref_method->getName();
@ -81,11 +108,28 @@ class APIlite
if ($ref_param->isOptional()) { if ($ref_param->isOptional()) {
$param['default'] = $ref_param->getDefaultValue(); $param['default'] = $ref_param->getDefaultValue();
} }
if (!is_null($method['doc'])) {
$param['doc'] = $this->parseParamDoc($method['doc'], $param['name']); $param['doc'] = $this->parseParamDoc($method['doc'], $param['name']);
}
$method['params'][] = $param; $method['params'][] = $param;
} }
if ($ref_method->hasReturnType()) { if ($ref_method->hasReturnType()) {
$method['return'] = $ref_method->getReturnType()->getName(); $ref_type = $ref_method->getReturnType();
if ($ref_type instanceof \ReflectionNamedType) {
$method['return'] = $ref_type->getName();
}
if ($ref_type instanceof \ReflectionUnionType
|| $ref_type instanceof \ReflectionIntersectionType )
{
$types = $ref_type->getTypes();
$method['return'] = [];
foreach ($types as $type) {
if ($type instanceof \ReflectionNamedType) {
$method['return'][] = $type->getName();
}
}
}
} }
$this->methods[] = $method; $this->methods[] = $method;
} }
@ -152,6 +196,7 @@ class APIlite
$this->response(array( $this->response(array(
'name' => $this->apiName, 'name' => $this->apiName,
'html_version' => $this->getCurrentUrl().'?format=html', 'html_version' => $this->getCurrentUrl().'?format=html',
'javascript_version' => $this->getCurrentUrl().'?format=javascript',
'typescript_version' => $this->getCurrentUrl().'?format=typescript', 'typescript_version' => $this->getCurrentUrl().'?format=typescript',
'actions' => $this->methods 'actions' => $this->methods
)); ));
@ -162,20 +207,36 @@ class APIlite
include __DIR__ . '/help.tpl.php'; include __DIR__ . '/help.tpl.php';
} }
private function printHelpTypescript(): void private function printHelpJavascript(): void
{ {
ob_clean(); ob_clean();
header('Content-Type: application/javascript'); header('Content-Type: application/javascript');
header('Content-Disposition: attachment; filename="' . $this->apiName . '.js"'); header('Content-Disposition: attachment; filename="' . $this->apiName . '.js"');
include __DIR__ . '/javascript.tpl.php';
}
private function printHelpTypescript(): void
{
ob_clean();
header('Content-Type: application/typescript');
header('Content-Disposition: attachment; filename="' . $this->apiName . '.ts"');
include __DIR__ . '/typescript.tpl.php'; include __DIR__ . '/typescript.tpl.php';
} }
private function getCurrentUrl(): string { private function getCurrentUrl(): string {
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $https = !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off';
$_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://"; $serverPort = isset($_SERVER['SERVER_PORT']) ? (int) $_SERVER['SERVER_PORT'] : null;
$host = $_SERVER['HTTP_HOST']; $protocol = ($https || $serverPort === 443) ? 'https://' : 'http://';
$uri = $_SERVER['REQUEST_URI']; $host = $_SERVER['HTTP_HOST'] ?? 'localhost';
$uri = $_SERVER['REQUEST_URI'] ?? ($_SERVER['SCRIPT_NAME'] ?? '/');
$path = parse_url($uri, PHP_URL_PATH); $path = parse_url($uri, PHP_URL_PATH);
if (!is_string($path) || $path === '') {
$path = '/';
}
$path = str_replace('\\', '/', $path);
if (substr($path, 0, 1) !== '/') {
$path = '/' . ltrim($path, '/');
}
return $protocol . $host . $path; return $protocol . $host . $path;
} }

View File

@ -236,6 +236,140 @@
} }
} }
/* Methods grid */
.methods-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1rem;
margin-bottom: 2rem;
}
.method-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 12px;
padding: 1.5rem;
text-align: center;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
position: relative;
overflow: hidden;
}
.method-card::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.5s;
}
.method-card:hover::before {
left: 100%;
}
.method-card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.2);
}
.method-card a {
color: white;
text-decoration: none;
font-weight: 600;
font-size: 1.1rem;
display: block;
position: relative;
z-index: 1;
}
/*
.method-card:nth-child(odd) {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.method-card:nth-child(even) {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
}
.method-card:nth-child(3n) {
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
}
.method-card:nth-child(4n) {
background: linear-gradient(135deg,rgb(86, 209, 127) 0%,rgb(52, 199, 172) 100%);
}
.method-card:nth-child(5n) {
background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);
}
*/
.method-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.methods-section-title {
font-size: 2rem;
font-weight: 700;
color: #2c3e50;
margin-bottom: 1.5rem;
text-align: center;
position: relative;
}
.methods-section-title::after {
content: '';
position: absolute;
bottom: -10px;
left: 50%;
transform: translateX(-50%);
width: 80px;
height: 4px;
background: linear-gradient(90deg, #667eea, #764ba2);
border-radius: 2px;
}
/* Responsive adjustments for methods grid */
@media (max-width: 768px) {
.methods-grid {
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 0.8rem;
}
.method-card {
padding: 1.2rem;
}
.method-card a {
font-size: 1rem;
}
.methods-section-title {
font-size: 1.7rem;
}
}
@media (max-width: 480px) {
.methods-grid {
grid-template-columns: 1fr;
gap: 0.6rem;
}
.method-card {
padding: 1rem;
}
.method-card a {
font-size: 0.95rem;
}
.methods-section-title {
font-size: 1.5rem;
}
}
/* Utility classes */ /* Utility classes */
.text-center { .text-center {
text-align: center; text-align: center;
@ -262,10 +396,23 @@
<nav class="nav-menu"> <nav class="nav-menu">
<div class="nav-links"> <div class="nav-links">
<a href="<?php echo $this->getCurrentUrl(); ?>?format=json">🔗 JSON Documentation</a> <a href="<?php echo $this->getCurrentUrl(); ?>?format=json">🔗 JSON Documentation</a>
<a href="<?php echo $this->getCurrentUrl(); ?>?format=javascript">📄 JavaScript backend script</a>
<a href="<?php echo $this->getCurrentUrl(); ?>?format=typescript">📄 TypeScript backend script</a> <a href="<?php echo $this->getCurrentUrl(); ?>?format=typescript">📄 TypeScript backend script</a>
</div> </div>
</nav> </nav>
<!-- List of Methods -->
<section class="method-section">
<h2 class="methods-section-title">List of Methods</h2>
<div class="methods-grid">
<?php if (is_array($this->methods)) foreach ($this->methods as $index => $method) { ?>
<div class="method-card">
<a href="#<?php echo $method['name']; ?>"><?php echo $method['name']; ?></a>
</div>
<?php } ?>
</div>
</section>
<!-- Main Content --> <!-- Main Content -->
<main class="main-content"> <main class="main-content">
<div class="endpoint-url mb-3"> <div class="endpoint-url mb-3">
@ -274,7 +421,7 @@
</div> </div>
<?php if (is_array($this->methods)) foreach ($this->methods as $index => $method) { ?> <?php if (is_array($this->methods)) foreach ($this->methods as $index => $method) { ?>
<section class="method-section"> <section class="method-section" id="<?php echo $method['name']; ?>">
<h2 class="method-title"><?php echo $method['name']; ?></h2> <h2 class="method-title"><?php echo $method['name']; ?></h2>
<?php if (!empty($method['description'])) { ?> <?php if (!empty($method['description'])) { ?>
@ -306,6 +453,16 @@
</div> </div>
</div> </div>
<?php } ?> <?php } ?>
<h3 style="margin-bottom: 1rem; color: #2c3e50; display: inline-block;">Return:</h3>
<?php if (is_string($method['return'])) { ?>
<code class="parameter-type"><?php echo $method['return']; ?></code>
<?php } ?>
<?php if (is_array($method['return'])) foreach ($method['return'] as $return) { ?>
<code class="parameter-type"><?php echo $return; ?></code>
<?php } ?>
</div> </div>
<?php } ?> <?php } ?>
</section> </section>

69
src/javascript.tpl.php Normal file
View File

@ -0,0 +1,69 @@
/**
* Generated by APIlite
* https://gitea.tpsoft.org/TPsoft.org/APIlite
*
* <?php echo date('Y-m-d H:i:s'); ?>
*/
class <?php echo $this->apiName; ?> {
endpoint = <?php echo sprintf(substr($this->endpoint, 0, 4) == 'http' ? '"%s"' : '%s', $this->endpoint); ?>;
/* ----------------------------------------------------
* General API call
*/
call(method, data, callback) {
var xhttp = new XMLHttpRequest();
xhttp.withCredentials = true;
xhttp.onreadystatechange = function() {
if (this.readyState === 4) {
if (this.status === 200) {
if (callback != null) callback(JSON.parse(this.responseText));
} else {
if (callback != null) callback({'status': 'ERROR', 'msg': 'HTTP STATUS ' + this.status});
}
}
}
var form_data = new FormData();
Object.keys(data).forEach(key => {
let val = data[key];
if (typeof val === 'undefined') return;
if (typeof val == 'object') val = JSON.stringify(val);
form_data.append(key, val);
});
xhttp.open('POST', this.endpoint + '?action=' + method);
xhttp.send(form_data);
}
callPromise(method, data) {
return new Promise((resolve, reject) => {
this.call(method, data, function(response) {
if (method === '__HELP__') {
resolve(response);
return;
}
if (response.status === 'OK') {
resolve(response.data);
} else {
reject(response.msg);
}
});
})
}
/* ----------------------------------------------------
* API actions
*/
help() {
return this.callPromise('__HELP__', {});
}
<?php if (is_array($this->methods)) foreach ($this->methods as $method) {
echo "\t".$method['name'].'('.implode(', ', array_map(function($param) { return $param['name']; }, $method['params'])).') {';
echo "\n\t\treturn this.callPromise('".$method['name']."', {".implode(', ', array_map(function($param) { return $param['name'].': '.$param['name']; }, $method['params']))."});";
echo "\n\t}\n\n";
}
?>
};
export default new <?php echo $this->apiName; ?>();

View File

@ -1,3 +1,39 @@
<?php
$mapType = function (?string $type): string {
if (!is_string($type) || $type === '') {
return 'unknown';
}
$type = strtolower(ltrim($type, '\\'));
return match ($type) {
'int', 'integer', 'float', 'double' => 'number',
'string' => 'string',
'bool', 'boolean' => 'boolean',
'array', 'iterable' => 'unknown[]',
'object', 'stdclass' => 'Record<string, unknown>',
'mixed', 'resource' => 'unknown',
'null', 'void' => 'null',
'scalar' => 'string | number | boolean',
'callable', 'closure' => '(...args: unknown[]) => unknown',
'never' => 'never',
default => 'unknown',
};
};
$mapUnionType = function (mixed $type) use ($mapType): string {
if (is_array($type)) {
$parts = array();
foreach ($type as $singleType) {
$parts[] = $mapType(is_string($singleType) ? $singleType : null);
}
$parts = array_values(array_unique($parts));
return empty($parts) ? 'unknown' : implode(' | ', $parts);
}
return $mapType(is_string($type) ? $type : null);
};
?>
/** /**
* Generated by APIlite * Generated by APIlite
* https://gitea.tpsoft.org/TPsoft.org/APIlite * https://gitea.tpsoft.org/TPsoft.org/APIlite
@ -5,58 +41,117 @@
* <?php echo date('Y-m-d H:i:s'); ?> * <?php echo date('Y-m-d H:i:s'); ?>
*/ */
export const backend = { export interface APIliteActionResponse<T> {
endpont: window.location.origin + "<?php echo $this->apiName; ?>.php", status: 'OK';
data: T;
}
/* ---------------------------------------------------- export interface APIliteErrorResponse {
* General API call status: 'ERROR';
*/ msg: string;
call(method, data, callback) { }
var xhttp = new XMLHttpRequest();
export interface APIliteMethodParam {
name: string;
type: string | null;
optional: boolean;
default: unknown;
doc: string | null;
}
export interface APIliteMethodDoc {
name: string;
doc: string | null;
description: string | null;
params: APIliteMethodParam[];
return: string | string[] | null;
}
export interface APIliteHelpResponse {
name: string;
html_version: string;
javascript_version: string;
typescript_version: string;
actions: APIliteMethodDoc[];
}
class <?php echo $this->apiName; ?> {
endpoint: string = <?php echo sprintf(substr($this->endpoint, 0, 4) == 'http' ? '"%s"' : '%s', $this->endpoint); ?>;
private call(
method: string,
data: Record<string, unknown>,
callback: (response: APIliteHelpResponse | APIliteActionResponse<unknown> | APIliteErrorResponse) => void
): void {
const xhttp = new XMLHttpRequest();
xhttp.withCredentials = true; xhttp.withCredentials = true;
xhttp.onreadystatechange = function() { xhttp.onreadystatechange = function() {
if (this.readyState == 4) { if (this.readyState === 4) {
if (this.status == 200) { if (this.status === 200) {
if (callback != null) callback(JSON.parse(this.responseText)); const response = JSON.parse(this.responseText) as APIliteHelpResponse | APIliteActionResponse<unknown> | APIliteErrorResponse;
callback(response);
} else { } else {
if (callback != null) callback({'status': 'ERROR', 'message': 'HTTP STATUS ' + this.status}); callback({ status: 'ERROR', msg: 'HTTP STATUS ' + this.status });
} }
} }
} };
var form_data = new FormData();
Object.keys(data).forEach(key => {
let val = data[key];
if (typeof val == 'object') val = JSON.stringify(val);
form_data.append(key, val);
});
xhttp.open('POST', this.endpont + '?action=' + method);
xhttp.send(form_data);
},
callPromise(method, data) { const formData = new FormData();
return new Promise((resolve, reject) => { Object.keys(data).forEach((key) => {
this.call(method, data, function(response) { const rawValue = data[key];
if (response.status == 'OK') { if (typeof rawValue === 'undefined') {
resolve(response.data); return;
}
let value: string | Blob;
if (rawValue instanceof Blob) {
value = rawValue;
} else if (typeof rawValue === 'object' && rawValue !== null) {
value = JSON.stringify(rawValue);
} else { } else {
value = String(rawValue);
}
formData.append(key, value);
});
xhttp.open('POST', this.endpoint + '?action=' + method);
xhttp.send(formData);
}
private callPromise<T>(method: string, data: Record<string, unknown>): Promise<T> {
return new Promise<T>((resolve, reject) => {
this.call(method, data, (response) => {
if (method === '__HELP__') {
resolve(response as T);
return;
}
if (response.status === 'OK') {
resolve(response.data as T);
return;
}
reject(response.msg); reject(response.msg);
}
}); });
}) });
}, }
/* ---------------------------------------------------- help(): Promise<APIliteHelpResponse> {
* API actions return this.callPromise<APIliteHelpResponse>('__HELP__', {});
*/ }
help() {
return this.callPromise('__HELP__', {});
},
<?php if (is_array($this->methods)) foreach ($this->methods as $method) { <?php if (is_array($this->methods)) foreach ($this->methods as $method) {
echo "\t".$method['name'].'('.implode(', ', array_map(function($param) { return $param['name']; }, $method['params'])).') {'; $paramsSignature = array();
echo "\n\t\treturn this.callPromise('".$method['name']."', {".implode(', ', array_map(function($param) { return $param['name'].': '.$param['name']; }, $method['params']))."});"; $paramsPayload = array();
echo "\n\t},\n\n"; foreach ($method['params'] as $param) {
$paramType = $mapType($param['type']);
$paramsSignature[] = $param['name'] . ($param['optional'] ? '?' : '') . ': ' . $paramType;
$paramsPayload[] = $param['name'];
} }
$returnType = $mapUnionType($method['return']);
?> ?>
<?php echo $method['name']; ?>(<?php echo implode(', ', $paramsSignature); ?>): Promise<APIliteActionResponse<<?php echo $returnType; ?>>> {
return this.callPromise<APIliteActionResponse<<?php echo $returnType; ?>>>('<?php echo $method['name']; ?>', { <?php echo implode(', ', $paramsPayload); ?> });
}
}; <?php } ?>
}
export default new <?php echo $this->apiName; ?>();