Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
216 changes: 38 additions & 178 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

105 changes: 105 additions & 0 deletions samples/livr-emitter/alloy-output/rules.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
{
"name": [
"required",
"string",
{
"max_length": 50
}
],
"lastName": [
"string",
{
"length_between": [2, 50]
}
],
"email": [
"required",
"email"
],
"age": [
"integer",
{
"min_number": 18
},
{
"max_number": 99
}
],
"address": [
"required",
{
"nested_object": {
"street": "string",
"city": "required",
"zip": [
"required",
"positive_integer"
],
"geo": {
"nested_object": {
"lat": "decimal",
"lng": "decimal"
}
}
}
}
],
"phones": [
{
"list_of": [
"string",
{
"max_length": 15
}
]
}
],
"preferences": {
"variable_object": [
"type",
{
"email": {
"frequency": {
"one_of": ["daily", "weekly", "monthly"]
}
},
"sms": {
"time": "string"
}
}
]
},
"tags": [
{
"list_of": "string"
}
],
"website": "url",
"created_at": "iso_date",
"status": {
"one_of": ["active", "inactive", "pending"]
},
"password": [
"required",
{
"min_length": 8
}
],
"confirm_password": [
"required",
{
"equal_to_field": "password"
}
],
"contact": {
"or": [
["email"],
[
"string",
{
"max_length": 15
}
]
]
}
}
31 changes: 31 additions & 0 deletions samples/livr-emitter/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "@alloy-js/sample-livr-emitter",
"private": "true",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "alloy build",
"clean": "rimraf dist/ .temp/",
"watch": "alloy build --watch"
},
"keywords": [],
"author": "",
"license": "MIT",
"dependencies": {
"@alloy-js/core": "workspace:~",
"@alloy-js/json": "workspace:~",
"@alloy-js/typescript": "workspace:~",
"@alloy-js/java": "workspace:~"
},
"devDependencies": {
"@alloy-js/cli": "workspace:~",
"@alloy-js/rollup-plugin": "workspace:~",
"@rollup/plugin-typescript": "catalog:",
"@types/node": "catalog:",
"concurrently": "catalog:",
"typescript": "catalog:",
"vitest": "catalog:"
},
"type": "module"
}
29 changes: 29 additions & 0 deletions samples/livr-emitter/src/component/Rule.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// src/component/Rule.tsx
import { For } from "@alloy-js/core";
import * as jsn from "@alloy-js/json";
import { LIVRFieldRules } from "../schema.js";
import { RuleProperty } from "./RuleProperty.jsx";

interface RuleProps {
rule: LIVRFieldRules;
}

export function Rule(props: RuleProps) {
const { rule } = props;

if (Array.isArray(rule)) {
// Array of rules: output as array of processed rules, with commas
return (
<jsn.JsonArray>
<For each={rule} comma>
{(r) => <RuleProperty rule={r} />}
</For>
</jsn.JsonArray>
);
}
if (typeof rule === "object" && rule !== null && "rule" in rule) {
return <RuleProperty rule={rule} />;
}
// Fallback: output as JSON
return <jsn.JsonValue jsValue={rule} />;
}
177 changes: 177 additions & 0 deletions samples/livr-emitter/src/component/RuleProperty.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
// RuleProperty.tsx
import { For } from "@alloy-js/core";
import * as jsn from "@alloy-js/json";
import {
ListOfDifferentObjectsRule,
ListOfObjectsRule,
ListOfRule,
LIVRRule,
LIVRSchema,
NestedObjectRule,
OrRule,
VariableObjectRule,
} from "../schema.js";
import { Rule } from "./Rule.jsx"; // for recursion

interface RulePropertyProps {
rule: LIVRRule;
}

function renderSimpleRule(rule: LIVRRule) {
// Render simple rules as string literals
return <jsn.JsonValue jsValue={rule.rule} />;
}

function renderSimpleObjectRule(name: string, jsValue: any) {
// Render simple object rules as { rule: value }
return (
<jsn.JsonObject>
<jsn.JsonObjectProperty name={name}>
<jsn.JsonValue jsValue={jsValue} />
</jsn.JsonObjectProperty>
</jsn.JsonObject>
);
}

function renderSchema(schema: LIVRSchema) {
return (
<jsn.JsonObject>
<For each={Object.entries(schema)} comma>
{([field, fieldRules]) => (
<jsn.JsonObjectProperty name={field}>
<Rule rule={fieldRules} />
</jsn.JsonObjectProperty>
)}
</For>
</jsn.JsonObject>
);
}

function renderNestedObjectRule(rule: NestedObjectRule) {
// Render nested object rules as { nested_object: { ... } }
return (
<jsn.JsonObject>
<jsn.JsonObjectProperty name="nested_object">
{renderSchema(rule.schema)}
</jsn.JsonObjectProperty>
</jsn.JsonObject>
);
}

