npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

gs-suite

v0.2.22

Published

Simple set utility stack for SPA applications

Downloads

1,041

Readme

GS-Suite (dev)

GS-Suite adalah framework UI/interaksi berbasis vanilla JavaScript untuk kebutuhan CMS/admin dashboard SPA. Fokus utamanya: lifecycle yang deterministik, runtime yang aman, dan plugin/modul yang modular.

Scope

  • Target: aplikasi admin/CMS yang hidup lama (8-12 jam) dengan cleanup yang konsisten.
  • Bukan: SSR framework, hydration, atau framework universal.

Prinsip Runtime

  • gs.boot() hanya boleh dipanggil 1x per runtime.
  • gs.config read-only setelah boot (set lewat gs.setup sebelum boot).
  • Module handler wajib return disposer (boleh noop). Disposer dipanggil saat route change dan destroy app.
  • Error code konsisten lewat gs.event.ERROR_CODE.

Quick Start

import { gs } from 'gs-suite';

gs.setup({
  config: {
    router: { base: '' },
    log: { level: 'debug' }
  }
});

gs.boot({
  root: '#app',
  modules: () => {
    gs.module.setLayer('screen_home', '<div wallscreen></div>');
    gs.module.setLayer('layout_home', '<div>Home</div>');
    gs.module.register({
      init: 'home',
      path: 'home',
      screen: 'screen_home',
      layout: 'layout_home',
      handler() {
        return () => {
          // disposer
        };
      }
    });
    gs.router.register({
      '/home': () => 'home'
    });
  },
  router: { start: true }
});

Error Codes

Gunakan gs.event.ERROR_CODE untuk mendeteksi error inti (contoh: GS_BOOT_ALREADY_CALLED, GS_ROOT_NOT_FOUND, GS_MODULE_NO_DISPOSER).

GS-Suite

GS-Suite adalah framework JavaScript opinionated untuk Admin / CMS Single-Page Applications (SPA).

Dirancang untuk: • Dashboard internal • CMS • Backoffice • Data-heavy admin tools

Bukan untuk: • Landing page publik • SEO / SSR • Website marketing • Menggantikan React / Vue / Next.js

Why GS-Suite?

GS-Suite fokus pada stabilitas jangka panjang admin SPA, bukan tren UI. • Explicit lifecycle — tidak ada auto-run • Deterministic boot — satu pintu gs.boot() • Runtime safety — read-only setelah ready • Mandatory cleanup — anti memory leak • Security-aware DOM — aman untuk admin • Zero framework dependency — ringan & dapat dikontrol

Core Principles 1. Host-controlled HTML GS-Suite tidak membuat index.html. Host yang menyediakan container. 2. One-time boot Aplikasi hanya boleh di-boot sekali. Tidak ada side-effect tersembunyi. 3. Lifecycle first Setiap module/plugin harus bisa dibersihkan (disposer). 4. Admin-first, opinionated Privilege, guard, dan workflow adalah use-case utama.

minimal usage

import gs from 'gs-suite'

gs.boot({ root: '#app', debug: true, adapter: { layerLoader: async (name) => fetch(/layers/${name}).then(r => r.text()), authGuard: async () => true } })

When NOT to use GS-Suite • Anda butuh SSR atau SEO publik • Anda membangun landing page marketing • Anda ingin ecosystem React/Vue • Anda ingin UI library dengan banyak theme

GS-Suite

Framework Vanilla JS ringan untuk SPA (tanpa dependency). Fokus pada modularisasi, plugin UI, dan flow SPA sederhana.

Instalasi

npm install gs-suite

CLI Scaffold

npx gs-suite create gs-suite-app

Opsional:

npx gs-suite create gs-suite-app --template blank
npx gs-suite create gs-suite-app --force --no-install

Flag:

  • --template <name>: basic atau blank (default: basic)
  • --force: overwrite jika folder target sudah berisi file
  • --no-install: skip npm install

Import Dasar

import { gs, store } from 'gs-suite';

Struktur runtime:

  • gs adalah facade utama (event, dom, plugin, module).
  • store adalah instance state helper (gs.state.create) untuk menyimpan data global.

Contoh penggunaan store:

store.set('user', { id: 1, name: 'Dipa' });
const user = store.get('user');

Konfigurasi Global

