Skip to content
Merged
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
41 changes: 41 additions & 0 deletions .github/workflows/converged-modeling-patterns.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: converged-modeling-patterns
on:
push:
branches: [main]
paths:
- 'labs/converged-modeling-patterns/**'
- '.github/workflows/converged-modeling-patterns.yml'
pull_request:
paths:
- 'labs/converged-modeling-patterns/**'
- '.github/workflows/converged-modeling-patterns.yml'
schedule:
- cron: '41 6 * * *' # nightly: substantive freshness, not cosmetic
workflow_dispatch:

jobs:
validate:
runs-on: ubuntu-latest
# Generous: this lab builds a large image (Oracle AI Database 26ai Free + ORDS
# + mongosh) before validating both lanes.
timeout-minutes: 55
defaults:
run:
working-directory: labs/converged-modeling-patterns
steps:
- uses: actions/checkout@v4

- name: Free disk space
run: sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache/CodeQL

- name: Build the lab images (DB + ORDS + mongosh, and the hands-on console)
run: docker compose build

- name: Validate both lanes (SQL + Oracle API for MongoDB)
# run.sh brings the stack up, waits for the database AND the MongoDB API,
# then runs every pattern across BOTH lanes — grading each .sql and .js;
# the cross-API parity scripts exit non-zero on mismatch. It ends with a
# combined "N passed, 0 failed" and a non-zero exit if anything failed.
# Stage 3 then runs the hands-on console's unit + integration suite in a
# container against the same database.
run: ./run.sh
1 change: 1 addition & 0 deletions labs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ Self-contained, runnable labs that validate converged-model claims against a liv
| --- | --- | --- |
| converged-database-lab | Runnable proofs for the converged-database article series — JSON Relational Duality, single-table vs. converged modeling, graph, vector, spatial, and full-text claims, each executing against a free Oracle AI Database 26ai container. | [./converged-database-lab](./converged-database-lab) |
| oracle-database-kafka-apis | Java integration tests demonstrating Oracle AI Database Transactional Event Queues through the Kafka APIs, backed by Testcontainers. | [./oracle-database-kafka-apis](./oracle-database-kafka-apis) |
| converged-modeling-patterns | Six document-modeling patterns as runnable side-by-sides — the document-model starting point and the converged alternative that keeps the read win without the write amplification — validated across SQL and the Oracle API for MongoDB with cross-API parity assertions, on a free Oracle AI Database 26ai container, plus a browser-based hands-on console (run and edit every query, measure document-model vs converged write cost live). | [./converged-modeling-patterns](./converged-modeling-patterns) |

*More labs coming as the content series expands.*
9 changes: 9 additions & 0 deletions labs/converged-modeling-patterns/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# .dockerignore (repo root)
.git
.superpowers/
.env
app/node_modules
app/public/vendor
app/test/artifacts
docs
node_modules
10 changes: 10 additions & 0 deletions labs/converged-modeling-patterns/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# OS / editor
.DS_Store
*.swp
*~

# local env overrides
.env

# nothing generated is checked in; the DB lives in a docker volume
.superpowers/
415 changes: 415 additions & 0 deletions labs/converged-modeling-patterns/README.md

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions labs/converged-modeling-patterns/app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules/
public/vendor/
test/artifacts/
26 changes: 26 additions & 0 deletions labs/converged-modeling-patterns/app/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# app/Dockerfile
FROM node:22-bookworm-slim AS deps
WORKDIR /app
COPY app/package.json app/package-lock.json ./
RUN npm ci
COPY app/ ./
RUN npm run build:vendor

FROM deps AS test
COPY patterns /patterns
ENV PATTERNS_DIR=/patterns
CMD ["npx", "vitest", "run"]