function renderOrRule(rule: OrRule) {
// Render OR rules as { or: [ ... ] }
return (
<jsn.JsonObject>
<jsn.JsonObjectProperty name="or">
<jsn.JsonArray>
<For each={rule.alternatives} comma>
{(alt) => <Rule rule={alt} />}
</For>
</jsn.JsonArray>
</jsn.JsonObjectProperty>
</jsn.JsonObject>
);
}

function renderSelectorBasedRules(
rule: VariableObjectRule | ListOfDifferentObjectsRule,
) {
return (
<jsn.JsonObject>
<jsn.JsonObjectProperty name={rule.rule}>
<jsn.JsonArray>
<jsn.JsonValue jsValue={rule.selectorField} />
<jsn.JsonObject>
<For each={Object.entries(rule.cases)} comma>
{([field, schema]) => (
<jsn.JsonObjectProperty name={field}>
{renderSchema(schema)}
</jsn.JsonObjectProperty>
)}
</For>
</jsn.JsonObject>
</jsn.JsonArray>
</jsn.JsonObjectProperty>
</jsn.JsonObject>
);
}

function renderListOfRule(rule: ListOfRule) {
return (
<jsn.JsonObject>
<jsn.JsonObjectProperty name="list_of">
<Rule rule={rule.rules} />
</jsn.JsonObjectProperty>
</jsn.JsonObject>
);
}

function renderListOfObjectsRule(rule: ListOfObjectsRule) {
return (
<jsn.JsonObject>
<jsn.JsonObjectProperty name="list_of_objects">
{renderSchema(rule.schema)}
</jsn.JsonObjectProperty>
</jsn.JsonObject>
);
}

export function RuleProperty(props: RulePropertyProps) {
const { rule } = props;

switch (rule.rule) {
case "required":
case "not_empty":
case "not_empty_list":
case "any_object":
case "string":
case "integer":
case "positive_integer":
case "decimal":
case "positive_decimal":
case "email":
case "url":
case "iso_date":
case "trim":
case "to_lc":
case "to_uc":
return renderSimpleRule(rule);
case "nested_object":
return renderNestedObjectRule(rule);
case "or":
return renderOrRule(rule);
case "variable_object":
return renderSelectorBasedRules(rule);
case "list_of":
return renderListOfRule(rule);
case "list_of_objects":
return renderListOfObjectsRule(rule);
case "list_of_different_objects":
return renderSelectorBasedRules(rule);
case "max_length":
case "min_length":
case "length_equal":
return renderSimpleObjectRule(rule.rule, rule.length);
case "length_between":
return renderSimpleObjectRule(rule.rule, rule.range);
case "max_number":
case "min_number":
case "eq":
return renderSimpleObjectRule(rule.rule, rule.value);
case "number_between":
return renderSimpleObjectRule(rule.rule, rule.range);
case "equal_to_field":
return renderSimpleObjectRule(rule.rule, rule.field);
case "one_of":
return renderSimpleObjectRule(rule.rule, rule.values);
case "like":
return renderSimpleObjectRule(rule.rule, rule.pattern);
case "remove":
case "leave_only":
return renderSimpleObjectRule(rule.rule, rule.characters);
case "default":
return renderSimpleObjectRule(rule.rule, rule.value);
default:
return <jsn.JsonValue jsValue={rule} />;
}
}
22 changes: 22 additions & 0 deletions samples/livr-emitter/src/context/livr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {
createContext,
useContext,
type ComponentContext,
} from "@alloy-js/core";
import { LIVRSchema } from "../schema.js";

interface LIVRContext {
schema: LIVRSchema;
}

// Add explicit return type annotation
export const LIVRContext: ComponentContext<LIVRContext> =
createContext<LIVRContext>();

export function useLivr(): LIVRContext {
return useContext(LIVRContext)!;
}

export function createLIVRContext(schema: LIVRSchema): LIVRContext {
return { schema };
}
27 changes: 27 additions & 0 deletions samples/livr-emitter/src/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { For, Output, render, writeOutput } from "@alloy-js/core";
import * as jsn from "@alloy-js/json";
import { Rule } from "./component/Rule.js";
import { LIVRContext, createLIVRContext } from "./context/livr.js";
import { livrApi } from "./schema.js";

// Main function to emit LIVR schema as JSON
const output = render(
<Output>
<LIVRContext.Provider value={createLIVRContext(livrApi)}>
<jsn.SourceFile path="rules.json">
<jsn.JsonObject>
<For each={Object.entries(livrApi)} comma>
{([fieldName, rules]) => (
<jsn.JsonObjectProperty name={fieldName}>
<Rule rule={rules} />
</jsn.JsonObjectProperty>
)}
</For>
</jsn.JsonObject>
</jsn.SourceFile>
</LIVRContext.Provider>
</Output>,
);

// Write the output to disk
writeOutput(output, "./alloy-output");
Loading