Getting Started
Custom JS lets you create your own Lamuki operation by writing a JavaScript function. Lamuki runs your function separately for every file in the workspace and provides the current file through the file parameter. Use the file's documented structures to read its data, modify its content, or provide operation output, then return the file when your operation is complete.
function customJavascript(file : ActiveFileState){
// write code here
return file
}
Understand the Structures
file.content
For text workspace , json workspace
{
value : string
}
file.operation.output
[
{
name : string,
type : "string" | "boolean" | "number",
value : string | boolean | number,
}
]
What to Modify and What to Avoid
A Custom JS function receives a file object that contains the file data and operation information. You can read the file object freely, but only supported properties should be modified. Lamuki applies changes to the file content and operation output; changes to other properties are not applied and may produce unexpected results.
Modify file.content when you want to change the actual file.
Modify file.operation.output when you want to provide output from the operation.
You can modify both file.content and file.operation.output when an operation needs to change the file and produce output.
Return the file object after making the required changes.
Do not modify other properties of the file object. Changes to properties other than file.content and file.operation.output are not applied by Lamuki and may produce unexpected results.
Examples
File Changer Examples
SCREAMING_SNAKE_CASE Converter
function screamingsnakecaseConverter(file : ActiveFileState) : ActiveFileState {
if(file.content == undefined) return file
const text = file.content.value as string;
const words = text
.trim()
.replace(/[^\p{L}\p{N}]+/gu, " ")
.split(/\s+/)
.filter(Boolean);
if (words.length === 0) return file
const newText = words
.map((word) => word.toUpperCase())
.join("_");
file.content.value = newText
return file
}camelCase Converter
function camelcaseConverter(file : ActiveFileState) :ActiveFileState{
if(file.content == undefined) return file
const text = file.content.value as string;
const words = text
.trim()
// Convert punctuation/separators to spaces.
.replace(/[^\p{L}\p{N}]+/gu, " ")
.split(/\s+/)
.filter(Boolean);
if (words.length === 0) return file;
const newText =
words[0].toLowerCase() +
words
.slice(1)
.map(
(word) =>
word.charAt(0).toUpperCase() +
word.slice(1).toLowerCase(),
)
.join("");
file.content.value = newText
return file
}snake_case Converter
function snakecaseConverter(file : ActiveFileState) : ActiveFileState {
if(file.content == undefined) return file
const text = file.content.value as string;
const words = text
.trim()
.replace(/[^\p{L}\p{N}]+/gu, " ")
.split(/\s+/)
.filter(Boolean);
if (words.length === 0){
return file
}
const newText = words
.map((word) => word.toLowerCase())
.join("_");
file.content.value = newText
return file
}UPPERCASE Converter
function uppercaseConverter(file : ActiveFileState): ActiveFileState {
if(file.content == undefined) return file
const text = file.content.value as string;
const newText = text.toUpperCase();
file.content.value = newText
return file
}TitleCase Converter
function titleCaseConverter(file : ActiveFileState): ActiveFileState {
if(file.content == undefined) return file
const text = file.content.value as string;
const newText = text
.toLowerCase()
.replace(/\b\w/g, (char) => char.toUpperCase());
file.content.value = newText
return file
}Non File Changer Examples
Word Counter
function wordCounter(file : ActiveFileState): ActiveFileState {
if(file.content == undefined) return file
const text = file.content.value as string;
const trimmedText = text.trim();
const words =
trimmedText === ""
? 0
: trimmedText.split(/\s+/).length;
const sentences =
trimmedText === ""
? 0
: trimmedText.split(/[.!?]+(?=\s|$)/).filter(Boolean).length;
const characters = text.length;
const paragraphs =
trimmedText === ""
? 0
: trimmedText.split(/\n\s*\n/).filter(Boolean).length;
file.operation.output = [
{
name: "Words",
value: words,
type: "number",
},
{
name: "Sentences",
value: sentences,
type: "number",
},
{
name: "Characters",
value: characters,
type: "number",
},
{
name: "Paragraphs",
value: paragraphs,
type: "number",
},
];
return file
}Character Counter
function characterCounter(file : ActiveFileState): ActiveFileState {
if(file.content == undefined) return file
const text = file.content.value as string;
const trimmedText = text.trim();
const words =
trimmedText === ""
? 0
: trimmedText.split(/\s+/).length;
const sentences =
trimmedText === ""
? 0
: trimmedText.split(/[.!?]+(?=\s|$)/).filter(Boolean).length;
const characters = text.length;
const paragraphs =
trimmedText === ""
? 0
: trimmedText.split(/\n\s*\n/).filter(Boolean).length;
file.operation.output = [
{
name: "Characters",
value: characters,
type: "number",
},
{
name: "Words",
value: words,
type: "number",
},
{
name: "Sentences",
value: sentences,
type: "number",
},
{
name: "Paragraphs",
value: paragraphs,
type: "number",
},
];
return file
}Line Counter
function lineCounter(file : ActiveFileState): ActiveFileState {
if(file.content == undefined) return file
const text = file.content.value as string;
const lines = text === "" ? 0 : text.split(/\r?\n/).length;
file.operation.output = [
{
name: "Lines",
value: lines,
type: "number",
},
];
return file
}Generate Schema
function generateJsonSchema(
value: unknown
): JsonSchema {
validateJsonValue(value);
return {
$schema: "https://json-schema.org/draft/2020-12/schema",
...inferSchema(value as JsonValue)
};
}
function validateJsonValue(value: unknown): void {
if (value === null) {
return;
}
if (Array.isArray(value)) {
for (const item of value) {
validateJsonValue(item);
}
return;
}
switch (typeof value) {
case "string":
case "boolean":
return;
case "number":
if (!Number.isFinite(value)) {
throw new TypeError(
"JSON cannot contain NaN or Infinity."
);
}
return;
case "object":
for (const [key, child] of Object.entries(value)) {
if (typeof key !== "string") {
throw new TypeError(
"JSON object keys must be strings."
);
}
validateJsonValue(child);
}
return;
default:
throw new TypeError(
`Value of type "${typeof value}" is not valid JSON.`
);
}
}
function inferSchema(value: JsonValue): JsonSchema {
if (value === null) {
return { type: "null" };
}
if (Array.isArray(value)) {
return inferArraySchema(value);
}
switch (typeof value) {
case "string":
return { type: "string" };
case "boolean":
return { type: "boolean" };
case "number":
return {
type: Number.isInteger(value)
? "integer"
: "number"
};
case "object":
return inferObjectSchema(value);
}
}
function inferObjectSchema(
value: { [key: string]: JsonValue }
): JsonSchema {
const properties: Record<string, JsonSchema> = {};
const required: string[] = [];
for (const [key, child] of Object.entries(value)) {
properties[key] = inferSchema(child);
required.push(key);
}
const schema: JsonSchema = {
type: "object"
};
if (Object.keys(properties).length > 0) {
schema.properties = properties;
schema.required = required;
}
return schema;
}
function inferArraySchema(
value: JsonValue[]
): JsonSchema {
if (value.length === 0) {
return {
type: "array",
items: {}
};
}
const itemSchemas = value.map(inferSchema);
return {
type: "array",
items: mergeSchemas(itemSchemas)
};
}
function mergeSchemas(
schemas: JsonSchema[]
): JsonSchema {
if (schemas.length === 0) {
return {};
}
const unique = removeDuplicateSchemas(schemas);
if (unique.length === 1) {
return unique[0];
}
if (
unique.every(schema =>
schema.type === "integer" ||
schema.type === "number"
)
) {
return {
type: "number"
};
}
if (
unique.every(
schema =>
typeof schema.type === "string" &&
!schema.properties &&
!schema.items &&
!schema.anyOf
)
) {
return {
type: unique.map(
schema => schema.type as string
)
};
}
if (unique.every(isObjectSchema)) {
return mergeObjectSchemas(unique);
}
if (unique.every(isArraySchema)) {
return mergeArraySchemas(unique);
}
return {
anyOf: unique
};
}
function mergeObjectSchemas(
schemas: JsonSchema[]
): JsonSchema {
const allKeys = new Set<string>();
for (const schema of schemas) {
for (const key of Object.keys(schema.properties ?? {})) {
allKeys.add(key);
}
}
const properties: Record<string, JsonSchema> = {};
const required: string[] = [];
for (const key of allKeys) {
const propertySchemas: JsonSchema[] = [];
for (const schema of schemas) {
const propertiesForSchema = schema.properties;
if (
propertiesForSchema &&
Object.hasOwn(propertiesForSchema, key)
) {
propertySchemas.push(
propertiesForSchema[key]
);
}
}
properties[key] = mergeSchemas(propertySchemas);
const isRequired = schemas.every(schema =>
schema.properties &&
Object.hasOwn(schema.properties, key) &&
schema.required?.includes(key) === true
);
if (isRequired) {
required.push(key);
}
}
const result: JsonSchema = {
type: "object",
properties
};
if (required.length > 0) {
result.required = required;
}
return result;
}
function mergeArraySchemas(
schemas: JsonSchema[]
): JsonSchema {
const itemSchemas = schemas
.map(schema => schema.items)
.filter(
(schema): schema is JsonSchema =>
schema !== undefined &&
Object.keys(schema).length > 0
);
if (itemSchemas.length === 0) {
return {
type: "array",
items: {}
};
}
return {
type: "array",
items: mergeSchemas(itemSchemas)
};
}
function isObjectSchema(
schema: JsonSchema
): boolean {
return (
schema.type === "object" &&
!schema.anyOf
);
}
function isArraySchema(
schema: JsonSchema
): boolean {
return (
schema.type === "array" &&
!schema.anyOf
);
}
function removeDuplicateSchemas(
schemas: JsonSchema[]
): JsonSchema[] {
const result: JsonSchema[] = [];
for (const schema of schemas) {
if (
!result.some(
existing =>
schemasEqual(existing, schema)
)
) {
result.push(schema);
}
}
return result;
}
function schemasEqual(
a: JsonSchema,
b: JsonSchema
): boolean {
return JSON.stringify(a) === JSON.stringify(b);
}
export default function generateSchemaMain(file: ActiveFileState) {
try {
const content = file.content!.value as string
const data = JSON.parse(content);
file.operation.output = [
{
name: "Schema",
type: "code",
value: JSON.stringify(generateJsonSchema(data), null, 2),
info: {
language: 'json'
}
}
]
}
catch {
file.operation.output = [
{
name: "JSON Decode Error",
type: "error",
value: "Invalid JSON file"
}
]
}
return file
}Validate
function Validate(file: ActiveFileState): ActiveFileState {
try{
const content = file.content!.value as string
const data = JSON.parse(content);
file.operation.output = [
{
name: "Messaage",
type: "string",
value: "Valid JSON"
}
]
}
catch{
file.operation.output = [
{
name: "JSON Decode Error",
type: "error",
value: "Invalid JSON file"
}
]
}
return file
}