magik 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +90 -0
- data/LICENSE +21 -0
- data/README.md +356 -0
- data/exe/magik +13 -0
- data/lib/magik/action.rb +44 -0
- data/lib/magik/admin.rb +42 -0
- data/lib/magik/api.rb +49 -0
- data/lib/magik/auth.rb +42 -0
- data/lib/magik/billing.rb +42 -0
- data/lib/magik/check.rb +46 -0
- data/lib/magik/cli.rb +196 -0
- data/lib/magik/core.rb +47 -0
- data/lib/magik/domains.rb +45 -0
- data/lib/magik/i18n.rb +47 -0
- data/lib/magik/jobs.rb +45 -0
- data/lib/magik/ledger.rb +47 -0
- data/lib/magik/model.rb +49 -0
- data/lib/magik/notify.rb +42 -0
- data/lib/magik/pwa.rb +43 -0
- data/lib/magik/realtime.rb +51 -0
- data/lib/magik/render.rb +49 -0
- data/lib/magik/router.rb +45 -0
- data/lib/magik/schema.rb +42 -0
- data/lib/magik/testing.rb +51 -0
- data/lib/magik/version.rb +19 -0
- data/lib/magik.rb +258 -0
- metadata +87 -0
data/lib/magik/pwa.rb
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Magik
|
|
4
|
+
# Installable progressive web app metadata. Explicitly no offline caching: the server
|
|
5
|
+
# is the single source of truth.
|
|
6
|
+
#
|
|
7
|
+
# Implements: **Phase 8 — i18n, PWA, Notifications** of `docs/idea/00-build-spec.md`.
|
|
8
|
+
#
|
|
9
|
+
# Planned DSL surface, copied from the spec:
|
|
10
|
+
#
|
|
11
|
+
# * `pwa do name/icon/display end`
|
|
12
|
+
#
|
|
13
|
+
# Status: **Not implemented — spec only.** Every entry point below raises
|
|
14
|
+
# {NotImplementedError}. Nothing here reads config, touches a database or
|
|
15
|
+
# emits a byte of HTML.
|
|
16
|
+
#
|
|
17
|
+
# @see Magik::SUBSYSTEMS
|
|
18
|
+
module PWA
|
|
19
|
+
# The build-spec phase this subsystem implements.
|
|
20
|
+
# @return [String]
|
|
21
|
+
SPEC_PHASE = "Phase 8 — i18n, PWA, Notifications"
|
|
22
|
+
|
|
23
|
+
# The DSL this subsystem will expose, verbatim from the spec.
|
|
24
|
+
# @return [Array<String>]
|
|
25
|
+
DSL_SURFACE = [
|
|
26
|
+
"pwa do name/icon/display end"
|
|
27
|
+
].freeze
|
|
28
|
+
|
|
29
|
+
# Implementation status of this subsystem.
|
|
30
|
+
# @return [String]
|
|
31
|
+
STATUS = "Not implemented — spec only"
|
|
32
|
+
|
|
33
|
+
# Entry point for the PWA DSL.
|
|
34
|
+
#
|
|
35
|
+
# @param _args [Array] ignored
|
|
36
|
+
# @param _options [Hash] ignored
|
|
37
|
+
# @return [void] never returns
|
|
38
|
+
# @raise [NotImplementedError] always, until Phase 8 lands
|
|
39
|
+
def self.define(*_args, **_options)
|
|
40
|
+
raise NotImplementedError, "Magik::PWA is spec-only; see docs/idea/00-build-spec.md"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Magik
|
|
4
|
+
# Opt-in realtime. Default is plain request/response; nothing costs anything until a
|
|
5
|
+
# screen declares `live` or a `channel` exists.
|
|
6
|
+
#
|
|
7
|
+
# Implements: **Phase 3 — Realtime (opt-in)** of `docs/idea/00-build-spec.md`.
|
|
8
|
+
#
|
|
9
|
+
# Planned DSL surface, copied from the spec:
|
|
10
|
+
#
|
|
11
|
+
# * `live :state_var, on: "channel:name"`
|
|
12
|
+
# * `channel :name do subscribe_to/on_create/on_update end`
|
|
13
|
+
# * `broadcast "channel", :event, payload`
|
|
14
|
+
# * `presence for online/cursor tracking`
|
|
15
|
+
# * `backend: Postgres LISTEN/NOTIFY (default) → Redis pub/sub (swap via config)`
|
|
16
|
+
#
|
|
17
|
+
# Status: **Not implemented — spec only.** Every entry point below raises
|
|
18
|
+
# {NotImplementedError}. Nothing here reads config, touches a database or
|
|
19
|
+
# emits a byte of HTML.
|
|
20
|
+
#
|
|
21
|
+
# @see Magik::SUBSYSTEMS
|
|
22
|
+
module Realtime
|
|
23
|
+
# The build-spec phase this subsystem implements.
|
|
24
|
+
# @return [String]
|
|
25
|
+
SPEC_PHASE = "Phase 3 — Realtime (opt-in)"
|
|
26
|
+
|
|
27
|
+
# The DSL this subsystem will expose, verbatim from the spec.
|
|
28
|
+
# @return [Array<String>]
|
|
29
|
+
DSL_SURFACE = [
|
|
30
|
+
"live :state_var, on: \"channel:name\"",
|
|
31
|
+
"channel :name do subscribe_to/on_create/on_update end",
|
|
32
|
+
"broadcast \"channel\", :event, payload",
|
|
33
|
+
"presence for online/cursor tracking",
|
|
34
|
+
"backend: Postgres LISTEN/NOTIFY (default) → Redis pub/sub (swap via config)"
|
|
35
|
+
].freeze
|
|
36
|
+
|
|
37
|
+
# Implementation status of this subsystem.
|
|
38
|
+
# @return [String]
|
|
39
|
+
STATUS = "Not implemented — spec only"
|
|
40
|
+
|
|
41
|
+
# Entry point for the Realtime DSL.
|
|
42
|
+
#
|
|
43
|
+
# @param _args [Array] ignored
|
|
44
|
+
# @param _options [Hash] ignored
|
|
45
|
+
# @return [void] never returns
|
|
46
|
+
# @raise [NotImplementedError] always, until Phase 3 lands
|
|
47
|
+
def self.define(*_args, **_options)
|
|
48
|
+
raise NotImplementedError, "Magik::Realtime is spec-only; see docs/idea/00-build-spec.md"
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
data/lib/magik/render.rb
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Magik
|
|
4
|
+
# The UI DSL. Compiles components and screens to server-rendered HTML plus htmx
|
|
5
|
+
# attributes. There is no SPA framework and there never will be.
|
|
6
|
+
#
|
|
7
|
+
# Implements: **Phase 2 — Rendering & Actions** of `docs/idea/00-build-spec.md`.
|
|
8
|
+
#
|
|
9
|
+
# Planned DSL surface, copied from the spec:
|
|
10
|
+
#
|
|
11
|
+
# * `component :Name do prop; body do ... end end`
|
|
12
|
+
# * `screen :Name do state/body end`
|
|
13
|
+
# * `kit: button, form, field, data_table, modal, toast, card, list, grid, tabs, stat, chart`
|
|
14
|
+
# * `theme system: design tokens, light/dark mode via CSS vars`
|
|
15
|
+
#
|
|
16
|
+
# Status: **Not implemented — spec only.** Every entry point below raises
|
|
17
|
+
# {NotImplementedError}. Nothing here reads config, touches a database or
|
|
18
|
+
# emits a byte of HTML.
|
|
19
|
+
#
|
|
20
|
+
# @see Magik::SUBSYSTEMS
|
|
21
|
+
module Render
|
|
22
|
+
# The build-spec phase this subsystem implements.
|
|
23
|
+
# @return [String]
|
|
24
|
+
SPEC_PHASE = "Phase 2 — Rendering & Actions"
|
|
25
|
+
|
|
26
|
+
# The DSL this subsystem will expose, verbatim from the spec.
|
|
27
|
+
# @return [Array<String>]
|
|
28
|
+
DSL_SURFACE = [
|
|
29
|
+
"component :Name do prop; body do ... end end",
|
|
30
|
+
"screen :Name do state/body end",
|
|
31
|
+
"kit: button, form, field, data_table, modal, toast, card, list, grid, tabs, stat, chart",
|
|
32
|
+
"theme system: design tokens, light/dark mode via CSS vars"
|
|
33
|
+
].freeze
|
|
34
|
+
|
|
35
|
+
# Implementation status of this subsystem.
|
|
36
|
+
# @return [String]
|
|
37
|
+
STATUS = "Not implemented — spec only"
|
|
38
|
+
|
|
39
|
+
# Entry point for the Render DSL.
|
|
40
|
+
#
|
|
41
|
+
# @param _args [Array] ignored
|
|
42
|
+
# @param _options [Hash] ignored
|
|
43
|
+
# @return [void] never returns
|
|
44
|
+
# @raise [NotImplementedError] always, until Phase 2 lands
|
|
45
|
+
def self.define(*_args, **_options)
|
|
46
|
+
raise NotImplementedError, "Magik::Render is spec-only; see docs/idea/00-build-spec.md"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
data/lib/magik/router.rb
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Magik
|
|
4
|
+
# Convention-driven routing: an action name becomes a path, a screen auto-routes. Also
|
|
5
|
+
# the dev server and hot reload entry point.
|
|
6
|
+
#
|
|
7
|
+
# Implements: **Phase 2 — Rendering & Actions (build order step 4)** of `docs/idea/00-build-spec.md`.
|
|
8
|
+
#
|
|
9
|
+
# Planned DSL surface, copied from the spec:
|
|
10
|
+
#
|
|
11
|
+
# * `convention: action name → path`
|
|
12
|
+
# * `screens are auto-routed by name`
|
|
13
|
+
#
|
|
14
|
+
# Status: **Not implemented — spec only.** Every entry point below raises
|
|
15
|
+
# {NotImplementedError}. Nothing here reads config, touches a database or
|
|
16
|
+
# emits a byte of HTML.
|
|
17
|
+
#
|
|
18
|
+
# @see Magik::SUBSYSTEMS
|
|
19
|
+
module Router
|
|
20
|
+
# The build-spec phase this subsystem implements.
|
|
21
|
+
# @return [String]
|
|
22
|
+
SPEC_PHASE = "Phase 2 — Rendering & Actions (build order step 4)"
|
|
23
|
+
|
|
24
|
+
# The DSL this subsystem will expose, verbatim from the spec.
|
|
25
|
+
# @return [Array<String>]
|
|
26
|
+
DSL_SURFACE = [
|
|
27
|
+
"convention: action name → path",
|
|
28
|
+
"screens are auto-routed by name"
|
|
29
|
+
].freeze
|
|
30
|
+
|
|
31
|
+
# Implementation status of this subsystem.
|
|
32
|
+
# @return [String]
|
|
33
|
+
STATUS = "Not implemented — spec only"
|
|
34
|
+
|
|
35
|
+
# Entry point for the Router DSL.
|
|
36
|
+
#
|
|
37
|
+
# @param _args [Array] ignored
|
|
38
|
+
# @param _options [Hash] ignored
|
|
39
|
+
# @return [void] never returns
|
|
40
|
+
# @raise [NotImplementedError] always, until Phase 2 lands
|
|
41
|
+
def self.define(*_args, **_options)
|
|
42
|
+
raise NotImplementedError, "Magik::Router is spec-only; see docs/idea/00-build-spec.md"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
data/lib/magik/schema.rb
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Magik
|
|
4
|
+
# The migration DSL over Sequel migrations.
|
|
5
|
+
#
|
|
6
|
+
# Implements: **Phase 1 — Foundation** of `docs/idea/00-build-spec.md`.
|
|
7
|
+
#
|
|
8
|
+
# Planned DSL surface, copied from the spec:
|
|
9
|
+
#
|
|
10
|
+
# * `migrate :Name do up/down end`
|
|
11
|
+
#
|
|
12
|
+
# Status: **Not implemented — spec only.** Every entry point below raises
|
|
13
|
+
# {NotImplementedError}. Nothing here reads config, touches a database or
|
|
14
|
+
# emits a byte of HTML.
|
|
15
|
+
#
|
|
16
|
+
# @see Magik::SUBSYSTEMS
|
|
17
|
+
module Schema
|
|
18
|
+
# The build-spec phase this subsystem implements.
|
|
19
|
+
# @return [String]
|
|
20
|
+
SPEC_PHASE = "Phase 1 — Foundation"
|
|
21
|
+
|
|
22
|
+
# The DSL this subsystem will expose, verbatim from the spec.
|
|
23
|
+
# @return [Array<String>]
|
|
24
|
+
DSL_SURFACE = [
|
|
25
|
+
"migrate :Name do up/down end"
|
|
26
|
+
].freeze
|
|
27
|
+
|
|
28
|
+
# Implementation status of this subsystem.
|
|
29
|
+
# @return [String]
|
|
30
|
+
STATUS = "Not implemented — spec only"
|
|
31
|
+
|
|
32
|
+
# Entry point for the Schema DSL.
|
|
33
|
+
#
|
|
34
|
+
# @param _args [Array] ignored
|
|
35
|
+
# @param _options [Hash] ignored
|
|
36
|
+
# @return [void] never returns
|
|
37
|
+
# @raise [NotImplementedError] always, until Phase 1 lands
|
|
38
|
+
def self.define(*_args, **_options)
|
|
39
|
+
raise NotImplementedError, "Magik::Schema is spec-only; see docs/idea/00-build-spec.md"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Magik
|
|
4
|
+
# The test DSL, compiling to Minitest (never RSpec), with auto-inferred factories and
|
|
5
|
+
# a parallel runner.
|
|
6
|
+
#
|
|
7
|
+
# Implements: **Phase 9 — Testing** of `docs/idea/00-build-spec.md`.
|
|
8
|
+
#
|
|
9
|
+
# Planned DSL surface, copied from the spec:
|
|
10
|
+
#
|
|
11
|
+
# * `test :Name do it "..." do expect(...) end end`
|
|
12
|
+
# * `helpers: perform_action, render_screen, concurrently(n), travel_to`
|
|
13
|
+
# * `helpers: assert_enqueued, assert_broadcast, assert_notified`
|
|
14
|
+
# * `parallel: one Ractor per test file group, workers: :auto, transactional rollback per test`
|
|
15
|
+
# * `magik test, magik test --watch, magik test --changed`
|
|
16
|
+
#
|
|
17
|
+
# Status: **Not implemented — spec only.** Every entry point below raises
|
|
18
|
+
# {NotImplementedError}. Nothing here reads config, touches a database or
|
|
19
|
+
# emits a byte of HTML.
|
|
20
|
+
#
|
|
21
|
+
# @see Magik::SUBSYSTEMS
|
|
22
|
+
module Testing
|
|
23
|
+
# The build-spec phase this subsystem implements.
|
|
24
|
+
# @return [String]
|
|
25
|
+
SPEC_PHASE = "Phase 9 — Testing"
|
|
26
|
+
|
|
27
|
+
# The DSL this subsystem will expose, verbatim from the spec.
|
|
28
|
+
# @return [Array<String>]
|
|
29
|
+
DSL_SURFACE = [
|
|
30
|
+
"test :Name do it \"...\" do expect(...) end end",
|
|
31
|
+
"helpers: perform_action, render_screen, concurrently(n), travel_to",
|
|
32
|
+
"helpers: assert_enqueued, assert_broadcast, assert_notified",
|
|
33
|
+
"parallel: one Ractor per test file group, workers: :auto, transactional rollback per test",
|
|
34
|
+
"magik test, magik test --watch, magik test --changed"
|
|
35
|
+
].freeze
|
|
36
|
+
|
|
37
|
+
# Implementation status of this subsystem.
|
|
38
|
+
# @return [String]
|
|
39
|
+
STATUS = "Not implemented — spec only"
|
|
40
|
+
|
|
41
|
+
# Entry point for the Testing DSL.
|
|
42
|
+
#
|
|
43
|
+
# @param _args [Array] ignored
|
|
44
|
+
# @param _options [Hash] ignored
|
|
45
|
+
# @return [void] never returns
|
|
46
|
+
# @raise [NotImplementedError] always, until Phase 9 lands
|
|
47
|
+
def self.define(*_args, **_options)
|
|
48
|
+
raise NotImplementedError, "Magik::Testing is spec-only; see docs/idea/00-build-spec.md"
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Magik
|
|
4
|
+
# The released version of the `magik` gem.
|
|
5
|
+
#
|
|
6
|
+
# `0.0.1` is a **name-reservation release**: the gem name is claimed on
|
|
7
|
+
# RubyGems, the public surface is a CLI shim plus documented, spec-only
|
|
8
|
+
# subsystem stubs. No framework behaviour from `docs/idea/00-build-spec.md`
|
|
9
|
+
# is implemented.
|
|
10
|
+
#
|
|
11
|
+
# The value follows [Semantic Versioning](https://semver.org). While the
|
|
12
|
+
# major version is `0`, minor bumps may break the public API.
|
|
13
|
+
#
|
|
14
|
+
# @return [String] a frozen `MAJOR.MINOR.PATCH` string
|
|
15
|
+
# @example Read the version at runtime
|
|
16
|
+
# require "magik"
|
|
17
|
+
# Magik::VERSION # => "0.0.1"
|
|
18
|
+
VERSION = "0.0.1"
|
|
19
|
+
end
|
data/lib/magik.rb
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pathname"
|
|
4
|
+
|
|
5
|
+
require_relative "magik/version"
|
|
6
|
+
|
|
7
|
+
# Magik is an opinionated, full-stack Ruby framework targeting TruffleRuby:
|
|
8
|
+
# one DSL for models, screens, actions, realtime channels, background jobs,
|
|
9
|
+
# ledgers, APIs and admin panels, rendered server-side as HTML + htmx.
|
|
10
|
+
#
|
|
11
|
+
# **Status as of 2026-08-26: spec only.** The product specification lives in
|
|
12
|
+
# `docs/idea/00-build-spec.md` and is entirely unimplemented. What ships in
|
|
13
|
+
# this gem today is:
|
|
14
|
+
#
|
|
15
|
+
# * {Magik::VERSION} — the name-reservation version string.
|
|
16
|
+
# * {Magik::Error} — the stable `MAGIK_*` error-code convention.
|
|
17
|
+
# * {Magik::CLI} — a working `magik version` / `magik help` command line.
|
|
18
|
+
# * One documented, spec-only stub module per planned subsystem
|
|
19
|
+
# (see {Magik::SUBSYSTEMS}); every entry point raises {NotImplementedError}.
|
|
20
|
+
#
|
|
21
|
+
# Nothing in this gem renders a page, talks to a database, or runs a job.
|
|
22
|
+
#
|
|
23
|
+
# @see https://github.com/developerz-ai/magik
|
|
24
|
+
module Magik
|
|
25
|
+
# Every planned subsystem, mapped from its file basename under `lib/magik/`
|
|
26
|
+
# to the constant it defines. Each entry mirrors one area of the build spec.
|
|
27
|
+
#
|
|
28
|
+
# The map drives {https://ruby-doc.org/core/Module.html#method-i-autoload
|
|
29
|
+
# autoload} registration below, so requiring `magik` is cheap: a subsystem
|
|
30
|
+
# file is only read when its constant is first referenced.
|
|
31
|
+
#
|
|
32
|
+
# @return [Hash{Symbol => Symbol}] file basename => constant name
|
|
33
|
+
# @example List the planned subsystems
|
|
34
|
+
# Magik::SUBSYSTEMS.keys # => [:core, :cli, :model, ...]
|
|
35
|
+
SUBSYSTEMS = {
|
|
36
|
+
core: :Core,
|
|
37
|
+
cli: :CLI,
|
|
38
|
+
model: :Model,
|
|
39
|
+
schema: :Schema,
|
|
40
|
+
render: :Render,
|
|
41
|
+
action: :Action,
|
|
42
|
+
router: :Router,
|
|
43
|
+
realtime: :Realtime,
|
|
44
|
+
jobs: :Jobs,
|
|
45
|
+
ledger: :Ledger,
|
|
46
|
+
api: :API,
|
|
47
|
+
auth: :Auth,
|
|
48
|
+
billing: :Billing,
|
|
49
|
+
admin: :Admin,
|
|
50
|
+
i18n: :I18n,
|
|
51
|
+
pwa: :PWA,
|
|
52
|
+
notify: :Notify,
|
|
53
|
+
testing: :Testing,
|
|
54
|
+
domains: :Domains,
|
|
55
|
+
check: :Check
|
|
56
|
+
}.freeze
|
|
57
|
+
|
|
58
|
+
# The subsystems that are documentation-only: their modules load and carry
|
|
59
|
+
# their spec metadata, but every entry point raises {NotImplementedError}.
|
|
60
|
+
#
|
|
61
|
+
# {Magik::CLI} is deliberately absent — it is the one subsystem with real,
|
|
62
|
+
# useful behaviour today.
|
|
63
|
+
#
|
|
64
|
+
# @return [Array<Symbol>] file basenames, a subset of {SUBSYSTEMS} keys
|
|
65
|
+
SPEC_ONLY_SUBSYSTEMS = (SUBSYSTEMS.keys - [:cli]).freeze
|
|
66
|
+
|
|
67
|
+
SUBSYSTEMS.each { |file, const| autoload const, "magik/#{file}" }
|
|
68
|
+
|
|
69
|
+
# Base class for every error Magik raises.
|
|
70
|
+
#
|
|
71
|
+
# Magik errors are not free-form strings. Each one carries three things, and
|
|
72
|
+
# all three are required:
|
|
73
|
+
#
|
|
74
|
+
# 1. a **stable code** matching {CODE_FORMAT} (`MAGIK_SOMETHING`) that is
|
|
75
|
+
# safe to grep for, link to, and match on in tests and CI;
|
|
76
|
+
# 2. a **cause** — one sentence saying what actually went wrong;
|
|
77
|
+
# 3. a **fix** — a runnable command or a concrete edit, never "check your
|
|
78
|
+
# configuration".
|
|
79
|
+
#
|
|
80
|
+
# The rendered message is deterministic:
|
|
81
|
+
#
|
|
82
|
+
# ```text
|
|
83
|
+
# MAGIK_LEDGER_UNBALANCED: entries for :Payouts do not balance (debits 1200, credits 900).
|
|
84
|
+
# fix: run `magik check --ledger Payouts`
|
|
85
|
+
# ```
|
|
86
|
+
#
|
|
87
|
+
# Subclasses declare their defaults with the {code} and {fix} class-level
|
|
88
|
+
# DSL, so a raise site only has to supply the cause.
|
|
89
|
+
#
|
|
90
|
+
# @example Raise a one-off error
|
|
91
|
+
# raise Magik::Error.new(
|
|
92
|
+
# "the app has no `App.define` block",
|
|
93
|
+
# code: "MAGIK_NO_APP",
|
|
94
|
+
# fix: "run `magik new myapp` to generate one"
|
|
95
|
+
# )
|
|
96
|
+
# @example Declare a reusable error class
|
|
97
|
+
# class MissingTenant < Magik::Error
|
|
98
|
+
# code "MAGIK_MISSING_TENANT"
|
|
99
|
+
# fix "add `tenant_by :subdomain` to your App.define block"
|
|
100
|
+
# end
|
|
101
|
+
# raise MissingTenant, "query on :Invoice has no tenant_id in WHERE"
|
|
102
|
+
class Error < StandardError
|
|
103
|
+
# The shape every Magik error code must take: `MAGIK_` followed by
|
|
104
|
+
# underscore-separated uppercase words.
|
|
105
|
+
#
|
|
106
|
+
# @return [Regexp]
|
|
107
|
+
CODE_FORMAT = /\AMAGIK_[A-Z0-9]+(?:_[A-Z0-9]+)*\z/
|
|
108
|
+
|
|
109
|
+
# Fallback code for {Magik::Error} itself.
|
|
110
|
+
# @return [String]
|
|
111
|
+
DEFAULT_CODE = "MAGIK_ERROR"
|
|
112
|
+
|
|
113
|
+
# Fallback fix for {Magik::Error} itself.
|
|
114
|
+
# @return [String]
|
|
115
|
+
DEFAULT_FIX = "read the raised cause above, then see docs/idea/00-build-spec.md"
|
|
116
|
+
|
|
117
|
+
class << self
|
|
118
|
+
# Get or set the default error code for this class and its subclasses.
|
|
119
|
+
#
|
|
120
|
+
# Called with an argument it is a writer; called without, a reader that
|
|
121
|
+
# walks up the superclass chain.
|
|
122
|
+
#
|
|
123
|
+
# @param value [String, nil] a code matching {CODE_FORMAT}, or nil to read
|
|
124
|
+
# @return [String] the effective code for this class
|
|
125
|
+
# @raise [ArgumentError] if `value` does not match {CODE_FORMAT}
|
|
126
|
+
def code(value = nil)
|
|
127
|
+
return @code = Error.validate_code!(value) unless value.nil?
|
|
128
|
+
|
|
129
|
+
@code || (superclass.respond_to?(:code) ? superclass.code : DEFAULT_CODE)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Get or set the default fix line for this class and its subclasses.
|
|
133
|
+
#
|
|
134
|
+
# @param value [String, nil] an actionable fix, or nil to read
|
|
135
|
+
# @return [String] the effective fix for this class
|
|
136
|
+
# @raise [ArgumentError] if `value` is blank
|
|
137
|
+
def fix(value = nil)
|
|
138
|
+
return @fix = Error.validate_fix!(value) unless value.nil?
|
|
139
|
+
|
|
140
|
+
@fix || (superclass.respond_to?(:fix) ? superclass.fix : DEFAULT_FIX)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# @api private
|
|
144
|
+
# @param value [String] candidate error code
|
|
145
|
+
# @return [String] the frozen, validated code
|
|
146
|
+
# @raise [ArgumentError] if the code is malformed
|
|
147
|
+
def validate_code!(value)
|
|
148
|
+
string = value.to_s
|
|
149
|
+
return string.freeze if CODE_FORMAT.match?(string)
|
|
150
|
+
|
|
151
|
+
raise ArgumentError, "error code #{string.inspect} must match #{CODE_FORMAT.source}"
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# @api private
|
|
155
|
+
# @param value [String, nil] candidate cause text
|
|
156
|
+
# @param klass [Class] the error class, used as the fallback cause
|
|
157
|
+
# @return [String] a frozen, non-empty cause
|
|
158
|
+
def normalize_cause(value, klass)
|
|
159
|
+
string = value.to_s
|
|
160
|
+
(string.strip.empty? ? klass.name.to_s : string).freeze
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# @api private
|
|
164
|
+
# @param value [String] candidate fix line
|
|
165
|
+
# @return [String] the frozen, validated fix
|
|
166
|
+
# @raise [ArgumentError] if the fix is blank
|
|
167
|
+
def validate_fix!(value)
|
|
168
|
+
string = value.to_s
|
|
169
|
+
return string.freeze unless string.strip.empty?
|
|
170
|
+
|
|
171
|
+
raise ArgumentError, "a Magik error needs a non-empty fix: line"
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# The stable, greppable error code.
|
|
176
|
+
# @return [String] e.g. `"MAGIK_UNKNOWN_COMMAND"`
|
|
177
|
+
attr_reader :code
|
|
178
|
+
|
|
179
|
+
# What went wrong, in one sentence.
|
|
180
|
+
#
|
|
181
|
+
# Named `cause_text` because `Exception#cause` is reserved by Ruby for the
|
|
182
|
+
# exception that was in flight when this one was raised.
|
|
183
|
+
#
|
|
184
|
+
# @return [String]
|
|
185
|
+
attr_reader :cause_text
|
|
186
|
+
|
|
187
|
+
# How to make it stop — a runnable command or a concrete edit.
|
|
188
|
+
# @return [String]
|
|
189
|
+
attr_reader :fix
|
|
190
|
+
|
|
191
|
+
# @param cause_text [String] what went wrong, one sentence
|
|
192
|
+
# @param code [String] a code matching {CODE_FORMAT}; defaults to the
|
|
193
|
+
# class-level {Error.code}
|
|
194
|
+
# @param fix [String] an actionable fix; defaults to the class-level
|
|
195
|
+
# {Error.fix}
|
|
196
|
+
# @raise [ArgumentError] if the code is malformed or the fix is blank
|
|
197
|
+
def initialize(cause_text = nil, code: self.class.code, fix: self.class.fix)
|
|
198
|
+
@code = Error.validate_code!(code)
|
|
199
|
+
@cause_text = Error.normalize_cause(cause_text, self.class)
|
|
200
|
+
@fix = Error.validate_fix!(fix)
|
|
201
|
+
super(self.class.render(@code, @cause_text, @fix))
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Render the canonical two-line message for an error triple.
|
|
205
|
+
#
|
|
206
|
+
# @param code [String] the error code
|
|
207
|
+
# @param cause_text [String] what went wrong
|
|
208
|
+
# @param fix [String] how to fix it
|
|
209
|
+
# @return [String] `"CODE: cause\n fix: fix"`
|
|
210
|
+
def self.render(code, cause_text, fix)
|
|
211
|
+
"#{code}: #{cause_text}\n fix: #{fix}"
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# The error as plain data, ready for `--json` output or a log line.
|
|
215
|
+
#
|
|
216
|
+
# @return [Hash{Symbol => String}] with keys `:code`, `:cause`, `:fix`
|
|
217
|
+
def to_h
|
|
218
|
+
{ code: code, cause: cause_text, fix: fix }
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Raised when a CLI command is named in the spec but not implemented yet.
|
|
223
|
+
#
|
|
224
|
+
# @see Magik::CLI
|
|
225
|
+
class CommandNotImplementedError < Error
|
|
226
|
+
code "MAGIK_COMMAND_NOT_IMPLEMENTED"
|
|
227
|
+
fix "run `magik help` for the commands that work today; " \
|
|
228
|
+
"track the rest in docs/idea/00-build-spec.md"
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# Raised when a CLI command is not a Magik command at all.
|
|
232
|
+
#
|
|
233
|
+
# @see Magik::CLI
|
|
234
|
+
class UnknownCommandError < Error
|
|
235
|
+
code "MAGIK_UNKNOWN_COMMAND"
|
|
236
|
+
fix "run `magik help` to list every command"
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# Raised when the command line carries a flag Magik does not understand.
|
|
240
|
+
#
|
|
241
|
+
# @see Magik::CLI
|
|
242
|
+
class InvalidOptionError < Error
|
|
243
|
+
code "MAGIK_INVALID_OPTION"
|
|
244
|
+
fix "run `magik help` to list the supported options"
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# The installed gem's root directory — the parent of `lib/`.
|
|
248
|
+
#
|
|
249
|
+
# Useful for locating packaged assets and the specification itself, e.g.
|
|
250
|
+
# `Magik.root.join("docs/idea/00-build-spec.md")`.
|
|
251
|
+
#
|
|
252
|
+
# @return [Pathname] an absolute, resolved path
|
|
253
|
+
# @example
|
|
254
|
+
# Magik.root.join("lib", "magik.rb").exist? # => true
|
|
255
|
+
def self.root
|
|
256
|
+
@root ||= Pathname.new(File.expand_path("..", __dir__))
|
|
257
|
+
end
|
|
258
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: magik
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- developerz.ai
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-08-26 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: |
|
|
14
|
+
Magik is a full-stack Ruby framework designed for TruffleRuby: one DSL for models,
|
|
15
|
+
screens, actions, realtime channels, background jobs, ledgers, APIs and admin panels.
|
|
16
|
+
The server renders HTML and htmx handles interactivity — there is no separate frontend
|
|
17
|
+
framework, no offline mode, and no heavy client-side compute.
|
|
18
|
+
|
|
19
|
+
Status as of 2026-08-26: this is a name-reservation release. The specification lives in
|
|
20
|
+
docs/idea/00-build-spec.md and is entirely unimplemented. What ships today is the gem
|
|
21
|
+
skeleton, the MAGIK_* error-code convention, a `magik version` / `magik help` CLI, and
|
|
22
|
+
one documented, spec-only stub module per planned subsystem. Nothing renders a page,
|
|
23
|
+
connects to a database, or runs a job yet.
|
|
24
|
+
email:
|
|
25
|
+
- admin@developerz.ai
|
|
26
|
+
executables:
|
|
27
|
+
- magik
|
|
28
|
+
extensions: []
|
|
29
|
+
extra_rdoc_files: []
|
|
30
|
+
files:
|
|
31
|
+
- CHANGELOG.md
|
|
32
|
+
- LICENSE
|
|
33
|
+
- README.md
|
|
34
|
+
- exe/magik
|
|
35
|
+
- lib/magik.rb
|
|
36
|
+
- lib/magik/action.rb
|
|
37
|
+
- lib/magik/admin.rb
|
|
38
|
+
- lib/magik/api.rb
|
|
39
|
+
- lib/magik/auth.rb
|
|
40
|
+
- lib/magik/billing.rb
|
|
41
|
+
- lib/magik/check.rb
|
|
42
|
+
- lib/magik/cli.rb
|
|
43
|
+
- lib/magik/core.rb
|
|
44
|
+
- lib/magik/domains.rb
|
|
45
|
+
- lib/magik/i18n.rb
|
|
46
|
+
- lib/magik/jobs.rb
|
|
47
|
+
- lib/magik/ledger.rb
|
|
48
|
+
- lib/magik/model.rb
|
|
49
|
+
- lib/magik/notify.rb
|
|
50
|
+
- lib/magik/pwa.rb
|
|
51
|
+
- lib/magik/realtime.rb
|
|
52
|
+
- lib/magik/render.rb
|
|
53
|
+
- lib/magik/router.rb
|
|
54
|
+
- lib/magik/schema.rb
|
|
55
|
+
- lib/magik/testing.rb
|
|
56
|
+
- lib/magik/version.rb
|
|
57
|
+
homepage: https://github.com/developerz-ai/magik
|
|
58
|
+
licenses:
|
|
59
|
+
- MIT
|
|
60
|
+
metadata:
|
|
61
|
+
homepage_uri: https://github.com/developerz-ai/magik
|
|
62
|
+
source_code_uri: https://github.com/developerz-ai/magik
|
|
63
|
+
changelog_uri: https://github.com/developerz-ai/magik/blob/main/CHANGELOG.md
|
|
64
|
+
documentation_uri: https://developerz-ai.github.io/magik/api/
|
|
65
|
+
bug_tracker_uri: https://github.com/developerz-ai/magik/issues
|
|
66
|
+
rubygems_mfa_required: 'true'
|
|
67
|
+
post_install_message:
|
|
68
|
+
rdoc_options: []
|
|
69
|
+
require_paths:
|
|
70
|
+
- lib
|
|
71
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - ">="
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '3.2'
|
|
76
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
77
|
+
requirements:
|
|
78
|
+
- - ">="
|
|
79
|
+
- !ruby/object:Gem::Version
|
|
80
|
+
version: '0'
|
|
81
|
+
requirements: []
|
|
82
|
+
rubygems_version: 3.4.20
|
|
83
|
+
signing_key:
|
|
84
|
+
specification_version: 4
|
|
85
|
+
summary: An opinionated, full-stack Ruby framework for TruffleRuby — one DSL for the
|
|
86
|
+
whole app.
|
|
87
|
+
test_files: []
|