libmodulor
Version:
A TypeScript library to create platform-agnostic applications
202 lines (198 loc) • 7.99 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
import { inject, injectable } from 'inversify';
import { NotAvailableError, NotImplementedError } from '../../error/index.js';
export const ROW_COLS = [
'aggregateId',
'appName',
'createdAt',
'data',
'executionMode',
'id',
'input',
'name',
'organizationId',
'userId',
];
/**
* @alpha This implementation is still a WIP and needs improvement
*/
let CloudflareD1UCDataStore = class CloudflareD1UCDataStore {
settingsManager;
// Defined as protected and not private so the `assertClient` guard works correctly
// See https://stackoverflow.com/a/74267363/1259118
// > Such property type guards only work when the property you are narrowing is not private; if the property is private then this gets narrowed to never because an object with a private property is not assignable to an object with a public property of the same key.
client;
constructor(settingsManager) {
this.settingsManager = settingsManager;
}
s() {
return {
uc_data_store_ucs_dataset_name: this.settingsManager.get()('uc_data_store_ucs_dataset_name'),
};
}
async clear() {
throw new NotImplementedError('clear');
}
async destroy() {
throw new NotImplementedError('destroy');
}
async exists() {
throw new NotImplementedError('exists');
}
async init() {
throw new NotAvailableError('initSync');
// Since workers do not accept top-level await, we cannot invoke this method when initiating it.
// Plus, calling it in every request handler would be overkill and would add too much overhead.
// In the meantime, process as follows :
/*
mkdir path_to_target/migrations
cat > path_to_target/migrations/001_init.sql <<'EOF'
-- Table Definition
CREATE TABLE IF NOT EXISTS uc_executions (
"aggregateId" uuid NOT NULL,
"appName" varchar(255) NOT NULL,
"createdAt" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
"data" jsonb,
"executionMode" varchar(255) NOT NULL,
"id" uuid NOT NULL,
"input" jsonb,
"name" varchar(255) NOT NULL,
"organizationId" uuid,
"userId" uuid,
PRIMARY KEY ("id")
);
-- Indices
CREATE INDEX IF NOT EXISTS uc_executions_aggregate_id_index ON uc_executions (aggregateId);
CREATE INDEX IF NOT EXISTS uc_executions_name_index ON uc_executions (name);
CREATE INDEX IF NOT EXISTS uc_executions_organization_id_index ON uc_executions (organizationId);
CREATE INDEX IF NOT EXISTS uc_executions_user_id_index ON uc_executions (userId);
EOF
pnpm wrangler d1 create my-app-uc-data-store
pnpm wrangler d1 execute my-app-uc-data-store --cwd path_to_target --local --file=./migrations/001_init.sql
pnpm wrangler d1 execute my-app-uc-data-store --cwd path_to_target --remote --file=./migrations/001_init.sql
pnpm wrangler d1 list
*/
}
initSync() {
throw new NotAvailableError('initSync');
}
async read(opts) {
this.assertClient();
// TODO : Consider using a query builder (or Google's pipe operator ?) when it gets too complicated or dangerous
const query = [
`select * from ${this.s().uc_data_store_ucs_dataset_name}`,
];
// Filter
const where = [];
const bindings = [];
if (opts?.filters) {
const { aggregateId, appName, idWithinInput, name, organizationId, userId, } = opts.filters;
this.filter(where, bindings, aggregateId, 'aggregateId');
this.filter(where, bindings, appName, 'appName');
this.filter(where, bindings, name, 'name');
this.filter(where, bindings, organizationId, 'organizationId');
this.filter(where, bindings, userId, 'userId');
if (idWithinInput !== undefined) {
// TODO : Implement CloudflareD1UCDataStore.read.opts.filters.idWithinInput
throw new Error('opts.filters.idWithinInput is not supported yet');
}
}
if (where.length > 0) {
query.push('where', where.join(' and '));
}
// Sort
query.push('order by createdAt asc');
const sql = query.join(' ');
const { results } = await this.client
.prepare(sql)
.bind(...bindings)
.run();
const records = results.map((r) => ({
...r,
data: JSON.parse(r.data),
input: JSON.parse(r.input),
}));
return {
records,
};
}
async readProjection(_name, _opts) {
throw new NotImplementedError('readProjection');
}
async startTx() {
throw new NotImplementedError('startTx');
}
supportedSpecificBindings() {
throw new NotImplementedError('supportedSpecificBindings');
}
async testKey(_encryptionKey) {
throw new NotAvailableError('testKey');
}
async write(record, _opts) {
this.assertClient();
const cols = ROW_COLS.join(', ');
const placeholders = ROW_COLS.map(() => '?').join(', ');
// Use variables only for variables declared here => never for data coming from outside this method !
const query = `insert into ${this.s().uc_data_store_ucs_dataset_name} (${cols}) values (${placeholders})`;
const values = ROW_COLS.map((c) => {
const val = record[c];
if (val instanceof Date) {
// D1_TYPE_ERROR: Type 'object' not supported for value 'Wed Jun 18 2025 13:32:27 GMT+0200 (Central European Summer Time)
return val.toISOString();
}
if (val !== null && typeof val === 'object') {
// D1_TYPE_ERROR: Type 'object' not supported for value '[object Object]'
return JSON.stringify(val);
}
return val;
});
await this.client
.prepare(query)
.bind(...values)
.run();
}
async writeBulk(_records, _opts) {
throw new NotImplementedError('writeBulk');
}
async writeProjection(_name, _data, _opts) {
throw new NotImplementedError('writeProjection');
}
setClient(client) {
this.client = client;
}
assertClient() {
if (this.client === undefined) {
throw new Error('You must call UCDataStore#setClient with the D1Database from the context (e.g. hono context)');
}
}
filter(query, bindings, value, field) {
if (value === undefined) {
return;
}
if (Array.isArray(value)) {
query.push(`${field} in (${value.map(() => '?')})`);
bindings.push(...value);
}
else {
query.push(`${field} = ?`);
bindings.push(value);
}
}
};
CloudflareD1UCDataStore = __decorate([
injectable(),
__param(0, inject('SettingsManager')),
__metadata("design:paramtypes", [Object])
], CloudflareD1UCDataStore);
export { CloudflareD1UCDataStore };