Spaces:
Running
Running
browser-sqlite-playground / source /teaql-ts /examples /browser-sqlite /orders-lib /src /teaql-ts.ts
Download source/teaql-ts/examples/browser-sqlite/orders-lib/src/teaql-ts.ts from teaql/browser-sqlite-playground: direct link, hf CLI and curl.
- Browser
- Download file 16.6 kB
-
https://huggingface.co/spaces/teaql/browser-sqlite-playground/resolve/main/source/teaql-ts/examples/browser-sqlite/orders-lib/src/teaql-ts.ts
- Command line
-
hf download hf://spaces/teaql/browser-sqlite-playground/source/teaql-ts/examples/browser-sqlite/orders-lib/src/teaql-ts.ts
-
curl -L -o teaql-ts.ts https://huggingface.co/spaces/teaql/browser-sqlite-playground/resolve/main/source/teaql-ts/examples/browser-sqlite/orders-lib/src/teaql-ts.ts
16.6 kB
| import { | |
| CheckException, EntityChecker, RuntimeModule, | |
| SelectQuery as RuntimeSelectQuery, UserContext, | |
| executeRelationFacets, QueryRequest, MutationIntent, MutationRequest, GraphMutationSession, | |
| } from "teaql-ts"; | |
| export { CheckException, EntityRoot, ObjectLocation, SmartList, UserContext, executeRelationFacets, MutationIntent } from "teaql-ts"; | |
| export { GraphMutationSession, LoadedScalarSnapshot, MutationTraceScope } from "teaql-ts"; | |
| export type { EntityKey } from "teaql-ts"; | |
| export type { TeaQLPage } from "teaql-ts"; | |
| import type { QueryOperation } from "teaql-ts"; | |
| function soundex(input: unknown): string { | |
| const text = String(input ?? "").toUpperCase().replace(/[^A-Z]/g, ""); | |
| if (!text) return ""; | |
| const code = (char: string): string => "BFPV".includes(char) ? "1" | |
| : "CGJKQSXZ".includes(char) ? "2" : "DT".includes(char) ? "3" | |
| : char === "L" ? "4" : "MN".includes(char) ? "5" : char === "R" ? "6" : ""; | |
| let result = text[0], previous = code(text[0]); | |
| for (const char of text.slice(1)) { | |
| const current = code(char); | |
| if (current && current !== previous) result += current; | |
| previous = current; | |
| if (result.length === 4) break; | |
| } | |
| return (result + "000").slice(0, 4); | |
| } | |
| /** Generated naming adapter; query policy and execution remain in the formal runtime. */ | |
| export class SelectQuery extends RuntimeSelectQuery { | |
| orderBy(field: string, direction: string): this { | |
| return this.order({ field, expr: null, direction: direction === "desc" ? "Desc" : "Asc" } as any); | |
| } | |
| relationQuery( | |
| name: string, query: RuntimeSelectQuery, localKey = "id", foreignKey = "id", many = true, | |
| operation?: QueryOperation, | |
| ): this { | |
| return super.relationQuery(name, query, localKey, foreignKey, many, operation); | |
| } | |
| } | |
| export interface TeaQLDataService { | |
| executeGraphSave<T>(intent: MutationIntent, work: (graph: GraphMutationSession) => Promise<T>): Promise<T>; | |
| preflightMutation(mutation: any): any; | |
| afterGraphCommit(work: () => void): void; | |
| afterGraphRollback(work: () => void): void; | |
| executeMutation(mutation: any): Promise<any>; | |
| executeQuery(query: any): Promise<any[]>; | |
| executeCount(query: any, alias?: string, operation?: QueryOperation): Promise<number>; | |
| executeForStream(query: any, chunkSize?: number): AsyncIterable<any[]>; | |
| close?(): Promise<void>; | |
| } | |
| export class TeaQLClient implements TeaQLDataService { | |
| private data: Record<string, Record<string, any>> = {}; | |
| private nextIds: Record<string, number> = {}; | |
| private readonly checkers: Record<string, EntityChecker> = {}; | |
| private userContext = new UserContext(); | |
| private graphSaveActive = false; | |
| private graphCommitActions: Array<() => void> = []; | |
| private graphRollbackActions: Array<() => void> = []; | |
| private graphSaveTail: Promise<void> = Promise.resolve(); | |
| private readonly mutationGovernanceEvents: any[] = []; | |
| constructor(private storagePath?: string) { | |
| if (storagePath) { | |
| const fs = require("fs"); | |
| if (fs.existsSync(storagePath)) { | |
| const state = JSON.parse(fs.readFileSync(storagePath, "utf8")); | |
| this.data = state.data || {}; | |
| this.nextIds = state.nextIds || {}; | |
| } | |
| } | |
| } | |
| /** Installs passive generated metadata. It never mutates storage. */ | |
| install(module: RuntimeModule): this { | |
| Object.assign(this.checkers, module.checkers); | |
| return this; | |
| } | |
| setUserContext(context: UserContext): this { | |
| this.userContext = context; | |
| return this; | |
| } | |
| get mutationGovernanceTrace(): readonly any[] { | |
| return [...this.mutationGovernanceEvents]; | |
| } | |
| private persist() { | |
| if (!this.storagePath) return; | |
| const fs = require("fs"); | |
| const path = require("path"); | |
| fs.mkdirSync(path.dirname(path.resolve(this.storagePath)), { recursive: true }); | |
| const temporaryPath = `${this.storagePath}.tmp`; | |
| fs.writeFileSync(temporaryPath, JSON.stringify({ data: this.data, nextIds: this.nextIds })); | |
| fs.renameSync(temporaryPath, this.storagePath); | |
| } | |
| async executeGraphSave<T>(intent: MutationIntent, work: (graph: GraphMutationSession) => Promise<T>): Promise<T> { | |
| const graph = new GraphMutationSession(intent); | |
| const predecessor = this.graphSaveTail; | |
| let release!: () => void; | |
| this.graphSaveTail = new Promise<void>(resolve => { release = resolve; }); | |
| await predecessor; | |
| const dataSnapshot = JSON.parse(JSON.stringify(this.data)); | |
| const nextIdsSnapshot = { ...this.nextIds }; | |
| let fixEvidenceStarted = false; | |
| let mutationPolicyGraphStarted = false; | |
| try { | |
| this.graphSaveActive = true; | |
| this.graphCommitActions = []; | |
| this.graphRollbackActions = []; | |
| this.userContext.insertResource("fixTime", new Date()); | |
| this.userContext.beginFixEvidence(); | |
| fixEvidenceStarted = true; | |
| this.userContext.beginMutationPolicyGraph(); | |
| mutationPolicyGraphStarted = true; | |
| const result = await work(graph); | |
| this.userContext.ensureMutationPolicyGraphComplete(); | |
| this.persist(); | |
| for (const action of this.graphCommitActions) action(); | |
| return result; | |
| } catch (error) { | |
| this.data = dataSnapshot; | |
| this.nextIds = nextIdsSnapshot; | |
| for (const action of [...this.graphRollbackActions].reverse()) action(); | |
| throw error; | |
| } finally { | |
| this.graphSaveActive = false; | |
| this.graphCommitActions = []; | |
| this.graphRollbackActions = []; | |
| if (mutationPolicyGraphStarted) this.userContext.endMutationPolicyGraph(); | |
| this.userContext.removeResource("fixTime"); | |
| if (fixEvidenceStarted) this.userContext.finishFixEvidence(); | |
| release(); | |
| } | |
| } | |
| afterGraphCommit(work: () => void): void { | |
| if (!this.graphSaveActive) throw new Error("No graph save is active"); | |
| this.graphCommitActions.push(work); | |
| } | |
| afterGraphRollback(work: () => void): void { | |
| if (!this.graphSaveActive) throw new Error("No graph save is active"); | |
| this.graphRollbackActions.push(work); | |
| } | |
| preflightMutation(mutation: any): any { | |
| mutation = this.checkAndFixMutation(mutation); | |
| this.userContext.recordMutationPolicyPreflight(mutation); | |
| return mutation; | |
| } | |
| private checkAndFixMutation(mutation: any): any { | |
| if (mutation instanceof MutationRequest) mutation = mutation.mutation; | |
| if (!String(mutation?.comment || "").trim()) throw new Error("Security audit failure: audit reason is required before mutation"); | |
| mutation = { ...mutation, payload: { ...(mutation?.payload || {}) } }; | |
| const checker = this.checkers[String(mutation.entity)]; | |
| if (!checker) return mutation; | |
| const results: any[] = []; | |
| const ownsFixTime = this.userContext.getResource("fixTime") === undefined; | |
| if (ownsFixTime) this.userContext.insertResource("fixTime", new Date()).beginFixEvidence(); | |
| try { | |
| checker.checkAndFix(this.userContext, mutation, results); | |
| if (mutation.ledgerKey && mutation.ledgerRoot) for (const [field, value] of Object.entries(mutation.payload)) mutation.ledgerRoot.set(mutation.ledgerKey, field, value); | |
| this.userContext.translateCheckResults(results); | |
| if (results.length) throw new CheckException(results); | |
| return mutation; | |
| } finally { if (ownsFixTime) this.userContext.removeResource("fixTime").finishFixEvidence(); } | |
| } | |
| async executeMutation(mutation: any): Promise<any> { | |
| mutation = this.checkAndFixMutation(mutation); | |
| const mutationGovernance = this.userContext.enterMutationPolicy(mutation); | |
| this.mutationGovernanceEvents.push(mutationGovernance); | |
| const table = this.data[mutation.entity] ||= {}; | |
| if (mutation.action === "Create") { | |
| const id = mutation.id ?? String(this.nextIds[mutation.entity] || 1); | |
| this.nextIds[mutation.entity] = Number(id) + 1; | |
| const record = { ...mutation.payload, id: String(id), version: Number(mutation.version || 0) + 1 }; | |
| table[String(id)] = record; | |
| if (!this.graphSaveActive) this.persist(); | |
| return { success: true, id: String(id), version: record.version, persistedRecord: { ...record } }; | |
| } | |
| if (mutation.action === "Update") { | |
| const id = String(mutation.id); | |
| if (!table[id]) throw new Error(`${mutation.entity}(${id}) does not exist`); | |
| const expectedVersion = Number(mutation.version); | |
| const currentVersion = Number(table[id].version || 0); | |
| if (!Number.isFinite(expectedVersion) || expectedVersion !== currentVersion) { | |
| throw new Error( | |
| `Optimistic lock conflict for ${mutation.entity}(${id}): expected ${expectedVersion}, current ${currentVersion}`); | |
| } | |
| table[id] = { ...table[id], ...mutation.payload, id, version: Number(table[id].version || 0) + 1 }; | |
| if (!this.graphSaveActive) this.persist(); | |
| return { success: true, id, version: table[id].version, persistedRecord: { ...table[id] } }; | |
| } | |
| if (mutation.action === "Delete") { | |
| const id = String(mutation.id); | |
| if (!table[id]) throw new Error(`${mutation.entity}(${id}) does not exist`); | |
| const expectedVersion = Number(mutation.version); | |
| const currentVersion = Number(table[id].version || 0); | |
| if (!Number.isFinite(expectedVersion) || expectedVersion !== currentVersion) { | |
| throw new Error( | |
| `Optimistic lock conflict for ${mutation.entity}(${id}): expected ${expectedVersion}, current ${currentVersion}`); | |
| } | |
| table[id] = { ...table[id], id, version: -(currentVersion + 1) }; | |
| if (!this.graphSaveActive) this.persist(); | |
| return { success: true, id, version: table[id].version, deleted: true, persistedRecord: { ...table[id] } }; | |
| } | |
| throw new Error(`Unsupported mutation action: ${mutation.action}`); | |
| } | |
| async query(context: any, req: any): Promise<any> { | |
| return { rows: [] }; | |
| } | |
| async executeQuery(query: any): Promise<any> { | |
| if (!(query instanceof RuntimeSelectQuery)) { | |
| throw new Error("TeaQL list execution requires the formal runtime SelectQuery"); | |
| } | |
| query.prepareForList(); | |
| let rows = Object.values(this.data[query.entity] || {}).map((row: any) => ({ ...row })); | |
| const matches = (row: any, expression: any): boolean => { | |
| if (expression?.$and) return expression.$and.every((item: any) => matches(row, item)); | |
| return Object.entries(expression || {}).every(([field, predicate]: [string, any]) => { | |
| if (predicate?.$eq !== undefined) return row[field] === (predicate.$eq?.id ?? predicate.$eq); | |
| if (predicate?.$ne !== undefined) return row[field] !== (predicate.$ne?.id ?? predicate.$ne); | |
| if (predicate?.$contains !== undefined) return String(row[field] ?? "").includes(String(predicate.$contains)); | |
| if (predicate?.$notContains !== undefined) return !String(row[field] ?? "").includes(String(predicate.$notContains)); | |
| if (predicate?.$startsWith !== undefined) return String(row[field] ?? "").startsWith(String(predicate.$startsWith)); | |
| if (predicate?.$notStartsWith !== undefined) return !String(row[field] ?? "").startsWith(String(predicate.$notStartsWith)); | |
| if (predicate?.$endsWith !== undefined) return String(row[field] ?? "").endsWith(String(predicate.$endsWith)); | |
| if (predicate?.$notEndsWith !== undefined) return !String(row[field] ?? "").endsWith(String(predicate.$notEndsWith)); | |
| if (predicate?.$soundLike !== undefined) return soundex(row[field]) === soundex(predicate.$soundLike); | |
| if (predicate?.$in !== undefined) return predicate.$in.some((value: any) => row[field] === (value?.id ?? value)); | |
| if (predicate?.$notIn !== undefined) return !predicate.$notIn.some((value: any) => row[field] === (value?.id ?? value)); | |
| if (predicate?.$between !== undefined) return row[field] >= predicate.$between[0] && row[field] <= predicate.$between[1]; | |
| if (predicate?.$isNull === true) return row[field] === null || row[field] === undefined; | |
| if (predicate?.$isNull === false) return row[field] !== null && row[field] !== undefined; | |
| if (predicate?.$gt !== undefined) return row[field] > predicate.$gt; | |
| if (predicate?.$gte !== undefined) return row[field] >= predicate.$gte; | |
| if (predicate?.$lt !== undefined) return row[field] < predicate.$lt; | |
| if (predicate?.$lte !== undefined) return row[field] <= predicate.$lte; | |
| return true; | |
| }); | |
| }; | |
| if (query.filterCondition) rows = rows.filter(row => matches(row, query.filterCondition)); | |
| if (query.aggregateItems?.length) { | |
| return [Object.fromEntries(query.aggregateItems.map((aggregate: any) => { | |
| if (String(aggregate.function).toLowerCase() !== "count") { | |
| throw new Error(`Unsupported local aggregate: ${aggregate.function}`); | |
| } | |
| return [aggregate.alias, rows.length]; | |
| }))]; | |
| } | |
| for (const order of [...query.orderItems].reverse()) { | |
| rows.sort((a, b) => (a[order.field] === b[order.field] ? 0 : a[order.field] > b[order.field] ? 1 : -1) * (String(order.direction).toLowerCase() === "desc" ? -1 : 1)); | |
| } | |
| const start = query.offsetValue || 0; | |
| rows = rows.slice(start, start + query.limitValue); | |
| for (const relation of (query.relations || []) as any[]) { | |
| const localValues = new Set(rows.map((row: any) => row[relation.localKey || "id"])); | |
| const relatedRows = (await this.executeQuery(relation.query)).filter( | |
| (row: any) => localValues.has(row[relation.foreignKey || "id"])); | |
| for (const row of rows) { | |
| const matches = relatedRows.filter( | |
| (related: any) => related[relation.foreignKey || "id"] === row[relation.localKey || "id"]); | |
| row[relation.name] = relation.many === false ? matches[0] : matches; | |
| } | |
| } | |
| if (query.selectItems.length > 0) { | |
| const selections = new Set(query.selectItems); | |
| for (const relation of (query.relations || []) as any[]) selections.add(relation.name); | |
| rows = rows.map((row: any) => Object.fromEntries( | |
| Object.entries(row).filter(([field]) => selections.has(field)))); | |
| } | |
| return rows; | |
| } | |
| async executeCount(query: any, countAlias?: string, operation?: QueryOperation): Promise<number> { | |
| if (typeof query?.forExactCount !== "function") { | |
| throw new Error("TeaQL exact count requires the formal runtime SelectQuery"); | |
| } | |
| const alias = countAlias ?? "__teaql_total"; | |
| const request = new QueryRequest(query).deriveOperation(query.forExactCount(alias), operation ?? { | |
| kind: "named-count", ...(countAlias === undefined ? {} : { alias: countAlias }) }); | |
| const rows = await this.executeQuery(request.query); | |
| const value = rows[0]?.[alias]; | |
| if (typeof value !== "number" || !Number.isFinite(value)) { | |
| throw new Error(`TeaQL provider did not return exact count alias ${alias}`); | |
| } | |
| return value; | |
| } | |
| async *executeForStream(query: any, chunkSize = 1000): AsyncIterable<any[]> { | |
| if (!Number.isInteger(chunkSize) || chunkSize <= 0) throw new Error("stream chunk size must be a positive integer"); | |
| const rows = await this.executeQuery(query); | |
| for (let offset = 0; offset < rows.length; offset += chunkSize) { | |
| yield rows.slice(offset, offset + chunkSize); | |
| } | |
| } | |
| } | |
| declare const require: any; | |
| export function eq(a: any, b: any) { return {type: 'eq', field: a, value: b}; } | |
| export function contain(a: any, b: any) { return {type: 'contain', field: a, value: b}; } |