gs.setup({
  config: {
    env: 'development',
    debug: ['localhost:8092'],
    log: {
      level: 'auto', // 'auto' | 'debug' | 'info' | 'warn' | 'error'
      silent: false,
    },
    path: {
      publish: '',
      additional: {},
    },
    request: {
      baseUrl: '',
      credentials: 'same-origin',
      timeout: 15000,
    },
    router: {
      mode: 'history', // 'history' | 'hash'
      linkAttr: 'data-route',
      backAttr: 'data-route-back',
      scrollRestoration: 'auto', // 'auto' | 'manual' | false
    },
    html: {
      mode: 'allow', // 'allow' | 'warn' | 'block'
      sanitizer: null,
    },
    locale: {
      locale: 'id-ID',
      timeZone: 'Asia/Jakarta',
    },
    performance: {
      useRAF: true,
      batchDOM: true,
    },
    runtime: {
      freeze: 'auto', // 'auto' | true | false
    },
    feature: {
      mode: 'warn', // 'silent' | 'warn' | 'throw'
      flags: {
        inputpicker: false,
      },
    },
  }
});

Catatan `feature`:
- `feature.flags` atau direct key `feature.<plugin>` bisa dipakai (backward compatible).
- `mode: 'warn'` → log warning, method jadi no-op.
- `mode: 'silent'` → no-op tanpa log.
- `mode: 'throw'` → akan throw (saat bootstrap/setup jika plugin missing, atau saat method dipanggil jika plugin disabled).

Catatan `runtime.freeze` (legacy):
- `gs.boot` akan mengunci runtime setelah ready. Opsi ini disimpan untuk kompatibilitas lama.

Catatan html.mode:

  • allow (default): render HTML seperti biasa.
  • warn: render HTML + tampilkan warning console.
  • block: semua HTML akan diperlakukan sebagai textContent. Gunakan gs.event.setHTML(element, html) untuk memasukkan HTML (kecuali editor).

Boot (Direkomendasikan)

await gs.boot({
  root: '#app',
  router: {
    base: '',
    start: true,
  },
  plugins: [
    { name: 'form', plugin: gs.plugin.form },
    { name: 'notification', plugin: gs.plugin.notification },
    { name: 'flybox', plugin: gs.plugin.flybox },
    { name: 'popup', plugin: gs.plugin.popup },
  ],
  modules: async () => {
    const registerModules = (await import('./src/registry/modules.js')).default;
    registerModules();
  }
});

Catatan gs.boot:

  • root wajib disediakan (selector atau HTMLElement).
  • Plugin hanya boleh diregister saat booting.
  • Runtime akan dikunci (read-only) setelah ready.
  • gs.setup dan gs.bootstrap dipertahankan untuk legacy.

Bootstrap

await gs.bootstrap({
  modules: async () => {
    const registerModules = (await import('./modules/index.js')).default;
    registerModules();
  },
  plugins: async () => {
    const registerPlugins = (await import('./plugins/index.js')).default;
    registerPlugins();
  },
  autoNavigate: true,
});

Router SPA (Resmi)

Router hanya memetakan URL ke module. Tidak render DOM secara langsung.

gs.router.register({
  '/': () => ({ init: 'home' }),
  '/users': () => ({ init: 'user_list' }),
  '/users/:id': (params) => ({
    init: 'user_detail',
    data: { userId: params.id }
  }),
});
gs.router.init();

Navigasi:

gs.router.push('/users/12?tab=info');

Hook:

gs.router.beforeEach((to, from) => {
  if (to.path.startsWith('admin') && !store.get('user')) return false;
});
gs.router.afterEach((to, from) => {
  console.log('route change', from, '->', to);
});

Catatan:

  • Elemen dengan atribut data-route (anchor atau non-anchor) akan dicegat router.
  • Jika memakai non-anchor, tambahkan role="link" dan tabindex="0" untuk aksesibilitas.
  • Elemen dengan data-route-back akan memanggil history.back().
  • gs.module.navigator() masih ada tapi deprecated (gunakan router).

Sistem Module (SPA)

GS-Suite memakai konsep module berbasis URL. Tiap module memiliki screen, layout, setup, handler, closing, dan cleanup.

Contoh Register Module

