okai
Version:
AI-powered code generation tool for ServiceStack Apps. Generate TypeScript data models, C# APIs, migrations, and UI components from natural language prompts using LLMs.
423 lines • 12.9 kB
JavaScript
export function plural(word, amount) {
if (amount !== undefined && amount === 1) {
return word;
}
const plural = {
'(quiz)$': "$1zes",
'^(ox)$': "$1en",
'([m|l])ouse$': "$1ice",
'(matr|vert|ind)ix|ex$': "$1ices",
'(x|ch|ss|sh)$': "$1es",
'([^aeiouy]|qu)y$': "$1ies",
'(hive)$': "$1s",
'(?:([^f])fe|([lr])f)$': "$1$2ves",
'(shea|lea|loa|thie)f$': "$1ves",
'sis$': "ses",
'([ti])um$': "$1a",
'(tomat|potat|ech|her|vet)o$': "$1oes",
'(bu)s$': "$1ses",
'(alias)$': "$1es",
'(octop)us$': "$1i",
'(ax|test)is$': "$1es",
'(us)$': "$1es",
'([^s]+)$': "$1s"
};
const irregular = {
'move': 'moves',
'foot': 'feet',
'goose': 'geese',
'sex': 'sexes',
'child': 'children',
'man': 'men',
'tooth': 'teeth',
'person': 'people'
};
const uncountable = [
'sheep',
'fish',
'deer',
'moose',
'series',
'species',
'money',
'rice',
'information',
'equipment',
'bison',
'cod',
'offspring',
'pike',
'salmon',
'shrimp',
'swine',
'trout',
'aircraft',
'hovercraft',
'spacecraft',
'sugar',
'tuna',
'you',
'wood'
];
// save some time in the case that singular and plural are the same
if (uncountable.indexOf(word.toLowerCase()) >= 0) {
return word;
}
// check for irregular forms
for (const w in irregular) {
const pattern = new RegExp(`${w}$`, 'i');
const replace = irregular[w];
if (pattern.test(word)) {
return word.replace(pattern, replace);
}
}
// check for matches using regular expressions
for (const reg in plural) {
const pattern = new RegExp(reg, 'i');
if (pattern.test(word)) {
return word.replace(pattern, plural[reg]);
}
}
return word;
}
export function indentLines(src, indent = ' ') {
return src.split('\n').map(x => indent + x).join('\n');
}
export function requestKey(date) {
return `requests/${timestampKey(date)}`;
}
export function acceptedKey(date) {
return `accepted/${timestampKey(date)}`;
}
export function chatKey(date) {
return `chat/${timestampKey(date)}`;
}
export function timestampKey(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const timestamp = date.valueOf();
return `${year}/${month}/${day}/${timestamp}`;
}
export function trimStart(str, chars = ' ') {
const cleanStr = String(str);
const charsToTrim = new Set(chars);
let start = 0;
while (start < cleanStr.length && charsToTrim.has(cleanStr[start])) {
start++;
}
return cleanStr.slice(start);
}
export function leftPart(s, needle) {
if (s == null)
return null;
let pos = s.indexOf(needle);
return pos == -1
? s
: s.substring(0, pos);
}
export function rightPart(s, needle) {
if (s == null)
return null;
let pos = s.indexOf(needle);
return pos == -1
? s
: s.substring(pos + needle.length);
}
export function lastLeftPart(s, needle) {
if (s == null)
return null;
let pos = s.lastIndexOf(needle);
return pos == -1
? s
: s.substring(0, pos);
}
export function lastRightPart(s, needle) {
if (s == null)
return null;
let pos = s.lastIndexOf(needle);
return pos == -1
? s
: s.substring(pos + needle.length);
}
export function splitCase(t) {
return typeof t != 'string' ? t : t.replace(/([A-Z]|[0-9]+)/g, ' $1').replace(/_/g, ' ').trim();
}
export function pick(o, keys) {
const to = {};
Object.keys(o).forEach(k => {
if (keys.indexOf(k) >= 0) {
to[k] = o[k];
}
});
return to;
}
export function appendQueryString(url, args) {
for (let k in args) {
if (args.hasOwnProperty(k)) {
let val = args[k];
if (typeof val == 'undefined' || typeof val == 'function' || typeof val == 'symbol')
continue;
url += url.indexOf("?") >= 0 ? "&" : "?";
url += k + (val === null ? '' : "=" + qsValue(val));
}
}
return url;
}
function qsValue(arg) {
if (arg == null)
return "";
return encodeURIComponent(arg) || "";
}
export function trimEnd(s, c) {
let end = s.length;
while (end > 0 && s[end - 1] === c) {
--end;
}
return (end < s.length) ? s.substring(0, end) : s;
}
export function refCount(t) {
return t.properties?.filter(x => x.attributes?.some(x => x.name === 'References')).length || 0;
}
export function getGroupName(ast) {
let types = ast.types.filter(x => !x.isEnum);
if (types.length == 0)
types = ast.operations.map(x => x.request);
if (types.length == 0)
return null;
return plural(types.sort((x, y) => refCount(y) - refCount(x))[0].name);
}
export function toPascalCase(s) {
if (!s)
return '';
const isAllCaps = s.match(/^[A-Z0-9_]+$/);
if (isAllCaps) {
const words = s.split('_');
return words.map(x => x[0].toUpperCase() + x.substring(1).toLowerCase()).join('');
}
if (s.includes('_')) {
return s.split('_').filter(x => x[0]).map(x => x[0].toUpperCase() + x.substring(1)).join('');
}
return s.charAt(0).toUpperCase() + s.substring(1);
}
export function toCamelCase(s) {
s = toPascalCase(s);
if (!s)
return '';
return s.charAt(0).toLowerCase() + s.substring(1);
}
export function camelToKebabCase(str) {
if (!str || str.length <= 1)
return str.toLowerCase();
// Insert hyphen before capitals and numbers, convert to lowercase
return str
.replace(/([A-Z0-9])/g, '-$1')
.toLowerCase()
// Remove leading hyphen if exists
.replace(/^-/, '')
// Replace multiple hyphens with single hyphen
.replace(/-+/g, '-');
}
export function replaceMyApp(input, projectName) {
const condensed = projectName.replace(/_/g, '');
const kebabCase = camelToKebabCase(condensed);
return input
.replace(/My_App/g, projectName)
.replace(/MyApp/g, projectName)
.replace(/My App/g, splitCase(condensed))
.replace(/my-app/g, kebabCase)
.replace(/myapp/g, condensed.toLowerCase())
.replace(/my_app/g, projectName.toLowerCase());
}
export class ResponseStatus {
constructor(init) { Object.assign(this, init); }
errorCode;
message;
stackTrace;
errors;
meta;
}
export class ResponseError {
constructor(init) { Object.assign(this, init); }
errorCode;
fieldName;
message;
meta;
}
export class ErrorResponse {
constructor(init) { Object.assign(this, init); }
type;
responseStatus;
}
export function createError(errorCode, message, fieldName) {
return new ErrorResponse({
responseStatus: new ResponseStatus({
errorCode,
message,
errors: fieldName ? [new ResponseError({ errorCode, message, fieldName })] : undefined
})
});
}
export function isBinary(contentType) {
return contentType && !contentType.endsWith('+xml') &&
(contentType.startsWith('image/')
|| contentType.startsWith('video/')
|| contentType.startsWith('audio/')
|| contentType.endsWith('octet-stream')
|| contentType.endsWith('compressed'));
}
export function withAliases(icons, aliases) {
const result = {};
Object.keys(icons).forEach(name => {
result[name.toLowerCase()] = icons[name].replaceAll('"', `'`);
});
Object.keys(aliases).forEach(name => {
for (const alias of aliases[name]) {
result[alias.toLowerCase()] = icons[name].replaceAll('"', `'`);
}
});
return result;
}
export function isCloudflareWorker() {
// Use a safe approach that works at both build and runtime
// Check for Worker-specific globals without directly referencing types
// that would cause build errors
const hasWorkerAPIs = typeof self !== 'undefined' &&
typeof caches !== 'undefined' &&
typeof addEventListener !== 'undefined' &&
'fetch' in self;
// Check for Node-specific features safely
const hasNodeFeatures = () => {
try {
// Check for global object without direct reference
return typeof globalThis !== 'undefined' &&
// Check for process without direct reference that would error in CF
!!globalThis.process;
}
catch {
return false;
}
};
// Check for service worker context safely
const isServiceWorkerContext = () => {
try {
return typeof self !== 'undefined' &&
self.constructor.name === 'ServiceWorkerGlobalScope';
}
catch {
return false;
}
};
// Return true if we have Worker indicators and don't have Node indicators
return hasWorkerAPIs && isServiceWorkerContext() && !hasNodeFeatures();
}
export function parseJsObject(js) {
if (isCloudflareWorker()) {
return safeJsObject(js);
}
// Parse object literals
try {
return (new Function(`return ${js}`))();
}
catch (e) {
// Cloudflare workers don't support eval or new Function
return safeJsObject(js);
}
}
export function safeJsObject(jsStr) {
let result = '';
let i = 0;
// State tracking
let inString = false;
let quoteType = null; // ' or "
let depth = 0;
const peek = (n = 1) => i + n < jsStr.length ? jsStr[i + n] : '';
while (i < jsStr.length) {
const char = jsStr[i];
const nextChar = peek();
const prevChar = i > 0 ? jsStr[i - 1] : '';
// Handle string boundaries
if ((char === '"' || char === "'") && prevChar !== '\\') {
if (!inString) {
// Starting a string
inString = true;
quoteType = char;
result += '"'; // Always use double quotes for JSON
}
else if (char === quoteType) {
// Ending a string
inString = false;
quoteType = null;
result += '"';
}
else {
// A quote character inside a string of a different type
result += char;
}
i++;
continue;
}
// Handle escaped characters in strings
if (inString && char === '\\') {
if (nextChar === quoteType) {
// Convert escaped quote to JSON format
result += '\\"';
i += 2;
continue;
}
else {
// Pass through other escape sequences
result += char;
i++;
continue;
}
}
// When not in a string, handle object/array syntax
if (!inString) {
// Track nesting depth
if (char === '{' || char === '[') {
depth++;
result += char;
i++;
continue;
}
else if (char === '}' || char === ']') {
depth--;
result += char;
i++;
continue;
}
// Handle property keys - match unquoted keys followed by colon
if (depth > 0 && /[a-zA-Z0-9_$]/.test(char)) {
let keyBuffer = '';
let j = i;
// Collect the potential key name
while (j < jsStr.length && /[a-zA-Z0-9_$]/.test(jsStr[j])) {
keyBuffer += jsStr[j];
j++;
}
// Skip whitespace
let k = j;
while (k < jsStr.length && /\s/.test(jsStr[k]))
k++;
// If followed by a colon, it's a property key
if (k < jsStr.length && jsStr[k] === ':') {
result += `"${keyBuffer}"`; // Add quoted key to result
i = k; // Move to the colon position
continue;
}
}
}
// Default: add character to result
result += char;
i++;
}
// Validate and return
try {
return JSON.parse(result);
}
catch (e) {
throw new Error(`Failed to produce valid JSON: ${e.message}`);
}
}
//# sourceMappingURL=utils.js.map