FROM node:22-bookworm-slim AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY app/package.json app/package-lock.json ./
RUN npm ci --omit=dev
COPY --from=deps /app/src ./src
COPY --from=deps /app/sql ./sql
COPY --from=deps /app/public ./public
COPY patterns /patterns
ENV PATTERNS_DIR=/patterns PORT=3000
EXPOSE 3000
USER node
CMD ["node", "src/server.js"]
47 changes: 47 additions & 0 deletions labs/converged-modeling-patterns/app/build/cm-entry.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// app/build/cm-entry.js
import { EditorView, basicSetup } from 'codemirror';
import { keymap } from '@codemirror/view';
import { Prec } from '@codemirror/state';
import { sql, PLSQL } from '@codemirror/lang-sql';
import { javascript } from '@codemirror/lang-javascript';
import { HighlightStyle, syntaxHighlighting } from '@codemirror/language';
import { tags as t } from '@lezer/highlight';

// Colours come from lab.css tokens, so the editor follows the page's light/dark scheme
// (CodeMirror's default highlight style is light-only and unreadable on the dark panel).
const labHighlight = HighlightStyle.define([
{ tag: [t.keyword, t.operatorKeyword, t.modifier, t.controlKeyword], color: 'var(--flow)' },
{ tag: [t.string, t.special(t.string), t.regexp], color: 'var(--hot)' },
{ tag: [t.number, t.bool, t.null, t.atom], color: 'var(--cool)' },
{ tag: [t.comment, t.lineComment, t.blockComment], color: 'var(--faint)', fontStyle: 'italic' },
{ tag: [t.typeName, t.standard(t.name)], color: 'var(--cool)' },
]);
const labTheme = EditorView.theme({
'&': { color: 'var(--ink)', backgroundColor: 'var(--panel)' },
'.cm-content': { caretColor: 'var(--ink)' },
'.cm-cursor, .cm-dropCursor': { borderLeftColor: 'var(--ink)' },
'.cm-gutters': { backgroundColor: 'var(--paper)', color: 'var(--faint)', borderRight: '1px solid var(--hairline)' },
'.cm-activeLine': { backgroundColor: 'var(--flow-soft)' },
'.cm-activeLineGutter': { backgroundColor: 'var(--chip)', color: 'var(--ink)' },
'&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground': { backgroundColor: 'var(--flow-soft)' },
});

export function createEditor(parent, { lang, doc = '', onRun }) {
const view = new EditorView({
parent,
doc,
extensions: [
basicSetup,
labTheme,
syntaxHighlighting(labHighlight),
lang === 'sql' ? sql({ dialect: PLSQL }) : javascript(),
Prec.highest(keymap.of([{ key: 'Mod-Enter', run: () => { onRun?.(); return true; } }])),
EditorView.lineWrapping,
],
});
return {
get: () => view.state.doc.toString(),
set: (text) => view.dispatch({ changes: { from: 0, to: view.state.doc.length, insert: text } }),
focus: () => view.focus(),
};
}
28 changes: 28 additions & 0 deletions labs/converged-modeling-patterns/app/build/vendor.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// app/build/vendor.mjs
import fs from 'node:fs';
import path from 'node:path';
import { build } from 'esbuild';

const root = path.resolve(import.meta.dirname, '..');
const out = path.join(root, 'public/vendor');
fs.mkdirSync(path.join(out, 'fonts'), { recursive: true });

await build({ entryPoints: [path.join(root, 'build/cm-entry.js')], bundle: true, format: 'esm', minify: true, outfile: path.join(out, 'cm.js') });

const FONTS = [
['bricolage-grotesque', 'Bricolage Grotesque', [600, 700, 800]],
['public-sans', 'Public Sans', [400, 500, 600, 700]],
['ibm-plex-mono', 'IBM Plex Mono', [400, 500, 600]],
];
let css = '';
for (const [pkg, family, weights] of FONTS) {
for (const w of weights) {
const file = `${pkg}-latin-${w}-normal.woff2`;
const src = path.join(root, 'node_modules/@fontsource', pkg, 'files', file);
if (!fs.existsSync(src)) throw new Error(`missing font file ${src}`);
fs.copyFileSync(src, path.join(out, 'fonts', file));
css += `@font-face{font-family:'${family}';font-style:normal;font-weight:${w};font-display:swap;src:url(fonts/${file}) format('woff2');}\n`;
}
}
fs.writeFileSync(path.join(out, 'fonts.css'), css);
console.log('vendor: cm.js + fonts.css written');
Loading
Loading