gs.module.register({
  init: 'home',
  path: 'home',
  auth: false,
  screen: 'screen_main',
  layout: 'page_home',
  setup: async (wall, data) => {
    // siapin state atau fetch data
  },
  handler: (wall) => {
    // scan plugin pada scope wall
  },
  closing: () => {
    // animasi close (opsional)
  },
  cleanup: () => {
    // bersihkan popup/flybox/plugin state (opsional)
  }
});

Setup Module (Layer & Routes)

gs.module.setup({
  layers: {
    screen_main: '<section wallscreen></section>',
    'gs_404': '<section>404</section>',
  },
  routes: {
    index: 'home',
    direct: '404',
  },
  list: [
    { init: 'home', path: 'home', screen: 'screen_main', layout: 'page_home' }
  ],
});

Wajib: layout screen_* harus memiliki elemen [wallscreen] untuk render konten module.

State Helper (Resmi)

State helper bersifat manual (tidak ada auto render).

const appState = gs.state.create({ user: null, loading: false });

const unwatch = appState.watch('user', (next, prev) => {
  console.log('user change', prev, '->', next);
});

appState.set('user', { id: 2 });
unwatch();

Data route yang masuk ke module akan disimpan ke store:

const moduleData = store.get('module');

Plugin Umum

Pola umum:

  1. register() (jika ada)
  2. setup() (sekali global)
  3. scan(root) saat DOM siap atau setelah render module

Form

Markup:

<form gs-form="user_add">
  <input gs-field="name" />
  <button type="button" gs-form-submit="user_add">Save</button>
</form>

Script:

gs.plugin.form.register({
  init: 'user_add',
  fields: { name: '' },
  submit: (data) => { console.log(data); }
});
gs.plugin.form.setup();
gs.plugin.form.scan('user_add');

InputPicker

Markup:

<div gs-inputpicker="privileges">
  <input gs-inputpicker-key="privileges" data-value="" />
</div>

Script:

gs.plugin.inputPicker.register({
  init: 'privileges',
  options: [
    { id: 1, value: 'Admin' },
    { id: 2, value: 'Editor' },
  ],
  multi: false,
  onChange: ({ id }) => console.log(id),
});
gs.plugin.inputPicker.setup();
gs.plugin.inputPicker.scan(document);

SelectPicker

Markup:

<button gs-selectpicker="region" type="button"></button>
<div gs-selectpicker-options="region"></div>

Script:

gs.plugin.selectpicker.register({
  init: 'region',
  options: [
    { id: 'id', value: 'Indonesia' },
    { id: 'sg', value: 'Singapore' },
  ],
  onChange: ({ id }) => console.log(id),
});
gs.plugin.selectpicker.setup();
gs.plugin.selectpicker.scan(document);

InputDatepicker

Markup:

<input gs-inputdatepicker-key="date_main" data-value="" />
<div gs-inputdatepicker-options="date_main"></div>

Script:

gs.plugin.inputDatepicker.register({
  init: 'date_main',
  range: false,
  allowManualInput: true,
});
gs.plugin.inputDatepicker.setup();
gs.plugin.inputDatepicker.scan(document);

Editor (WYSIWYG)

Markup:

<div gs-editor="article_body"></div>
<input type="hidden" name="body" gs-editor-input="article_body" />

<div gs-editor-toolbar="article_body">
  <button type="button" gs-editor-cmd="bold">B</button>
  <button type="button" gs-editor-cmd="italic">I</button>
  <button type="button" gs-editor-cmd="underline">U</button>
  <button type="button" gs-editor-link>Link</button>
  <button type="button" gs-editor-unlink>Unlink</button>

  <button type="button" gs-editor-block="article_body"></button>
  <div gs-editor-block-options="article_body"></div>
</div>

Script:

gs.plugin.editor.register({
  init: 'article_body',
  placeholder: 'Tulis konten...',
  pasteMode: 'clean',
  sanitizeOnInput: true,
});
gs.plugin.editor.setup();
gs.plugin.editor.scan(document);

Catatan:

  • Paste otomatis dibersihkan sesuai whitelist tag/atribut.
  • data-route masih bisa digunakan di luar editor, editor fokus ke konten saja.

Colorpicker

gs.plugin.colorpicker.register({ init: 'color_main' });
gs.plugin.colorpicker.setup();
gs.plugin.colorpicker.scan(document);

Tag

Markup:

<div gs-tag="tag_main">
  <div gs-tag-display="tag_main"></div>
  <input gs-tag-key="tag_main" />
</div>

Script:

gs.plugin.tag.register({ init: 'tag_main', delimiter: ',' });
gs.plugin.tag.setup();
gs.plugin.tag.scan(document);

Datatable + Paging

gs.plugin.datatable.register({
  init: 'user',
  form: 'user_form',
  tablePlacement: 'placement_table',
  paginationPlacement: 'placement_paging',
});

gs.plugin.datatable
  .lock('user')
  .setTableContent([
    { label: 'No', item: 'no' },
    { label: 'Name', item: 'name', sort: true },
    { label: 'Email', item: 'email' },
  ])
  .open();

gs.plugin.datatable.setData('user', {
  paging: { page: 1, last: 3, total: 100, limit: 10, next: 2, prev: 0, first: 1, from: 0 },
  response: [{ name: 'A', email: '[email protected]' }],
});
gs.plugin.datatable.lock('user').scan();

Paging (standalone)

const pagingDom = gs.plugin.paging.generate('user_form', pagingData);
document.querySelector('#paging').append(pagingDom);
gs.plugin.paging.setup();

Motion (IntersectionObserver)

gs.plugin.motion.register({
  init: 'main-screen',
  motionCalls: [{ stack: 'mt-bottom', staging: 'mt-on-bottom' }],
  showMode: 'all',
  autorun: true,
});
gs.plugin.motion.setup();
gs.plugin.motion.scan('main-screen');

Popup

gs.plugin.popup.register({
  init: 'detail',
  handler: ({ wall }) => { gs.event.setHTML(wall, '<div>Popup</div>'); }
});
gs.plugin.popup.setup();
gs.plugin.popup.open({ init: 'detail' });

Flybox (Overlay)

const entry = gs.plugin.flybox.open({
  trigger: document.querySelector('#btn'),
  handler: ({ wall }) => { gs.event.setHTML(wall, '<div>Flybox</div>'); },
});
// close:
gs.plugin.flybox.close(entry);

Notification

gs.plugin.notification.push({ message: 'Saved', type: 'success' });

Autogrow (Textarea)

gs.plugin.autogrow.setup({ element: '[auto-grow]' });
gs.plugin.autogrow.scan(document);

Input Radio / Checkbox

gs.plugin.inputRadio.setup();
gs.plugin.inputRadio.scan(document);
gs.plugin.inputCheckbox.setup();
gs.plugin.inputCheckbox.scan(document);

Uploader Image

Markup:

<div gs-uploader="avatar">
  <input gs-uploader-value />
  <div gs-uploader-display></div>
  <button type="button" gs-uploader-open>Upload</button>
</div>

Script:

gs.plugin.uploaderImage.register({ init: 'avatar', path: 'uploader' });
gs.plugin.uploaderImage.setup();
gs.plugin.uploaderImage.scan('avatar');

Input Price / Phone

gs.plugin.inputPrice.setup();
gs.plugin.inputPrice.scan(document);
gs.plugin.inputPhone.setup();
gs.plugin.inputPhone.scan(document);

Referensi API

  • Detail API tersedia di gs-documentation.json.

Contoh Struktur Project

src/
  gs/
    plugins/
      index.js
      popup.js
      inputPicker.js
      datatable.js
    modules/
      index.js
      module.home.js
      module.userAdd.js
    pages/
      page_home.html
      page_user_add.html
    screens/
      screen_main.html
      screen_auth.html

Contoh src/gs/modules/index.js:

import home from './module.home.js';
import userAdd from './module.userAdd.js';

export default () => {
  home();
  userAdd();
};

Contoh src/gs/plugins/index.js:

import popup from './popup.js';
import inputPicker from './inputPicker.js';
import datatable from './datatable.js';

export default () => {
  popup();
  inputPicker();
  datatable();
};

Best Practices

  • Selalu panggil scan(root) dengan scope module (hindari scan seluruh document) untuk performa.
  • Gunakan cleanup pada gs.module.register untuk menutup popup/flybox dan reset state plugin saat pindah module.
  • Atur html.mode sesuai kebutuhan keamanan:
    • block untuk memastikan tidak ada HTML injection.
    • warn untuk debug input HTML tanpa memblok.
    • allow untuk kebutuhan HTML penuh (pastikan sanitasi data sendiri).

gs-suite-0.3.x

gs-suite-0.3.x